257 files

This commit is contained in:
Home Assistant Version Control
2026-10-02 16:06:24 +00:00
parent 7c55603942
commit c29f5cc088
258 changed files with 24621 additions and 3136 deletions
+1 -1
View File
@@ -1 +1 @@
{"pid": 70, "version": 1, "ha_version": "2026.9.4", "start_ts": 1790832768.8204901}
{"pid": 72, "version": 1, "ha_version": "2026.9.4", "start_ts": 1790957177.2689593}
@@ -47,7 +47,6 @@ from .const import (
CONF_ADVANCED_CHECKLISTS,
CONF_ADVANCED_ENVIRONMENTAL,
CONF_ADVANCED_GROUPS,
CONF_ADVANCED_PREDICTIONS,
CONF_ADVANCED_SCHEDULE_TIME,
CONF_ADVANCED_SEASONAL,
CONF_BUDGET_MONTHLY,
@@ -63,6 +62,7 @@ from .const import (
DOCUMENT_TEXT_INDEX_KEY,
DOMAIN,
EVENT_UNSUBS_KEY,
FLAT_SCHEDULE_TYPES,
GLOBAL_UNIQUE_ID,
MAX_COST,
MAX_DURATION_MINUTES,
@@ -214,12 +214,17 @@ SERVICE_ADD_OBJECT_SCHEMA = vol.Schema(
# just a third UI onto the same storage, so a value the WS API rejects must not
# be reachable from an automation. `vol.Coerce` stays (unlike the WS schemas)
# because YAML/templated service data arrives as strings.
# Only the recurrences the flat service fields can express. "sensor_based"
# was offered but a service cannot set the trigger, so it silently became an
# interval or manual task (audit 2026-09-29); calendar kinds go via `schedule`.
_SERVICE_SCHEDULE_TYPE = vol.In(sorted(str(kind) for kind in FLAT_SCHEDULE_TYPES))
SERVICE_ADD_TASK_SCHEMA = vol.Schema(
{
vol.Required("entry_id"): cv.string,
vol.Required("name"): vol.All(cv.string, vol.Length(min=1, max=MAX_NAME_LENGTH)),
vol.Optional("task_type"): vol.All(cv.string, vol.Length(max=MAX_TYPE_LENGTH)),
vol.Optional("schedule_type"): vol.All(cv.string, vol.Length(max=MAX_TYPE_LENGTH)),
vol.Optional("schedule_type"): _SERVICE_SCHEDULE_TYPE,
vol.Optional("interval_days"): vol.All(
vol.Coerce(int), vol.Range(min=INTERVAL_DAYS_RANGE[0], max=INTERVAL_DAYS_RANGE[1])
),
@@ -240,7 +245,7 @@ SERVICE_UPDATE_TASK_SCHEMA = vol.Schema(
vol.Required("task_id"): cv.string,
vol.Optional("name"): vol.All(cv.string, vol.Length(min=1, max=MAX_NAME_LENGTH)),
vol.Optional("task_type"): vol.All(cv.string, vol.Length(max=MAX_TYPE_LENGTH)),
vol.Optional("schedule_type"): vol.All(cv.string, vol.Length(max=MAX_TYPE_LENGTH)),
vol.Optional("schedule_type"): _SERVICE_SCHEDULE_TYPE,
vol.Optional("interval_days"): vol.All(
vol.Coerce(int), vol.Range(min=INTERVAL_DAYS_RANGE[0], max=INTERVAL_DAYS_RANGE[1])
),
@@ -269,7 +274,7 @@ SERVICE_DELETE_TASK_SCHEMA = vol.Schema(
SERVICE_LIST_TASKS_SCHEMA = vol.Schema(
{
vol.Optional("entry_id"): cv.string,
vol.Optional("status"): vol.In(["ok", "due_soon", "overdue", "triggered"]),
vol.Optional("status"): vol.In(["ok", "due_soon", "overdue", "triggered", "paused"]),
}
)
@@ -430,6 +435,8 @@ def _trigger_target_and_unit(hass: HomeAssistant, tc: dict[str, Any]) -> tuple[f
return tc.get("trigger_runtime_hours"), "h"
if ttype == TriggerType.STATE_CHANGE:
return tc.get("trigger_target_changes"), None
if ttype == TriggerType.DUE_DATE:
return tc.get("trigger_days_before") or 0, "d"
if ttype == TriggerType.COMPOUND:
return None, None
from .entity.triggers import primary_entity_id
@@ -700,7 +707,9 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
include_history = call.data.get("include_history", True)
# Phase 1: gather data on the event loop (accesses HA APIs)
data = build_export_data(hass, include_history=include_history)
from .helpers.import_mapping import async_attach_move_hints
data = await async_attach_move_hints(hass, build_export_data(hass, include_history=include_history))
# Phase 2: serialize in executor (CPU-bound, no HA API calls)
file_path = hass.config.path(f"maintenance_export.{fmt}")
@@ -751,7 +760,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
entry_id=call.data["entry_id"],
name=call.data["name"],
task_type=call.data.get("task_type", "custom"),
schedule_type=call.data.get("schedule_type", "time_based"),
schedule_type=call.data.get("schedule_type"),
interval_days=call.data.get("interval_days"),
interval_unit=call.data.get("interval_unit", "days"),
due_date=call.data.get("due_date"),
@@ -1082,13 +1091,11 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
"""
found: list[ConfigEntry] = []
seen: set[str] = set()
# 2026.8 gives a device exactly one config entry (`config_entry_id`);
# the plural `config_entries` is a silently-deprecated compat shim
# there — no report_usage, so the deprecation gate cannot see it — and
# the only spelling 2026.7 has. Prefer the singular where it exists.
single = getattr(device, "config_entry_id", None)
owner_ids = (single,) if single is not None else tuple(getattr(device, "config_entries", ()) or ())
for ce_id in owner_ids:
# One config entry per device since 2026.8 (the plural is reported
# from 2026.10) — helpers.device_link.device_owner_ids reads it.
from .helpers.device_link import device_owner_ids
for ce_id in device_owner_ids(device):
ce = hass.config_entries.async_get_entry(ce_id)
if ce is not None and ce.domain == DOMAIN and ce.unique_id != GLOBAL_UNIQUE_ID:
found.append(ce)
@@ -1281,7 +1288,6 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
def _detect_advanced_feature_usage(hass: HomeAssistant, global_options: dict[str, Any]) -> dict[str, bool]:
"""Scan existing entries to detect which advanced features are in use."""
adaptive = False
predictions = False
seasonal = False
environmental = False
checklists = False
@@ -1295,8 +1301,6 @@ def _detect_advanced_feature_usage(hass: HomeAssistant, global_options: dict[str
ac = task_data.get("adaptive_config") or {}
if ac.get("enabled"):
adaptive = True
if ac.get("sensor_prediction_enabled"):
predictions = True
if ac.get("seasonal_enabled"):
seasonal = True
if ac.get("environmental_entity"):
@@ -1311,7 +1315,6 @@ def _detect_advanced_feature_usage(hass: HomeAssistant, global_options: dict[str
return {
CONF_ADVANCED_ADAPTIVE: adaptive,
CONF_ADVANCED_PREDICTIONS: predictions,
CONF_ADVANCED_SEASONAL: seasonal,
CONF_ADVANCED_ENVIRONMENTAL: environmental,
CONF_ADVANCED_BUDGET: budget,
@@ -1527,6 +1530,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: MaintenanceSupporterConf
# HA-started so the store has finished loading.
entry.async_on_unload(async_at_started(hass, _check_document_storage_issues))
# Devices an object created in a mistaken area before 2.96 move back,
# and the empty leftover areas are offered for removal (repair issue).
from .helpers.area_leftovers import async_check_leftover_areas
entry.async_on_unload(async_at_started(hass, async_check_leftover_areas))
# Backfill the document search index (#171) a couple of minutes after
# start — pypdf is CPU work, and boot is the wrong moment for it.
entry.async_on_unload(async_at_started(hass, _schedule_document_text_backfill))
@@ -1996,9 +2005,14 @@ async def _check_task_responsible_user_orphans(
changed = False
for tid, td in tasks.items():
ruid = td.get("responsible_user_id")
pool = [u for u in (td.get("assignee_pool") or []) if isinstance(u, str)]
raw_pool = td.get("assignee_pool")
pool = [u for u in raw_pool if isinstance(u, str)] if isinstance(raw_pool, list) else []
pruned_pool = [u for u in pool if u in valid_ids]
resp_orphaned = bool(ruid) and ruid not in valid_ids
# A value that is no user id at all (a list from a malformed
# backup) is as orphaned as a deleted user's — `in valid_ids`
# raised on it and failed the global entry's setup on every start
# (bug audit 2026-09-29).
resp_orphaned = bool(ruid) and (not isinstance(ruid, str) or ruid not in valid_ids)
if not resp_orphaned and pruned_pool == pool:
new_tasks[tid] = td
continue
@@ -2,25 +2,61 @@
#
# Handkopieren ist nicht nötig: Einstellungen -> Allgemein -> "Assist-Sätze
# installieren" einschalten, dann schreibt die Integration diese Datei nach
# config/custom_sentences/de/maintenance_supporter.yaml und lädt den
# Konversations-Agenten neu. Nur aus diesem Verzeichnis liest der klassische
# (Nicht-LLM-)Agent Sätze. LLM-basierte Assist-Pipelines brauchen davon
# nichts — dort sind die Intents automatisch als Tools verfügbar.
# config/custom_sentences/de/maintenance_supporter.yaml (und eine Kopie ohne ß
# nach de-CH/) und lädt den Konversations-Agenten neu. Nur aus diesem
# Verzeichnis liest der klassische (Nicht-LLM-)Agent Sätze. LLM-basierte
# Assist-Pipelines brauchen davon nichts — dort sind die Intents automatisch
# als Tools verfügbar.
#
# Eine eigene Bearbeitung ist ausdrücklich vorgesehen: sobald der Inhalt nicht
# mehr dem entspricht, was wir geschrieben haben, fassen Aktualisierungen die
# Datei nicht mehr an (und das Abschalten der Einstellung ebenso wenig).
#
# Eigene Sätze GEWINNEN gegen Home Assistants eingebaute, sobald beide passen.
# Ein Satz aus einem Verb, das die eingebauten auch benutzen (überspringen,
# stummschalten, pausieren), und einem freien Namen würde "überspringe dieses
# Lied" oder "Radio stummschalten" schlucken — solche Sätze tragen deshalb ein
# Wartungs-Wort (Wartung, Aufgabe, Erinnerung, "diesmal"). Ein Test lässt
# jeden eingebauten Satz gegen diese Datei laufen.
language: "de"
intents:
MaintenanceSupporterListTasks:
data:
- sentences:
- "welche Wartung[en] (ist|sind) [gerade] (fällig|überfällig)"
- "was ist [an Wartung] (fällig|überfällig)"
- "welche Wartung[en] (ist|sind) [gerade] fällig"
- "was ist [an Wartung] fällig"
- "steht [eine] Wartung an"
- "was muss gewartet werden"
# Bezug: die fragende Person. HA übergibt dem Handler, wer gesprochen
# hat — damit antwortet dies mit deren Aufgaben (inkl. Rotationsdienst).
- "welche Aufgaben sind (fällig|offen)"
- sentences:
- "was ist überfällig"
- "welche (Wartung[en]|Aufgaben) (ist|sind) überfällig"
- "(ist|gibt es) (etwas|was) Überfälliges"
slots:
status: "overdue"
- sentences:
- "was ist heute fällig"
- "welche (Wartung[en]|Aufgaben) (ist|sind) heute fällig"
slots:
within_days: 0
- sentences:
- "was ist morgen fällig"
slots:
within_days: 1
- sentences:
- "was ist diese Woche fällig"
- "welche (Wartung[en]|Aufgaben) (ist|sind) diese Woche fällig"
- "was steht diese Woche an"
slots:
within_days: 7
- sentences:
- "was ist diesen Monat fällig"
slots:
within_days: 30
- sentences:
- "was ist (im|in der|in) {area} fällig"
- "welche (Wartung[en]|Aufgaben) (ist|sind) (im|in der|in) {area} fällig"
- "was muss (im|in der|in) {area} gewartet werden"
- sentences:
- "was muss ich (machen|erledigen|tun)"
- "was sind meine (Aufgaben|Pflichten|Wartungen)"
@@ -28,8 +64,6 @@ intents:
- "bin ich [gerade] (dran|zuständig)"
slots:
scope: "mine"
# Bezug: der Raum, aus dem gefragt wurde — über den Bereich des
# Satelliten aufgelöst, damit ein Wandtablet für seinen Raum antwortet.
- sentences:
- "was ist hier [drin] (fällig|zu tun)"
- "welche Wartung ist (hier|in diesem Raum) fällig"
@@ -39,57 +73,121 @@ intents:
MaintenanceSupporterCompleteTask:
data:
- sentences:
- "[die] Wartung[saufgabe] {task_name:name} (erledigen|abschließen)"
- "markiere [die] [Wartung[saufgabe]] {task_name:name} als erledigt"
- "ich habe [die] {task_name:name} (erledigt|gemacht|abgeschlossen)"
- "[die] (Wartung[saufgabe]|Aufgabe) {task_name:name} (erledigen|abschließen)"
- "markiere <ms_art> [Wartung[saufgabe]|Aufgabe] {task_name:name} als erledigt"
- "ich habe <ms_art> {task_name:name} (erledigt|gemacht|abgeschlossen)"
- "erledige <ms_art> {task_name:name}"
- "<ms_art> {task_name:name} (ist|wurde) erledigt"
- "<ms_art> {task_name:name} erledigt"
MaintenanceSupporterTaskInstructions:
data:
- sentences:
- "wie (mache|erledige) ich [die] {task_name:name}"
- "(Anleitung|Anweisungen) für [die] {task_name:name}"
- "was brauche ich für [die] {task_name:name}"
- "wie (mache|erledige) ich <ms_art> {task_name:name}"
- "(Anleitung|Anweisungen) (für|zu|zum|zur) <ms_art> {task_name:name}"
- "was brauche ich für <ms_art> {task_name:name}"
MaintenanceSupporterTaskDue:
data:
- sentences:
- "wann ist [die] {task_name:name} fällig"
- "wann muss ich [die] {task_name:name} (machen|erledigen)"
- "wann ist <ms_art> {task_name:name} fällig"
- "wann muss ich <ms_art> {task_name:name} (machen|erledigen)"
- "wann steht <ms_art> {task_name:name} an"
MaintenanceSupporterSnoozeTask:
data:
- sentences:
- "[die] {task_name:name} (snoozen|stummschalten)"
- "Erinnerungen für [die] {task_name:name} (stummschalten|pausieren)"
- "<ms_art> Erinnerung[en] (für|zu|zum|zur|an) <ms_art> {task_name:name} (stummschalten|pausieren|schlummern)"
- "(schalte|stell|stelle) <ms_art> Erinnerung[en] (für|zu|zum|zur|an) <ms_art> {task_name:name} stumm"
- "<ms_art> (Wartung[saufgabe]|Aufgabe) {task_name:name} (snoozen|stummschalten|pausieren)"
MaintenanceSupporterPartStock:
data:
- sentences:
- "wie viele {part_name:name} haben wir [noch]"
- "wie ist der Bestand (von|an) {part_name:name}"
- "wie viele {part_name:name} sind [noch] (da|auf Lager|vorrätig)"
- "wie (ist|hoch ist) der (Bestand|Vorrat|Lagerbestand) (von|an|bei) <ms_art> {part_name:name}"
MaintenanceSupporterPostponeTask:
data:
- sentences:
- "verschiebe [die|das|den] {part_name:name} um {days} Tag[e]"
- "[die|das|den] {part_name:name} um {days} Tag[e] verschieben"
- "schiebe [die|das|den] {part_name:name} um {days} Tag[e] nach hinten"
- "verschiebe <ms_art> {task_name:name} um {days} Tag[e]"
- "<ms_art> {task_name:name} um {days} Tag[e] verschieben"
- "schiebe <ms_art> {task_name:name} um {days} Tag[e] (nach hinten|auf)"
- sentences:
- "verschiebe [die|das|den] {part_name:name} um eine Woche"
- "[die|das|den] {part_name:name} um eine Woche verschieben"
- "verschiebe <ms_art> {task_name:name} um eine Woche"
- "<ms_art> {task_name:name} um eine Woche verschieben"
slots:
days: 7
MaintenanceSupporterSkipTask:
data:
- sentences:
- "überspringe [die|das|den] {part_name:name} [diesmal]"
- "[die|das|den] {part_name:name} [diesmal] überspringen"
- "überspringe <ms_art> {task_name:name} (diesmal|dieses Mal)"
- "<ms_art> {task_name:name} (diesmal|dieses Mal) überspringen"
- "überspringe <ms_art> (Wartung[saufgabe]|Aufgabe) {task_name:name} [diesmal|dieses Mal]"
- "<ms_art> (Wartung[saufgabe]|Aufgabe) {task_name:name} [diesmal|dieses Mal] überspringen"
- "lass <ms_art> {task_name:name} (diesmal|dieses Mal) aus"
MaintenanceSupporterRecordReading:
data:
- sentences:
- "trage [den] (Zählerstand|Messwert|Wert|Stand) {reading_value:value} (für|bei|beim|zum|zur) <ms_art> {task_name:name} ein"
- "(erfasse|notiere) [den] (Zählerstand|Messwert|Wert|Stand) {reading_value:value} (für|bei|beim|zum|zur) <ms_art> {task_name:name}"
- "der (Zählerstand|Messwert|Stand) (von|vom|für|beim) <ms_art> {task_name:name} (ist|beträgt) {reading_value:value}"
- "<ms_art> {task_name:name} (steht auf|zeigt) {reading_value:value}"
MaintenanceSupporterAddNote:
data:
- sentences:
- "notiere (für|zu|zum|zur|bei|beim) <ms_art> {task_name:name} dass {note_text:note}"
- "(schreibe|mache) [mir] eine Notiz (für|zu|zum|zur|bei|beim) <ms_art> {task_name:name} dass {note_text:note}"
- "Notiz (für|zu|zum|zur|bei|beim) <ms_art> {task_name:name} mit dem Text {note_text:note}"
MaintenanceSupporterWhoseTurn:
data:
- sentences:
- "wer ist (mit|bei|beim|für) <ms_art> {task_name:name} dran"
- "wer ist für <ms_art> {task_name:name} zuständig"
- "wer muss <ms_art> {task_name:name} (machen|erledigen)"
- "wer ist (mit|bei|beim) <ms_art> {task_name:name} an der Reihe"
MaintenanceSupporterShoppingList:
data:
- sentences:
- "was (müssen|sollten) wir (kaufen|nachkaufen|besorgen)"
- "was muss (gekauft|nachgekauft|nachbestellt|besorgt) werden"
- "welche (Ersatzteile|Teile|Verbrauchsmaterialien|Vorräte) (sind knapp|gehen zur Neige|müssen nachgekauft werden|brauchen wir)"
- "was (ist|wird) [gerade] knapp"
MaintenanceSupporterBoughtPart:
data:
- sentences:
- "(ich habe|wir haben) {quantity} [neue] {part_name:name} gekauft"
- "(ich habe|wir haben) (neue|neuen|neues) {part_name:name} gekauft"
- "buche {quantity} {part_name:name} (ein|in den Bestand)"
MaintenanceSupporterLowBatteries:
data:
- sentences:
- "welche Batterien sind (leer|schwach|fast leer|bald leer)"
- "(sind|ist) (irgendwelche|irgendeine|eine) Batterie[n] (leer|schwach)"
- "welche Batterien (muss ich|müssen wir|sollte ich) (wechseln|tauschen)"
MaintenanceSupporterUndo:
data:
- sentences:
- "(mach|mache) das rückgängig"
- "(mach|mache) die letzte (Aktion|Änderung|Wartungsaktion) rückgängig"
- "das war (ein Fehler|falsch)"
lists:
# NOT called 'name': that is Home Assistant's built-in list of exposed
# ENTITY names, and the default agent resolves it against the registry
# before the handler runs — every sentence using it answered 'I am not
# aware of any device called ...' instead of reaching us. The
# {list:slot} alias keeps the value arriving in the 'name' slot.
# Nicht 'name' — das ist Home Assistants eingebaute Liste der ENTITÄTS-
# namen (siehe die englische Datei). {area} ist Home Assistants eigene
# Bereichsliste.
task_name:
wildcard: true
part_name:
wildcard: true
reading_value:
wildcard: true
note_text:
wildcard: true
days:
range:
from: 1
to: 365
quantity:
range:
from: 1
to: 100
expansion_rules:
# Eigener Präfix: diese Regeln werden mit Home Assistants eingebauten
# zusammengeführt, ein gleichnamiger Eintrag würde deren Regel ersetzen.
ms_art: "[der|die|das|den|dem]"
@@ -10,15 +10,50 @@
# Editing your own copy is fine and expected: once its content no longer
# matches what we wrote, upgrades leave it alone (and so does turning the
# setting back off).
#
# Custom sentences WIN over Home Assistant's built-in ones whenever both
# match. A sentence that starts with a verb the built-ins also use (skip,
# pause, mute, complete) followed by a free-text name would swallow "skip
# this song" or "pause the radio" — so those carry a maintenance word
# (maintenance, task, chore, reminder, "this time"). A test runs every
# built-in sentence against this file to keep it that way.
language: "en"
intents:
MaintenanceSupporterListTasks:
data:
- sentences:
- "what maintenance is (due|overdue)"
- "which maintenance [tasks] (are|is) (due|overdue|pending)"
- "what maintenance is (due|pending)"
- "which maintenance [tasks] (are|is) (due|pending)"
- "is [there] any maintenance due"
- "what needs (maintenance|servicing)"
- "what (chores|maintenance tasks) are (due|pending)"
- sentences:
- "what [maintenance] is overdue"
- "which [maintenance] (tasks|chores) are overdue"
- "is (anything|any maintenance) overdue"
slots:
status: "overdue"
- sentences:
- "what [maintenance] is due today"
- "what (chores|maintenance tasks) are due today"
slots:
within_days: 0
- sentences:
- "what [maintenance] is due tomorrow"
slots:
within_days: 1
- sentences:
- "what [maintenance] is due this week"
- "what (chores|maintenance tasks) are due this week"
slots:
within_days: 7
- sentences:
- "what [maintenance] is due this month"
slots:
within_days: 30
- sentences:
- "what [maintenance] is due in [the] {area}"
- "what needs (maintenance|servicing) in [the] {area}"
# Scope: the person asking. HA tells the handler who spoke, so this
# answers with that user's tasks (and their turn on a rotating chore).
- sentences:
@@ -39,57 +74,121 @@ intents:
MaintenanceSupporterCompleteTask:
data:
- sentences:
- "complete [the] [maintenance] [task] {task_name:name}"
- "mark [the] [maintenance] [task] {task_name:name} [as] (done|completed)"
- "I (did|finished) [the] {task_name:name}"
- "complete [the] (maintenance [task]|task|chore) [for] [the] {task_name:name}"
- "complete [the] {task_name:name} (maintenance|task|chore)"
- "mark [the] [maintenance] [task|chore] {task_name:name} [as] (done|completed|complete)"
- "I('ve| have) (done|finished|completed) [the] {task_name:name}"
- "I (did|finished|completed) [the] {task_name:name}"
- "[the] {task_name:name} is (done|finished|completed)"
MaintenanceSupporterTaskInstructions:
data:
- sentences:
- "how do I (do|perform) [the] {task_name:name}"
- "(instructions|guidance) for [the] {task_name:name}"
- "(instructions|guidance|steps) for [the] {task_name:name}"
- "what do I need for [the] {task_name:name}"
MaintenanceSupporterTaskDue:
data:
- sentences:
- "when is [the] {task_name:name} due"
- "when do I (need|have) to (do|perform) [the] {task_name:name}"
- "when is [the] next {task_name:name}"
MaintenanceSupporterSnoozeTask:
data:
- sentences:
- "snooze [the] [maintenance] [task] {task_name:name}"
- "(mute|silence) [the] reminders for [the] {task_name:name}"
- "snooze [the] (reminder|reminders|notification|notifications) (for|about|on) [the] {task_name:name}"
- "snooze [the] {task_name:name} (reminder|reminders|notification|notifications|maintenance|task|chore)"
- "snooze [the] (maintenance [task]|task|chore) {task_name:name}"
- "(mute|silence|pause) [the] [maintenance] (reminder|reminders|notification|notifications) (for|about|on) [the] {task_name:name}"
MaintenanceSupporterPartStock:
data:
- sentences:
- "how (many|much) {part_name:name} (do we have|are left|is left)"
- "how (many|much) {part_name:name} (do we have [left]|are left|is left|have we got)"
- "how many {part_name:name} are in stock"
- "what is the stock of [the] {part_name:name}"
MaintenanceSupporterPostponeTask:
data:
- sentences:
- "postpone [the] {part_name:name} by {days} day[s]"
- "push [the] {part_name:name} back [by] {days} day[s]"
- "delay [the] {part_name:name} [by] {days} day[s]"
- "postpone [the] {task_name:name} (by|for) {days} day[s]"
- "push [the] {task_name:name} back [by] {days} day[s]"
- "delay [the] {task_name:name} [by] {days} day[s]"
- sentences:
- "postpone [the] {part_name:name} by a week"
- "push [the] {part_name:name} back [by] a week"
- "(postpone|delay) [the] {task_name:name} (by|for) a week"
- "push [the] {task_name:name} back [by] a week"
slots:
days: 7
MaintenanceSupporterSkipTask:
data:
- sentences:
- "skip [the] {part_name:name} [this time]"
- "skip this (cycle|round) of [the] {part_name:name}"
- "skip [the] {task_name:name} this time"
- "skip this (cycle|round|time) of [the] {task_name:name}"
- "skip [the] (maintenance [task]|task|chore) {task_name:name} [this time]"
- "skip [the] {task_name:name} (maintenance|task|chore) [this time]"
MaintenanceSupporterRecordReading:
data:
- sentences:
- "record [a] reading [of] {reading_value:value} for [the] {task_name:name}"
- "(log|enter) [a] reading [of] {reading_value:value} for [the] {task_name:name}"
- "[the] {task_name:name} reading is {reading_value:value}"
- "[the] {task_name:name} reads {reading_value:value}"
MaintenanceSupporterAddNote:
data:
- sentences:
- "add [a] note (to|for|on) [the] {task_name:name} (saying|that says) {note_text:note}"
- "(make|take) a note (for|on) [the] {task_name:name} (saying|that) {note_text:note}"
MaintenanceSupporterWhoseTurn:
data:
- sentences:
- "whose turn is it (to do|for) [the] {task_name:name}"
- "who is (responsible|in charge) (for|of) [the] {task_name:name}"
- "who (has to|needs to|should) do [the] {task_name:name} [next]"
MaintenanceSupporterShoppingList:
data:
- sentences:
- "what (do|should) we (need to|have to) buy"
- "what do we need to (buy|restock|reorder)"
- "what (spare parts|parts|supplies|consumables) (do we need|are low|are running low|need restocking)"
- "what needs (restocking|reordering)"
- "what is running low"
MaintenanceSupporterBoughtPart:
data:
- sentences:
- "(I|we) (bought|purchased|restocked) {quantity} [new|more] {part_name:name}"
- "(I|we) (bought|purchased|restocked) [some] (new|more) {part_name:name}"
- "add {quantity} {part_name:name} to [the] (stock|inventory)"
MaintenanceSupporterLowBatteries:
data:
- sentences:
- "which batteries (are|is) (low|empty|running low|flat)"
- "(are|is) (there|any) batter(y|ies) (low|empty|running low|flat)"
- "are there any low batteries"
- "which batteries (do|should) (I|we) (replace|change)"
MaintenanceSupporterUndo:
data:
- sentences:
- "undo (that|this|it)"
- "undo the last [maintenance] (action|change|completion)"
- "that was a mistake"
- "revert (that|the last [maintenance] (action|change))"
lists:
# NOT called 'name': that is Home Assistant's built-in list of exposed
# ENTITY names, and the default agent resolves it against the registry
# before the handler runs — every sentence using it answered 'I am not
# aware of any device called ...' instead of reaching us. The
# {list:slot} alias keeps the value arriving in the 'name' slot.
# ({area} is Home Assistant's own list of area names.)
task_name:
wildcard: true
part_name:
wildcard: true
reading_value:
wildcard: true
note_text:
wildcard: true
days:
range:
from: 1
to: 365
quantity:
range:
from: 1
to: 100
@@ -10,6 +10,13 @@
# Puedes editar tu copia: en cuanto su contenido deje de coincidir con lo que
# escribimos, las actualizaciones ya no la tocan (y desactivar el ajuste
# tampoco la borra).
#
# Las frases propias GANAN a las de Home Assistant cuando ambas encajan. Un
# verbo que las integradas también usan (saltar, pausar, silenciar) seguido de
# un nombre libre se tragaría «salta esta canción» o «silencia la radio»; por
# eso esas frases llevan una palabra de mantenimiento (mantenimiento, tarea,
# avisos, «esta vez»). Un test comprueba cada frase integrada contra este
# archivo.
language: "es"
intents:
MaintenanceSupporterListTasks:
@@ -19,8 +26,34 @@ intents:
- "qué (tareas|mantenimientos) [de mantenimiento] (hay|están) pendientes"
- "hay (algo|algún mantenimiento) pendiente"
- "qué hay que (revisar|mantener)"
# Ámbito: quien pregunta. HA le indica al gestor quién ha hablado, así
# que esto responde con SUS tareas (incluido su turno en la rotación).
- sentences:
- "qué (mantenimiento|tarea) está (atrasado|atrasada|vencido|vencida)"
- "qué (mantenimientos|tareas) están (atrasados|atrasadas|vencidos|vencidas)"
- "qué está (atrasado|vencido)"
- "hay algo (atrasado|vencido)"
slots:
status: "overdue"
- sentences:
- "qué (toca|hay que hacer) hoy"
- "qué mantenimiento toca hoy"
slots:
within_days: 0
- sentences:
- "qué (toca|hay que hacer) mañana"
slots:
within_days: 1
- sentences:
- "qué (toca|hay que hacer) esta semana"
- "qué mantenimiento toca esta semana"
slots:
within_days: 7
- sentences:
- "qué (toca|hay que hacer) este mes"
slots:
within_days: 30
- sentences:
- "qué (toca|hay que hacer) en [el|la] {area}"
- "qué mantenimiento (toca|hay) en [el|la] {area}"
- sentences:
- "qué me toca [a mí] [hacer]"
- "qué tengo que hacer [yo]"
@@ -28,8 +61,6 @@ intents:
- "tengo algo pendiente"
slots:
scope: "mine"
# Ámbito: la habitación desde la que se pregunta, deducida de la zona del
# satélite — así una tableta de pared responde por su propia habitación.
- sentences:
- "qué hay que hacer aquí"
- "qué mantenimiento (toca|hay) (aquí|en esta habitación)"
@@ -39,54 +70,106 @@ intents:
MaintenanceSupporterCompleteTask:
data:
- sentences:
- "[ya] he (hecho|terminado|completado) [el|la|los|las] {task_name:name}"
- "marca [el|la|los|las] {task_name:name} como (hecho|hecha|terminado|terminada|completado|completada)"
- "[ya] he (hecho|terminado|completado) <ms_el> {task_name:name}"
- "marca <ms_el> (mantenimiento|tarea) [de|del|de la] {task_name:name} como (hecho|hecha|terminado|terminada|completado|completada)"
- "<ms_el> {task_name:name} (está|están) (hecho|hecha|hechos|hechas|terminado|terminada|listo|lista)"
MaintenanceSupporterTaskInstructions:
data:
- sentences:
- "cómo (hago|se hace) [el|la|los|las] {task_name:name}"
- "(instrucciones|indicaciones) para [el|la|los|las] {task_name:name}"
- "qué necesito para [el|la|los|las] {task_name:name}"
- "cómo (hago|se hace) <ms_el> {task_name:name}"
- "(instrucciones|indicaciones) para <ms_el> {task_name:name}"
- "qué necesito para <ms_el> {task_name:name}"
MaintenanceSupporterTaskDue:
data:
- sentences:
- "cuándo toca [el|la|los|las] {task_name:name}"
- "cuándo (hay que|tengo que) hacer [el|la|los|las] {task_name:name}"
- "cuándo toca <ms_el> {task_name:name}"
- "cuándo (hay que|tengo que) hacer <ms_el> {task_name:name}"
- "cuándo vence <ms_el> {task_name:name}"
MaintenanceSupporterSnoozeTask:
data:
- sentences:
- "(silencia|pausa) [el|la|los|las] {task_name:name}"
- "(silencia|pausa) los avisos (de|del|de la) {task_name:name}"
- "(silencia|pausa|pospón) los (avisos|recordatorios) (de|del|de la|de los|de las) {task_name:name}"
- "(silencia|pausa) (el mantenimiento|la tarea) [de|del|de la] {task_name:name}"
MaintenanceSupporterPartStock:
data:
- sentences:
- "cuántos {part_name:name} (nos quedan|tenemos|quedan)"
- "cuánt(os|as) {part_name:name} (nos quedan|tenemos)"
- "cuánto stock (hay|tenemos) de {part_name:name}"
MaintenanceSupporterPostponeTask:
data:
- sentences:
- "(aplaza|retrasa|pospón) [el|la|los|las] {task_name:name} {days} día[s]"
- "deja [el|la|los|las] {task_name:name} para dentro de {days} día[s]"
- "(aplaza|retrasa|pospón) <ms_el> {task_name:name} {days} día[s]"
- "deja <ms_el> {task_name:name} para dentro de {days} día[s]"
- sentences:
- "(aplaza|retrasa|pospón) [el|la|los|las] {task_name:name} una semana"
- "(aplaza|retrasa|pospón) <ms_el> {task_name:name} una semana"
slots:
days: 7
MaintenanceSupporterSkipTask:
data:
- sentences:
- "(salta|sáltate) [el|la|los|las] {task_name:name} [esta vez]"
- "(salta|sáltate) <ms_el> {task_name:name} esta vez"
- "(salta|sáltate) (el mantenimiento|la tarea) [de|del|de la] {task_name:name} [esta vez]"
- "(salta|sáltate) esta (ronda|vuelta) (de|del|de la|de los|de las) {task_name:name}"
MaintenanceSupporterRecordReading:
data:
- sentences:
- "(registra|anota|apunta) [una] lectura de {reading_value:value} (para|en) <ms_el> {task_name:name}"
- "la lectura (de|del|de la) {task_name:name} es [de] {reading_value:value}"
- "<ms_el> {task_name:name} marca {reading_value:value}"
MaintenanceSupporterAddNote:
data:
- sentences:
- "(añade|agrega|pon) una nota (a|al|a la|para|en) {task_name:name} (que diga|diciendo) [que] {note_text:note}"
- "anota (para|en) <ms_el> {task_name:name} que {note_text:note}"
MaintenanceSupporterWhoseTurn:
data:
- sentences:
- "a quién le toca [hacer] <ms_el> {task_name:name}"
- "quién (es responsable|se encarga) (de|del|de la|de los|de las) {task_name:name}"
- "quién tiene que hacer <ms_el> {task_name:name}"
MaintenanceSupporterShoppingList:
data:
- sentences:
- "qué (tenemos que|hay que|necesitamos) comprar"
- "qué (repuestos|piezas|consumibles|suministros) (faltan|necesitamos|hay que reponer)"
- "qué (falta|se está acabando|hay que reponer)"
MaintenanceSupporterBoughtPart:
data:
- sentences:
- "(he comprado|compré|hemos comprado|compramos) {quantity} {part_name:name}"
- "(he comprado|compré|hemos comprado|compramos) más {part_name:name}"
- "añade {quantity} {part_name:name} al (stock|inventario)"
MaintenanceSupporterLowBatteries:
data:
- sentences:
- "qué (pilas|baterías) (están|tienen) (bajas|agotadas|poca carga|casi agotadas)"
- "hay (pilas|baterías) (bajas|agotadas)"
- "qué (pilas|baterías) (tengo|hay) que (cambiar|reemplazar)"
MaintenanceSupporterUndo:
data:
- sentences:
- "deshaz (eso|lo último|la última acción)"
- "deshacer (eso|la última acción)"
- "eso fue un error"
lists:
# NO uses «name»: es la lista integrada de nombres de ENTIDADES de Home
# Assistant, que el agente por defecto resuelve antes de llamar al gestor —
# cualquier frase que la usara respondía «no conozco ningún dispositivo…» en
# vez de llegar hasta nosotros. El alias {lista:slot} mantiene el valor en el
# slot «name».
# No 'name': es la lista integrada de nombres de ENTIDADES de Home Assistant
# (ver el archivo inglés). {area} es la lista de áreas de Home Assistant.
task_name:
wildcard: true
part_name:
wildcard: true
reading_value:
wildcard: true
note_text:
wildcard: true
days:
range:
from: 1
to: 365
quantity:
range:
from: 1
to: 100
expansion_rules:
# Prefijo propio: estas reglas se fusionan con las de Home Assistant.
ms_el: "[el|la|los|las]"
@@ -10,24 +10,54 @@
# Vous pouvez modifier votre copie : dès que son contenu ne correspond plus à
# ce que nous avons écrit, les mises à jour n'y touchent plus (et désactiver le
# réglage ne la supprime pas non plus).
#
# Les phrases personnalisées l'EMPORTENT sur celles de Home Assistant dès que
# les deux correspondent. Un verbe que les phrases intégrées utilisent aussi
# (sauter, passer, mettre en pause) suivi d'un nom libre avalerait « passe à
# la chanson suivante » — ces phrases portent donc un mot d'entretien
# (entretien, tâche, rappels, « cette fois »). Un test vérifie chaque phrase
# intégrée contre ce fichier.
language: "fr"
intents:
MaintenanceSupporterListTasks:
data:
- sentences:
- "quel[le]s (entretien|entretiens|maintenances|tâches) (est|sont) (dû|due|dues|prévus|prévues)"
- "quel[le]s (entretien|entretiens|maintenances|tâches) (est|sont) (dû|due|dues|prévus|prévues|à faire)"
- "qu'est-ce qui est à (faire|entretenir)"
- "y a-t-il [de] l'entretien à faire"
# Portée : la personne qui parle. HA transmet au gestionnaire qui a parlé,
# donc ceci répond avec SES tâches (y compris son tour de rotation).
- sentences:
- "qu'est-ce qui est en retard"
- "quel[le]s (entretiens|tâches|maintenances) (est|sont) en retard"
- "y a-t-il (quelque chose|des tâches) en retard"
slots:
status: "overdue"
- sentences:
- "qu'est-ce qui est à faire aujourd'hui"
- "quel[le]s (entretiens|tâches) (sont|est) (prévu|prévue|prévus|prévues) aujourd'hui"
slots:
within_days: 0
- sentences:
- "qu'est-ce qui est à faire demain"
slots:
within_days: 1
- sentences:
- "qu'est-ce qui est à faire cette semaine"
- "quel[le]s (entretiens|tâches) (sont|est) (prévu|prévue|prévus|prévues) cette semaine"
slots:
within_days: 7
- sentences:
- "qu'est-ce qui est à faire ce mois-ci"
slots:
within_days: 30
- sentences:
- "qu'est-ce qui est à faire (dans|au|à) [la |le |l']{area}"
- "quel entretien est (dû|prévu) (dans|au|à) [la |le |l']{area}"
- sentences:
- "qu'est-ce que je dois faire"
- "quelles sont mes tâches"
- "c'est à moi de faire quelque chose"
slots:
scope: "mine"
# Portée : la pièce d'où vient la demande, résolue via la zone du
# satellite — une tablette murale répond ainsi pour sa propre pièce.
- sentences:
- "qu'est-ce qu'il y a à faire ici"
- "quel entretien est (dû|prévu) (ici|dans cette pièce)"
@@ -36,50 +66,109 @@ intents:
MaintenanceSupporterCompleteTask:
data:
- sentences:
- "j'ai (fait|terminé|fini) [le|la|les|l'] {task_name:name}"
- "marque [le|la|les|l'] {task_name:name} comme (fait|faite|terminé|terminée)"
- "j'ai (fait|terminé|fini) <ms_le>{task_name:name}"
- "marque <ms_le>(entretien|tâche) {task_name:name} comme (fait|faite|terminé|terminée)"
- "<ms_le>{task_name:name} est (fait|faite|terminé|terminée)"
MaintenanceSupporterTaskInstructions:
data:
- sentences:
- "comment (faire|je fais) [le|la|les|l'] {task_name:name}"
- "(instructions|consignes) pour [le|la|les|l'] {task_name:name}"
- "comment (faire|je fais) <ms_le>{task_name:name}"
- "(instructions|consignes) pour <ms_le>{task_name:name}"
- "de quoi ai-je besoin pour <ms_le>{task_name:name}"
MaintenanceSupporterTaskDue:
data:
- sentences:
- "quand est-ce que [le|la|les|l'] {task_name:name} est (dû|due|prévu|prévue)"
- "quand faut-il faire [le|la|les|l'] {task_name:name}"
- "quand est-ce que <ms_le>{task_name:name} est (dû|due|prévu|prévue)"
- "quand faut-il faire <ms_le>{task_name:name}"
- "quand dois-je faire <ms_le>{task_name:name}"
MaintenanceSupporterSnoozeTask:
data:
- sentences:
- "mets [le|la|les|l'] {task_name:name} en pause"
- "(mets|mettre) en pause les rappels (de|du|de la|des|pour) [l']{task_name:name}"
- "(coupe|désactive|suspends) les rappels (de|du|de la|des|pour) [l']{task_name:name}"
- "mets (l'entretien|la tâche) {task_name:name} en pause"
MaintenanceSupporterPartStock:
data:
- sentences:
- "combien de {part_name:name} (reste-t-il|il reste|avons-nous)"
- "combien de {part_name:name} (reste-t-il|il reste|avons-nous|nous reste-t-il)"
- "quel est le stock (de|du|de la|des|d') {part_name:name}"
MaintenanceSupporterPostponeTask:
data:
- sentences:
- "reporte [le|la|les|l'] {task_name:name} de {days} jour[s]"
- "repousse [le|la|les|l'] {task_name:name} de {days} jour[s]"
- "reporte <ms_le>{task_name:name} de {days} jour[s]"
- "repousse <ms_le>{task_name:name} de {days} jour[s]"
- sentences:
- "reporte [le|la|les|l'] {task_name:name} d'une semaine"
- "repousse [le|la|les|l'] {task_name:name} d'une semaine"
- "reporte <ms_le>{task_name:name} d'une semaine"
- "repousse <ms_le>{task_name:name} d'une semaine"
slots:
days: 7
MaintenanceSupporterSkipTask:
data:
- sentences:
- "(saute|passe) [le|la|les|l'] {task_name:name} [cette fois]"
- "(saute|passe) <ms_le>{task_name:name} cette fois"
- "(saute|passe) (l'entretien|la tâche) {task_name:name} [cette fois]"
- "(saute|passe) ce (tour|cycle) (de|du|de la|des|d') {task_name:name}"
MaintenanceSupporterRecordReading:
data:
- sentences:
- "(enregistre|note|saisis) [un|le] relevé (de|à) {reading_value:value} pour <ms_le>{task_name:name}"
- "le relevé (de|du|de la|des|d') {task_name:name} est [de] {reading_value:value}"
- "<ms_le>{task_name:name} (affiche|indique) {reading_value:value}"
MaintenanceSupporterAddNote:
data:
- sentences:
- "(ajoute|mets) une note (à|au|à la|aux|pour|sur) [l']{task_name:name} (disant|qui dit) [que] {note_text:note}"
- "note pour <ms_le>{task_name:name} que {note_text:note}"
MaintenanceSupporterWhoseTurn:
data:
- sentences:
- "c'est à qui (de faire|pour) <ms_le>{task_name:name}"
- "à qui le tour pour <ms_le>{task_name:name}"
- "qui (doit faire|s'occupe de|est responsable de) <ms_le>{task_name:name}"
MaintenanceSupporterShoppingList:
data:
- sentences:
- "qu'est-ce qu'il faut (acheter|racheter)"
- "qu'est-ce qu'on doit (acheter|racheter)"
- "quelles (pièces|pièces de rechange|fournitures|consommables) (manquent|faut-il racheter)"
- "qu'est-ce qui (manque|est en rupture) [de stock]"
MaintenanceSupporterBoughtPart:
data:
- sentences:
- "(j'ai|nous avons) acheté {quantity} [nouveaux|nouvelles] {part_name:name}"
- "(j'ai|nous avons) acheté (des|du|de la|de nouveaux|de nouvelles) {part_name:name}"
- "ajoute {quantity} {part_name:name} au stock"
MaintenanceSupporterLowBatteries:
data:
- sentences:
- "quelles (piles|batteries) sont (faibles|vides|à plat|presque vides)"
- "(y a-t-il|est-ce qu'il y a) des (piles|batteries) (faibles|vides|à changer)"
- "quelles (piles|batteries) (faut-il|dois-je) (changer|remplacer)"
MaintenanceSupporterUndo:
data:
- sentences:
- "annule (ça|cela|la dernière action|la dernière modification)"
- "c'était une erreur"
lists:
# PAS « name » : c'est la liste intégrée des noms d'ENTITÉS de Home Assistant,
# que l'agent par défaut résout avant d'appeler le gestionnaire — toute phrase
# l'utilisant répondait « je ne connais aucun appareil… » au lieu de nous
# atteindre. L'alias {liste:slot} garde la valeur dans le slot « name ».
# Pas 'name' : c'est la liste intégrée des noms d'ENTITÉS de Home Assistant
# (voir le fichier anglais). {area} est la liste des pièces de Home Assistant.
task_name:
wildcard: true
part_name:
wildcard: true
reading_value:
wildcard: true
note_text:
wildcard: true
days:
range:
from: 1
to: 365
quantity:
range:
from: 1
to: 100
expansion_rules:
# Préfixe propre : ces règles sont fusionnées avec celles de Home Assistant,
# une règle du même nom remplacerait la sienne (« le » y existe déjà).
ms_le: "[le |la |les |l']"
@@ -10,6 +10,13 @@
# Modificare la propria copia è previsto: appena il contenuto non corrisponde
# più a quello che abbiamo scritto, gli aggiornamenti non lo toccano più (e
# nemmeno disattivare l'impostazione lo cancella).
#
# Le frasi personalizzate VINCONO su quelle di Home Assistant quando entrambe
# corrispondono. Un verbo usato anche da quelle integrate (saltare, mettere in
# pausa, silenziare) seguito da un nome libero ingoierebbe «salta questa
# canzone» o «metti in pausa il timer»: queste frasi portano quindi una parola
# di manutenzione (manutenzione, attività, promemoria, «per questa volta»). Un
# test controlla ogni frase integrata contro questo file.
language: "it"
intents:
MaintenanceSupporterListTasks:
@@ -20,8 +27,33 @@ intents:
- "cosa c'è da fare"
- "c'è [qualche] manutenzione da fare"
- "cosa bisogna (fare|controllare)"
# Ambito: chi sta parlando. HA passa al gestore chi ha parlato, quindi
# questo risponde con le SUE attività (incluso il suo turno di rotazione).
- sentences:
- "cosa è in ritardo"
- "quali (manutenzioni|attività) sono (scadute|in ritardo)"
- "c'è qualcosa (di scaduto|in ritardo)"
slots:
status: "overdue"
- sentences:
- "cosa c'è da fare oggi"
- "quale manutenzione scade oggi"
slots:
within_days: 0
- sentences:
- "cosa c'è da fare domani"
slots:
within_days: 1
- sentences:
- "cosa c'è da fare questa settimana"
- "quali manutenzioni scadono questa settimana"
slots:
within_days: 7
- sentences:
- "cosa c'è da fare questo mese"
slots:
within_days: 30
- sentences:
- "cosa c'è da fare (in|nel|nella|nello|nell') {area}"
- "quale manutenzione serve (in|nel|nella|nello|nell') {area}"
- sentences:
- "cosa devo fare [io]"
- "quali sono i miei (compiti|lavori|impegni)"
@@ -29,8 +61,6 @@ intents:
- "ho qualcosa da fare"
slots:
scope: "mine"
# Ambito: la stanza da cui arriva la richiesta, ricavata dall'area del
# satellite — così un tablet a muro risponde per la stanza in cui si trova.
- sentences:
- "cosa c'è da fare (qui|in questa stanza)"
- "quale manutenzione serve (qui|in questa stanza)"
@@ -40,53 +70,106 @@ intents:
MaintenanceSupporterCompleteTask:
data:
- sentences:
- "ho (fatto|finito|completato) [il|lo|la|l'|i|gli|le] {task_name:name}"
- "segna [il|lo|la|l'|i|gli|le] {task_name:name} come (fatto|fatta|completato|completata)"
- "ho (fatto|finito|completato) <ms_il>{task_name:name}"
- "segna <ms_il>{task_name:name} come (fatto|fatta|completato|completata)"
- "<ms_il>{task_name:name} è (fatto|fatta|finito|finita)"
- "completa (la manutenzione|l'attività) {task_name:name}"
MaintenanceSupporterTaskInstructions:
data:
- sentences:
- "come (faccio|si fa) [il|lo|la|l'|i|gli|le] {task_name:name}"
- "(istruzioni|indicazioni) per [il|lo|la|l'|i|gli|le] {task_name:name}"
- "cosa mi serve per [il|lo|la|l'|i|gli|le] {task_name:name}"
- "come (faccio|si fa) <ms_il>{task_name:name}"
- "(istruzioni|indicazioni) per <ms_il>{task_name:name}"
- "cosa mi serve per <ms_il>{task_name:name}"
MaintenanceSupporterTaskDue:
data:
- sentences:
- "quando (scade|va fatto|va fatta) [il|lo|la|l'|i|gli|le] {task_name:name}"
- "quando devo fare [il|lo|la|l'|i|gli|le] {task_name:name}"
- "quando (scade|va fatto|va fatta) <ms_il>{task_name:name}"
- "quando devo fare <ms_il>{task_name:name}"
MaintenanceSupporterSnoozeTask:
data:
- sentences:
- "(silenzia|metti in pausa) [il|lo|la|l'|i|gli|le] {task_name:name}"
- "(silenzia|sospendi) i promemoria (di|del|della|dei|delle|per) {task_name:name}"
- "(silenzia|sospendi|metti in pausa) i promemoria (di|del|della|dello|dell'|dei|delle|per) {task_name:name}"
- "(silenzia|sospendi) (la manutenzione|l'attività) {task_name:name}"
MaintenanceSupporterPartStock:
data:
- sentences:
- "quanti {part_name:name} (abbiamo|ci sono|restano)"
- "quante scorte di {part_name:name} (abbiamo|ci sono)"
- "quant(i|e) {part_name:name} (abbiamo|restano|ci restano) [ancora]"
- "quante scorte (di|del|della|dei|delle) {part_name:name} (abbiamo|ci sono)"
- "quant(i|e) {part_name:name} ci sono in (magazzino|scorta)"
MaintenanceSupporterPostponeTask:
data:
- sentences:
- "(rimanda|rinvia|posticipa) [il|lo|la|l'|i|gli|le] {task_name:name} di {days} (giorno|giorni)"
- "(rimanda|rinvia|posticipa) <ms_il>{task_name:name} di {days} (giorno|giorni)"
- sentences:
- "(rimanda|rinvia|posticipa) [il|lo|la|l'|i|gli|le] {task_name:name} di una settimana"
- "(rimanda|rinvia|posticipa) <ms_il>{task_name:name} di una settimana"
slots:
days: 7
MaintenanceSupporterSkipTask:
data:
- sentences:
- "salta [il|lo|la|l'|i|gli|le] {task_name:name} [per questa volta]"
- "salta <ms_il>{task_name:name} per questa volta"
- "salta (la manutenzione|l'attività) {task_name:name} [per questa volta]"
- "salta questo giro (di|del|della|dei|delle) {task_name:name}"
MaintenanceSupporterRecordReading:
data:
- sentences:
- "(registra|annota|segna) [una] lettura di {reading_value:value} (per|su) <ms_il>{task_name:name}"
- "la lettura (di|del|della|dello|dell') {task_name:name} è [di] {reading_value:value}"
- "<ms_il>{task_name:name} segna {reading_value:value}"
MaintenanceSupporterAddNote:
data:
- sentences:
- "aggiungi una nota (a|al|alla|allo|all'|per) {task_name:name} (che dice|dicendo) [che] {note_text:note}"
- "annota (per|su) <ms_il>{task_name:name} che {note_text:note}"
MaintenanceSupporterWhoseTurn:
data:
- sentences:
- "a chi tocca [fare] <ms_il>{task_name:name}"
- "chi (deve fare|si occupa di|si occupa del|si occupa della) <ms_il>{task_name:name}"
- "chi è responsabile (di|del|della|dello|dell') {task_name:name}"
MaintenanceSupporterShoppingList:
data:
- sentences:
- "cosa (dobbiamo|bisogna) (comprare|ricomprare)"
- "cosa (sta finendo|va ricomprato)"
- "quali (ricambi|pezzi|materiali di consumo|scorte) (mancano|stanno finendo|servono)"
MaintenanceSupporterBoughtPart:
data:
- sentences:
- "(ho|abbiamo) comprato {quantity} {part_name:name}"
- "(ho|abbiamo) comprato (altri|altre|nuovi|nuove) {part_name:name}"
- "aggiungi {quantity} {part_name:name} alle scorte"
MaintenanceSupporterLowBatteries:
data:
- sentences:
- "quali batterie sono (scariche|quasi scariche|basse)"
- "ci sono batterie (scariche|basse|da cambiare)"
- "quali (pile|batterie) (devo|bisogna) (cambiare|sostituire)"
MaintenanceSupporterUndo:
data:
- sentences:
- "annulla (l'ultima azione|l'ultima modifica)"
- "annullalo"
- "è stato un errore"
lists:
# NON «name»: è la lista integrata dei nomi delle ENTITÀ di Home Assistant,
# che l'agente predefinito risolve prima di chiamare il gestore — ogni frase
# che la usava rispondeva «non conosco nessun dispositivo…» invece di
# arrivare fino a noi. L'alias {lista:slot} fa comunque arrivare il valore
# nello slot «name».
# Non 'name': è la lista integrata dei nomi di ENTITÀ di Home Assistant (vedi
# il file inglese). {area} è la lista delle aree di Home Assistant.
task_name:
wildcard: true
part_name:
wildcard: true
reading_value:
wildcard: true
note_text:
wildcard: true
days:
range:
from: 1
to: 365
quantity:
range:
from: 1
to: 100
expansion_rules:
# Prefisso proprio: queste regole si fondono con quelle di Home Assistant.
ms_il: "[il |lo |la |l'|i |gli |le ]"
@@ -10,6 +10,13 @@
# Je eigen kopie aanpassen mag: zodra de inhoud niet meer overeenkomt met wat
# wij geschreven hebben, blijven updates ervan af (en de instelling weer
# uitzetten verwijdert hem ook niet).
#
# Eigen zinnen WINNEN van de ingebouwde zinnen van Home Assistant zodra beide
# passen. Een werkwoord dat de ingebouwde zinnen ook gebruiken (overslaan, op
# stil zetten, markeren) met een vrije naam erachter zou "sla dit nummer over"
# inslikken — zulke zinnen dragen daarom een onderhoudswoord (onderhoud, taak,
# herinneringen, "deze keer"). Een test legt elke ingebouwde zin naast dit
# bestand.
language: "nl"
intents:
MaintenanceSupporterListTasks:
@@ -19,8 +26,33 @@ intents:
- "wat moet er onderhouden worden"
- "is er [nog] onderhoud te doen"
- "welke (klusjes|klussen|taken) staan open"
# Bereik: degene die vraagt. HA geeft aan de handler door wie er sprak,
# dus dit antwoordt met DIENS taken (inclusief zijn beurt in de rotatie).
- sentences:
- "wat is (te laat|achterstallig|over tijd)"
- "welk onderhoud is (te laat|achterstallig)"
- "welke (taken|klusjes|klussen) zijn (te laat|achterstallig)"
slots:
status: "overdue"
- sentences:
- "wat moet er vandaag gebeuren"
- "welk onderhoud staat [er] vandaag (gepland|open)"
slots:
within_days: 0
- sentences:
- "wat moet er morgen gebeuren"
slots:
within_days: 1
- sentences:
- "wat moet er deze week gebeuren"
- "welk onderhoud staat [er] deze week (gepland|open)"
slots:
within_days: 7
- sentences:
- "wat moet er deze maand gebeuren"
slots:
within_days: 30
- sentences:
- "wat moet er in [de|het] {area} gebeuren"
- "welk onderhoud is in [de|het] {area} nodig"
- sentences:
- "wat moet ik [nog] doen"
- "wat zijn mijn (taken|klusjes|klussen)"
@@ -28,8 +60,6 @@ intents:
- "heb ik [nog] (iets|wat) te doen"
slots:
scope: "mine"
# Bereik: de kamer waaruit gevraagd wordt, afgeleid uit het gebied van de
# satelliet — zo antwoordt een wandtablet voor zijn eigen kamer.
- sentences:
- "wat moet er hier gebeuren"
- "welk onderhoud is (hier|in deze kamer) nodig"
@@ -39,58 +69,111 @@ intents:
MaintenanceSupporterCompleteTask:
data:
- sentences:
- "ik heb [de|het] {task_name:name} (gedaan|afgerond)"
- "markeer [de|het] {task_name:name} als (gedaan|klaar|afgerond)"
- "[de|het] {task_name:name} is (klaar|gedaan)"
- "ik heb <ms_de> {task_name:name} (gedaan|afgerond)"
- "markeer <ms_de> (taak|onderhoud|klus) {task_name:name} als (gedaan|klaar|afgerond)"
- "<ms_de> {task_name:name} is (klaar|gedaan|afgerond)"
MaintenanceSupporterTaskInstructions:
data:
- sentences:
- "hoe doe ik [de|het] {task_name:name}"
- "(instructies|uitleg) voor [de|het] {task_name:name}"
- "wat heb ik nodig voor [de|het] {task_name:name}"
- "hoe doe ik <ms_de> {task_name:name}"
- "(instructies|uitleg) voor <ms_de> {task_name:name}"
- "wat heb ik nodig voor <ms_de> {task_name:name}"
MaintenanceSupporterTaskDue:
data:
- sentences:
- "wanneer moet [de|het] {task_name:name} (gebeuren|gedaan worden)"
- "wanneer is [de|het] {task_name:name} aan de beurt"
- "wanneer moet <ms_de> {task_name:name} (gebeuren|gedaan worden)"
- "wanneer is <ms_de> {task_name:name} aan de beurt"
MaintenanceSupporterSnoozeTask:
data:
- sentences:
- "zet [de|het] {task_name:name} op stil"
- "(pauzeer|stop) de herinneringen voor [de|het] {task_name:name}"
- "zet de herinneringen (voor|van) <ms_de> {task_name:name} op stil"
- "(pauzeer|stop) de herinneringen (voor|van) <ms_de> {task_name:name}"
- "zet <ms_de> (taak|onderhoud) {task_name:name} op stil"
MaintenanceSupporterPartStock:
data:
- sentences:
- "hoeveel {part_name:name} hebben we [nog]"
- "hoeveel {part_name:name} (is|zijn) er nog"
- "wat is de voorraad [van] [de|het] {part_name:name}"
- "wat is de voorraad [van] <ms_de> {part_name:name}"
MaintenanceSupporterPostponeTask:
data:
- sentences:
- "stel [de|het] {task_name:name} {days} dag[en] uit"
- "schuif [de|het] {task_name:name} {days} dag[en] op"
- "verplaats [de|het] {task_name:name} met {days} dag[en]"
- "stel <ms_de> {task_name:name} {days} dag[en] uit"
- "schuif <ms_de> {task_name:name} {days} dag[en] op"
- "verplaats <ms_de> {task_name:name} met {days} dag[en]"
- sentences:
- "stel [de|het] {task_name:name} een week uit"
- "schuif [de|het] {task_name:name} een week op"
- "stel <ms_de> {task_name:name} een week uit"
- "schuif <ms_de> {task_name:name} een week op"
slots:
days: 7
MaintenanceSupporterSkipTask:
data:
- sentences:
- "sla [de|het] {task_name:name} [deze keer] over"
- "sla deze (ronde|beurt) van [de|het] {task_name:name} over"
- "sla <ms_de> {task_name:name} deze keer over"
- "sla <ms_de> (taak|onderhoud|klus) {task_name:name} [deze keer] over"
- "sla deze (ronde|beurt) van <ms_de> {task_name:name} over"
MaintenanceSupporterRecordReading:
data:
- sentences:
- "(noteer|registreer) [een] (meterstand|stand|meting) van {reading_value:value} (voor|bij) <ms_de> {task_name:name}"
- "de (meterstand|stand) van <ms_de> {task_name:name} is {reading_value:value}"
MaintenanceSupporterAddNote:
data:
- sentences:
- "noteer (bij|voor) <ms_de> {task_name:name} dat {note_text:note}"
- "maak een notitie (bij|voor) <ms_de> {task_name:name} dat {note_text:note}"
MaintenanceSupporterWhoseTurn:
data:
- sentences:
- "wie is er aan de beurt (voor|met) <ms_de> {task_name:name}"
- "wie moet <ms_de> {task_name:name} doen"
- "wie is verantwoordelijk voor <ms_de> {task_name:name}"
MaintenanceSupporterShoppingList:
data:
- sentences:
- "wat moeten we (kopen|bijkopen|halen)"
- "wat moet er (gekocht|bijgekocht|besteld) worden"
- "welke (onderdelen|reserveonderdelen|voorraden|verbruiksartikelen) (zijn bijna op|raken op|moeten we kopen)"
- "wat raakt er op"
MaintenanceSupporterBoughtPart:
data:
- sentences:
- "(ik heb|we hebben) {quantity} [nieuwe] {part_name:name} gekocht"
- "(ik heb|we hebben) nieuwe {part_name:name} gekocht"
- "voeg {quantity} {part_name:name} toe aan de voorraad"
MaintenanceSupporterLowBatteries:
data:
- sentences:
- "welke batterijen zijn (bijna leeg|leeg|zwak)"
- "zijn er batterijen (bijna leeg|leeg|zwak)"
- "welke batterijen (moet ik|moeten we) (vervangen|wisselen)"
MaintenanceSupporterUndo:
data:
- sentences:
- "maak (dat|dit|de laatste actie) ongedaan"
- "draai (dat|dit|de laatste actie) terug"
- "dat was een fout"
lists:
# NIET "name": dat is de ingebouwde lijst met ENTITEITSnamen van Home
# Assistant, die de standaardagent al oplost voordat onze handler draait —
# elke zin die hem gebruikte antwoordde "ik ken geen apparaat dat zo heet…"
# in plaats van ons te bereiken. Het alias {lijst:slot} laat de waarde
# alsnog in het slot "name" aankomen.
# Niet 'name': dat is de ingebouwde lijst met ENTITEITSnamen van Home
# Assistant (zie het Engelse bestand). {area} is de gebiedenlijst van Home
# Assistant.
task_name:
wildcard: true
part_name:
wildcard: true
reading_value:
wildcard: true
note_text:
wildcard: true
days:
range:
from: 1
to: 365
quantity:
range:
from: 1
to: 100
expansion_rules:
# Eigen voorvoegsel: deze regels worden samengevoegd met die van Home
# Assistant.
ms_de: "[de|het]"
@@ -13,7 +13,7 @@
"skipped": "Tento cyklus úkolu '{task}' u zařízení {object} je přeskočený. Další termín je {date}.",
"skip_disabled": "Přeskakování je pro '{task}' vypnuto.",
"not_found": "Nenašel jsem úkol údržby, který by odpovídal '{name}'.",
"ambiguous": "Tomu odpovídá víc úkolů: {candidates}. Upřesni to, prosím.",
"ambiguous": "Tomu odpovídá víc úkolů: {candidates}. Řekni který, například „{example}“.",
"too_early": "Úkol '{task}' se dá dokončit až blíž k termínu.",
"st_overdue": "zpoždění {days} dní",
"st_overdue_one": "zpoždění jeden den",
@@ -43,5 +43,49 @@
"st_phase": " — další krok: {phase}",
"st_priority": ", vysoká priorita",
"completed_phase": "'{task}' na {object} dokončeno — krok: {phase}. Příště: {next}.",
"guide_phase": "Aktuální krok: {phase} (krok {index} z {count})."
"guide_phase": "Aktuální krok: {phase} (krok {index} z {count}).",
"part_ambiguous": "Tomu odpovídá víc dílů: {candidates}. Řekni který, například „{example}“.",
"st_no_date": "bez termínu",
"st_overdue_plain": "po termínu",
"st_due_soon_plain": "brzy na řadě",
"months": "ledna,února,března,dubna,května,června,července,srpna,září,října,listopadu,prosince",
"date_format": "{day}. {month}",
"date_format_year": "{day}. {month} {year}",
"skipped_no_date": "Tento cyklus úkolu '{task}' u zařízení {object} byl přeskočen.",
"snoozed_one": "Připomínky k úkolu '{task}' u zařízení {object} jsou ztlumené na hodinu.",
"guide_checklist_one": "jeden krok kontrolního seznamu: {steps}",
"guide_none_plain": "K úkolu '{task}' u zařízení {object} nejsou uložené žádné návody, dokumenty ani díly.",
"none_due_window": "V tomto období nic nevychází.",
"none_due_area": "V oblasti {area} není potřeba žádná údržba.",
"unknown_area_name": "Oblast s názvem '{area}' neznám.",
"reading_recorded": "Odečet {value}{unit} zapsán k úkolu '{task}' u zařízení {object}.",
"reading_not_a_reading": "Úkol '{task}' nezaznamenává odečty.",
"reading_multi": "Úkol '{task}' zaznamenává více odečtů — zadej je v panelu údržby.",
"reading_unclear": "V '{value}' jsem nezachytil žádné číslo.",
"note_added": "Poznámka přidána k úkolu '{task}' u zařízení {object}.",
"note_too_long": "Poznámky úkolu '{task}' jsou plné — nejdřív je zkrať v panelu údržby.",
"note_empty": "Co má být v poznámce?",
"turn_user": "Teď je na řadě: {user} — '{task}' u zařízení {object}.",
"turn_you": "Teď jsi na řadě ty: '{task}' u zařízení {object}.",
"turn_nobody": "K úkolu '{task}' u zařízení {object} není nikdo přiřazen.",
"buy_none": "Nic nedochází — není potřeba kupovat díly.",
"buy_list": "Docházejí díly, celkem {count}: {items}.",
"buy_list_one": "Dochází jeden díl: {items}.",
"buy_item": "{part} (zbývá {stock})",
"bought": "Přidáno {qty} × {part} — teď je skladem {stock}.",
"batteries_unavailable": "Žádné baterie se nesledují.",
"batteries_none": "Žádná baterie není slabá.",
"batteries_low": "Slabé baterie, celkem {count}: {items}.",
"batteries_low_one": "Jedna baterie je slabá: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Koupit: {needs}.",
"undo_none": "Za posledních {minutes} minut od tebe není co vracet.",
"undo_changed": "Úkol '{task}' se mezitím změnil, takže ho nechávám, jak je.",
"undone_completed": "Vráceno: úkol '{task}' u zařízení {object} už není splněný.",
"undone_reading": "Vráceno: odečet k úkolu '{task}' u zařízení {object} je odstraněn.",
"undone_skipped": "Vráceno: úkol '{task}' u zařízení {object} už není přeskočený.",
"undone_postponed": "Vráceno: úkol '{task}' u zařízení {object} má zase původní termín.",
"undone_snoozed": "Vráceno: připomínky k úkolu '{task}' u zařízení {object} jsou znovu zapnuté.",
"undone_note": "Vráceno: poznámka k úkolu '{task}' u zařízení {object} je odstraněna.",
"undone_bought": "Vráceno: {qty} × {part} znovu odebráno ze skladu."
}
@@ -13,7 +13,7 @@
"skipped": "Jeg har sprunget denne omgang af '{task}' på {object} over. Næste omgang skal laves den {date}.",
"skip_disabled": "Spring over er slået fra for '{task}'.",
"not_found": "Jeg kunne ikke finde en vedligeholdelsesopgave, der passer på '{name}'.",
"ambiguous": "Det passer på flere opgaver: {candidates}. Vær lige lidt mere præcis.",
"ambiguous": "Det passer på flere opgaver: {candidates}. Sig hvilken, for eksempel »{example}«.",
"too_early": "'{task}' kan først markeres som udført tættere på forfaldsdatoen.",
"st_overdue": "{days} dage for sent",
"st_overdue_one": "én dag for sent",
@@ -43,5 +43,49 @@
"st_phase": " — næste trin: {phase}",
"st_priority": ", høj prioritet",
"completed_phase": "'{task}' på {object} udført — trin: {phase}. Næste gang: {next}.",
"guide_phase": "Aktuelt trin: {phase} (trin {index} af {count})."
"guide_phase": "Aktuelt trin: {phase} (trin {index} af {count}).",
"part_ambiguous": "Det passer på flere reservedele: {candidates}. Sig hvilken, for eksempel »{example}«.",
"st_no_date": "uden forfaldsdato",
"st_overdue_plain": "forsinket",
"st_due_soon_plain": "snart",
"months": "januar,februar,marts,april,maj,juni,juli,august,september,oktober,november,december",
"date_format": "{day}. {month}",
"date_format_year": "{day}. {month} {year}",
"skipped_no_date": "Denne runde af '{task}' på {object} er sprunget over.",
"snoozed_one": "Påmindelser for '{task}' på {object} er sat på pause i en time.",
"guide_checklist_one": "ét tjeklistetrin: {steps}",
"guide_none_plain": "Der er ingen gemte vejledninger, dokumenter eller reservedele til '{task}' på {object}.",
"none_due_window": "Intet forfalder i den periode.",
"none_due_area": "Intet skal vedligeholdes i {area}.",
"unknown_area_name": "Jeg kender ikke noget område, der hedder '{area}'.",
"reading_recorded": "Aflæsning {value}{unit} registreret for '{task}' på {object}.",
"reading_not_a_reading": "'{task}' registrerer ingen aflæsning.",
"reading_multi": "'{task}' registrerer flere aflæsninger — indtast dem i vedligeholdelsespanelet.",
"reading_unclear": "Jeg fangede ikke noget tal i '{value}'.",
"note_added": "Noten er tilføjet til '{task}' på {object}.",
"note_too_long": "Noterne til '{task}' er fulde — gør dem kortere i vedligeholdelsespanelet først.",
"note_empty": "Hvad skal der stå i noten?",
"turn_user": "{user} har tur: '{task}' på {object}.",
"turn_you": "Det er din tur: '{task}' på {object}.",
"turn_nobody": "Ingen er tildelt '{task}' på {object}.",
"buy_none": "Intet er ved at slippe op — der skal ikke købes reservedele.",
"buy_list": "{count} reservedele er ved at slippe op: {items}.",
"buy_list_one": "Én reservedel er ved at slippe op: {items}.",
"buy_item": "{part} ({stock} tilbage)",
"bought": "{qty} × {part} lagt på lager — nu {stock} på lager.",
"batteries_unavailable": "Ingen batterier bliver overvåget.",
"batteries_none": "Ingen batterier er ved at være flade.",
"batteries_low": "{count} batterier er ved at være flade: {items}.",
"batteries_low_one": "Ét batteri er ved at være fladt: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Skal købes: {needs}.",
"undo_none": "Der er intet fra dig at fortryde fra de sidste {minutes} minutter.",
"undo_changed": "'{task}' er ændret siden, så jeg lader den være.",
"undone_completed": "Fortrudt: '{task}' på {object} er ikke længere udført.",
"undone_reading": "Fortrudt: aflæsningen for '{task}' på {object} er fjernet.",
"undone_skipped": "Fortrudt: '{task}' på {object} er ikke længere sprunget over.",
"undone_postponed": "Fortrudt: '{task}' på {object} har sin gamle dato igen.",
"undone_snoozed": "Fortrudt: påmindelserne for '{task}' på {object} er slået til igen.",
"undone_note": "Fortrudt: noten på '{task}' ({object}) er fjernet.",
"undone_bought": "Fortrudt: {qty} × {part} taget ud af lageret igen."
}
@@ -13,7 +13,7 @@
"skipped": "Dieser Durchgang von '{task}' an {object} wurde übersprungen. Der nächste ist am {date} fällig.",
"skip_disabled": "Überspringen ist für '{task}' ausgeschaltet.",
"not_found": "Ich habe keine Wartungsaufgabe zu '{name}' gefunden.",
"ambiguous": "Das passt auf mehrere Aufgaben: {candidates}. Bitte formuliere genauer.",
"ambiguous": "Das passt auf mehrere Aufgaben: {candidates}. Sag, welche du meinst, zum Beispiel „{example}“.",
"too_early": "'{task}' kann erst näher am Fälligkeitstermin erledigt werden.",
"st_overdue": "seit {days} Tagen überfällig",
"st_overdue_one": "seit einem Tag überfällig",
@@ -43,5 +43,49 @@
"st_phase": " — nächster Schritt: {phase}",
"st_priority": ", hohe Priorität",
"completed_phase": "'{task}' an {object} erledigt — Schritt: {phase}. Beim nächsten Mal: {next}.",
"guide_phase": "Aktueller Schritt: {phase} (Schritt {index} von {count})."
"guide_phase": "Aktueller Schritt: {phase} (Schritt {index} von {count}).",
"part_ambiguous": "Das passt auf mehrere Ersatzteile: {candidates}. Sag, welches du meinst, zum Beispiel „{example}“.",
"st_no_date": "ohne Fälligkeitsdatum",
"st_overdue_plain": "überfällig",
"st_due_soon_plain": "bald fällig",
"months": "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
"date_format": "{day}. {month}",
"date_format_year": "{day}. {month} {year}",
"skipped_no_date": "Dieser Durchgang von '{task}' an {object} wurde übersprungen.",
"snoozed_one": "Erinnerungen für '{task}' an {object} für eine Stunde stummgeschaltet.",
"guide_checklist_one": "ein Checklisten-Schritt: {steps}",
"guide_none_plain": "Zu '{task}' an {object} sind keine Anleitungen, Dokumente oder Ersatzteile hinterlegt.",
"none_due_window": "In diesem Zeitraum ist nichts fällig.",
"none_due_area": "Im Bereich {area} ist keine Wartung fällig.",
"unknown_area_name": "Einen Bereich namens '{area}' kenne ich nicht.",
"reading_recorded": "{value}{unit} für '{task}' an {object} eingetragen.",
"reading_not_a_reading": "'{task}' erfasst keinen Messwert.",
"reading_multi": "'{task}' erfasst mehrere Messwerte — trag sie im Wartungs-Panel ein.",
"reading_unclear": "In '{value}' habe ich keine Zahl erkannt.",
"note_added": "Notiz zu '{task}' an {object} hinzugefügt.",
"note_too_long": "Die Notizen zu '{task}' sind voll — kürze sie zuerst im Wartungs-Panel.",
"note_empty": "Was soll in der Notiz stehen?",
"turn_user": "{user} ist dran: '{task}' an {object}.",
"turn_you": "Du bist dran: '{task}' an {object}.",
"turn_nobody": "Für '{task}' an {object} ist niemand eingetragen.",
"buy_none": "Nichts wird knapp — es müssen keine Ersatzteile gekauft werden.",
"buy_list": "{count} Ersatzteile werden knapp: {items}.",
"buy_list_one": "Ein Ersatzteil wird knapp: {items}.",
"buy_item": "{part} (noch {stock})",
"bought": "{qty} × {part} eingebucht — jetzt {stock} auf Lager.",
"batteries_unavailable": "Es werden keine Batterien überwacht.",
"batteries_none": "Keine Batterie ist schwach.",
"batteries_low": "{count} Batterien sind schwach: {items}.",
"batteries_low_one": "Eine Batterie ist schwach: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Zu kaufen: {needs}.",
"undo_none": "Von dir gibt es aus den letzten {minutes} Minuten nichts rückgängig zu machen.",
"undo_changed": "'{task}' hat sich inzwischen geändert — ich lasse es, wie es ist.",
"undone_completed": "Rückgängig gemacht: '{task}' an {object} ist nicht mehr erledigt.",
"undone_reading": "Rückgängig gemacht: Der Messwert für '{task}' an {object} ist entfernt.",
"undone_skipped": "Rückgängig gemacht: '{task}' an {object} ist nicht mehr übersprungen.",
"undone_postponed": "Rückgängig gemacht: '{task}' an {object} hat wieder den alten Termin.",
"undone_snoozed": "Rückgängig gemacht: Die Erinnerungen für '{task}' an {object} sind wieder an.",
"undone_note": "Rückgängig gemacht: Die Notiz zu '{task}' an {object} ist entfernt.",
"undone_bought": "Rückgängig gemacht: {qty} × {part} wieder aus dem Bestand genommen."
}
@@ -13,7 +13,7 @@
"skipped": "Skipped this cycle of '{task}' on {object}. The next one is due {date}.",
"skip_disabled": "Skipping is turned off for '{task}'.",
"not_found": "I couldn't find a maintenance task matching '{name}'.",
"ambiguous": "That matches several tasks: {candidates}. Please be more specific.",
"ambiguous": "That matches several tasks: {candidates}. Say which one, for example “{example}”.",
"too_early": "'{task}' can only be completed closer to its due date.",
"st_overdue": "{days} days overdue",
"st_overdue_one": "1 day overdue",
@@ -43,5 +43,49 @@
"st_phase": " — next step: {phase}",
"st_priority": ", high priority",
"completed_phase": "Completed '{task}' on {object} — step done: {phase}. Next time: {next}.",
"guide_phase": "Current step: {phase} (step {index} of {count})."
"guide_phase": "Current step: {phase} (step {index} of {count}).",
"part_ambiguous": "That matches several spare parts: {candidates}. Say which one, for example “{example}”.",
"st_no_date": "no due date",
"st_overdue_plain": "overdue",
"st_due_soon_plain": "due soon",
"months": "January,February,March,April,May,June,July,August,September,October,November,December",
"date_format": "{month} {day}",
"date_format_year": "{month} {day}, {year}",
"skipped_no_date": "Skipped this cycle of '{task}' on {object}.",
"snoozed_one": "Snoozed reminders for '{task}' on {object} for one hour.",
"guide_checklist_one": "one checklist step: {steps}",
"guide_none_plain": "There are no stored instructions, documents or spare parts for '{task}' on {object}.",
"none_due_window": "Nothing is due in that time.",
"none_due_area": "Nothing needs maintenance in {area}.",
"unknown_area_name": "I don't know an area called '{area}'.",
"reading_recorded": "Recorded {value}{unit} for '{task}' on {object}.",
"reading_not_a_reading": "'{task}' doesn't record a reading.",
"reading_multi": "'{task}' records several readings — enter them in the Maintenance panel.",
"reading_unclear": "I didn't catch a number in '{value}'.",
"note_added": "Added the note to '{task}' on {object}.",
"note_too_long": "The notes of '{task}' are full — shorten them in the Maintenance panel first.",
"note_empty": "What should the note say?",
"turn_user": "It's {user}'s turn: '{task}' on {object}.",
"turn_you": "It's your turn: '{task}' on {object}.",
"turn_nobody": "Nobody is assigned to '{task}' on {object}.",
"buy_none": "Nothing is running low — no spare parts need buying.",
"buy_list": "{count} spare parts are running low: {items}.",
"buy_list_one": "One spare part is running low: {items}.",
"buy_item": "{part} ({stock} left)",
"bought": "Added {qty} × {part} — {stock} in stock now.",
"batteries_unavailable": "No batteries are being tracked.",
"batteries_none": "No batteries are low.",
"batteries_low": "{count} batteries are low: {items}.",
"batteries_low_one": "One battery is low: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " To buy: {needs}.",
"undo_none": "There is nothing of yours to undo from the last {minutes} minutes.",
"undo_changed": "'{task}' has changed since, so I left it as it is.",
"undone_completed": "Undone: '{task}' on {object} is no longer completed.",
"undone_reading": "Undone: the reading for '{task}' on {object} is removed.",
"undone_skipped": "Undone: '{task}' on {object} is no longer skipped.",
"undone_postponed": "Undone: '{task}' on {object} is back on its old date.",
"undone_snoozed": "Undone: reminders for '{task}' on {object} are on again.",
"undone_note": "Undone: the note on '{task}' on {object} is removed.",
"undone_bought": "Undone: took {qty} × {part} back out of stock."
}
@@ -13,7 +13,7 @@
"skipped": "Me he saltado este ciclo de '{task}' en {object}. El siguiente toca el {date}.",
"skip_disabled": "Omitir está desactivado para '{task}'.",
"not_found": "No he encontrado ninguna tarea de mantenimiento que coincida con '{name}'.",
"ambiguous": "Eso coincide con varias tareas: {candidates}. Concreta un poco más, por favor.",
"ambiguous": "Eso coincide con varias tareas: {candidates}. Di cuál, por ejemplo «{example}».",
"too_early": "'{task}' solo se puede completar cuando se acerque su fecha prevista.",
"st_overdue": "{days} días de retraso",
"st_overdue_one": "un día de retraso",
@@ -43,5 +43,49 @@
"st_phase": " — siguiente paso: {phase}",
"st_priority": ", prioridad alta",
"completed_phase": "'{task}' en {object} completado — paso: {phase}. La próxima vez: {next}.",
"guide_phase": "Paso actual: {phase} (paso {index} de {count})."
"guide_phase": "Paso actual: {phase} (paso {index} de {count}).",
"part_ambiguous": "Eso coincide con varios repuestos: {candidates}. Di cuál, por ejemplo «{example}».",
"st_no_date": "sin fecha",
"st_overdue_plain": "atrasada",
"st_due_soon_plain": "pronto",
"months": "enero,febrero,marzo,abril,mayo,junio,julio,agosto,septiembre,octubre,noviembre,diciembre",
"date_format": "{day} de {month}",
"date_format_year": "{day} de {month} de {year}",
"skipped_no_date": "Se ha saltado esta ronda de '{task}' en {object}.",
"snoozed_one": "Avisos de '{task}' en {object} silenciados durante una hora.",
"guide_checklist_one": "un paso de la lista: {steps}",
"guide_none_plain": "No hay instrucciones, documentos ni repuestos guardados para '{task}' en {object}.",
"none_due_window": "No toca nada en ese plazo.",
"none_due_area": "No hay mantenimiento pendiente en {area}.",
"unknown_area_name": "No conozco ninguna zona llamada '{area}'.",
"reading_recorded": "Lectura de {value}{unit} registrada para '{task}' en {object}.",
"reading_not_a_reading": "'{task}' no registra lecturas.",
"reading_multi": "'{task}' registra varias lecturas: introdúcelas en el panel de mantenimiento.",
"reading_unclear": "No he entendido ningún número en '{value}'.",
"note_added": "Nota añadida a '{task}' en {object}.",
"note_too_long": "Las notas de '{task}' están llenas: acórtalas primero en el panel de mantenimiento.",
"note_empty": "¿Qué debe decir la nota?",
"turn_user": "Le toca a {user}: '{task}' en {object}.",
"turn_you": "Te toca a ti: '{task}' en {object}.",
"turn_nobody": "Nadie tiene asignada '{task}' en {object}.",
"buy_none": "No se está acabando nada: no hay que comprar repuestos.",
"buy_list": "Se están acabando {count} repuestos: {items}.",
"buy_list_one": "Se está acabando un repuesto: {items}.",
"buy_item": "{part} (quedan {stock})",
"bought": "{qty} × {part} añadidos: ahora hay {stock} en stock.",
"batteries_unavailable": "No se está siguiendo ninguna batería.",
"batteries_none": "Ninguna batería está baja.",
"batteries_low": "{count} baterías están bajas: {items}.",
"batteries_low_one": "Una batería está baja: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Para comprar: {needs}.",
"undo_none": "No hay nada tuyo que deshacer de los últimos {minutes} minutos.",
"undo_changed": "'{task}' ha cambiado desde entonces, así que lo dejo como está.",
"undone_completed": "Deshecho: '{task}' en {object} ya no está completada.",
"undone_reading": "Deshecho: se ha quitado la lectura de '{task}' en {object}.",
"undone_skipped": "Deshecho: '{task}' en {object} ya no está saltada.",
"undone_postponed": "Deshecho: '{task}' en {object} vuelve a su fecha anterior.",
"undone_snoozed": "Deshecho: los avisos de '{task}' en {object} vuelven a estar activos.",
"undone_note": "Deshecho: se ha quitado la nota de '{task}' en {object}.",
"undone_bought": "Deshecho: {qty} × {part} retirados del stock."
}
@@ -13,7 +13,7 @@
"skipped": "Ohitin tehtävän '{task}' tämän kerran kohteessa {object}. Seuraava ajankohta: {date}.",
"skip_disabled": "Ohittaminen on poistettu käytöstä kohteelle '{task}'.",
"not_found": "En löytänyt huoltotehtävää, joka vastaisi hakua '{name}'.",
"ambiguous": "Tämä vastaa useaa tehtävää: {candidates}. Tarkenna vielä hieman.",
"ambiguous": "Tämä vastaa useaa tehtävää: {candidates}. Sano mikä, esimerkiksi ”{example}”.",
"too_early": "Tehtävän '{task}' voi kuitata tehdyksi vasta lähempänä sen ajankohtaa.",
"st_overdue": "{days} päivää myöhässä",
"st_overdue_one": "päivän myöhässä",
@@ -43,5 +43,49 @@
"st_phase": " — seuraava vaihe: {phase}",
"st_priority": ", korkea prioriteetti",
"completed_phase": "'{task}' ({object}) valmis — vaihe: {phase}. Ensi kerralla: {next}.",
"guide_phase": "Nykyinen vaihe: {phase} (vaihe {index}/{count})."
"guide_phase": "Nykyinen vaihe: {phase} (vaihe {index}/{count}).",
"part_ambiguous": "Tämä vastaa useaa varaosaa: {candidates}. Sano mikä, esimerkiksi ”{example}”.",
"st_no_date": "ei eräpäivää",
"st_overdue_plain": "myöhässä",
"st_due_soon_plain": "pian vuorossa",
"months": "tammikuuta,helmikuuta,maaliskuuta,huhtikuuta,toukokuuta,kesäkuuta,heinäkuuta,elokuuta,syyskuuta,lokakuuta,marraskuuta,joulukuuta",
"date_format": "{day}. {month}",
"date_format_year": "{day}. {month} {year}",
"skipped_no_date": "Ohitin tehtävän '{task}' tämän kerran kohteessa {object}.",
"snoozed_one": "Muistutukset tehtävästä '{task}' kohteessa {object} on vaimennettu tunniksi.",
"guide_checklist_one": "yksi tarkistuslistan kohta: {steps}",
"guide_none_plain": "Tehtävälle '{task}' kohteessa {object} ei ole tallennettu ohjeita, dokumentteja eikä varaosia.",
"none_due_window": "Sillä aikavälillä ei ole mitään vuorossa.",
"none_due_area": "Alueella {area} ei tarvita huoltoa.",
"unknown_area_name": "En tunne aluetta nimeltä '{area}'.",
"reading_recorded": "Kirjasin lukeman {value}{unit} tehtävälle '{task}' kohteessa {object}.",
"reading_not_a_reading": "Tehtävä '{task}' ei kirjaa lukemia.",
"reading_multi": "Tehtävä '{task}' kirjaa useita lukemia — syötä ne huoltopaneelissa.",
"reading_unclear": "En erottanut lukua kohdasta '{value}'.",
"note_added": "Lisäsin muistiinpanon tehtävälle '{task}' kohteessa {object}.",
"note_too_long": "Tehtävän '{task}' muistiinpanot ovat täynnä — lyhennä niitä ensin huoltopaneelissa.",
"note_empty": "Mitä muistiinpanoon kirjoitetaan?",
"turn_user": "Vuorossa on {user}: '{task}' kohteessa {object}.",
"turn_you": "Sinä olet vuorossa: '{task}' kohteessa {object}.",
"turn_nobody": "Tehtävälle '{task}' kohteessa {object} ei ole määritetty ketään.",
"buy_none": "Mikään ei ole loppumassa — varaosia ei tarvitse ostaa.",
"buy_list": "Varaosia on loppumassa, yhteensä {count}: {items}.",
"buy_list_one": "Yksi varaosa on loppumassa: {items}.",
"buy_item": "{part} (jäljellä {stock})",
"bought": "Lisäsin varastoon {qty} × {part} — nyt varastossa {stock}.",
"batteries_unavailable": "Paristoja ei seurata.",
"batteries_none": "Yksikään paristo ei ole heikko.",
"batteries_low": "Heikkoja paristoja on {count}: {items}.",
"batteries_low_one": "Yksi paristo on heikko: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Ostettavaa: {needs}.",
"undo_none": "Sinulta ei ole mitään peruttavaa viimeisen {minutes} minuutin ajalta.",
"undo_changed": "Tehtävä '{task}' on muuttunut sen jälkeen, joten jätän sen ennalleen.",
"undone_completed": "Peruttu: tehtävä '{task}' kohteessa {object} ei ole enää tehty.",
"undone_reading": "Peruttu: tehtävän '{task}' lukema kohteessa {object} on poistettu.",
"undone_skipped": "Peruttu: tehtävää '{task}' kohteessa {object} ei ole enää ohitettu.",
"undone_postponed": "Peruttu: tehtävä '{task}' kohteessa {object} on taas alkuperäisenä päivänä.",
"undone_snoozed": "Peruttu: tehtävän '{task}' muistutukset kohteessa {object} ovat taas päällä.",
"undone_note": "Peruttu: tehtävän '{task}' muistiinpano kohteessa {object} on poistettu.",
"undone_bought": "Peruttu: {qty} × {part} otettu taas pois varastosta."
}
@@ -13,7 +13,7 @@
"skipped": "J'ai sauté ce cycle de '{task}' sur {object}. Le prochain est à faire le {date}.",
"skip_disabled": "Ignorer est désactivé pour '{task}'.",
"not_found": "Je n'ai pas trouvé de tâche d'entretien qui corresponde à '{name}'.",
"ambiguous": "Ça correspond à plusieurs tâches : {candidates}. Sois un peu plus précis, s'il te plaît.",
"ambiguous": "Ça correspond à plusieurs tâches : {candidates}. Dis laquelle, par exemple « {example} ».",
"too_early": "Tu ne pourras valider '{task}' que plus près de sa date d'échéance.",
"st_overdue": "{days} jours de retard",
"st_overdue_one": "un jour de retard",
@@ -43,5 +43,49 @@
"st_phase": " — prochaine étape : {phase}",
"st_priority": ", priorité haute",
"completed_phase": "« {task} » sur {object} terminé — étape : {phase}. La prochaine fois : {next}.",
"guide_phase": "Étape actuelle : {phase} (étape {index} sur {count})."
"guide_phase": "Étape actuelle : {phase} (étape {index} sur {count}).",
"part_ambiguous": "Ça correspond à plusieurs pièces : {candidates}. Dis laquelle, par exemple « {example} ».",
"st_no_date": "sans échéance",
"st_overdue_plain": "en retard",
"st_due_soon_plain": "bientôt à faire",
"months": "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year}",
"skipped_no_date": "Ce cycle de '{task}' sur {object} a été sauté.",
"snoozed_one": "Rappels pour '{task}' sur {object} mis en pause pendant une heure.",
"guide_checklist_one": "une étape de checklist : {steps}",
"guide_none_plain": "Aucune instruction, aucun document ni aucune pièce n'est enregistré pour '{task}' sur {object}.",
"none_due_window": "Rien n'est prévu sur cette période.",
"none_due_area": "Aucun entretien n'est à faire dans {area}.",
"unknown_area_name": "Je ne connais pas de pièce appelée '{area}'.",
"reading_recorded": "Relevé {value}{unit} enregistré pour '{task}' sur {object}.",
"reading_not_a_reading": "'{task}' n'enregistre pas de relevé.",
"reading_multi": "'{task}' enregistre plusieurs relevés — saisis-les dans le panneau Maintenance.",
"reading_unclear": "Je n'ai pas compris de nombre dans '{value}'.",
"note_added": "Note ajoutée à '{task}' sur {object}.",
"note_too_long": "Les notes de '{task}' sont pleines — raccourcis-les d'abord dans le panneau Maintenance.",
"note_empty": "Que doit dire la note ?",
"turn_user": "C'est au tour de {user} : '{task}' sur {object}.",
"turn_you": "C'est ton tour : '{task}' sur {object}.",
"turn_nobody": "Personne n'est assigné à '{task}' sur {object}.",
"buy_none": "Rien ne manque — aucune pièce à acheter.",
"buy_list": "{count} pièces arrivent en rupture : {items}.",
"buy_list_one": "Une pièce arrive en rupture : {items}.",
"buy_item": "{part} ({stock} restant)",
"bought": "{qty} × {part} ajouté au stock — {stock} en stock maintenant.",
"batteries_unavailable": "Aucune pile n'est suivie.",
"batteries_none": "Aucune pile n'est faible.",
"batteries_low": "{count} piles sont faibles : {items}.",
"batteries_low_one": "Une pile est faible : {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " À acheter : {needs}.",
"undo_none": "Tu n'as rien à annuler des {minutes} dernières minutes.",
"undo_changed": "'{task}' a changé depuis, je le laisse tel quel.",
"undone_completed": "Annulé : '{task}' sur {object} n'est plus fait.",
"undone_reading": "Annulé : le relevé de '{task}' sur {object} est supprimé.",
"undone_skipped": "Annulé : '{task}' sur {object} n'est plus sauté.",
"undone_postponed": "Annulé : '{task}' sur {object} retrouve sa date d'origine.",
"undone_snoozed": "Annulé : les rappels pour '{task}' sur {object} sont réactivés.",
"undone_note": "Annulé : la note sur '{task}' ({object}) est supprimée.",
"undone_bought": "Annulé : {qty} × {part} retiré du stock."
}
@@ -13,7 +13,7 @@
"skipped": "{object} पर '{task}' का यह चक्र छोड़ दिया है। अगली बार {date} को करना है।",
"skip_disabled": "'{task}' के लिए छोड़ना बंद है।",
"not_found": "'{name}' से मिलता-जुलता कोई मेंटेनेंस काम नहीं मिला।",
"ambiguous": "इससे कई काम मेल खाते हैं: {candidates}। थोड़ा और साफ़ बताइए।",
"ambiguous": "इससे कई काम मेल खाते हैं: {candidates}। बताइए कौन-सा, जैसे “{example}”।",
"too_early": "'{task}' को तय तारीख़ के क़रीब आने पर ही पूरा किया जा सकता है।",
"st_overdue": "{days} दिन की देरी",
"st_overdue_one": "एक दिन की देरी",
@@ -43,5 +43,49 @@
"st_phase": " — अगला चरण: {phase}",
"st_priority": ", उच्च प्राथमिकता",
"completed_phase": "{object} पर '{task}' पूर्ण — चरण: {phase}। अगली बार: {next}।",
"guide_phase": "वर्तमान चरण: {phase} ({count} में से चरण {index})।"
"guide_phase": "वर्तमान चरण: {phase} ({count} में से चरण {index})।",
"part_ambiguous": "इससे कई स्पेयर पार्ट मेल खाते हैं: {candidates}। बताइए कौन-सा, जैसे “{example}”।",
"st_no_date": "कोई तारीख़ तय नहीं",
"st_overdue_plain": "देरी से",
"st_due_soon_plain": "जल्द होना है",
"months": "जनवरी,फ़रवरी,मार्च,अप्रैल,मई,जून,जुलाई,अगस्त,सितंबर,अक्टूबर,नवंबर,दिसंबर",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year}",
"skipped_no_date": "{object} पर '{task}' का यह चक्र छोड़ दिया है।",
"snoozed_one": "{object} पर '{task}' के रिमाइंडर एक घंटे के लिए बंद कर दिए हैं।",
"guide_checklist_one": "चेकलिस्ट का एक चरण: {steps}",
"guide_none_plain": "{object} पर '{task}' के लिए कोई निर्देश, दस्तावेज़ या स्पेयर पार्ट सहेजा नहीं गया है।",
"none_due_window": "इस अवधि में कुछ भी तय नहीं है।",
"none_due_area": "{area} में किसी मेंटेनेंस की ज़रूरत नहीं है।",
"unknown_area_name": "'{area}' नाम का कोई क्षेत्र मुझे नहीं पता।",
"reading_recorded": "{object} पर '{task}' के लिए {value}{unit} रीडिंग दर्ज कर दी है।",
"reading_not_a_reading": "'{task}' कोई रीडिंग दर्ज नहीं करता।",
"reading_multi": "'{task}' कई रीडिंग दर्ज करता है — उन्हें मेंटेनेंस पैनल में भरिए।",
"reading_unclear": "'{value}' में मुझे कोई संख्या सुनाई नहीं दी।",
"note_added": "{object} पर '{task}' में नोट जोड़ दिया है।",
"note_too_long": "'{task}' के नोट भर चुके हैं — पहले मेंटेनेंस पैनल में उन्हें छोटा कीजिए।",
"note_empty": "नोट में क्या लिखना है?",
"turn_user": "अभी बारी है: {user} — {object} पर '{task}'।",
"turn_you": "अभी आपकी बारी है: {object} पर '{task}'।",
"turn_nobody": "{object} पर '{task}' किसी को सौंपा नहीं गया है।",
"buy_none": "कुछ भी ख़त्म नहीं हो रहा — कोई स्पेयर पार्ट ख़रीदने की ज़रूरत नहीं है।",
"buy_list": "{count} स्पेयर पार्ट ख़त्म होने वाले हैं: {items}।",
"buy_list_one": "एक स्पेयर पार्ट ख़त्म होने वाला है: {items}।",
"buy_item": "{part} ({stock} बचे)",
"bought": "{qty} × {part} स्टॉक में जोड़ दिए — अब स्टॉक में {stock} हैं।",
"batteries_unavailable": "किसी बैटरी पर नज़र नहीं रखी जा रही है।",
"batteries_none": "कोई बैटरी कमज़ोर नहीं है।",
"batteries_low": "{count} बैटरियाँ कमज़ोर हैं: {items}।",
"batteries_low_one": "एक बैटरी कमज़ोर है: {items}।",
"battery_item": "{device} ({type})",
"batteries_buy": " ख़रीदना है: {needs}।",
"undo_none": "पिछले {minutes} मिनट में आपका कोई काम पलटने के लिए नहीं है।",
"undo_changed": "'{task}' तब से बदल चुका है, इसलिए मैं उसे वैसे ही छोड़ रहा हूँ।",
"undone_completed": "पलट दिया: {object} पर '{task}' अब पूरा नहीं है।",
"undone_reading": "पलट दिया: {object} पर '{task}' की रीडिंग हटा दी गई है।",
"undone_skipped": "पलट दिया: {object} पर '{task}' अब छोड़ा हुआ नहीं है।",
"undone_postponed": "पलट दिया: {object} पर '{task}' फिर से पुरानी तारीख़ पर है।",
"undone_snoozed": "पलट दिया: {object} पर '{task}' के रिमाइंडर फिर से चालू हैं।",
"undone_note": "पलट दिया: {object} पर '{task}' का नोट हटा दिया गया है।",
"undone_bought": "पलट दिया: {qty} × {part} फिर से स्टॉक से हटा दिए।"
}
@@ -13,7 +13,7 @@
"skipped": "Kihagytam ezt a fordulót: '{task}' – {object}. A következő időpont: {date}.",
"skip_disabled": "A kihagyás ki van kapcsolva ehhez: '{task}'.",
"not_found": "Nem találtam ehhez illő karbantartási feladatot: '{name}'.",
"ambiguous": "Több feladatra is illik: {candidates}. Fogalmazz pontosabban.",
"ambiguous": "Több feladatra is illik: {candidates}. Mondd meg, melyikre gondolsz, például: „{example}”.",
"too_early": "'{task}': ezt a feladatot csak az esedékesség közelében lehet elvégezni.",
"st_overdue": "{days} napja késésben",
"st_overdue_one": "egy napja késésben",
@@ -43,5 +43,49 @@
"st_phase": " — következő lépés: {phase}",
"st_priority": ", magas prioritás",
"completed_phase": "'{task}' ({object}) elvégezve — lépés: {phase}. Legközelebb: {next}.",
"guide_phase": "Aktuális lépés: {phase} ({index}. lépés a(z) {count} közül)."
"guide_phase": "Aktuális lépés: {phase} ({index}. lépés a(z) {count} közül).",
"part_ambiguous": "Több alkatrészre is illik: {candidates}. Mondd meg, melyikre gondolsz, például: „{example}”.",
"st_no_date": "nincs határideje",
"st_overdue_plain": "késésben",
"st_due_soon_plain": "hamarosan esedékes",
"months": "január,február,március,április,május,június,július,augusztus,szeptember,október,november,december",
"date_format": "{month} {day}.",
"date_format_year": "{year}. {month} {day}.",
"skipped_no_date": "Kihagytam ezt a fordulót: '{task}' – {object}.",
"snoozed_one": "'{task}' – {object}: egy órára elnémítottam az emlékeztetőket.",
"guide_checklist_one": "egy lépés az ellenőrzőlistán: {steps}",
"guide_none_plain": "Nincsenek mentett útmutatók, dokumentumok vagy alkatrészek ehhez: '{task}' – {object}.",
"none_due_window": "Ebben az időszakban nincs semmi esedékes.",
"none_due_area": "Itt nincs esedékes karbantartás: {area}.",
"unknown_area_name": "Nem ismerek ilyen nevű területet: '{area}'.",
"reading_recorded": "Rögzítettem a leolvasást: {value}{unit} – '{task}' – {object}.",
"reading_not_a_reading": "'{task}': ez a feladat nem rögzít leolvasást.",
"reading_multi": "'{task}': ez a feladat több leolvasást rögzít — add meg őket a karbantartási panelen.",
"reading_unclear": "Nem hallottam számot ebben: '{value}'.",
"note_added": "Hozzáadtam a jegyzetet: '{task}' – {object}.",
"note_too_long": "'{task}': a jegyzetek megteltek — előbb rövidítsd őket a karbantartási panelen.",
"note_empty": "Mi legyen a jegyzetben?",
"turn_user": "Most {user} következik: '{task}' – {object}.",
"turn_you": "Most te következel: '{task}' – {object}.",
"turn_nobody": "Senki sincs hozzárendelve ehhez: '{task}' – {object}.",
"buy_none": "Semmi sem fogy ki — nem kell alkatrészt venni.",
"buy_list": "Kifogyóban lévő alkatrészek, összesen {count}: {items}.",
"buy_list_one": "Egy alkatrész fogyóban van: {items}.",
"buy_item": "{part} (maradt {stock})",
"bought": "Készletre vettem: {qty} × {part} — most {stock} van készleten.",
"batteries_unavailable": "Nincsenek figyelt elemek.",
"batteries_none": "Egyik elem sem gyenge.",
"batteries_low": "Gyenge elemek, összesen {count}: {items}.",
"batteries_low_one": "Egy elem gyenge: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Venni kell: {needs}.",
"undo_none": "Az elmúlt {minutes} percből nincs tőled mit visszavonni.",
"undo_changed": "'{task}' azóta megváltozott, ezért nem nyúlok hozzá.",
"undone_completed": "Visszavonva: '{task}' – {object} már nincs elvégezve.",
"undone_reading": "Visszavonva: a leolvasás törölve: '{task}' – {object}.",
"undone_skipped": "Visszavonva: '{task}' – {object} már nincs kihagyva.",
"undone_postponed": "Visszavonva: '{task}' – {object} újra a korábbi időponton van.",
"undone_snoozed": "Visszavonva: '{task}' – {object} emlékeztetői újra be vannak kapcsolva.",
"undone_note": "Visszavonva: a jegyzet törölve: '{task}' – {object}.",
"undone_bought": "Visszavonva: {qty} × {part} újra levéve a készletről."
}
@@ -13,7 +13,7 @@
"skipped": "Ho saltato questo ciclo di '{task}' su {object}. Il prossimo è previsto per il {date}.",
"skip_disabled": "Saltare è disattivato per '{task}'.",
"not_found": "Non ho trovato nessuna attività di manutenzione che corrisponda a '{name}'.",
"ambiguous": "Corrisponde a più attività: {candidates}. Puoi essere un po' più preciso?",
"ambiguous": "Corrisponde a più attività: {candidates}. Di' quale, per esempio «{example}».",
"too_early": "Puoi completare '{task}' solo quando si avvicina la scadenza.",
"st_overdue": "{days} giorni di ritardo",
"st_overdue_one": "un giorno di ritardo",
@@ -43,5 +43,49 @@
"st_phase": " — prossimo passo: {phase}",
"st_priority": ", priorità alta",
"completed_phase": "'{task}' su {object} completato — passo: {phase}. La prossima volta: {next}.",
"guide_phase": "Passo attuale: {phase} (passo {index} di {count})."
"guide_phase": "Passo attuale: {phase} (passo {index} di {count}).",
"part_ambiguous": "Corrisponde a più ricambi: {candidates}. Di' quale, per esempio «{example}».",
"st_no_date": "senza scadenza",
"st_overdue_plain": "in ritardo",
"st_due_soon_plain": "in scadenza",
"months": "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year}",
"skipped_no_date": "Questo giro di '{task}' su {object} è stato saltato.",
"snoozed_one": "Promemoria per '{task}' su {object} silenziati per un'ora.",
"guide_checklist_one": "un passaggio della checklist: {steps}",
"guide_none_plain": "Per '{task}' su {object} non ci sono istruzioni, documenti o ricambi salvati.",
"none_due_window": "In quel periodo non scade nulla.",
"none_due_area": "In {area} non serve nessuna manutenzione.",
"unknown_area_name": "Non conosco nessuna area chiamata '{area}'.",
"reading_recorded": "Lettura di {value}{unit} registrata per '{task}' su {object}.",
"reading_not_a_reading": "'{task}' non registra letture.",
"reading_multi": "'{task}' registra più letture: inseriscile nel pannello Manutenzione.",
"reading_unclear": "Non ho capito nessun numero in '{value}'.",
"note_added": "Nota aggiunta a '{task}' su {object}.",
"note_too_long": "Le note di '{task}' sono piene: accorciale prima nel pannello Manutenzione.",
"note_empty": "Cosa deve dire la nota?",
"turn_user": "Tocca a {user}: '{task}' su {object}.",
"turn_you": "Tocca a te: '{task}' su {object}.",
"turn_nobody": "Nessuno è assegnato a '{task}' su {object}.",
"buy_none": "Non sta finendo niente: non serve comprare ricambi.",
"buy_list": "Stanno finendo {count} ricambi: {items}.",
"buy_list_one": "Sta finendo un ricambio: {items}.",
"buy_item": "{part} (ne restano {stock})",
"bought": "Aggiunti {qty} × {part}: ora ce ne sono {stock} in scorta.",
"batteries_unavailable": "Nessuna batteria è monitorata.",
"batteries_none": "Nessuna batteria è scarica.",
"batteries_low": "{count} batterie sono scariche: {items}.",
"batteries_low_one": "Una batteria è scarica: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Da comprare: {needs}.",
"undo_none": "Non c'è niente di tuo da annullare negli ultimi {minutes} minuti.",
"undo_changed": "'{task}' è cambiata nel frattempo, quindi la lascio così.",
"undone_completed": "Annullato: '{task}' su {object} non è più completata.",
"undone_reading": "Annullato: la lettura di '{task}' su {object} è stata rimossa.",
"undone_skipped": "Annullato: '{task}' su {object} non è più saltata.",
"undone_postponed": "Annullato: '{task}' su {object} torna alla data di prima.",
"undone_snoozed": "Annullato: i promemoria per '{task}' su {object} sono di nuovo attivi.",
"undone_note": "Annullato: la nota su '{task}' ({object}) è stata rimossa.",
"undone_bought": "Annullato: {qty} × {part} tolti dalle scorte."
}
@@ -13,7 +13,7 @@
"skipped": "{object}の「{task}」の今回分をスキップしました。次回は{date}が期限です。",
"skip_disabled": "'{task}'のスキップは無効になっています。",
"not_found": "「{name}」に一致するメンテナンスタスクは見つかりませんでした。",
"ambiguous": "いくつかのタスクが当てはまります:{candidates}。もう少し詳しく言ってください。",
"ambiguous": "いくつかのタスクが当てはまります:{candidates}。どれか言ってください。例:「{example}」。",
"too_early": "「{task}」は期限が近づいてからでないと完了にできません。",
"st_overdue": "{days}日遅れ",
"st_overdue_one": "1日遅れ",
@@ -43,5 +43,49 @@
"st_phase": "(次のステップ: {phase})",
"st_priority": "、優先度高",
"completed_phase": "{object}の「{task}」を完了しました(ステップ: {phase})。次回は: {next}。",
"guide_phase": "現在のステップ: {phase}({count}ステップ中{index}番目)。"
"guide_phase": "現在のステップ: {phase}({count}ステップ中{index}番目)。",
"part_ambiguous": "いくつかの交換部品が当てはまります:{candidates}。どれか言ってください。例:「{example}」。",
"st_no_date": "期限なし",
"st_overdue_plain": "期限切れ",
"st_due_soon_plain": "もうすぐ期限",
"months": "1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月",
"date_format": "{month}{day}日",
"date_format_year": "{year}年{month}{day}日",
"skipped_no_date": "{object}の「{task}」の今回分をスキップしました。",
"snoozed_one": "{object}の「{task}」のリマインダーを1時間、一時停止しました。",
"guide_checklist_one": "チェックリスト1項目:{steps}",
"guide_none_plain": "{object}の「{task}」には、手順も書類も交換部品も保存されていません。",
"none_due_window": "その期間に期限を迎えるものはありません。",
"none_due_area": "{area}で必要なメンテナンスはありません。",
"unknown_area_name": "「{area}」というエリアは見つかりません。",
"reading_recorded": "{object}の「{task}」に{value}{unit}を記録しました。",
"reading_not_a_reading": "「{task}」は測定値を記録するタスクではありません。",
"reading_multi": "「{task}」は複数の測定値を記録します。メンテナンスパネルで入力してください。",
"reading_unclear": "「{value}」から数値を聞き取れませんでした。",
"note_added": "{object}の「{task}」にメモを追加しました。",
"note_too_long": "「{task}」のメモがいっぱいです。先にメンテナンスパネルで短くしてください。",
"note_empty": "メモには何と書きますか?",
"turn_user": "今の担当は{user}です:{object}の「{task}」。",
"turn_you": "今の担当はあなたです:{object}の「{task}」。",
"turn_nobody": "{object}の「{task}」には担当者がいません。",
"buy_none": "残り少ないものはありません。交換部品を買う必要はありません。",
"buy_list": "残り少ない交換部品が{count}件あります:{items}。",
"buy_list_one": "残り少ない交換部品が1件あります:{items}。",
"buy_item": "{part}(残り{stock})",
"bought": "{qty} × {part}を在庫に追加しました。現在の在庫は{stock}です。",
"batteries_unavailable": "監視しているバッテリーはありません。",
"batteries_none": "残量が少ないバッテリーはありません。",
"batteries_low": "残量が少ないバッテリーが{count}個あります:{items}。",
"batteries_low_one": "残量が少ないバッテリーが1個あります:{items}。",
"battery_item": "{device}({type})",
"batteries_buy": " 購入するもの:{needs}。",
"undo_none": "この{minutes}分間に、あなたが取り消せる操作はありません。",
"undo_changed": "「{task}」はその後変更されたので、そのままにしておきます。",
"undone_completed": "取り消しました:{object}の「{task}」は未完了に戻りました。",
"undone_reading": "取り消しました:{object}の「{task}」の測定値を削除しました。",
"undone_skipped": "取り消しました:{object}の「{task}」のスキップを戻しました。",
"undone_postponed": "取り消しました:{object}の「{task}」は元の日付に戻りました。",
"undone_snoozed": "取り消しました:{object}の「{task}」のリマインダーを再開しました。",
"undone_note": "取り消しました:{object}の「{task}」のメモを削除しました。",
"undone_bought": "取り消しました:{qty} × {part}を在庫から戻しました。"
}
@@ -13,7 +13,7 @@
"skipped": "{object}의 '{task}', 이번 차례는 건너뛰었어요. 다음은 {date}에 예정돼 있어요.",
"skip_disabled": "'{task}'의 건너뛰기가 꺼져 있습니다.",
"not_found": "'{name}'에 맞는 정비 작업을 찾지 못했어요.",
"ambiguous": "여러 작업이 해당돼요: {candidates}. 조금 더 구체적으로 말씀해 주세요.",
"ambiguous": "여러 작업이 해당돼요: {candidates}. 어떤 건지 말해 주세요. 예: '{example}'.",
"too_early": "'{task}' 작업은 예정일이 가까워져야 완료할 수 있어요.",
"st_overdue": "{days}일 지연",
"st_overdue_one": "하루 지연",
@@ -43,5 +43,49 @@
"st_phase": " — 다음 단계: {phase}",
"st_priority": ", 높은 우선순위",
"completed_phase": "{object}의 '{task}' 완료 — 단계: {phase}. 다음번: {next}.",
"guide_phase": "현재 단계: {phase} ({count}단계 중 {index}단계)."
"guide_phase": "현재 단계: {phase} ({count}단계 중 {index}단계).",
"part_ambiguous": "여러 예비 부품이 해당돼요: {candidates}. 어떤 건지 말해 주세요. 예: '{example}'.",
"st_no_date": "예정일 없음",
"st_overdue_plain": "지연됨",
"st_due_soon_plain": "곧 예정",
"months": "1월,2월,3월,4월,5월,6월,7월,8월,9월,10월,11월,12월",
"date_format": "{month} {day}일",
"date_format_year": "{year}년 {month} {day}일",
"skipped_no_date": "{object}의 '{task}', 이번 차례는 건너뛰었어요.",
"snoozed_one": "{object}의 '{task}' 알림을 한 시간 동안 미뤄뒀어요.",
"guide_checklist_one": "체크리스트 1단계: {steps}",
"guide_none_plain": "{object}의 '{task}'에는 저장된 안내나 문서, 예비 부품이 없어요.",
"none_due_window": "그 기간에는 예정된 게 없어요.",
"none_due_area": "{area}에는 챙길 정비가 없어요.",
"unknown_area_name": "'{area}'라는 공간은 모르겠어요.",
"reading_recorded": "{object}의 '{task}'에 측정값 {value}{unit}, 기록했어요.",
"reading_not_a_reading": "'{task}' 작업은 측정값을 기록하지 않아요.",
"reading_multi": "'{task}' 작업은 측정값을 여러 개 기록해요. 정비 패널에서 입력해 주세요.",
"reading_unclear": "'{value}'에서 숫자를 알아듣지 못했어요.",
"note_added": "{object}의 '{task}'에 메모를 추가했어요.",
"note_too_long": "'{task}' 작업의 메모가 가득 찼어요. 먼저 정비 패널에서 줄여 주세요.",
"note_empty": "메모에 뭐라고 적을까요?",
"turn_user": "지금 차례: {user} — {object}의 '{task}'.",
"turn_you": "지금은 당신 차례예요: {object}의 '{task}'.",
"turn_nobody": "{object}의 '{task}' 작업에는 담당자가 없어요.",
"buy_none": "떨어져 가는 게 없어요. 예비 부품을 살 필요가 없어요.",
"buy_list": "떨어져 가는 예비 부품이 {count}개 있어요: {items}.",
"buy_list_one": "떨어져 가는 예비 부품이 1개 있어요: {items}.",
"buy_item": "{part} ({stock}개 남음)",
"bought": "{qty} × {part}, 재고에 추가했어요. 지금 재고: {stock}.",
"batteries_unavailable": "추적 중인 배터리가 없어요.",
"batteries_none": "약한 배터리는 없어요.",
"batteries_low": "약한 배터리가 {count}개 있어요: {items}.",
"batteries_low_one": "약한 배터리가 1개 있어요: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " 살 것: {needs}.",
"undo_none": "최근 {minutes}분 동안 되돌릴 작업이 없어요.",
"undo_changed": "'{task}' 작업이 그사이에 바뀌어서 그대로 둘게요.",
"undone_completed": "되돌렸어요: {object}의 '{task}', 완료를 취소했어요.",
"undone_reading": "되돌렸어요: {object}의 '{task}' 측정값을 지웠어요.",
"undone_skipped": "되돌렸어요: {object}의 '{task}', 건너뛰기를 취소했어요.",
"undone_postponed": "되돌렸어요: {object}의 '{task}', 원래 날짜로 돌아갔어요.",
"undone_snoozed": "되돌렸어요: {object}의 '{task}' 알림을 다시 켰어요.",
"undone_note": "되돌렸어요: {object}의 '{task}' 메모를 지웠어요.",
"undone_bought": "되돌렸어요: {qty} × {part}, 재고에서 다시 뺐어요."
}
@@ -13,7 +13,7 @@
"skipped": "Jeg hoppet over denne runden av '{task}' på {object}. Neste runde skal gjøres {date}.",
"skip_disabled": "Hopp over er slått av for '{task}'.",
"not_found": "Jeg fant ingen vedlikeholdsoppgave som passer med '{name}'.",
"ambiguous": "Det passer med flere oppgaver: {candidates}. Vær litt mer presis.",
"ambiguous": "Det passer med flere oppgaver: {candidates}. Si hvilken, for eksempel «{example}».",
"too_early": "'{task}' kan først krysses av nærmere forfallsdatoen.",
"st_overdue": "{days} dager for sent",
"st_overdue_one": "én dag for sent",
@@ -43,5 +43,49 @@
"st_phase": " — neste trinn: {phase}",
"st_priority": ", høy prioritet",
"completed_phase": "'{task}' på {object} fullført — trinn: {phase}. Neste gang: {next}.",
"guide_phase": "Gjeldende trinn: {phase} (trinn {index} av {count})."
"guide_phase": "Gjeldende trinn: {phase} (trinn {index} av {count}).",
"part_ambiguous": "Det passer med flere reservedeler: {candidates}. Si hvilken, for eksempel «{example}».",
"st_no_date": "uten forfallsdato",
"st_overdue_plain": "forsinket",
"st_due_soon_plain": "snart",
"months": "januar,februar,mars,april,mai,juni,juli,august,september,oktober,november,desember",
"date_format": "{day}. {month}",
"date_format_year": "{day}. {month} {year}",
"skipped_no_date": "Denne runden av '{task}' på {object} er hoppet over.",
"snoozed_one": "Påminnelser for '{task}' på {object} er satt på pause i én time.",
"guide_checklist_one": "ett sjekklistetrinn: {steps}",
"guide_none_plain": "Det finnes ingen lagrede instruksjoner, dokumenter eller reservedeler for '{task}' på {object}.",
"none_due_window": "Ingenting forfaller i den perioden.",
"none_due_area": "Ingenting trenger vedlikehold i {area}.",
"unknown_area_name": "Jeg kjenner ikke til noe område som heter '{area}'.",
"reading_recorded": "Avlesning {value}{unit} registrert for '{task}' på {object}.",
"reading_not_a_reading": "'{task}' registrerer ingen avlesning.",
"reading_multi": "'{task}' registrerer flere avlesninger — legg dem inn i vedlikeholdspanelet.",
"reading_unclear": "Jeg fikk ikke med meg noe tall i '{value}'.",
"note_added": "Notatet er lagt til '{task}' på {object}.",
"note_too_long": "Notatene for '{task}' er fulle — gjør dem kortere i vedlikeholdspanelet først.",
"note_empty": "Hva skal det stå i notatet?",
"turn_user": "{user} har tur: '{task}' på {object}.",
"turn_you": "Det er din tur: '{task}' på {object}.",
"turn_nobody": "Ingen er tildelt '{task}' på {object}.",
"buy_none": "Ingenting holder på å gå tomt — ingen reservedeler må kjøpes.",
"buy_list": "{count} reservedeler holder på å gå tomme: {items}.",
"buy_list_one": "Én reservedel holder på å gå tom: {items}.",
"buy_item": "{part} ({stock} igjen)",
"bought": "{qty} × {part} lagt på lager — nå {stock} på lager.",
"batteries_unavailable": "Ingen batterier blir overvåket.",
"batteries_none": "Ingen batterier er svake.",
"batteries_low": "{count} batterier er svake: {items}.",
"batteries_low_one": "Ett batteri er svakt: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Må kjøpes: {needs}.",
"undo_none": "Det er ingenting fra deg å angre fra de siste {minutes} minuttene.",
"undo_changed": "'{task}' er endret siden, så jeg lar den være.",
"undone_completed": "Angret: '{task}' på {object} er ikke lenger utført.",
"undone_reading": "Angret: avlesningen for '{task}' på {object} er fjernet.",
"undone_skipped": "Angret: '{task}' på {object} er ikke lenger hoppet over.",
"undone_postponed": "Angret: '{task}' på {object} har den gamle datoen igjen.",
"undone_snoozed": "Angret: påminnelsene for '{task}' på {object} er på igjen.",
"undone_note": "Angret: notatet på '{task}' ({object}) er fjernet.",
"undone_bought": "Angret: {qty} × {part} tatt ut av lageret igjen."
}
@@ -13,7 +13,7 @@
"skipped": "Ik heb deze ronde van '{task}' bij {object} overgeslagen. De volgende staat op {date}.",
"skip_disabled": "Overslaan is uitgeschakeld voor '{task}'.",
"not_found": "Ik kon geen onderhoudstaak vinden die past bij '{name}'.",
"ambiguous": "Dat past bij meerdere taken: {candidates}. Kun je het iets preciezer zeggen?",
"ambiguous": "Dat past bij meerdere taken: {candidates}. Zeg welke je bedoelt, bijvoorbeeld ‘{example}’.",
"too_early": "'{task}' kun je pas afvinken als de datum dichterbij is.",
"st_overdue": "{days} dagen te laat",
"st_overdue_one": "één dag te laat",
@@ -43,5 +43,49 @@
"st_phase": " — volgende stap: {phase}",
"st_priority": ", hoge prioriteit",
"completed_phase": "'{task}' op {object} afgerond — stap: {phase}. Volgende keer: {next}.",
"guide_phase": "Huidige stap: {phase} (stap {index} van {count})."
"guide_phase": "Huidige stap: {phase} (stap {index} van {count}).",
"part_ambiguous": "Dat past bij meerdere onderdelen: {candidates}. Zeg welk je bedoelt, bijvoorbeeld ‘{example}’.",
"st_no_date": "zonder vervaldatum",
"st_overdue_plain": "te laat",
"st_due_soon_plain": "binnenkort",
"months": "januari,februari,maart,april,mei,juni,juli,augustus,september,oktober,november,december",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year}",
"skipped_no_date": "Deze ronde van '{task}' bij {object} is overgeslagen.",
"snoozed_one": "Herinneringen voor '{task}' bij {object} staan een uur op stil.",
"guide_checklist_one": "één stap op de checklist: {steps}",
"guide_none_plain": "Voor '{task}' bij {object} zijn geen instructies, documenten of onderdelen opgeslagen.",
"none_due_window": "In die periode staat er niets gepland.",
"none_due_area": "In {area} is geen onderhoud nodig.",
"unknown_area_name": "Ik ken geen ruimte met de naam '{area}'.",
"reading_recorded": "{value}{unit} genoteerd voor '{task}' bij {object}.",
"reading_not_a_reading": "'{task}' houdt geen meterstand bij.",
"reading_multi": "'{task}' houdt meerdere meterstanden bij — vul ze in het onderhoudspaneel in.",
"reading_unclear": "Ik hoorde geen getal in '{value}'.",
"note_added": "Notitie toegevoegd aan '{task}' bij {object}.",
"note_too_long": "De notities van '{task}' zijn vol — maak ze eerst korter in het onderhoudspaneel.",
"note_empty": "Wat moet er in de notitie staan?",
"turn_user": "{user} is aan de beurt: '{task}' bij {object}.",
"turn_you": "Jij bent aan de beurt: '{task}' bij {object}.",
"turn_nobody": "Niemand is toegewezen aan '{task}' bij {object}.",
"buy_none": "Er raakt niets op — er hoeven geen onderdelen gekocht te worden.",
"buy_list": "{count} onderdelen raken op: {items}.",
"buy_list_one": "Eén onderdeel raakt op: {items}.",
"buy_item": "{part} (nog {stock})",
"bought": "{qty} × {part} toegevoegd — nu {stock} op voorraad.",
"batteries_unavailable": "Er worden geen batterijen bijgehouden.",
"batteries_none": "Geen enkele batterij is bijna leeg.",
"batteries_low": "{count} batterijen zijn bijna leeg: {items}.",
"batteries_low_one": "Eén batterij is bijna leeg: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Te kopen: {needs}.",
"undo_none": "Er is van jou niets uit de laatste {minutes} minuten om ongedaan te maken.",
"undo_changed": "'{task}' is intussen veranderd, dus ik laat het zoals het is.",
"undone_completed": "Ongedaan gemaakt: '{task}' bij {object} is niet meer afgerond.",
"undone_reading": "Ongedaan gemaakt: de meterstand voor '{task}' bij {object} is verwijderd.",
"undone_skipped": "Ongedaan gemaakt: '{task}' bij {object} is niet meer overgeslagen.",
"undone_postponed": "Ongedaan gemaakt: '{task}' bij {object} staat weer op de oude datum.",
"undone_snoozed": "Ongedaan gemaakt: de herinneringen voor '{task}' bij {object} staan weer aan.",
"undone_note": "Ongedaan gemaakt: de notitie bij '{task}' ({object}) is verwijderd.",
"undone_bought": "Ongedaan gemaakt: {qty} × {part} weer uit de voorraad gehaald."
}
@@ -13,7 +13,7 @@
"skipped": "Ten cykl zadania '{task}' na urządzeniu {object} został pominięty. Następny termin to {date}.",
"skip_disabled": "Pomijanie jest wyłączone dla '{task}'.",
"not_found": "Nie znalazłem zadania konserwacyjnego pasującego do '{name}'.",
"ambiguous": "To pasuje do kilku zadań: {candidates}. Doprecyzuj, proszę.",
"ambiguous": "To pasuje do kilku zadań: {candidates}. Powiedz, o które chodzi, na przykład „{example}”.",
"too_early": "Zadanie '{task}' można wykonać dopiero bliżej terminu.",
"st_overdue": "{days} dni po terminie",
"st_overdue_one": "jeden dzień po terminie",
@@ -43,5 +43,49 @@
"st_phase": " — następny krok: {phase}",
"st_priority": ", wysoki priorytet",
"completed_phase": "Ukończono '{task}' ({object}) — krok: {phase}. Następnym razem: {next}.",
"guide_phase": "Bieżący krok: {phase} (krok {index} z {count})."
"guide_phase": "Bieżący krok: {phase} (krok {index} z {count}).",
"part_ambiguous": "To pasuje do kilku części: {candidates}. Powiedz, o którą chodzi, na przykład „{example}”.",
"st_no_date": "bez terminu",
"st_overdue_plain": "po terminie",
"st_due_soon_plain": "wkrótce",
"months": "stycznia,lutego,marca,kwietnia,maja,czerwca,lipca,sierpnia,września,października,listopada,grudnia",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year}",
"skipped_no_date": "Pominięto ten cykl zadania '{task}' na urządzeniu {object}.",
"snoozed_one": "Przypomnienia dla zadania '{task}' na urządzeniu {object} wyciszone na godzinę.",
"guide_checklist_one": "jeden krok listy kontrolnej: {steps}",
"guide_none_plain": "Dla zadania '{task}' na urządzeniu {object} nie zapisano instrukcji, dokumentów ani części.",
"none_due_window": "W tym okresie nic nie przypada.",
"none_due_area": "W obszarze {area} nie trzeba nic konserwować.",
"unknown_area_name": "Nie znam obszaru o nazwie '{area}'.",
"reading_recorded": "Zapisano odczyt {value}{unit} dla zadania '{task}' na urządzeniu {object}.",
"reading_not_a_reading": "Zadanie '{task}' nie zapisuje odczytów.",
"reading_multi": "Zadanie '{task}' zapisuje kilka odczytów — wpisz je w panelu konserwacji.",
"reading_unclear": "Nie usłyszałem liczby w '{value}'.",
"note_added": "Dodano notatkę do zadania '{task}' na urządzeniu {object}.",
"note_too_long": "Notatki zadania '{task}' są pełne — najpierw skróć je w panelu konserwacji.",
"note_empty": "Co ma być w notatce?",
"turn_user": "Teraz kolej: {user} — '{task}' na urządzeniu {object}.",
"turn_you": "Teraz twoja kolej: '{task}' na urządzeniu {object}.",
"turn_nobody": "Nikt nie jest przypisany do zadania '{task}' na urządzeniu {object}.",
"buy_none": "Nic się nie kończy — nie trzeba kupować części.",
"buy_list": "Kończą się części, łącznie {count}: {items}.",
"buy_list_one": "Kończy się jedna część: {items}.",
"buy_item": "{part} (zostało {stock})",
"bought": "Dodano {qty} × {part} — teraz w magazynie: {stock}.",
"batteries_unavailable": "Żadne baterie nie są monitorowane.",
"batteries_none": "Żadna bateria nie jest słaba.",
"batteries_low": "Słabe baterie, łącznie {count}: {items}.",
"batteries_low_one": "Jedna bateria jest słaba: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Do kupienia: {needs}.",
"undo_none": "Z ostatnich {minutes} minut nie ma nic twojego do cofnięcia.",
"undo_changed": "Zadanie '{task}' zmieniło się w międzyczasie, więc zostawiam je bez zmian.",
"undone_completed": "Cofnięto: zadanie '{task}' na urządzeniu {object} nie jest już wykonane.",
"undone_reading": "Cofnięto: usunięto odczyt zadania '{task}' na urządzeniu {object}.",
"undone_skipped": "Cofnięto: zadanie '{task}' na urządzeniu {object} nie jest już pominięte.",
"undone_postponed": "Cofnięto: zadanie '{task}' na urządzeniu {object} wróciło na poprzedni termin.",
"undone_snoozed": "Cofnięto: przypomnienia dla zadania '{task}' na urządzeniu {object} są znowu włączone.",
"undone_note": "Cofnięto: usunięto notatkę z zadania '{task}' na urządzeniu {object}.",
"undone_bought": "Cofnięto: {qty} × {part} znów zdjęto ze stanu."
}
@@ -13,7 +13,7 @@
"skipped": "Pulei este ciclo de '{task}' em {object}. O próximo vence em {date}.",
"skip_disabled": "Pular está desativado para '{task}'.",
"not_found": "Não encontrei nenhuma tarefa de manutenção que corresponda a '{name}'.",
"ambiguous": "Isso corresponde a várias tarefas: {candidates}. Seja mais específico, por favor.",
"ambiguous": "Isso corresponde a várias tarefas: {candidates}. Diga qual, por exemplo “{example}”.",
"too_early": "'{task}' só pode ser concluída mais perto da data de vencimento.",
"st_overdue": "{days} dias de atraso",
"st_overdue_one": "um dia de atraso",
@@ -43,5 +43,49 @@
"st_phase": " — próximo passo: {phase}",
"st_priority": ", prioridade alta",
"completed_phase": "'{task}' em {object} concluído — passo: {phase}. Na próxima vez: {next}.",
"guide_phase": "Passo atual: {phase} (passo {index} de {count})."
"guide_phase": "Passo atual: {phase} (passo {index} de {count}).",
"part_ambiguous": "Isso corresponde a várias peças: {candidates}. Diga qual, por exemplo “{example}”.",
"st_no_date": "sem data",
"st_overdue_plain": "atrasada",
"st_due_soon_plain": "em breve",
"months": "janeiro,fevereiro,março,abril,maio,junho,julho,agosto,setembro,outubro,novembro,dezembro",
"date_format": "{day} de {month}",
"date_format_year": "{day} de {month} de {year}",
"skipped_no_date": "Este ciclo de '{task}' em {object} foi pulado.",
"snoozed_one": "Lembretes de '{task}' em {object} silenciados por uma hora.",
"guide_checklist_one": "uma etapa do checklist: {steps}",
"guide_none_plain": "Não há instruções, documentos nem peças salvos para '{task}' em {object}.",
"none_due_window": "Nada vence nesse período.",
"none_due_area": "Não há manutenção pendente em {area}.",
"unknown_area_name": "Não conheço nenhuma área chamada '{area}'.",
"reading_recorded": "Leitura de {value}{unit} registrada para '{task}' em {object}.",
"reading_not_a_reading": "'{task}' não registra leituras.",
"reading_multi": "'{task}' registra várias leituras — informe-as no painel de manutenção.",
"reading_unclear": "Não entendi nenhum número em '{value}'.",
"note_added": "Nota adicionada a '{task}' em {object}.",
"note_too_long": "As notas de '{task}' estão cheias — encurte-as primeiro no painel de manutenção.",
"note_empty": "O que a nota deve dizer?",
"turn_user": "É a vez de {user}: '{task}' em {object}.",
"turn_you": "É a sua vez: '{task}' em {object}.",
"turn_nobody": "Ninguém está designado para '{task}' em {object}.",
"buy_none": "Nada está acabando — não é preciso comprar peças.",
"buy_list": "{count} peças estão acabando: {items}.",
"buy_list_one": "Uma peça está acabando: {items}.",
"buy_item": "{part} (restam {stock})",
"bought": "{qty} × {part} adicionados — agora há {stock} em estoque.",
"batteries_unavailable": "Nenhuma bateria está sendo monitorada.",
"batteries_none": "Nenhuma bateria está fraca.",
"batteries_low": "{count} baterias estão fracas: {items}.",
"batteries_low_one": "Uma bateria está fraca: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Para comprar: {needs}.",
"undo_none": "Não há nada seu para desfazer dos últimos {minutes} minutos.",
"undo_changed": "'{task}' mudou desde então, então vou deixar como está.",
"undone_completed": "Desfeito: '{task}' em {object} não está mais concluída.",
"undone_reading": "Desfeito: a leitura de '{task}' em {object} foi removida.",
"undone_skipped": "Desfeito: '{task}' em {object} não está mais pulada.",
"undone_postponed": "Desfeito: '{task}' em {object} voltou para a data anterior.",
"undone_snoozed": "Desfeito: os lembretes de '{task}' em {object} estão ativos de novo.",
"undone_note": "Desfeito: a nota de '{task}' em {object} foi removida.",
"undone_bought": "Desfeito: {qty} × {part} retirados do estoque."
}
@@ -13,7 +13,7 @@
"skipped": "Saltei este ciclo de '{task}' em {object}. O próximo está previsto para {date}.",
"skip_disabled": "Ignorar está desativado para '{task}'.",
"not_found": "Não encontrei nenhuma tarefa de manutenção que corresponda a '{name}'.",
"ambiguous": "Isso corresponde a várias tarefas: {candidates}. Podes ser um pouco mais específico?",
"ambiguous": "Isso corresponde a várias tarefas: {candidates}. Diz qual, por exemplo «{example}».",
"too_early": "Só podes concluir '{task}' mais perto da data prevista.",
"st_overdue": "{days} dias de atraso",
"st_overdue_one": "um dia de atraso",
@@ -43,5 +43,49 @@
"st_phase": " — próximo passo: {phase}",
"st_priority": ", prioridade alta",
"completed_phase": "'{task}' em {object} concluído — passo: {phase}. Da próxima vez: {next}.",
"guide_phase": "Passo atual: {phase} (passo {index} de {count})."
"guide_phase": "Passo atual: {phase} (passo {index} de {count}).",
"part_ambiguous": "Isso corresponde a várias peças: {candidates}. Diz qual, por exemplo «{example}».",
"st_no_date": "sem data",
"st_overdue_plain": "em atraso",
"st_due_soon_plain": "em breve",
"months": "janeiro,fevereiro,março,abril,maio,junho,julho,agosto,setembro,outubro,novembro,dezembro",
"date_format": "{day} de {month}",
"date_format_year": "{day} de {month} de {year}",
"skipped_no_date": "Este ciclo de '{task}' em {object} foi saltado.",
"snoozed_one": "Lembretes de '{task}' em {object} silenciados durante uma hora.",
"guide_checklist_one": "um passo da lista: {steps}",
"guide_none_plain": "Não há instruções, documentos nem peças guardados para '{task}' em {object}.",
"none_due_window": "Nada vence nesse período.",
"none_due_area": "Não há manutenção a fazer em {area}.",
"unknown_area_name": "Não conheço nenhuma divisão chamada '{area}'.",
"reading_recorded": "Leitura de {value}{unit} registada para '{task}' em {object}.",
"reading_not_a_reading": "'{task}' não regista leituras.",
"reading_multi": "'{task}' regista várias leituras — introduz-as no painel de manutenção.",
"reading_unclear": "Não percebi nenhum número em '{value}'.",
"note_added": "Nota adicionada a '{task}' em {object}.",
"note_too_long": "As notas de '{task}' estão cheias — encurta-as primeiro no painel de manutenção.",
"note_empty": "O que deve dizer a nota?",
"turn_user": "É a vez de {user}: '{task}' em {object}.",
"turn_you": "É a tua vez: '{task}' em {object}.",
"turn_nobody": "Ninguém está atribuído a '{task}' em {object}.",
"buy_none": "Nada está a acabar — não é preciso comprar peças.",
"buy_list": "Estão a acabar {count} peças: {items}.",
"buy_list_one": "Está a acabar uma peça: {items}.",
"buy_item": "{part} (restam {stock})",
"bought": "Adicionados {qty} × {part} — agora há {stock} em stock.",
"batteries_unavailable": "Nenhuma bateria está a ser acompanhada.",
"batteries_none": "Nenhuma bateria está fraca.",
"batteries_low": "{count} baterias estão fracas: {items}.",
"batteries_low_one": "Uma bateria está fraca: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Para comprar: {needs}.",
"undo_none": "Não há nada teu para anular dos últimos {minutes} minutos.",
"undo_changed": "'{task}' mudou entretanto, por isso deixo como está.",
"undone_completed": "Anulado: '{task}' em {object} já não está concluída.",
"undone_reading": "Anulado: a leitura de '{task}' em {object} foi removida.",
"undone_skipped": "Anulado: '{task}' em {object} já não está saltada.",
"undone_postponed": "Anulado: '{task}' em {object} voltou à data anterior.",
"undone_snoozed": "Anulado: os lembretes de '{task}' em {object} voltaram a estar ativos.",
"undone_note": "Anulado: a nota de '{task}' em {object} foi removida.",
"undone_bought": "Anulado: {qty} × {part} retirados do stock."
}
@@ -13,7 +13,7 @@
"skipped": "Этот цикл задачи '{task}' у устройства {object} пропущен. Следующий срок — {date}.",
"skip_disabled": "Пропуск отключён для '{task}'.",
"not_found": "Я не нашёл задачу обслуживания, подходящую под '{name}'.",
"ambiguous": "Под это подходит несколько задач: {candidates}. Уточни, пожалуйста.",
"ambiguous": "Под это подходит несколько задач: {candidates}. Скажи, какую именно, например: «{example}».",
"too_early": "Задачу '{task}' можно выполнить только ближе к сроку.",
"st_overdue": "просрочка {days} дней",
"st_overdue_one": "просрочка один день",
@@ -43,5 +43,53 @@
"st_phase": " — следующий шаг: {phase}",
"st_priority": ", высокий приоритет",
"completed_phase": "«{task}» ({object}) выполнено — шаг: {phase}. В следующий раз: {next}.",
"guide_phase": "Текущий шаг: {phase} (шаг {index} из {count})."
"guide_phase": "Текущий шаг: {phase} (шаг {index} из {count}).",
"part_ambiguous": "Под это подходит несколько запчастей: {candidates}. Скажи, какую именно, например: «{example}».",
"st_no_date": "без срока",
"st_overdue_plain": "просрочено",
"st_due_soon_plain": "скоро срок",
"months": "января,февраля,марта,апреля,мая,июня,июля,августа,сентября,октября,ноября,декабря",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year} г.",
"skipped_no_date": "Этот цикл задачи '{task}' у устройства {object} пропущен.",
"snoozed_one": "Напоминания по задаче '{task}' у устройства {object} не будут приходить в течение часа.",
"guide_checklist_one": "один шаг контрольного списка: {steps}",
"guide_none_plain": "Для задачи '{task}' у устройства {object} нет сохранённых инструкций, документов или запчастей.",
"none_due_window": "На этот период ничего не запланировано.",
"none_due_area": "В зоне {area} обслуживание не требуется.",
"unknown_area_name": "Я не знаю зону с названием '{area}'.",
"reading_recorded": "Показание {value}{unit} записано для задачи '{task}' у устройства {object}.",
"reading_not_a_reading": "Задача '{task}' не записывает показания.",
"reading_multi": "Задача '{task}' записывает несколько показаний — введи их в панели обслуживания.",
"reading_unclear": "Я не расслышал число в '{value}'.",
"note_added": "Заметка добавлена к задаче '{task}' у устройства {object}.",
"note_too_long": "Заметки задачи '{task}' заполнены — сначала сократи их в панели обслуживания.",
"note_empty": "Что написать в заметке?",
"turn_user": "Сейчас очередь: {user} — '{task}' у устройства {object}.",
"turn_you": "Сейчас твоя очередь: '{task}' у устройства {object}.",
"turn_nobody": "За задачу '{task}' у устройства {object} никто не отвечает.",
"buy_none": "Ничего не заканчивается — покупать запчасти не нужно.",
"buy_list": "Заканчиваются запчасти, всего {count}: {items}.",
"buy_list_one": "Заканчивается одна запчасть: {items}.",
"buy_item": "{part} (осталось {stock})",
"bought": "Добавлено {qty} × {part} — теперь на складе {stock}.",
"batteries_unavailable": "Батареи не отслеживаются.",
"batteries_none": "Разряженных батарей нет.",
"batteries_low": "Разряжены батареи, всего {count}: {items}.",
"batteries_low_one": "Разряжена одна батарея: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Купить: {needs}.",
"undo_none": "За последние {minutes} минут от тебя нечего отменять.",
"undo_changed": "Задача '{task}' с тех пор изменилась, поэтому я её не трогаю.",
"undone_completed": "Отменено: задача '{task}' у устройства {object} больше не выполнена.",
"undone_reading": "Отменено: показание для задачи '{task}' у устройства {object} удалено.",
"undone_skipped": "Отменено: задача '{task}' у устройства {object} больше не пропущена.",
"undone_postponed": "Отменено: задача '{task}' у устройства {object} снова на прежней дате.",
"undone_snoozed": "Отменено: напоминания по задаче '{task}' у устройства {object} снова включены.",
"undone_note": "Отменено: заметка к задаче '{task}' у устройства {object} удалена.",
"undone_bought": "Отменено: {qty} × {part} снова убрано со склада.",
"st_overdue_few": "просрочка {days} дня",
"st_overdue_one_n": "просрочка {days} день",
"st_due_in_one_n": "срок в течение {days} дня",
"snoozed_one_n": "Напоминания по задаче '{task}' у устройства {object} не будут приходить в течение {hours} часа."
}
@@ -13,7 +13,7 @@
"skipped": "Jag har hoppat över den här omgången av '{task}' på {object}. Nästa omgång ska göras den {date}.",
"skip_disabled": "Hoppa över är avstängt för '{task}'.",
"not_found": "Jag hittade ingen underhållsuppgift som matchar '{name}'.",
"ambiguous": "Det matchar flera uppgifter: {candidates}. Var lite mer specifik.",
"ambiguous": "Det matchar flera uppgifter: {candidates}. Säg vilken, till exempel ”{example}”.",
"too_early": "'{task}' går att bocka av först närmare förfallodatumet.",
"st_overdue": "{days} dagar för sent",
"st_overdue_one": "en dag för sent",
@@ -43,5 +43,49 @@
"st_phase": " — nästa steg: {phase}",
"st_priority": ", hög prioritet",
"completed_phase": "'{task}' på {object} slutfört — steg: {phase}. Nästa gång: {next}.",
"guide_phase": "Aktuellt steg: {phase} (steg {index} av {count})."
"guide_phase": "Aktuellt steg: {phase} (steg {index} av {count}).",
"part_ambiguous": "Det matchar flera reservdelar: {candidates}. Säg vilken, till exempel ”{example}”.",
"st_no_date": "utan förfallodatum",
"st_overdue_plain": "försenad",
"st_due_soon_plain": "snart",
"months": "januari,februari,mars,april,maj,juni,juli,augusti,september,oktober,november,december",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year}",
"skipped_no_date": "Den här omgången av '{task}' på {object} hoppades över.",
"snoozed_one": "Påminnelser för '{task}' på {object} är pausade i en timme.",
"guide_checklist_one": "ett checklistesteg: {steps}",
"guide_none_plain": "Det finns inga sparade instruktioner, dokument eller reservdelar för '{task}' på {object}.",
"none_due_window": "Inget förfaller under den perioden.",
"none_due_area": "Inget behöver underhållas i {area}.",
"unknown_area_name": "Jag känner inte till något område som heter '{area}'.",
"reading_recorded": "Avläsning {value}{unit} registrerad för '{task}' på {object}.",
"reading_not_a_reading": "'{task}' registrerar ingen avläsning.",
"reading_multi": "'{task}' registrerar flera avläsningar — fyll i dem i underhållspanelen.",
"reading_unclear": "Jag uppfattade inget tal i '{value}'.",
"note_added": "Anteckningen har lagts till på '{task}' på {object}.",
"note_too_long": "Anteckningarna för '{task}' är fulla — korta ner dem i underhållspanelen först.",
"note_empty": "Vad ska det stå i anteckningen?",
"turn_user": "{user} har turen: '{task}' på {object}.",
"turn_you": "Det är din tur: '{task}' på {object}.",
"turn_nobody": "Ingen är tilldelad '{task}' på {object}.",
"buy_none": "Inget håller på att ta slut — inga reservdelar behöver köpas.",
"buy_list": "{count} reservdelar håller på att ta slut: {items}.",
"buy_list_one": "En reservdel håller på att ta slut: {items}.",
"buy_item": "{part} ({stock} kvar)",
"bought": "{qty} × {part} inlagda i lagret — nu {stock} i lager.",
"batteries_unavailable": "Inga batterier övervakas.",
"batteries_none": "Inga batterier är svaga.",
"batteries_low": "{count} batterier är svaga: {items}.",
"batteries_low_one": "Ett batteri är svagt: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Att köpa: {needs}.",
"undo_none": "Det finns inget från dig att ångra från de senaste {minutes} minuterna.",
"undo_changed": "'{task}' har ändrats sedan dess, så jag låter den vara.",
"undone_completed": "Ångrat: '{task}' på {object} är inte längre utförd.",
"undone_reading": "Ångrat: avläsningen för '{task}' på {object} är borttagen.",
"undone_skipped": "Ångrat: '{task}' på {object} är inte längre överhoppad.",
"undone_postponed": "Ångrat: '{task}' på {object} har sitt gamla datum igen.",
"undone_snoozed": "Ångrat: påminnelserna för '{task}' på {object} är på igen.",
"undone_note": "Ångrat: anteckningen på '{task}' ({object}) är borttagen.",
"undone_bought": "Ångrat: {qty} × {part} har tagits ur lagret igen."
}
@@ -13,7 +13,7 @@
"skipped": "{object} için '{task}' görevinin bu turu atlandı. Sıradaki {date} tarihinde.",
"skip_disabled": "'{task}' için atlama kapalı.",
"not_found": "'{name}' ile eşleşen bir bakım görevi bulamadım.",
"ambiguous": "Birden fazla göreve uyuyor: {candidates}. Biraz daha açık söyler misin?",
"ambiguous": "Birden fazla göreve uyuyor: {candidates}. Hangisi olduğunu söyle, örneğin “{example}”.",
"too_early": "'{task}' görevi ancak son tarihine yaklaşınca tamamlanabilir.",
"st_overdue": "{days} gün gecikmiş",
"st_overdue_one": "bir gün gecikmiş",
@@ -43,5 +43,49 @@
"st_phase": " — sonraki adım: {phase}",
"st_priority": ", yüksek öncelik",
"completed_phase": "{object} üzerindeki '{task}' tamamlandı — adım: {phase}. Bir dahaki sefere: {next}.",
"guide_phase": "Geçerli adım: {phase} ({count} adımdan {index}.)."
"guide_phase": "Geçerli adım: {phase} ({count} adımdan {index}.).",
"part_ambiguous": "Birden fazla yedek parçaya uyuyor: {candidates}. Hangisi olduğunu söyle, örneğin “{example}”.",
"st_no_date": "tarihi yok",
"st_overdue_plain": "gecikmiş",
"st_due_soon_plain": "yakında",
"months": "Ocak,Şubat,Mart,Nisan,Mayıs,Haziran,Temmuz,Ağustos,Eylül,Ekim,Kasım,Aralık",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year}",
"skipped_no_date": "{object} için '{task}' görevinin bu turu atlandı.",
"snoozed_one": "{object} için '{task}' görevinin hatırlatmaları bir saat susturuldu.",
"guide_checklist_one": "bir kontrol listesi adımı: {steps}",
"guide_none_plain": "{object} için '{task}' görevine ait kayıtlı bir talimat, belge ya da yedek parça yok.",
"none_due_window": "O süre içinde yapılacak bir şey yok.",
"none_due_area": "{area} alanında bakım gerekmiyor.",
"unknown_area_name": "'{area}' adında bir alan bilmiyorum.",
"reading_recorded": "{object} için '{task}' görevine {value}{unit} okuması kaydedildi.",
"reading_not_a_reading": "'{task}' görevi okuma kaydetmiyor.",
"reading_multi": "'{task}' görevi birden fazla okuma kaydediyor — bunları bakım panelinden gir.",
"reading_unclear": "'{value}' içinde bir sayı duyamadım.",
"note_added": "{object} için '{task}' görevine not eklendi.",
"note_too_long": "'{task}' görevinin notları dolu — önce bakım panelinden kısalt.",
"note_empty": "Notta ne yazsın?",
"turn_user": "Sıradaki kişi: {user} — {object} için '{task}'.",
"turn_you": "Sıra sende: {object} için '{task}'.",
"turn_nobody": "{object} için '{task}' görevine kimse atanmamış.",
"buy_none": "Tükenmek üzere olan bir şey yok — yedek parça almaya gerek yok.",
"buy_list": "{count} yedek parça tükenmek üzere: {items}.",
"buy_list_one": "Bir yedek parça tükenmek üzere: {items}.",
"buy_item": "{part} ({stock} kaldı)",
"bought": "{qty} × {part} stoğa eklendi — şimdi stokta {stock} var.",
"batteries_unavailable": "Takip edilen pil yok.",
"batteries_none": "Zayıf pil yok.",
"batteries_low": "{count} pil zayıf: {items}.",
"batteries_low_one": "Bir pil zayıf: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Alınacaklar: {needs}.",
"undo_none": "Son {minutes} dakikada senin geri alınacak bir işlemin yok.",
"undo_changed": "'{task}' o zamandan beri değişti, olduğu gibi bırakıyorum.",
"undone_completed": "Geri alındı: {object} için '{task}' artık tamamlanmış değil.",
"undone_reading": "Geri alındı: {object} için '{task}' okuması silindi.",
"undone_skipped": "Geri alındı: {object} için '{task}' artık atlanmış değil.",
"undone_postponed": "Geri alındı: {object} için '{task}' eski tarihine döndü.",
"undone_snoozed": "Geri alındı: {object} için '{task}' hatırlatmaları yeniden açık.",
"undone_note": "Geri alındı: {object} için '{task}' notu silindi.",
"undone_bought": "Geri alındı: {qty} × {part} stoktan yeniden düşüldü."
}
@@ -13,7 +13,7 @@
"skipped": "Цей цикл завдання '{task}' на пристрої {object} пропущено. Наступний термін — {date}.",
"skip_disabled": "Пропуск вимкнено для '{task}'.",
"not_found": "Я не знайшов завдання з обслуговування, яке відповідає '{name}'.",
"ambiguous": "Цьому відповідає кілька завдань: {candidates}. Уточни, будь ласка.",
"ambiguous": "Цьому відповідає кілька завдань: {candidates}. Скажи, яке саме, наприклад: «{example}».",
"too_early": "Завдання '{task}' можна виконати лише ближче до терміну.",
"st_overdue": "прострочення {days} днів",
"st_overdue_one": "прострочення один день",
@@ -43,5 +43,53 @@
"st_phase": " — наступний крок: {phase}",
"st_priority": ", високий пріоритет",
"completed_phase": "«{task}» ({object}) виконано — крок: {phase}. Наступного разу: {next}.",
"guide_phase": "Поточний крок: {phase} (крок {index} з {count})."
"guide_phase": "Поточний крок: {phase} (крок {index} з {count}).",
"part_ambiguous": "Цьому відповідає кілька запчастин: {candidates}. Скажи, яку саме, наприклад: «{example}».",
"st_no_date": "без терміну",
"st_overdue_plain": "прострочено",
"st_due_soon_plain": "термін скоро",
"months": "січня,лютого,березня,квітня,травня,червня,липня,серпня,вересня,жовтня,листопада,грудня",
"date_format": "{day} {month}",
"date_format_year": "{day} {month} {year} р.",
"skipped_no_date": "Цей цикл завдання '{task}' на пристрої {object} пропущено.",
"snoozed_one": "Нагадування щодо завдання '{task}' на пристрої {object} не надходитимуть протягом години.",
"guide_checklist_one": "один крок контрольного списку: {steps}",
"guide_none_plain": "Для завдання '{task}' на пристрої {object} немає збережених інструкцій, документів чи запчастин.",
"none_due_window": "На цей період нічого не заплановано.",
"none_due_area": "У зоні {area} обслуговування не потрібне.",
"unknown_area_name": "Я не знаю зони з назвою '{area}'.",
"reading_recorded": "Показник {value}{unit} записано для завдання '{task}' на пристрої {object}.",
"reading_not_a_reading": "Завдання '{task}' не записує показників.",
"reading_multi": "Завдання '{task}' записує кілька показників — введи їх у панелі обслуговування.",
"reading_unclear": "Я не почув числа в '{value}'.",
"note_added": "Нотатку додано до завдання '{task}' на пристрої {object}.",
"note_too_long": "Нотатки завдання '{task}' заповнені — спершу скороти їх у панелі обслуговування.",
"note_empty": "Що написати в нотатці?",
"turn_user": "Зараз черга: {user} — '{task}' на пристрої {object}.",
"turn_you": "Зараз твоя черга: '{task}' на пристрої {object}.",
"turn_nobody": "За завдання '{task}' на пристрої {object} ніхто не відповідає.",
"buy_none": "Нічого не закінчується — купувати запчастини не потрібно.",
"buy_list": "Закінчуються запчастини, усього {count}: {items}.",
"buy_list_one": "Закінчується одна запчастина: {items}.",
"buy_item": "{part} (залишилося {stock})",
"bought": "Додано {qty} × {part} — тепер на складі {stock}.",
"batteries_unavailable": "Батареї не відстежуються.",
"batteries_none": "Розряджених батарей немає.",
"batteries_low": "Розряджені батареї, усього {count}: {items}.",
"batteries_low_one": "Розряджена одна батарея: {items}.",
"battery_item": "{device} ({type})",
"batteries_buy": " Купити: {needs}.",
"undo_none": "За останні {minutes} хвилин від тебе нічого скасовувати.",
"undo_changed": "Завдання '{task}' відтоді змінилося, тож я його не чіпаю.",
"undone_completed": "Скасовано: завдання '{task}' на пристрої {object} більше не виконане.",
"undone_reading": "Скасовано: показник для завдання '{task}' на пристрої {object} видалено.",
"undone_skipped": "Скасовано: завдання '{task}' на пристрої {object} більше не пропущене.",
"undone_postponed": "Скасовано: завдання '{task}' на пристрої {object} знову на попередній даті.",
"undone_snoozed": "Скасовано: нагадування щодо завдання '{task}' на пристрої {object} знову ввімкнені.",
"undone_note": "Скасовано: нотатку до завдання '{task}' на пристрої {object} видалено.",
"undone_bought": "Скасовано: {qty} × {part} знову знято зі складу.",
"st_overdue_few": "прострочення {days} дні",
"st_overdue_one_n": "прострочення {days} день",
"st_due_in_one_n": "термін протягом {days} дня",
"snoozed_one_n": "Нагадування щодо завдання '{task}' на пристрої {object} не надходитимуть протягом {hours} години."
}
@@ -13,7 +13,7 @@
"skipped": "已跳过{object}的“{task}”这一次。下一次在{date}到期。",
"skip_disabled": "已为'{task}'关闭跳过。",
"not_found": "我没有找到和“{name}”对得上的维护任务。",
"ambiguous": "有好几项任务都对得上:{candidates}。请说得更具体一些。",
"ambiguous": "有好几项任务都对得上:{candidates}。请说是哪一项,例如“{example}”。",
"too_early": "“{task}”要等到接近到期日才能完成。",
"st_overdue": "已逾期{days}天",
"st_overdue_one": "已逾期一天",
@@ -43,5 +43,49 @@
"st_phase": "——下一步:{phase}",
"st_priority": ",高优先级",
"completed_phase": "已完成{object}的“{task}”——步骤:{phase}。下次:{next}。",
"guide_phase": "当前步骤:{phase}(第 {index} 步,共 {count} 步)。"
"guide_phase": "当前步骤:{phase}(第 {index} 步,共 {count} 步)。",
"part_ambiguous": "有好几个备件都对得上:{candidates}。请说是哪一个,例如“{example}”。",
"st_no_date": "没有到期日",
"st_overdue_plain": "已逾期",
"st_due_soon_plain": "即将到期",
"months": "1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月",
"date_format": "{month}{day}日",
"date_format_year": "{year}年{month}{day}日",
"skipped_no_date": "已跳过{object}的“{task}”这一次。",
"snoozed_one": "已把{object}的“{task}”提醒暂停1小时。",
"guide_checklist_one": "1个清单步骤:{steps}",
"guide_none_plain": "{object}的“{task}”没有保存任何操作说明、文档或备件。",
"none_due_window": "这段时间内没有到期的任务。",
"none_due_area": "{area}没有需要处理的维护。",
"unknown_area_name": "我不知道有叫“{area}”的区域。",
"reading_recorded": "已为{object}的“{task}”记录读数{value}{unit}。",
"reading_not_a_reading": "“{task}”不记录读数。",
"reading_multi": "“{task}”要记录多个读数,请在维护面板里填写。",
"reading_unclear": "我没从“{value}”里听出数字。",
"note_added": "已给{object}的“{task}”添加备注。",
"note_too_long": "“{task}”的备注已满,请先在维护面板里删减。",
"note_empty": "备注要写什么?",
"turn_user": "现在轮到{user}:{object}的“{task}”。",
"turn_you": "现在轮到你:{object}的“{task}”。",
"turn_nobody": "{object}的“{task}”没有指派任何人。",
"buy_none": "没有快用完的东西,不需要买备件。",
"buy_list": "有{count}个备件快用完了:{items}。",
"buy_list_one": "有1个备件快用完了:{items}。",
"buy_item": "{part}(剩{stock})",
"bought": "已入库{qty} × {part},现在库存{stock}。",
"batteries_unavailable": "没有在监控任何电池。",
"batteries_none": "没有电量低的电池。",
"batteries_low": "有{count}个电池电量低:{items}。",
"batteries_low_one": "有1个电池电量低:{items}。",
"battery_item": "{device}({type})",
"batteries_buy": " 需要购买:{needs}。",
"undo_none": "最近{minutes}分钟内没有你可以撤销的操作。",
"undo_changed": "“{task}”之后已经变了,我就不动它了。",
"undone_completed": "已撤销:{object}的“{task}”不再是已完成。",
"undone_reading": "已撤销:已删除{object}的“{task}”读数。",
"undone_skipped": "已撤销:{object}的“{task}”不再是已跳过。",
"undone_postponed": "已撤销:{object}的“{task}”回到原来的日期。",
"undone_snoozed": "已撤销:{object}的“{task}”提醒已重新开启。",
"undone_note": "已撤销:已删除{object}的“{task}”备注。",
"undone_bought": "已撤销:已把{qty} × {part}从库存中扣回。"
}
@@ -34,6 +34,7 @@ from .const import (
from .coordinator import MaintenanceCoordinator
from .entity.entity_base import MaintenanceEntity
from .helpers.aggregate import object_slug as aggregate_object_slug
from .helpers.coordinator_data import has_coordinator_data
from .helpers.status import compute_status_from_task_dict
if TYPE_CHECKING:
@@ -148,7 +149,7 @@ class MaintenanceBinarySensor(MaintenanceEntity, BinarySensorEntity):
Even if coordinator data is not yet refreshed, clearing _trigger_active
and recomputing status here ensures correctness regardless of ordering.
"""
if self.coordinator.data is None:
if not has_coordinator_data(self.coordinator):
return
tasks = self.coordinator.data.get(CONF_TASKS, {})
@@ -591,10 +591,31 @@ class MaintenanceCalendar(CalendarEntity):
self._cached_next_event: CalendarEvent | None = None
self._cache_time: datetime | None = None
_removed = False
def invalidate_cache(self) -> None:
"""Invalidate the cached next event (called by coordinators on update)."""
self._cache_time = None
@property
def is_live(self) -> bool:
"""Added to Home Assistant and not removed since."""
return self.hass is not None and not self._removed
async def async_will_remove_from_hass(self) -> None:
"""Stop the coordinators repainting a removed calendar.
Home Assistant's calendar arms an alarm for the end of the current
event on every state write and cancels them here. A coordinator
refresh between a reload's unload and set-up still wrote the old
entity — arming an alarm nobody cancelled any more (a timer left
behind by every reload of the global entry; bug audit 2026-09-29).
"""
self._removed = True
await super().async_will_remove_from_hass()
if self._hass.data.get(DOMAIN, {}).get("_calendar_entity") is self:
self._hass.data[DOMAIN].pop("_calendar_entity", None)
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event (cached for up to 1 hour)."""
@@ -697,6 +718,9 @@ class MaintenanceCalendar(CalendarEntity):
# Archived task → no calendar entries (inert).
if task.archived_at is not None:
continue
# #193: a paused task's schedule is frozen, like a paused object's.
if task.paused_at is not None:
continue
# Inject live trigger state from coordinator
live = live_tasks.get(task_id, {})
@@ -535,6 +535,11 @@ class MaintenanceSupporterConfigFlow(ScheduleStepsMixin, TriggerConfigMixin, Con
part = normalize_part({**praw, "id": pid})
except (PartValidationError, TypeError):
continue
# A backup's stock rides along; the entry's first setup moves
# it into the Store (storage.async_migrate_to_store).
stock = praw.get("stock") if isinstance(praw, dict) else None
if isinstance(stock, (int, float)) and not isinstance(stock, bool) and stock >= 0:
part["stock"] = stock
parts[part["id"]] = part
data: dict[str, Any] = {
@@ -704,6 +709,7 @@ class MaintenanceSupporterConfigFlow(ScheduleStepsMixin, TriggerConfigMixin, Con
counter_step=self.async_step_trigger_counter,
state_change_step=self.async_step_trigger_state_change,
runtime_step=self.async_step_trigger_runtime,
due_date_step=self.async_step_trigger_due_date,
compound_step=self.async_step_compound_logic,
)
@@ -743,6 +749,15 @@ class MaintenanceSupporterConfigFlow(ScheduleStepsMixin, TriggerConfigMixin, Con
on_complete=self._save_task_and_return,
)
async def async_step_trigger_due_date(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Configure due-date trigger."""
self._on_cancel = lambda: self.async_step_trigger_type()
return await self._trigger_due_date_config(
user_input,
step_id="trigger_due_date",
on_complete=self._save_task_and_return,
)
# --- Compound Trigger Steps ---
async def async_step_compound_logic(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
@@ -776,6 +791,7 @@ class MaintenanceSupporterConfigFlow(ScheduleStepsMixin, TriggerConfigMixin, Con
counter_step=self.async_step_compound_condition_counter,
state_change_step=self.async_step_compound_condition_state_change,
runtime_step=self.async_step_compound_condition_runtime,
due_date_step=self.async_step_compound_condition_due_date,
)
async def async_step_compound_condition_threshold(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
@@ -818,6 +834,16 @@ class MaintenanceSupporterConfigFlow(ScheduleStepsMixin, TriggerConfigMixin, Con
on_complete=self.async_step_compound_review,
)
async def async_step_compound_condition_due_date(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Configure due_date for compound condition."""
self._on_cancel = lambda: self.async_step_compound_condition_type()
return await self._trigger_compound_condition_config(
user_input,
"due_date",
step_id="compound_condition_due_date",
on_complete=self.async_step_compound_review,
)
async def async_step_compound_review(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Review compound trigger conditions."""
self._on_cancel = lambda: self.async_step_compound_logic()
@@ -260,13 +260,17 @@ def season_ends_schema(current_schedule: dict[str, Any] | None) -> dict[Any, Any
count = ends.get("count")
until = ends.get("until")
# Suggested, not default: an emptied field is left out of the submission
# and a default re-inserted the stored end, so a series could never be
# made endless again here (audit 2026-09-29). apply_season_ends reads
# absence as "no end".
count_key = (
vol.Optional(CONF_TASK_ENDS_COUNT, default=int(count))
vol.Optional(CONF_TASK_ENDS_COUNT, description={"suggested_value": int(count)})
if isinstance(count, int) and count >= 1
else vol.Optional(CONF_TASK_ENDS_COUNT)
)
until_key = (
vol.Optional(CONF_TASK_ENDS_UNTIL, default=str(until))
vol.Optional(CONF_TASK_ENDS_UNTIL, description={"suggested_value": str(until)})
if isinstance(until, str) and until
else vol.Optional(CONF_TASK_ENDS_UNTIL)
)
@@ -29,7 +29,6 @@ from .const import (
CONF_ADVANCED_COMPLETION_ACTIONS,
CONF_ADVANCED_ENVIRONMENTAL,
CONF_ADVANCED_GROUPS,
CONF_ADVANCED_PREDICTIONS,
CONF_ADVANCED_SCHEDULE_TIME,
CONF_ADVANCED_SEASONAL,
CONF_ARCHIVE_ONEOFF_DAYS,
@@ -72,6 +71,7 @@ from .const import (
CONF_PANEL_ENABLED,
CONF_PANEL_TITLE,
CONF_PART_SEARCH_URL_TEMPLATE,
CONF_PARTS_COST_MODE,
CONF_QUIET_HOURS_ENABLED,
CONF_QUIET_HOURS_END,
CONF_QUIET_HOURS_START,
@@ -91,6 +91,7 @@ from .const import (
MAX_PANEL_TITLE_LENGTH,
MAX_REMINDER_LEADS,
NOTIFY_COMPLETED_MODES,
PARTS_COST_MODES,
ROW_ACTION_STYLES,
TIME_HHMMSS_PATTERN,
)
@@ -742,10 +743,6 @@ class GlobalOptionsFlow(OptionsFlow):
CONF_ADVANCED_ADAPTIVE,
default=self._opt(CONF_ADVANCED_ADAPTIVE),
): selector.BooleanSelector(),
vol.Optional(
CONF_ADVANCED_PREDICTIONS,
default=self._opt(CONF_ADVANCED_PREDICTIONS),
): selector.BooleanSelector(),
vol.Optional(
CONF_ADVANCED_SEASONAL,
default=self._opt(CONF_ADVANCED_SEASONAL),
@@ -931,6 +928,18 @@ class GlobalOptionsFlow(OptionsFlow):
mode=selector.NumberSelectorMode.BOX,
)
),
# #104: spare parts count when bought or when used — every cost
# total follows it, not just the budget.
vol.Optional(
CONF_PARTS_COST_MODE,
default=self._opt(CONF_PARTS_COST_MODE),
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=list(PARTS_COST_MODES),
mode=selector.SelectSelectorMode.DROPDOWN,
translation_key="parts_cost_mode",
)
),
# #146 / #180: the battery fleet's low floor and the level a
# low battery must rise above to count as replaced.
vol.Optional(
@@ -126,7 +126,11 @@ class AdaptiveMixin:
"""Build the adaptive scheduling form schema."""
env_entity = current_adaptive.get("environmental_entity")
env_key = (
vol.Optional(CONF_ENVIRONMENTAL_ENTITY, default=env_entity) if env_entity else vol.Optional(CONF_ENVIRONMENTAL_ENTITY)
# suggested, not default — a cleared picker is left out of the
# submission and the handler reads absence as "no sensor"
vol.Optional(CONF_ENVIRONMENTAL_ENTITY, description={"suggested_value": env_entity})
if env_entity
else vol.Optional(CONF_ENVIRONMENTAL_ENTITY)
)
return vol.Schema(
{
@@ -231,7 +231,17 @@ class TaskCrudMixin:
updated_task["schedule"] = {"kind": "interval", **extras}
else:
updated_task.pop("schedule", None)
# schedule_time only present when global advanced flag is on; clear by submitting "".
# schedule_time only present when global advanced flag is on. The
# form carries the stored time as a SUGGESTED value, and the
# frontend leaves a cleared time field out of the submission —
# absence of an offered field therefore clears (audit 2026-09-29:
# with default=<stored> the time could never be removed here).
time_offered = (
is_schedule_time_enabled(self.hass)
and read_legacy_fields(task)["schedule_type"] in SCHEDULE_TIME_KINDS
)
if CONF_TASK_SCHEDULE_TIME not in user_input and time_offered:
updated_task.pop("schedule_time", None)
if CONF_TASK_SCHEDULE_TIME in user_input:
# HA's TimeSelector serialises "HH:MM:SS"; stored form is
# the canonical "HH:MM" (helpers.dates.normalize_hhmm).
@@ -314,12 +324,12 @@ class TaskCrudMixin:
updated_task.pop(CONF_TASK_READING_UNIT, None)
# #161 phase 2: reading slots, one "Name | Unit" per line. Ids
# are kept for lines whose name already exists (delta chain).
if "readings_text" in user_input:
slots = parse_reading_slots_text(user_input.get("readings_text") or "", task.get("readings"))
if slots:
updated_task["readings"] = slots
else:
updated_task.pop("readings", None)
# Absent = the textarea was emptied (suggested value, see the form).
slots = parse_reading_slots_text(user_input.get("readings_text") or "", task.get("readings"))
if slots:
updated_task["readings"] = slots
else:
updated_task.pop("readings", None)
from .helpers.sanitize import cap_task_fields
@@ -409,7 +419,10 @@ class TaskCrudMixin:
ecd_key = (
vol.Optional("earliest_completion_days")
if ecd_stored is None
else vol.Optional("earliest_completion_days", default=ecd_stored)
# suggested, not default: an emptied number field is left out of
# the submission and a default re-inserted the stored window
# (audit 2026-09-29) — the handler reads absence as "no window".
else vol.Optional("earliest_completion_days", description={"suggested_value": ecd_stored})
)
# Prefill the recurrence from whichever storage shape this task uses
@@ -479,9 +492,10 @@ class TaskCrudMixin:
# while the global advanced flag is on; clear by submitting "".
**(
{
vol.Optional(
CONF_TASK_SCHEDULE_TIME,
default=task.get("schedule_time", ""),
(
vol.Optional(CONF_TASK_SCHEDULE_TIME, description={"suggested_value": task["schedule_time"]})
if task.get("schedule_time")
else vol.Optional(CONF_TASK_SCHEDULE_TIME)
): _OptionalTimeSelector(),
}
if is_schedule_time_enabled(self.hass)
@@ -552,7 +566,7 @@ class TaskCrudMixin:
nfc_tag_key: selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)),
vol.Optional("require_tag_scan", default=bool(task.get("require_tag_scan"))): selector.BooleanSelector(),
reading_unit_key: selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)),
vol.Optional("readings_text", default=reading_slots_text(task.get("readings"))): selector.TextSelector(
_suggested("readings_text", reading_slots_text(task.get("readings"))): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiline=True)
),
vol.Optional("go_back", default=False): selector.BooleanSelector(),
@@ -596,7 +610,11 @@ class TaskCrudMixin:
step_id="edit_checklist",
data_schema=vol.Schema(
{
vol.Optional("checklist_text", default=default_text): selector.TextSelector(
(
vol.Optional("checklist_text", description={"suggested_value": default_text})
if default_text
else vol.Optional("checklist_text")
): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.TEXT,
multiline=True,
@@ -663,7 +681,11 @@ class TaskCrudMixin:
step_id="edit_phases",
data_schema=vol.Schema(
{
vol.Optional("phases_text", default=default_text): selector.TextSelector(
(
vol.Optional("phases_text", description={"suggested_value": default_text})
if default_text
else vol.Optional("phases_text")
): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.TEXT,
multiline=True,
@@ -85,6 +85,8 @@ class TriggerStepsMixin(TriggerConfigMixin):
elif ctype == TriggerType.RUNTIME:
if cond.get("trigger_runtime_hours") is not None:
parts.append(f"hours: {cond['trigger_runtime_hours']}")
elif ctype == TriggerType.DUE_DATE:
parts.append(f"days before: {cond.get('trigger_days_before', 0)}")
return parts
@staticmethod
@@ -348,6 +350,7 @@ class TriggerStepsMixin(TriggerConfigMixin):
counter_step=self.async_step_opt_trigger_counter,
state_change_step=self.async_step_opt_trigger_state_change,
runtime_step=self.async_step_opt_trigger_runtime,
due_date_step=self.async_step_opt_trigger_due_date,
compound_step=self.async_step_opt_compound_logic,
)
@@ -383,6 +386,14 @@ class TriggerStepsMixin(TriggerConfigMixin):
on_complete=self._trigger_on_complete,
)
async def async_step_opt_trigger_due_date(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Configure due-date trigger."""
return await self._trigger_due_date_config(
user_input,
step_id="opt_trigger_due_date",
on_complete=self._trigger_on_complete,
)
async def async_step_opt_compound_logic(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Select compound trigger logic."""
return await self._trigger_compound_logic(
@@ -408,6 +419,7 @@ class TriggerStepsMixin(TriggerConfigMixin):
counter_step=self.async_step_opt_compound_condition_counter,
state_change_step=self.async_step_opt_compound_condition_state_change,
runtime_step=self.async_step_opt_compound_condition_runtime,
due_date_step=self.async_step_opt_compound_condition_due_date,
)
async def async_step_opt_compound_condition_threshold(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
@@ -446,6 +458,15 @@ class TriggerStepsMixin(TriggerConfigMixin):
on_complete=self.async_step_opt_compound_review,
)
async def async_step_opt_compound_condition_due_date(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Configure due_date for compound condition."""
return await self._trigger_compound_condition_config(
user_input,
"due_date",
step_id="compound_condition_due_date",
on_complete=self.async_step_opt_compound_review,
)
async def async_step_opt_compound_review(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Review compound trigger conditions."""
return await self._trigger_compound_review(
@@ -471,4 +492,6 @@ class TriggerStepsMixin(TriggerConfigMixin):
async_step_compound_condition_runtime = async_step_opt_compound_condition_runtime
async_step_compound_condition_due_date = async_step_opt_compound_condition_due_date
async_step_compound_review = async_step_opt_compound_review
@@ -45,6 +45,7 @@ from .const import (
CONF_TRIGGER_ATTRIBUTE,
CONF_TRIGGER_BELOW,
CONF_TRIGGER_COMBINATOR,
CONF_TRIGGER_DAYS_BEFORE,
CONF_TRIGGER_DELTA_MODE,
CONF_TRIGGER_ENTITY,
CONF_TRIGGER_ENTITY_LOGIC,
@@ -296,6 +297,18 @@ def _runtime_hours_field() -> dict[Any, Any]:
}
def _days_before_field() -> dict[Any, Any]:
"""due_date: how many days before the reported date the task fires."""
low, high = TRIGGER_FIELD_RANGES[CONF_TRIGGER_DAYS_BEFORE]
return {
vol.Optional(CONF_TRIGGER_DAYS_BEFORE, default=0): selector.NumberSelector(
selector.NumberSelectorConfig(
min=low, max=high, step=1, mode=selector.NumberSelectorMode.BOX, unit_of_measurement="d"
)
)
}
def _states_from_input(user_input: dict[str, Any]) -> tuple[str, str]:
"""(from, to) of a state-change submission, trimmed and lowercased — HA
states are lowercase, so "ON"/"OFF" must match the state machine."""
@@ -575,6 +588,7 @@ class TriggerConfigMixin:
counter_step: Callable[[], Awaitable[ConfigFlowResult]],
state_change_step: Callable[[], Awaitable[ConfigFlowResult]],
runtime_step: Callable[[], Awaitable[ConfigFlowResult]],
due_date_step: Callable[[], Awaitable[ConfigFlowResult]],
compound_step: Callable[[], Awaitable[ConfigFlowResult]] | None = None,
) -> ConfigFlowResult:
"""Core logic for trigger type selection."""
@@ -592,6 +606,8 @@ class TriggerConfigMixin:
return await counter_step()
if trigger_type == TriggerType.RUNTIME:
return await runtime_step()
if trigger_type == TriggerType.DUE_DATE:
return await due_date_step()
if trigger_type == TriggerType.COMPOUND and compound_step:
return await compound_step()
return await state_change_step()
@@ -837,6 +853,34 @@ class TriggerConfigMixin:
},
)
async def _trigger_due_date_config(
self,
user_input: dict[str, Any] | None,
*,
step_id: str,
on_complete: Callable[[], ConfigFlowResult],
) -> ConfigFlowResult:
"""Core logic for due-date trigger configuration."""
if user_input is not None:
cancel = await self._mixin_check_go_back(user_input)
if cancel is not None:
return cancel
tc = self._current_task["trigger_config"]
tc[CONF_TRIGGER_DAYS_BEFORE] = int(user_input.get(CONF_TRIGGER_DAYS_BEFORE) or 0)
self._apply_type_step_tail(tc, user_input)
return on_complete()
state = self._trigger_entity_state
return self.async_show_form(
step_id=step_id,
data_schema=vol.Schema(self._mixin_add_go_back({**_days_before_field(), **self._type_step_tail_fields()})),
description_placeholders={
"entity_id": self._trigger_entity_id or "",
"current_state": state.state if state is not None else "",
},
)
# ------------------------------------------------------------------
# Compound trigger configuration steps
# ------------------------------------------------------------------
@@ -944,6 +988,7 @@ class TriggerConfigMixin:
counter_step: Callable[[], Awaitable[ConfigFlowResult]],
state_change_step: Callable[[], Awaitable[ConfigFlowResult]],
runtime_step: Callable[[], Awaitable[ConfigFlowResult]],
due_date_step: Callable[[], Awaitable[ConfigFlowResult]],
) -> ConfigFlowResult:
"""Select trigger type for a compound condition."""
if user_input is not None:
@@ -960,6 +1005,8 @@ class TriggerConfigMixin:
return await counter_step()
if trigger_type == TriggerType.RUNTIME:
return await runtime_step()
if trigger_type == TriggerType.DUE_DATE:
return await due_date_step()
return await state_change_step()
trigger_options = [t.value for t in TriggerType if t != TriggerType.COMPOUND]
@@ -1041,6 +1088,8 @@ class TriggerConfigMixin:
states = _parse_states(user_input.get(CONF_TRIGGER_ON_STATES))
if states:
cond["trigger_on_states"] = states
elif condition_type == TriggerType.DUE_DATE:
cond["trigger_days_before"] = int(user_input.get(CONF_TRIGGER_DAYS_BEFORE) or 0)
entity_ids = cond.get("entity_ids", [])
if len(entity_ids) > 1 and user_input.get(CONF_TRIGGER_ENTITY_LOGIC):
@@ -1076,6 +1125,8 @@ class TriggerConfigMixin:
default=[] if cond.get("entity_id") else "",
): _state_selector(cond.get("entity_id"), multiple=True),
}
elif condition_type == TriggerType.DUE_DATE:
schema_fields = _days_before_field()
schema_fields.update(_entity_logic_field(cond.get("entity_ids", [])))
@@ -148,7 +148,9 @@ THRESHOLD_PERCENT_RANGE: tuple[int, int] = (1, 90)
# --- Config Keys: Advanced Feature Visibility ---
CONF_ADVANCED_ADAPTIVE = "advanced_adaptive_visible"
CONF_ADVANCED_PREDICTIONS = "advanced_predictions_visible"
# advanced_predictions_visible was removed in 2.96 (audit 2026-09-28): it never
# gated anything — sensor predictions are on for every sensor task and switched
# off per task (adaptive_config.sensor_prediction_enabled). Stored values are ignored.
CONF_ADVANCED_SEASONAL = "advanced_seasonal_visible"
CONF_ADVANCED_ENVIRONMENTAL = "advanced_environmental_visible"
CONF_ADVANCED_BUDGET = "advanced_budget_visible"
@@ -543,6 +545,7 @@ CONF_TRIGGER_TO_STATE = "trigger_to_state"
CONF_TRIGGER_TARGET_CHANGES = "trigger_target_changes"
CONF_TRIGGER_RUNTIME_HOURS = "trigger_runtime_hours"
CONF_TRIGGER_ON_STATES = "trigger_on_states"
CONF_TRIGGER_DAYS_BEFORE = "trigger_days_before"
# --- Config Keys: Adaptive Scheduling ---
CONF_ADAPTIVE_CONFIG = "adaptive_config"
@@ -599,6 +602,11 @@ CONF_BUDGET_CURRENCY = "budget_currency"
# Decimal places for every displayed amount — KPIs, budgets, costs in lists and
# reports, budget alerts, buy-task notes. 0 = whole numbers (the default).
CONF_CURRENCY_DECIMALS = "currency_decimals"
# #104: when the value of spare parts counts as spending — when they are
# bought (the buy reminder's cost; default, the behaviour so far) or when
# they are used (every completion books the parts it consumed).
CONF_PARTS_COST_MODE = "parts_cost_mode"
PARTS_COST_MODES = ("purchase", "use")
DEFAULT_CURRENCY_DECIMALS = 0
BUDGET_CURRENCIES: dict[str, str] = {
@@ -757,6 +765,9 @@ class TriggerType(StrEnum):
COUNTER = "counter"
STATE_CHANGE = "state_change"
RUNTIME = "runtime"
# A sensor that REPORTS the date the maintenance is due (a purifier's
# "filter change due" timestamp): fires N days before that date.
DUE_DATE = "due_date"
COMPOUND = "compound"
@@ -913,6 +924,8 @@ MAX_PART_SEARCH_URL_TEMPLATE_LENGTH = 500
TRIGGER_FIELD_RANGES: dict[str, tuple[int, int]] = {
"trigger_for_minutes": (0, 1440), # #136 hold time, one day at most
"trigger_target_changes": (1, 10_000),
# due_date: fire this many days before the date the sensor reports.
"trigger_days_before": (0, 365),
}
# Runtime target hours: > 0 (the WS validator) / >= 1 (the flow selector).
TRIGGER_RUNTIME_HOURS_MAX = 100_000
@@ -39,6 +39,7 @@ from .const import (
EVENT_TASK_SKIPPED,
LIFECYCLE_HISTORY_TYPES,
MANUAL_COMPLETION_DEDUP_SECONDS,
MAX_INTERVAL_DAYS,
MISSING_ENTITY_THRESHOLD_REFRESHES,
NOTIFIABLE_STATUSES,
NOTIFICATION_MANAGER_KEY,
@@ -227,6 +228,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
obj_data = self.entry.data.get(CONF_OBJECT, {})
today = dt_util.now().date()
if not pause_due_for_auto_resume(obj_data, today):
await self._async_maybe_auto_resume_tasks(today)
return
new_data = build_resumed_entry_data(dict(self.entry.data), self._store, today.isoformat())
@@ -241,6 +243,23 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
obj_data.get("name"),
)
async def _async_maybe_auto_resume_tasks(self, today: date) -> None:
"""#193: end single-task pauses whose ``paused_until`` day has come —
the object auto-resume's rule, per task (fresh cycle for recurring)."""
from .helpers.pause import pause_due_for_auto_resume, resume_task_data
tasks = dict(self.entry.data.get(CONF_TASKS) or {})
due = [tid for tid, td in tasks.items() if isinstance(td, dict) and pause_due_for_auto_resume(td, today)]
if not due:
return
for tid in due:
tasks[tid] = resume_task_data(tid, tasks[tid], self._store, today.isoformat())
self.hass.config_entries.async_update_entry(self.entry, data={**self.entry.data, CONF_TASKS: tasks})
await self._store.async_save()
# A paused sensor task set up no triggers — only a reload wires them.
self.hass.config_entries.async_schedule_reload(self.entry.entry_id)
_LOGGER.info("Pause of %d task(s) on '%s' ended (paused_until reached) — resumed", len(due), self.maintenance_object.name)
async def _async_prefetch_calendar_occurrences(self) -> None:
"""#187: warm the shared calendar-entity event cache BEFORE statuses.
@@ -365,7 +384,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
# Same short-circuit surface as archived, but the object remains a
# first-class citizen in every view. `_paused` mirrors the status
# for the dict-twin recomputation in helpers.status.
if object_paused:
if object_paused or task.paused_at is not None:
result[CONF_TASKS][task_id] = _inert_task_result(task, MaintenanceStatus.PAUSED, _paused=True)
continue
@@ -413,7 +432,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
await self._async_check_budget(result[CONF_TASKS])
# Notify calendar entity if registered and added to hass
if self._calendar_entity is not None and self._calendar_entity.hass is not None:
if self._calendar_entity is not None and self._calendar_entity.is_live:
self._calendar_entity.invalidate_cache()
self._calendar_entity.async_write_ha_state()
@@ -660,6 +679,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
return
from .helpers.trigger_fallback import (
evaluate_counter,
evaluate_due_date,
evaluate_runtime,
evaluate_state_change,
evaluate_threshold,
@@ -675,6 +695,8 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
result = evaluate_state_change(task.trigger_config, entity_ids)
elif trigger_type == "runtime":
result = evaluate_runtime(task.trigger_config, entity_ids)
elif trigger_type == "due_date":
result = evaluate_due_date(self.hass.states.get, task.trigger_config, entity_ids)
else:
return
@@ -1393,6 +1415,12 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
from .helpers.reference_numbers import next_history_ref
task.history[-1]["ref_no"] = next_history_ref(self._store, task_id, task.history)
# #104: what the parts of this completion cost at this moment,
# whether it was a purchase, and how it is booked — decided now,
# so a later change of the setting never re-values it.
from .helpers.parts_cost import annotate_completion
annotate_completion(self.hass, self.entry, merged[task_id], task.history[-1])
# #73: a completed cycle retires its in-cycle checklist ticks — the
# snapshot that matters is in the history entry above. A pure backfill
# closed no current cycle, so the live ticks stay.
@@ -1482,6 +1510,15 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
except Exception:
_LOGGER.exception("Part consumption failed for task %s", task_id)
# #104 "when used" bookkeeping: a purchase is stock, not spending —
# what was actually paid becomes the part's price, so the parts it
# bought are valued at that price when they are used.
if is_latest and cost is not None and cost > 0:
from .helpers.parts_cost import COST_BASIS_USE, parts_cost_mode
if parts_cost_mode(self.hass) == COST_BASIS_USE:
self._record_purchase_price(merged[task_id], float(cost), restock_quantity)
# A status configured to notify ONCE (interval 0) stays silenced by the
# _SENT_ONCE sentinel until something clears it; nothing did after a
# completion, so a sensor task that re-triggered weeks later was never
@@ -1513,6 +1550,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
# automation can forward a meter value without reading history.
reading_value=reading_value,
reading_values=reading_values,
# #104: the value of the parts this completion used (None
# when none carried a price) — the same figure the history
# entry records.
parts_cost=(task.history[-1].get("parts_cost") if task.history else None),
# #133: the history entry's own timestamp — identical to what
# the history records, so automations can attribute backdated
# completions to the right period instead of time_fired.
@@ -1625,6 +1666,11 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self._lifecycle_event_payload(
task,
task_id,
# `date` is what the logbook and the docs read — the event
# only carried `reset_date`, so every reset showed as "reset
# to ?" since 2.19 (audit 2026-09-29). `reset_date` stays for
# automations written against the old payload.
date=task.last_performed,
reset_date=task.last_performed,
),
)
@@ -1638,6 +1684,16 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
_LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title)
return
self._require_active(task_id, merged[task_id], translation_key="task_inactive_postpone")
# At most one maximum interval out, like the panel — enforced HERE so
# voice ("postpone by 900 days" is one sentence away) cannot write the
# override the WS pre-check refuses (voice audit 2026-09-30). Beyond
# it the calendar's "next day" arithmetic overflowed (audit 2026-09-27).
if until > dt_util.now().date() + timedelta(days=MAX_INTERVAL_DAYS):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="postpone_too_far",
translation_placeholders={"days": str(MAX_INTERVAL_DAYS)},
)
task = MaintenanceTask.from_dict(merged[task_id])
task.due_override = until.isoformat()
# Only the due date moves: the sensor triggers keep their progress
@@ -1645,6 +1701,40 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
await self._persist_and_signal_task_change(task_id, task, occurrence_done=False)
_LOGGER.debug("Occurrence postponed to %s: %s on %s", until, task.name, self.maintenance_object.name)
async def async_restore_task_fields(
self,
task_id: str,
dynamic: dict[str, Any],
static: dict[str, Any],
) -> None:
"""Put a task back the way it was before one voice action (undo).
``dynamic`` maps Store fields to their earlier values (``None`` = the
field was absent); ``static`` does the same for the entry-level
fields a voice action writes (the rotation pointer, the notes).
Everything else is left as it is now — see ``helpers.voice_undo``
for which fields those are and why.
"""
if task_id not in (self.entry.data.get(CONF_TASKS) or {}):
return
self._store.update_task_state(task_id, **dynamic)
current = dict(self.entry.data[CONF_TASKS][task_id])
if any(current.get(key) != value for key, value in static.items()):
for key, value in static.items():
if value is None:
current.pop(key, None)
else:
current[key] = value
write_task(self.hass, self.entry, task_id, current)
# The undone completion must not swallow the real one that follows it
# as a double tap, nor hold the triggers in their post-completion
# cooldown.
self._recent_manual_completions.pop(task_id, None)
self._recently_completed.pop(task_id, None)
self._completion_cooldown.discard(task_id)
await self._store.async_save()
await self.async_refresh_now()
def _is_inert(self, task_data: dict[str, Any]) -> bool:
"""Archived / disabled task, or a paused object (helpers.pause.is_task_inert)."""
return is_task_inert(task_data, self.entry.data.get(CONF_OBJECT, {}))
@@ -1729,6 +1819,24 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
)
def _record_purchase_price(self, task_data: dict[str, Any], cost: float, packages: float | None) -> None:
"""A buy reminder's paid cost becomes its part's price (per package
with a package size, else per unit) — "when used" mode only."""
from .const import CONF_PARTS
from .helpers.parts import MAX_PART_COST, PART_REF_FIELD
from .helpers.parts_cost import purchase_unit_price
ref = task_data.get(PART_REF_FIELD)
parts = dict(self.entry.data.get(CONF_PARTS) or {})
part = parts.get(ref.get("part_id")) if isinstance(ref, dict) else None
if not isinstance(part, dict):
return
price = purchase_unit_price(part, cost, packages)
if price is None or price > MAX_PART_COST or price == part.get("cost"):
return
parts[part["id"]] = {**part, "cost": price}
self.hass.config_entries.async_update_entry(self.entry, data={**self.entry.data, CONF_PARTS: parts})
async def _async_notify_completed(self, task_id: str, label: str, source: str | None, completed_by: str | None, completed_at: str) -> None:
from .helpers.notification_manager import NotificationManager
@@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Any
from homeassistant.helpers import area_registry as ar
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -11,6 +12,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from ..const import CONF_OBJECT, DOMAIN
from ..coordinator import MaintenanceCoordinator
from ..helpers import device_link
from ..helpers.coordinator_data import has_coordinator_data
class MaintenanceEntity(CoordinatorEntity[MaintenanceCoordinator]):
@@ -106,15 +108,19 @@ class MaintenanceEntity(CoordinatorEntity[MaintenanceCoordinator]):
device_info["model"] = obj["model"]
if obj.get("serial_number"):
device_info["serial_number"] = obj["serial_number"]
if obj.get("area_id"):
device_info["suggested_area"] = obj["area_id"]
# suggested_area is looked up by NAME: the area id passed here made
# Home Assistant create a second area called e.g. "living_room" next
# to "Living Room" and put the device in it (bug audit 2026-09-29).
# An id no area has any more suggests nothing.
if isinstance(obj.get("area_id"), str) and (area := ar.async_get(self.hass).async_get_area(obj["area_id"])):
device_info["suggested_area"] = area.name
return device_info
@property
def _task_data(self) -> dict[str, Any]:
"""Return the current task data from coordinator."""
if self.coordinator.data is None:
if not has_coordinator_data(self.coordinator):
return {}
tasks: dict[str, Any] = self.coordinator.data.get("tasks", {})
result: dict[str, Any] = tasks.get(self._task_id, {})
@@ -8,6 +8,7 @@ from ...const import TriggerType
from .base_trigger import BaseTrigger
from .compound import CompoundTrigger
from .counter import CounterTrigger
from .due_date import DueDateTrigger
from .runtime import RuntimeTrigger
from .state_change import StateChangeTrigger
from .threshold import ThresholdTrigger
@@ -15,7 +16,7 @@ from .threshold import ThresholdTrigger
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
from ...sensor import MaintenanceSensor
from .base_trigger import TriggerHost
def normalize_entity_ids(trigger_config: dict[str, Any]) -> list[str]:
@@ -137,7 +138,7 @@ def _inject_per_entity_state(config: dict[str, Any], entity_state: dict[str, Any
def create_trigger(
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> BaseTrigger:
"""Create a trigger instance based on trigger type."""
@@ -151,6 +152,8 @@ def create_trigger(
return StateChangeTrigger(hass, entity, trigger_config)
if trigger_type == TriggerType.RUNTIME:
return RuntimeTrigger(hass, entity, trigger_config)
if trigger_type == TriggerType.DUE_DATE:
return DueDateTrigger(hass, entity, trigger_config)
if trigger_type == TriggerType.COMPOUND:
return CompoundTrigger(hass, entity, trigger_config)
@@ -159,7 +162,7 @@ def create_trigger(
def create_triggers(
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> list[BaseTrigger]:
"""Create trigger instances for all entity_ids in the config.
@@ -206,6 +209,7 @@ __all__ = [
"BaseTrigger",
"CompoundTrigger",
"CounterTrigger",
"DueDateTrigger",
"RuntimeTrigger",
"StateChangeTrigger",
"ThresholdTrigger",
@@ -7,7 +7,7 @@ import math
from abc import ABC, abstractmethod
from collections.abc import Coroutine
from datetime import datetime
from typing import TYPE_CHECKING, Any
from typing import Any, Protocol
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, State, callback
from homeassistant.helpers.event import (
@@ -15,10 +15,6 @@ from homeassistant.helpers.event import (
async_track_state_change_event,
)
if TYPE_CHECKING:
from ...coordinator import MaintenanceCoordinator
from ...sensor import MaintenanceSensor
from ...const import (
EVENT_TRIGGER_ACTIVATED,
EVENT_TRIGGER_DEACTIVATED,
@@ -28,6 +24,49 @@ from ...helpers.managed_timer import ManagedTimer
_LOGGER = logging.getLogger(__name__)
class TriggerCoordinator(Protocol):
"""What a trigger needs from its coordinator: the object's, or a compound
condition's proxy that routes these per condition."""
async def async_add_trigger_history_entry(self, task_id: str, trigger_value: float | None = None) -> None: ...
async def async_auto_complete_on_recovery(self, task_id: str, trigger_value: float) -> None: ...
async def async_persist_trigger_runtime(
self,
task_id: str,
runtime_data: dict[str, Any],
entity_id: str | None = None,
*,
immediate: bool = False,
) -> None: ...
async def async_request_refresh(self) -> None: ...
def note_trigger_edge(self, task_id: str, *, recovered: bool = True) -> None: ...
class TriggerHost(Protocol):
"""What a trigger needs from the entity it drives: the task's sensor, or
a compound condition's proxy entity."""
@property
def entity_id(self) -> str: ...
@property
def _task_id(self) -> str: ...
@property
def coordinator(self) -> TriggerCoordinator: ...
def async_update_trigger_state(
self,
is_triggered: bool,
current_value: float | None = None,
trigger_entity_id: str | None = None,
) -> bool | None: ...
# The initial-evaluation retry: an entity that is unknown/unavailable at
# setup is re-checked this often, this many times, before the state-change
# listener alone is trusted to catch its recovery (issue #1 family).
@@ -45,7 +84,7 @@ class BaseTrigger(ABC):
def __init__(
self,
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> None:
"""Initialize the trigger."""
@@ -75,7 +114,7 @@ class BaseTrigger(ABC):
self._logged_unavailable = False # Log-once pattern for unavailable
@property
def _coordinator(self) -> MaintenanceCoordinator:
def _coordinator(self) -> TriggerCoordinator:
"""Get the coordinator from the entity."""
return self.entity.coordinator
@@ -84,6 +123,14 @@ class BaseTrigger(ABC):
"""Get the task ID from the entity."""
return self.entity._task_id
def _event_ids(self) -> dict[str, Any]:
"""The task's ids for the trigger events — EXAMPLES.md tells
automations to deep-link with them (`?entry_id=…&task_id=…`), but
only the completed / skipped / reset events carried them (audit
2026-09-28)."""
entry = getattr(self._coordinator, "entry", None)
return {"entry_id": getattr(entry, "entry_id", None), "task_id": self._task_id}
async def async_setup(self) -> None:
"""Set up the trigger: validate entity and register listener.
@@ -307,6 +354,12 @@ class BaseTrigger(ABC):
"""
@property
def _is_compound_condition(self) -> bool:
"""This trigger watches one condition of a compound (its entity is
the condition's proxy), not the task itself."""
return getattr(self.entity, "is_compound_condition", False) is True
def _request_coordinator_refresh(self) -> None:
"""#175: a trigger flip must reach the coordinator now, not on the
next 5-minute tick. Notifications (and the ``maintenance_supporter_
@@ -332,6 +385,12 @@ class BaseTrigger(ABC):
current_value=value,
trigger_entity_id=self.entity_id,
)
if self._is_compound_condition:
# One condition of a compound: the compound decides whether the
# TASK activated and announces it. Firing here sent
# maintenance_supporter_trigger_activated with the task's ids when
# one half of an AND flipped (bug audit 2026-09-29).
return
# Add history entry for the trigger activation
self._track(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=value))
@@ -343,6 +402,7 @@ class BaseTrigger(ABC):
EVENT_TRIGGER_ACTIVATED,
{
"entity_id": self.entity.entity_id,
**self._event_ids(),
"trigger_entity": self.entity_id,
"trigger_attribute": self.attribute,
"trigger_value": value,
@@ -367,6 +427,8 @@ class BaseTrigger(ABC):
current_value=value,
trigger_entity_id=self.entity_id,
)
if self._is_compound_condition:
return # the compound announces the task's recovery (see activation)
self._request_coordinator_refresh()
@@ -375,6 +437,7 @@ class BaseTrigger(ABC):
EVENT_TRIGGER_DEACTIVATED,
{
"entity_id": self.entity.entity_id,
**self._event_ids(),
"trigger_entity": self.entity_id,
"trigger_attribute": self.attribute,
"trigger_value": value,
@@ -8,8 +8,7 @@ from typing import TYPE_CHECKING, Any
from homeassistant.core import HomeAssistant, callback
if TYPE_CHECKING:
from ...coordinator import MaintenanceCoordinator
from ...sensor import MaintenanceSensor
from .base_trigger import TriggerCoordinator, TriggerHost
from ...const import (
CONF_COMPOUND_CONDITIONS,
@@ -32,6 +31,11 @@ class CompoundSubEntity:
``CompoundTrigger`` of the condition-level result.
"""
# A condition's sub-trigger only reports to this proxy: the task-level
# side effects (the trigger events, the cooldown edge, the refresh) belong
# to the compound — BaseTrigger checks this flag.
is_compound_condition = True
def __init__(
self,
parent: CompoundTrigger,
@@ -52,9 +56,10 @@ class CompoundSubEntity:
self._per_entity_values: dict[str, float | None] = {}
self._entity_logic = condition_config.get("entity_logic", "any")
# Mirror attributes the real entity exposes for trigger access
self.entity_id = parent.entity.entity_id
self._task_id = parent.entity._task_id
self.coordinator = parent.entity.coordinator
self.entity_id: str = parent.entity.entity_id
self._task_id: str = parent.entity._task_id
# Replaced by the condition's _CompoundCoordinatorProxy right after.
self.coordinator: TriggerCoordinator = parent.entity.coordinator
@callback
def async_update_trigger_state(
@@ -91,7 +96,7 @@ class _CompoundCoordinatorProxy:
stores data under ``_trigger_state.conditions[idx][entity_id]``.
"""
def __init__(self, real_coordinator: MaintenanceCoordinator, condition_idx: int) -> None:
def __init__(self, real_coordinator: TriggerCoordinator, condition_idx: int) -> None:
"""Initialize the proxy."""
self._real = real_coordinator
self._condition_idx = condition_idx
@@ -110,19 +115,14 @@ class _CompoundCoordinatorProxy:
) -> None:
"""Persist under trigger_runtime as a per-condition compound key.
The real coordinator always has a Store; merge_task_data reshapes these
``_compound_<idx>[_<entity_id>]`` keys back into
``_trigger_state["conditions"][idx]`` on read.
The coordinator stores under whatever key it is given;
merge_task_data reshapes these ``_compound_<idx>[_<entity_id>]`` keys
back into ``_trigger_state["conditions"][idx]`` on read.
"""
store = self._real._store
compound_key = f"_compound_{self._condition_idx}"
if entity_id is not None:
compound_key = f"_compound_{self._condition_idx}_{entity_id}"
store.set_trigger_runtime(task_id, compound_key, runtime_data)
if immediate:
await store.async_save()
else:
store.async_delay_save()
await self._real.async_persist_trigger_runtime(task_id, runtime_data, compound_key, immediate=immediate)
# A condition's sub-trigger is not the task's trigger: only the compound
# decides when the TASK activates. Delegated to the real coordinator,
@@ -137,6 +137,14 @@ class _CompoundCoordinatorProxy:
def note_trigger_edge(self, task_id: str, *, recovered: bool = True) -> None:
"""No-op for a condition — the compound reports its own edge."""
async def async_request_refresh(self) -> None:
"""Delegate to the object's coordinator."""
await self._real.async_request_refresh()
async def async_auto_complete_on_recovery(self, task_id: str, trigger_value: float) -> None:
"""Delegate to the object's coordinator."""
await self._real.async_auto_complete_on_recovery(task_id, trigger_value)
class CompoundTrigger(BaseTrigger):
"""Compound trigger that combines multiple conditions with AND/OR logic.
@@ -154,7 +162,7 @@ class CompoundTrigger(BaseTrigger):
def __init__(
self,
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> None:
"""Initialize the compound trigger."""
@@ -222,9 +230,9 @@ class CompoundTrigger(BaseTrigger):
# Wrap the real coordinator with a proxy for persistence
proxy_coordinator = _CompoundCoordinatorProxy(self._coordinator, idx)
sub_entity.coordinator = proxy_coordinator # type: ignore[assignment]
sub_entity.coordinator = proxy_coordinator
sub_triggers = create_triggers(self.hass, sub_entity, cond_config) # type: ignore[arg-type]
sub_triggers = create_triggers(self.hass, sub_entity, cond_config)
self._sub_triggers.append(sub_triggers)
for trigger in sub_triggers:
@@ -326,6 +334,7 @@ class CompoundTrigger(BaseTrigger):
EVENT_TRIGGER_ACTIVATED,
{
"entity_id": self.entity.entity_id,
**self._event_ids(),
"trigger_type": "compound",
"compound_logic": self._compound_logic,
"condition_states": list(self._condition_states),
@@ -350,6 +359,7 @@ class CompoundTrigger(BaseTrigger):
EVENT_TRIGGER_DEACTIVATED,
{
"entity_id": self.entity.entity_id,
**self._event_ids(),
"trigger_type": "compound",
"compound_logic": self._compound_logic,
"condition_states": list(self._condition_states),
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any
from homeassistant.core import HomeAssistant
if TYPE_CHECKING:
from ...sensor import MaintenanceSensor
from .base_trigger import TriggerHost
from ...helpers.trigger_fallback import counter_baseline
from .base_trigger import BaseTrigger
@@ -27,7 +27,7 @@ class CounterTrigger(BaseTrigger):
def __init__(
self,
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> None:
"""Initialize counter trigger."""
@@ -27,7 +27,7 @@ from ...const import UNAVAILABLE_STATES
from ...helpers.managed_timer import ManagedTimer
if TYPE_CHECKING:
from ...sensor import MaintenanceSensor
from .base_trigger import TriggerHost
from homeassistant.util import dt as dt_util
from .base_trigger import BaseTrigger
@@ -50,7 +50,7 @@ class RuntimeTrigger(BaseTrigger):
def __init__(
self,
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> None:
"""Initialize runtime trigger."""
@@ -17,7 +17,7 @@ from ...const import UNAVAILABLE_STATES
from ...helpers.managed_timer import ManagedTimer
if TYPE_CHECKING:
from ...sensor import MaintenanceSensor
from .base_trigger import TriggerHost
from .base_trigger import BaseTrigger
@@ -48,7 +48,7 @@ class StateChangeTrigger(BaseTrigger):
def __init__(
self,
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> None:
"""Initialize state change trigger."""
@@ -10,7 +10,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.util import dt as dt_util
if TYPE_CHECKING:
from ...sensor import MaintenanceSensor
from .base_trigger import TriggerHost
from ...helpers.managed_timer import ManagedTimer
from ...helpers.trigger_fallback import threshold_exceeds
@@ -32,7 +32,7 @@ class ThresholdTrigger(BaseTrigger):
def __init__(
self,
hass: HomeAssistant,
entity: MaintenanceSensor,
entity: TriggerHost,
trigger_config: dict[str, Any],
) -> None:
"""Initialize threshold trigger."""
@@ -79,6 +79,9 @@ def _build_export_object(
"created_at": tdata.get("created_at"),
"archived_at": tdata.get("archived_at"),
"archived_reason": tdata.get("archived_reason"),
# #193: a paused task stays paused after a restore.
"paused_at": tdata.get("paused_at"),
"paused_until": tdata.get("paused_until"),
"schedule_type": sched["schedule_type"],
"interval_days": sched["interval_days"],
"interval_unit": sched["interval_unit"],
@@ -163,6 +166,16 @@ def _build_export_object(
# restored fleet task is a plain inspection task.
if tdata.get(BATTERY_FLEET_TASK_FLAG):
task[BATTERY_FLEET_TASK_FLAG] = True
# The fleet's replacement log and low latch live in the task's
# Store state, which merged_tasks does not overlay — a backup
# lost every recorded battery swap (round-trip audit 2026-09-29).
from .helpers.battery_fleet import LOW_LATCH_KEY
from .helpers.battery_lifetime import REPLACEMENT_LOG_KEY
state = store.get_task_state(tid) if store is not None else {}
for key in (REPLACEMENT_LOG_KEY, LOW_LATCH_KEY):
if state.get(key):
task[key] = state[key]
if include_history:
task["history"] = tdata.get("history") or []
@@ -280,7 +293,7 @@ def serialize_export(data: dict[str, Any], fmt: str = "json") -> str:
"""
if fmt == "yaml":
try:
import yaml # type: ignore[import-untyped]
import yaml
# Normalize through JSON first: yaml.safe_dump rejects types the
# JSON path coerces (e.g. tuples → lists), so YAML export would
@@ -368,4 +381,11 @@ def build_settings_export(hass: HomeAssistant) -> dict[str, Any]:
):
if key in opts:
settings[key] = opts[key]
return {"version": 1, "global_settings": settings}
out: dict[str, Any] = {"version": 1, "global_settings": settings}
# Group members and vacation exemptions point at tasks by id; a move to
# another instance mints new ids, so name them (helpers.import_mapping).
from .helpers.import_mapping import TASK_NAMES_KEY, settings_task_names
if task_names := settings_task_names(hass, settings):
out[TASK_NAMES_KEY] = task_names
return out
@@ -35,7 +35,6 @@ export interface ServiceCall {
/** Same shape as `frontend-src/types.ts::AdvancedFeatures`. */
export interface MockFeatures {
adaptive: boolean;
predictions: boolean;
seasonal: boolean;
environmental: boolean;
budget: boolean;
@@ -46,7 +45,7 @@ export interface MockFeatures {
}
export const DEFAULT_FEATURES: MockFeatures = {
adaptive: false, predictions: false, seasonal: false,
adaptive: false, seasonal: false,
environmental: false, budget: false, groups: false,
checklists: false, schedule_time: false, completion_actions: false,
};
@@ -136,6 +135,8 @@ export interface CreateMockHassOptions {
settingsResponse?: typeof DEFAULT_SETTINGS_RESPONSE;
/** Per-WS-type handlers — return a value or Promise. Wins over built-in defaults. */
handlers?: Record<string, WsHandler>;
/** Answers every WS type no handler and no built-in default covers. */
fallback?: WsHandler;
/** Optional `hass.services` registry (for ha-service-picker / schema-driven forms). */
services?: Record<string, Record<string, unknown>>;
/** Optional `hass.states` (entity_id → state) — e.g. for notify-entity pickers. */
@@ -169,6 +170,7 @@ export function createMockHass(opts: CreateMockHassOptions = {}): CreateMockHass
if (msg.type === "maintenance_supporter/users/list") return { users: [] };
if (msg.type === "maintenance_supporter/objects") return { objects: [] };
if (msg.type === "maintenance_supporter/tags/list") return { tags: [] };
if (opts.fallback) return await opts.fallback(msg);
return {};
};
@@ -54,7 +54,7 @@ describe("adopt-problem-sensors dialog: suggested part", () => {
el.shadowRoot!.querySelectorAll<HTMLElement>("ha-button")[1].click(); // Adopt selected
await el.updateComplete;
await new Promise((r) => setTimeout(r, 0));
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as {
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as unknown as {
selections: Array<{ entity_id: string; part_id?: string }>;
};
const byId = Object.fromEntries(adopt.selections.map((s) => [s.entity_id, s]));
@@ -72,7 +72,7 @@ describe("adopt-problem-sensors dialog: flicker filter (#136)", () => {
el.shadowRoot!.querySelectorAll<HTMLElement>("ha-button")[1].click();
await el.updateComplete;
await new Promise((r) => setTimeout(r, 0));
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as {
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as unknown as {
selections: Array<{ for_minutes?: number }>;
};
expect(adopt.selections.every((s) => s.for_minutes === undefined)).to.equal(true);
@@ -87,7 +87,7 @@ describe("adopt-problem-sensors dialog: flicker filter (#136)", () => {
el.shadowRoot!.querySelectorAll<HTMLElement>("ha-button")[1].click();
await el.updateComplete;
await new Promise((r) => setTimeout(r, 0));
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as {
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as unknown as {
selections: Array<{ for_minutes?: number }>;
};
expect(adopt.selections.length).to.be.greaterThan(0);
@@ -325,7 +325,7 @@ describe("complete-dialog", () => {
});
});
describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => {
describe("complete-dialog parts cost (#104)", () => {
async function mountWithParts(over: Partial<MaintenanceCompleteDialog> = {}) {
const { hass } = createMockHass({});
const el = await fixture<MaintenanceCompleteDialog>(html`
@@ -345,54 +345,85 @@ describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => {
const chip = (el: MaintenanceCompleteDialog) =>
el.shadowRoot!.querySelector<HTMLButtonElement>(".cost-suggestion");
const note = (el: MaintenanceCompleteDialog) =>
el.shadowRoot!.querySelector<HTMLElement>(".cost-note");
it("sums selected consumed parts (qty x unit cost) and fills on click", async () => {
const USING = {
parts: [
{ id: "p1", name: "Filter", cost: 12.5 },
{ id: "p2", name: "O-Ring", cost: 2.25 },
{ id: "p3", name: "Unpriced", cost: null },
] as never,
consumesParts: [
{ part_id: "p1", quantity: 1 },
{ part_id: "p2", quantity: 2 },
{ part_id: "p3", quantity: 1 },
] as never,
currencySymbol: "€",
};
it("parts used: their value is a line, never a suggestion into the cost field", async () => {
// 2.53 offered the parts' value as a one-click cost. A household that
// already booked the purchase counted the same filter twice.
const el = await mountWithParts(USING);
expect(chip(el), "no cost suggestion on a job that uses parts").to.equal(null);
expect(note(el)!.textContent).to.include("17 €");
expect(note(el)!.textContent).to.include("when they were bought");
});
it("parts used, counted when used: the line says they are booked", async () => {
const el = await mountWithParts({ ...USING, partsCostMode: "use" });
expect(chip(el)).to.equal(null);
expect(note(el)!.textContent).to.include("17 €");
expect(note(el)!.textContent).to.include("booked automatically");
});
it("parts used: a package price is spread over its contents (#98)", async () => {
const el = await mountWithParts({
parts: [
{ id: "p1", name: "Filter", cost: 12.5 },
{ id: "p2", name: "O-Ring", cost: 2.25 },
{ id: "p3", name: "Unpriced", cost: null },
] as never,
consumesParts: [
{ part_id: "p1", quantity: 1 },
{ part_id: "p2", quantity: 2 },
{ part_id: "p3", quantity: 1 },
] as never,
parts: [{ id: "p1", name: "Spray", cost: 12, package_size: 400, unit: "ml" }] as never,
consumesParts: [{ part_id: "p1", quantity: 100 }] as never,
currencySymbol: "€",
});
const c = chip(el)!;
expect(c, "suggestion chip rendered").to.exist;
expect(c.textContent).to.include("17 €");
expect(c.textContent).to.include("€");
c.click();
// 100 ml of a 400 ml can at 12 € = 3 €, not 100 × 12 €.
expect(note(el)!.textContent).to.include("3 €");
});
it("buy task: restock qty x price per package, follows the qty field, fills on click", async () => {
const el = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5 });
expect(chip(el)!.textContent).to.include("9");
setInput(el, 0, "4");
await el.updateComplete;
const cost = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>(".field-input")][1];
// Machine value for the <input type="number"> — never profile-formatted
// ("17,00" would be rejected by the input under a decimal_comma profile).
expect(cost.value).to.equal("17");
expect(chip(el)!.textContent).to.include("18");
chip(el)!.click();
await el.updateComplete;
// Buy task: restock qty, notes, cost.
const cost = [...el.shadowRoot!.querySelectorAll<HTMLInputElement>(".field-input")][2];
// Machine value for the <input type="number"> — never profile-formatted.
expect(cost.value).to.equal("18");
expect(chip(el), "chip hides once cost is set").to.equal(null);
});
it("buy task: restock qty x unit cost, follows the qty field", async () => {
const el = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5 });
expect(chip(el)!.textContent).to.include("9");
it("buy task, counted when used: the price becomes the part's price", async () => {
const el = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5, partsCostMode: "use" });
expect(chip(el)).to.exist;
expect(note(el)!.textContent).to.include("price");
const off = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5 });
expect(note(off), "no hint when purchases are the spending").to.equal(null);
});
it("no chip when no involved part carries a price", async () => {
it("nothing when no involved part carries a price", async () => {
const el = await mountWithParts({
parts: [{ id: "p1", name: "Filter", cost: null }] as never,
consumesParts: [{ part_id: "p1", quantity: 1 }] as never,
});
expect(chip(el)).to.equal(null);
expect(note(el)).to.equal(null);
});
it("no chip once the user typed a cost themselves", async () => {
const el = await mountWithParts({
parts: [{ id: "p1", name: "Filter", cost: 5 }] as never,
consumesParts: [{ part_id: "p1", quantity: 1 }] as never,
});
it("buy task: no chip once the user typed a cost themselves", async () => {
const el = await mountWithParts({ restockDefault: 1, restockUnitCost: 5 });
expect(chip(el)).to.exist;
setInput(el, 1, "3.10");
setInput(el, 2, "3.10");
await el.updateComplete;
expect(chip(el)).to.equal(null);
});
@@ -78,7 +78,7 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
installation_date: "2025-01-01", documentation_url: "https://x.test/",
notes: "test notes",
});
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
const banned = findBannedTags(el.shadowRoot!);
expect(banned, `object-dialog has banned lazy-load elements: ${banned.join(", ")}`)
.to.have.lengthOf(0);
@@ -106,10 +106,10 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
schedule_time: "09:00",
on_complete_action: { service: "button.press", target: { entity_id: "button.x" } },
});
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
// Expand <details> sections so action UI is in the DOM
el.shadowRoot!.querySelectorAll<HTMLDetailsElement>("details").forEach((d) => { d.open = true; });
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
const banned = findBannedTags(el.shadowRoot!);
expect(banned, `task-dialog has banned lazy-load elements: ${banned.join(", ")}`)
.to.have.lengthOf(0);
@@ -123,7 +123,7 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
html`<maintenance-group-dialog .hass=${hass}></maintenance-group-dialog>`,
);
el.openCreate();
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
const banned = findBannedTags(el.shadowRoot!);
expect(banned, `group-dialog has banned lazy-load elements: ${banned.join(", ")}`)
.to.have.lengthOf(0);
@@ -138,7 +138,7 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
);
el.entryId = "entry_x"; el.taskId = "t1"; el.taskName = "Test";
el.open();
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
const banned = findBannedTags(el.shadowRoot!);
expect(banned, `complete-dialog has banned lazy-load elements: ${banned.join(", ")}`)
.to.have.lengthOf(0);
@@ -156,7 +156,7 @@ describe("dialog tripwire: no lazy-loaded HA elements", () => {
type: "completed", timestamp: "2025-01-01T00:00:00",
notes: null, cost: null, duration: null, completed_by: null,
});
await (el as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
await (el as unknown as HTMLElement & { updateComplete: Promise<void> }).updateComplete;
const banned = findBannedTags(el.shadowRoot!);
expect(banned, `history-edit-dialog has banned lazy-load elements: ${banned.join(", ")}`)
.to.have.lengthOf(0);
@@ -99,7 +99,7 @@ describe("group-dialog task list (#40 alphabetical sort)", () => {
await el.updateComplete;
// Force a re-render with the same objects (ordering invariant).
el.objects = [...mockObjects()];
el.objects = [...mockObjects()] as never;
await el.updateComplete;
const cbAgain = el.shadowRoot!.querySelectorAll(".object-block")[0]
@@ -48,8 +48,10 @@ async function fireAndWait(detail: unknown): Promise<HTMLElement | null> {
document.dispatchEvent(
new CustomEvent("ll-custom", { detail, bubbles: true, composed: true }),
);
// handler imports dialog-mount.ts dynamically, then mounts the dialog
for (let i = 0; i < 40; i++) {
// handler imports dialog-mount.ts dynamically, then mounts the dialog — the
// first import compiles the whole dialog tree, which took over the old 1 s
// budget on a cold transform cache (failed three runs in a row, 2026-09-28)
for (let i = 0; i < 200; i++) {
const dlg = document.body.querySelector<HTMLElement>(OBJECT_DIALOG_TAG);
if (dlg) return dlg;
await tick(25);
@@ -181,6 +181,44 @@ describe("panel shell", () => {
expect(sr(el).querySelector(".today-person")).to.equal(null);
});
it("the person filter lists the responsible people and shows a saved view's person", async () => {
const { el } = await mountPanel(
[
obj("e1", [
task({ name: "Hers", responsible_user_id: "u-anna" }),
task({ name: "His", responsible_user_id: "u-ben" }),
task({ name: "Nobody's" }),
]),
],
{
"maintenance_supporter/users/list": () => ({ users: [{ id: "u-anna", name: "Anna" }, { id: "u-ben", name: "Ben" }, { id: "u-idle", name: "Idle" }] }),
"maintenance_supporter/views/list": () => ({ views: [{ id: "v-anna", name: "Anna's tasks", filters: { user_id: "u-anna" } }] }),
},
);
await new Promise((r) => setTimeout(r, 30));
await el.updateComplete;
const userSelect = () =>
[...sr(el).querySelectorAll<HTMLSelectElement>("select")].find((s) => s.querySelector('option[value="current_user"]'))!;
// Everyone responsible for a task, by name — not people without tasks.
expect([...userSelect().options].map((o) => o.textContent!.trim()).slice(2)).to.deep.equal(["Anna", "Ben"]);
// A saved view that narrows to one person shows that person — the select
// used to stay blank because only "All" and "My tasks" existed.
(el as unknown as { _applyView(id: string): void })._applyView("v-anna");
await el.updateComplete;
expect(userSelect().selectedOptions[0].textContent!.trim()).to.equal("Anna");
const rows = [...sr(el).querySelectorAll(".task-name")].map((n) => n.textContent!.trim());
expect(rows.some((r) => r.includes("Hers"))).to.equal(true);
expect(rows.some((r) => r.includes("His") || r.includes("Nobody"))).to.equal(false);
// Picking a person by hand filters the same way.
const sel = userSelect();
sel.value = "u-ben";
sel.dispatchEvent(new Event("change"));
await el.updateComplete;
expect((el as unknown as { _filterUser: string | null })._filterUser).to.equal("u-ben");
});
it("Today person chip stays readable on a phone next to a long object name (#169)", async () => {
localStorage.setItem("msp-overview-tab", "today");
await setViewport({ width: 360, height: 800 });
@@ -83,7 +83,7 @@ describe("suggested-setups dialog: counting start value (#102)", () => {
await el.updateComplete;
await new Promise((r) => setTimeout(r, 0));
const adopt = sent.find((m) => m.type === "maintenance_supporter/integration_setups/adopt")! as {
const adopt = sent.find((m) => m.type === "maintenance_supporter/integration_setups/adopt")! as unknown as {
selections: Array<{ device_id: string; entry_id?: string }>;
};
const byId = Object.fromEntries(adopt.selections.map((s) => [s.device_id, s]));
@@ -102,7 +102,7 @@ describe("suggested-setups dialog: counting start value (#102)", () => {
await el.updateComplete;
await new Promise((r) => setTimeout(r, 0));
const adopt = sent.find((m) => m.type === "maintenance_supporter/integration_setups/adopt")! as {
const adopt = sent.find((m) => m.type === "maintenance_supporter/integration_setups/adopt")! as unknown as {
selections: Array<{ device_id: string; baselines?: Record<string, number> }>;
};
const byId = Object.fromEntries(adopt.selections.map((s) => [s.device_id, s]));
@@ -48,7 +48,7 @@ function task(overrides: Record<string, unknown> = {}): MaintenanceTask {
function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
const { hass } = createMockHass({
handler: () => ({ documents: [] }),
fallback: () => ({ documents: [] }),
});
return {
lang: "en",
@@ -59,13 +59,15 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
objectDocUrl: null,
objectManualDocs: [],
openManualDoc: () => {},
setPhaseCursor: () => {},
moveTask: () => {},
setChecklistItem: () => {},
isOperator: false,
actionLoading: false,
moreMenuOpen: false,
activeTab: "overview",
features: {
adaptive: false, predictions: false, seasonal: false,
adaptive: false, seasonal: false,
environmental: false, budget: false, groups: false,
checklists: false, schedule_time: false, completion_actions: false,
},
@@ -105,6 +107,7 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
openComplete: () => undefined,
promptSkip: () => undefined,
toggleArchive: () => undefined,
togglePause: () => undefined,
openQr: () => undefined,
duplicateTask: () => undefined,
promptReset: () => undefined,
@@ -178,7 +181,7 @@ describe("task-detail renderer", () => {
expect(items.join(" ")).to.not.match(/edit|archive|delete|duplicate|move/i);
});
it("open more-menu lists edit/qr/worksheet/duplicate/reset/postpone/snooze/archive/delete and fires callbacks", () => {
it("open more-menu lists edit/qr/worksheet/duplicate/reset/postpone/snooze/pause/archive/delete and fires callbacks", () => {
const calls: string[] = [];
const host = mount(task(), ctx({
moreMenuOpen: true,
@@ -187,21 +190,36 @@ describe("task-detail renderer", () => {
promptPostpone: () => calls.push("postpone"),
snoozeTask: () => calls.push("snooze"),
printWorksheet: () => calls.push("worksheet"),
togglePause: (paused) => calls.push(paused ? "resume" : "pause"),
toggleArchive: () => calls.push("archive"),
}));
const items = [...host.querySelectorAll(".popup-menu-item")];
// edit, qr, worksheet, duplicate, move (#23), reset, postpone, snooze, archive, delete
expect(items.length).to.equal(10);
// edit, qr, worksheet, duplicate, move (#23), reset, postpone, snooze, pause (#193), archive, delete
expect(items.length).to.equal(11);
(items[6] as HTMLElement).click(); // postpone
(items[7] as HTMLElement).click(); // snooze
(items[2] as HTMLElement).click(); // work sheet (v2.21)
(items[8] as HTMLElement).click(); // archive (demoted from the header)
(items[9] as HTMLElement).click(); // delete (danger)
(items[8] as HTMLElement).click(); // pause this one task (#193)
(items[9] as HTMLElement).click(); // archive (demoted from the header)
(items[10] as HTMLElement).click(); // delete (danger)
expect(calls).to.deep.equal([
"close", "postpone", "close", "snooze", "close", "worksheet", "close", "archive", "close", "delete",
"close", "postpone", "close", "snooze", "close", "worksheet", "close", "pause", "close", "archive", "close", "delete",
]);
});
it("a paused task offers Resume and names its resume date (#193)", () => {
const calls: string[] = [];
const host = mount(task({ paused: true, paused_until: "2027-03-01", status: "paused" } as never), ctx({
moreMenuOpen: true,
togglePause: (paused) => calls.push(paused ? "resume" : "pause"),
}));
const resume = [...host.querySelectorAll<HTMLElement>(".popup-menu-item")].find((i) => i.textContent?.trim() === "Resume");
expect(resume, "Resume offered").to.exist;
resume!.click();
expect(calls).to.deep.equal(["resume"]);
expect(host.querySelector(".paused-until")?.textContent).to.include("until");
});
it("shows a bell-off badge for a muted task (#173), and none otherwise", () => {
const muted = mount(task({ notify_enabled: false }), ctx());
expect(muted.querySelector(".muted-badge")).to.exist;
@@ -277,7 +295,7 @@ describe("task-detail renderer", () => {
task({ checklist: ["Drain", "Clean", "Refill"], checklist_progress: { Clean: true } }),
ctx({
features: {
adaptive: false, predictions: false, seasonal: false,
adaptive: false, seasonal: false,
environmental: false, budget: false, groups: false,
checklists: true, schedule_time: false, completion_actions: false,
},
@@ -45,7 +45,7 @@ function task(overrides: Record<string, unknown> = {}): MaintenanceTask {
function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
const { hass } = createMockHass({
handler: () => ({ documents: [] }),
fallback: () => ({ documents: [] }),
});
return {
lang: "en",
@@ -56,13 +56,15 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
objectDocUrl: null,
objectManualDocs: [],
openManualDoc: () => {},
setPhaseCursor: () => {},
moveTask: () => {},
setChecklistItem: () => {},
isOperator: false,
actionLoading: false,
moreMenuOpen: false,
activeTab: "overview",
features: {
adaptive: false, predictions: false, seasonal: false,
adaptive: false, seasonal: false,
environmental: false, budget: false, groups: false,
checklists: false, schedule_time: false, completion_actions: false,
},
@@ -102,6 +104,7 @@ function ctx(overrides: Partial<TaskDetailContext> = {}): TaskDetailContext {
openComplete: () => undefined,
promptSkip: () => undefined,
toggleArchive: () => undefined,
togglePause: () => undefined,
openQr: () => undefined,
duplicateTask: () => undefined,
promptReset: () => undefined,
@@ -105,7 +105,7 @@ describe("task-dialog calendar kinds (Phase 4)", () => {
(el as any)._calOffset = "-2";
await (el as any)._save();
const msg = sent.find((m: any) => m.type === "maintenance_supporter/task/create");
expect(msg.schedule).to.deep.equal({
expect(msg!.schedule).to.deep.equal({
kind: "day_of_month", day: -1, business: true, offset: -2,
});
});
@@ -55,7 +55,7 @@ describe("task-dialog runtime on-states (#103)", () => {
await el.openEdit("entry_x", MOWER_TASK as any);
await el.updateComplete;
await (el as any)._save();
const update = sent.find((m) => m.type === "maintenance_supporter/task/update")! as {
const update = sent.find((m) => m.type === "maintenance_supporter/task/update")! as unknown as {
trigger_config: { trigger_on_states?: string[] };
};
expect(update.trigger_config.trigger_on_states).to.deep.equal(["mowing"]);
@@ -68,7 +68,7 @@ describe("task-dialog runtime on-states (#103)", () => {
(el as any)._triggerOnStates = "";
await el.updateComplete;
await (el as any)._save();
const update = sent.find((m) => m.type === "maintenance_supporter/task/update")! as {
const update = sent.find((m) => m.type === "maintenance_supporter/task/update")! as unknown as {
trigger_config: { trigger_on_states?: string[] };
};
expect(update.trigger_config.trigger_on_states).to.equal(undefined);
@@ -9,6 +9,8 @@ import { property, state } from "lit/decorators.js";
import { t, ensureLocale, langOf, formatDate, syncLocaleFromHass } from "../styles";
import { LS_KEYS, lsGet, lsSet } from "../helpers/storage-keys";
import { runWs } from "../helpers/ws-run";
import { canWrite, NO_DELEGATION, type WriteAccess } from "../helpers/permissions";
import { fetchSettingsOnce } from "../helpers/settings-cache";
import { isoDateLocal } from "../helpers/calendar-bucket";
import { px } from "../renderers/chart-utils";
import type { HomeAssistant } from "../types";
@@ -107,6 +109,12 @@ export class MaintenanceBatteryFleetSection extends LitElement {
@state() private _rosterSort: "name" | "urgency" = MaintenanceBatteryFleetSection._storedSort();
@state() private _typeFilter: string | null = null;
@state() private _recorded: string[] = [];
/** Every fleet action (mark replaced, record, exclude/include, add, the two
* options, repair) is WRITE tier on the server; the section offered them
* to every household member, who then got "not permitted" and a checkbox
* left flipped (audit 2026-09-29). Same rule as the panel and the other
* cards: helpers/permissions.canWrite. */
@state() private _access: WriteAccess = NO_DELEGATION;
private _historyRequested = false;
private _localeReady = false;
@@ -114,6 +122,10 @@ export class MaintenanceBatteryFleetSection extends LitElement {
return langOf(this.hass);
}
private get _canWrite(): boolean {
return canWrite(this.hass?.user, this._access);
}
connectedCallback(): void {
super.connectedCallback();
if (this.hass) this._load();
@@ -128,6 +140,7 @@ export class MaintenanceBatteryFleetSection extends LitElement {
if (changed.has("hass") && this.hass && !this._localeReady) {
this._localeReady = true;
ensureLocale(this._lang).then(() => this.requestUpdate());
void fetchSettingsOnce(this.hass).then((s) => { this._access = s.access; });
if (this._ov === null && !this._loading) this._load();
}
}
@@ -432,7 +445,8 @@ export class MaintenanceBatteryFleetSection extends LitElement {
? html`<span class="bf-offline bf-nosensor">${t("battery_fleet_no_sensor", L)}</span>`
: nothing;
const type = html`<span class="bf-type">${b.quantity}× ${b.battery_type}</span>`;
const showMark = o.mark === "always" || b.no_sensor || b.can_mark_replaced;
const writer = this._canWrite;
const showMark = writer && (o.mark === "always" || b.no_sensor || b.can_mark_replaced);
return html`
<div class="bf-row">
<span class="bf-dev">${b.device_name}</span>
@@ -461,7 +475,7 @@ export class MaintenanceBatteryFleetSection extends LitElement {
<ha-icon icon="mdi:battery-sync"></ha-icon>
</button>`
: nothing}
${o.jump ? this._jumpButton(b, L) : nothing}
${o.jump && writer ? this._jumpButton(b, L) : nothing}
${o.predicted && b.days_until != null
? html`<span
class="bf-predicted ${b.predicted_source === "trend" ? "bf-trend" : ""} ${b.forecast_overdue ? "bf-overdue" : ""}"
@@ -469,7 +483,7 @@ export class MaintenanceBatteryFleetSection extends LitElement {
>${b.forecast_overdue ? html`<ha-icon icon="mdi:calendar-alert"></ha-icon>` : nothing}~${this._predictedDate(b.days_until)}</span
>`
: nothing}
${o.exclude
${o.exclude && writer
? html`<button
class="bf-mark bf-exclude"
title=${t("battery_fleet_exclude", L)}
@@ -504,9 +518,11 @@ export class MaintenanceBatteryFleetSection extends LitElement {
? html`
<div class="bf-repair">
<span>${t("battery_fleet_trigger_lost", L)}</span>
<ha-button .disabled=${this._marking} @click=${this._repair}>
${t("battery_fleet_repair", L)}
</ha-button>
${this._canWrite
? html`<ha-button .disabled=${this._marking} @click=${this._repair}>
${t("battery_fleet_repair", L)}
</ha-button>`
: nothing}
</div>
`
: nothing}
@@ -521,11 +537,13 @@ export class MaintenanceBatteryFleetSection extends LitElement {
<div class="bf-rows">
${ov.low.map((b) => this._renderRow(b, L, { recharge: true, mark: "always", exclude: true }))}
</div>
<div class="bf-actions">
<ha-button .disabled=${this._marking} @click=${this._markAll}>
<ha-icon icon="mdi:battery-sync"></ha-icon> ${t("battery_fleet_mark_all", L)}
</ha-button>
</div>
${this._canWrite
? html`<div class="bf-actions">
<ha-button .disabled=${this._marking} @click=${this._markAll}>
<ha-icon icon="mdi:battery-sync"></ha-icon> ${t("battery_fleet_mark_all", L)}
</ha-button>
</div>`
: nothing}
`}
${ov.soon.length
@@ -564,6 +582,19 @@ export class MaintenanceBatteryFleetSection extends LitElement {
)}
</div>
<div class="bf-roster-hint">${t("battery_fleet_all_hint", L)}</div>
${this._canWrite ? this._renderRosterSettings(ov, L) : nothing}
</details>
`
: nothing}
${this._renderExcluded(ov, L)}
<div class="bf-total">${t("battery_fleet_total", L).replace("{n}", String(ov.total))}</div>
</div>
`;
}
/** Add a battery + the two fleet options — write tier, so writers only. */
private _renderRosterSettings(ov: Overview, L: string) {
return html`
<div class="bf-add">
<span class="bf-label">${t("battery_fleet_add", L)}</span>
<ha-selector
@@ -594,9 +625,12 @@ export class MaintenanceBatteryFleetSection extends LitElement {
${t("battery_fleet_due_without_sensor", L)}
</label>
<div class="bf-roster-hint">${t("battery_fleet_due_without_sensor_hint", L)}</div>
</details>
`
: nothing}
`;
}
/** Excluded devices stay listed for everyone; bringing one back is write tier. */
private _renderExcluded(ov: Overview, L: string) {
return html`
${ov.excluded?.length
? html`
<div class="bf-excluded">
@@ -605,22 +639,22 @@ export class MaintenanceBatteryFleetSection extends LitElement {
(x) => html`
<span class="bf-excluded-chip">
${x.device_name}
<button
class="bf-mark"
title=${t("battery_fleet_include", L)}
.disabled=${this._marking}
@click=${() => this._setExcluded(x.entity_id, false)}
>
<ha-icon icon="mdi:eye-outline"></ha-icon>
</button>
${this._canWrite
? html`<button
class="bf-mark"
title=${t("battery_fleet_include", L)}
.disabled=${this._marking}
@click=${() => this._setExcluded(x.entity_id, false)}
>
<ha-icon icon="mdi:eye-outline"></ha-icon>
</button>`
: nothing}
</span>
`,
)}
</div>
`
: nothing}
<div class="bf-total">${t("battery_fleet_total", L).replace("{n}", String(ov.total))}</div>
</div>
`;
}
@@ -9,6 +9,7 @@ import { runWs } from "../helpers/ws-run";
import { isoMinuteLocal } from "../helpers/calendar-bucket";
import { PART_QTY_RANGE, RESTOCK_QTY_RANGE } from "../helpers/setting-ranges";
import { partLinkKey, type LinkedPart } from "../helpers/shared-parts";
import { unitPrice, type PartsCostMode } from "../helpers/parts-cost";
import { REQUIRED_COMPLETION_LABELS } from "./required-completion-labels";
import { PhotoUploadController } from "../helpers/photo-upload-controller";
import { parseDurationMinutes } from "../helpers/duration";
@@ -45,8 +46,12 @@ export class MaintenanceCompleteDialog extends LitElement {
/** #104 follow-up: the buy task's part unit cost — powers the cost
* suggestion (restock qty × unit cost). */
@property({ attribute: false }) public restockUnitCost: number | null = null;
/** #98: one package of the part ("400 ml") — the quantity counts packages. */
@property({ attribute: false }) public restockPackage = "";
/** Currency symbol for the cost suggestion ("" = plain number). */
@property() public currencySymbol = "";
/** #104: when spare parts count ("purchase" | "use"). */
@property({ attribute: false }) public partsCostMode: PartsCostMode = "purchase";
/** #99: the parts offered on completion — enables the editable "parts used"
* section. Built by `partsForCompletion`: the object's own inventory plus
* every shared pool this task links to (#111), each tagged with its owner. */
@@ -309,45 +314,60 @@ export class MaintenanceCompleteDialog extends LitElement {
return this.requiredFields.includes(field) ? html`<span class="req-mark" aria-hidden="true">*</span>` : nothing;
}
/** #104 follow-up: suggested cost derived from the parts this completion
* touches — the SELECTED "parts used" (qty × each part's unit cost) on a
* consuming task, or restock qty × unit cost on a buy task. Null when no
* involved part carries a price. Follows the live selection, so ticking
* a part off updates the suggestion. */
private _partsCostSuggestion(): number | null {
if (this.restockDefault !== null) {
const qty = parseFloat(this._restockQty);
if (this.restockUnitCost == null || !Number.isFinite(qty) || qty <= 0) return null;
return Math.round(this.restockUnitCost * qty * 100) / 100;
}
if (!this.parts.length) return null;
/** A buy task's price: restock qty (packages) × the part's price per
* package. Null without a price. */
private _purchaseSuggestion(): number | null {
if (this.restockDefault === null) return null;
const qty = parseFloat(this._restockQty);
if (this.restockUnitCost == null || !Number.isFinite(qty) || qty <= 0) return null;
return Math.round(this.restockUnitCost * qty * 100) / 100;
}
/** #104: the value of the SELECTED parts used (qty × each part's price per
* unit — a package price spread over its contents, #98). Follows the live
* selection. Null when no selected part carries a price. */
private _partsValue(): number | null {
if (this.restockDefault !== null || !this.parts.length) return null;
let sum = 0;
let priced = false;
for (const link of Object.values(this._usedParts)) {
const def = this.parts.find(
(pt) => partLinkKey({ part_id: pt.id, entry_id: pt.entry_id }) === partLinkKey(link),
);
if (def?.cost != null) {
sum += def.cost * (link.quantity || 1);
const price = unitPrice(def);
if (price !== null) {
sum += price * (link.quantity || 1);
priced = true;
}
}
return priced ? Math.round(sum * 100) / 100 : null;
}
/** The one-click "use ≈ X from parts" chip under the cost field. Hidden
* once the user typed a cost themselves — a suggestion, never an
* overwrite. */
/** Under the cost field. A buy task gets the one-click price suggestion
* (and, when parts count when used, the note that the price becomes the
* part's price). A completion that USES parts gets their value as a line
* — booked automatically when parts count when used, or named as counted
* at purchase. Suggesting it into the cost field (2.53) double-counted a
* household that books its purchases (Discussion #104). */
private _renderCostSuggestion(L: string) {
if (this._cost.trim() !== "") return nothing;
const suggestion = this._partsCostSuggestion();
if (suggestion == null || suggestion <= 0) return nothing;
const amount = formatCost(suggestion, this.currencySymbol, L);
return html`<button
type="button"
class="cost-suggestion"
@click=${() => (this._cost = String(Math.round(suggestion * 100) / 100))}
>${t("cost_from_parts", L).replace("{amount}", amount)}</button>`;
const purchase = this._purchaseSuggestion();
if (this.restockDefault !== null) {
const hint = this.partsCostMode === "use"
? html`<div class="cost-note">${t("cost_purchase_use_hint", L)}</div>`
: nothing;
if (this._cost.trim() !== "" || purchase == null || purchase <= 0) return hint;
const amount = formatCost(purchase, this.currencySymbol, L);
return html`<button
type="button"
class="cost-suggestion"
@click=${() => (this._cost = String(Math.round(purchase * 100) / 100))}
>${t("cost_from_parts", L).replace("{amount}", amount)}</button>${hint}`;
}
const value = this._partsValue();
if (value == null || value <= 0) return nothing;
const amount = formatCost(value, this.currencySymbol, L);
const key = this.partsCostMode === "use" ? "cost_parts_booked" : "cost_parts_info";
return html`<div class="cost-note">${t(key, L).replace("{amount}", amount)}</div>`;
}
private _close(): void {
@@ -452,7 +472,7 @@ export class MaintenanceCompleteDialog extends LitElement {
${this.restockDefault !== null
? html`
<label class="field">
<span class="field-label">${t("restock_quantity_label", L)}</span>
<span class="field-label">${t("restock_quantity_label", L)}${this.restockPackage ? ` (× ${this.restockPackage})` : ""}</span>
<input type="number" step="0.01" min=${RESTOCK_QTY_RANGE[0]} max=${RESTOCK_QTY_RANGE[1]} class="field-input"
.value=${this._restockQty}
@input=${(e: Event) => (this._restockQty = (e.target as HTMLInputElement).value)} />
@@ -563,6 +583,12 @@ export class MaintenanceCompleteDialog extends LitElement {
font-weight: 600;
}
/* #104: one-click cost suggestion from parts — quiet link-style chip. */
.cost-note {
font-size: 12px;
color: var(--secondary-text-color);
margin-top: 4px;
line-height: 1.4;
}
.cost-suggestion {
align-self: flex-start;
margin-top: 4px;
@@ -1,8 +1,16 @@
/** Dialog for creating/editing a maintenance object. */
/** Dialog for creating, editing and replacing a maintenance object. */
import { LitElement, html, css, nothing } from "lit";
import { property, state } from "lit/decorators.js";
import type { HomeAssistant, MaintenanceObject, MaintenanceObjectResponse } from "../types";
/** What a device change did to the tasks' wiring (object/update + replace). */
export interface DeviceSwap {
moved: number;
unmatched: string[];
}
type ReplaceDevice = "keep" | "other" | "none";
import { t, langOf } from "../styles";
import { runWs } from "../helpers/ws-run";
@@ -32,6 +40,11 @@ export class MaintenanceObjectDialog extends LitElement {
@state() private _haDeviceId = "";
@state() private _parentEntryId = "";
@state() private _entryId: string | null = null; // null = create, string = update
// Replace (journey N1): the successor's device. The old unit's device is
// what the object links to now; a new unit usually is a new device.
@state() private _replacing = false;
@state() private _oldDeviceId = "";
@state() private _replaceDevice: ReplaceDevice = "keep";
private get _lang(): string {
return langOf(this.hass);
@@ -50,6 +63,22 @@ export class MaintenanceObjectDialog extends LitElement {
this._notes = "";
this._haDeviceId = "";
this._parentEntryId = "";
this._replacing = false;
this._error = "";
this._open = true;
}
/** Replace a worn-out object: the old one is archived in place, a
* successor carries its tasks and documents over. Asks for the name and
* for the new unit's device — keeping the old link silently made the
* successor watch the retired machine's sensors. */
public openReplace(entryId: string, obj: MaintenanceObject): void {
this._entryId = entryId;
this._replacing = true;
this._name = obj.name || "";
this._oldDeviceId = obj.ha_device_id || "";
this._haDeviceId = "";
this._replaceDevice = "keep";
this._error = "";
this._open = true;
}
@@ -67,10 +96,104 @@ export class MaintenanceObjectDialog extends LitElement {
this._notes = obj.notes || "";
this._haDeviceId = obj.ha_device_id || "";
this._parentEntryId = obj.parent_entry_id || "";
this._replacing = false;
this._error = "";
this._open = true;
}
private async _replace(): Promise<void> {
if (this._loading || !this._entryId) return;
const msg: Record<string, unknown> = {
type: "maintenance_supporter/object/replace",
entry_id: this._entryId,
name: this._name.trim() || null,
};
if (this._oldDeviceId) {
// "keep" sends nothing: the successor stays on the same device.
if (this._replaceDevice === "other") msg.ha_device_id = this._haDeviceId;
if (this._replaceDevice === "none") msg.ha_device_id = null;
} else if (this._haDeviceId) {
msg.ha_device_id = this._haDeviceId;
}
const res = await runWs<{ entry_id?: string; device_swap?: DeviceSwap }>(this, msg, {
busy: (b) => { this._loading = b; },
fallbackKey: "save_error",
onError: (m) => { this._error = m; },
});
if (res === undefined) return;
this._open = false;
this.dispatchEvent(new CustomEvent("object-replaced", {
detail: { entry_id: res?.entry_id, device_swap: res?.device_swap },
}));
}
private _deviceName(deviceId: string): string {
const devices = (this.hass as unknown as { devices?: Record<string, { name?: string | null; name_by_user?: string | null }> })?.devices;
const device = devices?.[deviceId];
return device?.name_by_user || device?.name || deviceId;
}
private _deviceSelector(label: string) {
return html`<ha-form
.hass=${this.hass}
.data=${{ device: this._haDeviceId || undefined }}
.schema=${[{ name: "device", selector: { device: {} } }]}
.computeLabel=${() => label}
@value-changed=${(e: CustomEvent) =>
(this._haDeviceId =
((e.detail.value as { device?: string })?.device as string) || "")}
></ha-form>`;
}
private _renderReplace() {
const L = this._lang;
const choice = (value: ReplaceDevice, label: string) => html`<label class="radio-row">
<input
type="radio"
name="replace-device"
.checked=${this._replaceDevice === value}
@change=${() => { this._replaceDevice = value; }}
/>
<span>${label}</span>
</label>`;
const blocked = this._replaceDevice === "other" && !this._haDeviceId;
return html`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${t("replace_object", L)}</div>
<div class="content">
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
<div class="hint">${t("replace_object_prompt", L)}</div>
<ms-textfield
label="${t("replace_name_label", L)}"
.value=${this._name}
@input=${(e: Event) => (this._name = (e.target as HTMLInputElement).value)}
></ms-textfield>
${this._oldDeviceId
? html`<div class="radio-group" role="radiogroup" aria-label=${t("replace_device_heading", L)}>
<div class="textarea-label">${t("replace_device_heading", L)}</div>
${choice("keep", t("replace_device_keep", L).replace("{device}", this._deviceName(this._oldDeviceId)))}
${this._replaceDevice === "keep" ? html`<div class="hint">${t("replace_device_keep_hint", L)}</div>` : nothing}
${choice("other", t("replace_device_other", L))}
${this._replaceDevice === "other"
? html`${this._deviceSelector(t("replace_device_pick", L))}
<div class="hint">${t("replace_device_other_hint", L)}</div>`
: nothing}
${choice("none", t("replace_device_none", L))}
</div>`
: this._deviceSelector(t("link_device_optional", L))}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel", L)}
</ha-button>
<ha-button @click=${this._replace} .disabled=${this._loading || blocked}>
${this._loading ? t("saving", L) : t("replace_object", L)}
</ha-button>
</div>
</ha-dialog>
`;
}
private async _save(): Promise<void> {
if (this._loading) return; // synchronous re-entry guard (double-click)
if (!this._name.trim()) return;
@@ -89,7 +212,7 @@ export class MaintenanceObjectDialog extends LitElement {
ha_device_id: this._haDeviceId || null,
parent_entry_id: this._parentEntryId || null,
};
const res = await runWs(
const res = await runWs<{ device_swap?: DeviceSwap }>(
this,
this._entryId
? { type: "maintenance_supporter/object/update", entry_id: this._entryId, ...fields }
@@ -98,7 +221,9 @@ export class MaintenanceObjectDialog extends LitElement {
);
if (res === undefined) return;
this._open = false;
this.dispatchEvent(new CustomEvent("object-saved"));
// A changed device moved the tasks' sensor links along — the panel says
// how many, and which could not be placed.
this.dispatchEvent(new CustomEvent("object-saved", { detail: { device_swap: res?.device_swap } }));
}
private _parentChoices(): MaintenanceObjectResponse[] {
@@ -111,6 +236,7 @@ export class MaintenanceObjectDialog extends LitElement {
render() {
if (!this._open) return html``;
if (this._replacing) return this._renderReplace();
const L = this._lang;
const title = this._entryId ? t("edit_object", L) : t("new_object", L);
return html`
@@ -275,6 +401,26 @@ export class MaintenanceObjectDialog extends LitElement {
color: var(--error-color, #f44336);
font-size: 13px;
}
.hint {
font-size: 13px;
color: var(--secondary-text-color);
line-height: 1.4;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.radio-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
cursor: pointer;
}
.radio-row input {
margin: 0;
}
`;
}
@@ -18,6 +18,7 @@ import { property, state } from "lit/decorators.js";
import { t, ensureLocale, langOf, formatCost, formatNumber, formatQty } from "../styles";
import { runWs } from "../helpers/ws-run";
import type { HomeAssistant, MaintenancePart } from "../types";
import { restockUnits, unitPrice } from "../helpers/parts-cost";
// Per-part document links (v2.26) — the task-documents component in part mode.
import "./task-documents";
@@ -34,6 +35,7 @@ interface PartForm {
stock: string;
reorder_threshold: string;
restock_quantity: string;
package_size: string;
auto_buy_task: boolean;
notes: string;
}
@@ -50,6 +52,7 @@ const EMPTY_FORM: PartForm = {
stock: "",
reorder_threshold: "",
restock_quantity: "",
package_size: "",
auto_buy_task: true,
notes: "",
};
@@ -111,6 +114,7 @@ export class MaintenancePartsSection extends LitElement {
stock: part.stock != null ? String(part.stock) : "",
reorder_threshold: part.reorder_threshold != null ? String(part.reorder_threshold) : "",
restock_quantity: part.restock_quantity != null ? String(part.restock_quantity) : "",
package_size: part.package_size != null ? String(part.package_size) : "",
auto_buy_task: !!part.auto_buy_task,
notes: part.notes || "",
};
@@ -131,6 +135,7 @@ export class MaintenancePartsSection extends LitElement {
stock: num(f.stock),
reorder_threshold: num(f.reorder_threshold),
restock_quantity: num(f.restock_quantity),
package_size: num(f.package_size),
auto_buy_task: f.auto_buy_task,
notes: f.notes.trim() || null,
};
@@ -221,6 +226,7 @@ export class MaintenancePartsSection extends LitElement {
? html`<span class="loc"><ha-icon icon="mdi:map-marker-outline"></ha-icon>${part.storage_location}</span>`
: nothing}
</div>
${part.notes ? html`<div class="part-notes">${part.notes}</div>` : nothing}
</div>
<ha-icon-button
title=${t("documents", L)}
@@ -254,7 +260,9 @@ export class MaintenancePartsSection extends LitElement {
@click=${() => {
this._restockFor = part.id;
this._restockInvalid = false;
this._restockQty = String(part.restock_quantity || 1);
// The stock counts units: one restock is its
// packages × package size (#98 follow-up).
this._restockQty = String(restockUnits(part));
}}
><ha-icon icon="mdi:plus-minus-variant"></ha-icon
></ha-icon-button>
@@ -315,7 +323,15 @@ export class MaintenancePartsSection extends LitElement {
${this._field(t("part_cost", L), "cost", { type: "number" })}
${this._field(t("part_stock", L), "stock", { type: "number" })}
${this._field(t("part_reorder_threshold", L), "reorder_threshold", { type: "number" })}
${this._field(t("part_restock_quantity", L), "restock_quantity", { type: "number" })}
${this._field(t("part_package_size", L), "package_size", { type: "number" })}
${this._field(
f.package_size.trim() ? t("part_restock_packages", L) : t("part_restock_quantity", L),
"restock_quantity",
{ type: "number" },
)}
${f.package_size.trim()
? html`<div class="form-hint">${t("part_package_hint", L)}</div>`
: nothing}
<label class="form-field checkbox">
<input
type="checkbox"
@@ -326,6 +342,20 @@ export class MaintenancePartsSection extends LitElement {
/>
<span>${t("part_auto_buy", L)}</span>
</label>
<!-- Documented and searched since the parts feature, but the form
never had a field — only the API and imports could set it
(audit 2026-09-28). -->
<label class="form-field notes">
<span>${t("notes_label", L)}</span>
<textarea
rows="2"
maxlength="500"
.value=${f.notes}
@input=${(e: Event) => {
this._editing = { ...this._editing!, notes: (e.target as HTMLTextAreaElement).value };
}}
></textarea>
</label>
</div>
<div class="form-actions">
<ha-button appearance="plain" @click=${() => (this._editing = null)}>${t("cancel", L)}</ha-button>
@@ -343,7 +373,8 @@ export class MaintenancePartsSection extends LitElement {
private _inventoryValue(): number | null {
let sum = 0, any = false;
for (const p of this.parts) {
const cost = typeof p.cost === "number" ? p.cost : null;
// Per unit: a package price spread over its contents (#98 follow-up).
const cost = unitPrice(p);
const stock = typeof p.stock === "number" ? p.stock : null;
if (cost !== null && stock !== null) { sum += cost * stock; any = true; }
}
@@ -475,6 +506,12 @@ export class MaintenancePartsSection extends LitElement {
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 8px 12px;
}
.form-hint {
grid-column: 1 / -1;
font-size: 12px;
color: var(--secondary-text-color);
line-height: 1.4;
}
.form-field {
display: flex;
flex-direction: column;
@@ -490,6 +527,25 @@ export class MaintenancePartsSection extends LitElement {
background: var(--card-background-color);
color: var(--primary-text-color);
}
.form-field.notes {
grid-column: 1 / -1;
}
.form-field textarea {
padding: 6px;
border: 1px solid var(--divider-color);
border-radius: 4px;
background: var(--card-background-color);
color: var(--primary-text-color);
font: inherit;
resize: vertical;
}
.part-notes {
font-size: 12px;
color: var(--secondary-text-color);
white-space: pre-line;
overflow-wrap: anywhere;
margin-top: 2px;
}
.form-field.checkbox {
flex-direction: row;
align-items: center;
@@ -9,7 +9,7 @@
import { LitElement, html, css, nothing } from "lit";
import { property, state } from "lit/decorators.js";
import type { HomeAssistant } from "../types";
import { t } from "../styles";
import { langOf, t } from "../styles";
import { downloadTextFile } from "../helpers/download";
interface QrResult {
@@ -43,7 +43,14 @@ function sanitizeFilename(s: string): string {
export class MaintenanceQrDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public lang = "en";
/** Set by the panel; the dashboard path (dialog-mount) leaves it empty, and
* the old "en" default then beat the user's language there (audit
* 2026-09-29) — _dialogLang falls back to hass.language instead. */
@property() public lang = "";
private get _dialogLang(): string {
return this.lang || langOf(this.hass);
}
@state() private _open = false;
@state() private _loading = false;
@@ -139,8 +146,8 @@ export class MaintenanceQrDialog extends LitElement {
const code = (err as Record<string, unknown>)?.code;
const msg = (err as Record<string, unknown>)?.message;
this._error = code === "no_url" || (typeof msg === "string" && msg.includes("No Home Assistant URL"))
? t("qr_error_no_url", this.lang)
: t("qr_error", this.lang);
? t("qr_error_no_url", this._dialogLang)
: t("qr_error", this._dialogLang);
} finally {
if (seq === this._generateSeq) this._loading = false;
}
@@ -175,7 +182,7 @@ export class MaintenanceQrDialog extends LitElement {
.join(" ");
const w = window.open("", "_blank", "width=600,height=500");
if (!w) return;
const L = this.lang || "en";
const L = this._dialogLang;
const safeTitle = escapeHtml(title);
const safeSub = escapeHtml(subtitle);
@@ -249,7 +256,7 @@ ${safeSub ? `<div class="sub">${safeSub}</div>` : ""}
render() {
if (!this._open) return html``;
const L = this.lang || this.hass?.language || "en";
const L = this._dialogLang;
const heading = this._taskName
? `${t("qr_code", L)}: ${this._objectName} — ${this._taskName}`
: `${t("qr_code", L)}: ${this._objectName}`;
@@ -113,6 +113,8 @@ interface SettingsResponse {
/** #173 follow-up: completion notifications — off | automatic | all. */
completed?: string;
extra_data?: string;
/** #44: default | object_name | task_name — was only in the Configure dialog. */
title_style?: string;
};
actions: {
complete_enabled: boolean;
@@ -132,6 +134,8 @@ interface SettingsResponse {
currency_symbol: string;
/** Decimal places for every displayed amount (0 = whole numbers). */
currency_decimals?: number;
/** #104: spare parts count when bought ("purchase") or used ("use"). */
parts_cost_mode?: string;
};
// v2.10.0 archive automation (panel-managed). Optional for forward-compat
// with a backend that predates the feature.
@@ -183,6 +187,9 @@ export class MaintenanceSettingsView extends LitElement {
@state() private _settings: SettingsResponse | null = null;
@state() private _loading = true;
@state() private _importCsv = "";
/** People an import's tasks pointed at that match nobody on this instance
* (by name) — their assignments were cleared; shown until the next import. */
@state() private _importUnmatched: string[] = [];
@state() private _importLoading = false;
@state() private _includeHistory = true;
@state() private _toast = "";
@@ -689,7 +696,6 @@ export class MaintenanceSettingsView extends LitElement {
// "groups" target also lands here as the closest match.
const items: { key: keyof AdvancedFeatures; settingKey: string; label: string; desc: string }[] = [
{ key: "adaptive", settingKey: "advanced_adaptive_visible", label: t("feat_adaptive", L), desc: t("feat_adaptive_desc", L) },
{ key: "predictions", settingKey: "advanced_predictions_visible", label: t("feat_predictions", L), desc: t("feat_predictions_desc", L) },
{ key: "seasonal", settingKey: "advanced_seasonal_visible", label: t("feat_seasonal", L), desc: t("feat_seasonal_desc", L) },
{ key: "environmental", settingKey: "advanced_environmental_visible", label: t("feat_environmental", L), desc: t("feat_environmental_desc", L) },
{ key: "budget", settingKey: "advanced_budget_visible", label: t("feat_budget", L), desc: t("feat_budget_desc", L) },
@@ -1056,6 +1062,15 @@ export class MaintenanceSettingsView extends LitElement {
</select>
</label>
<div class="setting-hint">${t("settings_currency_decimals_hint", L)}</div>
<label class="setting-row">
<span class="setting-label">${t("settings_parts_cost_mode", L)}</span>
<select class="parts-cost-mode" .value=${live(b.parts_cost_mode === "use" ? "use" : "purchase")}
@change=${(e: Event) => this._updateSetting("parts_cost_mode", (e.target as HTMLSelectElement).value)}>
<option value="purchase" ?selected=${b.parts_cost_mode !== "use"}>${t("settings_parts_cost_purchase", L)}</option>
<option value="use" ?selected=${b.parts_cost_mode === "use"}>${t("settings_parts_cost_use", L)}</option>
</select>
</label>
<div class="setting-hint">${t("settings_parts_cost_mode_hint", L)}</div>
<label class="setting-row">
<span class="setting-label">${t("settings_panel_enabled", L)}</span>
<input type="checkbox" .checked=${g.panel_enabled}
@@ -1093,7 +1108,7 @@ export class MaintenanceSettingsView extends LitElement {
<div class="setting-row">
<span class="setting-label">${t("test_notification", L)}</span>
<button class="ha-button secondary"
?disabled=${!g.notify_service || this._testingNotification}
?disabled=${(!g.notify_service && !this._settings?.notifications?.event_only) || this._testingNotification}
@click=${() => this._sendTestNotification()}>
${this._testingNotification ? t("testing", L) : t("send_test", L)}
</button>
@@ -1273,6 +1288,16 @@ export class MaintenanceSettingsView extends LitElement {
</select>
</label>
<div class="setting-hint">${t("settings_notify_completed_hint", L)}</div>
<label class="setting-row">
<span class="setting-label">${t("settings_title_style", L)}</span>
<select class="notify-title-style" .value=${live(n.title_style || "default")}
@change=${(e: Event) => this._updateSetting("notification_title_style", (e.target as HTMLSelectElement).value)}>
<option value="default">${t("title_style_default", L)}</option>
<option value="object_name">${t("title_style_object_name", L)}</option>
<option value="task_name">${t("title_style_task_name", L)}</option>
</select>
</label>
<div class="setting-hint">${t("settings_title_style_hint", L)}</div>
<div class="notify-rule">
<h4 style="margin: 16px 0 8px; font-size: 14px;">${t("settings_notify_rule", L)}</h4>
@@ -2015,6 +2040,11 @@ export class MaintenanceSettingsView extends LitElement {
${this._importLoading ? "…" : t("settings_import_btn", L)}
</button>
</div>
${this._importUnmatched.length
? html`<div class="import-notice">
${t("settings_import_unmatched_users", L).replace("{names}", this._importUnmatched.join(", "))}
</div>`
: nothing}
</div>
</div>
`;
@@ -2101,7 +2131,8 @@ export class MaintenanceSettingsView extends LitElement {
// (JSON `{`/`[` or YAML `version:`) goes to the structured importer,
// which parses JSON and YAML alike.
const isCsv = content.startsWith("object_name");
const result = await this._ws<{ created: number }>(
this._importUnmatched = [];
const result = await this._ws<{ created: number; unmatched_users?: string[] }>(
isCsv
? { type: "maintenance_supporter/csv/import", csv_content: content }
: { type: "maintenance_supporter/json/import", json_content: content },
@@ -2111,6 +2142,7 @@ export class MaintenanceSettingsView extends LitElement {
if (!result) return;
const count = result.created ?? 0;
this._showToast(t("settings_import_success", this._lang).replace("{count}", String(count)));
this._importUnmatched = Array.isArray(result.unmatched_users) ? result.unmatched_users : [];
this._importCsv = "";
this.dispatchEvent(new CustomEvent("settings-changed"));
}
@@ -2143,13 +2175,18 @@ export class MaintenanceSettingsView extends LitElement {
try {
const form = new FormData();
form.append("file", file, file.name);
const result = await postMultipart<{ blobs_written: number; documents_created: number }>(
this.hass, "/api/maintenance_supporter/documents/archive", form, "docs_archive_too_large",
const result = await postMultipart<{ blobs_written: number; documents_created: number; files_missing?: number }>(
this.hass, "/api/maintenance_supporter/documents/archive", form, "docs_archive_too_large", ["docs_archive_no_manifest"],
);
const restored = t("settings_docs_import_success", this._lang)
.replace("{blobs}", String(result.blobs_written ?? 0))
.replace("{docs}", String(result.documents_created ?? 0));
// Documents whose file the archive did not carry are not created —
// said, so a file left out when zipping again is noticed.
this._showToast(
t("settings_docs_import_success", this._lang)
.replace("{blobs}", String(result.blobs_written ?? 0))
.replace("{docs}", String(result.documents_created ?? 0))
result.files_missing
? `${restored} · ${t("settings_docs_import_missing", this._lang).replace("{n}", String(result.files_missing))}`
: restored,
);
this.dispatchEvent(new CustomEvent("settings-changed"));
} catch (e) {
@@ -2158,7 +2195,9 @@ export class MaintenanceSettingsView extends LitElement {
e instanceof Error && e.message === "docs_archive_too_large"
? t("docs_archive_too_large", this._lang)
.replace("{max}", `${formatNumber(DOCS_ARCHIVE_MAX_BYTES / (1024 * 1024), this._lang)} MB`)
: t("action_error", this._lang),
: e instanceof Error && e.message === "docs_archive_no_manifest"
? t("docs_archive_no_manifest", this._lang)
: t("action_error", this._lang),
);
}
input.value = "";
@@ -2168,6 +2207,12 @@ export class MaintenanceSettingsView extends LitElement {
// --- Styles ---
static styles = [personStyles, css`
.import-notice {
margin-top: 8px; padding: 8px 12px; border-radius: 8px;
background: rgba(255, 152, 0, 0.1);
border: 1px solid rgba(255, 152, 0, 0.35);
font-size: 13.5px; line-height: 1.45;
}
.bn-note {
display: flex; align-items: flex-start; gap: 10px;
margin: 6px 0 10px; padding: 10px 12px; border-radius: 8px;
@@ -2331,6 +2376,13 @@ export class MaintenanceSettingsView extends LitElement {
.settings-actions {
display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px;
}
/* Heading, hint and buttons stacked — as a row the hint landed between
the heading and the buttons, reading like a caption for neither. */
.docs-archive-block {
flex-direction: column;
align-items: flex-start;
}
.docs-archive-block h4, .docs-archive-block .section-desc { margin: 0; }
.settings-actions button {
padding: 8px 16px;
border-radius: 8px;
@@ -2443,7 +2495,9 @@ export class MaintenanceSettingsView extends LitElement {
}
.vac-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
/* The date fields carry a typing toggle and a clear button (2 × 36px)
next to the picker — 160px left the date itself cut to "11/1". */
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: 12px;
margin-bottom: 12px;
}
@@ -18,6 +18,7 @@ import {
EWA_ALPHA_RANGE,
PART_QTY_RANGE,
SCHEDULE_OFFSET_MAX_DAYS,
TRIGGER_DAYS_BEFORE_RANGE,
TRIGGER_FOR_MINUTES_RANGE,
TRIGGER_TARGET_CHANGES_RANGE,
WARNING_DAYS_RANGE,
@@ -39,7 +40,10 @@ const CALENDAR_KINDS = ["weekdays", "nth_weekday", "day_of_month", "calendar"];
// #168: kinds that produce a due DATE and therefore take a time of day
// (mirrors SCHEDULE_TIME_KINDS in config_flow_helpers.py).
const SCHEDULE_TIME_KINDS = ["time_based", "one_time", ...CALENDAR_KINDS];
const TRIGGER_TYPE_KEYS = ["threshold", "counter", "state_change", "runtime"];
const TRIGGER_TYPE_KEYS = ["threshold", "counter", "state_change", "runtime", "due_date"];
// "due_date" alone is the one-time task's date field — the trigger type
// carries its own label.
const triggerTypeLabel = (key: string, L: string): string => t(key === "due_date" ? "trigger_type_due_date" : key, L);
// The type selector additionally offers "compound" (a group of conditions
// joined by AND/OR); its per-condition sub-type is limited to the flat kinds.
const TRIGGER_TYPE_KEYS_WITH_COMPOUND = [...TRIGGER_TYPE_KEYS, "compound"];
@@ -68,6 +72,7 @@ interface CompoundConditionDraft {
targetChanges: string;
runtimeHours: string;
onStates: string;
daysBefore: string;
/** Original keys this editor has no fields for (attribute, baseline, ...).
* Spread back on save so a compound roundtrip never drops them (#103 class). */
carry: Partial<TriggerConfig>;
@@ -78,7 +83,7 @@ function emptyCondition(): CompoundConditionDraft {
entityIds: "", type: "threshold", attribute: "", above: "", below: "",
equals: "", notEquals: "", forMinutes: "0",
targetValue: "", deltaMode: false, fromState: "", toState: "",
targetChanges: "", runtimeHours: "", onStates: "", carry: {},
targetChanges: "", runtimeHours: "", onStates: "", daysBefore: "0", carry: {},
};
}
@@ -90,6 +95,7 @@ const MANAGED_CONDITION_KEYS = new Set([
"trigger_target_value", "trigger_delta_mode",
"trigger_from_state", "trigger_to_state", "trigger_target_changes",
"trigger_runtime_hours", "trigger_on_states",
"trigger_days_before",
]);
/** The per-type trigger fields BOTH editors own — the flat trigger form and
@@ -117,6 +123,7 @@ export function typeFieldsFromConfig(c: TriggerConfig): TriggerTypeFields {
targetChanges: c.trigger_target_changes?.toString() ?? "",
runtimeHours: c.trigger_runtime_hours?.toString() ?? "",
onStates: (c.trigger_on_states || []).join(", "),
daysBefore: c.trigger_days_before?.toString() ?? "0",
};
}
@@ -144,6 +151,9 @@ export function applyTypeFields(c: TriggerConfig, d: TriggerTypeFields): void {
const h = parseFloat(d.runtimeHours); if (!isNaN(h)) c.trigger_runtime_hours = h;
const on = (d.onStates || "").split(",").map((s) => s.trim()).filter(Boolean);
if (on.length > 0) c.trigger_on_states = on;
} else if (d.type === "due_date") {
const days = parseInt(d.daysBefore, 10);
c.trigger_days_before = isNaN(days) ? 0 : days;
}
}
@@ -184,6 +194,13 @@ export class MaintenanceTaskDialog extends LitElement {
@property({ type: Boolean, attribute: "checklists-enabled" }) public checklistsEnabled = false;
@property({ type: Boolean, attribute: "schedule-time-enabled" }) public scheduleTimeEnabled = false;
@property({ type: Boolean, attribute: "completion-actions-enabled" }) public completionActionsEnabled = false;
/** Advanced features (Settings → Advanced Features): the dialog offers what
* the panel then shows — with Adaptive off it let you switch adaptive
* scheduling on while the feedback question and every result stayed
* hidden (audit 2026-09-28). What a task already has stays visible. */
@property({ type: Boolean, attribute: "adaptive-feature" }) public adaptiveFeature = false;
@property({ type: Boolean, attribute: "seasonal-feature" }) public seasonalFeature = false;
@property({ type: Boolean, attribute: "environmental-feature" }) public environmentalFeature = false;
@property({ type: Number, attribute: "default-warning-days" }) public defaultWarningDays = 7;
/** The object's spare parts — offered as "consumes parts" checkboxes. */
@state() private parts: Array<{ id: string; name: string; unit?: string }> = [];
@@ -285,6 +302,7 @@ export class MaintenanceTaskDialog extends LitElement {
@state() private _triggerToState = "";
@state() private _triggerTargetChanges = "";
@state() private _triggerRuntimeHours = "";
@state() private _triggerDaysBefore = "0";
@state() private _triggerRuntimeMaxSession = "";
// Comma-separated "running" states for the runtime trigger (#103) —
// empty = the backend default ["on"]. Must roundtrip on edit: adopted
@@ -366,6 +384,13 @@ export class MaintenanceTaskDialog extends LitElement {
// so ha-form can drive the data fields when the service schema is known.
@state() private _actionService = "";
@state() private _actionTargetEntity = "";
/** The stored target as it came, and the entity the field started with.
* The field edits one entity, but the API and imports accept several
* entities, devices, areas, labels and floors — rebuilding the target
* from the field dropped them on every save, a rename included (audit
* 2026-09-28). An untouched field sends the stored target back verbatim. */
private _actionTargetStored: Record<string, unknown> | null = null;
private _actionTargetInitial = "";
/** 2.95: not when the task completes itself (a reset wired by discovery). */
@state() private _actionSkipAuto = false;
/** 2.95: the task already has an action (suggested setups wire counter
@@ -382,7 +407,7 @@ export class MaintenanceTaskDialog extends LitElement {
@state() private _qcNotes = "";
@state() private _qcCost = "";
@state() private _qcDuration = "";
@state() private _qcFeedback: "" | "needed" | "not_needed" = "";
@state() private _qcFeedback: "" | "needed" | "not_needed" | "not_sure" = "";
// Environmental entity (adaptive_config)
@state() private _environmentalEntity = "";
@@ -398,6 +423,9 @@ export class MaintenanceTaskDialog extends LitElement {
@state() private _adaptiveSeasonal = true;
@state() private _adaptivePrediction = true;
private _adaptiveInitial = "";
/** The task had adaptive scheduling on when the dialog opened — its
* section stays reachable even with the Adaptive feature off. */
private _adaptiveWasEnabled = false;
private _adaptiveSnapshot(): string {
return JSON.stringify([
@@ -544,6 +572,10 @@ export class MaintenanceTaskDialog extends LitElement {
this._actionService = oca.service;
const tgt = oca.target?.entity_id;
this._actionTargetEntity = Array.isArray(tgt) ? (tgt[0] || "") : (tgt || "");
this._actionTargetStored = oca.target && typeof oca.target === "object"
? JSON.parse(JSON.stringify(oca.target)) as Record<string, unknown>
: null;
this._actionTargetInitial = this._actionTargetEntity;
this._actionData = (oca.data && typeof oca.data === "object") ? { ...oca.data } : {};
this._actionDataJsonFallback = "";
this._actionSkipAuto = oca.skip_auto === true;
@@ -551,6 +583,8 @@ export class MaintenanceTaskDialog extends LitElement {
} else {
this._actionService = "";
this._actionTargetEntity = "";
this._actionTargetStored = null;
this._actionTargetInitial = "";
this._actionData = {};
this._actionDataJsonFallback = "";
this._actionSkipAuto = false;
@@ -560,7 +594,7 @@ export class MaintenanceTaskDialog extends LitElement {
this._qcNotes = qcd?.notes || "";
this._qcCost = qcd?.cost != null ? String(qcd.cost) : "";
this._qcDuration = qcd?.duration != null ? String(qcd.duration) : "";
this._qcFeedback = (qcd?.feedback as "needed" | "not_needed" | undefined) || "";
this._qcFeedback = (qcd?.feedback as "needed" | "not_needed" | "not_sure" | undefined) || "";
const ac: Partial<AdaptiveConfig> = task.adaptive_config || {};
this._environmentalEntity = ac.environmental_entity || "";
@@ -574,6 +608,7 @@ export class MaintenanceTaskDialog extends LitElement {
this._adaptiveSeasonal = ac.seasonal_enabled !== false;
this._adaptivePrediction = ac.sensor_prediction_enabled !== false;
this._adaptiveInitial = this._adaptiveSnapshot();
this._adaptiveWasEnabled = this._adaptiveEnabled;
if (task.trigger_config) {
const tc = task.trigger_config;
@@ -668,9 +703,12 @@ export class MaintenanceTaskDialog extends LitElement {
this._adaptiveSeasonal = true;
this._adaptivePrediction = true;
this._adaptiveInitial = this._adaptiveSnapshot();
this._adaptiveWasEnabled = false;
// v1.3.0
this._actionService = "";
this._actionTargetEntity = "";
this._actionTargetStored = null;
this._actionTargetInitial = "";
this._actionData = {};
this._actionDataJsonFallback = "";
this._actionSkipAuto = false;
@@ -703,6 +741,7 @@ export class MaintenanceTaskDialog extends LitElement {
targetChanges: this._triggerTargetChanges,
runtimeHours: this._triggerRuntimeHours,
onStates: this._triggerOnStates,
daysBefore: this._triggerDaysBefore,
};
}
@@ -721,6 +760,7 @@ export class MaintenanceTaskDialog extends LitElement {
this._triggerTargetChanges = f.targetChanges;
this._triggerRuntimeHours = f.runtimeHours;
this._triggerOnStates = f.onStates;
this._triggerDaysBefore = f.daysBefore;
}
private _resetTriggerFields(): void {
@@ -786,7 +826,7 @@ export class MaintenanceTaskDialog extends LitElement {
const svc = this._actionService.trim();
if (!svc || !/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(svc)) {
this._actionTestResult = "error";
this._actionTestError = "Invalid service format (expected 'domain.service')";
this._actionTestError = t("action_test_bad_format", this._lang);
setTimeout(() => { this._actionTestResult = ""; this._actionTestError = ""; }, 5000);
return;
}
@@ -795,7 +835,7 @@ export class MaintenanceTaskDialog extends LitElement {
// 2. Service exists?
if (!this.hass?.services?.[domain]?.[name]) {
this._actionTestResult = "error";
this._actionTestError = `Service "${svc}" is not registered in Home Assistant. Check spelling and that the integration providing it is loaded.`;
this._actionTestError = t("action_test_unknown_service", this._lang).replace("{service}", svc);
setTimeout(() => { this._actionTestResult = ""; this._actionTestError = ""; }, 8000);
return;
}
@@ -813,14 +853,19 @@ export class MaintenanceTaskDialog extends LitElement {
]);
if (entityDomain !== domain && !crossDomainServices.has(domain)) {
this._actionTestResult = "error";
this._actionTestError = `Service "${svc}" only works on ${domain}.* entities; entity "${tgt}" is in ${entityDomain}.* — pick a service that matches the entity domain (e.g. ${entityDomain}.${name})`;
this._actionTestError = t("action_test_domain_mismatch", this._lang)
.replace("{service}", svc)
.replace("{domain}", domain)
.replace("{entity}", tgt)
.replace("{entity_domain}", entityDomain)
.replace("{suggestion}", `${entityDomain}.${name}`);
setTimeout(() => { this._actionTestResult = ""; this._actionTestError = ""; }, 8000);
return;
}
// 5. Entity exists?
if (!this.hass.states?.[tgt]) {
this._actionTestResult = "error";
this._actionTestError = `Target entity "${tgt}" not found in Home Assistant — the entity may have been renamed or its integration removed.`;
this._actionTestError = t("action_test_entity_missing", this._lang).replace("{entity}", tgt);
setTimeout(() => { this._actionTestResult = ""; this._actionTestError = ""; }, 8000);
return;
}
@@ -868,6 +913,34 @@ export class MaintenanceTaskDialog extends LitElement {
}));
}
/** The stored target reaches beyond the one entity the field shows. */
private _actionTargetExtended(): boolean {
const st = this._actionTargetStored;
if (!st) return false;
return Object.entries(st).some(([key, v]) =>
key === "entity_id" ? Array.isArray(v) && v.length > 1 : Array.isArray(v) ? v.length > 0 : !!v);
}
/** Names of everything the stored target points at, for the hint. */
private _actionTargetSummary(): string {
const st = this._actionTargetStored || {};
const ids = (v: unknown): string[] =>
(Array.isArray(v) ? v : [v]).filter((s): s is string => typeof s === "string" && !!s);
const h = this.hass as unknown as {
states?: Record<string, { attributes?: { friendly_name?: string } }>;
devices?: Record<string, { name_by_user?: string | null; name?: string | null }>;
areas?: Record<string, { name?: string }>;
floors?: Record<string, { name?: string }>;
};
return [
...ids(st.entity_id).map((id) => h.states?.[id]?.attributes?.friendly_name || id),
...ids(st.device_id).map((id) => h.devices?.[id]?.name_by_user || h.devices?.[id]?.name || id),
...ids(st.area_id).map((id) => h.areas?.[id]?.name || id),
...ids(st.floor_id).map((id) => h.floors?.[id]?.name || id),
...ids(st.label_id),
].join(", ");
}
private _renderCompletionActionsSection(L: string) {
if (!this.completionActionsEnabled && !this._actionPresent) return nothing;
const schema = this._serviceSchema();
@@ -903,6 +976,9 @@ export class MaintenanceTaskDialog extends LitElement {
this._actionTargetEntity = v.target_entity || "";
}}
></ha-form>
${this._actionTargetExtended() && this._actionTargetEntity.trim() === this._actionTargetInitial
? html`<p class="field-help ca-target-more">${t("on_complete_action_target_more", L).replace("{targets}", this._actionTargetSummary())}</p>`
: nothing}
<p class="field-help ca-domain-hint">
${t("on_complete_action_target_hint", L)}
</p>
@@ -972,10 +1048,11 @@ export class MaintenanceTaskDialog extends LitElement {
></ms-textfield>
<select class="qc-feedback"
.value=${this._qcFeedback}
@change=${(e: Event) => { this._qcFeedback = (e.target as HTMLSelectElement).value as "" | "needed" | "not_needed"; }}>
@change=${(e: Event) => { this._qcFeedback = (e.target as HTMLSelectElement).value as "" | "needed" | "not_needed" | "not_sure"; }}>
<option value="">${t("quick_complete_defaults_feedback_none", L)}</option>
<option value="needed">${t("quick_complete_defaults_feedback_needed", L)}</option>
<option value="not_needed">${t("quick_complete_defaults_feedback_not_needed", L)}</option>
<option value="not_sure">${t("feedback_not_sure", L)}</option>
</select>
</details>` : nothing}
`;
@@ -1233,9 +1310,9 @@ export class MaintenanceTaskDialog extends LitElement {
.value=${s.unit || ""}
@input=${(e: Event) => this._patchReading(s.id, { unit: (e.target as HTMLInputElement).value })}
></ms-textfield>
<mwc-icon-button class="phase-remove reading-remove" @click=${() => (this._readings = this._readings.filter((x) => x.id !== s.id))}>
<ha-icon-button class="phase-remove reading-remove" @click=${() => (this._readings = this._readings.filter((x) => x.id !== s.id))}>
<ha-icon icon="mdi:delete-outline"></ha-icon>
</mwc-icon-button>
</ha-icon-button>
</div>
`)}
${this._readings.length < MAX_READING_SLOTS ? html`
@@ -1307,9 +1384,9 @@ export class MaintenanceTaskDialog extends LitElement {
@input=${(e: Event) => this._patchPhaseDef(d.id, { partQty: (e.target as HTMLInputElement).value })} />
` : nothing}
` : nothing}
<mwc-icon-button class="phase-remove" @click=${() => this._removePhaseDef(d.id)}>
<ha-icon-button class="phase-remove" @click=${() => this._removePhaseDef(d.id)}>
<ha-icon icon="mdi:delete-outline"></ha-icon>
</mwc-icon-button>
</ha-icon-button>
</div>
${this.checklistsEnabled ? html`
<textarea
@@ -1692,7 +1769,11 @@ export class MaintenanceTaskDialog extends LitElement {
if (svc && /^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(svc)) {
const action: Record<string, unknown> = { service: svc };
const tgt = this._actionTargetEntity.trim();
if (tgt) action.target = { entity_id: tgt };
if (this._actionTargetStored && tgt === this._actionTargetInitial) {
action.target = JSON.parse(JSON.stringify(this._actionTargetStored));
} else if (tgt) {
action.target = { entity_id: tgt };
}
const dataDict = this._buildActionData();
if (Object.keys(dataDict).length > 0) {
action.data = dataDict;
@@ -1741,7 +1822,7 @@ export class MaintenanceTaskDialog extends LitElement {
@change=${(e: Event) => (this._triggerType = (e.target as HTMLSelectElement).value as TriggerType)}
>
${TRIGGER_TYPE_KEYS_WITH_COMPOUND.map(
(key) => html`<option value=${key} ?selected=${key === this._triggerType}>${t(key, L)}</option>`
(key) => html`<option value=${key} ?selected=${key === this._triggerType}>${triggerTypeLabel(key, L)}</option>`
)}
</select>
</div>
@@ -1917,7 +1998,7 @@ export class MaintenanceTaskDialog extends LitElement {
@change=${(e: Event) => this._patchCondition(i, { type: (e.target as HTMLSelectElement).value as TriggerType })}
>
${TRIGGER_TYPE_KEYS.map(
(key) => html`<option value=${key} ?selected=${key === c.type}>${t(key, L)}</option>`
(key) => html`<option value=${key} ?selected=${key === c.type}>${triggerTypeLabel(key, L)}</option>`
)}
</select>
</div>
@@ -1990,6 +2071,7 @@ export class MaintenanceTaskDialog extends LitElement {
* adapt. Collapsed unless adaptive is already enabled. */
private _renderAdaptiveSection(L: string) {
if (this._scheduleType === "one_time" || this._scheduleType === "manual") return nothing;
if (!this.adaptiveFeature && !this._adaptiveWasEnabled) return nothing;
return html`
<details class="adaptive-section" ?open=${this._adaptiveEnabled}>
<summary>${t("adaptive_section_title", L)}</summary>
@@ -2025,14 +2107,14 @@ export class MaintenanceTaskDialog extends LitElement {
.value=${this._adaptiveAlpha}
@input=${(e: Event) => (this._adaptiveAlpha = (e.target as HTMLInputElement).value)}
></ms-textfield>
<label>
${this.seasonalFeature ? html`<label>
<input
type="checkbox"
.checked=${this._adaptiveSeasonal}
@change=${(e: Event) => (this._adaptiveSeasonal = (e.target as HTMLInputElement).checked)}
/>
${t("adaptive_seasonal_enabled", L)}
</label>
</label>` : nothing}
<label>
<input
type="checkbox"
@@ -2177,6 +2259,13 @@ export class MaintenanceTaskDialog extends LitElement {
})}
`;
}
if (c.type === "due_date") {
return html`
<ms-textfield label="${t("days_before", L)}" type="number" step="1"
min=${TRIGGER_DAYS_BEFORE_RANGE[0]} max=${TRIGGER_DAYS_BEFORE_RANGE[1]} .value=${c.daysBefore}
@input=${(e: Event) => this._patchCondition(i, { daysBefore: (e.target as HTMLInputElement).value })}></ms-textfield>
`;
}
return nothing;
}
@@ -2641,6 +2730,17 @@ export class MaintenanceTaskDialog extends LitElement {
).replace("{count}", String(n)),
);
parts.push(t("trigger_hint_state_now", L).replace("{value}", String(st.state)));
} else if (this._triggerType === "due_date") {
const days = parseInt(this._triggerDaysBefore, 10) || 0;
parts.push(
days === 0 ? t("trigger_hint_due_date_same_day", L)
: days === 1 ? t("trigger_hint_due_date_one", L)
: t("trigger_hint_due_date", L).replace("{days}", String(days)),
);
const reported = raw == null ? "" : String(raw);
if (reported && !isNaN(Date.parse(reported))) {
parts.push(t("trigger_hint_due_date_now", L).replace("{date}", formatDate(reported, L)));
}
}
if (!parts.length) return overlap;
return html`<div class="trigger-live-hint">${parts.join(" ")}</div>${overlap}`;
@@ -2788,6 +2888,20 @@ export class MaintenanceTaskDialog extends LitElement {
<div class="field-help">${t("runtime_on_states_help", L)}</div>
`;
}
if (this._triggerType === "due_date") {
return html`
<ms-textfield
label="${t("days_before", L)}"
type="number"
step="1"
min=${TRIGGER_DAYS_BEFORE_RANGE[0]}
max=${TRIGGER_DAYS_BEFORE_RANGE[1]}
.value=${this._triggerDaysBefore}
@input=${(e: Event) => (this._triggerDaysBefore = (e.target as HTMLInputElement).value)}
></ms-textfield>
<div class="field-help">${t("days_before_help", L)}</div>
`;
}
return nothing;
}
@@ -3086,7 +3200,7 @@ export class MaintenanceTaskDialog extends LitElement {
</div>` : nothing}
` : nothing}
${this._renderTriggerFields()}
${this._scheduleType === "sensor_based" ? html`
${this._scheduleType === "sensor_based" && (this.environmentalFeature || this._environmentalInitial) ? html`
${this._entityPickerFallback ? html`
<ms-textfield
label="${t("environmental_entity_optional", L)}"
@@ -3389,6 +3503,8 @@ export class MaintenanceTaskDialog extends LitElement {
font-size: 13px;
}
.phase-remove {
display: inline-flex;
--ha-icon-button-size: 36px;
--mdc-icon-button-size: 36px;
color: var(--secondary-text-color);
}
@@ -6,7 +6,8 @@
*
* • Quick info — name + status + next due + last performed + interval
* • Primary actions — Complete (existing dialog), Skip (inline), Reset (inline)
* • Secondary (admin) — Edit settings (existing dialog), QR Code, Delete
* • Secondary — QR Code, Postpone (inline), Snooze for everyone; Edit
* settings (existing dialog), Archive, Delete for writers
* • Footer — "Open in Maintenance Panel" deep-link for History/Statistics
*
* Mounted via dialog-mount.openTaskQuickActions(entryId, taskId).
@@ -16,16 +17,19 @@ import { LitElement, html, css, nothing } from "lit";
import { property, state } from "lit/decorators.js";
import { sharedStyles, t, formatDate, formatInterval, formatRecurrence, formatCost, formatDuration, currencySymbolOf, langOf, syncCurrencyDecimals} from "../styles";
import { runWs } from "../helpers/ws-run";
import { snoozedMessage, type SnoozeResult } from "../helpers/snooze";
import { focusModalShell, modalShellStyles, renderModalShell } from "../helpers/modal-shell";
import { isoDateLocal } from "../helpers/calendar-bucket";
import { buildCompleteDialogArgs } from "../helpers/complete-dialog-args";
import { phaseLabel } from "../helpers/phases";
import { renderNotesMarkdown } from "../helpers/notes-markdown";
import { buildHistoryEntryDraft } from "../helpers/history-draft";
import { readingSlotDelta } from "../helpers/reading-slots";
import { taskRef } from "../helpers/reference";
import { newestFirst, renderHistoryEntry } from "../renderers/history";
import { renderStatusBadge, statusColor } from "../renderers/status";
import { fetchSettingsOnce } from "../helpers/settings-cache";
import type { PartsCostMode } from "../helpers/parts-cost";
import { canWrite, NO_DELEGATION, type WriteAccess } from "../helpers/permissions";
import { confirmAction } from "../helpers/confirm";
import { ToastTimer } from "../helpers/toast";
@@ -65,12 +69,14 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
@state() private _error = "";
@state() private _showSkip = false;
@state() private _showReset = false;
@state() private _showPostpone = false;
@state() private _showDetails = false;
@state() private _showAdaptive = false;
@state() private _skipReason = "";
@state() private _resetDate = "";
@state() private _postponeDate = "";
@state() private _features: AdvancedFeatures = {
adaptive: false, predictions: false, seasonal: false, environmental: false,
adaptive: false, seasonal: false, environmental: false,
budget: false, groups: false, checklists: false, schedule_time: false,
completion_actions: false,
};
@@ -98,8 +104,10 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
this._error = "";
this._showSkip = false;
this._showReset = false;
this._showPostpone = false;
this._showAdaptive = false;
this._skipReason = "";
this._postponeDate = "";
// Local calendar date — toISOString() is UTC and prefills YESTERDAY for
// users east of UTC before their morning (bug audit 2026-08-22).
this._resetDate = isoDateLocal(new Date());
@@ -120,8 +128,12 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
this._access = settings.access;
this._currencySymbol = currencySymbolOf(settings.budget ?? undefined);
syncCurrencyDecimals(settings.budget ?? undefined);
this._partsCostMode = settings.partsCostMode;
}
/** #104: when spare parts count (the complete dialog explains them). */
@state() private _partsCostMode: PartsCostMode = "purchase";
public close(): void {
this._loadSeq++; // a load still in flight belongs to the closed dialog
this._open = false;
@@ -221,6 +233,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
// feedback exactly like the panel (DRY audit 2026-09-26).
features: this._features,
currencySymbol: this._currencySymbol,
partsCostMode: this._partsCostMode,
}),
);
if (ok) {
@@ -258,6 +271,43 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
}
}
/** Postpone and Snooze are household actions like Skip and Reset
* (helpers/permissions HOUSEHOLD_ACTIONS) — the panel's ⋮ menu offered
* them, the dashboard's dialog did not (audit 2026-09-29). */
private async _onPostponeConfirm(): Promise<void> {
if (!this._entryId || !this._taskId || !this._postponeDate) return;
const ok = await this._runWs({
type: "maintenance_supporter/task/postpone",
entry_id: this._entryId,
task_id: this._taskId,
until: this._postponeDate,
});
if (ok !== undefined) {
this._notifyChanged("postpone");
this.close();
}
}
private async _onSnooze(): Promise<void> {
if (!this._entryId || !this._taskId) return;
const ok = await this._runWs<SnoozeResult>({
type: "maintenance_supporter/task/snooze",
entry_id: this._entryId,
task_id: this._taskId,
});
if (ok !== undefined) {
this._notifyChanged("snooze");
// #193: the dialog closes, so Home Assistant's own toast says for how
// long (a card has no toast of its own).
this.dispatchEvent(new CustomEvent("hass-notification", {
detail: { message: snoozedMessage(ok, this._lang) },
bubbles: true,
composed: true,
}));
this.close();
}
}
private _onEdit(): void {
if (!this._entryId || !this._taskId) return;
import("../dialog-mount").then(({ openEditTaskDialog }) => {
@@ -365,7 +415,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
this._showToast(r?.recommended_interval
// The analyzer always works in DAYS (helpers/interval_analyzer.py), so
// the unit is pinned here rather than taken from the task's own unit.
? `${t("reanalyze_result", this._lang)}: ${formatInterval(r.recommended_interval, "days", this._lang)} (${r.data_points} pts)`
? `${t("reanalyze_result", this._lang)}: ${formatInterval(r.recommended_interval, "days", this._lang)} (${r.data_points} ${t("data_points", this._lang)})`
: t("reanalyze_insufficient_data", this._lang));
await this._loadTask();
}
@@ -517,6 +567,9 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
const L = this._lang;
const task = this._task;
const writer = canWrite(this.hass?.user, this._access);
// The server refuses to postpone an archived, disabled or paused task,
// and such a task sends no reminders to snooze.
const inert = !!task && (!!task.archived || task.enabled === false || task.status === "paused");
return renderModalShell(() => this.close(), html`
${task
@@ -550,6 +603,9 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
? html`<span><strong>${t("phase_current", L)}:</strong> ${phaseLabel(task)}</span>`
: nothing}
</div>
${task.notes
? html`<div class="notes-body">${renderNotesMarkdown(task.notes)}</div>`
: nothing}
</div>
${this._error
@@ -593,6 +649,28 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
</div>
</div>
`
: this._showPostpone
? html`
<div class="inline-form">
<label>${t("postpone_date_prompt", L)}</label>
<ms-date-field
kind="date"
.hass=${this.hass}
.lang=${L}
.value=${this._postponeDate}
@value-changed=${(e: CustomEvent) => { this._postponeDate = e.detail.value as string; }}
></ms-date-field>
<div class="inline-actions">
<button class="btn cancel" @click=${() => { this._showPostpone = false; }} ?disabled=${this._busy}>
${t("cancel", L)}
</button>
<button class="btn primary qa-postpone-confirm" @click=${this._onPostponeConfirm}
?disabled=${this._busy || !this._postponeDate}>
${t("postpone", L)}
</button>
</div>
</div>
`
: html`
<div class="actions primary-row">
<ha-button appearance="accent" variant="success" @click=${this._onComplete} .disabled=${this._busy}>
@@ -626,6 +704,17 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
<ha-icon slot="start" icon="mdi:qrcode"></ha-icon>
${t("qr_code", L)}
</ha-button>
${inert
? nothing
: html`<ha-button size="small" appearance="outlined" variant="neutral" class="qa-postpone"
@click=${() => { this._showPostpone = true; }} .disabled=${this._busy}>
<ha-icon slot="start" icon="mdi:calendar-arrow-right"></ha-icon>
${t("postpone", L)}…
</ha-button>
<ha-button size="small" appearance="outlined" variant="neutral" class="qa-snooze" @click=${this._onSnooze} .disabled=${this._busy}>
<ha-icon slot="start" icon="mdi:bell-sleep-outline"></ha-icon>
${t("snooze", L)}
</ha-button>`}
${writer
? html`<ha-button size="small" appearance="outlined" variant="neutral" class="qa-archive"
@click=${task.archived ? this._onUnarchive : this._onArchive}
@@ -706,6 +795,11 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
.actions.secondary-row {
padding-top: 8px; border-top: 1px solid var(--divider-color);
justify-content: flex-start;
/* 360 px: Edit + QR + Archive + Delete were 388 px in a 342 px row —
Delete sat past the dialog edge (audit 2026-09-29). Wrap instead;
Delete keeps its margin-left:auto on the second line. */
flex-wrap: wrap;
row-gap: 6px;
}
.actions.secondary-row .btn.danger,
.actions.secondary-row ha-button.danger {
@@ -734,6 +828,12 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
.btn.danger { color: var(--error-color); }
.btn ha-icon { --mdc-icon-size: 18px; }
.inline-form { display: flex; flex-direction: column; gap: 8px; }
.notes-body {
margin-top: 8px; padding: 8px; border-radius: 6px;
background: var(--secondary-background-color);
font-size: 13px; white-space: pre-wrap; word-break: break-word;
}
.notes-body ha-markdown { white-space: normal; }
.inline-form label { font-size: 13px; color: var(--secondary-text-color); }
.inline-form input {
padding: 8px; font-size: 14px;
@@ -46,7 +46,7 @@ import type { MaintenanceObjectQuickActionsDialog } from "./components/object-qu
import type { HomeAssistant, MaintenanceObject } from "./types";
import { ensureLocale, isLocaleLoaded, langOf, setProfilePrefs } from "./styles";
import { fillAndOpenCompleteDialog, type CompleteDialogArgs } from "./helpers/complete-dialog-args";
import { fetchSettingsOnce, invalidateSettingsCache } from "./helpers/settings-cache";
import { fetchSettingsOnce, invalidateSettingsCache, type SettingsCache } from "./helpers/settings-cache";
const OBJECT_DIALOG_TAG = "maintenance-object-dialog";
const TASK_DIALOG_TAG = "maintenance-task-dialog";
@@ -152,19 +152,12 @@ export function openCreateTaskDialog(
void (async () => {
const settings = await fetchSettingsOnce(hass);
const dlgFull = dlg as MaintenanceTaskDialog & {
checklistsEnabled: boolean;
scheduleTimeEnabled: boolean;
completionActionsEnabled: boolean;
defaultWarningDays: number;
openCreate: (
entryId: string,
objects?: Array<{ entry_id: string; object: { name: string } }>,
) => void;
};
dlgFull.checklistsEnabled = settings.features.checklists;
dlgFull.scheduleTimeEnabled = settings.features.schedule_time;
dlgFull.completionActionsEnabled = settings.features.completion_actions;
dlgFull.defaultWarningDays = settings.defaultWarningDays;
applyTaskDialogSettings(dlgFull, settings);
// openCreate NEEDS a target: a bare call left _entryId undefined and
// _objectChoices empty — no object picker, and save failed on the
// backend's required entry_id (bug audit 2026-08-22). Callers pass the
@@ -174,6 +167,18 @@ export function openCreateTaskDialog(
return true;
}
/** The settings the task dialog renders by — the same on both open paths
* (and the panel's, which binds them from its own copy). */
function applyTaskDialogSettings(dlg: MaintenanceTaskDialog, settings: SettingsCache): void {
dlg.checklistsEnabled = settings.features.checklists;
dlg.scheduleTimeEnabled = settings.features.schedule_time;
dlg.completionActionsEnabled = settings.features.completion_actions;
dlg.adaptiveFeature = settings.features.adaptive;
dlg.seasonalFeature = settings.features.seasonal;
dlg.environmentalFeature = settings.features.environmental;
dlg.defaultWarningDays = settings.defaultWarningDays;
}
export function openEditTaskDialog(
entryId: string,
taskId: string,
@@ -218,16 +223,9 @@ export function openEditTaskDialog(
return;
}
const dlgFull = dlg as MaintenanceTaskDialog & {
checklistsEnabled: boolean;
scheduleTimeEnabled: boolean;
completionActionsEnabled: boolean;
defaultWarningDays: number;
openEdit: (entryId: string, task: unknown) => Promise<void>;
};
dlgFull.checklistsEnabled = settings.features.checklists;
dlgFull.scheduleTimeEnabled = settings.features.schedule_time;
dlgFull.completionActionsEnabled = settings.features.completion_actions;
dlgFull.defaultWarningDays = settings.defaultWarningDays;
applyTaskDialogSettings(dlgFull, settings);
await dlgFull.openEdit(entryId, fullTask);
} catch (e) {
// eslint-disable-next-line no-console
@@ -14,7 +14,8 @@
* predict when a sensor will next fire.
*/
import type { MaintenanceObjectResponse } from "../types";
import type { HistoryEntry, MaintenanceObjectResponse } from "../types";
import { entrySpend } from "./parts-cost";
import { intervalSpanDays } from "./interval";
import { statusRank } from "../status-constants";
@@ -106,9 +107,10 @@ function addDaysIso(iso: string, days: number): string {
* non-null `cost` values). Note this differs from the panel KPI, which divides
* total_cost by times_performed — completions without a recorded cost are
* counted differently. This mean is used only for the calendar tooltip. */
function computeAvgCost(history: Array<{ cost?: number | null }> | undefined): number | null {
function computeAvgCost(history: ReadonlyArray<HistoryEntry> | undefined): number | null {
if (!history || history.length === 0) return null;
const costs = history.map((h) => h.cost).filter((c): c is number => typeof c === "number");
// What each entry counts (#104), not only the typed cost.
const costs = history.map((h) => entrySpend(h)).filter((c): c is number => c !== null);
if (costs.length === 0) return null;
return costs.reduce((a, b) => a + b, 0) / costs.length;
}
@@ -404,12 +406,12 @@ export function buildPastBuckets(
schedule_type: task.schedule_type,
interval_days: task.interval_days ?? null,
responsible_user_id: task.responsible_user_id ?? null,
avg_cost: typeof h.cost === "number" ? h.cost : null,
avg_cost: entrySpend(h),
adaptive_enabled: !!task.adaptive_config?.enabled,
prediction_confidence: null,
history_timestamp: h.timestamp,
history_type: evType,
history_cost: typeof h.cost === "number" ? h.cost : null,
history_cost: entrySpend(h),
history_notes: typeof h.notes === "string" ? h.notes : null,
history_duration: typeof h.duration === "number" ? h.duration : null,
});
@@ -36,6 +36,8 @@
"reset": "Reset",
"snooze": "Odložit",
"snoozed": "Odloženo",
"snoozed_for": "Připomínky ztlumeny na {hours} h — do {until}",
"snoozed_for_one": "Připomínky ztlumeny na hodinu — do {until}",
"cancel": "Zrušit",
"bulk_select": "Vybrat",
"bulk_select_all": "Vybrat vše",
@@ -61,7 +63,10 @@
"counter": "Čítač",
"state_change": "Změna stavu",
"runtime": "Doba běhu",
"trigger_type_due_date": "Datum ze senzoru",
"runtime_hours": "Cílová doba běhu (hodiny)",
"days_before": "Dní před datem",
"days_before_help": "Pro senzor, který hlásí, kdy je údržba splatná (datum nebo časové razítko). 0 = přímo v ten den. Vlastní tlačítko „hotovo“ zařízení posune datum a tím zruší spouštěč — nastav ho jako akci při dokončení.",
"target_value": "Cílová hodnota",
"target_changes": "Cílový počet změn",
"for_minutes": "Po dobu (minut)",
@@ -233,6 +238,8 @@
"model_optional": "Model (volitelné)",
"serial_number_optional": "Sériové číslo (volitelné)",
"serial_number_label": "S/N",
"object_replaces": "Předchůdce",
"object_replaced_by": "Nástupce",
"documentation_url_label": "Návod",
"object_notes_label": "Poznámky",
"sort_due_date": "Termín",
@@ -420,7 +427,7 @@
"qr_action_view": "Zobrazit informace o údržbě",
"qr_action_complete": "Označit údržbu jako dokončenou",
"qr_action_quick_complete": "Rychlé dokončení — bez dialogu",
"qr_print_quick_none": "Kódy rychlého dokončení vyžadují u úkolu výchozí hodnoty rychlého dokončení (dialog úkolu → Výchozí hodnoty rychlého dokončení) — žádný úkol vybraných objektů je zatím nemá.",
"qr_print_quick_none": "Kódy rychlého dokončení vyžadují u úkolu výchozí hodnoty rychlého dokončení (Nastavení → Pokročilé funkce → Akce při dokončení; dialog úkolu → Výchozí hodnoty rychlého dokončení) — žádný úkol vybraných objektů je zatím nemá.",
"qr_url_mode": "Typ odkazu",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Lokální (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Povolte nebo zakažte pokročilé funkce. Zakázání je skryje z UI, ale nesmaže data.",
"feat_adaptive": "Adaptivní plánování",
"feat_adaptive_desc": "Učte se optimální intervaly z historie údržby",
"feat_predictions": "Predikce senzorů",
"feat_predictions_desc": "Předpovídejte termíny spouštění z degradace senzoru",
"feat_seasonal": "Sezónní úpravy",
"feat_seasonal_desc": "Upravte intervaly podle sezónních vzorů",
"feat_environmental": "Korelace s prostředím",
@@ -669,6 +674,10 @@
"settings_currency": "Měna",
"settings_currency_decimals": "Desetinná místa částek",
"settings_currency_decimals_hint": "Kolik desetinných míst zobrazuje každá částka: KPI, rozpočty, náklady v seznamech a sestavách, rozpočtová upozornění. 0 = celá čísla.",
"settings_parts_cost_mode": "Díly se počítají jako výdaj",
"settings_parts_cost_purchase": "Při nákupu",
"settings_parts_cost_use": "Při spotřebě",
"settings_parts_cost_mode_hint": "Při nákupu: počítá se cena připomínky nákupu a díly spotřebované při práci se zobrazují jen informativně. Při spotřebě: každé dokončení zaúčtuje hodnotu spotřebovaných dílů a nákup nastaví cenu dílu, místo aby se počítal. Platí pro dokončení od teď — historie si ponechá původní zaúčtování.",
"settings_budget_monthly": "Měsíční rozpočet",
"settings_budget_yearly": "Roční rozpočet",
"settings_budget_alerts": "Rozpočtová upozornění",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Vložte sem obsah JSON nebo CSV…",
"settings_import_btn": "Importovat",
"settings_import_success": "{count} objektů úspěšně importováno.",
"settings_import_unmatched_users": "V této instanci nenalezeno: {names}. Jejich přiřazení úloh bylo odebráno — vytvořte v Home Assistantu osoby se stejnými jmény a importujte znovu, abyste je zachovali.",
"settings_export_success": "Export stažen.",
"settings_saved": "Nastavení uloženo.",
"settings_include_history": "Zahrnout historii",
@@ -746,7 +756,7 @@
"has_overdue": "Má úkoly po termínu",
"object": "Objekt",
"settings_panel_access": "Přístup k panelu",
"settings_panel_access_desc": "Administrátoři mají vždy plný přístup. Chcete-li delegovat vytváření, úpravy a mazání konkrétním uživatelům bez práv administrátora, zapněte tuto možnost a vyberte je níže — ostatní vidí pouze Dokončit a Přeskočit.",
"settings_panel_access_desc": "Administrátoři mají vždy plný přístup. Chcete-li delegovat vytváření, úpravy a mazání konkrétním uživatelům bez práv administrátora, zapněte tuto možnost a vyberte je níže — ostatní mají jen Dokončit, Přeskočit, Reset a Odložit.",
"settings_operator_write": "Povolit vybraným uživatelům vytvářet, upravovat a mazat",
"settings_operator_write_desc": "Vypnuto: obsah mohou měnit pouze administrátoři. Zapnuto: plný přístup získají i vybraní uživatelé níže.",
"no_non_admin_users": "Nenalezeni žádní uživatelé bez admin práv. Přidejte je v Nastavení → Lidé.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Testovat akci",
"on_complete_action_test_success": "Úspěch",
"on_complete_action_test_failed": "Selhalo",
"action_test_bad_format": "Neplatný formát služby (očekáváno 'domain.service')",
"action_test_unknown_service": "Služba „{service}“ není v Home Assistantu registrována. Zkontrolujte pravopis a zda je integrace, která ji poskytuje, načtená.",
"action_test_domain_mismatch": "Služba „{service}“ funguje jen s entitami {domain}.*; entita „{entity}“ patří do {entity_domain}.* — vyberte službu odpovídající doméně entity (např. {suggestion})",
"action_test_entity_missing": "Cílová entita „{entity}“ nebyla v Home Assistantu nalezena — mohla být přejmenována nebo mohla být odebrána její integrace.",
"quick_complete_defaults_title": "Výchozí hodnoty rychlého dokončení (pro QR skenů, volitelné)",
"quick_complete_defaults_desc": "Předvolené hodnoty pro QR rychlého dokončení. Bez nich QR otevře dialog.",
"quick_complete_defaults_notes": "Poznámky",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Spravovat přiřazení úkolů…",
"groups_open_panel": "Otevřít v panelu",
"on_complete_action_target_hint": "Poznámka: doména entity musí odpovídat službě — např. 'button.press' funguje jen na button.*, 'counter.increment' jen na counter.*, 'input_button.press' jen na input_button.* atd. Při neshodě akce tiše selže (HA zaznamená 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Tato akce má více cílů, než ukazuje toto pole: {targets}. Zůstanou zachovány — výběr jiné entity zde nahradí celý cíl.",
"on_complete_action_skip_auto": "Ne, když se úkol dokončí sám (jeho senzor se zotavil)",
"show_all_objects": "Zobrazit všechny objekty",
"show_all_tasks": "Vymazat filtr — zobrazit všechny úkoly",
@@ -844,6 +859,7 @@
"doc_too_large": "Soubor je příliš velký (max. 25 MB).",
"doc_upload_failed": "Nahrání selhalo.",
"docs_archive_too_large": "Archiv je příliš velký (max. {max}).",
"docs_archive_no_manifest": "Tento ZIP neobsahuje manifest.json, takže obnovení neví, ke kterému dokumentu soubor patří. Při opětovném zabalení ponechte manifest.json vedle složek.",
"completion_photos_optional": "Fotky z dokončení (volitelné)",
"completion_photos": "Fotky z dokončení",
"choose_photos": "Vybrat fotky",
@@ -904,6 +920,11 @@
"paused": "Pozastaveno",
"pause_object": "Pozastavit",
"resume_object": "Pokračovat",
"pause_task": "Pozastavit",
"resume_task": "Pokračovat",
"pause_task_prompt": "Zmrazí plán tohoto úkolu — nebude splatný ani nebude upozorňovat, dokud ho neobnovíš; obnovení začne nový cyklus od toho dne. Ostatní úkoly objektu běží dál. Volitelně datum automatického obnovení.",
"task_paused": "Úkol pozastaven",
"task_resumed": "Úkol obnoven — plán začíná znovu",
"pause_until_prompt": "Zmrazit plány tohoto objektu — nic není splatné a nic neupozorňuje, dokud nebude obnoven. Volitelně nastavte datum automatického obnovení.",
"pause_until_label": "Pokračovat dne (volitelné)",
"object_paused": "Objekt pozastaven",
@@ -913,7 +934,18 @@
"replace_object": "Nahradit…",
"replace_object_prompt": "Vyřadit tento objekt a vytvořit nástupce. Historie a náklady zůstávají archivované u starého; úkoly a dokumenty přecházejí na nový, počítadla začínají od nuly.",
"replace_name_label": "Název nástupce",
"replace_device_heading": "Zařízení v Home Assistant",
"replace_device_keep": "Stejné zařízení jako dosud ({device})",
"replace_device_keep_hint": "To je volba i tehdy, když nový kus v Home Assistant ještě není: jakmile bude, vyber ho v nastavení objektu jako zařízení — spouštěče senzorů se přesunou s ním.",
"replace_device_other": "Nový kus je jiné zařízení",
"replace_device_pick": "Zařízení nového kusu",
"replace_device_other_hint": "Spouštěče senzorů, akce po dokončení a nulování počítadla se přesunou na odpovídající entity tohoto zařízení.",
"replace_device_none": "Bez zařízení",
"object_replaced": "Objekt nahrazen — nástupce vytvořen",
"device_swap_moved": "Přesunuto vazeb na senzory na nové zařízení: {count}.",
"device_swap_moved_one": "Jedna vazba na senzor přesunuta na nové zařízení.",
"device_swap_unmatched": "Nepodařilo se přiřadit: {count} — zkontroluj spouštěče senzorů těchto úkolů.",
"device_swap_unmatched_one": "Jednu vazbu se nepodařilo přiřadit — zkontroluj spouštěč senzoru tohoto úkolu.",
"reading_unit_label": "Jednotka odečtu (např. kWh, m³)",
"reading_unit_help": "Zobrazí se vedle zaznamenané hodnoty při dokončení tohoto úkolu.",
"reading_value_label": "Hodnota odečtu",
@@ -1010,6 +1042,9 @@
"part_stock": "Zásoba",
"part_reorder_threshold": "Práh doobjednání",
"part_restock_quantity": "Množství doplnění",
"part_package_size": "Velikost balení (jednotky)",
"part_restock_packages": "Množství k doplnění (balení)",
"part_package_hint": "Kupuje se po baleních, spotřebovává po jednotkách: zásoba, spotřeba na úkol a hranice objednání počítají jednotky; množství k doplnění a cena platí za balení.",
"part_auto_buy": "Úkol nákupu při nízké zásobě",
"part_restock": "Upravit zásobu",
"parts_used_by": "Používá",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Nepodařilo se načíst díly tohoto objektu — možnosti spotřeby dílů nyní nejsou k dispozici.",
"settings_export_selection": "Omezit na vybrané objekty (volitelné)",
"settings_docs_archive": "Archiv dokumentů (se soubory)",
"settings_docs_archive_hint": "Exporty JSON/YAML/CSV obsahují jen nastavení. Tento ZIP obsahuje obsah nahraných souborů, aby obnovení bylo úplné.",
"settings_docs_archive_hint": "Exporty JSON a YAML uvádějí vaše dokumenty, ale neobsahují jejich soubory; CSV dokumenty vynechává. Tento ZIP obsahuje nahrané soubory — obnovte jej po importu JSON a nic nebude chybět.",
"settings_docs_export_btn": "Stáhnout ZIP dokumentů",
"settings_docs_import_btn": "Obnovit ZIP dokumentů",
"settings_docs_import_success": "Obnoveno: {blobs} souborů, {docs} dokumentů",
"settings_docs_import_missing": "Přeskočeno {n} — jejich soubory v ZIP chyběly",
"adopt_problem_button": "Převzít senzory problémů",
"adopt_problem_title": "Převzít senzory problémů",
"adopt_problem_hint": "Změní senzory problémů HA (chyby tiskárny, upozornění filtru, slabá baterie) na úkoly údržby, které se spustí, dokud je problém aktivní, a samy se dokončí, když se vyřeší.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Počítá spotřebu od posledního dokončení: splatné po +{target}; počítadlo se po každém dokončení restartuje.",
"trigger_hint_counter_abs": "Úkol bude splatný, jakmile senzor dosáhne {target}.",
"trigger_hint_runtime": "Úkol bude splatný po {hours} h nasčítaného provozu; počítadlo se po každém dokončení restartuje.",
"trigger_hint_due_date": "Úkol bude splatný {days} dní před datem, které hlásí senzor.",
"trigger_hint_due_date_one": "Úkol bude splatný jeden den před datem, které hlásí senzor.",
"trigger_hint_due_date_same_day": "Úkol bude splatný v den, který hlásí senzor.",
"trigger_hint_due_date_now": "Hlášené datum: {date}.",
"trigger_hint_state_change": "Úkol bude splatný po {count} změně/změnách stavu.",
"trigger_hint_state_change_to": "Úkol bude splatný po {count} změně/změnách na „{state}“.",
"trigger_hint_state_now": "Aktuální stav: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Připomenutí posílají jen úkoly odpovídající filtrům štítku/uživatele zvoleného zobrazení. Stav, řazení a seskupení zobrazení se zde ignorují.",
"settings_notify_completed": "Oznámení o dokončení",
"settings_notify_completed_hint": "Zpráva pro domácnost, když je úkol dokončen, s důvodem a tím, kdo to udělal. „Jen automatická“ hlásí pouze dokončení, která nikdo neudělal na místě: zotavení senzoru, odškrtnutí v nákupním seznamu, volání služby automatizací. Jen na službu domácnosti; tiché hodiny a denní limit platí.",
"settings_title_style": "Styl titulku oznámení",
"settings_title_style_hint": "Co se zobrazuje jako TITULEK oznámení. Výchozí zachovává text podle stavu (např. „Údržba po termínu!“). Název objektu / úkolu pomáhá, když telefon stohuje oznámení.",
"title_style_default": "Výchozí (titulek podle stavu)",
"title_style_object_name": "Název objektu jako titulek",
"title_style_task_name": "Název úkolu jako titulek",
"notify_completed_off": "Vypnuto",
"notify_completed_automatic": "Jen automatická dokončení",
"notify_completed_all": "Všechna dokončení",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON nebo YAML, Jinja povoleno; sloučí se do data volání notify (vaše klíče mají přednost). Proměnné: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Ověřte tlačítkem Odeslat test.",
"card_saved_view": "Uložené zobrazení",
"card_saved_view_none": "Žádné",
"card_saved_view_help": "Použije filtry stavu, uživatele a štítku zobrazení navíc k výše uvedeným filtrům. Řazení a seskupení zobrazení jsou nastavení panelu a na kartě se nepoužijí.",
"card_saved_view_help": "Použije filtry stavu, uživatele, štítku a priority zobrazení navíc k výše uvedeným filtrům. Řazení a seskupení zobrazení jsou nastavení panelu a na kartě se nepoužijí.",
"panel_card_tab": "Otevřít na kartě",
"panel_card_tab_default": "Zapamatovaná karta (jako v panelu)",
"panel_card_view": "Otevřít s uloženým pohledem",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Zpět na ikony",
"battery_fleet_forecast_overdue": "Předpovězené datum uplynulo — baterie stále hlásí dobrý stav. Pokud jste ji vyměnili, zaznamenejte výměnu; jinak byla předpověď mylná.",
"cost_from_parts": "Použít ≈ {amount} z dílů",
"cost_parts_booked": "Spotřebované díly ≈ {amount} se zaúčtují automaticky — zadej jen další náklady.",
"cost_parts_info": "Spotřebované díly ≈ {amount} — započítány při nákupu, ne tady.",
"cost_purchase_use_hint": "Zaplacená cena se stane cenou dílu — díly se počítají při spotřebě.",
"history_parts_counted": "Díly: {amount}",
"history_parts_counted_hint": "Hodnota spotřebovaných dílů — započtena jako výdaj",
"history_parts_info": "Díly ≈ {amount}",
"history_parts_info_hint": "Hodnota spotřebovaných dílů — jen pro informaci; peníze se započetly při nákupu",
"history_purchase_stock": "Koupeno za {amount} (počítá se při spotřebě)",
"dismiss": "Skrýt",
"gs_label": "Začínáme — tyto tipy zmizí, jak vaše nastavení poroste",
"gs_setups_chip": "Navrhovaná nastavení: nalezeno {n} zařízení s předpřipravenými spouštěči",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Dva týdny (14 dní)",
"cal_editor_window_month": "Měsíc (30 dní, výchozí)",
"cal_editor_window_year": "Rok (365 dní, prázdné dny skryty)",
"cal_editor_window_past_30": "Posledních 30 dní (historie)",
"cal_editor_window_past_90": "Posledních 90 dní (historie)",
"cal_editor_show_chips": "Zobrazit přepínače okna v kartě",
"cal_editor_chips_hint": "Skryjte přepínače, pokud je karta ve strategickém pohledu, který už slouží jako výběr okna.",
"cal_editor_show_user_filter": "Zobrazit filtr uživatele",
@@ -38,6 +38,8 @@
"reset": "Nulstil",
"snooze": "Udsæt",
"snoozed": "Udsat",
"snoozed_for": "Påmindelser slået fra i {hours} timer — indtil {until}",
"snoozed_for_one": "Påmindelser slået fra i en time — indtil {until}",
"cancel": "Annuller",
"bulk_select": "Vælg",
"bulk_select_all": "Vælg alle",
@@ -63,7 +65,10 @@
"counter": "Tæller",
"state_change": "Tilstandsændring",
"runtime": "Driftstid",
"trigger_type_due_date": "Dato fra en sensor",
"runtime_hours": "Måldriftstid (timer)",
"days_before": "Dage før datoen",
"days_before_help": "Til en sensor, der rapporterer, hvornår vedligeholdelsen forfalder (dato eller tidsstempel). 0 = på selve datoen. Enhedens egen \"færdig\"-knap flytter datoen frem og ophæver dermed udløseren — gør den til handlingen ved afslutning.",
"target_value": "Målværdi",
"target_changes": "Antal målændringer",
"for_minutes": "I (minutter)",
@@ -235,6 +240,8 @@
"model_optional": "Model (valgfrit)",
"serial_number_optional": "Serienummer (valgfrit)",
"serial_number_label": "S/N",
"object_replaces": "Erstatter",
"object_replaced_by": "Erstattet af",
"documentation_url_label": "Manual",
"object_notes_label": "Noter",
"sort_due_date": "Forfaldsdato",
@@ -422,7 +429,7 @@
"qr_action_view": "Vis vedligeholdelsesoplysninger",
"qr_action_complete": "Marker vedligeholdelse som fuldført",
"qr_action_quick_complete": "Hurtig-fuldfør — uden dialog",
"qr_print_quick_none": "Hurtig-fuldfør-koder kræver standardværdier for hurtig-fuldfør på en opgave (opgavedialogen → Standardværdier for hurtig-fuldfør) — ingen opgave i de valgte objekter har nogen endnu.",
"qr_print_quick_none": "Hurtig-fuldfør-koder kræver standardværdier for hurtig-fuldfør på en opgave (Indstillinger → Avancerede funktioner → Fuldførelseshandlinger; opgavedialogen → Standardværdier for hurtig-fuldfør) — ingen opgave i de valgte objekter har nogen endnu.",
"qr_url_mode": "Linktype",
"qr_mode_companion": "Companion-app",
"qr_mode_local": "Lokal (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "Aktiver eller deaktiver avancerede funktioner. Deaktivering skjuler dem fra brugerfladen, men sletter ikke data.",
"feat_adaptive": "Adaptiv planlægning",
"feat_adaptive_desc": "Lær optimale intervaller fra vedligeholdelseshistorik",
"feat_predictions": "Sensorforudsigelser",
"feat_predictions_desc": "Forudsig udløsningsdatoer fra sensornedbrydning",
"feat_seasonal": "Sæsonjusteringer",
"feat_seasonal_desc": "Juster intervaller baseret på sæsonmønstre",
"feat_environmental": "Miljøkorrelation",
@@ -671,6 +676,10 @@
"settings_currency": "Valuta",
"settings_currency_decimals": "Decimaler for beløb",
"settings_currency_decimals_hint": "Hvor mange decimaler hvert beløb viser: nøgletal, budgetter, omkostninger i lister og rapporter, budgetadvarsler. 0 = hele tal.",
"settings_parts_cost_mode": "Reservedele tæller som udgift",
"settings_parts_cost_purchase": "Ved køb",
"settings_parts_cost_use": "Ved forbrug",
"settings_parts_cost_mode_hint": "Ved køb: prisen på en købspåmindelse tæller, og de dele et job bruger vises som information. Ved forbrug: hver afslutning bogfører værdien af de brugte dele, og et køb sætter delens pris i stedet for at tælle. Gælder afslutninger fra nu af — historikken beholder, hvordan den blev bogført.",
"settings_budget_monthly": "Månedligt budget",
"settings_budget_yearly": "Årligt budget",
"settings_budget_alerts": "Budgetadvarsler",
@@ -683,6 +692,7 @@
"settings_import_placeholder": "Indsæt JSON- eller CSV-indhold her…",
"settings_import_btn": "Importer",
"settings_import_success": "{count} objekter importeret.",
"settings_import_unmatched_users": "Ikke fundet på denne instans: {names}. Deres opgavetildelinger blev fjernet — opret personer med de samme navne i Home Assistant, og importér igen for at beholde dem.",
"settings_export_success": "Eksport downloadet.",
"settings_saved": "Indstilling gemt.",
"settings_include_history": "Inkluder historik",
@@ -771,7 +781,7 @@
"has_overdue": "Har forfaldne opgaver",
"object": "Objekt",
"settings_panel_access": "Paneladgang",
"settings_panel_access_desc": "Administratorer har altid fuld adgang. For at delegere oprettelse, redigering og sletning til bestemte ikke-administratorer skal du slå dette til og vælge dem nedenfor — alle andre ser kun Fuldfør og Spring over.",
"settings_panel_access_desc": "Administratorer har altid fuld adgang. For at delegere oprettelse, redigering og sletning til bestemte ikke-administratorer skal du slå dette til og vælge dem nedenfor — alle andre har kun Fuldfør, Spring over, Nulstil, Udskyd og Udsæt.",
"settings_operator_write": "Tillad valgte brugere at oprette, redigere og slette",
"settings_operator_write_desc": "Fra: kun administratorer kan ændre indhold. Til: de valgte brugere nedenfor får også fuld adgang.",
"no_non_admin_users": "Ingen ikke-administratorbrugere fundet. Tilføj nogle under Indstillinger → Personer.",
@@ -782,11 +792,16 @@
"on_complete_action_desc": "Kalder en HA-tjeneste, når opgaven fuldføres — f.eks. nulstil en tæller på enheden.",
"on_complete_action_target": "Målenhed",
"on_complete_action_target_hint": "Bemærk: enhedens domæne skal matche tjenesten — f.eks. virker 'button.press' kun på button.*, 'counter.increment' kun på counter.*, 'input_button.press' kun på input_button.* osv. Ved et misforhold mislykkes handlingen lydløst (HA logger 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Denne handling har flere mål, end feltet viser: {targets}. De bevares, som de er — vælger du en anden entitet her, erstatter den hele målet.",
"on_complete_action_skip_auto": "Ikke når opgaven fuldføres af sig selv (dens sensor er normal igen)",
"on_complete_action_data": "Data (JSON, valgfrit)",
"on_complete_action_test": "Valider konfiguration",
"on_complete_action_test_success": "✓ Konfiguration gyldig (handlingen udløses kun ved fuldførelse af opgaven)",
"on_complete_action_test_failed": "Mislykkedes",
"action_test_bad_format": "Ugyldigt tjenesteformat (forventet 'domain.service')",
"action_test_unknown_service": "Tjenesten \"{service}\" er ikke registreret i Home Assistant. Kontrollér stavemåden, og at integrationen, der leverer den, er indlæst.",
"action_test_domain_mismatch": "Tjenesten \"{service}\" virker kun på {domain}.*-entiteter; entiteten \"{entity}\" hører til {entity_domain}.* — vælg en tjeneste, der passer til entitetens domæne (f.eks. {suggestion})",
"action_test_entity_missing": "Målentiteten \"{entity}\" blev ikke fundet i Home Assistant — den er måske omdøbt, eller dens integration er fjernet.",
"quick_complete_defaults_title": "Standardværdier for hurtig-fuldfør (til QR-scanninger, valgfrit)",
"quick_complete_defaults_desc": "Forudindstillede værdier til hurtig-fuldfør-QR-scanninger. Uden disse åbner QR-koden fuldfør-dialogen.",
"quick_complete_defaults_notes": "Noter",
@@ -844,6 +859,7 @@
"doc_too_large": "Filen er for stor (maks. 25 MB).",
"doc_upload_failed": "Upload mislykkedes.",
"docs_archive_too_large": "Arkivet er for stort (maks. {max}).",
"docs_archive_no_manifest": "Denne ZIP har ingen manifest.json, så gendannelsen ved ikke, hvilket dokument en fil hører til. Lad manifest.json ligge ved siden af mapperne, når du zipper igen.",
"completion_photos_optional": "Fotos ved fuldførelse (valgfrit)",
"completion_photos": "Fotos ved fuldførelse",
"choose_photos": "Vælg fotos",
@@ -904,6 +920,11 @@
"paused": "Sat på pause",
"pause_object": "Sæt på pause",
"resume_object": "Genoptag",
"pause_task": "Sæt på pause",
"resume_task": "Genoptag",
"pause_task_prompt": "Fryser opgavens plan — den forfalder ikke og giver ikke besked, før du genoptager den, og genoptagelse starter en ny cyklus fra den dag. Objektets andre opgaver kører videre. Valgfrit: en dato, hvor den genoptages af sig selv.",
"task_paused": "Opgave sat på pause",
"task_resumed": "Opgave genoptaget — planen starter forfra",
"pause_until_prompt": "Frys objektets planer — intet forfalder og intet notificerer, før det genoptages. Angiv eventuelt en dato for automatisk genoptagelse.",
"pause_until_label": "Genoptag den (valgfrit)",
"object_paused": "Objekt sat på pause",
@@ -913,7 +934,18 @@
"replace_object": "Udskift…",
"replace_object_prompt": "Pensioner dette objekt og opret en efterfølger. Historik og omkostninger forbliver arkiveret på det gamle; opgaver og dokumenter følger med til det nye, tællere starter forfra.",
"replace_name_label": "Efterfølgerens navn",
"replace_device_heading": "Enhed i Home Assistant",
"replace_device_keep": "Samme enhed som før ({device})",
"replace_device_keep_hint": "Vælg også dette, hvis den nye enhed ikke er i Home Assistant endnu: når den er, så vælg den som objektets enhed i indstillingerne — sensortriggerne flytter med.",
"replace_device_other": "Den nye enhed er en anden enhed",
"replace_device_pick": "Den nye enheds enhed i Home Assistant",
"replace_device_other_hint": "Sensortriggere, afslutningshandlinger og nulstilling af tælleren flytter til de tilsvarende entiteter på den enhed.",
"replace_device_none": "Ingen enhed",
"object_replaced": "Objekt udskiftet — efterfølger oprettet",
"device_swap_moved": "{count} sensorforbindelser flyttet til den nye enhed.",
"device_swap_moved_one": "Én sensorforbindelse flyttet til den nye enhed.",
"device_swap_unmatched": "{count} kunne ikke matches — tjek de opgavers sensortriggere.",
"device_swap_unmatched_one": "Én kunne ikke matches — tjek opgavens sensortrigger.",
"reading_unit_label": "Aflæsningsenhed (f.eks. kWh, m³)",
"reading_unit_help": "Vises ved siden af den registrerede værdi, når opgaven fuldføres.",
"reading_value_label": "Aflæst værdi",
@@ -1010,6 +1042,9 @@
"part_stock": "Lager",
"part_reorder_threshold": "Genbestillingsgrænse",
"part_restock_quantity": "Genopfyldningsmængde",
"part_package_size": "Pakningsstørrelse (enheder)",
"part_restock_packages": "Genopfyldningsmængde (pakninger)",
"part_package_hint": "Købes i pakninger, bruges i enheden: lager, forbrug pr. opgave og genbestillingsgrænse tæller enheder; genopfyldningsmængde og pris gælder pr. pakning.",
"part_auto_buy": "Købsopgave ved lav beholdning",
"part_restock": "Justér lager",
"parts_used_by": "Bruges af",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Kunne ikke indlæse objektets reservedele — forbrugsindstillingerne er ikke tilgængelige lige nu.",
"settings_export_selection": "Begræns til valgte objekter (valgfrit)",
"settings_docs_archive": "Dokumentarkiv (med filer)",
"settings_docs_archive_hint": "JSON/YAML/CSV-eksporter indeholder kun indstillinger. Denne ZIP indeholder de uploadede filers indhold, så en gendannelse er fuldstændig.",
"settings_docs_archive_hint": "JSON- og YAML-eksporterne viser dine dokumenter, men ikke deres filer; CSV udelader dokumenter. Denne ZIP indeholder de uploadede filer – gendan den efter import af JSON, så mangler intet.",
"settings_docs_export_btn": "Download dokument-ZIP",
"settings_docs_import_btn": "Gendan dokument-ZIP",
"settings_docs_import_success": "Gendannet: {blobs} filer, {docs} dokumenter",
"settings_docs_import_missing": "{n} sprunget over — deres filer var ikke i ZIP-filen",
"adopt_problem_button": "Adopter problemsensorer",
"adopt_problem_title": "Adopter problemsensorer",
"adopt_problem_hint": "Gør HA-problemsensorer (printerfejl, filteradvarsler, lavt batteri) til vedligeholdelsesopgaver, der udløses, mens problemet er aktivt, og afsluttes selv, når det er løst.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Tæller forbruget siden sidste fuldførelse: forfalder efter +{target}; tælleren starter forfra efter hver fuldførelse.",
"trigger_hint_counter_abs": "Opgaven forfalder, når sensoren når {target}.",
"trigger_hint_runtime": "Opgaven forfalder efter {hours} t akkumuleret driftstid; tælleren starter forfra efter hver fuldførelse.",
"trigger_hint_due_date": "Opgaven forfalder {days} dage før den dato, sensoren rapporterer.",
"trigger_hint_due_date_one": "Opgaven forfalder én dag før den dato, sensoren rapporterer.",
"trigger_hint_due_date_same_day": "Opgaven forfalder på den dato, sensoren rapporterer.",
"trigger_hint_due_date_now": "Rapporteret dato: {date}.",
"trigger_hint_state_change": "Opgaven forfalder efter {count} tilstandsskift.",
"trigger_hint_state_change_to": "Opgaven forfalder efter {count} skift til ”{state}”.",
"trigger_hint_state_now": "Aktuel tilstand: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Kun opgaver, der matcher den valgte visnings etiket-/brugerfiltre, sender påmindelser. Visningens status, sortering og gruppering ignoreres her.",
"settings_notify_completed": "Notifikationer om udførelse",
"settings_notify_completed_hint": "En besked til husstanden, når en opgave er udført, med årsag og hvem der gjorde det. \"Kun automatiske\" melder kun udførelser, som ingen lavede på stedet: sensorgendannelse, afkrydsning på indkøbslisten, et servicekald fra en automatisering. Kun til husstandstjenesten; stilletimer og dagsgrænsen gælder.",
"settings_title_style": "Stil for notifikationstitel",
"settings_title_style_hint": "Hvad der vises som notifikationens TITEL. Standard bevarer teksten pr. status (f.eks. 'Vedligeholdelse overskredet!'). Objektnavn / Opgavenavn hjælper, når telefonnotifikationer stables.",
"title_style_default": "Standard (titel pr. status)",
"title_style_object_name": "Objektnavn som titel",
"title_style_task_name": "Opgavenavn som titel",
"notify_completed_off": "Fra",
"notify_completed_automatic": "Kun automatiske udførelser",
"notify_completed_all": "Alle udførelser",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tilladt; flettes ind i notify-kaldets data (dine nøgler vinder). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test med knappen Send test.",
"card_saved_view": "Gemt visning",
"card_saved_view_none": "Ingen",
"card_saved_view_help": "Anvender visningens status-, bruger- og etiketfiltre oven på filtrene ovenfor. Visningens sortering og gruppering er panelvisningsindstillinger og anvendes ikke på kortet.",
"card_saved_view_help": "Anvender visningens status-, bruger-, etiket- og prioritetsfiltre oven på filtrene ovenfor. Visningens sortering og gruppering er panelvisningsindstillinger og anvendes ikke på kortet.",
"panel_card_tab": "Åbn på fanen",
"panel_card_tab_default": "Husket fane (som i panelet)",
"panel_card_view": "Åbn med gemt visning",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Tilbage til ikoner",
"battery_fleet_forecast_overdue": "Forudsagt dato er overskredet — batteriet melder stadig god tilstand. Hvis du har skiftet det, registrér udskiftningen; ellers ramte prognosen forbi.",
"cost_from_parts": "Brug ≈ {amount} fra dele",
"cost_parts_booked": "Brugte dele ≈ {amount} bogføres automatisk — indtast kun ekstra omkostninger.",
"cost_parts_info": "Brugte dele ≈ {amount} — talt med ved købet, ikke her.",
"cost_purchase_use_hint": "Den betalte pris bliver delens pris — reservedele tæller, når de bruges.",
"history_parts_counted": "Dele: {amount}",
"history_parts_counted_hint": "Værdien af de brugte dele — talt som udgift",
"history_parts_info": "Dele ≈ {amount}",
"history_parts_info_hint": "Værdien af de brugte dele — kun til information; pengene talte ved købet",
"history_purchase_stock": "Købt for {amount} (tæller ved forbrug)",
"dismiss": "Afvis",
"gs_label": "Kom godt i gang — disse tips forsvinder, efterhånden som opsætningen vokser",
"gs_setups_chip": "Foreslåede opsætninger fandt {n} enheder med forudindstillede udløsere",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "To uger (14 dage)",
"cal_editor_window_month": "Måned (30 dage, standard)",
"cal_editor_window_year": "År (365 dage, tomme dage skjult)",
"cal_editor_window_past_30": "Seneste 30 dage (historik)",
"cal_editor_window_past_90": "Seneste 90 dage (historik)",
"cal_editor_show_chips": "Vis vinduechips i kortet",
"cal_editor_chips_hint": "Skjul chips, når kortet er indlejret i en strategivisning, der allerede fungerer som vinduesvælger.",
"cal_editor_show_user_filter": "Vis brugerfilter",
@@ -38,6 +38,8 @@
"reset": "Zurücksetzen",
"snooze": "Schlummern",
"snoozed": "Schlummert",
"snoozed_for": "Erinnerungen für {hours} Stunden stumm — bis {until}",
"snoozed_for_one": "Erinnerungen für eine Stunde stumm — bis {until}",
"cancel": "Abbrechen",
"bulk_select": "Auswählen",
"bulk_select_all": "Alle auswählen",
@@ -63,7 +65,10 @@
"counter": "Zähler",
"state_change": "Zustandsänderung",
"runtime": "Laufzeit",
"trigger_type_due_date": "Datum von einem Sensor",
"runtime_hours": "Ziel-Laufzeit (Stunden)",
"days_before": "Tage vor dem Datum",
"days_before_help": "Für einen Sensor, der meldet, wann die Wartung fällig ist (Datum oder Zeitstempel). 0 = am Datum selbst. Der eigene „Erledigt“-Knopf des Geräts schiebt das Datum weiter und löst damit den Trigger — lege ihn als Aktion beim Erledigen an.",
"target_value": "Zielwert",
"target_changes": "Ziel-Änderungen",
"for_minutes": "Für (Minuten)",
@@ -235,6 +240,8 @@
"model_optional": "Modell (optional)",
"serial_number_optional": "Seriennummer (optional)",
"serial_number_label": "S/N",
"object_replaces": "Ersetzt",
"object_replaced_by": "Ersetzt durch",
"documentation_url_label": "Handbuch",
"object_notes_label": "Notizen",
"sort_due_date": "Fälligkeit",
@@ -422,7 +429,7 @@
"qr_action_view": "Wartungsinfo anzeigen",
"qr_action_complete": "Wartung als erledigt markieren",
"qr_action_quick_complete": "Schnell-Abschluss — ohne Dialog",
"qr_print_quick_none": "Schnell-Abschluss-Codes brauchen Schnell-Abschluss-Standardwerte an einer Aufgabe (Aufgabendialog → Schnell-Abschluss-Standardwerte) — noch keine Aufgabe der gewählten Objekte hat welche.",
"qr_print_quick_none": "Schnell-Abschluss-Codes brauchen Schnell-Abschluss-Standardwerte an einer Aufgabe (Einstellungen → Erweiterte Funktionen → Completion-Actions; Aufgabendialog → Schnell-Abschluss-Standardwerte) — noch keine Aufgabe der gewählten Objekte hat welche.",
"qr_url_mode": "Link-Typ",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Lokal (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "Erweiterte Funktionen ein- oder ausschalten. Deaktivieren blendet sie in der Oberfläche aus, löscht aber keine Daten.",
"feat_adaptive": "Adaptive Intervalle",
"feat_adaptive_desc": "Optimale Intervalle aus Wartungshistorie lernen",
"feat_predictions": "Sensorvorhersagen",
"feat_predictions_desc": "Trigger-Datum anhand von Sensordegradation vorhersagen",
"feat_seasonal": "Saisonale Anpassungen",
"feat_seasonal_desc": "Intervalle basierend auf saisonalen Mustern anpassen",
"feat_environmental": "Umgebungskorrelation",
@@ -671,6 +676,10 @@
"settings_currency": "Währung",
"settings_currency_decimals": "Nachkommastellen für Beträge",
"settings_currency_decimals_hint": "Wie viele Nachkommastellen jeder Betrag zeigt: KPIs, Budgets, Kosten in Listen und Berichten, Budget-Alarme. 0 = ganze Zahlen.",
"settings_parts_cost_mode": "Ersatzteile zählen als Ausgabe",
"settings_parts_cost_purchase": "Beim Kauf",
"settings_parts_cost_use": "Beim Verbrauch",
"settings_parts_cost_mode_hint": "Beim Kauf: Die Kosten einer Kauf-Erinnerung zählen, die bei einer Aufgabe verbrauchten Teile werden nur informativ angezeigt. Beim Verbrauch: Jede Erledigung bucht den Wert der verbrauchten Teile, und ein Kauf setzt den Preis des Teils, statt zu zählen. Gilt für Erledigungen ab jetzt — der Verlauf behält, wie er gebucht wurde.",
"settings_budget_monthly": "Monatsbudget",
"settings_budget_yearly": "Jahresbudget",
"settings_budget_alerts": "Budget-Warnungen",
@@ -683,6 +692,7 @@
"settings_import_placeholder": "JSON- oder CSV-Inhalt hier einfügen…",
"settings_import_btn": "Importieren",
"settings_import_success": "{count} Objekte erfolgreich importiert.",
"settings_import_unmatched_users": "Auf dieser Instanz nicht gefunden: {names}. Ihre Aufgabenzuweisungen wurden entfernt — lege Personen mit denselben Namen in Home Assistant an und importiere erneut, um sie zu behalten.",
"settings_export_success": "Export heruntergeladen.",
"settings_saved": "Einstellung gespeichert.",
"settings_include_history": "Verlauf einbeziehen",
@@ -771,7 +781,7 @@
"has_overdue": "Überfällige Aufgaben",
"object": "Objekt",
"settings_panel_access": "Panel-Zugriff",
"settings_panel_access_desc": "Admins haben immer Vollzugriff. Um Erstellen, Bearbeiten und Löschen an bestimmte Non-Admins zu delegieren, aktiviere dies und wähle sie unten aus — alle anderen sehen nur Abhaken/Überspringen.",
"settings_panel_access_desc": "Admins haben immer Vollzugriff. Um Erstellen, Bearbeiten und Löschen an bestimmte Non-Admins zu delegieren, aktiviere dies und wähle sie unten aus — alle anderen haben nur Erledigt, Überspringen, Zurücksetzen, Verschieben und Schlummern.",
"settings_operator_write": "Ausgewählten Nutzern Erstellen, Bearbeiten & Löschen erlauben",
"settings_operator_write_desc": "Aus: nur Admins können Inhalte ändern. Ein: die ausgewählten Nutzer unten erhalten ebenfalls Vollzugriff.",
"no_non_admin_users": "Keine Non-Admin-User gefunden. Lege welche unter Einstellungen → Personen an.",
@@ -782,11 +792,16 @@
"on_complete_action_desc": "Ruft beim Erledigen der Aufgabe einen HA-Service auf — z. B. einen Zähler am Gerät zurücksetzen.",
"on_complete_action_target": "Ziel-Entität",
"on_complete_action_target_hint": "Achtung: Domain der Entität muss zum Service passen — z. B. 'button.press' nur für button.*, 'counter.increment' nur für counter.*, 'input_button.press' nur für input_button.* etc. Bei Mismatch fired die Aktion nicht (HA loggt 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Diese Aktion hat mehr Ziele, als dieses Feld zeigt: {targets}. Sie bleiben erhalten — wählst du hier eine andere Entität, ersetzt sie das ganze Ziel.",
"on_complete_action_skip_auto": "Nicht, wenn sich die Aufgabe selbst erledigt (ihr Sensor hat sich erholt)",
"on_complete_action_data": "Daten (JSON, optional)",
"on_complete_action_test": "Konfiguration prüfen",
"on_complete_action_test_success": "✓ Konfiguration gültig (Aktion wird erst beim Abschluss ausgeführt)",
"on_complete_action_test_failed": "Konfigurationsfehler",
"action_test_bad_format": "Ungültiges Dienstformat (erwartet: 'domain.service')",
"action_test_unknown_service": "Der Dienst \"{service}\" ist in Home Assistant nicht registriert. Prüfe die Schreibweise und ob die Integration, die ihn bereitstellt, geladen ist.",
"action_test_domain_mismatch": "Der Dienst \"{service}\" wirkt nur auf {domain}.*-Entitäten; die Entität \"{entity}\" gehört zu {entity_domain}.* — wähle einen Dienst passend zur Domain der Entität (z. B. {suggestion})",
"action_test_entity_missing": "Die Ziel-Entität \"{entity}\" wurde in Home Assistant nicht gefunden — sie wurde vielleicht umbenannt oder ihre Integration entfernt.",
"quick_complete_defaults_title": "Schnell-Abschluss-Standardwerte (für QR-Scans, optional)",
"quick_complete_defaults_desc": "Voreingestellte Werte für Schnell-Abschluss-QR. Ohne Werte öffnet der QR den Abschluss-Dialog.",
"quick_complete_defaults_notes": "Notizen",
@@ -844,6 +859,7 @@
"doc_too_large": "Datei ist zu groß (max. 25 MB).",
"doc_upload_failed": "Upload fehlgeschlagen.",
"docs_archive_too_large": "Das Archiv ist zu groß (max. {max}).",
"docs_archive_no_manifest": "Dieses ZIP enthält keine manifest.json, daher weiß die Wiederherstellung nicht, zu welchem Dokument eine Datei gehört. Lass die manifest.json beim erneuten Zippen neben den Ordnern liegen.",
"completion_photos_optional": "Abschlussfotos (optional)",
"completion_photos": "Abschlussfotos",
"choose_photos": "Fotos auswählen",
@@ -904,6 +920,11 @@
"paused": "Pausiert",
"pause_object": "Pausieren",
"resume_object": "Fortsetzen",
"pause_task": "Pausieren",
"resume_task": "Fortsetzen",
"pause_task_prompt": "Friert den Zeitplan dieser Aufgabe ein — sie wird nicht fällig und meldet sich nicht, bis du sie fortsetzt; beim Fortsetzen beginnt ein frischer Zyklus ab diesem Tag. Die anderen Aufgaben des Objekts laufen weiter. Optional ein Datum, an dem sie von selbst weiterläuft.",
"task_paused": "Aufgabe pausiert",
"task_resumed": "Aufgabe fortgesetzt — ihr Zeitplan beginnt neu",
"pause_until_prompt": "Zeitpläne dieses Objekts einfrieren — nichts wird fällig und nichts benachrichtigt, bis es fortgesetzt wird. Optional ein Datum für die automatische Fortsetzung festlegen.",
"pause_until_label": "Fortsetzen am (optional)",
"object_paused": "Objekt pausiert",
@@ -913,7 +934,18 @@
"replace_object": "Ersetzen…",
"replace_object_prompt": "Dieses Objekt ausmustern und einen Nachfolger anlegen. Historie und Kosten bleiben am alten Objekt archiviert; Aufgaben und Dokumente wandern zum neuen, Zähler starten frisch.",
"replace_name_label": "Name des Nachfolgers",
"replace_device_heading": "Gerät in Home Assistant",
"replace_device_keep": "Dasselbe Gerät wie bisher ({device})",
"replace_device_keep_hint": "Auch die Wahl, wenn das neue Gerät noch nicht in Home Assistant ist: Sobald es da ist, wähle es in den Objekt-Einstellungen als Gerät — die Sensor-Trigger ziehen dann mit um.",
"replace_device_other": "Das neue Gerät ist ein anderes Gerät",
"replace_device_pick": "Gerät des neuen Exemplars",
"replace_device_other_hint": "Sensor-Trigger, Abschluss-Aktionen und der Zähler-Reset ziehen auf die passenden Entitäten dieses Geräts um.",
"replace_device_none": "Kein Gerät",
"object_replaced": "Objekt ersetzt — Nachfolger angelegt",
"device_swap_moved": "{count} Sensor-Verknüpfungen auf das neue Gerät umgezogen.",
"device_swap_moved_one": "Eine Sensor-Verknüpfung auf das neue Gerät umgezogen.",
"device_swap_unmatched": "{count} ließen sich nicht zuordnen — prüfe die Sensor-Trigger dieser Aufgaben.",
"device_swap_unmatched_one": "Eine ließ sich nicht zuordnen — prüfe den Sensor-Trigger dieser Aufgabe.",
"reading_unit_label": "Ableseeinheit (z. B. kWh, m³)",
"reading_unit_help": "Wird beim Erledigen dieser Aufgabe neben dem erfassten Wert angezeigt.",
"reading_value_label": "Ablesewert",
@@ -1010,6 +1042,9 @@
"part_stock": "Bestand",
"part_reorder_threshold": "Nachkauf-Schwelle",
"part_restock_quantity": "Auffüllmenge",
"part_package_size": "Packungsgröße (Einheiten)",
"part_restock_packages": "Nachkaufmenge (Packungen)",
"part_package_hint": "Gekauft in Packungen, verbraucht in der Einheit: Bestand, Verbrauch pro Aufgabe und Nachbestellgrenze zählen Einheiten; Nachkaufmenge und Preis gelten pro Packung.",
"part_auto_buy": "Kauf-Task bei Niedrigbestand",
"part_restock": "Bestand anpassen",
"parts_used_by": "Verwendet von",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Die Teile dieses Objekts konnten nicht geladen werden — die Teileverbrauch-Optionen sind gerade nicht verfügbar.",
"settings_export_selection": "Auf ausgewählte Objekte beschränken (optional)",
"settings_docs_archive": "Dokumentenarchiv (mit Dateien)",
"settings_docs_archive_hint": "Die JSON/YAML/CSV-Exporte enthalten nur Einstellungen. Dieses ZIP enthält die hochgeladenen Dateiinhalte, sodass eine Wiederherstellung vollständig ist.",
"settings_docs_archive_hint": "Die JSON- und YAML-Exporte führen deine Dokumente auf, aber nicht ihre Dateien; CSV lässt Dokumente weg. Dieses ZIP enthält die hochgeladenen Dateien – stelle es nach dem JSON-Import wieder her, dann fehlt nichts.",
"settings_docs_export_btn": "Dokumente-ZIP herunterladen",
"settings_docs_import_btn": "Dokumente-ZIP wiederherstellen",
"settings_docs_import_success": "Wiederhergestellt: {blobs} Dateien, {docs} Dokumente",
"settings_docs_import_missing": "{n} übersprungen — ihre Dateien fehlten im ZIP",
"adopt_problem_button": "Problem-Sensoren übernehmen",
"adopt_problem_title": "Problem-Sensoren übernehmen",
"adopt_problem_hint": "Verwandelt HA-Problem-Sensoren (Druckerfehler, Filterwarnungen, schwache Batterie) in Wartungsaufgaben, die auslösen, solange das Problem aktiv ist, und sich selbst erledigen, wenn es behoben ist.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Zählt den Verbrauch seit dem letzten Abschluss: fällig nach +{target}; der Zähler startet nach jedem Abschluss neu.",
"trigger_hint_counter_abs": "Die Aufgabe wird fällig, sobald der Sensor {target} erreicht.",
"trigger_hint_runtime": "Die Aufgabe wird nach {hours} h aufsummierter Laufzeit fällig; der Zähler startet nach jedem Abschluss neu.",
"trigger_hint_due_date": "Die Aufgabe wird {days} Tage vor dem Datum fällig, das der Sensor meldet.",
"trigger_hint_due_date_one": "Die Aufgabe wird einen Tag vor dem Datum fällig, das der Sensor meldet.",
"trigger_hint_due_date_same_day": "Die Aufgabe wird an dem Datum fällig, das der Sensor meldet.",
"trigger_hint_due_date_now": "Gemeldetes Datum: {date}.",
"trigger_hint_state_change": "Die Aufgabe wird nach {count} Zustandswechsel(n) fällig.",
"trigger_hint_state_change_to": "Die Aufgabe wird nach {count} Wechsel(n) zu „{state}“ fällig.",
"trigger_hint_state_now": "Aktueller Zustand: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Nur Aufgaben, die zu den Label-/Nutzer-Filtern der gewählten Ansicht passen, senden Erinnerungen. Status, Sortierung und Gruppierung der Ansicht werden hier ignoriert.",
"settings_notify_completed": "Erledigt-Benachrichtigungen",
"settings_notify_completed_hint": "Haushalts-Info, wenn eine Aufgabe erledigt wurde, mit Grund und Person. „Nur automatische“ meldet nur Erledigungen, die niemand vor Ort ausgelöst hat: Sensor-Erholung, Einkaufslisten-Abhaken, Service-Aufruf einer Automation. Geht nur an den Haushalts-Dienst; Ruhezeiten und Tageslimit gelten.",
"settings_title_style": "Format des Benachrichtigungs-Titels",
"settings_title_style_hint": "Was als TITEL der Benachrichtigung erscheint. „Standard“ behält den Status-Text (z. B. „Wartung überfällig!“). Objekt- / Aufgabenname helfen, wenn das Smartphone Benachrichtigungen stapelt.",
"title_style_default": "Standard (Status-Titel)",
"title_style_object_name": "Objektname als Titel",
"title_style_task_name": "Aufgabenname als Titel",
"notify_completed_off": "Aus",
"notify_completed_automatic": "Nur automatische Erledigungen",
"notify_completed_all": "Alle Erledigungen",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON oder YAML, Jinja erlaubt; wird in die data des Notify-Aufrufs gemischt (deine Schlüssel gewinnen). Variablen: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Prüfen mit „Test senden“.",
"card_saved_view": "Gespeicherte Ansicht",
"card_saved_view_none": "Keine",
"card_saved_view_help": "Wendet die Status-, Nutzer- und Label-Filter der Ansicht zusätzlich zu den obigen Filtern an. Sortierung und Gruppierung der Ansicht sind Panel-Darstellung und werden auf der Karte nicht angewendet.",
"card_saved_view_help": "Wendet die Status-, Nutzer-, Label- und Prioritätsfilter der Ansicht zusätzlich zu den obigen Filtern an. Sortierung und Gruppierung der Ansicht sind Panel-Darstellung und werden auf der Karte nicht angewendet.",
"panel_card_tab": "Startet auf Tab",
"panel_card_tab_default": "Gemerkter Tab (wie im Panel)",
"panel_card_view": "Startet mit gespeicherter Ansicht",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Zurück zu Symbolen",
"battery_fleet_forecast_overdue": "Prognosedatum überschritten — die Batterie meldet sich weiterhin gesund. Falls du sie gewechselt hast, trage den Wechsel nach; andernfalls lag die Prognose daneben.",
"cost_from_parts": "≈ {amount} aus Teilen übernehmen",
"cost_parts_booked": "Verbrauchte Teile ≈ {amount} werden automatisch gebucht — trag nur zusätzliche Kosten ein.",
"cost_parts_info": "Verbrauchte Teile ≈ {amount} — gezählt beim Kauf, nicht hier.",
"cost_purchase_use_hint": "Der gezahlte Preis wird zum Preis des Teils — Ersatzteile zählen beim Verbrauch.",
"history_parts_counted": "Teile: {amount}",
"history_parts_counted_hint": "Wert der verbrauchten Teile — als Ausgabe gezählt",
"history_parts_info": "Teile ≈ {amount}",
"history_parts_info_hint": "Wert der verbrauchten Teile — nur zur Information; gezählt wurde beim Kauf",
"history_purchase_stock": "Gekauft für {amount} (zählt beim Verbrauch)",
"dismiss": "Ausblenden",
"gs_label": "Erste Schritte — diese Hinweise verschwinden, wenn dein Setup wächst",
"gs_setups_chip": "Vorgeschlagene Setups: {n} Geräte mit vorverdrahteten Auslösern gefunden",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Zwei Wochen (14 Tage)",
"cal_editor_window_month": "Monat (30 Tage, Standard)",
"cal_editor_window_year": "Jahr (365 Tage, leere Tage ausgeblendet)",
"cal_editor_window_past_30": "Letzte 30 Tage (Verlauf)",
"cal_editor_window_past_90": "Letzte 90 Tage (Verlauf)",
"cal_editor_show_chips": "Zeitfenster-Chips in der Karte anzeigen",
"cal_editor_chips_hint": "Chips ausblenden, wenn die Karte in einer Strategie-Ansicht steckt, die bereits als Zeitfenster-Auswahl dient.",
"cal_editor_show_user_filter": "Benutzerfilter-Dropdown anzeigen",
@@ -38,6 +38,8 @@
"reset": "Reset",
"snooze": "Snooze",
"snoozed": "Snoozed",
"snoozed_for": "Reminders muted for {hours} hours — until {until}",
"snoozed_for_one": "Reminders muted for one hour — until {until}",
"cancel": "Cancel",
"bulk_select": "Select",
"bulk_select_all": "Select all",
@@ -63,7 +65,10 @@
"counter": "Counter",
"state_change": "State change",
"runtime": "Runtime",
"trigger_type_due_date": "Date from a sensor",
"runtime_hours": "Target runtime (hours)",
"days_before": "Days before the date",
"days_before_help": "For a sensor that reports when the maintenance is due (a date or timestamp). 0 = on the date itself. The device's own \"done\" button moves the date forward, which clears the trigger — make it the completion action.",
"target_value": "Target value",
"target_changes": "Target changes",
"for_minutes": "For (minutes)",
@@ -235,6 +240,8 @@
"model_optional": "Model (optional)",
"serial_number_optional": "Serial number (optional)",
"serial_number_label": "S/N",
"object_replaces": "Replaces",
"object_replaced_by": "Replaced by",
"documentation_url_label": "Manual",
"object_notes_label": "Notes",
"sort_due_date": "Due date",
@@ -422,7 +429,7 @@
"qr_action_view": "View maintenance info",
"qr_action_complete": "Mark maintenance as complete",
"qr_action_quick_complete": "Quick-complete — no dialog",
"qr_print_quick_none": "Quick-complete codes need quick-complete defaults on a task (task dialog → Quick-complete defaults) — none of the chosen objects' tasks has them yet.",
"qr_print_quick_none": "Quick-complete codes need quick-complete defaults on a task (Settings → Advanced Features → Completion actions; task dialog → Quick-complete defaults) — none of the chosen objects' tasks has them yet.",
"qr_url_mode": "Link type",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Local (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "Enable or disable advanced features. Disabling hides them from the UI but does not delete data.",
"feat_adaptive": "Adaptive Scheduling",
"feat_adaptive_desc": "Learn optimal intervals from maintenance history",
"feat_predictions": "Sensor Predictions",
"feat_predictions_desc": "Predict trigger dates from sensor degradation",
"feat_seasonal": "Seasonal Adjustments",
"feat_seasonal_desc": "Adjust intervals based on seasonal patterns",
"feat_environmental": "Environmental Correlation",
@@ -671,6 +676,10 @@
"settings_currency": "Currency",
"settings_currency_decimals": "Decimal places for amounts",
"settings_currency_decimals_hint": "How many decimals every amount shows: KPIs, budgets, costs in lists and reports, budget alerts. 0 = whole numbers.",
"settings_parts_cost_mode": "Spare parts count as spending",
"settings_parts_cost_purchase": "When bought",
"settings_parts_cost_use": "When used",
"settings_parts_cost_mode_hint": "When bought: a buy reminder's cost counts, and the parts a job uses are shown as information. When used: every completion books the value of the parts it used, and a purchase sets the part's price instead of counting. Applies to completions from now on — history keeps how it was booked.",
"settings_budget_monthly": "Monthly budget",
"settings_budget_yearly": "Yearly budget",
"settings_budget_alerts": "Budget alerts",
@@ -683,15 +692,17 @@
"settings_import_placeholder": "Paste JSON or CSV content here…",
"settings_import_btn": "Import",
"settings_import_success": "{count} objects imported successfully.",
"settings_import_unmatched_users": "Not found on this instance: {names}. Their task assignments were removed — add people with the same names in Home Assistant and import again to keep them.",
"settings_export_success": "Export downloaded.",
"settings_saved": "Setting saved.",
"settings_include_history": "Include history",
"settings_export_selection": "Limit to selected objects (optional)",
"settings_docs_archive": "Documents archive (with files)",
"settings_docs_archive_hint": "The JSON/YAML/CSV exports carry settings only. This ZIP includes the uploaded file contents so a restore is complete.",
"settings_docs_archive_hint": "The JSON and YAML exports list your documents but not their files; CSV leaves documents out. This ZIP holds the uploaded files — restore it after importing the JSON and nothing is missing.",
"settings_docs_export_btn": "Download documents ZIP",
"settings_docs_import_btn": "Restore documents ZIP",
"settings_docs_import_success": "Restored: {blobs} files, {docs} documents",
"settings_docs_import_missing": "{n} skipped — their files were not in the ZIP",
"sort_alphabetical": "Alphabetical",
"sort_due_soonest": "Due soonest",
"sort_task_count": "Task count",
@@ -777,7 +788,7 @@
"has_overdue": "Has overdue tasks",
"object": "Object",
"settings_panel_access": "Panel access",
"settings_panel_access_desc": "Admins always have full access. To delegate create, edit and delete to specific non-admins, switch this on and pick them below — everyone else sees only Complete and Skip.",
"settings_panel_access_desc": "Admins always have full access. To delegate create, edit and delete to specific non-admins, switch this on and pick them below — everyone else only gets Complete, Skip, Reset, Postpone and Snooze.",
"settings_operator_write": "Allow selected users to create, edit & delete",
"settings_operator_write_desc": "Off: only admins can change content. On: the selected users below get full access too.",
"no_non_admin_users": "No non-admin users found. Add some in Settings → People.",
@@ -788,11 +799,16 @@
"on_complete_action_desc": "Calls an HA service when the task is completed — e.g. reset a counter on the device.",
"on_complete_action_target": "Target entity",
"on_complete_action_target_hint": "Note: the entity domain must match the service — e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "This action targets more than this field shows: {targets}. That is kept as it is — choosing another entity here replaces the whole target.",
"on_complete_action_skip_auto": "Not when the task completes itself (its sensor recovered)",
"on_complete_action_data": "Data (JSON, optional)",
"on_complete_action_test": "Validate configuration",
"on_complete_action_test_success": "✓ Configuration valid (action will fire only on task completion)",
"on_complete_action_test_failed": "Failed",
"action_test_bad_format": "Invalid service format (expected 'domain.service')",
"action_test_unknown_service": "Service \"{service}\" is not registered in Home Assistant. Check the spelling and that the integration providing it is loaded.",
"action_test_domain_mismatch": "Service \"{service}\" only works on {domain}.* entities; entity \"{entity}\" is in {entity_domain}.* — pick a service that matches the entity's domain (e.g. {suggestion})",
"action_test_entity_missing": "Target entity \"{entity}\" was not found in Home Assistant — it may have been renamed or its integration removed.",
"quick_complete_defaults_title": "Quick-complete defaults (for QR scans, optional)",
"quick_complete_defaults_desc": "Pre-set values for quick-complete QR scans. Without these, the QR opens the complete dialog.",
"quick_complete_defaults_notes": "Notes",
@@ -850,6 +866,7 @@
"doc_too_large": "File is too large (max 25 MB).",
"doc_upload_failed": "Upload failed.",
"docs_archive_too_large": "The archive is too large (max {max}).",
"docs_archive_no_manifest": "This ZIP has no manifest.json, so the restore cannot tell which document a file belongs to. Keep manifest.json next to the folders when zipping again.",
"completion_photos_optional": "Completion photos (optional)",
"completion_photos": "Completion photos",
"choose_photos": "Choose photos",
@@ -910,6 +927,11 @@
"paused": "Paused",
"pause_object": "Pause",
"resume_object": "Resume",
"pause_task": "Pause",
"resume_task": "Resume",
"pause_task_prompt": "Freeze this task's schedule — it does not become due and does not notify until you resume it, and resuming starts a fresh cycle from that day. The object's other tasks keep running. Optionally set a date to resume by itself.",
"task_paused": "Task paused",
"task_resumed": "Task resumed — its schedule restarted",
"pause_until_prompt": "Freeze this object's schedules — nothing becomes due and nothing notifies until it is resumed. Optionally set an auto-resume date.",
"pause_until_label": "Resume on (optional)",
"object_paused": "Object paused",
@@ -919,7 +941,18 @@
"replace_object": "Replace…",
"replace_object_prompt": "Retire this object and create a successor. History and costs stay archived on the old one; tasks and documents carry over to the new one, counters start fresh.",
"replace_name_label": "Successor name",
"replace_device_heading": "Device in Home Assistant",
"replace_device_keep": "Same device as before ({device})",
"replace_device_keep_hint": "Also the choice when the new unit is not in Home Assistant yet: once it is, pick it as the object's device in its settings — the sensor triggers move along then.",
"replace_device_other": "The new unit is another device",
"replace_device_pick": "Device of the new unit",
"replace_device_other_hint": "Sensor triggers, completion actions and the counter reset move to the matching entities of that device.",
"replace_device_none": "No device",
"object_replaced": "Object replaced — successor created",
"device_swap_moved": "{count} sensor links moved to the new device.",
"device_swap_moved_one": "One sensor link moved to the new device.",
"device_swap_unmatched": "{count} could not be matched — check those tasks' sensor triggers.",
"device_swap_unmatched_one": "One could not be matched — check that task's sensor trigger.",
"reading_unit_label": "Reading unit (e.g. kWh, m³)",
"reading_unit_help": "Shown next to the recorded value when completing this task.",
"reading_value_label": "Reading value",
@@ -1016,6 +1049,9 @@
"part_stock": "Stock",
"part_reorder_threshold": "Reorder at",
"part_restock_quantity": "Restock quantity",
"part_package_size": "Package size (units)",
"part_restock_packages": "Restock amount (packages)",
"part_package_hint": "Bought in packages, used in the unit: stock, the amount a task uses and the reorder threshold count units; the restock amount and the price are per package.",
"part_auto_buy": "Auto-create buy task when low",
"part_restock": "Adjust stock",
"parts_used_by": "Used by",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Counts usage since the last completion: due after +{target}; the count restarts after each completion.",
"trigger_hint_counter_abs": "The task becomes due once the sensor reaches {target}.",
"trigger_hint_runtime": "The task becomes due after {hours} h of accumulated on-time; the counter restarts after each completion.",
"trigger_hint_due_date": "The task becomes due {days} days before the date the sensor reports.",
"trigger_hint_due_date_one": "The task becomes due one day before the date the sensor reports.",
"trigger_hint_due_date_same_day": "The task becomes due on the date the sensor reports.",
"trigger_hint_due_date_now": "Reported date: {date}.",
"trigger_hint_state_change": "The task becomes due after {count} state change(s).",
"trigger_hint_state_change_to": "The task becomes due after {count} change(s) to “{state}”.",
"trigger_hint_state_now": "Current state: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",
"settings_notify_completed": "Completion notifications",
"settings_notify_completed_hint": "Household news when a task is completed, with the reason and who did it. \"Automatic only\" announces just the completions nobody made on the spot: sensor recovery, shopping-list check-off, an automation's service call. Sent to the household service only; quiet hours and the daily cap apply.",
"settings_title_style": "Notification title style",
"settings_title_style_hint": "What appears as the notification's TITLE. Default keeps the per-status text (e.g. 'Maintenance overdue!'). Object name / Task name help when phone notifications stack.",
"title_style_default": "Default (per-status title)",
"title_style_object_name": "Object name as title",
"title_style_task_name": "Task name as title",
"notify_completed_off": "Off",
"notify_completed_automatic": "Automatic completions only",
"notify_completed_all": "All completions",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test it with the Send test button.",
"card_saved_view": "Saved view",
"card_saved_view_none": "None",
"card_saved_view_help": "Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",
"card_saved_view_help": "Applies the view's status, user, label and priority filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",
"panel_card_tab": "Open on tab",
"panel_card_tab_default": "Remembered tab (as in the panel)",
"panel_card_view": "Open with saved view",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Back to icons",
"battery_fleet_forecast_overdue": "Predicted date passed — the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",
"cost_from_parts": "Use ≈ {amount} from parts",
"cost_parts_booked": "Parts used ≈ {amount} are booked automatically — enter only additional costs.",
"cost_parts_info": "Parts used ≈ {amount} — counted when they were bought, not here.",
"cost_purchase_use_hint": "The price paid becomes the part's price — spare parts count when they are used.",
"history_parts_counted": "Parts: {amount}",
"history_parts_counted_hint": "Value of the parts used — counted as spending",
"history_parts_info": "Parts ≈ {amount}",
"history_parts_info_hint": "Value of the parts used — information only; the money counted when they were bought",
"history_purchase_stock": "Bought for {amount} (counted when used)",
"dismiss": "Dismiss",
"gs_label": "Getting started — these hints retire as your setup grows",
"gs_setups_chip": "Suggested setups found {n} devices with pre-wired triggers",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Fortnight (14 days)",
"cal_editor_window_month": "Month (30 days, default)",
"cal_editor_window_year": "Year (365 days, empty days collapsed)",
"cal_editor_window_past_30": "Past 30 days (history)",
"cal_editor_window_past_90": "Past 90 days (history)",
"cal_editor_show_chips": "Show window chips inside the card",
"cal_editor_chips_hint": "Hide the chips when the card is embedded in a strategy view that already serves as the window selector.",
"cal_editor_show_user_filter": "Show user filter dropdown",
@@ -36,6 +36,8 @@
"reset": "Restablecer",
"snooze": "Posponer",
"snoozed": "Pospuesta",
"snoozed_for": "Recordatorios silenciados durante {hours} horas, hasta {until}",
"snoozed_for_one": "Recordatorios silenciados durante una hora, hasta {until}",
"cancel": "Cancelar",
"bulk_select": "Seleccionar",
"bulk_select_all": "Seleccionar todo",
@@ -61,7 +63,10 @@
"counter": "Contador",
"state_change": "Cambio de estado",
"runtime": "Tiempo de funcionamiento",
"trigger_type_due_date": "Fecha de un sensor",
"runtime_hours": "Duración objetivo (horas)",
"days_before": "Días antes de la fecha",
"days_before_help": "Para un sensor que indica cuándo toca el mantenimiento (fecha o marca de tiempo). 0 = el mismo día. El botón de «hecho» del propio dispositivo aplaza la fecha y así desactiva el disparador: úsalo como acción al completar.",
"target_value": "Valor objetivo",
"target_changes": "Cambios objetivo",
"for_minutes": "Durante (minutos)",
@@ -233,6 +238,8 @@
"model_optional": "Modelo (opcional)",
"serial_number_optional": "Número de serie (opcional)",
"serial_number_label": "N/S",
"object_replaces": "Sustituye a",
"object_replaced_by": "Sustituido por",
"documentation_url_label": "Manual",
"object_notes_label": "Notas",
"sort_due_date": "Vencimiento",
@@ -420,7 +427,7 @@
"qr_action_view": "Ver info de mantenimiento",
"qr_action_complete": "Marcar mantenimiento como completado",
"qr_action_quick_complete": "Finalización rápida — sin diálogo",
"qr_print_quick_none": "Los códigos de finalización rápida necesitan valores predeterminados de finalización rápida en una tarea (diálogo de la tarea → Valores predeterminados de finalización rápida); ninguna tarea de los objetos elegidos los tiene todavía.",
"qr_print_quick_none": "Los códigos de finalización rápida necesitan valores predeterminados de finalización rápida en una tarea (Ajustes → Funciones avanzadas → Acciones de finalización; diálogo de la tarea → Valores predeterminados de finalización rápida); ninguna tarea de los objetos elegidos los tiene todavía.",
"qr_url_mode": "Tipo de enlace",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Local (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Active o desactive funciones avanzadas. Desactivar las oculta de la interfaz pero no elimina datos.",
"feat_adaptive": "Planificación adaptativa",
"feat_adaptive_desc": "Aprender intervalos óptimos del historial de mantenimiento",
"feat_predictions": "Predicciones de sensor",
"feat_predictions_desc": "Predecir fechas de activación por degradación del sensor",
"feat_seasonal": "Ajustes estacionales",
"feat_seasonal_desc": "Ajustar intervalos según patrones estacionales",
"feat_environmental": "Correlación ambiental",
@@ -669,6 +674,10 @@
"settings_currency": "Moneda",
"settings_currency_decimals": "Decimales de los importes",
"settings_currency_decimals_hint": "Cuántos decimales muestra cada importe: KPI, presupuestos, costes en listas e informes, alertas de presupuesto. 0 = números enteros.",
"settings_parts_cost_mode": "Los repuestos cuentan como gasto",
"settings_parts_cost_purchase": "Al comprarlos",
"settings_parts_cost_use": "Al usarlos",
"settings_parts_cost_mode_hint": "Al comprarlos: cuenta el coste de un recordatorio de compra y los repuestos que usa un trabajo se muestran como información. Al usarlos: cada tarea completada registra el valor de los repuestos usados y una compra fija el precio del repuesto en lugar de contar. Se aplica a partir de ahora: el historial conserva cómo se registró.",
"settings_budget_monthly": "Presupuesto mensual",
"settings_budget_yearly": "Presupuesto anual",
"settings_budget_alerts": "Alertas de presupuesto",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Pegue el contenido JSON o CSV aquí…",
"settings_import_btn": "Importar",
"settings_import_success": "{count} objetos importados correctamente.",
"settings_import_unmatched_users": "No encontrados en esta instancia: {names}. Se quitaron sus asignaciones de tareas — crea personas con los mismos nombres en Home Assistant e importa de nuevo para conservarlas.",
"settings_export_success": "Exportación descargada.",
"settings_saved": "Ajuste guardado.",
"settings_include_history": "Incluir historial",
@@ -746,7 +756,7 @@
"has_overdue": "Tareas vencidas",
"object": "Objeto",
"settings_panel_access": "Acceso al panel",
"settings_panel_access_desc": "Los administradores siempre tienen acceso completo. Para delegar crear, editar y eliminar a no administradores específicos, activa esto y selecciónalos abajo — los demás solo ven Completar y Omitir.",
"settings_panel_access_desc": "Los administradores siempre tienen acceso completo. Para delegar crear, editar y eliminar a no administradores específicos, activa esto y selecciónalos abajo — los demás solo tienen Completada, Omitir, Restablecer y Posponer.",
"settings_operator_write": "Permitir a los usuarios seleccionados crear, editar y eliminar",
"settings_operator_write_desc": "Desactivado: solo los administradores pueden cambiar el contenido. Activado: los usuarios seleccionados abajo también tienen acceso completo.",
"no_non_admin_users": "No se encontraron usuarios no administradores. Añade alguno en Ajustes → Personas.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Probar acción",
"on_complete_action_test_success": "Éxito",
"on_complete_action_test_failed": "Fallido",
"action_test_bad_format": "Formato de servicio no válido (se esperaba 'domain.service')",
"action_test_unknown_service": "El servicio \"{service}\" no está registrado en Home Assistant. Comprueba la ortografía y que la integración que lo proporciona esté cargada.",
"action_test_domain_mismatch": "El servicio \"{service}\" solo funciona con entidades {domain}.*; la entidad \"{entity}\" pertenece a {entity_domain}.* — elige un servicio que coincida con el dominio de la entidad (p. ej. {suggestion})",
"action_test_entity_missing": "No se encontró la entidad de destino \"{entity}\" en Home Assistant — puede que se haya renombrado o que se haya eliminado su integración.",
"quick_complete_defaults_title": "Valores predeterminados de finalización rápida (para escaneos QR, opcional)",
"quick_complete_defaults_desc": "Valores prefijados para QR de finalización rápida. Sin ellos, el QR abre el diálogo de completar.",
"quick_complete_defaults_notes": "Notas",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Gestionar asignaciones de tareas…",
"groups_open_panel": "Abrir en el panel",
"on_complete_action_target_hint": "Nota: el dominio de la entidad debe coincidir con el servicio — p. ej. 'button.press' solo funciona en button.*, 'counter.increment' solo en counter.*, 'input_button.press' solo en input_button.* etc. Si no coinciden, la acción falla silenciosamente (HA registra 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Esta acción tiene más destinos de los que muestra este campo: {targets}. Se conservan tal cual; si eliges aquí otra entidad, sustituye todo el destino.",
"on_complete_action_skip_auto": "No cuando la tarea se completa sola (su sensor se recuperó)",
"show_all_objects": "Mostrar todos los objetos",
"show_all_tasks": "Quitar filtro — mostrar todas las tareas",
@@ -844,6 +859,7 @@
"doc_too_large": "El archivo es demasiado grande (máx. 25 MB).",
"doc_upload_failed": "Error al subir.",
"docs_archive_too_large": "El archivo comprimido es demasiado grande (máx. {max}).",
"docs_archive_no_manifest": "Este ZIP no contiene manifest.json, así que la restauración no sabe a qué documento pertenece cada archivo. Deja manifest.json junto a las carpetas al volver a comprimir.",
"completion_photos_optional": "Fotos de finalización (opcional)",
"completion_photos": "Fotos de finalización",
"choose_photos": "Elegir fotos",
@@ -904,6 +920,11 @@
"paused": "Pausado",
"pause_object": "Pausar",
"resume_object": "Reanudar",
"pause_task": "Pausar",
"resume_task": "Reanudar",
"pause_task_prompt": "Congela la planificación de esta tarea: no vence ni avisa hasta que la reanudes, y al reanudarla empieza un ciclo nuevo desde ese día. Las demás tareas del objeto siguen. Opcional: una fecha para reanudarla sola.",
"task_paused": "Tarea pausada",
"task_resumed": "Tarea reanudada: su planificación vuelve a empezar",
"pause_until_prompt": "Congela los calendarios de este objeto: nada vence ni notifica hasta reanudarlo. Opcionalmente, fija una fecha de reanudación automática.",
"pause_until_label": "Reanudar el (opcional)",
"object_paused": "Objeto pausado",
@@ -913,7 +934,18 @@
"replace_object": "Sustituir…",
"replace_object_prompt": "Retira este objeto y crea un sucesor. El historial y los costes quedan archivados en el antiguo; las tareas y los documentos pasan al nuevo, los contadores empiezan de cero.",
"replace_name_label": "Nombre del sucesor",
"replace_device_heading": "Dispositivo en Home Assistant",
"replace_device_keep": "El mismo dispositivo que antes ({device})",
"replace_device_keep_hint": "También es la opción si la nueva unidad aún no está en Home Assistant: cuando esté, elígela como dispositivo en los ajustes del objeto y los disparadores de sensores se moverán con ella.",
"replace_device_other": "La nueva unidad es otro dispositivo",
"replace_device_pick": "Dispositivo de la nueva unidad",
"replace_device_other_hint": "Los disparadores de sensores, las acciones al completar y el reinicio del contador pasan a las entidades equivalentes de ese dispositivo.",
"replace_device_none": "Ningún dispositivo",
"object_replaced": "Objeto sustituido — sucesor creado",
"device_swap_moved": "{count} vínculos de sensores trasladados al nuevo dispositivo.",
"device_swap_moved_one": "Un vínculo de sensor trasladado al nuevo dispositivo.",
"device_swap_unmatched": "{count} no se pudieron emparejar: revisa los disparadores de sensores de esas tareas.",
"device_swap_unmatched_one": "Uno no se pudo emparejar: revisa el disparador de sensor de esa tarea.",
"reading_unit_label": "Unidad de lectura (p. ej. kWh, m³)",
"reading_unit_help": "Se muestra junto al valor registrado al completar esta tarea.",
"reading_value_label": "Valor de lectura",
@@ -1010,6 +1042,9 @@
"part_stock": "Existencias",
"part_reorder_threshold": "Umbral de reposición",
"part_restock_quantity": "Cantidad de reposición",
"part_package_size": "Tamaño del envase (unidades)",
"part_restock_packages": "Cantidad de reposición (envases)",
"part_package_hint": "Se compra por envases y se usa por unidades: el stock, el consumo por tarea y el umbral de pedido cuentan unidades; la cantidad de reposición y el precio son por envase.",
"part_auto_buy": "Crear tarea de compra si bajo",
"part_restock": "Ajustar existencias",
"parts_used_by": "Usado por",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "No se pudieron cargar las piezas de este objeto — las opciones de consumo de piezas no están disponibles ahora.",
"settings_export_selection": "Limitar a los objetos seleccionados (opcional)",
"settings_docs_archive": "Archivo de documentos (con archivos)",
"settings_docs_archive_hint": "Las exportaciones JSON/YAML/CSV solo contienen la configuración. Este ZIP incluye el contenido de los archivos subidos para una restauración completa.",
"settings_docs_archive_hint": "Las exportaciones JSON y YAML enumeran tus documentos, pero no sus archivos; el CSV omite los documentos. Este ZIP contiene los archivos subidos: restáuralo después de importar el JSON y no faltará nada.",
"settings_docs_export_btn": "Descargar ZIP de documentos",
"settings_docs_import_btn": "Restaurar ZIP de documentos",
"settings_docs_import_success": "Restaurado: {blobs} archivos, {docs} documentos",
"settings_docs_import_missing": "{n} omitidos — sus archivos no estaban en el ZIP",
"adopt_problem_button": "Adoptar sensores de problema",
"adopt_problem_title": "Adoptar sensores de problema",
"adopt_problem_hint": "Convierte los sensores de problema de HA (errores de impresora, avisos de filtro, batería baja) en tareas de mantenimiento que se activan mientras el problema está activo y se completan cuando se resuelve.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Cuenta el uso desde la última finalización: vence tras +{target}; el contador se reinicia tras cada finalización.",
"trigger_hint_counter_abs": "La tarea vence cuando el sensor alcance {target}.",
"trigger_hint_runtime": "La tarea vence tras {hours} h de funcionamiento acumulado; el contador se reinicia tras cada finalización.",
"trigger_hint_due_date": "La tarea vence {days} días antes de la fecha que indica el sensor.",
"trigger_hint_due_date_one": "La tarea vence un día antes de la fecha que indica el sensor.",
"trigger_hint_due_date_same_day": "La tarea vence en la fecha que indica el sensor.",
"trigger_hint_due_date_now": "Fecha indicada: {date}.",
"trigger_hint_state_change": "La tarea vence tras {count} cambio(s) de estado.",
"trigger_hint_state_change_to": "La tarea vence tras {count} cambio(s) a «{state}».",
"trigger_hint_state_now": "Estado actual: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Solo las tareas que coinciden con los filtros de etiqueta/usuario de la vista seleccionada envían recordatorios. El estado, el orden y la agrupación de la vista se ignoran aquí.",
"settings_notify_completed": "Notificaciones de finalización",
"settings_notify_completed_hint": "Aviso para el hogar cuando se completa una tarea, con el motivo y quién lo hizo. «Solo automáticas» anuncia solo las finalizaciones que nadie hizo en el sitio: recuperación del sensor, marcado en la lista de la compra, llamada de servicio de una automatización. Solo al servicio del hogar; se aplican las horas de silencio y el límite diario.",
"settings_title_style": "Estilo del título de notificación",
"settings_title_style_hint": "Lo que aparece como TÍTULO de la notificación. Por defecto se conserva el texto por estado (p. ej., '¡Mantenimiento vencido!'). Nombre del objeto / tarea ayudan cuando el teléfono apila notificaciones.",
"title_style_default": "Predeterminado (título por estado)",
"title_style_object_name": "Nombre del objeto como título",
"title_style_task_name": "Nombre de la tarea como título",
"notify_completed_off": "Desactivado",
"notify_completed_automatic": "Solo finalizaciones automáticas",
"notify_completed_all": "Todas las finalizaciones",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON o YAML, se permite Jinja; se fusiona en los data de la llamada notify (tus claves ganan). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Pruébalo con el botón Enviar prueba.",
"card_saved_view": "Vista guardada",
"card_saved_view_none": "Ninguna",
"card_saved_view_help": "Aplica los filtros de estado, usuario y etiqueta de la vista además de los filtros anteriores. El orden y la agrupación de la vista son ajustes de visualización del panel y no se aplican en la tarjeta.",
"card_saved_view_help": "Aplica los filtros de estado, usuario, etiqueta y prioridad de la vista además de los filtros anteriores. El orden y la agrupación de la vista son ajustes de visualización del panel y no se aplican en la tarjeta.",
"panel_card_tab": "Abrir en la pestaña",
"panel_card_tab_default": "Pestaña recordada (como en el panel)",
"panel_card_view": "Abrir con la vista guardada",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Volver a los iconos",
"battery_fleet_forecast_overdue": "Fecha prevista superada: la batería sigue informando buen estado. Si la cambiaste, registra el reemplazo; si no, la previsión falló.",
"cost_from_parts": "Usar ≈ {amount} de las piezas",
"cost_parts_booked": "Los repuestos usados ≈ {amount} se registran automáticamente: introduce solo los costes adicionales.",
"cost_parts_info": "Repuestos usados ≈ {amount}: se cuentan al comprarlos, no aquí.",
"cost_purchase_use_hint": "El precio pagado pasa a ser el precio del repuesto: los repuestos cuentan al usarlos.",
"history_parts_counted": "Repuestos: {amount}",
"history_parts_counted_hint": "Valor de los repuestos usados: contado como gasto",
"history_parts_info": "Repuestos ≈ {amount}",
"history_parts_info_hint": "Valor de los repuestos usados: solo informativo; el dinero se contó al comprarlos",
"history_purchase_stock": "Comprado por {amount} (cuenta al usarlo)",
"dismiss": "Descartar",
"gs_label": "Primeros pasos: estas sugerencias desaparecen a medida que crece tu configuración",
"gs_setups_chip": "Configuraciones sugeridas: {n} dispositivos con disparadores preconfigurados",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Quincena (14 días)",
"cal_editor_window_month": "Mes (30 días, predeterminado)",
"cal_editor_window_year": "Año (365 días, días vacíos ocultos)",
"cal_editor_window_past_30": "Últimos 30 días (historial)",
"cal_editor_window_past_90": "Últimos 90 días (historial)",
"cal_editor_show_chips": "Mostrar chips de ventana en la tarjeta",
"cal_editor_chips_hint": "Oculta los chips cuando la tarjeta está en una vista de estrategia que ya sirve como selector de ventana.",
"cal_editor_show_user_filter": "Mostrar filtro de usuario",
@@ -38,6 +38,8 @@
"reset": "Nollaa",
"snooze": "Torkku",
"snoozed": "Torkulla",
"snoozed_for": "Muistutukset mykistetty {hours} tunniksi — {until} asti",
"snoozed_for_one": "Muistutukset mykistetty tunniksi — {until} asti",
"cancel": "Peruuta",
"bulk_select": "Valitse",
"bulk_select_all": "Valitse kaikki",
@@ -63,7 +65,10 @@
"counter": "Laskuri",
"state_change": "Tilan muutos",
"runtime": "Käyttöaika",
"trigger_type_due_date": "Päivämäärä anturilta",
"runtime_hours": "Tavoitekäyttöaika (tuntia)",
"days_before": "Päiviä ennen päivämäärää",
"days_before_help": "Anturille, joka ilmoittaa huollon eräpäivän (päivämäärä tai aikaleima). 0 = itse päivänä. Laitteen oma \"valmis\"-painike siirtää päivää eteenpäin ja poistaa laukaisimen — aseta se valmistumisen toiminnoksi.",
"target_value": "Tavoitearvo",
"target_changes": "Tavoitemuutokset",
"for_minutes": "Vähintään (minuuttia)",
@@ -235,6 +240,8 @@
"model_optional": "Malli (valinnainen)",
"serial_number_optional": "Sarjanumero (valinnainen)",
"serial_number_label": "S/N",
"object_replaces": "Edeltäjä",
"object_replaced_by": "Seuraaja",
"documentation_url_label": "Käyttöohje",
"object_notes_label": "Muistiinpanot",
"sort_due_date": "Eräpäivä",
@@ -422,7 +429,7 @@
"qr_action_view": "Näytä huoltotiedot",
"qr_action_complete": "Merkitse huolto valmiiksi",
"qr_action_quick_complete": "Pikasuoritus — ilman valintaikkunaa",
"qr_print_quick_none": "Pikasuorituskoodit vaativat tehtävälle pikasuorituksen oletukset (tehtävän valintaikkuna → Pikasuorituksen oletukset) — minkään valitun kohteen tehtävällä ei ole niitä vielä.",
"qr_print_quick_none": "Pikasuorituskoodit vaativat tehtävälle pikasuorituksen oletukset (Asetukset → Lisäominaisuudet → Suoritustoiminnot; tehtävän valintaikkuna → Pikasuorituksen oletukset) — minkään valitun kohteen tehtävällä ei ole niitä vielä.",
"qr_url_mode": "Linkin tyyppi",
"qr_mode_companion": "Companion-sovellus",
"qr_mode_local": "Paikallinen (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "Ota lisäominaisuudet käyttöön tai pois käytöstä. Poistaminen piilottaa ne käyttöliittymästä mutta ei poista tietoja.",
"feat_adaptive": "Mukautuva ajoitus",
"feat_adaptive_desc": "Opi optimaaliset välit huoltohistoriasta",
"feat_predictions": "Anturiennusteet",
"feat_predictions_desc": "Ennusta laukaisupäivät anturin kulumisesta",
"feat_seasonal": "Kausisäädöt",
"feat_seasonal_desc": "Säädä välejä kausivaihteluiden mukaan",
"feat_environmental": "Ympäristökorrelaatio",
@@ -671,6 +676,10 @@
"settings_currency": "Valuutta",
"settings_currency_decimals": "Summien desimaalit",
"settings_currency_decimals_hint": "Montako desimaalia jokainen summa näyttää: tunnusluvut, budjetit, kustannukset listoissa ja raporteissa, budjettihälytykset. 0 = kokonaisluvut.",
"settings_parts_cost_mode": "Varaosat lasketaan kuluksi",
"settings_parts_cost_purchase": "Ostettaessa",
"settings_parts_cost_use": "Käytettäessä",
"settings_parts_cost_mode_hint": "Ostettaessa: ostomuistutuksen kulu lasketaan, ja työssä käytetyt osat näytetään tiedoksi. Käytettäessä: jokainen valmistuminen kirjaa käytettyjen osien arvon, ja osto asettaa osan hinnan sen sijaan, että laskettaisiin. Koskee valmistumisia tästä eteenpäin — historia säilyttää kirjaustavan.",
"settings_budget_monthly": "Kuukausibudjetti",
"settings_budget_yearly": "Vuosibudjetti",
"settings_budget_alerts": "Budjettihälytykset",
@@ -683,6 +692,7 @@
"settings_import_placeholder": "Liitä JSON- tai CSV-sisältö tähän…",
"settings_import_btn": "Tuo",
"settings_import_success": "{count} kohdetta tuotu onnistuneesti.",
"settings_import_unmatched_users": "Ei löytynyt tästä instanssista: {names}. Heidän tehtävämäärityksensä poistettiin — luo Home Assistantiin samannimiset henkilöt ja tuo uudelleen säilyttääksesi ne.",
"settings_export_success": "Vienti ladattu.",
"settings_saved": "Asetus tallennettu.",
"settings_include_history": "Sisällytä historia",
@@ -771,7 +781,7 @@
"has_overdue": "Sisältää myöhässä olevia tehtäviä",
"object": "Kohde",
"settings_panel_access": "Paneelin käyttöoikeus",
"settings_panel_access_desc": "Ylläpitäjillä on aina täydet oikeudet. Delegoidaksesi luonnin, muokkauksen ja poiston tietyille ei-ylläpitäjille, kytke tämä päälle ja valitse heidät alta — muut näkevät vain Valmis ja Ohita.",
"settings_panel_access_desc": "Ylläpitäjillä on aina täydet oikeudet. Delegoidaksesi luonnin, muokkauksen ja poiston tietyille ei-ylläpitäjille, kytke tämä päälle ja valitse heidät alta — muilla on vain Valmis, Ohita, Nollaa, Lykkää ja Torkku.",
"settings_operator_write": "Salli valittujen käyttäjien luoda, muokata ja poistaa",
"settings_operator_write_desc": "Pois: vain ylläpitäjät voivat muuttaa sisältöä. Päällä: myös alla valitut käyttäjät saavat täydet oikeudet.",
"no_non_admin_users": "Ei-ylläpitäjäkäyttäjiä ei löytynyt. Lisää heitä kohdassa Asetukset → Henkilöt.",
@@ -782,11 +792,16 @@
"on_complete_action_desc": "Kutsuu HA-palvelua, kun tehtävä suoritetaan — esim. nollaa laitteen laskuri.",
"on_complete_action_target": "Kohde-entiteetti",
"on_complete_action_target_hint": "Huom: entiteetin toimialueen on vastattava palvelua — esim. 'button.press' toimii vain button.*-entiteeteillä, 'counter.increment' vain counter.*-entiteeteillä, 'input_button.press' vain input_button.*-entiteeteillä jne. Epäsopivuuden sattuessa toiminto epäonnistuu hiljaisesti (HA kirjaa 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Tällä toiminnolla on enemmän kohteita kuin tämä kenttä näyttää: {targets}. Ne säilyvät ennallaan — jos valitset tähän toisen entiteetin, se korvaa koko kohteen.",
"on_complete_action_skip_auto": "Ei, kun tehtävä suorittuu itsestään (sen anturi palautui)",
"on_complete_action_data": "Tiedot (JSON, valinnainen)",
"on_complete_action_test": "Tarkista asetukset",
"on_complete_action_test_success": "✓ Asetukset kelvolliset (toiminto laukeaa vain tehtävän suorituksen yhteydessä)",
"on_complete_action_test_failed": "Epäonnistui",
"action_test_bad_format": "Virheellinen palvelun muoto (odotettiin 'domain.service')",
"action_test_unknown_service": "Palvelua \"{service}\" ei ole rekisteröity Home Assistantiin. Tarkista kirjoitusasu ja että sen tarjoava integraatio on ladattu.",
"action_test_domain_mismatch": "Palvelu \"{service}\" toimii vain {domain}.*-entiteeteillä; entiteetti \"{entity}\" kuuluu toimialueeseen {entity_domain}.* — valitse entiteetin toimialueeseen sopiva palvelu (esim. {suggestion})",
"action_test_entity_missing": "Kohde-entiteettiä \"{entity}\" ei löytynyt Home Assistantista — se on ehkä nimetty uudelleen tai sen integraatio poistettu.",
"quick_complete_defaults_title": "Pikasuorituksen oletukset (QR-skannauksille, valinnainen)",
"quick_complete_defaults_desc": "Esiasetetut arvot pikasuoritus-QR-skannauksille. Ilman näitä QR avaa suoritusikkunan.",
"quick_complete_defaults_notes": "Muistiinpanot",
@@ -844,6 +859,7 @@
"doc_too_large": "Tiedosto on liian suuri (enint. 25 Mt).",
"doc_upload_failed": "Lataus epäonnistui.",
"docs_archive_too_large": "Arkisto on liian suuri (enintään {max}).",
"docs_archive_no_manifest": "Tässä ZIP-tiedostossa ei ole manifest.json-tiedostoa, joten palautus ei tiedä, mihin asiakirjaan tiedosto kuuluu. Pidä manifest.json kansioiden vieressä, kun pakkaat uudelleen.",
"completion_photos_optional": "Valokuvat suorituksesta (valinnainen)",
"completion_photos": "Valokuvat suorituksesta",
"choose_photos": "Valitse kuvat",
@@ -904,6 +920,11 @@
"paused": "Keskeytetty",
"pause_object": "Keskeytä",
"resume_object": "Jatka",
"pause_task": "Keskeytä",
"resume_task": "Jatka",
"pause_task_prompt": "Jäädyttää tämän tehtävän aikataulun — se ei erääny eikä ilmoita ennen kuin jatkat sitä, ja jatkaminen aloittaa uuden jakson siitä päivästä. Kohteen muut tehtävät jatkuvat. Valinnainen päivä, jolloin se jatkuu itsestään.",
"task_paused": "Tehtävä keskeytetty",
"task_resumed": "Tehtävää jatkettu — aikataulu alkoi alusta",
"pause_until_prompt": "Jäädytä tämän kohteen aikataulut — mikään ei eräänny eikä ilmoita ennen jatkamista. Aseta halutessasi automaattisen jatkamisen päivämäärä.",
"pause_until_label": "Jatka (valinnainen)",
"object_paused": "Kohde keskeytetty",
@@ -913,7 +934,18 @@
"replace_object": "Korvaa…",
"replace_object_prompt": "Poista tämä kohde käytöstä ja luo seuraaja. Historia ja kustannukset pysyvät arkistoituina vanhassa; tehtävät ja asiakirjat siirtyvät uuteen, laskurit alkavat alusta.",
"replace_name_label": "Seuraajan nimi",
"replace_device_heading": "Laite Home Assistantissa",
"replace_device_keep": "Sama laite kuin ennen ({device})",
"replace_device_keep_hint": "Valitse tämä myös, jos uusi laite ei ole vielä Home Assistantissa: kun se on, valitse se kohteen asetuksissa laitteeksi — anturiliipaisimet siirtyvät mukana.",
"replace_device_other": "Uusi laite on eri laite",
"replace_device_pick": "Uuden laitteen laite",
"replace_device_other_hint": "Anturiliipaisimet, valmistumistoiminnot ja laskurin nollaus siirtyvät kyseisen laitteen vastaaviin entiteetteihin.",
"replace_device_none": "Ei laitetta",
"object_replaced": "Kohde korvattu — seuraaja luotu",
"device_swap_moved": "{count} anturiyhteyttä siirretty uuteen laitteeseen.",
"device_swap_moved_one": "Yksi anturiyhteys siirretty uuteen laitteeseen.",
"device_swap_unmatched": "{count} ei voitu yhdistää — tarkista niiden tehtävien anturiliipaisimet.",
"device_swap_unmatched_one": "Yhtä ei voitu yhdistää — tarkista tehtävän anturiliipaisin.",
"reading_unit_label": "Lukeman yksikkö (esim. kWh, m³)",
"reading_unit_help": "Näytetään kirjatun arvon vieressä tehtävää suoritettaessa.",
"reading_value_label": "Lukema",
@@ -1010,6 +1042,9 @@
"part_stock": "Varasto",
"part_reorder_threshold": "Tilausraja",
"part_restock_quantity": "Täydennysmäärä",
"part_package_size": "Pakkauskoko (yksikköä)",
"part_restock_packages": "Täydennysmäärä (pakkausta)",
"part_package_hint": "Ostetaan pakkauksina, kulutetaan yksikköinä: varasto, tehtävän kulutus ja tilausraja lasketaan yksikköinä; täydennysmäärä ja hinta ovat pakkauskohtaisia.",
"part_auto_buy": "Ostotehtävä kun vähissä",
"part_restock": "Muuta varastoa",
"parts_used_by": "Käyttäjät",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Kohteen osia ei voitu ladata — osien kulutusvalinnat eivät ole juuri nyt käytettävissä.",
"settings_export_selection": "Rajaa valittuihin kohteisiin (valinnainen)",
"settings_docs_archive": "Asiakirja-arkisto (tiedostoineen)",
"settings_docs_archive_hint": "JSON/YAML/CSV-viennit sisältävät vain asetukset. Tämä ZIP sisältää ladattujen tiedostojen sisällön, joten palautus on täydellinen.",
"settings_docs_archive_hint": "JSON- ja YAML-viennit luettelevat asiakirjasi mutta eivät sisällä niiden tiedostoja; CSV jättää asiakirjat pois. Tämä ZIP sisältää ladatut tiedostot – palauta se JSON-tuonnin jälkeen, niin mitään ei puutu.",
"settings_docs_export_btn": "Lataa asiakirjojen ZIP",
"settings_docs_import_btn": "Palauta asiakirjojen ZIP",
"settings_docs_import_success": "Palautettu: {blobs} tiedostoa, {docs} asiakirjaa",
"settings_docs_import_missing": "{n} ohitettu — niiden tiedostot puuttuivat ZIP-tiedostosta",
"adopt_problem_button": "Ota ongelma-anturit käyttöön",
"adopt_problem_title": "Ota ongelma-anturit käyttöön",
"adopt_problem_hint": "Muuttaa HA:n ongelma-anturit (tulostinvirheet, suodatinvaroitukset, heikko akku) huoltotehtäviksi, jotka laukeavat ongelman ollessa aktiivinen ja päättyvät itsestään, kun se ratkeaa.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Laskee käytön viime suorituksesta: erääntyy +{target} jälkeen; laskuri alkaa alusta jokaisen suorituksen jälkeen.",
"trigger_hint_counter_abs": "Tehtävä erääntyy, kun anturi saavuttaa {target}.",
"trigger_hint_runtime": "Tehtävä erääntyy {hours} h kertyneen käyntiajan jälkeen; laskuri alkaa alusta jokaisen suorituksen jälkeen.",
"trigger_hint_due_date": "Tehtävä erääntyy {days} päivää ennen anturin ilmoittamaa päivää.",
"trigger_hint_due_date_one": "Tehtävä erääntyy päivää ennen anturin ilmoittamaa päivää.",
"trigger_hint_due_date_same_day": "Tehtävä erääntyy anturin ilmoittamana päivänä.",
"trigger_hint_due_date_now": "Ilmoitettu päivämäärä: {date}.",
"trigger_hint_state_change": "Tehtävä erääntyy {count} tilanmuutoksen jälkeen.",
"trigger_hint_state_change_to": "Tehtävä erääntyy {count} muutoksen jälkeen tilaan ”{state}”.",
"trigger_hint_state_now": "Nykyinen tila: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Vain valitun näkymän tunniste-/käyttäjäsuodattimiin osuvat tehtävät lähettävät muistutuksia. Näkymän tila, lajittelu ja ryhmittely ohitetaan tässä.",
"settings_notify_completed": "Ilmoitukset suorituksista",
"settings_notify_completed_hint": "Tieto taloudelle, kun tehtävä on suoritettu, syyn ja tekijän kera. ”Vain automaattiset” ilmoittaa vain suoritukset, joita kukaan ei tehnyt paikan päällä: anturin palautuminen, ostoslistan rastitus, automaation palvelukutsu. Vain talouden palveluun; hiljaiset tunnit ja päiväraja pätevät.",
"settings_title_style": "Ilmoituksen otsikon tyyli",
"settings_title_style_hint": "Mikä näkyy ilmoituksen OTSIKKONA. Oletus säilyttää tilakohtaisen tekstin (esim. 'Huolto myöhässä!'). Kohteen nimi / Tehtävän nimi auttavat, kun puhelinilmoitukset pinoutuvat.",
"title_style_default": "Oletus (tilakohtainen otsikko)",
"title_style_object_name": "Kohteen nimi otsikkona",
"title_style_task_name": "Tehtävän nimi otsikkona",
"notify_completed_off": "Pois",
"notify_completed_automatic": "Vain automaattiset suoritukset",
"notify_completed_all": "Kaikki suoritukset",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON tai YAML, Jinja sallittu; yhdistetään notify-kutsun data-kenttään (omat avaimesi voittavat). Muuttujat: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Kokeile Lähetä testi -painikkeella.",
"card_saved_view": "Tallennettu näkymä",
"card_saved_view_none": "Ei mitään",
"card_saved_view_help": "Soveltaa näkymän tila-, käyttäjä- ja tunnistesuodattimet yllä olevien suodattimien lisäksi. Näkymän lajittelu ja ryhmittely ovat paneelin näyttöasetuksia eivätkä vaikuta korttiin.",
"card_saved_view_help": "Soveltaa näkymän tila-, käyttäjä-, tunniste- ja prioriteettisuodattimet yllä olevien suodattimien lisäksi. Näkymän lajittelu ja ryhmittely ovat paneelin näyttöasetuksia eivätkä vaikuta korttiin.",
"panel_card_tab": "Avaa välilehdellä",
"panel_card_tab_default": "Muistettu välilehti (kuten paneelissa)",
"panel_card_view": "Avaa tallennetulla näkymällä",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Takaisin kuvakkeisiin",
"battery_fleet_forecast_overdue": "Ennustettu päivä on ohitettu — akku ilmoittaa yhä hyvästä kunnosta. Jos vaihdoit sen, kirjaa vaihto; muuten ennuste oli pielessä.",
"cost_from_parts": "Käytä ≈ {amount} osista",
"cost_parts_booked": "Käytetyt osat ≈ {amount} kirjataan automaattisesti — syötä vain lisäkulut.",
"cost_parts_info": "Käytetyt osat ≈ {amount} — laskettu ostettaessa, ei tässä.",
"cost_purchase_use_hint": "Maksettu hinta tulee osan hinnaksi — varaosat lasketaan käytettäessä.",
"history_parts_counted": "Osat: {amount}",
"history_parts_counted_hint": "Käytettyjen osien arvo — laskettu kuluksi",
"history_parts_info": "Osat ≈ {amount}",
"history_parts_info_hint": "Käytettyjen osien arvo — vain tiedoksi; raha laskettiin ostettaessa",
"history_purchase_stock": "Ostettu hintaan {amount} (lasketaan käytettäessä)",
"dismiss": "Hylkää",
"gs_label": "Aloitus — nämä vihjeet poistuvat asennuksen kasvaessa",
"gs_setups_chip": "Ehdotetut asetukset löysivät {n} laitetta valmiilla laukaisimilla",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Kaksi viikkoa (14 päivää)",
"cal_editor_window_month": "Kuukausi (30 päivää, oletus)",
"cal_editor_window_year": "Vuosi (365 päivää, tyhjät päivät piilotettu)",
"cal_editor_window_past_30": "Viimeiset 30 päivää (historia)",
"cal_editor_window_past_90": "Viimeiset 90 päivää (historia)",
"cal_editor_show_chips": "Näytä ikkunavalinnat kortissa",
"cal_editor_chips_hint": "Piilota valinnat, kun kortti on strategianäkymässä, joka jo toimii ikkunan valitsimena.",
"cal_editor_show_user_filter": "Näytä käyttäjäsuodatin",
@@ -36,6 +36,8 @@
"reset": "Réinitialiser",
"snooze": "Mettre en attente",
"snoozed": "En attente",
"snoozed_for": "Rappels coupés pendant {hours} heures — jusqu'à {until}",
"snoozed_for_one": "Rappels coupés pendant une heure — jusqu'à {until}",
"cancel": "Annuler",
"bulk_select": "Sélectionner",
"bulk_select_all": "Tout sélectionner",
@@ -61,7 +63,10 @@
"counter": "Compteur",
"state_change": "Changement d'état",
"runtime": "Durée de fonctionnement",
"trigger_type_due_date": "Date d'un capteur",
"runtime_hours": "Durée cible (heures)",
"days_before": "Jours avant la date",
"days_before_help": "Pour un capteur qui indique quand l'entretien est dû (date ou horodatage). 0 = le jour même. Le bouton « fait » de l'appareil repousse la date, ce qui lève le déclencheur — faites-en l'action à la réalisation.",
"target_value": "Valeur cible",
"target_changes": "Changements cibles",
"for_minutes": "Pendant (minutes)",
@@ -233,6 +238,8 @@
"model_optional": "Modèle (optionnel)",
"serial_number_optional": "Numéro de série (optionnel)",
"serial_number_label": "N/S",
"object_replaces": "Remplace",
"object_replaced_by": "Remplacé par",
"documentation_url_label": "Manuel",
"object_notes_label": "Notes",
"sort_due_date": "Échéance",
@@ -420,7 +427,7 @@
"qr_action_view": "Afficher les infos de maintenance",
"qr_action_complete": "Marquer la maintenance comme terminée",
"qr_action_quick_complete": "Finalisation rapide — sans fenêtre",
"qr_print_quick_none": "Les codes de finalisation rapide nécessitent des valeurs par défaut de finalisation rapide sur une tâche (fenêtre de la tâche → Valeurs par défaut pour finalisation rapide) — aucune tâche des objets choisis n'en a encore.",
"qr_print_quick_none": "Les codes de finalisation rapide nécessitent des valeurs par défaut de finalisation rapide sur une tâche (Paramètres → Fonctions avancées → Actions de finalisation; fenêtre de la tâche → Valeurs par défaut pour finalisation rapide) — aucune tâche des objets choisis n'en a encore.",
"qr_url_mode": "Type de lien",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Local (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Activez ou désactivez les fonctions avancées. La désactivation les masque dans l'interface mais ne supprime pas les données.",
"feat_adaptive": "Planification adaptative",
"feat_adaptive_desc": "Apprendre les intervalles optimaux à partir de l'historique",
"feat_predictions": "Prédictions capteurs",
"feat_predictions_desc": "Prédire les dates de déclenchement par dégradation des capteurs",
"feat_seasonal": "Ajustements saisonniers",
"feat_seasonal_desc": "Ajuster les intervalles selon les tendances saisonnières",
"feat_environmental": "Corrélation environnementale",
@@ -669,6 +674,10 @@
"settings_currency": "Devise",
"settings_currency_decimals": "Décimales des montants",
"settings_currency_decimals_hint": "Nombre de décimales affichées pour chaque montant : indicateurs, budgets, coûts dans les listes et rapports, alertes budget. 0 = nombres entiers.",
"settings_parts_cost_mode": "Les pièces comptent comme dépense",
"settings_parts_cost_purchase": "À l'achat",
"settings_parts_cost_use": "À l'utilisation",
"settings_parts_cost_mode_hint": "À l'achat : le coût d'un rappel d'achat compte, les pièces utilisées par une tâche sont indiquées pour information. À l'utilisation : chaque réalisation comptabilise la valeur des pièces utilisées, et un achat fixe le prix de la pièce au lieu de compter. S'applique aux réalisations à partir de maintenant — l'historique garde sa comptabilisation.",
"settings_budget_monthly": "Budget mensuel",
"settings_budget_yearly": "Budget annuel",
"settings_budget_alerts": "Alertes budgétaires",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Collez le contenu JSON ou CSV ici…",
"settings_import_btn": "Importer",
"settings_import_success": "{count} objets importés avec succès.",
"settings_import_unmatched_users": "Introuvable sur cette instance : {names}. Leurs attributions de tâches ont été supprimées — créez des personnes portant les mêmes noms dans Home Assistant et importez à nouveau pour les conserver.",
"settings_export_success": "Export téléchargé.",
"settings_saved": "Paramètre enregistré.",
"settings_include_history": "Inclure l'historique",
@@ -746,7 +756,7 @@
"has_overdue": "Tâches en retard",
"object": "Objet",
"settings_panel_access": "Accès au panneau",
"settings_panel_access_desc": "Les administrateurs ont toujours un accès complet. Pour déléguer la création, la modification et la suppression à des non-administrateurs spécifiques, activez ceci et sélectionnez-les ci-dessous — les autres ne voient que Terminer et Ignorer.",
"settings_panel_access_desc": "Les administrateurs ont toujours un accès complet. Pour déléguer la création, la modification et la suppression à des non-administrateurs spécifiques, activez ceci et sélectionnez-les ci-dessous — les autres n'ont que Terminé, Passer, Réinitialiser, Reporter et Mettre en attente.",
"settings_operator_write": "Autoriser les utilisateurs sélectionnés à créer, modifier et supprimer",
"settings_operator_write_desc": "Désactivé : seuls les administrateurs peuvent modifier le contenu. Activé : les utilisateurs ci-dessous ont aussi un accès complet.",
"no_non_admin_users": "Aucun utilisateur non administrateur trouvé. Ajoutez-en dans Paramètres → Personnes.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Tester l'action",
"on_complete_action_test_success": "Réussi",
"on_complete_action_test_failed": "Échoué",
"action_test_bad_format": "Format de service invalide (attendu : 'domain.service')",
"action_test_unknown_service": "Le service « {service} » n'est pas enregistré dans Home Assistant. Vérifiez l'orthographe et que l'intégration qui le fournit est chargée.",
"action_test_domain_mismatch": "Le service « {service} » ne fonctionne que sur les entités {domain}.* ; l'entité « {entity} » appartient à {entity_domain}.* — choisissez un service correspondant au domaine de l'entité (par ex. {suggestion})",
"action_test_entity_missing": "L'entité cible « {entity} » est introuvable dans Home Assistant — elle a peut-être été renommée ou son intégration supprimée.",
"quick_complete_defaults_title": "Valeurs par défaut pour finalisation rapide (scans QR, optionnel)",
"quick_complete_defaults_desc": "Valeurs pré-définies pour les scans QR de finalisation rapide. Sans ces valeurs, le QR ouvre la boîte de dialogue.",
"quick_complete_defaults_notes": "Notes",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Gérer les affectations de tâches…",
"groups_open_panel": "Ouvrir dans le panneau",
"on_complete_action_target_hint": "Note : le domaine de l'entité doit correspondre au service — p. ex. 'button.press' ne fonctionne que sur button.*, 'counter.increment' seulement sur counter.*, 'input_button.press' seulement sur input_button.* etc. En cas de non-correspondance, l'action échoue silencieusement (HA journalise 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Cette action vise plus que ce que ce champ affiche : {targets}. C'est conservé tel quel — choisir une autre entité ici remplace toute la cible.",
"on_complete_action_skip_auto": "Pas quand la tâche se termine d'elle-même (son capteur s'est rétabli)",
"show_all_objects": "Afficher tous les objets",
"show_all_tasks": "Effacer le filtre — afficher toutes les tâches",
@@ -844,6 +859,7 @@
"doc_too_large": "Fichier trop volumineux (max. 25 Mo).",
"doc_upload_failed": "Échec du téléversement.",
"docs_archive_too_large": "Archive trop volumineuse (max. {max}).",
"docs_archive_no_manifest": "Ce ZIP ne contient pas de manifest.json : la restauration ne peut pas savoir à quel document appartient un fichier. Gardez manifest.json à côté des dossiers lorsque vous recompressez.",
"completion_photos_optional": "Photos de fin de tâche (facultatif)",
"completion_photos": "Photos de fin de tâche",
"choose_photos": "Choisir des photos",
@@ -904,6 +920,11 @@
"paused": "En pause",
"pause_object": "Mettre en pause",
"resume_object": "Reprendre",
"pause_task": "Mettre en pause",
"resume_task": "Reprendre",
"pause_task_prompt": "Gèle le planning de cette tâche — elle ne devient pas due et ne notifie rien jusqu'à ce que tu la reprennes ; la reprise démarre un nouveau cycle à partir de ce jour. Les autres tâches de l'objet continuent. Date de reprise automatique facultative.",
"task_paused": "Tâche mise en pause",
"task_resumed": "Tâche reprise — son planning redémarre",
"pause_until_prompt": "Gèle les calendriers de cet objet — rien n'arrive à échéance et rien ne notifie jusqu'à la reprise. Optionnellement, fixez une date de reprise automatique.",
"pause_until_label": "Reprendre le (optionnel)",
"object_paused": "Objet mis en pause",
@@ -913,7 +934,18 @@
"replace_object": "Remplacer…",
"replace_object_prompt": "Retire cet objet et crée un successeur. L'historique et les coûts restent archivés sur l'ancien ; les tâches et documents passent au nouveau, les compteurs repartent de zéro.",
"replace_name_label": "Nom du successeur",
"replace_device_heading": "Appareil dans Home Assistant",
"replace_device_keep": "Le même appareil qu'avant ({device})",
"replace_device_keep_hint": "C'est aussi le choix si le nouvel appareil n'est pas encore dans Home Assistant : une fois ajouté, choisis-le comme appareil dans les réglages de l'objet — les déclencheurs de capteurs suivent alors.",
"replace_device_other": "Le nouvel appareil est un autre appareil",
"replace_device_pick": "Appareil du nouvel exemplaire",
"replace_device_other_hint": "Les déclencheurs de capteurs, les actions de fin et la remise à zéro du compteur passent aux entités correspondantes de cet appareil.",
"replace_device_none": "Aucun appareil",
"object_replaced": "Objet remplacé — successeur créé",
"device_swap_moved": "{count} liens de capteurs déplacés vers le nouvel appareil.",
"device_swap_moved_one": "Un lien de capteur déplacé vers le nouvel appareil.",
"device_swap_unmatched": "{count} n'ont pas pu être associés — vérifie les déclencheurs de capteurs de ces tâches.",
"device_swap_unmatched_one": "Un n'a pas pu être associé — vérifie le déclencheur de capteur de cette tâche.",
"reading_unit_label": "Unité de relevé (p. ex. kWh, m³)",
"reading_unit_help": "Affichée à côté de la valeur enregistrée lors de l'exécution de cette tâche.",
"reading_value_label": "Valeur relevée",
@@ -1010,6 +1042,9 @@
"part_stock": "Stock",
"part_reorder_threshold": "Seuil de réappro",
"part_restock_quantity": "Quantité de réappro",
"part_package_size": "Taille du conditionnement (unités)",
"part_restock_packages": "Quantité de réapprovisionnement (conditionnements)",
"part_package_hint": "Acheté par conditionnement, utilisé à l'unité : le stock, la quantité utilisée par tâche et le seuil de réapprovisionnement comptent des unités ; la quantité de réapprovisionnement et le prix sont par conditionnement.",
"part_auto_buy": "Tâche d'achat auto si bas",
"part_restock": "Ajuster le stock",
"parts_used_by": "Utilisé par",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Impossible de charger les pièces de cet objet — les options de consommation de pièces sont indisponibles pour le moment.",
"settings_export_selection": "Limiter aux objets sélectionnés (facultatif)",
"settings_docs_archive": "Archive de documents (avec fichiers)",
"settings_docs_archive_hint": "Les exports JSON/YAML/CSV ne contiennent que les réglages. Ce ZIP inclut le contenu des fichiers téléversés pour une restauration complète.",
"settings_docs_archive_hint": "Les exports JSON et YAML listent vos documents, mais pas leurs fichiers ; le CSV omet les documents. Ce ZIP contient les fichiers téléversés — restaurez-le après l'import du JSON et rien ne manque.",
"settings_docs_export_btn": "Télécharger le ZIP des documents",
"settings_docs_import_btn": "Restaurer le ZIP des documents",
"settings_docs_import_success": "Restauré : {blobs} fichiers, {docs} documents",
"settings_docs_import_missing": "{n} ignorés — leurs fichiers n'étaient pas dans le ZIP",
"adopt_problem_button": "Adopter les capteurs de problème",
"adopt_problem_title": "Adopter les capteurs de problème",
"adopt_problem_hint": "Transforme les capteurs de problème HA (erreurs d'imprimante, alertes de filtre, batterie faible) en tâches de maintenance qui se déclenchent tant que le problème est actif et se terminent lorsqu'il est résolu.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Compte l'usage depuis la dernière exécution : échéance après +{target} ; le compteur repart après chaque exécution.",
"trigger_hint_counter_abs": "La tâche devient due dès que le capteur atteint {target}.",
"trigger_hint_runtime": "La tâche devient due après {hours} h de fonctionnement cumulé ; le compteur repart après chaque exécution.",
"trigger_hint_due_date": "La tâche devient due {days} jours avant la date indiquée par le capteur.",
"trigger_hint_due_date_one": "La tâche devient due un jour avant la date indiquée par le capteur.",
"trigger_hint_due_date_same_day": "La tâche devient due à la date indiquée par le capteur.",
"trigger_hint_due_date_now": "Date indiquée : {date}.",
"trigger_hint_state_change": "La tâche devient due après {count} changement(s) d'état.",
"trigger_hint_state_change_to": "La tâche devient due après {count} passage(s) à « {state} ».",
"trigger_hint_state_now": "État actuel : {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Seules les tâches correspondant aux filtres étiquette/utilisateur de la vue choisie envoient des rappels. Le statut, le tri et le regroupement de la vue sont ignorés ici.",
"settings_notify_completed": "Notifications d'achèvement",
"settings_notify_completed_hint": "Une info pour le foyer quand une tâche est terminée, avec la raison et l'auteur. « Automatiques seulement » n'annonce que les achèvements que personne n'a faits sur place : retour du capteur, case cochée dans la liste de courses, appel de service d'une automatisation. Envoyé au service du foyer seulement ; heures calmes et plafond quotidien s'appliquent.",
"settings_title_style": "Style du titre de notification",
"settings_title_style_hint": "Ce qui apparaît comme TITRE de la notification. Par défaut, le texte par statut est conservé (p. ex. « Maintenance en retard ! »). Nom de l'objet / tâche aide quand le téléphone empile les notifications.",
"title_style_default": "Par défaut (titre par statut)",
"title_style_object_name": "Nom de l'objet comme titre",
"title_style_task_name": "Nom de la tâche comme titre",
"notify_completed_off": "Désactivé",
"notify_completed_automatic": "Achèvements automatiques seulement",
"notify_completed_all": "Tous les achèvements",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja autorisé ; fusionné dans les data de l'appel notify (vos clés l'emportent). Variables : kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Vérifiez avec le bouton Envoyer un test.",
"card_saved_view": "Vue enregistrée",
"card_saved_view_none": "Aucune",
"card_saved_view_help": "Applique les filtres statut, utilisateur et étiquette de la vue en plus des filtres ci-dessus. Le tri et le regroupement de la vue sont des réglages d'affichage du panneau et ne s'appliquent pas à la carte.",
"card_saved_view_help": "Applique les filtres statut, utilisateur, étiquette et priorité de la vue en plus des filtres ci-dessus. Le tri et le regroupement de la vue sont des réglages d'affichage du panneau et ne s'appliquent pas à la carte.",
"panel_card_tab": "Ouvrir sur l'onglet",
"panel_card_tab_default": "Onglet mémorisé (comme dans le panneau)",
"panel_card_view": "Ouvrir avec la vue enregistrée",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Revenir aux icônes",
"battery_fleet_forecast_overdue": "Date prévue dépassée — la batterie se signale toujours en bon état. Si vous l'avez remplacée, enregistrez le remplacement ; sinon la prévision était erronée.",
"cost_from_parts": "Reprendre ≈ {amount} des pièces",
"cost_parts_booked": "Les pièces utilisées ≈ {amount} sont comptabilisées automatiquement — saisis seulement les frais supplémentaires.",
"cost_parts_info": "Pièces utilisées ≈ {amount} — comptées à l'achat, pas ici.",
"cost_purchase_use_hint": "Le prix payé devient le prix de la pièce — les pièces comptent à l'utilisation.",
"history_parts_counted": "Pièces : {amount}",
"history_parts_counted_hint": "Valeur des pièces utilisées — comptée comme dépense",
"history_parts_info": "Pièces ≈ {amount}",
"history_parts_info_hint": "Valeur des pièces utilisées — pour information ; l'argent a été compté à l'achat",
"history_purchase_stock": "Acheté pour {amount} (compté à l'utilisation)",
"dismiss": "Ignorer",
"gs_label": "Premiers pas — ces conseils disparaissent à mesure que votre configuration grandit",
"gs_setups_chip": "Configurations suggérées : {n} appareils avec déclencheurs pré-câblés trouvés",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Quinzaine (14 jours)",
"cal_editor_window_month": "Mois (30 jours, défaut)",
"cal_editor_window_year": "Année (365 jours, jours vides masqués)",
"cal_editor_window_past_30": "30 derniers jours (historique)",
"cal_editor_window_past_90": "90 derniers jours (historique)",
"cal_editor_show_chips": "Afficher les puces de fenêtre dans la carte",
"cal_editor_chips_hint": "Masquez les puces lorsque la carte est intégrée dans une vue stratégie qui sert déjà de sélecteur de fenêtre.",
"cal_editor_show_user_filter": "Afficher le filtre utilisateur",
@@ -38,6 +38,8 @@
"reset": "रीसेट करें",
"snooze": "स्नूज़",
"snoozed": "स्नूज़ किया गया",
"snoozed_for": "रिमाइंडर {hours} घंटे के लिए बंद — {until} तक",
"snoozed_for_one": "रिमाइंडर एक घंटे के लिए बंद — {until} तक",
"cancel": "रद्द करें",
"bulk_select": "चुनें",
"bulk_select_all": "सभी चुनें",
@@ -63,7 +65,10 @@
"counter": "काउंटर",
"state_change": "स्थिति परिवर्तन",
"runtime": "रनटाइम",
"trigger_type_due_date": "सेंसर से तारीख",
"runtime_hours": "लक्ष्य रनटाइम (घंटे)",
"days_before": "तारीख से पहले के दिन",
"days_before_help": "उस सेंसर के लिए जो बताता है कि रखरखाव कब देय है (तारीख या टाइमस्टैम्प)। 0 = उसी तारीख को। डिवाइस का अपना \"हो गया\" बटन तारीख आगे बढ़ाता है और ट्रिगर हटा देता है — इसे पूरा होने पर की जाने वाली क्रिया बनाएँ।",
"target_value": "लक्ष्य मान",
"target_changes": "लक्ष्य परिवर्तन",
"for_minutes": "के लिए (मिनट)",
@@ -235,6 +240,8 @@
"model_optional": "मॉडल (वैकल्पिक)",
"serial_number_optional": "सीरियल नंबर (वैकल्पिक)",
"serial_number_label": "S/N",
"object_replaces": "पूर्ववर्ती",
"object_replaced_by": "उत्तराधिकारी",
"documentation_url_label": "मैनुअल",
"object_notes_label": "टिप्पणियाँ",
"sort_due_date": "देय तिथि",
@@ -422,7 +429,7 @@
"qr_action_view": "रखरखाव जानकारी देखें",
"qr_action_complete": "रखरखाव को पूर्ण के रूप में चिह्नित करें",
"qr_action_quick_complete": "त्वरित-पूर्ण — बिना संवाद के",
"qr_print_quick_none": "त्वरित-पूर्ण कोड के लिए किसी कार्य पर त्वरित-पूर्ण डिफ़ॉल्ट चाहिए (कार्य संवाद → त्वरित-पूर्ण डिफ़ॉल्ट) — चुनी गई वस्तुओं के किसी कार्य पर अभी ये नहीं हैं।",
"qr_print_quick_none": "त्वरित-पूर्ण कोड के लिए किसी कार्य पर त्वरित-पूर्ण डिफ़ॉल्ट चाहिए (सेटिंग्स → उन्नत सुविधाएँ → पूर्णता क्रियाएँ; कार्य संवाद → त्वरित-पूर्ण डिफ़ॉल्ट) — चुनी गई वस्तुओं के किसी कार्य पर अभी ये नहीं हैं।",
"qr_url_mode": "लिंक प्रकार",
"qr_mode_companion": "Companion ऐप",
"qr_mode_local": "लोकल (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "उन्नत सुविधाओं को सक्षम या अक्षम करें। अक्षम करने से वे UI से छिप जाती हैं पर डेटा नहीं हटता।",
"feat_adaptive": "अनुकूली निर्धारण",
"feat_adaptive_desc": "रखरखाव इतिहास से इष्टतम अंतराल सीखें",
"feat_predictions": "सेंसर पूर्वानुमान",
"feat_predictions_desc": "सेंसर क्षरण से ट्रिगर तिथियों का अनुमान लगाएँ",
"feat_seasonal": "मौसमी समायोजन",
"feat_seasonal_desc": "मौसमी पैटर्न के आधार पर अंतराल समायोजित करें",
"feat_environmental": "पर्यावरणीय सहसंबंध",
@@ -671,6 +676,10 @@
"settings_currency": "मुद्रा",
"settings_currency_decimals": "राशियों के दशमलव स्थान",
"settings_currency_decimals_hint": "हर राशि कितने दशमलव दिखाए: KPI, बजट, सूचियों और रिपोर्टों में लागत, बजट अलर्ट। 0 = पूर्ण संख्याएँ।",
"settings_parts_cost_mode": "स्पेयर पार्ट खर्च में गिने जाते हैं",
"settings_parts_cost_purchase": "खरीदते समय",
"settings_parts_cost_use": "इस्तेमाल होने पर",
"settings_parts_cost_mode_hint": "खरीदते समय: खरीद रिमाइंडर की लागत गिनी जाती है, और किसी काम में लगे पार्ट सिर्फ़ जानकारी के लिए दिखते हैं। इस्तेमाल होने पर: हर पूरा हुआ काम लगे पार्ट की कीमत दर्ज करता है, और खरीद गिनने के बजाय पार्ट की कीमत तय करती है। अब से होने वाले कामों पर लागू — इतिहास पहले जैसा दर्ज रहता है।",
"settings_budget_monthly": "मासिक बजट",
"settings_budget_yearly": "वार्षिक बजट",
"settings_budget_alerts": "बजट अलर्ट",
@@ -683,6 +692,7 @@
"settings_import_placeholder": "JSON या CSV सामग्री यहाँ चिपकाएँ…",
"settings_import_btn": "आयात करें",
"settings_import_success": "{count} वस्तुएँ सफलतापूर्वक आयात की गईं।",
"settings_import_unmatched_users": "इस इंस्टेंस पर नहीं मिले: {names}। उनके कार्य असाइनमेंट हटा दिए गए — उन्हें बनाए रखने के लिए Home Assistant में इन्हीं नामों वाले लोग बनाएँ और फिर से इंपोर्ट करें।",
"settings_export_success": "निर्यात डाउनलोड किया गया।",
"settings_saved": "सेटिंग सहेजी गई।",
"settings_include_history": "इतिहास शामिल करें",
@@ -771,7 +781,7 @@
"has_overdue": "अतिदेय कार्य हैं",
"object": "वस्तु",
"settings_panel_access": "पैनल पहुँच",
"settings_panel_access_desc": "व्यवस्थापकों को हमेशा पूर्ण पहुँच होती है। विशिष्ट गैर-व्यवस्थापकों को बनाना, संपादित करना और हटाना सौंपने के लिए, इसे चालू करें और नीचे उन्हें चुनें — बाकी सभी को केवल पूर्ण करें और छोड़ें दिखता है।",
"settings_panel_access_desc": "व्यवस्थापकों को हमेशा पूर्ण पहुँच होती है। विशिष्ट गैर-व्यवस्थापकों को बनाना, संपादित करना और हटाना सौंपने के लिए, इसे चालू करें और नीचे उन्हें चुनें — बाकी सभी केवल पूर्ण करें, छोड़ें, रीसेट करें, टालें और स्नूज़ का उपयोग कर सकते हैं।",
"settings_operator_write": "चयनित उपयोगकर्ताओं को बनाने, संपादित करने और हटाने की अनुमति दें",
"settings_operator_write_desc": "बंद: केवल व्यवस्थापक सामग्री बदल सकते हैं। चालू: नीचे चयनित उपयोगकर्ताओं को भी पूर्ण पहुँच मिलती है।",
"no_non_admin_users": "कोई गैर-व्यवस्थापक उपयोगकर्ता नहीं मिला। सेटिंग्स → लोग में कुछ जोड़ें।",
@@ -782,11 +792,16 @@
"on_complete_action_desc": "कार्य पूर्ण होने पर एक HA सेवा कॉल करता है — उदा. डिवाइस पर काउंटर रीसेट करना।",
"on_complete_action_target": "लक्ष्य एंटिटी",
"on_complete_action_target_hint": "ध्यान दें: एंटिटी डोमेन सेवा से मेल खाना चाहिए — उदा. 'button.press' केवल button.* पर काम करता है, 'counter.increment' केवल counter.* पर, 'input_button.press' केवल input_button.* पर आदि। मेल न खाने पर क्रिया चुपचाप विफल हो जाएगी (HA लॉग करता है 'Referenced entities ... missing or not currently available')।",
"on_complete_action_target_more": "इस क्रिया के लक्ष्य इस फ़ील्ड में दिखने से अधिक हैं: {targets}। वे जैसे हैं वैसे ही रहेंगे — यहाँ कोई दूसरी एंटिटी चुनने पर पूरा लक्ष्य बदल जाएगा।",
"on_complete_action_skip_auto": "तब नहीं जब कार्य स्वयं पूर्ण हो (उसका सेंसर ठीक हो गया)",
"on_complete_action_data": "डेटा (JSON, वैकल्पिक)",
"on_complete_action_test": "कॉन्फ़िगरेशन सत्यापित करें",
"on_complete_action_test_success": "✓ कॉन्फ़िगरेशन वैध (क्रिया केवल कार्य पूर्ण होने पर चलेगी)",
"on_complete_action_test_failed": "विफल",
"action_test_bad_format": "सेवा का प्रारूप अमान्य है ('domain.service' अपेक्षित)",
"action_test_unknown_service": "सेवा \"{service}\" Home Assistant में पंजीकृत नहीं है। वर्तनी जाँचें और देखें कि इसे देने वाला इंटीग्रेशन लोड है।",
"action_test_domain_mismatch": "सेवा \"{service}\" केवल {domain}.* एंटिटी पर काम करती है; एंटिटी \"{entity}\" {entity_domain}.* में है — एंटिटी के डोमेन से मेल खाने वाली सेवा चुनें (उदा. {suggestion})",
"action_test_entity_missing": "लक्ष्य एंटिटी \"{entity}\" Home Assistant में नहीं मिली — हो सकता है उसका नाम बदला गया हो या उसका इंटीग्रेशन हटा दिया गया हो।",
"quick_complete_defaults_title": "त्वरित-पूर्ण डिफ़ॉल्ट (QR स्कैन के लिए, वैकल्पिक)",
"quick_complete_defaults_desc": "त्वरित-पूर्ण QR स्कैन के लिए पूर्व-निर्धारित मान। इनके बिना, QR पूर्ण करें संवाद खोलता है।",
"quick_complete_defaults_notes": "टिप्पणियाँ",
@@ -844,6 +859,7 @@
"doc_too_large": "फ़ाइल बहुत बड़ी है (अधिकतम 25 MB)।",
"doc_upload_failed": "अपलोड विफल रहा।",
"docs_archive_too_large": "आर्काइव बहुत बड़ा है (अधिकतम {max})।",
"docs_archive_no_manifest": "इस ZIP में manifest.json नहीं है, इसलिए पुनर्स्थापना यह नहीं जान सकती कि कोई फ़ाइल किस दस्तावेज़ की है। दोबारा ज़िप करते समय manifest.json को फ़ोल्डरों के साथ रखें।",
"completion_photos_optional": "पूर्णता की तस्वीरें (वैकल्पिक)",
"completion_photos": "पूर्णता की तस्वीरें",
"choose_photos": "तस्वीरें चुनें",
@@ -904,6 +920,11 @@
"paused": "रोका गया",
"pause_object": "रोकें",
"resume_object": "फिर से शुरू करें",
"pause_task": "रोकें",
"resume_task": "फिर शुरू करें",
"pause_task_prompt": "इस काम का शेड्यूल रोक देता है — जब तक आप इसे फिर शुरू नहीं करते, यह न देय होगा न सूचना देगा; फिर शुरू करने पर उसी दिन से नया चक्र शुरू होता है। वस्तु के दूसरे काम चलते रहते हैं। चाहें तो अपने-आप फिर शुरू होने की तारीख चुनें।",
"task_paused": "काम रोका गया",
"task_resumed": "काम फिर शुरू — उसका शेड्यूल नए सिरे से",
"pause_until_prompt": "इस ऑब्जेक्ट के शेड्यूल फ़्रीज़ करें — फिर से शुरू करने तक कुछ भी देय नहीं होगा और कोई सूचना नहीं आएगी। वैकल्पिक रूप से स्वतः पुनरारंभ की तारीख़ सेट करें।",
"pause_until_label": "फिर से शुरू करें (वैकल्पिक)",
"object_paused": "ऑब्जेक्ट रोका गया",
@@ -913,7 +934,18 @@
"replace_object": "बदलें…",
"replace_object_prompt": "इस ऑब्जेक्ट को सेवानिवृत्त करें और उत्तराधिकारी बनाएं। इतिहास और लागत पुराने पर संग्रहीत रहती है; कार्य और दस्तावेज़ नए में चले जाते हैं, काउंटर नए सिरे से शुरू होते हैं।",
"replace_name_label": "उत्तराधिकारी का नाम",
"replace_device_heading": "Home Assistant में डिवाइस",
"replace_device_keep": "पहले जैसा ही डिवाइस ({device})",
"replace_device_keep_hint": "नया डिवाइस अभी Home Assistant में नहीं है, तब भी यही चुनें: जुड़ जाने पर उसे ऑब्जेक्ट की सेटिंग में डिवाइस के रूप में चुनें — सेंसर ट्रिगर साथ चले जाएंगे।",
"replace_device_other": "नया यूनिट दूसरा डिवाइस है",
"replace_device_pick": "नए यूनिट का डिवाइस",
"replace_device_other_hint": "सेंसर ट्रिगर, पूरा होने पर होने वाले एक्शन और काउंटर रीसेट उस डिवाइस की मेल खाती एंटिटी पर चले जाएंगे।",
"replace_device_none": "कोई डिवाइस नहीं",
"object_replaced": "ऑब्जेक्ट बदला गया — उत्तराधिकारी बनाया गया",
"device_swap_moved": "{count} सेंसर लिंक नए डिवाइस पर चले गए।",
"device_swap_moved_one": "एक सेंसर लिंक नए डिवाइस पर चला गया।",
"device_swap_unmatched": "{count} का मिलान नहीं हो सका — उन कामों के सेंसर ट्रिगर जांचें।",
"device_swap_unmatched_one": "एक का मिलान नहीं हो सका — उस काम का सेंसर ट्रिगर जांचें।",
"reading_unit_label": "रीडिंग इकाई (जैसे kWh, m³)",
"reading_unit_help": "इस कार्य को पूरा करते समय दर्ज मान के बगल में दिखाई जाती है।",
"reading_value_label": "रीडिंग मान",
@@ -1010,6 +1042,9 @@
"part_stock": "स्टॉक",
"part_reorder_threshold": "पुनः ऑर्डर सीमा",
"part_restock_quantity": "पुनःपूर्ति मात्रा",
"part_package_size": "पैकेट का आकार (इकाइयाँ)",
"part_restock_packages": "दोबारा भरने की मात्रा (पैकेट)",
"part_package_hint": "पैकेट में खरीदा, इकाई में इस्तेमाल: स्टॉक, हर काम की खपत और दोबारा मंगाने की सीमा इकाइयों में गिनी जाती हैं; दोबारा भरने की मात्रा और कीमत प्रति पैकेट हैं।",
"part_auto_buy": "कम होने पर खरीद कार्य",
"part_restock": "स्टॉक समायोजित करें",
"parts_used_by": "द्वारा उपयोग",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "इस ऑब्जेक्ट के पुर्ज़े लोड नहीं हो सके — पुर्ज़ा-खपत विकल्प अभी उपलब्ध नहीं हैं।",
"settings_export_selection": "चयनित ऑब्जेक्ट तक सीमित करें (वैकल्पिक)",
"settings_docs_archive": "दस्तावेज़ संग्रह (फ़ाइलों सहित)",
"settings_docs_archive_hint": "JSON/YAML/CSV निर्यात केवल सेटिंग्स रखते हैं। इस ZIP में अपलोड की गई फ़ाइल सामग्री शामिल है ताकि पुनर्स्थापना पूर्ण हो।",
"settings_docs_archive_hint": "JSON और YAML निर्यात आपके दस्तावेज़ों की सूची देते हैं, लेकिन उनकी फ़ाइलें नहीं; CSV में दस्तावेज़ शामिल नहीं होते। इस ZIP में अपलोड की गई फ़ाइलें हैं — JSON आयात करने के बाद इसे पुनर्स्थापित करें, तब कुछ भी नहीं छूटेगा।",
"settings_docs_export_btn": "दस्तावेज़ ZIP डाउनलोड करें",
"settings_docs_import_btn": "दस्तावेज़ ZIP पुनर्स्थापित करें",
"settings_docs_import_success": "पुनर्स्थापित: {blobs} फ़ाइलें, {docs} दस्तावेज़",
"settings_docs_import_missing": "{n} छोड़े गए — उनकी फ़ाइलें ZIP में नहीं थीं",
"adopt_problem_button": "समस्या सेंसर अपनाएँ",
"adopt_problem_title": "समस्या सेंसर अपनाएँ",
"adopt_problem_hint": "HA समस्या सेंसर (प्रिंटर त्रुटियाँ, फ़िल्टर चेतावनियाँ, कम बैटरी) को रखरखाव कार्यों में बदलें जो समस्या सक्रिय रहने तक ट्रिगर होते हैं और हल होने पर स्वयं पूरे हो जाते हैं।",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "पिछली पूर्णता से उपयोग गिनता है: +{target} के बाद देय; हर पूर्णता के बाद गिनती फिर से शुरू होती है।",
"trigger_hint_counter_abs": "सेंसर {target} तक पहुँचते ही कार्य देय होगा।",
"trigger_hint_runtime": "{hours} घंटे की संचित चालू-अवधि के बाद कार्य देय होगा; हर पूर्णता के बाद गिनती फिर से शुरू होती है।",
"trigger_hint_due_date": "काम सेंसर द्वारा बताई गई तारीख से {days} दिन पहले देय होगा।",
"trigger_hint_due_date_one": "काम सेंसर द्वारा बताई गई तारीख से एक दिन पहले देय होगा।",
"trigger_hint_due_date_same_day": "काम सेंसर द्वारा बताई गई तारीख को देय होगा।",
"trigger_hint_due_date_now": "बताई गई तारीख: {date}।",
"trigger_hint_state_change": "{count} स्थिति परिवर्तन(ों) के बाद कार्य देय होगा।",
"trigger_hint_state_change_to": "“{state}” में {count} परिवर्तन(ों) के बाद कार्य देय होगा।",
"trigger_hint_state_now": "वर्तमान स्थिति: {value}।",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "केवल वे कार्य अनुस्मारक भेजते हैं जो चुने गए व्यू के लेबल/उपयोगकर्ता फ़िल्टर से मेल खाते हैं। व्यू की स्थिति, क्रम और समूहन यहाँ अनदेखे रहते हैं।",
"settings_notify_completed": "पूर्णता सूचनाएँ",
"settings_notify_completed_hint": "किसी कार्य के पूरा होने पर परिवार को कारण और करने वाले सहित सूचना। \"केवल स्वचालित\" सिर्फ़ वे पूर्णताएँ बताता है जो किसी ने मौके पर नहीं कीं: सेंसर रिकवरी, खरीदारी सूची में टिक, ऑटोमेशन का सर्विस कॉल। केवल परिवार की सेवा पर; शांत घंटे और दैनिक सीमा लागू।",
"settings_title_style": "सूचना शीर्षक शैली",
"settings_title_style_hint": "सूचना के शीर्षक के रूप में क्या दिखाई देता है। डिफ़ॉल्ट प्रति-स्थिति पाठ रखता है (उदा. 'रखरखाव अतिदेय!')। फ़ोन सूचनाएँ ढेर होने पर ऑब्जेक्ट नाम / कार्य नाम सहायक होते हैं।",
"title_style_default": "डिफ़ॉल्ट (प्रति-स्थिति शीर्षक)",
"title_style_object_name": "ऑब्जेक्ट नाम शीर्षक के रूप में",
"title_style_task_name": "कार्य नाम शीर्षक के रूप में",
"notify_completed_off": "बंद",
"notify_completed_automatic": "केवल स्वचालित पूर्णताएँ",
"notify_completed_all": "सभी पूर्णताएँ",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON या YAML, Jinja की अनुमति; notify कॉल के data में मिलाया जाता है (आपकी कुंजियाँ प्राथमिक)। वेरिएबल: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message। परीक्षण भेजें बटन से जाँचें।",
"card_saved_view": "सहेजा गया व्यू",
"card_saved_view_none": "कोई नहीं",
"card_saved_view_help": "ऊपर दिए फ़िल्टरों के अतिरिक्त व्यू के स्थिति, उपयोगकर्ता और लेबल फ़िल्टर लागू करता है। व्यू का क्रम और समूहन पैनल की प्रदर्शन सेटिंग हैं और कार्ड पर लागू नहीं होते।",
"card_saved_view_help": "ऊपर दिए फ़िल्टरों के अतिरिक्त व्यू के स्थिति, उपयोगकर्ता, लेबल और प्राथमिकता फ़िल्टर लागू करता है। व्यू का क्रम और समूहन पैनल की प्रदर्शन सेटिंग हैं और कार्ड पर लागू नहीं होते।",
"panel_card_tab": "इस टैब पर खोलें",
"panel_card_tab_default": "याद रखा गया टैब (पैनल जैसा)",
"panel_card_view": "सहेजे गए दृश्य के साथ खोलें",
@@ -1189,6 +1234,14 @@
"row_actions_back": "आइकन पर वापस जाएँ",
"battery_fleet_forecast_overdue": "अनुमानित तिथि बीत गई — बैटरी अब भी अच्छी स्थिति बता रही है। यदि आपने इसे बदला है, तो बदलाव दर्ज करें; अन्यथा पूर्वानुमान गलत था।",
"cost_from_parts": "पुर्ज़ों से ≈ {amount} लें",
"cost_parts_booked": "इस्तेमाल हुए पार्ट ≈ {amount} अपने-आप दर्ज होते हैं — सिर्फ़ अतिरिक्त लागत दर्ज करें।",
"cost_parts_info": "इस्तेमाल हुए पार्ट ≈ {amount} — खरीदते समय गिने गए, यहाँ नहीं।",
"cost_purchase_use_hint": "चुकाई गई कीमत पार्ट की कीमत बन जाती है — स्पेयर पार्ट इस्तेमाल होने पर गिने जाते हैं।",
"history_parts_counted": "पार्ट: {amount}",
"history_parts_counted_hint": "इस्तेमाल हुए पार्ट की कीमत — खर्च में गिनी गई",
"history_parts_info": "पार्ट ≈ {amount}",
"history_parts_info_hint": "इस्तेमाल हुए पार्ट की कीमत — सिर्फ़ जानकारी के लिए; पैसा खरीदते समय गिना गया",
"history_purchase_stock": "{amount} में खरीदा (इस्तेमाल होने पर गिना जाएगा)",
"dismiss": "हटाएँ",
"gs_label": "शुरुआत — सेटअप बढ़ने पर ये संकेत हट जाते हैं",
"gs_setups_chip": "सुझाए गए सेटअप: पूर्व-निर्धारित ट्रिगर वाले {n} उपकरण मिले",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "पखवाड़ा (14 दिन)",
"cal_editor_window_month": "महीना (30 दिन, डिफ़ॉल्ट)",
"cal_editor_window_year": "वर्ष (365 दिन, खाली दिन छिपे)",
"cal_editor_window_past_30": "पिछले 30 दिन (इतिहास)",
"cal_editor_window_past_90": "पिछले 90 दिन (इतिहास)",
"cal_editor_show_chips": "कार्ड में विंडो चिप्स दिखाएँ",
"cal_editor_chips_hint": "जब कार्ड ऐसी स्ट्रैटेजी व्यू में हो जो पहले से विंडो चयनक है, तो चिप्स छिपाएँ।",
"cal_editor_show_user_filter": "उपयोगकर्ता फ़िल्टर दिखाएँ",
@@ -38,6 +38,8 @@
"reset": "Visszaállítás",
"snooze": "Szundi",
"snoozed": "Szundiztatva",
"snoozed_for": "Emlékeztetők némítva {hours} órára — eddig: {until}",
"snoozed_for_one": "Emlékeztetők némítva egy órára — eddig: {until}",
"cancel": "Mégse",
"bulk_select": "Kijelölés",
"bulk_select_all": "Összes kijelölése",
@@ -63,7 +65,10 @@
"counter": "Számláló",
"state_change": "Állapotváltozás",
"runtime": "Üzemidő",
"trigger_type_due_date": "Dátum érzékelőtől",
"runtime_hours": "Cél üzemidő (óra)",
"days_before": "Nappal a dátum előtt",
"days_before_help": "Olyan érzékelőhöz, amely jelzi, mikor esedékes a karbantartás (dátum vagy időbélyeg). 0 = magán a napon. Az eszköz saját „kész” gombja előrébb teszi a dátumot, és ezzel megszünteti a triggert — állítsd be elvégzéskor végrehajtandó műveletnek.",
"target_value": "Célérték",
"target_changes": "Cél változásszám",
"for_minutes": "Ennyi ideig (perc)",
@@ -235,6 +240,8 @@
"model_optional": "Modell (opcionális)",
"serial_number_optional": "Sorozatszám (opcionális)",
"serial_number_label": "Sorozatszám",
"object_replaces": "Elődje",
"object_replaced_by": "Utódja",
"documentation_url_label": "Kézikönyv",
"object_notes_label": "Megjegyzések",
"sort_due_date": "Határidő",
@@ -422,7 +429,7 @@
"qr_action_view": "Karbantartási adatok megtekintése",
"qr_action_complete": "Karbantartás elvégzettként jelölése",
"qr_action_quick_complete": "Gyorselvégzés — párbeszédablak nélkül",
"qr_print_quick_none": "A gyorselvégzés-kódokhoz a feladaton gyorselvégzés-alapértékek kellenek (feladat párbeszédablaka → Gyorselvégzés alapértékei) — a kiválasztott objektumok egyik feladatán sincsenek még.",
"qr_print_quick_none": "A gyorselvégzés-kódokhoz a feladaton gyorselvégzés-alapértékek kellenek (Beállítások → Speciális funkciók → Elvégzési műveletek; feladat párbeszédablaka → Gyorselvégzés alapértékei) — a kiválasztott objektumok egyik feladatán sincsenek még.",
"qr_url_mode": "Hivatkozás típusa",
"qr_mode_companion": "Companion alkalmazás",
"qr_mode_local": "Helyi (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "Speciális funkciók be- és kikapcsolása. A kikapcsolás elrejti őket a felületről, de nem töröl adatot.",
"feat_adaptive": "Adaptív ütemezés",
"feat_adaptive_desc": "Optimális intervallumok tanulása a karbantartási előzményekből",
"feat_predictions": "Érzékelő-előrejelzések",
"feat_predictions_desc": "Kiváltási dátumok előrejelzése az érzékelőértékek romlásából",
"feat_seasonal": "Szezonális igazítások",
"feat_seasonal_desc": "Intervallumok igazítása szezonális minták alapján",
"feat_environmental": "Környezeti korreláció",
@@ -671,6 +676,10 @@
"settings_currency": "Pénznem",
"settings_currency_decimals": "Összegek tizedesjegyei",
"settings_currency_decimals_hint": "Hány tizedesjegyet mutasson minden összeg: KPI-k, költségvetések, költségek a listákban és jelentésekben, költségvetési riasztások. 0 = egész számok.",
"settings_parts_cost_mode": "Az alkatrészek kiadásként számítanak",
"settings_parts_cost_purchase": "Vásárláskor",
"settings_parts_cost_use": "Felhasználáskor",
"settings_parts_cost_mode_hint": "Vásárláskor: a vásárlási emlékeztető költsége számít, a munkához felhasznált alkatrészek csak tájékoztatásul jelennek meg. Felhasználáskor: minden elvégzés könyveli a felhasznált alkatrészek értékét, a vásárlás pedig beállítja az alkatrész árát ahelyett, hogy számítana. Mostantól érvényes — az előzmények megtartják a korábbi könyvelést.",
"settings_budget_monthly": "Havi költségkeret",
"settings_budget_yearly": "Éves költségkeret",
"settings_budget_alerts": "Költségkeret-riasztások",
@@ -683,15 +692,17 @@
"settings_import_placeholder": "Illessze be ide a JSON vagy CSV tartalmat…",
"settings_import_btn": "Importálás",
"settings_import_success": "{count} objektum sikeresen importálva.",
"settings_import_unmatched_users": "Ezen a példányon nem található: {names}. A feladat-hozzárendeléseik törlődtek — a megtartásukhoz hozz létre azonos nevű személyeket a Home Assistantben, és importálj újra.",
"settings_export_success": "Export letöltve.",
"settings_saved": "Beállítás mentve.",
"settings_include_history": "Előzményekkel együtt",
"settings_export_selection": "Csak a kiválasztott objektumok (opcionális)",
"settings_docs_archive": "Dokumentumarchívum (fájlokkal)",
"settings_docs_archive_hint": "A JSON/YAML/CSV export csak a beállításokat tartalmazza. Ez a ZIP a feltöltött fájlok tartalmát is magában foglalja, így a visszaállítás teljes.",
"settings_docs_archive_hint": "A JSON- és YAML-export felsorolja a dokumentumokat, de a fájljaikat nem tartalmazza; a CSV kihagyja a dokumentumokat. Ez a ZIP a feltöltött fájlokat tartalmazza – a JSON importálása után állítsd vissza, és semmi sem hiányzik.",
"settings_docs_export_btn": "Dokumentum-ZIP letöltése",
"settings_docs_import_btn": "Dokumentum-ZIP visszaállítása",
"settings_docs_import_success": "Visszaállítva: {blobs} fájl, {docs} dokumentum",
"settings_docs_import_missing": "{n} kihagyva — a fájljaik nem voltak a ZIP-ben",
"sort_alphabetical": "Ábécésorrend",
"sort_due_soonest": "Leghamarabb esedékes",
"sort_task_count": "Feladatok száma",
@@ -777,7 +788,7 @@
"has_overdue": "Van lejárt feladata",
"object": "Objektum",
"settings_panel_access": "Panel-hozzáférés",
"settings_panel_access_desc": "Az adminok mindig teljes hozzáféréssel rendelkeznek. Ha a létrehozást, szerkesztést és törlést adott nem admin felhasználókra bízná, kapcsolja be ezt, és válassza ki őket lent — mindenki más csak az Elvégzés és a Kihagyás gombot látja.",
"settings_panel_access_desc": "Az adminok mindig teljes hozzáféréssel rendelkeznek. Ha a létrehozást, szerkesztést és törlést adott nem admin felhasználókra bízná, kapcsolja be ezt, és válassza ki őket lent — mindenki más csak az Elvégzés, Kihagyás, Visszaállítás, Halasztás és Szundi műveleteket használhatja.",
"settings_operator_write": "A kiválasztott felhasználók létrehozhatnak, szerkeszthetnek és törölhetnek",
"settings_operator_write_desc": "Ki: csak adminok módosíthatnak tartalmat. Be: a lent kiválasztott felhasználók is teljes hozzáférést kapnak.",
"no_non_admin_users": "Nem található nem admin felhasználó. Vegyen fel néhányat a Beállítások → Személyek alatt.",
@@ -788,11 +799,16 @@
"on_complete_action_desc": "HA szolgáltatást hív meg a feladat elvégzésekor — pl. számláló nullázása az eszközön.",
"on_complete_action_target": "Cél entitás",
"on_complete_action_target_hint": "Megjegyzés: az entitás doménjének egyeznie kell a szolgáltatással — pl. a „button.press” csak button.* entitáson működik, a „counter.increment” csak counter.* entitáson, az „input_button.press” csak input_button.* entitáson stb. Eltérés esetén a művelet csendben meghiúsul (a HA naplóban: „Referenced entities ... missing or not currently available”).",
"on_complete_action_target_more": "Ennek a műveletnek több célja van, mint amennyit ez a mező mutat: {targets}. Ezek változatlanok maradnak — ha itt másik entitást választasz, az a teljes célt lecseréli.",
"on_complete_action_skip_auto": "Nem, ha a feladat magától elvégződik (az érzékelője helyreállt)",
"on_complete_action_data": "Adatok (JSON, opcionális)",
"on_complete_action_test": "Konfiguráció ellenőrzése",
"on_complete_action_test_success": "✓ A konfiguráció érvényes (a művelet csak a feladat elvégzésekor fut le)",
"on_complete_action_test_failed": "Sikertelen",
"action_test_bad_format": "Érvénytelen szolgáltatásformátum ('domain.service' várható)",
"action_test_unknown_service": "A(z) „{service}” szolgáltatás nincs regisztrálva a Home Assistantban. Ellenőrizd a helyesírást, és hogy a szolgáltatást nyújtó integráció be van-e töltve.",
"action_test_domain_mismatch": "A(z) „{service}” szolgáltatás csak {domain}.* entitásokon működik; a(z) „{entity}” entitás a(z) {entity_domain}.* tartományba tartozik — válassz az entitás tartományához illő szolgáltatást (pl. {suggestion})",
"action_test_entity_missing": "A(z) „{entity}” célentitás nem található a Home Assistantban — lehet, hogy átnevezték, vagy eltávolították az integrációját.",
"quick_complete_defaults_title": "Gyorselvégzés alapértékei (QR-beolvasáshoz, opcionális)",
"quick_complete_defaults_desc": "Előre beállított értékek a gyorselvégző QR-beolvasásokhoz. Ezek nélkül a QR az elvégzési párbeszédablakot nyitja meg.",
"quick_complete_defaults_notes": "Megjegyzések",
@@ -850,6 +866,7 @@
"doc_too_large": "A fájl túl nagy (max. 25 MB).",
"doc_upload_failed": "A feltöltés nem sikerült.",
"docs_archive_too_large": "Az archívum túl nagy (max. {max}).",
"docs_archive_no_manifest": "Ebben a ZIP-ben nincs manifest.json, ezért a visszaállítás nem tudja, melyik dokumentumhoz tartozik egy fájl. Újratömörítéskor hagyd a manifest.json fájlt a mappák mellett.",
"completion_photos_optional": "Fotók az elvégzésről (nem kötelező)",
"completion_photos": "Fotók az elvégzésről",
"choose_photos": "Fotók kiválasztása",
@@ -910,6 +927,11 @@
"paused": "Szüneteltetve",
"pause_object": "Szüneteltetés",
"resume_object": "Folytatás",
"pause_task": "Szüneteltetés",
"resume_task": "Folytatás",
"pause_task_prompt": "Befagyasztja a feladat ütemezését — nem válik esedékessé és nem értesít, amíg nem folytatod; a folytatás azon a napon új ciklust indít. Az objektum többi feladata fut tovább. Opcionálisan megadható egy dátum, amikor magától folytatódik.",
"task_paused": "Feladat szüneteltetve",
"task_resumed": "Feladat folytatva — az ütemezése újraindult",
"pause_until_prompt": "Az objektum ütemezéseinek befagyasztása — semmi nem válik esedékessé, és nincs értesítés a folytatásig. Megadhat automatikus folytatási dátumot is.",
"pause_until_label": "Folytatás ekkor (opcionális)",
"object_paused": "Objektum szüneteltetve",
@@ -919,7 +941,18 @@
"replace_object": "Csere…",
"replace_object_prompt": "Az objektum kivezetése és utód létrehozása. Az előzmények és a költségek archiválva a régin maradnak; a feladatok és a dokumentumok átkerülnek az újra, a számlálók nulláról indulnak.",
"replace_name_label": "Utód neve",
"replace_device_heading": "Eszköz a Home Assistantban",
"replace_device_keep": "Ugyanaz az eszköz, mint eddig ({device})",
"replace_device_keep_hint": "Akkor is ezt válaszd, ha az új példány még nincs a Home Assistantban: ha már ott lesz, válaszd ki az objektum beállításaiban eszközként — az érzékelő-triggerek vele költöznek.",
"replace_device_other": "Az új példány egy másik eszköz",
"replace_device_pick": "Az új példány eszköze",
"replace_device_other_hint": "Az érzékelő-triggerek, a befejezési műveletek és a számláló nullázása átkerülnek az eszköz megfelelő entitásaira.",
"replace_device_none": "Nincs eszköz",
"object_replaced": "Objektum lecserélve — utód létrehozva",
"device_swap_moved": "{count} érzékelő-kapcsolat átkerült az új eszközre.",
"device_swap_moved_one": "Egy érzékelő-kapcsolat átkerült az új eszközre.",
"device_swap_unmatched": "{count} nem párosítható — ellenőrizd ezeknek a feladatoknak az érzékelő-triggereit.",
"device_swap_unmatched_one": "Egy nem párosítható — ellenőrizd annak a feladatnak az érzékelő-triggerét.",
"reading_unit_label": "Leolvasás mértékegysége (pl. kWh, m³)",
"reading_unit_help": "A rögzített érték mellett jelenik meg a feladat elvégzésekor.",
"reading_value_label": "Leolvasott érték",
@@ -1016,6 +1049,9 @@
"part_stock": "Készlet",
"part_reorder_threshold": "Újrarendelés ennél",
"part_restock_quantity": "Feltöltési mennyiség",
"part_package_size": "Kiszerelés mérete (egység)",
"part_restock_packages": "Utántöltési mennyiség (kiszerelés)",
"part_package_hint": "Kiszerelésben vásárolva, egységben felhasználva: a készlet, a feladatonkénti fogyás és az újrarendelési küszöb egységet számol; az utántöltési mennyiség és az ár kiszerelésenként értendő.",
"part_auto_buy": "Vásárlási feladat automatikus létrehozása, ha kevés van",
"part_restock": "Készlet módosítása",
"parts_used_by": "Használja",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "A legutóbbi elvégzés óta számolja a használatot: esedékes +{target} után; a számlálás minden elvégzés után újraindul.",
"trigger_hint_counter_abs": "A feladat akkor válik esedékessé, amikor az érzékelő eléri a(z) {target} értéket.",
"trigger_hint_runtime": "A feladat {hours} óra összesített üzemidő után válik esedékessé; a számláló minden elvégzés után újraindul.",
"trigger_hint_due_date": "A feladat {days} nappal az érzékelő által jelzett dátum előtt válik esedékessé.",
"trigger_hint_due_date_one": "A feladat egy nappal az érzékelő által jelzett dátum előtt válik esedékessé.",
"trigger_hint_due_date_same_day": "A feladat az érzékelő által jelzett napon válik esedékessé.",
"trigger_hint_due_date_now": "Jelzett dátum: {date}.",
"trigger_hint_state_change": "A feladat {count} állapotváltozás után válik esedékessé.",
"trigger_hint_state_change_to": "A feladat {count} alkalommal „{state}” állapotra váltás után válik esedékessé.",
"trigger_hint_state_now": "Jelenlegi állapot: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Csak a kiválasztott mentett nézet címke-/felhasználószűrőinek megfelelő feladatok küldenek emlékeztetőt. A nézet állapot-, rendezési és csoportosítási beállításait itt figyelmen kívül hagyjuk.",
"settings_notify_completed": "Elvégzési értesítések",
"settings_notify_completed_hint": "Üzenet a háztartásnak, ha egy feladat elkészült, az okkal és a végrehajtóval. A „Csak automatikus” csak azokat jelenti, amelyeket senki nem végzett el a helyszínen: érzékelő helyreállása, kipipálás a bevásárlólistán, automatizálás szolgáltatáshívása. Csak a háztartási szolgáltatásra; a csendes órák és a napi korlát érvényes.",
"settings_title_style": "Értesítés címének stílusa",
"settings_title_style_hint": "Mi jelenjen meg az értesítés CÍMEKÉNT. Az alapértelmezés az állapotonkénti szöveget tartja meg (pl. „Karbantartás lejárt!”). Az objektumnév / feladatnév akkor segít, ha a telefonon egymásra tornyosulnak az értesítések.",
"title_style_default": "Alapértelmezett (állapotonkénti cím)",
"title_style_object_name": "Objektum neve címként",
"title_style_task_name": "Feladat neve címként",
"notify_completed_off": "Ki",
"notify_completed_automatic": "Csak automatikus elvégzések",
"notify_completed_all": "Minden elvégzés",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON vagy YAML, Jinja engedélyezett; beolvad a notify hívás data mezőjébe (a saját kulcsai nyernek). Változók: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Ellenőrizze a Teszt küldése gombbal.",
"card_saved_view": "Mentett nézet",
"card_saved_view_none": "Nincs",
"card_saved_view_help": "A nézet állapot-, felhasználó- és címkeszűrőit a fenti szűrőkön felül alkalmazza. A nézet rendezése és csoportosítása a panel megjelenítési beállítása, a kártyán nem érvényesül.",
"card_saved_view_help": "A nézet állapot-, felhasználó-, címke- és prioritásszűrőit a fenti szűrőkön felül alkalmazza. A nézet rendezése és csoportosítása a panel megjelenítési beállítása, a kártyán nem érvényesül.",
"panel_card_tab": "Megnyitás ezen a lapon",
"panel_card_tab_default": "Megjegyzett lap (mint a panelen)",
"panel_card_view": "Megnyitás mentett nézettel",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Vissza az ikonokhoz",
"battery_fleet_forecast_overdue": "Az előrejelzett dátum elmúlt — az elem továbbra is jó állapotot jelez. Ha kicserélted, rögzítsd a cserét; különben az előrejelzés tévedett.",
"cost_from_parts": "≈ {amount} átvétele az alkatrészekből",
"cost_parts_booked": "A felhasznált alkatrészek ≈ {amount} értéke automatikusan könyvelődik — csak a további költségeket add meg.",
"cost_parts_info": "Felhasznált alkatrészek ≈ {amount} — vásárláskor számítva, itt nem.",
"cost_purchase_use_hint": "A kifizetett ár lesz az alkatrész ára — az alkatrészek felhasználáskor számítanak.",
"history_parts_counted": "Alkatrészek: {amount}",
"history_parts_counted_hint": "A felhasznált alkatrészek értéke — kiadásként számítva",
"history_parts_info": "Alkatrészek ≈ {amount}",
"history_parts_info_hint": "A felhasznált alkatrészek értéke — csak tájékoztató; a pénz vásárláskor számított",
"history_purchase_stock": "Vásárolva: {amount} (felhasználáskor számít)",
"dismiss": "Elrejtés",
"gs_label": "Első lépések — a tippek eltűnnek, ahogy a beállítás bővül",
"gs_setups_chip": "Javasolt beállítások: {n} eszköz előre bekötött triggerekkel",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Két hét (14 nap)",
"cal_editor_window_month": "Hónap (30 nap, alapértelmezett)",
"cal_editor_window_year": "Év (365 nap, üres napok elrejtve)",
"cal_editor_window_past_30": "Elmúlt 30 nap (előzmények)",
"cal_editor_window_past_90": "Elmúlt 90 nap (előzmények)",
"cal_editor_show_chips": "Időablak-választók megjelenítése a kártyán",
"cal_editor_chips_hint": "Rejtsd el a választókat, ha a kártya olyan stratégianézetben van, amely már időablak-választóként szolgál.",
"cal_editor_show_user_filter": "Felhasználószűrő megjelenítése",
@@ -36,6 +36,8 @@
"reset": "Reimposta",
"snooze": "Posponi",
"snoozed": "Posposta",
"snoozed_for": "Promemoria silenziati per {hours} ore — fino a {until}",
"snoozed_for_one": "Promemoria silenziati per un'ora — fino a {until}",
"cancel": "Annulla",
"bulk_select": "Seleziona",
"bulk_select_all": "Seleziona tutto",
@@ -61,7 +63,10 @@
"counter": "Contatore",
"state_change": "Cambio di stato",
"runtime": "Tempo di funzionamento",
"trigger_type_due_date": "Data da un sensore",
"runtime_hours": "Durata obiettivo (ore)",
"days_before": "Giorni prima della data",
"days_before_help": "Per un sensore che indica quando è dovuta la manutenzione (data o timestamp). 0 = il giorno stesso. Il pulsante «fatto» del dispositivo sposta in avanti la data e annulla così il trigger — impostalo come azione al completamento.",
"target_value": "Valore obiettivo",
"target_changes": "Modifiche obiettivo",
"for_minutes": "Per (minuti)",
@@ -233,6 +238,8 @@
"model_optional": "Modello (opzionale)",
"serial_number_optional": "Numero di serie (opzionale)",
"serial_number_label": "N/S",
"object_replaces": "Sostituisce",
"object_replaced_by": "Sostituito da",
"documentation_url_label": "Manuale",
"object_notes_label": "Note",
"sort_due_date": "Scadenza",
@@ -420,7 +427,7 @@
"qr_action_view": "Visualizza info manutenzione",
"qr_action_complete": "Segna manutenzione come completata",
"qr_action_quick_complete": "Completamento rapido — senza finestra",
"qr_print_quick_none": "I codici di completamento rapido richiedono valori predefiniti di completamento rapido su un'attività (finestra dell'attività → Valori predefiniti completamento rapido) — nessuna attività degli oggetti scelti li ha ancora.",
"qr_print_quick_none": "I codici di completamento rapido richiedono valori predefiniti di completamento rapido su un'attività (Impostazioni → Funzioni avanzate → Azioni di completamento; finestra dell'attività → Valori predefiniti completamento rapido) — nessuna attività degli oggetti scelti li ha ancora.",
"qr_url_mode": "Tipo di link",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Locale (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Attiva o disattiva le funzioni avanzate. La disattivazione le nasconde dall'interfaccia ma non elimina i dati.",
"feat_adaptive": "Pianificazione adattiva",
"feat_adaptive_desc": "Impara intervalli ottimali dalla cronologia di manutenzione",
"feat_predictions": "Previsioni sensore",
"feat_predictions_desc": "Prevedi date di attivazione dalla degradazione dei sensori",
"feat_seasonal": "Adeguamenti stagionali",
"feat_seasonal_desc": "Adegua gli intervalli in base ai modelli stagionali",
"feat_environmental": "Correlazione ambientale",
@@ -669,6 +674,10 @@
"settings_currency": "Valuta",
"settings_currency_decimals": "Decimali degli importi",
"settings_currency_decimals_hint": "Quanti decimali mostra ogni importo: KPI, budget, costi in elenchi e report, avvisi di budget. 0 = numeri interi.",
"settings_parts_cost_mode": "I ricambi contano come spesa",
"settings_parts_cost_purchase": "All'acquisto",
"settings_parts_cost_use": "All'uso",
"settings_parts_cost_mode_hint": "All'acquisto: conta il costo di un promemoria di acquisto, i ricambi usati da un lavoro sono mostrati a titolo informativo. All'uso: ogni completamento registra il valore dei ricambi usati e un acquisto imposta il prezzo del ricambio invece di contare. Vale per i completamenti da ora in poi: lo storico resta com'era registrato.",
"settings_budget_monthly": "Budget mensile",
"settings_budget_yearly": "Budget annuale",
"settings_budget_alerts": "Avvisi budget",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Incolla il contenuto JSON o CSV qui…",
"settings_import_btn": "Importa",
"settings_import_success": "{count} oggetti importati con successo.",
"settings_import_unmatched_users": "Non trovati su questa istanza: {names}. Le loro assegnazioni delle attività sono state rimosse — crea persone con gli stessi nomi in Home Assistant e importa di nuovo per mantenerle.",
"settings_export_success": "Export scaricato.",
"settings_saved": "Impostazione salvata.",
"settings_include_history": "Includi cronologia",
@@ -746,7 +756,7 @@
"has_overdue": "Attività scadute",
"object": "Oggetto",
"settings_panel_access": "Accesso al pannello",
"settings_panel_access_desc": "Gli amministratori hanno sempre accesso completo. Per delegare creazione, modifica ed eliminazione a non amministratori specifici, attiva questa opzione e selezionali qui sotto — gli altri vedono solo Completa e Salta.",
"settings_panel_access_desc": "Gli amministratori hanno sempre accesso completo. Per delegare creazione, modifica ed eliminazione a non amministratori specifici, attiva questa opzione e selezionali qui sotto — gli altri hanno solo Completato, Salta, Reimposta, Posticipa e Posponi.",
"settings_operator_write": "Consenti agli utenti selezionati di creare, modificare ed eliminare",
"settings_operator_write_desc": "Disattivato: solo gli amministratori possono modificare i contenuti. Attivato: anche gli utenti selezionati qui sotto hanno accesso completo.",
"no_non_admin_users": "Nessun utente non amministratore trovato. Aggiungili in Impostazioni → Persone.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Testa azione",
"on_complete_action_test_success": "Riuscito",
"on_complete_action_test_failed": "Fallito",
"action_test_bad_format": "Formato del servizio non valido (atteso: 'domain.service')",
"action_test_unknown_service": "Il servizio \"{service}\" non è registrato in Home Assistant. Controlla l'ortografia e che l'integrazione che lo fornisce sia caricata.",
"action_test_domain_mismatch": "Il servizio \"{service}\" funziona solo sulle entità {domain}.*; l'entità \"{entity}\" appartiene a {entity_domain}.* — scegli un servizio adatto al dominio dell'entità (ad es. {suggestion})",
"action_test_entity_missing": "L'entità di destinazione \"{entity}\" non è stata trovata in Home Assistant — potrebbe essere stata rinominata o la sua integrazione rimossa.",
"quick_complete_defaults_title": "Valori predefiniti completamento rapido (per scansioni QR, opzionale)",
"quick_complete_defaults_desc": "Valori predefiniti per QR di completamento rapido. Senza, il QR apre la finestra di completamento.",
"quick_complete_defaults_notes": "Note",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Gestisci assegnazioni attività…",
"groups_open_panel": "Apri nel pannello",
"on_complete_action_target_hint": "Nota: il dominio dell'entità deve corrispondere al servizio — es. 'button.press' funziona solo su button.*, 'counter.increment' solo su counter.*, 'input_button.press' solo su input_button.* ecc. In caso di mancata corrispondenza l'azione fallisce silenziosamente (HA registra 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Questa azione ha più destinazioni di quante ne mostri questo campo: {targets}. Restano invariate — scegliendo qui un'altra entità sostituisci l'intera destinazione.",
"on_complete_action_skip_auto": "Non quando l'attività si completa da sola (il suo sensore si è ripristinato)",
"show_all_objects": "Mostra tutti gli oggetti",
"show_all_tasks": "Azzera filtro — mostra tutte le attività",
@@ -844,6 +859,7 @@
"doc_too_large": "File troppo grande (max 25 MB).",
"doc_upload_failed": "Caricamento non riuscito.",
"docs_archive_too_large": "L'archivio è troppo grande (max {max}).",
"docs_archive_no_manifest": "Questo ZIP non contiene manifest.json, quindi il ripristino non sa a quale documento appartiene un file. Lascia manifest.json accanto alle cartelle quando lo comprimi di nuovo.",
"completion_photos_optional": "Foto di completamento (facoltativo)",
"completion_photos": "Foto di completamento",
"choose_photos": "Scegli foto",
@@ -904,6 +920,11 @@
"paused": "In pausa",
"pause_object": "Metti in pausa",
"resume_object": "Riprendi",
"pause_task": "Metti in pausa",
"resume_task": "Riprendi",
"pause_task_prompt": "Congela la pianificazione di questa attività — non diventa dovuta e non notifica nulla finché non la riprendi; la ripresa avvia un nuovo ciclo da quel giorno. Le altre attività dell'oggetto continuano. Facoltativo: una data di ripresa automatica.",
"task_paused": "Attività in pausa",
"task_resumed": "Attività ripresa — la pianificazione riparte",
"pause_until_prompt": "Congela i calendari di questo oggetto: nulla scade e nulla notifica finché non viene ripreso. Facoltativamente imposta una data di ripresa automatica.",
"pause_until_label": "Riprendi il (facoltativo)",
"object_paused": "Oggetto in pausa",
@@ -913,7 +934,18 @@
"replace_object": "Sostituisci…",
"replace_object_prompt": "Ritira questo oggetto e crea un successore. Storico e costi restano archiviati sul vecchio; attività e documenti passano al nuovo, i contatori ripartono da zero.",
"replace_name_label": "Nome del successore",
"replace_device_heading": "Dispositivo in Home Assistant",
"replace_device_keep": "Lo stesso dispositivo di prima ({device})",
"replace_device_keep_hint": "È anche la scelta se il nuovo esemplare non è ancora in Home Assistant: quando c'è, sceglilo come dispositivo nelle impostazioni dell'oggetto e i trigger dei sensori si spostano con lui.",
"replace_device_other": "Il nuovo esemplare è un altro dispositivo",
"replace_device_pick": "Dispositivo del nuovo esemplare",
"replace_device_other_hint": "Trigger dei sensori, azioni al completamento e azzeramento del contatore passano alle entità corrispondenti di quel dispositivo.",
"replace_device_none": "Nessun dispositivo",
"object_replaced": "Oggetto sostituito — successore creato",
"device_swap_moved": "{count} collegamenti ai sensori spostati sul nuovo dispositivo.",
"device_swap_moved_one": "Un collegamento a un sensore spostato sul nuovo dispositivo.",
"device_swap_unmatched": "{count} non sono stati abbinati: controlla i trigger dei sensori di quelle attività.",
"device_swap_unmatched_one": "Uno non è stato abbinato: controlla il trigger del sensore di quell'attività.",
"reading_unit_label": "Unità di lettura (es. kWh, m³)",
"reading_unit_help": "Mostrata accanto al valore registrato al completamento di questa attività.",
"reading_value_label": "Valore letto",
@@ -1010,6 +1042,9 @@
"part_stock": "Scorta",
"part_reorder_threshold": "Soglia riordino",
"part_restock_quantity": "Quantità di riordino",
"part_package_size": "Dimensione confezione (unità)",
"part_restock_packages": "Quantità di riordino (confezioni)",
"part_package_hint": "Acquistato a confezioni, usato a unità: scorte, consumo per attività e soglia di riordino contano unità; quantità di riordino e prezzo sono per confezione.",
"part_auto_buy": "Task d'acquisto se scarso",
"part_restock": "Correggi scorta",
"parts_used_by": "Usato da",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Impossibile caricare i ricambi di questo oggetto — le opzioni di consumo ricambi non sono al momento disponibili.",
"settings_export_selection": "Limita agli oggetti selezionati (facoltativo)",
"settings_docs_archive": "Archivio documenti (con file)",
"settings_docs_archive_hint": "Gli export JSON/YAML/CSV contengono solo le impostazioni. Questo ZIP include il contenuto dei file caricati per un ripristino completo.",
"settings_docs_archive_hint": "Gli export JSON e YAML elencano i tuoi documenti, ma non i loro file; il CSV esclude i documenti. Questo ZIP contiene i file caricati: ripristinalo dopo aver importato il JSON e non mancherà nulla.",
"settings_docs_export_btn": "Scarica ZIP dei documenti",
"settings_docs_import_btn": "Ripristina ZIP dei documenti",
"settings_docs_import_success": "Ripristinati: {blobs} file, {docs} documenti",
"settings_docs_import_missing": "{n} saltati — i loro file non erano nello ZIP",
"adopt_problem_button": "Adotta sensori di problema",
"adopt_problem_title": "Adotta sensori di problema",
"adopt_problem_hint": "Trasforma i sensori di problema di HA (errori stampante, avvisi filtro, batteria scarica) in attività di manutenzione che si attivano finché il problema è attivo e si completano quando si risolve.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Conta l'uso dall'ultimo completamento: scade dopo +{target}; il conteggio riparte dopo ogni completamento.",
"trigger_hint_counter_abs": "L'attività scade quando il sensore raggiunge {target}.",
"trigger_hint_runtime": "L'attività scade dopo {hours} h di funzionamento accumulato; il conteggio riparte dopo ogni completamento.",
"trigger_hint_due_date": "L'attività diventa dovuta {days} giorni prima della data indicata dal sensore.",
"trigger_hint_due_date_one": "L'attività diventa dovuta un giorno prima della data indicata dal sensore.",
"trigger_hint_due_date_same_day": "L'attività diventa dovuta alla data indicata dal sensore.",
"trigger_hint_due_date_now": "Data indicata: {date}.",
"trigger_hint_state_change": "L'attività scade dopo {count} cambi(o) di stato.",
"trigger_hint_state_change_to": "L'attività scade dopo {count} passaggi(o) a «{state}».",
"trigger_hint_state_now": "Stato attuale: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Solo le attività che corrispondono ai filtri etichetta/utente della vista scelta inviano promemoria. Stato, ordinamento e raggruppamento della vista qui vengono ignorati.",
"settings_notify_completed": "Notifiche di completamento",
"settings_notify_completed_hint": "Una nota per la famiglia quando un'attività viene completata, con il motivo e chi l'ha fatto. \"Solo automatici\" annuncia solo i completamenti che nessuno ha fatto sul posto: ripristino del sensore, spunta nella lista della spesa, chiamata di servizio di un'automazione. Solo al servizio della famiglia; ore di silenzio e limite giornaliero si applicano.",
"settings_title_style": "Stile del titolo di notifica",
"settings_title_style_hint": "Cosa appare come TITOLO della notifica. Predefinito mantiene il testo per stato (es. «Manutenzione in ritardo!»). Nome oggetto / attività aiuta quando il telefono accumula le notifiche.",
"title_style_default": "Predefinito (titolo per stato)",
"title_style_object_name": "Nome dell'oggetto come titolo",
"title_style_task_name": "Nome dell'attività come titolo",
"notify_completed_off": "Disattivato",
"notify_completed_automatic": "Solo completamenti automatici",
"notify_completed_all": "Tutti i completamenti",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON o YAML, Jinja consentito; unito ai data della chiamata notify (le tue chiavi vincono). Variabili: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Prova con il pulsante Invia test.",
"card_saved_view": "Vista salvata",
"card_saved_view_none": "Nessuna",
"card_saved_view_help": "Applica i filtri stato, utente ed etichetta della vista in aggiunta ai filtri sopra. Ordinamento e raggruppamento della vista sono impostazioni di visualizzazione del pannello e non si applicano alla scheda.",
"card_saved_view_help": "Applica i filtri stato, utente, etichetta e priorità della vista in aggiunta ai filtri sopra. Ordinamento e raggruppamento della vista sono impostazioni di visualizzazione del pannello e non si applicano alla scheda.",
"panel_card_tab": "Apri sulla scheda",
"panel_card_tab_default": "Scheda memorizzata (come nel pannello)",
"panel_card_view": "Apri con la vista salvata",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Torna alle icone",
"battery_fleet_forecast_overdue": "Data prevista superata — la batteria risulta ancora in buono stato. Se l'hai sostituita, registra la sostituzione; altrimenti la previsione era errata.",
"cost_from_parts": "Usa ≈ {amount} dai ricambi",
"cost_parts_booked": "I ricambi usati ≈ {amount} vengono registrati automaticamente: inserisci solo i costi aggiuntivi.",
"cost_parts_info": "Ricambi usati ≈ {amount}: contati all'acquisto, non qui.",
"cost_purchase_use_hint": "Il prezzo pagato diventa il prezzo del ricambio: i ricambi contano all'uso.",
"history_parts_counted": "Ricambi: {amount}",
"history_parts_counted_hint": "Valore dei ricambi usati: conteggiato come spesa",
"history_parts_info": "Ricambi ≈ {amount}",
"history_parts_info_hint": "Valore dei ricambi usati: solo informativo; la spesa è stata contata all'acquisto",
"history_purchase_stock": "Acquistato per {amount} (conta all'uso)",
"dismiss": "Ignora",
"gs_label": "Primi passi — questi suggerimenti scompaiono man mano che la configurazione cresce",
"gs_setups_chip": "Configurazioni suggerite: trovati {n} dispositivi con trigger preconfigurati",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Due settimane (14 giorni)",
"cal_editor_window_month": "Mese (30 giorni, predefinito)",
"cal_editor_window_year": "Anno (365 giorni, giorni vuoti nascosti)",
"cal_editor_window_past_30": "Ultimi 30 giorni (cronologia)",
"cal_editor_window_past_90": "Ultimi 90 giorni (cronologia)",
"cal_editor_show_chips": "Mostra i chip della finestra nella scheda",
"cal_editor_chips_hint": "Nascondi i chip quando la scheda è in una vista strategia che funge già da selettore di finestra.",
"cal_editor_show_user_filter": "Mostra il filtro utente",
@@ -38,6 +38,8 @@
"reset": "リセット",
"snooze": "スヌーズ",
"snoozed": "スヌーズ中",
"snoozed_for": "リマインダーを {hours} 時間ミュート — {until} まで",
"snoozed_for_one": "リマインダーを 1 時間ミュート — {until} まで",
"cancel": "キャンセル",
"bulk_select": "選択",
"bulk_select_all": "すべて選択",
@@ -63,7 +65,10 @@
"counter": "カウンター",
"state_change": "状態変化",
"runtime": "稼働時間",
"trigger_type_due_date": "センサーの日付",
"runtime_hours": "目標稼働時間(時間)",
"days_before": "日付の何日前",
"days_before_help": "メンテナンスの期日(日付またはタイムスタンプ)を報告するセンサー用です。0 = その日当日。デバイス自身の「完了」ボタンが日付を先送りしてトリガーを解除します — 完了時のアクションに設定してください。",
"target_value": "目標値",
"target_changes": "変化回数",
"for_minutes": "継続(分)",
@@ -235,6 +240,8 @@
"model_optional": "型番(任意)",
"serial_number_optional": "シリアル番号(任意)",
"serial_number_label": "S/N",
"object_replaces": "置き換え前",
"object_replaced_by": "置き換え後",
"documentation_url_label": "取扱説明書",
"object_notes_label": "メモ",
"sort_due_date": "期限日",
@@ -422,7 +429,7 @@
"qr_action_view": "メンテナンス情報を表示",
"qr_action_complete": "メンテナンスを完了にする",
"qr_action_quick_complete": "クイック完了 — ダイアログなし",
"qr_print_quick_none": "クイック完了コードには、タスクのクイック完了の既定値が必要です(タスクのダイアログ → クイック完了の既定値)。選択したオブジェクトのタスクにはまだ設定されていません。",
"qr_print_quick_none": "クイック完了コードには、タスクのクイック完了の既定値が必要です(設定 → 高度な機能 → 完了時アクション; タスクのダイアログ → クイック完了の既定値)。選択したオブジェクトのタスクにはまだ設定されていません。",
"qr_url_mode": "リンク種別",
"qr_mode_companion": "コンパニオンアプリ",
"qr_mode_local": "ローカル(mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "高度な機能の有効/無効を切り替えます。無効にするとUIから非表示になりますが、データは削除されません。",
"feat_adaptive": "適応スケジューリング",
"feat_adaptive_desc": "メンテナンス履歴から最適な間隔を学習します",
"feat_predictions": "センサー予測",
"feat_predictions_desc": "センサーの劣化からトリガー日を予測します",
"feat_seasonal": "季節調整",
"feat_seasonal_desc": "季節パターンに基づいて間隔を調整します",
"feat_environmental": "環境相関",
@@ -671,6 +676,10 @@
"settings_currency": "通貨",
"settings_currency_decimals": "金額の小数点以下の桁数",
"settings_currency_decimals_hint": "各金額に表示する小数点以下の桁数:KPI、予算、リストやレポートの費用、予算アラート。0 = 整数。",
"settings_parts_cost_mode": "交換部品を支出に計上するタイミング",
"settings_parts_cost_purchase": "購入時",
"settings_parts_cost_use": "使用時",
"settings_parts_cost_mode_hint": "購入時:購入リマインダーの費用を計上し、作業で使った部品は参考として表示します。使用時:完了のたびに使った部品の価値を計上し、購入は計上せずに部品の価格を更新します。今後の完了から適用され、履歴は記録時の計上方法のままです。",
"settings_budget_monthly": "月間予算",
"settings_budget_yearly": "年間予算",
"settings_budget_alerts": "予算アラート",
@@ -683,6 +692,7 @@
"settings_import_placeholder": "JSONまたはCSVの内容をここに貼り付け…",
"settings_import_btn": "インポート",
"settings_import_success": "{count}件の対象を正常にインポートしました。",
"settings_import_unmatched_users": "このインスタンスに見つかりません: {names}。タスクの割り当ては削除されました — 割り当てを残すには、Home Assistant で同じ名前の人物を作成してから再度インポートしてください。",
"settings_export_success": "エクスポートをダウンロードしました。",
"settings_saved": "設定を保存しました。",
"settings_include_history": "履歴を含める",
@@ -771,7 +781,7 @@
"has_overdue": "期限超過のタスクあり",
"object": "対象",
"settings_panel_access": "パネルアクセス",
"settings_panel_access_desc": "管理者は常にフルアクセスできます。作成・編集・削除を特定の非管理者に委任するには、これをオンにして下記で選択してください。それ以外のユーザーには完了とスキップのみが表示されます。",
"settings_panel_access_desc": "管理者は常にフルアクセスできます。作成・編集・削除を特定の非管理者に委任するには、これをオンにして下記で選択してください。それ以外のユーザーは、完了・スキップ・リセット・延期・スヌーズのみ使用できます。",
"settings_operator_write": "選択したユーザーに作成・編集・削除を許可",
"settings_operator_write_desc": "オフ:管理者のみがコンテンツを変更できます。オン:下記で選択したユーザーもフルアクセスできます。",
"no_non_admin_users": "非管理者ユーザーが見つかりません。設定 → ユーザーで追加してください。",
@@ -782,11 +792,16 @@
"on_complete_action_desc": "タスク完了時にHAサービスを呼び出します — 例: デバイスのカウンターをリセット。",
"on_complete_action_target": "対象エンティティ",
"on_complete_action_target_hint": "注意: エンティティのドメインはサービスと一致する必要があります — 例: 'button.press'はbutton.*のみ、'counter.increment'はcounter.*のみ、'input_button.press'はinput_button.*のみで動作します。不一致の場合、アクションは何も表示せずに失敗します(HAのログに「Referenced entities ... missing or not currently available」と記録されます)。",
"on_complete_action_target_more": "このアクションには、この欄に表示される以上の対象があります:{targets}。そのまま保持されます — ここで別のエンティティを選ぶと、対象全体が置き換わります。",
"on_complete_action_skip_auto": "タスクが自動で完了したとき(センサーが回復したとき)は実行しない",
"on_complete_action_data": "データ(JSON、任意)",
"on_complete_action_test": "設定を検証",
"on_complete_action_test_success": "✓ 設定は有効です(アクションはタスク完了時のみ実行されます)",
"on_complete_action_test_failed": "失敗しました",
"action_test_bad_format": "サービスの形式が無効です('domain.service' の形式が必要です)",
"action_test_unknown_service": "サービス「{service}」は Home Assistant に登録されていません。綴りと、そのサービスを提供する統合が読み込まれているかを確認してください。",
"action_test_domain_mismatch": "サービス「{service}」は {domain}.* のエンティティにのみ使えます。エンティティ「{entity}」は {entity_domain}.* に属しています。エンティティのドメインに合うサービスを選んでください(例: {suggestion})",
"action_test_entity_missing": "対象エンティティ「{entity}」が Home Assistant に見つかりません。名前が変更されたか、その統合が削除された可能性があります。",
"quick_complete_defaults_title": "クイック完了の既定値(QRスキャン用、任意)",
"quick_complete_defaults_desc": "クイック完了QRスキャンの事前設定値。設定しない場合、QRは完了ダイアログを開きます。",
"quick_complete_defaults_notes": "メモ",
@@ -844,6 +859,7 @@
"doc_too_large": "ファイルが大きすぎます(最大 25 MB)。",
"doc_upload_failed": "アップロードに失敗しました。",
"docs_archive_too_large": "アーカイブが大きすぎます(最大 {max})。",
"docs_archive_no_manifest": "この ZIP には manifest.json がないため、復元時にファイルがどのドキュメントのものか判断できません。再度圧縮するときは manifest.json をフォルダーと同じ場所に置いてください。",
"completion_photos_optional": "完了写真(任意)",
"completion_photos": "完了写真",
"choose_photos": "写真を選択",
@@ -904,6 +920,11 @@
"paused": "一時停止中",
"pause_object": "一時停止",
"resume_object": "再開",
"pause_task": "一時停止",
"resume_task": "再開",
"pause_task_prompt": "このタスクのスケジュールを凍結します。再開するまで期日にならず通知もしません。再開するとその日から新しいサイクルが始まります。オブジェクトの他のタスクはそのまま動きます。自動で再開する日付も任意で設定できます。",
"task_paused": "タスクを一時停止しました",
"task_resumed": "タスクを再開しました — スケジュールを最初から始めます",
"pause_until_prompt": "このオブジェクトのスケジュールを凍結します。再開するまで期限も通知も発生しません。必要に応じて自動再開日を設定できます。",
"pause_until_label": "再開日(任意)",
"object_paused": "オブジェクトを一時停止しました",
@@ -913,7 +934,18 @@
"replace_object": "置き換え…",
"replace_object_prompt": "このオブジェクトを退役させ、後継を作成します。履歴とコストは旧オブジェクトにアーカイブされたまま残り、タスクと文書は新しいオブジェクトへ引き継がれ、カウンターはリセットされます。",
"replace_name_label": "後継の名前",
"replace_device_heading": "Home Assistant のデバイス",
"replace_device_keep": "以前と同じデバイス({device})",
"replace_device_keep_hint": "新しい機器がまだ Home Assistant にない場合もこちらを選択してください。追加後にオブジェクトの設定でデバイスとして選ぶと、センサートリガーも移行します。",
"replace_device_other": "新しい機器は別のデバイス",
"replace_device_pick": "新しい機器のデバイス",
"replace_device_other_hint": "センサートリガー、完了時アクション、カウンターのリセットは、そのデバイスの対応するエンティティに移行します。",
"replace_device_none": "デバイスなし",
"object_replaced": "オブジェクトを置き換えました — 後継を作成",
"device_swap_moved": "{count} 件のセンサー連携を新しいデバイスに移行しました。",
"device_swap_moved_one": "センサー連携を 1 件、新しいデバイスに移行しました。",
"device_swap_unmatched": "{count} 件は対応付けできませんでした。該当タスクのセンサートリガーを確認してください。",
"device_swap_unmatched_one": "1 件は対応付けできませんでした。そのタスクのセンサートリガーを確認してください。",
"reading_unit_label": "検針単位(例: kWh、m³)",
"reading_unit_help": "このタスクの完了時に記録値の横に表示されます。",
"reading_value_label": "検針値",
@@ -1010,6 +1042,9 @@
"part_stock": "在庫",
"part_reorder_threshold": "再注文しきい値",
"part_restock_quantity": "補充数量",
"part_package_size": "包装サイズ(単位数)",
"part_restock_packages": "補充数(包装単位)",
"part_package_hint": "包装単位で購入し、単位で使います。在庫、タスクごとの使用量、発注しきい値は単位で数え、補充数と価格は包装単位です。",
"part_auto_buy": "在庫少で購入タスク",
"part_restock": "在庫を調整",
"parts_used_by": "使用タスク",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "このオブジェクトの部品を読み込めませんでした — 部品消費オプションは現在利用できません。",
"settings_export_selection": "選択したオブジェクトに限定(任意)",
"settings_docs_archive": "ドキュメントアーカイブ(ファイル付き)",
"settings_docs_archive_hint": "JSON/YAML/CSV エクスポートには設定のみが含まれます。この ZIP にはアップロードしたファイルの内容が含まれ、復元が完全になります。",
"settings_docs_archive_hint": "JSON と YAML のエクスポートにはドキュメントの一覧が含まれますが、ファイル本体は含まれません。CSV にはドキュメントが含まれません。この ZIP にはアップロードしたファイルが入っています。JSON をインポートした後に復元すれば、何も欠けません。",
"settings_docs_export_btn": "ドキュメント ZIP をダウンロード",
"settings_docs_import_btn": "ドキュメント ZIP を復元",
"settings_docs_import_success": "復元: {blobs} 個のファイル、{docs} 件のドキュメント",
"settings_docs_import_missing": "{n} 件をスキップしました — ZIP にファイルがありませんでした",
"adopt_problem_button": "問題センサーを取り込む",
"adopt_problem_title": "問題センサーを取り込む",
"adopt_problem_hint": "HA の問題センサー(プリンターエラー、フィルター警告、バッテリー低下)を、問題が続く間トリガーされ、解消すると自動的に完了するメンテナンスタスクに変換します。",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "前回の完了からの使用量を数えます: +{target} で期限になり、完了のたびにカウントはやり直しになります。",
"trigger_hint_counter_abs": "センサーが {target} に達するとタスクが期限になります。",
"trigger_hint_runtime": "累積稼働 {hours} 時間でタスクが期限になります。完了のたびにカウントはやり直しになります。",
"trigger_hint_due_date": "タスクはセンサーが報告する日付の {days} 日前に期日になります。",
"trigger_hint_due_date_one": "タスクはセンサーが報告する日付の 1 日前に期日になります。",
"trigger_hint_due_date_same_day": "タスクはセンサーが報告する日付に期日になります。",
"trigger_hint_due_date_now": "報告された日付:{date}。",
"trigger_hint_state_change": "{count} 回の状態変化でタスクが期限になります。",
"trigger_hint_state_change_to": "「{state}」への {count} 回の変化でタスクが期限になります。",
"trigger_hint_state_now": "現在の状態: {value}。",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "選択した保存済みビューのラベル/ユーザーフィルターに一致するタスクのみがリマインダーを送信します。ビューのステータス・並び順・グループ化はここでは無視されます。",
"settings_notify_completed": "完了通知",
"settings_notify_completed_hint": "タスクが完了したときに、理由と実行者を添えて家族に知らせます。「自動のみ」は、その場で誰も操作していない完了だけを通知します:センサーの回復、買い物リストのチェック、オートメーションのサービス呼び出し。家庭用サービスにのみ送信され、静音時間と 1 日の上限が適用されます。",
"settings_title_style": "通知タイトルのスタイル",
"settings_title_style_hint": "通知のタイトルとして表示される内容です。既定ではステータスごとのテキスト (例: 「メンテナンスが期限切れです!」) を維持します。対象名 / タスク名は、スマートフォンの通知が積み重なるときに役立ちます。",
"title_style_default": "既定 (ステータスごとのタイトル)",
"title_style_object_name": "対象名をタイトルにする",
"title_style_task_name": "タスク名をタイトルにする",
"notify_completed_off": "オフ",
"notify_completed_automatic": "自動完了のみ",
"notify_completed_all": "すべての完了",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON または YAML、Jinja 可。notify 呼び出しの data にマージされます(あなたのキーが優先)。変数: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message。「テスト送信」ボタンで確認できます。",
"card_saved_view": "保存済みビュー",
"card_saved_view_none": "なし",
"card_saved_view_help": "上記のフィルターに加えて、ビューのステータス・ユーザー・ラベルのフィルターを適用します。ビューの並び順とグループ化はパネルの表示設定であり、カードには適用されません。",
"card_saved_view_help": "上記のフィルターに加えて、ビューのステータス・ユーザー・ラベル・優先度のフィルターを適用します。ビューの並び順とグループ化はパネルの表示設定であり、カードには適用されません。",
"panel_card_tab": "開くタブ",
"panel_card_tab_default": "記憶したタブ(パネルと同じ)",
"panel_card_view": "保存済みビューで開く",
@@ -1189,6 +1234,14 @@
"row_actions_back": "アイコンに戻す",
"battery_fleet_forecast_overdue": "予測日を過ぎましたが、電池はまだ正常と報告しています。交換済みなら交換を記録してください。そうでなければ予測が外れています。",
"cost_from_parts": "部品から ≈ {amount} を適用",
"cost_parts_booked": "使った部品 ≈ {amount} は自動で計上されます。追加費用だけ入力してください。",
"cost_parts_info": "使った部品 ≈ {amount}(購入時に計上済み、ここでは計上しません)",
"cost_purchase_use_hint": "支払った価格が部品の価格になります。交換部品は使用時に計上されます。",
"history_parts_counted": "部品:{amount}",
"history_parts_counted_hint": "使った部品の価値(支出として計上)",
"history_parts_info": "部品 ≈ {amount}",
"history_parts_info_hint": "使った部品の価値(参考のみ。費用は購入時に計上)",
"history_purchase_stock": "{amount} で購入(使用時に計上)",
"dismiss": "閉じる",
"gs_label": "はじめに — セットアップが進むとこれらのヒントは消えます",
"gs_setups_chip": "推奨セットアップ:トリガー設定済みのデバイスを{n}台検出",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "2週間(14日)",
"cal_editor_window_month": "1か月(30日・既定)",
"cal_editor_window_year": "1年(365日・空の日は省略)",
"cal_editor_window_past_30": "過去 30 日間(履歴)",
"cal_editor_window_past_90": "過去 90 日間(履歴)",
"cal_editor_show_chips": "カード内に期間チップを表示",
"cal_editor_chips_hint": "ストラテジービューが期間選択を担う場合はチップを非表示にします。",
"cal_editor_show_user_filter": "ユーザーフィルターを表示",
@@ -38,6 +38,8 @@
"reset": "초기화",
"snooze": "다시 알림",
"snoozed": "다시 알림 설정됨",
"snoozed_for": "{hours}시간 동안 알림 음소거 — {until}까지",
"snoozed_for_one": "1시간 동안 알림 음소거 — {until}까지",
"cancel": "취소",
"bulk_select": "선택",
"bulk_select_all": "모두 선택",
@@ -63,7 +65,10 @@
"counter": "카운터",
"state_change": "상태 변경",
"runtime": "가동 시간",
"trigger_type_due_date": "센서의 날짜",
"runtime_hours": "목표 가동 시간 (시간)",
"days_before": "날짜 며칠 전",
"days_before_help": "유지보수 기한(날짜 또는 타임스탬프)을 보고하는 센서용입니다. 0 = 당일. 기기 자체의 '완료' 버튼이 날짜를 뒤로 미루어 트리거를 해제합니다 — 완료 시 동작으로 지정하세요.",
"target_value": "목표값",
"target_changes": "목표 변경 횟수",
"for_minutes": "지속 시간 (분)",
@@ -235,6 +240,8 @@
"model_optional": "모델 (선택)",
"serial_number_optional": "일련번호 (선택)",
"serial_number_label": "일련번호",
"object_replaces": "이전 기기",
"object_replaced_by": "후속 기기",
"documentation_url_label": "설명서",
"object_notes_label": "메모",
"sort_due_date": "예정일",
@@ -422,7 +429,7 @@
"qr_action_view": "유지보수 정보 보기",
"qr_action_complete": "유지보수 완료로 표시",
"qr_action_quick_complete": "빠른 완료 — 대화 상자 없이",
"qr_print_quick_none": "빠른 완료 코드에는 작업의 빠른 완료 기본값이 필요합니다(작업 대화 상자 → 빠른 완료 기본값). 선택한 개체의 작업에는 아직 없습니다.",
"qr_print_quick_none": "빠른 완료 코드에는 작업의 빠른 완료 기본값이 필요합니다(설정 → 고급 기능 → 완료 동작; 작업 대화 상자 → 빠른 완료 기본값). 선택한 개체의 작업에는 아직 없습니다.",
"qr_url_mode": "링크 유형",
"qr_mode_companion": "Companion 앱",
"qr_mode_local": "로컬 (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "고급 기능을 켜거나 끕니다. 끄면 UI에서 숨겨질 뿐 데이터는 삭제되지 않습니다.",
"feat_adaptive": "적응형 일정",
"feat_adaptive_desc": "유지보수 기록에서 최적 주기를 학습합니다",
"feat_predictions": "센서 예측",
"feat_predictions_desc": "센서 열화로부터 트리거 시점을 예측합니다",
"feat_seasonal": "계절 조정",
"feat_seasonal_desc": "계절 패턴에 따라 주기를 조정합니다",
"feat_environmental": "환경 상관관계",
@@ -671,6 +676,10 @@
"settings_currency": "통화",
"settings_currency_decimals": "금액 소수 자릿수",
"settings_currency_decimals_hint": "모든 금액에 표시할 소수 자릿수: KPI, 예산, 목록과 보고서의 비용, 예산 알림. 0 = 정수.",
"settings_parts_cost_mode": "예비 부품을 지출로 계산하는 시점",
"settings_parts_cost_purchase": "구매할 때",
"settings_parts_cost_use": "사용할 때",
"settings_parts_cost_mode_hint": "구매할 때: 구매 알림의 비용을 계산하고, 작업에 쓴 부품은 참고용으로만 표시합니다. 사용할 때: 완료할 때마다 쓴 부품의 가치를 기록하고, 구매는 계산하지 않고 부품 가격만 정합니다. 앞으로의 완료부터 적용되며, 기록은 당시 방식대로 유지됩니다.",
"settings_budget_monthly": "월 예산",
"settings_budget_yearly": "연 예산",
"settings_budget_alerts": "예산 경고",
@@ -683,15 +692,17 @@
"settings_import_placeholder": "JSON 또는 CSV 내용을 여기에 붙여넣으세요…",
"settings_import_btn": "가져오기",
"settings_import_success": "객체 {count}개를 성공적으로 가져왔습니다.",
"settings_import_unmatched_users": "이 인스턴스에서 찾을 수 없음: {names}. 작업 할당이 제거되었습니다 — 유지하려면 Home Assistant에서 같은 이름의 사용자를 만든 후 다시 가져오세요.",
"settings_export_success": "내보내기 파일이 다운로드되었습니다.",
"settings_saved": "설정이 저장되었습니다.",
"settings_include_history": "기록 포함",
"settings_export_selection": "선택한 객체로 제한 (선택)",
"settings_docs_archive": "문서 아카이브 (파일 포함)",
"settings_docs_archive_hint": "JSON/YAML/CSV 내보내기에는 설정만 포함됩니다. 이 ZIP에는 업로드한 파일 내용까지 포함되어 완전한 복원이 가능합니다.",
"settings_docs_archive_hint": "JSON 및 YAML 내보내기에는 문서 목록이 포함되지만 파일 자체는 포함되지 않으며, CSV에는 문서가 포함되지 않습니다. 이 ZIP에는 업로드한 파일이 들어 있으므로 JSON을 가져온 뒤 복원하면 빠짐없이 옮겨집니다.",
"settings_docs_export_btn": "문서 ZIP 다운로드",
"settings_docs_import_btn": "문서 ZIP 복원",
"settings_docs_import_success": "복원됨: 파일 {blobs}개, 문서 {docs}개",
"settings_docs_import_missing": "{n}개 건너뜀 — ZIP에 해당 파일이 없었습니다",
"sort_alphabetical": "가나다순",
"sort_due_soonest": "예정일 빠른 순",
"sort_task_count": "작업 수",
@@ -777,7 +788,7 @@
"has_overdue": "기한 초과 작업 있음",
"object": "객체",
"settings_panel_access": "패널 접근 권한",
"settings_panel_access_desc": "관리자는 항상 전체 권한을 가집니다. 특정 비관리자에게 생성·편집·삭제를 위임하려면 이 옵션을 켜고 아래에서 선택하세요. 나머지 사용자에게는 완료와 건너뛰기만 표시됩니다.",
"settings_panel_access_desc": "관리자는 항상 전체 권한을 가집니다. 특정 비관리자에게 생성·편집·삭제를 위임하려면 이 옵션을 켜고 아래에서 선택하세요. 나머지 사용자는 완료, 건너뛰기, 초기화, 연기, 다시 알림만 사용할 수 있습니다.",
"settings_operator_write": "선택한 사용자에게 생성·편집·삭제 허용",
"settings_operator_write_desc": "끔: 관리자만 내용을 변경할 수 있습니다. 켬: 아래에서 선택한 사용자도 전체 권한을 갖습니다.",
"no_non_admin_users": "비관리자 사용자가 없습니다. 설정 → 사람에서 추가하세요.",
@@ -788,11 +799,16 @@
"on_complete_action_desc": "작업이 완료되면 HA 서비스를 호출합니다 — 예: 기기의 카운터 초기화.",
"on_complete_action_target": "대상 엔티티",
"on_complete_action_target_hint": "참고: 엔티티 도메인이 서비스와 일치해야 합니다 — 예: 'button.press'는 button.*에서만, 'counter.increment'는 counter.*에서만, 'input_button.press'는 input_button.*에서만 동작합니다. 불일치 시 동작이 조용히 실패합니다 (HA 로그에 'Referenced entities ... missing or not currently available' 기록).",
"on_complete_action_target_more": "이 동작에는 이 필드에 표시된 것보다 많은 대상이 있습니다: {targets}. 그대로 유지되며, 여기서 다른 엔티티를 선택하면 전체 대상이 바뀝니다.",
"on_complete_action_skip_auto": "작업이 스스로 완료될 때는 제외 (센서가 정상으로 돌아옴)",
"on_complete_action_data": "데이터 (JSON, 선택)",
"on_complete_action_test": "설정 검증",
"on_complete_action_test_success": "✓ 설정이 유효합니다 (동작은 작업 완료 시에만 실행됩니다)",
"on_complete_action_test_failed": "실패",
"action_test_bad_format": "서비스 형식이 잘못되었습니다('domain.service' 형식이어야 함)",
"action_test_unknown_service": "서비스 \"{service}\"이(가) Home Assistant에 등록되어 있지 않습니다. 철자와 이 서비스를 제공하는 통합구성요소가 로드되었는지 확인하세요.",
"action_test_domain_mismatch": "서비스 \"{service}\"은(는) {domain}.* 엔티티에서만 작동합니다. 엔티티 \"{entity}\"은(는) {entity_domain}.*에 속합니다 — 엔티티 도메인에 맞는 서비스를 선택하세요(예: {suggestion})",
"action_test_entity_missing": "대상 엔티티 \"{entity}\"을(를) Home Assistant에서 찾을 수 없습니다 — 이름이 바뀌었거나 통합구성요소가 제거되었을 수 있습니다.",
"quick_complete_defaults_title": "빠른 완료 기본값 (QR 스캔용, 선택)",
"quick_complete_defaults_desc": "빠른 완료 QR 스캔에 쓰이는 사전 설정값입니다. 없으면 QR이 완료 대화상자를 엽니다.",
"quick_complete_defaults_notes": "메모",
@@ -850,6 +866,7 @@
"doc_too_large": "파일이 너무 큽니다 (최대 25 MB).",
"doc_upload_failed": "업로드에 실패했습니다.",
"docs_archive_too_large": "아카이브가 너무 큽니다(최대 {max}).",
"docs_archive_no_manifest": "이 ZIP에는 manifest.json이 없어 복원 시 파일이 어느 문서에 속하는지 알 수 없습니다. 다시 압축할 때 manifest.json을 폴더 옆에 두세요.",
"completion_photos_optional": "완료 사진 (선택)",
"completion_photos": "완료 사진",
"choose_photos": "사진 선택",
@@ -910,6 +927,11 @@
"paused": "일시 중지됨",
"pause_object": "일시 중지",
"resume_object": "재개",
"pause_task": "일시 중지",
"resume_task": "재개",
"pause_task_prompt": "이 작업의 일정을 동결합니다. 재개할 때까지 기한이 되지 않고 알림도 없으며, 재개하면 그날부터 새 주기가 시작됩니다. 객체의 다른 작업은 계속 실행됩니다. 자동으로 재개할 날짜를 선택적으로 지정할 수 있습니다.",
"task_paused": "작업 일시 중지됨",
"task_resumed": "작업 재개됨 — 일정이 다시 시작됨",
"pause_until_prompt": "이 객체의 일정을 동결합니다 — 재개할 때까지 어떤 작업도 기한이 되지 않고 알림도 오지 않습니다. 자동 재개 날짜를 선택적으로 설정할 수 있습니다.",
"pause_until_label": "재개일 (선택)",
"object_paused": "객체가 일시 중지되었습니다",
@@ -919,7 +941,18 @@
"replace_object": "교체…",
"replace_object_prompt": "이 객체를 퇴역시키고 후속 객체를 만듭니다. 기록과 비용은 기존 객체에 보관된 채 남고, 작업과 문서는 새 객체로 이어지며 카운터는 새로 시작됩니다.",
"replace_name_label": "후속 객체 이름",
"replace_device_heading": "Home Assistant의 기기",
"replace_device_keep": "이전과 같은 기기({device})",
"replace_device_keep_hint": "새 기기가 아직 Home Assistant에 없을 때도 이것을 선택하세요. 추가된 뒤 개체 설정에서 기기로 고르면 센서 트리거가 함께 옮겨집니다.",
"replace_device_other": "새 기기는 다른 기기",
"replace_device_pick": "새 기기의 기기 항목",
"replace_device_other_hint": "센서 트리거, 완료 시 동작, 카운터 초기화가 그 기기의 대응하는 엔티티로 옮겨집니다.",
"replace_device_none": "기기 없음",
"object_replaced": "객체가 교체되었습니다 — 후속 객체 생성됨",
"device_swap_moved": "센서 연결 {count}개를 새 기기로 옮겼습니다.",
"device_swap_moved_one": "센서 연결 1개를 새 기기로 옮겼습니다.",
"device_swap_unmatched": "{count}개는 대응시키지 못했습니다. 해당 작업의 센서 트리거를 확인하세요.",
"device_swap_unmatched_one": "1개는 대응시키지 못했습니다. 그 작업의 센서 트리거를 확인하세요.",
"reading_unit_label": "검침 단위 (예: kWh, m³)",
"reading_unit_help": "이 작업을 완료할 때 기록 값 옆에 표시됩니다.",
"reading_value_label": "검침 값",
@@ -1016,6 +1049,9 @@
"part_stock": "재고",
"part_reorder_threshold": "재주문 기준",
"part_restock_quantity": "보충 수량",
"part_package_size": "포장 크기(단위)",
"part_restock_packages": "보충 수량(포장)",
"part_package_hint": "포장 단위로 사고 단위로 씁니다. 재고, 작업당 사용량, 재주문 기준은 단위로 세고, 보충 수량과 가격은 포장 기준입니다.",
"part_auto_buy": "재고 부족 시 구매 작업 자동 생성",
"part_restock": "재고 조정",
"parts_used_by": "사용처",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "마지막 완료 이후 사용량을 계수합니다: +{target} 후 기한이 되며, 완료할 때마다 계수가 다시 시작됩니다.",
"trigger_hint_counter_abs": "센서가 {target}에 도달하면 작업 기한이 됩니다.",
"trigger_hint_runtime": "누적 가동 {hours}시간 후 작업 기한이 됩니다. 카운터는 완료할 때마다 다시 시작됩니다.",
"trigger_hint_due_date": "작업은 센서가 보고하는 날짜보다 {days}일 앞서 기한이 됩니다.",
"trigger_hint_due_date_one": "작업은 센서가 보고하는 날짜보다 하루 앞서 기한이 됩니다.",
"trigger_hint_due_date_same_day": "작업은 센서가 보고하는 날짜에 기한이 됩니다.",
"trigger_hint_due_date_now": "보고된 날짜: {date}.",
"trigger_hint_state_change": "상태가 {count}회 변경되면 작업 기한이 됩니다.",
"trigger_hint_state_change_to": "“{state}”(으)로 {count}회 변경되면 작업 기한이 됩니다.",
"trigger_hint_state_now": "현재 상태: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "선택한 저장된 보기의 라벨/사용자 필터와 일치하는 작업만 알림을 보냅니다. 보기의 상태·정렬·그룹화는 여기서 무시됩니다.",
"settings_notify_completed": "완료 알림",
"settings_notify_completed_hint": "작업이 완료되면 사유와 실행자를 함께 가족에게 알립니다. \"자동만\"은 현장에서 아무도 하지 않은 완료만 알립니다: 센서 복구, 쇼핑 목록 체크, 자동화의 서비스 호출. 가정용 서비스로만 전송되며 방해 금지 시간과 일일 한도가 적용됩니다.",
"settings_title_style": "알림 제목 스타일",
"settings_title_style_hint": "알림의 제목으로 표시할 내용입니다. 기본값은 상태별 문구(예: '유지보수 기한 초과!')를 유지합니다. 휴대폰에 알림이 쌓일 때는 대상 이름/작업 이름이 도움이 됩니다.",
"title_style_default": "기본(상태별 제목)",
"title_style_object_name": "대상 이름을 제목으로",
"title_style_task_name": "작업 이름을 제목으로",
"notify_completed_off": "끄기",
"notify_completed_automatic": "자동 완료만",
"notify_completed_all": "모든 완료",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON 또는 YAML, Jinja 사용 가능. notify 호출의 data에 병합됩니다(사용자 키 우선). 변수: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. 테스트 보내기 버튼으로 확인하세요.",
"card_saved_view": "저장된 보기",
"card_saved_view_none": "없음",
"card_saved_view_help": "위 필터에 더해 보기의 상태·사용자·라벨 필터를 적용합니다. 보기의 정렬과 그룹화는 패널 표시 설정이며 카드에는 적용되지 않습니다.",
"card_saved_view_help": "위 필터에 더해 보기의 상태·사용자·라벨·우선순위 필터를 적용합니다. 보기의 정렬과 그룹화는 패널 표시 설정이며 카드에는 적용되지 않습니다.",
"panel_card_tab": "열 때 탭",
"panel_card_tab_default": "기억된 탭(패널과 동일)",
"panel_card_view": "저장된 보기로 열기",
@@ -1189,6 +1234,14 @@
"row_actions_back": "아이콘으로 되돌리기",
"battery_fleet_forecast_overdue": "예측 날짜가 지났지만 배터리는 여전히 정상으로 보고됩니다. 교체했다면 교체를 기록하세요. 아니라면 예측이 빗나간 것입니다.",
"cost_from_parts": "부품에서 ≈ {amount} 적용",
"cost_parts_booked": "사용한 부품 ≈ {amount}은 자동으로 기록됩니다. 추가 비용만 입력하세요.",
"cost_parts_info": "사용한 부품 ≈ {amount} — 구매할 때 계산되었고 여기서는 계산하지 않습니다.",
"cost_purchase_use_hint": "지불한 가격이 부품 가격이 됩니다. 예비 부품은 사용할 때 계산됩니다.",
"history_parts_counted": "부품: {amount}",
"history_parts_counted_hint": "사용한 부품의 가치 — 지출로 계산됨",
"history_parts_info": "부품 ≈ {amount}",
"history_parts_info_hint": "사용한 부품의 가치 — 참고용이며, 비용은 구매할 때 계산됨",
"history_purchase_stock": "{amount}에 구매 (사용할 때 계산)",
"dismiss": "닫기",
"gs_label": "시작하기 — 설정이 늘어나면 이 힌트는 사라집니다",
"gs_setups_chip": "추천 설정: 트리거가 준비된 기기 {n}대 발견",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "2주 (14일)",
"cal_editor_window_month": "1개월 (30일, 기본)",
"cal_editor_window_year": "1년 (365일, 빈 날은 접힘)",
"cal_editor_window_past_30": "지난 30일 (기록)",
"cal_editor_window_past_90": "지난 90일 (기록)",
"cal_editor_show_chips": "카드 안에 기간 칩 표시",
"cal_editor_chips_hint": "전략 뷰가 이미 기간 선택기 역할을 하면 칩을 숨기세요.",
"cal_editor_show_user_filter": "사용자 필터 표시",
@@ -38,6 +38,8 @@
"reset": "Tilbakestill",
"snooze": "Slumre",
"snoozed": "Slumrer",
"snoozed_for": "Påminnelser dempet i {hours} timer — til {until}",
"snoozed_for_one": "Påminnelser dempet i én time — til {until}",
"cancel": "Avbryt",
"bulk_select": "Velg",
"bulk_select_all": "Velg alle",
@@ -63,7 +65,10 @@
"counter": "Teller",
"state_change": "Tilstandsendring",
"runtime": "Driftstid",
"trigger_type_due_date": "Dato fra en sensor",
"runtime_hours": "Måldriftstid (timer)",
"days_before": "Dager før datoen",
"days_before_help": "For en sensor som rapporterer når vedlikeholdet forfaller (dato eller tidsstempel). 0 = på selve datoen. Enhetens egen «ferdig»-knapp flytter datoen fram og opphever dermed utløseren — gjør den til handlingen ved fullføring.",
"target_value": "Målverdi",
"target_changes": "Antall endringer",
"for_minutes": "I (minutter)",
@@ -235,6 +240,8 @@
"model_optional": "Modell (valgfritt)",
"serial_number_optional": "Serienummer (valgfritt)",
"serial_number_label": "S/N",
"object_replaces": "Erstatter",
"object_replaced_by": "Erstattet av",
"documentation_url_label": "Bruksanvisning",
"object_notes_label": "Notater",
"sort_due_date": "Forfallsdato",
@@ -422,7 +429,7 @@
"qr_action_view": "Vis vedlikeholdsinfo",
"qr_action_complete": "Merk vedlikehold som fullført",
"qr_action_quick_complete": "Hurtigfullføring — uten dialog",
"qr_print_quick_none": "Hurtigfullføringskoder krever standardverdier for hurtigfullføring på en oppgave (oppgavedialogen → Standardverdier for hurtigfullføring) — ingen oppgave i de valgte objektene har noen ennå.",
"qr_print_quick_none": "Hurtigfullføringskoder krever standardverdier for hurtigfullføring på en oppgave (Innstillinger → Avanserte funksjoner → Fullføringshandlinger; oppgavedialogen → Standardverdier for hurtigfullføring) — ingen oppgave i de valgte objektene har noen ennå.",
"qr_url_mode": "Lenketype",
"qr_mode_companion": "Companion-app",
"qr_mode_local": "Lokal (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "Aktiver eller deaktiver avanserte funksjoner. Deaktivering skjuler dem fra grensesnittet, men sletter ikke data.",
"feat_adaptive": "Adaptiv planlegging",
"feat_adaptive_desc": "Lær optimale intervaller fra vedlikeholdshistorikken",
"feat_predictions": "Sensorprediksjoner",
"feat_predictions_desc": "Forutsi utløserdatoer fra sensorforringelse",
"feat_seasonal": "Sesongjusteringer",
"feat_seasonal_desc": "Juster intervaller basert på sesongmønstre",
"feat_environmental": "Miljøkorrelasjon",
@@ -671,6 +676,10 @@
"settings_currency": "Valuta",
"settings_currency_decimals": "Desimaler for beløp",
"settings_currency_decimals_hint": "Hvor mange desimaler hvert beløp viser: nøkkeltall, budsjetter, kostnader i lister og rapporter, budsjettvarsler. 0 = hele tall.",
"settings_parts_cost_mode": "Reservedeler teller som utgift",
"settings_parts_cost_purchase": "Ved kjøp",
"settings_parts_cost_use": "Ved bruk",
"settings_parts_cost_mode_hint": "Ved kjøp: kostnaden for en kjøpspåminnelse teller, og delene en jobb bruker vises som informasjon. Ved bruk: hver fullføring bokfører verdien av delene som ble brukt, og et kjøp setter delens pris i stedet for å telle. Gjelder fullføringer fra nå av — historikken beholder hvordan den ble bokført.",
"settings_budget_monthly": "Månedsbudsjett",
"settings_budget_yearly": "Årsbudsjett",
"settings_budget_alerts": "Budsjettvarsler",
@@ -683,6 +692,7 @@
"settings_import_placeholder": "Lim inn JSON- eller CSV-innhold her…",
"settings_import_btn": "Importer",
"settings_import_success": "{count} objekter importert.",
"settings_import_unmatched_users": "Ikke funnet på denne instansen: {names}. Oppgavetildelingene deres ble fjernet — opprett personer med de samme navnene i Home Assistant og importer på nytt for å beholde dem.",
"settings_export_success": "Eksport lastet ned.",
"settings_saved": "Innstilling lagret.",
"settings_include_history": "Inkluder historikk",
@@ -771,7 +781,7 @@
"has_overdue": "Har forfalte oppgaver",
"object": "Objekt",
"settings_panel_access": "Paneltilgang",
"settings_panel_access_desc": "Administratorer har alltid full tilgang. For å delegere opprettelse, redigering og sletting til bestemte ikke-administratorer, slå dette på og velg dem nedenfor — alle andre ser kun Fullfør og Hopp over.",
"settings_panel_access_desc": "Administratorer har alltid full tilgang. For å delegere opprettelse, redigering og sletting til bestemte ikke-administratorer, slå dette på og velg dem nedenfor — alle andre har kun Fullfør, Hopp over, Tilbakestill, Utsett og Slumre.",
"settings_operator_write": "Tillat valgte brukere å opprette, redigere og slette",
"settings_operator_write_desc": "Av: bare administratorer kan endre innhold. På: de valgte brukerne nedenfor får også full tilgang.",
"no_non_admin_users": "Ingen ikke-administratorbrukere funnet. Legg til noen under Innstillinger → Personer.",
@@ -782,11 +792,16 @@
"on_complete_action_desc": "Kaller en HA-tjeneste når oppgaven fullføres — f.eks. tilbakestille en teller på enheten.",
"on_complete_action_target": "Målentitet",
"on_complete_action_target_hint": "Merk: entitetsdomenet må samsvare med tjenesten — f.eks. fungerer 'button.press' kun på button.*, 'counter.increment' kun på counter.*, 'input_button.press' kun på input_button.* osv. Ved manglende samsvar mislykkes handlingen i det stille (HA logger 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Denne handlingen har flere mål enn feltet viser: {targets}. De beholdes som de er — velger du en annen entitet her, erstatter den hele målet.",
"on_complete_action_skip_auto": "Ikke når oppgaven fullføres av seg selv (sensoren er normal igjen)",
"on_complete_action_data": "Data (JSON, valgfritt)",
"on_complete_action_test": "Valider konfigurasjon",
"on_complete_action_test_success": "✓ Konfigurasjon gyldig (handlingen utløses kun ved fullføring av oppgaven)",
"on_complete_action_test_failed": "Mislyktes",
"action_test_bad_format": "Ugyldig tjenesteformat (forventet 'domain.service')",
"action_test_unknown_service": "Tjenesten «{service}» er ikke registrert i Home Assistant. Kontroller stavemåten og at integrasjonen som leverer den, er lastet.",
"action_test_domain_mismatch": "Tjenesten «{service}» virker bare på {domain}.*-entiteter; entiteten «{entity}» hører til {entity_domain}.* — velg en tjeneste som passer til entitetens domene (f.eks. {suggestion})",
"action_test_entity_missing": "Målentiteten «{entity}» ble ikke funnet i Home Assistant — den kan ha fått nytt navn, eller integrasjonen kan være fjernet.",
"quick_complete_defaults_title": "Standardverdier for hurtigfullføring (for QR-skanninger, valgfritt)",
"quick_complete_defaults_desc": "Forhåndsinnstilte verdier for hurtigfullføring via QR-skanning. Uten disse åpner QR-koden fullføringsdialogen.",
"quick_complete_defaults_notes": "Notater",
@@ -844,6 +859,7 @@
"doc_too_large": "Filen er for stor (maks. 25 MB).",
"doc_upload_failed": "Opplasting mislyktes.",
"docs_archive_too_large": "Arkivet er for stort (maks. {max}).",
"docs_archive_no_manifest": "Denne ZIP-filen har ingen manifest.json, så gjenopprettingen vet ikke hvilket dokument en fil hører til. La manifest.json ligge ved siden av mappene når du zipper på nytt.",
"completion_photos_optional": "Bilder ved fullføring (valgfritt)",
"completion_photos": "Bilder ved fullføring",
"choose_photos": "Velg bilder",
@@ -904,6 +920,11 @@
"paused": "Satt på pause",
"pause_object": "Sett på pause",
"resume_object": "Gjenoppta",
"pause_task": "Sett på pause",
"resume_task": "Gjenoppta",
"pause_task_prompt": "Fryser oppgavens plan — den forfaller ikke og varsler ikke før du gjenopptar den, og gjenopptakelse starter en ny syklus fra den dagen. Objektets andre oppgaver fortsetter. Valgfritt: en dato da den gjenopptas av seg selv.",
"task_paused": "Oppgaven er satt på pause",
"task_resumed": "Oppgaven er gjenopptatt — planen starter på nytt",
"pause_until_prompt": "Frys objektets planer — ingenting forfaller og ingenting varsler før det gjenopptas. Angi eventuelt en dato for automatisk gjenopptakelse.",
"pause_until_label": "Gjenoppta den (valgfritt)",
"object_paused": "Objekt satt på pause",
@@ -913,7 +934,18 @@
"replace_object": "Erstatt…",
"replace_object_prompt": "Pensjoner dette objektet og opprett en etterfølger. Historikk og kostnader forblir arkivert på det gamle; oppgaver og dokumenter følger med til det nye, tellere starter på nytt.",
"replace_name_label": "Etterfølgerens navn",
"replace_device_heading": "Enhet i Home Assistant",
"replace_device_keep": "Samme enhet som før ({device})",
"replace_device_keep_hint": "Velg dette også hvis den nye enheten ikke er i Home Assistant ennå: når den er det, velg den som objektets enhet i innstillingene — sensorutløserne flytter da med.",
"replace_device_other": "Den nye enheten er en annen enhet",
"replace_device_pick": "Den nye enhetens enhet i Home Assistant",
"replace_device_other_hint": "Sensorutløsere, fullføringshandlinger og tellernullstillingen flytter til de tilsvarende entitetene på den enheten.",
"replace_device_none": "Ingen enhet",
"object_replaced": "Objekt erstattet — etterfølger opprettet",
"device_swap_moved": "{count} sensorkoblinger flyttet til den nye enheten.",
"device_swap_moved_one": "Én sensorkobling flyttet til den nye enheten.",
"device_swap_unmatched": "{count} kunne ikke matches — sjekk sensorutløserne til de oppgavene.",
"device_swap_unmatched_one": "Én kunne ikke matches — sjekk oppgavens sensorutløser.",
"reading_unit_label": "Avlesningsenhet (f.eks. kWh, m³)",
"reading_unit_help": "Vises ved siden av den registrerte verdien når oppgaven fullføres.",
"reading_value_label": "Avlest verdi",
@@ -1010,6 +1042,9 @@
"part_stock": "Lager",
"part_reorder_threshold": "Bestillingsgrense",
"part_restock_quantity": "Påfyllingsmengde",
"part_package_size": "Pakningsstørrelse (enheter)",
"part_restock_packages": "Påfyllingsmengde (pakninger)",
"part_package_hint": "Kjøpes i pakninger, brukes i enheten: lager, forbruk per oppgave og bestillingsgrense teller enheter; påfyllingsmengde og pris gjelder per pakning.",
"part_auto_buy": "Kjøpsoppgave ved lavt lager",
"part_restock": "Juster lager",
"parts_used_by": "Brukes av",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Kunne ikke laste objektets deler — forbruksvalgene er ikke tilgjengelige nå.",
"settings_export_selection": "Begrens til valgte objekter (valgfritt)",
"settings_docs_archive": "Dokumentarkiv (med filer)",
"settings_docs_archive_hint": "JSON/YAML/CSV-eksportene inneholder bare innstillinger. Denne ZIP-en inkluderer innholdet i de opplastede filene, slik at gjenoppretting blir fullstendig.",
"settings_docs_archive_hint": "JSON- og YAML-eksportene lister opp dokumentene dine, men ikke filene deres; CSV utelater dokumenter. Denne ZIP-en inneholder de opplastede filene – gjenopprett den etter at JSON er importert, så mangler ingenting.",
"settings_docs_export_btn": "Last ned dokument-ZIP",
"settings_docs_import_btn": "Gjenopprett dokument-ZIP",
"settings_docs_import_success": "Gjenopprettet: {blobs} filer, {docs} dokumenter",
"settings_docs_import_missing": "{n} hoppet over — filene deres var ikke i ZIP-filen",
"adopt_problem_button": "Ta i bruk problemsensorer",
"adopt_problem_title": "Ta i bruk problemsensorer",
"adopt_problem_hint": "Gjør HA-problemsensorer (skriverfeil, filteradvarsler, lavt batteri) om til vedlikeholdsoppgaver som utløses mens problemet er aktivt og fullføres selv når det løses.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Teller forbruket siden forrige fullføring: forfaller etter +{target}; telleren starter på nytt etter hver fullføring.",
"trigger_hint_counter_abs": "Oppgaven forfaller når sensoren når {target}.",
"trigger_hint_runtime": "Oppgaven forfaller etter {hours} t akkumulert driftstid; telleren starter på nytt etter hver fullføring.",
"trigger_hint_due_date": "Oppgaven forfaller {days} dager før datoen sensoren rapporterer.",
"trigger_hint_due_date_one": "Oppgaven forfaller én dag før datoen sensoren rapporterer.",
"trigger_hint_due_date_same_day": "Oppgaven forfaller på datoen sensoren rapporterer.",
"trigger_hint_due_date_now": "Rapportert dato: {date}.",
"trigger_hint_state_change": "Oppgaven forfaller etter {count} tilstandsendring(er).",
"trigger_hint_state_change_to": "Oppgaven forfaller etter {count} endring(er) til “{state}”.",
"trigger_hint_state_now": "Gjeldende tilstand: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Bare oppgaver som matcher etikett-/brukerfiltrene til valgt visning sender påminnelser. Visningens status, sortering og gruppering ignoreres her.",
"settings_notify_completed": "Varsler om fullføring",
"settings_notify_completed_hint": "En beskjed til husstanden når en oppgave er fullført, med årsak og hvem som gjorde det. «Bare automatiske» melder bare fullføringer ingen gjorde på stedet: sensorgjenoppretting, avkrysning i handlelisten, et tjenestekall fra en automasjon. Bare til husstandstjenesten; stilletimer og dagsgrensen gjelder.",
"settings_title_style": "Tittelstil for varsel",
"settings_title_style_hint": "Det som vises som varselets TITTEL. Standard beholder teksten per status (f.eks. «Vedlikehold forfalt!»). Objektnavn / Oppgavenavn hjelper når telefonvarsler stables.",
"title_style_default": "Standard (tittel per status)",
"title_style_object_name": "Objektnavn som tittel",
"title_style_task_name": "Oppgavenavn som tittel",
"notify_completed_off": "Av",
"notify_completed_automatic": "Bare automatiske fullføringer",
"notify_completed_all": "Alle fullføringer",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillatt; flettes inn i notify-kallets data (dine nøkler vinner). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test med knappen Send test.",
"card_saved_view": "Lagret visning",
"card_saved_view_none": "Ingen",
"card_saved_view_help": "Bruker visningens status-, bruker- og etikettfiltre i tillegg til filtrene over. Visningens sortering og gruppering er panelvisningsinnstillinger og brukes ikke på kortet.",
"card_saved_view_help": "Bruker visningens status-, bruker-, etikett- og prioritetsfiltre i tillegg til filtrene over. Visningens sortering og gruppering er panelvisningsinnstillinger og brukes ikke på kortet.",
"panel_card_tab": "Åpne på fanen",
"panel_card_tab_default": "Husket fane (som i panelet)",
"panel_card_view": "Åpne med lagret visning",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Tilbake til ikoner",
"battery_fleet_forecast_overdue": "Forutsagt dato er passert — batteriet melder fortsatt god tilstand. Hvis du byttet det, registrer byttet; ellers bommet prognosen.",
"cost_from_parts": "Bruk ≈ {amount} fra deler",
"cost_parts_booked": "Brukte deler ≈ {amount} bokføres automatisk — legg bare inn ekstra kostnader.",
"cost_parts_info": "Brukte deler ≈ {amount} — telt ved kjøpet, ikke her.",
"cost_purchase_use_hint": "Prisen som ble betalt blir delens pris — reservedeler teller når de brukes.",
"history_parts_counted": "Deler: {amount}",
"history_parts_counted_hint": "Verdien av delene som ble brukt — telt som utgift",
"history_parts_info": "Deler ≈ {amount}",
"history_parts_info_hint": "Verdien av delene som ble brukt — bare til informasjon; pengene talte ved kjøpet",
"history_purchase_stock": "Kjøpt for {amount} (teller ved bruk)",
"dismiss": "Avvis",
"gs_label": "Kom i gang — tipsene forsvinner etter hvert som oppsettet vokser",
"gs_setups_chip": "Foreslåtte oppsett fant {n} enheter med ferdigkoblede utløsere",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "To uker (14 dager)",
"cal_editor_window_month": "Måned (30 dager, standard)",
"cal_editor_window_year": "År (365 dager, tomme dager skjult)",
"cal_editor_window_past_30": "Siste 30 dager (historikk)",
"cal_editor_window_past_90": "Siste 90 dager (historikk)",
"cal_editor_show_chips": "Vis vindus-chips i kortet",
"cal_editor_chips_hint": "Skjul chipsene når kortet er innebygd i en strategivisning som allerede fungerer som vindusvelger.",
"cal_editor_show_user_filter": "Vis brukerfilter",
@@ -36,6 +36,8 @@
"reset": "Resetten",
"snooze": "Sluimeren",
"snoozed": "Gesluimerd",
"snoozed_for": "Herinneringen {hours} uur gedempt — tot {until}",
"snoozed_for_one": "Herinneringen een uur gedempt — tot {until}",
"cancel": "Annuleren",
"bulk_select": "Selecteren",
"bulk_select_all": "Alles selecteren",
@@ -61,7 +63,10 @@
"counter": "Teller",
"state_change": "Statuswijziging",
"runtime": "Looptijd",
"trigger_type_due_date": "Datum van een sensor",
"runtime_hours": "Doellooptijd (uren)",
"days_before": "Dagen vóór de datum",
"days_before_help": "Voor een sensor die meldt wanneer het onderhoud nodig is (datum of tijdstempel). 0 = op de datum zelf. De eigen 'klaar'-knop van het apparaat schuift de datum op en heft zo de trigger op — maak hem de actie bij afronden.",
"target_value": "Doelwaarde",
"target_changes": "Doelwijzigingen",
"for_minutes": "Voor (minuten)",
@@ -233,6 +238,8 @@
"model_optional": "Model (optioneel)",
"serial_number_optional": "Serienummer (optioneel)",
"serial_number_label": "S/N",
"object_replaces": "Vervangt",
"object_replaced_by": "Vervangen door",
"documentation_url_label": "Handleiding",
"object_notes_label": "Notities",
"sort_due_date": "Vervaldatum",
@@ -420,7 +427,7 @@
"qr_action_view": "Onderhoudsinfo bekijken",
"qr_action_complete": "Onderhoud als voltooid markeren",
"qr_action_quick_complete": "Snel voltooien — zonder dialoogvenster",
"qr_print_quick_none": "Snel-voltooien-codes hebben snel-voltooien-standaardwaarden bij een taak nodig (taakdialoog → Snel-voltooien-standaardwaarden) — nog geen taak van de gekozen objecten heeft ze.",
"qr_print_quick_none": "Snel-voltooien-codes hebben snel-voltooien-standaardwaarden bij een taak nodig (Instellingen → Geavanceerde functies → Voltooiings-acties; taakdialoog → Snel-voltooien-standaardwaarden) — nog geen taak van de gekozen objecten heeft ze.",
"qr_url_mode": "Linktype",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Lokaal (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Schakel geavanceerde functies in of uit. Uitschakelen verbergt ze in de interface maar verwijdert geen gegevens.",
"feat_adaptive": "Adaptieve planning",
"feat_adaptive_desc": "Leer optimale intervallen uit onderhoudsgeschiedenis",
"feat_predictions": "Sensorvoorspellingen",
"feat_predictions_desc": "Voorspel triggerdatums op basis van sensordegradatie",
"feat_seasonal": "Seizoensaanpassingen",
"feat_seasonal_desc": "Pas intervallen aan op seizoenspatronen",
"feat_environmental": "Omgevingscorrelatie",
@@ -669,6 +674,10 @@
"settings_currency": "Valuta",
"settings_currency_decimals": "Decimalen voor bedragen",
"settings_currency_decimals_hint": "Hoeveel decimalen elk bedrag toont: KPI's, budgetten, kosten in lijsten en rapporten, budgetmeldingen. 0 = hele getallen.",
"settings_parts_cost_mode": "Onderdelen tellen als uitgave",
"settings_parts_cost_purchase": "Bij aankoop",
"settings_parts_cost_use": "Bij gebruik",
"settings_parts_cost_mode_hint": "Bij aankoop: de kosten van een aankoopherinnering tellen, de onderdelen die een klus gebruikt worden ter informatie getoond. Bij gebruik: elke afronding boekt de waarde van de gebruikte onderdelen en een aankoop zet de prijs van het onderdeel in plaats van mee te tellen. Geldt vanaf nu — de geschiedenis houdt hoe hij geboekt is.",
"settings_budget_monthly": "Maandbudget",
"settings_budget_yearly": "Jaarbudget",
"settings_budget_alerts": "Budgetwaarschuwingen",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Plak JSON- of CSV-inhoud hier…",
"settings_import_btn": "Importeren",
"settings_import_success": "{count} objecten succesvol geïmporteerd.",
"settings_import_unmatched_users": "Niet gevonden op deze instantie: {names}. Hun taaktoewijzingen zijn verwijderd — maak personen met dezelfde namen aan in Home Assistant en importeer opnieuw om ze te behouden.",
"settings_export_success": "Export gedownload.",
"settings_saved": "Instelling opgeslagen.",
"settings_include_history": "Geschiedenis meenemen",
@@ -746,7 +756,7 @@
"has_overdue": "Heeft achterstallige taken",
"object": "Object",
"settings_panel_access": "Paneel-toegang",
"settings_panel_access_desc": "Admins hebben altijd volledige toegang. Om aanmaken, bewerken en verwijderen aan specifieke niet-admins te delegeren, schakel dit in en selecteer hen hieronder — alle anderen zien alleen Voltooien en Overslaan.",
"settings_panel_access_desc": "Admins hebben altijd volledige toegang. Om aanmaken, bewerken en verwijderen aan specifieke niet-admins te delegeren, schakel dit in en selecteer hen hieronder — alle anderen hebben alleen Voltooid, Overslaan, Resetten, Uitstellen en Sluimeren.",
"settings_operator_write": "Geselecteerde gebruikers laten aanmaken, bewerken en verwijderen",
"settings_operator_write_desc": "Uit: alleen admins kunnen inhoud wijzigen. Aan: de geselecteerde gebruikers hieronder krijgen ook volledige toegang.",
"no_non_admin_users": "Geen niet-admin gebruikers gevonden. Voeg ze toe in Instellingen → Personen.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Actie testen",
"on_complete_action_test_success": "Geslaagd",
"on_complete_action_test_failed": "Mislukt",
"action_test_bad_format": "Ongeldige serviceopmaak (verwacht: 'domain.service')",
"action_test_unknown_service": "Service \"{service}\" is niet geregistreerd in Home Assistant. Controleer de spelling en of de integratie die hem levert geladen is.",
"action_test_domain_mismatch": "Service \"{service}\" werkt alleen op {domain}.*-entiteiten; entiteit \"{entity}\" hoort bij {entity_domain}.* — kies een service die bij het domein van de entiteit past (bijv. {suggestion})",
"action_test_entity_missing": "Doelentiteit \"{entity}\" is niet gevonden in Home Assistant — misschien is ze hernoemd of is de integratie verwijderd.",
"quick_complete_defaults_title": "Snel-voltooien-standaardwaarden (voor QR-scans, optioneel)",
"quick_complete_defaults_desc": "Vooraf ingestelde waarden voor snel-voltooien-QR. Zonder deze opent de QR de voltooi-dialoog.",
"quick_complete_defaults_notes": "Notities",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Taaktoewijzingen beheren…",
"groups_open_panel": "Openen in paneel",
"on_complete_action_target_hint": "Let op: het domein van de entiteit moet bij de service passen — bijv. 'button.press' werkt alleen op button.*, 'counter.increment' alleen op counter.*, 'input_button.press' alleen op input_button.* enz. Bij een mismatch mislukt de actie stil (HA logt 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Deze actie heeft meer doelen dan dit veld toont: {targets}. Die blijven behouden — kies je hier een andere entiteit, dan vervangt die het hele doel.",
"on_complete_action_skip_auto": "Niet wanneer de taak zichzelf voltooit (de sensor is hersteld)",
"show_all_objects": "Alle objecten tonen",
"show_all_tasks": "Filter wissen — alle taken tonen",
@@ -844,6 +859,7 @@
"doc_too_large": "Bestand is te groot (max. 25 MB).",
"doc_upload_failed": "Uploaden mislukt.",
"docs_archive_too_large": "Het archief is te groot (max. {max}).",
"docs_archive_no_manifest": "Deze ZIP bevat geen manifest.json, dus het herstel weet niet bij welk document een bestand hoort. Laat manifest.json naast de mappen staan als je opnieuw zipt.",
"completion_photos_optional": "Foto's bij afronding (optioneel)",
"completion_photos": "Foto's bij afronding",
"choose_photos": "Foto's kiezen",
@@ -904,6 +920,11 @@
"paused": "Gepauzeerd",
"pause_object": "Pauzeren",
"resume_object": "Hervatten",
"pause_task": "Pauzeren",
"resume_task": "Hervatten",
"pause_task_prompt": "Bevriest het schema van deze taak — ze wordt niet verwacht en meldt niets tot je haar hervat; hervatten start een nieuwe cyclus vanaf die dag. De andere taken van het object lopen door. Optioneel een datum waarop ze vanzelf hervat.",
"task_paused": "Taak gepauzeerd",
"task_resumed": "Taak hervat — het schema begint opnieuw",
"pause_until_prompt": "Bevries de schema's van dit object — niets wordt verwacht en niets meldt tot het wordt hervat. Stel optioneel een datum voor automatisch hervatten in.",
"pause_until_label": "Hervatten op (optioneel)",
"object_paused": "Object gepauzeerd",
@@ -913,7 +934,18 @@
"replace_object": "Vervangen…",
"replace_object_prompt": "Dit object uitfaseren en een opvolger aanmaken. Geschiedenis en kosten blijven gearchiveerd bij het oude; taken en documenten gaan mee naar het nieuwe, tellers beginnen opnieuw.",
"replace_name_label": "Naam van de opvolger",
"replace_device_heading": "Apparaat in Home Assistant",
"replace_device_keep": "Hetzelfde apparaat als voorheen ({device})",
"replace_device_keep_hint": "Kies dit ook als het nieuwe exemplaar nog niet in Home Assistant zit: kies het daarna in de objectinstellingen als apparaat — de sensortriggers verhuizen dan mee.",
"replace_device_other": "Het nieuwe exemplaar is een ander apparaat",
"replace_device_pick": "Apparaat van het nieuwe exemplaar",
"replace_device_other_hint": "Sensortriggers, afrondacties en de tellerreset verhuizen naar de overeenkomende entiteiten van dat apparaat.",
"replace_device_none": "Geen apparaat",
"object_replaced": "Object vervangen — opvolger aangemaakt",
"device_swap_moved": "{count} sensorkoppelingen verhuisd naar het nieuwe apparaat.",
"device_swap_moved_one": "Eén sensorkoppeling verhuisd naar het nieuwe apparaat.",
"device_swap_unmatched": "{count} konden niet worden gekoppeld — controleer de sensortriggers van die taken.",
"device_swap_unmatched_one": "Eén kon niet worden gekoppeld — controleer de sensortrigger van die taak.",
"reading_unit_label": "Meeteenheid (bijv. kWh, m³)",
"reading_unit_help": "Wordt naast de vastgelegde waarde getoond bij het afronden van deze taak.",
"reading_value_label": "Meterstand",
@@ -1010,6 +1042,9 @@
"part_stock": "Voorraad",
"part_reorder_threshold": "Bestelgrens",
"part_restock_quantity": "Aanvulhoeveelheid",
"part_package_size": "Verpakkingsgrootte (eenheden)",
"part_restock_packages": "Bijvulhoeveelheid (verpakkingen)",
"part_package_hint": "Gekocht per verpakking, gebruikt per eenheid: voorraad, verbruik per taak en bestelgrens tellen eenheden; bijvulhoeveelheid en prijs gelden per verpakking.",
"part_auto_buy": "Kooptaak bij lage voorraad",
"part_restock": "Voorraad aanpassen",
"parts_used_by": "Gebruikt door",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Kon de onderdelen van dit object niet laden — de verbruiksopties zijn nu niet beschikbaar.",
"settings_export_selection": "Beperken tot geselecteerde objecten (optioneel)",
"settings_docs_archive": "Documentenarchief (met bestanden)",
"settings_docs_archive_hint": "De JSON/YAML/CSV-exports bevatten alleen instellingen. Deze ZIP bevat de geüploade bestandsinhoud voor een volledig herstel.",
"settings_docs_archive_hint": "De JSON- en YAML-exports noemen je documenten, maar bevatten hun bestanden niet; CSV laat documenten weg. Deze ZIP bevat de geüploade bestanden — zet hem terug na het importeren van de JSON en er ontbreekt niets.",
"settings_docs_export_btn": "Documenten-ZIP downloaden",
"settings_docs_import_btn": "Documenten-ZIP herstellen",
"settings_docs_import_success": "Hersteld: {blobs} bestanden, {docs} documenten",
"settings_docs_import_missing": "{n} overgeslagen — hun bestanden zaten niet in de ZIP",
"adopt_problem_button": "Probleemsensoren overnemen",
"adopt_problem_title": "Probleemsensoren overnemen",
"adopt_problem_hint": "Zet HA-probleemsensoren (printerfouten, filterwaarschuwingen, lege batterij) om in onderhoudstaken die triggeren zolang het probleem actief is en zichzelf afronden wanneer het is opgelost.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Telt het gebruik sinds de laatste afronding: vervalt na +{target}; de teller herstart na elke afronding.",
"trigger_hint_counter_abs": "De taak vervalt zodra de sensor {target} bereikt.",
"trigger_hint_runtime": "De taak vervalt na {hours} u opgetelde draaitijd; de teller herstart na elke afronding.",
"trigger_hint_due_date": "De taak wordt {days} dagen vóór de datum die de sensor meldt verwacht.",
"trigger_hint_due_date_one": "De taak wordt één dag vóór de datum die de sensor meldt verwacht.",
"trigger_hint_due_date_same_day": "De taak wordt verwacht op de datum die de sensor meldt.",
"trigger_hint_due_date_now": "Gemelde datum: {date}.",
"trigger_hint_state_change": "De taak vervalt na {count} statuswijziging(en).",
"trigger_hint_state_change_to": "De taak vervalt na {count} wijziging(en) naar “{state}”.",
"trigger_hint_state_now": "Huidige status: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Alleen taken die overeenkomen met de label-/gebruikersfilters van de gekozen weergave sturen herinneringen. Status, sortering en groepering van de weergave worden hier genegeerd.",
"settings_notify_completed": "Voltooiingsmeldingen",
"settings_notify_completed_hint": "Huishoudnieuws wanneer een taak is voltooid, met de reden en wie het deed. \"Alleen automatisch\" meldt alleen voltooiingen die niemand ter plekke deed: sensorherstel, afvinken op de boodschappenlijst, een service-aanroep van een automatisering. Alleen naar de huishouddienst; stille uren en het daglimiet gelden.",
"settings_title_style": "Stijl van meldingstitel",
"settings_title_style_hint": "Wat verschijnt als TITEL van de melding. Standaard behoudt de status-tekst (bijv. 'Onderhoud achterstallig!'). Objectnaam / taaknaam helpen wanneer de telefoon meldingen stapelt.",
"title_style_default": "Standaard (titel per status)",
"title_style_object_name": "Objectnaam als titel",
"title_style_task_name": "Taaknaam als titel",
"notify_completed_off": "Uit",
"notify_completed_automatic": "Alleen automatische voltooiingen",
"notify_completed_all": "Alle voltooiingen",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON of YAML, Jinja toegestaan; wordt samengevoegd met de data van de notify-aanroep (jouw sleutels winnen). Variabelen: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test met de knop Test verzenden.",
"card_saved_view": "Opgeslagen weergave",
"card_saved_view_none": "Geen",
"card_saved_view_help": "Past de status-, gebruikers- en labelfilters van de weergave toe bovenop de bovenstaande filters. Sortering en groepering van de weergave zijn paneelweergave-instellingen en gelden niet voor de kaart.",
"card_saved_view_help": "Past de status-, gebruikers-, label- en prioriteitsfilters van de weergave toe bovenop de bovenstaande filters. Sortering en groepering van de weergave zijn paneelweergave-instellingen en gelden niet voor de kaart.",
"panel_card_tab": "Openen op tabblad",
"panel_card_tab_default": "Onthouden tabblad (zoals in het paneel)",
"panel_card_view": "Openen met opgeslagen weergave",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Terug naar pictogrammen",
"battery_fleet_forecast_overdue": "Voorspelde datum verstreken — de batterij meldt zich nog steeds gezond. Heb je hem vervangen, registreer dan de vervanging; anders zat de voorspelling ernaast.",
"cost_from_parts": "≈ {amount} uit onderdelen overnemen",
"cost_parts_booked": "Gebruikte onderdelen ≈ {amount} worden automatisch geboekt — vul alleen extra kosten in.",
"cost_parts_info": "Gebruikte onderdelen ≈ {amount} — geteld bij aankoop, niet hier.",
"cost_purchase_use_hint": "De betaalde prijs wordt de prijs van het onderdeel — onderdelen tellen bij gebruik.",
"history_parts_counted": "Onderdelen: {amount}",
"history_parts_counted_hint": "Waarde van de gebruikte onderdelen — als uitgave geteld",
"history_parts_info": "Onderdelen ≈ {amount}",
"history_parts_info_hint": "Waarde van de gebruikte onderdelen — alleen ter informatie; geteld bij aankoop",
"history_purchase_stock": "Gekocht voor {amount} (telt bij gebruik)",
"dismiss": "Verbergen",
"gs_label": "Aan de slag — deze tips verdwijnen naarmate je installatie groeit",
"gs_setups_chip": "Voorgestelde setups: {n} apparaten met vooraf ingestelde triggers gevonden",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Twee weken (14 dagen)",
"cal_editor_window_month": "Maand (30 dagen, standaard)",
"cal_editor_window_year": "Jaar (365 dagen, lege dagen verborgen)",
"cal_editor_window_past_30": "Afgelopen 30 dagen (geschiedenis)",
"cal_editor_window_past_90": "Afgelopen 90 dagen (geschiedenis)",
"cal_editor_show_chips": "Vensterchips in de kaart tonen",
"cal_editor_chips_hint": "Verberg de chips wanneer de kaart in een strategieweergave staat die al als vensterkeuze dient.",
"cal_editor_show_user_filter": "Gebruikersfilter tonen",
@@ -36,6 +36,8 @@
"reset": "Resetuj",
"snooze": "Drzemka",
"snoozed": "Odłożone",
"snoozed_for": "Przypomnienia wyciszone na {hours} godz. — do {until}",
"snoozed_for_one": "Przypomnienia wyciszone na godzinę — do {until}",
"cancel": "Anuluj",
"bulk_select": "Zaznacz",
"bulk_select_all": "Zaznacz wszystko",
@@ -61,7 +63,10 @@
"counter": "Licznik",
"state_change": "Zmiana stanu",
"runtime": "Czas pracy",
"trigger_type_due_date": "Data z czujnika",
"runtime_hours": "Docelowy czas pracy (godziny)",
"days_before": "Dni przed datą",
"days_before_help": "Dla czujnika, który podaje termin konserwacji (data lub znacznik czasu). 0 = w sam dzień. Własny przycisk „zrobione” urządzenia przesuwa datę i tym samym wyłącza wyzwalacz — ustaw go jako akcję przy wykonaniu.",
"target_value": "Wartość docelowa",
"target_changes": "Docelowa liczba zmian",
"for_minutes": "Przez (minuty)",
@@ -233,6 +238,8 @@
"model_optional": "Model (opcjonalne)",
"serial_number_optional": "Numer seryjny (opcjonalne)",
"serial_number_label": "S/N",
"object_replaces": "Poprzednik",
"object_replaced_by": "Następca",
"documentation_url_label": "Instrukcja",
"object_notes_label": "Notatki",
"sort_due_date": "Termin",
@@ -420,7 +427,7 @@
"qr_action_view": "Wyświetl informacje o konserwacji",
"qr_action_complete": "Oznacz konserwację jako wykonaną",
"qr_action_quick_complete": "Szybkie zakończenie — bez okna dialogowego",
"qr_print_quick_none": "Kody szybkiego zakończenia wymagają wartości domyślnych szybkiego zakończenia w zadaniu (okno zadania → Wartości domyślne szybkiego zakończenia) — żadne zadanie wybranych obiektów ich jeszcze nie ma.",
"qr_print_quick_none": "Kody szybkiego zakończenia wymagają wartości domyślnych szybkiego zakończenia w zadaniu (Ustawienia → Funkcje zaawansowane → Akcje po zakończeniu; okno zadania → Wartości domyślne szybkiego zakończenia) — żadne zadanie wybranych obiektów ich jeszcze nie ma.",
"qr_url_mode": "Typ linku",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Lokalny (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Włącz lub wyłącz funkcje zaawansowane. Wyłączenie ukrywa je z UI, ale nie usuwa danych.",
"feat_adaptive": "Harmonogram adaptacyjny",
"feat_adaptive_desc": "Ucz się optymalnych interwałów z historii konserwacji",
"feat_predictions": "Predykcje czujników",
"feat_predictions_desc": "Przewiduj daty wyzwolenia z degradacji czujnika",
"feat_seasonal": "Korekty sezonowe",
"feat_seasonal_desc": "Dostosuj interwały do wzorców sezonowych",
"feat_environmental": "Korelacja środowiskowa",
@@ -669,6 +674,10 @@
"settings_currency": "Waluta",
"settings_currency_decimals": "Miejsca po przecinku w kwotach",
"settings_currency_decimals_hint": "Ile miejsc po przecinku pokazuje każda kwota: KPI, budżety, koszty na listach i w raportach, alerty budżetowe. 0 = liczby całkowite.",
"settings_parts_cost_mode": "Części liczą się jako wydatek",
"settings_parts_cost_purchase": "Przy zakupie",
"settings_parts_cost_use": "Przy zużyciu",
"settings_parts_cost_mode_hint": "Przy zakupie: liczy się koszt przypomnienia o zakupie, a części zużyte w pracy są pokazywane informacyjnie. Przy zużyciu: każde wykonanie księguje wartość zużytych części, a zakup ustawia cenę części zamiast się liczyć. Dotyczy wykonań od teraz — historia zachowuje sposób zaksięgowania.",
"settings_budget_monthly": "Budżet miesięczny",
"settings_budget_yearly": "Budżet roczny",
"settings_budget_alerts": "Alerty budżetowe",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Wklej tutaj zawartość JSON lub CSV…",
"settings_import_btn": "Importuj",
"settings_import_success": "{count} obiektów zaimportowanych pomyślnie.",
"settings_import_unmatched_users": "Nie znaleziono w tej instancji: {names}. Ich przypisania zadań zostały usunięte — utwórz w Home Assistant osoby o tych samych nazwach i zaimportuj ponownie, aby je zachować.",
"settings_export_success": "Eksport pobrany.",
"settings_saved": "Ustawienie zapisane.",
"settings_include_history": "Dołącz historię",
@@ -746,7 +756,7 @@
"has_overdue": "Ma zaległe zadania",
"object": "Obiekt",
"settings_panel_access": "Dostęp do panelu",
"settings_panel_access_desc": "Administratorzy zawsze mają pełny dostęp. Aby delegować tworzenie, edytowanie i usuwanie wybranym użytkownikom bez uprawnień administratora, włącz tę opcję i zaznacz ich poniżej — pozostali widzą tylko Wykonaj i Pomiń.",
"settings_panel_access_desc": "Administratorzy zawsze mają pełny dostęp. Aby delegować tworzenie, edytowanie i usuwanie wybranym użytkownikom bez uprawnień administratora, włącz tę opcję i zaznacz ich poniżej — pozostali mają tylko Wykonaj, Pomiń, Resetuj, Odłóż i Drzemka.",
"settings_operator_write": "Zezwól wybranym użytkownikom na tworzenie, edycję i usuwanie",
"settings_operator_write_desc": "Wyłączone: tylko administratorzy mogą zmieniać treść. Włączone: wybrani użytkownicy poniżej również mają pełny dostęp.",
"no_non_admin_users": "Nie znaleziono użytkowników nie-admin. Dodaj ich w Ustawienia → Osoby.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Testuj akcję",
"on_complete_action_test_success": "Sukces",
"on_complete_action_test_failed": "Błąd",
"action_test_bad_format": "Nieprawidłowy format usługi (oczekiwano 'domain.service')",
"action_test_unknown_service": "Usługa „{service}” nie jest zarejestrowana w Home Assistant. Sprawdź pisownię i czy integracja, która ją udostępnia, jest załadowana.",
"action_test_domain_mismatch": "Usługa „{service}” działa tylko z encjami {domain}.*; encja „{entity}” należy do {entity_domain}.* — wybierz usługę pasującą do domeny encji (np. {suggestion})",
"action_test_entity_missing": "Nie znaleziono encji docelowej „{entity}” w Home Assistant — mogła zostać przemianowana lub jej integracja usunięta.",
"quick_complete_defaults_title": "Wartości domyślne szybkiego zakończenia (dla skanów QR, opcjonalnie)",
"quick_complete_defaults_desc": "Predefiniowane wartości dla QR szybkiego zakończenia. Bez nich QR otwiera okno dialogowe.",
"quick_complete_defaults_notes": "Notatki",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Zarządzaj przypisaniami zadań…",
"groups_open_panel": "Otwórz w panelu",
"on_complete_action_target_hint": "Uwaga: domena encji musi pasować do usługi — np. 'button.press' działa tylko na button.*, 'counter.increment' tylko na counter.*, 'input_button.press' tylko na input_button.* itd. W przypadku niezgodności akcja po cichu się nie wykona (HA zapisze w dzienniku 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Ta akcja ma więcej celów, niż pokazuje to pole: {targets}. Pozostają bez zmian — wybranie tu innej encji zastąpi cały cel.",
"on_complete_action_skip_auto": "Nie, gdy zadanie wykona się samo (jego czujnik wrócił do normy)",
"show_all_objects": "Pokaż wszystkie obiekty",
"show_all_tasks": "Wyczyść filtr — pokaż wszystkie zadania",
@@ -844,6 +859,7 @@
"doc_too_large": "Plik jest za duży (maks. 25 MB).",
"doc_upload_failed": "Przesyłanie nie powiodło się.",
"docs_archive_too_large": "Archiwum jest za duże (maks. {max}).",
"docs_archive_no_manifest": "Ten ZIP nie zawiera pliku manifest.json, więc przywracanie nie wie, do którego dokumentu należy plik. Przy ponownym pakowaniu zostaw manifest.json obok folderów.",
"completion_photos_optional": "Zdjęcia z wykonania (opcjonalnie)",
"completion_photos": "Zdjęcia z wykonania",
"choose_photos": "Wybierz zdjęcia",
@@ -904,6 +920,11 @@
"paused": "Wstrzymane",
"pause_object": "Wstrzymaj",
"resume_object": "Wznów",
"pause_task": "Wstrzymaj",
"resume_task": "Wznów",
"pause_task_prompt": "Zamraża harmonogram tego zadania — nie stanie się wymagalne i nie będzie powiadamiać, dopóki go nie wznowisz; wznowienie zaczyna nowy cykl od tego dnia. Pozostałe zadania obiektu działają dalej. Opcjonalnie data samoczynnego wznowienia.",
"task_paused": "Zadanie wstrzymane",
"task_resumed": "Zadanie wznowione — harmonogram zaczyna się od nowa",
"pause_until_prompt": "Zamroź harmonogramy tego obiektu — nic nie staje się wymagane i nic nie powiadamia do czasu wznowienia. Opcjonalnie ustaw datę automatycznego wznowienia.",
"pause_until_label": "Wznów dnia (opcjonalnie)",
"object_paused": "Obiekt wstrzymany",
@@ -913,7 +934,18 @@
"replace_object": "Zastąp…",
"replace_object_prompt": "Wycofaj ten obiekt i utwórz następcę. Historia i koszty pozostają zarchiwizowane przy starym; zadania i dokumenty przechodzą do nowego, liczniki startują od zera.",
"replace_name_label": "Nazwa następcy",
"replace_device_heading": "Urządzenie w Home Assistant",
"replace_device_keep": "To samo urządzenie co wcześniej ({device})",
"replace_device_keep_hint": "To także wybór, gdy nowego egzemplarza nie ma jeszcze w Home Assistant: gdy się pojawi, wybierz go jako urządzenie w ustawieniach obiektu — wyzwalacze czujników przejdą na niego.",
"replace_device_other": "Nowy egzemplarz to inne urządzenie",
"replace_device_pick": "Urządzenie nowego egzemplarza",
"replace_device_other_hint": "Wyzwalacze czujników, akcje po wykonaniu i reset licznika przejdą na odpowiednie encje tego urządzenia.",
"replace_device_none": "Bez urządzenia",
"object_replaced": "Obiekt zastąpiony — utworzono następcę",
"device_swap_moved": "Przeniesiono powiązania z czujnikami na nowe urządzenie: {count}.",
"device_swap_moved_one": "Jedno powiązanie z czujnikiem przeniesiono na nowe urządzenie.",
"device_swap_unmatched": "Nie udało się dopasować: {count} — sprawdź wyzwalacze czujników tych zadań.",
"device_swap_unmatched_one": "Jednego nie udało się dopasować — sprawdź wyzwalacz czujnika tego zadania.",
"reading_unit_label": "Jednostka odczytu (np. kWh, m³)",
"reading_unit_help": "Wyświetlana obok zarejestrowanej wartości przy ukończeniu tego zadania.",
"reading_value_label": "Wartość odczytu",
@@ -1010,6 +1042,9 @@
"part_stock": "Stan",
"part_reorder_threshold": "Próg zamówienia",
"part_restock_quantity": "Ilość uzupełnienia",
"part_package_size": "Wielkość opakowania (jednostki)",
"part_restock_packages": "Ilość uzupełnienia (opakowania)",
"part_package_hint": "Kupowane w opakowaniach, zużywane w jednostkach: stan, zużycie na zadanie i próg zamówienia liczą jednostki; ilość uzupełnienia i cena dotyczą opakowania.",
"part_auto_buy": "Zadanie zakupu przy niskim stanie",
"part_restock": "Koryguj stan",
"parts_used_by": "Używane przez",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Nie udało się wczytać części tego obiektu — opcje zużycia części są teraz niedostępne.",
"settings_export_selection": "Ogranicz do wybranych obiektów (opcjonalnie)",
"settings_docs_archive": "Archiwum dokumentów (z plikami)",
"settings_docs_archive_hint": "Eksporty JSON/YAML/CSV zawierają tylko ustawienia. To ZIP zawiera treść przesłanych plików, aby przywracanie było kompletne.",
"settings_docs_archive_hint": "Eksporty JSON i YAML wymieniają Twoje dokumenty, ale nie zawierają ich plików; CSV pomija dokumenty. Ten ZIP zawiera przesłane pliki — przywróć go po zaimportowaniu JSON, a niczego nie zabraknie.",
"settings_docs_export_btn": "Pobierz ZIP dokumentów",
"settings_docs_import_btn": "Przywróć ZIP dokumentów",
"settings_docs_import_success": "Przywrócono: {blobs} plików, {docs} dokumentów",
"settings_docs_import_missing": "Pominięto {n} — ich plików nie było w ZIP",
"adopt_problem_button": "Przejmij czujniki problemów",
"adopt_problem_title": "Przejmij czujniki problemów",
"adopt_problem_hint": "Zamienia czujniki problemów HA (błędy drukarki, ostrzeżenia filtra, niski poziom baterii) w zadania konserwacji, które uruchamiają się, gdy problem jest aktywny, i kończą się po jego rozwiązaniu.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Liczy zużycie od ostatniego ukończenia: termin po +{target}; licznik startuje od nowa po każdym ukończeniu.",
"trigger_hint_counter_abs": "Zadanie staje się wymagalne, gdy czujnik osiągnie {target}.",
"trigger_hint_runtime": "Zadanie staje się wymagalne po {hours} h łącznej pracy; licznik startuje od nowa po każdym ukończeniu.",
"trigger_hint_due_date": "Zadanie stanie się wymagalne {days} dni przed datą podaną przez czujnik.",
"trigger_hint_due_date_one": "Zadanie stanie się wymagalne dzień przed datą podaną przez czujnik.",
"trigger_hint_due_date_same_day": "Zadanie stanie się wymagalne w dniu podanym przez czujnik.",
"trigger_hint_due_date_now": "Podana data: {date}.",
"trigger_hint_state_change": "Zadanie staje się wymagalne po {count} zmianie/zmianach stanu.",
"trigger_hint_state_change_to": "Zadanie staje się wymagalne po {count} zmianie/zmianach na „{state}”.",
"trigger_hint_state_now": "Bieżący stan: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Przypomnienia wysyłają tylko zadania pasujące do filtrów etykiety/użytkownika wybranego widoku. Status, sortowanie i grupowanie widoku są tu ignorowane.",
"settings_notify_completed": "Powiadomienia o wykonaniu",
"settings_notify_completed_hint": "Wiadomość dla domowników, gdy zadanie zostanie wykonane, z powodem i wykonawcą. „Tylko automatyczne” zgłasza wyłącznie wykonania, których nikt nie zrobił na miejscu: powrót czujnika, odhaczenie na liście zakupów, wywołanie usługi przez automatyzację. Tylko do usługi domowej; godziny ciszy i dzienny limit obowiązują.",
"settings_title_style": "Styl tytułu powiadomienia",
"settings_title_style_hint": "Co pojawia się jako TYTUŁ powiadomienia. Domyślnie zachowywany jest tekst dla statusu (np. „Konserwacja przeterminowana!”). Nazwa obiektu / zadania pomaga, gdy telefon układa powiadomienia w stos.",
"title_style_default": "Domyślny (tytuł według statusu)",
"title_style_object_name": "Nazwa obiektu jako tytuł",
"title_style_task_name": "Nazwa zadania jako tytuł",
"notify_completed_off": "Wył.",
"notify_completed_automatic": "Tylko automatyczne wykonania",
"notify_completed_all": "Wszystkie wykonania",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON lub YAML, Jinja dozwolone; scalane z data wywołania notify (Twoje klucze wygrywają). Zmienne: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Sprawdź przyciskiem Wyślij test.",
"card_saved_view": "Zapisany widok",
"card_saved_view_none": "Brak",
"card_saved_view_help": "Stosuje filtry statusu, użytkownika i etykiety widoku oprócz powyższych filtrów. Sortowanie i grupowanie widoku to ustawienia wyświetlania panelu i nie są stosowane na karcie.",
"card_saved_view_help": "Stosuje filtry statusu, użytkownika, etykiety i priorytetu widoku oprócz powyższych filtrów. Sortowanie i grupowanie widoku to ustawienia wyświetlania panelu i nie są stosowane na karcie.",
"panel_card_tab": "Otwieraj na karcie",
"panel_card_tab_default": "Zapamiętana karta (jak w panelu)",
"panel_card_view": "Otwieraj z zapisanym widokiem",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Wróć do ikon",
"battery_fleet_forecast_overdue": "Przewidywana data minęła — bateria nadal zgłasza dobry stan. Jeśli ją wymieniono, zapisz wymianę; w przeciwnym razie prognoza była błędna.",
"cost_from_parts": "Użyj ≈ {amount} z części",
"cost_parts_booked": "Zużyte części ≈ {amount} są księgowane automatycznie — wpisz tylko dodatkowe koszty.",
"cost_parts_info": "Zużyte części ≈ {amount} — liczone przy zakupie, nie tutaj.",
"cost_purchase_use_hint": "Zapłacona cena staje się ceną części — części liczą się przy zużyciu.",
"history_parts_counted": "Części: {amount}",
"history_parts_counted_hint": "Wartość zużytych części — liczona jako wydatek",
"history_parts_info": "Części ≈ {amount}",
"history_parts_info_hint": "Wartość zużytych części — tylko informacyjnie; pieniądze liczone przy zakupie",
"history_purchase_stock": "Kupiono za {amount} (liczy się przy zużyciu)",
"dismiss": "Odrzuć",
"gs_label": "Pierwsze kroki — te wskazówki znikają wraz z rozwojem konfiguracji",
"gs_setups_chip": "Sugerowane konfiguracje: znaleziono {n} urządzeń z gotowymi wyzwalaczami",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Dwa tygodnie (14 dni)",
"cal_editor_window_month": "Miesiąc (30 dni, domyślnie)",
"cal_editor_window_year": "Rok (365 dni, puste dni ukryte)",
"cal_editor_window_past_30": "Ostatnie 30 dni (historia)",
"cal_editor_window_past_90": "Ostatnie 90 dni (historia)",
"cal_editor_show_chips": "Pokaż przełączniki okna na karcie",
"cal_editor_chips_hint": "Ukryj przełączniki, gdy karta jest osadzona w widoku strategii, który już pełni rolę wyboru okna.",
"cal_editor_show_user_filter": "Pokaż filtr użytkownika",
@@ -38,6 +38,8 @@
"reset": "Redefinir",
"snooze": "Soneca",
"snoozed": "Em soneca",
"snoozed_for": "Lembretes silenciados por {hours} horas — até {until}",
"snoozed_for_one": "Lembretes silenciados por uma hora — até {until}",
"cancel": "Cancelar",
"bulk_select": "Selecionar",
"bulk_select_all": "Selecionar tudo",
@@ -63,7 +65,10 @@
"counter": "Contador",
"state_change": "Mudança de estado",
"runtime": "Tempo de operação",
"trigger_type_due_date": "Data de um sensor",
"runtime_hours": "Tempo de operação alvo (horas)",
"days_before": "Dias antes da data",
"days_before_help": "Para um sensor que informa quando a manutenção vence (data ou carimbo de data/hora). 0 = no próprio dia. O botão de \"feito\" do dispositivo adia a data e desativa o gatilho — use-o como ação ao concluir.",
"target_value": "Valor alvo",
"target_changes": "Mudanças alvo",
"for_minutes": "Durante (minutos)",
@@ -235,6 +240,8 @@
"model_optional": "Modelo (opcional)",
"serial_number_optional": "Número de série (opcional)",
"serial_number_label": "N/S",
"object_replaces": "Substitui",
"object_replaced_by": "Substituído por",
"documentation_url_label": "Manual",
"object_notes_label": "Observações",
"sort_due_date": "Data de vencimento",
@@ -422,7 +429,7 @@
"qr_action_view": "Ver informações de manutenção",
"qr_action_complete": "Marcar a manutenção como concluída",
"qr_action_quick_complete": "Conclusão rápida — sem janela",
"qr_print_quick_none": "Os códigos de conclusão rápida precisam de padrões de conclusão rápida em uma tarefa (janela da tarefa → Padrões de conclusão rápida) — nenhuma tarefa dos objetos escolhidos os tem ainda.",
"qr_print_quick_none": "Os códigos de conclusão rápida precisam de padrões de conclusão rápida em uma tarefa (Configurações → Recursos avançados → Ações de conclusão; janela da tarefa → Padrões de conclusão rápida) — nenhuma tarefa dos objetos escolhidos os tem ainda.",
"qr_url_mode": "Tipo de link",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Local (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "Ative ou desative recursos avançados. Desativar apenas os oculta da interface, sem excluir dados.",
"feat_adaptive": "Agendamento adaptativo",
"feat_adaptive_desc": "Aprende intervalos ideais a partir do histórico de manutenção",
"feat_predictions": "Previsões de sensor",
"feat_predictions_desc": "Prevê datas de acionamento a partir da degradação do sensor",
"feat_seasonal": "Ajustes sazonais",
"feat_seasonal_desc": "Ajusta intervalos com base em padrões sazonais",
"feat_environmental": "Correlação ambiental",
@@ -671,6 +676,10 @@
"settings_currency": "Moeda",
"settings_currency_decimals": "Casas decimais dos valores",
"settings_currency_decimals_hint": "Quantas casas decimais cada valor mostra: KPIs, orçamentos, custos em listas e relatórios, alertas de orçamento. 0 = números inteiros.",
"settings_parts_cost_mode": "As peças contam como despesa",
"settings_parts_cost_purchase": "Na compra",
"settings_parts_cost_use": "No uso",
"settings_parts_cost_mode_hint": "Na compra: conta o custo de um lembrete de compra, e as peças usadas numa tarefa aparecem só como informação. No uso: cada conclusão registra o valor das peças usadas, e uma compra define o preço da peça em vez de contar. Vale a partir de agora — o histórico mantém como foi registrado.",
"settings_budget_monthly": "Orçamento mensal",
"settings_budget_yearly": "Orçamento anual",
"settings_budget_alerts": "Alertas de orçamento",
@@ -683,15 +692,17 @@
"settings_import_placeholder": "Cole o conteúdo JSON ou CSV aqui…",
"settings_import_btn": "Importar",
"settings_import_success": "{count} objetos importados com sucesso.",
"settings_import_unmatched_users": "Não encontrados nesta instância: {names}. As atribuições de tarefas foram removidas — crie pessoas com os mesmos nomes no Home Assistant e importe novamente para mantê-las.",
"settings_export_success": "Exportação baixada.",
"settings_saved": "Configuração salva.",
"settings_include_history": "Incluir histórico",
"settings_export_selection": "Limitar aos objetos selecionados (opcional)",
"settings_docs_archive": "Arquivo de documentos (com arquivos)",
"settings_docs_archive_hint": "As exportações JSON/YAML/CSV levam apenas as configurações. Este ZIP inclui o conteúdo dos arquivos enviados para que a restauração seja completa.",
"settings_docs_archive_hint": "As exportações JSON e YAML listam seus documentos, mas não os arquivos deles; o CSV deixa os documentos de fora. Este ZIP contém os arquivos enviados — restaure-o depois de importar o JSON e nada ficará faltando.",
"settings_docs_export_btn": "Baixar ZIP de documentos",
"settings_docs_import_btn": "Restaurar ZIP de documentos",
"settings_docs_import_success": "Restaurados: {blobs} arquivos, {docs} documentos",
"settings_docs_import_missing": "{n} ignorados — os arquivos deles não estavam no ZIP",
"sort_alphabetical": "Alfabética",
"sort_due_soonest": "Vencimento mais próximo",
"sort_task_count": "Número de tarefas",
@@ -777,7 +788,7 @@
"has_overdue": "Tem tarefas atrasadas",
"object": "Objeto",
"settings_panel_access": "Acesso ao painel",
"settings_panel_access_desc": "Administradores sempre têm acesso total. Para delegar criação, edição e exclusão a não administradores específicos, ative esta opção e selecione-os abaixo — os demais veem apenas Concluir e Pular.",
"settings_panel_access_desc": "Administradores sempre têm acesso total. Para delegar criação, edição e exclusão a não administradores específicos, ative esta opção e selecione-os abaixo — os demais têm apenas Concluir, Pular, Redefinir, Adiar e Soneca.",
"settings_operator_write": "Permitir que os usuários selecionados criem, editem e excluam",
"settings_operator_write_desc": "Desligado: apenas administradores podem alterar o conteúdo. Ligado: os usuários selecionados abaixo também recebem acesso total.",
"no_non_admin_users": "Nenhum usuário não administrador encontrado. Adicione em Configurações → Pessoas.",
@@ -788,11 +799,16 @@
"on_complete_action_desc": "Chama um serviço do HA quando a tarefa é concluída — ex.: zerar um contador no dispositivo.",
"on_complete_action_target": "Entidade alvo",
"on_complete_action_target_hint": "Atenção: o domínio da entidade deve corresponder ao serviço — ex.: 'button.press' só funciona em button.*, 'counter.increment' só em counter.*, 'input_button.press' só em input_button.* etc. Em caso de divergência, a ação falhará silenciosamente (o HA registra 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Esta ação tem mais alvos do que este campo mostra: {targets}. Eles são mantidos como estão — escolher aqui outra entidade substitui o alvo inteiro.",
"on_complete_action_skip_auto": "Não quando a tarefa se conclui sozinha (o sensor se recuperou)",
"on_complete_action_data": "Dados (JSON, opcional)",
"on_complete_action_test": "Validar configuração",
"on_complete_action_test_success": "✓ Configuração válida (a ação só disparará na conclusão da tarefa)",
"on_complete_action_test_failed": "Falhou",
"action_test_bad_format": "Formato de serviço inválido (esperado: 'domain.service')",
"action_test_unknown_service": "O serviço \"{service}\" não está registrado no Home Assistant. Verifique a grafia e se a integração que o fornece está carregada.",
"action_test_domain_mismatch": "O serviço \"{service}\" só funciona com entidades {domain}.*; a entidade \"{entity}\" pertence a {entity_domain}.* — escolha um serviço compatível com o domínio da entidade (ex.: {suggestion})",
"action_test_entity_missing": "A entidade de destino \"{entity}\" não foi encontrada no Home Assistant — ela pode ter sido renomeada ou sua integração removida.",
"quick_complete_defaults_title": "Padrões de conclusão rápida (para leituras de QR, opcional)",
"quick_complete_defaults_desc": "Valores predefinidos para conclusões rápidas via QR. Sem eles, o QR abre o diálogo de conclusão.",
"quick_complete_defaults_notes": "Observações",
@@ -850,6 +866,7 @@
"doc_too_large": "O arquivo é muito grande (máx. 25 MB).",
"doc_upload_failed": "Falha no envio.",
"docs_archive_too_large": "O arquivo é grande demais (máx. {max}).",
"docs_archive_no_manifest": "Este ZIP não tem manifest.json, então a restauração não sabe a qual documento cada arquivo pertence. Mantenha o manifest.json junto às pastas ao compactar de novo.",
"completion_photos_optional": "Fotos de conclusão (opcional)",
"completion_photos": "Fotos de conclusão",
"choose_photos": "Escolher fotos",
@@ -910,6 +927,11 @@
"paused": "Pausado",
"pause_object": "Pausar",
"resume_object": "Retomar",
"pause_task": "Pausar",
"resume_task": "Retomar",
"pause_task_prompt": "Congela o agendamento desta tarefa — ela não vence nem notifica até você retomá-la, e a retomada inicia um novo ciclo a partir desse dia. As outras tarefas do objeto continuam. Opcional: uma data para retomar sozinha.",
"task_paused": "Tarefa pausada",
"task_resumed": "Tarefa retomada — o agendamento recomeçou",
"pause_until_prompt": "Congele os agendamentos deste objeto — nada vence e nada notifica até que ele seja retomado. Opcionalmente, defina uma data de retomada automática.",
"pause_until_label": "Retomar em (opcional)",
"object_paused": "Objeto pausado",
@@ -919,7 +941,18 @@
"replace_object": "Substituir…",
"replace_object_prompt": "Aposente este objeto e crie um sucessor. O histórico e os custos ficam arquivados no antigo; as tarefas e os documentos passam para o novo, e os contadores recomeçam do zero.",
"replace_name_label": "Nome do sucessor",
"replace_device_heading": "Dispositivo no Home Assistant",
"replace_device_keep": "O mesmo dispositivo de antes ({device})",
"replace_device_keep_hint": "Também é a escolha se a nova unidade ainda não estiver no Home Assistant: quando estiver, escolha-a como dispositivo nas configurações do objeto — os gatilhos de sensor passam para ela.",
"replace_device_other": "A nova unidade é outro dispositivo",
"replace_device_pick": "Dispositivo da nova unidade",
"replace_device_other_hint": "Gatilhos de sensor, ações de conclusão e o reset do contador passam para as entidades correspondentes desse dispositivo.",
"replace_device_none": "Nenhum dispositivo",
"object_replaced": "Objeto substituído — sucessor criado",
"device_swap_moved": "{count} vínculos de sensores passaram para o novo dispositivo.",
"device_swap_moved_one": "Um vínculo de sensor passou para o novo dispositivo.",
"device_swap_unmatched": "{count} não puderam ser associados — verifique os gatilhos de sensor dessas tarefas.",
"device_swap_unmatched_one": "Um não pôde ser associado — verifique o gatilho de sensor dessa tarefa.",
"reading_unit_label": "Unidade da leitura (ex.: kWh, m³)",
"reading_unit_help": "Exibida ao lado do valor registrado ao concluir esta tarefa.",
"reading_value_label": "Valor da leitura",
@@ -1016,6 +1049,9 @@
"part_stock": "Estoque",
"part_reorder_threshold": "Repor em",
"part_restock_quantity": "Quantidade de reposição",
"part_package_size": "Tamanho da embalagem (unidades)",
"part_restock_packages": "Quantidade de reposição (embalagens)",
"part_package_hint": "Comprado em embalagens, usado por unidade: estoque, consumo por tarefa e limite de reposição contam unidades; quantidade de reposição e preço são por embalagem.",
"part_auto_buy": "Criar tarefa de compra automaticamente quando estiver baixo",
"part_restock": "Ajustar estoque",
"parts_used_by": "Usado por",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Conta o uso desde a última conclusão: vence após +{target}; a contagem recomeça após cada conclusão.",
"trigger_hint_counter_abs": "A tarefa vence quando o sensor atinge {target}.",
"trigger_hint_runtime": "A tarefa vence após {hours} h de funcionamento acumulado; o contador recomeça após cada conclusão.",
"trigger_hint_due_date": "A tarefa vence {days} dias antes da data que o sensor informa.",
"trigger_hint_due_date_one": "A tarefa vence um dia antes da data que o sensor informa.",
"trigger_hint_due_date_same_day": "A tarefa vence na data que o sensor informa.",
"trigger_hint_due_date_now": "Data informada: {date}.",
"trigger_hint_state_change": "A tarefa vence após {count} mudança(s) de estado.",
"trigger_hint_state_change_to": "A tarefa vence após {count} mudança(s) para “{state}”.",
"trigger_hint_state_now": "Estado atual: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Apenas tarefas que correspondem aos filtros de etiqueta/usuário da visualização salva selecionada enviam lembretes. Status, ordenação e agrupamento da visualização são ignorados aqui.",
"settings_notify_completed": "Notificações de conclusão",
"settings_notify_completed_hint": "Aviso para a casa quando uma tarefa é concluída, com o motivo e quem fez. \"Só automáticas\" anuncia apenas as conclusões que ninguém fez na hora: recuperação do sensor, item marcado na lista de compras, chamada de serviço de uma automação. Só para o serviço da casa; horário de silêncio e limite diário se aplicam.",
"settings_title_style": "Estilo do título da notificação",
"settings_title_style_hint": "O que aparece como TÍTULO da notificação. O padrão mantém o texto por status (ex.: 'Manutenção atrasada!'). Nome do objeto / Nome da tarefa ajudam quando as notificações do celular se empilham.",
"title_style_default": "Padrão (título por status)",
"title_style_object_name": "Nome do objeto como título",
"title_style_task_name": "Nome da tarefa como título",
"notify_completed_off": "Desligado",
"notify_completed_automatic": "Só conclusões automáticas",
"notify_completed_all": "Todas as conclusões",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; mesclado nos data da chamada notify (suas chaves vencem). Variáveis: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Teste com o botão Enviar teste.",
"card_saved_view": "Visualização salva",
"card_saved_view_none": "Nenhuma",
"card_saved_view_help": "Aplica os filtros de status, usuário e etiqueta da visualização por cima dos filtros acima. A ordenação e o agrupamento da visualização são configurações de exibição do painel e não se aplicam ao cartão.",
"card_saved_view_help": "Aplica os filtros de status, usuário, etiqueta e prioridade da visualização por cima dos filtros acima. A ordenação e o agrupamento da visualização são configurações de exibição do painel e não se aplicam ao cartão.",
"panel_card_tab": "Abrir na aba",
"panel_card_tab_default": "Aba lembrada (como no painel)",
"panel_card_view": "Abrir com a visualização salva",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Voltar aos ícones",
"battery_fleet_forecast_overdue": "Data prevista ultrapassada — a bateria ainda reporta bom estado. Se você a trocou, registre a troca; caso contrário, a previsão errou.",
"cost_from_parts": "Usar ≈ {amount} das peças",
"cost_parts_booked": "As peças usadas ≈ {amount} são registradas automaticamente — informe só os custos adicionais.",
"cost_parts_info": "Peças usadas ≈ {amount} — contadas na compra, não aqui.",
"cost_purchase_use_hint": "O preço pago vira o preço da peça — as peças contam no uso.",
"history_parts_counted": "Peças: {amount}",
"history_parts_counted_hint": "Valor das peças usadas — contado como despesa",
"history_parts_info": "Peças ≈ {amount}",
"history_parts_info_hint": "Valor das peças usadas — só informativo; o dinheiro contou na compra",
"history_purchase_stock": "Comprado por {amount} (conta no uso)",
"dismiss": "Dispensar",
"gs_label": "Primeiros passos — estas dicas somem conforme a configuração cresce",
"gs_setups_chip": "Configurações sugeridas: {n} dispositivos com gatilhos pré-configurados",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Quinzena (14 dias)",
"cal_editor_window_month": "Mês (30 dias, padrão)",
"cal_editor_window_year": "Ano (365 dias, dias vazios ocultos)",
"cal_editor_window_past_30": "Últimos 30 dias (histórico)",
"cal_editor_window_past_90": "Últimos 90 dias (histórico)",
"cal_editor_show_chips": "Mostrar chips de janela no cartão",
"cal_editor_chips_hint": "Oculte os chips quando o cartão estiver em uma visão de estratégia que já serve como seletor de janela.",
"cal_editor_show_user_filter": "Mostrar filtro de usuário",
@@ -36,6 +36,8 @@
"reset": "Repor",
"snooze": "Adiar",
"snoozed": "Adiada",
"snoozed_for": "Lembretes silenciados durante {hours} horas — até {until}",
"snoozed_for_one": "Lembretes silenciados durante uma hora — até {until}",
"cancel": "Cancelar",
"bulk_select": "Selecionar",
"bulk_select_all": "Selecionar tudo",
@@ -61,7 +63,10 @@
"counter": "Contador",
"state_change": "Mudança de estado",
"runtime": "Tempo de funcionamento",
"trigger_type_due_date": "Data de um sensor",
"runtime_hours": "Duração alvo (horas)",
"days_before": "Dias antes da data",
"days_before_help": "Para um sensor que indica quando a manutenção é devida (data ou carimbo temporal). 0 = no próprio dia. O botão de «feito» do dispositivo adia a data e desativa assim o acionador — defina-o como ação ao concluir.",
"target_value": "Valor alvo",
"target_changes": "Alterações alvo",
"for_minutes": "Durante (minutos)",
@@ -233,6 +238,8 @@
"model_optional": "Modelo (opcional)",
"serial_number_optional": "Número de série (opcional)",
"serial_number_label": "N/S",
"object_replaces": "Substitui",
"object_replaced_by": "Substituído por",
"documentation_url_label": "Manual",
"object_notes_label": "Notas",
"sort_due_date": "Vencimento",
@@ -420,7 +427,7 @@
"qr_action_view": "Ver informações de manutenção",
"qr_action_complete": "Marcar manutenção como concluída",
"qr_action_quick_complete": "Conclusão rápida — sem janela",
"qr_print_quick_none": "Os códigos de conclusão rápida precisam de valores predefinidos de conclusão rápida numa tarefa (janela da tarefa → Valores predefinidos de conclusão rápida) — nenhuma tarefa dos objetos escolhidos os tem ainda.",
"qr_print_quick_none": "Os códigos de conclusão rápida precisam de valores predefinidos de conclusão rápida numa tarefa (Definições → Funções avançadas → Ações de conclusão; janela da tarefa → Valores predefinidos de conclusão rápida) — nenhuma tarefa dos objetos escolhidos os tem ainda.",
"qr_url_mode": "Tipo de ligação",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Local (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Ative ou desative funções avançadas. Desativar oculta-as da interface mas não elimina dados.",
"feat_adaptive": "Agendamento adaptativo",
"feat_adaptive_desc": "Aprender intervalos ideais a partir do histórico de manutenção",
"feat_predictions": "Previsões do sensor",
"feat_predictions_desc": "Prever datas de acionamento pela degradação do sensor",
"feat_seasonal": "Ajustes sazonais",
"feat_seasonal_desc": "Ajustar intervalos com base em padrões sazonais",
"feat_environmental": "Correlação ambiental",
@@ -669,6 +674,10 @@
"settings_currency": "Moeda",
"settings_currency_decimals": "Casas decimais dos montantes",
"settings_currency_decimals_hint": "Quantas casas decimais cada montante mostra: KPIs, orçamentos, custos em listas e relatórios, alertas de orçamento. 0 = números inteiros.",
"settings_parts_cost_mode": "As peças contam como despesa",
"settings_parts_cost_purchase": "Na compra",
"settings_parts_cost_use": "No uso",
"settings_parts_cost_mode_hint": "Na compra: conta o custo de um lembrete de compra e as peças usadas numa tarefa são mostradas como informação. No uso: cada conclusão regista o valor das peças usadas e uma compra define o preço da peça em vez de contar. Aplica-se a partir de agora — o histórico mantém como foi registado.",
"settings_budget_monthly": "Orçamento mensal",
"settings_budget_yearly": "Orçamento anual",
"settings_budget_alerts": "Alertas de orçamento",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Cole o conteúdo JSON ou CSV aqui…",
"settings_import_btn": "Importar",
"settings_import_success": "{count} objetos importados com sucesso.",
"settings_import_unmatched_users": "Não encontrados nesta instância: {names}. As atribuições de tarefas foram removidas — crie pessoas com os mesmos nomes no Home Assistant e importe novamente para as manter.",
"settings_export_success": "Exportação transferida.",
"settings_saved": "Definição guardada.",
"settings_include_history": "Incluir histórico",
@@ -746,7 +756,7 @@
"has_overdue": "Tarefas em atraso",
"object": "Objeto",
"settings_panel_access": "Acesso ao painel",
"settings_panel_access_desc": "Administradores sempre têm acesso completo. Para delegar criar, editar e excluir a não administradores específicos, ative isto e selecione-os abaixo — os demais só veem Concluir e Ignorar.",
"settings_panel_access_desc": "Administradores sempre têm acesso completo. Para delegar criar, editar e excluir a não administradores específicos, ative isto e selecione-os abaixo — os demais só têm Concluída, Saltar, Repor e Adiar.",
"settings_operator_write": "Permitir que os usuários selecionados criem, editem e excluam",
"settings_operator_write_desc": "Desativado: apenas administradores podem alterar o conteúdo. Ativado: os usuários selecionados abaixo também têm acesso completo.",
"no_non_admin_users": "Nenhum usuário não administrador encontrado. Adicione em Configurações → Pessoas.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Testar ação",
"on_complete_action_test_success": "Sucesso",
"on_complete_action_test_failed": "Falhou",
"action_test_bad_format": "Formato de serviço inválido (esperado: 'domain.service')",
"action_test_unknown_service": "O serviço \"{service}\" não está registado no Home Assistant. Verifique a ortografia e se a integração que o fornece está carregada.",
"action_test_domain_mismatch": "O serviço \"{service}\" só funciona com entidades {domain}.*; a entidade \"{entity}\" pertence a {entity_domain}.* — escolha um serviço adequado ao domínio da entidade (por ex. {suggestion})",
"action_test_entity_missing": "A entidade de destino \"{entity}\" não foi encontrada no Home Assistant — pode ter sido renomeada ou a sua integração removida.",
"quick_complete_defaults_title": "Valores predefinidos de conclusão rápida (para scans QR, opcional)",
"quick_complete_defaults_desc": "Valores predefinidos para QR de conclusão rápida. Sem eles, o QR abre o diálogo.",
"quick_complete_defaults_notes": "Notas",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Gerir atribuições de tarefas…",
"groups_open_panel": "Abrir no painel",
"on_complete_action_target_hint": "Nota: o domínio da entidade tem de corresponder ao serviço — p. ex. 'button.press' só funciona em button.*, 'counter.increment' só em counter.*, 'input_button.press' só em input_button.* etc. Em caso de incompatibilidade, a ação falha silenciosamente (o HA regista 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Esta ação tem mais alvos do que este campo mostra: {targets}. Mantêm-se tal como estão — escolher aqui outra entidade substitui o alvo inteiro.",
"on_complete_action_skip_auto": "Não quando a tarefa se conclui sozinha (o sensor recuperou)",
"show_all_objects": "Mostrar todos os objetos",
"show_all_tasks": "Limpar filtro — mostrar todas as tarefas",
@@ -844,6 +859,7 @@
"doc_too_large": "Ficheiro demasiado grande (máx. 25 MB).",
"doc_upload_failed": "Falha no carregamento.",
"docs_archive_too_large": "O arquivo é demasiado grande (máx. {max}).",
"docs_archive_no_manifest": "Este ZIP não contém manifest.json, pelo que o restauro não sabe a que documento pertence cada ficheiro. Mantenha o manifest.json junto às pastas ao comprimir novamente.",
"completion_photos_optional": "Fotos de conclusão (opcional)",
"completion_photos": "Fotos de conclusão",
"choose_photos": "Escolher fotos",
@@ -904,6 +920,11 @@
"paused": "Pausado",
"pause_object": "Pausar",
"resume_object": "Retomar",
"pause_task": "Pausar",
"resume_task": "Retomar",
"pause_task_prompt": "Congela o agendamento desta tarefa — não fica pendente nem notifica até a retomares, e a retoma inicia um novo ciclo a partir desse dia. As outras tarefas do objeto continuam. Opcional: uma data para retomar sozinha.",
"task_paused": "Tarefa pausada",
"task_resumed": "Tarefa retomada — o agendamento recomeçou",
"pause_until_prompt": "Congela os agendamentos deste objeto — nada vence nem notifica até ser retomado. Opcionalmente, defina uma data de retoma automática.",
"pause_until_label": "Retomar em (opcional)",
"object_paused": "Objeto pausado",
@@ -913,7 +934,18 @@
"replace_object": "Substituir…",
"replace_object_prompt": "Retire este objeto e crie um sucessor. O histórico e os custos ficam arquivados no antigo; as tarefas e os documentos passam para o novo, os contadores começam do zero.",
"replace_name_label": "Nome do sucessor",
"replace_device_heading": "Dispositivo no Home Assistant",
"replace_device_keep": "O mesmo dispositivo de antes ({device})",
"replace_device_keep_hint": "É também a escolha se a nova unidade ainda não estiver no Home Assistant: quando estiver, escolhe-a como dispositivo nas definições do objeto — os acionadores de sensores passam para ela.",
"replace_device_other": "A nova unidade é outro dispositivo",
"replace_device_pick": "Dispositivo da nova unidade",
"replace_device_other_hint": "Os acionadores de sensores, as ações de conclusão e a reposição do contador passam para as entidades correspondentes desse dispositivo.",
"replace_device_none": "Nenhum dispositivo",
"object_replaced": "Objeto substituído — sucessor criado",
"device_swap_moved": "{count} ligações a sensores passaram para o novo dispositivo.",
"device_swap_moved_one": "Uma ligação a sensor passou para o novo dispositivo.",
"device_swap_unmatched": "{count} não foi possível associar — verifica os acionadores de sensores dessas tarefas.",
"device_swap_unmatched_one": "Um não foi possível associar — verifica o acionador de sensor dessa tarefa.",
"reading_unit_label": "Unidade de leitura (p. ex. kWh, m³)",
"reading_unit_help": "Mostrada ao lado do valor registado ao concluir esta tarefa.",
"reading_value_label": "Valor de leitura",
@@ -1010,6 +1042,9 @@
"part_stock": "Estoque",
"part_reorder_threshold": "Limite de reposição",
"part_restock_quantity": "Quantidade de reposição",
"part_package_size": "Tamanho da embalagem (unidades)",
"part_restock_packages": "Quantidade de reposição (embalagens)",
"part_package_hint": "Comprado em embalagens, usado à unidade: o stock, o consumo por tarefa e o limite de encomenda contam unidades; a quantidade de reposição e o preço são por embalagem.",
"part_auto_buy": "Tarefa de compra se baixo",
"part_restock": "Ajustar estoque",
"parts_used_by": "Usado por",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Não foi possível carregar as peças deste objeto — as opções de consumo de peças estão indisponíveis.",
"settings_export_selection": "Limitar aos objetos selecionados (opcional)",
"settings_docs_archive": "Arquivo de documentos (com ficheiros)",
"settings_docs_archive_hint": "As exportações JSON/YAML/CSV contêm apenas as definições. Este ZIP inclui o conteúdo dos ficheiros carregados para uma restauração completa.",
"settings_docs_archive_hint": "As exportações JSON e YAML listam os seus documentos, mas não os respetivos ficheiros; o CSV omite os documentos. Este ZIP contém os ficheiros carregados — restaure-o depois de importar o JSON e nada fica em falta.",
"settings_docs_export_btn": "Descarregar ZIP de documentos",
"settings_docs_import_btn": "Restaurar ZIP de documentos",
"settings_docs_import_success": "Restaurado: {blobs} ficheiros, {docs} documentos",
"settings_docs_import_missing": "{n} ignorados — os respetivos ficheiros não estavam no ZIP",
"adopt_problem_button": "Adotar sensores de problema",
"adopt_problem_title": "Adotar sensores de problema",
"adopt_problem_hint": "Transforma os sensores de problema do HA (erros de impressora, avisos de filtro, bateria fraca) em tarefas de manutenção que são acionadas enquanto o problema está ativo e se concluem quando é resolvido.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Conta o uso desde a última conclusão: vence após +{target}; a contagem recomeça após cada conclusão.",
"trigger_hint_counter_abs": "A tarefa vence quando o sensor atingir {target}.",
"trigger_hint_runtime": "A tarefa vence após {hours} h de funcionamento acumulado; a contagem recomeça após cada conclusão.",
"trigger_hint_due_date": "A tarefa fica pendente {days} dias antes da data que o sensor indica.",
"trigger_hint_due_date_one": "A tarefa fica pendente um dia antes da data que o sensor indica.",
"trigger_hint_due_date_same_day": "A tarefa fica pendente na data que o sensor indica.",
"trigger_hint_due_date_now": "Data indicada: {date}.",
"trigger_hint_state_change": "A tarefa vence após {count} mudança(s) de estado.",
"trigger_hint_state_change_to": "A tarefa vence após {count} mudança(s) para «{state}».",
"trigger_hint_state_now": "Estado atual: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Apenas as tarefas que correspondem aos filtros de etiqueta/utilizador da vista escolhida enviam lembretes. O estado, a ordenação e o agrupamento da vista são ignorados aqui.",
"settings_notify_completed": "Notificações de conclusão",
"settings_notify_completed_hint": "Aviso para a casa quando uma tarefa é concluída, com o motivo e quem a fez. \"Só automáticas\" anuncia apenas as conclusões que ninguém fez no local: recuperação do sensor, item marcado na lista de compras, chamada de serviço de uma automação. Só para o serviço da casa; horas de silêncio e limite diário aplicam-se.",
"settings_title_style": "Estilo do título da notificação",
"settings_title_style_hint": "O que aparece como TÍTULO da notificação. Padrão mantém o texto por status (ex. 'Manutenção atrasada!'). Nome do objeto / tarefa ajuda quando o telefone empilha notificações.",
"title_style_default": "Padrão (título por status)",
"title_style_object_name": "Nome do objeto como título",
"title_style_task_name": "Nome da tarefa como título",
"notify_completed_off": "Desligado",
"notify_completed_automatic": "Só conclusões automáticas",
"notify_completed_all": "Todas as conclusões",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; é fundido nos data da chamada notify (as suas chaves ganham). Variáveis: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Teste com o botão Enviar teste.",
"card_saved_view": "Vista guardada",
"card_saved_view_none": "Nenhuma",
"card_saved_view_help": "Aplica os filtros de estado, utilizador e etiqueta da vista além dos filtros acima. A ordenação e o agrupamento da vista são definições de apresentação do painel e não se aplicam ao cartão.",
"card_saved_view_help": "Aplica os filtros de estado, utilizador, etiqueta e prioridade da vista além dos filtros acima. A ordenação e o agrupamento da vista são definições de apresentação do painel e não se aplicam ao cartão.",
"panel_card_tab": "Abrir no separador",
"panel_card_tab_default": "Separador memorizado (como no painel)",
"panel_card_view": "Abrir com a vista guardada",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Voltar aos ícones",
"battery_fleet_forecast_overdue": "Data prevista ultrapassada — a bateria continua a reportar bom estado. Se a substituiu, registe a substituição; caso contrário, a previsão falhou.",
"cost_from_parts": "Usar ≈ {amount} das peças",
"cost_parts_booked": "As peças usadas ≈ {amount} são registadas automaticamente — introduz só os custos adicionais.",
"cost_parts_info": "Peças usadas ≈ {amount} — contadas na compra, não aqui.",
"cost_purchase_use_hint": "O preço pago passa a ser o preço da peça — as peças contam no uso.",
"history_parts_counted": "Peças: {amount}",
"history_parts_counted_hint": "Valor das peças usadas — contado como despesa",
"history_parts_info": "Peças ≈ {amount}",
"history_parts_info_hint": "Valor das peças usadas — só informativo; o dinheiro contou na compra",
"history_purchase_stock": "Comprado por {amount} (conta no uso)",
"dismiss": "Dispensar",
"gs_label": "Primeiros passos — estas dicas desaparecem à medida que a configuração cresce",
"gs_setups_chip": "Configurações sugeridas: {n} dispositivos com gatilhos pré-configurados",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Quinzena (14 dias)",
"cal_editor_window_month": "Mês (30 dias, predefinido)",
"cal_editor_window_year": "Ano (365 dias, dias vazios ocultos)",
"cal_editor_window_past_30": "Últimos 30 dias (histórico)",
"cal_editor_window_past_90": "Últimos 90 dias (histórico)",
"cal_editor_show_chips": "Mostrar chips de janela no cartão",
"cal_editor_chips_hint": "Oculte os chips quando o cartão está numa vista de estratégia que já serve de seletor de janela.",
"cal_editor_show_user_filter": "Mostrar filtro de utilizador",
@@ -36,6 +36,8 @@
"reset": "Сбросить",
"snooze": "Отложить",
"snoozed": "Отложено",
"snoozed_for": "Напоминания отключены на {hours} ч — до {until}",
"snoozed_for_one": "Напоминания отключены на час — до {until}",
"cancel": "Отмена",
"bulk_select": "Выбрать",
"bulk_select_all": "Выбрать все",
@@ -61,7 +63,10 @@
"counter": "Счётчик",
"state_change": "Изменение состояния",
"runtime": "Время работы",
"trigger_type_due_date": "Дата от датчика",
"runtime_hours": "Целевое время работы (часы)",
"days_before": "Дней до даты",
"days_before_help": "Для датчика, который сообщает срок обслуживания (дата или метка времени). 0 = в сам день. Собственная кнопка «выполнено» устройства переносит дату вперёд и снимает триггер — назначьте её действием при выполнении.",
"target_value": "Целевое значение",
"target_changes": "Целевые изменения",
"for_minutes": "На (минут)",
@@ -232,6 +237,8 @@
"model_optional": "Модель (опционально)",
"serial_number_optional": "Серийный номер (опционально)",
"serial_number_label": "С/Н",
"object_replaces": "Предшественник",
"object_replaced_by": "Преемник",
"documentation_url_label": "Руководство",
"object_notes_label": "Заметки",
"sort_due_date": "Срок",
@@ -420,7 +427,7 @@
"qr_action_view": "Просмотр",
"qr_action_complete": "Отметить обслуживание как выполненное",
"qr_action_quick_complete": "Быстрое завершение — без диалога",
"qr_print_quick_none": "Для кодов быстрого завершения у задачи должны быть значения по умолчанию для быстрого завершения (окно задачи → Значения по умолчанию для быстрого завершения) — ни у одной задачи выбранных объектов их пока нет.",
"qr_print_quick_none": "Для кодов быстрого завершения у задачи должны быть значения по умолчанию для быстрого завершения (Настройки → Расширенные функции → Действия при завершении; окно задачи → Значения по умолчанию для быстрого завершения) — ни у одной задачи выбранных объектов их пока нет.",
"qr_url_mode": "Тип ссылки",
"qr_mode_companion": "Приложение-компаньон",
"qr_mode_local": "Локальный (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Включите или отключите расширенные функции. Отключение скрывает их из интерфейса, но не удаляет данные.",
"feat_adaptive": "Адаптивное планирование",
"feat_adaptive_desc": "Изучать оптимальные интервалы из истории обслуживания",
"feat_predictions": "Предсказания по датчикам",
"feat_predictions_desc": "Предсказывать даты срабатывания по деградации датчика",
"feat_seasonal": "Сезонные корректировки",
"feat_seasonal_desc": "Корректировать интервалы на основе сезонных паттернов",
"feat_environmental": "Экологическая корреляция",
@@ -669,6 +674,10 @@
"settings_currency": "Валюта",
"settings_currency_decimals": "Знаков после запятой в суммах",
"settings_currency_decimals_hint": "Сколько знаков после запятой показывает каждая сумма: KPI, бюджеты, расходы в списках и отчётах, оповещения о бюджете. 0 = целые числа.",
"settings_parts_cost_mode": "Запчасти считаются расходом",
"settings_parts_cost_purchase": "При покупке",
"settings_parts_cost_use": "При использовании",
"settings_parts_cost_mode_hint": "При покупке: учитывается стоимость напоминания о покупке, а использованные в работе запчасти показываются для информации. При использовании: каждое выполнение учитывает стоимость использованных запчастей, а покупка задаёт цену запчасти вместо учёта. Действует для выполнений с этого момента — история сохраняет прежний учёт.",
"settings_budget_monthly": "Месячный бюджет",
"settings_budget_yearly": "Годовой бюджет",
"settings_budget_alerts": "Оповещения о бюджете",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Вставьте содержимое JSON или CSV здесь…",
"settings_import_btn": "Импортировать",
"settings_import_success": "Импортировано объектов: {count}.",
"settings_import_unmatched_users": "Не найдены в этом экземпляре: {names}. Их назначения задач удалены — создайте в Home Assistant людей с такими же именами и импортируйте снова, чтобы сохранить их.",
"settings_export_success": "Экспорт загружен.",
"settings_saved": "Настройка сохранена.",
"settings_include_history": "Включать историю",
@@ -746,7 +756,7 @@
"has_overdue": "Просроченные задачи",
"object": "Объект",
"settings_panel_access": "Доступ к панели",
"settings_panel_access_desc": "Администраторы всегда имеют полный доступ. Чтобы делегировать создание, редактирование и удаление определённым не-админ пользователям, включите это и выберите их ниже — остальные видят только Выполнить и Пропустить.",
"settings_panel_access_desc": "Администраторы всегда имеют полный доступ. Чтобы делегировать создание, редактирование и удаление определённым не-админ пользователям, включите это и выберите их ниже — остальным доступны только «Выполнить», «Пропустить», «Сбросить» и «Отложить».",
"settings_operator_write": "Разрешить выбранным пользователям создавать, редактировать и удалять",
"settings_operator_write_desc": "Выключено: только администраторы могут изменять содержимое. Включено: выбранные пользователи ниже также получают полный доступ.",
"no_non_admin_users": "Не найдено не-админ пользователей. Добавьте их в Настройках → Люди.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Тестировать действие",
"on_complete_action_test_success": "Успешно",
"on_complete_action_test_failed": "Ошибка",
"action_test_bad_format": "Неверный формат службы (ожидается 'domain.service')",
"action_test_unknown_service": "Служба «{service}» не зарегистрирована в Home Assistant. Проверьте написание и то, что интеграция, которая её предоставляет, загружена.",
"action_test_domain_mismatch": "Служба «{service}» работает только с объектами {domain}.*; объект «{entity}» относится к {entity_domain}.* — выберите службу для домена этого объекта (например, {suggestion})",
"action_test_entity_missing": "Целевой объект «{entity}» не найден в Home Assistant — возможно, его переименовали или удалили его интеграцию.",
"quick_complete_defaults_title": "Значения по умолчанию для быстрого завершения (для QR-сканов, опционально)",
"quick_complete_defaults_desc": "Предустановленные значения для QR быстрого завершения. Без них QR открывает диалог.",
"quick_complete_defaults_notes": "Заметки",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Управление назначениями задач…",
"groups_open_panel": "Открыть на панели",
"on_complete_action_target_hint": "Примечание: домен сущности должен соответствовать сервису — напр. 'button.press' работает только с button.*, 'counter.increment' только с counter.*, 'input_button.press' только с input_button.* и т. д. При несоответствии действие молча не выполнится (HA запишет в журнал 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "У этого действия больше целей, чем показывает это поле: {targets}. Они сохраняются как есть — выбор здесь другой сущности заменит всю цель.",
"on_complete_action_skip_auto": "Не когда задача выполняется сама (датчик восстановился)",
"show_all_objects": "Показать все объекты",
"show_all_tasks": "Сбросить фильтр — показать все задачи",
@@ -844,6 +859,7 @@
"doc_too_large": "Файл слишком большой (макс. 25 МБ).",
"doc_upload_failed": "Не удалось загрузить.",
"docs_archive_too_large": "Архив слишком большой (макс. {max}).",
"docs_archive_no_manifest": "В этом ZIP нет manifest.json, поэтому при восстановлении неизвестно, к какому документу относится файл. При повторной упаковке оставляйте manifest.json рядом с папками.",
"completion_photos_optional": "Фото выполнения (необязательно)",
"completion_photos": "Фото выполнения",
"choose_photos": "Выбрать фото",
@@ -904,6 +920,11 @@
"paused": "Приостановлено",
"pause_object": "Приостановить",
"resume_object": "Возобновить",
"pause_task": "Приостановить",
"resume_task": "Возобновить",
"pause_task_prompt": "Замораживает расписание этой задачи — она не станет актуальной и не будет уведомлять, пока вы её не возобновите; возобновление начинает новый цикл с этого дня. Остальные задачи объекта продолжают работать. Можно указать дату автоматического возобновления.",
"task_paused": "Задача приостановлена",
"task_resumed": "Задача возобновлена — расписание началось заново",
"pause_until_prompt": "Заморозить расписания этого объекта — ничего не наступает и не уведомляет до возобновления. При желании укажите дату автоматического возобновления.",
"pause_until_label": "Возобновить (необязательно)",
"object_paused": "Объект приостановлен",
@@ -913,7 +934,18 @@
"replace_object": "Заменить…",
"replace_object_prompt": "Списать этот объект и создать преемника. История и затраты остаются в архиве старого; задачи и документы переходят к новому, счётчики начинаются заново.",
"replace_name_label": "Имя преемника",
"replace_device_heading": "Устройство в Home Assistant",
"replace_device_keep": "То же устройство, что и раньше ({device})",
"replace_device_keep_hint": "Этот вариант подходит и тогда, когда новое устройство ещё не добавлено в Home Assistant: после добавления выбери его в настройках объекта — триггеры датчиков перейдут на него.",
"replace_device_other": "Новое устройство — другое устройство",
"replace_device_pick": "Устройство нового экземпляра",
"replace_device_other_hint": "Триггеры датчиков, действия при выполнении и сброс счётчика перейдут на соответствующие сущности этого устройства.",
"replace_device_none": "Без устройства",
"object_replaced": "Объект заменён — преемник создан",
"device_swap_moved": "Перенесено связей с датчиками на новое устройство: {count}.",
"device_swap_moved_one": "Одна связь с датчиком перенесена на новое устройство.",
"device_swap_unmatched": "Не удалось сопоставить: {count} — проверь триггеры датчиков этих задач.",
"device_swap_unmatched_one": "Одну связь не удалось сопоставить — проверь триггер датчика этой задачи.",
"reading_unit_label": "Единица показания (напр. кВт·ч, м³)",
"reading_unit_help": "Отображается рядом с записанным значением при выполнении этой задачи.",
"reading_value_label": "Показание",
@@ -1010,6 +1042,9 @@
"part_stock": "Запас",
"part_reorder_threshold": "Порог дозаказа",
"part_restock_quantity": "Количество пополнения",
"part_package_size": "Размер упаковки (единиц)",
"part_restock_packages": "Количество для пополнения (упаковок)",
"part_package_hint": "Покупается упаковками, расходуется единицами: запас, расход на задачу и порог дозаказа считаются в единицах; количество для пополнения и цена — за упаковку.",
"part_auto_buy": "Задача покупки при малом запасе",
"part_restock": "Изменить запас",
"parts_used_by": "Используется",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Не удалось загрузить запчасти этого объекта — параметры расхода запчастей сейчас недоступны.",
"settings_export_selection": "Ограничить выбранными объектами (необязательно)",
"settings_docs_archive": "Архив документов (с файлами)",
"settings_docs_archive_hint": "Экспорты JSON/YAML/CSV содержат только настройки. Этот ZIP включает содержимое загруженных файлов, чтобы восстановление было полным.",
"settings_docs_archive_hint": "Экспорт в JSON и YAML перечисляет ваши документы, но не содержит их файлов; CSV документы не включает. В этом ZIP загруженные файлы — восстановите его после импорта JSON, и ничего не пропадёт.",
"settings_docs_export_btn": "Скачать ZIP документов",
"settings_docs_import_btn": "Восстановить ZIP документов",
"settings_docs_import_success": "Восстановлено: {blobs} файлов, {docs} документов",
"settings_docs_import_missing": "Пропущено: {n} — их файлов не было в ZIP",
"adopt_problem_button": "Принять сенсоры проблем",
"adopt_problem_title": "Принять сенсоры проблем",
"adopt_problem_hint": "Превращает сенсоры проблем HA (ошибки принтера, предупреждения фильтра, низкий заряд) в задачи обслуживания, которые срабатывают, пока проблема активна, и завершаются сами при её устранении.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Считает расход с последнего выполнения: срок после +{target}; счётчик начинается заново после каждого выполнения.",
"trigger_hint_counter_abs": "Задача наступит, когда датчик достигнет {target}.",
"trigger_hint_runtime": "Задача наступит после {hours} ч суммарной наработки; счётчик начинается заново после каждого выполнения.",
"trigger_hint_due_date": "Задача станет актуальной за {days} дн. до даты, которую сообщает датчик.",
"trigger_hint_due_date_one": "Задача станет актуальной за один день до даты, которую сообщает датчик.",
"trigger_hint_due_date_same_day": "Задача станет актуальной в дату, которую сообщает датчик.",
"trigger_hint_due_date_now": "Сообщённая дата: {date}.",
"trigger_hint_state_change": "Задача наступит после {count} изменения(й) состояния.",
"trigger_hint_state_change_to": "Задача наступит после {count} перехода(ов) в «{state}».",
"trigger_hint_state_now": "Текущее состояние: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Напоминания отправляют только задачи, совпадающие с фильтрами метки/пользователя выбранного представления. Статус, сортировка и группировка представления здесь игнорируются.",
"settings_notify_completed": "Уведомления о выполнении",
"settings_notify_completed_hint": "Сообщение для всех домашних, когда задача выполнена, с причиной и исполнителем. «Только автоматические» сообщает лишь о выполнениях, которые никто не делал на месте: восстановление датчика, отметка в списке покупок, вызов службы автоматизацией. Только на общий сервис; тихие часы и дневной лимит действуют.",
"settings_title_style": "Стиль заголовка уведомления",
"settings_title_style_hint": "Что отображается в ЗАГОЛОВКЕ уведомления. По умолчанию — текст по статусу (например, «Обслуживание просрочено!»). Имя объекта / задачи удобно, если телефон складывает уведомления в стопку.",
"title_style_default": "По умолчанию (заголовок по статусу)",
"title_style_object_name": "Имя объекта как заголовок",
"title_style_task_name": "Имя задачи как заголовок",
"notify_completed_off": "Выкл.",
"notify_completed_automatic": "Только автоматические выполнения",
"notify_completed_all": "Все выполнения",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON или YAML, Jinja разрешён; добавляется в data вызова notify (ваши ключи важнее). Переменные: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Проверьте кнопкой «Отправить тест».",
"card_saved_view": "Сохранённое представление",
"card_saved_view_none": "Нет",
"card_saved_view_help": "Применяет фильтры статуса, пользователя и метки представления в дополнение к фильтрам выше. Сортировка и группировка представления — настройки отображения панели и на карточке не применяются.",
"card_saved_view_help": "Применяет фильтры статуса, пользователя, метки и приоритета представления в дополнение к фильтрам выше. Сортировка и группировка представления — настройки отображения панели и на карточке не применяются.",
"panel_card_tab": "Открывать на вкладке",
"panel_card_tab_default": "Запомненная вкладка (как в панели)",
"panel_card_view": "Открывать с сохранённым видом",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Вернуть значки",
"battery_fleet_forecast_overdue": "Прогнозируемая дата прошла — батарея по-прежнему сообщает о хорошем состоянии. Если вы её заменили, зафиксируйте замену; иначе прогноз оказался неверным.",
"cost_from_parts": "Взять ≈ {amount} из запчастей",
"cost_parts_booked": "Использованные запчасти ≈ {amount} учитываются автоматически — укажи только дополнительные расходы.",
"cost_parts_info": "Использованные запчасти ≈ {amount} — учтены при покупке, не здесь.",
"cost_purchase_use_hint": "Уплаченная цена становится ценой запчасти — запчасти учитываются при использовании.",
"history_parts_counted": "Запчасти: {amount}",
"history_parts_counted_hint": "Стоимость использованных запчастей — учтена как расход",
"history_parts_info": "Запчасти ≈ {amount}",
"history_parts_info_hint": "Стоимость использованных запчастей — только для информации; деньги учтены при покупке",
"history_purchase_stock": "Куплено за {amount} (учтётся при использовании)",
"dismiss": "Скрыть",
"gs_label": "Первые шаги — эти подсказки исчезнут по мере роста настройки",
"gs_setups_chip": "Рекомендуемые настройки: найдено {n} устройств с готовыми триггерами",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Две недели (14 дней)",
"cal_editor_window_month": "Месяц (30 дней, по умолчанию)",
"cal_editor_window_year": "Год (365 дней, пустые дни скрыты)",
"cal_editor_window_past_30": "Последние 30 дней (история)",
"cal_editor_window_past_90": "Последние 90 дней (история)",
"cal_editor_show_chips": "Показывать чипы окна в карточке",
"cal_editor_chips_hint": "Скройте чипы, если карточка встроена в стратегию, которая уже служит выбором окна.",
"cal_editor_show_user_filter": "Показывать фильтр пользователя",
@@ -36,6 +36,8 @@
"reset": "Återställ",
"snooze": "Snooza",
"snoozed": "Snoozad",
"snoozed_for": "Påminnelser tystade i {hours} timmar — till {until}",
"snoozed_for_one": "Påminnelser tystade i en timme — till {until}",
"cancel": "Avbryt",
"bulk_select": "Välj",
"bulk_select_all": "Välj alla",
@@ -61,7 +63,10 @@
"counter": "Räknare",
"state_change": "Tillståndsändring",
"runtime": "Körtid",
"trigger_type_due_date": "Datum från en sensor",
"runtime_hours": "Måltid (timmar)",
"days_before": "Dagar före datumet",
"days_before_help": "För en sensor som rapporterar när underhållet förfaller (datum eller tidsstämpel). 0 = på själva datumet. Enhetens egen ”klar”-knapp flyttar fram datumet och släpper därmed utlösaren — gör den till åtgärden vid slutförande.",
"target_value": "Målvärde",
"target_changes": "Antal målförändringar",
"for_minutes": "Under (minuter)",
@@ -233,6 +238,8 @@
"model_optional": "Modell (valfritt)",
"serial_number_optional": "Serienummer (valfritt)",
"serial_number_label": "S/N",
"object_replaces": "Ersätter",
"object_replaced_by": "Ersatt av",
"documentation_url_label": "Manual",
"object_notes_label": "Anteckningar",
"sort_due_date": "Förfallodatum",
@@ -420,7 +427,7 @@
"qr_action_view": "Visa underhållsinformation",
"qr_action_complete": "Markera underhåll som slutfört",
"qr_action_quick_complete": "Snabb-slutför — utan dialogruta",
"qr_print_quick_none": "Snabb-slutför-koder kräver snabb-slutför standardvärden på en uppgift (uppgiftsdialogen → Snabb-slutför standardvärden) — ingen uppgift i de valda objekten har några ännu.",
"qr_print_quick_none": "Snabb-slutför-koder kräver snabb-slutför standardvärden på en uppgift (Inställningar → Avancerade funktioner → Slutförande-åtgärder; uppgiftsdialogen → Snabb-slutför standardvärden) — ingen uppgift i de valda objekten har några ännu.",
"qr_url_mode": "Länktyp",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Lokal (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Aktivera eller inaktivera avancerade funktioner. Inaktivering döljer dem från UI men tar inte bort data.",
"feat_adaptive": "Adaptiv schemaläggning",
"feat_adaptive_desc": "Lär dig optimala intervall från underhållshistorik",
"feat_predictions": "Sensorpredictions",
"feat_predictions_desc": "Förutsäg utlösningsdatum från sensordegradering",
"feat_seasonal": "Säsongsjusteringar",
"feat_seasonal_desc": "Justera intervall baserat på säsongsmönster",
"feat_environmental": "Miljökorrelation",
@@ -669,6 +674,10 @@
"settings_currency": "Valuta",
"settings_currency_decimals": "Decimaler för belopp",
"settings_currency_decimals_hint": "Hur många decimaler varje belopp visar: nyckeltal, budgetar, kostnader i listor och rapporter, budgetvarningar. 0 = hela tal.",
"settings_parts_cost_mode": "Reservdelar räknas som utgift",
"settings_parts_cost_purchase": "Vid köp",
"settings_parts_cost_use": "Vid användning",
"settings_parts_cost_mode_hint": "Vid köp: kostnaden för en köppåminnelse räknas och delarna ett jobb använder visas som information. Vid användning: varje slutförande bokför värdet av de delar som användes, och ett köp sätter delens pris i stället för att räknas. Gäller slutföranden från och med nu — historiken behåller hur den bokfördes.",
"settings_budget_monthly": "Månatlig budget",
"settings_budget_yearly": "Årlig budget",
"settings_budget_alerts": "Budgetvarningar",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Klistra in JSON- eller CSV-innehåll här…",
"settings_import_btn": "Importera",
"settings_import_success": "{count} objekt importerade.",
"settings_import_unmatched_users": "Hittades inte på den här instansen: {names}. Deras uppgiftstilldelningar togs bort — skapa personer med samma namn i Home Assistant och importera igen för att behålla dem.",
"settings_export_success": "Export nedladdad.",
"settings_saved": "Inställning sparad.",
"settings_include_history": "Inkludera historik",
@@ -746,7 +756,7 @@
"has_overdue": "Har försenade uppgifter",
"object": "Objekt",
"settings_panel_access": "Paneltillgång",
"settings_panel_access_desc": "Administratörer har alltid full åtkomst. För att delegera skapa, redigera och ta bort till specifika icke-admin-användare, slå på detta och välj dem nedan — alla andra ser endast Slutför och Hoppa över.",
"settings_panel_access_desc": "Administratörer har alltid full åtkomst. För att delegera skapa, redigera och ta bort till specifika icke-admin-användare, slå på detta och välj dem nedan — alla andra har bara Slutför, Hoppa över, Återställ, Skjut upp och Snooza.",
"settings_operator_write": "Tillåt valda användare att skapa, redigera och ta bort",
"settings_operator_write_desc": "Av: endast administratörer kan ändra innehåll. På: de valda användarna nedan får också full åtkomst.",
"no_non_admin_users": "Inga icke-admin-användare hittades. Lägg till några i Inställningar → Personer.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Testa åtgärd",
"on_complete_action_test_success": "Lyckades",
"on_complete_action_test_failed": "Misslyckades",
"action_test_bad_format": "Ogiltigt tjänstformat (förväntat 'domain.service')",
"action_test_unknown_service": "Tjänsten ”{service}” är inte registrerad i Home Assistant. Kontrollera stavningen och att integrationen som tillhandahåller den är laddad.",
"action_test_domain_mismatch": "Tjänsten ”{service}” fungerar bara med {domain}.*-entiteter; entiteten ”{entity}” tillhör {entity_domain}.* — välj en tjänst som passar entitetens domän (t.ex. {suggestion})",
"action_test_entity_missing": "Målentiteten ”{entity}” hittades inte i Home Assistant — den kan ha bytt namn eller så har dess integration tagits bort.",
"quick_complete_defaults_title": "Snabb-slutför standardvärden (för QR-skanningar, valfritt)",
"quick_complete_defaults_desc": "Förinställda värden för snabb-slutför-QR. Utan dessa öppnar QR slutför-dialogen.",
"quick_complete_defaults_notes": "Anteckningar",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Hantera uppgiftstilldelningar…",
"groups_open_panel": "Öppna i panelen",
"on_complete_action_target_hint": "Obs: entitetens domän måste matcha tjänsten — t.ex. 'button.press' fungerar bara på button.*, 'counter.increment' bara på counter.*, 'input_button.press' bara på input_button.* osv. Vid en avvikelse misslyckas åtgärden tyst (HA loggar 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Den här åtgärden har fler mål än fältet visar: {targets}. De behålls som de är — väljer du en annan entitet här ersätter den hela målet.",
"on_complete_action_skip_auto": "Inte när uppgiften slutförs av sig själv (sensorn har återhämtat sig)",
"show_all_objects": "Visa alla objekt",
"show_all_tasks": "Rensa filter — visa alla uppgifter",
@@ -844,6 +859,7 @@
"doc_too_large": "Filen är för stor (max 25 MB).",
"doc_upload_failed": "Uppladdningen misslyckades.",
"docs_archive_too_large": "Arkivet är för stort (max {max}).",
"docs_archive_no_manifest": "Den här ZIP-filen saknar manifest.json, så återställningen vet inte vilket dokument en fil hör till. Låt manifest.json ligga bredvid mapparna när du zippar igen.",
"completion_photos_optional": "Foton vid slutförande (valfritt)",
"completion_photos": "Foton vid slutförande",
"choose_photos": "Välj foton",
@@ -904,6 +920,11 @@
"paused": "Pausad",
"pause_object": "Pausa",
"resume_object": "Återuppta",
"pause_task": "Pausa",
"resume_task": "Återuppta",
"pause_task_prompt": "Fryser uppgiftens schema — den förfaller inte och meddelar inget förrän du återupptar den, och då börjar en ny cykel från den dagen. Objektets andra uppgifter fortsätter. Valfritt: ett datum då den återupptas av sig själv.",
"task_paused": "Uppgiften pausad",
"task_resumed": "Uppgiften återupptagen — schemat börjar om",
"pause_until_prompt": "Frys objektets scheman — inget förfaller och inget aviserar förrän det återupptas. Ange valfritt ett datum för automatisk återupptagning.",
"pause_until_label": "Återuppta den (valfritt)",
"object_paused": "Objekt pausat",
@@ -913,7 +934,18 @@
"replace_object": "Ersätt…",
"replace_object_prompt": "Pensionera detta objekt och skapa en efterträdare. Historik och kostnader arkiveras på det gamla; uppgifter och dokument följer med till det nya, räknare börjar om.",
"replace_name_label": "Efterträdarens namn",
"replace_device_heading": "Enhet i Home Assistant",
"replace_device_keep": "Samma enhet som förut ({device})",
"replace_device_keep_hint": "Välj det också om den nya enheten inte finns i Home Assistant än: när den gör det, välj den som objektets enhet i inställningarna — sensortriggarna följer då med.",
"replace_device_other": "Den nya enheten är en annan enhet",
"replace_device_pick": "Den nya enhetens enhet i Home Assistant",
"replace_device_other_hint": "Sensortriggare, slutförandeåtgärder och räknaråterställningen flyttar till motsvarande entiteter på den enheten.",
"replace_device_none": "Ingen enhet",
"object_replaced": "Objekt ersatt — efterträdare skapad",
"device_swap_moved": "{count} sensorkopplingar flyttade till den nya enheten.",
"device_swap_moved_one": "En sensorkoppling flyttad till den nya enheten.",
"device_swap_unmatched": "{count} kunde inte matchas — kontrollera de uppgifternas sensortriggare.",
"device_swap_unmatched_one": "En kunde inte matchas — kontrollera uppgiftens sensortrigger.",
"reading_unit_label": "Avläsningsenhet (t.ex. kWh, m³)",
"reading_unit_help": "Visas bredvid det registrerade värdet när uppgiften slutförs.",
"reading_value_label": "Avläst värde",
@@ -1010,6 +1042,9 @@
"part_stock": "Lager",
"part_reorder_threshold": "Beställningsgräns",
"part_restock_quantity": "Påfyllningsantal",
"part_package_size": "Förpackningsstorlek (enheter)",
"part_restock_packages": "Påfyllningsmängd (förpackningar)",
"part_package_hint": "Köps i förpackningar, används i enheten: lager, åtgång per uppgift och beställningsgräns räknar enheter; påfyllningsmängd och pris gäller per förpackning.",
"part_auto_buy": "Köpuppgift vid lågt lager",
"part_restock": "Justera lager",
"parts_used_by": "Används av",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Kunde inte läsa in objektets delar — förbrukningsalternativen är inte tillgängliga just nu.",
"settings_export_selection": "Begränsa till valda objekt (valfritt)",
"settings_docs_archive": "Dokumentarkiv (med filer)",
"settings_docs_archive_hint": "JSON/YAML/CSV-exporterna innehåller bara inställningar. Denna ZIP innehåller de uppladdade filernas innehåll så att en återställning blir fullständig.",
"settings_docs_archive_hint": "JSON- och YAML-exporterna listar dina dokument men inte deras filer; CSV utelämnar dokument. Den här ZIP-filen innehåller de uppladdade filerna – återställ den efter att JSON-filen importerats så saknas ingenting.",
"settings_docs_export_btn": "Ladda ner dokument-ZIP",
"settings_docs_import_btn": "Återställ dokument-ZIP",
"settings_docs_import_success": "Återställt: {blobs} filer, {docs} dokument",
"settings_docs_import_missing": "{n} hoppades över — deras filer fanns inte i ZIP-filen",
"adopt_problem_button": "Anta problemsensorer",
"adopt_problem_title": "Anta problemsensorer",
"adopt_problem_hint": "Gör HA:s problemsensorer (skrivarfel, filtervarningar, lågt batteri) till underhållsuppgifter som utlöses medan problemet är aktivt och avslutas själva när det löses.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Räknar förbrukningen sedan senaste slutförandet: förfaller efter +{target}; räknaren börjar om efter varje slutförande.",
"trigger_hint_counter_abs": "Uppgiften förfaller när sensorn når {target}.",
"trigger_hint_runtime": "Uppgiften förfaller efter {hours} h ackumulerad drifttid; räknaren börjar om efter varje slutförande.",
"trigger_hint_due_date": "Uppgiften förfaller {days} dagar före datumet som sensorn rapporterar.",
"trigger_hint_due_date_one": "Uppgiften förfaller en dag före datumet som sensorn rapporterar.",
"trigger_hint_due_date_same_day": "Uppgiften förfaller på datumet som sensorn rapporterar.",
"trigger_hint_due_date_now": "Rapporterat datum: {date}.",
"trigger_hint_state_change": "Uppgiften förfaller efter {count} tillståndsändring(ar).",
"trigger_hint_state_change_to": "Uppgiften förfaller efter {count} ändring(ar) till ”{state}”.",
"trigger_hint_state_now": "Aktuellt tillstånd: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Endast uppgifter som matchar den valda vyns etikett-/användarfilter skickar påminnelser. Vyns status, sortering och gruppering ignoreras här.",
"settings_notify_completed": "Aviseringar om slutförande",
"settings_notify_completed_hint": "Ett meddelande till hushållet när en uppgift slutförts, med orsak och vem som gjorde det. \"Endast automatiska\" meddelar bara slutföranden som ingen gjorde på plats: sensoråterhämtning, avbockning i inköpslistan, ett tjänsteanrop från en automation. Bara till hushållstjänsten; tysta timmar och dagsgränsen gäller.",
"settings_title_style": "Stil för aviseringstitel",
"settings_title_style_hint": "Vad som visas som aviseringens TITEL. Standard behåller texten per status (t.ex. ”Underhåll försenat!”). Objektnamn / uppgiftsnamn hjälper när telefonen staplar aviseringar.",
"title_style_default": "Standard (titel per status)",
"title_style_object_name": "Objektnamn som titel",
"title_style_task_name": "Uppgiftsnamn som titel",
"notify_completed_off": "Av",
"notify_completed_automatic": "Endast automatiska slutföranden",
"notify_completed_all": "Alla slutföranden",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillåtet; slås ihop med notify-anropets data (dina nycklar vinner). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Testa med knappen Skicka test.",
"card_saved_view": "Sparad vy",
"card_saved_view_none": "Ingen",
"card_saved_view_help": "Tillämpar vyns status-, användar- och etikettfilter utöver filtren ovan. Vyns sortering och gruppering är panelvisningsinställningar och tillämpas inte på kortet.",
"card_saved_view_help": "Tillämpar vyns status-, användar-, etikett- och prioritetsfilter utöver filtren ovan. Vyns sortering och gruppering är panelvisningsinställningar och tillämpas inte på kortet.",
"panel_card_tab": "Öppna på fliken",
"panel_card_tab_default": "Ihågkommen flik (som i panelen)",
"panel_card_view": "Öppna med sparad vy",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Tillbaka till ikoner",
"battery_fleet_forecast_overdue": "Förutsagt datum har passerat — batteriet rapporterar fortfarande god status. Om du bytte det, registrera bytet; annars slog prognosen fel.",
"cost_from_parts": "Använd ≈ {amount} från delar",
"cost_parts_booked": "Använda delar ≈ {amount} bokförs automatiskt — ange bara extra kostnader.",
"cost_parts_info": "Använda delar ≈ {amount} — räknade vid köpet, inte här.",
"cost_purchase_use_hint": "Det betalda priset blir delens pris — reservdelar räknas när de används.",
"history_parts_counted": "Delar: {amount}",
"history_parts_counted_hint": "Värdet av använda delar — räknat som utgift",
"history_parts_info": "Delar ≈ {amount}",
"history_parts_info_hint": "Värdet av använda delar — endast information; pengarna räknades vid köpet",
"history_purchase_stock": "Köpt för {amount} (räknas när det används)",
"dismiss": "Avfärda",
"gs_label": "Kom igång — tipsen försvinner när din installation växer",
"gs_setups_chip": "Föreslagna uppsättningar hittade {n} enheter med förkopplade utlösare",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Två veckor (14 dagar)",
"cal_editor_window_month": "Månad (30 dagar, standard)",
"cal_editor_window_year": "År (365 dagar, tomma dagar dolda)",
"cal_editor_window_past_30": "Senaste 30 dagarna (historik)",
"cal_editor_window_past_90": "Senaste 90 dagarna (historik)",
"cal_editor_show_chips": "Visa fönsterchips i kortet",
"cal_editor_chips_hint": "Dölj chipsen när kortet är inbäddat i en strategivy som redan fungerar som fönsterväljare.",
"cal_editor_show_user_filter": "Visa användarfilter",
@@ -38,6 +38,8 @@
"reset": "Sıfırla",
"snooze": "Ertele",
"snoozed": "Ertelendi",
"snoozed_for": "Hatırlatıcılar {hours} saat sessize alındı — {until} tarihine kadar",
"snoozed_for_one": "Hatırlatıcılar bir saat sessize alındı — {until} tarihine kadar",
"cancel": "İptal",
"bulk_select": "Seç",
"bulk_select_all": "Tümünü seç",
@@ -63,7 +65,10 @@
"counter": "Sayaç",
"state_change": "Durum değişikliği",
"runtime": "Çalışma süresi",
"trigger_type_due_date": "Sensörden tarih",
"runtime_hours": "Hedef çalışma süresi (saat)",
"days_before": "Tarihten önceki gün",
"days_before_help": "Bakımın ne zaman gerektiğini bildiren (tarih veya zaman damgası) bir sensör için. 0 = tam o gün. Cihazın kendi \"tamamlandı\" düğmesi tarihi ileri alır ve böylece tetikleyiciyi kaldırır — onu tamamlama eylemi yapın.",
"target_value": "Hedef değer",
"target_changes": "Hedef değişiklik sayısı",
"for_minutes": "Süre (dakika)",
@@ -235,6 +240,8 @@
"model_optional": "Model (isteğe bağlı)",
"serial_number_optional": "Seri numarası (isteğe bağlı)",
"serial_number_label": "S/N",
"object_replaces": "Yerine geçtiği",
"object_replaced_by": "Yerine geçen",
"documentation_url_label": "Kılavuz",
"object_notes_label": "Notlar",
"sort_due_date": "Vade tarihi",
@@ -422,7 +429,7 @@
"qr_action_view": "Bakım bilgisini görüntüle",
"qr_action_complete": "Bakımı tamamlandı olarak işaretle",
"qr_action_quick_complete": "Hızlı tamamlama — iletişim kutusu olmadan",
"qr_print_quick_none": "Hızlı tamamlama kodları için bir görevde hızlı tamamlama varsayılanları gerekir (görev penceresi → Hızlı tamamlama varsayılanları) — seçilen nesnelerin hiçbir görevinde henüz yok.",
"qr_print_quick_none": "Hızlı tamamlama kodları için bir görevde hızlı tamamlama varsayılanları gerekir (Ayarlar → Gelişmiş Özellikler → Tamamlama eylemleri; görev penceresi → Hızlı tamamlama varsayılanları) — seçilen nesnelerin hiçbir görevinde henüz yok.",
"qr_url_mode": "Bağlantı türü",
"qr_mode_companion": "Companion uygulaması",
"qr_mode_local": "Yerel (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "Gelişmiş özellikleri etkinleştirin veya devre dışı bırakın. Devre dışı bırakmak onları arayüzden gizler ancak verileri silmez.",
"feat_adaptive": "Uyarlanabilir Zamanlama",
"feat_adaptive_desc": "Bakım geçmişinden en uygun aralıkları öğren",
"feat_predictions": "Sensör Tahminleri",
"feat_predictions_desc": "Sensör bozulmasından tetiklenme tarihlerini öngör",
"feat_seasonal": "Mevsimsel Ayarlamalar",
"feat_seasonal_desc": "Aralıkları mevsimsel desenlere göre ayarla",
"feat_environmental": "Çevresel Korelasyon",
@@ -671,6 +676,10 @@
"settings_currency": "Para birimi",
"settings_currency_decimals": "Tutarlarda ondalık basamak",
"settings_currency_decimals_hint": "Her tutarın kaç ondalık basamak göstereceği: KPI'lar, bütçeler, liste ve raporlardaki maliyetler, bütçe uyarıları. 0 = tam sayılar.",
"settings_parts_cost_mode": "Yedek parçalar gider sayılır",
"settings_parts_cost_purchase": "Satın alındığında",
"settings_parts_cost_use": "Kullanıldığında",
"settings_parts_cost_mode_hint": "Satın alındığında: satın alma hatırlatıcısının maliyeti sayılır, bir işte kullanılan parçalar bilgi olarak gösterilir. Kullanıldığında: her tamamlama kullanılan parçaların değerini kaydeder, satın alma ise sayılmak yerine parçanın fiyatını belirler. Bundan sonraki tamamlamalar için geçerlidir — geçmiş kaydedildiği gibi kalır.",
"settings_budget_monthly": "Aylık bütçe",
"settings_budget_yearly": "Yıllık bütçe",
"settings_budget_alerts": "Bütçe uyarıları",
@@ -683,15 +692,17 @@
"settings_import_placeholder": "JSON veya CSV içeriğini buraya yapıştırın…",
"settings_import_btn": "İçe aktar",
"settings_import_success": "{count} nesne başarıyla içe aktarıldı.",
"settings_import_unmatched_users": "Bu kurulumda bulunamadı: {names}. Görev atamaları kaldırıldı — korumak için Home Assistant'ta aynı adlara sahip kişiler oluşturup yeniden içe aktarın.",
"settings_export_success": "Dışa aktarma indirildi.",
"settings_saved": "Ayar kaydedildi.",
"settings_include_history": "Geçmişi dahil et",
"settings_export_selection": "Seçili nesnelerle sınırla (isteğe bağlı)",
"settings_docs_archive": "Belge arşivi (dosyalarla birlikte)",
"settings_docs_archive_hint": "JSON/YAML/CSV dışa aktarmaları yalnızca ayarları içerir. Bu ZIP, geri yüklemenin eksiksiz olması için yüklenen dosya içeriklerini de kapsar.",
"settings_docs_archive_hint": "JSON ve YAML dışa aktarmaları belgelerinizi listeler ancak dosyalarını içermez; CSV belgeleri dışarıda bırakır. Bu ZIP yüklenen dosyaları içerir — JSON'u içe aktardıktan sonra geri yükleyin, hiçbir şey eksik kalmaz.",
"settings_docs_export_btn": "Belge ZIP'ini indir",
"settings_docs_import_btn": "Belge ZIP'ini geri yükle",
"settings_docs_import_success": "Geri yüklendi: {blobs} dosya, {docs} belge",
"settings_docs_import_missing": "{n} atlandı — dosyaları ZIP içinde yoktu",
"sort_alphabetical": "Alfabetik",
"sort_due_soonest": "En yakın vade",
"sort_task_count": "Görev sayısı",
@@ -777,7 +788,7 @@
"has_overdue": "Gecikmiş görevi var",
"object": "Nesne",
"settings_panel_access": "Panel erişimi",
"settings_panel_access_desc": "Yöneticiler her zaman tam erişime sahiptir. Oluşturma, düzenleme ve silmeyi belirli yönetici olmayan kullanıcılara devretmek için bunu açın ve onları aşağıdan seçin — diğer herkes yalnızca Tamamla ve Atla görür.",
"settings_panel_access_desc": "Yöneticiler her zaman tam erişime sahiptir. Oluşturma, düzenleme ve silmeyi belirli yönetici olmayan kullanıcılara devretmek için bunu açın ve onları aşağıdan seçin — diğer herkes yalnızca Tamamla, Atla, Sıfırla, Ötele ve Ertele'yi kullanabilir.",
"settings_operator_write": "Seçili kullanıcılara oluşturma, düzenleme ve silme izni ver",
"settings_operator_write_desc": "Kapalı: içeriği yalnızca yöneticiler değiştirebilir. Açık: aşağıda seçilen kullanıcılar da tam erişim alır.",
"no_non_admin_users": "Yönetici olmayan kullanıcı bulunamadı. Ayarlar → Kişiler bölümünden ekleyin.",
@@ -788,11 +799,16 @@
"on_complete_action_desc": "Görev tamamlandığında bir HA servisi çağırır — örn. cihazdaki bir sayacı sıfırlar.",
"on_complete_action_target": "Hedef varlık",
"on_complete_action_target_hint": "Not: varlık alan adı servisle eşleşmelidir — örn. 'button.press' yalnızca button.* üzerinde, 'counter.increment' yalnızca counter.* üzerinde, 'input_button.press' yalnızca input_button.* üzerinde çalışır. Eşleşmezse eylem sessizce başarısız olur (HA günlüğü: 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Bu eylemin bu alanın gösterdiğinden fazla hedefi var: {targets}. Olduğu gibi korunur — burada başka bir varlık seçmek tüm hedefi değiştirir.",
"on_complete_action_skip_auto": "Görev kendiliğinden tamamlandığında değil (sensörü düzeldi)",
"on_complete_action_data": "Veri (JSON, isteğe bağlı)",
"on_complete_action_test": "Yapılandırmayı doğrula",
"on_complete_action_test_success": "✓ Yapılandırma geçerli (eylem yalnızca görev tamamlandığında çalışır)",
"on_complete_action_test_failed": "Başarısız",
"action_test_bad_format": "Geçersiz hizmet biçimi ('domain.service' bekleniyordu)",
"action_test_unknown_service": "\"{service}\" hizmeti Home Assistant'ta kayıtlı değil. Yazımı ve onu sağlayan entegrasyonun yüklü olduğunu kontrol edin.",
"action_test_domain_mismatch": "\"{service}\" hizmeti yalnızca {domain}.* varlıklarında çalışır; \"{entity}\" varlığı {entity_domain}.* alanındadır — varlığın alanına uyan bir hizmet seçin (ör. {suggestion})",
"action_test_entity_missing": "Hedef varlık \"{entity}\" Home Assistant'ta bulunamadı — yeniden adlandırılmış veya entegrasyonu kaldırılmış olabilir.",
"quick_complete_defaults_title": "Hızlı tamamlama varsayılanları (QR taramaları için, isteğe bağlı)",
"quick_complete_defaults_desc": "Hızlı tamamlama QR taramaları için önceden ayarlı değerler. Bunlar olmadan QR, tamamlama penceresini açar.",
"quick_complete_defaults_notes": "Notlar",
@@ -850,6 +866,7 @@
"doc_too_large": "Dosya çok büyük (en fazla 25 MB).",
"doc_upload_failed": "Yükleme başarısız oldu.",
"docs_archive_too_large": "Arşiv çok büyük (en fazla {max}).",
"docs_archive_no_manifest": "Bu ZIP'te manifest.json yok, bu yüzden geri yükleme bir dosyanın hangi belgeye ait olduğunu bilemez. Yeniden sıkıştırırken manifest.json dosyasını klasörlerin yanında tutun.",
"completion_photos_optional": "Tamamlama fotoğrafları (isteğe bağlı)",
"completion_photos": "Tamamlama fotoğrafları",
"choose_photos": "Fotoğraf seç",
@@ -910,6 +927,11 @@
"paused": "Duraklatıldı",
"pause_object": "Duraklat",
"resume_object": "Devam ettir",
"pause_task": "Duraklat",
"resume_task": "Devam ettir",
"pause_task_prompt": "Bu görevin planını dondurur — siz devam ettirene kadar vadesi gelmez ve bildirim göndermez; devam ettirince o günden itibaren yeni bir döngü başlar. Nesnenin diğer görevleri çalışmaya devam eder. İsteğe bağlı olarak kendiliğinden devam edeceği bir tarih seçin.",
"task_paused": "Görev duraklatıldı",
"task_resumed": "Görev devam ettirildi — planı yeniden başladı",
"pause_until_prompt": "Bu nesnenin zamanlamalarını dondurur — devam ettirilene kadar hiçbir şeyin vadesi gelmez ve bildirim gönderilmez. İsteğe bağlı olarak bir otomatik devam tarihi ayarlayın.",
"pause_until_label": "Devam tarihi (isteğe bağlı)",
"object_paused": "Nesne duraklatıldı",
@@ -919,7 +941,18 @@
"replace_object": "Değiştir…",
"replace_object_prompt": "Bu nesneyi emekliye ayırın ve bir halef oluşturun. Geçmiş ve maliyetler eski nesnede arşivli kalır; görevler ve belgeler yenisine taşınır, sayaçlar sıfırdan başlar.",
"replace_name_label": "Yeni nesne adı",
"replace_device_heading": "Home Assistant'taki cihaz",
"replace_device_keep": "Öncekiyle aynı cihaz ({device})",
"replace_device_keep_hint": "Yeni cihaz henüz Home Assistant'ta değilse de bunu seç: eklendiğinde nesnenin ayarlarında cihaz olarak seç — sensör tetikleyicileri de taşınır.",
"replace_device_other": "Yeni cihaz başka bir cihaz",
"replace_device_pick": "Yeni cihazın cihazı",
"replace_device_other_hint": "Sensör tetikleyicileri, tamamlama eylemleri ve sayaç sıfırlama o cihazın karşılık gelen varlıklarına taşınır.",
"replace_device_none": "Cihaz yok",
"object_replaced": "Nesne değiştirildi — halefi oluşturuldu",
"device_swap_moved": "{count} sensör bağlantısı yeni cihaza taşındı.",
"device_swap_moved_one": "Bir sensör bağlantısı yeni cihaza taşındı.",
"device_swap_unmatched": "{count} eşleştirilemedi — bu görevlerin sensör tetikleyicilerini kontrol et.",
"device_swap_unmatched_one": "Biri eşleştirilemedi — o görevin sensör tetikleyicisini kontrol et.",
"reading_unit_label": "Okuma birimi (örn. kWh, m³)",
"reading_unit_help": "Bu görevi tamamlarken kaydedilen değerin yanında gösterilir.",
"reading_value_label": "Okuma değeri",
@@ -1016,6 +1049,9 @@
"part_stock": "Stok",
"part_reorder_threshold": "Sipariş eşiği",
"part_restock_quantity": "Stok yenileme miktarı",
"part_package_size": "Paket boyutu (birim)",
"part_restock_packages": "Yeniden stok miktarı (paket)",
"part_package_hint": "Paketle alınır, birimle kullanılır: stok, görev başına kullanım ve yeniden sipariş eşiği birim sayar; yeniden stok miktarı ve fiyat paket başınadır.",
"part_auto_buy": "Azaldığında otomatik satın alma görevi oluştur",
"part_restock": "Stoku ayarla",
"parts_used_by": "Kullanan",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Son tamamlamadan bu yana kullanımı sayar: +{target} sonrasında vadesi gelir; sayım her tamamlamadan sonra yeniden başlar.",
"trigger_hint_counter_abs": "Sensör {target} değerine ulaştığında görevin vadesi gelir.",
"trigger_hint_runtime": "Görevin vadesi, toplam {hours} saatlik çalışma süresinden sonra gelir; sayaç her tamamlamadan sonra yeniden başlar.",
"trigger_hint_due_date": "Görev, sensörün bildirdiği tarihten {days} gün önce vadesi gelir.",
"trigger_hint_due_date_one": "Görev, sensörün bildirdiği tarihten bir gün önce vadesi gelir.",
"trigger_hint_due_date_same_day": "Görevin vadesi sensörün bildirdiği tarihte gelir.",
"trigger_hint_due_date_now": "Bildirilen tarih: {date}.",
"trigger_hint_state_change": "Görevin vadesi {count} durum değişikliğinden sonra gelir.",
"trigger_hint_state_change_to": "Görevin vadesi, “{state}” durumuna {count} geçişten sonra gelir.",
"trigger_hint_state_now": "Mevcut durum: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Yalnızca seçili kayıtlı görünümün etiket/kullanıcı filtreleriyle eşleşen görevler hatırlatma gönderir. Görünümün durum, sıralama ve gruplaması burada dikkate alınmaz.",
"settings_notify_completed": "Tamamlama bildirimleri",
"settings_notify_completed_hint": "Bir görev tamamlandığında ev halkına nedeni ve kimin yaptığıyla birlikte haber verir. \"Yalnızca otomatik\" sadece kimsenin yerinde yapmadığı tamamlamaları bildirir: sensör toparlanması, alışveriş listesinde işaretleme, bir otomasyonun servis çağrısı. Yalnızca ev servisine gider; sessiz saatler ve günlük sınır geçerlidir.",
"settings_title_style": "Bildirim başlığı stili",
"settings_title_style_hint": "Bildirimin BAŞLIĞI olarak ne görüneceği. Varsayılan, duruma özgü metni korur (örn. 'Bakım gecikti!'). Nesne adı / Görev adı, telefon bildirimleri üst üste yığıldığında yardımcı olur.",
"title_style_default": "Varsayılan (duruma özgü başlık)",
"title_style_object_name": "Başlık olarak nesne adı",
"title_style_task_name": "Başlık olarak görev adı",
"notify_completed_off": "Kapalı",
"notify_completed_automatic": "Yalnızca otomatik tamamlamalar",
"notify_completed_all": "Tüm tamamlamalar",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON veya YAML, Jinja kullanılabilir; notify çağrısının data alanına birleştirilir (sizin anahtarlarınız öncelikli). Değişkenler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test gönder düğmesiyle deneyin.",
"card_saved_view": "Kayıtlı görünüm",
"card_saved_view_none": "Yok",
"card_saved_view_help": "Görünümün durum, kullanıcı ve etiket filtrelerini yukarıdaki filtrelerin üzerine uygular. Görünümün sıralaması ve gruplaması panel görüntüleme ayarlarıdır ve kartta uygulanmaz.",
"card_saved_view_help": "Görünümün durum, kullanıcı, etiket ve öncelik filtrelerini yukarıdaki filtrelerin üzerine uygular. Görünümün sıralaması ve gruplaması panel görüntüleme ayarlarıdır ve kartta uygulanmaz.",
"panel_card_tab": "Şu sekmede aç",
"panel_card_tab_default": "Hatırlanan sekme (paneldeki gibi)",
"panel_card_view": "Kayıtlı görünümle aç",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Simgelere geri dön",
"battery_fleet_forecast_overdue": "Öngörülen tarih geçti — pil hâlâ sağlıklı görünüyor. Değiştirdiyseniz değişimi kaydedin; aksi halde tahmin yanılmış demektir.",
"cost_from_parts": "Parçalardan ≈ {amount} kullan",
"cost_parts_booked": "Kullanılan parçalar ≈ {amount} otomatik kaydedilir — yalnızca ek maliyetleri gir.",
"cost_parts_info": "Kullanılan parçalar ≈ {amount} — satın alındığında sayıldı, burada değil.",
"cost_purchase_use_hint": "Ödenen fiyat parçanın fiyatı olur — yedek parçalar kullanıldığında sayılır.",
"history_parts_counted": "Parçalar: {amount}",
"history_parts_counted_hint": "Kullanılan parçaların değeri — gider olarak sayıldı",
"history_parts_info": "Parçalar ≈ {amount}",
"history_parts_info_hint": "Kullanılan parçaların değeri — yalnızca bilgi; para satın alındığında sayıldı",
"history_purchase_stock": "{amount} karşılığında alındı (kullanıldığında sayılır)",
"dismiss": "Kapat",
"gs_label": "Başlarken — kurulumunuz büyüdükçe bu ipuçları kaybolur",
"gs_setups_chip": "Önerilen kurulumlar {n} cihaz buldu (hazır tetikleyicilerle)",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "İki hafta (14 gün)",
"cal_editor_window_month": "Ay (30 gün, varsayılan)",
"cal_editor_window_year": "Yıl (365 gün, boş günler gizli)",
"cal_editor_window_past_30": "Son 30 gün (geçmiş)",
"cal_editor_window_past_90": "Son 90 gün (geçmiş)",
"cal_editor_show_chips": "Pencere seçeneklerini kartta göster",
"cal_editor_chips_hint": "Kart, zaten pencere seçici görevi gören bir strateji görünümüne gömülüyse seçenekleri gizleyin.",
"cal_editor_show_user_filter": "Kullanıcı filtresini göster",
@@ -36,6 +36,8 @@
"reset": "Скинути",
"snooze": "Відкласти",
"snoozed": "Відкладено",
"snoozed_for": "Нагадування вимкнено на {hours} год — до {until}",
"snoozed_for_one": "Нагадування вимкнено на годину — до {until}",
"cancel": "Скасувати",
"bulk_select": "Вибрати",
"bulk_select_all": "Вибрати все",
@@ -61,7 +63,10 @@
"counter": "Лічильник",
"state_change": "Зміна стану",
"runtime": "Напрацювання",
"trigger_type_due_date": "Дата від датчика",
"runtime_hours": "Цільове напрацювання (години)",
"days_before": "Днів до дати",
"days_before_help": "Для датчика, що повідомляє термін обслуговування (дата або мітка часу). 0 = у сам день. Власна кнопка «виконано» пристрою переносить дату вперед і знімає тригер — призначте її дією під час виконання.",
"target_value": "Цільове значення",
"target_changes": "Цільова кількість змін",
"for_minutes": "Протягом (хвилин)",
@@ -232,6 +237,8 @@
"model_optional": "Модель (необов'язково)",
"serial_number_optional": "Серійний номер (необов'язково)",
"serial_number_label": "С/Н",
"object_replaces": "Попередник",
"object_replaced_by": "Наступник",
"documentation_url_label": "Посібник",
"object_notes_label": "Примітки",
"last_performed_optional": "Останнé виконання (необов'язково)",
@@ -420,7 +427,7 @@
"qr_action_view": "Переглянути",
"qr_action_complete": "Позначити обслуговування виконаним",
"qr_action_quick_complete": "Швидке завершення — без діалогу",
"qr_print_quick_none": "Для кодів швидкого завершення завдання має мати стандартні значення швидкого завершення (вікно завдання → Стандартні значення швидкого завершення) — жодне завдання вибраних об'єктів їх ще не має.",
"qr_print_quick_none": "Для кодів швидкого завершення завдання має мати стандартні значення швидкого завершення (Налаштування → Розширені функції → Дії при завершенні; вікно завдання → Стандартні значення швидкого завершення) — жодне завдання вибраних об'єктів їх ще не має.",
"qr_url_mode": "Тип посилання",
"qr_mode_companion": "Companion App",
"qr_mode_local": "Локальний (mDNS)",
@@ -576,8 +583,6 @@
"settings_features_desc": "Увімкніть або вимкніть розширені функції. Вимкнення приховує їх з інтерфейсу, але не видаляє дані.",
"feat_adaptive": "Адаптивне планування",
"feat_adaptive_desc": "Навчатися оптимальним інтервалам з історії обслуговування",
"feat_predictions": "Прогнози за сенсорами",
"feat_predictions_desc": "Прогнозувати дати спрацювання за деградацією сенсора",
"feat_seasonal": "Сезонні корекції",
"feat_seasonal_desc": "Коригувати інтервали на основі сезонних закономірностей",
"feat_environmental": "Кореляція з довкіллям",
@@ -669,6 +674,10 @@
"settings_currency": "Валюта",
"settings_currency_decimals": "Знаків після коми в сумах",
"settings_currency_decimals_hint": "Скільки знаків після коми показує кожна сума: KPI, бюджети, витрати у списках і звітах, сповіщення про бюджет. 0 = цілі числа.",
"settings_parts_cost_mode": "Запчастини вважаються витратою",
"settings_parts_cost_purchase": "Під час купівлі",
"settings_parts_cost_use": "Під час використання",
"settings_parts_cost_mode_hint": "Під час купівлі: враховується вартість нагадування про купівлю, а використані в роботі запчастини показуються для довідки. Під час використання: кожне виконання враховує вартість використаних запчастин, а купівля задає ціну запчастини замість обліку. Діє для виконань відтепер — історія зберігає попередній облік.",
"settings_budget_monthly": "Щомісячний бюджет",
"settings_budget_yearly": "Щорічний бюджет",
"settings_budget_alerts": "Сповіщення про бюджет",
@@ -681,6 +690,7 @@
"settings_import_placeholder": "Вставте вміст JSON або CSV сюди…",
"settings_import_btn": "Імпортувати",
"settings_import_success": "{count} об'єктів успішно імпортовано.",
"settings_import_unmatched_users": "Не знайдено в цьому екземплярі: {names}. Їхні призначення завдань видалено — створіть у Home Assistant людей з такими самими іменами й імпортуйте знову, щоб їх зберегти.",
"settings_export_success": "Експорт завантажено.",
"settings_saved": "Налаштування збережено.",
"settings_include_history": "Включити історію",
@@ -746,7 +756,7 @@
"has_overdue": "Прострочені завдання",
"object": "Об'єкт",
"settings_panel_access": "Доступ до панелі",
"settings_panel_access_desc": "Адміністратори завжди мають повний доступ. Щоб делегувати створення, редагування та видалення певним не-адмін користувачам, увімкніть це та виберіть їх нижче — інші бачать лише Виконати та Пропустити.",
"settings_panel_access_desc": "Адміністратори завжди мають повний доступ. Щоб делегувати створення, редагування та видалення певним не-адмін користувачам, увімкніть це та виберіть їх нижче — іншим доступні лише «Виконати», «Пропустити», «Скинути» та «Відкласти».",
"settings_operator_write": "Дозволити вибраним користувачам створювати, редагувати та видаляти",
"settings_operator_write_desc": "Вимкнено: лише адміністратори можуть змінювати вміст. Увімкнено: вибрані користувачі нижче також отримують повний доступ.",
"no_non_admin_users": "Не знайдено не-адмін користувачів. Додайте їх у Налаштуваннях → Особи.",
@@ -760,6 +770,10 @@
"on_complete_action_test": "Тестувати дію",
"on_complete_action_test_success": "Успіх",
"on_complete_action_test_failed": "Помилка",
"action_test_bad_format": "Неправильний формат служби (очікується 'domain.service')",
"action_test_unknown_service": "Службу «{service}» не зареєстровано в Home Assistant. Перевірте написання і те, що інтеграцію, яка її надає, завантажено.",
"action_test_domain_mismatch": "Служба «{service}» працює лише із сутностями {domain}.*; сутність «{entity}» належить до {entity_domain}.* — виберіть службу для домену цієї сутності (наприклад, {suggestion})",
"action_test_entity_missing": "Цільову сутність «{entity}» не знайдено в Home Assistant — можливо, її перейменовано або видалено її інтеграцію.",
"quick_complete_defaults_title": "Стандартні значення швидкого завершення (для QR-сканів, опційно)",
"quick_complete_defaults_desc": "Попередньо встановлені значення для QR швидкого завершення. Без них QR відкриває діалог.",
"quick_complete_defaults_notes": "Нотатки",
@@ -795,6 +809,7 @@
"groups_manage_tasks": "Керування призначеннями завдань…",
"groups_open_panel": "Відкрити на панелі",
"on_complete_action_target_hint": "Примітка: домен сутності має відповідати сервісу — напр. 'button.press' працює лише з button.*, 'counter.increment' лише з counter.*, 'input_button.press' лише з input_button.* тощо. У разі невідповідності дія мовчки не виконається (HA запише в журнал 'Referenced entities ... missing or not currently available').",
"on_complete_action_target_more": "Ця дія має більше цілей, ніж показує це поле: {targets}. Вони зберігаються як є — вибір тут іншої сутності замінить усю ціль.",
"on_complete_action_skip_auto": "Не коли завдання виконується саме (датчик відновився)",
"show_all_objects": "Показати всі об'єкти",
"show_all_tasks": "Скинути фільтр — показати всі завдання",
@@ -844,6 +859,7 @@
"doc_too_large": "Файл завеликий (макс. 25 МБ).",
"doc_upload_failed": "Не вдалося завантажити.",
"docs_archive_too_large": "Архів завеликий (макс. {max}).",
"docs_archive_no_manifest": "У цьому ZIP немає manifest.json, тому під час відновлення невідомо, до якого документа належить файл. Під час повторного пакування залишайте manifest.json поруч із теками.",
"completion_photos_optional": "Фото виконання (необов'язково)",
"completion_photos": "Фото виконання",
"choose_photos": "Вибрати фото",
@@ -904,6 +920,11 @@
"paused": "Призупинено",
"pause_object": "Призупинити",
"resume_object": "Відновити",
"pause_task": "Призупинити",
"resume_task": "Відновити",
"pause_task_prompt": "Заморожує розклад цього завдання — воно не стане актуальним і не сповіщатиме, доки ви його не відновите; відновлення починає новий цикл з цього дня. Інші завдання об'єкта працюють далі. Можна вказати дату автоматичного відновлення.",
"task_paused": "Завдання призупинено",
"task_resumed": "Завдання відновлено — розклад почався заново",
"pause_until_prompt": "Заморозити розклади цього об'єкта — нічого не настає і не сповіщає до відновлення. За бажанням укажіть дату автоматичного відновлення.",
"pause_until_label": "Відновити (необов'язково)",
"object_paused": "Об'єкт призупинено",
@@ -913,7 +934,18 @@
"replace_object": "Замінити…",
"replace_object_prompt": "Списати цей об'єкт і створити наступника. Історія та витрати лишаються в архіві старого; завдання й документи переходять до нового, лічильники починаються заново.",
"replace_name_label": "Ім'я наступника",
"replace_device_heading": "Пристрій у Home Assistant",
"replace_device_keep": "Той самий пристрій, що й раніше ({device})",
"replace_device_keep_hint": "Цей варіант підходить і тоді, коли нового пристрою ще немає в Home Assistant: після додавання вибери його в налаштуваннях об'єкта — тригери датчиків перейдуть на нього.",
"replace_device_other": "Новий пристрій — інший пристрій",
"replace_device_pick": "Пристрій нового екземпляра",
"replace_device_other_hint": "Тригери датчиків, дії після виконання та скидання лічильника перейдуть на відповідні сутності цього пристрою.",
"replace_device_none": "Без пристрою",
"object_replaced": "Об'єкт замінено — наступника створено",
"device_swap_moved": "Перенесено зв'язків із датчиками на новий пристрій: {count}.",
"device_swap_moved_one": "Один зв'язок із датчиком перенесено на новий пристрій.",
"device_swap_unmatched": "Не вдалося зіставити: {count} — перевір тригери датчиків цих завдань.",
"device_swap_unmatched_one": "Один зв'язок не вдалося зіставити — перевір тригер датчика цього завдання.",
"reading_unit_label": "Одиниця показання (напр. кВт·год, м³)",
"reading_unit_help": "Показується поруч із записаним значенням під час виконання цієї задачі.",
"reading_value_label": "Показання",
@@ -1010,6 +1042,9 @@
"part_stock": "Запас",
"part_reorder_threshold": "Поріг замовлення",
"part_restock_quantity": "Кількість поповнення",
"part_package_size": "Розмір упаковки (одиниць)",
"part_restock_packages": "Кількість для поповнення (упаковок)",
"part_package_hint": "Купується упаковками, витрачається одиницями: запас, витрата на завдання та поріг дозамовлення рахуються в одиницях; кількість для поповнення та ціна — за упаковку.",
"part_auto_buy": "Завдання купівлі при малому запасі",
"part_restock": "Змінити запас",
"parts_used_by": "Використовується",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "Не вдалося завантажити запчастини цього об'єкта — параметри витрати запчастин зараз недоступні.",
"settings_export_selection": "Обмежити вибраними об'єктами (необов'язково)",
"settings_docs_archive": "Архів документів (з файлами)",
"settings_docs_archive_hint": "Експорти JSON/YAML/CSV містять лише налаштування. Цей ZIP містить вміст завантажених файлів, щоб відновлення було повним.",
"settings_docs_archive_hint": "Експорт у JSON і YAML перелічує ваші документи, але не містить їхніх файлів; CSV документів не включає. У цьому ZIP завантажені файли — відновіть його після імпорту JSON, і нічого не зникне.",
"settings_docs_export_btn": "Завантажити ZIP документів",
"settings_docs_import_btn": "Відновити ZIP документів",
"settings_docs_import_success": "Відновлено: {blobs} файлів, {docs} документів",
"settings_docs_import_missing": "Пропущено: {n} — їхніх файлів не було в ZIP",
"adopt_problem_button": "Прийняти сенсори проблем",
"adopt_problem_title": "Прийняти сенсори проблем",
"adopt_problem_hint": "Перетворює сенсори проблем HA (помилки принтера, попередження фільтра, низький заряд) на завдання обслуговування, які спрацьовують, поки проблема активна, і завершуються самі після її усунення.",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "Рахує використання з останнього виконання: термін після +{target}; лічильник починається заново після кожного виконання.",
"trigger_hint_counter_abs": "Завдання настане, коли датчик досягне {target}.",
"trigger_hint_runtime": "Завдання настане після {hours} год сумарного напрацювання; лічильник починається заново після кожного виконання.",
"trigger_hint_due_date": "Завдання стане актуальним за {days} дн. до дати, яку повідомляє датчик.",
"trigger_hint_due_date_one": "Завдання стане актуальним за один день до дати, яку повідомляє датчик.",
"trigger_hint_due_date_same_day": "Завдання стане актуальним у дату, яку повідомляє датчик.",
"trigger_hint_due_date_now": "Повідомлена дата: {date}.",
"trigger_hint_state_change": "Завдання настане після {count} зміни(-) стану.",
"trigger_hint_state_change_to": "Завдання настане після {count} переходу(-ів) у «{state}».",
"trigger_hint_state_now": "Поточний стан: {value}.",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "Нагадування надсилають лише завдання, що відповідають фільтрам мітки/користувача обраного подання. Статус, сортування та групування подання тут ігноруються.",
"settings_notify_completed": "Сповіщення про виконання",
"settings_notify_completed_hint": "Повідомлення для всіх домашніх, коли завдання виконано, з причиною та виконавцем. «Лише автоматичні» повідомляє тільки про виконання, які ніхто не робив на місці: відновлення датчика, позначка у списку покупок, виклик служби автоматизацією. Лише на спільний сервіс; тихі години та денний ліміт діють.",
"settings_title_style": "Стиль заголовка сповіщення",
"settings_title_style_hint": "Що відображається як ЗАГОЛОВОК сповіщення. За замовчуванням — текст за статусом (наприклад, «Обслуговування прострочено!»). Ім'я об'єкта / завдання допомагає, коли телефон складає сповіщення в стек.",
"title_style_default": "За замовчуванням (заголовок за статусом)",
"title_style_object_name": "Ім'я об'єкта як заголовок",
"title_style_task_name": "Ім'я завдання як заголовок",
"notify_completed_off": "Вимк.",
"notify_completed_automatic": "Лише автоматичні виконання",
"notify_completed_all": "Усі виконання",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON або YAML, Jinja дозволено; додається до data виклику notify (ваші ключі мають перевагу). Змінні: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Перевірте кнопкою «Надіслати тест».",
"card_saved_view": "Збережене подання",
"card_saved_view_none": "Немає",
"card_saved_view_help": "Застосовує фільтри статусу, користувача та мітки подання додатково до фільтрів вище. Сортування та групування подання — налаштування відображення панелі й на картці не застосовуються.",
"card_saved_view_help": "Застосовує фільтри статусу, користувача, мітки та пріоритету подання додатково до фільтрів вище. Сортування та групування подання — налаштування відображення панелі й на картці не застосовуються.",
"panel_card_tab": "Відкривати на вкладці",
"panel_card_tab_default": "Запам'ятована вкладка (як у панелі)",
"panel_card_view": "Відкривати зі збереженим виглядом",
@@ -1189,6 +1234,14 @@
"row_actions_back": "Повернути значки",
"battery_fleet_forecast_overdue": "Прогнозована дата минула — батарея й далі повідомляє про добрий стан. Якщо ви її замінили, зафіксуйте заміну; інакше прогноз не справдився.",
"cost_from_parts": "Узяти ≈ {amount} із запчастин",
"cost_parts_booked": "Використані запчастини ≈ {amount} враховуються автоматично — вкажи лише додаткові витрати.",
"cost_parts_info": "Використані запчастини ≈ {amount} — враховані під час купівлі, не тут.",
"cost_purchase_use_hint": "Сплачена ціна стає ціною запчастини — запчастини враховуються під час використання.",
"history_parts_counted": "Запчастини: {amount}",
"history_parts_counted_hint": "Вартість використаних запчастин — врахована як витрата",
"history_parts_info": "Запчастини ≈ {amount}",
"history_parts_info_hint": "Вартість використаних запчастин — лише для довідки; гроші враховано під час купівлі",
"history_purchase_stock": "Куплено за {amount} (врахується під час використання)",
"dismiss": "Приховати",
"gs_label": "Перші кроки — ці підказки зникнуть у міру зростання налаштування",
"gs_setups_chip": "Рекомендовані налаштування: знайдено {n} пристроїв із готовими тригерами",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "Два тижні (14 днів)",
"cal_editor_window_month": "Місяць (30 днів, типово)",
"cal_editor_window_year": "Рік (365 днів, порожні дні приховано)",
"cal_editor_window_past_30": "Останні 30 днів (історія)",
"cal_editor_window_past_90": "Останні 90 днів (історія)",
"cal_editor_show_chips": "Показувати чипи вікна в картці",
"cal_editor_chips_hint": "Приховайте чипи, якщо картка вбудована в стратегію, що вже слугує вибором вікна.",
"cal_editor_show_user_filter": "Показувати фільтр користувача",
@@ -38,6 +38,8 @@
"reset": "重置",
"snooze": "稍后提醒",
"snoozed": "已推迟",
"snoozed_for": "提醒已静音 {hours} 小时——直到 {until}",
"snoozed_for_one": "提醒已静音 1 小时——直到 {until}",
"cancel": "取消",
"bulk_select": "选择",
"bulk_select_all": "全选",
@@ -63,7 +65,10 @@
"counter": "计数器",
"state_change": "状态变化",
"runtime": "运行时间",
"trigger_type_due_date": "传感器报告的日期",
"runtime_hours": "目标运行时长 (小时)",
"days_before": "日期前天数",
"days_before_help": "适用于报告维护到期时间(日期或时间戳)的传感器。0 = 当天。设备自带的“已完成”按钮会把日期后移,从而解除触发器——把它设为完成时的动作。",
"target_value": "目标值",
"target_changes": "目标变化次数",
"for_minutes": "持续 (分钟)",
@@ -235,6 +240,8 @@
"model_optional": "型号 (可选)",
"serial_number_optional": "序列号 (可选)",
"serial_number_label": "序列号",
"object_replaces": "替换前",
"object_replaced_by": "替换后",
"documentation_url_label": "手册",
"object_notes_label": "备注",
"sort_due_date": "到期时间",
@@ -422,7 +429,7 @@
"qr_action_view": "查看维护信息",
"qr_action_complete": "标记维护为已完成",
"qr_action_quick_complete": "快速完成 — 无对话框",
"qr_print_quick_none": "快速完成二维码需要任务设置快速完成默认值(任务对话框 → 快速完成默认值)— 所选对象的任务尚未设置。",
"qr_print_quick_none": "快速完成二维码需要任务设置快速完成默认值(设置 → 高级功能 → 完成动作; 任务对话框 → 快速完成默认值)— 所选对象的任务尚未设置。",
"qr_url_mode": "链接类型",
"qr_mode_companion": "Companion App",
"qr_mode_local": "本地 (mDNS)",
@@ -578,8 +585,6 @@
"settings_features_desc": "启用或禁用高级功能。禁用将从界面隐藏相关功能,但不会删除数据。",
"feat_adaptive": "自适应计划",
"feat_adaptive_desc": "根据维护历史学习最佳间隔",
"feat_predictions": "传感器预测",
"feat_predictions_desc": "根据传感器损耗趋势预测触发日期",
"feat_seasonal": "季节性调整",
"feat_seasonal_desc": "根据季节性模式自动调整间隔",
"feat_environmental": "环境关联",
@@ -671,6 +676,10 @@
"settings_currency": "货币单位",
"settings_currency_decimals": "金额小数位数",
"settings_currency_decimals_hint": "每个金额显示几位小数:KPI、预算、列表和报告中的费用、预算提醒。0 = 整数。",
"settings_parts_cost_mode": "备件计入支出",
"settings_parts_cost_purchase": "购买时",
"settings_parts_cost_use": "使用时",
"settings_parts_cost_mode_hint": "购买时:计入购买提醒的费用,任务用掉的备件仅作参考显示。使用时:每次完成都会计入所用备件的价值,购买则只更新备件价格而不计入。从现在起的完成记录生效——历史记录保留原来的计法。",
"settings_budget_monthly": "月度预算",
"settings_budget_yearly": "年度预算",
"settings_budget_alerts": "预算警报",
@@ -683,6 +692,7 @@
"settings_import_placeholder": "在此粘贴 JSON 或 CSV 内容…",
"settings_import_btn": "导入",
"settings_import_success": "成功导入 {count} 个维护项。",
"settings_import_unmatched_users": "此实例中未找到:{names}。他们的任务分配已被移除——请在 Home Assistant 中创建同名人员后重新导入以保留分配。",
"settings_export_success": "导出已下载。",
"settings_saved": "设置已保存。",
"settings_include_history": "包含历史记录",
@@ -771,7 +781,7 @@
"has_overdue": "有超期任务",
"object": "设备",
"settings_panel_access": "面板访问权限",
"settings_panel_access_desc": "管理员始终拥有完整访问权限。要将创建、编辑和删除权限委派给特定的普通用户,请开启此项并在下方选择他们 — 其他用户将仅能看到“完成”和“跳过”。",
"settings_panel_access_desc": "管理员始终拥有完整访问权限。要将创建、编辑和删除权限委派给特定的普通用户,请开启此项并在下方选择他们 — 其他用户仅能使用“完成”“跳过”“重置”“推迟”和“稍后提醒”。",
"settings_operator_write": "允许所选用户创建、编辑和删除",
"settings_operator_write_desc": "关闭时:仅管理员可修改内容。开启时:下方所选用户也获得完整访问权限。",
"no_non_admin_users": "未找到普通用户。请在“设置 -> 人员”中添加。",
@@ -782,11 +792,16 @@
"on_complete_action_desc": "任务完成时调用 HA 服务 — 例如重置设备上的物理计数器。",
"on_complete_action_target": "目标实体",
"on_complete_action_target_hint": "注意:实体的域必须与服务匹配 — 例如 'button.press' 仅适用于 button.* 实体,'counter.increment' 仅适用于 counter.* 实体等。如果不匹配,动作将静默失败。",
"on_complete_action_target_more": "此动作的目标多于此字段所显示的:{targets}。它们将保持不变——在此选择其他实体会替换整个目标。",
"on_complete_action_skip_auto": "任务自行完成时(其传感器已恢复)不触发",
"on_complete_action_data": "数据(JSON, 可选)",
"on_complete_action_test": "验证配置",
"on_complete_action_test_success": "✓ 配置有效(动作将在任务完成时触发)",
"on_complete_action_test_failed": "失败",
"action_test_bad_format": "服务格式无效(应为 'domain.service')",
"action_test_unknown_service": "服务“{service}”未在 Home Assistant 中注册。请检查拼写,并确认提供该服务的集成已加载。",
"action_test_domain_mismatch": "服务“{service}”只适用于 {domain}.* 实体;实体“{entity}”属于 {entity_domain}.*——请选择与该实体域相符的服务(例如 {suggestion})",
"action_test_entity_missing": "在 Home Assistant 中找不到目标实体“{entity}”——它可能已被重命名,或其集成已被移除。",
"quick_complete_defaults_title": "快速完成默认值(用于二维码扫描, 可选)",
"quick_complete_defaults_desc": "扫描快速完成二维码时使用的预设值。如果不设置,二维码将打开完成对话框。",
"quick_complete_defaults_notes": "备注",
@@ -844,6 +859,7 @@
"doc_too_large": "文件过大(最大 25 MB)。",
"doc_upload_failed": "上传失败。",
"docs_archive_too_large": "压缩包过大(最大 {max})。",
"docs_archive_no_manifest": "此 ZIP 中没有 manifest.json,因此恢复时无法判断文件属于哪个文档。重新压缩时请把 manifest.json 放在各文件夹旁边。",
"completion_photos_optional": "完成照片(可选)",
"completion_photos": "完成照片",
"choose_photos": "选择照片",
@@ -904,6 +920,11 @@
"paused": "已暂停",
"pause_object": "暂停",
"resume_object": "恢复",
"pause_task": "暂停",
"resume_task": "继续",
"pause_task_prompt": "冻结此任务的计划——在你恢复之前它不会到期,也不会通知;恢复时从当天开始新的周期。该对象的其他任务照常运行。可选:设置自动恢复的日期。",
"task_paused": "任务已暂停",
"task_resumed": "任务已恢复——计划重新开始",
"pause_until_prompt": "冻结此对象的日程——在恢复之前不会有任何到期或通知。可选设置自动恢复日期。",
"pause_until_label": "恢复日期(可选)",
"object_paused": "对象已暂停",
@@ -913,7 +934,18 @@
"replace_object": "更换…",
"replace_object_prompt": "让此对象退役并创建继任者。历史和费用留在旧对象的归档中;任务和文档转移到新对象,计数器重新开始。",
"replace_name_label": "继任者名称",
"replace_device_heading": "Home Assistant 中的设备",
"replace_device_keep": "与之前相同的设备({device})",
"replace_device_keep_hint": "如果新设备尚未接入 Home Assistant,也选这一项:接入后在对象设置中将其选为设备,传感器触发器随之迁移。",
"replace_device_other": "新设备是另一个设备",
"replace_device_pick": "新设备对应的设备",
"replace_device_other_hint": "传感器触发器、完成后操作和计数器重置会迁移到该设备上对应的实体。",
"replace_device_none": "无设备",
"object_replaced": "对象已更换——已创建继任者",
"device_swap_moved": "已有 {count} 个传感器关联迁移到新设备。",
"device_swap_moved_one": "已有 1 个传感器关联迁移到新设备。",
"device_swap_unmatched": "有 {count} 个无法匹配——请检查这些任务的传感器触发器。",
"device_swap_unmatched_one": "有 1 个无法匹配——请检查该任务的传感器触发器。",
"reading_unit_label": "读数单位(如 kWh、m³)",
"reading_unit_help": "完成此任务时显示在记录值旁边。",
"reading_value_label": "读数值",
@@ -1010,6 +1042,9 @@
"part_stock": "库存",
"part_reorder_threshold": "补货阈值",
"part_restock_quantity": "补货数量",
"part_package_size": "包装规格(单位数)",
"part_restock_packages": "补货数量(包)",
"part_package_hint": "按包购买、按单位使用:库存、每项任务用量和补货阈值都按单位计;补货数量和价格按包计。",
"part_auto_buy": "库存低时自动建购买任务",
"part_restock": "调整库存",
"parts_used_by": "使用于",
@@ -1022,10 +1057,11 @@
"parts_load_failed": "无法加载此对象的配件 — 配件消耗选项暂时不可用。",
"settings_export_selection": "仅限所选对象(可选)",
"settings_docs_archive": "文档存档(含文件)",
"settings_docs_archive_hint": "JSON/YAML/CSV 导出仅包含设置。此 ZIP 包含已上传的文件内容,以便完整恢复。",
"settings_docs_archive_hint": "JSON 和 YAML 导出会列出你的文档,但不包含文件本身;CSV 不包含文档。此 ZIP 包含已上传的文件——导入 JSON 之后再恢复它,就不会缺少任何内容。",
"settings_docs_export_btn": "下载文档 ZIP",
"settings_docs_import_btn": "恢复文档 ZIP",
"settings_docs_import_success": "已恢复:{blobs} 个文件,{docs} 个文档",
"settings_docs_import_missing": "已跳过 {n} 个——ZIP 中没有它们的文件",
"adopt_problem_button": "采用问题传感器",
"adopt_problem_title": "采用问题传感器",
"adopt_problem_hint": "将 HA 问题传感器(打印机错误、滤网警告、电量低)转换为维护任务:问题存在时触发,问题解决后自动完成。",
@@ -1056,6 +1092,10 @@
"trigger_hint_counter_delta_edit": "从上次完成开始计算用量:+{target} 后到期;每次完成后重新计数。",
"trigger_hint_counter_abs": "当传感器达到 {target} 时任务到期。",
"trigger_hint_runtime": "累计运行 {hours} 小时后任务到期;每次完成后重新计数。",
"trigger_hint_due_date": "任务会在传感器报告的日期前 {days} 天到期。",
"trigger_hint_due_date_one": "任务会在传感器报告的日期前一天到期。",
"trigger_hint_due_date_same_day": "任务会在传感器报告的日期到期。",
"trigger_hint_due_date_now": "报告的日期:{date}。",
"trigger_hint_state_change": "经过 {count} 次状态变化后任务到期。",
"trigger_hint_state_change_to": "变为“{state}”共 {count} 次后任务到期。",
"trigger_hint_state_now": "当前状态:{value}。",
@@ -1067,6 +1107,11 @@
"settings_notify_scope_hint": "只有匹配所选视图的标签/用户筛选的任务才会发送提醒。视图的状态、排序和分组在此处被忽略。",
"settings_notify_completed": "完成通知",
"settings_notify_completed_hint": "任务完成时通知家庭成员,附上原因和执行者。“仅自动”只通报无人现场操作的完成:传感器恢复、购物清单勾选、自动化的服务调用。仅发送到家庭通知服务;免打扰时段和每日上限有效。",
"settings_title_style": "通知标题样式",
"settings_title_style_hint": "通知的标题显示内容。默认保持状态文本(如:‘维护已超期!’)。当手机通知堆叠时,‘维护项名称 / 任务名称’ 更有帮助。",
"title_style_default": "默认 (显示状态标题)",
"title_style_object_name": "使用维护项名称作为标题",
"title_style_task_name": "使用任务名称作为标题",
"notify_completed_off": "关闭",
"notify_completed_automatic": "仅自动完成",
"notify_completed_all": "所有完成",
@@ -1078,7 +1123,7 @@
"settings_notify_extra_data_hint": "JSON 或 YAML,可用 Jinja;合并进 notify 调用的 data(你的键优先)。变量:kind、status、priority、labels、notes、area_name、object_name、task_name、object_ref、task_ref、days_until_due、next_due、last_performed、responsible_user_id、sensor_entity_id、url、target、title、message。用“发送测试”按钮验证。",
"card_saved_view": "已保存的视图",
"card_saved_view_none": "无",
"card_saved_view_help": "在上述筛选之外再应用该视图的状态、用户和标签筛选。视图的排序和分组属于面板显示设置,不会应用到卡片上。",
"card_saved_view_help": "在上述筛选之外再应用该视图的状态、用户、标签和优先级筛选。视图的排序和分组属于面板显示设置,不会应用到卡片上。",
"panel_card_tab": "打开时的标签页",
"panel_card_tab_default": "记住的标签页(与面板一致)",
"panel_card_view": "打开时使用已保存的视图",
@@ -1189,6 +1234,14 @@
"row_actions_back": "改回图标",
"battery_fleet_forecast_overdue": "预测日期已过——电池仍报告状态良好。如果您已更换电池,请记录更换;否则说明预测有误。",
"cost_from_parts": "采用配件合计 ≈ {amount}",
"cost_parts_booked": "所用备件 ≈ {amount} 会自动计入——只需填写额外费用。",
"cost_parts_info": "所用备件 ≈ {amount}——已在购买时计入,此处不计。",
"cost_purchase_use_hint": "所付价格将成为该备件的价格——备件在使用时计入。",
"history_parts_counted": "备件:{amount}",
"history_parts_counted_hint": "所用备件的价值——已计入支出",
"history_parts_info": "备件 ≈ {amount}",
"history_parts_info_hint": "所用备件的价值——仅供参考;费用已在购买时计入",
"history_purchase_stock": "以 {amount} 购入(使用时计入)",
"dismiss": "忽略",
"gs_label": "入门提示——随着配置的完善,这些提示会自动消失",
"gs_setups_chip": "推荐配置发现 {n} 台设备(含预设触发器)",
@@ -1203,6 +1256,8 @@
"cal_editor_window_fortnight": "两周(14天)",
"cal_editor_window_month": "一个月(30天,默认)",
"cal_editor_window_year": "一年(365天,空白日折叠)",
"cal_editor_window_past_30": "过去 30 天(历史)",
"cal_editor_window_past_90": "过去 90 天(历史)",
"cal_editor_show_chips": "在卡片内显示时间窗标签",
"cal_editor_chips_hint": "当卡片嵌入已充当时间窗选择器的策略视图时,请隐藏标签。",
"cal_editor_show_user_filter": "显示用户筛选",
@@ -40,6 +40,7 @@ import {
import { calendarStyles } from "./calendar-styles";
import { syncLocaleFromHass, sharedStyles, currencySymbolOf, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, formatInterval, formatWeekday, formatMonth, langOf, formatCost, syncCurrencyDecimals} from "./styles";
import { registerCustomCard } from "./helpers/register-card";
import { healCardRegistry } from "./helpers/registry-heal";
import { loadHistoryEntryDraft } from "./helpers/history-draft";
import { canWrite } from "./helpers/permissions";
import { fetchSettingsOnce } from "./helpers/settings-cache";
@@ -107,15 +108,17 @@ export class MaintenanceCalendarCard extends LitElement {
setConfig(config: CalendarCardConfig): void {
this._config = { ...config };
if (config.past_days && [30, 90].includes(config.past_days)) {
this._pastDays = config.past_days as PastDays;
} else if (config.window_days && [7, 14, 30, 365].includes(config.window_days)) {
this._windowDays = config.window_days as WindowDays;
this._pastDays = 0;
}
if (typeof config.user_filter === "string") {
this._userFilter = config.user_filter;
}
// The config decides the window every time it changes: removing
// past_days in the editor left the card in its past view (audit
// 2026-09-29).
this._windowDays = config.window_days && [7, 14, 30, 365].includes(config.window_days)
? (config.window_days as WindowDays)
: 30;
this._pastDays = config.past_days && [30, 90].includes(config.past_days) ? (config.past_days as PastDays) : 0;
// Like the window above: the config decides the filters every time — the
// editor DELETES user_filter when it goes back to "All users", and a
// card kept its old filter (bug audit 2026-09-29).
this._userFilter = typeof config.user_filter === "string" ? config.user_filter : "";
if (typeof config.object_filter === "string") {
this._objectFilter = config.object_filter;
this._configuredObjects = [];
@@ -127,6 +130,9 @@ export class MaintenanceCalendarCard extends LitElement {
// card's filter_objects that #83 asked for.
this._objectFilter = values.length === 1 ? values[0] : "";
this._configuredObjects = values.length > 1 ? values : [];
} else {
this._objectFilter = "";
this._configuredObjects = [];
}
}
@@ -466,7 +472,7 @@ export class MaintenanceCalendarCard extends LitElement {
return html`
<ha-card .header=${title}>
${showChips || showUserFilter
${showChips || showUserFilter || showObjectFilter
? html`
<div class="cal-controls">
${showChips
@@ -557,6 +563,12 @@ const WINDOW_DAY_KEYS: Array<{ value: WindowDays; key: string }> = [
{ value: 30, key: "cal_editor_window_month" },
{ value: 365, key: "cal_editor_window_year" },
];
/** past_days had no editor control and silently beat the window picked
* here (audit 2026-09-29) — the past windows share the one dropdown. */
const PAST_DAY_KEYS: Array<{ value: PastDays; key: string }> = [
{ value: 30, key: "cal_editor_window_past_30" },
{ value: 90, key: "cal_editor_window_past_90" },
];
class MaintenanceCalendarCardEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -597,6 +609,24 @@ class MaintenanceCalendarCardEditor extends LitElement {
if (key === "user_filter" && value === "") {
delete (newConfig as unknown as Record<string, unknown>).user_filter;
}
this._emit(newConfig);
}
/** One dropdown, two keys: "past-N" writes past_days, a number writes
* window_days — each drops the other, since past_days wins in the card. */
private _windowChanged(value: string): void {
const next = { ...this._config } as unknown as Record<string, unknown>;
if (value.startsWith("past-")) {
next.past_days = Number(value.slice(5));
delete next.window_days;
} else {
next.window_days = Number(value);
delete next.past_days;
}
this._emit(next as unknown as CalendarCardConfig);
}
private _emit(newConfig: CalendarCardConfig): void {
this._config = newConfig;
this.dispatchEvent(
new CustomEvent("config-changed", {
@@ -609,7 +639,7 @@ class MaintenanceCalendarCardEditor extends LitElement {
render() {
const L = this._lang;
const currentWindow = this._config.window_days ?? 30;
const currentWindow = this._config.past_days ? `past-${this._config.past_days}` : `${this._config.window_days ?? 30}`;
const showChips = this._config.show_window_chips !== false;
const showUserFilter = this._config.show_user_filter !== false;
const userFilter = this._config.user_filter ?? "";
@@ -630,15 +660,15 @@ class MaintenanceCalendarCardEditor extends LitElement {
<label for="window">${t("cal_editor_window", L)}</label>
<select
id="window"
@change=${(e: Event) =>
this._valueChanged(
"window_days",
Number((e.target as HTMLSelectElement).value) as WindowDays,
)}
@change=${(e: Event) => this._windowChanged((e.target as HTMLSelectElement).value)}
>
${WINDOW_DAY_KEYS.map(
(o) =>
html`<option value="${o.value}" ?selected=${o.value === currentWindow}>${t(o.key, L)}</option>`,
html`<option value="${o.value}" ?selected=${`${o.value}` === currentWindow}>${t(o.key, L)}</option>`,
)}
${PAST_DAY_KEYS.map(
(o) =>
html`<option value="past-${o.value}" ?selected=${`past-${o.value}` === currentWindow}>${t(o.key, L)}</option>`,
)}
</select>
</div>
@@ -747,6 +777,9 @@ if (!customElements.get("maintenance-supporter-calendar-card-editor")) {
// The type MUST match the registered element tag exactly (custom:X → tag X),
// or the picker entry resolves to a non-existent element and the strategy's
// calendar mode throws a config error.
// HA's boot-time registry swap can strand these definitions (audit 2026-09-29).
healCardRegistry(["maintenance-supporter-calendar-card"], import.meta.url);
registerCustomCard({
type: "maintenance-supporter-calendar-card",
name: "Maintenance Supporter — Calendar",
@@ -58,8 +58,10 @@ export class MaintenanceSupporterCardEditor extends LitElement {
private _valueChanged(key: string, value: unknown): void {
const newConfig = { ...this._config, [key]: value };
// Drop empty arrays / empty strings so the saved YAML stays clean
if ((Array.isArray(value) && value.length === 0) || value === "") {
// Drop empty arrays / empty strings / cleared values so the saved YAML
// stays clean — "Follow the household setting" wrote `action_style:
// undefined` into the stored config (audit 2026-09-29).
if ((Array.isArray(value) && value.length === 0) || value === "" || value === undefined || value === null) {
delete (newConfig as Record<string, unknown>)[key];
}
this._config = newConfig;
@@ -8,6 +8,7 @@ import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, t, ensureLocale, isLoc
import { openSignedDocument } from "./helpers/document-url";
import { isSafeHttpUrl } from "./helpers/url";
import { registerCustomCard } from "./helpers/register-card";
import { healCardRegistry } from "./helpers/registry-heal";
import { localizePickerWhenReady } from "./helpers/picker-i18n";
import type {
HomeAssistant,
@@ -328,7 +329,9 @@ export class MaintenanceSupporterCard extends LitElement {
}
}
private get _flatTasks(): FlatTask[] {
/** Every task the card's filters (and saved view) select, sorted — the
* rows before the `max_items` cap, and what the header badges count. */
private get _selectedTasks(): FlatTask[] {
const tasks: FlatTask[] = [];
const {
filter_status,
@@ -339,7 +342,6 @@ export class MaintenanceSupporterCard extends LitElement {
filter_labels,
filter_priority,
filter_areas,
max_items,
} = this._config;
const entityFilter = entity_ids?.length ? new Set(entity_ids) : null;
const hasDueRange =
@@ -407,10 +409,6 @@ export class MaintenanceSupporterCard extends LitElement {
// Within a status, soonest-due first; tasks without a due date go last.
return (a.task.days_until_due ?? Infinity) - (b.task.days_until_due ?? Infinity);
});
if (max_items && max_items > 0) {
return tasks.slice(0, max_items);
}
return tasks;
}
@@ -434,6 +432,7 @@ export class MaintenanceSupporterCard extends LitElement {
lang: this._lang,
features: this._settings.features,
currencySymbol: currencySymbolOf(this._stats?.budget),
partsCostMode: this._settings.partsCostMode,
}),
this._lang,
);
@@ -441,15 +440,14 @@ export class MaintenanceSupporterCard extends LitElement {
/** Header badges from the LIVE object list — the statistics call ran once
* per load, so the subscription updated the rows while the badges kept
* the old counts (bug audit 2026-09-26). Same rule as the server's
* compute_status_counts: a task counts in the bucket of its status. */
private get _headerCounts(): { overdue: number; due_soon: number; triggered: number } {
* the old counts (bug audit 2026-09-26). A task counts in the bucket of
* its status, over the tasks the card selects: a card filtered to one
* room showed the whole home's counts (audit 2026-09-29). */
private _headerCounts(tasks: FlatTask[]): { overdue: number; due_soon: number; triggered: number } {
const counts = { overdue: 0, due_soon: 0, triggered: 0 };
for (const obj of this._objects) {
for (const task of obj.tasks) {
if (isActionableStatus(task.status)) {
counts[task.status] += 1;
}
for (const { task } of tasks) {
if (isActionableStatus(task.status)) {
counts[task.status] += 1;
}
}
return counts;
@@ -474,8 +472,10 @@ export class MaintenanceSupporterCard extends LitElement {
// Creating objects/tasks is write tier — the panel's canWrite rule.
const showCreate = showActions && canWrite(this.hass?.user, this._settings.access);
const compact = this._config.compact || false;
const tasks = this._flatTasks;
const s = this._objects.length || this._stats ? this._headerCounts : null;
const selected = this._selectedTasks;
const { max_items } = this._config;
const tasks = max_items && max_items > 0 ? selected.slice(0, max_items) : selected;
const s = this._objects.length || this._stats ? this._headerCounts(selected) : null;
return html`
<ha-card>
@@ -493,20 +493,20 @@ export class MaintenanceSupporterCard extends LitElement {
: nothing}
${showCreate
? html`
<mwc-icon-button
<ha-icon-button
class="hdr-add"
title="${t("new_object", L)}"
@click=${() => openCreateObjectDialog()}
>
<ha-icon icon="mdi:plus-box"></ha-icon>
</mwc-icon-button>
<mwc-icon-button
</ha-icon-button>
<ha-icon-button
class="hdr-add"
title="${t("add_task", L)}"
@click=${() => openCreateTaskDialog("", this._objects)}
>
<ha-icon icon="mdi:playlist-plus"></ha-icon>
</mwc-icon-button>
</ha-icon-button>
`
: nothing}
</div>
@@ -599,7 +599,7 @@ export class MaintenanceSupporterCard extends LitElement {
`
: showActions
? html`
<mwc-icon-button
<ha-icon-button
class="complete-btn"
title="${t("complete", L)}"
@click=${(e: Event) => {
@@ -609,7 +609,7 @@ export class MaintenanceSupporterCard extends LitElement {
}}
>
<ha-icon icon="mdi:check"></ha-icon>
</mwc-icon-button>
</ha-icon-button>
`
: nothing}
</div>
@@ -641,6 +641,8 @@ export class MaintenanceSupporterCard extends LitElement {
.header-right { display: flex; align-items: center; gap: 6px; }
.header-stats { display: flex; gap: 6px; }
.hdr-add {
display: inline-flex;
--ha-icon-button-size: 32px;
--mdc-icon-button-size: 32px;
--mdc-icon-size: 20px;
color: var(--primary-color);
@@ -762,6 +764,8 @@ export class MaintenanceSupporterCard extends LitElement {
.overdue-text { color: var(--error-color); font-weight: 500; }
.complete-btn {
display: inline-flex;
--ha-icon-button-size: 32px;
--mdc-icon-button-size: 32px;
--mdc-icon-size: 18px;
color: var(--primary-color);
@@ -794,3 +798,8 @@ registerCustomCard({
});
// The picker entries of every bundle, in the user's language once HA is up.
localizePickerWhenReady();
// HA's boot-time registry swap can strand these definitions (audit 2026-09-29).
healCardRegistry(
["maintenance-supporter-card", "maintenance-battery-fleet-card", "maintenance-supporter-panel-card"],
import.meta.url,
);
@@ -849,7 +849,7 @@ interface MaintenanceSectionStrategyConfig {
class MaintenanceSectionStrategy extends HTMLElement {
static async generate(
config: MaintenanceSectionStrategyConfig | undefined,
hass: HassLike,
_hass: HassLike,
): Promise<SectionConfig> {
const card: CardConfig = {
type: "custom:maintenance-supporter-card",
@@ -866,23 +866,14 @@ class MaintenanceSectionStrategy extends HTMLElement {
card.filter_due_max_days = config.filter_due_max_days;
}
// Resolve area_id → object names via WS (the card filters by name).
// Falls back gracefully if the WS call fails — empty filter means
// "show everything", which still renders something useful.
let names: string[] | undefined = config?.filter_objects;
if (config?.area_id && !names) {
try {
const r = await hass.connection.sendMessagePromise<{
objects: MaintenanceObjectResp[];
}>({ type: "maintenance_supporter/objects" });
names = (r.objects || [])
.filter((o) => o.object.area_id === config.area_id)
.map((o) => o.object.name);
} catch {
// ignore — card will show all
}
}
if (names && names.length > 0) card.filter_objects = names;
// An area section hands the card its own area filter. It used to look
// the area's object NAMES up and pass those — an area with no objects
// (or a failed lookup) left the filter empty and the "Kitchen" section
// listed every task in the house; a renamed object dropped out until the
// dashboard was regenerated (audit 2026-09-29). filter_areas is live and
// exact: an empty area shows the card's empty state.
if (config?.filter_objects?.length) card.filter_objects = config.filter_objects;
else if (config?.area_id) card.filter_areas = [config.area_id];
const cards: CardConfig[] = [];
if (config?.title) {
@@ -1236,7 +1227,7 @@ registerStrategy({
description:
"Auto-generated dashboard. Group views by area, status, floor, or due date — picked from the strategy editor or YAML.",
documentationURL:
"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy",
"https://github.com/iluebbe/maintenance_supporter/blob/master/docs/EXAMPLES.md#dashboard-strategy",
});
registerStrategy({
@@ -1246,7 +1237,7 @@ registerStrategy({
description:
"Embed maintenance tasks (filterable by area, status, due date) as a section in any dashboard view.",
documentationURL:
"https://github.com/iluebbe/maintenance_supporter#section-strategy",
"https://github.com/iluebbe/maintenance_supporter/blob/master/docs/EXAMPLES.md#section-strategy",
});
// Phase 5 status sections
@@ -61,6 +61,7 @@ const SEARCH_GROUP_CAP = { objects: 6, tasks: 10, parts: 6, documents: 8, histor
const MDI_MAGNIFY = "M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z";
import type {
HomeAssistant,
MaintenanceObject,
MaintenanceObjectResponse,
MaintenanceTask,
MaintenanceGroup,
@@ -81,7 +82,7 @@ import { UserService } from "./user-service";
// esbuild code-split chunks loaded by _ensureLazyUi() right after mount
// (roadmap perf wave 2, item 5). Only their types are imported — esbuild
// tree-shakes type-only imports, so they cost the entry bundle nothing.
import type { MaintenanceObjectDialog } from "./components/object-dialog";
import type { DeviceSwap, MaintenanceObjectDialog } from "./components/object-dialog";
import "./components/documents-section";
import "./components/parts-section";
import "./components/object-history-section";
@@ -123,12 +124,14 @@ import "./components/task-detail-view";
import { computeWindow, VIRTUAL_MIN_ROWS } from "./helpers/virtual-window";
import { INITIAL_STICKY, nextStickyState, stickyStateOnSelect, stickyTop, type StickyState } from "./helpers/sticky-pane";
import { invalidateSettingsCache, parseSettings, type SettingsWire } from "./helpers/settings-cache";
import type { PartsCostMode } from "./helpers/parts-cost";
import { canWrite } from "./helpers/permissions";
import { renderStatusBadge } from "./renderers/status";
import { TOAST_MS, ACTION_TOAST_MS } from "./helpers/toast";
import { buildHistoryEntryDraft } from "./helpers/history-draft";
import { readingSlotDelta } from "./helpers/reading-slots";
import { bulkResultMessage, runWs, runWsEach } from "./helpers/ws-run";
import { snoozedMessage, type SnoozeResult } from "./helpers/snooze";
import { STATUS_ORDER, statusRank } from "./status-constants";
// #191: all_areas = the areas page, area = one area's history and costs.
@@ -144,6 +147,8 @@ interface PartsOverviewRow {
name: string;
unit?: string | null;
cost?: number | null;
/** #98 follow-up: cost is then per package of this many units. */
package_size?: number | null;
storage_location?: string | null;
vendor?: string | null;
reorder_threshold?: number | null;
@@ -256,7 +261,7 @@ export class MaintenanceSupporterPanel extends LitElement {
@state() private _groups: Record<string, MaintenanceGroup> = {};
@state() private _detailStatsData: Map<string, StatisticsPoint[]> = new Map();
@state() private _miniStatsData: Map<string, StatisticsPoint[]> = new Map();
@state() private _features: AdvancedFeatures = { adaptive: false, predictions: false, seasonal: false, environmental: false, budget: false, groups: false, checklists: false, schedule_time: false, completion_actions: false };
@state() private _features: AdvancedFeatures = { adaptive: false, seasonal: false, environmental: false, budget: false, groups: false, checklists: false, schedule_time: false, completion_actions: false };
// HA user IDs (UUIDs) granted full panel access despite not being HA admins.
@state() private _adminPanelUserIds: string[] = [];
// v2.8.4: master switch — the allowlist only grants the full panel when this
@@ -270,6 +275,8 @@ export class MaintenanceSupporterPanel extends LitElement {
@state() private _rowActionStyle = "buttons_compact";
/** #170: reference numbers in front of names in the lists (global setting). */
@state() private _refsInLists = false;
/** #104: when spare parts count — what the complete dialog says about them. */
@state() private _partsCostMode: PartsCostMode = "purchase";
/** #145: one-time "rows look different now" notice for existing installs. */
@state() private _rowActionNotice = false;
@state() private _actionLoading = false;
@@ -591,7 +598,8 @@ export class MaintenanceSupporterPanel extends LitElement {
if (!this._userService) {
this._userService = new UserService(this.hass);
this._userService.getUsers();
// Names feed the person filter and the row chips — render once known.
void this._userService.getUsers().then(() => this.requestUpdate());
} else {
this._userService.updateHass(this.hass);
}
@@ -794,6 +802,7 @@ export class MaintenanceSupporterPanel extends LitElement {
this._rowActionNotice = s.rowActionNoticePending;
this._refsInLists = s.refsInLists;
this._objectsTableColumns = sanitizeColumns(s.objectsTableColumns);
this._partsCostMode = s.partsCostMode;
}
// Fetch mini-sparkline data for overview (non-blocking)
@@ -1520,6 +1529,18 @@ export class MaintenanceSupporterPanel extends LitElement {
/** #179: `section` opens one collapsed section for this visit (a search
* hit, a `?section=` link) and scrolls to it; the saved set is untouched. */
/** Replacement lineage (object ⋮ → Replace): the retired unit and its
* successor point at each other — stored since the feature shipped, but
* neither page linked the other (audit 2026-09-28). An object that is no
* longer loaded (deleted) shows nothing. */
private _renderLineageLink(entryId: string | null | undefined, label: string) {
const other = entryId ? this._objects.find((x) => x.entry_id === entryId) : undefined;
if (!other) return nothing;
return html`<p class="meta">${label}:
<a href="#" class="object-lineage-link" @click=${(e: Event) => { e.preventDefault(); this._showObject(other.entry_id); }}
>${other.object.name}</a></p>`;
}
private _showObject(entryId: string, section: ObjectSection | null = null): void {
this._pushPanelState("object", entryId);
this._view = "object";
@@ -2494,6 +2515,33 @@ export class MaintenanceSupporterPanel extends LitElement {
}
}
// #193: pause ONE task (items used in rotation: the filter comes out of use
// for a while) — the object pause, per task. Pausing asks for an optional
// resume date; resuming starts a fresh cycle from that day.
private async _togglePauseTask(entryId: string, taskId: string, paused: boolean): Promise<void> {
if (!paused) {
const dlg = this.shadowRoot!.querySelector<MaintenanceConfirmDialog>("maintenance-confirm-dialog");
const result = await dlg?.prompt({
title: t("pause_task", this._lang),
message: t("pause_task_prompt", this._lang),
confirmText: t("pause_task", this._lang),
inputLabel: t("pause_until_label", this._lang),
inputType: "date",
});
if (!result?.confirmed) return;
const msg: Record<string, unknown> = { type: "maintenance_supporter/task/pause", entry_id: entryId, task_id: taskId };
if (result.value) msg.until = result.value;
if (await this._runAction(msg)) {
this._showUndoToast(t("task_paused", this._lang), () => this._togglePauseTask(entryId, taskId, true));
}
return;
}
await this._runAction(
{ type: "maintenance_supporter/task/resume", entry_id: entryId, task_id: taskId },
{ successToast: t("task_resumed", this._lang) },
);
}
// v2.10.0: archive / unarchive an object. Archiving cascades to its tasks but
// is fully reversible, so instead of a blocking confirm we run it immediately
// and offer an Undo toast (v2.14.0).
@@ -2542,27 +2590,34 @@ export class MaintenanceSupporterPanel extends LitElement {
// v2.20 (N1): replace a worn-out object with a successor — the old one is
// archived in place (history/costs stay browsable), the new one starts as a
// pre-filled fresh unit with tasks and documents carried over.
private async _replaceObject(entryId: string, currentName: string): Promise<void> {
const dlg = this.shadowRoot!.querySelector<MaintenanceConfirmDialog>("maintenance-confirm-dialog");
const result = await dlg?.prompt({
title: t("replace_object", this._lang),
message: t("replace_object_prompt", this._lang),
confirmText: t("replace_object", this._lang),
inputLabel: t("replace_name_label", this._lang),
inputType: "text",
inputValue: currentName,
});
if (!result?.confirmed) return;
const res = await this._runAction<{ entry_id?: string }>(
{
type: "maintenance_supporter/object/replace",
entry_id: entryId,
name: result.value || currentName,
},
{ successToast: t("object_replaced", this._lang) },
);
if (res?.entry_id) this._showObject(res.entry_id);
// pre-filled fresh unit with tasks and documents carried over. The object
// dialog asks for the name AND the new unit's device (2.96).
private _replaceObject(entryId: string, obj: MaintenanceObject): void {
void this._ui<MaintenanceObjectDialog>("maintenance-object-dialog").then((d) => d?.openReplace(entryId, obj));
}
private _onObjectReplaced = async (e: CustomEvent<{ entry_id?: string; device_swap?: DeviceSwap }>): Promise<void> => {
// " · ": the replaced line carries no full stop in every language.
this._showToast([t("object_replaced", this._lang), this._deviceSwapText(e.detail.device_swap)].filter(Boolean).join(" · "), "info");
try { await this._loadData(); } catch { /* subscription will sync */ }
if (e.detail.entry_id) this._showObject(e.detail.entry_id);
};
private _onObjectSaved = async (e: CustomEvent<{ device_swap?: DeviceSwap }>): Promise<void> => {
const swap = this._deviceSwapText(e.detail?.device_swap);
if (swap) this._showToast(swap, "info");
await this._onDialogEvent();
};
/** "3 sensor links moved to the new device. 1 could not be matched." */
private _deviceSwapText(swap: DeviceSwap | undefined): string {
if (!swap || (!swap.moved && !swap.unmatched.length)) return "";
const parts: string[] = [];
if (swap.moved) parts.push(this._countText("device_swap_moved", "device_swap_moved_one", "count", swap.moved));
if (swap.unmatched.length) {
parts.push(this._countText("device_swap_unmatched", "device_swap_unmatched_one", "count", swap.unmatched.length));
}
return parts.join(" ");
}
private async _skipTask(entryId: string, taskId: string, reason?: string): Promise<void> {
@@ -2677,10 +2732,11 @@ export class MaintenanceSupporterPanel extends LitElement {
// Now reloads like every sibling action — this was the one mutation that
// skipped the refresh, leaving the snoozed due date stale until the next
// poll.
await this._runAction(
// #193: say for how long — the task itself shows no change.
const res = await this._runAction<SnoozeResult>(
{ type: "maintenance_supporter/task/snooze", entry_id: entryId, task_id: taskId },
{ successToast: t("snoozed", this._lang) },
);
if (res) this._showToast(snoozedMessage(res, this._lang), "info");
}
private _dismissSuggestion(entryId?: string, taskId?: string): void {
@@ -2921,6 +2977,7 @@ export class MaintenanceSupporterPanel extends LitElement {
adaptiveEnabled,
currencySymbol: this._currencySymbol,
viaTagScan: opts?.viaTagScan,
partsCostMode: this._partsCostMode,
}),
this._lang,
);
@@ -2990,13 +3047,17 @@ export class MaintenanceSupporterPanel extends LitElement {
<maintenance-object-dialog
.hass=${this.hass}
.objects=${this._objects}
@object-saved=${this._onDialogEvent}
@object-saved=${this._onObjectSaved}
@object-replaced=${this._onObjectReplaced}
></maintenance-object-dialog>
<maintenance-task-dialog
.hass=${this.hass}
.checklistsEnabled=${this._features.checklists}
.scheduleTimeEnabled=${this._features.schedule_time}
.completionActionsEnabled=${this._features.completion_actions}
.adaptiveFeature=${this._features.adaptive}
.seasonalFeature=${this._features.seasonal}
.environmentalFeature=${this._features.environmental}
.defaultWarningDays=${this._defaultWarningDays}
@task-saved=${this._onDialogEvent}
></maintenance-task-dialog>
@@ -3313,6 +3374,18 @@ export class MaintenanceSupporterPanel extends LitElement {
const archivedCount = this._objects.reduce(
(n, o) => n + o.tasks.filter((tk) => tk.archived).length, 0,
);
// The people someone can filter by: everyone responsible for a task, plus
// the person a saved view narrows to — otherwise that view leaves the
// select blank while it filters.
const filterPeople = new Set<string>();
for (const o of this._objects) {
for (const tk of o.tasks) if (tk.responsible_user_id) filterPeople.add(tk.responsible_user_id);
}
if (this._filterUser && this._filterUser !== "current_user") filterPeople.add(this._filterUser);
const personOptions = [...filterPeople]
.map((id) => ({ id, name: this._userService?.getUserName(id) ?? null }))
.filter((p) => p.name !== null || p.id === this._filterUser)
.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
// Filters actively narrowing the list — shown on the collapsed toggle so
// "why is my list short?" has a visible answer even with filters hidden.
@@ -3381,7 +3454,8 @@ export class MaintenanceSupporterPanel extends LitElement {
}}
>
<option value="">${t("all_users", L)}</option>
<option value="current_user">${t("my_tasks", L)}</option>
<option value="current_user" ?selected=${this._filterUser === "current_user"}>${t("my_tasks", L)}</option>
${personOptions.map((p) => html`<option value=${p.id} ?selected=${this._filterUser === p.id}>${p.name ?? p.id}</option>`)}
</select>
</label>
${this._allLabels.length > 0 ? html`
@@ -3962,7 +4036,11 @@ export class MaintenanceSupporterPanel extends LitElement {
<td>${row.object_name || "—"}</td>
<td>${row.stock !== null ? formatQty(row.stock, row.unit, L) : "—"}</td>
<td>${row.reorder_threshold ?? "—"}</td>
<td>${row.cost != null ? formatCost(row.cost, currency, L) : "—"}</td>
<td>${row.cost != null
? row.package_size
? `${formatCost(row.cost, currency, L)} / ${formatQty(row.package_size, row.unit ?? undefined, L)}`
: formatCost(row.cost, currency, L)
: "—"}</td>
<td>${row.storage_location || "—"}</td>
<td>
${row.consumers.length === 0
@@ -4157,9 +4235,9 @@ export class MaintenanceSupporterPanel extends LitElement {
return html`<td class="oc-ref_no">${objectRef(o) ? `#${objectRef(o)}` : "—"}</td>`;
case "actions":
return html`<td class="oc-actions">
<mwc-icon-button title="${t("qr_code", L)}" @click=${(e: Event) => { e.stopPropagation(); this._openQrForObject(obj.entry_id, o.name); }}>
<ha-icon-button class="obj-table-qr" title="${t("qr_code", L)}" @click=${(e: Event) => { e.stopPropagation(); this._openQrForObject(obj.entry_id, o.name); }}>
<ha-icon icon="mdi:qrcode"></ha-icon>
</mwc-icon-button>
</ha-icon-button>
</td>`;
default:
return html`<td></td>`;
@@ -4204,12 +4282,12 @@ export class MaintenanceSupporterPanel extends LitElement {
<div class="group-card-head">
<div class="group-card-name">${group.name}</div>
<div class="group-card-actions">
<mwc-icon-button title="${t("edit", L)}" @click=${() => this._openGroupEdit(gid)}>
<ha-icon-button title="${t("edit", L)}" @click=${() => this._openGroupEdit(gid)}>
<ha-svg-icon path="M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z"></ha-svg-icon>
</mwc-icon-button>
<mwc-icon-button title="${t("delete", L)}" @click=${() => this._deleteGroup(gid, group.name)}>
</ha-icon-button>
<ha-icon-button title="${t("delete", L)}" @click=${() => this._deleteGroup(gid, group.name)}>
<ha-svg-icon path="M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6v12z"></ha-svg-icon>
</mwc-icon-button>
</ha-icon-button>
</div>
</div>
${group.description ? html`<div class="group-card-desc">${group.description}</div>` : nothing}
@@ -4416,13 +4494,13 @@ export class MaintenanceSupporterPanel extends LitElement {
}
return html`
<span class="row-actions">
<mwc-icon-button class="btn-complete" title="${t("complete", L)}" @click=${(e: Event) => { e.stopPropagation(); onComplete(); }}>
<ha-icon-button class="btn-complete" title="${t("complete", L)}" @click=${(e: Event) => { e.stopPropagation(); onComplete(); }}>
<ha-icon icon="mdi:check"></ha-icon>
</mwc-icon-button>
</ha-icon-button>
${allowSkip ? html`
<mwc-icon-button class="btn-skip" title="${t("skip", L)}" .disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip?.(); }}>
<ha-icon-button class="btn-skip" title="${t("skip", L)}" .disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip?.(); }}>
<ha-icon icon="mdi:skip-next"></ha-icon>
</mwc-icon-button>` : nothing}
</ha-icon-button>` : nothing}
</span>`;
}
@@ -4480,7 +4558,7 @@ export class MaintenanceSupporterPanel extends LitElement {
<div class="popup-menu-item" @click=${() => { this._closeObjMenu(); this._duplicateObject(obj.entry_id); }}>${t("duplicate", L)}</div>
${!o.archived ? html`
<div class="popup-menu-item" @click=${() => { this._closeObjMenu(); this._togglePauseObject(obj.entry_id, !!o.paused); }}>${o.paused ? t("resume_object", L) : t("pause_object", L)}</div>
<div class="popup-menu-item" @click=${() => { this._closeObjMenu(); this._replaceObject(obj.entry_id, o.name); }}>${t("replace_object", L)}</div>
<div class="popup-menu-item" @click=${() => { this._closeObjMenu(); this._replaceObject(obj.entry_id, o); }}>${t("replace_object", L)}</div>
` : nothing}
<div class="popup-menu-item" @click=${() => { this._closeObjMenu(); this._toggleArchiveObject(obj.entry_id, !!o.archived); }}>${o.archived ? t("unarchive_object", L) : t("archive_object", L)}</div>
<div class="popup-menu-divider"></div>
@@ -4503,6 +4581,8 @@ export class MaintenanceSupporterPanel extends LitElement {
? html`<p class="meta">${[o.manufacturer, o.model].filter(Boolean).join(" ")}</p>`
: nothing}
${o.serial_number ? html`<p class="meta">${t("serial_number_label", L)}: ${o.serial_number}</p>` : nothing}
${this._renderLineageLink(o.predecessor_entry_id, t("object_replaces", L))}
${this._renderLineageLink(o.replaced_by_entry_id, t("object_replaced_by", L))}
${o.area_id
? html`<p class="meta">${t("area", L)}:
<a href="#" class="object-area-link" @click=${(e: Event) => { e.preventDefault(); this._showArea(o.area_id!); }}
@@ -4950,6 +5030,7 @@ export class MaintenanceSupporterPanel extends LitElement {
openComplete: (tk) => this._openCompleteDialog(entryId, taskId, tk.name, this._features.checklists ? tk.checklist : undefined, this._features.adaptive && !!tk.adaptive_config?.enabled),
promptSkip: () => this._promptSkipTask(entryId, taskId),
toggleArchive: (archived) => this._toggleArchiveTask(entryId, taskId, archived),
togglePause: (paused) => this._togglePauseTask(entryId, taskId, paused),
openQr: (taskName) => this._openQrForTask(entryId, taskId, obj?.object.name || "", taskName),
duplicateTask: () => this._duplicateTask(entryId, taskId),
moveTask: () => this._moveTask(entryId, taskId),
@@ -175,7 +175,7 @@ const PICKER_ENTRIES: NonNullable<typeof w.customStrategies> = [
description:
"Auto-generated dashboard. Group views by area, status, floor, or due date — picked from the strategy editor or YAML.",
documentationURL:
"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy",
"https://github.com/iluebbe/maintenance_supporter/blob/master/docs/EXAMPLES.md#dashboard-strategy",
},
// The section entries used to be pushed by the heavy bundle only, so the
// "Add section" picker on a foreign dashboard listed none of them until the
@@ -187,7 +187,7 @@ const PICKER_ENTRIES: NonNullable<typeof w.customStrategies> = [
description:
"Embed maintenance tasks (filterable by area, status, due date) as a section in any dashboard view.",
documentationURL:
"https://github.com/iluebbe/maintenance_supporter#section-strategy",
"https://github.com/iluebbe/maintenance_supporter/blob/master/docs/EXAMPLES.md#section-strategy",
},
{
type: "maintenance-supporter-vacation",

Some files were not shown because too many files have changed in this diff Show More