217 files
This commit is contained in:
@@ -47,6 +47,7 @@ from .const import (
|
||||
CONF_BUDGET_MONTHLY,
|
||||
CONF_BUDGET_YEARLY,
|
||||
CONF_GROUPS,
|
||||
CONF_INSTALL_ASSIST_SENTENCES,
|
||||
CONF_OBJECT,
|
||||
CONF_PANEL_ENABLED,
|
||||
CONF_TASKS,
|
||||
@@ -58,6 +59,11 @@ from .const import (
|
||||
GLOBAL_UNIQUE_ID,
|
||||
MAX_COST,
|
||||
MAX_DURATION_MINUTES,
|
||||
MAX_LABEL_LENGTH,
|
||||
MAX_LABELS,
|
||||
MAX_NAME_LENGTH,
|
||||
MAX_TEXT_LENGTH,
|
||||
MAX_TYPE_LENGTH,
|
||||
PLATFORMS,
|
||||
SERVICE_ADD_OBJECT,
|
||||
SERVICE_ADD_TASK,
|
||||
@@ -81,10 +87,16 @@ from .const import (
|
||||
from .coordinator import MaintenanceCoordinator
|
||||
from .entity.summary_coordinator import MaintenanceSummaryCoordinator
|
||||
from .frontend import async_register_card
|
||||
from .helpers.assist_sentences import async_sync as async_sync_assist_sentences
|
||||
from .helpers.dates import INTERVAL_UNITS
|
||||
from .helpers.documents import DocumentStore
|
||||
from .helpers.notification_manager import NotificationManager
|
||||
from .helpers.schedule import normalize_task_storage
|
||||
from .helpers.task_fields import (
|
||||
INTERVAL_DAYS_RANGE,
|
||||
TASK_PRIORITIES,
|
||||
WARNING_DAYS_RANGE,
|
||||
)
|
||||
from .panel import async_register_panel, async_unregister_panel
|
||||
from .storage import MaintenanceStore, async_migrate_to_store
|
||||
from .websocket import async_register_commands
|
||||
@@ -158,21 +170,29 @@ SERVICE_ADD_OBJECT_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
# The service task schemas mirror the WS `task/create` / `task/update` schemas
|
||||
# field-for-field, consuming the SAME constants + ranges (const.py /
|
||||
# helpers.task_fields) rather than restating literals — the service path is
|
||||
# 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.
|
||||
SERVICE_ADD_TASK_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("entry_id"): cv.string,
|
||||
vol.Required("name"): vol.All(cv.string, vol.Length(min=1, max=255)),
|
||||
vol.Optional("task_type"): cv.string,
|
||||
vol.Optional("schedule_type"): cv.string,
|
||||
vol.Optional("interval_days"): vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
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("interval_days"): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=INTERVAL_DAYS_RANGE[0], max=INTERVAL_DAYS_RANGE[1])
|
||||
),
|
||||
vol.Optional("interval_unit"): vol.In(INTERVAL_UNITS),
|
||||
vol.Optional("due_date"): cv.string,
|
||||
# Nested recurrence for the calendar kinds (weekdays / nth_weekday /
|
||||
# day_of_month); takes precedence over the flat fields above.
|
||||
vol.Optional("schedule"): dict,
|
||||
vol.Optional("warning_days"): vol.All(vol.Coerce(int), vol.Range(min=0)),
|
||||
vol.Optional("warning_days"): vol.All(vol.Coerce(int), vol.Range(min=WARNING_DAYS_RANGE[0], max=WARNING_DAYS_RANGE[1])),
|
||||
vol.Optional("enabled"): cv.boolean,
|
||||
vol.Optional("notes"): cv.string,
|
||||
vol.Optional("notes"): vol.All(cv.string, vol.Length(max=MAX_TEXT_LENGTH)),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -180,18 +200,20 @@ SERVICE_UPDATE_TASK_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("entry_id"): cv.string,
|
||||
vol.Required("task_id"): cv.string,
|
||||
vol.Optional("name"): vol.All(cv.string, vol.Length(min=1, max=255)),
|
||||
vol.Optional("task_type"): cv.string,
|
||||
vol.Optional("schedule_type"): cv.string,
|
||||
vol.Optional("interval_days"): vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
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("interval_days"): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=INTERVAL_DAYS_RANGE[0], max=INTERVAL_DAYS_RANGE[1])
|
||||
),
|
||||
vol.Optional("interval_unit"): vol.In(INTERVAL_UNITS),
|
||||
vol.Optional("due_date"): cv.string,
|
||||
vol.Optional("schedule"): dict,
|
||||
vol.Optional("warning_days"): vol.All(vol.Coerce(int), vol.Range(min=0)),
|
||||
vol.Optional("warning_days"): vol.All(vol.Coerce(int), vol.Range(min=WARNING_DAYS_RANGE[0], max=WARNING_DAYS_RANGE[1])),
|
||||
vol.Optional("enabled"): cv.boolean,
|
||||
vol.Optional("notes"): cv.string,
|
||||
vol.Optional("priority"): vol.In(["low", "normal", "high"]),
|
||||
vol.Optional("labels"): [cv.string],
|
||||
vol.Optional("notes"): vol.All(cv.string, vol.Length(max=MAX_TEXT_LENGTH)),
|
||||
vol.Optional("priority"): vol.In(TASK_PRIORITIES),
|
||||
vol.Optional("labels"): vol.All([vol.All(cv.string, vol.Length(max=MAX_LABEL_LENGTH))], vol.Length(max=MAX_LABELS)),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -702,7 +724,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
try:
|
||||
if action_type == "complete":
|
||||
_LOGGER.info("Completing task %s via notification action", task_id)
|
||||
await runtime_data.coordinator.complete_maintenance(task_id=task_id)
|
||||
await runtime_data.coordinator.complete_maintenance(task_id=task_id, unattended=True)
|
||||
elif action_type == "skip":
|
||||
_LOGGER.info("Skipping task %s via notification action", task_id)
|
||||
await runtime_data.coordinator.skip_maintenance(task_id=task_id, reason="Skipped from notification")
|
||||
@@ -711,6 +733,17 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
if nm is not None:
|
||||
nm.snooze_task(entry_id, task_id)
|
||||
return # Snooze: keep notification visible for later reminder
|
||||
except ServiceValidationError as err:
|
||||
# The task demands details a notification button cannot capture.
|
||||
# Not an error in our code — log it plainly and deliberately do
|
||||
# NOT dismiss the notification, so the reminder survives until the
|
||||
# user completes the task properly in the panel.
|
||||
_LOGGER.warning(
|
||||
"Notification completion of task %s rejected: %s",
|
||||
task_id,
|
||||
err,
|
||||
)
|
||||
return
|
||||
except Exception:
|
||||
_LOGGER.exception(
|
||||
"Failed to handle notification action %s for task %s",
|
||||
@@ -755,11 +788,18 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
tag_id,
|
||||
)
|
||||
user_id = event.context.user_id if event.context else None
|
||||
try:
|
||||
await runtime_data.coordinator.complete_maintenance(
|
||||
task_id=task_id,
|
||||
completed_by=user_id,
|
||||
notes="Completed via NFC tag",
|
||||
unattended=True,
|
||||
)
|
||||
except ServiceValidationError as err:
|
||||
# A tag tap cannot capture a cost or a photo. Log it
|
||||
# plainly and leave the task open so the user finishes it
|
||||
# in the panel — the honest outcome, not a silent no-op.
|
||||
_LOGGER.warning("NFC completion of task %s rejected: %s", task_id, err)
|
||||
return
|
||||
|
||||
# No match found — not our tag, ignore silently
|
||||
@@ -1074,6 +1114,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: MaintenanceSupporterConf
|
||||
# Listen for options changes (panel toggle)
|
||||
entry.async_on_unload(entry.add_update_listener(_async_global_options_updated))
|
||||
|
||||
# Keep <config>/custom_sentences/ in step with the opt-in setting. Runs
|
||||
# on every setup, not just on change, so an upgrade that ships new
|
||||
# sentences reaches an install that already opted in.
|
||||
await async_sync_assist_sentences(
|
||||
hass, entry.options.get(CONF_INSTALL_ASSIST_SENTENCES, False)
|
||||
)
|
||||
|
||||
# Initial orphan check for admin_panel_user_ids (HA users deleted
|
||||
# while the integration was offline land here as repair issues).
|
||||
await _check_admin_panel_user_orphans(hass, entry)
|
||||
@@ -1305,6 +1352,10 @@ async def _async_global_options_updated(hass: HomeAssistant, entry: ConfigEntry)
|
||||
await _check_admin_panel_user_orphans(hass, entry)
|
||||
# The notify service may have just been (re)configured — re-check it.
|
||||
_verify_notify_service(hass)
|
||||
# Install or remove the Assist sentence files to match the setting.
|
||||
await async_sync_assist_sentences(
|
||||
hass, entry.options.get(CONF_INSTALL_ASSIST_SENTENCES, False)
|
||||
)
|
||||
|
||||
|
||||
_ORPHAN_ISSUE_PREFIX = "orphan_admin_panel_user_"
|
||||
@@ -1466,6 +1517,16 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
# as a fixable repair issue so the user can restore it (#86).
|
||||
_sync_missing_global_entry_issue(hass)
|
||||
return
|
||||
# #111: hand any shared spare-part pool to a borrower BEFORE the Store goes
|
||||
# — the stock numbers exist nowhere else. Must happen here rather than in
|
||||
# the panel's delete: HA's own Configure-UI removal never reaches that path.
|
||||
from .helpers.shared_parts import async_transfer_pools_on_removal
|
||||
|
||||
try:
|
||||
await async_transfer_pools_on_removal(hass, entry)
|
||||
except Exception:
|
||||
_LOGGER.exception("Could not transfer shared spare parts from %s", entry.title)
|
||||
|
||||
store = hass.data.get(STORES_CACHE_KEY, {}).pop(entry.entry_id, None)
|
||||
if store is None:
|
||||
store = MaintenanceStore(hass, entry.entry_id)
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# Assist-Sätze für Maintenance Supporter (Deutsch).
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 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).
|
||||
language: "de"
|
||||
intents:
|
||||
MaintenanceSupporterListTasks:
|
||||
data:
|
||||
- sentences:
|
||||
- "welche Wartung[en] (ist|sind) [gerade] (fällig|überfällig)"
|
||||
- "was ist [an Wartung] (fällig|überfä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).
|
||||
- sentences:
|
||||
- "was muss ich (machen|erledigen|tun)"
|
||||
- "was sind meine (Aufgaben|Pflichten|Wartungen)"
|
||||
- "(habe|hab) ich [noch] (was|etwas) zu (tun|erledigen)"
|
||||
- "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"
|
||||
- "(gibt es|steht) hier [drin] (etwas|was) an"
|
||||
slots:
|
||||
scope: "here"
|
||||
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)"
|
||||
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}"
|
||||
MaintenanceSupporterTaskDue:
|
||||
data:
|
||||
- sentences:
|
||||
- "wann ist [die] {task_name:name} fällig"
|
||||
- "wann muss ich [die] {task_name:name} (machen|erledigen)"
|
||||
MaintenanceSupporterSnoozeTask:
|
||||
data:
|
||||
- sentences:
|
||||
- "[die] {task_name:name} (snoozen|stummschalten)"
|
||||
- "Erinnerungen für [die] {task_name:name} (stummschalten|pausieren)"
|
||||
MaintenanceSupporterPartStock:
|
||||
data:
|
||||
- sentences:
|
||||
- "wie viele {part_name:name} haben wir [noch]"
|
||||
- "wie ist der Bestand (von|an) {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"
|
||||
- sentences:
|
||||
- "verschiebe [die|das|den] {part_name:name} um eine Woche"
|
||||
- "[die|das|den] {part_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"
|
||||
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.
|
||||
task_name:
|
||||
wildcard: true
|
||||
part_name:
|
||||
wildcard: true
|
||||
days:
|
||||
range:
|
||||
from: 1
|
||||
to: 365
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
# Assist sentences for Maintenance Supporter (English).
|
||||
#
|
||||
# You do not need to copy this by hand: turn on Settings -> General ->
|
||||
# "Install Assist sentences" and the integration writes it to
|
||||
# config/custom_sentences/en/maintenance_supporter.yaml and reloads the
|
||||
# conversation agent. That is the only directory the classic (non-LLM) agent
|
||||
# reads sentences from. LLM-based Assist pipelines need none of this — they
|
||||
# pick the intents up as tools automatically.
|
||||
#
|
||||
# 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).
|
||||
language: "en"
|
||||
intents:
|
||||
MaintenanceSupporterListTasks:
|
||||
data:
|
||||
- sentences:
|
||||
- "what maintenance is (due|overdue)"
|
||||
- "which maintenance [tasks] (are|is) (due|overdue|pending)"
|
||||
- "is [there] any maintenance due"
|
||||
- "what needs (maintenance|servicing)"
|
||||
# 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:
|
||||
- "what [maintenance] do I (need to|have to) do"
|
||||
- "what (is|are) my (chores|tasks|maintenance [tasks])"
|
||||
- "(is|are) [there] anything [assigned] for me [to do]"
|
||||
- "what am I (due|responsible) for"
|
||||
slots:
|
||||
scope: "mine"
|
||||
# Scope: the room the request came from, resolved from the satellite's
|
||||
# area — so a wall tablet answers for where it is standing.
|
||||
- sentences:
|
||||
- "what needs (doing|maintenance|servicing) (in here|here)"
|
||||
- "what maintenance is due (in here|here|in this room)"
|
||||
- "is [there] anything [to do] (in here|in this room)"
|
||||
slots:
|
||||
scope: "here"
|
||||
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}"
|
||||
MaintenanceSupporterTaskInstructions:
|
||||
data:
|
||||
- sentences:
|
||||
- "how do I (do|perform) [the] {task_name:name}"
|
||||
- "(instructions|guidance) 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}"
|
||||
MaintenanceSupporterSnoozeTask:
|
||||
data:
|
||||
- sentences:
|
||||
- "snooze [the] [maintenance] [task] {task_name:name}"
|
||||
- "(mute|silence) [the] reminders for [the] {task_name:name}"
|
||||
MaintenanceSupporterPartStock:
|
||||
data:
|
||||
- sentences:
|
||||
- "how (many|much) {part_name:name} (do we have|are left|is left)"
|
||||
- "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]"
|
||||
- sentences:
|
||||
- "postpone [the] {part_name:name} by a week"
|
||||
- "push [the] {part_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}"
|
||||
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.
|
||||
task_name:
|
||||
wildcard: true
|
||||
part_name:
|
||||
wildcard: true
|
||||
days:
|
||||
range:
|
||||
from: 1
|
||||
to: 365
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# Frases de Assist para Maintenance Supporter (español).
|
||||
#
|
||||
# No hace falta copiar este archivo a mano: activa Ajustes -> General ->
|
||||
# «Instalar las frases de Assist» y la integración lo escribe en
|
||||
# config/custom_sentences/es/maintenance_supporter.yaml y recarga el agente de
|
||||
# conversación. Ese es el único directorio del que el agente clásico (sin LLM)
|
||||
# lee sus frases. Las canalizaciones de Assist basadas en un LLM no necesitan
|
||||
# nada de esto: reciben las intenciones como herramientas.
|
||||
#
|
||||
# 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).
|
||||
language: "es"
|
||||
intents:
|
||||
MaintenanceSupporterListTasks:
|
||||
data:
|
||||
- sentences:
|
||||
- "qué mantenimiento (toca|hay que hacer|está pendiente)"
|
||||
- "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é me toca [a mí] [hacer]"
|
||||
- "qué tengo que hacer [yo]"
|
||||
- "cuáles son mis (tareas|mantenimientos)"
|
||||
- "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)"
|
||||
- "hay algo pendiente (aquí|en esta habitación)"
|
||||
slots:
|
||||
scope: "here"
|
||||
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)"
|
||||
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}"
|
||||
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}"
|
||||
MaintenanceSupporterSnoozeTask:
|
||||
data:
|
||||
- sentences:
|
||||
- "(silencia|pausa) [el|la|los|las] {task_name:name}"
|
||||
- "(silencia|pausa) los avisos (de|del|de la) {task_name:name}"
|
||||
MaintenanceSupporterPartStock:
|
||||
data:
|
||||
- sentences:
|
||||
- "cuántos {part_name:name} (nos quedan|tenemos|quedan)"
|
||||
- "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]"
|
||||
- sentences:
|
||||
- "(aplaza|retrasa|pospón) [el|la|los|las] {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) esta (ronda|vuelta) (de|del|de la|de los|de las) {task_name:name}"
|
||||
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».
|
||||
task_name:
|
||||
wildcard: true
|
||||
part_name:
|
||||
wildcard: true
|
||||
days:
|
||||
range:
|
||||
from: 1
|
||||
to: 365
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
# Phrases Assist pour Maintenance Supporter (français).
|
||||
#
|
||||
# Pas besoin de copier ce fichier à la main : activez Paramètres -> Général ->
|
||||
# « Installer les phrases Assist » et l'intégration l'écrit dans
|
||||
# config/custom_sentences/fr/maintenance_supporter.yaml puis recharge l'agent
|
||||
# conversationnel. C'est le seul dossier où l'agent classique (non-LLM) lit ses
|
||||
# phrases. Les pipelines Assist basées sur un LLM n'en ont pas besoin : elles
|
||||
# récupèrent les intentions comme outils.
|
||||
#
|
||||
# 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).
|
||||
language: "fr"
|
||||
intents:
|
||||
MaintenanceSupporterListTasks:
|
||||
data:
|
||||
- sentences:
|
||||
- "quel[le]s (entretien|entretiens|maintenances|tâches) (est|sont) (dû|due|dues|prévus|prévues)"
|
||||
- "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 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)"
|
||||
slots:
|
||||
scope: "here"
|
||||
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)"
|
||||
MaintenanceSupporterTaskInstructions:
|
||||
data:
|
||||
- sentences:
|
||||
- "comment (faire|je fais) [le|la|les|l'] {task_name:name}"
|
||||
- "(instructions|consignes) pour [le|la|les|l'] {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}"
|
||||
MaintenanceSupporterSnoozeTask:
|
||||
data:
|
||||
- sentences:
|
||||
- "mets [le|la|les|l'] {task_name:name} en pause"
|
||||
MaintenanceSupporterPartStock:
|
||||
data:
|
||||
- sentences:
|
||||
- "combien de {part_name:name} (reste-t-il|il reste|avons-nous)"
|
||||
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]"
|
||||
- sentences:
|
||||
- "reporte [le|la|les|l'] {task_name:name} d'une semaine"
|
||||
- "repousse [le|la|les|l'] {task_name:name} d'une semaine"
|
||||
slots:
|
||||
days: 7
|
||||
MaintenanceSupporterSkipTask:
|
||||
data:
|
||||
- sentences:
|
||||
- "(saute|passe) [le|la|les|l'] {task_name:name} [cette fois]"
|
||||
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 ».
|
||||
task_name:
|
||||
wildcard: true
|
||||
part_name:
|
||||
wildcard: true
|
||||
days:
|
||||
range:
|
||||
from: 1
|
||||
to: 365
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
# Frasi Assist per Maintenance Supporter (italiano).
|
||||
#
|
||||
# Non serve copiare questo file a mano: attiva Impostazioni -> Generale ->
|
||||
# «Installa le frasi di Assist» e l'integrazione lo scrive in
|
||||
# config/custom_sentences/it/maintenance_supporter.yaml e ricarica l'agente
|
||||
# conversazionale. È l'unica cartella da cui l'agente classico (non LLM) legge
|
||||
# le sue frasi. Le pipeline Assist basate su LLM non ne hanno bisogno: ricevono
|
||||
# gli intent automaticamente come strumenti.
|
||||
#
|
||||
# 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).
|
||||
language: "it"
|
||||
intents:
|
||||
MaintenanceSupporterListTasks:
|
||||
data:
|
||||
- sentences:
|
||||
- "quale manutenzione (è in scadenza|serve|va fatta)"
|
||||
- "quali manutenzioni (sono|ci sono) in scadenza"
|
||||
- "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 devo fare [io]"
|
||||
- "quali sono i miei (compiti|lavori|impegni)"
|
||||
- "tocca a me [fare qualcosa]"
|
||||
- "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)"
|
||||
- "c'è qualcosa da fare (qui|in questa stanza)"
|
||||
slots:
|
||||
scope: "here"
|
||||
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)"
|
||||
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}"
|
||||
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}"
|
||||
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}"
|
||||
MaintenanceSupporterPartStock:
|
||||
data:
|
||||
- sentences:
|
||||
- "quanti {part_name:name} (abbiamo|ci sono|restano)"
|
||||
- "quante scorte di {part_name:name} (abbiamo|ci sono)"
|
||||
MaintenanceSupporterPostponeTask:
|
||||
data:
|
||||
- sentences:
|
||||
- "(rimanda|rinvia|posticipa) [il|lo|la|l'|i|gli|le] {task_name:name} di {days} (giorno|giorni)"
|
||||
- sentences:
|
||||
- "(rimanda|rinvia|posticipa) [il|lo|la|l'|i|gli|le] {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 questo giro (di|del|della|dei|delle) {task_name:name}"
|
||||
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».
|
||||
task_name:
|
||||
wildcard: true
|
||||
part_name:
|
||||
wildcard: true
|
||||
days:
|
||||
range:
|
||||
from: 1
|
||||
to: 365
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
# Assist-zinnen voor Maintenance Supporter (Nederlands).
|
||||
#
|
||||
# Handmatig kopiëren hoeft niet: zet Instellingen -> Algemeen ->
|
||||
# "Assist-zinnen installeren" aan, dan schrijft de integratie dit bestand naar
|
||||
# config/custom_sentences/nl/maintenance_supporter.yaml en herlaadt de
|
||||
# gespreksagent. Alleen uit die map leest de klassieke (niet-LLM) agent zijn
|
||||
# zinnen. Op een LLM gebaseerde Assist-pijplijnen hebben dit niet nodig: die
|
||||
# krijgen de intents automatisch als tools.
|
||||
#
|
||||
# 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).
|
||||
language: "nl"
|
||||
intents:
|
||||
MaintenanceSupporterListTasks:
|
||||
data:
|
||||
- sentences:
|
||||
- "welk onderhoud (staat open|moet er gebeuren|is er nodig)"
|
||||
- "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 moet ik [nog] doen"
|
||||
- "wat zijn mijn (taken|klusjes|klussen)"
|
||||
- "ben ik aan de beurt"
|
||||
- "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"
|
||||
- "is hier [nog] (iets|wat) te doen"
|
||||
slots:
|
||||
scope: "here"
|
||||
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)"
|
||||
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}"
|
||||
MaintenanceSupporterTaskDue:
|
||||
data:
|
||||
- sentences:
|
||||
- "wanneer moet [de|het] {task_name:name} (gebeuren|gedaan worden)"
|
||||
- "wanneer is [de|het] {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}"
|
||||
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}"
|
||||
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]"
|
||||
- sentences:
|
||||
- "stel [de|het] {task_name:name} een week uit"
|
||||
- "schuif [de|het] {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"
|
||||
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.
|
||||
task_name:
|
||||
wildcard: true
|
||||
part_name:
|
||||
wildcard: true
|
||||
days:
|
||||
range:
|
||||
from: 1
|
||||
to: 365
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Všechno je v pořádku — žádná údržba nevyžaduje pozornost.",
|
||||
"none_due_mine": "Tobě teď není přiřazeno nic.",
|
||||
"none_due_here": "V této místnosti nevyžaduje údržbu nic.",
|
||||
"unknown_user": "Nevím, kdo se ptá, takže nemůžu určit, které úkoly jsou tvoje.",
|
||||
"unknown_area": "Nevím, ve které místnosti je toto zařízení — nejdřív ho přiřaď k oblasti.",
|
||||
"tasks_due": "Pozornost vyžadují tyto úkoly údržby, celkem {count}: {items}.",
|
||||
"task_due_one": "Pozornost vyžaduje jeden úkol údržby: {items}.",
|
||||
"completed": "Úkol '{task}' u zařízení {object} je zapsaný jako hotový.",
|
||||
"postponed": "Úkol '{task}' u zařízení {object} je odložený na {date}.",
|
||||
"postpone_needs_when": "Řekni mi, o kolik ho mám odložit — třeba o pět dní.",
|
||||
"postpone_past": "Tím by '{task}' spadl do minulosti.",
|
||||
"skipped": "Tento cyklus úkolu '{task}' u zařízení {object} je přeskočený. Další termín je {date}.",
|
||||
"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.",
|
||||
"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",
|
||||
"st_due_today": "termín dnes",
|
||||
"st_due_in": "termín do {days} dní",
|
||||
"st_due_in_one": "termín zítra",
|
||||
"st_triggered": "spuštěno",
|
||||
"item_on": "{task} — {object}",
|
||||
"due_date_suffix": " Další termín je {date}.",
|
||||
"guide_header": "Uložené informace k úkolu '{task}' u zařízení {object}: {segments}.",
|
||||
"guide_none": "K úkolu '{task}' u zařízení {object} nejsou uložené žádné pokyny, dokumenty ani náhradní díly. Můžu místo toho dát obecné, neověřené rady — chceš?",
|
||||
"guide_notes": "poznámky: {notes}",
|
||||
"guide_checklist": "kroky kontrolního seznamu, celkem {count}: {steps}",
|
||||
"guide_doc": "propojený dokument '{title}'",
|
||||
"guide_doc_page": "propojený dokument '{title}', strana {page}",
|
||||
"guide_url": "je uložený odkaz na dokumentaci",
|
||||
"guide_part": "potřeba: {qty} × {part}{extras}",
|
||||
"guide_part_loc": "místo uložení: {loc}",
|
||||
"guide_part_stock": "na skladě: {stock}",
|
||||
"snoozed": "Připomínky k úkolu '{task}' u zařízení {object} jsou ztlumené po dobu {hours} hodin.",
|
||||
"snooze_unavailable": "Oznámení nejsou nastavená, takže není co ztlumit.",
|
||||
"stock_line": "{stock} × {part} na skladě{loc}{low}.",
|
||||
"stock_loc": " (místo uložení: {loc})",
|
||||
"stock_low": " — na úrovni objednacího limitu nebo pod ním",
|
||||
"stock_untracked": "{part} — u tohoto dílu sklad nesleduju.",
|
||||
"part_not_found": "Nenašel jsem náhradní díl, který by odpovídal '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Alt er i orden — der er ingen vedligeholdelse, der kræver opmærksomhed.",
|
||||
"none_due_mine": "Der er ikke noget tildelt dig lige nu.",
|
||||
"none_due_here": "Der skal ikke laves vedligeholdelse i det her rum.",
|
||||
"unknown_user": "Jeg ved ikke, hvem der spørger, så jeg kan ikke sige, hvilke opgaver der er dine.",
|
||||
"unknown_area": "Jeg ved ikke, hvilket rum den her enhed står i — knyt den til et område først.",
|
||||
"tasks_due": "{count} vedligeholdelsesopgaver kræver opmærksomhed: {items}.",
|
||||
"task_due_one": "Én vedligeholdelsesopgave kræver opmærksomhed: {items}.",
|
||||
"completed": "Jeg har markeret '{task}' på {object} som udført.",
|
||||
"postponed": "Jeg har udskudt '{task}' på {object} til den {date}.",
|
||||
"postpone_needs_when": "Sig, hvor langt jeg skal udskyde den — for eksempel fem dage.",
|
||||
"postpone_past": "Så ville '{task}' ligge i fortiden.",
|
||||
"skipped": "Jeg har sprunget denne omgang af '{task}' på {object} over. Næste omgang skal laves den {date}.",
|
||||
"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.",
|
||||
"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",
|
||||
"st_due_today": "skal laves i dag",
|
||||
"st_due_in": "skal laves om {days} dage",
|
||||
"st_due_in_one": "skal laves i morgen",
|
||||
"st_triggered": "udløst",
|
||||
"item_on": "{task} på {object}",
|
||||
"due_date_suffix": " Næste gang skal det laves den {date}.",
|
||||
"guide_header": "Gemte oplysninger om '{task}' på {object}: {segments}.",
|
||||
"guide_none": "Der er ikke gemt nogen vejledning, dokumenter eller reservedele til '{task}' på {object}. Jeg kan i stedet give generelle, ikke-bekræftede råd — vil du have det?",
|
||||
"guide_notes": "noter: {notes}",
|
||||
"guide_checklist": "{count} trin på tjeklisten: {steps}",
|
||||
"guide_doc": "tilknyttet dokument '{title}'",
|
||||
"guide_doc_page": "tilknyttet dokument '{title}', side {page}",
|
||||
"guide_url": "der er gemt et link til dokumentation",
|
||||
"guide_part": "{qty} × {part} skal bruges{extras}",
|
||||
"guide_part_loc": "opbevaringssted {loc}",
|
||||
"guide_part_stock": "{stock} på lager",
|
||||
"snoozed": "Påmindelser for '{task}' på {object} er sat på pause i {hours} timer.",
|
||||
"snooze_unavailable": "Notifikationer er ikke sat op, så der er ikke noget at sætte på pause.",
|
||||
"stock_line": "{stock} × {part} på lager{loc}{low}.",
|
||||
"stock_loc": " (opbevaringssted {loc})",
|
||||
"stock_low": " — på eller under genbestillingsgrænsen",
|
||||
"stock_untracked": "Der føres ikke lager for {part}.",
|
||||
"part_not_found": "Jeg kunne ikke finde en reservedel, der passer på '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Alles in Ordnung — keine Wartung fällig.",
|
||||
"none_due_mine": "Dir ist gerade nichts zugewiesen.",
|
||||
"none_due_here": "In diesem Raum ist keine Wartung fällig.",
|
||||
"unknown_user": "Ich weiß nicht, wer fragt, und kann deine Aufgaben deshalb nicht bestimmen.",
|
||||
"unknown_area": "Ich weiß nicht, in welchem Raum dieses Gerät steht — ordne es zuerst einem Bereich zu.",
|
||||
"tasks_due": "{count} Wartungsaufgaben brauchen Aufmerksamkeit: {items}.",
|
||||
"task_due_one": "Eine Wartungsaufgabe braucht Aufmerksamkeit: {items}.",
|
||||
"completed": "'{task}' an {object} als erledigt eingetragen.",
|
||||
"postponed": "'{task}' an {object} auf den {date} verschoben.",
|
||||
"postpone_needs_when": "Sag mir, um wie lange ich verschieben soll — zum Beispiel um fünf Tage.",
|
||||
"postpone_past": "Damit läge '{task}' in der Vergangenheit.",
|
||||
"skipped": "Dieser Durchgang von '{task}' an {object} wurde übersprungen. Der nächste ist am {date} fällig.",
|
||||
"not_found": "Ich habe keine Wartungsaufgabe zu '{name}' gefunden.",
|
||||
"ambiguous": "Das passt auf mehrere Aufgaben: {candidates}. Bitte formuliere genauer.",
|
||||
"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",
|
||||
"st_due_today": "heute fällig",
|
||||
"st_due_in": "fällig in {days} Tagen",
|
||||
"st_due_in_one": "morgen fällig",
|
||||
"st_triggered": "ausgelöst",
|
||||
"item_on": "{task} an {object}",
|
||||
"due_date_suffix": " Der nächste Fälligkeitstermin ist der {date}.",
|
||||
"guide_header": "Hinterlegte Informationen zu '{task}' an {object}: {segments}.",
|
||||
"guide_none": "Zu '{task}' an {object} sind keine Anleitungen, Dokumente oder Ersatzteile hinterlegt. Ich kann stattdessen allgemeine, ungeprüfte Hinweise geben — möchtest du das?",
|
||||
"guide_notes": "Notizen: {notes}",
|
||||
"guide_checklist": "{count} Checklisten-Schritte: {steps}",
|
||||
"guide_doc": "verknüpftes Dokument '{title}'",
|
||||
"guide_doc_page": "verknüpftes Dokument '{title}', Seite {page}",
|
||||
"guide_url": "ein Dokumentations-Link ist hinterlegt",
|
||||
"guide_part": "{qty} × {part} benötigt{extras}",
|
||||
"guide_part_loc": "Lagerort {loc}",
|
||||
"guide_part_stock": "{stock} auf Lager",
|
||||
"snoozed": "Erinnerungen für '{task}' an {object} für {hours} Stunden stummgeschaltet.",
|
||||
"snooze_unavailable": "Benachrichtigungen sind nicht eingerichtet — es gibt nichts stummzuschalten.",
|
||||
"stock_line": "{stock} × {part} auf Lager{loc}{low}.",
|
||||
"stock_loc": " (Lagerort: {loc})",
|
||||
"stock_low": " — an oder unter der Nachbestellgrenze",
|
||||
"stock_untracked": "Für {part} wird kein Bestand geführt.",
|
||||
"part_not_found": "Ich habe kein Ersatzteil zu '{name}' gefunden."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Everything is OK — no maintenance needs attention.",
|
||||
"none_due_mine": "Nothing is assigned to you right now.",
|
||||
"none_due_here": "Nothing needs maintenance in this room.",
|
||||
"unknown_user": "I don't know who is asking, so I can't tell which tasks are yours.",
|
||||
"unknown_area": "I don't know which room this device is in — assign it to an area first.",
|
||||
"tasks_due": "{count} maintenance tasks need attention: {items}.",
|
||||
"task_due_one": "One maintenance task needs attention: {items}.",
|
||||
"completed": "Completed '{task}' on {object}.",
|
||||
"postponed": "Postponed '{task}' on {object} to {date}.",
|
||||
"postpone_needs_when": "Tell me how far to postpone it — for example, by five days.",
|
||||
"postpone_past": "That would put '{task}' in the past.",
|
||||
"skipped": "Skipped this cycle of '{task}' on {object}. The next one is due {date}.",
|
||||
"not_found": "I couldn't find a maintenance task matching '{name}'.",
|
||||
"ambiguous": "That matches several tasks: {candidates}. Please be more specific.",
|
||||
"too_early": "'{task}' can only be completed closer to its due date.",
|
||||
"st_overdue": "{days} days overdue",
|
||||
"st_overdue_one": "1 day overdue",
|
||||
"st_due_today": "due today",
|
||||
"st_due_in": "due in {days} days",
|
||||
"st_due_in_one": "due tomorrow",
|
||||
"st_triggered": "triggered",
|
||||
"item_on": "{task} on {object}",
|
||||
"due_date_suffix": " The next due date is {date}.",
|
||||
"guide_header": "Stored guidance for '{task}' on {object}: {segments}.",
|
||||
"guide_none": "There are no stored instructions, documents or spare parts for '{task}' on {object}. I can offer general, non-verified advice instead — would you like that?",
|
||||
"guide_notes": "notes: {notes}",
|
||||
"guide_checklist": "{count} checklist steps: {steps}",
|
||||
"guide_doc": "linked document '{title}'",
|
||||
"guide_doc_page": "linked document '{title}', page {page}",
|
||||
"guide_url": "a documentation link is on file",
|
||||
"guide_part": "{qty} × {part} needed{extras}",
|
||||
"guide_part_loc": "stored at {loc}",
|
||||
"guide_part_stock": "{stock} in stock",
|
||||
"snoozed": "Snoozed reminders for '{task}' on {object} for {hours} hours.",
|
||||
"snooze_unavailable": "Notifications aren't configured, so there is nothing to snooze.",
|
||||
"stock_line": "{stock} × {part} in stock{loc}{low}.",
|
||||
"stock_loc": " (stored at {loc})",
|
||||
"stock_low": " — at or below the reorder threshold",
|
||||
"stock_untracked": "Stock isn't tracked for {part}.",
|
||||
"part_not_found": "I couldn't find a spare part matching '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Todo en orden — no hay ningún mantenimiento pendiente.",
|
||||
"none_due_mine": "Ahora mismo no tienes nada asignado.",
|
||||
"none_due_here": "En esta habitación no hay ningún mantenimiento pendiente.",
|
||||
"unknown_user": "No sé quién me está hablando, así que no puedo decirte qué tareas son tuyas.",
|
||||
"unknown_area": "No sé en qué habitación está este dispositivo — asígnalo primero a un área.",
|
||||
"tasks_due": "Hay {count} tareas de mantenimiento pendientes: {items}.",
|
||||
"task_due_one": "Hay una tarea de mantenimiento pendiente: {items}.",
|
||||
"completed": "Hecho. He anotado '{task}' en {object}.",
|
||||
"postponed": "He aplazado '{task}' en {object} al {date}.",
|
||||
"postpone_needs_when": "Dime cuánto quieres aplazarlo — por ejemplo, cinco días.",
|
||||
"postpone_past": "Eso dejaría '{task}' en el pasado.",
|
||||
"skipped": "Me he saltado este ciclo de '{task}' en {object}. El siguiente toca el {date}.",
|
||||
"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.",
|
||||
"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",
|
||||
"st_due_today": "toca hoy",
|
||||
"st_due_in": "toca en {days} días",
|
||||
"st_due_in_one": "toca mañana",
|
||||
"st_triggered": "se ha activado",
|
||||
"item_on": "{task} en {object}",
|
||||
"due_date_suffix": " La próxima fecha prevista es el {date}.",
|
||||
"guide_header": "Esto es lo que hay guardado sobre '{task}' en {object}: {segments}.",
|
||||
"guide_none": "No hay instrucciones, documentos ni recambios guardados para '{task}' en {object}. En su lugar puedo darte consejos generales, sin verificar. ¿Quieres que lo haga?",
|
||||
"guide_notes": "notas: {notes}",
|
||||
"guide_checklist": "{count} pasos de la lista: {steps}",
|
||||
"guide_doc": "documento vinculado '{title}'",
|
||||
"guide_doc_page": "documento vinculado '{title}', página {page}",
|
||||
"guide_url": "hay guardado un enlace a la documentación",
|
||||
"guide_part": "hacen falta {qty} × {part}{extras}",
|
||||
"guide_part_loc": "guardado en {loc}",
|
||||
"guide_part_stock": "{stock} en stock",
|
||||
"snoozed": "He silenciado los avisos de '{task}' en {object} durante {hours} horas.",
|
||||
"snooze_unavailable": "Las notificaciones no están configuradas, así que no hay nada que silenciar.",
|
||||
"stock_line": "{stock} × {part} en stock{loc}{low}.",
|
||||
"stock_loc": " (guardado en {loc})",
|
||||
"stock_low": " — en el umbral de reposición o por debajo",
|
||||
"stock_untracked": "De {part} no se lleva control de stock.",
|
||||
"part_not_found": "No he encontrado ningún recambio que coincida con '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Kaikki on kunnossa — mikään huolto ei vaadi huomiota.",
|
||||
"none_due_mine": "Sinulle ei ole juuri nyt osoitettu mitään.",
|
||||
"none_due_here": "Tässä huoneessa ei ole mitään huollettavaa.",
|
||||
"unknown_user": "En tiedä, kuka kysyy, joten en osaa kertoa, mitkä tehtävät ovat sinun.",
|
||||
"unknown_area": "En tiedä, missä huoneessa tämä laite on — liitä se ensin alueeseen.",
|
||||
"tasks_due": "{count} huoltotehtävää vaatii huomiota: {items}.",
|
||||
"task_due_one": "Yksi huoltotehtävä vaatii huomiota: {items}.",
|
||||
"completed": "Merkitsin tehtävän '{task}' tehdyksi kohteessa {object}.",
|
||||
"postponed": "Siirsin tehtävää '{task}' kohteessa {object} myöhemmäksi. Uusi ajankohta: {date}.",
|
||||
"postpone_needs_when": "Kerro, kuinka paljon siirrän sitä — esimerkiksi viisi päivää.",
|
||||
"postpone_past": "Silloin tehtävä '{task}' menisi menneisyyteen.",
|
||||
"skipped": "Ohitin tehtävän '{task}' tämän kerran kohteessa {object}. Seuraava ajankohta: {date}.",
|
||||
"not_found": "En löytänyt huoltotehtävää, joka vastaisi hakua '{name}'.",
|
||||
"ambiguous": "Tämä vastaa useaa tehtävää: {candidates}. Tarkenna vielä hieman.",
|
||||
"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ä",
|
||||
"st_due_today": "vuorossa tänään",
|
||||
"st_due_in": "vuorossa {days} päivän päästä",
|
||||
"st_due_in_one": "vuorossa huomenna",
|
||||
"st_triggered": "laukesi",
|
||||
"item_on": "{task} – kohteessa {object}",
|
||||
"due_date_suffix": " Seuraava ajankohta on {date}.",
|
||||
"guide_header": "Tallennetut tiedot tehtävälle '{task}' kohteessa {object}: {segments}.",
|
||||
"guide_none": "Tehtävälle '{task}' kohteessa {object} ei ole tallennettu ohjeita, dokumentteja eikä varaosia. Voin antaa sen sijaan yleisiä, varmistamattomia neuvoja — sopiiko se sinulle?",
|
||||
"guide_notes": "muistiinpanot: {notes}",
|
||||
"guide_checklist": "{count} tarkistuslistan kohtaa: {steps}",
|
||||
"guide_doc": "linkitetty dokumentti '{title}'",
|
||||
"guide_doc_page": "linkitetty dokumentti '{title}', sivu {page}",
|
||||
"guide_url": "tallennettuna on linkki dokumentaatioon",
|
||||
"guide_part": "tarvitaan {qty} × {part}{extras}",
|
||||
"guide_part_loc": "säilytyspaikka {loc}",
|
||||
"guide_part_stock": "{stock} varastossa",
|
||||
"snoozed": "Muistutukset tehtävästä '{task}' kohteessa {object} on vaimennettu {hours} tunniksi.",
|
||||
"snooze_unavailable": "Ilmoituksia ei ole määritetty, joten vaimennettavaa ei ole.",
|
||||
"stock_line": "{stock} × {part} varastossa{loc}{low}.",
|
||||
"stock_loc": " (säilytyspaikka {loc})",
|
||||
"stock_low": " — tilausrajalla tai sen alapuolella",
|
||||
"stock_untracked": "Varastosaldoa ei seurata varaosalle {part}.",
|
||||
"part_not_found": "En löytänyt varaosaa, joka vastaisi hakua '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Tout va bien — il n'y a aucun entretien à faire.",
|
||||
"none_due_mine": "Rien ne t'est attribué pour le moment.",
|
||||
"none_due_here": "Il n'y a aucun entretien à faire dans cette pièce.",
|
||||
"unknown_user": "Je ne sais pas qui me parle, donc je ne peux pas dire quelles tâches sont les tiennes.",
|
||||
"unknown_area": "Je ne sais pas dans quelle pièce se trouve cet appareil — commence par l'affecter à une zone.",
|
||||
"tasks_due": "{count} tâches d'entretien demandent ton attention : {items}.",
|
||||
"task_due_one": "Une tâche d'entretien demande ton attention : {items}.",
|
||||
"completed": "C'est fait pour '{task}' sur {object}.",
|
||||
"postponed": "J'ai reporté '{task}' sur {object} au {date}.",
|
||||
"postpone_needs_when": "Dis-moi de combien je dois le reporter — par exemple, de cinq jours.",
|
||||
"postpone_past": "Ça mettrait '{task}' dans le passé.",
|
||||
"skipped": "J'ai sauté ce cycle de '{task}' sur {object}. Le prochain est à faire le {date}.",
|
||||
"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.",
|
||||
"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",
|
||||
"st_due_today": "à faire aujourd'hui",
|
||||
"st_due_in": "à faire dans {days} jours",
|
||||
"st_due_in_one": "à faire demain",
|
||||
"st_triggered": "déclenché",
|
||||
"item_on": "{task} sur {object}",
|
||||
"due_date_suffix": " La prochaine échéance est le {date}.",
|
||||
"guide_header": "Voilà ce qui est enregistré pour '{task}' sur {object} : {segments}.",
|
||||
"guide_none": "Il n'y a ni instructions, ni documents, ni pièces détachées enregistrés pour '{task}' sur {object}. Je peux te donner des conseils généraux, non vérifiés — ça t'intéresse ?",
|
||||
"guide_notes": "notes : {notes}",
|
||||
"guide_checklist": "{count} étapes de la checklist : {steps}",
|
||||
"guide_doc": "document lié '{title}'",
|
||||
"guide_doc_page": "document lié '{title}', page {page}",
|
||||
"guide_url": "un lien vers la documentation est enregistré",
|
||||
"guide_part": "il faut {qty} × {part}{extras}",
|
||||
"guide_part_loc": "rangé à {loc}",
|
||||
"guide_part_stock": "{stock} en stock",
|
||||
"snoozed": "J'ai mis les rappels de '{task}' sur {object} en pause pendant {hours} heures.",
|
||||
"snooze_unavailable": "Les notifications ne sont pas configurées, il n'y a donc rien à mettre en pause.",
|
||||
"stock_line": "{stock} × {part} en stock{loc}{low}.",
|
||||
"stock_loc": " (rangé à {loc})",
|
||||
"stock_low": " — au niveau ou en dessous du seuil de réapprovisionnement",
|
||||
"stock_untracked": "Le stock n'est pas suivi pour {part}.",
|
||||
"part_not_found": "Je n'ai pas trouvé de pièce détachée qui corresponde à '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "सब ठीक है — किसी मेंटेनेंस पर ध्यान देने की ज़रूरत नहीं है।",
|
||||
"none_due_mine": "अभी आपके नाम पर कुछ भी असाइन नहीं है।",
|
||||
"none_due_here": "इस कमरे में किसी चीज़ की मेंटेनेंस बाकी नहीं है।",
|
||||
"unknown_user": "मुझे नहीं पता कि कौन पूछ रहा है, इसलिए यह बताना मुश्किल है कि कौन से काम आपके हैं।",
|
||||
"unknown_area": "मुझे नहीं पता कि यह डिवाइस किस कमरे में है — पहले इसे किसी एरिया में असाइन कर दीजिए।",
|
||||
"tasks_due": "{count} मेंटेनेंस कामों पर ध्यान देना है: {items}।",
|
||||
"task_due_one": "एक मेंटेनेंस काम पर ध्यान देना है: {items}।",
|
||||
"completed": "{object} पर '{task}' को पूरा कर दिया है।",
|
||||
"postponed": "{object} पर '{task}' को {date} तक टाल दिया है।",
|
||||
"postpone_needs_when": "बताइए कितना टालना है — जैसे पाँच दिन।",
|
||||
"postpone_past": "इससे '{task}' की तारीख़ पिछली हो जाएगी।",
|
||||
"skipped": "{object} पर '{task}' का यह चक्र छोड़ दिया है। अगली बार {date} को करना है।",
|
||||
"not_found": "'{name}' से मिलता-जुलता कोई मेंटेनेंस काम नहीं मिला।",
|
||||
"ambiguous": "इससे कई काम मेल खाते हैं: {candidates}। थोड़ा और साफ़ बताइए।",
|
||||
"too_early": "'{task}' को तय तारीख़ के क़रीब आने पर ही पूरा किया जा सकता है।",
|
||||
"st_overdue": "{days} दिन की देरी",
|
||||
"st_overdue_one": "एक दिन की देरी",
|
||||
"st_due_today": "आज होना है",
|
||||
"st_due_in": "{days} दिन में होना है",
|
||||
"st_due_in_one": "कल होना है",
|
||||
"st_triggered": "ट्रिगर हुआ",
|
||||
"item_on": "{object} पर {task}",
|
||||
"due_date_suffix": " अगली तारीख़ {date} है।",
|
||||
"guide_header": "{object} पर '{task}' के लिए सहेजी गई जानकारी: {segments}।",
|
||||
"guide_none": "{object} पर '{task}' के लिए कोई निर्देश, दस्तावेज़ या स्पेयर पार्ट सहेजा नहीं गया है। इसके बदले आम, बिना जाँची हुई सलाह दी जा सकती है — क्या आप चाहेंगे?",
|
||||
"guide_notes": "नोट्स: {notes}",
|
||||
"guide_checklist": "चेकलिस्ट के {count} चरण: {steps}",
|
||||
"guide_doc": "जुड़ा हुआ दस्तावेज़ '{title}'",
|
||||
"guide_doc_page": "जुड़ा हुआ दस्तावेज़ '{title}', पेज {page}",
|
||||
"guide_url": "एक दस्तावेज़ लिंक सहेजा हुआ है",
|
||||
"guide_part": "{qty} × {part} चाहिए{extras}",
|
||||
"guide_part_loc": "रखने की जगह: {loc}",
|
||||
"guide_part_stock": "स्टॉक में {stock}",
|
||||
"snoozed": "{object} पर '{task}' के रिमाइंडर {hours} घंटे के लिए बंद कर दिए हैं।",
|
||||
"snooze_unavailable": "नोटिफ़िकेशन सेट नहीं हैं, इसलिए बंद करने के लिए कुछ है ही नहीं।",
|
||||
"stock_line": "स्टॉक: {stock} × {part}{loc}{low}।",
|
||||
"stock_loc": " (रखने की जगह: {loc})",
|
||||
"stock_low": " — दोबारा ऑर्डर करने की सीमा पर या उससे नीचे",
|
||||
"stock_untracked": "{part} के लिए स्टॉक ट्रैक नहीं किया जाता।",
|
||||
"part_not_found": "'{name}' से मिलता-जुलता कोई स्पेयर पार्ट नहीं मिला।"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Minden rendben — nincs esedékes karbantartás.",
|
||||
"none_due_mine": "Jelenleg nincs hozzád rendelve semmi.",
|
||||
"none_due_here": "Ebben a szobában nincs esedékes karbantartás.",
|
||||
"unknown_user": "Nem tudom, ki kérdez, ezért nem tudom megmondani, melyik feladat a tiéd.",
|
||||
"unknown_area": "Nem tudom, melyik szobában van ez az eszköz — rendeld hozzá előbb egy területhez.",
|
||||
"tasks_due": "{count} karbantartási feladat esedékes: {items}.",
|
||||
"task_due_one": "Egy karbantartási feladat esedékes: {items}.",
|
||||
"completed": "Elvégzettnek jelöltem a feladatot: '{task}' – {object}.",
|
||||
"postponed": "Elhalasztottam ezt a feladatot: '{task}' – {object}. Az új időpont: {date}.",
|
||||
"postpone_needs_when": "Mondd meg, mennyivel halasszam el — például öt nappal.",
|
||||
"postpone_past": "'{task}': ezzel a feladat a múltba kerülne.",
|
||||
"skipped": "Kihagytam ezt a fordulót: '{task}' – {object}. A következő időpont: {date}.",
|
||||
"not_found": "Nem találtam ehhez illő karbantartási feladatot: '{name}'.",
|
||||
"ambiguous": "Több feladatra is illik: {candidates}. Fogalmazz pontosabban.",
|
||||
"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",
|
||||
"st_due_today": "ma esedékes",
|
||||
"st_due_in": "{days} nap múlva esedékes",
|
||||
"st_due_in_one": "holnap esedékes",
|
||||
"st_triggered": "aktiválódott",
|
||||
"item_on": "{task} – {object}",
|
||||
"due_date_suffix": " A következő esedékesség: {date}.",
|
||||
"guide_header": "'{task}' – {object}. A mentett információk: {segments}.",
|
||||
"guide_none": "Nincsenek mentett útmutatók, dokumentumok vagy alkatrészek ehhez: '{task}' – {object}. Helyette tudok általános, nem ellenőrzött tanácsot adni — kéred?",
|
||||
"guide_notes": "jegyzetek: {notes}",
|
||||
"guide_checklist": "{count} lépés az ellenőrzőlistán: {steps}",
|
||||
"guide_doc": "'{title}' című csatolt dokumentum",
|
||||
"guide_doc_page": "'{title}' című csatolt dokumentum, {page}. oldal",
|
||||
"guide_url": "el van mentve egy dokumentációs link",
|
||||
"guide_part": "{qty} × {part} szükséges{extras}",
|
||||
"guide_part_loc": "tárolási hely: {loc}",
|
||||
"guide_part_stock": "{stock} van készleten",
|
||||
"snoozed": "'{task}' – {object}: {hours} órára elnémítottam az emlékeztetőket.",
|
||||
"snooze_unavailable": "Nincsenek beállítva értesítések, így nincs mit elnémítani.",
|
||||
"stock_line": "{stock} × {part} van készleten{loc}{low}.",
|
||||
"stock_loc": " (tárolási hely: {loc})",
|
||||
"stock_low": " — az újrarendelési küszöbön vagy alatta",
|
||||
"stock_untracked": "Ehhez nincs készletnyilvántartás: {part}.",
|
||||
"part_not_found": "Nem találtam ehhez illő alkatrészt: '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Tutto a posto — non c'è nessuna manutenzione da fare.",
|
||||
"none_due_mine": "Al momento non hai niente assegnato.",
|
||||
"none_due_here": "In questa stanza non c'è nessuna manutenzione da fare.",
|
||||
"unknown_user": "Non so chi mi sta parlando, quindi non posso dirti quali attività sono tue.",
|
||||
"unknown_area": "Non so in quale stanza si trova questo dispositivo — assegnalo prima a un'area.",
|
||||
"tasks_due": "Ci sono {count} attività di manutenzione da fare: {items}.",
|
||||
"task_due_one": "C'è un'attività di manutenzione da fare: {items}.",
|
||||
"completed": "Fatto. Ho segnato '{task}' su {object}.",
|
||||
"postponed": "Ho rimandato '{task}' su {object} al {date}.",
|
||||
"postpone_needs_when": "Dimmi di quanto rimandarla — per esempio, di cinque giorni.",
|
||||
"postpone_past": "Così '{task}' finirebbe nel passato.",
|
||||
"skipped": "Ho saltato questo ciclo di '{task}' su {object}. Il prossimo è previsto per il {date}.",
|
||||
"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?",
|
||||
"too_early": "Puoi completare '{task}' solo quando si avvicina la scadenza.",
|
||||
"st_overdue": "{days} giorni di ritardo",
|
||||
"st_overdue_one": "un giorno di ritardo",
|
||||
"st_due_today": "da fare oggi",
|
||||
"st_due_in": "da fare tra {days} giorni",
|
||||
"st_due_in_one": "da fare domani",
|
||||
"st_triggered": "attivata",
|
||||
"item_on": "{task} su {object}",
|
||||
"due_date_suffix": " La prossima scadenza è il {date}.",
|
||||
"guide_header": "Ecco cosa è salvato per '{task}' su {object}: {segments}.",
|
||||
"guide_none": "Per '{task}' su {object} non ci sono istruzioni, documenti o ricambi salvati. Posso darti dei consigli generali, non verificati — ti va?",
|
||||
"guide_notes": "note: {notes}",
|
||||
"guide_checklist": "{count} passaggi della checklist: {steps}",
|
||||
"guide_doc": "documento collegato '{title}'",
|
||||
"guide_doc_page": "documento collegato '{title}', pagina {page}",
|
||||
"guide_url": "è salvato un link alla documentazione",
|
||||
"guide_part": "servono {qty} × {part}{extras}",
|
||||
"guide_part_loc": "conservato in {loc}",
|
||||
"guide_part_stock": "{stock} in magazzino",
|
||||
"snoozed": "Ho messo in pausa i promemoria di '{task}' su {object} per {hours} ore.",
|
||||
"snooze_unavailable": "Le notifiche non sono configurate, quindi non c'è niente da mettere in pausa.",
|
||||
"stock_line": "{stock} × {part} in magazzino{loc}{low}.",
|
||||
"stock_loc": " (conservato in {loc})",
|
||||
"stock_low": " — pari o sotto la soglia di riordino",
|
||||
"stock_untracked": "Per {part} non viene tenuta la giacenza.",
|
||||
"part_not_found": "Non ho trovato nessun ricambio che corrisponda a '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "すべて順調です。対応が必要なメンテナンスはありません。",
|
||||
"none_due_mine": "今のところ、あなたに割り当てられているものはありません。",
|
||||
"none_due_here": "この部屋にメンテナンスが必要なものはありません。",
|
||||
"unknown_user": "どなたが話しているかわからないので、どれがあなたのタスクかお伝えできません。",
|
||||
"unknown_area": "この機器がどの部屋にあるかわかりません。先にエリアを割り当ててください。",
|
||||
"tasks_due": "対応が必要なメンテナンスが{count}件あります:{items}。",
|
||||
"task_due_one": "対応が必要なメンテナンスが1件あります:{items}。",
|
||||
"completed": "{object}の「{task}」を完了にしました。",
|
||||
"postponed": "{object}の「{task}」を{date}に延期しました。",
|
||||
"postpone_needs_when": "どのくらい先に延ばすか教えてください。たとえば「5日」のように言ってください。",
|
||||
"postpone_past": "それだと「{task}」の日付が過去になってしまいます。",
|
||||
"skipped": "{object}の「{task}」の今回分をスキップしました。次回は{date}が期限です。",
|
||||
"not_found": "「{name}」に一致するメンテナンスタスクは見つかりませんでした。",
|
||||
"ambiguous": "いくつかのタスクが当てはまります:{candidates}。もう少し詳しく言ってください。",
|
||||
"too_early": "「{task}」は期限が近づいてからでないと完了にできません。",
|
||||
"st_overdue": "{days}日遅れ",
|
||||
"st_overdue_one": "1日遅れ",
|
||||
"st_due_today": "今日が期限",
|
||||
"st_due_in": "期限まであと{days}日",
|
||||
"st_due_in_one": "明日が期限",
|
||||
"st_triggered": "条件成立",
|
||||
"item_on": "{object}の{task}",
|
||||
"due_date_suffix": "次回の期限は{date}です。",
|
||||
"guide_header": "{object}の「{task}」について、次の情報が保存されています:{segments}。",
|
||||
"guide_none": "{object}の「{task}」には、手順も書類も交換部品も保存されていません。代わりに一般的な、確認済みではないアドバイスならお伝えできますが、いかがですか?",
|
||||
"guide_notes": "メモ:{notes}",
|
||||
"guide_checklist": "チェックリスト{count}項目:{steps}",
|
||||
"guide_doc": "関連書類「{title}」",
|
||||
"guide_doc_page": "関連書類「{title}」の{page}ページ",
|
||||
"guide_url": "説明書のリンクが保存されています",
|
||||
"guide_part": "{qty} × {part}が必要{extras}",
|
||||
"guide_part_loc": "保管場所は{loc}",
|
||||
"guide_part_stock": "在庫は{stock}",
|
||||
"snoozed": "{object}の「{task}」のリマインダーを{hours}時間、一時停止しました。",
|
||||
"snooze_unavailable": "通知が設定されていないので、一時停止するものがありません。",
|
||||
"stock_line": "{stock} × {part}の在庫があります{loc}{low}。",
|
||||
"stock_loc": "(保管場所は{loc})",
|
||||
"stock_low": "。在庫は発注の目安以下です",
|
||||
"stock_untracked": "{part}の在庫は管理していません。",
|
||||
"part_not_found": "「{name}」に一致する交換部品は見つかりませんでした。"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "다 괜찮아요. 지금 챙길 정비는 없어요.",
|
||||
"none_due_mine": "지금 배정된 작업은 없어요.",
|
||||
"none_due_here": "이 방에는 정비가 필요한 게 없어요.",
|
||||
"unknown_user": "누가 물어보는지 알 수 없어서, 어떤 작업이 배정돼 있는지 알려드릴 수 없어요.",
|
||||
"unknown_area": "이 기기가 어느 방에 있는지 몰라요. 먼저 구역을 지정해 주세요.",
|
||||
"tasks_due": "정비 작업 {count}개를 챙겨야 해요: {items}.",
|
||||
"task_due_one": "정비 작업 하나를 챙겨야 해요: {items}.",
|
||||
"completed": "{object}의 '{task}', 완료로 기록했어요.",
|
||||
"postponed": "{object}의 '{task}', {date}까지 미뤘어요.",
|
||||
"postpone_needs_when": "얼마나 미룰지 알려주세요. 예를 들어 '닷새 뒤'처럼요.",
|
||||
"postpone_past": "그러면 '{task}' 날짜가 과거가 돼요.",
|
||||
"skipped": "{object}의 '{task}', 이번 차례는 건너뛰었어요. 다음은 {date}에 예정돼 있어요.",
|
||||
"not_found": "'{name}'에 맞는 정비 작업을 찾지 못했어요.",
|
||||
"ambiguous": "여러 작업이 해당돼요: {candidates}. 조금 더 구체적으로 말씀해 주세요.",
|
||||
"too_early": "'{task}' 작업은 예정일이 가까워져야 완료할 수 있어요.",
|
||||
"st_overdue": "{days}일 지연",
|
||||
"st_overdue_one": "하루 지연",
|
||||
"st_due_today": "오늘 예정",
|
||||
"st_due_in": "{days}일 뒤 예정",
|
||||
"st_due_in_one": "내일 예정",
|
||||
"st_triggered": "조건 충족",
|
||||
"item_on": "{object}의 {task}",
|
||||
"due_date_suffix": " 다음은 {date}에 예정돼 있어요.",
|
||||
"guide_header": "{object}의 '{task}'에 저장된 정보예요: {segments}.",
|
||||
"guide_none": "{object}의 '{task}'에는 저장된 안내나 문서, 예비 부품이 없어요. 대신 일반적인, 검증되지 않은 조언을 드릴 수 있는데, 들어보실래요?",
|
||||
"guide_notes": "메모: {notes}",
|
||||
"guide_checklist": "체크리스트 {count}단계: {steps}",
|
||||
"guide_doc": "연결된 문서 '{title}'",
|
||||
"guide_doc_page": "연결된 문서 '{title}' {page}쪽",
|
||||
"guide_url": "설명서 링크가 저장돼 있어요",
|
||||
"guide_part": "{qty} × {part} 필요{extras}",
|
||||
"guide_part_loc": "보관 위치는 {loc}",
|
||||
"guide_part_stock": "재고 {stock}",
|
||||
"snoozed": "{object}의 '{task}' 알림을 {hours}시간 동안 미뤄뒀어요.",
|
||||
"snooze_unavailable": "알림이 설정돼 있지 않아서 미뤄둘 게 없어요.",
|
||||
"stock_line": "{stock} × {part} 재고가 있어요{loc}{low}.",
|
||||
"stock_loc": " (보관 위치: {loc})",
|
||||
"stock_low": ". 재주문 기준 이하예요",
|
||||
"stock_untracked": "{part} 재고는 관리하지 않아요.",
|
||||
"part_not_found": "'{name}'에 맞는 예비 부품을 찾지 못했어요."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Alt er i orden — ingenting trenger vedlikehold nå.",
|
||||
"none_due_mine": "Ingenting er tildelt deg akkurat nå.",
|
||||
"none_due_here": "Ingenting trenger vedlikehold i dette rommet.",
|
||||
"unknown_user": "Jeg vet ikke hvem som spør, så jeg kan ikke si hvilke oppgaver som er dine.",
|
||||
"unknown_area": "Jeg vet ikke hvilket rom denne enheten står i — knytt den til et område først.",
|
||||
"tasks_due": "{count} vedlikeholdsoppgaver trenger oppmerksomhet: {items}.",
|
||||
"task_due_one": "Én vedlikeholdsoppgave trenger oppmerksomhet: {items}.",
|
||||
"completed": "Jeg har krysset av '{task}' på {object}.",
|
||||
"postponed": "Jeg har utsatt '{task}' på {object} til {date}.",
|
||||
"postpone_needs_when": "Si hvor langt jeg skal utsette den — for eksempel fem dager.",
|
||||
"postpone_past": "Da ville '{task}' havne i fortiden.",
|
||||
"skipped": "Jeg hoppet over denne runden av '{task}' på {object}. Neste runde skal gjøres {date}.",
|
||||
"not_found": "Jeg fant ingen vedlikeholdsoppgave som passer med '{name}'.",
|
||||
"ambiguous": "Det passer med flere oppgaver: {candidates}. Vær litt mer presis.",
|
||||
"too_early": "'{task}' kan først krysses av nærmere forfallsdatoen.",
|
||||
"st_overdue": "{days} dager for sent",
|
||||
"st_overdue_one": "én dag for sent",
|
||||
"st_due_today": "skal gjøres i dag",
|
||||
"st_due_in": "skal gjøres om {days} dager",
|
||||
"st_due_in_one": "skal gjøres i morgen",
|
||||
"st_triggered": "utløst",
|
||||
"item_on": "{task} på {object}",
|
||||
"due_date_suffix": " Neste forfallsdato er {date}.",
|
||||
"guide_header": "Lagret informasjon om '{task}' på {object}: {segments}.",
|
||||
"guide_none": "Det er ikke lagret noen instruksjoner, dokumenter eller reservedeler for '{task}' på {object}. Jeg kan i stedet gi generelle, ubekreftede råd — vil du det?",
|
||||
"guide_notes": "notater: {notes}",
|
||||
"guide_checklist": "{count} punkter på sjekklisten: {steps}",
|
||||
"guide_doc": "tilknyttet dokument '{title}'",
|
||||
"guide_doc_page": "tilknyttet dokument '{title}', side {page}",
|
||||
"guide_url": "det er lagret en lenke til dokumentasjon",
|
||||
"guide_part": "{qty} × {part} trengs{extras}",
|
||||
"guide_part_loc": "oppbevaringssted {loc}",
|
||||
"guide_part_stock": "{stock} på lager",
|
||||
"snoozed": "Påminnelser for '{task}' på {object} er satt på pause i {hours} timer.",
|
||||
"snooze_unavailable": "Varsler er ikke satt opp, så det er ingenting å sette på pause.",
|
||||
"stock_line": "{stock} × {part} på lager{loc}{low}.",
|
||||
"stock_loc": " (oppbevaringssted {loc})",
|
||||
"stock_low": " — på eller under bestillingsgrensen",
|
||||
"stock_untracked": "Det føres ikke lager for {part}.",
|
||||
"part_not_found": "Jeg fant ingen reservedel som passer med '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Alles in orde — er is geen onderhoud nodig.",
|
||||
"none_due_mine": "Er staat op dit moment niets voor je open.",
|
||||
"none_due_here": "In deze kamer is geen onderhoud nodig.",
|
||||
"unknown_user": "Ik weet niet wie er vraagt, dus ik kan niet zeggen welke taken van jou zijn.",
|
||||
"unknown_area": "Ik weet niet in welke kamer dit apparaat staat — wijs het eerst aan een gebied toe.",
|
||||
"tasks_due": "Er zijn {count} onderhoudstaken die aandacht nodig hebben: {items}.",
|
||||
"task_due_one": "Er is één onderhoudstaak die aandacht nodig heeft: {items}.",
|
||||
"completed": "Gedaan. Ik heb '{task}' bij {object} afgevinkt.",
|
||||
"postponed": "Ik heb '{task}' bij {object} uitgesteld naar {date}.",
|
||||
"postpone_needs_when": "Zeg even hoe lang ik het moet uitstellen — bijvoorbeeld vijf dagen.",
|
||||
"postpone_past": "Dan zou '{task}' in het verleden liggen.",
|
||||
"skipped": "Ik heb deze ronde van '{task}' bij {object} overgeslagen. De volgende staat op {date}.",
|
||||
"not_found": "Ik kon geen onderhoudstaak vinden die past bij '{name}'.",
|
||||
"ambiguous": "Dat past bij meerdere taken: {candidates}. Kun je het iets preciezer zeggen?",
|
||||
"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",
|
||||
"st_due_today": "vandaag aan de beurt",
|
||||
"st_due_in": "over {days} dagen aan de beurt",
|
||||
"st_due_in_one": "morgen aan de beurt",
|
||||
"st_triggered": "geactiveerd",
|
||||
"item_on": "{task} bij {object}",
|
||||
"due_date_suffix": " De volgende keer is op {date}.",
|
||||
"guide_header": "Dit is er opgeslagen over '{task}' bij {object}: {segments}.",
|
||||
"guide_none": "Er zijn geen instructies, documenten of onderdelen opgeslagen voor '{task}' bij {object}. Ik kan je wel algemene, niet-gecontroleerde tips geven — wil je dat?",
|
||||
"guide_notes": "notities: {notes}",
|
||||
"guide_checklist": "{count} stappen op de checklist: {steps}",
|
||||
"guide_doc": "gekoppeld document '{title}'",
|
||||
"guide_doc_page": "gekoppeld document '{title}', pagina {page}",
|
||||
"guide_url": "er is een link naar de documentatie opgeslagen",
|
||||
"guide_part": "je hebt {qty} × {part} nodig{extras}",
|
||||
"guide_part_loc": "ligt in {loc}",
|
||||
"guide_part_stock": "{stock} op voorraad",
|
||||
"snoozed": "Ik heb de herinneringen voor '{task}' bij {object} {hours} uur gepauzeerd.",
|
||||
"snooze_unavailable": "Meldingen zijn niet ingesteld, dus er valt niets te pauzeren.",
|
||||
"stock_line": "{stock} × {part} op voorraad{loc}{low}.",
|
||||
"stock_loc": " (ligt in {loc})",
|
||||
"stock_low": " — op of onder de bestelgrens",
|
||||
"stock_untracked": "Van {part} wordt de voorraad niet bijgehouden.",
|
||||
"part_not_found": "Ik kon geen onderdeel vinden dat past bij '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Wszystko w porządku — żadna konserwacja nie wymaga uwagi.",
|
||||
"none_due_mine": "W tej chwili nic nie jest do ciebie przypisane.",
|
||||
"none_due_here": "W tym pomieszczeniu nic nie wymaga konserwacji.",
|
||||
"unknown_user": "Nie wiem, kto pyta, więc nie mogę powiedzieć, które zadania są twoje.",
|
||||
"unknown_area": "Nie wiem, w którym pomieszczeniu jest to urządzenie — najpierw przypisz je do obszaru.",
|
||||
"tasks_due": "Zadania konserwacyjne wymagające uwagi, łącznie {count}: {items}.",
|
||||
"task_due_one": "Jedno zadanie konserwacyjne wymaga uwagi: {items}.",
|
||||
"completed": "Zadanie '{task}' na urządzeniu {object} zapisane jako wykonane.",
|
||||
"postponed": "Zadanie '{task}' na urządzeniu {object} przełożone na {date}.",
|
||||
"postpone_needs_when": "Powiedz mi, o ile mam przełożyć — na przykład o pięć dni.",
|
||||
"postpone_past": "Wtedy '{task}' wypadłoby w przeszłości.",
|
||||
"skipped": "Ten cykl zadania '{task}' na urządzeniu {object} został pominięty. Następny termin to {date}.",
|
||||
"not_found": "Nie znalazłem zadania konserwacyjnego pasującego do '{name}'.",
|
||||
"ambiguous": "To pasuje do kilku zadań: {candidates}. Doprecyzuj, proszę.",
|
||||
"too_early": "Zadanie '{task}' można wykonać dopiero bliżej terminu.",
|
||||
"st_overdue": "{days} dni po terminie",
|
||||
"st_overdue_one": "jeden dzień po terminie",
|
||||
"st_due_today": "termin dzisiaj",
|
||||
"st_due_in": "termin za {days} dni",
|
||||
"st_due_in_one": "termin jutro",
|
||||
"st_triggered": "wyzwolone",
|
||||
"item_on": "{task} — {object}",
|
||||
"due_date_suffix": " Następny termin to {date}.",
|
||||
"guide_header": "Zapisane informacje dla zadania '{task}' na urządzeniu {object}: {segments}.",
|
||||
"guide_none": "Do zadania '{task}' na urządzeniu {object} nie ma zapisanych instrukcji, dokumentów ani części zamiennych. Mogę zamiast tego podać ogólne, niezweryfikowane wskazówki — chcesz?",
|
||||
"guide_notes": "notatki: {notes}",
|
||||
"guide_checklist": "kroki listy kontrolnej, łącznie {count}: {steps}",
|
||||
"guide_doc": "powiązany dokument '{title}'",
|
||||
"guide_doc_page": "powiązany dokument '{title}', strona {page}",
|
||||
"guide_url": "zapisany jest link do dokumentacji",
|
||||
"guide_part": "potrzebne: {qty} × {part}{extras}",
|
||||
"guide_part_loc": "miejsce przechowywania: {loc}",
|
||||
"guide_part_stock": "w magazynie: {stock}",
|
||||
"snoozed": "Przypomnienia dla zadania '{task}' na urządzeniu {object} wyciszone przez okres {hours} godzin.",
|
||||
"snooze_unavailable": "Powiadomienia nie są skonfigurowane, więc nie ma czego wyciszać.",
|
||||
"stock_line": "{stock} × {part} w magazynie{loc}{low}.",
|
||||
"stock_loc": " (miejsce przechowywania: {loc})",
|
||||
"stock_low": " — na poziomie progu zamówienia lub poniżej",
|
||||
"stock_untracked": "{part} — nie prowadzę stanu magazynowego dla tej części.",
|
||||
"part_not_found": "Nie znalazłem części zamiennej pasującej do '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Está tudo em ordem — nenhuma manutenção precisa de atenção.",
|
||||
"none_due_mine": "Nada está atribuído a você no momento.",
|
||||
"none_due_here": "Nada precisa de manutenção neste cômodo.",
|
||||
"unknown_user": "Não sei quem está perguntando, então não consigo dizer quais tarefas são suas.",
|
||||
"unknown_area": "Não sei em que cômodo este dispositivo está — atribua ele a uma área primeiro.",
|
||||
"tasks_due": "{count} tarefas de manutenção precisam de atenção: {items}.",
|
||||
"task_due_one": "Uma tarefa de manutenção precisa de atenção: {items}.",
|
||||
"completed": "Marquei '{task}' em {object} como concluída.",
|
||||
"postponed": "Adiei '{task}' em {object} para {date}.",
|
||||
"postpone_needs_when": "Me diga por quanto tempo adiar — por exemplo, cinco dias.",
|
||||
"postpone_past": "Isso colocaria '{task}' no passado.",
|
||||
"skipped": "Pulei este ciclo de '{task}' em {object}. O próximo vence em {date}.",
|
||||
"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.",
|
||||
"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",
|
||||
"st_due_today": "vence hoje",
|
||||
"st_due_in": "vence em {days} dias",
|
||||
"st_due_in_one": "vence amanhã",
|
||||
"st_triggered": "acionada",
|
||||
"item_on": "{task} em {object}",
|
||||
"due_date_suffix": " O próximo vencimento é {date}.",
|
||||
"guide_header": "Informações registradas sobre '{task}' em {object}: {segments}.",
|
||||
"guide_none": "Não há instruções, documentos nem peças de reposição registrados para '{task}' em {object}. Posso dar dicas gerais e não verificadas — você quer?",
|
||||
"guide_notes": "observações: {notes}",
|
||||
"guide_checklist": "{count} passos da lista de verificação: {steps}",
|
||||
"guide_doc": "documento vinculado '{title}'",
|
||||
"guide_doc_page": "documento vinculado '{title}', página {page}",
|
||||
"guide_url": "há um link de documentação registrado",
|
||||
"guide_part": "peça necessária: {qty} × {part}{extras}",
|
||||
"guide_part_loc": "local: {loc}",
|
||||
"guide_part_stock": "{stock} em estoque",
|
||||
"snoozed": "Silenciei os lembretes de '{task}' em {object} por {hours} horas.",
|
||||
"snooze_unavailable": "As notificações não estão configuradas, então não há nada para silenciar.",
|
||||
"stock_line": "{stock} × {part} em estoque{loc}{low}.",
|
||||
"stock_loc": " (local: {loc})",
|
||||
"stock_low": " — no limite de reposição ou abaixo dele",
|
||||
"stock_untracked": "Não há controle de estoque para {part}.",
|
||||
"part_not_found": "Não encontrei nenhuma peça de reposição que corresponda a '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Está tudo bem — não há nenhuma manutenção pendente.",
|
||||
"none_due_mine": "Neste momento não tens nada atribuído.",
|
||||
"none_due_here": "Nesta divisão não há nenhuma manutenção pendente.",
|
||||
"unknown_user": "Não sei quem está a falar, por isso não consigo dizer que tarefas são tuas.",
|
||||
"unknown_area": "Não sei em que divisão está este dispositivo — atribui-o primeiro a uma área.",
|
||||
"tasks_due": "Há {count} tarefas de manutenção a precisar de atenção: {items}.",
|
||||
"task_due_one": "Há uma tarefa de manutenção a precisar de atenção: {items}.",
|
||||
"completed": "Feito. Registei '{task}' em {object}.",
|
||||
"postponed": "Adiei '{task}' em {object} para {date}.",
|
||||
"postpone_needs_when": "Diz-me quanto tempo queres adiar — por exemplo, cinco dias.",
|
||||
"postpone_past": "Assim '{task}' ficaria no passado.",
|
||||
"skipped": "Saltei este ciclo de '{task}' em {object}. O próximo está previsto para {date}.",
|
||||
"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?",
|
||||
"too_early": "Só podes concluir '{task}' mais perto da data prevista.",
|
||||
"st_overdue": "{days} dias de atraso",
|
||||
"st_overdue_one": "um dia de atraso",
|
||||
"st_due_today": "para fazer hoje",
|
||||
"st_due_in": "para fazer daqui a {days} dias",
|
||||
"st_due_in_one": "para fazer amanhã",
|
||||
"st_triggered": "ativada",
|
||||
"item_on": "{task} em {object}",
|
||||
"due_date_suffix": " A próxima data prevista é {date}.",
|
||||
"guide_header": "Isto é o que está guardado sobre '{task}' em {object}: {segments}.",
|
||||
"guide_none": "Não há instruções, documentos nem peças guardadas para '{task}' em {object}. Em vez disso posso dar-te conselhos gerais, não verificados — queres?",
|
||||
"guide_notes": "notas: {notes}",
|
||||
"guide_checklist": "{count} passos da lista: {steps}",
|
||||
"guide_doc": "documento ligado '{title}'",
|
||||
"guide_doc_page": "documento ligado '{title}', página {page}",
|
||||
"guide_url": "está guardado um link para a documentação",
|
||||
"guide_part": "precisas de {qty} × {part}{extras}",
|
||||
"guide_part_loc": "guardado em {loc}",
|
||||
"guide_part_stock": "{stock} em stock",
|
||||
"snoozed": "Silenciei os lembretes de '{task}' em {object} durante {hours} horas.",
|
||||
"snooze_unavailable": "As notificações não estão configuradas, por isso não há nada para silenciar.",
|
||||
"stock_line": "{stock} × {part} em stock{loc}{low}.",
|
||||
"stock_loc": " (guardado em {loc})",
|
||||
"stock_low": " — no limite de reposição ou abaixo",
|
||||
"stock_untracked": "O stock de {part} não é acompanhado.",
|
||||
"part_not_found": "Não encontrei nenhuma peça que corresponda a '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Всё в порядке — обслуживание пока не требуется.",
|
||||
"none_due_mine": "На тебя сейчас ничего не назначено.",
|
||||
"none_due_here": "В этой комнате обслуживание не требуется.",
|
||||
"unknown_user": "Я не знаю, кто спрашивает, поэтому не могу определить, какие задачи твои.",
|
||||
"unknown_area": "Я не знаю, в какой комнате это устройство — сначала назначь ему зону.",
|
||||
"tasks_due": "Внимания требуют задачи обслуживания, всего {count}: {items}.",
|
||||
"task_due_one": "Внимания требует одна задача обслуживания: {items}.",
|
||||
"completed": "Задача '{task}' у устройства {object} отмечена как выполненная.",
|
||||
"postponed": "Задача '{task}' у устройства {object} перенесена на {date}.",
|
||||
"postpone_needs_when": "Скажи, на сколько перенести — например, на пять дней.",
|
||||
"postpone_past": "Тогда '{task}' окажется в прошлом.",
|
||||
"skipped": "Этот цикл задачи '{task}' у устройства {object} пропущен. Следующий срок — {date}.",
|
||||
"not_found": "Я не нашёл задачу обслуживания, подходящую под '{name}'.",
|
||||
"ambiguous": "Под это подходит несколько задач: {candidates}. Уточни, пожалуйста.",
|
||||
"too_early": "Задачу '{task}' можно выполнить только ближе к сроку.",
|
||||
"st_overdue": "просрочка {days} дней",
|
||||
"st_overdue_one": "просрочка один день",
|
||||
"st_due_today": "срок сегодня",
|
||||
"st_due_in": "срок в течение {days} дней",
|
||||
"st_due_in_one": "срок завтра",
|
||||
"st_triggered": "сработало",
|
||||
"item_on": "{task} — {object}",
|
||||
"due_date_suffix": " Следующий срок — {date}.",
|
||||
"guide_header": "Сохранённые сведения о задаче '{task}' у устройства {object}: {segments}.",
|
||||
"guide_none": "Для задачи '{task}' у устройства {object} не сохранено ни инструкций, ни документов, ни запчастей. Могу дать общие, непроверенные советы — хочешь?",
|
||||
"guide_notes": "заметки: {notes}",
|
||||
"guide_checklist": "шаги контрольного списка, всего {count}: {steps}",
|
||||
"guide_doc": "связанный документ '{title}'",
|
||||
"guide_doc_page": "связанный документ '{title}', страница {page}",
|
||||
"guide_url": "сохранена ссылка на документацию",
|
||||
"guide_part": "нужно: {qty} × {part}{extras}",
|
||||
"guide_part_loc": "место хранения: {loc}",
|
||||
"guide_part_stock": "на складе: {stock}",
|
||||
"snoozed": "Напоминания по задаче '{task}' у устройства {object} не будут приходить в течение {hours} часов.",
|
||||
"snooze_unavailable": "Уведомления не настроены, так что отключать нечего.",
|
||||
"stock_line": "{stock} × {part} на складе{loc}{low}.",
|
||||
"stock_loc": " (место хранения: {loc})",
|
||||
"stock_low": " — на уровне порога дозаказа или ниже",
|
||||
"stock_untracked": "{part} — запас по этой детали не отслеживается.",
|
||||
"part_not_found": "Я не нашёл запчасть, подходящую под '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Allt är i sin ordning — ingen underhållsuppgift behöver åtgärdas.",
|
||||
"none_due_mine": "Ingenting är tilldelat dig just nu.",
|
||||
"none_due_here": "Inget underhåll behöver göras i det här rummet.",
|
||||
"unknown_user": "Jag vet inte vem som frågar, så jag kan inte säga vilka uppgifter som är dina.",
|
||||
"unknown_area": "Jag vet inte vilket rum den här enheten står i — koppla den till ett område först.",
|
||||
"tasks_due": "{count} underhållsuppgifter behöver åtgärdas: {items}.",
|
||||
"task_due_one": "En underhållsuppgift behöver åtgärdas: {items}.",
|
||||
"completed": "Jag har bockat av '{task}' på {object}.",
|
||||
"postponed": "Jag har skjutit upp '{task}' på {object} till den {date}.",
|
||||
"postpone_needs_when": "Säg hur långt jag ska skjuta upp den — till exempel fem dagar.",
|
||||
"postpone_past": "Då skulle '{task}' hamna i det förflutna.",
|
||||
"skipped": "Jag har hoppat över den här omgången av '{task}' på {object}. Nästa omgång ska göras den {date}.",
|
||||
"not_found": "Jag hittade ingen underhållsuppgift som matchar '{name}'.",
|
||||
"ambiguous": "Det matchar flera uppgifter: {candidates}. Var lite mer specifik.",
|
||||
"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",
|
||||
"st_due_today": "ska göras idag",
|
||||
"st_due_in": "ska göras om {days} dagar",
|
||||
"st_due_in_one": "ska göras imorgon",
|
||||
"st_triggered": "utlöst",
|
||||
"item_on": "{task} på {object}",
|
||||
"due_date_suffix": " Nästa gång är det dags den {date}.",
|
||||
"guide_header": "Sparad information om '{task}' på {object}: {segments}.",
|
||||
"guide_none": "Det finns ingen sparad instruktion, inga dokument och inga reservdelar för '{task}' på {object}. Jag kan ge allmänna, overifierade råd i stället — vill du det?",
|
||||
"guide_notes": "anteckningar: {notes}",
|
||||
"guide_checklist": "{count} steg i checklistan: {steps}",
|
||||
"guide_doc": "länkat dokument '{title}'",
|
||||
"guide_doc_page": "länkat dokument '{title}', sidan {page}",
|
||||
"guide_url": "det finns en länk till dokumentation",
|
||||
"guide_part": "{qty} × {part} behövs{extras}",
|
||||
"guide_part_loc": "förvaringsplats {loc}",
|
||||
"guide_part_stock": "{stock} i lager",
|
||||
"snoozed": "Jag har pausat påminnelserna för '{task}' på {object} i {hours} timmar.",
|
||||
"snooze_unavailable": "Aviseringar är inte inställda, så det finns inget att pausa.",
|
||||
"stock_line": "{stock} × {part} i lager{loc}{low}.",
|
||||
"stock_loc": " (förvaringsplats {loc})",
|
||||
"stock_low": " — vid eller under beställningsgränsen",
|
||||
"stock_untracked": "Lagersaldot följs inte för {part}.",
|
||||
"part_not_found": "Jag hittade ingen reservdel som matchar '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Her şey yolunda — bakım bekleyen bir şey yok.",
|
||||
"none_due_mine": "Şu anda sana atanmış bir şey yok.",
|
||||
"none_due_here": "Bu odada bakım bekleyen bir şey yok.",
|
||||
"unknown_user": "Kimin sorduğunu bilmiyorum, bu yüzden hangi görevlerin sana ait olduğunu söyleyemem.",
|
||||
"unknown_area": "Bu cihazın hangi odada olduğunu bilmiyorum — önce onu bir alana ata.",
|
||||
"tasks_due": "{count} bakım görevi ilgilenmeyi bekliyor: {items}.",
|
||||
"task_due_one": "Bir bakım görevi ilgilenmeyi bekliyor: {items}.",
|
||||
"completed": "{object} için '{task}' görevi tamamlandı.",
|
||||
"postponed": "{object} için '{task}' görevi {date} tarihine ertelendi.",
|
||||
"postpone_needs_when": "Ne kadar erteleyeceğimi söyle — örneğin beş gün.",
|
||||
"postpone_past": "Bu durumda '{task}' görevi geçmişte kalır.",
|
||||
"skipped": "{object} için '{task}' görevinin bu turu atlandı. Sıradaki {date} tarihinde.",
|
||||
"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?",
|
||||
"too_early": "'{task}' görevi ancak son tarihine yaklaşınca tamamlanabilir.",
|
||||
"st_overdue": "{days} gün gecikmiş",
|
||||
"st_overdue_one": "bir gün gecikmiş",
|
||||
"st_due_today": "bugün yapılacak",
|
||||
"st_due_in": "{days} gün sonra yapılacak",
|
||||
"st_due_in_one": "yarın yapılacak",
|
||||
"st_triggered": "tetiklendi",
|
||||
"item_on": "{object} için {task}",
|
||||
"due_date_suffix": " Sıradaki bakım tarihi: {date}.",
|
||||
"guide_header": "{object} için '{task}' görevine ait kayıtlı bilgiler: {segments}.",
|
||||
"guide_none": "{object} için '{task}' görevine ait kayıtlı bir talimat, belge ya da yedek parça yok. İstersen bunun yerine genel, doğrulanmamış tavsiyeler verebilirim — ister misin?",
|
||||
"guide_notes": "notlar: {notes}",
|
||||
"guide_checklist": "{count} kontrol listesi adımı: {steps}",
|
||||
"guide_doc": "bağlı belge '{title}'",
|
||||
"guide_doc_page": "bağlı belge '{title}', sayfa {page}",
|
||||
"guide_url": "kayıtlı bir doküman bağlantısı var",
|
||||
"guide_part": "{qty} × {part} gerekiyor{extras}",
|
||||
"guide_part_loc": "saklama yeri: {loc}",
|
||||
"guide_part_stock": "stokta {stock} adet",
|
||||
"snoozed": "{object} için '{task}' görevinin hatırlatmaları {hours} saat susturuldu.",
|
||||
"snooze_unavailable": "Bildirimler ayarlanmamış, o yüzden susturulacak bir şey yok.",
|
||||
"stock_line": "Stokta {stock} × {part} var{loc}{low}.",
|
||||
"stock_loc": " (saklama yeri: {loc})",
|
||||
"stock_low": " — yeniden sipariş sınırında ya da altında",
|
||||
"stock_untracked": "{part} için stok takibi yapılmıyor.",
|
||||
"part_not_found": "'{name}' ile eşleşen bir yedek parça bulamadım."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "Усе гаразд — обслуговування наразі не потрібне.",
|
||||
"none_due_mine": "На тебе зараз нічого не призначено.",
|
||||
"none_due_here": "У цій кімнаті обслуговування не потрібне.",
|
||||
"unknown_user": "Я не знаю, хто питає, тому не можу визначити, які завдання твої.",
|
||||
"unknown_area": "Я не знаю, у якій кімнаті цей пристрій — спершу признач йому зону.",
|
||||
"tasks_due": "Уваги потребують завдання з обслуговування, усього {count}: {items}.",
|
||||
"task_due_one": "Уваги потребує одне завдання з обслуговування: {items}.",
|
||||
"completed": "Завдання '{task}' на пристрої {object} позначено як виконане.",
|
||||
"postponed": "Завдання '{task}' на пристрої {object} перенесено на {date}.",
|
||||
"postpone_needs_when": "Скажи, на скільки перенести — наприклад, на п’ять днів.",
|
||||
"postpone_past": "Тоді '{task}' опиниться в минулому.",
|
||||
"skipped": "Цей цикл завдання '{task}' на пристрої {object} пропущено. Наступний термін — {date}.",
|
||||
"not_found": "Я не знайшов завдання з обслуговування, яке відповідає '{name}'.",
|
||||
"ambiguous": "Цьому відповідає кілька завдань: {candidates}. Уточни, будь ласка.",
|
||||
"too_early": "Завдання '{task}' можна виконати лише ближче до терміну.",
|
||||
"st_overdue": "прострочення {days} днів",
|
||||
"st_overdue_one": "прострочення один день",
|
||||
"st_due_today": "термін сьогодні",
|
||||
"st_due_in": "термін протягом {days} днів",
|
||||
"st_due_in_one": "термін завтра",
|
||||
"st_triggered": "спрацювало",
|
||||
"item_on": "{task} — {object}",
|
||||
"due_date_suffix": " Наступний термін — {date}.",
|
||||
"guide_header": "Збережені відомості про завдання '{task}' на пристрої {object}: {segments}.",
|
||||
"guide_none": "Для завдання '{task}' на пристрої {object} не збережено ані інструкцій, ані документів, ані запчастин. Можу натомість дати загальні, неперевірені поради — хочеш?",
|
||||
"guide_notes": "нотатки: {notes}",
|
||||
"guide_checklist": "кроки контрольного списку, усього {count}: {steps}",
|
||||
"guide_doc": "пов’язаний документ '{title}'",
|
||||
"guide_doc_page": "пов’язаний документ '{title}', сторінка {page}",
|
||||
"guide_url": "збережено посилання на документацію",
|
||||
"guide_part": "потрібно: {qty} × {part}{extras}",
|
||||
"guide_part_loc": "місце зберігання: {loc}",
|
||||
"guide_part_stock": "на складі: {stock}",
|
||||
"snoozed": "Нагадування щодо завдання '{task}' на пристрої {object} не надходитимуть протягом {hours} годин.",
|
||||
"snooze_unavailable": "Сповіщення не налаштовані, тож вимикати нічого.",
|
||||
"stock_line": "{stock} × {part} на складі{loc}{low}.",
|
||||
"stock_loc": " (місце зберігання: {loc})",
|
||||
"stock_low": " — на рівні порога дозамовлення або нижче",
|
||||
"stock_untracked": "{part} — запас цієї деталі не відстежується.",
|
||||
"part_not_found": "Я не знайшов запчастину, яка відповідає '{name}'."
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"none_due": "一切正常,没有需要处理的维护。",
|
||||
"none_due_mine": "现在没有分配给你的任务。",
|
||||
"none_due_here": "这个房间里没有需要维护的东西。",
|
||||
"unknown_user": "我不知道是谁在问,所以没法判断哪些任务是你的。",
|
||||
"unknown_area": "我不知道这个设备在哪个房间,请先把它分配到一个区域。",
|
||||
"tasks_due": "有{count}项维护任务需要处理:{items}。",
|
||||
"task_due_one": "有一项维护任务需要处理:{items}。",
|
||||
"completed": "已完成{object}的“{task}”。",
|
||||
"postponed": "已把{object}的“{task}”推迟到{date}。",
|
||||
"postpone_needs_when": "告诉我要推迟多久,比如推迟五天。",
|
||||
"postpone_past": "那样的话,“{task}”的日期就会落在过去了。",
|
||||
"skipped": "已跳过{object}的“{task}”这一次。下一次在{date}到期。",
|
||||
"not_found": "我没有找到和“{name}”对得上的维护任务。",
|
||||
"ambiguous": "有好几项任务都对得上:{candidates}。请说得更具体一些。",
|
||||
"too_early": "“{task}”要等到接近到期日才能完成。",
|
||||
"st_overdue": "已逾期{days}天",
|
||||
"st_overdue_one": "已逾期一天",
|
||||
"st_due_today": "今天到期",
|
||||
"st_due_in": "{days}天后到期",
|
||||
"st_due_in_one": "明天到期",
|
||||
"st_triggered": "已触发",
|
||||
"item_on": "{object}的{task}",
|
||||
"due_date_suffix": "下一个到期日是{date}。",
|
||||
"guide_header": "关于{object}的“{task}”,保存的信息有:{segments}。",
|
||||
"guide_none": "{object}的“{task}”没有保存任何操作说明、文档或备件。我可以给你一些通用的、未经核实的建议,需要吗?",
|
||||
"guide_notes": "备注:{notes}",
|
||||
"guide_checklist": "{count}个清单步骤:{steps}",
|
||||
"guide_doc": "关联文档“{title}”",
|
||||
"guide_doc_page": "关联文档“{title}”第{page}页",
|
||||
"guide_url": "已保存一个说明书链接",
|
||||
"guide_part": "需要 {qty} × {part}{extras}",
|
||||
"guide_part_loc": "存放在{loc}",
|
||||
"guide_part_stock": "库存{stock}",
|
||||
"snoozed": "已把{object}的“{task}”提醒暂停{hours}小时。",
|
||||
"snooze_unavailable": "还没有设置通知,所以没有可以暂停的提醒。",
|
||||
"stock_line": "库存 {stock} × {part}{loc}{low}。",
|
||||
"stock_loc": "(存放在{loc})",
|
||||
"stock_low": ",已达到或低于补货下限",
|
||||
"stock_untracked": "{part}的库存没有记录。",
|
||||
"part_not_found": "我没有找到和“{name}”对得上的备件。"
|
||||
}
|
||||
@@ -115,7 +115,9 @@ class MaintenanceActionButton(MaintenanceEntity, ButtonEntity):
|
||||
if not self._task_data:
|
||||
raise HomeAssistantError(f"Task {self._task_id} no longer exists")
|
||||
if self._action == "complete":
|
||||
await self.coordinator.complete_maintenance(self._task_id, notes="Completed from dashboard button")
|
||||
await self.coordinator.complete_maintenance(
|
||||
self._task_id, notes="Completed from dashboard button", unattended=True
|
||||
)
|
||||
elif self._action == "skip":
|
||||
await self.coordinator.skip_maintenance(self._task_id, reason="Skipped from dashboard button")
|
||||
elif self._action == "reset":
|
||||
|
||||
@@ -138,6 +138,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Das Format des Benachrichtigungsdienstes ist ungültig. Verwenden Sie 'notify.dienstname'.",
|
||||
"failed": "❌ Testbenachrichtigung konnte nicht gesendet werden. Bitte prüfen Sie Ihre Konfiguration.",
|
||||
"push_message": "🔧 Testbenachrichtigung — Ihre Benachrichtigungseinrichtung funktioniert!",
|
||||
"user_no_device": "ℹ️ Diesem Nutzer ist kein Companion-Gerät zugeordnet — seine Erinnerungen gehen an den Haushalts-Dienst.",
|
||||
},
|
||||
"nl": {
|
||||
"success": "✅ Testmelding verzonden — uw service werkt. Krijgt een specifiek apparaat niets, controleer dat apparaat in uw notify-groep (en het HA-logboek).",
|
||||
@@ -145,6 +146,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Het formaat van de meldingsservice is ongeldig. Gebruik 'notify.servicenaam'.",
|
||||
"failed": "❌ Testmelding kon niet worden verzonden. Controleer uw configuratie.",
|
||||
"push_message": "🔧 Testmelding — uw meldingsinstellingen werken!",
|
||||
"user_no_device": "ℹ️ Aan deze gebruiker is geen Companion-apparaat gekoppeld — hun herinneringen gaan naar de huishoudelijke dienst.",
|
||||
},
|
||||
"fr": {
|
||||
"success": "✅ Notification de test envoyée — votre service fonctionne. Si un appareil précis ne reçoit rien, vérifiez-le dans votre groupe notify (et les journaux HA).",
|
||||
@@ -152,6 +154,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Le format du service de notification est invalide. Utilisez 'notify.nom_du_service'.",
|
||||
"failed": "❌ Impossible d'envoyer la notification de test. Veuillez vérifier votre configuration.",
|
||||
"push_message": "🔧 Notification de test — votre configuration de notifications fonctionne !",
|
||||
"user_no_device": "ℹ️ Aucun appareil Companion n'est lié à cet utilisateur — ses rappels partent vers le service du foyer.",
|
||||
},
|
||||
"it": {
|
||||
"success": "✅ Notifica di test inviata — il servizio funziona. Se un dispositivo specifico non riceve nulla, controllalo nel tuo gruppo notify (e nei log di HA).",
|
||||
@@ -159,6 +162,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Il formato del servizio di notifica non è valido. Usa 'notify.nome_servizio'.",
|
||||
"failed": "❌ Impossibile inviare la notifica di test. Verifica la tua configurazione.",
|
||||
"push_message": "🔧 Notifica di test — la configurazione delle notifiche funziona!",
|
||||
"user_no_device": "ℹ️ Nessun dispositivo Companion è collegato a questo utente — i suoi promemoria vanno al servizio della casa.",
|
||||
},
|
||||
"es": {
|
||||
"success": "✅ Notificación de prueba enviada — tu servicio funciona. Si un dispositivo concreto no recibe nada, revísalo en tu grupo notify (y los registros de HA).",
|
||||
@@ -166,6 +170,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ El formato del servicio de notificación no es válido. Use 'notify.nombre_servicio'.",
|
||||
"failed": "❌ No se pudo enviar la notificación de prueba. Verifique su configuración.",
|
||||
"push_message": "🔧 Notificación de prueba — ¡su configuración de notificaciones funciona!",
|
||||
"user_no_device": "ℹ️ Este usuario no tiene ningún dispositivo Companion vinculado — sus recordatorios van al servicio del hogar.",
|
||||
},
|
||||
"en": {
|
||||
"success": "✅ Test notification sent — your service works. If a specific device gets nothing, check that device inside your notify group (and Home Assistant's logs).",
|
||||
@@ -173,6 +178,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ The notification service format is invalid. Use 'notify.service_name'.",
|
||||
"failed": "❌ Failed to send the test notification. Please verify your service configuration.",
|
||||
"push_message": "🔧 Test notification — your notification setup is working!",
|
||||
"user_no_device": "ℹ️ No Companion device is linked to this user — their reminders go to the household service.",
|
||||
},
|
||||
"ru": {
|
||||
"success": "✅ Тестовое уведомление отправлено — сервис работает. Если конкретное устройство ничего не получает, проверьте его в вашей notify-группе (и в журналах HA).",
|
||||
@@ -180,6 +186,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Неверный формат сервиса уведомлений. Используйте 'notify.имя_сервиса'.",
|
||||
"failed": "❌ Не удалось отправить тестовое уведомление. Проверьте настройки сервиса.",
|
||||
"push_message": "🔧 Тестовое уведомление — ваша система уведомлений работает!",
|
||||
"user_no_device": "ℹ️ К этому пользователю не привязано устройство Companion — его напоминания уходят в общий сервис.",
|
||||
},
|
||||
"uk": {
|
||||
"success": "✅ Тестове сповіщення надіслано — служба працює. Якщо певний пристрій нічого не отримує, перевірте його у вашій notify-групі (та в журналах HA).",
|
||||
@@ -187,6 +194,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Невірний формат служби сповіщень. Використовуйте 'notify.service_name'.",
|
||||
"failed": "❌ Не вдалося надіслати тестове сповіщення. Перевірте конфігурацію служби.",
|
||||
"push_message": "🔧 Тестове сповіщення — ваші сповіщення працюють!",
|
||||
"user_no_device": "ℹ️ До цього користувача не прив'язано пристрій Companion — його нагадування надходять до загальної служби.",
|
||||
},
|
||||
"pt": {
|
||||
"success": "✅ Notificação de teste enviada — o seu serviço funciona. Se um dispositivo específico não receber nada, verifique-o no seu grupo notify (e nos registos do HA).",
|
||||
@@ -194,6 +202,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Formato inválido do serviço de notificação. Use 'notify.nome_do_servico'.",
|
||||
"failed": "❌ Falha ao enviar a notificação de teste. Verifique a configuração do serviço.",
|
||||
"push_message": "🔧 Notificação de teste — as suas notificações estão a funcionar!",
|
||||
"user_no_device": "ℹ️ Este utilizador não tem nenhum dispositivo Companion associado — os lembretes seguem para o serviço da casa.",
|
||||
},
|
||||
"zh": {
|
||||
"success": "✅ 测试通知已发送 — 您的服务正常。如果某个设备未收到,请在您的 notify 群组中检查该设备(以及 HA 日志)。",
|
||||
@@ -201,6 +210,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ 通知服务格式无效。请使用 'notify.服务名称' 格式。",
|
||||
"failed": "❌ 测试通知发送失败。请验证您的服务配置。",
|
||||
"push_message": "🔧 测试通知 — 您的通知设置已生效!",
|
||||
"user_no_device": "ℹ️ 该用户未关联 Companion 设备 — 其提醒将发送到家庭通知服务。",
|
||||
},
|
||||
"pt-br": {
|
||||
"success": "✅ Notificação de teste enviada — seu serviço funciona. Se um dispositivo específico não receber nada, verifique esse dispositivo dentro do seu grupo de notificação (e os logs do Home Assistant).",
|
||||
@@ -208,6 +218,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ O formato do serviço de notificação é inválido. Use 'notify.nome_do_servico'.",
|
||||
"failed": "❌ Falha ao enviar a notificação de teste. Verifique a configuração do serviço.",
|
||||
"push_message": "🔧 Notificação de teste — sua configuração de notificações está funcionando!",
|
||||
"user_no_device": "ℹ️ Este usuário não tem nenhum dispositivo Companion vinculado — os lembretes vão para o serviço da casa.",
|
||||
},
|
||||
"hu": {
|
||||
"success": "✅ Tesztértesítés elküldve — a szolgáltatás működik. Ha egy adott eszközre nem érkezik semmi, ellenőrizze az eszközt az értesítési csoportban (és a Home Assistant naplóit).",
|
||||
@@ -215,6 +226,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Az értesítési szolgáltatás formátuma érvénytelen. Használja a 'notify.szolgaltatas_nev' formát.",
|
||||
"failed": "❌ A tesztértesítés küldése nem sikerült. Ellenőrizze a szolgáltatás beállításait.",
|
||||
"push_message": "🔧 Tesztértesítés — az értesítési rendszere működik!",
|
||||
"user_no_device": "ℹ️ Ehhez a felhasználóhoz nincs Companion eszköz rendelve — az emlékeztetői a háztartási szolgáltatásra mennek.",
|
||||
},
|
||||
"ko": {
|
||||
"success": "✅ 테스트 알림을 보냈습니다 — 서비스가 작동합니다. 특정 기기에 알림이 오지 않으면 알림 그룹 내 해당 기기와 Home Assistant 로그를 확인하세요.",
|
||||
@@ -222,6 +234,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ 알림 서비스 형식이 잘못되었습니다. 'notify.service_name' 형식을 사용하세요.",
|
||||
"failed": "❌ 테스트 알림 전송에 실패했습니다. 서비스 설정을 확인하세요.",
|
||||
"push_message": "🔧 테스트 알림 — 알림 설정이 정상 작동합니다!",
|
||||
"user_no_device": "ℹ️ 이 사용자에게 연결된 Companion 기기가 없습니다 — 알림은 가정 서비스로 전송됩니다.",
|
||||
},
|
||||
"tr": {
|
||||
"success": "✅ Test bildirimi gönderildi — servisiniz çalışıyor. Belirli bir cihaza bildirim gelmiyorsa bildirim grubunuzdaki o cihazı (ve Home Assistant günlüklerini) kontrol edin.",
|
||||
@@ -229,6 +242,7 @@ _TEST_NOTIFICATION_RESULTS: dict[str, dict[str, str]] = {
|
||||
"invalid_service": "❌ Bildirim servisi biçimi geçersiz. 'notify.servis_adi' kullanın.",
|
||||
"failed": "❌ Test bildirimi gönderilemedi. Servis yapılandırmanızı kontrol edin.",
|
||||
"push_message": "🔧 Test bildirimi — bildirim kurulumunuz çalışıyor!",
|
||||
"user_no_device": "ℹ️ Bu kullanıcıya bağlı bir Companion cihazı yok — hatırlatıcıları ev servisine gider.",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -240,14 +254,36 @@ def _get_test_result_text(hass: HomeAssistant, key: str) -> str:
|
||||
return texts.get(key, texts.get("failed", key))
|
||||
|
||||
|
||||
async def send_test_notification(hass: HomeAssistant, options: dict[str, Any]) -> str:
|
||||
"""Send a test notification using the configured notify service.
|
||||
async def send_test_notification(
|
||||
hass: HomeAssistant,
|
||||
options: dict[str, Any],
|
||||
user_id: str | None = None,
|
||||
) -> str:
|
||||
"""Send a test notification, optionally to ONE household member.
|
||||
|
||||
Returns a result key ("success", "no_service", "invalid_service", "failed")
|
||||
that callers map to localized text. Action buttons are included whenever
|
||||
the corresponding action-feature toggles are enabled, so the rendered
|
||||
notification matches the real layout users see for actual tasks.
|
||||
Returns a result key ("success", "no_service", "invalid_service",
|
||||
"failed", "user_no_device") that callers map to localized text. Action
|
||||
buttons are included whenever the corresponding action-feature toggles are
|
||||
enabled, so the rendered notification matches the real layout users see
|
||||
for actual tasks.
|
||||
|
||||
With ``user_id`` the target is resolved through the SAME lookup the real
|
||||
per-task routing uses (``get_user_notify_services``). That is the whole
|
||||
point of a per-user test: one that took its own path could report success
|
||||
while real reminders still went somewhere else — which is exactly how the
|
||||
wrong-service bug behind #75 stayed invisible.
|
||||
"""
|
||||
if user_id:
|
||||
from .helpers.notification_manager import get_user_notify_services
|
||||
|
||||
user_services = await get_user_notify_services(hass, user_id)
|
||||
if not user_services:
|
||||
# Not a failure: this member simply has no Companion device, so
|
||||
# their reminders fall back to the household service. Saying so is
|
||||
# more useful than sending a test that proves nothing.
|
||||
return "user_no_device"
|
||||
return await _send_test_to(hass, options, user_services)
|
||||
|
||||
notify_service = str(options.get(CONF_NOTIFY_SERVICE, ""))
|
||||
if not notify_service:
|
||||
return "no_service"
|
||||
@@ -258,6 +294,15 @@ async def send_test_notification(hass: HomeAssistant, options: dict[str, Any]) -
|
||||
if error:
|
||||
return "invalid_service"
|
||||
|
||||
return await _send_test_to(hass, options, [normalized])
|
||||
|
||||
|
||||
async def _send_test_to(
|
||||
hass: HomeAssistant,
|
||||
options: dict[str, Any],
|
||||
services: list[str],
|
||||
) -> str:
|
||||
"""Send the test payload to every resolved service; "success" if any went."""
|
||||
try:
|
||||
from .helpers.notification_manager import async_dispatch_notify
|
||||
|
||||
@@ -279,11 +324,13 @@ async def send_test_notification(hass: HomeAssistant, options: dict[str, Any]) -
|
||||
test_actions.append({"action": "MS_TEST_SNOOZE", "title": "\U0001f4a4 Snooze"})
|
||||
service_data["data"] = {"actions": test_actions}
|
||||
# Dual-path: legacy notify service OR notify entity (send_message).
|
||||
if not await async_dispatch_notify(hass, normalized, service_data, blocking=True):
|
||||
return "failed"
|
||||
return "success"
|
||||
sent_any = False
|
||||
for service in services:
|
||||
if await async_dispatch_notify(hass, service, service_data, blocking=True):
|
||||
sent_any = True
|
||||
return "success" if sent_any else "failed"
|
||||
except Exception: # noqa: BLE001 - any failure mode reports "failed" to the UI
|
||||
_LOGGER.debug("Test notification failed for %s", notify_service, exc_info=True)
|
||||
_LOGGER.debug("Test notification failed for %s", services, exc_info=True)
|
||||
return "failed"
|
||||
|
||||
|
||||
|
||||
@@ -249,6 +249,11 @@ CONF_SNOOZE_DURATION_HOURS = "snooze_duration_hours"
|
||||
|
||||
# v2.15.0: opt-in weekly digest — a single Monday-morning summary notification.
|
||||
CONF_WEEKLY_DIGEST_ENABLED = "weekly_digest_enabled"
|
||||
|
||||
# v2.44: opt-in copy of the shipped Assist sentence files into
|
||||
# <config>/custom_sentences/. The classic conversation agent reads sentences
|
||||
# from there and nowhere else, and the HACS release ZIP never carried them.
|
||||
CONF_INSTALL_ASSIST_SENTENCES = "install_assist_sentences"
|
||||
CONF_WARRANTY_REMINDER_ENABLED = "warranty_reminder_enabled"
|
||||
CONF_WARRANTY_REMINDER_DAYS = "warranty_reminder_days"
|
||||
DEFAULT_WARRANTY_REMINDER_DAYS = 30
|
||||
|
||||
@@ -5,11 +5,12 @@ from __future__ import annotations
|
||||
import logging
|
||||
import time
|
||||
from collections.abc import Mapping
|
||||
from datetime import date, datetime, timedelta
|
||||
from datetime import date, timedelta
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -49,6 +50,7 @@ from .const import (
|
||||
ScheduleType,
|
||||
TriggerEntityState,
|
||||
)
|
||||
from .helpers.budget import compute_spend
|
||||
from .helpers.schedule import normalize_task_storage, read_legacy_fields
|
||||
from .models.maintenance_object import MaintenanceObject
|
||||
from .models.maintenance_task import MaintenanceTask
|
||||
@@ -786,53 +788,18 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
Stores the result in hass.data[DOMAIN][BUDGET_CACHE_KEY] so that
|
||||
every coordinator reads from the same cache instead of each one
|
||||
re-scanning all entries on every 5-minute refresh.
|
||||
|
||||
The scan itself lives in :func:`helpers.budget.compute_spend`, shared
|
||||
with the ``budget_status`` WS command — this method only owns the
|
||||
*caching* around it. The two used to hold divergent copies of the same
|
||||
loop, so the alert and the panel could report different spend.
|
||||
"""
|
||||
now = dt_util.now()
|
||||
monthly = 0.0
|
||||
yearly = 0.0
|
||||
|
||||
for ce in self.hass.config_entries.async_entries(DOMAIN):
|
||||
if ce.unique_id == GLOBAL_UNIQUE_ID:
|
||||
continue
|
||||
|
||||
rd = getattr(ce, "runtime_data", None)
|
||||
ce_store = getattr(rd, "store", None) if rd else None
|
||||
|
||||
for tid in ce.data.get(CONF_TASKS, {}):
|
||||
if ce_store is not None:
|
||||
history = ce_store.get_history(tid)
|
||||
else:
|
||||
history = ce.data.get(CONF_TASKS, {}).get(tid, {}).get("history", [])
|
||||
|
||||
for h_entry in history:
|
||||
if h_entry.get("type") != "completed":
|
||||
continue
|
||||
cost = h_entry.get("cost")
|
||||
if cost is None:
|
||||
continue
|
||||
try:
|
||||
cost_val = float(cost)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
ts = h_entry.get("timestamp", "")
|
||||
try:
|
||||
entry_dt = datetime.fromisoformat(ts)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
# Ensure TZ-aware so year/month boundaries are evaluated
|
||||
# in HA's local timezone (otherwise off-by-one near midnight).
|
||||
if entry_dt.tzinfo is None:
|
||||
entry_dt = entry_dt.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE)
|
||||
entry_dt = dt_util.as_local(entry_dt)
|
||||
if entry_dt.year == now.year:
|
||||
yearly += cost_val
|
||||
if entry_dt.month == now.month:
|
||||
monthly += cost_val
|
||||
monthly, yearly = compute_spend(self.hass)
|
||||
|
||||
self.hass.data.setdefault(DOMAIN, {})[BUDGET_CACHE_KEY] = {
|
||||
"monthly_spent": monthly,
|
||||
"yearly_spent": yearly,
|
||||
"last_updated": now,
|
||||
"last_updated": dt_util.now(),
|
||||
}
|
||||
|
||||
async def _async_check_budget(self, task_results: dict[str, Any]) -> None:
|
||||
@@ -965,13 +932,63 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
restock_quantity: float | None = None,
|
||||
used_parts: list[dict[str, Any]] | None = None,
|
||||
auto: bool = False,
|
||||
unattended: bool = False,
|
||||
) -> None:
|
||||
"""Mark a task as completed and persist."""
|
||||
"""Mark a task as completed and persist.
|
||||
|
||||
``unattended`` marks a surface that cannot ask a human for anything —
|
||||
a button press, a to-do tick, an NFC tap, a notification button, a
|
||||
voice command. Those paths attach a canned provenance note
|
||||
("Completed via NFC tag"), which must NOT be mistaken for the note a
|
||||
task demands: the point of a required note is that somebody wrote it.
|
||||
"""
|
||||
merged = self._get_merged_tasks_data()
|
||||
if task_id not in merged:
|
||||
_LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title)
|
||||
return
|
||||
|
||||
# Required completion details. Checked HERE — the one point every
|
||||
# surface funnels through — so a task demanding a note cannot be
|
||||
# closed out from a button, the to-do list, an NFC tag, a
|
||||
# notification action, voice or a service call.
|
||||
#
|
||||
# Deliberately BEFORE the double-complete guard below: a rejected
|
||||
# attempt must not stamp that guard, or the corrected completion the
|
||||
# user makes seconds later (after filling in the dialog) would be
|
||||
# silently swallowed as a duplicate.
|
||||
#
|
||||
# Automatic completions are exempt — a self-clearing problem sensor
|
||||
# has nobody to ask, and a required photo would strand the task.
|
||||
if not auto:
|
||||
from .helpers.completion_requirements import (
|
||||
missing_completion_fields,
|
||||
required_completion_fields,
|
||||
)
|
||||
|
||||
if unattended:
|
||||
# Nobody was asked, so nothing the caller attached counts as
|
||||
# an answer — a canned "Completed from the To-do list" note is
|
||||
# provenance, not the note the task demands.
|
||||
missing = required_completion_fields(merged[task_id])
|
||||
else:
|
||||
missing = missing_completion_fields(
|
||||
merged[task_id],
|
||||
notes=notes,
|
||||
cost=cost,
|
||||
duration=duration,
|
||||
photo_doc_id=photo_doc_id,
|
||||
completed_by=completed_by,
|
||||
)
|
||||
if missing:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="completion_details_required",
|
||||
translation_placeholders={
|
||||
"task_name": str(merged[task_id].get("name", task_id)),
|
||||
"fields": ", ".join(missing),
|
||||
},
|
||||
)
|
||||
|
||||
# Household double-complete guard (journey M1): two people seeing the
|
||||
# same overdue task and both tapping Complete within seconds would
|
||||
# record two completions — duplicated history/cost and a DOUBLE
|
||||
@@ -1241,6 +1258,28 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self._lifecycle_event_payload(task, task_id, reason=reason),
|
||||
)
|
||||
|
||||
|
||||
async def async_refresh_now(self) -> None:
|
||||
"""Recompute immediately — for changes a person just made.
|
||||
|
||||
``async_request_refresh`` is debounced, and Home Assistant's default
|
||||
window is **ten seconds** (``REQUEST_REFRESH_DEFAULT_COOLDOWN``). That
|
||||
is right for trigger-driven churn — a noisy sensor must not recompute
|
||||
the object on every state change — and wrong for a user action: any
|
||||
second action within that window had its request coalesced to the end
|
||||
of it, so the panel kept showing the old computed status for up to ten
|
||||
seconds and the change looked like it had done nothing.
|
||||
|
||||
Assigning a user and then postponing the task was the reported case
|
||||
(#111 review, 2026-07-28), but nothing about it is specific to those
|
||||
two: completing two tasks on one object in quick succession, or any
|
||||
bulk action, hit the same window.
|
||||
|
||||
The recompute covers one object's tasks, so doing it per user action is
|
||||
cheap; the debounced path stays exactly as it was for triggers.
|
||||
"""
|
||||
await self.async_refresh()
|
||||
|
||||
async def _persist_and_signal_task_change(
|
||||
self,
|
||||
task_id: str,
|
||||
@@ -1261,7 +1300,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self.hass,
|
||||
SIGNAL_TASK_RESET.format(entry_id=self.entry.entry_id, task_id=task_id),
|
||||
)
|
||||
await self.async_request_refresh()
|
||||
await self.async_refresh_now()
|
||||
|
||||
def _lifecycle_event_payload(
|
||||
self,
|
||||
@@ -1321,7 +1360,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
new_data = dict(self.entry.data)
|
||||
new_data[CONF_TASKS] = tasks_data
|
||||
self.hass.config_entries.async_update_entry(self.entry, data=new_data)
|
||||
await self.async_request_refresh()
|
||||
await self.async_refresh_now()
|
||||
|
||||
async def async_persist_trigger_runtime(
|
||||
self,
|
||||
|
||||
@@ -165,6 +165,9 @@ class CounterTrigger(BaseTrigger):
|
||||
# falls back to the RAW counter value — a 27,000 km odometer reads
|
||||
# as "27000/15000, overdue" (issue #102). Debounced upstream, and
|
||||
# the periodic refresh never writes baselines, so no loop.
|
||||
# Debounced ON PURPOSE: a counter entity can change many times
|
||||
# a minute, and this is the path the ten-second coalescing
|
||||
# window exists for. User actions use async_refresh_now.
|
||||
await self._coordinator.async_request_refresh()
|
||||
|
||||
def reset(self) -> None:
|
||||
|
||||
+43
-27
@@ -11,18 +11,14 @@ import { property, state } from "lit/decorators.js";
|
||||
import { t, ensureLocale, DEFAULT_CURRENCY_SYMBOL } from "../styles";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { sectionCardSharedStyles } from "./section-card-shared-styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
interface BudgetStatus {
|
||||
monthly_budget?: number;
|
||||
monthly_spent?: number;
|
||||
yearly_budget?: number;
|
||||
yearly_spent?: number;
|
||||
currency_symbol?: string;
|
||||
}
|
||||
import type { BudgetStatus, HomeAssistant } from "../types";
|
||||
|
||||
interface CardConfig { type: string; title?: string; }
|
||||
|
||||
/** Backend default for budget_alert_threshold (const.py / settings_registry).
|
||||
* Only reached if an older core omits the field from budget_status. */
|
||||
const DEFAULT_ALERT_THRESHOLD_PCT = 80;
|
||||
|
||||
export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@state() private _config: CardConfig = { type: "" };
|
||||
@@ -109,10 +105,21 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
return html`<ha-card><div class="loading">${t("loading", L) || "Loading…"}</div></ha-card>`;
|
||||
}
|
||||
const sym = s.currency_symbol || DEFAULT_CURRENCY_SYMBOL;
|
||||
const mPct = s.monthly_budget ? Math.min(100, ((s.monthly_spent || 0) / s.monthly_budget) * 100) : 0;
|
||||
const yPct = s.yearly_budget ? Math.min(100, ((s.yearly_spent || 0) / s.yearly_budget) * 100) : 0;
|
||||
const mWarn = mPct >= 100 ? "danger" : mPct >= 80 ? "warning" : "ok";
|
||||
const yWarn = yPct >= 100 ? "danger" : yPct >= 80 ? "warning" : "ok";
|
||||
// The amber step is the CONFIGURED budget_alert_threshold, not a literal 80
|
||||
// — same rule the panel's budget bar uses (maintenance-panel._renderBudgetBar).
|
||||
const threshold = s.alert_threshold_pct ?? DEFAULT_ALERT_THRESHOLD_PCT;
|
||||
const tracks = [
|
||||
{
|
||||
label: t("budget_monthly", L) || "Monthly",
|
||||
spent: s.monthly_spent || 0,
|
||||
budget: s.monthly_budget || 0,
|
||||
},
|
||||
{
|
||||
label: t("budget_yearly", L) || "Yearly",
|
||||
spent: s.yearly_spent || 0,
|
||||
budget: s.yearly_budget || 0,
|
||||
},
|
||||
];
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
@@ -127,25 +134,34 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
|
||||
${tracks.map((track) => {
|
||||
// #104: budget tracking without a maximum — a bar needs a
|
||||
// denominator, so show the plain spent total instead of "9 / 0 €"
|
||||
// over an always-empty bar. Mirrors the panel's spent-only lines.
|
||||
if (!(track.budget > 0)) {
|
||||
return html`
|
||||
<div class="track spent-only">
|
||||
<div class="track-label-row">
|
||||
<label>${track.label}</label>
|
||||
<span class="track-numbers ok">${track.spent.toFixed(0)} ${sym}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
const pct = Math.min(100, Math.max(0, (track.spent / track.budget) * 100));
|
||||
const warn = pct >= 100 ? "danger" : pct >= threshold ? "warning" : "ok";
|
||||
return html`
|
||||
<div class="track">
|
||||
<div class="track-label-row">
|
||||
<label>${t("budget_monthly", L) || "Monthly"}</label>
|
||||
<span class="track-numbers ${mWarn}">
|
||||
${(s.monthly_spent || 0).toFixed(0)} / ${(s.monthly_budget || 0).toFixed(0)} ${sym}
|
||||
<label>${track.label}</label>
|
||||
<span class="track-numbers ${warn}">
|
||||
${track.spent.toFixed(0)} / ${track.budget.toFixed(0)} ${sym}
|
||||
</span>
|
||||
</div>
|
||||
<div class="bar"><div class="bar-fill ${mWarn}" style="width:${mPct}%"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="track">
|
||||
<div class="track-label-row">
|
||||
<label>${t("budget_yearly", L) || "Yearly"}</label>
|
||||
<span class="track-numbers ${yWarn}">
|
||||
${(s.yearly_spent || 0).toFixed(0)} / ${(s.yearly_budget || 0).toFixed(0)} ${sym}
|
||||
</span>
|
||||
</div>
|
||||
<div class="bar"><div class="bar-fill ${yWarn}" style="width:${yPct}%"></div></div>
|
||||
<div class="bar"><div class="bar-fill ${warn}" style="width:${pct}%"></div></div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
|
||||
${this._isAdmin
|
||||
? html`
|
||||
|
||||
+86
-23
@@ -2,9 +2,11 @@
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HomeAssistant, TaskPartLink } from "../types";
|
||||
import { t, nativeFieldStyles } from "../styles";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { partLinkKey, type LinkedPart } from "../helpers/shared-parts";
|
||||
import { REQUIRED_COMPLETION_LABELS } from "./required-completion-labels";
|
||||
|
||||
export class MaintenanceCompleteDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -19,12 +21,20 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
@property() public readingUnit = "";
|
||||
/** Buy task (part_ref): default restock quantity — shows an editable qty field. */
|
||||
@property({ attribute: false }) public restockDefault: number | null = null;
|
||||
/** #99: the object's parts — enables the editable "parts used" section. */
|
||||
@property({ attribute: false }) public parts: Array<{ id: string; name: string; unit?: string | null; stock?: number | null }> = [];
|
||||
/** #99: the task's fixed consumes_parts links (prefill for the section). */
|
||||
@property({ attribute: false }) public consumesParts: Array<{ part_id: string; quantity: number }> = [];
|
||||
/** #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. */
|
||||
@property({ attribute: false }) public parts: LinkedPart[] = [];
|
||||
/** #99: the task's fixed consumes_parts links (prefill for the section).
|
||||
* A link may carry an `entry_id` (#111) and MUST keep it through the edit —
|
||||
* without it the completion would decrement the wrong inventory, or none. */
|
||||
@property({ attribute: false }) public consumesParts: TaskPartLink[] = [];
|
||||
/** "Consumes: 1× HEPA-Filter (Shelf B)" hint lines for consuming tasks. */
|
||||
@property({ type: Array }) public consumesInfo: string[] = [];
|
||||
/** Details this task demands before it counts as done (v2.44). The backend
|
||||
* enforces the same list at every completion surface; blocking Save here
|
||||
* just means the user never has to meet that rejection. */
|
||||
@property({ type: Array }) public requiredFields: string[] = [];
|
||||
@state() private _open = false;
|
||||
@state() private _notes = "";
|
||||
@state() private _cost = "";
|
||||
@@ -38,7 +48,9 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
@state() private _photoUploading = false;
|
||||
@state() private _readingValue = "";
|
||||
@state() private _restockQty = "";
|
||||
@state() private _usedParts: Record<string, number> = {};
|
||||
/** Keyed by `partLinkKey` — the (entry_id, part_id) pair — because two
|
||||
* objects can carry the same part id, so part_id alone would merge pools. */
|
||||
@state() private _usedParts: Record<string, TaskPartLink> = {};
|
||||
|
||||
public open(): void {
|
||||
if (this._open) return;
|
||||
@@ -55,8 +67,9 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
this._readingValue = "";
|
||||
this._restockQty = this.restockDefault !== null ? String(this.restockDefault) : "";
|
||||
// #99: prefill "parts used" with the task's fixed links — the user can
|
||||
// untick or adjust before completing.
|
||||
this._usedParts = Object.fromEntries(this.consumesParts.map((l) => [l.part_id, l.quantity]));
|
||||
// untick or adjust before completing. The whole link is kept, entry_id
|
||||
// included, so a shared pool survives the edit (#111).
|
||||
this._usedParts = Object.fromEntries(this.consumesParts.map((l) => [partLinkKey(l), { ...l }]));
|
||||
}
|
||||
|
||||
private _toggleCheck(idx: number): void {
|
||||
@@ -149,10 +162,16 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
}
|
||||
// #99: with a parts section shown, send the explicit selection — it
|
||||
// replaces the automatic consumes_parts deduction (empty = none used).
|
||||
// entry_id travels only when the pool is somebody else's (#111), so an
|
||||
// own-part payload is byte-identical to what shipped before.
|
||||
if (this.parts.length > 0) {
|
||||
data.used_parts = Object.entries(this._usedParts)
|
||||
.filter(([, qty]) => Number.isFinite(qty) && qty > 0)
|
||||
.map(([part_id, quantity]) => ({ part_id, quantity }));
|
||||
data.used_parts = Object.values(this._usedParts)
|
||||
.filter((l) => Number.isFinite(l.quantity) && l.quantity > 0)
|
||||
.map((l) =>
|
||||
l.entry_id
|
||||
? { part_id: l.part_id, quantity: l.quantity, entry_id: l.entry_id }
|
||||
: { part_id: l.part_id, quantity: l.quantity },
|
||||
);
|
||||
}
|
||||
await this.hass.connection.sendMessagePromise(data);
|
||||
this._open = false;
|
||||
@@ -164,6 +183,28 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** Required details the user has not supplied yet (drives Save + markers). */
|
||||
private get _missingRequired(): string[] {
|
||||
const filled: Record<string, boolean> = {
|
||||
notes: this._notes.trim() !== "",
|
||||
cost: this._cost.trim() !== "",
|
||||
duration: this._duration.trim() !== "",
|
||||
photo: this._photoDocId !== "",
|
||||
// "Who did it" is filled in server-side from the authenticated
|
||||
// connection (websocket/tasks_actions.py), so the dialog satisfies it
|
||||
// as long as we ARE a logged-in user. Claiming it is always satisfied
|
||||
// was how a task requiring "user" ended up unclosable: Save stayed
|
||||
// enabled and the backend rejected the completion every time.
|
||||
user: !!this.hass?.user,
|
||||
};
|
||||
return this.requiredFields.filter((f) => !filled[f]);
|
||||
}
|
||||
|
||||
/** Marker appended to a required field's label. */
|
||||
private _req(field: string) {
|
||||
return this.requiredFields.includes(field) ? html`<span class="req-mark" aria-hidden="true">*</span>` : nothing;
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._open = false;
|
||||
}
|
||||
@@ -200,25 +241,36 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
? html`<div class="used-parts">
|
||||
<span class="field-label">${t("complete_parts_used", L)}</span>
|
||||
${this.parts.map((pt) => {
|
||||
const qty = this._usedParts[pt.id];
|
||||
const checked = qty !== undefined;
|
||||
const key = partLinkKey({ part_id: pt.id, entry_id: pt.entry_id });
|
||||
const link = this._usedParts[key];
|
||||
const checked = link !== undefined;
|
||||
const base: TaskPartLink = pt.entry_id
|
||||
? { part_id: pt.id, quantity: 1, entry_id: pt.entry_id }
|
||||
: { part_id: pt.id, quantity: 1 };
|
||||
return html`<div class="used-part-row">
|
||||
<label class="used-part-check">
|
||||
<input type="checkbox" .checked=${checked}
|
||||
@change=${(e: Event) => {
|
||||
const next = { ...this._usedParts };
|
||||
if ((e.target as HTMLInputElement).checked) next[pt.id] = next[pt.id] || 1;
|
||||
else delete next[pt.id];
|
||||
if ((e.target as HTMLInputElement).checked) next[key] = next[key] || base;
|
||||
else delete next[key];
|
||||
this._usedParts = next;
|
||||
}} />
|
||||
<span>${pt.name}${pt.stock !== null && pt.stock !== undefined ? ` (${pt.stock}${pt.unit ? " " + pt.unit : ""})` : ""}</span>
|
||||
<span
|
||||
>${pt.name}${pt.owner_name
|
||||
? html`<span class="used-part-owner"> (${pt.owner_name})</span>`
|
||||
: nothing}${pt.stock !== null && pt.stock !== undefined ? ` (${pt.stock}${pt.unit ? " " + pt.unit : ""})` : ""}</span
|
||||
>
|
||||
</label>
|
||||
${checked
|
||||
? html`<input class="used-part-qty" type="number" min="0.01" max="999" step="0.01"
|
||||
.value=${String(qty)}
|
||||
.value=${String(link.quantity)}
|
||||
@input=${(e: Event) => {
|
||||
const v = parseFloat((e.target as HTMLInputElement).value);
|
||||
this._usedParts = { ...this._usedParts, [pt.id]: Number.isFinite(v) && v >= 0.01 ? v : 1 };
|
||||
this._usedParts = {
|
||||
...this._usedParts,
|
||||
[key]: { ...base, quantity: Number.isFinite(v) && v >= 0.01 ? v : 1 },
|
||||
};
|
||||
}} />`
|
||||
: nothing}
|
||||
</div>`;
|
||||
@@ -245,25 +297,25 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
only sees the title + Cancel/Complete buttons — the original
|
||||
bug report. Native inputs always render. -->
|
||||
<label class="field">
|
||||
<span class="field-label">${t("notes_optional", L)}</span>
|
||||
<span class="field-label">${t("notes_optional", L)}${this._req("notes")}</span>
|
||||
<input type="text" class="field-input"
|
||||
.value=${this._notes}
|
||||
@input=${(e: Event) => (this._notes = (e.target as HTMLInputElement).value)} />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span class="field-label">${t("cost_optional", L)}</span>
|
||||
<span class="field-label">${t("cost_optional", L)}${this._req("cost")}</span>
|
||||
<input type="number" step="0.01" min="0" class="field-input"
|
||||
.value=${this._cost}
|
||||
@input=${(e: Event) => (this._cost = (e.target as HTMLInputElement).value)} />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span class="field-label">${t("duration_minutes", L)}</span>
|
||||
<span class="field-label">${t("duration_minutes", L)}${this._req("duration")}</span>
|
||||
<input type="number" step="0.01" min="0" class="field-input"
|
||||
.value=${this._duration}
|
||||
@input=${(e: Event) => (this._duration = (e.target as HTMLInputElement).value)} />
|
||||
</label>
|
||||
<div class="field">
|
||||
<span class="field-label">${t("completion_photo_optional", L)}</span>
|
||||
<span class="field-label">${t("completion_photo_optional", L)}${this._req("photo")}</span>
|
||||
${this._photoPreview
|
||||
? html`
|
||||
<div class="photo-preview">
|
||||
@@ -306,7 +358,10 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
</ha-button>
|
||||
<ha-button
|
||||
@click=${this._complete}
|
||||
.disabled=${this._loading}
|
||||
.disabled=${this._loading || this._missingRequired.length > 0}
|
||||
title=${this._missingRequired.length
|
||||
? this._missingRequired.map((f) => t("err_required", L).replace("{field}", t(REQUIRED_COMPLETION_LABELS[f] ?? f, L))).join(" · ")
|
||||
: ""}
|
||||
>
|
||||
${this._loading ? t("completing", L) : t("complete", L)}
|
||||
</ha-button>
|
||||
@@ -316,6 +371,11 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
}
|
||||
|
||||
static styles = [nativeFieldStyles, css`
|
||||
.req-mark {
|
||||
color: var(--error-color, #f44336);
|
||||
margin-left: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.dialog-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
@@ -348,6 +408,9 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
font-size: 13px; cursor: pointer;
|
||||
}
|
||||
.used-part-check input { cursor: pointer; }
|
||||
/* #111: whose stock this row draws on. Muted but never omitted — an
|
||||
unlabelled foreign pool is indistinguishable from an own part. */
|
||||
.used-part-owner { color: var(--secondary-text-color); }
|
||||
.used-part-qty {
|
||||
width: 76px; padding: 4px 6px; border-radius: 4px; font: inherit; font-size: 13px;
|
||||
border: 1px solid var(--divider-color);
|
||||
|
||||
@@ -155,9 +155,15 @@ export class MaintenanceQrDialog extends LitElement {
|
||||
const completeLabel = escapeHtml(t("qr_action_complete", L));
|
||||
|
||||
w.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
<meta name="color-scheme" content="light">
|
||||
<title>${safeTitle}</title>
|
||||
<style>
|
||||
body{font-family:sans-serif;text-align:center;padding:20px}
|
||||
/* Printable sheet — must not inherit the phone's dark theme. The QR images
|
||||
carry their own white quiet zone and stay scannable either way, but the
|
||||
labels below are explicit dark greys and would vanish on a WebView's dark
|
||||
canvas. Same reasoning as helpers/report.ts. */
|
||||
:root{color-scheme:light}
|
||||
body{font-family:sans-serif;text-align:center;padding:20px;background:#fff;color:#1a1a1a}
|
||||
h2{margin:0 0 4px}
|
||||
.sub{color:#666;font-size:14px;margin-bottom:16px}
|
||||
.qr-row{display:flex;justify-content:center;gap:24px;margin:12px 0}
|
||||
|
||||
@@ -9,6 +9,15 @@ import { UserService } from "../user-service";
|
||||
import { OBJECT_COLUMNS, sanitizeColumns } from "../helpers/object-columns";
|
||||
import { downloadTextFile } from "../helpers/download";
|
||||
|
||||
/** One household member and the notify services they actually resolve to.
|
||||
* Empty `services` means no Companion device is linked, so their reminders
|
||||
* fall back to the household notification service. */
|
||||
interface PersonNotifyTarget {
|
||||
user_id: string;
|
||||
name: string;
|
||||
services: string[];
|
||||
}
|
||||
|
||||
/* Settings response shape from WS maintenance_supporter/settings */
|
||||
interface SettingsResponse {
|
||||
features: AdvancedFeatures;
|
||||
@@ -27,6 +36,8 @@ interface SettingsResponse {
|
||||
notify_targets?: string[];
|
||||
panel_enabled: boolean;
|
||||
panel_title: string;
|
||||
/** Opt-in copy of the shipped Assist sentences into the config dir. */
|
||||
install_assist_sentences?: boolean;
|
||||
};
|
||||
notifications: {
|
||||
due_soon_enabled: boolean;
|
||||
@@ -108,6 +119,8 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
@state() private _includeHistory = true;
|
||||
@state() private _toast = "";
|
||||
@state() private _testingNotification = false;
|
||||
@state() private _personTargets: PersonNotifyTarget[] = [];
|
||||
@state() private _testingUser = "";
|
||||
@state() private _users: HAUser[] = [];
|
||||
@state() private _savedViews: Array<{ id: string; name: string }> = [];
|
||||
|
||||
@@ -169,6 +182,23 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
} catch {
|
||||
this._users = [];
|
||||
}
|
||||
this._loadNotifyTargets();
|
||||
}
|
||||
|
||||
/** Which notify services each household member resolves to.
|
||||
*
|
||||
* Resolved by the backend through the same helper the reminder path uses,
|
||||
* so the list shown here is the list that will actually be used.
|
||||
*/
|
||||
private async _loadNotifyTargets(): Promise<void> {
|
||||
try {
|
||||
const res = await this.hass.connection.sendMessagePromise<{ targets: PersonNotifyTarget[] }>({
|
||||
type: "maintenance_supporter/notify/user_targets",
|
||||
});
|
||||
this._personTargets = res.targets || [];
|
||||
} catch {
|
||||
this._personTargets = [];
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadSettings(): Promise<void> {
|
||||
@@ -220,11 +250,13 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _sendTestNotification = async (): Promise<void> => {
|
||||
this._testingNotification = true;
|
||||
private _sendTestNotification = async (userId?: string): Promise<void> => {
|
||||
if (userId) this._testingUser = userId;
|
||||
else this._testingNotification = true;
|
||||
try {
|
||||
const res = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/global/test_notification",
|
||||
...(userId ? { user_id: userId } : {}),
|
||||
}) as { success: boolean; message?: string };
|
||||
const msg = res.message
|
||||
|| (res.success ? t("test_notification_success", this._lang) : t("test_notification_failed", this._lang));
|
||||
@@ -232,7 +264,8 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
} catch {
|
||||
this._showToast(t("test_notification_failed", this._lang));
|
||||
} finally {
|
||||
this._testingNotification = false;
|
||||
if (userId) this._testingUser = "";
|
||||
else this._testingNotification = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -570,6 +603,12 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
@change=${(e: Event) => this._updateSetting("panel_title", (e.target as HTMLInputElement).value.trim())} />
|
||||
</label>
|
||||
` : ""}
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_install_assist_sentences", L)}</span>
|
||||
<input type="checkbox" .checked=${g.install_assist_sentences ?? false}
|
||||
@change=${(e: Event) => this._updateSetting("install_assist_sentences", (e.target as HTMLInputElement).checked)} />
|
||||
</label>
|
||||
<div class="setting-hint">${t("settings_install_assist_sentences_hint", L)}</div>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_notifications", L)}</span>
|
||||
<input type="checkbox" .checked=${g.notifications_enabled}
|
||||
@@ -588,10 +627,28 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
<span class="setting-label">${t("test_notification", L)}</span>
|
||||
<button class="ha-button secondary"
|
||||
?disabled=${!g.notify_service || this._testingNotification}
|
||||
@click=${this._sendTestNotification}>
|
||||
@click=${() => this._sendTestNotification()}>
|
||||
${this._testingNotification ? t("testing", L) : t("send_test", L)}
|
||||
</button>
|
||||
</div>
|
||||
${this._personTargets.length ? html`
|
||||
<div class="notify-per-person">
|
||||
<span class="setting-label">${t("notify_per_person", L)}</span>
|
||||
${this._personTargets.map((target) => html`
|
||||
<div class="notify-person-row">
|
||||
<span class="notify-person-name">${target.name}</span>
|
||||
<span class="notify-person-target ${target.services.length ? "" : "muted"}">
|
||||
${target.services.length ? target.services.join(", ") : t("notify_no_own_device", L)}
|
||||
</span>
|
||||
<button class="ha-button secondary"
|
||||
?disabled=${!target.services.length || this._testingUser === target.user_id}
|
||||
@click=${() => this._sendTestNotification(target.user_id)}>
|
||||
${this._testingUser === target.user_id ? t("testing", L) : t("send_test", L)}
|
||||
</button>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
` : nothing}
|
||||
` : nothing}
|
||||
</div>
|
||||
`;
|
||||
@@ -1606,6 +1663,31 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
cursor: pointer;
|
||||
gap: 12px;
|
||||
}
|
||||
.notify-per-person {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--divider-color, #e0e0e0);
|
||||
}
|
||||
.notify-person-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 6px 0 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.notify-person-name {
|
||||
font-weight: 500;
|
||||
min-width: 120px;
|
||||
}
|
||||
.notify-person-target {
|
||||
flex: 1;
|
||||
min-width: 160px;
|
||||
font-size: 0.9em;
|
||||
word-break: break-word;
|
||||
color: var(--secondary-text-color, #727272);
|
||||
}
|
||||
.notify-person-target.muted {
|
||||
font-style: italic;
|
||||
}
|
||||
/* v2.27: template gallery clustered by category */
|
||||
.tpl-group { margin-top: 14px; }
|
||||
.tpl-group-head {
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import type { AdaptiveConfig, HomeAssistant, MaintenanceTask, TriggerConfig, HAUser } from "../types";
|
||||
import type { AdaptiveConfig, HomeAssistant, MaintenanceTask, TaskPartLink, TriggerConfig, HAUser } from "../types";
|
||||
import { formatDate, t, weekdayName } from "../styles";
|
||||
import { UserService } from "../user-service";
|
||||
import { partLinkKey } from "../helpers/shared-parts";
|
||||
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { REQUIRED_COMPLETION_KEYS, REQUIRED_COMPLETION_LABELS } from "./required-completion-labels";
|
||||
import "./ms-textfield";
|
||||
|
||||
const MAINTENANCE_TYPE_KEYS = ["cleaning", "inspection", "replacement", "calibration", "service", "reading", "custom"];
|
||||
@@ -122,6 +124,14 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@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 }> = [];
|
||||
/** #111: OTHER objects that own spare parts, so a task can draw on a shared
|
||||
* pool (three vacuums, one box of dust bags). Grouped by owner in the UI —
|
||||
* which pool a link means must never be a guess. */
|
||||
@state() private _foreignOwners: Array<{
|
||||
entry_id: string;
|
||||
name: string;
|
||||
parts: Array<{ id: string; name: string; unit?: string }>;
|
||||
}> = [];
|
||||
@state() private _open = false;
|
||||
@state() private _loading = false;
|
||||
@state() private _error = "";
|
||||
@@ -210,7 +220,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@state() private _nfcTagId = "";
|
||||
// v2.20 (#83): unit for `reading`-type tasks ("kWh", "m³", ...)
|
||||
@state() private _readingUnit = "";
|
||||
@state() private _consumesParts: Record<string, number> = {};
|
||||
/** The picked links, keyed by `partLinkKey` — the (entry_id, part_id) pair,
|
||||
* since the same part id can exist on two objects (battery fleet). */
|
||||
@state() private _consumesParts: Record<string, TaskPartLink> = {};
|
||||
@state() private _partsLoadFailed = false;
|
||||
@state() private _availableTags: Array<{id: string; name: string}> = [];
|
||||
|
||||
@@ -222,6 +234,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
|
||||
// Checklist (newline-separated steps, one per line)
|
||||
@state() private _checklistText = "";
|
||||
/** Details this task demands on completion — enforced by the backend on
|
||||
* every surface, so a button press or voice command cannot bypass it. */
|
||||
@state() private _requiredCompletion: string[] = [];
|
||||
|
||||
// Schedule time (HH:MM, advanced feature)
|
||||
@state() private _scheduleTime = "";
|
||||
@@ -271,7 +286,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._objectChoices = [];
|
||||
}
|
||||
this._resetFields();
|
||||
await Promise.all([this._loadUsers(), this._loadTags(), this._loadParts()]);
|
||||
await Promise.all([this._loadUsers(), this._loadTags(), this._loadParts(), this._loadForeignPools()]);
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
@@ -321,12 +336,17 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._lastPerformed = task.last_performed || "";
|
||||
this._nfcTagId = task.nfc_tag_id || "";
|
||||
this._readingUnit = task.reading_unit || "";
|
||||
this._consumesParts = Object.fromEntries((task.consumes_parts || []).map((l) => [l.part_id, l.quantity]));
|
||||
// Whole link, entry_id included — hydrating only part_id would turn every
|
||||
// shared-pool link into an own-part link on the next save (#111).
|
||||
this._consumesParts = Object.fromEntries(
|
||||
(task.consumes_parts || []).map((l) => [partLinkKey(l), { ...l }]),
|
||||
);
|
||||
this._responsibleUserId = task.responsible_user_id || null;
|
||||
this._assigneePool = [...(task.assignee_pool || [])];
|
||||
this._rotationStrategy = task.rotation_strategy || "";
|
||||
|
||||
this._checklistText = (task.checklist || []).join("\n");
|
||||
this._requiredCompletion = [...(task.required_completion_fields || [])];
|
||||
this._scheduleTime = task.schedule_time || "";
|
||||
|
||||
// v1.3.0: hydrate on_complete_action + quick_complete_defaults
|
||||
@@ -395,7 +415,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._fetchEntityAttributes(this._triggerEntityId);
|
||||
}
|
||||
|
||||
await Promise.all([this._loadUsers(), this._loadTags(), this._loadParts()]);
|
||||
await Promise.all([this._loadUsers(), this._loadTags(), this._loadParts(), this._loadForeignPools()]);
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
@@ -434,6 +454,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._assigneePool = [];
|
||||
this._rotationStrategy = "";
|
||||
this._checklistText = "";
|
||||
this._requiredCompletion = [];
|
||||
this._scheduleTime = "";
|
||||
this._environmentalEntity = "";
|
||||
this._environmentalAttribute = "";
|
||||
@@ -736,6 +757,40 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** #111: the other objects' spare-part pools this task could draw on.
|
||||
*
|
||||
* A sibling of `_loadParts`, run in the SAME Promise.all rather than nested
|
||||
* inside it: chaining it after the own-parts fetch delays the dialog opening
|
||||
* by a further round trip for a list that is secondary to it.
|
||||
*
|
||||
* Failure is soft on purpose — the own-parts picker is the primary path and
|
||||
* must not disappear because this second call did not come back. */
|
||||
private async _loadForeignPools(): Promise<void> {
|
||||
this._foreignOwners = [];
|
||||
if (!this._entryId) return;
|
||||
try {
|
||||
const result = (await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/objects",
|
||||
})) as {
|
||||
objects?: Array<{
|
||||
entry_id: string;
|
||||
object?: { name?: string };
|
||||
parts?: Array<{ id: string; name: string; unit?: string }>;
|
||||
}>;
|
||||
};
|
||||
this._foreignOwners = (result.objects || [])
|
||||
.filter((o) => o.entry_id !== this._entryId && (o.parts || []).length > 0)
|
||||
.map((o) => ({
|
||||
entry_id: o.entry_id,
|
||||
name: o.object?.name || o.entry_id,
|
||||
parts: o.parts || [],
|
||||
}))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
} catch {
|
||||
this._foreignOwners = [];
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadTags(): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
@@ -773,6 +828,69 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** A task already drawing on a shared pool opens that section expanded — a
|
||||
* collapsed disclosure would hide a link that is very much active. */
|
||||
private get _hasForeignPick(): boolean {
|
||||
return Object.values(this._consumesParts).some((l) => !!l.entry_id);
|
||||
}
|
||||
|
||||
/** One "consumes parts" checkbox + quantity.
|
||||
*
|
||||
* `ownerEntryId` is undefined for the object's own parts and set for a pool
|
||||
* owned by another object (#111) — that argument is the ONLY difference
|
||||
* between the two lists, which is why they share this renderer. */
|
||||
private _renderConsumesRow(
|
||||
part: { id: string; name: string; unit?: string },
|
||||
ownerEntryId?: string,
|
||||
) {
|
||||
const key = partLinkKey({ part_id: part.id, entry_id: ownerEntryId });
|
||||
const link = this._consumesParts[key];
|
||||
const base: TaskPartLink = ownerEntryId
|
||||
? { part_id: part.id, quantity: 1, entry_id: ownerEntryId }
|
||||
: { part_id: part.id, quantity: 1 };
|
||||
return html`
|
||||
<div class="consumes-row">
|
||||
<label class="consumes-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${link !== undefined}
|
||||
@change=${(e: Event) => {
|
||||
const next = { ...this._consumesParts };
|
||||
if ((e.target as HTMLInputElement).checked) next[key] = next[key] || base;
|
||||
else delete next[key];
|
||||
this._consumesParts = next;
|
||||
}}
|
||||
/>
|
||||
<span>${part.name}${part.unit ? ` (${part.unit})` : ""}</span>
|
||||
</label>
|
||||
${link !== undefined
|
||||
? html`<input
|
||||
class="consumes-qty"
|
||||
type="number"
|
||||
min="0.01"
|
||||
max="999"
|
||||
step="0.01"
|
||||
.value=${String(link.quantity)}
|
||||
@input=${(e: Event) => {
|
||||
const v = parseFloat((e.target as HTMLInputElement).value);
|
||||
this._consumesParts = {
|
||||
...this._consumesParts,
|
||||
[key]: { ...base, quantity: Number.isFinite(v) && v >= 0.01 ? v : 1 },
|
||||
};
|
||||
}}
|
||||
/>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggleRequired(field: string, on: boolean): void {
|
||||
const next = new Set(this._requiredCompletion);
|
||||
if (on) next.add(field);
|
||||
else next.delete(field);
|
||||
this._requiredCompletion = [...next];
|
||||
}
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (this._loading) return; // synchronous re-entry guard (double-click)
|
||||
if (!this._name.trim()) return;
|
||||
@@ -787,7 +905,16 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
name: this._name,
|
||||
task_type: this._type,
|
||||
schedule_type: this._scheduleType,
|
||||
warning_days: parseInt(this._warningDays, 10) || 7,
|
||||
// `0` is a legal, meaningful value — "no due-soon window, go straight
|
||||
// from ok to overdue" (backend range is 0–365). The old
|
||||
// `parseInt(...) || 7` treated it as falsy and silently rewrote a
|
||||
// stored 0 to 7 on EVERY save, even when the user never touched the
|
||||
// field. Same class as bug #42, but worse: it needed no user action.
|
||||
// Only a genuinely unparseable field falls back, and to the
|
||||
// configured default rather than a hardcoded 7.
|
||||
warning_days: Number.isNaN(parseInt(this._warningDays, 10))
|
||||
? this.defaultWarningDays
|
||||
: Math.max(0, parseInt(this._warningDays, 10)),
|
||||
};
|
||||
const ecd = this._earliestCompletionDays.trim();
|
||||
data.earliest_completion_days = ecd === "" ? null : Math.max(0, parseInt(ecd, 10) || 0);
|
||||
@@ -836,14 +963,20 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
data.last_performed = this._lastPerformed || null;
|
||||
data.nfc_tag_id = this._nfcTagId || null;
|
||||
data.reading_unit = this._readingUnit.trim() || null;
|
||||
if (this.parts.length) {
|
||||
data.consumes_parts = Object.entries(this._consumesParts).map(([part_id, quantity]) => ({
|
||||
part_id,
|
||||
quantity,
|
||||
}));
|
||||
// Only send when a picker was actually rendered. A failed parts load
|
||||
// leaves both lists empty, and sending [] then would silently wipe links
|
||||
// the user never saw. entry_id is written ONLY for a foreign pick, so an
|
||||
// own-parts task saves byte-identically to before (#111).
|
||||
if (this.parts.length || this._foreignOwners.length) {
|
||||
data.consumes_parts = Object.values(this._consumesParts).map((l) =>
|
||||
l.entry_id
|
||||
? { part_id: l.part_id, quantity: l.quantity, entry_id: l.entry_id }
|
||||
: { part_id: l.part_id, quantity: l.quantity },
|
||||
);
|
||||
}
|
||||
data.responsible_user_id = this._responsibleUserId;
|
||||
data.assignee_pool = this._assigneePool;
|
||||
data.required_completion_fields = this._requiredCompletion;
|
||||
data.rotation_strategy =
|
||||
this._assigneePool.length >= 2 && this._rotationStrategy
|
||||
? this._rotationStrategy
|
||||
@@ -1700,6 +1833,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._entryId = (e.target as HTMLSelectElement).value;
|
||||
this._consumesParts = {};
|
||||
this._loadParts();
|
||||
// The new owner drops out of the shared-pool list and the old
|
||||
// one joins it, so this has to be recomputed too (#111).
|
||||
this._loadForeignPools();
|
||||
}}
|
||||
>
|
||||
${this._objectChoices.map(
|
||||
@@ -1738,45 +1874,28 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
${this._partsLoadFailed
|
||||
? html`<div class="field-help parts-load-failed">${t("parts_load_failed", L)}</div>`
|
||||
: nothing}
|
||||
${this.parts.length
|
||||
${this.parts.length || this._foreignOwners.length
|
||||
? html`
|
||||
<div class="field">
|
||||
<label>${t("consumes_parts_label", L)}</label>
|
||||
${this.parts.map((part) => {
|
||||
const qty = this._consumesParts[part.id];
|
||||
return html`
|
||||
<div class="consumes-row">
|
||||
<label class="consumes-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${qty !== undefined}
|
||||
@change=${(e: Event) => {
|
||||
const next = { ...this._consumesParts };
|
||||
if ((e.target as HTMLInputElement).checked) next[part.id] = next[part.id] || 1;
|
||||
else delete next[part.id];
|
||||
this._consumesParts = next;
|
||||
}}
|
||||
/>
|
||||
<span>${part.name}${part.unit ? ` (${part.unit})` : ""}</span>
|
||||
</label>
|
||||
${qty !== undefined
|
||||
? html`<input
|
||||
class="consumes-qty"
|
||||
type="number"
|
||||
min="0.01"
|
||||
max="999"
|
||||
step="0.01"
|
||||
.value=${String(qty)}
|
||||
@input=${(e: Event) => {
|
||||
const v = parseFloat((e.target as HTMLInputElement).value);
|
||||
this._consumesParts = { ...this._consumesParts, [part.id]: Number.isFinite(v) && v >= 0.01 ? v : 1 };
|
||||
}}
|
||||
/>`
|
||||
${this.parts.map((part) => this._renderConsumesRow(part))}
|
||||
${this._foreignOwners.length
|
||||
? html`
|
||||
<details class="shared-pools" ?open=${this._hasForeignPick}>
|
||||
<summary>${t("shared_parts_other_objects", L)}</summary>
|
||||
<div class="field-help">${t("shared_parts_help", L)}</div>
|
||||
${this._foreignOwners.map(
|
||||
(owner) => html`
|
||||
<div class="shared-pool-owner">${owner.name}</div>
|
||||
${owner.parts.map((part) =>
|
||||
this._renderConsumesRow(part, owner.entry_id),
|
||||
)}
|
||||
`,
|
||||
)}
|
||||
</details>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
<div class="select-row">
|
||||
@@ -1857,6 +1976,8 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
<ms-textfield
|
||||
label="${t("warning_days", L)}"
|
||||
type="number"
|
||||
min="0"
|
||||
max="365"
|
||||
.value=${this._warningDays}
|
||||
@input=${(e: Event) => (this._warningDays = (e.target as HTMLInputElement).value)}
|
||||
></ms-textfield>
|
||||
@@ -1879,6 +2000,19 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
></textarea>
|
||||
<div class="field-help">${t("checklist_help", L)}</div>
|
||||
` : nothing}
|
||||
<h3>${t("require_on_completion", L)}</h3>
|
||||
<div class="required-completion">
|
||||
${REQUIRED_COMPLETION_KEYS.map((field) => html`
|
||||
<label class="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${this._requiredCompletion.includes(field)}
|
||||
@change=${(e: Event) => this._toggleRequired(field, (e.target as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span>${t(REQUIRED_COMPLETION_LABELS[field], L)}</span>
|
||||
</label>
|
||||
`)}
|
||||
</div>
|
||||
<ms-textfield
|
||||
label="${t("last_performed_optional", L)}"
|
||||
type="date"
|
||||
@@ -2127,6 +2261,24 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
background: var(--card-background-color);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
/* #111: other objects' pools sit behind a disclosure so the object's OWN
|
||||
parts stay the primary list; each group is headed by the owning object's
|
||||
name, so which pool a checkbox means is never a guess. */
|
||||
.shared-pools {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.shared-pools > summary {
|
||||
cursor: pointer;
|
||||
padding: 2px 0;
|
||||
font-size: 13px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.shared-pool-owner {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.field-help {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
|
||||
+4
-2
@@ -14,7 +14,7 @@
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatRecurrence } from "../styles";
|
||||
import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInterval, formatRecurrence } from "../styles";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { renderWeibullSection } from "../renderers/weibull";
|
||||
import { renderPredictionSection } from "../renderers/prediction";
|
||||
@@ -294,7 +294,9 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
task_id: this._taskId,
|
||||
});
|
||||
this._toast = r.recommended_interval
|
||||
? `${t("reanalyze_result", this._lang) || "Recomputed"}: ${r.recommended_interval}d (${r.data_points} pts)`
|
||||
// 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) || "Recomputed"}: ${formatInterval(r.recommended_interval, "days", this._lang)} (${r.data_points} pts)`
|
||||
: (t("reanalyze_insufficient_data", this._lang) || "Not enough data");
|
||||
await this._loadTask();
|
||||
setTimeout(() => { this._toast = ""; }, 3500);
|
||||
|
||||
@@ -252,6 +252,8 @@ export function openCompleteDialog(args: {
|
||||
task_name: string;
|
||||
checklist?: string[];
|
||||
adaptive_enabled?: boolean;
|
||||
/** Details the task demands before it counts as done (v2.44). */
|
||||
required_completion_fields?: string[];
|
||||
}): boolean {
|
||||
const dlg = getOrCreate<MaintenanceCompleteDialog>(COMPLETE_DIALOG_TAG);
|
||||
if (!syncHass(dlg)) return false;
|
||||
@@ -260,6 +262,7 @@ export function openCompleteDialog(args: {
|
||||
dlg.taskName = args.task_name;
|
||||
dlg.checklist = args.checklist ?? [];
|
||||
dlg.adaptiveEnabled = !!args.adaptive_enabled;
|
||||
dlg.requiredFields = args.required_completion_fields ?? [];
|
||||
dlg.lang = (getHass()?.language) || "en";
|
||||
dlg.open();
|
||||
return true;
|
||||
|
||||
@@ -23,6 +23,14 @@ const common = {
|
||||
sourcemap: false,
|
||||
external: [],
|
||||
define: { __MS_BUNDLE_VERSION__: JSON.stringify(manifestVersion) },
|
||||
// A readable banner as well as the define. After the minifier is done the
|
||||
// stamped version survives only as `var xy="2.44.1"` with a generated name,
|
||||
// which nothing outside the bundle can reliably find — so 2.44.0 shipped a
|
||||
// bundle built before the version bump and every install showed a permanent
|
||||
// "reload the panel" banner (#112). This line is what
|
||||
// tests/test_frontend_bundle_version.py checks, and it also lets anyone read
|
||||
// the built version straight out of devtools.
|
||||
banner: { js: `/*! maintenance_supporter frontend ${manifestVersion} */` },
|
||||
};
|
||||
|
||||
// Panel
|
||||
|
||||
@@ -73,10 +73,19 @@ export function buildObjectReportHtml(
|
||||
const totalCost = tasks.reduce((n, t) => n + (t.total_cost ?? 0), 0);
|
||||
|
||||
return `<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
<meta name="color-scheme" content="light">
|
||||
<title>${esc(labels.title)} — ${esc(obj.name)}</title>
|
||||
<style>
|
||||
/* This is a PRINTABLE sheet, not part of the app's theme: it opens as a
|
||||
blob in whatever viewer the OS supplies. In the Companion app that is a
|
||||
WebView, and a WebView on a dark-themed phone paints a DARK default
|
||||
canvas — against which the dark body text below disappeared completely,
|
||||
leaving only the pale row borders showing as stripes. Declaring the
|
||||
scheme AND painting the background keeps the sheet identical everywhere,
|
||||
and matches what comes out of a printer. */
|
||||
:root { color-scheme: light; }
|
||||
* { box-sizing: border-box; }
|
||||
body { font: 13px/1.5 -apple-system, Segoe UI, Roboto, sans-serif; color: #1a1a1a; margin: 32px; }
|
||||
body { font: 13px/1.5 -apple-system, Segoe UI, Roboto, sans-serif; color: #1a1a1a; background: #fff; margin: 32px; }
|
||||
h1 { font-size: 22px; margin: 0 0 2px; }
|
||||
.sub { color: #666; margin: 0 0 20px; }
|
||||
.meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 6px 24px; margin-bottom: 20px; }
|
||||
|
||||
@@ -83,11 +83,17 @@ export function buildTaskWorksheetHtml(
|
||||
: "";
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html><head><meta charset="utf-8"><title>${esc(task.name)} — ${esc(L.title)}</title>
|
||||
<html><head><meta charset="utf-8"><meta name="color-scheme" content="light">
|
||||
<title>${esc(task.name)} — ${esc(L.title)}</title>
|
||||
<style>
|
||||
/* A work sheet is meant to be printed or read as a sheet, so it must not
|
||||
inherit the phone's dark theme: the Companion app opens it in a WebView
|
||||
that paints a dark canvas, and this dark text would vanish against it.
|
||||
See the same note in report.ts. */
|
||||
:root { color-scheme: light; }
|
||||
@page { size: A4; margin: 14mm; }
|
||||
* { box-sizing: border-box; }
|
||||
body { font: 13px/1.45 -apple-system, "Segoe UI", Roboto, sans-serif; color: #111; margin: 0; }
|
||||
body { font: 13px/1.45 -apple-system, "Segoe UI", Roboto, sans-serif; color: #111; background: #fff; margin: 0; }
|
||||
header { display: flex; justify-content: space-between; align-items: flex-start;
|
||||
border-bottom: 3px solid #111; padding-bottom: 8px; margin-bottom: 12px; }
|
||||
h1 { font-size: 22px; margin: 0 0 2px; }
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Hodnota spouštěče",
|
||||
"complete_title": "Dokončit: ",
|
||||
"checklist": "Kontrolní seznam",
|
||||
"require_on_completion": "Vyžadovat při dokončení",
|
||||
"checklist_steps_optional": "Kroky kontrolního seznamu (volitelné)",
|
||||
"checklist_placeholder": "Vyčistit filtr\nVyměnit těsnění\nOtestovat tlak",
|
||||
"checklist_help": "Jeden krok na řádek. Max 100 položek.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Prázdné = zobrazit všechny stavy.",
|
||||
"card_filter_objects": "Filtrovat podle objektů",
|
||||
"card_filter_objects_help": "Prázdné = zobrazit všechny objekty.",
|
||||
"card_filter_areas": "Filtrovat podle oblastí",
|
||||
"card_filter_areas_help": "Prázdné = zobrazit všechny oblasti.",
|
||||
"card_filter_entities": "Filtrovat podle entit (entity_ids)",
|
||||
"card_filter_entities_help": "Vyberte entity sensor / binary_sensor z této integrace. Prázdné = všechny.",
|
||||
"card_loading_objects": "Načítání objektů…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Název panelu",
|
||||
"settings_notifications": "Oznámení",
|
||||
"settings_notify_service": "Služba oznámení",
|
||||
"settings_install_assist_sentences": "Nainstalovat věty pro Assist",
|
||||
"settings_install_assist_sentences_hint": "Zkopíruje hlasové věty do vaší konfigurace, aby je klasický agent Assist rozpoznal. Soubor, který jste upravili, nebude nikdy přepsán.",
|
||||
"test_notification": "Testovací oznámení",
|
||||
"send_test": "Odeslat test",
|
||||
"testing": "Odesílání…",
|
||||
"test_notification_success": "Testovací oznámení odesláno",
|
||||
"test_notification_failed": "Testovací oznámení se nezdařilo",
|
||||
"notify_per_person": "Doručování podle osoby",
|
||||
"notify_no_own_device": "Žádné vlastní zařízení — použije domácí službu",
|
||||
"settings_notify_due_soon": "Oznámit když brzy",
|
||||
"settings_notify_overdue": "Oznámit když po termínu",
|
||||
"settings_notify_triggered": "Oznámit když spuštěno",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Podle uživatele",
|
||||
"filter_label": "Filtr",
|
||||
"user_label": "Uživatel",
|
||||
"photo_label": "Fotografie",
|
||||
"sort_label": "Řazení",
|
||||
"group_by_label": "Seskupit podle",
|
||||
"state_value_help": "Použijte hodnotu stavu HA (obvykle malými písmeny, např. \"on\"/\"off\"). Velikost písmen se při uložení normalizuje.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Upravit zásobu",
|
||||
"restock_quantity_label": "Zakoupené množství",
|
||||
"consumes_parts_label": "Spotřebovává díly",
|
||||
"shared_parts_other_objects": "Díly z jiných objektů",
|
||||
"shared_parts_help": "Několik objektů může sdílet jeden sklad. Dokončení tohoto úkolu odečte zásobu z vlastnícího objektu.",
|
||||
"shared_part_unknown": "Neznámý díl",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Udløserværdi",
|
||||
"complete_title": "Fuldfør: ",
|
||||
"checklist": "Tjekliste",
|
||||
"require_on_completion": "Kræv ved fuldførelse",
|
||||
"checklist_steps_optional": "Tjeklistetrin (valgfrit)",
|
||||
"checklist_placeholder": "Rengør filter\nUdskift pakning\nTest tryk",
|
||||
"checklist_help": "Ét trin pr. linje. Maks. 100 elementer.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Tom = vis alle statusser.",
|
||||
"card_filter_objects": "Filtrer efter objekter",
|
||||
"card_filter_objects_help": "Tom = vis alle objekter.",
|
||||
"card_filter_areas": "Filtrer efter områder",
|
||||
"card_filter_areas_help": "Tom = vis alle områder.",
|
||||
"card_filter_entities": "Filtrer efter enheder (entity_ids)",
|
||||
"card_filter_entities_help": "Vælg sensor- / binary_sensor-enheder fra denne integration. Tom = alle.",
|
||||
"card_loading_objects": "Indlæser objekter…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Sidepanelets titel",
|
||||
"settings_notifications": "Notifikationer",
|
||||
"settings_notify_service": "Notifikationstjeneste",
|
||||
"settings_install_assist_sentences": "Installer Assist-sætninger",
|
||||
"settings_install_assist_sentences_hint": "Kopierer stemmesætningerne til din konfiguration, så den klassiske Assist-agent genkender dem. En fil, du selv har redigeret, overskrives aldrig.",
|
||||
"test_notification": "Testnotifikation",
|
||||
"send_test": "Send test",
|
||||
"testing": "Sender…",
|
||||
"test_notification_success": "Testnotifikation sendt",
|
||||
"test_notification_failed": "Testnotifikation mislykkedes",
|
||||
"notify_per_person": "Levering pr. person",
|
||||
"notify_no_own_device": "Ingen egen enhed — bruger husstandens tjeneste",
|
||||
"settings_notify_due_soon": "Notificer ved snart forfalden",
|
||||
"settings_notify_overdue": "Notificer ved forfalden",
|
||||
"settings_notify_triggered": "Notificer ved udløst",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "Efter bruger",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Bruger",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Sortering",
|
||||
"group_by_label": "Grupper efter",
|
||||
"state_value_help": "Brug HA-tilstandsværdien (normalt med små bogstaver, f.eks. \"on\"/\"off\"). Store/små bogstaver normaliseres ved lagring.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Justér lager",
|
||||
"restock_quantity_label": "Købt mængde",
|
||||
"consumes_parts_label": "Forbruger dele",
|
||||
"shared_parts_other_objects": "Dele fra andre objekter",
|
||||
"shared_parts_help": "Flere objekter kan dele det samme lager. Når opgaven fuldføres, trækkes der fra det ejende objekt.",
|
||||
"shared_part_unknown": "Ukendt del",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Trigger-Wert",
|
||||
"complete_title": "Erledigt: ",
|
||||
"checklist": "Checkliste",
|
||||
"require_on_completion": "Beim Abschließen verlangen",
|
||||
"checklist_steps_optional": "Checkliste-Schritte (optional)",
|
||||
"checklist_placeholder": "Filter reinigen\nDichtung ersetzen\nDruck testen",
|
||||
"checklist_help": "Ein Schritt pro Zeile. Max. 100 Einträge.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Leer = alle Status zeigen.",
|
||||
"card_filter_objects": "Nach Objekten filtern",
|
||||
"card_filter_objects_help": "Leer = alle Objekte zeigen.",
|
||||
"card_filter_areas": "Nach Bereichen filtern",
|
||||
"card_filter_areas_help": "Leer = alle Bereiche zeigen.",
|
||||
"card_filter_entities": "Nach Entitäten filtern (entity_ids)",
|
||||
"card_filter_entities_help": "Wähle Sensor-/Binary-Sensor-Entitäten dieser Integration. Leer = alle.",
|
||||
"card_loading_objects": "Lade Objekte…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Panel-Titel",
|
||||
"settings_notifications": "Benachrichtigungen",
|
||||
"settings_notify_service": "Benachrichtigungsdienst",
|
||||
"settings_install_assist_sentences": "Assist-Sätze installieren",
|
||||
"settings_install_assist_sentences_hint": "Kopiert die Sprachbefehle in deine Konfiguration, damit der klassische Assist-Agent sie erkennt. Eine selbst bearbeitete Datei wird nie überschrieben.",
|
||||
"test_notification": "Test-Benachrichtigung",
|
||||
"send_test": "Test senden",
|
||||
"testing": "Sende…",
|
||||
"test_notification_success": "Test-Benachrichtigung gesendet",
|
||||
"test_notification_failed": "Test-Benachrichtigung fehlgeschlagen",
|
||||
"notify_per_person": "Zustellung pro Person",
|
||||
"notify_no_own_device": "Kein eigenes Gerät — nutzt den Haushaltsdienst",
|
||||
"settings_notify_due_soon": "Bei baldiger Fälligkeit benachrichtigen",
|
||||
"settings_notify_overdue": "Bei Überfälligkeit benachrichtigen",
|
||||
"settings_notify_triggered": "Bei Auslösung benachrichtigen",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "Nach Verantwortlichem",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Benutzer",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Sortierung",
|
||||
"group_by_label": "Gruppieren nach",
|
||||
"state_value_help": "Verwende den HA-Zustandswert (meist kleingeschrieben, z. B. \"on\"/\"off\"). Groß-/Kleinschreibung wird beim Speichern normalisiert.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Bestand anpassen",
|
||||
"restock_quantity_label": "Gekaufte Menge",
|
||||
"consumes_parts_label": "Verbraucht Teile",
|
||||
"shared_parts_other_objects": "Teile anderer Objekte",
|
||||
"shared_parts_help": "Mehrere Objekte können sich einen Bestand teilen. Beim Abschließen wird vom besitzenden Objekt abgebucht.",
|
||||
"shared_part_unknown": "Unbekanntes Teil",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Trigger value",
|
||||
"complete_title": "Complete: ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Require on completion",
|
||||
"checklist_steps_optional": "Checklist steps (optional)",
|
||||
"checklist_placeholder": "Clean filter\nReplace seal\nTest pressure",
|
||||
"checklist_help": "One step per line. Max 100 items.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Empty = show all statuses.",
|
||||
"card_filter_objects": "Filter by objects",
|
||||
"card_filter_objects_help": "Empty = show all objects.",
|
||||
"card_filter_areas": "Filter by areas",
|
||||
"card_filter_areas_help": "Empty = show all areas.",
|
||||
"card_filter_entities": "Filter by entities (entity_ids)",
|
||||
"card_filter_entities_help": "Pick sensor / binary_sensor entities from this integration. Empty = all.",
|
||||
"card_loading_objects": "Loading objects…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Sidebar panel title",
|
||||
"settings_notifications": "Notifications",
|
||||
"settings_notify_service": "Notification service",
|
||||
"settings_install_assist_sentences": "Install Assist sentences",
|
||||
"settings_install_assist_sentences_hint": "Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",
|
||||
"test_notification": "Test notification",
|
||||
"send_test": "Send test",
|
||||
"testing": "Sending…",
|
||||
"test_notification_success": "Test notification sent",
|
||||
"test_notification_failed": "Test notification failed",
|
||||
"notify_per_person": "Per-person delivery",
|
||||
"notify_no_own_device": "No own device — uses the household service",
|
||||
"settings_notify_due_soon": "Notify when due soon",
|
||||
"settings_notify_overdue": "Notify when overdue",
|
||||
"settings_notify_triggered": "Notify when triggered",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "By user",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "User",
|
||||
"photo_label": "Photo",
|
||||
"sort_label": "Sort",
|
||||
"group_by_label": "Group by",
|
||||
"state_value_help": "Use the HA state value (usually lowercase, e.g. \"on\"/\"off\"). Case is normalised on save.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "Adjust stock",
|
||||
"restock_quantity_label": "Quantity bought",
|
||||
"consumes_parts_label": "Consumes parts",
|
||||
"shared_parts_other_objects": "Parts from other objects",
|
||||
"shared_parts_help": "Several objects can share one stock. Completing this task takes from the owning object.",
|
||||
"shared_part_unknown": "Unknown part",
|
||||
"parts_load_failed": "Couldn't load this object's parts — the consumes-parts options are unavailable right now.",
|
||||
"adopt_problem_button": "Adopt problem sensors",
|
||||
"adopt_problem_title": "Adopt problem sensors",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Valor del disparador",
|
||||
"complete_title": "Completada: ",
|
||||
"checklist": "Lista de verificación",
|
||||
"require_on_completion": "Exigir al completar",
|
||||
"checklist_steps_optional": "Pasos de la lista de verificación (opcional)",
|
||||
"checklist_placeholder": "Limpiar filtro\nReemplazar junta\nProbar presión",
|
||||
"checklist_help": "Un paso por línea. Máx. 100 elementos.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Vacío = mostrar todos los estados.",
|
||||
"card_filter_objects": "Filtrar por objetos",
|
||||
"card_filter_objects_help": "Vacío = mostrar todos los objetos.",
|
||||
"card_filter_areas": "Filtrar por áreas",
|
||||
"card_filter_areas_help": "Vacío = mostrar todas las áreas.",
|
||||
"card_filter_entities": "Filtrar por entidades (entity_ids)",
|
||||
"card_filter_entities_help": "Selecciona entidades sensor / binary_sensor de esta integración. Vacío = todas.",
|
||||
"card_loading_objects": "Cargando objetos…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Título del panel lateral",
|
||||
"settings_notifications": "Notificaciones",
|
||||
"settings_notify_service": "Servicio de notificación",
|
||||
"settings_install_assist_sentences": "Instalar frases de Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia las frases de voz en tu configuración para que el agente clásico de Assist las reconozca. Un archivo que hayas editado nunca se sobrescribe.",
|
||||
"test_notification": "Notificación de prueba",
|
||||
"send_test": "Enviar prueba",
|
||||
"testing": "Enviando…",
|
||||
"test_notification_success": "Notificación de prueba enviada",
|
||||
"test_notification_failed": "La notificación de prueba falló",
|
||||
"notify_per_person": "Entrega por persona",
|
||||
"notify_no_own_device": "Sin dispositivo propio — usa el servicio del hogar",
|
||||
"settings_notify_due_soon": "Notificar cuando esté próxima",
|
||||
"settings_notify_overdue": "Notificar cuando esté vencida",
|
||||
"settings_notify_triggered": "Notificar cuando se active",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Por usuario",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Usuario",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Ordenar",
|
||||
"group_by_label": "Agrupar por",
|
||||
"state_value_help": "Usa el valor de estado de HA (normalmente en minúsculas, p. ej. \"on\"/\"off\"). Las mayúsculas se normalizan al guardar.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Ajustar existencias",
|
||||
"restock_quantity_label": "Cantidad comprada",
|
||||
"consumes_parts_label": "Consume piezas",
|
||||
"shared_parts_other_objects": "Piezas de otros objetos",
|
||||
"shared_parts_help": "Varios objetos pueden compartir un mismo stock. Al completar esta tarea se descuenta del objeto propietario.",
|
||||
"shared_part_unknown": "Pieza desconocida",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Laukaisuarvo",
|
||||
"complete_title": "Suorita: ",
|
||||
"checklist": "Tarkistuslista",
|
||||
"require_on_completion": "Vaadi valmistuessa",
|
||||
"checklist_steps_optional": "Tarkistuslistan vaiheet (valinnainen)",
|
||||
"checklist_placeholder": "Puhdista suodatin\nVaihda tiiviste\nTarkista paine",
|
||||
"checklist_help": "Yksi vaihe riviä kohden. Enintään 100 kohdetta.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Tyhjä = näytä kaikki tilat.",
|
||||
"card_filter_objects": "Suodata kohteiden mukaan",
|
||||
"card_filter_objects_help": "Tyhjä = näytä kaikki kohteet.",
|
||||
"card_filter_areas": "Suodata alueiden mukaan",
|
||||
"card_filter_areas_help": "Tyhjä = näytä kaikki alueet.",
|
||||
"card_filter_entities": "Suodata entiteettien mukaan (entity_id)",
|
||||
"card_filter_entities_help": "Valitse sensor- / binary_sensor-entiteetit tästä integraatiosta. Tyhjä = kaikki.",
|
||||
"card_loading_objects": "Ladataan kohteita…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Sivupalkin paneelin otsikko",
|
||||
"settings_notifications": "Ilmoitukset",
|
||||
"settings_notify_service": "Ilmoituspalvelu",
|
||||
"settings_install_assist_sentences": "Asenna Assist-lauseet",
|
||||
"settings_install_assist_sentences_hint": "Kopioi äänikomennot asetuksiisi, jotta perinteinen Assist-agentti tunnistaa ne. Itse muokkaamaasi tiedostoa ei koskaan korvata.",
|
||||
"test_notification": "Testi-ilmoitus",
|
||||
"send_test": "Lähetä testi",
|
||||
"testing": "Lähetetään…",
|
||||
"test_notification_success": "Testi-ilmoitus lähetetty",
|
||||
"test_notification_failed": "Testi-ilmoitus epäonnistui",
|
||||
"notify_per_person": "Toimitus henkilöittäin",
|
||||
"notify_no_own_device": "Ei omaa laitetta — käyttää kotitalouden palvelua",
|
||||
"settings_notify_due_soon": "Ilmoita, kun erääntyy pian",
|
||||
"settings_notify_overdue": "Ilmoita, kun myöhässä",
|
||||
"settings_notify_triggered": "Ilmoita, kun laukaistu",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "Käyttäjän mukaan",
|
||||
"filter_label": "Suodata",
|
||||
"user_label": "Käyttäjä",
|
||||
"photo_label": "Valokuva",
|
||||
"sort_label": "Lajittele",
|
||||
"group_by_label": "Ryhmittele",
|
||||
"state_value_help": "Käytä HA:n tila-arvoa (yleensä pienillä kirjaimilla, esim. \"on\"/\"off\"). Kirjainkoko normalisoidaan tallennettaessa.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Muuta varastoa",
|
||||
"restock_quantity_label": "Ostettu määrä",
|
||||
"consumes_parts_label": "Kuluttaa osia",
|
||||
"shared_parts_other_objects": "Muiden kohteiden osat",
|
||||
"shared_parts_help": "Useat kohteet voivat jakaa saman varaston. Tämän tehtävän suorittaminen vähentää omistavan kohteen varastoa.",
|
||||
"shared_part_unknown": "Tuntematon osa",
|
||||
"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)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Valeur du déclencheur",
|
||||
"complete_title": "Terminé : ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Exiger à la clôture",
|
||||
"checklist_steps_optional": "Étapes de la checklist (optionnel)",
|
||||
"checklist_placeholder": "Nettoyer le filtre\nRemplacer le joint\nTester la pression",
|
||||
"checklist_help": "Une étape par ligne. Max 100 éléments.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Vide = afficher tous les statuts.",
|
||||
"card_filter_objects": "Filtrer par objets",
|
||||
"card_filter_objects_help": "Vide = afficher tous les objets.",
|
||||
"card_filter_areas": "Filtrer par zones",
|
||||
"card_filter_areas_help": "Vide = afficher toutes les zones.",
|
||||
"card_filter_entities": "Filtrer par entités (entity_ids)",
|
||||
"card_filter_entities_help": "Choisissez des entités sensor / binary_sensor de cette intégration. Vide = toutes.",
|
||||
"card_loading_objects": "Chargement des objets…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Titre du panneau latéral",
|
||||
"settings_notifications": "Notifications",
|
||||
"settings_notify_service": "Service de notification",
|
||||
"settings_install_assist_sentences": "Installer les phrases Assist",
|
||||
"settings_install_assist_sentences_hint": "Copie les phrases vocales dans votre configuration afin que l'agent Assist classique les reconnaisse. Un fichier que vous avez modifié n'est jamais écrasé.",
|
||||
"test_notification": "Notification de test",
|
||||
"send_test": "Envoyer le test",
|
||||
"testing": "Envoi en cours…",
|
||||
"test_notification_success": "Notification de test envoyée",
|
||||
"test_notification_failed": "Échec de la notification de test",
|
||||
"notify_per_person": "Distribution par personne",
|
||||
"notify_no_own_device": "Aucun appareil propre — utilise le service du foyer",
|
||||
"settings_notify_due_soon": "Notifier quand bientôt dû",
|
||||
"settings_notify_overdue": "Notifier quand en retard",
|
||||
"settings_notify_triggered": "Notifier quand déclenché",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Par utilisateur",
|
||||
"filter_label": "Filtre",
|
||||
"user_label": "Utilisateur",
|
||||
"photo_label": "Photo",
|
||||
"sort_label": "Tri",
|
||||
"group_by_label": "Grouper par",
|
||||
"state_value_help": "Utilisez la valeur d'état HA (généralement en minuscules, p. ex. \"on\"/\"off\"). La casse est normalisée à l'enregistrement.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Ajuster le stock",
|
||||
"restock_quantity_label": "Quantité achetée",
|
||||
"consumes_parts_label": "Consomme des pièces",
|
||||
"shared_parts_other_objects": "Pièces d'autres objets",
|
||||
"shared_parts_help": "Plusieurs objets peuvent partager un même stock. Terminer cette tâche décompte du stock de l'objet propriétaire.",
|
||||
"shared_part_unknown": "Pièce inconnue",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "ट्रिगर मान",
|
||||
"complete_title": "पूर्ण करें: ",
|
||||
"checklist": "चेकलिस्ट",
|
||||
"require_on_completion": "पूर्ण करते समय आवश्यक",
|
||||
"checklist_steps_optional": "चेकलिस्ट चरण (वैकल्पिक)",
|
||||
"checklist_placeholder": "फ़िल्टर साफ़ करें\nसील बदलें\nदबाव जाँचें",
|
||||
"checklist_help": "प्रति पंक्ति एक चरण। अधिकतम 100 आइटम।",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "खाली = सभी स्थितियाँ दिखाएँ।",
|
||||
"card_filter_objects": "वस्तुओं के अनुसार फ़िल्टर करें",
|
||||
"card_filter_objects_help": "खाली = सभी वस्तुएँ दिखाएँ।",
|
||||
"card_filter_areas": "क्षेत्रों के अनुसार फ़िल्टर करें",
|
||||
"card_filter_areas_help": "खाली = सभी क्षेत्र दिखाएँ।",
|
||||
"card_filter_entities": "एंटिटी के अनुसार फ़िल्टर करें (entity_ids)",
|
||||
"card_filter_entities_help": "इस इंटीग्रेशन से sensor / binary_sensor एंटिटी चुनें। खाली = सभी।",
|
||||
"card_loading_objects": "वस्तुएँ लोड हो रही हैं…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "साइडबार पैनल शीर्षक",
|
||||
"settings_notifications": "सूचनाएँ",
|
||||
"settings_notify_service": "सूचना सेवा",
|
||||
"settings_install_assist_sentences": "Assist वाक्य इंस्टॉल करें",
|
||||
"settings_install_assist_sentences_hint": "वॉइस वाक्यों को आपके कॉन्फ़िगरेशन में कॉपी करता है ताकि क्लासिक Assist एजेंट उन्हें पहचान सके। आपके द्वारा संपादित फ़ाइल कभी अधिलेखित नहीं होती।",
|
||||
"test_notification": "परीक्षण सूचना",
|
||||
"send_test": "परीक्षण भेजें",
|
||||
"testing": "भेजा जा रहा है…",
|
||||
"test_notification_success": "परीक्षण सूचना भेजी गई",
|
||||
"test_notification_failed": "परीक्षण सूचना विफल",
|
||||
"notify_per_person": "प्रति व्यक्ति वितरण",
|
||||
"notify_no_own_device": "कोई निजी डिवाइस नहीं — घरेलू सेवा का उपयोग करता है",
|
||||
"settings_notify_due_soon": "जल्द देय होने पर सूचित करें",
|
||||
"settings_notify_overdue": "अतिदेय होने पर सूचित करें",
|
||||
"settings_notify_triggered": "ट्रिगर होने पर सूचित करें",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "उपयोगकर्ता के अनुसार",
|
||||
"filter_label": "फ़िल्टर",
|
||||
"user_label": "उपयोगकर्ता",
|
||||
"photo_label": "फ़ोटो",
|
||||
"sort_label": "क्रमबद्ध करें",
|
||||
"group_by_label": "इसके अनुसार समूहित करें",
|
||||
"state_value_help": "HA स्थिति मान का उपयोग करें (आमतौर पर लोअरकेस, उदा. \"on\"/\"off\")। सहेजने पर केस सामान्यीकृत हो जाता है।",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "स्टॉक समायोजित करें",
|
||||
"restock_quantity_label": "खरीदी गई मात्रा",
|
||||
"consumes_parts_label": "पुर्ज़े खपत",
|
||||
"shared_parts_other_objects": "अन्य वस्तुओं के पुर्ज़े",
|
||||
"shared_parts_help": "कई वस्तुएँ एक ही स्टॉक साझा कर सकती हैं। यह कार्य पूरा करने पर स्वामी वस्तु के स्टॉक से घटाया जाता है।",
|
||||
"shared_part_unknown": "अज्ञात पुर्ज़ा",
|
||||
"parts_load_failed": "इस ऑब्जेक्ट के पुर्ज़े लोड नहीं हो सके — पुर्ज़ा-खपत विकल्प अभी उपलब्ध नहीं हैं।",
|
||||
"settings_export_selection": "चयनित ऑब्जेक्ट तक सीमित करें (वैकल्पिक)",
|
||||
"settings_docs_archive": "दस्तावेज़ संग्रह (फ़ाइलों सहित)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Kiváltó érték",
|
||||
"complete_title": "Elvégzés: ",
|
||||
"checklist": "Ellenőrzőlista",
|
||||
"require_on_completion": "Befejezéskor kötelező",
|
||||
"checklist_steps_optional": "Ellenőrzőlista lépései (opcionális)",
|
||||
"checklist_placeholder": "Szűrő tisztítása\nTömítés cseréje\nNyomáspróba",
|
||||
"checklist_help": "Soronként egy lépés. Legfeljebb 100 elem.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Üres = minden állapot megjelenik.",
|
||||
"card_filter_objects": "Szűrés objektumok szerint",
|
||||
"card_filter_objects_help": "Üres = minden objektum megjelenik.",
|
||||
"card_filter_areas": "Szűrés területek szerint",
|
||||
"card_filter_areas_help": "Üres = minden terület megjelenik.",
|
||||
"card_filter_entities": "Szűrés entitások szerint (entity_id-k)",
|
||||
"card_filter_entities_help": "Válasszon sensor / binary_sensor entitásokat ebből az integrációból. Üres = mind.",
|
||||
"card_loading_objects": "Objektumok betöltése…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Oldalsáv-panel címe",
|
||||
"settings_notifications": "Értesítések",
|
||||
"settings_notify_service": "Értesítési szolgáltatás",
|
||||
"settings_install_assist_sentences": "Assist mondatok telepítése",
|
||||
"settings_install_assist_sentences_hint": "Átmásolja a hangmondatokat a konfigurációdba, hogy a klasszikus Assist ügynök felismerje őket. Az általad szerkesztett fájlt soha nem írja felül.",
|
||||
"test_notification": "Tesztértesítés",
|
||||
"send_test": "Teszt küldése",
|
||||
"testing": "Küldés…",
|
||||
"test_notification_success": "Tesztértesítés elküldve",
|
||||
"test_notification_failed": "A tesztértesítés nem sikerült",
|
||||
"notify_per_person": "Kézbesítés személyenként",
|
||||
"notify_no_own_device": "Nincs saját eszköz — a háztartási szolgáltatást használja",
|
||||
"settings_notify_due_soon": "Értesítés, ha hamarosan esedékes",
|
||||
"settings_notify_overdue": "Értesítés lejáratkor",
|
||||
"settings_notify_triggered": "Értesítés kiváltáskor",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "Felhasználó szerint",
|
||||
"filter_label": "Szűrő",
|
||||
"user_label": "Felhasználó",
|
||||
"photo_label": "Fénykép",
|
||||
"sort_label": "Rendezés",
|
||||
"group_by_label": "Csoportosítás",
|
||||
"state_value_help": "A HA állapotértékét használja (általában kisbetűs, pl. „on”/„off”). A kis- és nagybetűk mentéskor normalizálódnak.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "Készlet módosítása",
|
||||
"restock_quantity_label": "Vásárolt mennyiség",
|
||||
"consumes_parts_label": "Felhasznált alkatrészek",
|
||||
"shared_parts_other_objects": "Más objektumok alkatrészei",
|
||||
"shared_parts_help": "Több objektum is használhatja ugyanazt a készletet. A feladat elvégzése a tulajdonos objektum készletéből von le.",
|
||||
"shared_part_unknown": "Ismeretlen alkatrész",
|
||||
"parts_load_failed": "Az objektum alkatrészei nem tölthetők be — a felhasznált alkatrészek beállításai most nem érhetők el.",
|
||||
"adopt_problem_button": "Problémaérzékelők átvétele",
|
||||
"adopt_problem_title": "Problémaérzékelők átvétele",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Valore trigger",
|
||||
"complete_title": "Completato: ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Richiedi al completamento",
|
||||
"checklist_steps_optional": "Passaggi della checklist (opzionale)",
|
||||
"checklist_placeholder": "Pulire il filtro\nSostituire la guarnizione\nTestare la pressione",
|
||||
"checklist_help": "Un passaggio per riga. Max 100 elementi.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Vuoto = mostra tutti gli stati.",
|
||||
"card_filter_objects": "Filtra per oggetti",
|
||||
"card_filter_objects_help": "Vuoto = mostra tutti gli oggetti.",
|
||||
"card_filter_areas": "Filtra per aree",
|
||||
"card_filter_areas_help": "Vuoto = mostra tutte le aree.",
|
||||
"card_filter_entities": "Filtra per entità (entity_ids)",
|
||||
"card_filter_entities_help": "Seleziona entità sensor / binary_sensor da questa integrazione. Vuoto = tutte.",
|
||||
"card_loading_objects": "Caricamento oggetti…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Titolo pannello laterale",
|
||||
"settings_notifications": "Notifiche",
|
||||
"settings_notify_service": "Servizio di notifica",
|
||||
"settings_install_assist_sentences": "Installa le frasi di Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia le frasi vocali nella tua configurazione affinché l'agente Assist classico le riconosca. Un file modificato da te non viene mai sovrascritto.",
|
||||
"test_notification": "Notifica di test",
|
||||
"send_test": "Invia test",
|
||||
"testing": "Invio in corso…",
|
||||
"test_notification_success": "Notifica di test inviata",
|
||||
"test_notification_failed": "Notifica di test non riuscita",
|
||||
"notify_per_person": "Consegna per persona",
|
||||
"notify_no_own_device": "Nessun dispositivo proprio — usa il servizio della casa",
|
||||
"settings_notify_due_soon": "Notifica quando in scadenza",
|
||||
"settings_notify_overdue": "Notifica quando scaduto",
|
||||
"settings_notify_triggered": "Notifica quando attivato",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Per utente",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Utente",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Ordinamento",
|
||||
"group_by_label": "Raggruppa per",
|
||||
"state_value_help": "Usa il valore di stato HA (di solito minuscolo, es. \"on\"/\"off\"). Il case viene normalizzato al salvataggio.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Correggi scorta",
|
||||
"restock_quantity_label": "Quantità acquistata",
|
||||
"consumes_parts_label": "Consuma ricambi",
|
||||
"shared_parts_other_objects": "Ricambi di altri oggetti",
|
||||
"shared_parts_help": "Più oggetti possono condividere una sola scorta. Completando questa attività si preleva dall'oggetto proprietario.",
|
||||
"shared_part_unknown": "Ricambio sconosciuto",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "トリガー値",
|
||||
"complete_title": "完了: ",
|
||||
"checklist": "チェックリスト",
|
||||
"require_on_completion": "完了時に必須",
|
||||
"checklist_steps_optional": "チェックリストの手順(任意)",
|
||||
"checklist_placeholder": "フィルター清掃\nシール交換\n圧力テスト",
|
||||
"checklist_help": "1行に1手順。最大100項目。",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "空欄=すべての状態を表示。",
|
||||
"card_filter_objects": "対象で絞り込み",
|
||||
"card_filter_objects_help": "空欄=すべての対象を表示。",
|
||||
"card_filter_areas": "エリアで絞り込み",
|
||||
"card_filter_areas_help": "空欄=すべてのエリアを表示。",
|
||||
"card_filter_entities": "エンティティで絞り込み(entity_ids)",
|
||||
"card_filter_entities_help": "この統合のsensor/binary_sensorエンティティを選択。空欄=すべて。",
|
||||
"card_loading_objects": "対象を読み込み中…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "サイドバーパネルのタイトル",
|
||||
"settings_notifications": "通知",
|
||||
"settings_notify_service": "通知サービス",
|
||||
"settings_install_assist_sentences": "Assist の文を導入する",
|
||||
"settings_install_assist_sentences_hint": "音声フレーズを設定に複製し、従来の Assist エージェントが認識できるようにします。自分で編集したファイルが上書きされることはありません。",
|
||||
"test_notification": "テスト通知",
|
||||
"send_test": "テスト送信",
|
||||
"testing": "送信中…",
|
||||
"test_notification_success": "テスト通知を送信しました",
|
||||
"test_notification_failed": "テスト通知に失敗しました",
|
||||
"notify_per_person": "メンバーごとの配信",
|
||||
"notify_no_own_device": "個人の端末なし — 家庭用サービスを使用",
|
||||
"settings_notify_due_soon": "期限間近で通知",
|
||||
"settings_notify_overdue": "期限超過で通知",
|
||||
"settings_notify_triggered": "トリガー発生で通知",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "ユーザー別",
|
||||
"filter_label": "絞り込み",
|
||||
"user_label": "ユーザー",
|
||||
"photo_label": "写真",
|
||||
"sort_label": "並べ替え",
|
||||
"group_by_label": "グループ化",
|
||||
"state_value_help": "HAの状態値を使用してください(通常は小文字、例: \"on\"/\"off\")。保存時に大文字小文字は正規化されます。",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "在庫を調整",
|
||||
"restock_quantity_label": "購入数量",
|
||||
"consumes_parts_label": "部品を消費",
|
||||
"shared_parts_other_objects": "他の対象の部品",
|
||||
"shared_parts_help": "複数の対象で同じ在庫を共有できます。このタスクを完了すると、所有する対象の在庫から差し引かれます。",
|
||||
"shared_part_unknown": "不明な部品",
|
||||
"parts_load_failed": "このオブジェクトの部品を読み込めませんでした — 部品消費オプションは現在利用できません。",
|
||||
"settings_export_selection": "選択したオブジェクトに限定(任意)",
|
||||
"settings_docs_archive": "ドキュメントアーカイブ(ファイル付き)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "트리거 값",
|
||||
"complete_title": "완료: ",
|
||||
"checklist": "체크리스트",
|
||||
"require_on_completion": "완료 시 필수 입력",
|
||||
"checklist_steps_optional": "체크리스트 단계 (선택)",
|
||||
"checklist_placeholder": "필터 청소\n씰 교체\n압력 테스트",
|
||||
"checklist_help": "한 줄에 한 단계씩 입력하세요. 최대 100개.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "비워두면 모든 상태를 표시합니다.",
|
||||
"card_filter_objects": "객체로 필터",
|
||||
"card_filter_objects_help": "비워두면 모든 객체를 표시합니다.",
|
||||
"card_filter_areas": "구역으로 필터",
|
||||
"card_filter_areas_help": "비워두면 모든 구역을 표시합니다.",
|
||||
"card_filter_entities": "엔티티로 필터 (entity_ids)",
|
||||
"card_filter_entities_help": "이 통합구성요소의 sensor / binary_sensor 엔티티를 선택하세요. 비워두면 전체.",
|
||||
"card_loading_objects": "객체 불러오는 중…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "사이드바 패널 제목",
|
||||
"settings_notifications": "알림",
|
||||
"settings_notify_service": "알림 서비스",
|
||||
"settings_install_assist_sentences": "Assist 문장 설치",
|
||||
"settings_install_assist_sentences_hint": "음성 문장을 설정에 복사하여 기존 Assist 에이전트가 인식하도록 합니다. 직접 수정한 파일은 절대 덮어쓰지 않습니다.",
|
||||
"test_notification": "테스트 알림",
|
||||
"send_test": "테스트 보내기",
|
||||
"testing": "보내는 중…",
|
||||
"test_notification_success": "테스트 알림을 보냈습니다",
|
||||
"test_notification_failed": "테스트 알림 전송 실패",
|
||||
"notify_per_person": "사용자별 전송",
|
||||
"notify_no_own_device": "개인 기기 없음 — 가정 서비스 사용",
|
||||
"settings_notify_due_soon": "기한 임박 시 알림",
|
||||
"settings_notify_overdue": "기한 초과 시 알림",
|
||||
"settings_notify_triggered": "트리거 시 알림",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "사용자별",
|
||||
"filter_label": "필터",
|
||||
"user_label": "사용자",
|
||||
"photo_label": "사진",
|
||||
"sort_label": "정렬",
|
||||
"group_by_label": "그룹화",
|
||||
"state_value_help": "HA 상태 값을 사용하세요(보통 소문자, 예: \"on\"/\"off\"). 대소문자는 저장 시 정규화됩니다.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "재고 조정",
|
||||
"restock_quantity_label": "구매 수량",
|
||||
"consumes_parts_label": "사용하는 부품",
|
||||
"shared_parts_other_objects": "다른 객체의 부품",
|
||||
"shared_parts_help": "여러 객체가 하나의 재고를 공유할 수 있습니다. 이 작업을 완료하면 소유 객체의 재고에서 차감됩니다.",
|
||||
"shared_part_unknown": "알 수 없는 부품",
|
||||
"parts_load_failed": "이 객체의 부품을 불러올 수 없습니다 — 부품 사용 옵션을 지금은 쓸 수 없습니다.",
|
||||
"adopt_problem_button": "문제 센서 가져오기",
|
||||
"adopt_problem_title": "문제 센서 가져오기",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Utløserverdi",
|
||||
"complete_title": "Fullfør: ",
|
||||
"checklist": "Sjekkliste",
|
||||
"require_on_completion": "Krev ved fullføring",
|
||||
"checklist_steps_optional": "Sjekklistetrinn (valgfritt)",
|
||||
"checklist_placeholder": "Rengjør filter\nBytt pakning\nTest trykk",
|
||||
"checklist_help": "Ett trinn per linje. Maks 100 elementer.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Tom = vis alle statuser.",
|
||||
"card_filter_objects": "Filtrer etter objekter",
|
||||
"card_filter_objects_help": "Tom = vis alle objekter.",
|
||||
"card_filter_areas": "Filtrer etter områder",
|
||||
"card_filter_areas_help": "Tom = vis alle områder.",
|
||||
"card_filter_entities": "Filtrer etter entiteter (entity_ids)",
|
||||
"card_filter_entities_help": "Velg sensor-/binary_sensor-entiteter fra denne integrasjonen. Tom = alle.",
|
||||
"card_loading_objects": "Laster objekter…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Tittel på sidepanel",
|
||||
"settings_notifications": "Varsler",
|
||||
"settings_notify_service": "Varslingstjeneste",
|
||||
"settings_install_assist_sentences": "Installer Assist-setninger",
|
||||
"settings_install_assist_sentences_hint": "Kopierer talesetningene til konfigurasjonen din slik at den klassiske Assist-agenten gjenkjenner dem. En fil du selv har endret, blir aldri overskrevet.",
|
||||
"test_notification": "Testvarsel",
|
||||
"send_test": "Send test",
|
||||
"testing": "Sender…",
|
||||
"test_notification_success": "Testvarsel sendt",
|
||||
"test_notification_failed": "Testvarsel mislyktes",
|
||||
"notify_per_person": "Levering per person",
|
||||
"notify_no_own_device": "Ingen egen enhet — bruker husstandens tjeneste",
|
||||
"settings_notify_due_soon": "Varsle når noe forfaller snart",
|
||||
"settings_notify_overdue": "Varsle når noe er forfalt",
|
||||
"settings_notify_triggered": "Varsle når noe utløses",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "Etter bruker",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Bruker",
|
||||
"photo_label": "Bilde",
|
||||
"sort_label": "Sorter",
|
||||
"group_by_label": "Grupper etter",
|
||||
"state_value_help": "Bruk HA-tilstandsverdien (vanligvis små bokstaver, f.eks. \"on\"/\"off\"). Store/små bokstaver normaliseres ved lagring.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Juster lager",
|
||||
"restock_quantity_label": "Kjøpt mengde",
|
||||
"consumes_parts_label": "Forbruker deler",
|
||||
"shared_parts_other_objects": "Deler fra andre objekter",
|
||||
"shared_parts_help": "Flere objekter kan dele samme lager. Når oppgaven fullføres, trekkes det fra det eiende objektet.",
|
||||
"shared_part_unknown": "Ukjent del",
|
||||
"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)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Triggerwaarde",
|
||||
"complete_title": "Voltooid: ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Vereisen bij afronden",
|
||||
"checklist_steps_optional": "Checklist-stappen (optioneel)",
|
||||
"checklist_placeholder": "Filter schoonmaken\nPakking vervangen\nDruk testen",
|
||||
"checklist_help": "Eén stap per regel. Max. 100 items.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Leeg = alle statussen tonen.",
|
||||
"card_filter_objects": "Filteren op objecten",
|
||||
"card_filter_objects_help": "Leeg = alle objecten tonen.",
|
||||
"card_filter_areas": "Filteren op gebieden",
|
||||
"card_filter_areas_help": "Leeg = alle gebieden tonen.",
|
||||
"card_filter_entities": "Filteren op entiteiten (entity_ids)",
|
||||
"card_filter_entities_help": "Kies sensor/binary_sensor entiteiten van deze integratie. Leeg = alle.",
|
||||
"card_loading_objects": "Objecten laden…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Titel zijbalkpaneel",
|
||||
"settings_notifications": "Meldingen",
|
||||
"settings_notify_service": "Meldingsservice",
|
||||
"settings_install_assist_sentences": "Assist-zinnen installeren",
|
||||
"settings_install_assist_sentences_hint": "Kopieert de spraakzinnen naar je configuratie zodat de klassieke Assist-agent ze herkent. Een bestand dat je zelf hebt bewerkt wordt nooit overschreven.",
|
||||
"test_notification": "Testmelding",
|
||||
"send_test": "Test versturen",
|
||||
"testing": "Verzenden…",
|
||||
"test_notification_success": "Testmelding verzonden",
|
||||
"test_notification_failed": "Testmelding mislukt",
|
||||
"notify_per_person": "Bezorging per persoon",
|
||||
"notify_no_own_device": "Geen eigen apparaat — gebruikt de huishoudelijke dienst",
|
||||
"settings_notify_due_soon": "Melding bij bijna verlopen",
|
||||
"settings_notify_overdue": "Melding bij achterstallig",
|
||||
"settings_notify_triggered": "Melding bij geactiveerd",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Per gebruiker",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Gebruiker",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Sorteren",
|
||||
"group_by_label": "Groeperen op",
|
||||
"state_value_help": "Gebruik de HA-statuswaarde (meestal in kleine letters, bv. \"on\"/\"off\"). Hoofdletters worden bij opslaan genormaliseerd.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Voorraad aanpassen",
|
||||
"restock_quantity_label": "Gekochte hoeveelheid",
|
||||
"consumes_parts_label": "Verbruikt onderdelen",
|
||||
"shared_parts_other_objects": "Onderdelen van andere objecten",
|
||||
"shared_parts_help": "Meerdere objecten kunnen één voorraad delen. Bij afronden wordt van het eigenaarsobject afgeboekt.",
|
||||
"shared_part_unknown": "Onbekend onderdeel",
|
||||
"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)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Wartość wyzwalacza",
|
||||
"complete_title": "Wykonaj: ",
|
||||
"checklist": "Lista kontrolna",
|
||||
"require_on_completion": "Wymagaj przy zakończeniu",
|
||||
"checklist_steps_optional": "Kroki listy kontrolnej (opcjonalne)",
|
||||
"checklist_placeholder": "Wyczyść filtr\nWymień uszczelkę\nSprawdź ciśnienie",
|
||||
"checklist_help": "Jeden krok na linię. Maks. 100 elementów.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Puste = pokaż wszystkie statusy.",
|
||||
"card_filter_objects": "Filtruj wg obiektów",
|
||||
"card_filter_objects_help": "Puste = pokaż wszystkie obiekty.",
|
||||
"card_filter_areas": "Filtruj wg obszarów",
|
||||
"card_filter_areas_help": "Puste = pokaż wszystkie obszary.",
|
||||
"card_filter_entities": "Filtruj wg encji (entity_ids)",
|
||||
"card_filter_entities_help": "Wybierz encje sensor / binary_sensor z tej integracji. Puste = wszystkie.",
|
||||
"card_loading_objects": "Ładowanie obiektów…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Tytuł panelu bocznego",
|
||||
"settings_notifications": "Powiadomienia",
|
||||
"settings_notify_service": "Usługa powiadomień",
|
||||
"settings_install_assist_sentences": "Zainstaluj zdania Assist",
|
||||
"settings_install_assist_sentences_hint": "Kopiuje zdania głosowe do Twojej konfiguracji, aby klasyczny agent Assist je rozpoznawał. Plik zmieniony przez Ciebie nigdy nie zostanie nadpisany.",
|
||||
"test_notification": "Powiadomienie testowe",
|
||||
"send_test": "Wyślij test",
|
||||
"testing": "Wysyłanie…",
|
||||
"test_notification_success": "Powiadomienie testowe wysłane",
|
||||
"test_notification_failed": "Powiadomienie testowe nie powiodło się",
|
||||
"notify_per_person": "Dostarczanie dla każdej osoby",
|
||||
"notify_no_own_device": "Brak własnego urządzenia — używa usługi domowej",
|
||||
"settings_notify_due_soon": "Powiadom gdy wkrótce",
|
||||
"settings_notify_overdue": "Powiadom gdy zaległe",
|
||||
"settings_notify_triggered": "Powiadom gdy wyzwolone",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Wg użytkownika",
|
||||
"filter_label": "Filtr",
|
||||
"user_label": "Użytkownik",
|
||||
"photo_label": "Zdjęcie",
|
||||
"sort_label": "Sortowanie",
|
||||
"group_by_label": "Grupuj wg",
|
||||
"state_value_help": "Użyj wartości stanu HA (zwykle małymi literami, np. \"on\"/\"off\"). Wielkość liter jest normalizowana przy zapisie.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Koryguj stan",
|
||||
"restock_quantity_label": "Kupiona ilość",
|
||||
"consumes_parts_label": "Zużywa części",
|
||||
"shared_parts_other_objects": "Części z innych obiektów",
|
||||
"shared_parts_help": "Kilka obiektów może korzystać z jednego zapasu. Wykonanie tego zadania odejmuje ze stanu obiektu będącego właścicielem.",
|
||||
"shared_part_unknown": "Nieznana część",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Valor do gatilho",
|
||||
"complete_title": "Concluir: ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Exigir ao concluir",
|
||||
"checklist_steps_optional": "Etapas do checklist (opcional)",
|
||||
"checklist_placeholder": "Limpar o filtro\nTrocar a vedação\nTestar a pressão",
|
||||
"checklist_help": "Uma etapa por linha. Máx. 100 itens.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Vazio = mostrar todos os status.",
|
||||
"card_filter_objects": "Filtrar por objetos",
|
||||
"card_filter_objects_help": "Vazio = mostrar todos os objetos.",
|
||||
"card_filter_areas": "Filtrar por áreas",
|
||||
"card_filter_areas_help": "Vazio = mostrar todas as áreas.",
|
||||
"card_filter_entities": "Filtrar por entidades (entity_ids)",
|
||||
"card_filter_entities_help": "Escolha entidades sensor / binary_sensor desta integração. Vazio = todas.",
|
||||
"card_loading_objects": "Carregando objetos…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Título do painel na barra lateral",
|
||||
"settings_notifications": "Notificações",
|
||||
"settings_notify_service": "Serviço de notificação",
|
||||
"settings_install_assist_sentences": "Instalar frases do Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia as frases de voz para a sua configuração para que o agente Assist clássico as reconheça. Um arquivo que você editou nunca é sobrescrito.",
|
||||
"test_notification": "Notificação de teste",
|
||||
"send_test": "Enviar teste",
|
||||
"testing": "Enviando…",
|
||||
"test_notification_success": "Notificação de teste enviada",
|
||||
"test_notification_failed": "Falha na notificação de teste",
|
||||
"notify_per_person": "Entrega por pessoa",
|
||||
"notify_no_own_device": "Sem dispositivo próprio — usa o serviço da casa",
|
||||
"settings_notify_due_soon": "Notificar quando vencer em breve",
|
||||
"settings_notify_overdue": "Notificar quando atrasada",
|
||||
"settings_notify_triggered": "Notificar quando acionada",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "Por usuário",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Usuário",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Ordenar",
|
||||
"group_by_label": "Agrupar por",
|
||||
"state_value_help": "Use o valor de estado do HA (geralmente minúsculo, ex.: \"on\"/\"off\"). Maiúsculas e minúsculas são normalizadas ao salvar.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "Ajustar estoque",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
"consumes_parts_label": "Consome peças",
|
||||
"shared_parts_other_objects": "Peças de outros objetos",
|
||||
"shared_parts_help": "Vários objetos podem compartilhar o mesmo estoque. Concluir esta tarefa desconta do objeto proprietário.",
|
||||
"shared_part_unknown": "Peça desconhecida",
|
||||
"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 no momento.",
|
||||
"adopt_problem_button": "Adotar sensores de problema",
|
||||
"adopt_problem_title": "Adotar sensores de problema",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Valor do acionador",
|
||||
"complete_title": "Concluída: ",
|
||||
"checklist": "Lista de verificação",
|
||||
"require_on_completion": "Exigir ao concluir",
|
||||
"checklist_steps_optional": "Passos da lista de verificação (opcional)",
|
||||
"checklist_placeholder": "Limpar filtro\nSubstituir vedação\nTestar pressão",
|
||||
"checklist_help": "Um passo por linha. Máx. 100 itens.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Vazio = mostrar todos os estados.",
|
||||
"card_filter_objects": "Filtrar por objetos",
|
||||
"card_filter_objects_help": "Vazio = mostrar todos os objetos.",
|
||||
"card_filter_areas": "Filtrar por áreas",
|
||||
"card_filter_areas_help": "Vazio = mostrar todas as áreas.",
|
||||
"card_filter_entities": "Filtrar por entidades (entity_ids)",
|
||||
"card_filter_entities_help": "Selecione entidades sensor / binary_sensor desta integração. Vazio = todas.",
|
||||
"card_loading_objects": "A carregar objetos…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Título do painel lateral",
|
||||
"settings_notifications": "Notificações",
|
||||
"settings_notify_service": "Serviço de notificação",
|
||||
"settings_install_assist_sentences": "Instalar frases do Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia as frases de voz para a sua configuração para que o agente Assist clássico as reconheça. Um ficheiro que editou nunca é substituído.",
|
||||
"test_notification": "Notificação de teste",
|
||||
"send_test": "Enviar teste",
|
||||
"testing": "A enviar…",
|
||||
"test_notification_success": "Notificação de teste enviada",
|
||||
"test_notification_failed": "Falha na notificação de teste",
|
||||
"notify_per_person": "Entrega por pessoa",
|
||||
"notify_no_own_device": "Sem dispositivo próprio — usa o serviço da casa",
|
||||
"settings_notify_due_soon": "Notificar quando próxima",
|
||||
"settings_notify_overdue": "Notificar quando atrasada",
|
||||
"settings_notify_triggered": "Notificar quando acionada",
|
||||
@@ -483,6 +490,7 @@
|
||||
"sort_group": "Grupo",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Utilizador",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Ordenar",
|
||||
"group_by_label": "Agrupar por",
|
||||
"state_value_help": "Use o valor de estado HA (normalmente em minúsculas, p. ex. \"on\"/\"off\"). As maiúsculas são normalizadas ao guardar.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Ajustar estoque",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
"consumes_parts_label": "Consome peças",
|
||||
"shared_parts_other_objects": "Peças de outros objetos",
|
||||
"shared_parts_help": "Vários objetos podem partilhar o mesmo stock. Concluir esta tarefa desconta do objeto proprietário.",
|
||||
"shared_part_unknown": "Peça desconhecida",
|
||||
"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)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Значение триггера",
|
||||
"complete_title": "Выполнить: ",
|
||||
"checklist": "Контрольный список",
|
||||
"require_on_completion": "Требовать при завершении",
|
||||
"checklist_steps_optional": "Шаги контрольного списка (необязательно)",
|
||||
"checklist_placeholder": "Очистить фильтр\nЗаменить уплотнитель\nПроверить давление",
|
||||
"checklist_help": "Один шаг на строку. Макс. 100 элементов.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Пусто = показать все статусы.",
|
||||
"card_filter_objects": "Фильтровать по объектам",
|
||||
"card_filter_objects_help": "Пусто = показать все объекты.",
|
||||
"card_filter_areas": "Фильтровать по зонам",
|
||||
"card_filter_areas_help": "Пусто = показать все зоны.",
|
||||
"card_filter_entities": "Фильтровать по сущностям (entity_ids)",
|
||||
"card_filter_entities_help": "Выберите сущности sensor / binary_sensor из этой интеграции. Пусто = все.",
|
||||
"card_loading_objects": "Загрузка объектов…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Заголовок панели",
|
||||
"settings_notifications": "Уведомления",
|
||||
"settings_notify_service": "Сервис уведомлений",
|
||||
"settings_install_assist_sentences": "Установить фразы Assist",
|
||||
"settings_install_assist_sentences_hint": "Копирует голосовые фразы в вашу конфигурацию, чтобы классический агент Assist их распознавал. Файл, который вы изменили, никогда не перезаписывается.",
|
||||
"test_notification": "Тестовое уведомление",
|
||||
"send_test": "Отправить тест",
|
||||
"testing": "Отправка…",
|
||||
"test_notification_success": "Тестовое уведомление отправлено",
|
||||
"test_notification_failed": "Не удалось отправить тестовое уведомление",
|
||||
"notify_per_person": "Доставка по пользователям",
|
||||
"notify_no_own_device": "Нет своего устройства — используется общий сервис",
|
||||
"settings_notify_due_soon": "Уведомлять, когда срок скоро истекает",
|
||||
"settings_notify_overdue": "Уведомлять при просрочке",
|
||||
"settings_notify_triggered": "Уведомлять при срабатывании",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "По пользователю",
|
||||
"filter_label": "Фильтр",
|
||||
"user_label": "Пользователь",
|
||||
"photo_label": "Фото",
|
||||
"sort_label": "Сортировка",
|
||||
"group_by_label": "Группировать по",
|
||||
"state_value_help": "Используйте значение состояния HA (обычно в нижнем регистре, напр. \"on\"/\"off\"). Регистр нормализуется при сохранении.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Изменить запас",
|
||||
"restock_quantity_label": "Куплено, шт.",
|
||||
"consumes_parts_label": "Расходует детали",
|
||||
"shared_parts_other_objects": "Детали других объектов",
|
||||
"shared_parts_help": "Несколько объектов могут использовать один запас. Выполнение этой задачи списывает детали у объекта-владельца.",
|
||||
"shared_part_unknown": "Неизвестная деталь",
|
||||
"parts_load_failed": "Не удалось загрузить запчасти этого объекта — параметры расхода запчастей сейчас недоступны.",
|
||||
"settings_export_selection": "Ограничить выбранными объектами (необязательно)",
|
||||
"settings_docs_archive": "Архив документов (с файлами)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Utlösarvärde",
|
||||
"complete_title": "Slutför: ",
|
||||
"checklist": "Checklista",
|
||||
"require_on_completion": "Kräv vid slutförande",
|
||||
"checklist_steps_optional": "Checkliststeg (valfritt)",
|
||||
"checklist_placeholder": "Rengör filter\nByt tätning\nTesta tryck",
|
||||
"checklist_help": "Ett steg per rad. Max 100 objekt.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Tomt = visa alla statusar.",
|
||||
"card_filter_objects": "Filtrera efter objekt",
|
||||
"card_filter_objects_help": "Tomt = visa alla objekt.",
|
||||
"card_filter_areas": "Filtrera efter områden",
|
||||
"card_filter_areas_help": "Tomt = visa alla områden.",
|
||||
"card_filter_entities": "Filtrera efter entiteter (entity_ids)",
|
||||
"card_filter_entities_help": "Välj sensor- / binary_sensor-entiteter från denna integration. Tomt = alla.",
|
||||
"card_loading_objects": "Laddar objekt…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Sidopanelens titel",
|
||||
"settings_notifications": "Notifikationer",
|
||||
"settings_notify_service": "Notifikationstjänst",
|
||||
"settings_install_assist_sentences": "Installera Assist-meningar",
|
||||
"settings_install_assist_sentences_hint": "Kopierar rösmeningarna till din konfiguration så att den klassiska Assist-agenten känner igen dem. En fil du själv har ändrat skrivs aldrig över.",
|
||||
"test_notification": "Testnotifikation",
|
||||
"send_test": "Skicka test",
|
||||
"testing": "Skickar…",
|
||||
"test_notification_success": "Testnotifikation skickad",
|
||||
"test_notification_failed": "Testnotifikation misslyckades",
|
||||
"notify_per_person": "Leverans per person",
|
||||
"notify_no_own_device": "Ingen egen enhet — använder hushållets tjänst",
|
||||
"settings_notify_due_soon": "Notifiera när snart förfallande",
|
||||
"settings_notify_overdue": "Notifiera när försenad",
|
||||
"settings_notify_triggered": "Notifiera när utlöst",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Per användare",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Användare",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Sortering",
|
||||
"group_by_label": "Gruppera efter",
|
||||
"state_value_help": "Använd HA-tillståndsvärdet (vanligtvis med små bokstäver, t.ex. \"on\"/\"off\"). Versaler normaliseras vid sparande.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Justera lager",
|
||||
"restock_quantity_label": "Köpt antal",
|
||||
"consumes_parts_label": "Förbrukar delar",
|
||||
"shared_parts_other_objects": "Delar från andra objekt",
|
||||
"shared_parts_help": "Flera objekt kan dela samma lager. När uppgiften slutförs dras det från det ägande objektet.",
|
||||
"shared_part_unknown": "Okänd del",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Tetikleme değeri",
|
||||
"complete_title": "Tamamla: ",
|
||||
"checklist": "Kontrol listesi",
|
||||
"require_on_completion": "Tamamlarken zorunlu tut",
|
||||
"checklist_steps_optional": "Kontrol listesi adımları (isteğe bağlı)",
|
||||
"checklist_placeholder": "Filtreyi temizle\nContayı değiştir\nBasıncı test et",
|
||||
"checklist_help": "Her satıra bir adım. En fazla 100 madde.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Boş = tüm durumları göster.",
|
||||
"card_filter_objects": "Nesnelere göre filtrele",
|
||||
"card_filter_objects_help": "Boş = tüm nesneleri göster.",
|
||||
"card_filter_areas": "Alanlara göre filtrele",
|
||||
"card_filter_areas_help": "Boş = tüm alanları göster.",
|
||||
"card_filter_entities": "Varlıklara göre filtrele (entity_id)",
|
||||
"card_filter_entities_help": "Bu entegrasyonun sensor / binary_sensor varlıklarını seçin. Boş = tümü.",
|
||||
"card_loading_objects": "Nesneler yükleniyor…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Kenar çubuğu paneli başlığı",
|
||||
"settings_notifications": "Bildirimler",
|
||||
"settings_notify_service": "Bildirim servisi",
|
||||
"settings_install_assist_sentences": "Assist cümlelerini yükle",
|
||||
"settings_install_assist_sentences_hint": "Sesli komut cümlelerini yapılandırmanıza kopyalar, böylece klasik Assist aracısı bunları tanır. Kendi düzenlediğiniz bir dosyanın üzerine asla yazılmaz.",
|
||||
"test_notification": "Test bildirimi",
|
||||
"send_test": "Test gönder",
|
||||
"testing": "Gönderiliyor…",
|
||||
"test_notification_success": "Test bildirimi gönderildi",
|
||||
"test_notification_failed": "Test bildirimi başarısız oldu",
|
||||
"notify_per_person": "Kişi bazında iletim",
|
||||
"notify_no_own_device": "Kendi cihazı yok — ev servisini kullanır",
|
||||
"settings_notify_due_soon": "Vade yaklaştığında bildir",
|
||||
"settings_notify_overdue": "Geciktiğinde bildir",
|
||||
"settings_notify_triggered": "Tetiklendiğinde bildir",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "Kullanıcıya göre",
|
||||
"filter_label": "Filtre",
|
||||
"user_label": "Kullanıcı",
|
||||
"photo_label": "Fotoğraf",
|
||||
"sort_label": "Sırala",
|
||||
"group_by_label": "Grupla",
|
||||
"state_value_help": "HA durum değerini kullanın (genellikle küçük harf, örn. \"on\"/\"off\"). Büyük/küçük harf kaydederken normalleştirilir.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "Stoku ayarla",
|
||||
"restock_quantity_label": "Satın alınan miktar",
|
||||
"consumes_parts_label": "Parça tüketir",
|
||||
"shared_parts_other_objects": "Diğer nesnelerin parçaları",
|
||||
"shared_parts_help": "Birden fazla nesne aynı stoğu paylaşabilir. Bu görev tamamlandığında stok, sahibi olan nesneden düşülür.",
|
||||
"shared_part_unknown": "Bilinmeyen parça",
|
||||
"parts_load_failed": "Bu nesnenin parçaları yüklenemedi — parça tüketimi seçenekleri şu anda kullanılamıyor.",
|
||||
"adopt_problem_button": "Sorun sensörlerini devral",
|
||||
"adopt_problem_title": "Sorun sensörlerini devral",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Значення тригера",
|
||||
"complete_title": "Виконати: ",
|
||||
"checklist": "Чекліст",
|
||||
"require_on_completion": "Вимагати при завершенні",
|
||||
"checklist_steps_optional": "Кроки чекліста (необов'язково)",
|
||||
"checklist_placeholder": "Очистити фільтр\nЗамінити ущільнювач\nПеревірити тиск",
|
||||
"checklist_help": "Один крок на рядок. Макс. 100 елементів.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Порожньо = показати всі статуси.",
|
||||
"card_filter_objects": "Фільтрувати за об'єктами",
|
||||
"card_filter_objects_help": "Порожньо = показати всі об'єкти.",
|
||||
"card_filter_areas": "Фільтрувати за зонами",
|
||||
"card_filter_areas_help": "Порожньо = показати всі зони.",
|
||||
"card_filter_entities": "Фільтрувати за сутностями (entity_ids)",
|
||||
"card_filter_entities_help": "Виберіть сутності sensor / binary_sensor з цієї інтеграції. Порожньо = всі.",
|
||||
"card_loading_objects": "Завантаження об'єктів…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Заголовок панелі",
|
||||
"settings_notifications": "Сповіщення",
|
||||
"settings_notify_service": "Служба сповіщень",
|
||||
"settings_install_assist_sentences": "Встановити фрази Assist",
|
||||
"settings_install_assist_sentences_hint": "Копіює голосові фрази до вашої конфігурації, щоб класичний агент Assist їх розпізнавав. Файл, який ви редагували, ніколи не перезаписується.",
|
||||
"test_notification": "Тестове сповіщення",
|
||||
"send_test": "Надіслати тест",
|
||||
"testing": "Надсилання…",
|
||||
"test_notification_success": "Тестове сповіщення надіслано",
|
||||
"test_notification_failed": "Не вдалося надіслати тестове сповіщення",
|
||||
"notify_per_person": "Доставка за особами",
|
||||
"notify_no_own_device": "Немає власного пристрою — використовується загальна служба",
|
||||
"settings_notify_due_soon": "Сповіщати, коли термін наближається",
|
||||
"settings_notify_overdue": "Сповіщати про прострочення",
|
||||
"settings_notify_triggered": "Сповіщати про спрацювання",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "За користувачем",
|
||||
"filter_label": "Фільтр",
|
||||
"user_label": "Користувач",
|
||||
"photo_label": "Фото",
|
||||
"sort_label": "Сортування",
|
||||
"group_by_label": "Групувати за",
|
||||
"state_value_help": "Використовуйте значення стану HA (зазвичай у нижньому регістрі, напр. \"on\"/\"off\"). Регістр нормалізується при збереженні.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Змінити запас",
|
||||
"restock_quantity_label": "Куплена кількість",
|
||||
"consumes_parts_label": "Витрачає деталі",
|
||||
"shared_parts_other_objects": "Деталі інших об'єктів",
|
||||
"shared_parts_help": "Кілька об'єктів можуть використовувати один запас. Виконання цього завдання списує деталі з об'єкта-власника.",
|
||||
"shared_part_unknown": "Невідома деталь",
|
||||
"parts_load_failed": "Не вдалося завантажити запчастини цього об'єкта — параметри витрати запчастин зараз недоступні.",
|
||||
"settings_export_selection": "Обмежити вибраними об'єктами (необов'язково)",
|
||||
"settings_docs_archive": "Архів документів (з файлами)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "触发值",
|
||||
"complete_title": "完成: ",
|
||||
"checklist": "检查清单",
|
||||
"require_on_completion": "完成时必填",
|
||||
"checklist_steps_optional": "检查步骤 (可选)",
|
||||
"checklist_placeholder": "清理过滤器\n更换密封圈\n测试压力",
|
||||
"checklist_help": "每行一个步骤。最多 100 项。",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "留空则显示所有状态。",
|
||||
"card_filter_objects": "按维护项过滤",
|
||||
"card_filter_objects_help": "留空则显示所有维护项。",
|
||||
"card_filter_areas": "按区域过滤",
|
||||
"card_filter_areas_help": "留空则显示所有区域。",
|
||||
"card_filter_entities": "按实体过滤 (entity_ids)",
|
||||
"card_filter_entities_help": "选择该集成的传感器或二进制传感器实体。留空则显示全部。",
|
||||
"card_loading_objects": "正在加载维护项…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "侧边栏面板标题",
|
||||
"settings_notifications": "通知",
|
||||
"settings_notify_service": "通知服务",
|
||||
"settings_install_assist_sentences": "安装 Assist 语句",
|
||||
"settings_install_assist_sentences_hint": "将语音语句复制到你的配置中,让传统 Assist 代理能够识别它们。你自己修改过的文件永远不会被覆盖。",
|
||||
"test_notification": "测试通知",
|
||||
"send_test": "发送测试",
|
||||
"testing": "正在发送…",
|
||||
"test_notification_success": "测试通知已发送",
|
||||
"test_notification_failed": "测试通知发送失败",
|
||||
"notify_per_person": "按成员分别投递",
|
||||
"notify_no_own_device": "无个人设备 — 使用家庭通知服务",
|
||||
"settings_notify_due_soon": "到期前通知提醒",
|
||||
"settings_notify_overdue": "超期后通知提醒",
|
||||
"settings_notify_triggered": "触发时通知提醒",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "按负责人",
|
||||
"filter_label": "过滤器",
|
||||
"user_label": "用户",
|
||||
"photo_label": "照片",
|
||||
"sort_label": "排序",
|
||||
"group_by_label": "分组依据",
|
||||
"state_value_help": "使用 HA 状态值(通常为小写,例如 \"on\"/\"off\")。保存时大小写会自动规范化。",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "调整库存",
|
||||
"restock_quantity_label": "购买数量",
|
||||
"consumes_parts_label": "消耗配件",
|
||||
"shared_parts_other_objects": "其他设备的配件",
|
||||
"shared_parts_help": "多个设备可以共用同一批库存。完成此任务将从所属设备的库存中扣减。",
|
||||
"shared_part_unknown": "未知配件",
|
||||
"parts_load_failed": "无法加载此对象的配件 — 配件消耗选项暂时不可用。",
|
||||
"settings_export_selection": "仅限所选对象(可选)",
|
||||
"settings_docs_archive": "文档存档(含文件)",
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
type CalendarEvent,
|
||||
} from "./helpers/calendar-bucket";
|
||||
import { calendarStyles } from "./calendar-styles";
|
||||
import { sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setDateTimePrefs } from "./styles";
|
||||
import { sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays } from "./styles";
|
||||
import type {
|
||||
HomeAssistant,
|
||||
MaintenanceObjectResponse,
|
||||
@@ -267,7 +267,7 @@ export class MaintenanceCalendarCard extends LitElement {
|
||||
const statusClass = `cal-status-${ev.status}`;
|
||||
const projClass = ev.projected ? "cal-event-projected" : "";
|
||||
const overdueLabel = ev.status === "overdue" && ev.days_until_due != null
|
||||
? ` (${Math.abs(ev.days_until_due)}d ${t("overdue", L).toLowerCase()})`
|
||||
? ` (${formatDueDays(ev.days_until_due, L)})`
|
||||
: "";
|
||||
const recurEvery = ev.projected && ev.interval_days
|
||||
? html`<span class="cal-event-recur">${
|
||||
|
||||
@@ -81,6 +81,18 @@ export class MaintenanceSupporterCardEditor extends LitElement {
|
||||
this._valueChanged("filter_objects", [...current]);
|
||||
}
|
||||
|
||||
private _toggleLabel(label: string, on: boolean): void {
|
||||
const current = new Set(this._config.filter_labels || []);
|
||||
if (on) current.add(label); else current.delete(label);
|
||||
this._valueChanged("filter_labels", [...current]);
|
||||
}
|
||||
|
||||
private _toggleArea(areaId: string, on: boolean): void {
|
||||
const current = new Set(this._config.filter_areas || []);
|
||||
if (on) current.add(areaId); else current.delete(areaId);
|
||||
this._valueChanged("filter_areas", [...current]);
|
||||
}
|
||||
|
||||
private _onEntitiesChanged = (e: CustomEvent<{ value: string[] }>): void => {
|
||||
this._valueChanged("entity_ids", e.detail.value || []);
|
||||
};
|
||||
@@ -92,6 +104,30 @@ export class MaintenanceSupporterCardEditor extends LitElement {
|
||||
const objectNames = [...this._objects]
|
||||
.map((o) => o.object.name)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
// Areas (C8): offer exactly the areas that actually hold an object — an
|
||||
// area with nothing in it could only ever empty the card. Names resolve
|
||||
// through hass.areas; an area HA no longer knows falls back to its raw id
|
||||
// so an existing config stays visible (and un-checkable) instead of
|
||||
// vanishing silently.
|
||||
const selectedAreas = new Set(this._config.filter_areas || []);
|
||||
const areaIds = [
|
||||
...new Set(
|
||||
this._objects
|
||||
.map((o) => o.object.area_id)
|
||||
.filter((a): a is string => !!a)
|
||||
.concat([...selectedAreas]),
|
||||
),
|
||||
];
|
||||
const areaName = (id: string): string => this.hass?.areas?.[id]?.name || id;
|
||||
const areas = areaIds
|
||||
.map((id) => ({ id, name: areaName(id) }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
const selectedLabels = new Set(this._config.filter_labels || []);
|
||||
// Labels are free-form per task, so the picker offers exactly the ones in
|
||||
// use — an empty list simply hides the section.
|
||||
const labelNames = [
|
||||
...new Set(this._objects.flatMap((o) => o.tasks.flatMap((tk) => tk.labels || []))),
|
||||
].sort((a, b) => a.localeCompare(b));
|
||||
// Build the list of OUR sensor + binary_sensor entity_ids so the
|
||||
// ha-entities-picker only shows maintenance_supporter entities, not
|
||||
// every sensor in HA.
|
||||
@@ -152,6 +188,38 @@ export class MaintenanceSupporterCardEditor extends LitElement {
|
||||
`
|
||||
}
|
||||
</div>
|
||||
<!-- Area filter (C8): selects whole objects by the room they sit in.
|
||||
Hidden while no object has an area — the section would be an
|
||||
empty box otherwise. -->
|
||||
${areas.length ? html`
|
||||
<div class="field">
|
||||
<div class="field-label">${t("card_filter_areas", L)}</div>
|
||||
<div class="object-list">
|
||||
${areas.map((a) => html`
|
||||
<label class="object-row">
|
||||
<input type="checkbox"
|
||||
.checked=${selectedAreas.has(a.id)}
|
||||
@change=${(e: Event) => this._toggleArea(a.id, (e.target as HTMLInputElement).checked)} />
|
||||
<span>${a.name}</span>
|
||||
</label>
|
||||
`)}
|
||||
</div>
|
||||
<div class="field-help">${t("card_filter_areas_help", L)}</div>
|
||||
</div>` : nothing}
|
||||
${labelNames.length ? html`
|
||||
<div class="field">
|
||||
<div class="field-label">${t("labels", L)}</div>
|
||||
<div class="object-list">
|
||||
${labelNames.map((name) => html`
|
||||
<label class="object-row">
|
||||
<input type="checkbox"
|
||||
.checked=${selectedLabels.has(name)}
|
||||
@change=${(e: Event) => this._toggleLabel(name, (e.target as HTMLInputElement).checked)} />
|
||||
<span>${name}</span>
|
||||
</label>
|
||||
`)}
|
||||
</div>
|
||||
</div>` : nothing}
|
||||
|
||||
<!-- Entity-id filter (HA-native pattern). Limited to our integration's
|
||||
sensor + binary_sensor entities via includeEntities so the picker
|
||||
@@ -219,6 +287,14 @@ export class MaintenanceSupporterCardEditor extends LitElement {
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
|
||||
<ha-formfield label="${t("documents", L)}">
|
||||
<ha-switch
|
||||
.checked=${this._config.show_documents !== false}
|
||||
@change=${(e: Event) =>
|
||||
this._valueChanged("show_documents", (e.target as HTMLInputElement).checked)}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
|
||||
<ha-formfield label="${t("card_compact", L)}">
|
||||
<ha-switch
|
||||
.checked=${this._config.compact || false}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setDateTimePrefs } from "./styles";
|
||||
import { sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays } from "./styles";
|
||||
import type {
|
||||
HomeAssistant,
|
||||
MaintenanceObjectResponse,
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
SavedViewFilters,
|
||||
} from "./types";
|
||||
import { UserService } from "./user-service";
|
||||
import { partsForCompletion } from "./helpers/shared-parts";
|
||||
import "./maintenance-card-editor";
|
||||
import "./components/complete-dialog";
|
||||
import {
|
||||
@@ -21,6 +22,13 @@ import {
|
||||
openTaskQuickActions,
|
||||
} from "./dialog-mount";
|
||||
|
||||
interface CardDoc {
|
||||
id: string;
|
||||
title: string;
|
||||
kind: string;
|
||||
url?: string | null;
|
||||
}
|
||||
|
||||
interface FlatTask {
|
||||
entry_id: string;
|
||||
object_name: string;
|
||||
@@ -39,6 +47,9 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
@state() private _userNames: Record<string, string> = {};
|
||||
private _userService: UserService | null = null;
|
||||
private _userNamesLoaded = false;
|
||||
/** entry_id → (task_id → documents) for the row chips. */
|
||||
@state() private _taskDocs: Record<string, Record<string, CardDoc[]>> = {};
|
||||
private _docsLoadedFor = new Set<string>();
|
||||
|
||||
private get _lang(): string {
|
||||
return this.hass?.language || "en";
|
||||
@@ -114,6 +125,15 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
) {
|
||||
this._loadUserNames();
|
||||
}
|
||||
// Documents: same shape of guard — fetch per object, once, and only for
|
||||
// objects that actually have a document attached to one of their tasks.
|
||||
if (this.hass && this._config.show_documents !== false) {
|
||||
for (const obj of this._objects) {
|
||||
if (this._docsLoadedFor.has(obj.entry_id)) continue;
|
||||
if (!obj.tasks.some((tk) => (tk.document_count ?? 0) > 0)) continue;
|
||||
this._loadDocuments(obj.entry_id);
|
||||
}
|
||||
}
|
||||
if (changedProps.has("hass") && this.hass) {
|
||||
if (!this._dataLoaded) {
|
||||
this._dataLoaded = true;
|
||||
@@ -146,12 +166,6 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
await this._loadViewFilters();
|
||||
}
|
||||
|
||||
/** Resolve display names for the assignee badge (best-effort).
|
||||
*
|
||||
* `users/list` is a READ-tier command, so the household members this card
|
||||
* is built for can call it without admin rights. A failure (or a task
|
||||
* whose user was deleted) leaves the name unresolved and the badge simply
|
||||
* does not render — never a raw user id. */
|
||||
/** Display name of the task's responsible user, or "" when the badge must
|
||||
* stay hidden (feature off, nobody assigned, or the name not resolved).
|
||||
* With a rotation this is whoever is up next — the pointer the engine
|
||||
@@ -163,6 +177,12 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
return this._userNames[id] || "";
|
||||
}
|
||||
|
||||
/** Resolve display names for the assignee badge (best-effort).
|
||||
*
|
||||
* `users/list` is a READ-tier command, so the household members this card
|
||||
* is built for can call it without admin rights. A failure (or a task
|
||||
* whose user was deleted) leaves the name unresolved and the badge simply
|
||||
* does not render — never a raw user id. */
|
||||
private async _loadUserNames(): Promise<void> {
|
||||
this._userNamesLoaded = true;
|
||||
if (!this._userService) this._userService = new UserService(this.hass);
|
||||
@@ -175,6 +195,62 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch one object's documents and index them by task.
|
||||
*
|
||||
* `documents/list` is READ tier, like `users/list` — the household members
|
||||
* this card is for may call it. A failure leaves the row without chips
|
||||
* rather than breaking the card. */
|
||||
private async _loadDocuments(entryId: string): Promise<void> {
|
||||
this._docsLoadedFor.add(entryId);
|
||||
try {
|
||||
const res = (await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/documents/list",
|
||||
entry_id: entryId,
|
||||
})) as { documents: Array<CardDoc & { task_ids?: string[] }> };
|
||||
const byTask: Record<string, CardDoc[]> = {};
|
||||
for (const doc of res.documents || []) {
|
||||
for (const taskId of doc.task_ids || []) {
|
||||
(byTask[taskId] ||= []).push({ id: doc.id, title: doc.title, kind: doc.kind, url: doc.url });
|
||||
}
|
||||
}
|
||||
this._taskDocs = { ...this._taskDocs, [entryId]: byTask };
|
||||
} catch {
|
||||
// no chips for this object; the card is unaffected otherwise
|
||||
}
|
||||
}
|
||||
|
||||
/** Chips to render on a row: linked documents plus the task's own manual
|
||||
* link, which is the same "the manual is one tap away" affordance. */
|
||||
private _docsFor(entryId: string, task: MaintenanceTask): CardDoc[] {
|
||||
if (this._config.show_documents === false) return [];
|
||||
const linked = this._taskDocs[entryId]?.[task.id] || [];
|
||||
const out = [...linked];
|
||||
if (task.documentation_url) {
|
||||
out.push({ id: `url:${task.id}`, title: t("documentation_label", this._lang), kind: "weblink", url: task.documentation_url });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Open a chip: a web link directly, a stored file through a signed path
|
||||
* (the same route the panel uses, so it works in the Companion app). */
|
||||
private async _openDoc(doc: CardDoc): Promise<void> {
|
||||
if (doc.kind === "weblink" && doc.url) {
|
||||
window.open(doc.url, "_blank", "noopener");
|
||||
return;
|
||||
}
|
||||
const win = window.open("about:blank", "_blank");
|
||||
try {
|
||||
const signed = await this.hass.connection.sendMessagePromise<{ path: string }>({
|
||||
type: "auth/sign_path",
|
||||
path: `/api/maintenance_supporter/document/${doc.id}`,
|
||||
expires: 300,
|
||||
});
|
||||
if (win) win.location.href = new URL(signed.path, window.location.origin).href;
|
||||
} catch {
|
||||
if (win) win.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve the configured saved view's filters (best-effort). A missing or
|
||||
* deleted view degrades to "no view filter" — same fallback semantics as
|
||||
* the backend's notification routing, never an inexplicably empty card. */
|
||||
@@ -222,6 +298,8 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
entity_ids,
|
||||
filter_due_min_days,
|
||||
filter_due_max_days,
|
||||
filter_labels,
|
||||
filter_areas,
|
||||
max_items,
|
||||
} = this._config;
|
||||
const entityFilter = entity_ids?.length ? new Set(entity_ids) : null;
|
||||
@@ -238,6 +316,13 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
|
||||
for (const obj of this._objects) {
|
||||
if (filter_objects?.length && !filter_objects.includes(obj.object.name)) continue;
|
||||
// Areas (C8): object-level, so it selects whole objects like
|
||||
// filter_objects does — "the tasks for this room". An object with no
|
||||
// area_id can never satisfy a non-empty list.
|
||||
if (filter_areas?.length) {
|
||||
const areaId = obj.object.area_id;
|
||||
if (!areaId || !filter_areas.includes(areaId)) continue;
|
||||
}
|
||||
for (const task of obj.tasks) {
|
||||
// Completed one-time tasks ("done") are hidden from the active list.
|
||||
if (task.is_done) continue;
|
||||
@@ -245,6 +330,8 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
// never shown on the Lovelace card.
|
||||
if (task.archived || obj.object.archived) continue;
|
||||
if (filter_status?.length && !filter_status.includes(task.status)) continue;
|
||||
// Labels: a task passes when it carries at least one configured label.
|
||||
if (filter_labels?.length && !(task.labels || []).some((lb) => filter_labels.includes(lb))) continue;
|
||||
// entity_ids: HA-native filter — match the task's sensor or
|
||||
// binary_sensor entity_id. Both fields come pre-resolved from the
|
||||
// backend WS response (see _build_task_summary in websocket/__init__.py).
|
||||
@@ -384,13 +471,26 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._docsFor(entry_id, task).length
|
||||
? html`<div class="doc-chips">
|
||||
${this._docsFor(entry_id, task).map((doc) => html`
|
||||
<button
|
||||
type="button"
|
||||
class="doc-chip"
|
||||
title="${doc.title}"
|
||||
@click=${(e: Event) => { e.stopPropagation(); void this._openDoc(doc); }}
|
||||
>
|
||||
<ha-icon icon=${doc.kind === "weblink" ? "mdi:link-variant" : "mdi:file-document-outline"}></ha-icon>
|
||||
<span>${doc.title}</span>
|
||||
</button>
|
||||
`)}
|
||||
</div>`
|
||||
: nothing}
|
||||
<div class="task-due">
|
||||
${task.days_until_due !== null && task.days_until_due !== undefined
|
||||
? task.days_until_due < 0
|
||||
? html`<span class="overdue-text">${Math.abs(task.days_until_due)}${L.startsWith("de") ? "T" : "d"}</span>`
|
||||
: task.days_until_due === 0
|
||||
? t("today", L)
|
||||
: `${task.days_until_due}${L.startsWith("de") ? "T" : "d"}`
|
||||
? html`<span class="overdue-text">${formatDueDays(task.days_until_due, L)}</span>`
|
||||
: formatDueDays(task.days_until_due, L)
|
||||
: task.trigger_active
|
||||
? "⚡"
|
||||
: "—"}
|
||||
@@ -411,13 +511,17 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
dlg.adaptiveEnabled = !!task.adaptive_config?.enabled;
|
||||
dlg.taskType = task.type || "";
|
||||
dlg.readingUnit = (task as any).reading_unit || "";
|
||||
dlg.requiredFields = task.required_completion_fields || [];
|
||||
dlg.lang = L;
|
||||
// #99: editable per-completion parts selection
|
||||
// (skip on buy tasks — those restock instead).
|
||||
const obj = this._objects.find((o) => o.entry_id === entry_id);
|
||||
// #111: the list also carries the shared pools
|
||||
// this task draws on, each named after its
|
||||
// owner — resolving against the object's own
|
||||
// parts alone left a foreign link invisible.
|
||||
const isBuy = !!(task as any).part_ref;
|
||||
dlg.parts = isBuy ? [] : (obj?.parts || []);
|
||||
dlg.consumesParts = isBuy ? [] : ((task as any).consumes_parts || []);
|
||||
dlg.parts = isBuy ? [] : partsForCompletion(task, entry_id, this._objects, L);
|
||||
dlg.consumesParts = isBuy ? [] : (task.consumes_parts || []);
|
||||
dlg.open();
|
||||
}}
|
||||
>
|
||||
@@ -534,7 +638,37 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.task-due { font-size: 13px; color: var(--secondary-text-color); min-width: 40px; text-align: right; }
|
||||
.doc-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-right: 6px;
|
||||
max-width: 45%;
|
||||
}
|
||||
.doc-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
max-width: 14ch;
|
||||
padding: 1px 6px;
|
||||
border: 1px solid var(--divider-color, #e0e0e0);
|
||||
border-radius: 10px;
|
||||
background: none;
|
||||
color: var(--secondary-text-color);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.doc-chip:hover { color: var(--primary-color); border-color: var(--primary-color); }
|
||||
.doc-chip ha-icon { --mdc-icon-size: 12px; width: 12px; height: 12px; }
|
||||
/* nowrap: the due label is localized via formatDueDays ("5 d overdue",
|
||||
"5 T überfällig") — without it a narrow phone card wraps that onto a
|
||||
second line and the row grows taller. The name column ellipsizes
|
||||
instead, which it already does by design. */
|
||||
.task-due { font-size: 13px; color: var(--secondary-text-color); min-width: 40px; text-align: right; white-space: nowrap; }
|
||||
.overdue-text { color: var(--error-color); font-weight: 500; }
|
||||
|
||||
.complete-btn {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { warrantyStatus } from "./helpers/warranty";
|
||||
import { OBJECT_COLUMNS, DEFAULT_OBJECTS_TABLE_COLUMNS, sanitizeColumns } from "./helpers/object-columns";
|
||||
import { downloadTextFile } from "./helpers/download";
|
||||
import { buildTaskWorksheetHtml, type WorksheetExcerpt, type WorksheetLabels } from "./helpers/worksheet";
|
||||
import { describePartLink, partsForCompletion } from "./helpers/shared-parts";
|
||||
import { describeWsError } from "./ws-errors";
|
||||
import { panelStyles } from "./panel-styles";
|
||||
import type {
|
||||
@@ -1759,17 +1760,13 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
statusLabel: (st: string) => t(st, L),
|
||||
parts: t("consumes_parts_label", L),
|
||||
};
|
||||
// Required parts as checkable lines: qty × name (stock unit) — location.
|
||||
const wsParts = obj.parts || [];
|
||||
const partsLines = (task.consumes_parts || [])
|
||||
.map((link) => {
|
||||
const pt = wsParts.find((x) => x.id === link.part_id);
|
||||
if (!pt) return "";
|
||||
const stock = pt.stock !== null && pt.stock !== undefined ? ` (${pt.stock}${pt.unit ? " " + pt.unit : ""})` : "";
|
||||
const loc = pt.storage_location ? ` — ${pt.storage_location}` : "";
|
||||
return `${link.quantity}× ${pt.name}${stock}${loc}`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
// Required parts as checkable lines: qty × name (owner) (stock unit) —
|
||||
// location. A pool owned by another object (#111) names that object, and
|
||||
// a link that resolves to nothing prints "Unknown part" rather than the
|
||||
// blank line the old own-parts-only lookup produced.
|
||||
const partsLines = (task.consumes_parts || []).map((link) =>
|
||||
describePartLink(link, obj.entry_id, this._objects, L),
|
||||
);
|
||||
const html = buildTaskWorksheetHtml(
|
||||
task, obj.object.name, labels,
|
||||
(iso) => formatDate(iso, L),
|
||||
@@ -1804,24 +1801,22 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
?.tasks.find((tsk) => tsk.id === taskId);
|
||||
dlg.taskType = tk?.type || "";
|
||||
dlg.readingUnit = tk?.reading_unit || "";
|
||||
dlg.requiredFields = tk?.required_completion_fields || [];
|
||||
// Spare parts: a buy task gets an editable restock-qty field; a consuming
|
||||
// task shows what it will decrement (incl. the storage location).
|
||||
const objParts = this._objects.find((o) => o.entry_id === entryId)?.parts || [];
|
||||
const partById = new Map(objParts.map((pt) => [pt.id, pt]));
|
||||
const refPart = tk?.part_ref ? partById.get(tk.part_ref.part_id) : undefined;
|
||||
const refPart = tk?.part_ref ? objParts.find((pt) => pt.id === tk.part_ref!.part_id) : undefined;
|
||||
dlg.restockDefault = tk?.part_ref ? (refPart?.restock_quantity ?? 1) : null;
|
||||
dlg.consumesInfo = (tk?.consumes_parts || [])
|
||||
.map((link) => {
|
||||
const pt = partById.get(link.part_id);
|
||||
if (!pt) return "";
|
||||
const loc = pt.storage_location ? ` — ${pt.storage_location}` : "";
|
||||
const stock = pt.stock !== null && pt.stock !== undefined ? ` (${pt.stock}${pt.unit ? " " + pt.unit : ""})` : "";
|
||||
return `${link.quantity}× ${pt.name}${stock}${loc}`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
// #111: a link may point at another object's pool — name that object, and
|
||||
// never drop a line that fails to resolve (the old .filter(Boolean) hid it).
|
||||
dlg.consumesInfo = (tk?.consumes_parts || []).map((link) =>
|
||||
describePartLink(link, entryId, this._objects, this._lang),
|
||||
);
|
||||
// #99: editable per-completion parts selection (not on buy tasks — those
|
||||
// RESTOCK via the qty field instead of consuming).
|
||||
dlg.parts = tk?.part_ref ? [] : objParts;
|
||||
// RESTOCK via the qty field instead of consuming). The list carries the
|
||||
// object's own parts plus the shared pools this task draws on, so a foreign
|
||||
// link is visible and untickable rather than silently absent.
|
||||
dlg.parts = tk?.part_ref ? [] : partsForCompletion(tk, entryId, this._objects, this._lang);
|
||||
dlg.consumesParts = tk?.part_ref ? [] : (tk?.consumes_parts || []);
|
||||
dlg.open();
|
||||
}
|
||||
|
||||
@@ -164,6 +164,8 @@ export interface MaintenanceTask {
|
||||
labels?: string[];
|
||||
assignee_pool?: string[];
|
||||
rotation_strategy?: string | null;
|
||||
/** Details this task demands on completion (v2.44): notes/cost/duration/photo/user. */
|
||||
required_completion_fields?: string[];
|
||||
earliest_completion_days?: number | null;
|
||||
// v1.3.0: completion-action + quick-complete (gated by completion_actions feature)
|
||||
on_complete_action?: {
|
||||
@@ -236,11 +238,25 @@ export interface MaintenanceTask {
|
||||
sensor_entity_id?: string | null;
|
||||
binary_sensor_entity_id?: string | null;
|
||||
/** Spare parts consumed by completing this task. */
|
||||
consumes_parts?: Array<{ part_id: string; quantity: number }> | null;
|
||||
consumes_parts?: TaskPartLink[] | null;
|
||||
/** Present on an auto-created "buy" reminder: the owning part. */
|
||||
part_ref?: { part_id: string } | null;
|
||||
}
|
||||
|
||||
/** One entry of a task's `consumes_parts`.
|
||||
*
|
||||
* `entry_id` ABSENT = the part belongs to the task's own object. That is what
|
||||
* every link written before #111 looks like and what is still written for own
|
||||
* parts — nothing emits an entry_id for them. PRESENT = the task draws on a
|
||||
* stock pool owned by that other object (three vacuums, one box of dust bags),
|
||||
* and completing the task decrements the other object's stock.
|
||||
*/
|
||||
export interface TaskPartLink {
|
||||
part_id: string;
|
||||
quantity: number;
|
||||
entry_id?: string;
|
||||
}
|
||||
|
||||
/** A spare part / consumable on an object (full definition + derived state). */
|
||||
export interface MaintenancePart {
|
||||
id: string;
|
||||
@@ -328,6 +344,18 @@ export interface CardConfig {
|
||||
// Defaults to ON — with rotations the row is the only place a household
|
||||
// sees who is up next. Rows without an assignee simply render nothing.
|
||||
show_assignee?: boolean;
|
||||
// Labels to limit the card to (v2.44). A task passes when it carries at
|
||||
// least one of them — same OR semantics as filter_status / filter_objects.
|
||||
filter_labels?: string[];
|
||||
// HA area ids to limit the card to (C8). A task passes when its parent
|
||||
// OBJECT sits in one of these areas — same OR semantics as filter_objects,
|
||||
// and ANDed with every other filter. Objects without an area never match a
|
||||
// non-empty list. Empty / unset = no area filtering.
|
||||
filter_areas?: string[];
|
||||
// Show the task's linked documents (and its documentation link) as chips
|
||||
// on the row, so the manual is one tap away. Defaults to ON; rows without
|
||||
// a document render nothing extra.
|
||||
show_documents?: boolean;
|
||||
// Saved-view scope (v2.26): apply a saved view's task-selecting filters
|
||||
// (status / user / label) ON TOP of the card's own filters. The view's
|
||||
// sort/group dimensions are panel display state and are not applied here.
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Hodnota spouštěče",
|
||||
"complete_title": "Dokončit: ",
|
||||
"checklist": "Kontrolní seznam",
|
||||
"require_on_completion": "Vyžadovat při dokončení",
|
||||
"checklist_steps_optional": "Kroky kontrolního seznamu (volitelné)",
|
||||
"checklist_placeholder": "Vyčistit filtr\nVyměnit těsnění\nOtestovat tlak",
|
||||
"checklist_help": "Jeden krok na řádek. Max 100 položek.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Prázdné = zobrazit všechny stavy.",
|
||||
"card_filter_objects": "Filtrovat podle objektů",
|
||||
"card_filter_objects_help": "Prázdné = zobrazit všechny objekty.",
|
||||
"card_filter_areas": "Filtrovat podle oblastí",
|
||||
"card_filter_areas_help": "Prázdné = zobrazit všechny oblasti.",
|
||||
"card_filter_entities": "Filtrovat podle entit (entity_ids)",
|
||||
"card_filter_entities_help": "Vyberte entity sensor / binary_sensor z této integrace. Prázdné = všechny.",
|
||||
"card_loading_objects": "Načítání objektů…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Název panelu",
|
||||
"settings_notifications": "Oznámení",
|
||||
"settings_notify_service": "Služba oznámení",
|
||||
"settings_install_assist_sentences": "Nainstalovat věty pro Assist",
|
||||
"settings_install_assist_sentences_hint": "Zkopíruje hlasové věty do vaší konfigurace, aby je klasický agent Assist rozpoznal. Soubor, který jste upravili, nebude nikdy přepsán.",
|
||||
"test_notification": "Testovací oznámení",
|
||||
"send_test": "Odeslat test",
|
||||
"testing": "Odesílání…",
|
||||
"test_notification_success": "Testovací oznámení odesláno",
|
||||
"test_notification_failed": "Testovací oznámení se nezdařilo",
|
||||
"notify_per_person": "Doručování podle osoby",
|
||||
"notify_no_own_device": "Žádné vlastní zařízení — použije domácí službu",
|
||||
"settings_notify_due_soon": "Oznámit když brzy",
|
||||
"settings_notify_overdue": "Oznámit když po termínu",
|
||||
"settings_notify_triggered": "Oznámit když spuštěno",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Podle uživatele",
|
||||
"filter_label": "Filtr",
|
||||
"user_label": "Uživatel",
|
||||
"photo_label": "Fotografie",
|
||||
"sort_label": "Řazení",
|
||||
"group_by_label": "Seskupit podle",
|
||||
"state_value_help": "Použijte hodnotu stavu HA (obvykle malými písmeny, např. \"on\"/\"off\"). Velikost písmen se při uložení normalizuje.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Upravit zásobu",
|
||||
"restock_quantity_label": "Zakoupené množství",
|
||||
"consumes_parts_label": "Spotřebovává díly",
|
||||
"shared_parts_other_objects": "Díly z jiných objektů",
|
||||
"shared_parts_help": "Několik objektů může sdílet jeden sklad. Dokončení tohoto úkolu odečte zásobu z vlastnícího objektu.",
|
||||
"shared_part_unknown": "Neznámý díl",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Udløserværdi",
|
||||
"complete_title": "Fuldfør: ",
|
||||
"checklist": "Tjekliste",
|
||||
"require_on_completion": "Kræv ved fuldførelse",
|
||||
"checklist_steps_optional": "Tjeklistetrin (valgfrit)",
|
||||
"checklist_placeholder": "Rengør filter\nUdskift pakning\nTest tryk",
|
||||
"checklist_help": "Ét trin pr. linje. Maks. 100 elementer.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Tom = vis alle statusser.",
|
||||
"card_filter_objects": "Filtrer efter objekter",
|
||||
"card_filter_objects_help": "Tom = vis alle objekter.",
|
||||
"card_filter_areas": "Filtrer efter områder",
|
||||
"card_filter_areas_help": "Tom = vis alle områder.",
|
||||
"card_filter_entities": "Filtrer efter enheder (entity_ids)",
|
||||
"card_filter_entities_help": "Vælg sensor- / binary_sensor-enheder fra denne integration. Tom = alle.",
|
||||
"card_loading_objects": "Indlæser objekter…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Sidepanelets titel",
|
||||
"settings_notifications": "Notifikationer",
|
||||
"settings_notify_service": "Notifikationstjeneste",
|
||||
"settings_install_assist_sentences": "Installer Assist-sætninger",
|
||||
"settings_install_assist_sentences_hint": "Kopierer stemmesætningerne til din konfiguration, så den klassiske Assist-agent genkender dem. En fil, du selv har redigeret, overskrives aldrig.",
|
||||
"test_notification": "Testnotifikation",
|
||||
"send_test": "Send test",
|
||||
"testing": "Sender…",
|
||||
"test_notification_success": "Testnotifikation sendt",
|
||||
"test_notification_failed": "Testnotifikation mislykkedes",
|
||||
"notify_per_person": "Levering pr. person",
|
||||
"notify_no_own_device": "Ingen egen enhed — bruger husstandens tjeneste",
|
||||
"settings_notify_due_soon": "Notificer ved snart forfalden",
|
||||
"settings_notify_overdue": "Notificer ved forfalden",
|
||||
"settings_notify_triggered": "Notificer ved udløst",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "Efter bruger",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Bruger",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Sortering",
|
||||
"group_by_label": "Grupper efter",
|
||||
"state_value_help": "Brug HA-tilstandsværdien (normalt med små bogstaver, f.eks. \"on\"/\"off\"). Store/små bogstaver normaliseres ved lagring.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Justér lager",
|
||||
"restock_quantity_label": "Købt mængde",
|
||||
"consumes_parts_label": "Forbruger dele",
|
||||
"shared_parts_other_objects": "Dele fra andre objekter",
|
||||
"shared_parts_help": "Flere objekter kan dele det samme lager. Når opgaven fuldføres, trækkes der fra det ejende objekt.",
|
||||
"shared_part_unknown": "Ukendt del",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Trigger-Wert",
|
||||
"complete_title": "Erledigt: ",
|
||||
"checklist": "Checkliste",
|
||||
"require_on_completion": "Beim Abschließen verlangen",
|
||||
"checklist_steps_optional": "Checkliste-Schritte (optional)",
|
||||
"checklist_placeholder": "Filter reinigen\nDichtung ersetzen\nDruck testen",
|
||||
"checklist_help": "Ein Schritt pro Zeile. Max. 100 Einträge.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Leer = alle Status zeigen.",
|
||||
"card_filter_objects": "Nach Objekten filtern",
|
||||
"card_filter_objects_help": "Leer = alle Objekte zeigen.",
|
||||
"card_filter_areas": "Nach Bereichen filtern",
|
||||
"card_filter_areas_help": "Leer = alle Bereiche zeigen.",
|
||||
"card_filter_entities": "Nach Entitäten filtern (entity_ids)",
|
||||
"card_filter_entities_help": "Wähle Sensor-/Binary-Sensor-Entitäten dieser Integration. Leer = alle.",
|
||||
"card_loading_objects": "Lade Objekte…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Panel-Titel",
|
||||
"settings_notifications": "Benachrichtigungen",
|
||||
"settings_notify_service": "Benachrichtigungsdienst",
|
||||
"settings_install_assist_sentences": "Assist-Sätze installieren",
|
||||
"settings_install_assist_sentences_hint": "Kopiert die Sprachbefehle in deine Konfiguration, damit der klassische Assist-Agent sie erkennt. Eine selbst bearbeitete Datei wird nie überschrieben.",
|
||||
"test_notification": "Test-Benachrichtigung",
|
||||
"send_test": "Test senden",
|
||||
"testing": "Sende…",
|
||||
"test_notification_success": "Test-Benachrichtigung gesendet",
|
||||
"test_notification_failed": "Test-Benachrichtigung fehlgeschlagen",
|
||||
"notify_per_person": "Zustellung pro Person",
|
||||
"notify_no_own_device": "Kein eigenes Gerät — nutzt den Haushaltsdienst",
|
||||
"settings_notify_due_soon": "Bei baldiger Fälligkeit benachrichtigen",
|
||||
"settings_notify_overdue": "Bei Überfälligkeit benachrichtigen",
|
||||
"settings_notify_triggered": "Bei Auslösung benachrichtigen",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "Nach Verantwortlichem",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Benutzer",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Sortierung",
|
||||
"group_by_label": "Gruppieren nach",
|
||||
"state_value_help": "Verwende den HA-Zustandswert (meist kleingeschrieben, z. B. \"on\"/\"off\"). Groß-/Kleinschreibung wird beim Speichern normalisiert.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Bestand anpassen",
|
||||
"restock_quantity_label": "Gekaufte Menge",
|
||||
"consumes_parts_label": "Verbraucht Teile",
|
||||
"shared_parts_other_objects": "Teile anderer Objekte",
|
||||
"shared_parts_help": "Mehrere Objekte können sich einen Bestand teilen. Beim Abschließen wird vom besitzenden Objekt abgebucht.",
|
||||
"shared_part_unknown": "Unbekanntes Teil",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Trigger value",
|
||||
"complete_title": "Complete: ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Require on completion",
|
||||
"checklist_steps_optional": "Checklist steps (optional)",
|
||||
"checklist_placeholder": "Clean filter\nReplace seal\nTest pressure",
|
||||
"checklist_help": "One step per line. Max 100 items.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Empty = show all statuses.",
|
||||
"card_filter_objects": "Filter by objects",
|
||||
"card_filter_objects_help": "Empty = show all objects.",
|
||||
"card_filter_areas": "Filter by areas",
|
||||
"card_filter_areas_help": "Empty = show all areas.",
|
||||
"card_filter_entities": "Filter by entities (entity_ids)",
|
||||
"card_filter_entities_help": "Pick sensor / binary_sensor entities from this integration. Empty = all.",
|
||||
"card_loading_objects": "Loading objects…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Sidebar panel title",
|
||||
"settings_notifications": "Notifications",
|
||||
"settings_notify_service": "Notification service",
|
||||
"settings_install_assist_sentences": "Install Assist sentences",
|
||||
"settings_install_assist_sentences_hint": "Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",
|
||||
"test_notification": "Test notification",
|
||||
"send_test": "Send test",
|
||||
"testing": "Sending…",
|
||||
"test_notification_success": "Test notification sent",
|
||||
"test_notification_failed": "Test notification failed",
|
||||
"notify_per_person": "Per-person delivery",
|
||||
"notify_no_own_device": "No own device — uses the household service",
|
||||
"settings_notify_due_soon": "Notify when due soon",
|
||||
"settings_notify_overdue": "Notify when overdue",
|
||||
"settings_notify_triggered": "Notify when triggered",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "By user",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "User",
|
||||
"photo_label": "Photo",
|
||||
"sort_label": "Sort",
|
||||
"group_by_label": "Group by",
|
||||
"state_value_help": "Use the HA state value (usually lowercase, e.g. \"on\"/\"off\"). Case is normalised on save.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "Adjust stock",
|
||||
"restock_quantity_label": "Quantity bought",
|
||||
"consumes_parts_label": "Consumes parts",
|
||||
"shared_parts_other_objects": "Parts from other objects",
|
||||
"shared_parts_help": "Several objects can share one stock. Completing this task takes from the owning object.",
|
||||
"shared_part_unknown": "Unknown part",
|
||||
"parts_load_failed": "Couldn't load this object's parts — the consumes-parts options are unavailable right now.",
|
||||
"adopt_problem_button": "Adopt problem sensors",
|
||||
"adopt_problem_title": "Adopt problem sensors",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Valor del disparador",
|
||||
"complete_title": "Completada: ",
|
||||
"checklist": "Lista de verificación",
|
||||
"require_on_completion": "Exigir al completar",
|
||||
"checklist_steps_optional": "Pasos de la lista de verificación (opcional)",
|
||||
"checklist_placeholder": "Limpiar filtro\nReemplazar junta\nProbar presión",
|
||||
"checklist_help": "Un paso por línea. Máx. 100 elementos.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Vacío = mostrar todos los estados.",
|
||||
"card_filter_objects": "Filtrar por objetos",
|
||||
"card_filter_objects_help": "Vacío = mostrar todos los objetos.",
|
||||
"card_filter_areas": "Filtrar por áreas",
|
||||
"card_filter_areas_help": "Vacío = mostrar todas las áreas.",
|
||||
"card_filter_entities": "Filtrar por entidades (entity_ids)",
|
||||
"card_filter_entities_help": "Selecciona entidades sensor / binary_sensor de esta integración. Vacío = todas.",
|
||||
"card_loading_objects": "Cargando objetos…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Título del panel lateral",
|
||||
"settings_notifications": "Notificaciones",
|
||||
"settings_notify_service": "Servicio de notificación",
|
||||
"settings_install_assist_sentences": "Instalar frases de Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia las frases de voz en tu configuración para que el agente clásico de Assist las reconozca. Un archivo que hayas editado nunca se sobrescribe.",
|
||||
"test_notification": "Notificación de prueba",
|
||||
"send_test": "Enviar prueba",
|
||||
"testing": "Enviando…",
|
||||
"test_notification_success": "Notificación de prueba enviada",
|
||||
"test_notification_failed": "La notificación de prueba falló",
|
||||
"notify_per_person": "Entrega por persona",
|
||||
"notify_no_own_device": "Sin dispositivo propio — usa el servicio del hogar",
|
||||
"settings_notify_due_soon": "Notificar cuando esté próxima",
|
||||
"settings_notify_overdue": "Notificar cuando esté vencida",
|
||||
"settings_notify_triggered": "Notificar cuando se active",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Por usuario",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Usuario",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Ordenar",
|
||||
"group_by_label": "Agrupar por",
|
||||
"state_value_help": "Usa el valor de estado de HA (normalmente en minúsculas, p. ej. \"on\"/\"off\"). Las mayúsculas se normalizan al guardar.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Ajustar existencias",
|
||||
"restock_quantity_label": "Cantidad comprada",
|
||||
"consumes_parts_label": "Consume piezas",
|
||||
"shared_parts_other_objects": "Piezas de otros objetos",
|
||||
"shared_parts_help": "Varios objetos pueden compartir un mismo stock. Al completar esta tarea se descuenta del objeto propietario.",
|
||||
"shared_part_unknown": "Pieza desconocida",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Laukaisuarvo",
|
||||
"complete_title": "Suorita: ",
|
||||
"checklist": "Tarkistuslista",
|
||||
"require_on_completion": "Vaadi valmistuessa",
|
||||
"checklist_steps_optional": "Tarkistuslistan vaiheet (valinnainen)",
|
||||
"checklist_placeholder": "Puhdista suodatin\nVaihda tiiviste\nTarkista paine",
|
||||
"checklist_help": "Yksi vaihe riviä kohden. Enintään 100 kohdetta.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Tyhjä = näytä kaikki tilat.",
|
||||
"card_filter_objects": "Suodata kohteiden mukaan",
|
||||
"card_filter_objects_help": "Tyhjä = näytä kaikki kohteet.",
|
||||
"card_filter_areas": "Suodata alueiden mukaan",
|
||||
"card_filter_areas_help": "Tyhjä = näytä kaikki alueet.",
|
||||
"card_filter_entities": "Suodata entiteettien mukaan (entity_id)",
|
||||
"card_filter_entities_help": "Valitse sensor- / binary_sensor-entiteetit tästä integraatiosta. Tyhjä = kaikki.",
|
||||
"card_loading_objects": "Ladataan kohteita…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Sivupalkin paneelin otsikko",
|
||||
"settings_notifications": "Ilmoitukset",
|
||||
"settings_notify_service": "Ilmoituspalvelu",
|
||||
"settings_install_assist_sentences": "Asenna Assist-lauseet",
|
||||
"settings_install_assist_sentences_hint": "Kopioi äänikomennot asetuksiisi, jotta perinteinen Assist-agentti tunnistaa ne. Itse muokkaamaasi tiedostoa ei koskaan korvata.",
|
||||
"test_notification": "Testi-ilmoitus",
|
||||
"send_test": "Lähetä testi",
|
||||
"testing": "Lähetetään…",
|
||||
"test_notification_success": "Testi-ilmoitus lähetetty",
|
||||
"test_notification_failed": "Testi-ilmoitus epäonnistui",
|
||||
"notify_per_person": "Toimitus henkilöittäin",
|
||||
"notify_no_own_device": "Ei omaa laitetta — käyttää kotitalouden palvelua",
|
||||
"settings_notify_due_soon": "Ilmoita, kun erääntyy pian",
|
||||
"settings_notify_overdue": "Ilmoita, kun myöhässä",
|
||||
"settings_notify_triggered": "Ilmoita, kun laukaistu",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "Käyttäjän mukaan",
|
||||
"filter_label": "Suodata",
|
||||
"user_label": "Käyttäjä",
|
||||
"photo_label": "Valokuva",
|
||||
"sort_label": "Lajittele",
|
||||
"group_by_label": "Ryhmittele",
|
||||
"state_value_help": "Käytä HA:n tila-arvoa (yleensä pienillä kirjaimilla, esim. \"on\"/\"off\"). Kirjainkoko normalisoidaan tallennettaessa.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Muuta varastoa",
|
||||
"restock_quantity_label": "Ostettu määrä",
|
||||
"consumes_parts_label": "Kuluttaa osia",
|
||||
"shared_parts_other_objects": "Muiden kohteiden osat",
|
||||
"shared_parts_help": "Useat kohteet voivat jakaa saman varaston. Tämän tehtävän suorittaminen vähentää omistavan kohteen varastoa.",
|
||||
"shared_part_unknown": "Tuntematon osa",
|
||||
"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)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Valeur du déclencheur",
|
||||
"complete_title": "Terminé : ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Exiger à la clôture",
|
||||
"checklist_steps_optional": "Étapes de la checklist (optionnel)",
|
||||
"checklist_placeholder": "Nettoyer le filtre\nRemplacer le joint\nTester la pression",
|
||||
"checklist_help": "Une étape par ligne. Max 100 éléments.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Vide = afficher tous les statuts.",
|
||||
"card_filter_objects": "Filtrer par objets",
|
||||
"card_filter_objects_help": "Vide = afficher tous les objets.",
|
||||
"card_filter_areas": "Filtrer par zones",
|
||||
"card_filter_areas_help": "Vide = afficher toutes les zones.",
|
||||
"card_filter_entities": "Filtrer par entités (entity_ids)",
|
||||
"card_filter_entities_help": "Choisissez des entités sensor / binary_sensor de cette intégration. Vide = toutes.",
|
||||
"card_loading_objects": "Chargement des objets…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Titre du panneau latéral",
|
||||
"settings_notifications": "Notifications",
|
||||
"settings_notify_service": "Service de notification",
|
||||
"settings_install_assist_sentences": "Installer les phrases Assist",
|
||||
"settings_install_assist_sentences_hint": "Copie les phrases vocales dans votre configuration afin que l'agent Assist classique les reconnaisse. Un fichier que vous avez modifié n'est jamais écrasé.",
|
||||
"test_notification": "Notification de test",
|
||||
"send_test": "Envoyer le test",
|
||||
"testing": "Envoi en cours…",
|
||||
"test_notification_success": "Notification de test envoyée",
|
||||
"test_notification_failed": "Échec de la notification de test",
|
||||
"notify_per_person": "Distribution par personne",
|
||||
"notify_no_own_device": "Aucun appareil propre — utilise le service du foyer",
|
||||
"settings_notify_due_soon": "Notifier quand bientôt dû",
|
||||
"settings_notify_overdue": "Notifier quand en retard",
|
||||
"settings_notify_triggered": "Notifier quand déclenché",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Par utilisateur",
|
||||
"filter_label": "Filtre",
|
||||
"user_label": "Utilisateur",
|
||||
"photo_label": "Photo",
|
||||
"sort_label": "Tri",
|
||||
"group_by_label": "Grouper par",
|
||||
"state_value_help": "Utilisez la valeur d'état HA (généralement en minuscules, p. ex. \"on\"/\"off\"). La casse est normalisée à l'enregistrement.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Ajuster le stock",
|
||||
"restock_quantity_label": "Quantité achetée",
|
||||
"consumes_parts_label": "Consomme des pièces",
|
||||
"shared_parts_other_objects": "Pièces d'autres objets",
|
||||
"shared_parts_help": "Plusieurs objets peuvent partager un même stock. Terminer cette tâche décompte du stock de l'objet propriétaire.",
|
||||
"shared_part_unknown": "Pièce inconnue",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "ट्रिगर मान",
|
||||
"complete_title": "पूर्ण करें: ",
|
||||
"checklist": "चेकलिस्ट",
|
||||
"require_on_completion": "पूर्ण करते समय आवश्यक",
|
||||
"checklist_steps_optional": "चेकलिस्ट चरण (वैकल्पिक)",
|
||||
"checklist_placeholder": "फ़िल्टर साफ़ करें\nसील बदलें\nदबाव जाँचें",
|
||||
"checklist_help": "प्रति पंक्ति एक चरण। अधिकतम 100 आइटम।",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "खाली = सभी स्थितियाँ दिखाएँ।",
|
||||
"card_filter_objects": "वस्तुओं के अनुसार फ़िल्टर करें",
|
||||
"card_filter_objects_help": "खाली = सभी वस्तुएँ दिखाएँ।",
|
||||
"card_filter_areas": "क्षेत्रों के अनुसार फ़िल्टर करें",
|
||||
"card_filter_areas_help": "खाली = सभी क्षेत्र दिखाएँ।",
|
||||
"card_filter_entities": "एंटिटी के अनुसार फ़िल्टर करें (entity_ids)",
|
||||
"card_filter_entities_help": "इस इंटीग्रेशन से sensor / binary_sensor एंटिटी चुनें। खाली = सभी।",
|
||||
"card_loading_objects": "वस्तुएँ लोड हो रही हैं…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "साइडबार पैनल शीर्षक",
|
||||
"settings_notifications": "सूचनाएँ",
|
||||
"settings_notify_service": "सूचना सेवा",
|
||||
"settings_install_assist_sentences": "Assist वाक्य इंस्टॉल करें",
|
||||
"settings_install_assist_sentences_hint": "वॉइस वाक्यों को आपके कॉन्फ़िगरेशन में कॉपी करता है ताकि क्लासिक Assist एजेंट उन्हें पहचान सके। आपके द्वारा संपादित फ़ाइल कभी अधिलेखित नहीं होती।",
|
||||
"test_notification": "परीक्षण सूचना",
|
||||
"send_test": "परीक्षण भेजें",
|
||||
"testing": "भेजा जा रहा है…",
|
||||
"test_notification_success": "परीक्षण सूचना भेजी गई",
|
||||
"test_notification_failed": "परीक्षण सूचना विफल",
|
||||
"notify_per_person": "प्रति व्यक्ति वितरण",
|
||||
"notify_no_own_device": "कोई निजी डिवाइस नहीं — घरेलू सेवा का उपयोग करता है",
|
||||
"settings_notify_due_soon": "जल्द देय होने पर सूचित करें",
|
||||
"settings_notify_overdue": "अतिदेय होने पर सूचित करें",
|
||||
"settings_notify_triggered": "ट्रिगर होने पर सूचित करें",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "उपयोगकर्ता के अनुसार",
|
||||
"filter_label": "फ़िल्टर",
|
||||
"user_label": "उपयोगकर्ता",
|
||||
"photo_label": "फ़ोटो",
|
||||
"sort_label": "क्रमबद्ध करें",
|
||||
"group_by_label": "इसके अनुसार समूहित करें",
|
||||
"state_value_help": "HA स्थिति मान का उपयोग करें (आमतौर पर लोअरकेस, उदा. \"on\"/\"off\")। सहेजने पर केस सामान्यीकृत हो जाता है।",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "स्टॉक समायोजित करें",
|
||||
"restock_quantity_label": "खरीदी गई मात्रा",
|
||||
"consumes_parts_label": "पुर्ज़े खपत",
|
||||
"shared_parts_other_objects": "अन्य वस्तुओं के पुर्ज़े",
|
||||
"shared_parts_help": "कई वस्तुएँ एक ही स्टॉक साझा कर सकती हैं। यह कार्य पूरा करने पर स्वामी वस्तु के स्टॉक से घटाया जाता है।",
|
||||
"shared_part_unknown": "अज्ञात पुर्ज़ा",
|
||||
"parts_load_failed": "इस ऑब्जेक्ट के पुर्ज़े लोड नहीं हो सके — पुर्ज़ा-खपत विकल्प अभी उपलब्ध नहीं हैं।",
|
||||
"settings_export_selection": "चयनित ऑब्जेक्ट तक सीमित करें (वैकल्पिक)",
|
||||
"settings_docs_archive": "दस्तावेज़ संग्रह (फ़ाइलों सहित)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Kiváltó érték",
|
||||
"complete_title": "Elvégzés: ",
|
||||
"checklist": "Ellenőrzőlista",
|
||||
"require_on_completion": "Befejezéskor kötelező",
|
||||
"checklist_steps_optional": "Ellenőrzőlista lépései (opcionális)",
|
||||
"checklist_placeholder": "Szűrő tisztítása\nTömítés cseréje\nNyomáspróba",
|
||||
"checklist_help": "Soronként egy lépés. Legfeljebb 100 elem.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Üres = minden állapot megjelenik.",
|
||||
"card_filter_objects": "Szűrés objektumok szerint",
|
||||
"card_filter_objects_help": "Üres = minden objektum megjelenik.",
|
||||
"card_filter_areas": "Szűrés területek szerint",
|
||||
"card_filter_areas_help": "Üres = minden terület megjelenik.",
|
||||
"card_filter_entities": "Szűrés entitások szerint (entity_id-k)",
|
||||
"card_filter_entities_help": "Válasszon sensor / binary_sensor entitásokat ebből az integrációból. Üres = mind.",
|
||||
"card_loading_objects": "Objektumok betöltése…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Oldalsáv-panel címe",
|
||||
"settings_notifications": "Értesítések",
|
||||
"settings_notify_service": "Értesítési szolgáltatás",
|
||||
"settings_install_assist_sentences": "Assist mondatok telepítése",
|
||||
"settings_install_assist_sentences_hint": "Átmásolja a hangmondatokat a konfigurációdba, hogy a klasszikus Assist ügynök felismerje őket. Az általad szerkesztett fájlt soha nem írja felül.",
|
||||
"test_notification": "Tesztértesítés",
|
||||
"send_test": "Teszt küldése",
|
||||
"testing": "Küldés…",
|
||||
"test_notification_success": "Tesztértesítés elküldve",
|
||||
"test_notification_failed": "A tesztértesítés nem sikerült",
|
||||
"notify_per_person": "Kézbesítés személyenként",
|
||||
"notify_no_own_device": "Nincs saját eszköz — a háztartási szolgáltatást használja",
|
||||
"settings_notify_due_soon": "Értesítés, ha hamarosan esedékes",
|
||||
"settings_notify_overdue": "Értesítés lejáratkor",
|
||||
"settings_notify_triggered": "Értesítés kiváltáskor",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "Felhasználó szerint",
|
||||
"filter_label": "Szűrő",
|
||||
"user_label": "Felhasználó",
|
||||
"photo_label": "Fénykép",
|
||||
"sort_label": "Rendezés",
|
||||
"group_by_label": "Csoportosítás",
|
||||
"state_value_help": "A HA állapotértékét használja (általában kisbetűs, pl. „on”/„off”). A kis- és nagybetűk mentéskor normalizálódnak.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "Készlet módosítása",
|
||||
"restock_quantity_label": "Vásárolt mennyiség",
|
||||
"consumes_parts_label": "Felhasznált alkatrészek",
|
||||
"shared_parts_other_objects": "Más objektumok alkatrészei",
|
||||
"shared_parts_help": "Több objektum is használhatja ugyanazt a készletet. A feladat elvégzése a tulajdonos objektum készletéből von le.",
|
||||
"shared_part_unknown": "Ismeretlen alkatrész",
|
||||
"parts_load_failed": "Az objektum alkatrészei nem tölthetők be — a felhasznált alkatrészek beállításai most nem érhetők el.",
|
||||
"adopt_problem_button": "Problémaérzékelők átvétele",
|
||||
"adopt_problem_title": "Problémaérzékelők átvétele",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Valore trigger",
|
||||
"complete_title": "Completato: ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Richiedi al completamento",
|
||||
"checklist_steps_optional": "Passaggi della checklist (opzionale)",
|
||||
"checklist_placeholder": "Pulire il filtro\nSostituire la guarnizione\nTestare la pressione",
|
||||
"checklist_help": "Un passaggio per riga. Max 100 elementi.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Vuoto = mostra tutti gli stati.",
|
||||
"card_filter_objects": "Filtra per oggetti",
|
||||
"card_filter_objects_help": "Vuoto = mostra tutti gli oggetti.",
|
||||
"card_filter_areas": "Filtra per aree",
|
||||
"card_filter_areas_help": "Vuoto = mostra tutte le aree.",
|
||||
"card_filter_entities": "Filtra per entità (entity_ids)",
|
||||
"card_filter_entities_help": "Seleziona entità sensor / binary_sensor da questa integrazione. Vuoto = tutte.",
|
||||
"card_loading_objects": "Caricamento oggetti…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Titolo pannello laterale",
|
||||
"settings_notifications": "Notifiche",
|
||||
"settings_notify_service": "Servizio di notifica",
|
||||
"settings_install_assist_sentences": "Installa le frasi di Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia le frasi vocali nella tua configurazione affinché l'agente Assist classico le riconosca. Un file modificato da te non viene mai sovrascritto.",
|
||||
"test_notification": "Notifica di test",
|
||||
"send_test": "Invia test",
|
||||
"testing": "Invio in corso…",
|
||||
"test_notification_success": "Notifica di test inviata",
|
||||
"test_notification_failed": "Notifica di test non riuscita",
|
||||
"notify_per_person": "Consegna per persona",
|
||||
"notify_no_own_device": "Nessun dispositivo proprio — usa il servizio della casa",
|
||||
"settings_notify_due_soon": "Notifica quando in scadenza",
|
||||
"settings_notify_overdue": "Notifica quando scaduto",
|
||||
"settings_notify_triggered": "Notifica quando attivato",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Per utente",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Utente",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Ordinamento",
|
||||
"group_by_label": "Raggruppa per",
|
||||
"state_value_help": "Usa il valore di stato HA (di solito minuscolo, es. \"on\"/\"off\"). Il case viene normalizzato al salvataggio.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Correggi scorta",
|
||||
"restock_quantity_label": "Quantità acquistata",
|
||||
"consumes_parts_label": "Consuma ricambi",
|
||||
"shared_parts_other_objects": "Ricambi di altri oggetti",
|
||||
"shared_parts_help": "Più oggetti possono condividere una sola scorta. Completando questa attività si preleva dall'oggetto proprietario.",
|
||||
"shared_part_unknown": "Ricambio sconosciuto",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "トリガー値",
|
||||
"complete_title": "完了: ",
|
||||
"checklist": "チェックリスト",
|
||||
"require_on_completion": "完了時に必須",
|
||||
"checklist_steps_optional": "チェックリストの手順(任意)",
|
||||
"checklist_placeholder": "フィルター清掃\nシール交換\n圧力テスト",
|
||||
"checklist_help": "1行に1手順。最大100項目。",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "空欄=すべての状態を表示。",
|
||||
"card_filter_objects": "対象で絞り込み",
|
||||
"card_filter_objects_help": "空欄=すべての対象を表示。",
|
||||
"card_filter_areas": "エリアで絞り込み",
|
||||
"card_filter_areas_help": "空欄=すべてのエリアを表示。",
|
||||
"card_filter_entities": "エンティティで絞り込み(entity_ids)",
|
||||
"card_filter_entities_help": "この統合のsensor/binary_sensorエンティティを選択。空欄=すべて。",
|
||||
"card_loading_objects": "対象を読み込み中…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "サイドバーパネルのタイトル",
|
||||
"settings_notifications": "通知",
|
||||
"settings_notify_service": "通知サービス",
|
||||
"settings_install_assist_sentences": "Assist の文を導入する",
|
||||
"settings_install_assist_sentences_hint": "音声フレーズを設定に複製し、従来の Assist エージェントが認識できるようにします。自分で編集したファイルが上書きされることはありません。",
|
||||
"test_notification": "テスト通知",
|
||||
"send_test": "テスト送信",
|
||||
"testing": "送信中…",
|
||||
"test_notification_success": "テスト通知を送信しました",
|
||||
"test_notification_failed": "テスト通知に失敗しました",
|
||||
"notify_per_person": "メンバーごとの配信",
|
||||
"notify_no_own_device": "個人の端末なし — 家庭用サービスを使用",
|
||||
"settings_notify_due_soon": "期限間近で通知",
|
||||
"settings_notify_overdue": "期限超過で通知",
|
||||
"settings_notify_triggered": "トリガー発生で通知",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "ユーザー別",
|
||||
"filter_label": "絞り込み",
|
||||
"user_label": "ユーザー",
|
||||
"photo_label": "写真",
|
||||
"sort_label": "並べ替え",
|
||||
"group_by_label": "グループ化",
|
||||
"state_value_help": "HAの状態値を使用してください(通常は小文字、例: \"on\"/\"off\")。保存時に大文字小文字は正規化されます。",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "在庫を調整",
|
||||
"restock_quantity_label": "購入数量",
|
||||
"consumes_parts_label": "部品を消費",
|
||||
"shared_parts_other_objects": "他の対象の部品",
|
||||
"shared_parts_help": "複数の対象で同じ在庫を共有できます。このタスクを完了すると、所有する対象の在庫から差し引かれます。",
|
||||
"shared_part_unknown": "不明な部品",
|
||||
"parts_load_failed": "このオブジェクトの部品を読み込めませんでした — 部品消費オプションは現在利用できません。",
|
||||
"settings_export_selection": "選択したオブジェクトに限定(任意)",
|
||||
"settings_docs_archive": "ドキュメントアーカイブ(ファイル付き)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "트리거 값",
|
||||
"complete_title": "완료: ",
|
||||
"checklist": "체크리스트",
|
||||
"require_on_completion": "완료 시 필수 입력",
|
||||
"checklist_steps_optional": "체크리스트 단계 (선택)",
|
||||
"checklist_placeholder": "필터 청소\n씰 교체\n압력 테스트",
|
||||
"checklist_help": "한 줄에 한 단계씩 입력하세요. 최대 100개.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "비워두면 모든 상태를 표시합니다.",
|
||||
"card_filter_objects": "객체로 필터",
|
||||
"card_filter_objects_help": "비워두면 모든 객체를 표시합니다.",
|
||||
"card_filter_areas": "구역으로 필터",
|
||||
"card_filter_areas_help": "비워두면 모든 구역을 표시합니다.",
|
||||
"card_filter_entities": "엔티티로 필터 (entity_ids)",
|
||||
"card_filter_entities_help": "이 통합구성요소의 sensor / binary_sensor 엔티티를 선택하세요. 비워두면 전체.",
|
||||
"card_loading_objects": "객체 불러오는 중…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "사이드바 패널 제목",
|
||||
"settings_notifications": "알림",
|
||||
"settings_notify_service": "알림 서비스",
|
||||
"settings_install_assist_sentences": "Assist 문장 설치",
|
||||
"settings_install_assist_sentences_hint": "음성 문장을 설정에 복사하여 기존 Assist 에이전트가 인식하도록 합니다. 직접 수정한 파일은 절대 덮어쓰지 않습니다.",
|
||||
"test_notification": "테스트 알림",
|
||||
"send_test": "테스트 보내기",
|
||||
"testing": "보내는 중…",
|
||||
"test_notification_success": "테스트 알림을 보냈습니다",
|
||||
"test_notification_failed": "테스트 알림 전송 실패",
|
||||
"notify_per_person": "사용자별 전송",
|
||||
"notify_no_own_device": "개인 기기 없음 — 가정 서비스 사용",
|
||||
"settings_notify_due_soon": "기한 임박 시 알림",
|
||||
"settings_notify_overdue": "기한 초과 시 알림",
|
||||
"settings_notify_triggered": "트리거 시 알림",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "사용자별",
|
||||
"filter_label": "필터",
|
||||
"user_label": "사용자",
|
||||
"photo_label": "사진",
|
||||
"sort_label": "정렬",
|
||||
"group_by_label": "그룹화",
|
||||
"state_value_help": "HA 상태 값을 사용하세요(보통 소문자, 예: \"on\"/\"off\"). 대소문자는 저장 시 정규화됩니다.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "재고 조정",
|
||||
"restock_quantity_label": "구매 수량",
|
||||
"consumes_parts_label": "사용하는 부품",
|
||||
"shared_parts_other_objects": "다른 객체의 부품",
|
||||
"shared_parts_help": "여러 객체가 하나의 재고를 공유할 수 있습니다. 이 작업을 완료하면 소유 객체의 재고에서 차감됩니다.",
|
||||
"shared_part_unknown": "알 수 없는 부품",
|
||||
"parts_load_failed": "이 객체의 부품을 불러올 수 없습니다 — 부품 사용 옵션을 지금은 쓸 수 없습니다.",
|
||||
"adopt_problem_button": "문제 센서 가져오기",
|
||||
"adopt_problem_title": "문제 센서 가져오기",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Utløserverdi",
|
||||
"complete_title": "Fullfør: ",
|
||||
"checklist": "Sjekkliste",
|
||||
"require_on_completion": "Krev ved fullføring",
|
||||
"checklist_steps_optional": "Sjekklistetrinn (valgfritt)",
|
||||
"checklist_placeholder": "Rengjør filter\nBytt pakning\nTest trykk",
|
||||
"checklist_help": "Ett trinn per linje. Maks 100 elementer.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Tom = vis alle statuser.",
|
||||
"card_filter_objects": "Filtrer etter objekter",
|
||||
"card_filter_objects_help": "Tom = vis alle objekter.",
|
||||
"card_filter_areas": "Filtrer etter områder",
|
||||
"card_filter_areas_help": "Tom = vis alle områder.",
|
||||
"card_filter_entities": "Filtrer etter entiteter (entity_ids)",
|
||||
"card_filter_entities_help": "Velg sensor-/binary_sensor-entiteter fra denne integrasjonen. Tom = alle.",
|
||||
"card_loading_objects": "Laster objekter…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Tittel på sidepanel",
|
||||
"settings_notifications": "Varsler",
|
||||
"settings_notify_service": "Varslingstjeneste",
|
||||
"settings_install_assist_sentences": "Installer Assist-setninger",
|
||||
"settings_install_assist_sentences_hint": "Kopierer talesetningene til konfigurasjonen din slik at den klassiske Assist-agenten gjenkjenner dem. En fil du selv har endret, blir aldri overskrevet.",
|
||||
"test_notification": "Testvarsel",
|
||||
"send_test": "Send test",
|
||||
"testing": "Sender…",
|
||||
"test_notification_success": "Testvarsel sendt",
|
||||
"test_notification_failed": "Testvarsel mislyktes",
|
||||
"notify_per_person": "Levering per person",
|
||||
"notify_no_own_device": "Ingen egen enhet — bruker husstandens tjeneste",
|
||||
"settings_notify_due_soon": "Varsle når noe forfaller snart",
|
||||
"settings_notify_overdue": "Varsle når noe er forfalt",
|
||||
"settings_notify_triggered": "Varsle når noe utløses",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "Etter bruker",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Bruker",
|
||||
"photo_label": "Bilde",
|
||||
"sort_label": "Sorter",
|
||||
"group_by_label": "Grupper etter",
|
||||
"state_value_help": "Bruk HA-tilstandsverdien (vanligvis små bokstaver, f.eks. \"on\"/\"off\"). Store/små bokstaver normaliseres ved lagring.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Juster lager",
|
||||
"restock_quantity_label": "Kjøpt mengde",
|
||||
"consumes_parts_label": "Forbruker deler",
|
||||
"shared_parts_other_objects": "Deler fra andre objekter",
|
||||
"shared_parts_help": "Flere objekter kan dele samme lager. Når oppgaven fullføres, trekkes det fra det eiende objektet.",
|
||||
"shared_part_unknown": "Ukjent del",
|
||||
"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)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Triggerwaarde",
|
||||
"complete_title": "Voltooid: ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Vereisen bij afronden",
|
||||
"checklist_steps_optional": "Checklist-stappen (optioneel)",
|
||||
"checklist_placeholder": "Filter schoonmaken\nPakking vervangen\nDruk testen",
|
||||
"checklist_help": "Eén stap per regel. Max. 100 items.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Leeg = alle statussen tonen.",
|
||||
"card_filter_objects": "Filteren op objecten",
|
||||
"card_filter_objects_help": "Leeg = alle objecten tonen.",
|
||||
"card_filter_areas": "Filteren op gebieden",
|
||||
"card_filter_areas_help": "Leeg = alle gebieden tonen.",
|
||||
"card_filter_entities": "Filteren op entiteiten (entity_ids)",
|
||||
"card_filter_entities_help": "Kies sensor/binary_sensor entiteiten van deze integratie. Leeg = alle.",
|
||||
"card_loading_objects": "Objecten laden…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Titel zijbalkpaneel",
|
||||
"settings_notifications": "Meldingen",
|
||||
"settings_notify_service": "Meldingsservice",
|
||||
"settings_install_assist_sentences": "Assist-zinnen installeren",
|
||||
"settings_install_assist_sentences_hint": "Kopieert de spraakzinnen naar je configuratie zodat de klassieke Assist-agent ze herkent. Een bestand dat je zelf hebt bewerkt wordt nooit overschreven.",
|
||||
"test_notification": "Testmelding",
|
||||
"send_test": "Test versturen",
|
||||
"testing": "Verzenden…",
|
||||
"test_notification_success": "Testmelding verzonden",
|
||||
"test_notification_failed": "Testmelding mislukt",
|
||||
"notify_per_person": "Bezorging per persoon",
|
||||
"notify_no_own_device": "Geen eigen apparaat — gebruikt de huishoudelijke dienst",
|
||||
"settings_notify_due_soon": "Melding bij bijna verlopen",
|
||||
"settings_notify_overdue": "Melding bij achterstallig",
|
||||
"settings_notify_triggered": "Melding bij geactiveerd",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Per gebruiker",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Gebruiker",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Sorteren",
|
||||
"group_by_label": "Groeperen op",
|
||||
"state_value_help": "Gebruik de HA-statuswaarde (meestal in kleine letters, bv. \"on\"/\"off\"). Hoofdletters worden bij opslaan genormaliseerd.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Voorraad aanpassen",
|
||||
"restock_quantity_label": "Gekochte hoeveelheid",
|
||||
"consumes_parts_label": "Verbruikt onderdelen",
|
||||
"shared_parts_other_objects": "Onderdelen van andere objecten",
|
||||
"shared_parts_help": "Meerdere objecten kunnen één voorraad delen. Bij afronden wordt van het eigenaarsobject afgeboekt.",
|
||||
"shared_part_unknown": "Onbekend onderdeel",
|
||||
"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)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Wartość wyzwalacza",
|
||||
"complete_title": "Wykonaj: ",
|
||||
"checklist": "Lista kontrolna",
|
||||
"require_on_completion": "Wymagaj przy zakończeniu",
|
||||
"checklist_steps_optional": "Kroki listy kontrolnej (opcjonalne)",
|
||||
"checklist_placeholder": "Wyczyść filtr\nWymień uszczelkę\nSprawdź ciśnienie",
|
||||
"checklist_help": "Jeden krok na linię. Maks. 100 elementów.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Puste = pokaż wszystkie statusy.",
|
||||
"card_filter_objects": "Filtruj wg obiektów",
|
||||
"card_filter_objects_help": "Puste = pokaż wszystkie obiekty.",
|
||||
"card_filter_areas": "Filtruj wg obszarów",
|
||||
"card_filter_areas_help": "Puste = pokaż wszystkie obszary.",
|
||||
"card_filter_entities": "Filtruj wg encji (entity_ids)",
|
||||
"card_filter_entities_help": "Wybierz encje sensor / binary_sensor z tej integracji. Puste = wszystkie.",
|
||||
"card_loading_objects": "Ładowanie obiektów…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Tytuł panelu bocznego",
|
||||
"settings_notifications": "Powiadomienia",
|
||||
"settings_notify_service": "Usługa powiadomień",
|
||||
"settings_install_assist_sentences": "Zainstaluj zdania Assist",
|
||||
"settings_install_assist_sentences_hint": "Kopiuje zdania głosowe do Twojej konfiguracji, aby klasyczny agent Assist je rozpoznawał. Plik zmieniony przez Ciebie nigdy nie zostanie nadpisany.",
|
||||
"test_notification": "Powiadomienie testowe",
|
||||
"send_test": "Wyślij test",
|
||||
"testing": "Wysyłanie…",
|
||||
"test_notification_success": "Powiadomienie testowe wysłane",
|
||||
"test_notification_failed": "Powiadomienie testowe nie powiodło się",
|
||||
"notify_per_person": "Dostarczanie dla każdej osoby",
|
||||
"notify_no_own_device": "Brak własnego urządzenia — używa usługi domowej",
|
||||
"settings_notify_due_soon": "Powiadom gdy wkrótce",
|
||||
"settings_notify_overdue": "Powiadom gdy zaległe",
|
||||
"settings_notify_triggered": "Powiadom gdy wyzwolone",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Wg użytkownika",
|
||||
"filter_label": "Filtr",
|
||||
"user_label": "Użytkownik",
|
||||
"photo_label": "Zdjęcie",
|
||||
"sort_label": "Sortowanie",
|
||||
"group_by_label": "Grupuj wg",
|
||||
"state_value_help": "Użyj wartości stanu HA (zwykle małymi literami, np. \"on\"/\"off\"). Wielkość liter jest normalizowana przy zapisie.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Koryguj stan",
|
||||
"restock_quantity_label": "Kupiona ilość",
|
||||
"consumes_parts_label": "Zużywa części",
|
||||
"shared_parts_other_objects": "Części z innych obiektów",
|
||||
"shared_parts_help": "Kilka obiektów może korzystać z jednego zapasu. Wykonanie tego zadania odejmuje ze stanu obiektu będącego właścicielem.",
|
||||
"shared_part_unknown": "Nieznana część",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Valor do gatilho",
|
||||
"complete_title": "Concluir: ",
|
||||
"checklist": "Checklist",
|
||||
"require_on_completion": "Exigir ao concluir",
|
||||
"checklist_steps_optional": "Etapas do checklist (opcional)",
|
||||
"checklist_placeholder": "Limpar o filtro\nTrocar a vedação\nTestar a pressão",
|
||||
"checklist_help": "Uma etapa por linha. Máx. 100 itens.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Vazio = mostrar todos os status.",
|
||||
"card_filter_objects": "Filtrar por objetos",
|
||||
"card_filter_objects_help": "Vazio = mostrar todos os objetos.",
|
||||
"card_filter_areas": "Filtrar por áreas",
|
||||
"card_filter_areas_help": "Vazio = mostrar todas as áreas.",
|
||||
"card_filter_entities": "Filtrar por entidades (entity_ids)",
|
||||
"card_filter_entities_help": "Escolha entidades sensor / binary_sensor desta integração. Vazio = todas.",
|
||||
"card_loading_objects": "Carregando objetos…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Título do painel na barra lateral",
|
||||
"settings_notifications": "Notificações",
|
||||
"settings_notify_service": "Serviço de notificação",
|
||||
"settings_install_assist_sentences": "Instalar frases do Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia as frases de voz para a sua configuração para que o agente Assist clássico as reconheça. Um arquivo que você editou nunca é sobrescrito.",
|
||||
"test_notification": "Notificação de teste",
|
||||
"send_test": "Enviar teste",
|
||||
"testing": "Enviando…",
|
||||
"test_notification_success": "Notificação de teste enviada",
|
||||
"test_notification_failed": "Falha na notificação de teste",
|
||||
"notify_per_person": "Entrega por pessoa",
|
||||
"notify_no_own_device": "Sem dispositivo próprio — usa o serviço da casa",
|
||||
"settings_notify_due_soon": "Notificar quando vencer em breve",
|
||||
"settings_notify_overdue": "Notificar quando atrasada",
|
||||
"settings_notify_triggered": "Notificar quando acionada",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "Por usuário",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Usuário",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Ordenar",
|
||||
"group_by_label": "Agrupar por",
|
||||
"state_value_help": "Use o valor de estado do HA (geralmente minúsculo, ex.: \"on\"/\"off\"). Maiúsculas e minúsculas são normalizadas ao salvar.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "Ajustar estoque",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
"consumes_parts_label": "Consome peças",
|
||||
"shared_parts_other_objects": "Peças de outros objetos",
|
||||
"shared_parts_help": "Vários objetos podem compartilhar o mesmo estoque. Concluir esta tarefa desconta do objeto proprietário.",
|
||||
"shared_part_unknown": "Peça desconhecida",
|
||||
"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 no momento.",
|
||||
"adopt_problem_button": "Adotar sensores de problema",
|
||||
"adopt_problem_title": "Adotar sensores de problema",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Valor do acionador",
|
||||
"complete_title": "Concluída: ",
|
||||
"checklist": "Lista de verificação",
|
||||
"require_on_completion": "Exigir ao concluir",
|
||||
"checklist_steps_optional": "Passos da lista de verificação (opcional)",
|
||||
"checklist_placeholder": "Limpar filtro\nSubstituir vedação\nTestar pressão",
|
||||
"checklist_help": "Um passo por linha. Máx. 100 itens.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Vazio = mostrar todos os estados.",
|
||||
"card_filter_objects": "Filtrar por objetos",
|
||||
"card_filter_objects_help": "Vazio = mostrar todos os objetos.",
|
||||
"card_filter_areas": "Filtrar por áreas",
|
||||
"card_filter_areas_help": "Vazio = mostrar todas as áreas.",
|
||||
"card_filter_entities": "Filtrar por entidades (entity_ids)",
|
||||
"card_filter_entities_help": "Selecione entidades sensor / binary_sensor desta integração. Vazio = todas.",
|
||||
"card_loading_objects": "A carregar objetos…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Título do painel lateral",
|
||||
"settings_notifications": "Notificações",
|
||||
"settings_notify_service": "Serviço de notificação",
|
||||
"settings_install_assist_sentences": "Instalar frases do Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia as frases de voz para a sua configuração para que o agente Assist clássico as reconheça. Um ficheiro que editou nunca é substituído.",
|
||||
"test_notification": "Notificação de teste",
|
||||
"send_test": "Enviar teste",
|
||||
"testing": "A enviar…",
|
||||
"test_notification_success": "Notificação de teste enviada",
|
||||
"test_notification_failed": "Falha na notificação de teste",
|
||||
"notify_per_person": "Entrega por pessoa",
|
||||
"notify_no_own_device": "Sem dispositivo próprio — usa o serviço da casa",
|
||||
"settings_notify_due_soon": "Notificar quando próxima",
|
||||
"settings_notify_overdue": "Notificar quando atrasada",
|
||||
"settings_notify_triggered": "Notificar quando acionada",
|
||||
@@ -483,6 +490,7 @@
|
||||
"sort_group": "Grupo",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Utilizador",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Ordenar",
|
||||
"group_by_label": "Agrupar por",
|
||||
"state_value_help": "Use o valor de estado HA (normalmente em minúsculas, p. ex. \"on\"/\"off\"). As maiúsculas são normalizadas ao guardar.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Ajustar estoque",
|
||||
"restock_quantity_label": "Quantidade comprada",
|
||||
"consumes_parts_label": "Consome peças",
|
||||
"shared_parts_other_objects": "Peças de outros objetos",
|
||||
"shared_parts_help": "Vários objetos podem partilhar o mesmo stock. Concluir esta tarefa desconta do objeto proprietário.",
|
||||
"shared_part_unknown": "Peça desconhecida",
|
||||
"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)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Значение триггера",
|
||||
"complete_title": "Выполнить: ",
|
||||
"checklist": "Контрольный список",
|
||||
"require_on_completion": "Требовать при завершении",
|
||||
"checklist_steps_optional": "Шаги контрольного списка (необязательно)",
|
||||
"checklist_placeholder": "Очистить фильтр\nЗаменить уплотнитель\nПроверить давление",
|
||||
"checklist_help": "Один шаг на строку. Макс. 100 элементов.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Пусто = показать все статусы.",
|
||||
"card_filter_objects": "Фильтровать по объектам",
|
||||
"card_filter_objects_help": "Пусто = показать все объекты.",
|
||||
"card_filter_areas": "Фильтровать по зонам",
|
||||
"card_filter_areas_help": "Пусто = показать все зоны.",
|
||||
"card_filter_entities": "Фильтровать по сущностям (entity_ids)",
|
||||
"card_filter_entities_help": "Выберите сущности sensor / binary_sensor из этой интеграции. Пусто = все.",
|
||||
"card_loading_objects": "Загрузка объектов…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Заголовок панели",
|
||||
"settings_notifications": "Уведомления",
|
||||
"settings_notify_service": "Сервис уведомлений",
|
||||
"settings_install_assist_sentences": "Установить фразы Assist",
|
||||
"settings_install_assist_sentences_hint": "Копирует голосовые фразы в вашу конфигурацию, чтобы классический агент Assist их распознавал. Файл, который вы изменили, никогда не перезаписывается.",
|
||||
"test_notification": "Тестовое уведомление",
|
||||
"send_test": "Отправить тест",
|
||||
"testing": "Отправка…",
|
||||
"test_notification_success": "Тестовое уведомление отправлено",
|
||||
"test_notification_failed": "Не удалось отправить тестовое уведомление",
|
||||
"notify_per_person": "Доставка по пользователям",
|
||||
"notify_no_own_device": "Нет своего устройства — используется общий сервис",
|
||||
"settings_notify_due_soon": "Уведомлять, когда срок скоро истекает",
|
||||
"settings_notify_overdue": "Уведомлять при просрочке",
|
||||
"settings_notify_triggered": "Уведомлять при срабатывании",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "По пользователю",
|
||||
"filter_label": "Фильтр",
|
||||
"user_label": "Пользователь",
|
||||
"photo_label": "Фото",
|
||||
"sort_label": "Сортировка",
|
||||
"group_by_label": "Группировать по",
|
||||
"state_value_help": "Используйте значение состояния HA (обычно в нижнем регистре, напр. \"on\"/\"off\"). Регистр нормализуется при сохранении.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Изменить запас",
|
||||
"restock_quantity_label": "Куплено, шт.",
|
||||
"consumes_parts_label": "Расходует детали",
|
||||
"shared_parts_other_objects": "Детали других объектов",
|
||||
"shared_parts_help": "Несколько объектов могут использовать один запас. Выполнение этой задачи списывает детали у объекта-владельца.",
|
||||
"shared_part_unknown": "Неизвестная деталь",
|
||||
"parts_load_failed": "Не удалось загрузить запчасти этого объекта — параметры расхода запчастей сейчас недоступны.",
|
||||
"settings_export_selection": "Ограничить выбранными объектами (необязательно)",
|
||||
"settings_docs_archive": "Архив документов (с файлами)",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Utlösarvärde",
|
||||
"complete_title": "Slutför: ",
|
||||
"checklist": "Checklista",
|
||||
"require_on_completion": "Kräv vid slutförande",
|
||||
"checklist_steps_optional": "Checkliststeg (valfritt)",
|
||||
"checklist_placeholder": "Rengör filter\nByt tätning\nTesta tryck",
|
||||
"checklist_help": "Ett steg per rad. Max 100 objekt.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Tomt = visa alla statusar.",
|
||||
"card_filter_objects": "Filtrera efter objekt",
|
||||
"card_filter_objects_help": "Tomt = visa alla objekt.",
|
||||
"card_filter_areas": "Filtrera efter områden",
|
||||
"card_filter_areas_help": "Tomt = visa alla områden.",
|
||||
"card_filter_entities": "Filtrera efter entiteter (entity_ids)",
|
||||
"card_filter_entities_help": "Välj sensor- / binary_sensor-entiteter från denna integration. Tomt = alla.",
|
||||
"card_loading_objects": "Laddar objekt…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Sidopanelens titel",
|
||||
"settings_notifications": "Notifikationer",
|
||||
"settings_notify_service": "Notifikationstjänst",
|
||||
"settings_install_assist_sentences": "Installera Assist-meningar",
|
||||
"settings_install_assist_sentences_hint": "Kopierar rösmeningarna till din konfiguration så att den klassiska Assist-agenten känner igen dem. En fil du själv har ändrat skrivs aldrig över.",
|
||||
"test_notification": "Testnotifikation",
|
||||
"send_test": "Skicka test",
|
||||
"testing": "Skickar…",
|
||||
"test_notification_success": "Testnotifikation skickad",
|
||||
"test_notification_failed": "Testnotifikation misslyckades",
|
||||
"notify_per_person": "Leverans per person",
|
||||
"notify_no_own_device": "Ingen egen enhet — använder hushållets tjänst",
|
||||
"settings_notify_due_soon": "Notifiera när snart förfallande",
|
||||
"settings_notify_overdue": "Notifiera när försenad",
|
||||
"settings_notify_triggered": "Notifiera när utlöst",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "Per användare",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Användare",
|
||||
"photo_label": "Foto",
|
||||
"sort_label": "Sortering",
|
||||
"group_by_label": "Gruppera efter",
|
||||
"state_value_help": "Använd HA-tillståndsvärdet (vanligtvis med små bokstäver, t.ex. \"on\"/\"off\"). Versaler normaliseras vid sparande.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Justera lager",
|
||||
"restock_quantity_label": "Köpt antal",
|
||||
"consumes_parts_label": "Förbrukar delar",
|
||||
"shared_parts_other_objects": "Delar från andra objekt",
|
||||
"shared_parts_help": "Flera objekt kan dela samma lager. När uppgiften slutförs dras det från det ägande objektet.",
|
||||
"shared_part_unknown": "Okänd del",
|
||||
"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)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "Tetikleme değeri",
|
||||
"complete_title": "Tamamla: ",
|
||||
"checklist": "Kontrol listesi",
|
||||
"require_on_completion": "Tamamlarken zorunlu tut",
|
||||
"checklist_steps_optional": "Kontrol listesi adımları (isteğe bağlı)",
|
||||
"checklist_placeholder": "Filtreyi temizle\nContayı değiştir\nBasıncı test et",
|
||||
"checklist_help": "Her satıra bir adım. En fazla 100 madde.",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "Boş = tüm durumları göster.",
|
||||
"card_filter_objects": "Nesnelere göre filtrele",
|
||||
"card_filter_objects_help": "Boş = tüm nesneleri göster.",
|
||||
"card_filter_areas": "Alanlara göre filtrele",
|
||||
"card_filter_areas_help": "Boş = tüm alanları göster.",
|
||||
"card_filter_entities": "Varlıklara göre filtrele (entity_id)",
|
||||
"card_filter_entities_help": "Bu entegrasyonun sensor / binary_sensor varlıklarını seçin. Boş = tümü.",
|
||||
"card_loading_objects": "Nesneler yükleniyor…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "Kenar çubuğu paneli başlığı",
|
||||
"settings_notifications": "Bildirimler",
|
||||
"settings_notify_service": "Bildirim servisi",
|
||||
"settings_install_assist_sentences": "Assist cümlelerini yükle",
|
||||
"settings_install_assist_sentences_hint": "Sesli komut cümlelerini yapılandırmanıza kopyalar, böylece klasik Assist aracısı bunları tanır. Kendi düzenlediğiniz bir dosyanın üzerine asla yazılmaz.",
|
||||
"test_notification": "Test bildirimi",
|
||||
"send_test": "Test gönder",
|
||||
"testing": "Gönderiliyor…",
|
||||
"test_notification_success": "Test bildirimi gönderildi",
|
||||
"test_notification_failed": "Test bildirimi başarısız oldu",
|
||||
"notify_per_person": "Kişi bazında iletim",
|
||||
"notify_no_own_device": "Kendi cihazı yok — ev servisini kullanır",
|
||||
"settings_notify_due_soon": "Vade yaklaştığında bildir",
|
||||
"settings_notify_overdue": "Geciktiğinde bildir",
|
||||
"settings_notify_triggered": "Tetiklendiğinde bildir",
|
||||
@@ -494,6 +501,7 @@
|
||||
"groupby_user": "Kullanıcıya göre",
|
||||
"filter_label": "Filtre",
|
||||
"user_label": "Kullanıcı",
|
||||
"photo_label": "Fotoğraf",
|
||||
"sort_label": "Sırala",
|
||||
"group_by_label": "Grupla",
|
||||
"state_value_help": "HA durum değerini kullanın (genellikle küçük harf, örn. \"on\"/\"off\"). Büyük/küçük harf kaydederken normalleştirilir.",
|
||||
@@ -728,6 +736,9 @@
|
||||
"part_restock": "Stoku ayarla",
|
||||
"restock_quantity_label": "Satın alınan miktar",
|
||||
"consumes_parts_label": "Parça tüketir",
|
||||
"shared_parts_other_objects": "Diğer nesnelerin parçaları",
|
||||
"shared_parts_help": "Birden fazla nesne aynı stoğu paylaşabilir. Bu görev tamamlandığında stok, sahibi olan nesneden düşülür.",
|
||||
"shared_part_unknown": "Bilinmeyen parça",
|
||||
"parts_load_failed": "Bu nesnenin parçaları yüklenemedi — parça tüketimi seçenekleri şu anda kullanılamıyor.",
|
||||
"adopt_problem_button": "Sorun sensörlerini devral",
|
||||
"adopt_problem_title": "Sorun sensörlerini devral",
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
"trigger_val": "Значення тригера",
|
||||
"complete_title": "Виконати: ",
|
||||
"checklist": "Чекліст",
|
||||
"require_on_completion": "Вимагати при завершенні",
|
||||
"checklist_steps_optional": "Кроки чекліста (необов'язково)",
|
||||
"checklist_placeholder": "Очистити фільтр\nЗамінити ущільнювач\nПеревірити тиск",
|
||||
"checklist_help": "Один крок на рядок. Макс. 100 елементів.",
|
||||
@@ -362,6 +363,8 @@
|
||||
"card_filter_status_help": "Порожньо = показати всі статуси.",
|
||||
"card_filter_objects": "Фільтрувати за об'єктами",
|
||||
"card_filter_objects_help": "Порожньо = показати всі об'єкти.",
|
||||
"card_filter_areas": "Фільтрувати за зонами",
|
||||
"card_filter_areas_help": "Порожньо = показати всі зони.",
|
||||
"card_filter_entities": "Фільтрувати за сутностями (entity_ids)",
|
||||
"card_filter_entities_help": "Виберіть сутності sensor / binary_sensor з цієї інтеграції. Порожньо = всі.",
|
||||
"card_loading_objects": "Завантаження об'єктів…",
|
||||
@@ -431,11 +434,15 @@
|
||||
"settings_panel_title": "Заголовок панелі",
|
||||
"settings_notifications": "Сповіщення",
|
||||
"settings_notify_service": "Служба сповіщень",
|
||||
"settings_install_assist_sentences": "Встановити фрази Assist",
|
||||
"settings_install_assist_sentences_hint": "Копіює голосові фрази до вашої конфігурації, щоб класичний агент Assist їх розпізнавав. Файл, який ви редагували, ніколи не перезаписується.",
|
||||
"test_notification": "Тестове сповіщення",
|
||||
"send_test": "Надіслати тест",
|
||||
"testing": "Надсилання…",
|
||||
"test_notification_success": "Тестове сповіщення надіслано",
|
||||
"test_notification_failed": "Не вдалося надіслати тестове сповіщення",
|
||||
"notify_per_person": "Доставка за особами",
|
||||
"notify_no_own_device": "Немає власного пристрою — використовується загальна служба",
|
||||
"settings_notify_due_soon": "Сповіщати, коли термін наближається",
|
||||
"settings_notify_overdue": "Сповіщати про прострочення",
|
||||
"settings_notify_triggered": "Сповіщати про спрацювання",
|
||||
@@ -487,6 +494,7 @@
|
||||
"groupby_user": "За користувачем",
|
||||
"filter_label": "Фільтр",
|
||||
"user_label": "Користувач",
|
||||
"photo_label": "Фото",
|
||||
"sort_label": "Сортування",
|
||||
"group_by_label": "Групувати за",
|
||||
"state_value_help": "Використовуйте значення стану HA (зазвичай у нижньому регістрі, напр. \"on\"/\"off\"). Регістр нормалізується при збереженні.",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "Змінити запас",
|
||||
"restock_quantity_label": "Куплена кількість",
|
||||
"consumes_parts_label": "Витрачає деталі",
|
||||
"shared_parts_other_objects": "Деталі інших об'єктів",
|
||||
"shared_parts_help": "Кілька об'єктів можуть використовувати один запас. Виконання цього завдання списує деталі з об'єкта-власника.",
|
||||
"shared_part_unknown": "Невідома деталь",
|
||||
"parts_load_failed": "Не вдалося завантажити запчастини цього об'єкта — параметри витрати запчастин зараз недоступні.",
|
||||
"settings_export_selection": "Обмежити вибраними об'єктами (необов'язково)",
|
||||
"settings_docs_archive": "Архів документів (з файлами)",
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"trigger_val": "触发值",
|
||||
"complete_title": "完成: ",
|
||||
"checklist": "检查清单",
|
||||
"require_on_completion": "完成时必填",
|
||||
"checklist_steps_optional": "检查步骤 (可选)",
|
||||
"checklist_placeholder": "清理过滤器\n更换密封圈\n测试压力",
|
||||
"checklist_help": "每行一个步骤。最多 100 项。",
|
||||
@@ -363,6 +364,8 @@
|
||||
"card_filter_status_help": "留空则显示所有状态。",
|
||||
"card_filter_objects": "按维护项过滤",
|
||||
"card_filter_objects_help": "留空则显示所有维护项。",
|
||||
"card_filter_areas": "按区域过滤",
|
||||
"card_filter_areas_help": "留空则显示所有区域。",
|
||||
"card_filter_entities": "按实体过滤 (entity_ids)",
|
||||
"card_filter_entities_help": "选择该集成的传感器或二进制传感器实体。留空则显示全部。",
|
||||
"card_loading_objects": "正在加载维护项…",
|
||||
@@ -432,11 +435,15 @@
|
||||
"settings_panel_title": "侧边栏面板标题",
|
||||
"settings_notifications": "通知",
|
||||
"settings_notify_service": "通知服务",
|
||||
"settings_install_assist_sentences": "安装 Assist 语句",
|
||||
"settings_install_assist_sentences_hint": "将语音语句复制到你的配置中,让传统 Assist 代理能够识别它们。你自己修改过的文件永远不会被覆盖。",
|
||||
"test_notification": "测试通知",
|
||||
"send_test": "发送测试",
|
||||
"testing": "正在发送…",
|
||||
"test_notification_success": "测试通知已发送",
|
||||
"test_notification_failed": "测试通知发送失败",
|
||||
"notify_per_person": "按成员分别投递",
|
||||
"notify_no_own_device": "无个人设备 — 使用家庭通知服务",
|
||||
"settings_notify_due_soon": "到期前通知提醒",
|
||||
"settings_notify_overdue": "超期后通知提醒",
|
||||
"settings_notify_triggered": "触发时通知提醒",
|
||||
@@ -488,6 +495,7 @@
|
||||
"groupby_user": "按负责人",
|
||||
"filter_label": "过滤器",
|
||||
"user_label": "用户",
|
||||
"photo_label": "照片",
|
||||
"sort_label": "排序",
|
||||
"group_by_label": "分组依据",
|
||||
"state_value_help": "使用 HA 状态值(通常为小写,例如 \"on\"/\"off\")。保存时大小写会自动规范化。",
|
||||
@@ -722,6 +730,9 @@
|
||||
"part_restock": "调整库存",
|
||||
"restock_quantity_label": "购买数量",
|
||||
"consumes_parts_label": "消耗配件",
|
||||
"shared_parts_other_objects": "其他设备的配件",
|
||||
"shared_parts_help": "多个设备可以共用同一批库存。完成此任务将从所属设备的库存中扣减。",
|
||||
"shared_part_unknown": "未知配件",
|
||||
"parts_load_failed": "无法加载此对象的配件 — 配件消耗选项暂时不可用。",
|
||||
"settings_export_selection": "仅限所选对象(可选)",
|
||||
"settings_docs_archive": "文档存档(含文件)",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.44.1 */
|
||||
var d="maintenance-supporter",C=`ll-strategy-dashboard-${d}`,L="hui-maintenance-supporter-strategy-editor",D="/maintenance_supporter_strategy/maintenance-dashboard-strategy.js",m=null;function k(){return m||(m=import(D)),m}async function N(){let r=await k();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await k(),document.createElement(L)}static async generate(c,h){return(await N()).generate(c,h)}};function M(){try{customElements.define(C,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===d&&r.strategyType==="dashboard")||w.customStrategies.push({type:d,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,h=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${d}`;function E(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let l of Array.from(i))t.push(l)}return null}function S(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function R(){try{let t=E("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=S(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function _(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let l=e.pop();if(i++,!l)continue;let u=l;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&h.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let b=l.children;if(b)for(let s of Array.from(b))e.push(s)}return n?!0:o?!1:a&&t<3&&R()}let A="/maintenance_supporter_strategy_shim.js",y=0,v=0;function T(){let a=Date.now();a-v<5e3||y>=3||(v=a,y+=1,import(`${A}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function f(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}_()?T():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",f):f(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||f()})}catch{}})();
|
||||
|
||||
+29
-29
@@ -1,56 +1,56 @@
|
||||
import{a as f}from"./chunk-Q7DQUCT7.js";import{a as h,b as o,d as b,e as m,f as g,g as n,h as _,i as a,k as v,s as p}from"./chunk-X7OW5OLQ.js";import{a as i}from"./chunk-PEGRBZWY.js";var s=class extends m{constructor(){super(...arguments);this._config={type:""};this._status=null;this._busy=!1;this._error="";this._localMonthly="";this._localYearly="";this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return this.hass?.language||"en"}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load(),v(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"});this._status=t,this._localMonthly=t.monthly_budget?String(t.monthly_budget):"",this._localYearly=t.yearly_budget?String(t.yearly_budget):"",this._dirty=!1}catch(t){this._error=p(t,this._lang)}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=parseFloat(this._localMonthly),e=parseFloat(this._localYearly),r={};!isNaN(t)&&t>=0&&(r.budget_monthly=t),!isNaN(e)&&e>=0&&(r.budget_yearly=e),await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/global/update",settings:r}),await this._load()}catch(t){this._error=p(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_budget"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,e=this._status;if(!e)return o`<ha-card><div class="loading">${a("loading",t)||"Loading\u2026"}</div></ha-card>`;let r=e.currency_symbol||_,l=e.monthly_budget?Math.min(100,(e.monthly_spent||0)/e.monthly_budget*100):0,d=e.yearly_budget?Math.min(100,(e.yearly_spent||0)/e.yearly_budget*100):0,u=l>=100?"danger":l>=80?"warning":"ok",y=d>=100?"danger":d>=80?"warning":"ok";return o`
|
||||
/*! maintenance_supporter frontend 2.44.1 */
|
||||
import{a as v}from"./chunk-TF44IC56.js";import{a as u,b as o,d as h,e as b,f as y,g as l,h as g,i as r,k as m,t as p}from"./chunk-RLJYCMAK.js";import{a as n}from"./chunk-KRBXISES.js";var x=80,s=class extends b{constructor(){super(...arguments);this._config={type:""};this._status=null;this._busy=!1;this._error="";this._localMonthly="";this._localYearly="";this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return this.hass?.language||"en"}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load(),m(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"});this._status=t,this._localMonthly=t.monthly_budget?String(t.monthly_budget):"",this._localYearly=t.yearly_budget?String(t.yearly_budget):"",this._dirty=!1}catch(t){this._error=p(t,this._lang)}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=parseFloat(this._localMonthly),a=parseFloat(this._localYearly),i={};!isNaN(t)&&t>=0&&(i.budget_monthly=t),!isNaN(a)&&a>=0&&(i.budget_yearly=a),await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/global/update",settings:i}),await this._load()}catch(t){this._error=p(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_budget"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,a=this._status;if(!a)return o`<ha-card><div class="loading">${r("loading",t)||"Loading\u2026"}</div></ha-card>`;let i=a.currency_symbol||g,_=a.alert_threshold_pct??x,f=[{label:r("budget_monthly",t)||"Monthly",spent:a.monthly_spent||0,budget:a.monthly_budget||0},{label:r("budget_yearly",t)||"Yearly",spent:a.yearly_spent||0,budget:a.yearly_budget||0}];return o`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<span class="emoji">💰</span>
|
||||
<span>${this._config.title||a("settings_budget",t)||"Budget"}</span>
|
||||
<span>${this._config.title||r("settings_budget",t)||"Budget"}</span>
|
||||
</div>
|
||||
<span class="currency">${r}</span>
|
||||
<span class="currency">${i}</span>
|
||||
</div>
|
||||
|
||||
${this._error?o`<div class="error">${this._error}</div>`:b}
|
||||
${this._error?o`<div class="error">${this._error}</div>`:h}
|
||||
|
||||
${f.map(e=>{if(!(e.budget>0))return o`
|
||||
<div class="track spent-only">
|
||||
<div class="track-label-row">
|
||||
<label>${e.label}</label>
|
||||
<span class="track-numbers ok">${e.spent.toFixed(0)} ${i}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;let d=Math.min(100,Math.max(0,e.spent/e.budget*100)),c=d>=100?"danger":d>=_?"warning":"ok";return o`
|
||||
<div class="track">
|
||||
<div class="track-label-row">
|
||||
<label>${a("budget_monthly",t)||"Monthly"}</label>
|
||||
<span class="track-numbers ${u}">
|
||||
${(e.monthly_spent||0).toFixed(0)} / ${(e.monthly_budget||0).toFixed(0)} ${r}
|
||||
<label>${e.label}</label>
|
||||
<span class="track-numbers ${c}">
|
||||
${e.spent.toFixed(0)} / ${e.budget.toFixed(0)} ${i}
|
||||
</span>
|
||||
</div>
|
||||
<div class="bar"><div class="bar-fill ${u}" style="width:${l}%"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="track">
|
||||
<div class="track-label-row">
|
||||
<label>${a("budget_yearly",t)||"Yearly"}</label>
|
||||
<span class="track-numbers ${y}">
|
||||
${(e.yearly_spent||0).toFixed(0)} / ${(e.yearly_budget||0).toFixed(0)} ${r}
|
||||
</span>
|
||||
</div>
|
||||
<div class="bar"><div class="bar-fill ${y}" style="width:${d}%"></div></div>
|
||||
<div class="bar"><div class="bar-fill ${c}" style="width:${d}%"></div></div>
|
||||
</div>
|
||||
`})}
|
||||
|
||||
${this._isAdmin?o`
|
||||
<div class="inputs-row">
|
||||
<div class="input-field">
|
||||
<label>${a("budget_monthly_set",t)||"Set monthly"}</label>
|
||||
<label>${r("budget_monthly_set",t)||"Set monthly"}</label>
|
||||
<div class="input-wrap">
|
||||
<input type="number" min="0" step="1"
|
||||
.value=${this._localMonthly}
|
||||
?disabled=${this._busy}
|
||||
@input=${c=>{this._localMonthly=c.target.value,this._dirty=!0}} />
|
||||
<span class="input-suffix">${r}</span>
|
||||
@input=${e=>{this._localMonthly=e.target.value,this._dirty=!0}} />
|
||||
<span class="input-suffix">${i}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-field">
|
||||
<label>${a("budget_yearly_set",t)||"Set yearly"}</label>
|
||||
<label>${r("budget_yearly_set",t)||"Set yearly"}</label>
|
||||
<div class="input-wrap">
|
||||
<input type="number" min="0" step="1"
|
||||
.value=${this._localYearly}
|
||||
?disabled=${this._busy}
|
||||
@input=${c=>{this._localYearly=c.target.value,this._dirty=!0}} />
|
||||
<span class="input-suffix">${r}</span>
|
||||
@input=${e=>{this._localYearly=e.target.value,this._dirty=!0}} />
|
||||
<span class="input-suffix">${i}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,20 +59,20 @@ import{a as f}from"./chunk-Q7DQUCT7.js";import{a as h,b as o,d as b,e as m,f as
|
||||
@click=${this._save}
|
||||
?disabled=${this._busy||!this._dirty}>
|
||||
<ha-icon icon="${this._dirty?"mdi:content-save":"mdi:check"}"></ha-icon>
|
||||
${this._dirty?a("save",t)||"Save":a("saved",t)||"Saved"}
|
||||
${this._dirty?r("save",t)||"Save":r("saved",t)||"Saved"}
|
||||
</button>
|
||||
<button class="btn link" @click=${this._onDeepLink}>
|
||||
${a("budget_advanced",t)||"Currency, alerts\u2026"}
|
||||
${r("budget_advanced",t)||"Currency, alerts\u2026"}
|
||||
</button>
|
||||
</div>
|
||||
`:o`
|
||||
<button class="btn link" @click=${this._onDeepLink}>
|
||||
${a("budget_open_panel",t)||"Open in panel"}
|
||||
${r("budget_open_panel",t)||"Open in panel"}
|
||||
</button>
|
||||
`}
|
||||
</div>
|
||||
</ha-card>
|
||||
`}};s.styles=[f,h`
|
||||
`}};s.styles=[v,u`
|
||||
.currency {
|
||||
font-size: 14px; font-weight: 600;
|
||||
color: var(--secondary-text-color);
|
||||
@@ -122,4 +122,4 @@ import{a as f}from"./chunk-Q7DQUCT7.js";import{a as h,b as o,d as b,e as m,f as
|
||||
pointer-events: none;
|
||||
}
|
||||
.actions { display: flex; gap: 8px; align-items: center; }
|
||||
`],i([g({attribute:!1})],s.prototype,"hass",2),i([n()],s.prototype,"_config",2),i([n()],s.prototype,"_status",2),i([n()],s.prototype,"_busy",2),i([n()],s.prototype,"_error",2),i([n()],s.prototype,"_localMonthly",2),i([n()],s.prototype,"_localYearly",2),i([n()],s.prototype,"_dirty",2);customElements.get("maintenance-budget-section-card")||customElements.define("maintenance-budget-section-card",s);window.customCards=window.customCards||[];window.customCards.push({type:"maintenance-budget-section-card",name:"Maintenance Supporter \u2014 Budget",description:"Inline monthly + yearly budget editor",preview:!1});export{s as MaintenanceBudgetSectionCard};
|
||||
`],n([y({attribute:!1})],s.prototype,"hass",2),n([l()],s.prototype,"_config",2),n([l()],s.prototype,"_status",2),n([l()],s.prototype,"_busy",2),n([l()],s.prototype,"_error",2),n([l()],s.prototype,"_localMonthly",2),n([l()],s.prototype,"_localYearly",2),n([l()],s.prototype,"_dirty",2);customElements.get("maintenance-budget-section-card")||customElements.define("maintenance-budget-section-card",s);window.customCards=window.customCards||[];window.customCards.push({type:"maintenance-budget-section-card",name:"Maintenance Supporter \u2014 Budget",description:"Inline monthly + yearly budget editor",preview:!1});export{s as MaintenanceBudgetSectionCard};
|
||||
+1
@@ -1 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.44.1 */
|
||||
var s=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var t=(a,r,c,o)=>{for(var e=o>1?void 0:o?l(r,c):r,i=a.length-1,d;i>=0;i--)(d=a[i])&&(e=(o?d(r,c,e):d(e))||e);return o&&e&&s(r,c,e),e};var m={ok:"var(--success-color, #4caf50)",due_soon:"var(--warning-color, #ff9800)",overdue:"var(--error-color, #f44336)",triggered:"var(--deep-orange-color, #ff5722)",archived:"var(--disabled-color, #9e9e9e)",paused:"var(--info-color, #2196f3)"},v={ok:"mdi:check-circle",due_soon:"mdi:alert-circle",overdue:"mdi:alert-octagon",triggered:"mdi:bell-alert",archived:"mdi:archive-outline",paused:"mdi:pause-circle-outline",completed:"mdi:check-circle",skipped:"mdi:skip-next",missed:"mdi:calendar-remove",reset:"mdi:refresh"};export{t as a,m as b,v as c};
|
||||
+5
-4
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user