278 files
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
2026.9.1
|
||||
2026.9.2
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"pid": 71, "version": 1, "ha_version": "2026.9.1", "start_ts": 1789128360.9194858}
|
||||
{"pid": 71, "version": 1, "ha_version": "2026.9.2", "start_ts": 1789331676.107018}
|
||||
+34
-29
@@ -26,12 +26,12 @@
|
||||
},
|
||||
{
|
||||
"id": "ee7be1b18ae84060a5d515640f6e5739",
|
||||
"url": "/seatemperatures_frontend/sea-temperatures-card.js?v=3.2.0",
|
||||
"url": "/seatemperatures_frontend/sea-temperatures-card.js?v=3.3.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "53837b51a371459b95ffc0989ce615fb",
|
||||
"url": "/hacsfiles/calendar-card-pro/calendar-card-pro.js?hacstag=939311749410",
|
||||
"url": "/hacsfiles/calendar-card-pro/calendar-card-pro.js?hacstag=939311749420",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
@@ -81,117 +81,117 @@
|
||||
},
|
||||
{
|
||||
"id": "88b90f2b1d6b45f0b44bee786ee1a525",
|
||||
"url": "/taskmate/taskmate-attr-resolver.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-attr-resolver.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "0796c92931dd458e81dc9c2677248274",
|
||||
"url": "/taskmate/taskmate-localize.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-localize.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "d2e281920ac7409583eafff1f1db69bd",
|
||||
"url": "/taskmate/taskmate-design.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-design.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "9eae9632f0734480be5edd784b030673",
|
||||
"url": "/taskmate/taskmate-badges-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-badges-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "5df49cd8a4eb420497b5df22cb31362c",
|
||||
"url": "/taskmate/taskmate-child-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-child-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "b44c87d0d74f468cb96c5eabc340f9ee",
|
||||
"url": "/taskmate/taskmate-rewards-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-rewards-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "787a96f9c30548708165a48457c99feb",
|
||||
"url": "/taskmate/taskmate-approvals-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-approvals-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "84031597de424ad68e7c2bc66d06b72e",
|
||||
"url": "/taskmate/taskmate-points-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-points-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "02613225844746b699bfbfac9642eca2",
|
||||
"url": "/taskmate/taskmate-reorder-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-reorder-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "7a06a07e17ba4b31b712bc91b77360b4",
|
||||
"url": "/taskmate/taskmate-overview-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-overview-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "08b42b53b33b4fa88db932455cf8d5aa",
|
||||
"url": "/taskmate/taskmate-activity-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-activity-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "15907eff51784acdba88ff694b574509",
|
||||
"url": "/taskmate/taskmate-streak-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-streak-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "1bd6bb5c0a6d4f0d916e3d82cb7fac39",
|
||||
"url": "/taskmate/taskmate-weekly-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-weekly-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "289ce5943f5a4972a593697db4c96ece",
|
||||
"url": "/taskmate/taskmate-graph-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-graph-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "d2904acee4c74a398dcc7b996c23e7e7",
|
||||
"url": "/taskmate/taskmate-reward-progress-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-reward-progress-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "46d378736fd54a26be253f2dfb27202e",
|
||||
"url": "/taskmate/taskmate-leaderboard-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-leaderboard-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "db11ae36457842fa826c7991e1d9c6f4",
|
||||
"url": "/taskmate/taskmate-parent-dashboard-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-parent-dashboard-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "ce85f301b59a412e9abfe5ffd51cb9a2",
|
||||
"url": "/taskmate/taskmate-penalties-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-penalties-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "1959e1629de2422daa8adaf87069c288",
|
||||
"url": "/taskmate/taskmate-bonuses-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-bonuses-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "2548524b269c4959bc7e0e967df943af",
|
||||
"url": "/taskmate/taskmate-points-display-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-points-display-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "36ada49368db4187a283fbbbef45454f",
|
||||
"url": "/taskmate/taskmate-calendar-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-calendar-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "67c32687acd2497b90030ef141f4b6db",
|
||||
"url": "/taskmate/taskmate-photo-gallery-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-photo-gallery-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "6f0c8348a91f4b74a9b846ab3f62621f",
|
||||
"url": "/taskmate/taskmate-family-goal-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-family-goal-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
@@ -276,7 +276,7 @@
|
||||
},
|
||||
{
|
||||
"id": "73dbd2de03de4530b451a55bfe6c46cf",
|
||||
"url": "/taskmate/taskmate-routine-card.js?v=5.5.0",
|
||||
"url": "/taskmate/taskmate-routine-card.js?v=5.6.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
@@ -310,13 +310,18 @@
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "793e9c66f5e349949e23fe3e64088352",
|
||||
"id": "aafb0bb764874be384629a0ca0a18c53",
|
||||
"url": "/my_music_library/my-music-library-card.js?v=3.14.0",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "01719b0125f941f38ea1e62eead86a94",
|
||||
"url": "/climate_scheduler/static/climate-scheduler-card.js?v=1.15.1",
|
||||
"type": "module"
|
||||
},
|
||||
{
|
||||
"id": "aafb0bb764874be384629a0ca0a18c53",
|
||||
"url": "/my_music_library/my-music-library-card.js?v=3.14.0",
|
||||
"id": "1a4939ce1d2b47f89502176d3c5ac6e7",
|
||||
"url": "/taskmate/taskmate-sounds.js?v=5.6.0",
|
||||
"type": "module"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -23,6 +23,7 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
__short_version__ as current_version,
|
||||
)
|
||||
from homeassistant.core import Context, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
@@ -34,6 +35,7 @@ from homeassistant.helpers import (
|
||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from packaging.version import Version
|
||||
from pyhilo import API
|
||||
from pyhilo.device import HiloDevice
|
||||
from pyhilo.devices import Devices
|
||||
@@ -157,11 +159,23 @@ def _async_migrate_gateway_device_identifier(
|
||||
return
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
old_device = device_registry.async_get_device(identifiers={(DOMAIN, old_dsn)})
|
||||
if Version(current_version) >= Version("2026.8"):
|
||||
old_device = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, old_dsn), entry.entry_id
|
||||
)
|
||||
|
||||
else:
|
||||
old_device = device_registry.async_get_device(identifiers={(DOMAIN, old_dsn)})
|
||||
|
||||
if old_device is None:
|
||||
return # fresh install, or already migrated
|
||||
|
||||
new_device = device_registry.async_get_device(identifiers={(DOMAIN, new_mac)})
|
||||
if Version(current_version) >= Version("2026.8"):
|
||||
new_device = device_registry.async_get_device_by_identifier(
|
||||
(DOMAIN, new_mac), entry.entry_id
|
||||
)
|
||||
else:
|
||||
new_device = device_registry.async_get_device(identifiers={(DOMAIN, new_mac)})
|
||||
if new_device is not None and new_device.id != old_device.id:
|
||||
LOG.warning(
|
||||
"Gateway device already registered under new identifier %s, skipping device migration",
|
||||
@@ -781,7 +795,13 @@ class Hilo:
|
||||
returns its old DSN-based identifier if it exists. If it doesn't,
|
||||
it returns Noneto use the MAC address instead."""
|
||||
device_registry = dr.async_get(self._hass)
|
||||
for device in device_registry.devices.values():
|
||||
devices = device_registry.devices
|
||||
device_iter = (
|
||||
devices
|
||||
if Version(current_version) >= Version("2026.9")
|
||||
else devices.values()
|
||||
)
|
||||
for device in device_iter:
|
||||
if device.manufacturer != "Hilo" or device.model != "EQ000017":
|
||||
continue
|
||||
for domain, identifier in device.identifiers:
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"issue_tracker": "https://github.com/dvd-dev/hilo/issues",
|
||||
"requirements": ["python-hilo>=2026.9.1"],
|
||||
"version": "2026.9.1"
|
||||
"version": "2026.9.4"
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
BATTERY_FLEET_OBJECT_FLAG,
|
||||
COMPLETION_PROVENANCE_NOTES,
|
||||
CONF_ADMIN_PANEL_USER_IDS,
|
||||
CONF_ADVANCED_ADAPTIVE,
|
||||
CONF_ADVANCED_BUDGET,
|
||||
@@ -99,10 +100,12 @@ from .entity.summary_coordinator import MaintenanceSummaryCoordinator
|
||||
from .frontend import async_register_card
|
||||
from .helpers.aggregate import object_name as aggregate_object_name
|
||||
from .helpers.assist_sentences import async_sync as async_sync_assist_sentences
|
||||
from .helpers.dates import INTERVAL_UNITS
|
||||
from .helpers.dates import INTERVAL_UNITS, local_date_from_iso
|
||||
from .helpers.documents import DocumentStore
|
||||
from .helpers.global_options import get_global_entry
|
||||
from .helpers.notification_gates import task_may_notify
|
||||
from .helpers.notification_manager import NotificationManager
|
||||
from .helpers.notify_hooks import KIND_LEAD_TIME
|
||||
from .helpers.schedule import normalize_task_storage
|
||||
from .helpers.task_fields import (
|
||||
INTERVAL_DAYS_RANGE,
|
||||
@@ -345,7 +348,7 @@ async def async_maybe_send_lead_reminders(hass: HomeAssistant) -> None:
|
||||
per cycle overall (the day-match only occurs on one day per lead).
|
||||
Complements — does not replace — the warning_days status-change path.
|
||||
"""
|
||||
from .const import CONF_REMINDER_LEAD_DAYS
|
||||
from .const import CONF_REMINDER_LEAD_DAYS, MaintenanceStatus
|
||||
from .models.maintenance_task import MaintenanceTask
|
||||
|
||||
global_entry = get_global_entry(hass)
|
||||
@@ -383,8 +386,10 @@ async def async_maybe_send_lead_reminders(hass: HomeAssistant) -> None:
|
||||
continue
|
||||
if task_data.get("archived_at") is not None:
|
||||
continue
|
||||
if task_data.get("notify_enabled") is False:
|
||||
continue # #173: muted task — no lead reminders either
|
||||
# The per-task gates the lead-time kind declares (#173 mute,
|
||||
# vacation, snooze) — before the model is even built.
|
||||
if not task_may_notify(hass, entry.entry_id, task_id, MaintenanceStatus.DUE_SOON, task_data, kind=KIND_LEAD_TIME, manager=nm):
|
||||
continue
|
||||
task = MaintenanceTask.from_dict(task_data)
|
||||
days = task.days_until_due
|
||||
# days == 0 is a valid lead ("on the due date"); negatives are the
|
||||
@@ -454,6 +459,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
|
||||
# Create the notification manager (shared across all entries)
|
||||
hass.data[DOMAIN][NOTIFICATION_MANAGER_KEY] = NotificationManager(hass)
|
||||
await hass.data[DOMAIN][NOTIFICATION_MANAGER_KEY].async_load()
|
||||
|
||||
# Create + load the global document store (per-object doc metadata + the
|
||||
# content-addressed blob registry; binaries live on disk under /config).
|
||||
@@ -595,6 +601,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
raise ServiceValidationError(str(err)) from err
|
||||
await coordinator.complete_maintenance(
|
||||
task_id=task_id,
|
||||
source="service",
|
||||
notes=call.data.get("notes"),
|
||||
cost=call.data.get("cost"),
|
||||
duration=call.data.get("duration"),
|
||||
@@ -901,7 +908,9 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
_LOGGER.info("Completing task %s via notification action", task_id)
|
||||
await runtime_data.coordinator.complete_maintenance(
|
||||
task_id=task_id,
|
||||
notes=COMPLETION_PROVENANCE_NOTES["notification_action"],
|
||||
unattended=True,
|
||||
source="notification_action",
|
||||
# mobile_app fires the action with the registration's
|
||||
# user in the context - credit them (#128 attribution,
|
||||
# feeds least_completed rotation); bug audit 2026-08-29.
|
||||
@@ -936,7 +945,9 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
|
||||
# Action succeeded — dismiss notification and clear NM rate-limit state
|
||||
if nm is not None:
|
||||
await nm.async_dismiss_task_notification(task_id)
|
||||
cfg_entry = hass.config_entries.async_get_entry(entry_id)
|
||||
task_cfg = ((cfg_entry.data.get(CONF_TASKS) or {}).get(task_id) or {}) if cfg_entry is not None else {}
|
||||
await nm.async_dismiss_task_notification(task_id, responsible_user_id=task_cfg.get("responsible_user_id"))
|
||||
nm.clear_task_state(entry_id, task_id)
|
||||
|
||||
unsub_notification = hass.bus.async_listen("mobile_app_notification_action", _handle_notification_action)
|
||||
@@ -974,8 +985,9 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
await runtime_data.coordinator.complete_maintenance(
|
||||
task_id=task_id,
|
||||
completed_by=user_id,
|
||||
notes="Completed via NFC tag",
|
||||
notes=COMPLETION_PROVENANCE_NOTES["nfc"],
|
||||
unattended=True,
|
||||
source="nfc",
|
||||
tag_verified=True,
|
||||
)
|
||||
except ServiceValidationError as err:
|
||||
@@ -1302,8 +1314,8 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
ts for h in history if isinstance(h, dict) and isinstance((ts := h.get("timestamp")), str) and ts
|
||||
]
|
||||
if timestamps:
|
||||
# ISO timestamps sort lexicographically; take YYYY-MM-DD
|
||||
anchor = min(timestamps)[:10]
|
||||
# ISO timestamps sort lexicographically; the earliest one's local day
|
||||
anchor = local_date_from_iso(min(timestamps)) or today_iso
|
||||
new_td = dict(td)
|
||||
new_td["created_at"] = anchor
|
||||
new_tasks[task_id] = new_td
|
||||
@@ -1630,7 +1642,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: MaintenanceSupporterConf
|
||||
result["pruned"],
|
||||
result["orphans_removed"],
|
||||
)
|
||||
if result.get("trigger_healed") or result["added"]:
|
||||
if result.get("trigger_healed") or result["added"] or result.get("migrated"):
|
||||
# #156: the recovery flag only reaches the LIVE trigger
|
||||
# after a reload — and so does the stock SENSOR of a part
|
||||
# added here: the sensor platform was set up before this
|
||||
@@ -2007,6 +2019,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: MaintenanceSupporterCon
|
||||
nm = hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY)
|
||||
if nm is not None:
|
||||
await nm.async_unload()
|
||||
# The index's backfill timer / extraction tasks must not outlive the
|
||||
# integration when the LAST entry to go is an object entry (the
|
||||
# global entry may already be gone) - bug audit 2026-09-12.
|
||||
text_index = hass.data.get(DOMAIN, {}).get(DOCUMENT_TEXT_INDEX_KEY)
|
||||
if text_index is not None:
|
||||
text_index.cancel()
|
||||
for unsub in hass.data.get(DOMAIN, {}).get(EVENT_UNSUBS_KEY, []):
|
||||
unsub()
|
||||
hass.data.pop(DOMAIN, None)
|
||||
|
||||
@@ -16,6 +16,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
COMPLETION_PROVENANCE_NOTES,
|
||||
CONF_OBJECT,
|
||||
CONF_TASK_ENABLED,
|
||||
CONF_TASKS,
|
||||
@@ -118,8 +119,9 @@ class MaintenanceActionButton(MaintenanceEntity, ButtonEntity):
|
||||
if self._action == "complete":
|
||||
await self.coordinator.complete_maintenance(
|
||||
self._task_id,
|
||||
notes="Completed from dashboard button",
|
||||
notes=COMPLETION_PROVENANCE_NOTES["button"],
|
||||
unattended=True,
|
||||
source="button",
|
||||
completed_by=self._context.user_id if self._context else None,
|
||||
)
|
||||
elif self._action == "skip":
|
||||
|
||||
@@ -20,7 +20,7 @@ from .const import (
|
||||
ScheduleType,
|
||||
)
|
||||
from .helpers.aggregate import merged_tasks
|
||||
from .helpers.dates import interval_span_days
|
||||
from .helpers.dates import interval_span_days, parse_hhmm
|
||||
from .helpers.global_options import is_schedule_time_enabled
|
||||
from .helpers.i18n import normalize_language
|
||||
from .models.maintenance_task import MaintenanceTask
|
||||
@@ -762,18 +762,12 @@ class MaintenanceCalendar(CalendarEntity):
|
||||
start: date | datetime = next_due
|
||||
end: date | datetime = next_due + timedelta(days=1)
|
||||
if task.schedule_time and self._is_schedule_time_feature_enabled():
|
||||
try:
|
||||
# Tolerate "HH:MM" and "HH:MM:SS" (HA TimeSelector's format).
|
||||
parts = str(task.schedule_time).split(":")
|
||||
start_dt = datetime.combine(
|
||||
next_due,
|
||||
time(int(parts[0]), int(parts[1])),
|
||||
tzinfo=dt_util.DEFAULT_TIME_ZONE,
|
||||
)
|
||||
# "HH:MM" and "HH:MM:SS" alike; malformed → stays all-day.
|
||||
at = parse_hhmm(task.schedule_time)
|
||||
if at is not None:
|
||||
start_dt = datetime.combine(next_due, at, tzinfo=dt_util.DEFAULT_TIME_ZONE)
|
||||
start = start_dt
|
||||
end = start_dt + timedelta(minutes=30)
|
||||
except (ValueError, TypeError, IndexError):
|
||||
pass # malformed schedule_time → fall back to all-day
|
||||
|
||||
return CalendarEvent(
|
||||
summary=f"{prefix} {_task_label(task)} ({object_name})",
|
||||
|
||||
@@ -35,12 +35,14 @@ from .const import (
|
||||
CONF_BUDGET_CURRENCY,
|
||||
CONF_BUDGET_MONTHLY,
|
||||
CONF_BUDGET_YEARLY,
|
||||
CONF_CURRENCY_DECIMALS,
|
||||
CONF_DEFAULT_WARNING_DAYS,
|
||||
CONF_MAX_NOTIFICATIONS_PER_DAY,
|
||||
CONF_NOTIFICATION_BUNDLE_THRESHOLD,
|
||||
CONF_NOTIFICATION_BUNDLING_ENABLED,
|
||||
CONF_NOTIFICATION_TITLE_STYLE,
|
||||
CONF_NOTIFICATIONS_ENABLED,
|
||||
CONF_NOTIFY_COMPLETED,
|
||||
CONF_NOTIFY_DUE_SOON_ENABLED,
|
||||
CONF_NOTIFY_DUE_SOON_INTERVAL,
|
||||
CONF_NOTIFY_EVENT_ONLY,
|
||||
@@ -59,17 +61,15 @@ from .const import (
|
||||
CONF_SHOPPING_LIST_ENTITY,
|
||||
CONF_SNOOZE_DURATION_HOURS,
|
||||
DEFAULT_BUDGET_CURRENCY,
|
||||
DEFAULT_MAX_NOTIFICATIONS_PER_DAY,
|
||||
DEFAULT_PANEL_ENABLED,
|
||||
DEFAULT_SNOOZE_DURATION_HOURS,
|
||||
DEFAULT_WARNING_DAYS,
|
||||
MAX_NOTIFY_EXTRA_DATA_LENGTH,
|
||||
MAX_PANEL_TITLE_LENGTH,
|
||||
NOTIFY_COMPLETED_MODES,
|
||||
TIME_HHMMSS_PATTERN,
|
||||
)
|
||||
from .helpers.dates import normalize_hhmm
|
||||
from .helpers.i18n import normalize_language
|
||||
from .helpers.notify_targets import build_notify_targets
|
||||
from .helpers.settings_registry import float_range, int_range
|
||||
from .helpers.settings_registry import float_range, int_range, setting_default
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -376,29 +376,25 @@ async def _send_test_to(
|
||||
) -> str:
|
||||
"""Send the test payload to every resolved service; "success" if any went."""
|
||||
try:
|
||||
from .helpers.notify_hooks import KIND_TEST, async_emit_and_dispatch, notification_context
|
||||
from .helpers.notification_manager import build_action_buttons
|
||||
from .helpers.notify_hooks import async_emit_and_dispatch, sample_notification_context
|
||||
|
||||
push_msg = _get_test_result_text(hass, "push_message")
|
||||
service_data: dict[str, Any] = {
|
||||
"title": "Maintenance Supporter",
|
||||
"message": push_msg,
|
||||
}
|
||||
actions_enabled = options.get(CONF_ACTION_COMPLETE_ENABLED, False)
|
||||
skip_enabled = options.get(CONF_ACTION_SKIP_ENABLED, False)
|
||||
snooze_enabled = options.get(CONF_ACTION_SNOOZE_ENABLED, False)
|
||||
if actions_enabled or skip_enabled or snooze_enabled:
|
||||
test_actions: list[dict[str, str]] = []
|
||||
if actions_enabled:
|
||||
test_actions.append({"action": "MS_TEST_COMPLETE", "title": "\u2705 Complete"})
|
||||
if skip_enabled:
|
||||
test_actions.append({"action": "MS_TEST_SKIP", "title": "\u23ed\ufe0f Skip"})
|
||||
if snooze_enabled:
|
||||
test_actions.append({"action": "MS_TEST_SNOOZE", "title": "\U0001f4a4 Snooze"})
|
||||
# The same buttons (translated, per the action toggles) a real
|
||||
# reminder carries \u2014 test ids, which the action listener ignores.
|
||||
test_actions = build_action_buttons(hass, options, normalize_language(hass), entry_id=None, task_id=None, skip_allowed=True)
|
||||
if test_actions:
|
||||
service_data["data"] = {"actions": test_actions}
|
||||
# Dual-path: legacy notify service OR notify entity (send_message).
|
||||
# #165: the test walks the same hook as a real notification, so the
|
||||
# event and the extra-data template can be verified from Settings.
|
||||
context = notification_context(hass, KIND_TEST)
|
||||
# Sample values for EVERY field of the contract (#178), so a template
|
||||
# can be written and verified from Settings before a real reminder fires.
|
||||
context = sample_notification_context(hass)
|
||||
sent_any = False
|
||||
for service in services:
|
||||
if await async_emit_and_dispatch(hass, service, service_data, context, blocking=True):
|
||||
@@ -417,9 +413,35 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
"""Get current options."""
|
||||
return dict(self.config_entry.options or self.config_entry.data)
|
||||
|
||||
def _opt(self, key: str) -> Any:
|
||||
"""The stored value of a setting, or its registry default — every form
|
||||
``default=`` goes through here so the flow can't drift from the WS
|
||||
``settings`` echo (tests/test_settings_defaults.py)."""
|
||||
return self._current.get(key, setting_default(key))
|
||||
|
||||
def _save_and_return(self, user_input: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Merge user input into options and return to the menu."""
|
||||
from .helpers.settings_registry import ALLOWED_SETTING_KEYS
|
||||
|
||||
merged = self._current
|
||||
# HA's NumberSelector hands back floats; the settings registry (WS
|
||||
# writes, export/import) types these keys as int. Store what the
|
||||
# registry expects so a settings backup round-trips (bug audit
|
||||
# 2026-09-12).
|
||||
for key, value in list(user_input.items()):
|
||||
if ALLOWED_SETTING_KEYS.get(key) is int and isinstance(value, float) and value.is_integer():
|
||||
user_input[key] = int(value)
|
||||
# The TimeSelector hands back "HH:MM:SS"; the registry caps the quiet
|
||||
# hours at 5 chars, so an 8-char value was dropped on a settings
|
||||
# export/import. Store the canonical "HH:MM" (an unparseable value
|
||||
# keeps the previous setting — the form's default coerces it anyway).
|
||||
for key in (CONF_QUIET_HOURS_START, CONF_QUIET_HOURS_END):
|
||||
if key in user_input:
|
||||
normalized = normalize_hhmm(user_input[key])
|
||||
if normalized is None:
|
||||
user_input.pop(key)
|
||||
else:
|
||||
user_input[key] = normalized
|
||||
merged.update(user_input)
|
||||
self.hass.config_entries.async_update_entry(self.config_entry, options=merged)
|
||||
return self.async_show_menu(
|
||||
@@ -475,43 +497,41 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
if user_input is not None:
|
||||
return self._save_and_return(user_input)
|
||||
|
||||
current = self._current
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="advanced_features",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_ADAPTIVE,
|
||||
default=current.get(CONF_ADVANCED_ADAPTIVE, False),
|
||||
default=self._opt(CONF_ADVANCED_ADAPTIVE),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_PREDICTIONS,
|
||||
default=current.get(CONF_ADVANCED_PREDICTIONS, False),
|
||||
default=self._opt(CONF_ADVANCED_PREDICTIONS),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_SEASONAL,
|
||||
default=current.get(CONF_ADVANCED_SEASONAL, False),
|
||||
default=self._opt(CONF_ADVANCED_SEASONAL),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_ENVIRONMENTAL,
|
||||
default=current.get(CONF_ADVANCED_ENVIRONMENTAL, False),
|
||||
default=self._opt(CONF_ADVANCED_ENVIRONMENTAL),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_BUDGET,
|
||||
default=current.get(CONF_ADVANCED_BUDGET, False),
|
||||
default=self._opt(CONF_ADVANCED_BUDGET),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_GROUPS,
|
||||
default=current.get(CONF_ADVANCED_GROUPS, False),
|
||||
default=self._opt(CONF_ADVANCED_GROUPS),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_CHECKLISTS,
|
||||
default=current.get(CONF_ADVANCED_CHECKLISTS, False),
|
||||
default=self._opt(CONF_ADVANCED_CHECKLISTS),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADVANCED_SCHEDULE_TIME,
|
||||
default=current.get(CONF_ADVANCED_SCHEDULE_TIME, False),
|
||||
default=self._opt(CONF_ADVANCED_SCHEDULE_TIME),
|
||||
): selector.BooleanSelector(),
|
||||
}
|
||||
),
|
||||
@@ -542,18 +562,17 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
for u in non_admin
|
||||
]
|
||||
|
||||
current = self._current
|
||||
return self.async_show_form(
|
||||
step_id="panel_access",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_OPERATOR_WRITE_ENABLED,
|
||||
default=current.get(CONF_OPERATOR_WRITE_ENABLED, False),
|
||||
default=self._opt(CONF_OPERATOR_WRITE_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ADMIN_PANEL_USER_IDS,
|
||||
default=current.get(CONF_ADMIN_PANEL_USER_IDS, []),
|
||||
default=self._opt(CONF_ADMIN_PANEL_USER_IDS),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=options,
|
||||
@@ -605,7 +624,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
return self._save_and_return(user_input)
|
||||
|
||||
current = self._current
|
||||
currency_code = current.get(CONF_BUDGET_CURRENCY, DEFAULT_BUDGET_CURRENCY)
|
||||
currency_code = self._opt(CONF_BUDGET_CURRENCY)
|
||||
currency_options = [
|
||||
selector.SelectOptionDict(value=code, label=f"{code} ({symbol})") for code, symbol in BUDGET_CURRENCIES.items()
|
||||
]
|
||||
@@ -623,7 +642,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_DEFAULT_WARNING_DAYS,
|
||||
default=current.get(CONF_DEFAULT_WARNING_DAYS, DEFAULT_WARNING_DAYS),
|
||||
default=self._opt(CONF_DEFAULT_WARNING_DAYS),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(min=_WARN_MIN, max=_WARN_MAX, step=1, mode=selector.NumberSelectorMode.BOX)
|
||||
),
|
||||
@@ -636,13 +655,25 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
# Decimal places for every displayed amount (0 = whole numbers).
|
||||
vol.Optional(
|
||||
CONF_CURRENCY_DECIMALS,
|
||||
default=self._opt(CONF_CURRENCY_DECIMALS),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=int_range(CONF_CURRENCY_DECIMALS)[0],
|
||||
max=int_range(CONF_CURRENCY_DECIMALS)[1],
|
||||
step=1,
|
||||
mode=selector.NumberSelectorMode.BOX,
|
||||
)
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_NOTIFICATIONS_ENABLED,
|
||||
default=current.get(CONF_NOTIFICATIONS_ENABLED, False),
|
||||
default=self._opt(CONF_NOTIFICATIONS_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_SERVICE,
|
||||
default=current.get(CONF_NOTIFY_SERVICE, ""),
|
||||
default=self._opt(CONF_NOTIFY_SERVICE),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=notify_services,
|
||||
@@ -658,7 +689,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
): selector.EntitySelector(selector.EntitySelectorConfig(domain="todo")),
|
||||
vol.Optional(
|
||||
CONF_PANEL_ENABLED,
|
||||
default=current.get(CONF_PANEL_ENABLED, DEFAULT_PANEL_ENABLED),
|
||||
default=self._opt(CONF_PANEL_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
# Blank clears the override → panel falls back to the default
|
||||
# title ("Maintenance"). suggested_value pre-fills the current
|
||||
@@ -692,11 +723,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Due Soon ---
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_DUE_SOON_ENABLED,
|
||||
default=current.get(CONF_NOTIFY_DUE_SOON_ENABLED, True),
|
||||
default=self._opt(CONF_NOTIFY_DUE_SOON_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_DUE_SOON_INTERVAL,
|
||||
default=current.get(CONF_NOTIFY_DUE_SOON_INTERVAL, 24),
|
||||
default=self._opt(CONF_NOTIFY_DUE_SOON_INTERVAL),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -705,11 +736,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Overdue ---
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_OVERDUE_ENABLED,
|
||||
default=current.get(CONF_NOTIFY_OVERDUE_ENABLED, True),
|
||||
default=self._opt(CONF_NOTIFY_OVERDUE_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_OVERDUE_INTERVAL,
|
||||
default=current.get(CONF_NOTIFY_OVERDUE_INTERVAL, 12),
|
||||
default=self._opt(CONF_NOTIFY_OVERDUE_INTERVAL),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -718,11 +749,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Triggered ---
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_TRIGGERED_ENABLED,
|
||||
default=current.get(CONF_NOTIFY_TRIGGERED_ENABLED, True),
|
||||
default=self._opt(CONF_NOTIFY_TRIGGERED_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_TRIGGERED_INTERVAL,
|
||||
default=current.get(CONF_NOTIFY_TRIGGERED_INTERVAL, 0),
|
||||
default=self._opt(CONF_NOTIFY_TRIGGERED_INTERVAL),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_NOTIFY_INTERVAL_MIN, max=_NOTIFY_INTERVAL_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -731,7 +762,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Quiet Hours ---
|
||||
vol.Optional(
|
||||
CONF_QUIET_HOURS_ENABLED,
|
||||
default=current.get(CONF_QUIET_HOURS_ENABLED, True),
|
||||
default=self._opt(CONF_QUIET_HOURS_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
# v1.4.6 (#44 follow-up): use `or` instead of dict-default so
|
||||
# empty-string / null / non-HH:MM values in storage don't
|
||||
@@ -742,16 +773,16 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# fallback whenever the persisted value isn't a usable time.
|
||||
vol.Optional(
|
||||
CONF_QUIET_HOURS_START,
|
||||
default=_safe_time(current.get(CONF_QUIET_HOURS_START), "22:00"),
|
||||
default=_safe_time(current.get(CONF_QUIET_HOURS_START), setting_default(CONF_QUIET_HOURS_START)),
|
||||
): selector.TimeSelector(),
|
||||
vol.Optional(
|
||||
CONF_QUIET_HOURS_END,
|
||||
default=_safe_time(current.get(CONF_QUIET_HOURS_END), "08:00"),
|
||||
default=_safe_time(current.get(CONF_QUIET_HOURS_END), setting_default(CONF_QUIET_HOURS_END)),
|
||||
): selector.TimeSelector(),
|
||||
# --- Daily Limit ---
|
||||
vol.Optional(
|
||||
CONF_MAX_NOTIFICATIONS_PER_DAY,
|
||||
default=current.get(CONF_MAX_NOTIFICATIONS_PER_DAY, DEFAULT_MAX_NOTIFICATIONS_PER_DAY),
|
||||
default=self._opt(CONF_MAX_NOTIFICATIONS_PER_DAY),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_MAX_PER_DAY_MIN, max=_MAX_PER_DAY_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -760,11 +791,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# --- Bundling ---
|
||||
vol.Optional(
|
||||
CONF_NOTIFICATION_BUNDLING_ENABLED,
|
||||
default=current.get(CONF_NOTIFICATION_BUNDLING_ENABLED, False),
|
||||
default=self._opt(CONF_NOTIFICATION_BUNDLING_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_NOTIFICATION_BUNDLE_THRESHOLD,
|
||||
default=current.get(CONF_NOTIFICATION_BUNDLE_THRESHOLD, 2),
|
||||
default=self._opt(CONF_NOTIFICATION_BUNDLE_THRESHOLD),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_BUNDLE_MIN, max=_BUNDLE_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -773,7 +804,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
# v1.4.0 (#44): notification title style
|
||||
vol.Optional(
|
||||
CONF_NOTIFICATION_TITLE_STYLE,
|
||||
default=current.get(CONF_NOTIFICATION_TITLE_STYLE, "default"),
|
||||
default=self._opt(CONF_NOTIFICATION_TITLE_STYLE),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=["default", "object_name", "task_name"],
|
||||
@@ -781,15 +812,29 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
translation_key="notification_title_style",
|
||||
)
|
||||
),
|
||||
# #173 follow-up: completion notifications (activity kind).
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_COMPLETED,
|
||||
default=self._opt(CONF_NOTIFY_COMPLETED),
|
||||
): selector.SelectSelector(
|
||||
selector.SelectSelectorConfig(
|
||||
options=list(NOTIFY_COMPLETED_MODES),
|
||||
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||
translation_key="notify_completed",
|
||||
)
|
||||
),
|
||||
# 2.80 (#165) / #173: your own notification rule — the same
|
||||
# two settings the panel's Settings tab offers.
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_EVENT_ONLY,
|
||||
default=current.get(CONF_NOTIFY_EVENT_ONLY, False),
|
||||
default=self._opt(CONF_NOTIFY_EVENT_ONLY),
|
||||
): selector.BooleanSelector(),
|
||||
# default (not suggested_value): an emptied optional text
|
||||
# field is omitted from user_input and the old template
|
||||
# survived - a default round-trips "" (bug audit 2026-09-12).
|
||||
vol.Optional(
|
||||
CONF_NOTIFY_EXTRA_DATA,
|
||||
description={"suggested_value": current.get(CONF_NOTIFY_EXTRA_DATA, "")},
|
||||
default=self._opt(CONF_NOTIFY_EXTRA_DATA),
|
||||
): selector.TextSelector(
|
||||
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiline=True)
|
||||
),
|
||||
@@ -804,27 +849,25 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
if user_input is not None:
|
||||
return self._save_and_return(user_input)
|
||||
|
||||
current = self._current
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="notification_actions",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_ACTION_COMPLETE_ENABLED,
|
||||
default=current.get(CONF_ACTION_COMPLETE_ENABLED, False),
|
||||
default=self._opt(CONF_ACTION_COMPLETE_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ACTION_SKIP_ENABLED,
|
||||
default=current.get(CONF_ACTION_SKIP_ENABLED, False),
|
||||
default=self._opt(CONF_ACTION_SKIP_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_ACTION_SNOOZE_ENABLED,
|
||||
default=current.get(CONF_ACTION_SNOOZE_ENABLED, False),
|
||||
default=self._opt(CONF_ACTION_SNOOZE_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_SNOOZE_DURATION_HOURS,
|
||||
default=current.get(CONF_SNOOZE_DURATION_HOURS, DEFAULT_SNOOZE_DURATION_HOURS),
|
||||
default=self._opt(CONF_SNOOZE_DURATION_HOURS),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_SNOOZE_MIN, max=_SNOOZE_MAX, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
@@ -863,9 +906,8 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
if user_input is not None:
|
||||
return self._save_and_return(user_input)
|
||||
|
||||
current = self._current
|
||||
currency_code = current.get(CONF_BUDGET_CURRENCY, DEFAULT_BUDGET_CURRENCY)
|
||||
currency_symbol = BUDGET_CURRENCIES.get(currency_code, "€")
|
||||
currency_code = self._opt(CONF_BUDGET_CURRENCY)
|
||||
currency_symbol = BUDGET_CURRENCIES.get(currency_code, BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY])
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="budget_settings",
|
||||
@@ -873,7 +915,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_BUDGET_MONTHLY,
|
||||
default=current.get(CONF_BUDGET_MONTHLY, 0.0),
|
||||
default=self._opt(CONF_BUDGET_MONTHLY),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_BUDGET_MONTHLY_MIN,
|
||||
@@ -885,7 +927,7 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_BUDGET_YEARLY,
|
||||
default=current.get(CONF_BUDGET_YEARLY, 0.0),
|
||||
default=self._opt(CONF_BUDGET_YEARLY),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_BUDGET_YEARLY_MIN,
|
||||
@@ -897,11 +939,11 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_BUDGET_ALERTS_ENABLED,
|
||||
default=current.get(CONF_BUDGET_ALERTS_ENABLED, False),
|
||||
default=self._opt(CONF_BUDGET_ALERTS_ENABLED),
|
||||
): selector.BooleanSelector(),
|
||||
vol.Optional(
|
||||
CONF_BUDGET_ALERT_THRESHOLD,
|
||||
default=current.get(CONF_BUDGET_ALERT_THRESHOLD, 80),
|
||||
default=self._opt(CONF_BUDGET_ALERT_THRESHOLD),
|
||||
): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=_ALERT_MIN,
|
||||
|
||||
@@ -19,6 +19,7 @@ from .const import (
|
||||
CONF_OBJECT,
|
||||
CONF_TASKS,
|
||||
)
|
||||
from .helpers.history import completed_entries
|
||||
from .helpers.schedule import (
|
||||
normalize_task_storage,
|
||||
)
|
||||
@@ -53,14 +54,7 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow):
|
||||
def _save_new_task(self) -> ConfigFlowResult:
|
||||
"""Save the current task and return to init."""
|
||||
from .config_flow_schedule import build_new_task_record
|
||||
from .const import MAX_TASKS_PER_OBJECT
|
||||
|
||||
# Same bound the WS create chokepoint enforces (drift audit 2026-08:
|
||||
# this options-flow copy had no cap, so the flow could inflate
|
||||
# ConfigEntry.data past the limit the API refuses).
|
||||
if len(self.config_entry.data.get(CONF_TASKS, {})) >= MAX_TASKS_PER_OBJECT:
|
||||
self._current_task = {}
|
||||
return self._show_init_menu()
|
||||
from .helpers.entry_tasks import insert_new_task
|
||||
|
||||
task_id = uuid4().hex
|
||||
task_data = build_new_task_record(
|
||||
@@ -69,33 +63,17 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow):
|
||||
object_id=self.config_entry.data.get(CONF_OBJECT, {}).get("id", ""),
|
||||
hass=self.hass,
|
||||
)
|
||||
new_data = dict(self.config_entry.data)
|
||||
new_tasks = dict(new_data.get(CONF_TASKS, {}))
|
||||
new_tasks[task_id] = task_data
|
||||
new_data[CONF_TASKS] = new_tasks
|
||||
|
||||
obj = dict(new_data.get(CONF_OBJECT, {}))
|
||||
task_ids = list(obj.get("task_ids", []))
|
||||
task_ids.append(task_id)
|
||||
obj["task_ids"] = task_ids
|
||||
new_data[CONF_OBJECT] = obj
|
||||
|
||||
self._update_config_entry(new_data)
|
||||
|
||||
# Initialize dynamic state in Store
|
||||
rd = getattr(self.config_entry, "runtime_data", None)
|
||||
store = getattr(rd, "store", None) if rd else None
|
||||
last_performed = self._current_task.get("last_performed")
|
||||
# The one create rule shared with task/create and add_task (cap,
|
||||
# task_ids, Store init). The flow keeps its own save/reload timing:
|
||||
# delay-save now, reload on the done step. At the cap the flow simply
|
||||
# returns to the menu — the cap is enforced upstream by the same rule.
|
||||
try:
|
||||
store = insert_new_task(self.hass, self.config_entry, task_data, last_performed=self._current_task.get("last_performed"))
|
||||
except ValueError:
|
||||
self._current_task = {}
|
||||
return self._show_init_menu()
|
||||
if store is not None:
|
||||
store.init_task(task_id, last_performed=last_performed)
|
||||
store.async_delay_save()
|
||||
elif last_performed:
|
||||
# Legacy: put last_performed in ConfigEntry.data
|
||||
task_data["last_performed"] = last_performed
|
||||
task_data["history"] = []
|
||||
new_tasks[task_id] = task_data
|
||||
new_data[CONF_TASKS] = new_tasks
|
||||
self._update_config_entry(new_data)
|
||||
|
||||
self._current_task = {}
|
||||
|
||||
@@ -189,7 +167,7 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow):
|
||||
sched = Schedule.parse(merged)
|
||||
lp_raw = merged.get("last_performed")
|
||||
lp = date_cls.fromisoformat(lp_raw[:10]) if isinstance(lp_raw, str) and lp_raw else None
|
||||
times = sum(1 for e in merged.get("history") or [] if e.get("type") == "completed")
|
||||
times = len(completed_entries(merged.get("history")))
|
||||
dates, _ended = preview_occurrences(
|
||||
sched, last_performed=lp, times_performed=times, today=dt_util.now().date()
|
||||
)
|
||||
|
||||
@@ -49,7 +49,9 @@ from .const import (
|
||||
MaintenanceTypeEnum,
|
||||
ScheduleType,
|
||||
)
|
||||
from .helpers.dates import normalize_hhmm
|
||||
from .helpers.global_options import get_default_warning_days
|
||||
from .helpers.pause import write_anchor
|
||||
from .helpers.reading_slots import parse_reading_slots_text, reading_slots_text
|
||||
from .helpers.schedule import (
|
||||
read_legacy_fields,
|
||||
@@ -205,15 +207,11 @@ class TaskCrudMixin:
|
||||
updated_task.pop("schedule", None)
|
||||
# schedule_time only present when global advanced flag is on; clear by submitting "".
|
||||
if CONF_TASK_SCHEDULE_TIME in user_input:
|
||||
sched = (user_input.get(CONF_TASK_SCHEDULE_TIME) or "").strip()
|
||||
# HA's TimeSelector serialises "HH:MM:SS"; every consumer
|
||||
# parses "HH:MM", so normalise to the first two components
|
||||
# here (else the calendar/next-due land at midnight).
|
||||
parts = sched.split(":")
|
||||
if len(parts) >= 2 and parts[0].isdigit() and parts[1].isdigit():
|
||||
sched = f"{int(parts[0]):02d}:{int(parts[1]):02d}"
|
||||
if sched:
|
||||
updated_task["schedule_time"] = sched
|
||||
# HA's TimeSelector serialises "HH:MM:SS"; stored form is
|
||||
# the canonical "HH:MM" (helpers.dates.normalize_hhmm).
|
||||
sched_time = normalize_hhmm(user_input.get(CONF_TASK_SCHEDULE_TIME))
|
||||
if sched_time:
|
||||
updated_task["schedule_time"] = sched_time
|
||||
else:
|
||||
updated_task.pop("schedule_time", None)
|
||||
updated_task["warning_days"] = int(
|
||||
@@ -228,7 +226,7 @@ class TaskCrudMixin:
|
||||
if user_input.get(CONF_TASK_DOCUMENTATION_URL):
|
||||
updated_task[CONF_TASK_DOCUMENTATION_URL] = user_input[CONF_TASK_DOCUMENTATION_URL]
|
||||
if user_input.get(CONF_TASK_LAST_PERFORMED):
|
||||
updated_task[CONF_TASK_LAST_PERFORMED] = str(user_input[CONF_TASK_LAST_PERFORMED])
|
||||
write_anchor(updated_task, str(user_input[CONF_TASK_LAST_PERFORMED]))
|
||||
pool = user_input.get(CONF_TASK_ASSIGNEE_POOL, [])
|
||||
if pool:
|
||||
updated_task["assignee_pool"] = pool
|
||||
@@ -291,6 +289,21 @@ class TaskCrudMixin:
|
||||
new_data[CONF_TASKS] = new_tasks
|
||||
self._update_config_entry(new_data)
|
||||
|
||||
# last_performed is DYNAMIC state: the Store's value overlays
|
||||
# the static copy in the merge, so the edit above was masked
|
||||
# for every task that had ever been completed. Write the
|
||||
# Store too — a MOVED anchor drops the old cycle's postpone,
|
||||
# an unchanged one (the form re-submits it) keeps it.
|
||||
if user_input.get(CONF_TASK_LAST_PERFORMED) and self._selected_task_id:
|
||||
lp_rd = getattr(self.config_entry, "runtime_data", None)
|
||||
lp_store = getattr(lp_rd, "store", None)
|
||||
if lp_store is not None:
|
||||
new_lp = str(user_input[CONF_TASK_LAST_PERFORMED])
|
||||
lp_store.set_anchor(
|
||||
self._selected_task_id, new_lp, clear_modifiers=new_lp != lp_store.get_last_performed(self._selected_task_id)
|
||||
)
|
||||
lp_store.async_delay_save()
|
||||
|
||||
return self._show_task_action_menu()
|
||||
|
||||
type_options = [t.value for t in MaintenanceTypeEnum]
|
||||
|
||||
@@ -129,6 +129,9 @@ CONF_PANEL_TITLE = "panel_title"
|
||||
# installs so the panel can announce the new look with a "back to icons" button.
|
||||
CONF_ROW_ACTION_STYLE = "row_action_style"
|
||||
CONF_ROW_ACTION_NOTICE = "row_action_notice_pending"
|
||||
# #170 round 2: show the reference numbers (#8, #8.3) in front of object and
|
||||
# task names in every list, not only on the detail pages.
|
||||
CONF_REF_NUMBERS_IN_LISTS = "ref_numbers_in_lists"
|
||||
ROW_ACTION_STYLES: tuple[str, ...] = ("buttons_compact", "buttons", "icons")
|
||||
DEFAULT_ROW_ACTION_STYLE = "buttons_compact"
|
||||
# #146: household defaults for the "low" thresholds that discovery and the
|
||||
@@ -137,6 +140,8 @@ DEFAULT_ROW_ACTION_STYLE = "buttons_compact"
|
||||
# floor for batteries without their own Battery Notes threshold.
|
||||
CONF_DEFAULT_CONSUMABLE_THRESHOLD = "default_consumable_threshold"
|
||||
CONF_BATTERY_LOW_PERCENT = "battery_low_percent"
|
||||
# D#162 follow-up: per-type typical lifetime overrides {canonical type: months}
|
||||
CONF_BATTERY_LIFETIME_MONTHS = "battery_lifetime_months"
|
||||
DEFAULT_CONSUMABLE_THRESHOLD = 10
|
||||
DEFAULT_BATTERY_LOW_PERCENT = 20
|
||||
THRESHOLD_PERCENT_RANGE: tuple[int, int] = (1, 90)
|
||||
@@ -286,6 +291,37 @@ CONF_NOTIFY_EVENT_ONLY = "notify_event_only"
|
||||
CONF_NOTIFY_EXTRA_DATA = "notify_extra_data"
|
||||
MAX_NOTIFY_EXTRA_DATA_LENGTH = 2000
|
||||
NOTIFICATION_TITLE_STYLES = ("default", "object_name", "task_name")
|
||||
# #173 follow-up: completion notifications — off, only completions no person
|
||||
# made on the spot (sensor recovery, shopping-list sync, an automation's
|
||||
# service call), or every completion.
|
||||
CONF_NOTIFY_COMPLETED = "notify_completed"
|
||||
NOTIFY_COMPLETED_MODES = ("off", "automatic", "all")
|
||||
# Completion sources (``complete_maintenance(source=…)``): where a completion
|
||||
# came from. The "automatic" ones had no person acting on the spot.
|
||||
COMPLETION_SOURCES = (
|
||||
"panel",
|
||||
"qr",
|
||||
"nfc",
|
||||
"button",
|
||||
"todo",
|
||||
"voice",
|
||||
"notification_action",
|
||||
"shopping_list",
|
||||
"service",
|
||||
"auto_recovery",
|
||||
)
|
||||
COMPLETION_SOURCES_AUTOMATIC = frozenset({"shopping_list", "service", "auto_recovery"})
|
||||
# The canned history note an UNATTENDED surface attaches (nobody was asked
|
||||
# for one) — provenance, never the note a task demands. One table, so the
|
||||
# wording lives once; tests/test_completion_sources.py keeps it complete.
|
||||
COMPLETION_PROVENANCE_NOTES: dict[str, str] = {
|
||||
"button": "Completed from dashboard button",
|
||||
"todo": "Completed from the To-do list",
|
||||
"voice": "Completed by voice",
|
||||
"notification_action": "Completed from the notification",
|
||||
"nfc": "Completed via NFC tag",
|
||||
"shopping_list": "Completed from the shopping list",
|
||||
}
|
||||
|
||||
# --- Config Keys: Notification Actions ---
|
||||
CONF_ACTION_COMPLETE_ENABLED = "action_complete_enabled"
|
||||
@@ -529,6 +565,10 @@ CONF_BUDGET_YEARLY = "budget_yearly"
|
||||
CONF_BUDGET_ALERTS_ENABLED = "budget_alerts_enabled"
|
||||
CONF_BUDGET_ALERT_THRESHOLD = "budget_alert_threshold"
|
||||
CONF_BUDGET_CURRENCY = "budget_currency"
|
||||
# Decimal places for every displayed amount — KPIs, budgets, costs in lists and
|
||||
# reports, budget alerts, buy-task notes. 0 = whole numbers (the default).
|
||||
CONF_CURRENCY_DECIMALS = "currency_decimals"
|
||||
DEFAULT_CURRENCY_DECIMALS = 0
|
||||
|
||||
BUDGET_CURRENCIES: dict[str, str] = {
|
||||
"EUR": "€",
|
||||
@@ -642,6 +682,14 @@ class MaintenanceStatus(StrEnum):
|
||||
PAUSED = "paused"
|
||||
|
||||
|
||||
# The statuses a reminder can be about — the same set the coordinator, the
|
||||
# notification manager, the to-do list and the aggregate counters used to
|
||||
# spell out by hand (DRY review 2026-09-12).
|
||||
NOTIFIABLE_STATUSES: frozenset[str] = frozenset(
|
||||
{MaintenanceStatus.DUE_SOON, MaintenanceStatus.OVERDUE, MaintenanceStatus.TRIGGERED}
|
||||
)
|
||||
|
||||
|
||||
class MaintenanceTypeEnum(StrEnum):
|
||||
"""Type/category of maintenance."""
|
||||
|
||||
@@ -775,6 +823,7 @@ MAX_LABEL_LENGTH = 40 # single label/tag
|
||||
MAX_GROUP_TASK_REFS = 200
|
||||
MAX_ID_LENGTH = 64 # entry_id, task_id, group_id (uuid hex = 32)
|
||||
MAX_DATE_LENGTH = 20 # ISO 8601 date strings (e.g. 2026-04-21)
|
||||
MAX_TIMESTAMP_LENGTH = 64 # ISO 8601 timestamps with offset (history entries)
|
||||
MAX_ENTITY_ID_LENGTH = 255 # HA entity_id max
|
||||
|
||||
# "No usable reading" state pair — 13 call sites hand-typed the tuple before
|
||||
@@ -798,6 +847,10 @@ MAX_SCHEDULE_TIME_LENGTH = 5 # "HH:MM"
|
||||
# already had MAX_PARTS_PER_OBJECT (50); tasks and documents were unbounded.
|
||||
MAX_TASKS_PER_OBJECT = 200 # mirrors MAX_GROUP_TASK_REFS; ~10x any real device
|
||||
MAX_DOCS_PER_OBJECT = 100 # manuals + invoices + photos — 100 is very generous
|
||||
# Vacation exempt list: task ids that keep their schedule during a vacation.
|
||||
# One cap for the WS schema, the sanitiser and the settings import (DRY
|
||||
# review 2026-09-13) — 2000 covers ten fully-loaded objects.
|
||||
MAX_VACATION_EXEMPT_TASKS = 2000
|
||||
|
||||
# --- Trigger Entity Availability ---
|
||||
STARTUP_GRACE_PERIOD_SECONDS = 300 # 5 minutes
|
||||
|
||||
@@ -28,6 +28,7 @@ from .const import (
|
||||
CONF_BUDGET_CURRENCY,
|
||||
CONF_BUDGET_MONTHLY,
|
||||
CONF_BUDGET_YEARLY,
|
||||
CONF_CURRENCY_DECIMALS,
|
||||
CONF_OBJECT,
|
||||
CONF_TASKS,
|
||||
DEFAULT_BUDGET_CURRENCY,
|
||||
@@ -39,6 +40,7 @@ from .const import (
|
||||
EVENT_TASK_SKIPPED,
|
||||
MANUAL_COMPLETION_DEDUP_SECONDS,
|
||||
MISSING_ENTITY_THRESHOLD_REFRESHES,
|
||||
NOTIFIABLE_STATUSES,
|
||||
NOTIFICATION_MANAGER_KEY,
|
||||
SIGNAL_TASK_RESET,
|
||||
STARTUP_GRACE_PERIOD_SECONDS,
|
||||
@@ -53,7 +55,12 @@ from .const import (
|
||||
)
|
||||
from .helpers.budget import compute_spend
|
||||
from .helpers.entry_tasks import write_task
|
||||
from .helpers.global_options import get_global_options, is_schedule_time_enabled
|
||||
from .helpers.global_options import global_option, is_schedule_time_enabled
|
||||
from .helpers.history import completed_entries
|
||||
from .helpers.interval_analyzer import hemisphere
|
||||
from .helpers.notification_gates import task_may_notify
|
||||
from .helpers.notify_hooks import KIND_STATUS
|
||||
from .helpers.pause import is_task_inert
|
||||
from .helpers.schedule import normalize_task_storage, read_legacy_fields
|
||||
from .models.maintenance_object import MaintenanceObject
|
||||
from .models.maintenance_task import MaintenanceTask
|
||||
@@ -117,6 +124,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
# Trigger completion cooldown tracking
|
||||
self._recently_completed: dict[str, float] = {} # task_id -> monotonic timestamp
|
||||
# Which of those cooldowns came from a COMPLETION (not a skip/reset):
|
||||
# only those require the sensor to recover before a re-activation
|
||||
# counts as a new edge (bug audit 2026-09-12).
|
||||
self._completion_cooldown: set[str] = set()
|
||||
# Manual completions only — the double-tap dedup window (journey M1).
|
||||
self._recent_manual_completions: dict[str, float] = {}
|
||||
# Backdated completions (explicit completed_at) get their OWN dedup,
|
||||
@@ -208,6 +219,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self._recently_completed = {
|
||||
tid: ts for tid, ts in self._recently_completed.items() if now_mono - ts < TRIGGER_COMPLETION_COOLDOWN_SECONDS
|
||||
}
|
||||
self._completion_cooldown &= set(self._recently_completed)
|
||||
self._recent_manual_completions = {
|
||||
tid: ts for tid, ts in self._recent_manual_completions.items() if now_mono - ts < MANUAL_COMPLETION_DEDUP_SECONDS
|
||||
}
|
||||
@@ -279,6 +291,9 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
task_result = task.to_dict()
|
||||
task_result["_status"] = status
|
||||
task_result["_days_until_due"] = task.days_until_due
|
||||
# The span-capped warning window (#58) for the dict status twin
|
||||
# the entities recompute from after a live trigger update.
|
||||
task_result["_warning_days_effective"] = task.effective_warning_days
|
||||
task_result["_next_due"] = task.next_due.isoformat() if task.next_due else None
|
||||
task_result["_is_done"] = task.is_done
|
||||
task_result["_trigger_active"] = task._trigger_active
|
||||
@@ -330,7 +345,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
# Inject hemisphere and current month for seasonal awareness.
|
||||
# latitude is None on an un-onboarded HA → default to north.
|
||||
analysis_config = dict(task.adaptive_config)
|
||||
analysis_config["hemisphere"] = "south" if (self.hass.config.latitude or 0) < 0 else "north"
|
||||
analysis_config["hemisphere"] = hemisphere(self.hass)
|
||||
analysis_config["_current_month"] = dt_util.now().month
|
||||
analysis = analyzer.analyze(task_result, analysis_config)
|
||||
task_result["_suggested_interval"] = analysis.recommended_interval
|
||||
@@ -439,15 +454,17 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
from .helpers.notification_manager import NotificationManager
|
||||
|
||||
nm = self.hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY)
|
||||
notify_statuses = {
|
||||
MaintenanceStatus.DUE_SOON,
|
||||
MaintenanceStatus.OVERDUE,
|
||||
MaintenanceStatus.TRIGGERED,
|
||||
}
|
||||
notify_statuses = NOTIFIABLE_STATUSES
|
||||
# Seed the manager only on the entry's FIRST refresh in this
|
||||
# process: a reload (every task edit) hands a fresh coordinator an
|
||||
# empty _previous_statuses too, but the manager's stamps are live
|
||||
# and a still-pending notification must not be marked as sent
|
||||
# (bug audit 2026-09-12).
|
||||
seeding = isinstance(nm, NotificationManager) and nm.begin_startup_seed(self.entry.entry_id)
|
||||
for task_id_n, task_result_n in result[CONF_TASKS].items():
|
||||
status = task_result_n.get("_status", MaintenanceStatus.OK)
|
||||
self._previous_statuses[task_id_n] = status
|
||||
if isinstance(nm, NotificationManager) and status in notify_statuses:
|
||||
if seeding and status in notify_statuses:
|
||||
nm.seed_startup_state(self.entry.entry_id, task_id_n, status)
|
||||
else:
|
||||
await self._async_notify_status_changes(result[CONF_TASKS])
|
||||
@@ -714,58 +731,35 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
return
|
||||
|
||||
obj_name = self.maintenance_object.name
|
||||
notify_statuses = {
|
||||
MaintenanceStatus.DUE_SOON,
|
||||
MaintenanceStatus.OVERDUE,
|
||||
MaintenanceStatus.TRIGGERED,
|
||||
}
|
||||
|
||||
# Collect all tasks with notifiable statuses.
|
||||
# The NM's own rate-limiting decides whether to actually send.
|
||||
# Collect the tasks with a notifiable status that pass the per-task
|
||||
# gates of the status kind (status toggle, #173 mute, saved-view
|
||||
# scope, vacation, snooze — helpers.notification_gates). Filtering
|
||||
# HERE, before the bundle-threshold check, matters: a gated task must
|
||||
# neither count toward the threshold nor ride inside the bundle (N-1
|
||||
# silenced tasks must not force one). The manager re-checks on send
|
||||
# and owns the rate limiting. The mute flag lives in the task's
|
||||
# config (entry.data), which the computed task_result (model dict)
|
||||
# drops — so the gate sees config + payload merged.
|
||||
task_configs = self.entry.data.get(CONF_TASKS) or {}
|
||||
notifiable: list[tuple[str, dict[str, Any], str, str | None]] = []
|
||||
for task_id, task_result in task_results.items():
|
||||
new_status = task_result.get("_status")
|
||||
old_status = self._previous_statuses.get(task_id)
|
||||
if new_status in notify_statuses:
|
||||
notifiable.append((task_id, task_result, new_status, old_status))
|
||||
|
||||
# Vacation mode silences some tasks. Filter them out HERE — before the
|
||||
# bundle-threshold check below — so a silenced/exempt task neither
|
||||
# triggers a bundle nor rides inside one. async_send_bundled has no
|
||||
# per-task vacation gate (async_task_status_changed does), and the bundle
|
||||
# COUNT must also exclude them so N-1 silenced tasks don't force a bundle.
|
||||
from .helpers.vacation import get_vacation_state
|
||||
|
||||
_vac = get_vacation_state(self.hass)
|
||||
notifiable = [row for row in notifiable if not _vac.is_silent_for(row[0])]
|
||||
# A status the user switched off, or a task they snoozed, must neither
|
||||
# count toward the bundle threshold nor ride inside the bundle - the
|
||||
# per-task path checks both, the bundle path did not (bug audit
|
||||
# 2026-08-29).
|
||||
notifiable = [
|
||||
row
|
||||
for row in notifiable
|
||||
if nm._is_status_enabled(row[2]) and not nm._is_snoozed(f"{self.entry.entry_id}_{row[0]}_{row[2]}")
|
||||
]
|
||||
# #173: a task muted in its own settings sends nothing — neither on
|
||||
# its own nor inside a bundle. The flag lives in the task's config
|
||||
# (entry.data), which the computed task_result (model dict) drops.
|
||||
task_configs = self.entry.data.get(CONF_TASKS) or {}
|
||||
notifiable = [row for row in notifiable if (task_configs.get(row[0]) or {}).get("notify_enabled") is not False]
|
||||
|
||||
# v2.26 notification routing: a saved-view scope ("only notify about
|
||||
# view X") drops tasks the view's label/user filters don't match —
|
||||
# BEFORE the bundle threshold, like the vacation filter above. A stale
|
||||
# view id (view deleted) means no scope, never "silence everything".
|
||||
from .const import CONF_NOTIFY_SCOPE_VIEW_ID
|
||||
|
||||
scope_view_id = get_global_options(self.hass).get(CONF_NOTIFY_SCOPE_VIEW_ID) or ""
|
||||
if scope_view_id:
|
||||
from .helpers.saved_views import list_saved_views, view_matches_task
|
||||
|
||||
scope = next((v for v in list_saved_views(self.hass) if v["id"] == scope_view_id), None)
|
||||
if scope is not None:
|
||||
notifiable = [row for row in notifiable if view_matches_task(scope["filters"], row[1])]
|
||||
if new_status not in NOTIFIABLE_STATUSES:
|
||||
continue
|
||||
gate = task_may_notify(
|
||||
self.hass,
|
||||
self.entry.entry_id,
|
||||
task_id,
|
||||
new_status,
|
||||
{**(task_configs.get(task_id) or {}), **task_result},
|
||||
kind=KIND_STATUS,
|
||||
manager=nm,
|
||||
)
|
||||
if not gate:
|
||||
_LOGGER.debug("Task %s not notifiable (%s)", task_id, gate.blocked_by)
|
||||
continue
|
||||
notifiable.append((task_id, task_result, new_status, self._previous_statuses.get(task_id)))
|
||||
|
||||
if not notifiable:
|
||||
# No notifications needed — still update the cache
|
||||
@@ -779,9 +773,8 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
CONF_NOTIFICATION_BUNDLING_ENABLED,
|
||||
)
|
||||
|
||||
global_options = get_global_options(self.hass)
|
||||
bundling_enabled = global_options.get(CONF_NOTIFICATION_BUNDLING_ENABLED, False)
|
||||
bundle_threshold = int(global_options.get(CONF_NOTIFICATION_BUNDLE_THRESHOLD, 2))
|
||||
bundling_enabled = global_option(self.hass, CONF_NOTIFICATION_BUNDLING_ENABLED)
|
||||
bundle_threshold = int(global_option(self.hass, CONF_NOTIFICATION_BUNDLE_THRESHOLD))
|
||||
|
||||
if bundling_enabled and len(notifiable) >= bundle_threshold:
|
||||
await nm.async_send_bundled(
|
||||
@@ -798,6 +791,9 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
],
|
||||
)
|
||||
else:
|
||||
# High priority first — with a daily limit the order decides who
|
||||
# gets the last slots (the manager's own reserves do the rest).
|
||||
notifiable.sort(key=lambda item: nm._priority_rank({**(task_configs.get(item[0]) or {}), **item[1]}))
|
||||
for task_id, task_result, new_status, _old_status in notifiable:
|
||||
await nm.async_task_status_changed(
|
||||
entry_id=self.entry.entry_id,
|
||||
@@ -842,19 +838,19 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
if not isinstance(nm, NotificationManager) or not nm.enabled:
|
||||
return
|
||||
|
||||
global_options = get_global_options(self.hass)
|
||||
if not global_options.get(CONF_BUDGET_ALERTS_ENABLED, False):
|
||||
if not global_option(self.hass, CONF_BUDGET_ALERTS_ENABLED):
|
||||
return
|
||||
|
||||
threshold_pct = int(global_options.get(CONF_BUDGET_ALERT_THRESHOLD, 80)) / 100.0
|
||||
monthly_budget = float(global_options.get(CONF_BUDGET_MONTHLY, 0))
|
||||
yearly_budget = float(global_options.get(CONF_BUDGET_YEARLY, 0))
|
||||
threshold_pct = int(global_option(self.hass, CONF_BUDGET_ALERT_THRESHOLD)) / 100.0
|
||||
monthly_budget = float(global_option(self.hass, CONF_BUDGET_MONTHLY))
|
||||
yearly_budget = float(global_option(self.hass, CONF_BUDGET_YEARLY))
|
||||
|
||||
if monthly_budget <= 0 and yearly_budget <= 0:
|
||||
return
|
||||
|
||||
currency_code = str(global_options.get(CONF_BUDGET_CURRENCY, DEFAULT_BUDGET_CURRENCY))
|
||||
currency_symbol = BUDGET_CURRENCIES.get(currency_code, "€")
|
||||
currency_code = str(global_option(self.hass, CONF_BUDGET_CURRENCY))
|
||||
currency_symbol = BUDGET_CURRENCIES.get(currency_code, BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY])
|
||||
decimals = int(global_option(self.hass, CONF_CURRENCY_DECIMALS))
|
||||
|
||||
# Use cached budget totals (recalculate if stale or missing)
|
||||
cache: dict[str, Any] | None = self.hass.data.get(DOMAIN, {}).get(BUDGET_CACHE_KEY)
|
||||
@@ -878,11 +874,11 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
# Check monthly
|
||||
if monthly_budget > 0 and monthly_spent >= monthly_budget * threshold_pct:
|
||||
await nm.async_budget_alert("monthly", monthly_spent, monthly_budget, currency_symbol)
|
||||
await nm.async_budget_alert("monthly", monthly_spent, monthly_budget, currency_symbol, decimals=decimals)
|
||||
|
||||
# Check yearly
|
||||
if yearly_budget > 0 and yearly_spent >= yearly_budget * threshold_pct:
|
||||
await nm.async_budget_alert("yearly", yearly_spent, yearly_budget, currency_symbol)
|
||||
await nm.async_budget_alert("yearly", yearly_spent, yearly_budget, currency_symbol, decimals=decimals)
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
@@ -896,18 +892,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
lp = td.get("last_performed")
|
||||
if lp is not None:
|
||||
self._store.set_last_performed(task_id, lp)
|
||||
lpd = td.get("last_planned_due")
|
||||
state = self._store._ensure_task(task_id)
|
||||
if lpd is not None:
|
||||
state["last_planned_due"] = lpd
|
||||
elif "last_planned_due" in state:
|
||||
del state["last_planned_due"]
|
||||
# Per-occurrence postpone (set by async_postpone_task, cleared on complete).
|
||||
do = td.get("due_override")
|
||||
if do is not None:
|
||||
state["due_override"] = do
|
||||
elif "due_override" in state:
|
||||
del state["due_override"]
|
||||
# The cycle modifiers: the drift-free planned anchor and the
|
||||
# per-occurrence postpone (set by async_postpone_task, cleared on
|
||||
# complete) — None removes the key.
|
||||
self._store.update_task_state(task_id, last_planned_due=td.get("last_planned_due"), due_override=td.get("due_override"))
|
||||
# Phase cursor (#139): to_dict emits it only for phase-carrying tasks.
|
||||
pc = td.get("phase_cursor")
|
||||
if pc is not None:
|
||||
@@ -978,9 +966,15 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
unattended: bool = False,
|
||||
completed_at: datetime | None = None,
|
||||
tag_verified: bool = False,
|
||||
source: str | None = None,
|
||||
) -> None:
|
||||
"""Mark a task as completed and persist.
|
||||
|
||||
``source`` (#173 follow-up) names the surface: panel, qr, nfc, button,
|
||||
todo, voice, notification_action, shopping_list, service,
|
||||
auto_recovery — carried by the completed event and by the optional
|
||||
completion notification (``notify_completed``).
|
||||
|
||||
``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
|
||||
@@ -1082,17 +1076,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
# panel or a voice command record a completion on a task nobody can
|
||||
# see any more - rotation advanced, parts consumed. Same gate here.
|
||||
if not auto:
|
||||
_td = merged[task_id]
|
||||
if (
|
||||
_td.get("archived_at") is not None
|
||||
or _td.get("enabled") is False
|
||||
or self.entry.data.get(CONF_OBJECT, {}).get("paused_at") is not None
|
||||
):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="task_inactive",
|
||||
translation_placeholders={"task_name": str(_td.get("name", task_id))},
|
||||
)
|
||||
self._require_active(task_id, merged[task_id], translation_key="task_inactive")
|
||||
|
||||
# Completion window (earliest_completion_days) - at the choke point like
|
||||
# every other rule. The WS / to-do / voice surfaces pre-check it for a
|
||||
@@ -1179,11 +1163,11 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
# must not run for it (bug audit 2026-08-29) - an explicit used_parts
|
||||
# selection is still honoured. Mirrors MaintenanceTask.complete's
|
||||
# is_latest split (ISO-date string compare).
|
||||
_is_backfill = (
|
||||
completed_at is not None
|
||||
and bool(task.last_performed)
|
||||
and effective_ts.date().isoformat() < str(task.last_performed)
|
||||
)
|
||||
# The very same split MaintenanceTask.complete makes (timestamp
|
||||
# anchors, not the bare date): a same-day completion dated BEFORE the
|
||||
# latest one recorded "used parts" in history while the stock stayed
|
||||
# untouched (bug audit 2026-09-12).
|
||||
_is_backfill = completed_at is not None and not task.would_be_latest(effective_ts)
|
||||
record_links = (
|
||||
used_parts
|
||||
if used_parts is not None
|
||||
@@ -1258,7 +1242,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
# Inject hemisphere + month/date of the EFFECTIVE completion
|
||||
# moment for seasonal awareness (a completion logged today but
|
||||
# performed in March belongs to March).
|
||||
task.adaptive_config["hemisphere"] = "south" if (self.hass.config.latitude or 0) < 0 else "north"
|
||||
task.adaptive_config["hemisphere"] = hemisphere(self.hass)
|
||||
task.adaptive_config["_current_month"] = effective_ts.month
|
||||
task.adaptive_config["_current_date"] = effective_ts.date().isoformat()
|
||||
try:
|
||||
@@ -1269,6 +1253,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
try:
|
||||
await self._persist_and_signal_task_change(task_id, task)
|
||||
self._completion_cooldown.add(task_id)
|
||||
except Exception:
|
||||
# The completion did not land on disk - release the double-tap
|
||||
# guard stamped above, or the user's retry within 30 s would be
|
||||
@@ -1323,6 +1308,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
_LOGGER.debug("Maintenance completed: %s on %s", task.name, self.maintenance_object.name)
|
||||
|
||||
# #173 follow-up: which surface completed it (see COMPLETION_SOURCES);
|
||||
# a trigger recovery names itself.
|
||||
effective_source = source or ("auto_recovery" if auto else None)
|
||||
|
||||
# Fire event after persistence — power users wire HA automations on
|
||||
# this; the integration's own action_listener also subscribes here
|
||||
# to dispatch the per-task on_complete_action service-call.
|
||||
@@ -1336,6 +1325,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
duration=duration,
|
||||
feedback=feedback,
|
||||
completed_by=completed_by,
|
||||
source=effective_source,
|
||||
# Recorded readings (#83 scalar / #161 phase 2 slots) so an
|
||||
# automation can forward a meter value without reading history.
|
||||
reading_value=reading_value,
|
||||
@@ -1350,6 +1340,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
backfill=not is_latest,
|
||||
),
|
||||
)
|
||||
# #173 follow-up: the opt-in completion notification (household news).
|
||||
# A backfill is bookkeeping, not news.
|
||||
if is_latest:
|
||||
await self._async_notify_completed(task_id, task, effective_source, completed_by, effective_ts.isoformat())
|
||||
|
||||
async def async_auto_complete_on_recovery(self, task_id: str, trigger_value: float) -> None:
|
||||
"""Record a completion because the task's trigger cleared itself (#53).
|
||||
@@ -1365,21 +1359,16 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
task_data = merged.get(task_id)
|
||||
if task_data is None:
|
||||
return
|
||||
# Inert tasks never auto-complete.
|
||||
if task_data.get("archived_at") is not None or task_data.get("enabled") is False:
|
||||
return
|
||||
# A paused object fires nothing — the periodic evaluator gates on this,
|
||||
# but this event-driven recovery path must too (else a paused object
|
||||
# whose sensor recovers records a real completion, defeating the pause).
|
||||
if self.entry.data.get(CONF_OBJECT, {}).get("paused_at") is not None:
|
||||
# Inert tasks never auto-complete. A paused object fires nothing — the
|
||||
# periodic evaluator gates on this, but this event-driven recovery
|
||||
# path must too (else a paused object whose sensor recovers records a
|
||||
# real completion, defeating the pause). Silent, unlike the manual gates.
|
||||
if self._is_inert(task_data):
|
||||
return
|
||||
# Race guard: if a completion was recorded moments ago (e.g. a manual
|
||||
# complete whose trigger reset crossed paths with a queued state
|
||||
# change), don't record a second one.
|
||||
history = task_data.get("history") or []
|
||||
for entry in reversed(history):
|
||||
if entry.get("type") != "completed":
|
||||
continue
|
||||
for entry in reversed(completed_entries(task_data.get("history"))):
|
||||
# parse_persisted_utc, NOT dt_util.parse_datetime: history can
|
||||
# hold NAIVE timestamps (the history-edit dialog sends
|
||||
# datetime-local without an offset), and `aware - naive` raised
|
||||
@@ -1416,14 +1405,18 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
date: date | None = None,
|
||||
) -> None:
|
||||
"""Reset the last performed date of a task."""
|
||||
# A reset starts a new cycle — a following completion is a NEW
|
||||
# real-world action, never a double-tap of the previous one.
|
||||
self._recent_manual_completions.pop(task_id, None)
|
||||
self._clear_notification_state(task_id)
|
||||
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
|
||||
# Same gate as complete/skip: a retired / disabled / paused task gets
|
||||
# no new cycle from a stale notification button or an old NFC sticker.
|
||||
self._require_active(task_id, merged[task_id], translation_key="task_inactive_reset")
|
||||
|
||||
# A reset starts a new cycle — a following completion is a NEW
|
||||
# real-world action, never a double-tap of the previous one.
|
||||
self._recent_manual_completions.pop(task_id, None)
|
||||
self._clear_notification_state(task_id)
|
||||
|
||||
task = MaintenanceTask.from_dict(merged[task_id])
|
||||
task.reset(reset_date=date)
|
||||
@@ -1449,11 +1442,32 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
if task_id not in merged:
|
||||
_LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title)
|
||||
return
|
||||
self._require_active(task_id, merged[task_id], translation_key="task_inactive_postpone")
|
||||
task = MaintenanceTask.from_dict(merged[task_id])
|
||||
task.due_override = until.isoformat()
|
||||
await self._persist_and_signal_task_change(task_id, task)
|
||||
_LOGGER.debug("Occurrence postponed to %s: %s on %s", until, task.name, self.maintenance_object.name)
|
||||
|
||||
def _is_inert(self, task_data: dict[str, Any]) -> bool:
|
||||
"""Archived / disabled task, or a paused object (helpers.pause.is_task_inert)."""
|
||||
return is_task_inert(task_data, self.entry.data.get(CONF_OBJECT, {}))
|
||||
|
||||
def _require_active(self, task_id: str, task_data: dict[str, Any], *, translation_key: str) -> None:
|
||||
"""Refuse a lifecycle action on an inert task with a translated error.
|
||||
|
||||
The one gate behind complete / skip / reset / postpone — a stale
|
||||
notification button, an old NFC sticker or a voice command must not
|
||||
touch a retired, disabled or paused task's cycle. ``translation_key``
|
||||
picks the wording (``task_inactive`` / ``task_inactive_skip`` /
|
||||
``task_inactive_reset`` / ``task_inactive_postpone``).
|
||||
"""
|
||||
if self._is_inert(task_data):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key=translation_key,
|
||||
translation_placeholders={"task_name": str(task_data.get("name", task_id))},
|
||||
)
|
||||
|
||||
def _clear_notification_state(self, task_id: str) -> None:
|
||||
"""Forget the per-status notification bookkeeping for a task.
|
||||
|
||||
@@ -1488,17 +1502,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
# Same gate as complete_maintenance: a retired / disabled / paused
|
||||
# task must not get a new cycle from a stale notification button or
|
||||
# an old NFC sticker (bug review 2026-09-04).
|
||||
_td = merged[task_id]
|
||||
if (
|
||||
_td.get("archived_at") is not None
|
||||
or _td.get("enabled") is False
|
||||
or self.entry.data.get(CONF_OBJECT, {}).get("paused_at") is not None
|
||||
):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="task_inactive_skip",
|
||||
translation_placeholders={"task_name": str(_td.get("name", task_id))},
|
||||
)
|
||||
self._require_active(task_id, merged[task_id], translation_key="task_inactive_skip")
|
||||
|
||||
# Only now that the skip is going to happen: like reset, skipping
|
||||
# restarts the cycle — clear the double-tap window so a follow-up
|
||||
@@ -1526,7 +1530,27 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
)
|
||||
|
||||
|
||||
def note_trigger_edge(self, task_id: str) -> None:
|
||||
async def _async_notify_completed(self, task_id: str, task: MaintenanceTask, source: str | None, completed_by: str | None, completed_at: str) -> None:
|
||||
from .helpers.notification_manager import NotificationManager
|
||||
|
||||
nm = self.hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY)
|
||||
if not isinstance(nm, NotificationManager):
|
||||
return
|
||||
try:
|
||||
await nm.async_task_completed(
|
||||
entry_id=self.entry.entry_id,
|
||||
task_id=task_id,
|
||||
task_name=task.name,
|
||||
object_name=self.maintenance_object.name,
|
||||
source=source,
|
||||
completed_by=completed_by,
|
||||
completed_at=completed_at,
|
||||
task_data=(self.entry.data.get(CONF_TASKS) or {}).get(task_id),
|
||||
)
|
||||
except Exception: # noqa: BLE001 - a notification must never fail the completion
|
||||
_LOGGER.debug("Completion notification failed for %s", task_id, exc_info=True)
|
||||
|
||||
def note_trigger_edge(self, task_id: str, *, recovered: bool = True) -> None:
|
||||
"""A trigger just latched on a REAL state edge — lift the post-completion
|
||||
cooldown for that task.
|
||||
|
||||
@@ -1537,7 +1561,13 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
this, the refresh a flip now requests (#175) — and before that, any
|
||||
5-minute tick inside the window — wiped the fresh latch again.
|
||||
"""
|
||||
# A re-activation right after a COMPLETION while the sensor never left
|
||||
# the triggering side (the user tapped Complete before refilling) is
|
||||
# not an edge; after a skip/reset it is (bug audit 2026-09-12).
|
||||
if not recovered and task_id in self._completion_cooldown:
|
||||
return
|
||||
self._recently_completed.pop(task_id, None)
|
||||
self._completion_cooldown.discard(task_id)
|
||||
|
||||
async def async_refresh_now(self) -> None:
|
||||
"""Recompute immediately — for changes a person just made.
|
||||
@@ -1576,6 +1606,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self._persist_dynamic_state(task_id, task)
|
||||
await self._store.async_save() # Flush immediately for user actions
|
||||
self._recently_completed[task_id] = time.monotonic()
|
||||
self._completion_cooldown.discard(task_id) # complete_maintenance re-adds it
|
||||
async_dispatcher_send(
|
||||
self.hass,
|
||||
SIGNAL_TASK_RESET.format(entry_id=self.entry.entry_id, task_id=task_id),
|
||||
|
||||
@@ -5,13 +5,13 @@ from __future__ import annotations
|
||||
import logging
|
||||
import math
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Coroutine
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, State, callback
|
||||
from homeassistant.helpers.event import (
|
||||
EventStateChangedData,
|
||||
async_call_later,
|
||||
async_track_state_change_event,
|
||||
)
|
||||
|
||||
@@ -24,11 +24,21 @@ from ...const import (
|
||||
EVENT_TRIGGER_DEACTIVATED,
|
||||
UNAVAILABLE_STATES,
|
||||
)
|
||||
from ...helpers.managed_timer import ManagedTimer
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# The initial-evaluation retry: an entity that is unknown/unavailable at
|
||||
# setup is re-checked this often, this many times, before the state-change
|
||||
# listener alone is trusted to catch its recovery (issue #1 family).
|
||||
RETRY_DELAY_SECONDS = 30.0
|
||||
RETRY_MAX_ATTEMPTS = 10
|
||||
|
||||
|
||||
class BaseTrigger(ABC):
|
||||
# Class-level default: subclasses built without __init__ in tests still
|
||||
# carry the flag (see the instance comment in __init__).
|
||||
_recovered_since_reset: bool = True
|
||||
"""Base class for all maintenance triggers."""
|
||||
|
||||
def __init__(
|
||||
@@ -45,9 +55,18 @@ class BaseTrigger(ABC):
|
||||
self.attribute = trigger_config.get("attribute")
|
||||
|
||||
self._triggered = False
|
||||
# Bug audit 2026-09-12: after a completion resets the trigger, the
|
||||
# sensor may still read beyond the threshold (the user tapped Complete
|
||||
# before refilling). Such a re-activation is NOT a new edge and must
|
||||
# not lift the coordinator's post-completion cooldown - only an
|
||||
# activation after the value was seen on the other side is.
|
||||
self._recovered_since_reset = True
|
||||
self._current_value: float | None = None
|
||||
self._unsub_listener: CALLBACK_TYPE | None = None
|
||||
self._unsub_retry: CALLBACK_TYPE | None = None
|
||||
# One timer per purpose (a retry and a subclass's for/hold window can
|
||||
# be pending at the same time); the tasks a trigger spawns (history
|
||||
# entry, refresh, persist) are tracked here and cancelled at teardown.
|
||||
self._retry_timer = ManagedTimer(hass, f"{type(self).__name__}:{self.entity_id}:retry")
|
||||
self._logged_unavailable = False # Log-once pattern for unavailable
|
||||
|
||||
@property
|
||||
@@ -108,42 +127,47 @@ class BaseTrigger(ABC):
|
||||
)
|
||||
|
||||
def _schedule_retry(self) -> None:
|
||||
"""Schedule a retry of the initial evaluation after 30 seconds."""
|
||||
self._cancel_retry()
|
||||
"""Schedule a retry of the initial evaluation (30 s, capped).
|
||||
|
||||
@callback
|
||||
def _retry_initial_evaluation(_now: datetime) -> None:
|
||||
"""Re-check entity state after a delay."""
|
||||
self._unsub_retry = None
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
if state is None or state.state in UNAVAILABLE_STATES:
|
||||
_LOGGER.debug(
|
||||
"Trigger entity %s still %s after retry",
|
||||
self.entity_id,
|
||||
state.state if state else "missing",
|
||||
)
|
||||
return
|
||||
value = self._get_numeric_value(state)
|
||||
if value is not None:
|
||||
self._current_value = value
|
||||
self._evaluate_and_update(value)
|
||||
_LOGGER.info(
|
||||
"Trigger entity %s recovered after retry (value=%s)",
|
||||
self.entity_id,
|
||||
value,
|
||||
)
|
||||
Before the ManagedTimer this was a single hard-coded shot: an entity
|
||||
still unavailable 30 s after setup was never re-checked by the timer
|
||||
again (the state-change listener catches a LATER recovery, but not
|
||||
one that happened while we were not looking). Now it re-arms until
|
||||
the entity reports or the budget is spent.
|
||||
"""
|
||||
self._retry_timer.retry(self._retry_initial_evaluation, delay=RETRY_DELAY_SECONDS, max_attempts=RETRY_MAX_ATTEMPTS)
|
||||
|
||||
self._unsub_retry = async_call_later(self.hass, 30, _retry_initial_evaluation)
|
||||
@callback
|
||||
def _retry_initial_evaluation(self, _now: datetime) -> None:
|
||||
"""Re-check entity state after a delay."""
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
if state is None or state.state in UNAVAILABLE_STATES:
|
||||
_LOGGER.debug(
|
||||
"Trigger entity %s still %s after retry",
|
||||
self.entity_id,
|
||||
state.state if state else "missing",
|
||||
)
|
||||
self._schedule_retry()
|
||||
return
|
||||
self._retry_timer.reset_retries()
|
||||
value = self._get_numeric_value(state)
|
||||
if value is not None:
|
||||
self._current_value = value
|
||||
self._evaluate_and_update(value)
|
||||
_LOGGER.info(
|
||||
"Trigger entity %s recovered after retry (value=%s)",
|
||||
self.entity_id,
|
||||
value,
|
||||
)
|
||||
|
||||
def _cancel_retry(self) -> None:
|
||||
"""Cancel pending retry timer."""
|
||||
if self._unsub_retry is not None:
|
||||
self._unsub_retry()
|
||||
self._unsub_retry = None
|
||||
def _track(self, coro: Coroutine[Any, Any, Any], *, cancel_on_close: bool = True) -> None:
|
||||
"""Spawn a fire-and-forget task the trigger owns (cancelled at
|
||||
teardown unless it must land regardless — see the auto-complete)."""
|
||||
self._retry_timer.track_task(coro, cancel_on_close=cancel_on_close)
|
||||
|
||||
async def async_teardown(self) -> None:
|
||||
"""Remove the trigger listener."""
|
||||
self._cancel_retry()
|
||||
"""Remove the trigger listener, the retry timer and owned tasks."""
|
||||
self._retry_timer.close()
|
||||
if self._unsub_listener is not None:
|
||||
self._unsub_listener()
|
||||
self._unsub_listener = None
|
||||
@@ -206,6 +230,8 @@ class BaseTrigger(ABC):
|
||||
was_triggered = self._triggered
|
||||
is_triggered = self.evaluate(value)
|
||||
self._triggered = is_triggered
|
||||
if not is_triggered:
|
||||
self._recovered_since_reset = True
|
||||
|
||||
if is_triggered and not was_triggered:
|
||||
self._on_trigger_activated(value)
|
||||
@@ -229,7 +255,7 @@ class BaseTrigger(ABC):
|
||||
full update interval — 30 s once, 4 min the next time. Debounced on
|
||||
purpose (HA's ten-second window): a noisy sensor must not recompute the
|
||||
object on every state change; user actions use async_refresh_now."""
|
||||
self.hass.async_create_task(self._coordinator.async_request_refresh())
|
||||
self._track(self._coordinator.async_request_refresh())
|
||||
|
||||
def _on_trigger_activated(self, value: float) -> None:
|
||||
"""Handle trigger activation."""
|
||||
@@ -248,8 +274,8 @@ class BaseTrigger(ABC):
|
||||
)
|
||||
|
||||
# Add history entry for the trigger activation
|
||||
self.hass.async_create_task(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=value))
|
||||
self._coordinator.note_trigger_edge(self._task_id)
|
||||
self._track(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=value))
|
||||
self._coordinator.note_trigger_edge(self._task_id, recovered=self._recovered_since_reset)
|
||||
self._request_coordinator_refresh()
|
||||
|
||||
# Fire event
|
||||
@@ -301,7 +327,9 @@ class BaseTrigger(ABC):
|
||||
# resets the trigger via reset(), which never lands here, so the
|
||||
# manual flow cannot double-record.
|
||||
if self.config.get("auto_complete_on_recovery"):
|
||||
self.hass.async_create_task(self._coordinator.async_auto_complete_on_recovery(self._task_id, value))
|
||||
# cancel_on_close=False: a completion in flight must land even
|
||||
# when the recovery coincides with a reload of the entry.
|
||||
self._track(self._coordinator.async_auto_complete_on_recovery(self._task_id, value), cancel_on_close=False)
|
||||
|
||||
def _get_numeric_value(self, state: State) -> float | None:
|
||||
"""Extract numeric value from state or attribute."""
|
||||
@@ -323,3 +351,4 @@ class BaseTrigger(ABC):
|
||||
def reset(self) -> None:
|
||||
"""Reset the trigger (called after maintenance completion)."""
|
||||
self._triggered = False
|
||||
self._recovered_since_reset = False
|
||||
|
||||
@@ -247,8 +247,8 @@ class CompoundTrigger(BaseTrigger):
|
||||
current_value=None,
|
||||
trigger_entity_id=None,
|
||||
)
|
||||
self.hass.async_create_task(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=None))
|
||||
self._coordinator.note_trigger_edge(self._task_id)
|
||||
self._track(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=None))
|
||||
self._coordinator.note_trigger_edge(self._task_id, recovered=self._recovered_since_reset)
|
||||
self._request_coordinator_refresh()
|
||||
self.hass.bus.async_fire(
|
||||
EVENT_TRIGGER_ACTIVATED,
|
||||
|
||||
@@ -17,14 +17,14 @@ import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, State, callback
|
||||
from homeassistant.core import Event, HomeAssistant, State, callback
|
||||
from homeassistant.helpers.event import (
|
||||
EventStateChangedData,
|
||||
async_track_state_change_event,
|
||||
async_track_time_interval,
|
||||
)
|
||||
|
||||
from ...const import UNAVAILABLE_STATES
|
||||
from ...helpers.managed_timer import ManagedTimer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...sensor import MaintenanceSensor
|
||||
@@ -88,7 +88,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
else:
|
||||
self._on_states = _DEFAULT_ON_STATES
|
||||
|
||||
self._unsub_periodic: CALLBACK_TYPE | None = None
|
||||
self._periodic_timer = ManagedTimer(hass, f"RuntimeTrigger:{self.entity_id}:persist")
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
"""Set up runtime trigger with state restoration."""
|
||||
@@ -160,17 +160,11 @@ class RuntimeTrigger(BaseTrigger):
|
||||
|
||||
def _start_periodic_timer(self) -> None:
|
||||
"""Start the periodic persistence timer."""
|
||||
self._unsub_periodic = async_track_time_interval(
|
||||
self.hass,
|
||||
self._periodic_callback,
|
||||
_PERSIST_INTERVAL,
|
||||
)
|
||||
self._periodic_timer.schedule_interval(_PERSIST_INTERVAL, self._periodic_callback)
|
||||
|
||||
async def async_teardown(self) -> None:
|
||||
"""Remove listeners and periodic timer."""
|
||||
if self._unsub_periodic is not None:
|
||||
self._unsub_periodic()
|
||||
self._unsub_periodic = None
|
||||
self._periodic_timer.close()
|
||||
await super().async_teardown()
|
||||
|
||||
@callback
|
||||
@@ -199,7 +193,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._on_since_dt = now
|
||||
self._on_since = now.isoformat()
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._persist_runtime_soon()
|
||||
elif not self._is_on(new_val) and self._on_since_dt is not None:
|
||||
# Restored anchor but the device APPEARS off (deferred setup
|
||||
# kept the anchor, then the first real state is OFF). Without
|
||||
@@ -211,7 +205,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._persist_runtime_soon()
|
||||
self._update_evaluation()
|
||||
return
|
||||
|
||||
@@ -224,7 +218,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._persist_runtime_soon()
|
||||
if not self._logged_unavailable:
|
||||
_LOGGER.warning(
|
||||
"Runtime trigger entity %s became %s (runtime paused)",
|
||||
@@ -252,7 +246,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._persist_runtime_soon()
|
||||
_LOGGER.debug(
|
||||
"Runtime trigger: %s turned OFF (accumulated=%.2fh)",
|
||||
self.entity_id,
|
||||
@@ -264,7 +258,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._on_since_dt = now
|
||||
self._on_since = now.isoformat()
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._persist_runtime_soon()
|
||||
_LOGGER.debug(
|
||||
"Runtime trigger: %s turned ON (tracking started)",
|
||||
self.entity_id,
|
||||
@@ -278,7 +272,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._persist_runtime_soon()
|
||||
|
||||
self._update_evaluation()
|
||||
|
||||
@@ -343,7 +337,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._accumulate_elapsed(now)
|
||||
self._on_since_dt = now
|
||||
self._on_since = now.isoformat()
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._persist_runtime_soon()
|
||||
|
||||
# Re-evaluate (runtime may have crossed threshold)
|
||||
self._update_evaluation()
|
||||
@@ -354,6 +348,11 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._accumulated_seconds / 3600.0,
|
||||
)
|
||||
|
||||
def _persist_runtime_soon(self) -> None:
|
||||
"""Fire-and-forget persist, owned by the trigger (cancelled at teardown;
|
||||
the Store write itself happens synchronously at task start)."""
|
||||
self._track(self._persist_runtime())
|
||||
|
||||
async def _persist_runtime(self) -> None:
|
||||
"""Persist accumulated runtime and on_since to the Store."""
|
||||
data: dict[str, Any] = {
|
||||
@@ -382,7 +381,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
now = dt_util.utcnow()
|
||||
self._on_since_dt = now
|
||||
self._on_since = now.isoformat()
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._persist_runtime_soon()
|
||||
_LOGGER.debug(
|
||||
"Runtime trigger reset: %s (accumulated hours cleared)",
|
||||
self.entity_id,
|
||||
|
||||
@@ -6,15 +6,15 @@ import logging
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers.event import (
|
||||
EventStateChangedData,
|
||||
async_call_later,
|
||||
async_track_state_change_event,
|
||||
)
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from ...const import UNAVAILABLE_STATES
|
||||
from ...helpers.managed_timer import ManagedTimer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...sensor import MaintenanceSensor
|
||||
@@ -44,7 +44,6 @@ class StateChangeTrigger(BaseTrigger):
|
||||
_pending_state: str | None = None
|
||||
_pending_since: str | None = None
|
||||
_interrupted_pending: str | None = None
|
||||
_timer_cancel: CALLBACK_TYPE | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -77,7 +76,7 @@ class StateChangeTrigger(BaseTrigger):
|
||||
# sensors glitching for seconds at night) and the cycle counter
|
||||
# (a flicker is not a wash cycle).
|
||||
self._for_minutes: int = int(trigger_config.get("trigger_for_minutes", 0) or 0)
|
||||
self._timer_cancel: CALLBACK_TYPE | None = None
|
||||
self._hold_timer = ManagedTimer(hass, f"StateChangeTrigger:{self.entity_id}:hold")
|
||||
# A window cut short by an unavailability blip — the only case that
|
||||
# may re-open on recovery (see _handle_state_transition).
|
||||
self._interrupted_pending: str | None = None
|
||||
@@ -225,53 +224,45 @@ class StateChangeTrigger(BaseTrigger):
|
||||
|
||||
def _start_pending(self, new_val: str) -> None:
|
||||
"""(Re)open the hold window for *new_val* — commits when the timer fires."""
|
||||
self._cancel_timer()
|
||||
self._hold_timer.cancel()
|
||||
self._pending_state = new_val
|
||||
self._pending_since = dt_util.utcnow().isoformat()
|
||||
self._persist_runtime_soon()
|
||||
self._start_hold_timer()
|
||||
|
||||
def _start_hold_timer(self, remaining_seconds: float | None = None) -> None:
|
||||
self._cancel_timer()
|
||||
duration = remaining_seconds if remaining_seconds is not None else self._for_minutes * 60
|
||||
self._hold_timer.schedule(duration, self._hold_timer_fired)
|
||||
|
||||
@callback
|
||||
def _timer_fired(_now: datetime) -> None:
|
||||
pending = self._pending_state
|
||||
self._pending_state = None
|
||||
self._pending_since = None
|
||||
self._timer_cancel = None
|
||||
if pending is None:
|
||||
return
|
||||
# Safety net: only commit while the state still holds.
|
||||
live = self.hass.states.get(self.entity_id)
|
||||
if live is None or _norm_state(live.state) != _norm_state(pending):
|
||||
self._persist_runtime_soon()
|
||||
return
|
||||
_LOGGER.debug(
|
||||
"State hold timer fired: %s held %r for %d min",
|
||||
self.entity_id,
|
||||
pending,
|
||||
self._for_minutes,
|
||||
)
|
||||
self._commit_transition(pending, None)
|
||||
|
||||
self._timer_cancel = async_call_later(self.hass, duration, _timer_fired)
|
||||
@callback
|
||||
def _hold_timer_fired(self, _now: datetime) -> None:
|
||||
pending = self._pending_state
|
||||
self._pending_state = None
|
||||
self._pending_since = None
|
||||
if pending is None:
|
||||
return
|
||||
# Safety net: only commit while the state still holds.
|
||||
live = self.hass.states.get(self.entity_id)
|
||||
if live is None or _norm_state(live.state) != _norm_state(pending):
|
||||
self._persist_runtime_soon()
|
||||
return
|
||||
_LOGGER.debug(
|
||||
"State hold timer fired: %s held %r for %d min",
|
||||
self.entity_id,
|
||||
pending,
|
||||
self._for_minutes,
|
||||
)
|
||||
self._commit_transition(pending, None)
|
||||
|
||||
def _clear_pending(self) -> None:
|
||||
"""Abandon the hold window (state moved on before it elapsed)."""
|
||||
if self._pending_state is None and self._timer_cancel is None:
|
||||
if self._pending_state is None and not self._hold_timer.pending:
|
||||
return
|
||||
self._cancel_timer()
|
||||
self._hold_timer.cancel()
|
||||
self._pending_state = None
|
||||
self._pending_since = None
|
||||
self._persist_runtime_soon()
|
||||
|
||||
def _cancel_timer(self) -> None:
|
||||
if self._timer_cancel is not None:
|
||||
self._timer_cancel()
|
||||
self._timer_cancel = None
|
||||
|
||||
def _commit_transition(self, new_val: str, old_val: str | None) -> None:
|
||||
"""Count one matching transition (immediately, or after its hold)."""
|
||||
self._pending_state = None
|
||||
@@ -442,7 +433,7 @@ class StateChangeTrigger(BaseTrigger):
|
||||
|
||||
def _persist_runtime_soon(self) -> None:
|
||||
if self.hass.is_running:
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._track(self._persist_runtime())
|
||||
|
||||
async def _persist_runtime(self) -> None:
|
||||
"""Persist the full runtime dict (count + hold window) to the Store.
|
||||
@@ -462,13 +453,13 @@ class StateChangeTrigger(BaseTrigger):
|
||||
|
||||
async def async_teardown(self) -> None:
|
||||
"""Clean up the hold timer on teardown."""
|
||||
self._cancel_timer()
|
||||
self._hold_timer.close()
|
||||
await super().async_teardown()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset trigger, counter and any running hold window."""
|
||||
super().reset()
|
||||
self._cancel_timer()
|
||||
self._hold_timer.cancel()
|
||||
self._pending_state = None
|
||||
self._pending_since = None
|
||||
self._interrupted_pending = None
|
||||
|
||||
@@ -6,13 +6,13 @@ import logging
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...sensor import MaintenanceSensor
|
||||
|
||||
from ...helpers.managed_timer import ManagedTimer
|
||||
from ...helpers.trigger_fallback import threshold_exceeds
|
||||
from .base_trigger import BaseTrigger
|
||||
|
||||
@@ -45,7 +45,7 @@ class ThresholdTrigger(BaseTrigger):
|
||||
self._for_minutes: int = trigger_config.get("trigger_for_minutes", 0)
|
||||
|
||||
self._threshold_exceeded = False
|
||||
self._timer_cancel: CALLBACK_TYPE | None = None
|
||||
self._for_timer = ManagedTimer(hass, f"ThresholdTrigger:{self.entity_id}:for")
|
||||
|
||||
# Restore persisted exceeded-since timestamp (survives HA restarts)
|
||||
from ...helpers.dates import parse_persisted_utc
|
||||
@@ -99,7 +99,7 @@ class ThresholdTrigger(BaseTrigger):
|
||||
self._exceeded_since = dt_util.utcnow().isoformat()
|
||||
self._exceeded_since_dt = None
|
||||
if self.hass.is_running:
|
||||
self.hass.async_create_task(self._persist_exceeded_since())
|
||||
self._track(self._persist_exceeded_since())
|
||||
self._start_for_timer()
|
||||
return False
|
||||
# Timer running or already triggered
|
||||
@@ -111,9 +111,9 @@ class ThresholdTrigger(BaseTrigger):
|
||||
self._exceeded_since = None
|
||||
self._exceeded_since_dt = None
|
||||
if self.hass.is_running:
|
||||
self.hass.async_create_task(self._persist_exceeded_since())
|
||||
self._track(self._persist_exceeded_since())
|
||||
self._threshold_exceeded = False
|
||||
self._cancel_timer()
|
||||
self._for_timer.cancel()
|
||||
return False
|
||||
|
||||
def _start_for_timer(self, remaining_seconds: float | None = None) -> None:
|
||||
@@ -124,45 +124,37 @@ class ThresholdTrigger(BaseTrigger):
|
||||
The exceeded-since timestamp is persisted so the timer survives HA
|
||||
restarts.
|
||||
"""
|
||||
self._cancel_timer()
|
||||
duration = remaining_seconds if remaining_seconds is not None else self._for_minutes * 60
|
||||
self._for_timer.schedule(duration, self._for_timer_fired)
|
||||
|
||||
@callback
|
||||
def _timer_fired(_now: datetime) -> None:
|
||||
"""Handle timer completion."""
|
||||
# Safety net (mirrors the state_change hold timer): only commit
|
||||
# while the premise still HOLDS. _threshold_exceeded is cleared
|
||||
# only by a numeric in-range reading, so a sensor that went
|
||||
# unavailable right after crossing kept it True and the timer
|
||||
# activated on a value nobody had observed for the whole window
|
||||
# (bug audit 2026-08-22). Discard the window entirely — a bare
|
||||
# return would leave the latch set and evaluate() would swallow
|
||||
# every future exceeding reading; the next one re-arms fresh.
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
live = self._get_numeric_value(state) if state is not None else None
|
||||
if live is None or not self._value_exceeds_threshold(live):
|
||||
self._threshold_exceeded = False
|
||||
self._exceeded_since = None
|
||||
self._exceeded_since_dt = None
|
||||
if self.hass.is_running:
|
||||
self.hass.async_create_task(self._persist_exceeded_since())
|
||||
return
|
||||
if self._threshold_exceeded:
|
||||
_LOGGER.debug(
|
||||
"Threshold for-timer fired: %s (%d min)",
|
||||
self.entity_id,
|
||||
self._for_minutes,
|
||||
)
|
||||
self._triggered = True
|
||||
self._on_trigger_activated(self._current_value or 0.0)
|
||||
|
||||
self._timer_cancel = async_call_later(self.hass, duration, _timer_fired)
|
||||
|
||||
def _cancel_timer(self) -> None:
|
||||
"""Cancel the for-duration timer."""
|
||||
if self._timer_cancel is not None:
|
||||
self._timer_cancel()
|
||||
self._timer_cancel = None
|
||||
@callback
|
||||
def _for_timer_fired(self, _now: datetime) -> None:
|
||||
"""Handle for-timer completion."""
|
||||
# Safety net (mirrors the state_change hold timer): only commit
|
||||
# while the premise still HOLDS. _threshold_exceeded is cleared
|
||||
# only by a numeric in-range reading, so a sensor that went
|
||||
# unavailable right after crossing kept it True and the timer
|
||||
# activated on a value nobody had observed for the whole window
|
||||
# (bug audit 2026-08-22). Discard the window entirely — a bare
|
||||
# return would leave the latch set and evaluate() would swallow
|
||||
# every future exceeding reading; the next one re-arms fresh.
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
live = self._get_numeric_value(state) if state is not None else None
|
||||
if live is None or not self._value_exceeds_threshold(live):
|
||||
self._threshold_exceeded = False
|
||||
self._exceeded_since = None
|
||||
self._exceeded_since_dt = None
|
||||
if self.hass.is_running:
|
||||
self._track(self._persist_exceeded_since())
|
||||
return
|
||||
if self._threshold_exceeded:
|
||||
_LOGGER.debug(
|
||||
"Threshold for-timer fired: %s (%d min)",
|
||||
self.entity_id,
|
||||
self._for_minutes,
|
||||
)
|
||||
self._triggered = True
|
||||
self._on_trigger_activated(self._current_value or 0.0)
|
||||
|
||||
async def _persist_exceeded_since(self) -> None:
|
||||
"""Persist exceeded-since timestamp for survival across restarts."""
|
||||
@@ -174,7 +166,7 @@ class ThresholdTrigger(BaseTrigger):
|
||||
|
||||
async def async_teardown(self) -> None:
|
||||
"""Clean up timer on teardown."""
|
||||
self._cancel_timer()
|
||||
self._for_timer.close()
|
||||
await super().async_teardown()
|
||||
|
||||
def reset(self) -> None:
|
||||
@@ -184,5 +176,5 @@ class ThresholdTrigger(BaseTrigger):
|
||||
self._exceeded_since = None
|
||||
self._exceeded_since_dt = None
|
||||
if self.hass.is_running:
|
||||
self.hass.async_create_task(self._persist_exceeded_since())
|
||||
self._cancel_timer()
|
||||
self._track(self._persist_exceeded_since())
|
||||
self._for_timer.cancel()
|
||||
|
||||
@@ -24,6 +24,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
)
|
||||
from .helpers.aggregate import get_object_entries, merged_tasks
|
||||
from .helpers.documents import doc_wire_dict
|
||||
from .helpers.schedule import Schedule, read_legacy_fields
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -41,37 +42,10 @@ def _export_documents(doc_store: Any, object_id: str) -> list[dict[str, Any]]:
|
||||
``id`` rides along for the same reason: history entries (completion
|
||||
photos) and spare parts (``doc_id``) point at documents by id, and the
|
||||
importer mints fresh ids — without the old one it could not remap them.
|
||||
The record itself is ``helpers.documents.doc_wire_dict`` — shared with
|
||||
the documents ZIP archive so the two exports cannot drift.
|
||||
"""
|
||||
out: list[dict[str, Any]] = []
|
||||
for d in doc_store.for_object(object_id):
|
||||
if d.get("kind") == "weblink":
|
||||
out.append(
|
||||
{
|
||||
"id": d.get("id"),
|
||||
"kind": "weblink",
|
||||
"url": d.get("url"),
|
||||
"title": d.get("title"),
|
||||
"tags": d.get("tags") or [],
|
||||
"task_ids": d.get("task_ids") or [],
|
||||
"part_ids": d.get("part_ids") or [],
|
||||
}
|
||||
)
|
||||
else:
|
||||
out.append(
|
||||
{
|
||||
"id": d.get("id"),
|
||||
"kind": "file",
|
||||
"hash": d.get("hash"),
|
||||
"title": d.get("title"),
|
||||
"filename": d.get("filename"),
|
||||
"mime": d.get("mime"),
|
||||
"size": d.get("size"),
|
||||
"tags": d.get("tags") or [],
|
||||
"task_ids": d.get("task_ids") or [],
|
||||
"part_ids": d.get("part_ids") or [],
|
||||
}
|
||||
)
|
||||
return out
|
||||
return [doc_wire_dict(d, include_id=True) for d in doc_store.for_object(object_id)]
|
||||
|
||||
|
||||
def _build_export_object(
|
||||
|
||||
@@ -65,7 +65,7 @@ export function obj(entryId: string, tasks: unknown[], name = "Pool Pump") {
|
||||
export async function mountPanel(
|
||||
objects: unknown[],
|
||||
extraHandlers: Record<string, WsHandler> = {},
|
||||
opts: { user?: { id: string; is_admin: boolean } } = {},
|
||||
opts: { user?: { id: string; is_admin: boolean }; embedded?: boolean; presets?: { tab?: string; view?: string } } = {},
|
||||
) {
|
||||
const { hass, sent, subscriptions } = createMockHass({
|
||||
handlers: {
|
||||
@@ -90,6 +90,8 @@ export async function mountPanel(
|
||||
const el = await fixture<HTMLElement & { updateComplete: Promise<unknown> }>(html`
|
||||
<maintenance-supporter-panel
|
||||
.hass=${hass}
|
||||
.embedded=${opts.embedded ?? false}
|
||||
.presets=${opts.presets ?? {}}
|
||||
style="display:block; height: 600px;"
|
||||
></maintenance-supporter-panel>
|
||||
`);
|
||||
|
||||
+2
-2
@@ -362,7 +362,7 @@ describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => {
|
||||
});
|
||||
const c = chip(el)!;
|
||||
expect(c, "suggestion chip rendered").to.exist;
|
||||
expect(c.textContent).to.include("17.00");
|
||||
expect(c.textContent).to.include("17 €");
|
||||
expect(c.textContent).to.include("€");
|
||||
c.click();
|
||||
await el.updateComplete;
|
||||
@@ -375,7 +375,7 @@ describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => {
|
||||
|
||||
it("buy task: restock qty x unit cost, follows the qty field", async () => {
|
||||
const el = await mountWithParts({ restockDefault: 2, restockUnitCost: 4.5 });
|
||||
expect(chip(el)!.textContent).to.include("9.00");
|
||||
expect(chip(el)!.textContent).to.include("9");
|
||||
});
|
||||
|
||||
it("no chip when no involved part carries a price", async () => {
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"_comment": "Shared examples for the tolerant matcher (#171): __tests__/search-match.test.ts AND tests/test_search_match.py run every case here, so the panel's local groups and the server's document/history groups agree on what matches. Scores are named (EXACT/PREFIX/SUBSTRING/FUZZY/NONE) — each side maps them to its constants.",
|
||||
"fold_length_preserving": ["Kühlschrank", "Straße", "Ærø", "ÉCOLE", "naïve café", "Łódź"],
|
||||
"fold": [["Kühlschrank", "kuhlschrank"], ["Straße", "strase"], ["ÉCOLE", "ecole"], ["Łódź", "lodz"]],
|
||||
"words": [["Fehler-Code E-24, Zulauf!", ["fehler", "code", "e", "24", "zulauf"]]],
|
||||
"compact": [["E-24 / WM14T5", "e24wm14t5"]],
|
||||
"query_tokens": [
|
||||
["Spuel-Maschine", [["spuel", "spul"], ["maschine"]]],
|
||||
["Strasse", [["strasse", "strase"]]],
|
||||
["Bauer", [["bauer", "baur"]]],
|
||||
[" ", []]
|
||||
],
|
||||
"within_one_edit": [
|
||||
["abc", "abc", true],
|
||||
["reinigen", "reinigne", true],
|
||||
["filter", "fiter", true],
|
||||
["filter", "fillter", true],
|
||||
["filter", "filtar", true],
|
||||
["filter", "flitre", false],
|
||||
["abc", "abcde", false]
|
||||
],
|
||||
"word_score": [
|
||||
[["filter"], "filter", "EXACT"],
|
||||
[["filt"], "filter", "PREFIX"],
|
||||
[["leitung"], "bedienungsanleitung", "SUBSTRING"],
|
||||
[["reinigne"], "reinigen", "FUZZY"],
|
||||
[["reinigugn"], "reinigungsmittel", "FUZZY"],
|
||||
[["er"], "filter", "NONE"],
|
||||
[["filt"], "fitler", "NONE"]
|
||||
],
|
||||
"field_score": [
|
||||
[["kuhl"], "Kühlschrank", "PREFIX"],
|
||||
[["spul"], "Spülmaschine", "PREFIX"],
|
||||
[["e24"], "Fehlercode E-24", "SUBSTRING"]
|
||||
],
|
||||
"score_fields": {
|
||||
"fields": [["Filter reinigen", 3], ["Spülmaschine", 2]],
|
||||
"hits": ["spülm filt", "spuel filter"],
|
||||
"misses": ["spülm garten"],
|
||||
"order_independent": [["filt spülm", "spülm filt"]],
|
||||
"name_outranks_notes": {
|
||||
"query": "filter",
|
||||
"in_name": [["Filter", 3], ["", 1]],
|
||||
"in_notes": [["Pumpe", 3], ["Filter wechseln", 1]]
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -36,8 +36,8 @@ describe("budget KPI tiles: spent-only display (#104)", () => {
|
||||
}));
|
||||
const tiles = sr(el).querySelectorAll(".budget-tile");
|
||||
expect(tiles.length).to.equal(2);
|
||||
expect(tiles[0].textContent).to.contain("9.00 €");
|
||||
expect(tiles[1].textContent).to.contain("429.60 €");
|
||||
expect(tiles[0].textContent).to.contain("9 €");
|
||||
expect(tiles[1].textContent).to.contain("430 €");
|
||||
expect(sr(el).querySelectorAll(".budget-tile-bar").length).to.equal(0);
|
||||
});
|
||||
|
||||
@@ -51,9 +51,9 @@ describe("budget KPI tiles: spent-only display (#104)", () => {
|
||||
// 2026-08-24: the spent amount carries the full stat-value typography
|
||||
// (same size as the other KPI chips); the "/ max" ratio is its own
|
||||
// small line so it can't overflow the grid cell.
|
||||
expect(tiles[0].querySelector(".stat-value")!.textContent).to.contain("9.00 €");
|
||||
expect(tiles[0].querySelector(".stat-value")!.textContent).to.contain("9 €");
|
||||
expect(tiles[0].querySelector(".budget-tile-max")!.textContent).to.contain("/ 150 €");
|
||||
expect(tiles[1].textContent).to.contain("429.60 €");
|
||||
expect(tiles[1].textContent).to.contain("430 €");
|
||||
expect(tiles[1].querySelector(".budget-tile-max"), "spent-only tile has no ratio line").to.equal(null);
|
||||
});
|
||||
|
||||
|
||||
+5
-3
@@ -166,9 +166,11 @@ describe("panel deep links (QR scan routing)", () => {
|
||||
);
|
||||
await settleRaf(el);
|
||||
await settleRaf(el);
|
||||
expect((el as unknown as { _toastMessage: string })._toastMessage).to.equal(
|
||||
"Earliest completion is 2026-09-01",
|
||||
);
|
||||
// The known code gives the localized headline; the server's detail
|
||||
// (which says more than the code) follows in parentheses.
|
||||
const toast = (el as unknown as { _toastMessage: string })._toastMessage;
|
||||
expect(toast).to.match(/^Too early — /);
|
||||
expect(toast).to.include("(Earliest completion is 2026-09-01)");
|
||||
const dlg = completeDialog(el);
|
||||
expect(dlg?.shadowRoot?.querySelector("ha-dialog") ?? null, "no dialog").to.be.null;
|
||||
});
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ describe("parts-section", () => {
|
||||
await el.updateComplete;
|
||||
const chip = el.shadowRoot!.querySelector(".inventory-value")!;
|
||||
expect(chip, "value chip rendered").to.exist;
|
||||
expect(chip.textContent).to.include("37.50");
|
||||
expect(chip.textContent).to.include("38");
|
||||
expect(chip.textContent).to.include("€");
|
||||
});
|
||||
|
||||
|
||||
+183
-109
@@ -41,12 +41,19 @@ interface BatteryRow {
|
||||
no_sensor?: boolean;
|
||||
/** D#162 follow-up: the note has a replaced button → per-row Replaced action. */
|
||||
can_mark_replaced?: boolean;
|
||||
/** D#162 follow-up: which type lifetime the typical forecast used. */
|
||||
lifetime_months?: number | null;
|
||||
lifetime_source?: "override" | "learned_device" | "learned_model" | "table" | "default" | null;
|
||||
lifetime_samples?: number;
|
||||
/** ISO date the forecast anchors on (Battery Notes' last replaced). */
|
||||
last_replaced?: string | null;
|
||||
}
|
||||
interface RosterRow extends BatteryRow {
|
||||
status: "low" | "soon" | "ok";
|
||||
}
|
||||
/** What the shared row template takes: a low/soon row, or a roster row
|
||||
* (which adds the status). */
|
||||
type FleetRow = BatteryRow & { status?: RosterRow["status"] };
|
||||
/** 30 d downsampled level history per battery, for the roster sparklines.
|
||||
* threshold = the same low threshold the trend forecast regresses toward,
|
||||
* so the dotted projection ends exactly where the ~date comes from.
|
||||
@@ -252,8 +259,37 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
|
||||
/** Lazy: the recorder-backed history is fetched once, when the roster is
|
||||
* first expanded — most panel visits never open it. */
|
||||
/** D#162: the roster is open unless this browser collapsed it before. */
|
||||
private _rosterOpen(): boolean {
|
||||
return lsGet(LS_KEYS.batteryRosterOpen) !== "0";
|
||||
}
|
||||
|
||||
/** The ~date tooltip: trend (with confidence) or the type lifetime with
|
||||
* where it came from — "18 months typical for CR2032 (learned from 5
|
||||
* replacements)" (D#162 follow-up). */
|
||||
private _predictedTitle(b: BatteryRow, L: string): string {
|
||||
if (b.forecast_overdue) return t("battery_fleet_forecast_overdue", L);
|
||||
const date = this._predictedDate(b.days_until ?? 0);
|
||||
if (b.predicted_source === "trend") {
|
||||
return t("battery_fleet_predicted_trend", L)
|
||||
.replace("{date}", date)
|
||||
.replace("{confidence}", t("cal_confidence_" + (b.prediction_confidence || "medium"), L));
|
||||
}
|
||||
if (b.lifetime_months != null && b.lifetime_source) {
|
||||
const source = t("lifetime_source_" + b.lifetime_source, L).replace("{n}", String(b.lifetime_samples ?? 0));
|
||||
return t("battery_fleet_predicted_typical", L)
|
||||
.replace("{date}", date)
|
||||
.replace("{months}", String(b.lifetime_months))
|
||||
.replace("{type}", b.battery_type)
|
||||
.replace("{source}", source);
|
||||
}
|
||||
return t("battery_fleet_predicted_on", L).replace("{date}", date);
|
||||
}
|
||||
|
||||
private _loadHistory = async (e: Event): Promise<void> => {
|
||||
if (!(e.target as HTMLDetailsElement).open || this._historyRequested) return;
|
||||
const open = (e.target as HTMLDetailsElement).open;
|
||||
lsSet(LS_KEYS.batteryRosterOpen, open ? "1" : "0");
|
||||
if (!open || this._historyRequested) return;
|
||||
this._historyRequested = true;
|
||||
try {
|
||||
const res = await this.hass.connection.sendMessagePromise<{ series: HistorySeries }>({
|
||||
@@ -269,7 +305,7 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
* where the ~date comes from the discharge trend — a dotted projection
|
||||
* from the last reading down to the threshold, so the date is visible
|
||||
* instead of merely stated. */
|
||||
private _sparkline(b: RosterRow) {
|
||||
private _sparkline(b: FleetRow) {
|
||||
const h = this._history?.[b.entity_id];
|
||||
if (!h || h.points.length < 2) return nothing;
|
||||
const W = 110, H = 24, P = 2;
|
||||
@@ -406,6 +442,106 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
></span>`;
|
||||
}
|
||||
|
||||
/** The "record this replacement" chip: the level history shows an upward
|
||||
* jump nobody recorded in Battery Notes (roster only). */
|
||||
private _jumpButton(b: BatteryRow, L: string) {
|
||||
const jump = this._history?.[b.entity_id]?.jump;
|
||||
if (!jump || this._recorded.includes(b.entity_id)) return nothing;
|
||||
return html`<button
|
||||
class="bf-mark bf-jump"
|
||||
title=${t("battery_fleet_record_replacement", L).replace("{date}", this._fmtDate(jump.at * 1000))}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._recordJump(b.entity_id, jump)}
|
||||
>
|
||||
<ha-icon icon="mdi:calendar-sync"></ha-icon>
|
||||
</button>`;
|
||||
}
|
||||
|
||||
/** ONE row template for the three lists (low / needed soon / roster).
|
||||
* They were hand-copied and had drifted: only the low list said
|
||||
* "recharged" on a rechargeable's mark action and only it showed the
|
||||
* offline hint (DRY audit 2026-09-12). `o` names the per-list extras.
|
||||
*
|
||||
* DOM order is load-bearing for the grid CSS: `.bf-status + .bf-type +
|
||||
* .bf-offline` (the roster's chip sits AFTER the type, the low/soon
|
||||
* lists' BEFORE it), `.bf-level + .bf-mark.bf-replaced`, and the phone
|
||||
* layout's column assignments — keep it. */
|
||||
private _renderRow(
|
||||
b: FleetRow,
|
||||
L: string,
|
||||
o: {
|
||||
/** Roster: status chip in column 2, the sensor chip after the type. */
|
||||
status?: boolean;
|
||||
/** Rechargeable badge (low list + roster). */
|
||||
recharge?: boolean;
|
||||
/** 30 d level sparkline (roster). */
|
||||
sparkline?: boolean;
|
||||
/** "always": every row gets the mark action (low list); "replaced":
|
||||
* only a sensorless row or a note with a replaced button. */
|
||||
mark: "always" | "replaced";
|
||||
jump?: boolean;
|
||||
predicted?: boolean;
|
||||
exclude?: boolean;
|
||||
},
|
||||
) {
|
||||
const chip =
|
||||
b.available === false
|
||||
? html`<span class="bf-offline">${t("battery_fleet_offline", L)}</span>`
|
||||
: b.no_sensor
|
||||
? html`<span class="bf-offline bf-nosensor">${t("battery_fleet_no_sensor", L)}</span>`
|
||||
: nothing;
|
||||
const type = html`<span class="bf-type">${b.quantity}× ${b.battery_type}</span>`;
|
||||
const showMark = o.mark === "always" || b.no_sensor || b.can_mark_replaced;
|
||||
return html`
|
||||
<div class="bf-row">
|
||||
<span class="bf-dev">${b.device_name}</span>
|
||||
${o.status
|
||||
? html`<span class="bf-status bf-${b.status}"
|
||||
>${b.no_sensor && b.status === "low"
|
||||
? t("battery_fleet_status_due", L)
|
||||
: t("battery_fleet_status_" + b.status, L)}</span
|
||||
>${type}${chip}`
|
||||
: html`${chip}${type}`}
|
||||
${o.recharge && b.rechargeable
|
||||
? html`<span class="bf-recharge" title=${t("battery_fleet_rechargeable", L)}
|
||||
><ha-icon icon="mdi:battery-charging-outline"></ha-icon
|
||||
></span>`
|
||||
: nothing}
|
||||
${o.sparkline ? this._sparkline(b) : nothing}
|
||||
${this._levelBar(b)}
|
||||
${b.level != null ? html`<span class="bf-level">${b.level}%</span>` : nothing}
|
||||
${showMark
|
||||
? html`<button
|
||||
class="bf-mark${o.mark === "replaced" ? " bf-replaced" : ""}"
|
||||
title=${b.rechargeable ? t("battery_fleet_mark_recharged", L) : t("battery_fleet_mark_one", L)}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._mark([b.entity_id])}
|
||||
>
|
||||
<ha-icon icon="mdi:battery-sync"></ha-icon>
|
||||
</button>`
|
||||
: nothing}
|
||||
${o.jump ? this._jumpButton(b, L) : nothing}
|
||||
${o.predicted && b.days_until != null
|
||||
? html`<span
|
||||
class="bf-predicted ${b.predicted_source === "trend" ? "bf-trend" : ""} ${b.forecast_overdue ? "bf-overdue" : ""}"
|
||||
title=${this._predictedTitle(b, L)}
|
||||
>${b.forecast_overdue ? html`<ha-icon icon="mdi:calendar-alert"></ha-icon>` : nothing}~${this._predictedDate(b.days_until)}</span
|
||||
>`
|
||||
: nothing}
|
||||
${o.exclude
|
||||
? html`<button
|
||||
class="bf-mark bf-exclude"
|
||||
title=${t("battery_fleet_exclude", L)}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._setExcluded(b.entity_id, true)}
|
||||
>
|
||||
<ha-icon icon="mdi:eye-off-outline"></ha-icon>
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const L = this._lang;
|
||||
if (this._loading && this._ov === null) return html`<div class="bf-card"><div class="bf-loading">…</div></div>`;
|
||||
@@ -442,42 +578,7 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
<span class="bf-list">${this._shoppingLine(ov.needs_now)}</span>
|
||||
</div>
|
||||
<div class="bf-rows">
|
||||
${ov.low.map(
|
||||
(b) => html`
|
||||
<div class="bf-row">
|
||||
<span class="bf-dev">${b.device_name}</span>
|
||||
${b.available === false
|
||||
? html`<span class="bf-offline">${t("battery_fleet_offline", L)}</span>`
|
||||
: b.no_sensor
|
||||
? html`<span class="bf-offline bf-nosensor">${t("battery_fleet_no_sensor", L)}</span>`
|
||||
: nothing}
|
||||
<span class="bf-type">${b.quantity}× ${b.battery_type}</span>
|
||||
${b.rechargeable
|
||||
? html`<span class="bf-recharge" title=${t("battery_fleet_rechargeable", L)}
|
||||
><ha-icon icon="mdi:battery-charging-outline"></ha-icon
|
||||
></span>`
|
||||
: nothing}
|
||||
${this._levelBar(b)}
|
||||
${b.level != null ? html`<span class="bf-level">${b.level}%</span>` : nothing}
|
||||
<button
|
||||
class="bf-mark"
|
||||
title=${b.rechargeable ? t("battery_fleet_mark_recharged", L) : t("battery_fleet_mark_one", L)}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._mark([b.entity_id])}
|
||||
>
|
||||
<ha-icon icon="mdi:battery-sync"></ha-icon>
|
||||
</button>
|
||||
<button
|
||||
class="bf-mark bf-exclude"
|
||||
title=${t("battery_fleet_exclude", L)}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._setExcluded(b.entity_id, true)}
|
||||
>
|
||||
<ha-icon icon="mdi:eye-off-outline"></ha-icon>
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
${ov.low.map((b) => this._renderRow(b, L, { recharge: true, mark: "always", exclude: true }))}
|
||||
</div>
|
||||
<div class="bf-actions">
|
||||
<ha-button .disabled=${this._marking} @click=${this._markAll}>
|
||||
@@ -493,11 +594,14 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
<span class="bf-list">${this._shoppingLine(ov.needs_soon)}</span>
|
||||
<div class="bf-soon-hint">${t("battery_fleet_soon_hint", L)}</div>
|
||||
</div>
|
||||
<div class="bf-rows bf-soon-rows">
|
||||
${ov.soon.map((b) => this._renderRow(b, L, { mark: "replaced", predicted: true }))}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${ov.all?.length
|
||||
? html`
|
||||
<details class="bf-roster" @toggle=${this._loadHistory}>
|
||||
<details class="bf-roster" ?open=${this._rosterOpen()} @toggle=${this._loadHistory}>
|
||||
<summary>${t("battery_fleet_all", L)} (${ov.all.length})</summary>
|
||||
<div class="bf-roster-tools">
|
||||
<button
|
||||
@@ -514,72 +618,8 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
</button>
|
||||
</div>
|
||||
<div class="bf-rows">
|
||||
${this._sortedRoster(ov.all).map(
|
||||
(b) => html`
|
||||
<div class="bf-row">
|
||||
<span class="bf-dev">${b.device_name}</span>
|
||||
<span class="bf-status bf-${b.status}"
|
||||
>${b.no_sensor && b.status === "low"
|
||||
? t("battery_fleet_status_due", L)
|
||||
: t("battery_fleet_status_" + b.status, L)}</span
|
||||
>
|
||||
<span class="bf-type">${b.quantity}× ${b.battery_type}</span>
|
||||
${b.no_sensor
|
||||
? html`<span class="bf-offline bf-nosensor">${t("battery_fleet_no_sensor", L)}</span>`
|
||||
: nothing}
|
||||
${b.rechargeable
|
||||
? html`<span class="bf-recharge" title=${t("battery_fleet_rechargeable", L)}
|
||||
><ha-icon icon="mdi:battery-charging-outline"></ha-icon
|
||||
></span>`
|
||||
: nothing}
|
||||
${this._sparkline(b)}
|
||||
${this._levelBar(b)}
|
||||
${b.level != null ? html`<span class="bf-level">${b.level}%</span>` : nothing}
|
||||
${b.no_sensor || b.can_mark_replaced
|
||||
? html`<button
|
||||
class="bf-mark bf-replaced"
|
||||
title=${t("battery_fleet_mark_one", L)}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._mark([b.entity_id])}
|
||||
>
|
||||
<ha-icon icon="mdi:battery-sync"></ha-icon>
|
||||
</button>`
|
||||
: nothing}
|
||||
${(() => {
|
||||
const jump = this._history?.[b.entity_id]?.jump;
|
||||
if (!jump || this._recorded.includes(b.entity_id)) return nothing;
|
||||
return html`<button
|
||||
class="bf-mark bf-jump"
|
||||
title=${t("battery_fleet_record_replacement", L).replace("{date}", this._fmtDate(jump.at * 1000))}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._recordJump(b.entity_id, jump)}
|
||||
>
|
||||
<ha-icon icon="mdi:calendar-sync"></ha-icon>
|
||||
</button>`;
|
||||
})()}
|
||||
${b.days_until != null
|
||||
? html`<span
|
||||
class="bf-predicted ${b.predicted_source === "trend" ? "bf-trend" : ""} ${b.forecast_overdue ? "bf-overdue" : ""}"
|
||||
title=${b.forecast_overdue
|
||||
? t("battery_fleet_forecast_overdue", L)
|
||||
: b.predicted_source === "trend"
|
||||
? t("battery_fleet_predicted_trend", L)
|
||||
.replace("{date}", this._predictedDate(b.days_until))
|
||||
.replace("{confidence}", t("cal_confidence_" + (b.prediction_confidence || "medium"), L))
|
||||
: t("battery_fleet_predicted_on", L).replace("{date}", this._predictedDate(b.days_until))}
|
||||
>${b.forecast_overdue ? html`<ha-icon icon="mdi:calendar-alert"></ha-icon>` : nothing}~${this._predictedDate(b.days_until)}</span
|
||||
>`
|
||||
: nothing}
|
||||
<button
|
||||
class="bf-mark bf-exclude"
|
||||
title=${t("battery_fleet_exclude", L)}
|
||||
.disabled=${this._marking}
|
||||
@click=${() => this._setExcluded(b.entity_id, true)}
|
||||
>
|
||||
<ha-icon icon="mdi:eye-off-outline"></ha-icon>
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
${this._sortedRoster(ov.all).map((b) =>
|
||||
this._renderRow(b, L, { status: true, recharge: true, sparkline: true, mark: "replaced", jump: true, predicted: true, exclude: true }),
|
||||
)}
|
||||
</div>
|
||||
<div class="bf-roster-hint">${t("battery_fleet_all_hint", L)}</div>
|
||||
@@ -761,9 +801,9 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
.bf-bar {
|
||||
grid-column: 6;
|
||||
}
|
||||
/* D#162: the roster's "No sensor" chip takes the level bar's slot
|
||||
* (such a row has no bar); column 2 stays the status chip's. */
|
||||
.bf-status + .bf-type + .bf-nosensor {
|
||||
/* D#162: the roster's "No sensor" / offline chip takes the level bar's
|
||||
* slot (such a row has no bar); column 2 stays the status chip's. */
|
||||
.bf-status + .bf-type + .bf-offline {
|
||||
grid-column: 6;
|
||||
justify-self: start;
|
||||
white-space: nowrap;
|
||||
@@ -783,6 +823,13 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
grid-column: 7;
|
||||
justify-self: end;
|
||||
}
|
||||
/* D#162 (maisun's iPhone): since 2.79 rows WITH a level offer Replaced
|
||||
* too — level and button shared column 7 and overlapped. With a
|
||||
* percentage present the button takes the row-action column instead. */
|
||||
.bf-row .bf-level + .bf-mark.bf-replaced {
|
||||
grid-column: 8;
|
||||
justify-self: start;
|
||||
}
|
||||
.bf-predicted {
|
||||
grid-column: 9;
|
||||
justify-self: end;
|
||||
@@ -855,11 +902,38 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
||||
.bf-row .bf-mark {
|
||||
grid-row: 2;
|
||||
}
|
||||
/* Phone (D#162, maisun): the percentage belongs to the name line - it
|
||||
* is what you scan for - and the Replaced action to line 2 with type
|
||||
* and date. A row WITH a percentage therefore lifts it to line 1 (the
|
||||
* name yields column 8; in the "Needed soon" list, which has no status
|
||||
* chip, it takes the chip's place at the right edge) and the action
|
||||
* takes the percentage's slot on line 2 - the same slot a sensorless
|
||||
* row uses, so every row's action lines up. Before, the action shared
|
||||
* the slot with the percentage and painted over it. */
|
||||
.bf-row .bf-level {
|
||||
grid-row: 1;
|
||||
grid-column: 8;
|
||||
justify-self: end;
|
||||
}
|
||||
.bf-soon-rows .bf-row .bf-level {
|
||||
grid-column: 9 / 11;
|
||||
}
|
||||
.bf-row:has(.bf-level) .bf-dev {
|
||||
grid-column: 1 / 8;
|
||||
}
|
||||
.bf-soon-rows .bf-row:has(.bf-level) .bf-dev {
|
||||
grid-column: 1 / 9;
|
||||
}
|
||||
.bf-row .bf-level + .bf-mark.bf-replaced {
|
||||
grid-row: 2;
|
||||
grid-column: 7;
|
||||
justify-self: end;
|
||||
}
|
||||
/* The roster's "No sensor" chip would widen the shared bar track
|
||||
* for EVERY row (subgrid) - on phones the missing percentage and
|
||||
* the "Due" status already tell the story, so it yields like the
|
||||
* bar and the sparkline do. */
|
||||
.bf-status + .bf-type + .bf-nosensor {
|
||||
.bf-status + .bf-type + .bf-offline {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-14
@@ -8,7 +8,7 @@
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { t, syncLocaleFromHass, DEFAULT_CURRENCY_SYMBOL, langOf, formatNumber, formatCost } from "../styles";
|
||||
import { t, syncLocaleFromHass, currencySymbolOf, langOf, formatCost, syncCurrencyDecimals} from "../styles";
|
||||
import { registerCustomCard } from "../helpers/register-card";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { sectionCardSharedStyles } from "./section-card-shared-styles";
|
||||
@@ -64,6 +64,7 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
type: "maintenance_supporter/budget_status",
|
||||
});
|
||||
this._status = r;
|
||||
syncCurrencyDecimals(r);
|
||||
this._localMonthly = r.monthly_budget ? String(r.monthly_budget) : "";
|
||||
this._localYearly = r.yearly_budget ? String(r.yearly_budget) : "";
|
||||
this._dirty = false;
|
||||
@@ -107,20 +108,20 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
const L = this._lang;
|
||||
const s = this._status;
|
||||
if (!s) {
|
||||
return html`<ha-card><div class="loading">${t("loading", L) || "Loading…"}</div></ha-card>`;
|
||||
return html`<ha-card><div class="loading">${t("loading", L)}</div></ha-card>`;
|
||||
}
|
||||
const sym = s.currency_symbol || DEFAULT_CURRENCY_SYMBOL;
|
||||
const sym = currencySymbolOf(s);
|
||||
// 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",
|
||||
label: t("budget_monthly", L),
|
||||
spent: s.monthly_spent || 0,
|
||||
budget: s.monthly_budget || 0,
|
||||
},
|
||||
{
|
||||
label: t("budget_yearly", L) || "Yearly",
|
||||
label: t("budget_yearly", L),
|
||||
spent: s.yearly_spent || 0,
|
||||
budget: s.yearly_budget || 0,
|
||||
},
|
||||
@@ -132,7 +133,7 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<span class="emoji">💰</span>
|
||||
<span>${this._config.title || t("settings_budget", L) || "Budget"}</span>
|
||||
<span>${this._config.title || t("settings_budget", L)}</span>
|
||||
</div>
|
||||
<span class="currency">${sym}</span>
|
||||
</div>
|
||||
@@ -148,7 +149,7 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
<div class="track spent-only">
|
||||
<div class="track-label-row">
|
||||
<label>${track.label}</label>
|
||||
<span class="track-numbers ok">${formatCost(track.spent, sym, L, 0)}</span>
|
||||
<span class="track-numbers ok">${formatCost(track.spent, sym, L)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -160,7 +161,14 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
<div class="track-label-row">
|
||||
<label>${track.label}</label>
|
||||
<span class="track-numbers ${warn}">
|
||||
${formatNumber(track.spent, L, 0)} / ${formatCost(track.budget, sym, L, 0)}
|
||||
${
|
||||
// Spent and budget must round alike: the spent half was
|
||||
// pinned to 0 decimals while the budget followed the
|
||||
// "Decimal places for amounts" setting ("65 / 100.00 €";
|
||||
// bug audit 2026-09-12). formatCost without a symbol =
|
||||
// the setting-driven decimals, symbol once at the end.
|
||||
formatCost(track.spent, undefined, L)
|
||||
} / ${formatCost(track.budget, sym, L)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="bar"><div class="bar-fill ${warn}" style="width:${pct}%"></div></div>
|
||||
@@ -172,7 +180,7 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
? html`
|
||||
<div class="inputs-row">
|
||||
<div class="input-field">
|
||||
<label>${t("budget_monthly_set", L) || "Set monthly"}</label>
|
||||
<label>${t("budget_monthly_set", L)}</label>
|
||||
<div class="input-wrap">
|
||||
<input type="number" min="0" step="1"
|
||||
.value=${this._localMonthly}
|
||||
@@ -185,7 +193,7 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-field">
|
||||
<label>${t("budget_yearly_set", L) || "Set yearly"}</label>
|
||||
<label>${t("budget_yearly_set", L)}</label>
|
||||
<div class="input-wrap">
|
||||
<input type="number" min="0" step="1"
|
||||
.value=${this._localYearly}
|
||||
@@ -204,17 +212,17 @@ export class MaintenanceBudgetSectionCard extends LitElement {
|
||||
?disabled=${this._busy || !this._dirty}>
|
||||
<ha-icon icon="${this._dirty ? "mdi:content-save" : "mdi:check"}"></ha-icon>
|
||||
${this._dirty
|
||||
? (t("save", L) || "Save")
|
||||
: (t("saved", L) || "Saved")}
|
||||
? t("save", L)
|
||||
: t("saved", L)}
|
||||
</button>
|
||||
<button class="btn link" @click=${this._onDeepLink}>
|
||||
${t("budget_advanced", L) || "Currency, alerts…"}
|
||||
${t("budget_advanced", L)}
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<button class="btn link" @click=${this._onDeepLink}>
|
||||
${t("budget_open_panel", L) || "Open in panel"}
|
||||
${t("budget_open_panel", L)}
|
||||
</button>
|
||||
`}
|
||||
</div>
|
||||
|
||||
+28
-117
@@ -7,14 +7,11 @@ import { lastReadingBefore, type ReadingHistoryEntry } from "../helpers/reading-
|
||||
import { t, nativeFieldStyles, formatCost, formatNumber } from "../styles";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { partLinkKey, type LinkedPart } from "../helpers/shared-parts";
|
||||
import { isAndroidCompanion } from "../helpers/companion";
|
||||
import { REQUIRED_COMPLETION_LABELS } from "./required-completion-labels";
|
||||
import {
|
||||
MAX_COMPLETION_PHOTOS,
|
||||
discardUploadedPhotos,
|
||||
uploadCompletionPhoto,
|
||||
} from "../helpers/photo-upload";
|
||||
import { PhotoUploadController } from "../helpers/photo-upload-controller";
|
||||
import "./ms-date-field";
|
||||
import "./ms-photo-picker";
|
||||
import { photoPickerStyles } from "./ms-photo-picker";
|
||||
|
||||
export class MaintenanceCompleteDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -76,12 +73,12 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
@state() private _error = "";
|
||||
@state() private _checklistState: Record<string, boolean> = {};
|
||||
@state() private _feedback: string = "needed";
|
||||
/** #161: the photos attached so far, in pick order (preview = object URL). */
|
||||
@state() private _photos: Array<{ id: string; preview: string }> = [];
|
||||
/** Docs uploaded by THIS dialog session; dropped again on Cancel so an
|
||||
* abandoned completion leaves no orphan files behind. */
|
||||
private _uploadedIds: string[] = [];
|
||||
@state() private _photoUploading = false;
|
||||
/** #161: the photos attached so far (uploads of this session are dropped
|
||||
* again on Cancel so an abandoned completion leaves no orphans). */
|
||||
private readonly _photos = new PhotoUploadController(this, {
|
||||
entryId: () => this.entryId,
|
||||
hass: () => this.hass,
|
||||
});
|
||||
@state() private _readingValue = "";
|
||||
/** #161 phase 2: typed text per slot id (parsed on save; "" = unread). */
|
||||
@state() private _readingValues: Record<string, string> = {};
|
||||
@@ -114,10 +111,7 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
.filter(([, done]) => done),
|
||||
);
|
||||
this._feedback = "needed";
|
||||
this._photos.forEach((p) => URL.revokeObjectURL(p.preview));
|
||||
this._photos = [];
|
||||
this._uploadedIds = [];
|
||||
this._photoUploading = false;
|
||||
this._photos.reset();
|
||||
this._readingValue = "";
|
||||
this._readingValues = {};
|
||||
this._restockQty = this.restockDefault !== null ? String(this.restockDefault) : "";
|
||||
@@ -140,57 +134,10 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
this._feedback = value;
|
||||
}
|
||||
|
||||
/** The Android Companion app answers a multi-select with an empty file
|
||||
* list (its chooser ignores the intent's ClipData — #161 follow-up), so
|
||||
* the gallery picker is single-select there and a hint says each pick
|
||||
* is added. Evaluated once per dialog; the host does not change. */
|
||||
private readonly _singlePick = isAndroidCompanion();
|
||||
|
||||
/** #161: both pickers (camera = one shot, gallery = multiple) land here.
|
||||
* Files upload one after another so a slow connection still shows
|
||||
* progress tile by tile; anything beyond the cap is dropped with a
|
||||
* note rather than silently. */
|
||||
private async _onPhotoInput(e: Event): Promise<void> {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const files = Array.from(input.files ?? []);
|
||||
input.value = ""; // allow re-picking the same file
|
||||
if (files.length === 0) return;
|
||||
const room = MAX_COMPLETION_PHOTOS - this._photos.length;
|
||||
const accepted = files.slice(0, Math.max(room, 0));
|
||||
this._photoUploading = true;
|
||||
this._error = "";
|
||||
try {
|
||||
for (const file of accepted) {
|
||||
const id = await uploadCompletionPhoto(this.hass, this.entryId, file);
|
||||
this._uploadedIds = [...this._uploadedIds, id];
|
||||
this._photos = [...this._photos, { id, preview: URL.createObjectURL(file) }];
|
||||
}
|
||||
if (files.length > accepted.length) {
|
||||
this._error = t("photos_limit", this.lang).replace("{max}", String(MAX_COMPLETION_PHOTOS));
|
||||
}
|
||||
} catch (e) {
|
||||
const key = e instanceof Error && e.message === "doc_too_large" ? "doc_too_large" : "doc_upload_failed";
|
||||
this._error = t(key, this.lang);
|
||||
} finally {
|
||||
this._photoUploading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** ✕ on a tile: drop it from the completion AND delete the upload —
|
||||
* the file only ever existed for this dialog session. */
|
||||
private _removePhoto(id: string): void {
|
||||
const gone = this._photos.find((p) => p.id === id);
|
||||
if (gone) URL.revokeObjectURL(gone.preview);
|
||||
this._photos = this._photos.filter((p) => p.id !== id);
|
||||
if (this._uploadedIds.includes(id)) {
|
||||
this._uploadedIds = this._uploadedIds.filter((x) => x !== id);
|
||||
void discardUploadedPhotos(this.hass, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
private async _complete(): Promise<void> {
|
||||
this._loading = true;
|
||||
this._error = "";
|
||||
this._photos.clearError();
|
||||
try {
|
||||
const data: Record<string, unknown> = {
|
||||
type: "maintenance_supporter/task/complete",
|
||||
@@ -212,8 +159,8 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
if (this.adaptiveEnabled) {
|
||||
data.feedback = this._feedback;
|
||||
}
|
||||
if (this._photos.length > 0) {
|
||||
data.photo_doc_ids = this._photos.map((p) => p.id);
|
||||
if (this._photos.photos.length > 0) {
|
||||
data.photo_doc_ids = this._photos.ids;
|
||||
}
|
||||
// Scan fallback: the backend accepts via_tag_scan on task/complete so a
|
||||
// require_tag_scan task can still be finished from the dialog the scan
|
||||
@@ -266,7 +213,7 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
);
|
||||
}
|
||||
await this.hass.connection.sendMessagePromise(data);
|
||||
this._uploadedIds = []; // attached now — Cancel cleanup must not touch them
|
||||
this._photos.markAttached(); // attached now — Cancel cleanup must not touch them
|
||||
this._open = false;
|
||||
this.dispatchEvent(new CustomEvent("task-completed"));
|
||||
} catch (e) {
|
||||
@@ -306,7 +253,7 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
notes: this._notes.trim() !== "",
|
||||
cost: this._cost.trim() !== "",
|
||||
duration: this._duration.trim() !== "",
|
||||
photo: this._photos.length > 0,
|
||||
photo: this._photos.photos.length > 0,
|
||||
// "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
|
||||
@@ -365,11 +312,7 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
|
||||
private _close(): void {
|
||||
this._open = false;
|
||||
if (this._uploadedIds.length > 0) {
|
||||
const orphans = this._uploadedIds;
|
||||
this._uploadedIds = [];
|
||||
void discardUploadedPhotos(this.hass, orphans);
|
||||
}
|
||||
this._photos.discardOrphans();
|
||||
}
|
||||
|
||||
/** Seed the backdate field with the current minute (local, seconds zeroed). */
|
||||
@@ -382,13 +325,14 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
render() {
|
||||
if (!this._open) return html``;
|
||||
const L = this.lang || this.hass?.language || "en";
|
||||
const error = this._error || this._photos.errorText(L);
|
||||
return html`
|
||||
<ha-dialog open @closed=${this._close}>
|
||||
<div class="dialog-title">${t("complete_title", L)}${this.taskName}</div>
|
||||
${this.phaseLabel ? html`<div class="phase-line">${t("phase_current", L)}: ${this.phaseLabel}</div>` : nothing}
|
||||
${this.requireTagScan && !this.viaTagScan ? html`<div class="scan-required-note">${t("require_tag_scan_hint", L)}</div>` : nothing}
|
||||
<div class="content">
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
${error ? html`<div class="error">${error}</div>` : nothing}
|
||||
${this.checklist.length > 0 ? html`
|
||||
<div class="checklist-section">
|
||||
<label class="checklist-label">${t("checklist", L)}</label>
|
||||
@@ -509,35 +453,21 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
</div>
|
||||
<div class="field">
|
||||
<span class="field-label">${t("completion_photos_optional", L)}${this._req("photo")}</span>
|
||||
${this._photos.length > 0
|
||||
${this._photos.photos.length > 0
|
||||
? html`<div class="photo-strip">
|
||||
${this._photos.map((p) => html`
|
||||
${this._photos.photos.map((p) => html`
|
||||
<div class="photo-preview">
|
||||
<img src=${p.preview} alt="" />
|
||||
<button type="button" class="photo-remove" @click=${() => this._removePhoto(p.id)}
|
||||
<button type="button" class="photo-remove" @click=${() => this._photos.remove(p.id)}
|
||||
title="${t("remove", L)}">✕</button>
|
||||
</div>`)}
|
||||
</div>`
|
||||
: nothing}
|
||||
${this._photos.length < MAX_COMPLETION_PHOTOS
|
||||
? html`<div class="photo-pickers">
|
||||
<label class="photo-pick photo-pick-camera">
|
||||
<ha-icon icon="mdi:camera"></ha-icon>
|
||||
<span>${this._photoUploading ? t("uploading", L) : t("doc_camera", L)}</span>
|
||||
<input type="file" accept="image/*" capture="environment"
|
||||
?disabled=${this._photoUploading}
|
||||
@change=${this._onPhotoInput} />
|
||||
</label>
|
||||
<label class="photo-pick photo-pick-gallery">
|
||||
<ha-icon icon="mdi:image-multiple"></ha-icon>
|
||||
<span>${t(this._singlePick ? "choose_photo" : "choose_photos", L)}</span>
|
||||
<input type="file" accept="image/*" ?multiple=${!this._singlePick}
|
||||
?disabled=${this._photoUploading}
|
||||
@change=${this._onPhotoInput} />
|
||||
</label>
|
||||
</div>
|
||||
${this._singlePick ? html`<div class="photo-limit photo-android-hint">${t("photos_android_hint", L)}</div>` : nothing}`
|
||||
: html`<div class="photo-limit">${t("photos_limit", L).replace("{max}", String(MAX_COMPLETION_PHOTOS))}</div>`}
|
||||
${!this._photos.full
|
||||
? html`<ms-photo-picker .lang=${L} .busy=${this._photos.uploading} .remaining=${this._photos.remaining}
|
||||
@files-picked=${(e: CustomEvent<{ files: File[] }>) => this._photos.addFiles(e.detail.files)}
|
||||
></ms-photo-picker>`
|
||||
: html`<div class="photo-limit">${t("photos_limit", L).replace("{max}", String(this._photos.max))}</div>`}
|
||||
</div>
|
||||
${this.adaptiveEnabled ? html`
|
||||
<div class="feedback-section">
|
||||
@@ -577,7 +507,7 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [nativeFieldStyles, css`
|
||||
static styles = [nativeFieldStyles, photoPickerStyles, css`
|
||||
.req-mark {
|
||||
color: var(--error-color, #f44336);
|
||||
margin-left: 2px;
|
||||
@@ -663,19 +593,7 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
font-size: 12px;
|
||||
color: var(--warning-color, #ff9800);
|
||||
}
|
||||
.photo-pick {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border: 1px dashed var(--divider-color);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: var(--secondary-text-color);
|
||||
width: fit-content;
|
||||
}
|
||||
.photo-pick:hover { border-color: var(--primary-color); }
|
||||
/* .photo-pick / .photo-pickers / .photo-android-hint come from photoPickerStyles */
|
||||
/* #163: the backdate moment starts EMPTY (= now); the button seeds the
|
||||
HA date+time picker with the current minute instead of the picker's
|
||||
own 00:00 default, so a backdated completion never lands at midnight
|
||||
@@ -696,15 +614,9 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
--mdc-icon-size: 18px;
|
||||
}
|
||||
.backdate-pick:hover { border-color: var(--primary-color); }
|
||||
.photo-pick input[type="file"] { display: none; }
|
||||
/* #161: several photos per completion — tiles wrap into a strip,
|
||||
the two pickers (camera / gallery) sit underneath while there is
|
||||
room left under the cap. */
|
||||
.photo-pickers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.photo-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -715,7 +627,6 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.photo-android-hint { margin-top: 4px; }
|
||||
.photo-preview {
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
|
||||
+87
-35
@@ -16,8 +16,13 @@ import { downloadUrl } from "../helpers/download";
|
||||
import { downloadSignedDocument, openSignedDocument, signDocumentPath } from "../helpers/document-url";
|
||||
import { formatBytes } from "../helpers/format-bytes";
|
||||
import { docDisplayName, CATEGORIES, CATEGORY_ICONS } from "../helpers/document-categories";
|
||||
import { DOC_FILTER_MIN, filterDocuments } from "../helpers/document-filter";
|
||||
import { DOC_FILTER_MIN, DOC_SORT_MODES, asDocSortMode, filterDocuments, sortDocuments, type DocSortMode } from "../helpers/document-filter";
|
||||
import { LS_KEYS, lsGet, lsSet } from "../helpers/storage-keys";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { isAndroidCompanion } from "../helpers/companion";
|
||||
import { uploadDocument } from "../helpers/photo-upload";
|
||||
import "./ms-photo-picker";
|
||||
import { photoPickerStyles } from "./ms-photo-picker";
|
||||
|
||||
interface MaintenanceDocument {
|
||||
id: string;
|
||||
@@ -28,6 +33,8 @@ interface MaintenanceDocument {
|
||||
mime?: string;
|
||||
size?: number;
|
||||
tags?: string[];
|
||||
/** #164: a free-text note per document. */
|
||||
description?: string;
|
||||
added_at?: string;
|
||||
}
|
||||
|
||||
@@ -52,6 +59,10 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
@state() private _editingId = "";
|
||||
@state() private _editTitle = "";
|
||||
@state() private _editCategory = "manual";
|
||||
@state() private _editDescription = "";
|
||||
@state() private _linkDescription = "";
|
||||
/** #164: list order, remembered per browser. */
|
||||
@state() private _sort: DocSortMode = asDocSortMode(lsGet(LS_KEYS.docSort));
|
||||
@state() private _dragOver = false;
|
||||
|
||||
private _loadedFor: string | null = null;
|
||||
@@ -134,12 +145,10 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
input.value = ""; // let the same file be re-picked
|
||||
}
|
||||
|
||||
private _onCameraInput(e: Event): void {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const files = Array.from(input.files ?? []);
|
||||
if (files.length) void this._uploadFiles(files, "photo");
|
||||
input.value = "";
|
||||
}
|
||||
/** The Android Companion app answers a multi-select with an EMPTY file
|
||||
* list (helpers/companion.ts) — "Upload file" is single-select there.
|
||||
* Evaluated once; the host app does not change. */
|
||||
private readonly _singlePick = isAndroidCompanion();
|
||||
|
||||
private _onDrop(e: DragEvent): void {
|
||||
e.preventDefault();
|
||||
@@ -170,21 +179,18 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
let deduped = 0;
|
||||
let dupInObject = 0;
|
||||
try {
|
||||
// Bulk upload: one refused file (too large, rejected) is noted and
|
||||
// the rest still go up — unlike the photo dialogs, which stop.
|
||||
for (const file of files) {
|
||||
const form = new FormData();
|
||||
form.append("entry_id", this.entryId);
|
||||
form.append("tags", cat);
|
||||
form.append("file", file, file.name);
|
||||
const resp = await fetch("/api/maintenance_supporter/document/upload", {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${this.hass.auth?.data?.access_token ?? ""}` },
|
||||
body: form,
|
||||
});
|
||||
if (!resp.ok) {
|
||||
this._error = resp.status === 413 ? t("doc_too_large", this._lang) : t("doc_upload_failed", this._lang);
|
||||
let doc;
|
||||
try {
|
||||
doc = await uploadDocument(this.hass, this.entryId, file, [cat]);
|
||||
} catch (e) {
|
||||
const key = e instanceof Error ? e.message : "";
|
||||
if (key !== "doc_too_large" && key !== "doc_upload_failed") throw e;
|
||||
this._error = t(key, this._lang);
|
||||
continue;
|
||||
}
|
||||
const doc = (await resp.json()) as { deduped?: boolean; duplicate_in_object?: string | null };
|
||||
if (doc.duplicate_in_object) dupInObject++;
|
||||
else if (doc.deduped) deduped++;
|
||||
}
|
||||
@@ -233,6 +239,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
this._editingId = doc.id;
|
||||
this._editTitle = doc.title || "";
|
||||
this._editCategory = this._category_of(doc);
|
||||
this._editDescription = doc.description || "";
|
||||
this._addingLink = false;
|
||||
this._error = "";
|
||||
}
|
||||
@@ -241,6 +248,11 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
this._editingId = "";
|
||||
}
|
||||
|
||||
private _setSort(raw: string): void {
|
||||
this._sort = asDocSortMode(raw);
|
||||
lsSet(LS_KEYS.docSort, this._sort);
|
||||
}
|
||||
|
||||
private async _saveEdit(doc: MaintenanceDocument): Promise<void> {
|
||||
// Keep any free (non-category) tags; swap in the chosen category.
|
||||
const freeTags = (doc.tags || []).filter(
|
||||
@@ -255,6 +267,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
doc_id: doc.id,
|
||||
title: this._editTitle.trim() || doc.filename || doc.url || "",
|
||||
tags,
|
||||
description: this._editDescription.trim(),
|
||||
});
|
||||
this._editingId = "";
|
||||
await this._load();
|
||||
@@ -294,9 +307,11 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
entry_id: this.entryId,
|
||||
url,
|
||||
title: this._linkTitle.trim() || null,
|
||||
description: this._linkDescription.trim() || null,
|
||||
});
|
||||
this._linkUrl = "";
|
||||
this._linkTitle = "";
|
||||
this._linkDescription = "";
|
||||
this._addingLink = false;
|
||||
await this._load();
|
||||
} catch (e) {
|
||||
@@ -341,19 +356,11 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
>
|
||||
<ha-icon icon="mdi:upload"></ha-icon>
|
||||
${this._busy ? t("doc_uploading", L) : t("doc_upload", L)}
|
||||
<input type="file" multiple hidden ?disabled=${this._busy} @change=${this._onFileInput} />
|
||||
</label>
|
||||
<label
|
||||
class="btn camera-btn ${this._busy ? "disabled" : ""}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label=${t("doc_camera", L)}
|
||||
title=${t("doc_camera", L)}
|
||||
@keydown=${this._labelKeydown}
|
||||
>
|
||||
<ha-icon icon="mdi:camera"></ha-icon>
|
||||
<input type="file" accept="image/*" capture="environment" hidden ?disabled=${this._busy} @change=${this._onCameraInput} />
|
||||
<input type="file" ?multiple=${!this._singlePick} hidden ?disabled=${this._busy} @change=${this._onFileInput} />
|
||||
</label>
|
||||
<ms-photo-picker compact .showGallery=${false} .lang=${L} .disabled=${this._busy}
|
||||
@files-picked=${(e: CustomEvent<{ files: File[] }>) => this._uploadFiles(e.detail.files, "photo")}
|
||||
></ms-photo-picker>
|
||||
<button class="btn" ?disabled=${this._busy} @click=${() => (this._addingLink = !this._addingLink)}>
|
||||
<ha-icon icon="mdi:link-variant"></ha-icon> ${t("doc_add_link", L)}
|
||||
</button>
|
||||
@@ -382,6 +389,14 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
?disabled=${this._busy}
|
||||
@input=${(e: Event) => (this._linkTitle = (e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
class="link-desc"
|
||||
placeholder=${t("doc_description", L)}
|
||||
.value=${this._linkDescription}
|
||||
?disabled=${this._busy}
|
||||
@input=${(e: Event) => (this._linkDescription = (e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
<button class="btn primary" ?disabled=${this._busy || !this._linkUrl.trim()} @click=${this._addLink}>
|
||||
${t("add", L)}
|
||||
</button>
|
||||
@@ -392,6 +407,15 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
|
||||
${this._loaded && this._docs.length >= 2
|
||||
? html`<div class="doc-tools">
|
||||
<ha-icon icon="mdi:sort"></ha-icon>
|
||||
<select class="sort-select" aria-label=${t("doc_sort", L)} .value=${this._sort}
|
||||
@change=${(e: Event) => this._setSort((e.target as HTMLSelectElement).value)}>
|
||||
${DOC_SORT_MODES.map((m) => html`<option value=${m} ?selected=${m === this._sort}>${t(`doc_sort_${m}`, L)}</option>`)}
|
||||
</select>
|
||||
</div>`
|
||||
: nothing}
|
||||
${this._loaded && this._docs.length >= DOC_FILTER_MIN
|
||||
? html`<div class="doc-filter">
|
||||
<ha-icon icon="mdi:magnify"></ha-icon>
|
||||
@@ -404,7 +428,8 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
: this._docs.length === 0
|
||||
? html`<div class="doc-empty">${t("documents_empty", L)}</div>`
|
||||
: (() => {
|
||||
const shown = filterDocuments(this._docs, this._filter);
|
||||
// A query ranks by match; otherwise the remembered order applies.
|
||||
const shown = this._filter.trim() ? filterDocuments(this._docs, this._filter) : sortDocuments(this._docs, this._sort);
|
||||
return shown.length === 0
|
||||
? html`<div class="doc-empty">${t("doc_search_none", L)}</div>`
|
||||
: html`<div class="doc-list">${shown.map((doc) => this._renderDoc(doc, L))}</div>`;
|
||||
@@ -460,6 +485,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
>
|
||||
<div class="doc-title">${docDisplayName(doc)}</div>
|
||||
<div class="doc-meta">${meta}</div>
|
||||
${doc.description ? html`<div class="doc-desc">${doc.description}</div>` : nothing}
|
||||
</div>
|
||||
<div class="doc-row-actions">
|
||||
${isFile
|
||||
@@ -514,6 +540,14 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
)}
|
||||
</select>`
|
||||
: nothing}
|
||||
<input
|
||||
class="edit-desc"
|
||||
type="text"
|
||||
placeholder=${t("doc_description", L)}
|
||||
.value=${this._editDescription}
|
||||
?disabled=${this._busy}
|
||||
@input=${(e: Event) => (this._editDescription = (e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
<button class="icon-btn" title=${t("save", L)} ?disabled=${this._busy || !this._editTitle.trim()} @click=${() => this._saveEdit(doc)}>
|
||||
<ha-icon icon="mdi:check"></ha-icon>
|
||||
</button>
|
||||
@@ -524,7 +558,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
static styles = [photoPickerStyles, css`
|
||||
:host { display: block; margin: 8px 0 4px; }
|
||||
.doc-zone { position: relative; }
|
||||
.doc-zone.drag-over {
|
||||
@@ -538,7 +572,15 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
background: var(--card-background-color, rgba(255, 255, 255, 0.85));
|
||||
}
|
||||
.drop-overlay ha-icon { --mdc-icon-size: 24px; }
|
||||
.camera-btn { padding: 6px 10px; }
|
||||
/* The camera picker (ms-photo-picker, light DOM) wears the toolbar's
|
||||
.btn look instead of the dialogs' dashed tile. */
|
||||
.doc-actions .photo-pick {
|
||||
padding: 6px 10px; gap: 6px; border-style: solid; border-radius: 6px;
|
||||
background: var(--secondary-background-color, rgba(0,0,0,0.06));
|
||||
color: var(--primary-text-color); border-color: var(--divider-color);
|
||||
}
|
||||
.doc-actions .photo-pick ha-icon { --mdc-icon-size: 18px; }
|
||||
.doc-actions .photo-pick.disabled { opacity: 0.5; pointer-events: none; }
|
||||
.doc-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 12px; flex-wrap: wrap;
|
||||
@@ -620,6 +662,16 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
.doc-info:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; }
|
||||
.doc-title { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.doc-meta { font-size: 12px; color: var(--secondary-text-color, #888); }
|
||||
.doc-desc { font-size: 12.5px; color: var(--secondary-text-color, #888); margin-top: 2px; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.doc-tools { display: flex; align-items: center; gap: 6px; margin: 6px 0 2px; color: var(--secondary-text-color, #888); }
|
||||
.doc-tools ha-icon { --mdc-icon-size: 18px; }
|
||||
.sort-select {
|
||||
padding: 4px 6px; border-radius: 6px; font: inherit; font-size: 13px;
|
||||
background: var(--secondary-background-color, rgba(0,0,0,0.06));
|
||||
color: var(--primary-text-color); border: 1px solid var(--divider-color);
|
||||
}
|
||||
.doc-row.editing { flex-wrap: wrap; }
|
||||
.edit-desc, .link-desc { flex: 1 1 100%; min-width: 0; }
|
||||
.doc-row-actions { display: flex; gap: 4px; flex: none; }
|
||||
.icon-btn {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
@@ -631,7 +683,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
||||
.icon-btn.danger { color: var(--error-color, #f44336); }
|
||||
.icon-btn[disabled] { opacity: 0.4; pointer-events: none; }
|
||||
.icon-btn ha-icon { --mdc-icon-size: 20px; }
|
||||
`;
|
||||
`];
|
||||
}
|
||||
|
||||
if (!customElements.get("maintenance-documents-section")) {
|
||||
|
||||
+12
-13
@@ -125,8 +125,7 @@ export class MaintenanceGroupsSectionCard extends LitElement {
|
||||
|
||||
private async _deleteGroup(id: string, name: string): Promise<void> {
|
||||
if (!this._isAdmin) return;
|
||||
const confirmText = (t("group_delete_confirm", this._lang)
|
||||
|| "Delete group \"{name}\"?").replace("{name}", name);
|
||||
const confirmText = t("group_delete_confirm", this._lang).replace("{name}", name);
|
||||
if (!window.confirm(confirmText)) return;
|
||||
this._busy = true;
|
||||
try {
|
||||
@@ -162,7 +161,7 @@ export class MaintenanceGroupsSectionCard extends LitElement {
|
||||
render() {
|
||||
const L = this._lang;
|
||||
if (!this._loaded) {
|
||||
return html`<ha-card><div class="loading">${t("loading", L) || "Loading…"}</div></ha-card>`;
|
||||
return html`<ha-card><div class="loading">${t("loading", L)}</div></ha-card>`;
|
||||
}
|
||||
const ids = Object.keys(this._groups);
|
||||
|
||||
@@ -172,7 +171,7 @@ export class MaintenanceGroupsSectionCard extends LitElement {
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<span class="emoji">🏷️</span>
|
||||
<span>${this._config.title || (t("groups", L) || "Groups")}</span>
|
||||
<span>${this._config.title || t("groups", L)}</span>
|
||||
<span class="count">${ids.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,7 +179,7 @@ export class MaintenanceGroupsSectionCard extends LitElement {
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
|
||||
${ids.length === 0
|
||||
? html`<div class="empty">${t("groups_empty", L) || "No groups yet."}</div>`
|
||||
? html`<div class="empty">${t("groups_empty", L)}</div>`
|
||||
: html`
|
||||
<div class="group-list">
|
||||
${ids.map((id) => {
|
||||
@@ -201,11 +200,11 @@ export class MaintenanceGroupsSectionCard extends LitElement {
|
||||
<button class="btn small primary"
|
||||
@click=${this._saveEdit}
|
||||
?disabled=${this._busy || !this._editingName.trim()}>
|
||||
${t("save", L) || "Save"}
|
||||
${t("save", L)}
|
||||
</button>
|
||||
<button class="btn small"
|
||||
@click=${() => { this._editingId = null; }}>
|
||||
${t("cancel", L) || "Cancel"}
|
||||
${t("cancel", L)}
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
@@ -214,13 +213,13 @@ export class MaintenanceGroupsSectionCard extends LitElement {
|
||||
${this._isAdmin
|
||||
? html`
|
||||
<button class="icon-btn"
|
||||
title="${t("edit", L) || "Edit"}"
|
||||
title="${t("edit", L)}"
|
||||
@click=${() => this._startEdit(id)}
|
||||
?disabled=${this._busy}>
|
||||
<ha-icon icon="mdi:pencil"></ha-icon>
|
||||
</button>
|
||||
<button class="icon-btn danger"
|
||||
title="${t("delete", L) || "Delete"}"
|
||||
title="${t("delete", L)}"
|
||||
@click=${() => this._deleteGroup(id, g.name || "Unnamed")}
|
||||
?disabled=${this._busy}>
|
||||
<ha-icon icon="mdi:delete"></ha-icon>
|
||||
@@ -238,7 +237,7 @@ export class MaintenanceGroupsSectionCard extends LitElement {
|
||||
? html`
|
||||
<div class="add-row">
|
||||
<input type="text"
|
||||
placeholder="${t("group_new_placeholder", L) || "Add group…"}"
|
||||
placeholder="${t("group_new_placeholder", L)}"
|
||||
.value=${this._newName}
|
||||
?disabled=${this._busy}
|
||||
@input=${(e: Event) => {
|
||||
@@ -249,16 +248,16 @@ export class MaintenanceGroupsSectionCard extends LitElement {
|
||||
@click=${this._addGroup}
|
||||
?disabled=${this._busy || !this._newName.trim()}>
|
||||
<ha-icon icon="mdi:plus"></ha-icon>
|
||||
${t("add", L) || "Add"}
|
||||
${t("add", L)}
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn link" @click=${this._onDeepLink}>
|
||||
${t("groups_manage_tasks", L) || "Manage task assignments…"}
|
||||
${t("groups_manage_tasks", L)}
|
||||
</button>
|
||||
`
|
||||
: html`
|
||||
<button class="btn link" @click=${this._onDeepLink}>
|
||||
${t("groups_open_panel", L) || "Open in panel"}
|
||||
${t("groups_open_panel", L)}
|
||||
</button>
|
||||
`}
|
||||
</div>
|
||||
|
||||
+75
-90
@@ -12,13 +12,11 @@ import { property, state } from "lit/decorators.js";
|
||||
import { t, langOf, formatNumber } from "../styles";
|
||||
import type { HomeAssistant, ReadingSlot, ReadingValue } from "../types";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import {
|
||||
MAX_COMPLETION_PHOTOS,
|
||||
discardUploadedPhotos,
|
||||
uploadCompletionPhoto,
|
||||
} from "../helpers/photo-upload";
|
||||
import { PhotoUploadController } from "../helpers/photo-upload-controller";
|
||||
import "./ms-date-field";
|
||||
import "./history-photo";
|
||||
import "./ms-photo-picker";
|
||||
import { photoPickerStyles } from "./ms-photo-picker";
|
||||
|
||||
export interface HistoryEntryDraft {
|
||||
entry_id: string;
|
||||
@@ -79,11 +77,14 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
@state() private _partQty: Record<string, number> = {};
|
||||
private _partQtyOriginal = "";
|
||||
|
||||
// #161: the edited photo list; the original as a JSON key for the
|
||||
// "did it change" check, plus the docs THIS session uploaded so an
|
||||
// abandoned edit can drop them again (a removed pre-existing photo is
|
||||
// only detached — the file stays in the object's documents).
|
||||
@state() private _photos: string[] = [];
|
||||
// #161: the edited photo list (seeded with the entry's photos; the docs
|
||||
// THIS session uploaded are dropped again by an abandoned edit, a removed
|
||||
// pre-existing photo is only detached — the file stays in the object's
|
||||
// documents); the original as a JSON key for the "did it change" check.
|
||||
private readonly _photos = new PhotoUploadController(this, {
|
||||
entryId: () => this._draft?.entry_id ?? "",
|
||||
hass: () => this.hass,
|
||||
});
|
||||
private _photosOriginal = "";
|
||||
|
||||
// #161 phase 2: the reading rows (entry snapshot ∪ task slots) with the
|
||||
@@ -91,8 +92,6 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
@state() private _readingRows: ReadingSlot[] = [];
|
||||
@state() private _readingText: Record<string, string> = {};
|
||||
private _readingsOriginal = "";
|
||||
private _uploadedIds: string[] = [];
|
||||
@state() private _photoUploading = false;
|
||||
|
||||
/** Open the dialog with the given history-entry data. The caller must
|
||||
* pass `original_timestamp` (the entry's current timestamp before edit)
|
||||
@@ -105,10 +104,8 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
this._partOptions = null;
|
||||
this._partQty = {};
|
||||
this._partQtyOriginal = "";
|
||||
this._photos = [...(draft.photo_doc_ids ?? [])];
|
||||
this._photosOriginal = JSON.stringify(this._photos);
|
||||
this._uploadedIds = [];
|
||||
this._photoUploading = false;
|
||||
this._photos.reset(draft.photo_doc_ids ?? []);
|
||||
this._photosOriginal = JSON.stringify(this._photos.ids);
|
||||
this._seedReadings(draft);
|
||||
void this._loadPartOptions();
|
||||
}
|
||||
@@ -147,41 +144,6 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
return out;
|
||||
}
|
||||
|
||||
private async _onPhotoInput(e: Event): Promise<void> {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const files = Array.from(input.files ?? []);
|
||||
input.value = "";
|
||||
const draft = this._draft;
|
||||
if (files.length === 0 || !draft) return;
|
||||
const room = MAX_COMPLETION_PHOTOS - this._photos.length;
|
||||
const accepted = files.slice(0, Math.max(room, 0));
|
||||
this._photoUploading = true;
|
||||
this._error = "";
|
||||
try {
|
||||
for (const file of accepted) {
|
||||
const id = await uploadCompletionPhoto(this.hass, draft.entry_id, file);
|
||||
this._uploadedIds = [...this._uploadedIds, id];
|
||||
this._photos = [...this._photos, id];
|
||||
}
|
||||
if (files.length > accepted.length) {
|
||||
this._error = t("photos_limit", this._lang).replace("{max}", String(MAX_COMPLETION_PHOTOS));
|
||||
}
|
||||
} catch (err) {
|
||||
const key = err instanceof Error && err.message === "doc_too_large" ? "doc_too_large" : "doc_upload_failed";
|
||||
this._error = t(key, this._lang);
|
||||
} finally {
|
||||
this._photoUploading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _removePhoto(id: string): void {
|
||||
this._photos = this._photos.filter((x) => x !== id);
|
||||
if (this._uploadedIds.includes(id)) {
|
||||
this._uploadedIds = this._uploadedIds.filter((x) => x !== id);
|
||||
void discardUploadedPhotos(this.hass, [id]);
|
||||
}
|
||||
}
|
||||
|
||||
/** The object's own parts + pooled parts this task draws on — from the
|
||||
* instance-wide overview so pooled owners resolve without extra calls. */
|
||||
private async _loadPartOptions(): Promise<void> {
|
||||
@@ -248,12 +210,8 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
this._error = "";
|
||||
this._draft = null;
|
||||
this._originalSnapshot = null;
|
||||
if (this._uploadedIds.length > 0) {
|
||||
// Cancelled after uploading: nothing references those files.
|
||||
const orphans = this._uploadedIds;
|
||||
this._uploadedIds = [];
|
||||
void discardUploadedPhotos(this.hass, orphans);
|
||||
}
|
||||
// Cancelled after uploading: nothing references those files.
|
||||
this._photos.discardOrphans();
|
||||
}
|
||||
|
||||
private _set<K extends keyof HistoryEntryDraft>(
|
||||
@@ -263,10 +221,42 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
this._draft = { ...this._draft, [key]: value };
|
||||
}
|
||||
|
||||
/** #170: remove the entry altogether (after a confirm). The backend
|
||||
* re-anchors last_performed on what remains; photos stay in the object's
|
||||
* documents, consumed parts are not restocked. */
|
||||
private async _delete(): Promise<void> {
|
||||
if (!this._draft || !this._originalSnapshot) return;
|
||||
const L = this._lang;
|
||||
if (!window.confirm(t("history_delete_confirm", L))) return;
|
||||
this._saving = true;
|
||||
this._error = "";
|
||||
try {
|
||||
await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/task/history/delete",
|
||||
entry_id: this._draft.entry_id,
|
||||
task_id: this._draft.task_id,
|
||||
timestamp: this._originalSnapshot.original_timestamp,
|
||||
});
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("history-entry-saved", {
|
||||
detail: { entry_id: this._draft.entry_id, task_id: this._draft.task_id, deleted: true },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
this.close();
|
||||
} catch (e) {
|
||||
this._error = describeWsError(e, L);
|
||||
} finally {
|
||||
this._saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._draft || !this._originalSnapshot) return;
|
||||
this._saving = true;
|
||||
this._error = "";
|
||||
this._photos.clearError();
|
||||
try {
|
||||
const patch: Record<string, unknown> = {
|
||||
type: "maintenance_supporter/task/history/update",
|
||||
@@ -315,8 +305,8 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
patch.reading_values = map;
|
||||
}
|
||||
// #161: the photo list, only when it differs from what we opened with.
|
||||
if (JSON.stringify(this._photos) !== this._photosOriginal) {
|
||||
patch.photo_doc_ids = [...this._photos];
|
||||
if (JSON.stringify(this._photos.ids) !== this._photosOriginal) {
|
||||
patch.photo_doc_ids = this._photos.ids;
|
||||
}
|
||||
// Nothing changed → close without WS call
|
||||
const changedKeys = Object.keys(patch).filter(
|
||||
@@ -327,7 +317,7 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
return;
|
||||
}
|
||||
await this.hass.connection.sendMessagePromise(patch);
|
||||
this._uploadedIds = []; // saved — they belong to the entry now
|
||||
this._photos.markAttached(); // saved — they belong to the entry now
|
||||
// Notify upstream so they can refresh
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("history-entry-saved", {
|
||||
@@ -352,10 +342,11 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
if (!this._open || !this._draft) return nothing;
|
||||
const L = this._lang;
|
||||
const d = this._draft;
|
||||
const error = this._error || this._photos.errorText(L);
|
||||
return html`
|
||||
<div class="backdrop" @click=${this.close}></div>
|
||||
<div class="dialog" role="dialog" aria-modal="true">
|
||||
<h2>${t("history_edit_title", L) || "Edit history entry"}</h2>
|
||||
<h2>${t("history_edit_title", L)}</h2>
|
||||
<div class="entry-type">
|
||||
<ha-icon icon="mdi:tag-outline"></ha-icon>
|
||||
<span>${t(d.type, L) || d.type}</span>
|
||||
@@ -365,7 +356,7 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
required
|
||||
.hass=${this.hass}
|
||||
.lang=${L}
|
||||
.label=${t("history_edit_timestamp", L) || "Timestamp"}
|
||||
.label=${t("history_edit_timestamp", L)}
|
||||
.value=${d.timestamp.slice(0, 19)}
|
||||
@value-changed=${(e: CustomEvent) => {
|
||||
// Always "YYYY-MM-DDTHH:MM:SS" from the field; the naive value
|
||||
@@ -386,7 +377,7 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
</label>
|
||||
<div class="row">
|
||||
<label>
|
||||
<span>${t("cost", L) || "Cost"}</span>
|
||||
<span>${t("cost", L)}</span>
|
||||
<input type="number" min="0" step="0.01"
|
||||
.value=${d.cost != null ? String(d.cost) : ""}
|
||||
@input=${(e: Event) => {
|
||||
@@ -395,7 +386,7 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
}} />
|
||||
</label>
|
||||
<label>
|
||||
<span>${t("duration", L) || "Duration (min)"}</span>
|
||||
<span>${t("duration", L)}</span>
|
||||
<input type="number" min="0"
|
||||
.value=${d.duration != null ? String(d.duration) : ""}
|
||||
@input=${(e: Event) => {
|
||||
@@ -434,32 +425,32 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
` : nothing}
|
||||
<div class="photos-block">
|
||||
<span class="parts-title">${t("completion_photos", L)}</span>
|
||||
${this._photos.length > 0 ? html`
|
||||
${this._photos.photos.length > 0 ? html`
|
||||
<div class="photo-strip">
|
||||
${this._photos.map((id) => html`
|
||||
${this._photos.photos.map((p) => html`
|
||||
<div class="photo-tile">
|
||||
<maintenance-history-photo .hass=${this.hass} .docId=${id}></maintenance-history-photo>
|
||||
<maintenance-history-photo .hass=${this.hass} .docId=${p.id}></maintenance-history-photo>
|
||||
<button type="button" class="photo-remove" title=${t("remove", L)}
|
||||
@click=${() => this._removePhoto(id)}>✕</button>
|
||||
@click=${() => this._photos.remove(p.id)}>✕</button>
|
||||
</div>`)}
|
||||
</div>` : nothing}
|
||||
${this._photos.length < MAX_COMPLETION_PHOTOS ? html`
|
||||
<label class="photo-add">
|
||||
<ha-icon icon="mdi:image-plus"></ha-icon>
|
||||
<span>${this._photoUploading ? t("uploading", L) : t("add_photos", L)}</span>
|
||||
<input type="file" accept="image/*" multiple
|
||||
?disabled=${this._photoUploading}
|
||||
@change=${this._onPhotoInput} />
|
||||
</label>` : html`<span class="photos-hint">${t("photos_limit", L).replace("{max}", String(MAX_COMPLETION_PHOTOS))}</span>`}
|
||||
${!this._photos.full
|
||||
? html`<ms-photo-picker .lang=${L} .busy=${this._photos.uploading} .remaining=${this._photos.remaining}
|
||||
@files-picked=${(e: CustomEvent<{ files: File[] }>) => this._photos.addFiles(e.detail.files)}
|
||||
></ms-photo-picker>`
|
||||
: html`<span class="photos-hint">${t("photos_limit", L).replace("{max}", String(this._photos.max))}</span>`}
|
||||
<span class="photos-hint">${t("history_edit_photos_hint", L)}</span>
|
||||
</div>
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
${error ? html`<div class="error">${error}</div>` : nothing}
|
||||
<div class="actions">
|
||||
<button class="delete-entry" @click=${this._delete} ?disabled=${this._saving} title=${t("history_delete_entry", L)}>
|
||||
<ha-icon icon="mdi:delete-outline"></ha-icon> ${t("history_delete_entry", L)}
|
||||
</button>
|
||||
<button class="cancel" @click=${this.close} ?disabled=${this._saving}>
|
||||
${t("cancel", L) || "Cancel"}
|
||||
${t("cancel", L)}
|
||||
</button>
|
||||
<button class="save" @click=${this._save} ?disabled=${this._saving}>
|
||||
${this._saving ? (t("saving", L) || "Saving…") : (t("save", L) || "Save")}
|
||||
${this._saving ? t("saving", L) : t("save", L)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -499,7 +490,7 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
</label>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
static styles = [photoPickerStyles, css`
|
||||
:host { display: contents; }
|
||||
.backdrop {
|
||||
position: fixed; inset: 0;
|
||||
@@ -536,6 +527,8 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
width: 100%; box-sizing: border-box;
|
||||
font-family: inherit;
|
||||
}
|
||||
.delete-entry { margin-right: auto; color: var(--error-color, #d32f2f); background: transparent; border: 1px solid var(--error-color, #d32f2f); border-radius: 6px; padding: 6px 10px; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; }
|
||||
.delete-entry ha-icon { --mdc-icon-size: 18px; }
|
||||
.actions {
|
||||
display: flex; gap: 8px; justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
@@ -601,17 +594,9 @@ export class MaintenanceHistoryEditDialog extends LitElement {
|
||||
background: var(--error-color, #db4437); color: #fff;
|
||||
cursor: pointer; font-size: 11px; line-height: 1; padding: 0;
|
||||
}
|
||||
.photo-add {
|
||||
display: inline-flex; flex-direction: row; align-items: center; gap: 8px;
|
||||
width: fit-content; padding: 6px 10px;
|
||||
border: 1px dashed var(--divider-color, #444); border-radius: 8px;
|
||||
cursor: pointer; font-size: 13px; color: var(--secondary-text-color);
|
||||
--mdc-icon-size: 18px;
|
||||
}
|
||||
.photo-add:hover { border-color: var(--primary-color); }
|
||||
.photo-add input[type="file"] { display: none; }
|
||||
/* the camera / gallery pickers come from photoPickerStyles (ms-photo-picker) */
|
||||
.photos-hint { font-size: 12px; color: var(--secondary-text-color); }
|
||||
`;
|
||||
`];
|
||||
}
|
||||
|
||||
if (!customElements.get("maintenance-history-edit-dialog")) {
|
||||
|
||||
+8
-9
@@ -107,8 +107,7 @@ export class MaintenanceObjectQuickActionsDialog extends LitElement {
|
||||
if (!this._entryId || !this._data) return;
|
||||
const archived = !!this._data.object.archived;
|
||||
if (!archived) {
|
||||
const confirmText = t("confirm_archive_object", this._lang)
|
||||
|| "Archive this object and its tasks?";
|
||||
const confirmText = t("confirm_archive_object", this._lang);
|
||||
if (!window.confirm(confirmText)) return;
|
||||
}
|
||||
this._busy = true;
|
||||
@@ -165,11 +164,11 @@ export class MaintenanceObjectQuickActionsDialog extends LitElement {
|
||||
|
||||
<div class="tasks-section">
|
||||
<div class="section-header">
|
||||
<strong>${t("tasks", L) || "Tasks"}</strong>
|
||||
<strong>${t("tasks", L)}</strong>
|
||||
<span class="count">${tasks.length}</span>
|
||||
</div>
|
||||
${tasks.length === 0
|
||||
? html`<div class="empty">${t("no_tasks", L) || "No tasks yet."}</div>`
|
||||
? html`<div class="empty">${t("no_tasks", L)}</div>`
|
||||
: html`
|
||||
<div class="task-list">
|
||||
${tasks.map((task) => html`
|
||||
@@ -197,25 +196,25 @@ export class MaintenanceObjectQuickActionsDialog extends LitElement {
|
||||
<div class="actions">
|
||||
<button class="btn primary" @click=${this._onAddTask} ?disabled=${this._busy}>
|
||||
<ha-icon icon="mdi:plus"></ha-icon>
|
||||
${t("add_task", L) || "Add task"}
|
||||
${t("add_task", L)}
|
||||
</button>
|
||||
<button class="btn" @click=${this._onEditObject} ?disabled=${this._busy}>
|
||||
<ha-icon icon="mdi:pencil"></ha-icon>
|
||||
${t("edit", L) || "Edit"}
|
||||
${t("edit", L)}
|
||||
</button>
|
||||
<button class="btn" @click=${this._onArchiveObject} ?disabled=${this._busy}>
|
||||
<ha-icon icon="${obj.archived ? 'mdi:archive-arrow-up-outline' : 'mdi:archive-outline'}"></ha-icon>
|
||||
${obj.archived ? (t("unarchive_object", L) || "Unarchive object") : (t("archive_object", L) || "Archive object")}
|
||||
${obj.archived ? t("unarchive_object", L) : t("archive_object", L)}
|
||||
</button>
|
||||
<button class="btn danger" @click=${this._onDelete} ?disabled=${this._busy}>
|
||||
<ha-icon icon="mdi:delete"></ha-icon>
|
||||
${t("delete", L) || "Delete"}
|
||||
${t("delete", L)}
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
: html`<div class="loading">${t("loading", L) || "Loading…"}</div>`}
|
||||
: html`<div class="loading">${t("loading", L)}</div>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -509,4 +509,6 @@ export class MaintenancePartsSection extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
customElements.define("maintenance-parts-section", MaintenancePartsSection);
|
||||
if (!customElements.get("maintenance-parts-section")) {
|
||||
customElements.define("maintenance-parts-section", MaintenancePartsSection);
|
||||
}
|
||||
|
||||
+319
-201
@@ -5,7 +5,7 @@ import { property, state } from "lit/decorators.js";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { live } from "lit/directives/live.js";
|
||||
import type { HomeAssistant, AdvancedFeatures, BudgetStatus, HAUser } from "../types";
|
||||
import { t, langOf, personStyles } from "../styles";
|
||||
import { t, langOf, personStyles, syncCurrencyDecimals} from "../styles";
|
||||
import { signApiPath } from "../helpers/document-url";
|
||||
import { downloadUrl } from "../helpers/download";
|
||||
import { UserService } from "../user-service";
|
||||
@@ -13,6 +13,9 @@ import { AVATAR_PALETTE, personOf, renderPersonAvatar } from "../helpers/person"
|
||||
import { OBJECT_COLUMNS, sanitizeColumns } from "../helpers/object-columns";
|
||||
import { downloadTextFile } from "../helpers/download";
|
||||
import { invalidateSettingsCache } from "../helpers/settings-cache";
|
||||
import { SETTING_INT_RANGES, settingIntRange } from "../helpers/setting-ranges";
|
||||
import { isoDateLocal } from "../helpers/calendar-bucket";
|
||||
import { runWs } from "../helpers/ws-run";
|
||||
import "./ms-date-field";
|
||||
|
||||
/** One household member and the notify services they actually resolve to.
|
||||
@@ -51,6 +54,18 @@ interface SettingsResponse {
|
||||
/** #146: household "low" floors (percent) for discovery + battery fleet. */
|
||||
default_consumable_threshold?: number;
|
||||
battery_low_percent?: number;
|
||||
/** D#162 follow-up: per-type lifetime overrides + the computed catalog. */
|
||||
battery_lifetime_months?: Record<string, number>;
|
||||
battery_lifetimes?: {
|
||||
type: string;
|
||||
months: number;
|
||||
source: "override" | "table" | "default";
|
||||
default_months: number;
|
||||
override_months: number | null;
|
||||
/** What the fleet learned for this type, per device model (never applied type-wide). */
|
||||
learned_models: { model: string; model_key: string; months: number; samples: number }[];
|
||||
in_fleet: boolean;
|
||||
}[];
|
||||
/** Computed: what Battery Notes reports (null when not installed). */
|
||||
battery_notes?: {
|
||||
default: number;
|
||||
@@ -60,6 +75,8 @@ interface SettingsResponse {
|
||||
} | null;
|
||||
/** #145: how task rows show Complete / Skip. */
|
||||
row_action_style?: "buttons_compact" | "buttons" | "icons";
|
||||
/** #170: reference numbers in front of names in every list. */
|
||||
ref_numbers_in_lists?: boolean;
|
||||
row_action_notice_pending?: boolean;
|
||||
};
|
||||
notifications: {
|
||||
@@ -79,6 +96,8 @@ interface SettingsResponse {
|
||||
scope_view_id: string;
|
||||
/** #165: your own notification rule — event-only delivery + extra-data template. */
|
||||
event_only?: boolean;
|
||||
/** #173 follow-up: completion notifications — off | automatic | all. */
|
||||
completed?: string;
|
||||
extra_data?: string;
|
||||
};
|
||||
actions: {
|
||||
@@ -97,6 +116,8 @@ interface SettingsResponse {
|
||||
alert_threshold_pct: number;
|
||||
currency: string;
|
||||
currency_symbol: string;
|
||||
/** Decimal places for every displayed amount (0 = whole numbers). */
|
||||
currency_decimals?: number;
|
||||
};
|
||||
// v2.10.0 archive automation (panel-managed). Optional for forward-compat
|
||||
// with a backend that predates the feature.
|
||||
@@ -135,6 +156,11 @@ const CURRENCIES = [
|
||||
"CZK", "PLN", "RUB", "SEK", "NOK", "DKK", "UAH",
|
||||
];
|
||||
|
||||
/** The currency_decimals <select> options — derived from the registry range
|
||||
* (0..3), not a literal list. */
|
||||
const CURRENCY_DECIMAL_OPTIONS: readonly number[] = (([lo, hi]) =>
|
||||
Array.from({ length: hi - lo + 1 }, (_, i) => lo + i))(SETTING_INT_RANGES.currency_decimals);
|
||||
|
||||
export class MaintenanceSettingsView extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public features!: AdvancedFeatures;
|
||||
@@ -163,6 +189,9 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
@state() private _vacAllTasks: Array<{ entry_id: string; object_name: string; task_id: string; task_name: string }> = [];
|
||||
@state() private _vacPreview: VacationPreviewRow[] = [];
|
||||
@state() private _vacPreviewLoading = false;
|
||||
/** A preview row's Complete/Skip is in flight — the buttons disable so a
|
||||
* double-tap cannot send the completion twice (DRY audit 2026-09-12). */
|
||||
@state() private _previewBusy = false;
|
||||
@state() private _vacSaving = false;
|
||||
|
||||
// Print QR codes section state
|
||||
@@ -236,6 +265,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
type: "maintenance_supporter/settings",
|
||||
});
|
||||
this._settings = result as SettingsResponse;
|
||||
syncCurrencyDecimals(this._settings.budget);
|
||||
this._hydrateVacationFromSettings();
|
||||
} catch {
|
||||
/* ignore */
|
||||
@@ -267,6 +297,64 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
/** #146 follow-up: what Battery Notes currently reports, with the rule
|
||||
* spelled out — warn-toned when its default sits above our floor (then a
|
||||
* lowered floor would silently change nothing for noted batteries). */
|
||||
/** D#162 follow-up: the typical-lifetime table — fleet types first, each
|
||||
* with its effective months and where they come from; editing a value
|
||||
* writes the household override, "Use default" removes it. Only batteries
|
||||
* WITHOUT a percentage use these (the hint says so). */
|
||||
private _renderBatteryLifetimes(L: string) {
|
||||
const rows = this._settings?.general?.battery_lifetimes;
|
||||
if (!rows || !rows.length) return nothing;
|
||||
const overrides = this._settings?.general?.battery_lifetime_months ?? {};
|
||||
const sourceLabel = (r: NonNullable<typeof rows>[number]) => t("lifetime_source_" + r.source, L);
|
||||
const unit = t("settings_battery_lifetime_months", L);
|
||||
const learnedLine = (r: NonNullable<typeof rows>[number]) =>
|
||||
r.learned_models.length
|
||||
? html`<div class="bl-learned">${t("settings_battery_lifetime_learned_models", L).replace(
|
||||
"{list}",
|
||||
r.learned_models.map((m) => `${m.model} ${m.months} ${unit} (${m.samples})`).join(" · "),
|
||||
)}</div>`
|
||||
: nothing;
|
||||
// No live() on the month inputs (bug audit 2026-09-12, #176 class): the
|
||||
// view re-renders on every hass assignment and live() reset a half-typed
|
||||
// value to the stored months. An out-of-range or rejected entry snaps
|
||||
// back by writing the input directly instead.
|
||||
const commit = (type: string, input: HTMLInputElement, stored: number) => {
|
||||
const months = Number.parseInt(input.value, 10);
|
||||
if (!Number.isFinite(months) || months < 1 || months > 240) { input.value = String(stored); return; }
|
||||
void this._updateSetting("battery_lifetime_months", { ...overrides, [type]: months })
|
||||
.then((ok) => { if (!ok) input.value = String(stored); });
|
||||
};
|
||||
const reset = (type: string) => {
|
||||
const next = { ...overrides };
|
||||
delete next[type];
|
||||
this._updateSetting("battery_lifetime_months", next);
|
||||
};
|
||||
const fleetRows = rows.filter((r) => r.in_fleet);
|
||||
const otherRows = rows.filter((r) => !r.in_fleet);
|
||||
const row = (r: NonNullable<typeof rows>[number]) => html`
|
||||
<div class="bl-row ${r.source === "override" ? "bl-override" : ""}">
|
||||
<span class="bl-type">${r.type}${r.in_fleet ? html` <span class="bl-fleet">${t("settings_battery_lifetime_in_fleet", L)}</span>` : nothing}</span>
|
||||
<input class="bl-months" type="number" min="1" max="240" .value=${String(r.months)}
|
||||
@change=${(e: Event) => commit(r.type, e.target as HTMLInputElement, r.months)} />
|
||||
<span class="bl-unit">${t("settings_battery_lifetime_months", L)}</span>
|
||||
<span class="bl-source">${sourceLabel(r)}</span>
|
||||
${r.source === "override"
|
||||
? html`<button type="button" class="bl-reset" @click=${() => reset(r.type)}>${t("settings_battery_lifetime_reset", L)}</button>`
|
||||
: nothing}
|
||||
${learnedLine(r)}
|
||||
</div>`;
|
||||
return html`
|
||||
<h4 class="bl-title">${t("settings_battery_lifetimes", L)}</h4>
|
||||
<div class="setting-hint">${t("settings_battery_lifetimes_hint", L)}</div>
|
||||
<div class="bl-table">
|
||||
${fleetRows.map(row)}
|
||||
${otherRows.length
|
||||
? html`<details class="bl-more"><summary>${otherRows.length} ×</summary>${otherRows.map(row)}</details>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderBatteryNotesHint(L: string) {
|
||||
const bn = this._settings?.general?.battery_notes;
|
||||
if (!bn || !bn.devices) return nothing;
|
||||
@@ -358,25 +446,33 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _updateSetting(key: string, value: unknown): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/global/update",
|
||||
settings: { [key]: value },
|
||||
});
|
||||
this._settings = result as SettingsResponse;
|
||||
/** Resolves true when the server accepted the value, false on a reject
|
||||
* (the caller decides how to snap its control back). */
|
||||
private async _updateSetting(key: string, value: unknown): Promise<boolean> {
|
||||
const result = await this._ws<SettingsResponse>({
|
||||
type: "maintenance_supporter/global/update",
|
||||
settings: { [key]: value },
|
||||
});
|
||||
if (result) {
|
||||
this._settings = result;
|
||||
syncCurrencyDecimals(this._settings.budget);
|
||||
// The card / Lovelace dialogs read their copy through a page-wide
|
||||
// cache — drop it so they follow the change without a reload.
|
||||
invalidateSettingsCache();
|
||||
this._showToast(t("settings_saved", this._lang));
|
||||
this.dispatchEvent(new CustomEvent("settings-changed"));
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
return true;
|
||||
} else {
|
||||
// The control already shows the rejected value (the browser changed it
|
||||
// before we asked the server). `_settings` is unchanged, so a plain
|
||||
// re-render would not touch the DOM — the selects bind their value
|
||||
// through `live()` precisely so this re-render snaps them back.
|
||||
// through `live()` precisely so this re-render snaps them back. Typed
|
||||
// inputs do NOT use live() (bug audit 2026-09-12: the view re-renders
|
||||
// on every hass assignment, and live() reset a half-typed number to
|
||||
// the stored value mid-typing — the #176 class); their callers snap
|
||||
// `input.value` back themselves when this resolves false.
|
||||
this.requestUpdate();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,15 +500,29 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
setTimeout(() => { this._toast = ""; }, 3000);
|
||||
}
|
||||
|
||||
/** One WS round-trip; a rejection becomes a toast carrying the server's
|
||||
* localized reason ("Limit reached", "Invalid date", …) and resolves
|
||||
* undefined. The silent best-effort loaders above deliberately do NOT
|
||||
* use this — they want a fallback value, not a toast. */
|
||||
private _ws<T = unknown>(
|
||||
payload: Record<string, unknown>,
|
||||
fallbackKey?: string,
|
||||
busy?: (busy: boolean) => void,
|
||||
): Promise<T | undefined> {
|
||||
return runWs<T>(this, payload, { fallbackKey, busy, onError: (msg) => this._showToast(msg) });
|
||||
}
|
||||
|
||||
/** Bounded integer settings: an out-of-range entry used to be dropped
|
||||
* silently while the field kept showing it (bug review 2026-09-04). Now
|
||||
* it is named in a toast and the field snaps back to the stored value;
|
||||
* a server reject snaps back through `live()` like the selects. */
|
||||
* a server reject snaps back the same way — by writing `input.value`
|
||||
* directly, since these inputs no longer bind through `live()` (bug
|
||||
* audit 2026-09-12). */
|
||||
private _onBoundedIntChange(e: Event, key: string, min: number, max: number, current: number): void {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const v = parseInt(input.value, 10);
|
||||
if (Number.isInteger(v) && v >= min && v <= max) {
|
||||
void this._updateSetting(key, v);
|
||||
void this._updateSetting(key, v).then((ok) => { if (!ok) input.value = String(current); });
|
||||
return;
|
||||
}
|
||||
this._showToast(
|
||||
@@ -421,6 +531,17 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
input.value = String(current);
|
||||
}
|
||||
|
||||
/** A bounded-integer setting input. min/max come from the registry mirror
|
||||
* (helpers/setting-ranges.ts — the backend's exact bounds, which the WS
|
||||
* sanitiser enforces by silently DROPPING an out-of-range value), and an
|
||||
* out-of-range or cleared entry snaps back with a toast instead of
|
||||
* sending a default. */
|
||||
private _intSetting(key: string, current: number) {
|
||||
const [lo, hi] = settingIntRange(key);
|
||||
return html`<input type="number" min=${lo} max=${hi} .value=${String(current)}
|
||||
@change=${(e: Event) => this._onBoundedIntChange(e, key, lo, hi, current)} />`;
|
||||
}
|
||||
|
||||
private _downloadFile(content: string, filename: string, mime: string): void {
|
||||
// Companion-app safe download (target=_blank + DOM + deferred revoke).
|
||||
downloadTextFile(content, filename, mime);
|
||||
@@ -725,24 +846,22 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
<h3>${t("settings_general", L)}</h3>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_default_warning", L)}</span>
|
||||
<input type="number" min="0" max="365" .value=${live(String(g.default_warning_days))}
|
||||
@change=${(e: Event) =>
|
||||
// 0 = no warning window (due soon only on the due date) — #145.
|
||||
this._onBoundedIntChange(e, "default_warning_days", 0, 365, g.default_warning_days)} />
|
||||
<!-- No live() on the typed number inputs below (bug audit
|
||||
2026-09-12, #176 class): a hass re-render must not reset a
|
||||
half-typed value; rejects snap back via _onBoundedIntChange. -->
|
||||
<!-- 0 = no warning window (due soon only on the due date) — #145. -->
|
||||
${this._intSetting("default_warning_days", g.default_warning_days)}
|
||||
</label>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_consumable_threshold", L)}</span>
|
||||
<input type="number" min="1" max="90" .value=${live(String(g.default_consumable_threshold ?? 10))}
|
||||
@change=${(e: Event) =>
|
||||
this._onBoundedIntChange(e, "default_consumable_threshold", 1, 90, g.default_consumable_threshold ?? 10)} />
|
||||
${this._intSetting("default_consumable_threshold", g.default_consumable_threshold ?? 10)}
|
||||
</label>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_battery_low_percent", L)}</span>
|
||||
<input type="number" min="1" max="90" .value=${live(String(g.battery_low_percent ?? 20))}
|
||||
@change=${(e: Event) =>
|
||||
this._onBoundedIntChange(e, "battery_low_percent", 1, 90, g.battery_low_percent ?? 20)} />
|
||||
${this._intSetting("battery_low_percent", g.battery_low_percent ?? 20)}
|
||||
</label>
|
||||
${this._renderBatteryNotesHint(L)}
|
||||
${this._renderBatteryLifetimes(L)}
|
||||
<div class="setting-hint">${t("settings_thresholds_hint", L)}</div>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_row_actions", L)}</span>
|
||||
@@ -752,12 +871,26 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
<option value=${v} ?selected=${(g.row_action_style || "buttons_compact") === v}>${t(`row_actions_${v}`, L)}</option>`)}
|
||||
</select>
|
||||
</label>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_ref_numbers_in_lists", L)}</span>
|
||||
<input type="checkbox" class="refs-in-lists" .checked=${g.ref_numbers_in_lists === true}
|
||||
@change=${(e: Event) => this._updateSetting("ref_numbers_in_lists", (e.target as HTMLInputElement).checked)} />
|
||||
</label>
|
||||
<div class="setting-hint">${t("settings_ref_numbers_in_lists_hint", L)}</div>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_currency", L)}</span>
|
||||
<select .value=${live(b.currency)} @change=${(e: Event) => this._updateSetting("budget_currency", (e.target as HTMLSelectElement).value)}>
|
||||
${CURRENCIES.map((c) => html`<option value=${c} ?selected=${b.currency === c}>${c}</option>`)}
|
||||
</select>
|
||||
</label>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_currency_decimals", L)}</span>
|
||||
<select class="currency-decimals" .value=${live(String(b.currency_decimals ?? 0))}
|
||||
@change=${(e: Event) => this._updateSetting("currency_decimals", Number((e.target as HTMLSelectElement).value))}>
|
||||
${CURRENCY_DECIMAL_OPTIONS.map((d) => html`<option value=${String(d)} ?selected=${(b.currency_decimals ?? 0) === d}>${d}</option>`)}
|
||||
</select>
|
||||
</label>
|
||||
<div class="setting-hint">${t("settings_currency_decimals_hint", L)}</div>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_panel_enabled", L)}</span>
|
||||
<input type="checkbox" .checked=${g.panel_enabled}
|
||||
@@ -861,8 +994,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
${n.due_soon_enabled ? html`
|
||||
<label class="setting-row sub-row">
|
||||
<span class="setting-desc">${t("settings_interval_hours", L)}</span>
|
||||
<input type="number" min="0" max="720" .value=${String(n.due_soon_interval_hours)}
|
||||
@change=${(e: Event) => this._updateSetting("notify_due_soon_interval_hours", parseInt((e.target as HTMLInputElement).value, 10) || 0)} />
|
||||
${this._intSetting("notify_due_soon_interval_hours", n.due_soon_interval_hours)}
|
||||
</label>
|
||||
` : nothing}
|
||||
|
||||
@@ -876,8 +1008,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
${n.overdue_enabled ? html`
|
||||
<label class="setting-row sub-row">
|
||||
<span class="setting-desc">${t("settings_interval_hours", L)}</span>
|
||||
<input type="number" min="0" max="720" .value=${String(n.overdue_interval_hours)}
|
||||
@change=${(e: Event) => this._updateSetting("notify_overdue_interval_hours", parseInt((e.target as HTMLInputElement).value, 10) || 0)} />
|
||||
${this._intSetting("notify_overdue_interval_hours", n.overdue_interval_hours)}
|
||||
</label>
|
||||
` : nothing}
|
||||
|
||||
@@ -891,8 +1022,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
${n.triggered_enabled ? html`
|
||||
<label class="setting-row sub-row">
|
||||
<span class="setting-desc">${t("settings_interval_hours", L)}</span>
|
||||
<input type="number" min="0" max="720" .value=${String(n.triggered_interval_hours)}
|
||||
@change=${(e: Event) => this._updateSetting("notify_triggered_interval_hours", parseInt((e.target as HTMLInputElement).value, 10) || 0)} />
|
||||
${this._intSetting("notify_triggered_interval_hours", n.triggered_interval_hours)}
|
||||
</label>
|
||||
` : nothing}
|
||||
|
||||
@@ -928,8 +1058,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_max_per_day", L)}</span>
|
||||
<input type="number" min="0" max="100" .value=${String(n.max_per_day)}
|
||||
@change=${(e: Event) => this._updateSetting("max_notifications_per_day", parseInt((e.target as HTMLInputElement).value, 10) || 0)} />
|
||||
${this._intSetting("max_notifications_per_day", n.max_per_day)}
|
||||
</label>
|
||||
|
||||
<label class="setting-row">
|
||||
@@ -940,8 +1069,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
${n.bundling_enabled ? html`
|
||||
<label class="setting-row sub-row">
|
||||
<span class="setting-desc">${t("settings_bundle_threshold", L)}</span>
|
||||
<input type="number" min="2" max="20" .value=${String(n.bundle_threshold)}
|
||||
@change=${(e: Event) => this._updateSetting("notification_bundle_threshold", parseInt((e.target as HTMLInputElement).value, 10) || 2)} />
|
||||
${this._intSetting("notification_bundle_threshold", n.bundle_threshold)}
|
||||
</label>
|
||||
` : nothing}
|
||||
<label class="setting-row">
|
||||
@@ -970,6 +1098,16 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
</select>
|
||||
</label>
|
||||
<div class="setting-hint">${t("settings_notify_scope_hint", L)}</div>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_notify_completed", L)}</span>
|
||||
<select class="notify-completed" .value=${live(n.completed || "off")}
|
||||
@change=${(e: Event) => this._updateSetting("notify_completed", (e.target as HTMLSelectElement).value)}>
|
||||
<option value="off">${t("notify_completed_off", L)}</option>
|
||||
<option value="automatic">${t("notify_completed_automatic", L)}</option>
|
||||
<option value="all">${t("notify_completed_all", L)}</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="setting-hint">${t("settings_notify_completed_hint", L)}</div>
|
||||
|
||||
<div class="notify-rule">
|
||||
<h4 style="margin: 16px 0 8px; font-size: 14px;">${t("settings_notify_rule", L)}</h4>
|
||||
@@ -1011,8 +1149,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
${a.snooze_enabled ? html`
|
||||
<label class="setting-row sub-row">
|
||||
<span class="setting-desc">${t("settings_snooze_hours", L)}</span>
|
||||
<input type="number" min="1" max="168" .value=${String(a.snooze_duration_hours)}
|
||||
@change=${(e: Event) => this._updateSetting("snooze_duration_hours", parseInt((e.target as HTMLInputElement).value, 10) || 4)} />
|
||||
${this._intSetting("snooze_duration_hours", a.snooze_duration_hours)}
|
||||
</label>
|
||||
` : nothing}
|
||||
<label class="setting-row">
|
||||
@@ -1029,8 +1166,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
${a.warranty_reminder_enabled ? html`
|
||||
<label class="setting-row sub-row">
|
||||
<span class="setting-desc">${t("settings_warranty_reminder_days", L)}</span>
|
||||
<input type="number" min="1" max="365" .value=${String(a.warranty_reminder_days)}
|
||||
@change=${(e: Event) => this._updateSetting("warranty_reminder_days", parseInt((e.target as HTMLInputElement).value, 10) || 30)} />
|
||||
${this._intSetting("warranty_reminder_days", a.warranty_reminder_days)}
|
||||
</label>
|
||||
` : nothing}
|
||||
<div class="setting-hint">${t("settings_warranty_reminder_hint", L)}</div>
|
||||
@@ -1063,8 +1199,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
${b.alerts_enabled ? html`
|
||||
<label class="setting-row sub-row">
|
||||
<span class="setting-desc">${t("settings_budget_threshold", L)}</span>
|
||||
<input type="number" min="1" max="100" .value=${String(b.alert_threshold_pct)}
|
||||
@change=${(e: Event) => this._updateSetting("budget_alert_threshold", parseInt((e.target as HTMLInputElement).value, 10) || 80)} />
|
||||
${this._intSetting("budget_alert_threshold", b.alert_threshold_pct)}
|
||||
</label>
|
||||
` : nothing}
|
||||
</div>
|
||||
@@ -1081,13 +1216,11 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
<p class="section-desc">${t("settings_archive_desc", L)}</p>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_archive_oneoff_days", L)}</span>
|
||||
<input type="number" min="0" max="3650" step="1" .value=${String(a.oneoff_days)}
|
||||
@change=${(e: Event) => this._updateSetting("archive_oneoff_days", parseInt((e.target as HTMLInputElement).value, 10) || 0)} />
|
||||
${this._intSetting("archive_oneoff_days", a.oneoff_days)}
|
||||
</label>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_delete_archived_oneoff_days", L)}</span>
|
||||
<input type="number" min="0" max="3650" step="1" .value=${String(a.delete_archived_oneoff_days)}
|
||||
@change=${(e: Event) => this._updateSetting("delete_archived_oneoff_days", parseInt((e.target as HTMLInputElement).value, 10) || 0)} />
|
||||
${this._intSetting("delete_archived_oneoff_days", a.delete_archived_oneoff_days)}
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
@@ -1235,9 +1368,9 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
<div class="vac-preview-events">${eventLabel}</div>
|
||||
</div>
|
||||
<div class="vac-preview-actions">
|
||||
<button @click=${() => this._previewActionComplete(row)}>${t("qr_action_complete", L)}</button>
|
||||
<button .disabled=${this._previewBusy} @click=${() => this._previewActionComplete(row)}>${t("qr_action_complete", L)}</button>
|
||||
${row.kind === "time_based" && row.allow_skip !== false
|
||||
? html`<button @click=${() => this._previewActionSkip(row)}>${t("qr_action_skip", L)}</button>`
|
||||
? html`<button .disabled=${this._previewBusy} @click=${() => this._previewActionSkip(row)}>${t("qr_action_skip", L)}</button>`
|
||||
: nothing}
|
||||
<button class=${isExempt ? "vac-notify-on" : ""}
|
||||
@click=${() => this._toggleVacationExempt(row.task_id, !isExempt)}>
|
||||
@@ -1254,39 +1387,35 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
// --- Vacation actions ---
|
||||
|
||||
private async _loadAllTasksForVacation(): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/objects",
|
||||
}) as { objects: Array<{
|
||||
entry_id: string;
|
||||
object: { name: string };
|
||||
tasks: Array<{ id: string; name: string }>;
|
||||
}> };
|
||||
const flat: typeof this._vacAllTasks = [];
|
||||
for (const obj of result.objects || []) {
|
||||
for (const t of obj.tasks || []) {
|
||||
flat.push({
|
||||
entry_id: obj.entry_id,
|
||||
object_name: obj.object.name || "",
|
||||
task_id: t.id,
|
||||
task_name: t.name || "",
|
||||
});
|
||||
}
|
||||
const result = await this._ws<{ objects: Array<{
|
||||
entry_id: string;
|
||||
object: { name: string };
|
||||
tasks: Array<{ id: string; name: string }>;
|
||||
}> }>({ type: "maintenance_supporter/objects" });
|
||||
if (!result) return;
|
||||
const flat: typeof this._vacAllTasks = [];
|
||||
for (const obj of result.objects || []) {
|
||||
for (const t of obj.tasks || []) {
|
||||
flat.push({
|
||||
entry_id: obj.entry_id,
|
||||
object_name: obj.object.name || "",
|
||||
task_id: t.id,
|
||||
task_name: t.name || "",
|
||||
});
|
||||
}
|
||||
this._vacAllTasks = flat;
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
this._vacAllTasks = flat;
|
||||
}
|
||||
|
||||
private async _saveVacation(patch: Record<string, unknown>): Promise<void> {
|
||||
if (this._vacSaving) return;
|
||||
this._vacSaving = true;
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
const result = await this._ws<SettingsResponse["vacation"]>({
|
||||
type: "maintenance_supporter/vacation/update",
|
||||
...patch,
|
||||
}) as SettingsResponse["vacation"];
|
||||
});
|
||||
if (!result) return;
|
||||
// Server is the source of truth — re-hydrate from response.
|
||||
this._vacEnabled = result.enabled;
|
||||
this._vacStart = result.start || "";
|
||||
@@ -1297,9 +1426,6 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
this._vacWindowEnd = result.window_end;
|
||||
// Notify the panel (so the Vacation tab can appear/disappear)
|
||||
this.dispatchEvent(new CustomEvent("settings-changed"));
|
||||
} catch (e: unknown) {
|
||||
const msg = (e as { message?: string })?.message || t("action_error", this._lang);
|
||||
this._showToast(msg);
|
||||
} finally {
|
||||
this._vacSaving = false;
|
||||
}
|
||||
@@ -1327,62 +1453,58 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadVacationPreview(): Promise<void> {
|
||||
this._vacPreviewLoading = true;
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/vacation/preview",
|
||||
}) as { rows: VacationPreviewRow[] };
|
||||
this._vacPreview = result.rows || [];
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
} finally {
|
||||
this._vacPreviewLoading = false;
|
||||
}
|
||||
const result = await this._ws<{ rows: VacationPreviewRow[] }>(
|
||||
{ type: "maintenance_supporter/vacation/preview" },
|
||||
undefined,
|
||||
(busy) => { this._vacPreviewLoading = busy; },
|
||||
);
|
||||
if (result) this._vacPreview = result.rows || [];
|
||||
}
|
||||
|
||||
private async _previewActionComplete(row: VacationPreviewRow): Promise<void> {
|
||||
if (this._previewBusy) return;
|
||||
this._previewBusy = true;
|
||||
try {
|
||||
await this.hass.connection.sendMessagePromise({
|
||||
const ok = await this._ws({
|
||||
type: "maintenance_supporter/task/complete",
|
||||
entry_id: row.entry_id,
|
||||
task_id: row.task_id,
|
||||
});
|
||||
if (ok === undefined) return;
|
||||
this._showToast(t("vacation_marked_complete", this._lang));
|
||||
await this._loadVacationPreview();
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
} finally {
|
||||
this._previewBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _previewActionSkip(row: VacationPreviewRow): Promise<void> {
|
||||
if (this._previewBusy) return;
|
||||
this._previewBusy = true;
|
||||
try {
|
||||
await this.hass.connection.sendMessagePromise({
|
||||
const ok = await this._ws({
|
||||
type: "maintenance_supporter/task/skip",
|
||||
entry_id: row.entry_id,
|
||||
task_id: row.task_id,
|
||||
reason: "Skipped before vacation",
|
||||
});
|
||||
if (ok === undefined) return;
|
||||
this._showToast(t("vacation_marked_skip", this._lang));
|
||||
await this._loadVacationPreview();
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
} finally {
|
||||
this._previewBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _endVacationNow(): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/vacation/end_now",
|
||||
}) as SettingsResponse["vacation"];
|
||||
this._vacEnabled = result.enabled;
|
||||
this._vacEnd = result.end || "";
|
||||
this._vacIsActive = result.is_active;
|
||||
this._vacWindowEnd = result.window_end;
|
||||
this.dispatchEvent(new CustomEvent("settings-changed"));
|
||||
this._showToast(t("vacation_ended", this._lang));
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
const result = await this._ws<SettingsResponse["vacation"]>({ type: "maintenance_supporter/vacation/end_now" });
|
||||
if (!result) return;
|
||||
this._vacEnabled = result.enabled;
|
||||
this._vacEnd = result.end || "";
|
||||
this._vacIsActive = result.is_active;
|
||||
this._vacWindowEnd = result.window_end;
|
||||
this.dispatchEvent(new CustomEvent("settings-changed"));
|
||||
this._showToast(t("vacation_ended", this._lang));
|
||||
}
|
||||
|
||||
// --- Section: Print QR codes (v1.1.0) ---
|
||||
@@ -1484,19 +1606,24 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadQrObjects(): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/objects",
|
||||
}) as { objects: Array<{ entry_id: string; object: { name: string }; tasks: unknown[] }> };
|
||||
this._qrObjects = (result.objects || []).map((o) => ({
|
||||
entry_id: o.entry_id,
|
||||
name: o.object.name,
|
||||
task_count: (o.tasks || []).length,
|
||||
})).sort((a, b) => a.name.localeCompare(b.name));
|
||||
this._qrObjectsLoaded = true;
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
const rows = await this._loadObjectRows();
|
||||
if (!rows) return;
|
||||
this._qrObjects = rows;
|
||||
this._qrObjectsLoaded = true;
|
||||
}
|
||||
|
||||
/** {entry_id, name, task_count} of every object, name-sorted — the QR
|
||||
* batch and the export section both pick objects from this list. */
|
||||
private async _loadObjectRows(): Promise<Array<{ entry_id: string; name: string; task_count: number }> | undefined> {
|
||||
const result = await this._ws<{ objects: Array<{ entry_id: string; object: { name: string }; tasks: unknown[] }> }>({
|
||||
type: "maintenance_supporter/objects",
|
||||
});
|
||||
if (!result) return undefined;
|
||||
return (result.objects || []).map((o) => ({
|
||||
entry_id: o.entry_id,
|
||||
name: o.object.name,
|
||||
task_count: (o.tasks || []).length,
|
||||
})).sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
private _toggleQrObject(entryId: string, on: boolean): void {
|
||||
@@ -1529,20 +1656,18 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
if (this._qrSelectedEntries.size > 0) {
|
||||
msg.entry_ids = [...this._qrSelectedEntries];
|
||||
}
|
||||
const result = await this.hass.connection.sendMessagePromise<{
|
||||
const result = await this._ws<{
|
||||
qrs: Array<{
|
||||
entry_id: string; task_id: string;
|
||||
object_name: string; task_name: string;
|
||||
action: string; svg: string;
|
||||
}>;
|
||||
}>(msg);
|
||||
if (!result) return;
|
||||
this._qrBatchResults = result.qrs || [];
|
||||
if (this._qrBatchResults.length === 0) {
|
||||
this._showToast(t("qr_print_empty", this._lang));
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
const msg = (e as { message?: string })?.message || t("action_error", this._lang);
|
||||
this._showToast(msg);
|
||||
} finally {
|
||||
this._qrBatchLoading = false;
|
||||
}
|
||||
@@ -1702,19 +1827,10 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadExportObjects(): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/objects",
|
||||
}) as { objects: Array<{ entry_id: string; object: { name: string }; tasks: unknown[] }> };
|
||||
this._exportObjects = (result.objects || []).map((o) => ({
|
||||
entry_id: o.entry_id,
|
||||
name: o.object.name,
|
||||
task_count: (o.tasks || []).length,
|
||||
})).sort((a, b) => a.name.localeCompare(b.name));
|
||||
this._exportObjectsLoaded = true;
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
const rows = await this._loadObjectRows();
|
||||
if (!rows) return;
|
||||
this._exportObjects = rows;
|
||||
this._exportObjectsLoaded = true;
|
||||
}
|
||||
|
||||
private _toggleExportObject(entryId: string, on: boolean): void {
|
||||
@@ -1728,92 +1844,75 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
}
|
||||
|
||||
private async _exportJson(): Promise<void> {
|
||||
try {
|
||||
const ids = this._selectedEntryIds;
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/export",
|
||||
format: "json",
|
||||
include_history: this._includeHistory,
|
||||
...(ids ? { entry_ids: ids } : {}),
|
||||
}) as { data: string };
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
this._downloadFile(result.data, `maintenance_export_${ts}.json`, "application/json");
|
||||
this._showToast(t("settings_export_success", this._lang));
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
const ids = this._selectedEntryIds;
|
||||
const result = await this._ws<{ data: string }>({
|
||||
type: "maintenance_supporter/export",
|
||||
format: "json",
|
||||
include_history: this._includeHistory,
|
||||
...(ids ? { entry_ids: ids } : {}),
|
||||
});
|
||||
if (!result) return;
|
||||
const ts = isoDateLocal(new Date());
|
||||
this._downloadFile(result.data, `maintenance_export_${ts}.json`, "application/json");
|
||||
this._showToast(t("settings_export_success", this._lang));
|
||||
}
|
||||
|
||||
/** The SECOND export: the global entry's settings (groups, saved views,
|
||||
* vacation, notification/budget settings, feature toggles) — the objects
|
||||
* export deliberately excludes them. Re-import via the regular import. */
|
||||
private async _exportSettings(): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/settings/export",
|
||||
}) as { data: string };
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
this._downloadFile(result.data, `maintenance_settings_${ts}.json`, "application/json");
|
||||
this._showToast(t("settings_export_success", this._lang));
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
const result = await this._ws<{ data: string }>({ type: "maintenance_supporter/settings/export" });
|
||||
if (!result) return;
|
||||
const ts = isoDateLocal(new Date());
|
||||
this._downloadFile(result.data, `maintenance_settings_${ts}.json`, "application/json");
|
||||
this._showToast(t("settings_export_success", this._lang));
|
||||
}
|
||||
|
||||
private async _exportYaml(): Promise<void> {
|
||||
try {
|
||||
const ids = this._selectedEntryIds;
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/export",
|
||||
format: "yaml",
|
||||
include_history: this._includeHistory,
|
||||
...(ids ? { entry_ids: ids } : {}),
|
||||
}) as { data: string };
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
this._downloadFile(result.data, `maintenance_export_${ts}.yaml`, "application/yaml");
|
||||
this._showToast(t("settings_export_success", this._lang));
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
const ids = this._selectedEntryIds;
|
||||
const result = await this._ws<{ data: string }>({
|
||||
type: "maintenance_supporter/export",
|
||||
format: "yaml",
|
||||
include_history: this._includeHistory,
|
||||
...(ids ? { entry_ids: ids } : {}),
|
||||
});
|
||||
if (!result) return;
|
||||
const ts = isoDateLocal(new Date());
|
||||
this._downloadFile(result.data, `maintenance_export_${ts}.yaml`, "application/yaml");
|
||||
this._showToast(t("settings_export_success", this._lang));
|
||||
}
|
||||
|
||||
private async _exportCsv(): Promise<void> {
|
||||
try {
|
||||
const ids = this._selectedEntryIds;
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/csv/export",
|
||||
...(ids ? { entry_ids: ids } : {}),
|
||||
}) as { csv: string };
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
this._downloadFile(result.csv, `maintenance_export_${ts}.csv`, "text/csv");
|
||||
this._showToast(t("settings_export_success", this._lang));
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
const ids = this._selectedEntryIds;
|
||||
const result = await this._ws<{ csv: string }>({
|
||||
type: "maintenance_supporter/csv/export",
|
||||
...(ids ? { entry_ids: ids } : {}),
|
||||
});
|
||||
if (!result) return;
|
||||
const ts = isoDateLocal(new Date());
|
||||
this._downloadFile(result.csv, `maintenance_export_${ts}.csv`, "text/csv");
|
||||
this._showToast(t("settings_export_success", this._lang));
|
||||
}
|
||||
|
||||
private async _importCsvAction(): Promise<void> {
|
||||
const content = this._importCsv.trim();
|
||||
if (!content) return;
|
||||
this._importLoading = true;
|
||||
try {
|
||||
// CSV exports start with the "object_name" header; anything else
|
||||
// (JSON `{`/`[` or YAML `version:`) goes to the structured importer,
|
||||
// which parses JSON and YAML alike.
|
||||
const isCsv = content.startsWith("object_name");
|
||||
const result = await this.hass.connection.sendMessagePromise(
|
||||
isCsv
|
||||
? { type: "maintenance_supporter/csv/import", csv_content: content }
|
||||
: { type: "maintenance_supporter/json/import", json_content: content }
|
||||
) as { created: number };
|
||||
const count = result.created ?? 0;
|
||||
this._showToast(t("settings_import_success", this._lang).replace("{count}", String(count)));
|
||||
this._importCsv = "";
|
||||
this.dispatchEvent(new CustomEvent("settings-changed"));
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
this._importLoading = false;
|
||||
// CSV exports start with the "object_name" header; anything else
|
||||
// (JSON `{`/`[` or YAML `version:`) goes to the structured importer,
|
||||
// which parses JSON and YAML alike.
|
||||
const isCsv = content.startsWith("object_name");
|
||||
const result = await this._ws<{ created: number }>(
|
||||
isCsv
|
||||
? { type: "maintenance_supporter/csv/import", csv_content: content }
|
||||
: { type: "maintenance_supporter/json/import", json_content: content },
|
||||
undefined,
|
||||
(busy) => { this._importLoading = busy; },
|
||||
);
|
||||
if (!result) return;
|
||||
const count = result.created ?? 0;
|
||||
this._showToast(t("settings_import_success", this._lang).replace("{count}", String(count)));
|
||||
this._importCsv = "";
|
||||
this.dispatchEvent(new CustomEvent("settings-changed"));
|
||||
}
|
||||
|
||||
// --- Documents archive (ZIP with file contents) ---
|
||||
@@ -2051,6 +2150,23 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
.import-section { margin-top: 16px; }
|
||||
|
||||
.setting-row-block { flex-direction: column; align-items: stretch; gap: 6px; }
|
||||
/* D#162 follow-up: battery lifetime table */
|
||||
.bl-title { margin: 16px 0 8px; font-size: 14px; }
|
||||
.bl-table { display: flex; flex-direction: column; gap: 4px; margin: 8px 0 12px; }
|
||||
.bl-row { display: grid; grid-template-columns: minmax(0, 1fr) 72px max-content minmax(0, 1.4fr) max-content; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--divider-color); }
|
||||
.bl-type { font-weight: 500; }
|
||||
.bl-fleet { font-size: 11px; color: var(--secondary-text-color); font-weight: 400; margin-left: 4px; }
|
||||
.bl-months { width: 72px; padding: 4px 6px; border: 1px solid var(--divider-color); border-radius: 4px; background: var(--card-background-color); color: var(--primary-text-color); font: inherit; }
|
||||
.bl-unit, .bl-source { font-size: 12px; color: var(--secondary-text-color); }
|
||||
.bl-override .bl-source { color: var(--primary-color); }
|
||||
.bl-learned { grid-column: 1 / -1; font-size: 12px; color: var(--secondary-text-color); }
|
||||
.bl-reset { background: none; border: 1px solid var(--divider-color); border-radius: 12px; padding: 2px 10px; font-size: 12px; color: var(--primary-text-color); cursor: pointer; }
|
||||
.bl-more > summary { cursor: pointer; font-size: 12px; color: var(--secondary-text-color); padding: 4px 0; }
|
||||
@media (max-width: 640px) {
|
||||
.bl-row { grid-template-columns: minmax(0, 1fr) 64px max-content; }
|
||||
.bl-source { grid-column: 1 / 3; }
|
||||
.bl-reset { grid-column: 3; justify-self: end; }
|
||||
}
|
||||
.notify-extra { min-height: 72px; font-family: var(--code-font-family, monospace); font-size: 12.5px; }
|
||||
.import-area {
|
||||
width: 100%;
|
||||
@@ -2387,4 +2503,6 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
`];
|
||||
}
|
||||
|
||||
customElements.define("maintenance-settings-view", MaintenanceSettingsView);
|
||||
if (!customElements.get("maintenance-settings-view")) {
|
||||
customElements.define("maintenance-settings-view", MaintenanceSettingsView);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
} from "../helpers/trigger-domains";
|
||||
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { runWs } from "../helpers/ws-run";
|
||||
import type { TriggerType } from "../types";
|
||||
import { REQUIRED_COMPLETION_KEYS, REQUIRED_COMPLETION_LABELS } from "./required-completion-labels";
|
||||
import "./ms-textfield";
|
||||
import "./ms-date-field";
|
||||
@@ -36,7 +38,7 @@ const ADAPTIVE_DEFAULTS = { alpha: "0.3", min: "7", max: "365" } as const;
|
||||
* String-typed like the top-level fields (form inputs); coerced on save. */
|
||||
interface CompoundConditionDraft {
|
||||
entityIds: string; // comma-separated raw input
|
||||
type: string; // threshold | counter | state_change | runtime
|
||||
type: TriggerType;
|
||||
attribute: string; // "" = use the entity state
|
||||
above: string;
|
||||
below: string;
|
||||
@@ -161,6 +163,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
private _pickerProbeStrikes = 0;
|
||||
@state() private _loading = false;
|
||||
@state() private _error = "";
|
||||
/** The task saved but a Store-managed sub-save (environmental entity,
|
||||
* adaptive tuning) was rejected — shown without closing the dialog. */
|
||||
@state() private _warning = "";
|
||||
@state() private _entryId = "";
|
||||
@state() private _taskId: string | null = null; // null = create
|
||||
// When openCreate is called without an entry_id and a list of objects is supplied,
|
||||
@@ -210,7 +215,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@state() private _triggerEntityIds: string[] = [];
|
||||
@state() private _triggerEntityLogic: "any" | "all" = "any";
|
||||
@state() private _triggerAttribute = "";
|
||||
@state() private _triggerType = "threshold";
|
||||
@state() private _triggerType: TriggerType = "threshold";
|
||||
@state() private _triggerAbove = "";
|
||||
@state() private _triggerBelow = "";
|
||||
@state() private _triggerEquals = "";
|
||||
@@ -344,6 +349,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._entryId = entryId;
|
||||
this._taskId = null;
|
||||
this._error = "";
|
||||
this._warning = "";
|
||||
// If no entryId is preset but caller passed objects, expose them as a dropdown.
|
||||
// Sort alphabetically by name so the user doesn't have to scan for a target
|
||||
// object in creation order (#40). First object after sort becomes the
|
||||
@@ -365,6 +371,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._entryId = entryId;
|
||||
this._taskId = task.id;
|
||||
this._error = "";
|
||||
this._warning = "";
|
||||
// The object picker belongs to CREATE only. openCreate("", objects) left
|
||||
// the choices behind, so Create → Cancel → Edit showed the dropdown in
|
||||
// edit mode — and picking another object re-pointed _entryId while
|
||||
@@ -1501,23 +1508,28 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
const envChanged =
|
||||
this._environmentalEntity !== this._environmentalInitial
|
||||
|| this._environmentalAttribute !== this._environmentalAttributeInitial;
|
||||
// A rejected sub-save is non-fatal (the task itself is saved) but not
|
||||
// silent any more: it becomes a warning line and the dialog stays
|
||||
// open so the user sees it (DRY round 2026-09 — both catches swallowed).
|
||||
this._warning = "";
|
||||
const subSaveFailed = (detail: string) => {
|
||||
this._warning = t("subsave_warning", this._lang).replace("{detail}", detail);
|
||||
};
|
||||
if (
|
||||
savedTaskId
|
||||
&& this._scheduleType === "sensor_based"
|
||||
&& envChanged
|
||||
) {
|
||||
try {
|
||||
await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/task/set_environmental_entity",
|
||||
entry_id: this._entryId,
|
||||
task_id: savedTaskId,
|
||||
environmental_entity: this._environmentalEntity || null,
|
||||
environmental_attribute: this._environmentalAttribute || null,
|
||||
});
|
||||
const ok = await runWs(this, {
|
||||
type: "maintenance_supporter/task/set_environmental_entity",
|
||||
entry_id: this._entryId,
|
||||
task_id: savedTaskId,
|
||||
environmental_entity: this._environmentalEntity || null,
|
||||
environmental_attribute: this._environmentalAttribute || null,
|
||||
}, { onError: subSaveFailed });
|
||||
if (ok !== undefined) {
|
||||
this._environmentalInitial = this._environmentalEntity;
|
||||
this._environmentalAttributeInitial = this._environmentalAttribute;
|
||||
} catch {
|
||||
/* non-fatal — task itself saved */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1527,25 +1539,27 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
const alpha = parseFloat(this._adaptiveAlpha);
|
||||
const minIv = parseInt(this._adaptiveMin, 10);
|
||||
const maxIv = parseInt(this._adaptiveMax, 10);
|
||||
try {
|
||||
await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/task/set_adaptive",
|
||||
entry_id: this._entryId,
|
||||
task_id: savedTaskId,
|
||||
enabled: this._adaptiveEnabled,
|
||||
...(alpha >= 0.1 && alpha <= 0.9 ? { ewa_alpha: alpha } : {}),
|
||||
...(!isNaN(minIv) && minIv >= 1 ? { min_interval_days: minIv } : {}),
|
||||
...(!isNaN(maxIv) && maxIv >= 1 ? { max_interval_days: maxIv } : {}),
|
||||
seasonal_enabled: this._adaptiveSeasonal,
|
||||
sensor_prediction_enabled: this._adaptivePrediction,
|
||||
});
|
||||
this._adaptiveInitial = this._adaptiveSnapshot();
|
||||
} catch {
|
||||
/* non-fatal — task itself saved */
|
||||
}
|
||||
const ok = await runWs(this, {
|
||||
type: "maintenance_supporter/task/set_adaptive",
|
||||
entry_id: this._entryId,
|
||||
task_id: savedTaskId,
|
||||
enabled: this._adaptiveEnabled,
|
||||
...(alpha >= 0.1 && alpha <= 0.9 ? { ewa_alpha: alpha } : {}),
|
||||
...(!isNaN(minIv) && minIv >= 1 ? { min_interval_days: minIv } : {}),
|
||||
...(!isNaN(maxIv) && maxIv >= 1 ? { max_interval_days: maxIv } : {}),
|
||||
seasonal_enabled: this._adaptiveSeasonal,
|
||||
sensor_prediction_enabled: this._adaptivePrediction,
|
||||
}, { onError: subSaveFailed });
|
||||
if (ok !== undefined) this._adaptiveInitial = this._adaptiveSnapshot();
|
||||
}
|
||||
|
||||
this._open = false;
|
||||
if (this._warning) {
|
||||
// Keep the dialog open on the (now existing) task so a retry is an
|
||||
// update, never a second create; the panel still refreshes.
|
||||
if (savedTaskId) this._taskId = savedTaskId;
|
||||
} else {
|
||||
this._open = false;
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent("task-saved"));
|
||||
} catch (e) {
|
||||
this._error = describeWsError(e, this._lang, t("save_error", this._lang));
|
||||
@@ -1556,6 +1570,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
|
||||
private _close(): void {
|
||||
this._open = false;
|
||||
this._warning = "";
|
||||
if (this._pickerProbeTimer !== undefined) {
|
||||
clearTimeout(this._pickerProbeTimer);
|
||||
this._pickerProbeTimer = undefined;
|
||||
@@ -1574,7 +1589,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
<label>${t("trigger_type", L)}</label>
|
||||
<select
|
||||
.value=${this._triggerType}
|
||||
@change=${(e: Event) => (this._triggerType = (e.target as HTMLSelectElement).value)}
|
||||
@change=${(e: Event) => (this._triggerType = (e.target as HTMLSelectElement).value as TriggerType)}
|
||||
>
|
||||
${TRIGGER_TYPE_KEYS_WITH_COMPOUND.map(
|
||||
(key) => html`<option value=${key} ?selected=${key === this._triggerType}>${t(key, L)}</option>`
|
||||
@@ -1750,7 +1765,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
<label>${t("trigger_type", L)}</label>
|
||||
<select
|
||||
.value=${c.type}
|
||||
@change=${(e: Event) => this._patchCondition(i, { type: (e.target as HTMLSelectElement).value })}
|
||||
@change=${(e: Event) => this._patchCondition(i, { type: (e.target as HTMLSelectElement).value as TriggerType })}
|
||||
>
|
||||
${TRIGGER_TYPE_KEYS.map(
|
||||
(key) => html`<option value=${key} ?selected=${key === c.type}>${t(key, L)}</option>`
|
||||
@@ -2602,6 +2617,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
<div class="dialog-title">${title}</div>
|
||||
<div class="content">
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
${this._warning ? html`<div class="error warning">${this._warning}</div>` : nothing}
|
||||
${this._taskId === null && this._objectChoices.length > 0 ? html`
|
||||
<div class="select-row">
|
||||
<label>${t("object", L)}</label>
|
||||
@@ -3373,6 +3389,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
color: var(--text-primary-color, #fff);
|
||||
border-color: var(--primary-color, #03a9f4);
|
||||
}
|
||||
.error.warning { color: var(--warning-color, #ff9800); }
|
||||
.error {
|
||||
color: var(--error-color, #f44336);
|
||||
font-size: 13px;
|
||||
|
||||
@@ -261,6 +261,7 @@ export class MaintenanceTaskDocuments extends LitElement {
|
||||
}}
|
||||
>
|
||||
<div class="tdoc-title">${docDisplayName(doc)}</div>
|
||||
${(doc as { description?: string }).description ? html`<div class="tdoc-desc">${(doc as { description?: string }).description}</div>` : nothing}
|
||||
<div class="tdoc-meta">
|
||||
${meta}${page ? html` · <span class="tdoc-pagetag">${t("doc_page", L)} ${page}</span>` : nothing}
|
||||
</div>
|
||||
@@ -328,6 +329,7 @@ export class MaintenanceTaskDocuments extends LitElement {
|
||||
.tdoc-info:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; }
|
||||
.tdoc-title { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.tdoc-meta { font-size: 12px; color: var(--secondary-text-color, #888); }
|
||||
.tdoc-desc { font-size: 12px; color: var(--secondary-text-color, #888); white-space: pre-line; }
|
||||
.tdoc-pagetag { color: var(--primary-color); font-weight: 500; }
|
||||
.tdoc-page {
|
||||
flex: none; width: 76px; padding: 5px 8px; border-radius: 6px; font: inherit; font-size: 13px;
|
||||
|
||||
+65
-154
@@ -14,14 +14,15 @@
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInterval, formatRecurrence, formatNumber, langOf } from "../styles";
|
||||
import { sharedStyles, t, STATUS_COLORS, formatDate, formatInterval, formatRecurrence, formatCost, formatDuration, currencySymbolOf, langOf, syncCurrencyDecimals} from "../styles";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { isoDateLocal } from "../helpers/calendar-bucket";
|
||||
import { buildCompleteDialogArgs } from "../helpers/complete-dialog-args";
|
||||
import { phaseLabel } from "../helpers/phases";
|
||||
import { historyPhotoIds } from "../helpers/history-photos";
|
||||
import { entryReadingValues, readingSlotDelta } from "../helpers/reading-slots";
|
||||
import "./history-photo";
|
||||
import { buildHistoryEntryDraft } from "../helpers/history-draft";
|
||||
import { readingSlotDelta } from "../helpers/reading-slots";
|
||||
import { taskRef } from "../helpers/reference";
|
||||
import { renderHistoryEntry } from "../renderers/history";
|
||||
import { renderWeibullSection } from "../renderers/weibull";
|
||||
import { renderPredictionSection } from "../renderers/prediction";
|
||||
import { renderRecommendationBars } from "../renderers/recommendation";
|
||||
@@ -40,7 +41,7 @@ import type {
|
||||
|
||||
interface MaintenanceObjectFull {
|
||||
entry_id: string;
|
||||
object: { id: string; name: string };
|
||||
object: { id: string; name: string; ref_no?: number | null };
|
||||
tasks: MaintenanceTask[];
|
||||
}
|
||||
|
||||
@@ -52,6 +53,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
@state() private _taskId: string | null = null;
|
||||
@state() private _task: MaintenanceTask | null = null;
|
||||
@state() private _objectName = "";
|
||||
/** #170: "8.3" once the backend numbered the task — the history rows' "#8.3-n" chips. */
|
||||
@state() private _taskRef: string | null = null;
|
||||
@state() private _busy = false;
|
||||
@state() private _error = "";
|
||||
@state() private _showSkip = false;
|
||||
@@ -100,12 +103,13 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
try {
|
||||
const r = await this.hass.connection.sendMessagePromise<{
|
||||
features?: Partial<AdvancedFeatures>;
|
||||
budget?: { currency_symbol?: string };
|
||||
budget?: { currency_symbol?: string; currency_decimals?: number };
|
||||
}>({ type: "maintenance_supporter/settings" });
|
||||
if (r?.features) {
|
||||
this._features = { ...this._features, ...r.features };
|
||||
}
|
||||
this._currencySymbol = r?.budget?.currency_symbol || "";
|
||||
this._currencySymbol = currencySymbolOf(r?.budget);
|
||||
syncCurrencyDecimals(r?.budget);
|
||||
this._featuresLoaded = true;
|
||||
} catch {
|
||||
// Settings endpoint unavailable — leave defaults (all false), the
|
||||
@@ -129,6 +133,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
this._objectName = r.object?.name || "";
|
||||
const found = (r.tasks || []).find((t) => t.id === this._taskId);
|
||||
this._task = found ?? null;
|
||||
this._taskRef = taskRef(r.object, found);
|
||||
} catch (e) {
|
||||
this._error = describeWsError(e, this._lang);
|
||||
}
|
||||
@@ -311,7 +316,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
interval: this._task.suggested_interval,
|
||||
});
|
||||
if (ok) {
|
||||
this._toast = t("suggestion_applied", this._lang) || "Applied";
|
||||
this._toast = t("suggestion_applied", this._lang);
|
||||
this._notifyChanged("apply_suggestion");
|
||||
// Refresh local task so the recommendation card hides
|
||||
await this._loadTask();
|
||||
@@ -336,8 +341,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
this._toast = r.recommended_interval
|
||||
// The analyzer always works in DAYS (helpers/interval_analyzer.py), so
|
||||
// the unit is pinned here rather than taken from the task's own unit.
|
||||
? `${t("reanalyze_result", this._lang) || "Recomputed"}: ${formatInterval(r.recommended_interval, "days", this._lang)} (${r.data_points} pts)`
|
||||
: (t("reanalyze_insufficient_data", this._lang) || "Not enough data");
|
||||
? `${t("reanalyze_result", this._lang)}: ${formatInterval(r.recommended_interval, "days", this._lang)} (${r.data_points} pts)`
|
||||
: t("reanalyze_insufficient_data", this._lang);
|
||||
await this._loadTask();
|
||||
setTimeout(() => { this._toast = ""; }, 3500);
|
||||
} catch (e) {
|
||||
@@ -349,26 +354,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
|
||||
private _onEditHistoryEntry(entry: HistoryEntry): void {
|
||||
if (!this._entryId || !this._taskId) return;
|
||||
import("../dialog-mount").then(({ openHistoryEditDialog }) => {
|
||||
openHistoryEditDialog({
|
||||
entry_id: this._entryId!,
|
||||
task_id: this._taskId!,
|
||||
original_timestamp: entry.timestamp,
|
||||
type: entry.type,
|
||||
timestamp: entry.timestamp,
|
||||
notes: entry.notes ?? null,
|
||||
cost: entry.cost ?? null,
|
||||
duration: entry.duration ?? null,
|
||||
completed_by: entry.completed_by ?? null,
|
||||
used_parts: entry.used_parts ?? null,
|
||||
photo_doc_ids: historyPhotoIds(entry),
|
||||
reading_value: entry.reading_value ?? null,
|
||||
reading_values: entryReadingValues(entry),
|
||||
readings: this._task?.readings ?? [],
|
||||
task_type: this._task?.type ?? null,
|
||||
reading_unit: this._task?.reading_unit ?? null,
|
||||
});
|
||||
});
|
||||
const draft = buildHistoryEntryDraft(this._entryId, this._taskId, entry, this._task);
|
||||
import("../dialog-mount").then(({ openHistoryEditDialog }) => openHistoryEditDialog(draft));
|
||||
}
|
||||
|
||||
/** Inline recommendation card (Current vs Suggested with apply/reanalyze).
|
||||
@@ -426,7 +413,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
|
||||
if (!hasRecommendation && !hasPrediction && !hasWeibull && !hasSeasonal) {
|
||||
return html`<div class="adaptive-empty">
|
||||
${t("adaptive_no_data", L) || "Not enough completion history yet for adaptive analysis."}
|
||||
${t("adaptive_no_data", L)}
|
||||
</div>`;
|
||||
}
|
||||
return html`
|
||||
@@ -448,30 +435,6 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
/** #161: the entry's readings — per-slot lines with the delta against the
|
||||
* previous entry carrying the slot, or the single value of a scalar task.
|
||||
* Parity with the panel timeline for the Lovelace / strategy path. */
|
||||
private _renderHistoryReadings(entry: HistoryEntry, history: HistoryEntry[], L: string) {
|
||||
const values = entryReadingValues(entry);
|
||||
const fmt = (n: number) => formatNumber(n, L, { maximumFractionDigits: 3 });
|
||||
if (values.length > 0) {
|
||||
return html`<div class="history-readings">
|
||||
${values.map((v) => {
|
||||
const d = readingSlotDelta(history, entry, v.id);
|
||||
return html`<span class="history-reading"><span class="history-reading-name">${v.name}</span>
|
||||
<span class="history-reading-value">${fmt(v.value)}${v.unit ? ` ${v.unit}` : ""}${d == null ? "" : ` (${d >= 0 ? "+" : ""}${fmt(d)})`}</span></span>`;
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
if (entry.reading_value != null) {
|
||||
const unit = this._task?.reading_unit ? ` ${this._task.reading_unit}` : "";
|
||||
return html`<div class="history-readings"><span class="history-reading">
|
||||
<span class="history-reading-name">${t("reading_label", L)}</span>
|
||||
<span class="history-reading-value">${fmt(entry.reading_value)}${unit}</span></span></div>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
/** Read-only details panel: stats + history. Shown when the user clicks
|
||||
* "Show details" in the dialog. Edit-buttons on history entries open the
|
||||
* existing history-edit dialog (which lives in the same dialog-mount). */
|
||||
@@ -495,64 +458,37 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<div class="details">
|
||||
<div class="stats-grid">
|
||||
<div class="stat">
|
||||
<span class="stat-label">${t("times_performed", L) || "Performed"}</span>
|
||||
<span class="stat-label">${t("times_performed", L)}</span>
|
||||
<span class="stat-value">${completed.length}</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">${t("total_cost", L) || "Total cost"}</span>
|
||||
<span class="stat-value">${formatNumber(totalCost, L, 2)}</span>
|
||||
<span class="stat-label">${t("total_cost", L)}</span>
|
||||
<span class="stat-value">${formatCost(totalCost, this._currencySymbol, L)}</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">${t("avg_duration", L) || "Avg duration"}</span>
|
||||
<span class="stat-value">${avgDuration != null ? `${avgDuration}m` : "—"}</span>
|
||||
<span class="stat-label">${t("avg_duration", L)}</span>
|
||||
<span class="stat-value">${formatDuration(avgDuration, L)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="history-header">
|
||||
<strong>${t("history", L) || "History"}</strong>
|
||||
<strong>${t("history", L)}</strong>
|
||||
<span class="history-count">${history.length}</span>
|
||||
</div>
|
||||
${history.length === 0
|
||||
? html`<div class="history-empty">${t("history_empty", L) || "No history yet."}</div>`
|
||||
? html`<div class="history-empty">${t("history_empty", L)}</div>`
|
||||
: html`
|
||||
<div class="history-list">
|
||||
${[...history].reverse().slice(0, 20).map((entry) => {
|
||||
const editable = ["completed", "reset", "skipped"].includes(entry.type);
|
||||
return html`
|
||||
<div class="history-entry">
|
||||
<div class="history-line">
|
||||
<span class="history-type type-${entry.type}">${t(entry.type, L)}</span>
|
||||
<span class="history-date">${formatDateTime(entry.timestamp, L)}</span>
|
||||
${editable
|
||||
? html`<button class="history-edit"
|
||||
title="${t("history_edit_button", L) || "Edit"}"
|
||||
@click=${() => this._onEditHistoryEntry(entry)}>
|
||||
<ha-icon icon="mdi:pencil"></ha-icon>
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>
|
||||
${entry.notes
|
||||
? html`<div class="history-notes">${entry.notes}</div>`
|
||||
: nothing}
|
||||
${this._renderHistoryReadings(entry, history, L)}
|
||||
${(() => {
|
||||
const photos = historyPhotoIds(entry);
|
||||
return photos.length
|
||||
? html`<div class="history-photos">
|
||||
${photos.map((docId) => html`<maintenance-history-photo .hass=${this.hass} .docId=${docId}></maintenance-history-photo>`)}
|
||||
</div>`
|
||||
: nothing;
|
||||
})()}
|
||||
${entry.cost != null || entry.duration != null
|
||||
? html`<div class="history-meta">
|
||||
${entry.cost != null ? html`<span>💰 ${formatNumber(entry.cost, L, 2)}</span>` : nothing}
|
||||
${entry.duration != null ? html`<span>⏱️ ${entry.duration}m</span>` : nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
${[...history].reverse().slice(0, 20).map((entry) => renderHistoryEntry(entry, {
|
||||
lang: L,
|
||||
hass: this.hass,
|
||||
currencySymbol: this._currencySymbol,
|
||||
openEdit: (e) => this._onEditHistoryEntry(e),
|
||||
readingUnit: task.reading_unit,
|
||||
readingSlotDelta: (e, slotId) => readingSlotDelta(history, e, slotId),
|
||||
taskRef: this._taskRef,
|
||||
}, { compact: true }))}
|
||||
${history.length > 20
|
||||
? html`<div class="history-more">… +${history.length - 20} ${t("older_entries", L) || "older"}</div>`
|
||||
? html`<div class="history-more">… +${history.length - 20} ${t("older_entries", L)}</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`}
|
||||
@@ -587,13 +523,13 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
</div>
|
||||
<div class="quick-info">
|
||||
${task.next_due
|
||||
? html`<span><strong>${t("next_due", L) || "Next due"}:</strong> ${formatDate(task.next_due, L)}</span>`
|
||||
? html`<span><strong>${t("next_due", L)}:</strong> ${formatDate(task.next_due, L)}</span>`
|
||||
: nothing}
|
||||
${task.last_performed
|
||||
? html`<span><strong>${t("last_performed", L) || "Last"}:</strong> ${formatDate(task.last_performed, L)}</span>`
|
||||
? html`<span><strong>${t("last_performed", L)}:</strong> ${formatDate(task.last_performed, L)}</span>`
|
||||
: nothing}
|
||||
${(task.schedule?.kind && !["manual", "one_time"].includes(task.schedule.kind)) || task.interval_days != null
|
||||
? html`<span><strong>${t("interval", L) || "Interval"}:</strong> ${formatRecurrence(task, L)}</span>`
|
||||
? html`<span><strong>${t("interval", L)}:</strong> ${formatRecurrence(task, L)}</span>`
|
||||
: nothing}
|
||||
${phaseLabel(task)
|
||||
? html`<span><strong>${t("phase_current", L)}:</strong> ${phaseLabel(task)}</span>`
|
||||
@@ -608,15 +544,15 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
${this._showSkip
|
||||
? html`
|
||||
<div class="inline-form">
|
||||
<label>${t("skip_reason", L) || "Skip reason (optional)"}</label>
|
||||
<label>${t("skip_reason", L)}</label>
|
||||
<input type="text" .value=${this._skipReason}
|
||||
@input=${(e: Event) => { this._skipReason = (e.target as HTMLInputElement).value; }} />
|
||||
<div class="inline-actions">
|
||||
<button class="btn cancel" @click=${() => { this._showSkip = false; }} ?disabled=${this._busy}>
|
||||
${t("cancel", L) || "Cancel"}
|
||||
${t("cancel", L)}
|
||||
</button>
|
||||
<button class="btn primary" @click=${this._onSkipConfirm} ?disabled=${this._busy}>
|
||||
${t("skip", L) || "Skip"}
|
||||
${t("skip", L)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -624,7 +560,7 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
: this._showReset
|
||||
? html`
|
||||
<div class="inline-form">
|
||||
<label>${t("reset_to_date", L) || "Reset last_performed to"}</label>
|
||||
<label>${t("reset_to_date", L)}</label>
|
||||
<ms-date-field
|
||||
kind="date"
|
||||
.hass=${this.hass}
|
||||
@@ -634,10 +570,10 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
></ms-date-field>
|
||||
<div class="inline-actions">
|
||||
<button class="btn cancel" @click=${() => { this._showReset = false; }} ?disabled=${this._busy}>
|
||||
${t("cancel", L) || "Cancel"}
|
||||
${t("cancel", L)}
|
||||
</button>
|
||||
<button class="btn primary" @click=${this._onResetConfirm} ?disabled=${this._busy}>
|
||||
${t("reset", L) || "Reset"}
|
||||
${t("reset", L)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -646,19 +582,19 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<div class="actions primary-row">
|
||||
<ha-button appearance="accent" variant="success" @click=${this._onComplete} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:check"></ha-icon>
|
||||
${t("complete", L) || "Complete"}
|
||||
${t("complete", L)}
|
||||
</ha-button>
|
||||
${task.allow_skip !== false
|
||||
? html`
|
||||
<ha-button appearance="outlined" variant="warning" @click=${() => { this._showSkip = true; }} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:skip-next"></ha-icon>
|
||||
${t("skip", L) || "Skip"}
|
||||
${t("skip", L)}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-button appearance="outlined" variant="neutral" @click=${() => { this._showReset = true; }} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:restart"></ha-icon>
|
||||
${t("reset", L) || "Reset"}
|
||||
${t("reset", L)}
|
||||
</ha-button>
|
||||
</div>
|
||||
${isAdmin
|
||||
@@ -666,21 +602,21 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<div class="actions secondary-row">
|
||||
<ha-button size="small" appearance="outlined" variant="neutral" @click=${this._onEdit} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:pencil"></ha-icon>
|
||||
${t("edit", L) || "Edit"}
|
||||
${t("edit", L)}
|
||||
</ha-button>
|
||||
<ha-button size="small" appearance="outlined" variant="neutral" @click=${this._onQr} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:qrcode"></ha-icon>
|
||||
${t("qr_code", L) || "QR"}
|
||||
${t("qr_code", L)}
|
||||
</ha-button>
|
||||
<ha-button size="small" appearance="outlined" variant="neutral"
|
||||
@click=${task.archived ? this._onUnarchive : this._onArchive}
|
||||
.disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="${task.archived ? 'mdi:archive-arrow-up-outline' : 'mdi:archive-outline'}"></ha-icon>
|
||||
${task.archived ? (t("unarchive", L) || "Unarchive") : (t("archive", L) || "Archive")}
|
||||
${task.archived ? t("unarchive", L) : t("archive", L)}
|
||||
</ha-button>
|
||||
<ha-button size="small" appearance="outlined" variant="danger" class="danger" @click=${this._onDelete} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:delete"></ha-icon>
|
||||
${t("delete", L) || "Delete"}
|
||||
${t("delete", L)}
|
||||
</ha-button>
|
||||
</div>
|
||||
`
|
||||
@@ -689,8 +625,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<button class="link" @click=${() => { this._showDetails = !this._showDetails; }}>
|
||||
<ha-icon icon="${this._showDetails ? 'mdi:chevron-up' : 'mdi:chevron-down'}"></ha-icon>
|
||||
${this._showDetails
|
||||
? (t("hide_details", L) || "Hide details")
|
||||
: (t("show_details", L) || "Show history + stats")}
|
||||
? t("hide_details", L)
|
||||
: t("show_details", L)}
|
||||
</button>
|
||||
${this._features.adaptive
|
||||
|| this._features.seasonal
|
||||
@@ -698,8 +634,8 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
? html`<button class="link" @click=${() => { this._showAdaptive = !this._showAdaptive; }}>
|
||||
<ha-icon icon="${this._showAdaptive ? 'mdi:chart-line' : 'mdi:chart-line-variant'}"></ha-icon>
|
||||
${this._showAdaptive
|
||||
? (t("hide_stats", L) || "Hide stats")
|
||||
: (t("show_stats", L) || "Show stats + graphs")}
|
||||
? t("hide_stats", L)
|
||||
: t("show_stats", L)}
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>
|
||||
@@ -708,12 +644,12 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<div class="footer">
|
||||
<button class="link" @click=${this._onOpenInPanel}>
|
||||
<ha-icon icon="mdi:open-in-new"></ha-icon>
|
||||
${t("open_in_panel", L) || "Open in Maintenance panel"}
|
||||
${t("open_in_panel", L)}
|
||||
</button>
|
||||
</div>
|
||||
`}
|
||||
`
|
||||
: html`<div class="loading">${t("loading", L) || "Loading…"}</div>`}
|
||||
: html`<div class="loading">${t("loading", L)}</div>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -843,41 +779,16 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px;
|
||||
}
|
||||
.history-empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; }
|
||||
.history-list { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow: auto; }
|
||||
.history-entry {
|
||||
padding: 6px 8px; border-radius: 6px;
|
||||
/* The rows themselves are the shared renderer's (.history-entry.compact
|
||||
+ the history-* classes from sharedStyles); only the list chrome is
|
||||
local. */
|
||||
.history-list { display: flex; flex-direction: column; max-height: 280px; overflow: auto; }
|
||||
.history-list .history-entry {
|
||||
padding: 6px 8px; border-radius: 6px; border-bottom: none; margin-bottom: 6px;
|
||||
background: var(--secondary-background-color, rgba(255,255,255,0.03));
|
||||
font-size: 13px;
|
||||
}
|
||||
.history-line {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.history-type {
|
||||
font-weight: 600; font-size: 11px;
|
||||
padding: 2px 6px; border-radius: 4px;
|
||||
text-transform: uppercase; letter-spacing: 0.5px;
|
||||
}
|
||||
.type-completed { background: rgba(46,125,50,0.2); color: #66bb6a; }
|
||||
.type-skipped { background: rgba(158,158,158,0.2); color: var(--secondary-text-color); }
|
||||
.type-reset { background: rgba(33,150,243,0.2); color: #64b5f6; }
|
||||
.type-triggered { background: rgba(255,87,34,0.2); color: #ff8a65; }
|
||||
.history-date { font-size: 11px; color: var(--secondary-text-color); flex: 1; text-align: right; }
|
||||
.history-edit {
|
||||
background: transparent; border: none; cursor: pointer;
|
||||
padding: 4px; border-radius: 4px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.history-edit:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); color: var(--primary-color); }
|
||||
.history-edit ha-icon { --mdc-icon-size: 14px; }
|
||||
.history-notes { margin-top: 4px; color: var(--primary-text-color); }
|
||||
.history-meta { display: flex; gap: 12px; margin-top: 4px; color: var(--secondary-text-color); font-size: 11px; }
|
||||
/* #161: readings + photos on the entry (panel-timeline parity) */
|
||||
.history-readings { display: flex; flex-wrap: wrap; gap: 2px 16px; margin-top: 4px; font-size: 12px; }
|
||||
.history-reading { display: inline-flex; gap: 6px; }
|
||||
.history-reading-name { color: var(--secondary-text-color); }
|
||||
.history-reading-value { font-variant-numeric: tabular-nums; }
|
||||
.history-photos { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
|
||||
.history-list .history-date { font-size: 11px; }
|
||||
.history-list .history-details { font-size: 11px; }
|
||||
.history-more { padding: 8px; text-align: center; font-size: 12px; color: var(--secondary-text-color); font-style: italic; }
|
||||
|
||||
/* Adaptive section — wraps the panel renderers (which assume sharedStyles
|
||||
|
||||
+16
-17
@@ -128,8 +128,7 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
|
||||
private async _endNow(): Promise<void> {
|
||||
if (!this._isAdmin) return;
|
||||
if (!window.confirm(t("vacation_end_now_confirm", this._lang)
|
||||
|| "End vacation immediately?")) return;
|
||||
if (!window.confirm(t("vacation_end_now_confirm", this._lang))) return;
|
||||
this._busy = true;
|
||||
try {
|
||||
const r = await this.hass.connection.sendMessagePromise<VacationState>({
|
||||
@@ -155,16 +154,16 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
const L = this._lang;
|
||||
const s = this._state;
|
||||
if (!s) {
|
||||
return html`<ha-card><div class="loading">${t("loading", L) || "Loading…"}</div></ha-card>`;
|
||||
return html`<ha-card><div class="loading">${t("loading", L)}</div></ha-card>`;
|
||||
}
|
||||
const active = s.is_active === true;
|
||||
const enabled = s.enabled === true;
|
||||
const exemptCount = s.exempt_task_ids?.length ?? 0;
|
||||
const statusLabel = active
|
||||
? (t("vacation_status_active", L) || "Active now")
|
||||
? t("vacation_status_active", L)
|
||||
: enabled
|
||||
? (t("vacation_status_scheduled", L) || "Scheduled")
|
||||
: (t("vacation_status_inactive", L) || "Inactive");
|
||||
? t("vacation_status_scheduled", L)
|
||||
: t("vacation_status_inactive", L);
|
||||
const statusClass = active ? "active" : enabled ? "scheduled" : "inactive";
|
||||
|
||||
return html`
|
||||
@@ -173,7 +172,7 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<span class="emoji">🏖️</span>
|
||||
<span>${this._config.title || (t("vacation_mode", L) || "Vacation mode")}</span>
|
||||
<span>${this._config.title || t("vacation_mode", L)}</span>
|
||||
</div>
|
||||
<span class="status-pill ${statusClass}">${statusLabel}</span>
|
||||
</div>
|
||||
@@ -183,7 +182,7 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
${this._isAdmin
|
||||
? html`
|
||||
<div class="row toggle-row">
|
||||
<label>${t("enable", L) || "Enable"}</label>
|
||||
<label>${t("enable", L)}</label>
|
||||
<ha-switch
|
||||
.checked=${enabled}
|
||||
.disabled=${this._busy}
|
||||
@@ -194,7 +193,7 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
|
||||
<div class="dates-row">
|
||||
<div class="date-field">
|
||||
<label>${t("vacation_start", L) || "Start"}</label>
|
||||
<label>${t("vacation_start", L)}</label>
|
||||
<ms-date-field
|
||||
kind="date"
|
||||
clearable
|
||||
@@ -209,7 +208,7 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
></ms-date-field>
|
||||
</div>
|
||||
<div class="date-field">
|
||||
<label>${t("vacation_end", L) || "End"}</label>
|
||||
<label>${t("vacation_end", L)}</label>
|
||||
<ms-date-field
|
||||
kind="date"
|
||||
clearable
|
||||
@@ -224,7 +223,7 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
></ms-date-field>
|
||||
</div>
|
||||
<div class="date-field buffer">
|
||||
<label>${t("vacation_buffer", L) || "Buffer days"}</label>
|
||||
<label>${t("vacation_buffer", L)}</label>
|
||||
<input type="number" min="0" max="14"
|
||||
.value=${String(this._localBuffer)}
|
||||
?disabled=${this._busy}
|
||||
@@ -242,24 +241,24 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
?disabled=${this._busy || !this._dirty}>
|
||||
<ha-icon icon="${this._dirty ? "mdi:content-save" : "mdi:check"}"></ha-icon>
|
||||
${this._dirty
|
||||
? (t("save", L) || "Save")
|
||||
: (t("saved", L) || "Saved")}
|
||||
? t("save", L)
|
||||
: t("saved", L)}
|
||||
</button>
|
||||
${active
|
||||
? html`<button class="btn"
|
||||
@click=${this._endNow}
|
||||
?disabled=${this._busy}>
|
||||
${t("vacation_end_now", L) || "End now"}
|
||||
${t("vacation_end_now", L)}
|
||||
</button>`
|
||||
: nothing}
|
||||
${exemptCount > 0
|
||||
? html`<button class="btn link"
|
||||
@click=${this._onDeepLink}>
|
||||
${exemptCount} ${t("vacation_exempt_count", L) || "exempt"}…
|
||||
${exemptCount} ${t("vacation_exempt_count", L)}…
|
||||
</button>`
|
||||
: html`<button class="btn link"
|
||||
@click=${this._onDeepLink}>
|
||||
${t("vacation_advanced", L) || "Advanced…"}
|
||||
${t("vacation_advanced", L)}
|
||||
</button>`}
|
||||
</div>
|
||||
`
|
||||
@@ -269,7 +268,7 @@ export class MaintenanceVacationSectionCard extends LitElement {
|
||||
? html`<div>${s.start} → ${s.end}</div>`
|
||||
: nothing}
|
||||
<button class="btn link" @click=${this._onDeepLink}>
|
||||
${t("vacation_open_panel", L) || "Open in panel"}
|
||||
${t("vacation_open_panel", L)}
|
||||
</button>
|
||||
</div>
|
||||
`}
|
||||
|
||||
@@ -257,7 +257,7 @@ export function openHistoryEditDialog(draft: HistoryEntryDraft): boolean {
|
||||
export function openCompleteDialog(args: CompleteDialogArgs): boolean {
|
||||
const dlg = getOrCreate<MaintenanceCompleteDialog>(COMPLETE_DIALOG_TAG);
|
||||
if (!syncHass(dlg)) return false;
|
||||
fillAndOpenCompleteDialog(dlg, args, (getHass()?.language) || "en");
|
||||
fillAndOpenCompleteDialog(dlg, args, langOf(getHass()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
export const LS_KEYS = {
|
||||
/** #170: service-booklet print options (layout + include switches). */
|
||||
printOptions: "msp-print-options",
|
||||
/** D#162: the battery roster starts open; a deliberate collapse is remembered. */
|
||||
batteryRosterOpen: "msp-bf-roster-open",
|
||||
/** #164: how the object's document list is ordered (newest | oldest | title | category). */
|
||||
docSort: "msp-doc-sort",
|
||||
/** #161: the camera (deviceId) the in-app viewfinder should open with. */
|
||||
cameraDevice: "msp-camera-device",
|
||||
overviewTab: "msp-overview-tab",
|
||||
collapsedSections: "msp-collapsed-sections",
|
||||
chartRange: "msp-chart-range",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Doba běhu",
|
||||
"runtime_hours": "Cílová doba běhu (hodiny)",
|
||||
"target_value": "Cílová hodnota",
|
||||
"baseline": "Základní hodnota",
|
||||
"target_changes": "Cílový počet změn",
|
||||
"for_minutes": "Po dobu (minut)",
|
||||
"time_based": "Časový",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Vytvořeno",
|
||||
"report_times_done": "Provedeno",
|
||||
"report_total_cost": "Celkové náklady",
|
||||
"report_every": "každých {n} {unit}",
|
||||
"report_notes": "Poznámky",
|
||||
"report_col_type": "Typ",
|
||||
"report_col_status": "Stav",
|
||||
"report_col_schedule": "Plán",
|
||||
"duration": "Doba trvání",
|
||||
"minutes_short": "min",
|
||||
"both": "Obojí",
|
||||
"trigger_val": "Hodnota spouštěče",
|
||||
"complete_title": "Dokončit: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: špatný typ (očekáván: {type})",
|
||||
"err_invalid_choice": "{field}: nepovolená hodnota",
|
||||
"err_invalid_value": "{field}: neplatná hodnota",
|
||||
"ws_err_not_found": "Nenalezeno — mezitím to mohlo být smazáno",
|
||||
"ws_err_invalid_input": "Neplatný vstup",
|
||||
"ws_err_invalid_date": "Neplatné datum",
|
||||
"ws_err_invalid_format": "Neplatný formát",
|
||||
"ws_err_invalid_url": "Neplatný odkaz",
|
||||
"ws_err_not_configured": "Zatím nenastaveno",
|
||||
"ws_err_not_loaded": "Zatím nenačteno — zkuste to za chvíli znovu",
|
||||
"ws_err_limit_reached": "Dosažen limit",
|
||||
"ws_err_too_many": "Příliš mnoho položek",
|
||||
"ws_err_too_large": "Příliš velké",
|
||||
"ws_err_not_archived": "Není archivováno",
|
||||
"ws_err_invalid_target": "Neplatný cíl",
|
||||
"ws_err_create_failed": "Nepodařilo se vytvořit",
|
||||
"ws_err_archived": "Archivováno — nejprve obnovte",
|
||||
"ws_err_already_archived": "Již archivováno",
|
||||
"ws_err_unavailable": "Momentálně nedostupné",
|
||||
"ws_err_unauthorized": "Pro vašeho uživatele není povoleno",
|
||||
"ws_err_too_many_views": "Příliš mnoho uložených pohledů",
|
||||
"ws_err_storage_unavailable": "Úložiště není dostupné",
|
||||
"ws_err_replace_failed": "Nahrazení se nezdařilo",
|
||||
"ws_err_not_paused": "Není pozastaveno",
|
||||
"ws_err_not_available": "Nic k dispozici",
|
||||
"ws_err_no_url": "Nebyl zadán odkaz",
|
||||
"ws_err_no_phases": "Tento úkol nemá fáze",
|
||||
"ws_err_invalid_view": "Neplatný pohled",
|
||||
"ws_err_invalid_user": "Neznámý uživatel",
|
||||
"ws_err_invalid_range": "Neplatný rozsah",
|
||||
"ws_err_invalid_parent": "Neplatný nadřazený objekt",
|
||||
"ws_err_invalid_device": "Neznámé zařízení",
|
||||
"ws_err_empty_csv": "Soubor CSV je prázdný",
|
||||
"ws_err_empty": "Není co importovat",
|
||||
"ws_err_duplicate_failed": "Nepodařilo se duplikovat",
|
||||
"ws_err_already_paused": "Již pozastaveno",
|
||||
"ws_err_invalid_cursor": "Neplatná pozice fáze",
|
||||
"ws_err_invalid_entity_slug": "Neplatný název entity",
|
||||
"ws_err_invalid_trigger_config": "Neplatná konfigurace spouštěče",
|
||||
"ws_err_no_defaults": "Nejsou nastaveny výchozí hodnoty pro rychlé dokončení",
|
||||
"ws_err_self_link_device": "Objekt nelze propojit sám se sebou",
|
||||
"ws_err_too_early": "Příliš brzy — tento úkol lze dokončit až blíže k termínu",
|
||||
"feat_schedule_time": "Plánování podle denní doby",
|
||||
"feat_schedule_time_desc": "Úkoly se stanou po termínu v určenou denní dobu místo o půlnoci.",
|
||||
"schedule_time_toggle": "Termín v určitou hodinu",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Spustit při rovnosti (=)",
|
||||
"trigger_not_equals": "Spustit při odlišnosti od (≠)",
|
||||
"for_at_least_minutes": "Po dobu alespoň (minut)",
|
||||
"safety_interval_days": "Bezpečnostní interval (dny, volitelný)",
|
||||
"safety_interval": "Bezpečnostní interval (volitelný)",
|
||||
"trigger_combinator": "Kombinovat spouštěč a interval",
|
||||
"trigger_combinator_any": "Spouštěč nebo interval (co dřív)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Moje úkoly",
|
||||
"tab_calendar": "Kalendář",
|
||||
"cal_no_events": "Bez údržby",
|
||||
"cal_window_7": "7 dní",
|
||||
"cal_window_14": "14 dní",
|
||||
"cal_window_30": "30 dní",
|
||||
"cal_window_365": "1 rok",
|
||||
"cal_every_n_days": "každých {n} dní",
|
||||
"cal_source_time": "Čas",
|
||||
"cal_source_time_adaptive": "Čas (adaptivní)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Nepodařilo se vygenerovat QR kód.",
|
||||
"qr_error_no_url": "Není nakonfigurováno URL HA. Nastavte externí nebo interní URL v Nastavení → Systém → Síť.",
|
||||
"save_error": "Nepodařilo se uložit. Zkuste to znovu.",
|
||||
"subsave_warning": "Uloženo, ale jedno nastavení bylo odmítnuto: {detail}",
|
||||
"qr_print": "Tisk",
|
||||
"qr_download": "Stáhnout SVG",
|
||||
"qr_action": "Akce při skenování",
|
||||
"qr_action_view": "Zobrazit informace o údržbě",
|
||||
"qr_action_complete": "Označit údržbu jako dokončenou",
|
||||
"qr_url_mode": "Typ odkazu",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Lokální (mDNS)",
|
||||
"qr_mode_server": "URL serveru",
|
||||
"overview": "Přehled",
|
||||
"analysis": "Analýza",
|
||||
"recent_activities": "Nedávné aktivity",
|
||||
"search_notes": "Hledat v poznámkách",
|
||||
"avg_cost": "Prům. náklady",
|
||||
"no_advanced_features": "Žádné pokročilé funkce nejsou povoleny",
|
||||
"no_advanced_features_hint": "Povolte „Adaptivní intervaly” nebo „Sezónní vzory” v nastavení integrace pro zobrazení analytických dat.",
|
||||
"analysis_not_enough_data": "Zatím nedostatek dat pro analýzu.",
|
||||
"analysis_not_enough_data_hint": "Weibullova analýza vyžaduje alespoň 5 dokončených údržeb; sezónní vzory se stanou viditelné po 6+ datových bodech na měsíc.",
|
||||
"analysis_manual_task_hint": "Manuální úkoly bez intervalu negenerují analytická data.",
|
||||
"completions": "dokončení",
|
||||
"current": "Aktuální",
|
||||
"shorter": "Kratší",
|
||||
"longer": "Delší",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Minulá okna",
|
||||
"cal_forward_windows": "Budoucí okna",
|
||||
"history_edit_title": "Upravit záznam historie",
|
||||
"history_delete_entry": "Smazat záznam",
|
||||
"history_delete_confirm": "Smazat tento záznam historie? Datum posledního provedení se vrátí k předchozímu dokončení; fotografie zůstanou u objektu a spotřebované díly se nevrátí na sklad.",
|
||||
"history_edit_timestamp": "Časové razítko",
|
||||
"manufacturer": "Výrobce",
|
||||
"model": "Model",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Výchozí dny upozornění",
|
||||
"settings_consumable_threshold": "Práh „spotřební materiál nízký“ (%)",
|
||||
"settings_battery_low_percent": "Práh „baterie slabá“ (%)",
|
||||
"settings_battery_lifetimes": "Typická životnost baterií",
|
||||
"settings_battery_lifetimes_hint": "Použije se jen u baterií bez procent: poznámka Battery Notes jen s typem a datem výměny nebo senzor hlásící pouze „slabá“. Splatnost = poslední výměna + tato životnost. Baterie hlásící úroveň mají prognózu z naměřeného vybíjení. Po třech výměnách daného typu nahradí medián intervalů vestavěnou hodnotu — vaše hodnota má vždy přednost.",
|
||||
"settings_battery_lifetime_months": "měsíců",
|
||||
"settings_battery_lifetime_reset": "Použít výchozí",
|
||||
"settings_battery_lifetime_in_fleet": "ve vaší flotile",
|
||||
"settings_battery_lifetime_learned_models": "Naučeno podle modelu: {list}",
|
||||
"settings_thresholds_hint": "Výchozí hodnoty pro nová Navrhovaná nastavení (zbývající procenta) a pro baterie flotily bez vlastního prahu Battery Notes. Stávající úkoly si ponechají svou hodnotu.",
|
||||
"settings_value_out_of_range": "Hodnota musí být mezi {min} a {max}",
|
||||
"bn_summary": "{name}: {pct} % pro {n} baterií",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} zařízení s vlastním prahem:",
|
||||
"bn_more": "+ {n} dalších",
|
||||
"settings_row_actions": "Akce v řádcích úkolů",
|
||||
"settings_ref_numbers_in_lists": "Referenční čísla v seznamech",
|
||||
"settings_ref_numbers_in_lists_hint": "Zobrazí štítky #8 / #8.3 před názvy objektů a úkolů v seznamu Dnes, tabulce úkolů, kartách objektů a tabulce objektů.",
|
||||
"row_actions_buttons_compact": "Tlačítka (na telefonu jen ikony)",
|
||||
"row_actions_buttons": "Tlačítka s textem",
|
||||
"row_actions_icons": "Ikony (klasické)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Doba odložení (hodiny)",
|
||||
"settings_budget": "Rozpočet",
|
||||
"settings_currency": "Měna",
|
||||
"settings_currency_decimals": "Desetinná místa částek",
|
||||
"settings_currency_decimals_hint": "Kolik desetinných míst zobrazuje každá částka: KPI, rozpočty, náklady v seznamech a sestavách, rozpočtová upozornění. 0 = celá čísla.",
|
||||
"settings_budget_monthly": "Měsíční rozpočet",
|
||||
"settings_budget_yearly": "Roční rozpočet",
|
||||
"settings_budget_alerts": "Rozpočtová upozornění",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exportovat YAML",
|
||||
"settings_export_csv": "Exportovat CSV",
|
||||
"settings_export_settings": "Exportovat nastavení (JSON)",
|
||||
"settings_import_csv": "Importovat CSV",
|
||||
"settings_import_placeholder": "Vložte sem obsah JSON nebo CSV…",
|
||||
"settings_import_btn": "Importovat",
|
||||
"settings_import_success": "{count} objektů úspěšně importováno.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Akce HA na úlohu při dokončení + QR rychlého dokončení s předvolenými hodnotami.",
|
||||
"on_complete_action_title": "Při dokončení: spustit akci HA (volitelné)",
|
||||
"on_complete_action_desc": "Volá službu HA, když je úloha dokončena — např. resetuje čítač na zařízení.",
|
||||
"on_complete_action_service": "Služba",
|
||||
"on_complete_action_target": "Cílová entita",
|
||||
"on_complete_action_data": "Data (JSON, volitelné)",
|
||||
"on_complete_action_test": "Testovat akci",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Vybrat fotky",
|
||||
"choose_photo": "Vybrat fotku",
|
||||
"photos_android_hint": "Aplikace pro Android předá jednu fotku na výběr — každý výběr se přidá.",
|
||||
"add_photos": "Přidat fotky",
|
||||
"photos_limit": "Nejvýše {max} fotek na dokončení",
|
||||
"history_edit_photos_hint": "Zde odebraná fotka zůstává v dokumentech objektu.",
|
||||
"uploading": "Nahrávání…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Stáhnout",
|
||||
"doc_close": "Zavřít",
|
||||
"doc_camera": "Vyfotit",
|
||||
"camera_capture_shoot": "Vyfotit",
|
||||
"camera_switch_lens": "Přepnout kameru",
|
||||
"doc_drop_hint": "Přetáhněte soubory sem",
|
||||
"doc_task_none": "Žádné dokumenty spojené s tímto úkolem.",
|
||||
"doc_link_existing": "Připojit dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Zaznamená dokončení (nastaví poslední provedení), jakmile se spouštěč sám vyřeší — např. doplněná sůl, vyměněný filtr.",
|
||||
"doc_search": "Hledat dokumenty…",
|
||||
"doc_search_none": "Žádné odpovídající dokumenty",
|
||||
"doc_description": "Popis (volitelné)",
|
||||
"doc_sort": "Seřadit dokumenty",
|
||||
"doc_sort_newest": "Nejnovější první",
|
||||
"doc_sort_oldest": "Nejstarší první",
|
||||
"doc_sort_title": "Podle názvu (1, 2, 3 …)",
|
||||
"doc_sort_category": "Podle kategorie",
|
||||
"link_device_optional": "Propojit s existujícím zařízením (volitelné)",
|
||||
"parent_object_optional": "Nadřazený objekt (volitelné)",
|
||||
"parent_none": "(Žádný nadřazený)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Upozorňovat jen pro zobrazení",
|
||||
"settings_notify_scope_all": "Všechny úkoly",
|
||||
"settings_notify_scope_hint": "Připomenutí posílají jen úkoly odpovídající filtrům štítku/uživatele zvoleného zobrazení. Stav, řazení a seskupení zobrazení se zde ignorují.",
|
||||
"settings_notify_completed": "Oznámení o dokončení",
|
||||
"settings_notify_completed_hint": "Zpráva pro domácnost, když je úkol dokončen, s důvodem a tím, kdo to udělal. „Jen automatická“ hlásí pouze dokončení, která nikdo neudělal na místě: zotavení senzoru, odškrtnutí v nákupním seznamu, volání služby automatizací. Jen na službu domácnosti; tiché hodiny a denní limit platí.",
|
||||
"notify_completed_off": "Vypnuto",
|
||||
"notify_completed_automatic": "Jen automatická dokončení",
|
||||
"notify_completed_all": "Všechna dokončení",
|
||||
"settings_notify_rule": "Vlastní pravidlo oznámení",
|
||||
"settings_notify_rule_hint": "Každé oznámení navíc vyvolá událost maintenance_supporter_notification (druh, stav, objekt, úkol, referenční čísla, priorita, odkaz, data). Nasměrujte ji automatizací — např. do Tickeru, Telegramu nebo Pushoveru.",
|
||||
"settings_notify_event_only": "Jen událost, nic neposílat",
|
||||
"settings_notify_event_only_hint": "Doručení zajišťuje vaše automatizace. Intervaly opakování, tiché hodiny a denní limit dál rozhodují, kdy se událost vyvolá.",
|
||||
"settings_notify_extra_data": "Další data oznámení (šablona)",
|
||||
"settings_notify_extra_data_hint": "JSON nebo YAML, Jinja povoleno; sloučí se do data volání notify (vaše klíče mají přednost). Proměnné: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Ověřte tlačítkem Odeslat test.",
|
||||
"settings_notify_extra_data_hint": "JSON nebo YAML, Jinja povoleno; sloučí se do data volání notify (vaše klíče mají přednost). Proměnné: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Ověřte tlačítkem Odeslat test.",
|
||||
"card_saved_view": "Uložené zobrazení",
|
||||
"card_saved_view_none": "Žádné",
|
||||
"card_saved_view_help": "Použije filtry stavu, uživatele a štítku zobrazení navíc k výše uvedeným filtrům. Řazení a seskupení zobrazení jsou nastavení panelu a na kartě se nepoužijí.",
|
||||
"panel_card_tab": "Otevřít na kartě",
|
||||
"panel_card_tab_default": "Zapamatovaná karta (jako v panelu)",
|
||||
"panel_card_view": "Otevřít s uloženým pohledem",
|
||||
"panel_card_height": "Výška",
|
||||
"panel_card_height_help": "Prázdné = vyplní obrazovku pod kartou (zobrazení panel). Jinak délka CSS jako 600px nebo 80vh pro zobrazení s dalšími kartami.",
|
||||
"panel_card_not_registered": "Panel Maintenance Supporter není k dispozici — je integrace nastavená?",
|
||||
"panel_card_load_failed": "Panel Maintenance Supporter se nepodařilo načíst — obnovte stránku (po aktualizaci: vymažte mezipaměť prohlížeče).",
|
||||
"doc_part_none": "K tomuto dílu nejsou připojeny žádné dokumenty.",
|
||||
"settings_templates_toggle_group": "Zapnout nebo vypnout všechny šablony v této skupině",
|
||||
"setups_button": "Navrhovaná nastavení",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "V pořádku",
|
||||
"battery_fleet_status_due": "K výměně",
|
||||
"battery_fleet_predicted_on": "Očekáváno kolem {date}",
|
||||
"battery_fleet_predicted_typical": "Očekáváno kolem {date} — {months} měsíců typicky pro {type} ({source})",
|
||||
"lifetime_source_default": "vestavěná výchozí hodnota",
|
||||
"lifetime_source_table": "vestavěná tabulka",
|
||||
"lifetime_source_learned": "naučeno z {n} výměn",
|
||||
"lifetime_source_override": "vaše nastavení",
|
||||
"lifetime_source_learned_device": "naučeno z {n} výměn tohoto zařízení",
|
||||
"lifetime_source_learned_model": "naučeno z {n} výměn u zařízení tohoto modelu",
|
||||
"battery_fleet_predicted_trend": "Předpověď z trendu vybíjení této baterie: přibližně {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Akumulátor: nabíjí se místo výměny — nikdy na nákupním seznamu",
|
||||
"battery_fleet_sort_name": "Řadit podle názvu",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Použité díly",
|
||||
"print_inc_photos": "Fotky",
|
||||
"print_inc_documents": "Propojené dokumenty",
|
||||
"print_inc_doc_desc": "Popisy dokumentů",
|
||||
"print_inc_checklist": "Kontrolní seznam",
|
||||
"print_inc_notes": "Poznámky",
|
||||
"print_inc_costs": "Náklady a doba",
|
||||
"print_inc_person": "Provedl",
|
||||
"print_inc_refs": "Referenční čísla",
|
||||
"print_inc_bare": "Dokončení bez podrobností",
|
||||
"print_inc_qr": "QR kód pro každý úkol",
|
||||
"print_button": "Tisk",
|
||||
"report_scan_hint": "Naskenujte pro otevření úkolu",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Driftstid",
|
||||
"runtime_hours": "Måldriftstid (timer)",
|
||||
"target_value": "Målværdi",
|
||||
"baseline": "Referenceværdi",
|
||||
"target_changes": "Antal målændringer",
|
||||
"for_minutes": "I (minutter)",
|
||||
"time_based": "Tidsbaseret",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Genereret",
|
||||
"report_times_done": "Udført",
|
||||
"report_total_cost": "Samlede omkostninger",
|
||||
"report_every": "hver {n}. {unit}",
|
||||
"report_notes": "Noter",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Tidsplan",
|
||||
"duration": "Varighed",
|
||||
"minutes_short": "min",
|
||||
"both": "Begge",
|
||||
"trigger_val": "Udløserværdi",
|
||||
"complete_title": "Fuldfør: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: forkert type (forventet: {type})",
|
||||
"err_invalid_choice": "{field}: ikke en tilladt værdi",
|
||||
"err_invalid_value": "{field}: ugyldig værdi",
|
||||
"ws_err_not_found": "Ikke fundet — det er måske blevet slettet i mellemtiden",
|
||||
"ws_err_invalid_input": "Ugyldig input",
|
||||
"ws_err_invalid_date": "Ugyldig dato",
|
||||
"ws_err_invalid_format": "Ugyldigt format",
|
||||
"ws_err_invalid_url": "Ugyldigt link",
|
||||
"ws_err_not_configured": "Ikke sat op endnu",
|
||||
"ws_err_not_loaded": "Ikke indlæst endnu — prøv igen om lidt",
|
||||
"ws_err_limit_reached": "Grænsen er nået",
|
||||
"ws_err_too_many": "For mange poster",
|
||||
"ws_err_too_large": "For stort",
|
||||
"ws_err_not_archived": "Ikke arkiveret",
|
||||
"ws_err_invalid_target": "Ugyldigt mål",
|
||||
"ws_err_create_failed": "Kunne ikke oprettes",
|
||||
"ws_err_archived": "Arkiveret — gendan først",
|
||||
"ws_err_already_archived": "Allerede arkiveret",
|
||||
"ws_err_unavailable": "Ikke tilgængelig lige nu",
|
||||
"ws_err_unauthorized": "Ikke tilladt for din bruger",
|
||||
"ws_err_too_many_views": "For mange gemte visninger",
|
||||
"ws_err_storage_unavailable": "Lageret er ikke tilgængeligt",
|
||||
"ws_err_replace_failed": "Udskiftning mislykkedes",
|
||||
"ws_err_not_paused": "Ikke sat på pause",
|
||||
"ws_err_not_available": "Intet tilgængeligt",
|
||||
"ws_err_no_url": "Intet link angivet",
|
||||
"ws_err_no_phases": "Opgaven har ingen faser",
|
||||
"ws_err_invalid_view": "Ugyldig visning",
|
||||
"ws_err_invalid_user": "Ukendt bruger",
|
||||
"ws_err_invalid_range": "Ugyldigt interval",
|
||||
"ws_err_invalid_parent": "Ugyldigt overordnet objekt",
|
||||
"ws_err_invalid_device": "Ukendt enhed",
|
||||
"ws_err_empty_csv": "CSV-filen er tom",
|
||||
"ws_err_empty": "Intet at importere",
|
||||
"ws_err_duplicate_failed": "Kunne ikke duplikeres",
|
||||
"ws_err_already_paused": "Allerede sat på pause",
|
||||
"ws_err_invalid_cursor": "Ugyldig faseposition",
|
||||
"ws_err_invalid_entity_slug": "Ugyldigt entitetsnavn",
|
||||
"ws_err_invalid_trigger_config": "Ugyldig triggerkonfiguration",
|
||||
"ws_err_no_defaults": "Ingen standardværdier for hurtig fuldførelse",
|
||||
"ws_err_self_link_device": "Et objekt kan ikke knyttes til sig selv",
|
||||
"ws_err_too_early": "For tidligt — opgaven kan først fuldføres tættere på forfaldsdatoen",
|
||||
"feat_schedule_time": "Planlægning efter tidspunkt på dagen",
|
||||
"feat_schedule_time_desc": "Opgaver bliver forfaldne på et bestemt tidspunkt på dagen i stedet for midnat.",
|
||||
"schedule_time_toggle": "Forfalder på et bestemt tidspunkt",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Udløs ved lig med (=)",
|
||||
"trigger_not_equals": "Udløs ved forskellig fra (≠)",
|
||||
"for_at_least_minutes": "I mindst (minutter)",
|
||||
"safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)",
|
||||
"safety_interval": "Sikkerhedsinterval (valgfrit)",
|
||||
"trigger_combinator": "Kombinér trigger og interval",
|
||||
"trigger_combinator_any": "Trigger eller interval (først opfyldt)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Mine opgaver",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Ingen vedligeholdelse",
|
||||
"cal_window_7": "7 dage",
|
||||
"cal_window_14": "14 dage",
|
||||
"cal_window_30": "30 dage",
|
||||
"cal_window_365": "1 år",
|
||||
"cal_every_n_days": "hver {n}. dag",
|
||||
"cal_source_time": "Tidsbaseret",
|
||||
"cal_source_time_adaptive": "Tidsbaseret (adaptiv)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "Kunne ikke generere QR-kode.",
|
||||
"qr_error_no_url": "Ingen HA-URL konfigureret. Angiv en ekstern eller intern URL under Indstillinger → System → Netværk.",
|
||||
"save_error": "Kunne ikke gemme. Prøv igen.",
|
||||
"subsave_warning": "Gemt, men én indstilling blev afvist: {detail}",
|
||||
"qr_print": "Udskriv",
|
||||
"qr_download": "Download SVG",
|
||||
"qr_action": "Handling ved scanning",
|
||||
"qr_action_view": "Vis vedligeholdelsesoplysninger",
|
||||
"qr_action_complete": "Marker vedligeholdelse som fuldført",
|
||||
"qr_url_mode": "Linktype",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Lokal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Oversigt",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Seneste aktiviteter",
|
||||
"search_notes": "Søg i noter",
|
||||
"avg_cost": "Gns. omkostning",
|
||||
"no_advanced_features": "Ingen avancerede funktioner aktiveret",
|
||||
"no_advanced_features_hint": "Aktiver „Adaptive intervaller” eller „Sæsonmønstre” i integrationsindstillingerne for at se analysedata her.",
|
||||
"analysis_not_enough_data": "Ikke nok data til analyse endnu.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analyse kræver mindst 5 fuldførte vedligeholdelser; sæsonmønstre bliver synlige efter 6+ datapunkter pr. måned.",
|
||||
"analysis_manual_task_hint": "Manuelle opgaver uden et interval genererer ikke analysedata.",
|
||||
"completions": "fuldførelser",
|
||||
"current": "Nuværende",
|
||||
"shorter": "Kortere",
|
||||
"longer": "Længere",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Tidligere vinduer",
|
||||
"cal_forward_windows": "Kommende vinduer",
|
||||
"history_edit_title": "Rediger historikpost",
|
||||
"history_delete_entry": "Slet post",
|
||||
"history_delete_confirm": "Slet denne historikpost? Datoen for seneste udførelse falder tilbage til den forrige; fotos bliver hos objektet, og forbrugte dele lægges ikke tilbage på lager.",
|
||||
"history_edit_timestamp": "Tidsstempel",
|
||||
"manufacturer": "Producent",
|
||||
"model": "Model",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Standard advarselsdage",
|
||||
"settings_consumable_threshold": "Tærskel „forbrugsvare lav“ (%)",
|
||||
"settings_battery_low_percent": "Tærskel „batteri lavt“ (%)",
|
||||
"settings_battery_lifetimes": "Typiske batterilevetider",
|
||||
"settings_battery_lifetimes_hint": "Bruges kun til batterier uden procent: en Battery Notes-note med kun type og udskiftningsdato, eller en sensor der kun melder \"lavt\". Forfaldsdato = seneste udskiftning + denne levetid. Batterier der melder niveau får prognosen fra den målte afladning. Når flåden har set tre udskiftninger af en type, overtager medianintervallet fra den indbyggede værdi — din egen værdi vinder altid.",
|
||||
"settings_battery_lifetime_months": "måneder",
|
||||
"settings_battery_lifetime_reset": "Brug standard",
|
||||
"settings_battery_lifetime_in_fleet": "i din flåde",
|
||||
"settings_battery_lifetime_learned_models": "Lært pr. model: {list}",
|
||||
"settings_thresholds_hint": "Standardværdier for nye Foreslåede opsætninger (resterende procent) og for flådebatterier uden egen Battery Notes-tærskel. Eksisterende opgaver beholder deres værdi.",
|
||||
"settings_value_out_of_range": "Værdien skal være mellem {min} og {max}",
|
||||
"bn_summary": "{name}: {pct} % for {n} batterier",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} enheder med egen tærskel:",
|
||||
"bn_more": "+ {n} mere",
|
||||
"settings_row_actions": "Handlinger i opgaverækker",
|
||||
"settings_ref_numbers_in_lists": "Referencenumre i lister",
|
||||
"settings_ref_numbers_in_lists_hint": "Viser brikkerne #8 / #8.3 foran objekt- og opgavenavne i I dag-listen, opgavetabellen, objektkortene og objekttabellen.",
|
||||
"row_actions_buttons_compact": "Knapper (kun ikoner på telefonen)",
|
||||
"row_actions_buttons": "Knapper med tekst",
|
||||
"row_actions_icons": "Ikoner (klassisk)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Udsættelsesvarighed (timer)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Decimaler for beløb",
|
||||
"settings_currency_decimals_hint": "Hvor mange decimaler hvert beløb viser: nøgletal, budgetter, omkostninger i lister og rapporter, budgetadvarsler. 0 = hele tal.",
|
||||
"settings_budget_monthly": "Månedligt budget",
|
||||
"settings_budget_yearly": "Årligt budget",
|
||||
"settings_budget_alerts": "Budgetadvarsler",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Eksporter YAML",
|
||||
"settings_export_csv": "Eksporter CSV",
|
||||
"settings_export_settings": "Eksportér indstillinger (JSON)",
|
||||
"settings_import_csv": "Importer CSV",
|
||||
"settings_import_placeholder": "Indsæt JSON- eller CSV-indhold her…",
|
||||
"settings_import_btn": "Importer",
|
||||
"settings_import_success": "{count} objekter importeret.",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "HA-handling pr. opgave ved fuldførelse + hurtig-fuldfør-QR med forudindstillede værdier.",
|
||||
"on_complete_action_title": "Ved fuldførelse: udløs HA-handling (valgfrit)",
|
||||
"on_complete_action_desc": "Kalder en HA-tjeneste, når opgaven fuldføres — f.eks. nulstil en tæller på enheden.",
|
||||
"on_complete_action_service": "Tjeneste",
|
||||
"on_complete_action_target": "Målenhed",
|
||||
"on_complete_action_target_hint": "Bemærk: enhedens domæne skal matche tjenesten — f.eks. virker 'button.press' kun på button.*, 'counter.increment' kun på counter.*, 'input_button.press' kun på input_button.* osv. Ved et misforhold mislykkes handlingen lydløst (HA logger 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Data (JSON, valgfrit)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Vælg fotos",
|
||||
"choose_photo": "Vælg foto",
|
||||
"photos_android_hint": "Android-appen afleverer ét foto pr. valg — hvert valg tilføjes.",
|
||||
"add_photos": "Tilføj fotos",
|
||||
"photos_limit": "Højst {max} fotos pr. fuldførelse",
|
||||
"history_edit_photos_hint": "Et foto, der fjernes her, bliver i objektets dokumenter.",
|
||||
"uploading": "Uploader…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Download",
|
||||
"doc_close": "Luk",
|
||||
"doc_camera": "Tag billede",
|
||||
"camera_capture_shoot": "Tag billede",
|
||||
"camera_switch_lens": "Skift kamera",
|
||||
"doc_drop_hint": "Slip filer her",
|
||||
"doc_task_none": "Ingen dokumenter knyttet til denne opgave.",
|
||||
"doc_link_existing": "Knyt et dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registrerer en fuldførelse (sætter senest udført), når udløseren løser sig selv — f.eks. salt påfyldt, filter udskiftet.",
|
||||
"doc_search": "Søg dokumenter…",
|
||||
"doc_search_none": "Ingen matchende dokumenter",
|
||||
"doc_description": "Beskrivelse (valgfri)",
|
||||
"doc_sort": "Sortér dokumenter",
|
||||
"doc_sort_newest": "Nyeste først",
|
||||
"doc_sort_oldest": "Ældste først",
|
||||
"doc_sort_title": "Efter titel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Efter kategori",
|
||||
"link_device_optional": "Tilknyt eksisterende enhed (valgfrit)",
|
||||
"parent_object_optional": "Overordnet objekt (valgfrit)",
|
||||
"parent_none": "(Intet overordnet)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Underret kun for visning",
|
||||
"settings_notify_scope_all": "Alle opgaver",
|
||||
"settings_notify_scope_hint": "Kun opgaver, der matcher den valgte visnings etiket-/brugerfiltre, sender påmindelser. Visningens status, sortering og gruppering ignoreres her.",
|
||||
"settings_notify_completed": "Notifikationer om udførelse",
|
||||
"settings_notify_completed_hint": "En besked til husstanden, når en opgave er udført, med årsag og hvem der gjorde det. \"Kun automatiske\" melder kun udførelser, som ingen lavede på stedet: sensorgendannelse, afkrydsning på indkøbslisten, et servicekald fra en automatisering. Kun til husstandstjenesten; stilletimer og dagsgrænsen gælder.",
|
||||
"notify_completed_off": "Fra",
|
||||
"notify_completed_automatic": "Kun automatiske udførelser",
|
||||
"notify_completed_all": "Alle udførelser",
|
||||
"settings_notify_rule": "Din egen notifikationsregel",
|
||||
"settings_notify_rule_hint": "Hver notifikation udløser også hændelsen maintenance_supporter_notification (type, status, objekt, opgave, referencenumre, prioritet, link, nyttelast). Videresend den med en automatisering — fx til Ticker, Telegram eller Pushover.",
|
||||
"settings_notify_event_only": "Udløs kun hændelsen, send intet selv",
|
||||
"settings_notify_event_only_hint": "Din automatisering står for hele leveringen. Gentagelsesintervaller, stille timer og dagsloftet gælder stadig for, hvornår hændelsen udløses.",
|
||||
"settings_notify_extra_data": "Ekstra notifikationsdata (skabelon)",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tilladt; flettes ind i notify-kaldets data (dine nøgler vinder). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test med knappen Send test.",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tilladt; flettes ind i notify-kaldets data (dine nøgler vinder). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test med knappen Send test.",
|
||||
"card_saved_view": "Gemt visning",
|
||||
"card_saved_view_none": "Ingen",
|
||||
"card_saved_view_help": "Anvender visningens status-, bruger- og etiketfiltre oven på filtrene ovenfor. Visningens sortering og gruppering er panelvisningsindstillinger og anvendes ikke på kortet.",
|
||||
"panel_card_tab": "Åbn på fanen",
|
||||
"panel_card_tab_default": "Husket fane (som i panelet)",
|
||||
"panel_card_view": "Åbn med gemt visning",
|
||||
"panel_card_height": "Højde",
|
||||
"panel_card_height_help": "Tom = fylder skærmen under kortet (panelvisning). Ellers en CSS-længde som 600px eller 80vh til visninger med andre kort.",
|
||||
"panel_card_not_registered": "Maintenance Supporter-panelet er ikke tilgængeligt — er integrationen sat op?",
|
||||
"panel_card_load_failed": "Maintenance Supporter-panelet kunne ikke indlæses — genindlæs siden (efter en opdatering: ryd browserens cache).",
|
||||
"doc_part_none": "Ingen dokumenter knyttet til denne del.",
|
||||
"settings_templates_toggle_group": "Slå alle skabeloner i denne gruppe til eller fra",
|
||||
"setups_button": "Foreslåede opsætninger",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "I orden",
|
||||
"battery_fleet_status_due": "Forfalden",
|
||||
"battery_fleet_predicted_on": "Forventes omkring {date}",
|
||||
"battery_fleet_predicted_typical": "Forventes omkring {date} — {months} måneder typisk for {type} ({source})",
|
||||
"lifetime_source_default": "indbygget standardværdi",
|
||||
"lifetime_source_table": "indbygget tabel",
|
||||
"lifetime_source_learned": "lært fra {n} udskiftninger",
|
||||
"lifetime_source_override": "din indstilling",
|
||||
"lifetime_source_learned_device": "lært fra {n} udskiftninger på denne enhed",
|
||||
"lifetime_source_learned_model": "lært fra {n} udskiftninger på enheder af denne model",
|
||||
"battery_fleet_predicted_trend": "Forudsagt ud fra batteriets afladningstendens: omkring {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Genopladeligt: oplad i stedet for at udskifte — aldrig på indkøbslisten",
|
||||
"battery_fleet_sort_name": "Sortér efter navn",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Brugte dele",
|
||||
"print_inc_photos": "Fotos",
|
||||
"print_inc_documents": "Tilknyttede dokumenter",
|
||||
"print_inc_doc_desc": "Dokumentbeskrivelser",
|
||||
"print_inc_checklist": "Tjekliste",
|
||||
"print_inc_notes": "Noter",
|
||||
"print_inc_costs": "Omkostning og varighed",
|
||||
"print_inc_person": "Udført af",
|
||||
"print_inc_refs": "Referencenumre",
|
||||
"print_inc_bare": "Udførelser uden detaljer",
|
||||
"print_inc_qr": "QR-kode pr. opgave",
|
||||
"print_button": "Udskriv",
|
||||
"report_scan_hint": "Scan for at åbne opgaven",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Laufzeit",
|
||||
"runtime_hours": "Ziel-Laufzeit (Stunden)",
|
||||
"target_value": "Zielwert",
|
||||
"baseline": "Nulllinie",
|
||||
"target_changes": "Ziel-Änderungen",
|
||||
"for_minutes": "Für (Minuten)",
|
||||
"time_based": "Zeitbasiert",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Erstellt",
|
||||
"report_times_done": "Erledigt",
|
||||
"report_total_cost": "Gesamtkosten",
|
||||
"report_every": "alle {n} {unit}",
|
||||
"report_notes": "Notizen",
|
||||
"report_col_type": "Typ",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Zeitplan",
|
||||
"duration": "Dauer",
|
||||
"minutes_short": "Min.",
|
||||
"both": "Beides",
|
||||
"trigger_val": "Trigger-Wert",
|
||||
"complete_title": "Erledigt: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: falscher Typ (erwartet: {type})",
|
||||
"err_invalid_choice": "{field}: nicht erlaubter Wert",
|
||||
"err_invalid_value": "{field}: ungültiger Wert",
|
||||
"ws_err_not_found": "Nicht gefunden — es wurde vielleicht inzwischen gelöscht",
|
||||
"ws_err_invalid_input": "Ungültige Eingabe",
|
||||
"ws_err_invalid_date": "Ungültiges Datum",
|
||||
"ws_err_invalid_format": "Ungültiges Format",
|
||||
"ws_err_invalid_url": "Ungültiger Link",
|
||||
"ws_err_not_configured": "Noch nicht eingerichtet",
|
||||
"ws_err_not_loaded": "Noch nicht geladen — gleich noch einmal versuchen",
|
||||
"ws_err_limit_reached": "Limit erreicht",
|
||||
"ws_err_too_many": "Zu viele Einträge",
|
||||
"ws_err_too_large": "Zu groß",
|
||||
"ws_err_not_archived": "Nicht archiviert",
|
||||
"ws_err_invalid_target": "Ungültiges Ziel",
|
||||
"ws_err_create_failed": "Konnte nicht angelegt werden",
|
||||
"ws_err_archived": "Archiviert — zuerst wiederherstellen",
|
||||
"ws_err_already_archived": "Bereits archiviert",
|
||||
"ws_err_unavailable": "Derzeit nicht verfügbar",
|
||||
"ws_err_unauthorized": "Für deinen Benutzer nicht erlaubt",
|
||||
"ws_err_too_many_views": "Zu viele gespeicherte Ansichten",
|
||||
"ws_err_storage_unavailable": "Der Speicher ist nicht verfügbar",
|
||||
"ws_err_replace_failed": "Ersetzen fehlgeschlagen",
|
||||
"ws_err_not_paused": "Nicht pausiert",
|
||||
"ws_err_not_available": "Nichts verfügbar",
|
||||
"ws_err_no_url": "Kein Link angegeben",
|
||||
"ws_err_no_phases": "Diese Aufgabe hat keine Phasen",
|
||||
"ws_err_invalid_view": "Ungültige Ansicht",
|
||||
"ws_err_invalid_user": "Unbekannter Benutzer",
|
||||
"ws_err_invalid_range": "Ungültiger Zeitraum",
|
||||
"ws_err_invalid_parent": "Ungültiges übergeordnetes Objekt",
|
||||
"ws_err_invalid_device": "Unbekanntes Gerät",
|
||||
"ws_err_empty_csv": "Die CSV-Datei ist leer",
|
||||
"ws_err_empty": "Nichts zu importieren",
|
||||
"ws_err_duplicate_failed": "Konnte nicht dupliziert werden",
|
||||
"ws_err_already_paused": "Bereits pausiert",
|
||||
"ws_err_invalid_cursor": "Ungültige Phasenposition",
|
||||
"ws_err_invalid_entity_slug": "Ungültiger Entitätsname",
|
||||
"ws_err_invalid_trigger_config": "Ungültige Trigger-Konfiguration",
|
||||
"ws_err_no_defaults": "Keine Vorgaben für das Schnell-Erledigen hinterlegt",
|
||||
"ws_err_self_link_device": "Ein Objekt kann nicht mit sich selbst verknüpft werden",
|
||||
"ws_err_too_early": "Zu früh — diese Aufgabe kann erst näher am Fälligkeitsdatum erledigt werden",
|
||||
"feat_schedule_time": "Uhrzeit-Scheduling",
|
||||
"feat_schedule_time_desc": "Tasks werden zu einer festen Uhrzeit fällig statt um Mitternacht.",
|
||||
"schedule_time_toggle": "Zu einer bestimmten Uhrzeit fällig",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Auslösen bei genau (=)",
|
||||
"trigger_not_equals": "Auslösen bei abweichend von (≠)",
|
||||
"for_at_least_minutes": "Für mindestens (Minuten)",
|
||||
"safety_interval_days": "Sicherheitsintervall (Tage, optional)",
|
||||
"safety_interval": "Sicherheitsintervall (optional)",
|
||||
"trigger_combinator": "Trigger und Intervall kombinieren",
|
||||
"trigger_combinator_any": "Trigger oder Intervall (zuerst erfüllt)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Meine Aufgaben",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Keine Wartung",
|
||||
"cal_window_7": "7 Tage",
|
||||
"cal_window_14": "14 Tage",
|
||||
"cal_window_30": "30 Tage",
|
||||
"cal_window_365": "1 Jahr",
|
||||
"cal_every_n_days": "alle {n} Tage",
|
||||
"cal_source_time": "Zeit-basiert",
|
||||
"cal_source_time_adaptive": "Zeit-basiert (adaptiv)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR-Code konnte nicht generiert werden.",
|
||||
"qr_error_no_url": "Keine HA-URL konfiguriert. Bitte unter Einstellungen → System → Netzwerk eine externe oder interne URL setzen.",
|
||||
"save_error": "Fehler beim Speichern. Bitte erneut versuchen.",
|
||||
"subsave_warning": "Gespeichert, aber eine Einstellung wurde abgelehnt: {detail}",
|
||||
"qr_print": "Drucken",
|
||||
"qr_download": "SVG herunterladen",
|
||||
"qr_action": "Aktion beim Scannen",
|
||||
"qr_action_view": "Wartungsinfo anzeigen",
|
||||
"qr_action_complete": "Wartung als erledigt markieren",
|
||||
"qr_url_mode": "Link-Typ",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Lokal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Übersicht",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Letzte Aktivitäten",
|
||||
"search_notes": "Notizen durchsuchen",
|
||||
"avg_cost": "Ø Kosten",
|
||||
"no_advanced_features": "Keine erweiterten Funktionen aktiviert",
|
||||
"no_advanced_features_hint": "Aktiviere „Adaptive Intervalle“ oder „Saisonale Muster“ in den Integrationseinstellungen, um hier Analysedaten zu sehen.",
|
||||
"analysis_not_enough_data": "Noch nicht genügend Daten für die Analyse vorhanden.",
|
||||
"analysis_not_enough_data_hint": "Die Weibull-Analyse benötigt mindestens 5 abgeschlossene Wartungen, saisonale Muster werden nach 6+ Datenpunkten pro Monat sichtbar.",
|
||||
"analysis_manual_task_hint": "Manuelle Aufgaben ohne Intervall erzeugen keine Analysedaten.",
|
||||
"completions": "Abschlüsse",
|
||||
"current": "Aktuell",
|
||||
"shorter": "Kürzer",
|
||||
"longer": "Länger",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Vergangene Fenster",
|
||||
"cal_forward_windows": "Zukünftige Fenster",
|
||||
"history_edit_title": "Verlaufseintrag bearbeiten",
|
||||
"history_delete_entry": "Eintrag löschen",
|
||||
"history_delete_confirm": "Diesen Verlaufseintrag löschen? Das Datum der letzten Erledigung fällt auf die vorherige zurück; Fotos bleiben beim Objekt, verbrauchte Teile werden nicht zurückgebucht.",
|
||||
"history_edit_timestamp": "Zeitstempel",
|
||||
"manufacturer": "Hersteller",
|
||||
"model": "Modell",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Standard-Warntage",
|
||||
"settings_consumable_threshold": "Schwelle „Verbrauchsmaterial niedrig“ (%)",
|
||||
"settings_battery_low_percent": "Schwelle „Batterie niedrig“ (%)",
|
||||
"settings_battery_lifetimes": "Typische Batterie-Lebensdauern",
|
||||
"settings_battery_lifetimes_hint": "Wird nur für Batterien ohne Prozentanzeige verwendet: eine Battery-Notes-Notiz, die nur Typ und Austauschdatum kennt, oder ein Sensor, der nur „schwach“ meldet. Deren Fälligkeit ist letzter Wechsel + diese Lebensdauer. Batterien mit Füllstand bekommen ihre Prognose aus der gemessenen Entladung. Sobald die Flotte drei Wechsel eines Typs gesehen hat, ersetzt das mittlere Intervall den eingebauten Wert — dein eigener Wert gewinnt immer.",
|
||||
"settings_battery_lifetime_months": "Monate",
|
||||
"settings_battery_lifetime_reset": "Standard verwenden",
|
||||
"settings_battery_lifetime_in_fleet": "in deiner Flotte",
|
||||
"settings_battery_lifetime_learned_models": "Gelernt je Modell: {list}",
|
||||
"settings_thresholds_hint": "Vorgaben für neue Vorgeschlagene Einrichtungen (Prozent-Restwert) und für Flotten-Batterien ohne eigene Battery-Notes-Schwelle. Bestehende Aufgaben behalten ihren Wert.",
|
||||
"settings_value_out_of_range": "Der Wert muss zwischen {min} und {max} liegen",
|
||||
"bn_summary": "{name}: {pct} % für {n} Batterien",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} Geräte mit eigener Schwelle:",
|
||||
"bn_more": "+ {n} weitere",
|
||||
"settings_row_actions": "Aktionen in Aufgabenzeilen",
|
||||
"settings_ref_numbers_in_lists": "Referenznummern in Listen",
|
||||
"settings_ref_numbers_in_lists_hint": "Zeigt die Chips #8 / #8.3 vor Objekt- und Aufgabennamen in der Heute-Liste, der Aufgabentabelle, den Objektkarten und der Objekttabelle.",
|
||||
"row_actions_buttons_compact": "Buttons (auf dem Handy nur Symbole)",
|
||||
"row_actions_buttons": "Buttons mit Text",
|
||||
"row_actions_icons": "Symbole (klassisch)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Schlummerdauer (Stunden)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Währung",
|
||||
"settings_currency_decimals": "Nachkommastellen für Beträge",
|
||||
"settings_currency_decimals_hint": "Wie viele Nachkommastellen jeder Betrag zeigt: KPIs, Budgets, Kosten in Listen und Berichten, Budget-Alarme. 0 = ganze Zahlen.",
|
||||
"settings_budget_monthly": "Monatsbudget",
|
||||
"settings_budget_yearly": "Jahresbudget",
|
||||
"settings_budget_alerts": "Budget-Warnungen",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML exportieren",
|
||||
"settings_export_csv": "CSV exportieren",
|
||||
"settings_export_settings": "Einstellungen exportieren (JSON)",
|
||||
"settings_import_csv": "CSV importieren",
|
||||
"settings_import_placeholder": "JSON- oder CSV-Inhalt hier einfügen…",
|
||||
"settings_import_btn": "Importieren",
|
||||
"settings_import_success": "{count} Objekte erfolgreich importiert.",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "Pro Aufgabe HA-Action beim Abschluss konfigurieren + Quick-Complete-QR mit voreingestellten Werten.",
|
||||
"on_complete_action_title": "Beim Abschluss: HA-Action auslösen (optional)",
|
||||
"on_complete_action_desc": "Ruft beim Erledigen der Aufgabe einen HA-Service auf — z. B. einen Zähler am Gerät zurücksetzen.",
|
||||
"on_complete_action_service": "Service",
|
||||
"on_complete_action_target": "Ziel-Entität",
|
||||
"on_complete_action_target_hint": "Achtung: Domain der Entität muss zum Service passen — z. B. 'button.press' nur für button.*, 'counter.increment' nur für counter.*, 'input_button.press' nur für input_button.* etc. Bei Mismatch fired die Aktion nicht (HA loggt 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Daten (JSON, optional)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Fotos auswählen",
|
||||
"choose_photo": "Foto wählen",
|
||||
"photos_android_hint": "Die Android-App übergibt pro Auswahl ein Foto — jede Auswahl wird hinzugefügt.",
|
||||
"add_photos": "Fotos hinzufügen",
|
||||
"photos_limit": "Bis zu {max} Fotos pro Abschluss",
|
||||
"history_edit_photos_hint": "Ein hier entferntes Foto bleibt in den Dokumenten des Objekts erhalten.",
|
||||
"uploading": "Wird hochgeladen…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Herunterladen",
|
||||
"doc_close": "Schließen",
|
||||
"doc_camera": "Foto aufnehmen",
|
||||
"camera_capture_shoot": "Aufnehmen",
|
||||
"camera_switch_lens": "Kamera wechseln",
|
||||
"doc_drop_hint": "Dateien hier ablegen",
|
||||
"doc_task_none": "Keine Dokumente mit dieser Aufgabe verknüpft.",
|
||||
"doc_link_existing": "Dokument verknüpfen…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Verbucht eine Wartung (setzt „zuletzt durchgeführt“), sobald sich der Auslöser von selbst auflöst — z. B. Salz nachgefüllt, Filter getauscht.",
|
||||
"doc_search": "Dokumente durchsuchen…",
|
||||
"doc_search_none": "Keine passenden Dokumente",
|
||||
"doc_description": "Beschreibung (optional)",
|
||||
"doc_sort": "Dokumente sortieren",
|
||||
"doc_sort_newest": "Neueste zuerst",
|
||||
"doc_sort_oldest": "Älteste zuerst",
|
||||
"doc_sort_title": "Nach Titel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Nach Kategorie",
|
||||
"link_device_optional": "Mit bestehendem Gerät verknüpfen (optional)",
|
||||
"parent_object_optional": "Übergeordnetes Objekt (optional)",
|
||||
"parent_none": "(Kein übergeordnetes Objekt)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Nur benachrichtigen für Ansicht",
|
||||
"settings_notify_scope_all": "Alle Aufgaben",
|
||||
"settings_notify_scope_hint": "Nur Aufgaben, die zu den Label-/Nutzer-Filtern der gewählten Ansicht passen, senden Erinnerungen. Status, Sortierung und Gruppierung der Ansicht werden hier ignoriert.",
|
||||
"settings_notify_completed": "Erledigt-Benachrichtigungen",
|
||||
"settings_notify_completed_hint": "Haushalts-Info, wenn eine Aufgabe erledigt wurde, mit Grund und Person. „Nur automatische“ meldet nur Erledigungen, die niemand vor Ort ausgelöst hat: Sensor-Erholung, Einkaufslisten-Abhaken, Service-Aufruf einer Automation. Geht nur an den Haushalts-Dienst; Ruhezeiten und Tageslimit gelten.",
|
||||
"notify_completed_off": "Aus",
|
||||
"notify_completed_automatic": "Nur automatische Erledigungen",
|
||||
"notify_completed_all": "Alle Erledigungen",
|
||||
"settings_notify_rule": "Eigene Benachrichtigungsregel",
|
||||
"settings_notify_rule_hint": "Jede Benachrichtigung löst zusätzlich das Ereignis maintenance_supporter_notification aus (Art, Status, Objekt, Aufgabe, Referenznummern, Priorität, Deep-Link, Nutzlast). Leite es per Automation weiter — z. B. an Ticker, Telegram oder Pushover.",
|
||||
"settings_notify_event_only": "Nur das Ereignis auslösen, selbst nichts senden",
|
||||
"settings_notify_event_only_hint": "Deine Automation übernimmt den kompletten Versand. Wiederholintervalle, Ruhezeiten und Tageslimit gelten weiterhin dafür, wann das Ereignis ausgelöst wird.",
|
||||
"settings_notify_extra_data": "Zusätzliche Benachrichtigungsdaten (Template)",
|
||||
"settings_notify_extra_data_hint": "JSON oder YAML, Jinja erlaubt; wird in die data des Notify-Aufrufs gemischt (deine Schlüssel gewinnen). Variablen: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Prüfen mit „Test senden“.",
|
||||
"settings_notify_extra_data_hint": "JSON oder YAML, Jinja erlaubt; wird in die data des Notify-Aufrufs gemischt (deine Schlüssel gewinnen). Variablen: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Prüfen mit „Test senden“.",
|
||||
"card_saved_view": "Gespeicherte Ansicht",
|
||||
"card_saved_view_none": "Keine",
|
||||
"card_saved_view_help": "Wendet die Status-, Nutzer- und Label-Filter der Ansicht zusätzlich zu den obigen Filtern an. Sortierung und Gruppierung der Ansicht sind Panel-Darstellung und werden auf der Karte nicht angewendet.",
|
||||
"panel_card_tab": "Startet auf Tab",
|
||||
"panel_card_tab_default": "Gemerkter Tab (wie im Panel)",
|
||||
"panel_card_view": "Startet mit gespeicherter Ansicht",
|
||||
"panel_card_height": "Höhe",
|
||||
"panel_card_height_help": "Leer = füllt den Bildschirm unterhalb der Karte (Panel-Ansicht). Sonst eine CSS-Länge wie 600px oder 80vh für Ansichten mit weiteren Karten.",
|
||||
"panel_card_not_registered": "Das Maintenance-Supporter-Panel ist nicht verfügbar — ist die Integration eingerichtet?",
|
||||
"panel_card_load_failed": "Das Maintenance-Supporter-Panel konnte nicht geladen werden — Seite neu laden (nach einem Update: Browser-Cache leeren).",
|
||||
"doc_part_none": "Keine Dokumente mit diesem Ersatzteil verknüpft.",
|
||||
"settings_templates_toggle_group": "Alle Vorlagen dieser Gruppe ein- oder ausschalten",
|
||||
"setups_button": "Setup-Vorschläge",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "In Ordnung",
|
||||
"battery_fleet_status_due": "Fällig",
|
||||
"battery_fleet_predicted_on": "Voraussichtlich um {date}",
|
||||
"battery_fleet_predicted_typical": "Voraussichtlich um {date} — {months} Monate typisch für {type} ({source})",
|
||||
"lifetime_source_default": "eingebauter Standardwert",
|
||||
"lifetime_source_table": "eingebaute Tabelle",
|
||||
"lifetime_source_learned": "gelernt aus {n} Wechseln",
|
||||
"lifetime_source_override": "deine Einstellung",
|
||||
"lifetime_source_learned_device": "gelernt aus {n} Wechseln dieses Geräts",
|
||||
"lifetime_source_learned_model": "gelernt aus {n} Wechseln bei Geräten dieses Modells",
|
||||
"battery_fleet_predicted_trend": "Aus dem Entladetrend dieser Batterie vorhergesagt: etwa {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Wiederaufladbar: laden statt ersetzen — nie auf der Einkaufsliste",
|
||||
"battery_fleet_sort_name": "Nach Name sortieren",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Verbrauchte Teile",
|
||||
"print_inc_photos": "Fotos",
|
||||
"print_inc_documents": "Verknüpfte Dokumente",
|
||||
"print_inc_doc_desc": "Dokumentbeschreibungen",
|
||||
"print_inc_checklist": "Checkliste",
|
||||
"print_inc_notes": "Notizen",
|
||||
"print_inc_costs": "Kosten und Dauer",
|
||||
"print_inc_person": "Erledigt von",
|
||||
"print_inc_refs": "Referenznummern",
|
||||
"print_inc_bare": "Erledigungen ohne Details",
|
||||
"print_inc_qr": "QR-Code je Aufgabe",
|
||||
"print_button": "Drucken",
|
||||
"report_scan_hint": "Scannen öffnet die Aufgabe",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Runtime",
|
||||
"runtime_hours": "Target runtime (hours)",
|
||||
"target_value": "Target value",
|
||||
"baseline": "Baseline",
|
||||
"target_changes": "Target changes",
|
||||
"for_minutes": "For (minutes)",
|
||||
"time_based": "Time-based",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Generated",
|
||||
"report_times_done": "Done",
|
||||
"report_total_cost": "Total cost",
|
||||
"report_every": "every {n} {unit}",
|
||||
"report_notes": "Notes",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Schedule",
|
||||
"duration": "Duration",
|
||||
"minutes_short": "min",
|
||||
"both": "Both",
|
||||
"trigger_val": "Trigger value",
|
||||
"complete_title": "Complete: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: wrong type (expected: {type})",
|
||||
"err_invalid_choice": "{field}: not an allowed value",
|
||||
"err_invalid_value": "{field}: invalid value",
|
||||
"ws_err_not_found": "Not found — it may have been deleted meanwhile",
|
||||
"ws_err_invalid_input": "Invalid input",
|
||||
"ws_err_invalid_date": "Invalid date",
|
||||
"ws_err_invalid_format": "Invalid format",
|
||||
"ws_err_invalid_url": "Invalid link",
|
||||
"ws_err_not_configured": "Not set up yet",
|
||||
"ws_err_not_loaded": "Not loaded yet — try again in a moment",
|
||||
"ws_err_limit_reached": "Limit reached",
|
||||
"ws_err_too_many": "Too many entries",
|
||||
"ws_err_too_large": "Too large",
|
||||
"ws_err_not_archived": "Not archived",
|
||||
"ws_err_invalid_target": "Invalid target",
|
||||
"ws_err_create_failed": "Could not be created",
|
||||
"ws_err_archived": "Archived — restore it first",
|
||||
"ws_err_already_archived": "Already archived",
|
||||
"ws_err_unavailable": "Currently unavailable",
|
||||
"ws_err_unauthorized": "Not permitted for your user",
|
||||
"ws_err_too_many_views": "Too many saved views",
|
||||
"ws_err_storage_unavailable": "Storage is not available",
|
||||
"ws_err_replace_failed": "Replacement failed",
|
||||
"ws_err_not_paused": "Not paused",
|
||||
"ws_err_not_available": "Nothing available",
|
||||
"ws_err_no_url": "No link given",
|
||||
"ws_err_no_phases": "This task has no phases",
|
||||
"ws_err_invalid_view": "Invalid view",
|
||||
"ws_err_invalid_user": "Unknown user",
|
||||
"ws_err_invalid_range": "Invalid range",
|
||||
"ws_err_invalid_parent": "Invalid parent object",
|
||||
"ws_err_invalid_device": "Unknown device",
|
||||
"ws_err_empty_csv": "The CSV file is empty",
|
||||
"ws_err_empty": "Nothing to import",
|
||||
"ws_err_duplicate_failed": "Could not duplicate",
|
||||
"ws_err_already_paused": "Already paused",
|
||||
"ws_err_invalid_cursor": "Invalid phase position",
|
||||
"ws_err_invalid_entity_slug": "Invalid entity name",
|
||||
"ws_err_invalid_trigger_config": "Invalid trigger configuration",
|
||||
"ws_err_no_defaults": "No quick-complete defaults set",
|
||||
"ws_err_self_link_device": "An object cannot be linked to itself",
|
||||
"ws_err_too_early": "Too early — this task can only be completed closer to its due date",
|
||||
"feat_schedule_time": "Time-of-day scheduling",
|
||||
"feat_schedule_time_desc": "Tasks become overdue at a specific time of day instead of midnight.",
|
||||
"schedule_time_toggle": "Due at a specific time",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Trigger when equal to (=)",
|
||||
"trigger_not_equals": "Trigger when different from (≠)",
|
||||
"for_at_least_minutes": "For at least (minutes)",
|
||||
"safety_interval_days": "Safety interval (days, optional)",
|
||||
"safety_interval": "Safety interval (optional)",
|
||||
"trigger_combinator": "Combine trigger and interval",
|
||||
"trigger_combinator_any": "Trigger or interval (whichever first)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "My Tasks",
|
||||
"tab_calendar": "Calendar",
|
||||
"cal_no_events": "No maintenance",
|
||||
"cal_window_7": "7 days",
|
||||
"cal_window_14": "14 days",
|
||||
"cal_window_30": "30 days",
|
||||
"cal_window_365": "1 year",
|
||||
"cal_every_n_days": "every {n} days",
|
||||
"cal_source_time": "Time-based",
|
||||
"cal_source_time_adaptive": "Time-based (adaptive)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "Failed to generate QR code.",
|
||||
"qr_error_no_url": "No HA URL configured. Please set an external or internal URL in Settings → System → Network.",
|
||||
"save_error": "Failed to save. Please try again.",
|
||||
"subsave_warning": "Saved, but one setting was rejected: {detail}",
|
||||
"qr_print": "Print",
|
||||
"qr_download": "Download SVG",
|
||||
"qr_action": "Action on scan",
|
||||
"qr_action_view": "View maintenance info",
|
||||
"qr_action_complete": "Mark maintenance as complete",
|
||||
"qr_url_mode": "Link type",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "Server URL",
|
||||
"overview": "Overview",
|
||||
"analysis": "Analysis",
|
||||
"recent_activities": "Recent Activities",
|
||||
"search_notes": "Search notes",
|
||||
"avg_cost": "Avg Cost",
|
||||
"no_advanced_features": "No advanced features enabled",
|
||||
"no_advanced_features_hint": "Enable “Adaptive Intervals” or “Seasonal Patterns” in the integration settings to see analysis data here.",
|
||||
"analysis_not_enough_data": "Not enough data for analysis yet.",
|
||||
"analysis_not_enough_data_hint": "Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.",
|
||||
"analysis_manual_task_hint": "Manual tasks without an interval do not generate analysis data.",
|
||||
"completions": "completions",
|
||||
"current": "Current",
|
||||
"shorter": "Shorter",
|
||||
"longer": "Longer",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Past windows",
|
||||
"cal_forward_windows": "Forward windows",
|
||||
"history_edit_title": "Edit history entry",
|
||||
"history_delete_entry": "Delete entry",
|
||||
"history_delete_confirm": "Delete this history entry? The task's last-performed date falls back to the previous completion; photos stay with the object and consumed parts are not restocked.",
|
||||
"history_edit_timestamp": "Timestamp",
|
||||
"manufacturer": "Manufacturer",
|
||||
"model": "Model",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Default warning days",
|
||||
"settings_consumable_threshold": "Consumable low threshold (%)",
|
||||
"settings_battery_low_percent": "Battery low threshold (%)",
|
||||
"settings_battery_lifetimes": "Typical battery lifetimes",
|
||||
"settings_battery_lifetimes_hint": "Only used for batteries without a percentage: a Battery Notes note that has just a type and a last-replaced date, or a sensor that only reports \"low\". Their due date is last replaced + this lifetime. Batteries that report a level get their forecast from the measured discharge instead. Once the fleet has seen three replacements of a type, the median interval takes over from the built-in value — your own value always wins.",
|
||||
"settings_battery_lifetime_months": "months",
|
||||
"settings_battery_lifetime_reset": "Use default",
|
||||
"settings_battery_lifetime_in_fleet": "in your fleet",
|
||||
"settings_battery_lifetime_learned_models": "Learned per model: {list}",
|
||||
"settings_thresholds_hint": "Defaults for new Suggested setups (percent-remaining consumables) and for fleet batteries without their own Battery Notes threshold. Existing tasks keep their value.",
|
||||
"settings_value_out_of_range": "Value must be between {min} and {max}",
|
||||
"bn_summary": "{name}: {pct} % for {n} batteries",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} devices with their own threshold:",
|
||||
"bn_more": "+ {n} more",
|
||||
"settings_row_actions": "Task row actions",
|
||||
"settings_ref_numbers_in_lists": "Reference numbers in lists",
|
||||
"settings_ref_numbers_in_lists_hint": "Show the #8 / #8.3 chips in front of object and task names in the Today list, the task table, the object cards and the objects table.",
|
||||
"row_actions_buttons_compact": "Buttons (icons only on phones)",
|
||||
"row_actions_buttons": "Buttons with text",
|
||||
"row_actions_icons": "Icons (classic)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Snooze duration (hours)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Currency",
|
||||
"settings_currency_decimals": "Decimal places for amounts",
|
||||
"settings_currency_decimals_hint": "How many decimals every amount shows: KPIs, budgets, costs in lists and reports, budget alerts. 0 = whole numbers.",
|
||||
"settings_budget_monthly": "Monthly budget",
|
||||
"settings_budget_yearly": "Yearly budget",
|
||||
"settings_budget_alerts": "Budget alerts",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Export YAML",
|
||||
"settings_export_csv": "Export CSV",
|
||||
"settings_export_settings": "Export settings (JSON)",
|
||||
"settings_import_csv": "Import CSV",
|
||||
"settings_import_placeholder": "Paste JSON or CSV content here…",
|
||||
"settings_import_btn": "Import",
|
||||
"settings_import_success": "{count} objects imported successfully.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "Per-task HA action on complete + quick-complete QR with pre-set values.",
|
||||
"on_complete_action_title": "On complete: trigger HA action (optional)",
|
||||
"on_complete_action_desc": "Calls an HA service when the task is completed — e.g. reset a counter on the device.",
|
||||
"on_complete_action_service": "Service",
|
||||
"on_complete_action_target": "Target entity",
|
||||
"on_complete_action_target_hint": "Note: the entity domain must match the service — e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Data (JSON, optional)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "Choose photos",
|
||||
"choose_photo": "Choose photo",
|
||||
"photos_android_hint": "The Android app hands over one photo per pick — each pick is added.",
|
||||
"add_photos": "Add photos",
|
||||
"photos_limit": "Up to {max} photos per completion",
|
||||
"history_edit_photos_hint": "Removing a photo here keeps the file in the object's documents.",
|
||||
"uploading": "Uploading…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "Download",
|
||||
"doc_close": "Close",
|
||||
"doc_camera": "Take photo",
|
||||
"camera_capture_shoot": "Capture",
|
||||
"camera_switch_lens": "Switch camera",
|
||||
"doc_drop_hint": "Drop files here",
|
||||
"doc_task_none": "No documents linked to this task.",
|
||||
"doc_link_existing": "Link a document…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "Records a completion (sets last performed) when the trigger clears itself — e.g. salt refilled, filter replaced.",
|
||||
"doc_search": "Search documents…",
|
||||
"doc_search_none": "No matching documents",
|
||||
"doc_description": "Description (optional)",
|
||||
"doc_sort": "Sort documents",
|
||||
"doc_sort_newest": "Newest first",
|
||||
"doc_sort_oldest": "Oldest first",
|
||||
"doc_sort_title": "By title (1, 2, 3 …)",
|
||||
"doc_sort_category": "By category",
|
||||
"link_device_optional": "Link to existing device (optional)",
|
||||
"parent_object_optional": "Parent object (optional)",
|
||||
"parent_none": "(No parent)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notify only for view",
|
||||
"settings_notify_scope_all": "All tasks",
|
||||
"settings_notify_scope_hint": "Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",
|
||||
"settings_notify_completed": "Completion notifications",
|
||||
"settings_notify_completed_hint": "Household news when a task is completed, with the reason and who did it. \"Automatic only\" announces just the completions nobody made on the spot: sensor recovery, shopping-list check-off, an automation's service call. Sent to the household service only; quiet hours and the daily cap apply.",
|
||||
"notify_completed_off": "Off",
|
||||
"notify_completed_automatic": "Automatic completions only",
|
||||
"notify_completed_all": "All completions",
|
||||
"settings_notify_rule": "Your own notification rule",
|
||||
"settings_notify_rule_hint": "Every notification also fires the event maintenance_supporter_notification (kind, status, object, task, reference numbers, priority, deep link, the payload). Route it with an automation — e.g. into Ticker, Telegram or Pushover.",
|
||||
"settings_notify_event_only": "Only fire the event, send nothing myself",
|
||||
"settings_notify_event_only_hint": "Your automation is the whole delivery. Rate limits, quiet hours and the daily cap still apply to when the event fires.",
|
||||
"settings_notify_extra_data": "Extra notification data (template)",
|
||||
"settings_notify_extra_data_hint": "JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test it with the Send test button.",
|
||||
"settings_notify_extra_data_hint": "JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test it with the Send test button.",
|
||||
"card_saved_view": "Saved view",
|
||||
"card_saved_view_none": "None",
|
||||
"card_saved_view_help": "Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",
|
||||
"panel_card_tab": "Open on tab",
|
||||
"panel_card_tab_default": "Remembered tab (as in the panel)",
|
||||
"panel_card_view": "Open with saved view",
|
||||
"panel_card_height": "Height",
|
||||
"panel_card_height_help": "Empty = fill the screen below the card (panel view). Otherwise a CSS length such as 600px or 80vh for views with other cards.",
|
||||
"panel_card_not_registered": "The Maintenance Supporter panel is not available — is the integration set up?",
|
||||
"panel_card_load_failed": "The Maintenance Supporter panel could not be loaded — reload the page (after an update: clear the browser cache).",
|
||||
"doc_part_none": "No documents linked to this part.",
|
||||
"settings_templates_toggle_group": "Enable or disable all templates in this group",
|
||||
"setups_button": "Suggested setups",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Healthy",
|
||||
"battery_fleet_status_due": "Due",
|
||||
"battery_fleet_predicted_on": "Expected around {date}",
|
||||
"battery_fleet_predicted_typical": "Expected around {date} — {months} months typical for {type} ({source})",
|
||||
"lifetime_source_default": "built-in default",
|
||||
"lifetime_source_table": "built-in table",
|
||||
"lifetime_source_learned": "learned from {n} replacements",
|
||||
"lifetime_source_override": "your setting",
|
||||
"lifetime_source_learned_device": "learned from this device's {n} replacements",
|
||||
"lifetime_source_learned_model": "learned from {n} replacements on devices of this model",
|
||||
"battery_fleet_predicted_trend": "Predicted from this battery's discharge trend: around {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Rechargeable: charge instead of replacing — never on the shopping list",
|
||||
"battery_fleet_sort_name": "Sort by name",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Parts used",
|
||||
"print_inc_photos": "Photos",
|
||||
"print_inc_documents": "Linked documents",
|
||||
"print_inc_doc_desc": "Document descriptions",
|
||||
"print_inc_checklist": "Checklist",
|
||||
"print_inc_notes": "Notes",
|
||||
"print_inc_costs": "Cost and duration",
|
||||
"print_inc_person": "Completed by",
|
||||
"print_inc_refs": "Reference numbers",
|
||||
"print_inc_bare": "Completions without details",
|
||||
"print_inc_qr": "QR code per task",
|
||||
"print_button": "Print",
|
||||
"report_scan_hint": "Scan to open the task",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Tiempo de funcionamiento",
|
||||
"runtime_hours": "Duración objetivo (horas)",
|
||||
"target_value": "Valor objetivo",
|
||||
"baseline": "Línea base",
|
||||
"target_changes": "Cambios objetivo",
|
||||
"for_minutes": "Durante (minutos)",
|
||||
"time_based": "Temporal",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Generado",
|
||||
"report_times_done": "Hecho",
|
||||
"report_total_cost": "Coste total",
|
||||
"report_every": "cada {n} {unit}",
|
||||
"report_notes": "Notas",
|
||||
"report_col_type": "Tipo",
|
||||
"report_col_status": "Estado",
|
||||
"report_col_schedule": "Programación",
|
||||
"duration": "Duración",
|
||||
"minutes_short": "min",
|
||||
"both": "Ambos",
|
||||
"trigger_val": "Valor del disparador",
|
||||
"complete_title": "Completada: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: tipo incorrecto (esperado: {type})",
|
||||
"err_invalid_choice": "{field}: valor no permitido",
|
||||
"err_invalid_value": "{field}: valor inválido",
|
||||
"ws_err_not_found": "No encontrado — puede que se haya eliminado mientras tanto",
|
||||
"ws_err_invalid_input": "Entrada no válida",
|
||||
"ws_err_invalid_date": "Fecha no válida",
|
||||
"ws_err_invalid_format": "Formato no válido",
|
||||
"ws_err_invalid_url": "Enlace no válido",
|
||||
"ws_err_not_configured": "Aún no configurado",
|
||||
"ws_err_not_loaded": "Aún no cargado — inténtalo de nuevo en un momento",
|
||||
"ws_err_limit_reached": "Límite alcanzado",
|
||||
"ws_err_too_many": "Demasiadas entradas",
|
||||
"ws_err_too_large": "Demasiado grande",
|
||||
"ws_err_not_archived": "No archivado",
|
||||
"ws_err_invalid_target": "Destino no válido",
|
||||
"ws_err_create_failed": "No se pudo crear",
|
||||
"ws_err_archived": "Archivado — restáuralo primero",
|
||||
"ws_err_already_archived": "Ya archivado",
|
||||
"ws_err_unavailable": "No disponible actualmente",
|
||||
"ws_err_unauthorized": "No permitido para tu usuario",
|
||||
"ws_err_too_many_views": "Demasiadas vistas guardadas",
|
||||
"ws_err_storage_unavailable": "El almacenamiento no está disponible",
|
||||
"ws_err_replace_failed": "Error al reemplazar",
|
||||
"ws_err_not_paused": "No está en pausa",
|
||||
"ws_err_not_available": "Nada disponible",
|
||||
"ws_err_no_url": "No se indicó ningún enlace",
|
||||
"ws_err_no_phases": "Esta tarea no tiene fases",
|
||||
"ws_err_invalid_view": "Vista no válida",
|
||||
"ws_err_invalid_user": "Usuario desconocido",
|
||||
"ws_err_invalid_range": "Rango no válido",
|
||||
"ws_err_invalid_parent": "Objeto principal no válido",
|
||||
"ws_err_invalid_device": "Dispositivo desconocido",
|
||||
"ws_err_empty_csv": "El archivo CSV está vacío",
|
||||
"ws_err_empty": "Nada que importar",
|
||||
"ws_err_duplicate_failed": "No se pudo duplicar",
|
||||
"ws_err_already_paused": "Ya está en pausa",
|
||||
"ws_err_invalid_cursor": "Posición de fase no válida",
|
||||
"ws_err_invalid_entity_slug": "Nombre de entidad no válido",
|
||||
"ws_err_invalid_trigger_config": "Configuración del disparador no válida",
|
||||
"ws_err_no_defaults": "No hay valores predeterminados para completar rápido",
|
||||
"ws_err_self_link_device": "Un objeto no puede vincularse a sí mismo",
|
||||
"ws_err_too_early": "Demasiado pronto — esta tarea solo puede completarse más cerca de su vencimiento",
|
||||
"feat_schedule_time": "Programación por hora",
|
||||
"feat_schedule_time_desc": "Las tareas vencen a una hora específica en lugar de medianoche.",
|
||||
"schedule_time_toggle": "Vence a una hora concreta",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Activar cuando sea igual a (=)",
|
||||
"trigger_not_equals": "Activar cuando sea distinto de (≠)",
|
||||
"for_at_least_minutes": "Durante al menos (minutos)",
|
||||
"safety_interval_days": "Intervalo de seguridad (días, opcional)",
|
||||
"safety_interval": "Intervalo de seguridad (opcional)",
|
||||
"trigger_combinator": "Combinar disparador e intervalo",
|
||||
"trigger_combinator_any": "Disparador o intervalo (el primero)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Mis tareas",
|
||||
"tab_calendar": "Calendario",
|
||||
"cal_no_events": "Sin mantenimiento",
|
||||
"cal_window_7": "7 días",
|
||||
"cal_window_14": "14 días",
|
||||
"cal_window_30": "30 días",
|
||||
"cal_window_365": "1 año",
|
||||
"cal_every_n_days": "cada {n} días",
|
||||
"cal_source_time": "Basado en tiempo",
|
||||
"cal_source_time_adaptive": "Basado en tiempo (adaptativo)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "No se pudo generar el código QR.",
|
||||
"qr_error_no_url": "No hay URL de HA configurada. Establezca una URL externa o interna en Ajustes → Sistema → Red.",
|
||||
"save_error": "Error al guardar. Inténtelo de nuevo.",
|
||||
"subsave_warning": "Guardado, pero se rechazó un ajuste: {detail}",
|
||||
"qr_print": "Imprimir",
|
||||
"qr_download": "Descargar SVG",
|
||||
"qr_action": "Acción al escanear",
|
||||
"qr_action_view": "Ver info de mantenimiento",
|
||||
"qr_action_complete": "Marcar mantenimiento como completado",
|
||||
"qr_url_mode": "Tipo de enlace",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "URL del servidor",
|
||||
"overview": "Resumen",
|
||||
"analysis": "Análisis",
|
||||
"recent_activities": "Actividades recientes",
|
||||
"search_notes": "Buscar en notas",
|
||||
"avg_cost": "Ø Coste",
|
||||
"no_advanced_features": "Sin funciones avanzadas activadas",
|
||||
"no_advanced_features_hint": "Active “Intervalos Adaptativos” o “Patrones Estacionales” en la configuración de la integración para ver datos de análisis aquí.",
|
||||
"analysis_not_enough_data": "Aún no hay suficientes datos para el análisis.",
|
||||
"analysis_not_enough_data_hint": "El análisis Weibull requiere al menos 5 mantenimientos completados; los patrones estacionales son visibles tras 6+ puntos de datos por mes.",
|
||||
"analysis_manual_task_hint": "Las tareas manuales sin intervalo no generan datos de análisis.",
|
||||
"completions": "finalizaciones",
|
||||
"current": "Actual",
|
||||
"shorter": "Más corto",
|
||||
"longer": "Más largo",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Ventanas pasadas",
|
||||
"cal_forward_windows": "Ventanas futuras",
|
||||
"history_edit_title": "Editar entrada del historial",
|
||||
"history_delete_entry": "Eliminar entrada",
|
||||
"history_delete_confirm": "¿Eliminar esta entrada del historial? La fecha de última realización vuelve a la anterior; las fotos permanecen con el objeto y las piezas consumidas no se reponen.",
|
||||
"history_edit_timestamp": "Marca de tiempo",
|
||||
"manufacturer": "Fabricante",
|
||||
"model": "Modelo",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Días de aviso predeterminados",
|
||||
"settings_consumable_threshold": "Umbral „consumible bajo“ (%)",
|
||||
"settings_battery_low_percent": "Umbral „pila baja“ (%)",
|
||||
"settings_battery_lifetimes": "Duraciones típicas de las pilas",
|
||||
"settings_battery_lifetimes_hint": "Solo se usa para pilas sin porcentaje: una nota de Battery Notes que solo tiene tipo y fecha de sustitución, o un sensor que solo avisa de « baja ». Su vencimiento es última sustitución + esta duración. Las pilas que informan de un nivel obtienen su previsión de la descarga medida. Cuando la flota ha visto tres sustituciones de un tipo, el intervalo mediano sustituye al valor integrado — tu valor siempre gana.",
|
||||
"settings_battery_lifetime_months": "meses",
|
||||
"settings_battery_lifetime_reset": "Usar predeterminado",
|
||||
"settings_battery_lifetime_in_fleet": "en tu flota",
|
||||
"settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}",
|
||||
"settings_thresholds_hint": "Valores predeterminados para nuevas Configuraciones sugeridas (porcentaje restante) y para pilas de la flota sin umbral propio de Battery Notes. Las tareas existentes conservan su valor.",
|
||||
"settings_value_out_of_range": "El valor debe estar entre {min} y {max}",
|
||||
"bn_summary": "{name}: {pct} % para {n} pilas",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} dispositivos con umbral propio:",
|
||||
"bn_more": "+ {n} más",
|
||||
"settings_row_actions": "Acciones en las filas de tareas",
|
||||
"settings_ref_numbers_in_lists": "Números de referencia en las listas",
|
||||
"settings_ref_numbers_in_lists_hint": "Muestra las etiquetas #8 / #8.3 delante de los nombres de objetos y tareas en la lista Hoy, la tabla de tareas, las tarjetas de objetos y la tabla de objetos.",
|
||||
"row_actions_buttons_compact": "Botones (solo iconos en el móvil)",
|
||||
"row_actions_buttons": "Botones con texto",
|
||||
"row_actions_icons": "Iconos (clásico)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Duración de posposición (horas)",
|
||||
"settings_budget": "Presupuesto",
|
||||
"settings_currency": "Moneda",
|
||||
"settings_currency_decimals": "Decimales de los importes",
|
||||
"settings_currency_decimals_hint": "Cuántos decimales muestra cada importe: KPI, presupuestos, costes en listas e informes, alertas de presupuesto. 0 = números enteros.",
|
||||
"settings_budget_monthly": "Presupuesto mensual",
|
||||
"settings_budget_yearly": "Presupuesto anual",
|
||||
"settings_budget_alerts": "Alertas de presupuesto",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exportar YAML",
|
||||
"settings_export_csv": "Exportar CSV",
|
||||
"settings_export_settings": "Exportar ajustes (JSON)",
|
||||
"settings_import_csv": "Importar CSV",
|
||||
"settings_import_placeholder": "Pegue el contenido JSON o CSV aquí…",
|
||||
"settings_import_btn": "Importar",
|
||||
"settings_import_success": "{count} objetos importados correctamente.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Acción HA por tarea al completar + QR de finalización rápida con valores prefijados.",
|
||||
"on_complete_action_title": "Al completar: ejecutar acción HA (opcional)",
|
||||
"on_complete_action_desc": "Llama un servicio HA cuando se completa la tarea — p. ej. reiniciar un contador del dispositivo.",
|
||||
"on_complete_action_service": "Servicio",
|
||||
"on_complete_action_target": "Entidad objetivo",
|
||||
"on_complete_action_data": "Datos (JSON, opcional)",
|
||||
"on_complete_action_test": "Probar acción",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Elegir fotos",
|
||||
"choose_photo": "Elegir foto",
|
||||
"photos_android_hint": "La app de Android entrega una foto por selección — cada selección se añade.",
|
||||
"add_photos": "Añadir fotos",
|
||||
"photos_limit": "Hasta {max} fotos por finalización",
|
||||
"history_edit_photos_hint": "Una foto quitada aquí se conserva en los documentos del objeto.",
|
||||
"uploading": "Subiendo…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Descargar",
|
||||
"doc_close": "Cerrar",
|
||||
"doc_camera": "Tomar foto",
|
||||
"camera_capture_shoot": "Capturar",
|
||||
"camera_switch_lens": "Cambiar de cámara",
|
||||
"doc_drop_hint": "Suelta los archivos aquí",
|
||||
"doc_task_none": "Ningún documento vinculado a esta tarea.",
|
||||
"doc_link_existing": "Vincular un documento…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registra una finalización (fija la última realización) cuando el disparador se resuelve solo — p. ej., sal rellenada, filtro cambiado.",
|
||||
"doc_search": "Buscar documentos…",
|
||||
"doc_search_none": "Ningún documento coincidente",
|
||||
"doc_description": "Descripción (opcional)",
|
||||
"doc_sort": "Ordenar documentos",
|
||||
"doc_sort_newest": "Más recientes primero",
|
||||
"doc_sort_oldest": "Más antiguos primero",
|
||||
"doc_sort_title": "Por título (1, 2, 3 …)",
|
||||
"doc_sort_category": "Por categoría",
|
||||
"link_device_optional": "Vincular a dispositivo existente (opcional)",
|
||||
"parent_object_optional": "Objeto principal (opcional)",
|
||||
"parent_none": "(Sin objeto principal)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notificar solo para la vista",
|
||||
"settings_notify_scope_all": "Todas las tareas",
|
||||
"settings_notify_scope_hint": "Solo las tareas que coinciden con los filtros de etiqueta/usuario de la vista seleccionada envían recordatorios. El estado, el orden y la agrupación de la vista se ignoran aquí.",
|
||||
"settings_notify_completed": "Notificaciones de finalización",
|
||||
"settings_notify_completed_hint": "Aviso para el hogar cuando se completa una tarea, con el motivo y quién lo hizo. «Solo automáticas» anuncia solo las finalizaciones que nadie hizo en el sitio: recuperación del sensor, marcado en la lista de la compra, llamada de servicio de una automatización. Solo al servicio del hogar; se aplican las horas de silencio y el límite diario.",
|
||||
"notify_completed_off": "Desactivado",
|
||||
"notify_completed_automatic": "Solo finalizaciones automáticas",
|
||||
"notify_completed_all": "Todas las finalizaciones",
|
||||
"settings_notify_rule": "Tu propia regla de notificación",
|
||||
"settings_notify_rule_hint": "Cada notificación dispara además el evento maintenance_supporter_notification (tipo, estado, objeto, tarea, números de referencia, prioridad, enlace, carga útil). Enrútalo con una automatización — p. ej. a Ticker, Telegram o Pushover.",
|
||||
"settings_notify_event_only": "Solo disparar el evento, no enviar nada",
|
||||
"settings_notify_event_only_hint": "Tu automatización se encarga de toda la entrega. Los intervalos de repetición, las horas de silencio y el límite diario siguen decidiendo cuándo se dispara el evento.",
|
||||
"settings_notify_extra_data": "Datos de notificación adicionales (plantilla)",
|
||||
"settings_notify_extra_data_hint": "JSON o YAML, se permite Jinja; se fusiona en los data de la llamada notify (tus claves ganan). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Pruébalo con el botón Enviar prueba.",
|
||||
"settings_notify_extra_data_hint": "JSON o YAML, se permite Jinja; se fusiona en los data de la llamada notify (tus claves ganan). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Pruébalo con el botón Enviar prueba.",
|
||||
"card_saved_view": "Vista guardada",
|
||||
"card_saved_view_none": "Ninguna",
|
||||
"card_saved_view_help": "Aplica los filtros de estado, usuario y etiqueta de la vista además de los filtros anteriores. El orden y la agrupación de la vista son ajustes de visualización del panel y no se aplican en la tarjeta.",
|
||||
"panel_card_tab": "Abrir en la pestaña",
|
||||
"panel_card_tab_default": "Pestaña recordada (como en el panel)",
|
||||
"panel_card_view": "Abrir con la vista guardada",
|
||||
"panel_card_height": "Altura",
|
||||
"panel_card_height_help": "Vacío = llena la pantalla debajo de la tarjeta (vista panel). Si no, una longitud CSS como 600px u 80vh para vistas con otras tarjetas.",
|
||||
"panel_card_not_registered": "El panel de Maintenance Supporter no está disponible — ¿está configurada la integración?",
|
||||
"panel_card_load_failed": "No se pudo cargar el panel de Maintenance Supporter — recarga la página (tras una actualización: vacía la caché del navegador).",
|
||||
"doc_part_none": "No hay documentos vinculados a esta pieza.",
|
||||
"settings_templates_toggle_group": "Activar o desactivar todas las plantillas de este grupo",
|
||||
"setups_button": "Configuraciones sugeridas",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Correcta",
|
||||
"battery_fleet_status_due": "Pendiente",
|
||||
"battery_fleet_predicted_on": "Previsto hacia {date}",
|
||||
"battery_fleet_predicted_typical": "Previsto hacia el {date} — {months} meses habituales para {type} ({source})",
|
||||
"lifetime_source_default": "valor predeterminado integrado",
|
||||
"lifetime_source_table": "tabla integrada",
|
||||
"lifetime_source_learned": "aprendido de {n} sustituciones",
|
||||
"lifetime_source_override": "tu ajuste",
|
||||
"lifetime_source_learned_device": "aprendido de {n} sustituciones de este dispositivo",
|
||||
"lifetime_source_learned_model": "aprendido de {n} sustituciones en dispositivos de este modelo",
|
||||
"battery_fleet_predicted_trend": "Predicción según la tendencia de descarga de esta pila: hacia {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Recargable: se recarga en lugar de sustituirse — nunca en la lista de compra",
|
||||
"battery_fleet_sort_name": "Ordenar por nombre",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Piezas usadas",
|
||||
"print_inc_photos": "Fotos",
|
||||
"print_inc_documents": "Documentos vinculados",
|
||||
"print_inc_doc_desc": "Descripciones de los documentos",
|
||||
"print_inc_checklist": "Lista de verificación",
|
||||
"print_inc_notes": "Notas",
|
||||
"print_inc_costs": "Coste y duración",
|
||||
"print_inc_person": "Realizado por",
|
||||
"print_inc_refs": "Números de referencia",
|
||||
"print_inc_bare": "Realizaciones sin detalles",
|
||||
"print_inc_qr": "Código QR por tarea",
|
||||
"print_button": "Imprimir",
|
||||
"report_scan_hint": "Escanea para abrir la tarea",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Käyttöaika",
|
||||
"runtime_hours": "Tavoitekäyttöaika (tuntia)",
|
||||
"target_value": "Tavoitearvo",
|
||||
"baseline": "Lähtötaso",
|
||||
"target_changes": "Tavoitemuutokset",
|
||||
"for_minutes": "Vähintään (minuuttia)",
|
||||
"time_based": "Aikaperusteinen",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Luotu",
|
||||
"report_times_done": "Tehty",
|
||||
"report_total_cost": "Kokonaiskustannus",
|
||||
"report_every": "joka {n}. {unit}",
|
||||
"report_notes": "Muistiinpanot",
|
||||
"report_col_type": "Tyyppi",
|
||||
"report_col_status": "Tila",
|
||||
"report_col_schedule": "Aikataulu",
|
||||
"duration": "Kesto",
|
||||
"minutes_short": "min",
|
||||
"both": "Molemmat",
|
||||
"trigger_val": "Laukaisuarvo",
|
||||
"complete_title": "Suorita: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: väärä tyyppi (odotettiin: {type})",
|
||||
"err_invalid_choice": "{field}: ei sallittu arvo",
|
||||
"err_invalid_value": "{field}: virheellinen arvo",
|
||||
"ws_err_not_found": "Ei löytynyt — se on ehkä poistettu sillä välin",
|
||||
"ws_err_invalid_input": "Virheellinen syöte",
|
||||
"ws_err_invalid_date": "Virheellinen päivämäärä",
|
||||
"ws_err_invalid_format": "Virheellinen muoto",
|
||||
"ws_err_invalid_url": "Virheellinen linkki",
|
||||
"ws_err_not_configured": "Ei vielä määritetty",
|
||||
"ws_err_not_loaded": "Ei vielä ladattu — yritä hetken kuluttua uudelleen",
|
||||
"ws_err_limit_reached": "Raja saavutettu",
|
||||
"ws_err_too_many": "Liian monta merkintää",
|
||||
"ws_err_too_large": "Liian suuri",
|
||||
"ws_err_not_archived": "Ei arkistoitu",
|
||||
"ws_err_invalid_target": "Virheellinen kohde",
|
||||
"ws_err_create_failed": "Luominen epäonnistui",
|
||||
"ws_err_archived": "Arkistoitu — palauta ensin",
|
||||
"ws_err_already_archived": "On jo arkistoitu",
|
||||
"ws_err_unavailable": "Ei tällä hetkellä käytettävissä",
|
||||
"ws_err_unauthorized": "Ei sallittu käyttäjällesi",
|
||||
"ws_err_too_many_views": "Liian monta tallennettua näkymää",
|
||||
"ws_err_storage_unavailable": "Tallennustila ei ole käytettävissä",
|
||||
"ws_err_replace_failed": "Korvaaminen epäonnistui",
|
||||
"ws_err_not_paused": "Ei keskeytetty",
|
||||
"ws_err_not_available": "Ei mitään saatavilla",
|
||||
"ws_err_no_url": "Linkkiä ei annettu",
|
||||
"ws_err_no_phases": "Tällä tehtävällä ei ole vaiheita",
|
||||
"ws_err_invalid_view": "Virheellinen näkymä",
|
||||
"ws_err_invalid_user": "Tuntematon käyttäjä",
|
||||
"ws_err_invalid_range": "Virheellinen aikaväli",
|
||||
"ws_err_invalid_parent": "Virheellinen yläobjekti",
|
||||
"ws_err_invalid_device": "Tuntematon laite",
|
||||
"ws_err_empty_csv": "CSV-tiedosto on tyhjä",
|
||||
"ws_err_empty": "Ei mitään tuotavaa",
|
||||
"ws_err_duplicate_failed": "Monistaminen epäonnistui",
|
||||
"ws_err_already_paused": "On jo keskeytetty",
|
||||
"ws_err_invalid_cursor": "Virheellinen vaiheen sijainti",
|
||||
"ws_err_invalid_entity_slug": "Virheellinen entiteetin nimi",
|
||||
"ws_err_invalid_trigger_config": "Virheellinen laukaisimen määritys",
|
||||
"ws_err_no_defaults": "Pikavalmistumisen oletuksia ei ole asetettu",
|
||||
"ws_err_self_link_device": "Objektia ei voi linkittää itseensä",
|
||||
"ws_err_too_early": "Liian aikaista — tehtävän voi merkitä tehdyksi vasta lähempänä eräpäivää",
|
||||
"feat_schedule_time": "Kellonajan ajoitus",
|
||||
"feat_schedule_time_desc": "Tehtävät erääntyvät tiettyyn kellonaikaan keskiyön sijaan.",
|
||||
"schedule_time_toggle": "Erääntyy tiettyyn kellonaikaan",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Laukaise kun yhtä suuri kuin (=)",
|
||||
"trigger_not_equals": "Laukaise kun eri kuin (≠)",
|
||||
"for_at_least_minutes": "Vähintään (minuuttia)",
|
||||
"safety_interval_days": "Turvaväli (päivää, valinnainen)",
|
||||
"safety_interval": "Turvaväli (valinnainen)",
|
||||
"trigger_combinator": "Yhdistä laukaisin ja väli",
|
||||
"trigger_combinator_any": "Laukaisin tai väli (ensin täyttyvä)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Omat tehtävät",
|
||||
"tab_calendar": "Kalenteri",
|
||||
"cal_no_events": "Ei huoltoja",
|
||||
"cal_window_7": "7 päivää",
|
||||
"cal_window_14": "14 päivää",
|
||||
"cal_window_30": "30 päivää",
|
||||
"cal_window_365": "1 vuosi",
|
||||
"cal_every_n_days": "{n} päivän välein",
|
||||
"cal_source_time": "Aikaperusteinen",
|
||||
"cal_source_time_adaptive": "Aikaperusteinen (mukautuva)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR-koodin luonti epäonnistui.",
|
||||
"qr_error_no_url": "HA:n URL-osoitetta ei ole määritetty. Aseta ulkoinen tai sisäinen URL kohdassa Asetukset → Järjestelmä → Verkko.",
|
||||
"save_error": "Tallennus epäonnistui. Yritä uudelleen.",
|
||||
"subsave_warning": "Tallennettu, mutta yksi asetus hylättiin: {detail}",
|
||||
"qr_print": "Tulosta",
|
||||
"qr_download": "Lataa SVG",
|
||||
"qr_action": "Toiminto skannattaessa",
|
||||
"qr_action_view": "Näytä huoltotiedot",
|
||||
"qr_action_complete": "Merkitse huolto valmiiksi",
|
||||
"qr_url_mode": "Linkin tyyppi",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Paikallinen (mDNS)",
|
||||
"qr_mode_server": "Palvelimen URL",
|
||||
"overview": "Yleiskatsaus",
|
||||
"analysis": "Analyysi",
|
||||
"recent_activities": "Viimeaikaiset toimet",
|
||||
"search_notes": "Hae muistiinpanoja",
|
||||
"avg_cost": "Keskim. kustannus",
|
||||
"no_advanced_features": "Lisäominaisuuksia ei ole otettu käyttöön",
|
||||
"no_advanced_features_hint": "Ota käyttöön ”Mukautuvat välit” tai ”Kausivaihtelut” integraation asetuksissa nähdäksesi analyysitiedot täällä.",
|
||||
"analysis_not_enough_data": "Ei vielä tarpeeksi tietoja analyysiin.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analyysi vaatii vähintään 5 suoritettua huoltoa; kausivaihtelut näkyvät kun kuukautta kohden on yli 6 tietopistettä.",
|
||||
"analysis_manual_task_hint": "Manuaaliset tehtävät ilman väliä eivät tuota analyysitietoja.",
|
||||
"completions": "suoritusta",
|
||||
"current": "Nykyinen",
|
||||
"shorter": "Lyhyempi",
|
||||
"longer": "Pidempi",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Menneet jaksot",
|
||||
"cal_forward_windows": "Tulevat jaksot",
|
||||
"history_edit_title": "Muokkaa historiamerkintää",
|
||||
"history_delete_entry": "Poista merkintä",
|
||||
"history_delete_confirm": "Poistetaanko tämä historiamerkintä? Viimeisimmän suorituksen päivä palautuu edelliseen; kuvat jäävät kohteelle eikä kulutettuja osia palauteta varastoon.",
|
||||
"history_edit_timestamp": "Aikaleima",
|
||||
"manufacturer": "Valmistaja",
|
||||
"model": "Malli",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Oletusvaroituspäivät",
|
||||
"settings_consumable_threshold": "Kynnys „kulutustarvike vähissä“ (%)",
|
||||
"settings_battery_low_percent": "Kynnys „paristo vähissä“ (%)",
|
||||
"settings_battery_lifetimes": "Paristojen tyypilliset käyttöiät",
|
||||
"settings_battery_lifetimes_hint": "Käytetään vain paristoille ilman prosenttilukua: Battery Notes -merkintä, jolla on vain tyyppi ja vaihtopäivä, tai anturi, joka ilmoittaa vain ”vähissä”. Erääntyminen = viimeisin vaihto + tämä käyttöikä. Tasoa raportoivien paristojen ennuste tulee mitatusta purkautumisesta. Kun laivasto on nähnyt kolme saman tyypin vaihtoa, mediaaniväli korvaa sisäänrakennetun arvon — oma arvosi voittaa aina.",
|
||||
"settings_battery_lifetime_months": "kuukautta",
|
||||
"settings_battery_lifetime_reset": "Käytä oletusta",
|
||||
"settings_battery_lifetime_in_fleet": "laivastossasi",
|
||||
"settings_battery_lifetime_learned_models": "Opittu mallikohtaisesti: {list}",
|
||||
"settings_thresholds_hint": "Oletukset uusille Ehdotetuille asetuksille (jäljellä oleva prosentti) ja paristokannan paristoille ilman omaa Battery Notes -kynnystä. Olemassa olevat tehtävät säilyttävät arvonsa.",
|
||||
"settings_value_out_of_range": "Arvon on oltava välillä {min}–{max}",
|
||||
"bn_summary": "{name}: {pct} % {n} paristolle",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} laitetta, joilla on oma kynnys:",
|
||||
"bn_more": "+ {n} lisää",
|
||||
"settings_row_actions": "Tehtävärivien toiminnot",
|
||||
"settings_ref_numbers_in_lists": "Viitenumerot luetteloissa",
|
||||
"settings_ref_numbers_in_lists_hint": "Näyttää tunnisteet #8 / #8.3 kohteiden ja tehtävien nimien edessä Tänään-luettelossa, tehtävätaulukossa, kohdekorteissa ja kohdetaulukossa.",
|
||||
"row_actions_buttons_compact": "Painikkeet (puhelimessa vain kuvakkeet)",
|
||||
"row_actions_buttons": "Painikkeet tekstillä",
|
||||
"row_actions_icons": "Kuvakkeet (perinteinen)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Torkun kesto (tuntia)",
|
||||
"settings_budget": "Budjetti",
|
||||
"settings_currency": "Valuutta",
|
||||
"settings_currency_decimals": "Summien desimaalit",
|
||||
"settings_currency_decimals_hint": "Montako desimaalia jokainen summa näyttää: tunnusluvut, budjetit, kustannukset listoissa ja raporteissa, budjettihälytykset. 0 = kokonaisluvut.",
|
||||
"settings_budget_monthly": "Kuukausibudjetti",
|
||||
"settings_budget_yearly": "Vuosibudjetti",
|
||||
"settings_budget_alerts": "Budjettihälytykset",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Vie YAML",
|
||||
"settings_export_csv": "Vie CSV",
|
||||
"settings_export_settings": "Vie asetukset (JSON)",
|
||||
"settings_import_csv": "Tuo CSV",
|
||||
"settings_import_placeholder": "Liitä JSON- tai CSV-sisältö tähän…",
|
||||
"settings_import_btn": "Tuo",
|
||||
"settings_import_success": "{count} kohdetta tuotu onnistuneesti.",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "Tehtäväkohtainen HA-toiminto suorituksen yhteydessä + pikasuoritus-QR esiasetetuilla arvoilla.",
|
||||
"on_complete_action_title": "Suorituksen yhteydessä: laukaise HA-toiminto (valinnainen)",
|
||||
"on_complete_action_desc": "Kutsuu HA-palvelua, kun tehtävä suoritetaan — esim. nollaa laitteen laskuri.",
|
||||
"on_complete_action_service": "Palvelu",
|
||||
"on_complete_action_target": "Kohde-entiteetti",
|
||||
"on_complete_action_target_hint": "Huom: entiteetin toimialueen on vastattava palvelua — esim. 'button.press' toimii vain button.*-entiteeteillä, 'counter.increment' vain counter.*-entiteeteillä, 'input_button.press' vain input_button.*-entiteeteillä jne. Epäsopivuuden sattuessa toiminto epäonnistuu hiljaisesti (HA kirjaa 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Tiedot (JSON, valinnainen)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Valitse kuvat",
|
||||
"choose_photo": "Valitse kuva",
|
||||
"photos_android_hint": "Android-sovellus välittää yhden kuvan per valinta — jokainen valinta lisätään.",
|
||||
"add_photos": "Lisää kuvia",
|
||||
"photos_limit": "Enintään {max} kuvaa suoritusta kohti",
|
||||
"history_edit_photos_hint": "Tästä poistettu kuva säilyy kohteen asiakirjoissa.",
|
||||
"uploading": "Ladataan…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Lataa",
|
||||
"doc_close": "Sulje",
|
||||
"doc_camera": "Ota kuva",
|
||||
"camera_capture_shoot": "Ota kuva",
|
||||
"camera_switch_lens": "Vaihda kameraa",
|
||||
"doc_drop_hint": "Pudota tiedostot tähän",
|
||||
"doc_task_none": "Tähän tehtävään ei ole liitetty asiakirjoja.",
|
||||
"doc_link_existing": "Liitä asiakirja…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Kirjaa suorituksen (asettaa viimeksi tehdyn), kun laukaisin poistuu itsestään — esim. suola lisätty, suodatin vaihdettu.",
|
||||
"doc_search": "Hae asiakirjoja…",
|
||||
"doc_search_none": "Ei vastaavia asiakirjoja",
|
||||
"doc_description": "Kuvaus (valinnainen)",
|
||||
"doc_sort": "Lajittele asiakirjat",
|
||||
"doc_sort_newest": "Uusimmat ensin",
|
||||
"doc_sort_oldest": "Vanhimmat ensin",
|
||||
"doc_sort_title": "Otsikon mukaan (1, 2, 3 …)",
|
||||
"doc_sort_category": "Luokan mukaan",
|
||||
"link_device_optional": "Liitä olemassa olevaan laitteeseen (valinnainen)",
|
||||
"parent_object_optional": "Yläobjekti (valinnainen)",
|
||||
"parent_none": "(Ei yläobjektia)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Ilmoita vain näkymälle",
|
||||
"settings_notify_scope_all": "Kaikki tehtävät",
|
||||
"settings_notify_scope_hint": "Vain valitun näkymän tunniste-/käyttäjäsuodattimiin osuvat tehtävät lähettävät muistutuksia. Näkymän tila, lajittelu ja ryhmittely ohitetaan tässä.",
|
||||
"settings_notify_completed": "Ilmoitukset suorituksista",
|
||||
"settings_notify_completed_hint": "Tieto taloudelle, kun tehtävä on suoritettu, syyn ja tekijän kera. ”Vain automaattiset” ilmoittaa vain suoritukset, joita kukaan ei tehnyt paikan päällä: anturin palautuminen, ostoslistan rastitus, automaation palvelukutsu. Vain talouden palveluun; hiljaiset tunnit ja päiväraja pätevät.",
|
||||
"notify_completed_off": "Pois",
|
||||
"notify_completed_automatic": "Vain automaattiset suoritukset",
|
||||
"notify_completed_all": "Kaikki suoritukset",
|
||||
"settings_notify_rule": "Oma ilmoitussääntö",
|
||||
"settings_notify_rule_hint": "Jokainen ilmoitus laukaisee myös tapahtuman maintenance_supporter_notification (laji, tila, kohde, tehtävä, viitenumerot, prioriteetti, linkki, sisältö). Ohjaa se automaatiolla — esim. Tickeriin, Telegramiin tai Pushoveriin.",
|
||||
"settings_notify_event_only": "Laukaise vain tapahtuma, älä lähetä itse mitään",
|
||||
"settings_notify_event_only_hint": "Automaatiosi hoitaa koko toimituksen. Toistovälit, hiljaiset tunnit ja päiväraja määräävät yhä, milloin tapahtuma laukeaa.",
|
||||
"settings_notify_extra_data": "Ilmoituksen lisätiedot (malli)",
|
||||
"settings_notify_extra_data_hint": "JSON tai YAML, Jinja sallittu; yhdistetään notify-kutsun data-kenttään (omat avaimesi voittavat). Muuttujat: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Kokeile Lähetä testi -painikkeella.",
|
||||
"settings_notify_extra_data_hint": "JSON tai YAML, Jinja sallittu; yhdistetään notify-kutsun data-kenttään (omat avaimesi voittavat). Muuttujat: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Kokeile Lähetä testi -painikkeella.",
|
||||
"card_saved_view": "Tallennettu näkymä",
|
||||
"card_saved_view_none": "Ei mitään",
|
||||
"card_saved_view_help": "Soveltaa näkymän tila-, käyttäjä- ja tunnistesuodattimet yllä olevien suodattimien lisäksi. Näkymän lajittelu ja ryhmittely ovat paneelin näyttöasetuksia eivätkä vaikuta korttiin.",
|
||||
"panel_card_tab": "Avaa välilehdellä",
|
||||
"panel_card_tab_default": "Muistettu välilehti (kuten paneelissa)",
|
||||
"panel_card_view": "Avaa tallennetulla näkymällä",
|
||||
"panel_card_height": "Korkeus",
|
||||
"panel_card_height_help": "Tyhjä = täyttää näytön kortin alapuolella (paneelinäkymä). Muuten CSS-pituus, kuten 600px tai 80vh, näkymille, joissa on muita kortteja.",
|
||||
"panel_card_not_registered": "Maintenance Supporter -paneeli ei ole käytettävissä — onko integraatio määritetty?",
|
||||
"panel_card_load_failed": "Maintenance Supporter -paneelia ei voitu ladata — lataa sivu uudelleen (päivityksen jälkeen: tyhjennä selaimen välimuisti).",
|
||||
"doc_part_none": "Tähän osaan ei ole linkitetty asiakirjoja.",
|
||||
"settings_templates_toggle_group": "Ota kaikki tämän ryhmän mallit käyttöön tai pois",
|
||||
"setups_button": "Ehdotetut asennukset",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Kunnossa",
|
||||
"battery_fleet_status_due": "Erääntynyt",
|
||||
"battery_fleet_predicted_on": "Odotettavissa noin {date}",
|
||||
"battery_fleet_predicted_typical": "Odotettavissa noin {date} — {months} kuukautta tyypillisesti tyypille {type} ({source})",
|
||||
"lifetime_source_default": "sisäänrakennettu oletus",
|
||||
"lifetime_source_table": "sisäänrakennettu taulukko",
|
||||
"lifetime_source_learned": "opittu {n} vaihdosta",
|
||||
"lifetime_source_override": "oma asetuksesi",
|
||||
"lifetime_source_learned_device": "opittu tämän laitteen {n} vaihdosta",
|
||||
"lifetime_source_learned_model": "opittu tämän mallin laitteiden {n} vaihdosta",
|
||||
"battery_fleet_predicted_trend": "Ennustettu tämän pariston purkautumistrendistä: noin {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Ladattava: lataa vaihtamisen sijaan — ei koskaan ostoslistalle",
|
||||
"battery_fleet_sort_name": "Lajittele nimen mukaan",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Käytetyt osat",
|
||||
"print_inc_photos": "Kuvat",
|
||||
"print_inc_documents": "Liitetyt asiakirjat",
|
||||
"print_inc_doc_desc": "Asiakirjojen kuvaukset",
|
||||
"print_inc_checklist": "Tarkistuslista",
|
||||
"print_inc_notes": "Muistiinpanot",
|
||||
"print_inc_costs": "Kustannus ja kesto",
|
||||
"print_inc_person": "Suorittaja",
|
||||
"print_inc_refs": "Viitenumerot",
|
||||
"print_inc_bare": "Suoritukset ilman tietoja",
|
||||
"print_inc_qr": "QR-koodi tehtävää kohti",
|
||||
"print_button": "Tulosta",
|
||||
"report_scan_hint": "Skannaa avataksesi tehtävän",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Durée de fonctionnement",
|
||||
"runtime_hours": "Durée cible (heures)",
|
||||
"target_value": "Valeur cible",
|
||||
"baseline": "Ligne de base",
|
||||
"target_changes": "Changements cibles",
|
||||
"for_minutes": "Pendant (minutes)",
|
||||
"time_based": "Temporel",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Généré",
|
||||
"report_times_done": "Effectué",
|
||||
"report_total_cost": "Coût total",
|
||||
"report_every": "tous les {n} {unit}",
|
||||
"report_notes": "Notes",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Statut",
|
||||
"report_col_schedule": "Planning",
|
||||
"duration": "Durée",
|
||||
"minutes_short": "min",
|
||||
"both": "Les deux",
|
||||
"trigger_val": "Valeur du déclencheur",
|
||||
"complete_title": "Terminé : ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field} : mauvais type (attendu : {type})",
|
||||
"err_invalid_choice": "{field} : valeur non autorisée",
|
||||
"err_invalid_value": "{field} : valeur invalide",
|
||||
"ws_err_not_found": "Introuvable — il a peut-être été supprimé entre-temps",
|
||||
"ws_err_invalid_input": "Saisie non valide",
|
||||
"ws_err_invalid_date": "Date non valide",
|
||||
"ws_err_invalid_format": "Format non valide",
|
||||
"ws_err_invalid_url": "Lien non valide",
|
||||
"ws_err_not_configured": "Pas encore configuré",
|
||||
"ws_err_not_loaded": "Pas encore chargé — réessayez dans un instant",
|
||||
"ws_err_limit_reached": "Limite atteinte",
|
||||
"ws_err_too_many": "Trop d'entrées",
|
||||
"ws_err_too_large": "Trop volumineux",
|
||||
"ws_err_not_archived": "Non archivé",
|
||||
"ws_err_invalid_target": "Cible non valide",
|
||||
"ws_err_create_failed": "Impossible de créer",
|
||||
"ws_err_archived": "Archivé — restaurez-le d'abord",
|
||||
"ws_err_already_archived": "Déjà archivé",
|
||||
"ws_err_unavailable": "Actuellement indisponible",
|
||||
"ws_err_unauthorized": "Non autorisé pour votre utilisateur",
|
||||
"ws_err_too_many_views": "Trop de vues enregistrées",
|
||||
"ws_err_storage_unavailable": "Le stockage n'est pas disponible",
|
||||
"ws_err_replace_failed": "Échec du remplacement",
|
||||
"ws_err_not_paused": "Pas en pause",
|
||||
"ws_err_not_available": "Rien de disponible",
|
||||
"ws_err_no_url": "Aucun lien indiqué",
|
||||
"ws_err_no_phases": "Cette tâche n'a pas de phases",
|
||||
"ws_err_invalid_view": "Vue non valide",
|
||||
"ws_err_invalid_user": "Utilisateur inconnu",
|
||||
"ws_err_invalid_range": "Plage non valide",
|
||||
"ws_err_invalid_parent": "Objet parent non valide",
|
||||
"ws_err_invalid_device": "Appareil inconnu",
|
||||
"ws_err_empty_csv": "Le fichier CSV est vide",
|
||||
"ws_err_empty": "Rien à importer",
|
||||
"ws_err_duplicate_failed": "Impossible de dupliquer",
|
||||
"ws_err_already_paused": "Déjà en pause",
|
||||
"ws_err_invalid_cursor": "Position de phase non valide",
|
||||
"ws_err_invalid_entity_slug": "Nom d'entité non valide",
|
||||
"ws_err_invalid_trigger_config": "Configuration du déclencheur non valide",
|
||||
"ws_err_no_defaults": "Aucune valeur par défaut pour la validation rapide",
|
||||
"ws_err_self_link_device": "Un objet ne peut pas être lié à lui-même",
|
||||
"ws_err_too_early": "Trop tôt — cette tâche ne peut être terminée que plus près de son échéance",
|
||||
"feat_schedule_time": "Planification à l'heure",
|
||||
"feat_schedule_time_desc": "Les tâches arrivent à échéance à une heure précise plutôt qu'à minuit.",
|
||||
"schedule_time_toggle": "Échéance à une heure précise",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Déclencher si égal à (=)",
|
||||
"trigger_not_equals": "Déclencher si différent de (≠)",
|
||||
"for_at_least_minutes": "Pendant au moins (minutes)",
|
||||
"safety_interval_days": "Intervalle de sécurité (jours, optionnel)",
|
||||
"safety_interval": "Intervalle de sécurité (optionnel)",
|
||||
"trigger_combinator": "Combiner déclencheur et intervalle",
|
||||
"trigger_combinator_any": "Déclencheur ou intervalle (premier atteint)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Mes tâches",
|
||||
"tab_calendar": "Calendrier",
|
||||
"cal_no_events": "Aucun entretien",
|
||||
"cal_window_7": "7 jours",
|
||||
"cal_window_14": "14 jours",
|
||||
"cal_window_30": "30 jours",
|
||||
"cal_window_365": "1 an",
|
||||
"cal_every_n_days": "tous les {n} jours",
|
||||
"cal_source_time": "Basé sur le temps",
|
||||
"cal_source_time_adaptive": "Basé sur le temps (adaptatif)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Impossible de générer le QR code.",
|
||||
"qr_error_no_url": "Aucune URL HA configurée. Veuillez définir une URL externe ou interne dans Paramètres → Système → Réseau.",
|
||||
"save_error": "Échec de l'enregistrement. Veuillez réessayer.",
|
||||
"subsave_warning": "Enregistré, mais un réglage a été refusé : {detail}",
|
||||
"qr_print": "Imprimer",
|
||||
"qr_download": "Télécharger SVG",
|
||||
"qr_action": "Action au scan",
|
||||
"qr_action_view": "Afficher les infos de maintenance",
|
||||
"qr_action_complete": "Marquer la maintenance comme terminée",
|
||||
"qr_url_mode": "Type de lien",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "URL serveur",
|
||||
"overview": "Aperçu",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Activités récentes",
|
||||
"search_notes": "Rechercher dans les notes",
|
||||
"avg_cost": "Ø Coût",
|
||||
"no_advanced_features": "Aucune fonction avancée activée",
|
||||
"no_advanced_features_hint": "Activez « Intervalles adaptatifs » ou « Tendances saisonnières » dans les paramètres de l'intégration pour voir les données d'analyse ici.",
|
||||
"analysis_not_enough_data": "Pas encore assez de données pour l'analyse.",
|
||||
"analysis_not_enough_data_hint": "L'analyse Weibull nécessite au moins 5 maintenances terminées ; les tendances saisonnières apparaissent après 6+ points par mois.",
|
||||
"analysis_manual_task_hint": "Les tâches manuelles sans intervalle ne génèrent pas de données d'analyse.",
|
||||
"completions": "réalisations",
|
||||
"current": "Actuel",
|
||||
"shorter": "Plus court",
|
||||
"longer": "Plus long",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Fenêtres passées",
|
||||
"cal_forward_windows": "Fenêtres à venir",
|
||||
"history_edit_title": "Modifier l'entrée d'historique",
|
||||
"history_delete_entry": "Supprimer l'entrée",
|
||||
"history_delete_confirm": "Supprimer cette entrée d'historique ? La date de dernière réalisation revient à la précédente ; les photos restent avec l'objet et les pièces consommées ne sont pas remises en stock.",
|
||||
"history_edit_timestamp": "Horodatage",
|
||||
"manufacturer": "Fabricant",
|
||||
"model": "Modèle",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Jours d'avertissement par défaut",
|
||||
"settings_consumable_threshold": "Seuil « consommable bas » (%)",
|
||||
"settings_battery_low_percent": "Seuil « pile faible » (%)",
|
||||
"settings_battery_lifetimes": "Durées de vie typiques des piles",
|
||||
"settings_battery_lifetimes_hint": "Utilisé uniquement pour les piles sans pourcentage : une note Battery Notes qui ne connaît qu'un type et une date de remplacement, ou un capteur qui ne signale que « faible ». Leur échéance est dernier remplacement + cette durée. Les piles qui remontent un niveau sont prévues d'après la décharge mesurée. Dès que le parc a vu trois remplacements d'un type, l'intervalle médian remplace la valeur intégrée — votre valeur l'emporte toujours.",
|
||||
"settings_battery_lifetime_months": "mois",
|
||||
"settings_battery_lifetime_reset": "Valeur par défaut",
|
||||
"settings_battery_lifetime_in_fleet": "dans votre parc",
|
||||
"settings_battery_lifetime_learned_models": "Appris par modèle : {list}",
|
||||
"settings_thresholds_hint": "Valeurs par défaut pour les nouvelles Configurations suggérées (pourcentage restant) et pour les piles de la flotte sans seuil Battery Notes propre. Les tâches existantes gardent leur valeur.",
|
||||
"settings_value_out_of_range": "La valeur doit être comprise entre {min} et {max}",
|
||||
"bn_summary": "{name} : {pct} % pour {n} piles",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} appareils avec leur propre seuil :",
|
||||
"bn_more": "+ {n} autres",
|
||||
"settings_row_actions": "Actions des lignes de tâches",
|
||||
"settings_ref_numbers_in_lists": "Numéros de référence dans les listes",
|
||||
"settings_ref_numbers_in_lists_hint": "Affiche les puces #8 / #8.3 devant les noms d'objets et de tâches dans la liste Aujourd'hui, le tableau des tâches, les cartes d'objets et le tableau des objets.",
|
||||
"row_actions_buttons_compact": "Boutons (icônes seules sur téléphone)",
|
||||
"row_actions_buttons": "Boutons avec texte",
|
||||
"row_actions_icons": "Icônes (classique)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Durée de report (heures)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Devise",
|
||||
"settings_currency_decimals": "Décimales des montants",
|
||||
"settings_currency_decimals_hint": "Nombre de décimales affichées pour chaque montant : indicateurs, budgets, coûts dans les listes et rapports, alertes budget. 0 = nombres entiers.",
|
||||
"settings_budget_monthly": "Budget mensuel",
|
||||
"settings_budget_yearly": "Budget annuel",
|
||||
"settings_budget_alerts": "Alertes budgétaires",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exporter YAML",
|
||||
"settings_export_csv": "Exporter CSV",
|
||||
"settings_export_settings": "Exporter les réglages (JSON)",
|
||||
"settings_import_csv": "Importer CSV",
|
||||
"settings_import_placeholder": "Collez le contenu JSON ou CSV ici…",
|
||||
"settings_import_btn": "Importer",
|
||||
"settings_import_success": "{count} objets importés avec succès.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Action HA par tâche lors de la finalisation + QR de finalisation rapide avec valeurs pré-définies.",
|
||||
"on_complete_action_title": "À la finalisation : déclencher une action HA (optionnel)",
|
||||
"on_complete_action_desc": "Appelle un service HA quand la tâche est terminée — p. ex. réinitialiser un compteur sur l'appareil.",
|
||||
"on_complete_action_service": "Service",
|
||||
"on_complete_action_target": "Entité cible",
|
||||
"on_complete_action_data": "Données (JSON, optionnel)",
|
||||
"on_complete_action_test": "Tester l'action",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Choisir des photos",
|
||||
"choose_photo": "Choisir une photo",
|
||||
"photos_android_hint": "L'application Android ne transmet qu'une photo par sélection — chaque sélection est ajoutée.",
|
||||
"add_photos": "Ajouter des photos",
|
||||
"photos_limit": "Jusqu'à {max} photos par fin de tâche",
|
||||
"history_edit_photos_hint": "Une photo retirée ici reste dans les documents de l'objet.",
|
||||
"uploading": "Téléversement…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Télécharger",
|
||||
"doc_close": "Fermer",
|
||||
"doc_camera": "Prendre une photo",
|
||||
"camera_capture_shoot": "Prendre la photo",
|
||||
"camera_switch_lens": "Changer de caméra",
|
||||
"doc_drop_hint": "Déposez les fichiers ici",
|
||||
"doc_task_none": "Aucun document lié à cette tâche.",
|
||||
"doc_link_existing": "Lier un document…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Enregistre une exécution (met à jour la dernière réalisation) lorsque le déclencheur se résout de lui-même — p. ex. sel rechargé, filtre remplacé.",
|
||||
"doc_search": "Rechercher des documents…",
|
||||
"doc_search_none": "Aucun document correspondant",
|
||||
"doc_description": "Description (facultatif)",
|
||||
"doc_sort": "Trier les documents",
|
||||
"doc_sort_newest": "Plus récents d'abord",
|
||||
"doc_sort_oldest": "Plus anciens d'abord",
|
||||
"doc_sort_title": "Par titre (1, 2, 3 …)",
|
||||
"doc_sort_category": "Par catégorie",
|
||||
"link_device_optional": "Lier à un appareil existant (optionnel)",
|
||||
"parent_object_optional": "Objet parent (optionnel)",
|
||||
"parent_none": "(Aucun parent)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notifier uniquement pour la vue",
|
||||
"settings_notify_scope_all": "Toutes les tâches",
|
||||
"settings_notify_scope_hint": "Seules les tâches correspondant aux filtres étiquette/utilisateur de la vue choisie envoient des rappels. Le statut, le tri et le regroupement de la vue sont ignorés ici.",
|
||||
"settings_notify_completed": "Notifications d'achèvement",
|
||||
"settings_notify_completed_hint": "Une info pour le foyer quand une tâche est terminée, avec la raison et l'auteur. « Automatiques seulement » n'annonce que les achèvements que personne n'a faits sur place : retour du capteur, case cochée dans la liste de courses, appel de service d'une automatisation. Envoyé au service du foyer seulement ; heures calmes et plafond quotidien s'appliquent.",
|
||||
"notify_completed_off": "Désactivé",
|
||||
"notify_completed_automatic": "Achèvements automatiques seulement",
|
||||
"notify_completed_all": "Tous les achèvements",
|
||||
"settings_notify_rule": "Votre propre règle de notification",
|
||||
"settings_notify_rule_hint": "Chaque notification déclenche aussi l'événement maintenance_supporter_notification (type, statut, objet, tâche, numéros de référence, priorité, lien, charge utile). Routez-le avec une automatisation — vers Ticker, Telegram ou Pushover par exemple.",
|
||||
"settings_notify_event_only": "Déclencher seulement l'événement, ne rien envoyer",
|
||||
"settings_notify_event_only_hint": "Votre automatisation assure toute la livraison. Intervalles de répétition, heures calmes et plafond quotidien s'appliquent toujours au déclenchement de l'événement.",
|
||||
"settings_notify_extra_data": "Données de notification supplémentaires (modèle)",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja autorisé ; fusionné dans les data de l'appel notify (vos clés l'emportent). Variables : kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Vérifiez avec le bouton Envoyer un test.",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja autorisé ; fusionné dans les data de l'appel notify (vos clés l'emportent). Variables : kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Vérifiez avec le bouton Envoyer un test.",
|
||||
"card_saved_view": "Vue enregistrée",
|
||||
"card_saved_view_none": "Aucune",
|
||||
"card_saved_view_help": "Applique les filtres statut, utilisateur et étiquette de la vue en plus des filtres ci-dessus. Le tri et le regroupement de la vue sont des réglages d'affichage du panneau et ne s'appliquent pas à la carte.",
|
||||
"panel_card_tab": "Ouvrir sur l'onglet",
|
||||
"panel_card_tab_default": "Onglet mémorisé (comme dans le panneau)",
|
||||
"panel_card_view": "Ouvrir avec la vue enregistrée",
|
||||
"panel_card_height": "Hauteur",
|
||||
"panel_card_height_help": "Vide = remplit l'écran sous la carte (vue panneau). Sinon une longueur CSS comme 600px ou 80vh pour les vues avec d'autres cartes.",
|
||||
"panel_card_not_registered": "Le panneau Maintenance Supporter n'est pas disponible — l'intégration est-elle configurée ?",
|
||||
"panel_card_load_failed": "Le panneau Maintenance Supporter n'a pas pu être chargé — rechargez la page (après une mise à jour : videz le cache du navigateur).",
|
||||
"doc_part_none": "Aucun document lié à cette pièce.",
|
||||
"settings_templates_toggle_group": "Activer ou désactiver tous les modèles de ce groupe",
|
||||
"setups_button": "Configurations suggérées",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Bon état",
|
||||
"battery_fleet_status_due": "À remplacer",
|
||||
"battery_fleet_predicted_on": "Prévu vers {date}",
|
||||
"battery_fleet_predicted_typical": "Prévu vers le {date} — {months} mois typiques pour {type} ({source})",
|
||||
"lifetime_source_default": "valeur par défaut intégrée",
|
||||
"lifetime_source_table": "table intégrée",
|
||||
"lifetime_source_learned": "appris de {n} remplacements",
|
||||
"lifetime_source_override": "votre réglage",
|
||||
"lifetime_source_learned_device": "appris des {n} remplacements de cet appareil",
|
||||
"lifetime_source_learned_model": "appris de {n} remplacements sur des appareils de ce modèle",
|
||||
"battery_fleet_predicted_trend": "Prédit à partir de la tendance de décharge de cette pile : vers {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Rechargeable : à recharger plutôt qu'à remplacer — jamais sur la liste d'achats",
|
||||
"battery_fleet_sort_name": "Trier par nom",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Pièces utilisées",
|
||||
"print_inc_photos": "Photographies",
|
||||
"print_inc_documents": "Documents liés",
|
||||
"print_inc_doc_desc": "Descriptions des documents",
|
||||
"print_inc_checklist": "Liste de contrôle",
|
||||
"print_inc_notes": "Remarques",
|
||||
"print_inc_costs": "Coût et durée",
|
||||
"print_inc_person": "Réalisé par",
|
||||
"print_inc_refs": "Numéros de référence",
|
||||
"print_inc_bare": "Réalisations sans détails",
|
||||
"print_inc_qr": "QR code par tâche",
|
||||
"print_button": "Imprimer",
|
||||
"report_scan_hint": "Scanner pour ouvrir la tâche",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "रनटाइम",
|
||||
"runtime_hours": "लक्ष्य रनटाइम (घंटे)",
|
||||
"target_value": "लक्ष्य मान",
|
||||
"baseline": "आधार रेखा",
|
||||
"target_changes": "लक्ष्य परिवर्तन",
|
||||
"for_minutes": "के लिए (मिनट)",
|
||||
"time_based": "समय-आधारित",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "जनरेट किया गया",
|
||||
"report_times_done": "पूर्ण",
|
||||
"report_total_cost": "कुल लागत",
|
||||
"report_every": "हर {n} {unit}",
|
||||
"report_notes": "नोट्स",
|
||||
"report_col_type": "प्रकार",
|
||||
"report_col_status": "स्थिति",
|
||||
"report_col_schedule": "शेड्यूल",
|
||||
"duration": "अवधि",
|
||||
"minutes_short": "मिनट",
|
||||
"both": "दोनों",
|
||||
"trigger_val": "ट्रिगर मान",
|
||||
"complete_title": "पूर्ण करें: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: गलत प्रकार (अपेक्षित: {type})",
|
||||
"err_invalid_choice": "{field}: अनुमत मान नहीं है",
|
||||
"err_invalid_value": "{field}: अमान्य मान",
|
||||
"ws_err_not_found": "नहीं मिला — शायद इस बीच हटा दिया गया है",
|
||||
"ws_err_invalid_input": "अमान्य इनपुट",
|
||||
"ws_err_invalid_date": "अमान्य तारीख",
|
||||
"ws_err_invalid_format": "अमान्य प्रारूप",
|
||||
"ws_err_invalid_url": "अमान्य लिंक",
|
||||
"ws_err_not_configured": "अभी सेट नहीं किया गया",
|
||||
"ws_err_not_loaded": "अभी लोड नहीं हुआ — कुछ देर बाद फिर से कोशिश करें",
|
||||
"ws_err_limit_reached": "सीमा पूरी हो गई",
|
||||
"ws_err_too_many": "बहुत अधिक प्रविष्टियाँ",
|
||||
"ws_err_too_large": "बहुत बड़ा",
|
||||
"ws_err_not_archived": "संग्रहीत नहीं",
|
||||
"ws_err_invalid_target": "अमान्य लक्ष्य",
|
||||
"ws_err_create_failed": "बनाया नहीं जा सका",
|
||||
"ws_err_archived": "संग्रहीत — पहले पुनर्स्थापित करें",
|
||||
"ws_err_already_archived": "पहले से संग्रहीत",
|
||||
"ws_err_unavailable": "फ़िलहाल उपलब्ध नहीं",
|
||||
"ws_err_unauthorized": "आपके उपयोगकर्ता के लिए अनुमति नहीं",
|
||||
"ws_err_too_many_views": "बहुत अधिक सहेजे गए दृश्य",
|
||||
"ws_err_storage_unavailable": "स्टोरेज उपलब्ध नहीं है",
|
||||
"ws_err_replace_failed": "बदलना विफल रहा",
|
||||
"ws_err_not_paused": "रोका नहीं गया",
|
||||
"ws_err_not_available": "कुछ उपलब्ध नहीं",
|
||||
"ws_err_no_url": "कोई लिंक नहीं दिया गया",
|
||||
"ws_err_no_phases": "इस कार्य में कोई चरण नहीं है",
|
||||
"ws_err_invalid_view": "अमान्य दृश्य",
|
||||
"ws_err_invalid_user": "अज्ञात उपयोगकर्ता",
|
||||
"ws_err_invalid_range": "अमान्य सीमा",
|
||||
"ws_err_invalid_parent": "अमान्य मूल ऑब्जेक्ट",
|
||||
"ws_err_invalid_device": "अज्ञात डिवाइस",
|
||||
"ws_err_empty_csv": "CSV फ़ाइल खाली है",
|
||||
"ws_err_empty": "आयात करने के लिए कुछ नहीं",
|
||||
"ws_err_duplicate_failed": "डुप्लिकेट नहीं किया जा सका",
|
||||
"ws_err_already_paused": "पहले से रोका गया",
|
||||
"ws_err_invalid_cursor": "अमान्य चरण स्थिति",
|
||||
"ws_err_invalid_entity_slug": "अमान्य एंटिटी नाम",
|
||||
"ws_err_invalid_trigger_config": "अमान्य ट्रिगर कॉन्फ़िगरेशन",
|
||||
"ws_err_no_defaults": "त्वरित पूर्णता के लिए डिफ़ॉल्ट सेट नहीं",
|
||||
"ws_err_self_link_device": "कोई ऑब्जेक्ट स्वयं से लिंक नहीं हो सकता",
|
||||
"ws_err_too_early": "बहुत जल्दी — यह कार्य केवल नियत तारीख के करीब ही पूरा किया जा सकता है",
|
||||
"feat_schedule_time": "दिन का समय निर्धारण",
|
||||
"feat_schedule_time_desc": "कार्य मध्यरात्रि के बजाय दिन के एक विशिष्ट समय पर अतिदेय हो जाते हैं।",
|
||||
"schedule_time_toggle": "किसी निश्चित समय पर देय",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "बराबर होने पर ट्रिगर करें (=)",
|
||||
"trigger_not_equals": "भिन्न होने पर ट्रिगर करें (≠)",
|
||||
"for_at_least_minutes": "कम से कम (मिनट)",
|
||||
"safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)",
|
||||
"safety_interval": "सुरक्षा अंतराल (वैकल्पिक)",
|
||||
"trigger_combinator": "ट्रिगर और अंतराल संयोजित करें",
|
||||
"trigger_combinator_any": "ट्रिगर या अंतराल (जो पहले हो)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "मेरे कार्य",
|
||||
"tab_calendar": "कैलेंडर",
|
||||
"cal_no_events": "कोई रखरखाव नहीं",
|
||||
"cal_window_7": "7 दिन",
|
||||
"cal_window_14": "14 दिन",
|
||||
"cal_window_30": "30 दिन",
|
||||
"cal_window_365": "1 वर्ष",
|
||||
"cal_every_n_days": "हर {n} दिन",
|
||||
"cal_source_time": "समय-आधारित",
|
||||
"cal_source_time_adaptive": "समय-आधारित (अनुकूली)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR कोड बनाने में विफल।",
|
||||
"qr_error_no_url": "कोई HA URL कॉन्फ़िगर नहीं है। कृपया सेटिंग्स → सिस्टम → नेटवर्क में एक बाहरी या आंतरिक URL सेट करें।",
|
||||
"save_error": "सहेजने में विफल। कृपया पुनः प्रयास करें।",
|
||||
"subsave_warning": "सहेजा गया, लेकिन एक सेटिंग अस्वीकृत हुई: {detail}",
|
||||
"qr_print": "प्रिंट करें",
|
||||
"qr_download": "SVG डाउनलोड करें",
|
||||
"qr_action": "स्कैन पर क्रिया",
|
||||
"qr_action_view": "रखरखाव जानकारी देखें",
|
||||
"qr_action_complete": "रखरखाव को पूर्ण के रूप में चिह्नित करें",
|
||||
"qr_url_mode": "लिंक प्रकार",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "लोकल (mDNS)",
|
||||
"qr_mode_server": "सर्वर URL",
|
||||
"overview": "अवलोकन",
|
||||
"analysis": "विश्लेषण",
|
||||
"recent_activities": "हाल की गतिविधियाँ",
|
||||
"search_notes": "टिप्पणियाँ खोजें",
|
||||
"avg_cost": "औसत लागत",
|
||||
"no_advanced_features": "कोई उन्नत सुविधा सक्षम नहीं",
|
||||
"no_advanced_features_hint": "यहाँ विश्लेषण डेटा देखने के लिए इंटीग्रेशन सेटिंग्स में “अनुकूली अंतराल” या “मौसमी पैटर्न” सक्षम करें।",
|
||||
"analysis_not_enough_data": "अभी विश्लेषण के लिए पर्याप्त डेटा नहीं।",
|
||||
"analysis_not_enough_data_hint": "वीबुल विश्लेषण के लिए कम से कम 5 पूर्ण रखरखाव आवश्यक हैं; मौसमी पैटर्न प्रति माह 6+ डेटा बिंदुओं के बाद दिखाई देते हैं।",
|
||||
"analysis_manual_task_hint": "अंतराल के बिना मैनुअल कार्य विश्लेषण डेटा उत्पन्न नहीं करते।",
|
||||
"completions": "पूर्णताएँ",
|
||||
"current": "वर्तमान",
|
||||
"shorter": "छोटा",
|
||||
"longer": "लंबा",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "पिछली अवधियाँ",
|
||||
"cal_forward_windows": "आगामी अवधियाँ",
|
||||
"history_edit_title": "इतिहास प्रविष्टि संपादित करें",
|
||||
"history_delete_entry": "प्रविष्टि हटाएँ",
|
||||
"history_delete_confirm": "क्या यह इतिहास प्रविष्टि हटाएँ? अंतिम निष्पादन की तिथि पिछली पूर्णता पर लौट जाएगी; फ़ोटो ऑब्जेक्ट के पास रहेंगे और उपयोग किए गए पुर्जे स्टॉक में वापस नहीं जोड़े जाएँगे।",
|
||||
"history_edit_timestamp": "टाइमस्टैम्प",
|
||||
"manufacturer": "निर्माता",
|
||||
"model": "मॉडल",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "डिफ़ॉल्ट चेतावनी दिन",
|
||||
"settings_consumable_threshold": "उपभोग्य „कम“ सीमा (%)",
|
||||
"settings_battery_low_percent": "बैटरी „कम“ सीमा (%)",
|
||||
"settings_battery_lifetimes": "बैटरी की सामान्य आयु",
|
||||
"settings_battery_lifetimes_hint": "केवल बिना प्रतिशत वाली बैटरियों के लिए: सिर्फ़ प्रकार और बदलने की तारीख वाला Battery Notes नोट, या केवल \"कम\" बताने वाला सेंसर। नियत तिथि = अंतिम बदलाव + यह आयु। स्तर बताने वाली बैटरियों का पूर्वानुमान मापे गए डिस्चार्ज से बनता है। एक प्रकार के तीन बदलाव देखने के बाद माध्यिका अंतराल अंतर्निहित मान की जगह लेता है — आपका अपना मान हमेशा प्राथमिकता पाता है।",
|
||||
"settings_battery_lifetime_months": "महीने",
|
||||
"settings_battery_lifetime_reset": "डिफ़ॉल्ट उपयोग करें",
|
||||
"settings_battery_lifetime_in_fleet": "आपके बेड़े में",
|
||||
"settings_battery_lifetime_learned_models": "मॉडल अनुसार सीखा: {list}",
|
||||
"settings_thresholds_hint": "नए सुझाए गए सेटअप (शेष प्रतिशत) और अपनी Battery Notes सीमा न रखने वाली बेड़े की बैटरियों के लिए डिफ़ॉल्ट। मौजूदा कार्य अपना मान रखते हैं।",
|
||||
"settings_value_out_of_range": "मान {min} और {max} के बीच होना चाहिए",
|
||||
"bn_summary": "{name}: {n} बैटरियों के लिए {pct} %",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} डिवाइस अपनी सीमा के साथ:",
|
||||
"bn_more": "+ {n} और",
|
||||
"settings_row_actions": "कार्य पंक्ति क्रियाएँ",
|
||||
"settings_ref_numbers_in_lists": "सूचियों में संदर्भ संख्या",
|
||||
"settings_ref_numbers_in_lists_hint": "आज सूची, कार्य तालिका, ऑब्जेक्ट कार्ड और ऑब्जेक्ट तालिका में ऑब्जेक्ट और कार्य नामों के आगे #8 / #8.3 चिप दिखाता है।",
|
||||
"row_actions_buttons_compact": "बटन (फ़ोन पर केवल आइकन)",
|
||||
"row_actions_buttons": "टेक्स्ट वाले बटन",
|
||||
"row_actions_icons": "आइकन (क्लासिक)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "स्नूज़ अवधि (घंटे)",
|
||||
"settings_budget": "बजट",
|
||||
"settings_currency": "मुद्रा",
|
||||
"settings_currency_decimals": "राशियों के दशमलव स्थान",
|
||||
"settings_currency_decimals_hint": "हर राशि कितने दशमलव दिखाए: KPI, बजट, सूचियों और रिपोर्टों में लागत, बजट अलर्ट। 0 = पूर्ण संख्याएँ।",
|
||||
"settings_budget_monthly": "मासिक बजट",
|
||||
"settings_budget_yearly": "वार्षिक बजट",
|
||||
"settings_budget_alerts": "बजट अलर्ट",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML निर्यात करें",
|
||||
"settings_export_csv": "CSV निर्यात करें",
|
||||
"settings_export_settings": "सेटिंग्स निर्यात करें (JSON)",
|
||||
"settings_import_csv": "CSV आयात करें",
|
||||
"settings_import_placeholder": "JSON या CSV सामग्री यहाँ चिपकाएँ…",
|
||||
"settings_import_btn": "आयात करें",
|
||||
"settings_import_success": "{count} वस्तुएँ सफलतापूर्वक आयात की गईं।",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "पूर्ण होने पर प्रति-कार्य HA क्रिया + पूर्व-निर्धारित मानों के साथ त्वरित-पूर्ण QR।",
|
||||
"on_complete_action_title": "पूर्ण होने पर: HA क्रिया ट्रिगर करें (वैकल्पिक)",
|
||||
"on_complete_action_desc": "कार्य पूर्ण होने पर एक HA सेवा कॉल करता है — उदा. डिवाइस पर काउंटर रीसेट करना।",
|
||||
"on_complete_action_service": "सेवा",
|
||||
"on_complete_action_target": "लक्ष्य एंटिटी",
|
||||
"on_complete_action_target_hint": "ध्यान दें: एंटिटी डोमेन सेवा से मेल खाना चाहिए — उदा. 'button.press' केवल button.* पर काम करता है, 'counter.increment' केवल counter.* पर, 'input_button.press' केवल input_button.* पर आदि। मेल न खाने पर क्रिया चुपचाप विफल हो जाएगी (HA लॉग करता है 'Referenced entities ... missing or not currently available')।",
|
||||
"on_complete_action_data": "डेटा (JSON, वैकल्पिक)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "तस्वीरें चुनें",
|
||||
"choose_photo": "फ़ोटो चुनें",
|
||||
"photos_android_hint": "Android ऐप प्रति चयन एक फ़ोटो देता है — हर चयन जोड़ा जाता है।",
|
||||
"add_photos": "तस्वीरें जोड़ें",
|
||||
"photos_limit": "प्रति पूर्णता अधिकतम {max} तस्वीरें",
|
||||
"history_edit_photos_hint": "यहाँ हटाई गई तस्वीर ऑब्जेक्ट के दस्तावेज़ों में बनी रहती है।",
|
||||
"uploading": "अपलोड हो रहा है…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "डाउनलोड करें",
|
||||
"doc_close": "बंद करें",
|
||||
"doc_camera": "फ़ोटो लें",
|
||||
"camera_capture_shoot": "कैप्चर करें",
|
||||
"camera_switch_lens": "कैमरा बदलें",
|
||||
"doc_drop_hint": "फ़ाइलें यहाँ छोड़ें",
|
||||
"doc_task_none": "इस कार्य से कोई दस्तावेज़ संबद्ध नहीं है।",
|
||||
"doc_link_existing": "दस्तावेज़ जोड़ें…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "जब ट्रिगर स्वयं हल हो जाता है तो एक पूर्णता दर्ज करता है (अंतिम निष्पादन सेट करता है) — जैसे नमक भरा गया, फ़िल्टर बदला गया।",
|
||||
"doc_search": "दस्तावेज़ खोजें…",
|
||||
"doc_search_none": "कोई मेल खाता दस्तावेज़ नहीं",
|
||||
"doc_description": "विवरण (वैकल्पिक)",
|
||||
"doc_sort": "दस्तावेज़ क्रमबद्ध करें",
|
||||
"doc_sort_newest": "नवीनतम पहले",
|
||||
"doc_sort_oldest": "सबसे पुराने पहले",
|
||||
"doc_sort_title": "शीर्षक के अनुसार (1, 2, 3 …)",
|
||||
"doc_sort_category": "श्रेणी के अनुसार",
|
||||
"link_device_optional": "मौजूदा डिवाइस से लिंक करें (वैकल्पिक)",
|
||||
"parent_object_optional": "मूल ऑब्जेक्ट (वैकल्पिक)",
|
||||
"parent_none": "(कोई मूल नहीं)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "केवल इस व्यू के लिए सूचित करें",
|
||||
"settings_notify_scope_all": "सभी कार्य",
|
||||
"settings_notify_scope_hint": "केवल वे कार्य अनुस्मारक भेजते हैं जो चुने गए व्यू के लेबल/उपयोगकर्ता फ़िल्टर से मेल खाते हैं। व्यू की स्थिति, क्रम और समूहन यहाँ अनदेखे रहते हैं।",
|
||||
"settings_notify_completed": "पूर्णता सूचनाएँ",
|
||||
"settings_notify_completed_hint": "किसी कार्य के पूरा होने पर परिवार को कारण और करने वाले सहित सूचना। \"केवल स्वचालित\" सिर्फ़ वे पूर्णताएँ बताता है जो किसी ने मौके पर नहीं कीं: सेंसर रिकवरी, खरीदारी सूची में टिक, ऑटोमेशन का सर्विस कॉल। केवल परिवार की सेवा पर; शांत घंटे और दैनिक सीमा लागू।",
|
||||
"notify_completed_off": "बंद",
|
||||
"notify_completed_automatic": "केवल स्वचालित पूर्णताएँ",
|
||||
"notify_completed_all": "सभी पूर्णताएँ",
|
||||
"settings_notify_rule": "आपका अपना सूचना नियम",
|
||||
"settings_notify_rule_hint": "हर सूचना maintenance_supporter_notification इवेंट भी ट्रिगर करती है (प्रकार, स्थिति, ऑब्जेक्ट, कार्य, संदर्भ संख्याएँ, प्राथमिकता, डीप लिंक, पेलोड)। इसे ऑटोमेशन से रूट करें — जैसे Ticker, Telegram या Pushover में।",
|
||||
"settings_notify_event_only": "केवल इवेंट ट्रिगर करें, स्वयं कुछ न भेजें",
|
||||
"settings_notify_event_only_hint": "आपका ऑटोमेशन पूरी डिलीवरी संभालता है। दोहराव अंतराल, शांत घंटे और दैनिक सीमा अभी भी तय करते हैं कि इवेंट कब ट्रिगर हो।",
|
||||
"settings_notify_extra_data": "अतिरिक्त सूचना डेटा (टेम्पलेट)",
|
||||
"settings_notify_extra_data_hint": "JSON या YAML, Jinja की अनुमति; notify कॉल के data में मिलाया जाता है (आपकी कुंजियाँ प्राथमिक)। वेरिएबल: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message। परीक्षण भेजें बटन से जाँचें।",
|
||||
"settings_notify_extra_data_hint": "JSON या YAML, Jinja की अनुमति; notify कॉल के data में मिलाया जाता है (आपकी कुंजियाँ प्राथमिक)। वेरिएबल: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message। परीक्षण भेजें बटन से जाँचें।",
|
||||
"card_saved_view": "सहेजा गया व्यू",
|
||||
"card_saved_view_none": "कोई नहीं",
|
||||
"card_saved_view_help": "ऊपर दिए फ़िल्टरों के अतिरिक्त व्यू के स्थिति, उपयोगकर्ता और लेबल फ़िल्टर लागू करता है। व्यू का क्रम और समूहन पैनल की प्रदर्शन सेटिंग हैं और कार्ड पर लागू नहीं होते।",
|
||||
"panel_card_tab": "इस टैब पर खोलें",
|
||||
"panel_card_tab_default": "याद रखा गया टैब (पैनल जैसा)",
|
||||
"panel_card_view": "सहेजे गए दृश्य के साथ खोलें",
|
||||
"panel_card_height": "ऊँचाई",
|
||||
"panel_card_height_help": "खाली = कार्ड के नीचे की स्क्रीन भरता है (पैनल दृश्य)। अन्यथा अन्य कार्डों वाले दृश्यों के लिए 600px या 80vh जैसी CSS लंबाई।",
|
||||
"panel_card_not_registered": "Maintenance Supporter पैनल उपलब्ध नहीं है — क्या इंटीग्रेशन सेट है?",
|
||||
"panel_card_load_failed": "Maintenance Supporter पैनल लोड नहीं हो सका — पेज फिर से लोड करें (अपडेट के बाद: ब्राउज़र कैश साफ़ करें)।",
|
||||
"doc_part_none": "इस पुर्ज़े से कोई दस्तावेज़ जुड़ा नहीं है।",
|
||||
"settings_templates_toggle_group": "इस समूह के सभी टेम्पलेट चालू या बंद करें",
|
||||
"setups_button": "सुझाए गए सेटअप",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "ठीक",
|
||||
"battery_fleet_status_due": "देय",
|
||||
"battery_fleet_predicted_on": "{date} के आसपास अपेक्षित",
|
||||
"battery_fleet_predicted_typical": "लगभग {date} अपेक्षित — {type} के लिए सामान्यतः {months} महीने ({source})",
|
||||
"lifetime_source_default": "अंतर्निहित डिफ़ॉल्ट",
|
||||
"lifetime_source_table": "अंतर्निहित तालिका",
|
||||
"lifetime_source_learned": "{n} बदलावों से सीखा",
|
||||
"lifetime_source_override": "आपकी सेटिंग",
|
||||
"lifetime_source_learned_device": "इस डिवाइस के {n} बदलावों से सीखा",
|
||||
"lifetime_source_learned_model": "इस मॉडल के डिवाइसों के {n} बदलावों से सीखा",
|
||||
"battery_fleet_predicted_trend": "इस बैटरी के डिस्चार्ज रुझान से अनुमानित: लगभग {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "रिचार्जेबल: बदलने के बजाय चार्ज करें — खरीदारी सूची में कभी नहीं",
|
||||
"battery_fleet_sort_name": "नाम के अनुसार क्रमबद्ध करें",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "प्रयुक्त पुर्जे",
|
||||
"print_inc_photos": "फ़ोटो",
|
||||
"print_inc_documents": "लिंक किए गए दस्तावेज़",
|
||||
"print_inc_doc_desc": "दस्तावेज़ विवरण",
|
||||
"print_inc_checklist": "चेकलिस्ट",
|
||||
"print_inc_notes": "नोट्स",
|
||||
"print_inc_costs": "लागत और अवधि",
|
||||
"print_inc_person": "पूर्ण किया",
|
||||
"print_inc_refs": "संदर्भ संख्याएँ",
|
||||
"print_inc_bare": "बिना विवरण के पूर्ण किए गए कार्य",
|
||||
"print_inc_qr": "प्रति कार्य QR कोड",
|
||||
"print_button": "प्रिंट",
|
||||
"report_scan_hint": "कार्य खोलने के लिए स्कैन करें",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Üzemidő",
|
||||
"runtime_hours": "Cél üzemidő (óra)",
|
||||
"target_value": "Célérték",
|
||||
"baseline": "Kiindulási érték",
|
||||
"target_changes": "Cél változásszám",
|
||||
"for_minutes": "Ennyi ideig (perc)",
|
||||
"time_based": "Időalapú",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Létrehozva",
|
||||
"report_times_done": "Elvégezve",
|
||||
"report_total_cost": "Összköltség",
|
||||
"report_every": "minden {n}. {unit}",
|
||||
"report_notes": "Megjegyzések",
|
||||
"report_col_type": "Típus",
|
||||
"report_col_status": "Állapot",
|
||||
"report_col_schedule": "Ütemezés",
|
||||
"duration": "Időtartam",
|
||||
"minutes_short": "perc",
|
||||
"both": "Mindkettő",
|
||||
"trigger_val": "Kiváltó érték",
|
||||
"complete_title": "Elvégzés: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: hibás típus (várt: {type})",
|
||||
"err_invalid_choice": "{field}: nem megengedett érték",
|
||||
"err_invalid_value": "{field}: érvénytelen érték",
|
||||
"ws_err_not_found": "Nem található — időközben törölhették",
|
||||
"ws_err_invalid_input": "Érvénytelen bevitel",
|
||||
"ws_err_invalid_date": "Érvénytelen dátum",
|
||||
"ws_err_invalid_format": "Érvénytelen formátum",
|
||||
"ws_err_invalid_url": "Érvénytelen hivatkozás",
|
||||
"ws_err_not_configured": "Még nincs beállítva",
|
||||
"ws_err_not_loaded": "Még nincs betöltve — próbálja újra egy pillanat múlva",
|
||||
"ws_err_limit_reached": "Elérte a korlátot",
|
||||
"ws_err_too_many": "Túl sok bejegyzés",
|
||||
"ws_err_too_large": "Túl nagy",
|
||||
"ws_err_not_archived": "Nincs archiválva",
|
||||
"ws_err_invalid_target": "Érvénytelen cél",
|
||||
"ws_err_create_failed": "Nem sikerült létrehozni",
|
||||
"ws_err_archived": "Archiválva — először állítsa vissza",
|
||||
"ws_err_already_archived": "Már archiválva",
|
||||
"ws_err_unavailable": "Jelenleg nem elérhető",
|
||||
"ws_err_unauthorized": "A felhasználója számára nem engedélyezett",
|
||||
"ws_err_too_many_views": "Túl sok mentett nézet",
|
||||
"ws_err_storage_unavailable": "A tároló nem elérhető",
|
||||
"ws_err_replace_failed": "A csere sikertelen",
|
||||
"ws_err_not_paused": "Nincs szüneteltetve",
|
||||
"ws_err_not_available": "Semmi sem elérhető",
|
||||
"ws_err_no_url": "Nincs megadva hivatkozás",
|
||||
"ws_err_no_phases": "Ennek a feladatnak nincsenek fázisai",
|
||||
"ws_err_invalid_view": "Érvénytelen nézet",
|
||||
"ws_err_invalid_user": "Ismeretlen felhasználó",
|
||||
"ws_err_invalid_range": "Érvénytelen tartomány",
|
||||
"ws_err_invalid_parent": "Érvénytelen szülőobjektum",
|
||||
"ws_err_invalid_device": "Ismeretlen eszköz",
|
||||
"ws_err_empty_csv": "A CSV-fájl üres",
|
||||
"ws_err_empty": "Nincs mit importálni",
|
||||
"ws_err_duplicate_failed": "Nem sikerült duplikálni",
|
||||
"ws_err_already_paused": "Már szünetel",
|
||||
"ws_err_invalid_cursor": "Érvénytelen fázispozíció",
|
||||
"ws_err_invalid_entity_slug": "Érvénytelen entitásnév",
|
||||
"ws_err_invalid_trigger_config": "Érvénytelen eseményindító-beállítás",
|
||||
"ws_err_no_defaults": "Nincs beállítva gyors befejezési alapérték",
|
||||
"ws_err_self_link_device": "Egy objektum nem kapcsolható önmagához",
|
||||
"ws_err_too_early": "Túl korai — ez a feladat csak a határidőhöz közelebb fejezhető be",
|
||||
"feat_schedule_time": "Napszakhoz kötött ütemezés",
|
||||
"feat_schedule_time_desc": "A feladatok nem éjfélkor, hanem egy megadott napszakban válnak lejárttá.",
|
||||
"schedule_time_toggle": "Adott időpontban esedékes",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Aktiválás ha egyenlő (=)",
|
||||
"trigger_not_equals": "Aktiválás ha eltér ettől (≠)",
|
||||
"for_at_least_minutes": "Legalább ennyi ideig (perc)",
|
||||
"safety_interval_days": "Biztonsági intervallum (nap, opcionális)",
|
||||
"safety_interval": "Biztonsági intervallum (opcionális)",
|
||||
"trigger_combinator": "Trigger és időköz kombinálása",
|
||||
"trigger_combinator_any": "Trigger vagy időköz (amelyik előbb)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Saját feladataim",
|
||||
"tab_calendar": "Naptár",
|
||||
"cal_no_events": "Nincs karbantartás",
|
||||
"cal_window_7": "7 nap",
|
||||
"cal_window_14": "14 nap",
|
||||
"cal_window_30": "30 nap",
|
||||
"cal_window_365": "1 év",
|
||||
"cal_every_n_days": "{n} naponta",
|
||||
"cal_source_time": "Időalapú",
|
||||
"cal_source_time_adaptive": "Időalapú (adaptív)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "A QR-kód generálása nem sikerült.",
|
||||
"qr_error_no_url": "Nincs HA URL beállítva. Adjon meg külső vagy belső URL-t a Beállítások → Rendszer → Hálózat alatt.",
|
||||
"save_error": "A mentés nem sikerült. Próbálja újra.",
|
||||
"subsave_warning": "Mentve, de egy beállítást elutasított a rendszer: {detail}",
|
||||
"qr_print": "Nyomtatás",
|
||||
"qr_download": "SVG letöltése",
|
||||
"qr_action": "Művelet beolvasáskor",
|
||||
"qr_action_view": "Karbantartási adatok megtekintése",
|
||||
"qr_action_complete": "Karbantartás elvégzettként jelölése",
|
||||
"qr_url_mode": "Hivatkozás típusa",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Helyi (mDNS)",
|
||||
"qr_mode_server": "Szerver URL",
|
||||
"overview": "Áttekintés",
|
||||
"analysis": "Elemzés",
|
||||
"recent_activities": "Legutóbbi tevékenységek",
|
||||
"search_notes": "Keresés a megjegyzésekben",
|
||||
"avg_cost": "Átl. költség",
|
||||
"no_advanced_features": "Nincsenek engedélyezett speciális funkciók",
|
||||
"no_advanced_features_hint": "Az elemzési adatok megjelenítéséhez engedélyezze az „Adaptív ütemezés” vagy a „Szezonális minták” funkciót az integráció beállításaiban.",
|
||||
"analysis_not_enough_data": "Még nincs elég adat az elemzéshez.",
|
||||
"analysis_not_enough_data_hint": "A Weibull-elemzéshez legalább 5 elvégzett karbantartás szükséges; a szezonális minták havi 6+ adatpont után válnak láthatóvá.",
|
||||
"analysis_manual_task_hint": "Az intervallum nélküli kézi feladatok nem termelnek elemzési adatot.",
|
||||
"completions": "elvégzés",
|
||||
"current": "Jelenlegi",
|
||||
"shorter": "Rövidebb",
|
||||
"longer": "Hosszabb",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Korábbi időszakok",
|
||||
"cal_forward_windows": "Jövőbeli időszakok",
|
||||
"history_edit_title": "Előzménybejegyzés szerkesztése",
|
||||
"history_delete_entry": "Bejegyzés törlése",
|
||||
"history_delete_confirm": "Törli ezt az előzménybejegyzést? Az utolsó elvégzés dátuma az előzőre áll vissza; a fotók az objektumnál maradnak, a felhasznált alkatrészek nem kerülnek vissza a készletbe.",
|
||||
"history_edit_timestamp": "Időbélyeg",
|
||||
"manufacturer": "Gyártó",
|
||||
"model": "Modell",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Alapértelmezett figyelmeztetési napok",
|
||||
"settings_consumable_threshold": "„Fogyóeszköz alacsony” küszöb (%)",
|
||||
"settings_battery_low_percent": "„Alacsony akkumulátor” küszöb (%)",
|
||||
"settings_battery_lifetimes": "Jellemző elem-élettartamok",
|
||||
"settings_battery_lifetimes_hint": "Csak százalék nélküli elemeknél használjuk: olyan Battery Notes-bejegyzésnél, amelynek csak típusa és cseredátuma van, vagy csak „alacsony”-t jelentő érzékelőnél. Esedékesség = utolsó csere + ez az élettartam. A szintet jelentő elemek előrejelzése a mért kisülésből jön. Ha a flotta már három cserét látott egy típusból, a medián időköz veszi át a beépített értéket — a saját értéked mindig nyer.",
|
||||
"settings_battery_lifetime_months": "hónap",
|
||||
"settings_battery_lifetime_reset": "Alapérték",
|
||||
"settings_battery_lifetime_in_fleet": "a flottádban",
|
||||
"settings_battery_lifetime_learned_models": "Modellenként tanulva: {list}",
|
||||
"settings_thresholds_hint": "Alapértékek az új Javasolt beállításokhoz (százalékos maradék) és a saját Battery Notes-küszöb nélküli flottaelemekhez. A meglévő feladatok megtartják az értéküket.",
|
||||
"settings_value_out_of_range": "Az értéknek {min} és {max} között kell lennie",
|
||||
"bn_summary": "{name}: {pct} % {n} elemhez",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} eszköz saját küszöbbel:",
|
||||
"bn_more": "+ még {n}",
|
||||
"settings_row_actions": "Műveletek a feladatsorokban",
|
||||
"settings_ref_numbers_in_lists": "Hivatkozási számok a listákban",
|
||||
"settings_ref_numbers_in_lists_hint": "Megjeleníti a #8 / #8.3 címkéket az objektum- és feladatnevek előtt a Ma listában, a feladattáblában, az objektumkártyákon és az objektumtáblában.",
|
||||
"row_actions_buttons_compact": "Gombok (telefonon csak ikonok)",
|
||||
"row_actions_buttons": "Gombok szöveggel",
|
||||
"row_actions_icons": "Ikonok (klasszikus)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Szundi időtartama (óra)",
|
||||
"settings_budget": "Költségkeret",
|
||||
"settings_currency": "Pénznem",
|
||||
"settings_currency_decimals": "Összegek tizedesjegyei",
|
||||
"settings_currency_decimals_hint": "Hány tizedesjegyet mutasson minden összeg: KPI-k, költségvetések, költségek a listákban és jelentésekben, költségvetési riasztások. 0 = egész számok.",
|
||||
"settings_budget_monthly": "Havi költségkeret",
|
||||
"settings_budget_yearly": "Éves költségkeret",
|
||||
"settings_budget_alerts": "Költségkeret-riasztások",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML exportálása",
|
||||
"settings_export_csv": "CSV exportálása",
|
||||
"settings_export_settings": "Beállítások exportálása (JSON)",
|
||||
"settings_import_csv": "CSV importálása",
|
||||
"settings_import_placeholder": "Illessze be ide a JSON vagy CSV tartalmat…",
|
||||
"settings_import_btn": "Importálás",
|
||||
"settings_import_success": "{count} objektum sikeresen importálva.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "Feladatonkénti HA művelet elvégzéskor + gyorselvégző QR előre beállított értékekkel.",
|
||||
"on_complete_action_title": "Elvégzéskor: HA művelet indítása (opcionális)",
|
||||
"on_complete_action_desc": "HA szolgáltatást hív meg a feladat elvégzésekor — pl. számláló nullázása az eszközön.",
|
||||
"on_complete_action_service": "Szolgáltatás",
|
||||
"on_complete_action_target": "Cél entitás",
|
||||
"on_complete_action_target_hint": "Megjegyzés: az entitás doménjének egyeznie kell a szolgáltatással — pl. a „button.press” csak button.* entitáson működik, a „counter.increment” csak counter.* entitáson, az „input_button.press” csak input_button.* entitáson stb. Eltérés esetén a művelet csendben meghiúsul (a HA naplóban: „Referenced entities ... missing or not currently available”).",
|
||||
"on_complete_action_data": "Adatok (JSON, opcionális)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "Fotók kiválasztása",
|
||||
"choose_photo": "Fotó kiválasztása",
|
||||
"photos_android_hint": "Az Android-alkalmazás választásonként egy fotót ad át — minden választás hozzáadódik.",
|
||||
"add_photos": "Fotók hozzáadása",
|
||||
"photos_limit": "Legfeljebb {max} fotó elvégzésenként",
|
||||
"history_edit_photos_hint": "Az itt eltávolított fotó megmarad az objektum dokumentumai között.",
|
||||
"uploading": "Feltöltés…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "Letöltés",
|
||||
"doc_close": "Bezárás",
|
||||
"doc_camera": "Fotó készítése",
|
||||
"camera_capture_shoot": "Felvétel",
|
||||
"camera_switch_lens": "Kamera váltása",
|
||||
"doc_drop_hint": "Húzza ide a fájlokat",
|
||||
"doc_task_none": "Ehhez a feladathoz nincs dokumentum csatolva.",
|
||||
"doc_link_existing": "Dokumentum hozzárendelése…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "Elvégzést rögzít (beállítja az utolsó elvégzést), amikor a kiváltás magától megszűnik — pl. só utántöltve, szűrő kicserélve.",
|
||||
"doc_search": "Dokumentumok keresése…",
|
||||
"doc_search_none": "Nincs egyező dokumentum",
|
||||
"doc_description": "Leírás (opcionális)",
|
||||
"doc_sort": "Dokumentumok rendezése",
|
||||
"doc_sort_newest": "Legújabb elöl",
|
||||
"doc_sort_oldest": "Legrégebbi elöl",
|
||||
"doc_sort_title": "Cím szerint (1, 2, 3 …)",
|
||||
"doc_sort_category": "Kategória szerint",
|
||||
"link_device_optional": "Meglévő eszközhöz kapcsolás (opcionális)",
|
||||
"parent_object_optional": "Szülőobjektum (opcionális)",
|
||||
"parent_none": "(Nincs szülő)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Értesítés csak erre a nézetre",
|
||||
"settings_notify_scope_all": "Minden feladat",
|
||||
"settings_notify_scope_hint": "Csak a kiválasztott mentett nézet címke-/felhasználószűrőinek megfelelő feladatok küldenek emlékeztetőt. A nézet állapot-, rendezési és csoportosítási beállításait itt figyelmen kívül hagyjuk.",
|
||||
"settings_notify_completed": "Elvégzési értesítések",
|
||||
"settings_notify_completed_hint": "Üzenet a háztartásnak, ha egy feladat elkészült, az okkal és a végrehajtóval. A „Csak automatikus” csak azokat jelenti, amelyeket senki nem végzett el a helyszínen: érzékelő helyreállása, kipipálás a bevásárlólistán, automatizálás szolgáltatáshívása. Csak a háztartási szolgáltatásra; a csendes órák és a napi korlát érvényes.",
|
||||
"notify_completed_off": "Ki",
|
||||
"notify_completed_automatic": "Csak automatikus elvégzések",
|
||||
"notify_completed_all": "Minden elvégzés",
|
||||
"settings_notify_rule": "Saját értesítési szabály",
|
||||
"settings_notify_rule_hint": "Minden értesítés kiváltja a maintenance_supporter_notification eseményt is (típus, állapot, tárgy, feladat, hivatkozási számok, prioritás, mélylink, adatok). Irányítsa automatizálással — pl. a Tickerbe, Telegramba vagy Pushoverbe.",
|
||||
"settings_notify_event_only": "Csak az esemény, saját küldés nélkül",
|
||||
"settings_notify_event_only_hint": "A kézbesítést teljes egészében az automatizálás végzi. Az ismétlési időközök, a csendes órák és a napi korlát továbbra is meghatározzák, mikor váltódik ki az esemény.",
|
||||
"settings_notify_extra_data": "További értesítési adatok (sablon)",
|
||||
"settings_notify_extra_data_hint": "JSON vagy YAML, Jinja engedélyezett; beolvad a notify hívás data mezőjébe (a saját kulcsai nyernek). Változók: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Ellenőrizze a Teszt küldése gombbal.",
|
||||
"settings_notify_extra_data_hint": "JSON vagy YAML, Jinja engedélyezett; beolvad a notify hívás data mezőjébe (a saját kulcsai nyernek). Változók: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Ellenőrizze a Teszt küldése gombbal.",
|
||||
"card_saved_view": "Mentett nézet",
|
||||
"card_saved_view_none": "Nincs",
|
||||
"card_saved_view_help": "A nézet állapot-, felhasználó- és címkeszűrőit a fenti szűrőkön felül alkalmazza. A nézet rendezése és csoportosítása a panel megjelenítési beállítása, a kártyán nem érvényesül.",
|
||||
"panel_card_tab": "Megnyitás ezen a lapon",
|
||||
"panel_card_tab_default": "Megjegyzett lap (mint a panelen)",
|
||||
"panel_card_view": "Megnyitás mentett nézettel",
|
||||
"panel_card_height": "Magasság",
|
||||
"panel_card_height_help": "Üres = kitölti a képernyőt a kártya alatt (panelnézet). Egyébként egy CSS-hossz, például 600px vagy 80vh, más kártyákat is tartalmazó nézetekhez.",
|
||||
"panel_card_not_registered": "A Maintenance Supporter panel nem érhető el — be van állítva az integráció?",
|
||||
"panel_card_load_failed": "A Maintenance Supporter panelt nem sikerült betölteni — töltse újra az oldalt (frissítés után: ürítse a böngésző gyorsítótárát).",
|
||||
"doc_part_none": "Ehhez az alkatrészhez nincs dokumentum csatolva.",
|
||||
"settings_templates_toggle_group": "A csoport összes sablonjának be- vagy kikapcsolása",
|
||||
"setups_button": "Javasolt beállítások",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Rendben",
|
||||
"battery_fleet_status_due": "Esedékes",
|
||||
"battery_fleet_predicted_on": "Várhatóan {date} körül",
|
||||
"battery_fleet_predicted_typical": "Várhatóan {date} körül — {type} esetén jellemzően {months} hónap ({source})",
|
||||
"lifetime_source_default": "beépített alapérték",
|
||||
"lifetime_source_table": "beépített táblázat",
|
||||
"lifetime_source_learned": "{n} cseréből tanulva",
|
||||
"lifetime_source_override": "saját beállításod",
|
||||
"lifetime_source_learned_device": "ennek az eszköznek {n} cseréjéből tanulva",
|
||||
"lifetime_source_learned_model": "ennek a modellnek {n} cseréjéből tanulva",
|
||||
"battery_fleet_predicted_trend": "Az elem merülési trendjéből előrejelezve: kb. {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Újratölthető: cserélés helyett töltse fel — soha nem kerül a bevásárlólistára",
|
||||
"battery_fleet_sort_name": "Rendezés név szerint",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Felhasznált alkatrészek",
|
||||
"print_inc_photos": "Fotók",
|
||||
"print_inc_documents": "Csatolt dokumentumok",
|
||||
"print_inc_doc_desc": "Dokumentumleírások",
|
||||
"print_inc_checklist": "Ellenőrzőlista",
|
||||
"print_inc_notes": "Jegyzetek",
|
||||
"print_inc_costs": "Költség és időtartam",
|
||||
"print_inc_person": "Elvégezte",
|
||||
"print_inc_refs": "Hivatkozási számok",
|
||||
"print_inc_bare": "Részletek nélküli elvégzések",
|
||||
"print_inc_qr": "QR-kód feladatonként",
|
||||
"print_button": "Nyomtatás",
|
||||
"report_scan_hint": "Olvassa be a feladat megnyitásához",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Tempo di funzionamento",
|
||||
"runtime_hours": "Durata obiettivo (ore)",
|
||||
"target_value": "Valore obiettivo",
|
||||
"baseline": "Linea di base",
|
||||
"target_changes": "Modifiche obiettivo",
|
||||
"for_minutes": "Per (minuti)",
|
||||
"time_based": "Temporale",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Generato",
|
||||
"report_times_done": "Fatto",
|
||||
"report_total_cost": "Costo totale",
|
||||
"report_every": "ogni {n} {unit}",
|
||||
"report_notes": "Note",
|
||||
"report_col_type": "Tipo",
|
||||
"report_col_status": "Stato",
|
||||
"report_col_schedule": "Pianificazione",
|
||||
"duration": "Durata",
|
||||
"minutes_short": "min",
|
||||
"both": "Entrambi",
|
||||
"trigger_val": "Valore trigger",
|
||||
"complete_title": "Completato: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: tipo errato (atteso: {type})",
|
||||
"err_invalid_choice": "{field}: valore non consentito",
|
||||
"err_invalid_value": "{field}: valore non valido",
|
||||
"ws_err_not_found": "Non trovato — potrebbe essere stato eliminato nel frattempo",
|
||||
"ws_err_invalid_input": "Input non valido",
|
||||
"ws_err_invalid_date": "Data non valida",
|
||||
"ws_err_invalid_format": "Formato non valido",
|
||||
"ws_err_invalid_url": "Link non valido",
|
||||
"ws_err_not_configured": "Non ancora configurato",
|
||||
"ws_err_not_loaded": "Non ancora caricato — riprova tra un istante",
|
||||
"ws_err_limit_reached": "Limite raggiunto",
|
||||
"ws_err_too_many": "Troppe voci",
|
||||
"ws_err_too_large": "Troppo grande",
|
||||
"ws_err_not_archived": "Non archiviato",
|
||||
"ws_err_invalid_target": "Destinazione non valida",
|
||||
"ws_err_create_failed": "Impossibile creare",
|
||||
"ws_err_archived": "Archiviato — ripristinalo prima",
|
||||
"ws_err_already_archived": "Già archiviato",
|
||||
"ws_err_unavailable": "Al momento non disponibile",
|
||||
"ws_err_unauthorized": "Non consentito al tuo utente",
|
||||
"ws_err_too_many_views": "Troppe viste salvate",
|
||||
"ws_err_storage_unavailable": "Archiviazione non disponibile",
|
||||
"ws_err_replace_failed": "Sostituzione non riuscita",
|
||||
"ws_err_not_paused": "Non in pausa",
|
||||
"ws_err_not_available": "Niente di disponibile",
|
||||
"ws_err_no_url": "Nessun link indicato",
|
||||
"ws_err_no_phases": "Questa attività non ha fasi",
|
||||
"ws_err_invalid_view": "Vista non valida",
|
||||
"ws_err_invalid_user": "Utente sconosciuto",
|
||||
"ws_err_invalid_range": "Intervallo non valido",
|
||||
"ws_err_invalid_parent": "Oggetto principale non valido",
|
||||
"ws_err_invalid_device": "Dispositivo sconosciuto",
|
||||
"ws_err_empty_csv": "Il file CSV è vuoto",
|
||||
"ws_err_empty": "Niente da importare",
|
||||
"ws_err_duplicate_failed": "Impossibile duplicare",
|
||||
"ws_err_already_paused": "Già in pausa",
|
||||
"ws_err_invalid_cursor": "Posizione della fase non valida",
|
||||
"ws_err_invalid_entity_slug": "Nome entità non valido",
|
||||
"ws_err_invalid_trigger_config": "Configurazione del trigger non valida",
|
||||
"ws_err_no_defaults": "Nessun valore predefinito per il completamento rapido",
|
||||
"ws_err_self_link_device": "Un oggetto non può essere collegato a sé stesso",
|
||||
"ws_err_too_early": "Troppo presto — questa attività può essere completata solo più vicino alla scadenza",
|
||||
"feat_schedule_time": "Pianificazione oraria",
|
||||
"feat_schedule_time_desc": "Le attività scadono a un'ora specifica anziché a mezzanotte.",
|
||||
"schedule_time_toggle": "Scadenza a un orario preciso",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Attiva quando uguale a (=)",
|
||||
"trigger_not_equals": "Attiva quando diverso da (≠)",
|
||||
"for_at_least_minutes": "Per almeno (minuti)",
|
||||
"safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)",
|
||||
"safety_interval": "Intervallo di sicurezza (opzionale)",
|
||||
"trigger_combinator": "Combina trigger e intervallo",
|
||||
"trigger_combinator_any": "Trigger o intervallo (il primo)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Le mie attività",
|
||||
"tab_calendar": "Calendario",
|
||||
"cal_no_events": "Nessuna manutenzione",
|
||||
"cal_window_7": "7 giorni",
|
||||
"cal_window_14": "14 giorni",
|
||||
"cal_window_30": "30 giorni",
|
||||
"cal_window_365": "1 anno",
|
||||
"cal_every_n_days": "ogni {n} giorni",
|
||||
"cal_source_time": "Basato sul tempo",
|
||||
"cal_source_time_adaptive": "Basato sul tempo (adattivo)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Impossibile generare il codice QR.",
|
||||
"qr_error_no_url": "Nessun URL HA configurato. Impostare un URL esterno o interno in Impostazioni → Sistema → Rete.",
|
||||
"save_error": "Salvataggio non riuscito. Riprovare.",
|
||||
"subsave_warning": "Salvato, ma un'impostazione è stata rifiutata: {detail}",
|
||||
"qr_print": "Stampa",
|
||||
"qr_download": "Scarica SVG",
|
||||
"qr_action": "Azione alla scansione",
|
||||
"qr_action_view": "Visualizza info manutenzione",
|
||||
"qr_action_complete": "Segna manutenzione come completata",
|
||||
"qr_url_mode": "Tipo di link",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Locale (mDNS)",
|
||||
"qr_mode_server": "URL server",
|
||||
"overview": "Panoramica",
|
||||
"analysis": "Analisi",
|
||||
"recent_activities": "Attività recenti",
|
||||
"search_notes": "Cerca nelle note",
|
||||
"avg_cost": "Ø Costo",
|
||||
"no_advanced_features": "Nessuna funzione avanzata attivata",
|
||||
"no_advanced_features_hint": "Attiva “Intervalli Adattivi” o “Modelli Stagionali” nelle impostazioni dell'integrazione per vedere i dati di analisi qui.",
|
||||
"analysis_not_enough_data": "Non ci sono ancora abbastanza dati per l'analisi.",
|
||||
"analysis_not_enough_data_hint": "L'analisi Weibull richiede almeno 5 manutenzioni completate; i modelli stagionali diventano visibili dopo 6+ punti dati al mese.",
|
||||
"analysis_manual_task_hint": "Le attività manuali senza intervallo non generano dati di analisi.",
|
||||
"completions": "completamenti",
|
||||
"current": "Attuale",
|
||||
"shorter": "Più breve",
|
||||
"longer": "Più lungo",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Finestre passate",
|
||||
"cal_forward_windows": "Finestre future",
|
||||
"history_edit_title": "Modifica voce dello storico",
|
||||
"history_delete_entry": "Elimina voce",
|
||||
"history_delete_confirm": "Eliminare questa voce della cronologia? La data dell'ultima esecuzione torna al completamento precedente; le foto restano con l'oggetto e le parti consumate non vengono rimesse a magazzino.",
|
||||
"history_edit_timestamp": "Data e ora",
|
||||
"manufacturer": "Produttore",
|
||||
"model": "Modello",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Giorni di avviso predefiniti",
|
||||
"settings_consumable_threshold": "Soglia „consumabile basso“ (%)",
|
||||
"settings_battery_low_percent": "Soglia „batteria scarica“ (%)",
|
||||
"settings_battery_lifetimes": "Durate tipiche delle batterie",
|
||||
"settings_battery_lifetimes_hint": "Usato solo per batterie senza percentuale: una nota Battery Notes con solo tipo e data di sostituzione, o un sensore che segnala solo « scarica ». La scadenza è ultima sostituzione + questa durata. Le batterie che riportano un livello ricavano la previsione dalla scarica misurata. Dopo tre sostituzioni dello stesso tipo, l'intervallo mediano sostituisce il valore integrato — il tuo valore vince sempre.",
|
||||
"settings_battery_lifetime_months": "mesi",
|
||||
"settings_battery_lifetime_reset": "Usa predefinito",
|
||||
"settings_battery_lifetime_in_fleet": "nel tuo parco",
|
||||
"settings_battery_lifetime_learned_models": "Appreso per modello: {list}",
|
||||
"settings_thresholds_hint": "Valori predefiniti per le nuove Configurazioni suggerite (percentuale residua) e per le batterie della flotta senza soglia Battery Notes propria. Le attività esistenti mantengono il loro valore.",
|
||||
"settings_value_out_of_range": "Il valore deve essere compreso tra {min} e {max}",
|
||||
"bn_summary": "{name}: {pct} % per {n} batterie",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} dispositivi con soglia propria:",
|
||||
"bn_more": "+ altri {n}",
|
||||
"settings_row_actions": "Azioni nelle righe delle attività",
|
||||
"settings_ref_numbers_in_lists": "Numeri di riferimento negli elenchi",
|
||||
"settings_ref_numbers_in_lists_hint": "Mostra le etichette #8 / #8.3 davanti ai nomi di oggetti e attività nell'elenco Oggi, nella tabella delle attività, nelle schede degli oggetti e nella tabella degli oggetti.",
|
||||
"row_actions_buttons_compact": "Pulsanti (solo icone sul telefono)",
|
||||
"row_actions_buttons": "Pulsanti con testo",
|
||||
"row_actions_icons": "Icone (classico)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Durata posticipo (ore)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Decimali degli importi",
|
||||
"settings_currency_decimals_hint": "Quanti decimali mostra ogni importo: KPI, budget, costi in elenchi e report, avvisi di budget. 0 = numeri interi.",
|
||||
"settings_budget_monthly": "Budget mensile",
|
||||
"settings_budget_yearly": "Budget annuale",
|
||||
"settings_budget_alerts": "Avvisi budget",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Esporta YAML",
|
||||
"settings_export_csv": "Esporta CSV",
|
||||
"settings_export_settings": "Esporta impostazioni (JSON)",
|
||||
"settings_import_csv": "Importa CSV",
|
||||
"settings_import_placeholder": "Incolla il contenuto JSON o CSV qui…",
|
||||
"settings_import_btn": "Importa",
|
||||
"settings_import_success": "{count} oggetti importati con successo.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Azione HA per attività al completamento + QR completamento rapido con valori predefiniti.",
|
||||
"on_complete_action_title": "Al completamento: attiva azione HA (opzionale)",
|
||||
"on_complete_action_desc": "Chiama un servizio HA quando l'attività viene completata — es. azzerare un contatore sul dispositivo.",
|
||||
"on_complete_action_service": "Servizio",
|
||||
"on_complete_action_target": "Entità target",
|
||||
"on_complete_action_data": "Dati (JSON, opzionale)",
|
||||
"on_complete_action_test": "Testa azione",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Scegli foto",
|
||||
"choose_photo": "Scegli foto",
|
||||
"photos_android_hint": "L'app Android passa una foto per selezione — ogni selezione viene aggiunta.",
|
||||
"add_photos": "Aggiungi foto",
|
||||
"photos_limit": "Fino a {max} foto per completamento",
|
||||
"history_edit_photos_hint": "Una foto rimossa qui resta nei documenti dell'oggetto.",
|
||||
"uploading": "Caricamento…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Scarica",
|
||||
"doc_close": "Chiudi",
|
||||
"doc_camera": "Scatta foto",
|
||||
"camera_capture_shoot": "Scatta",
|
||||
"camera_switch_lens": "Cambia fotocamera",
|
||||
"doc_drop_hint": "Trascina qui i file",
|
||||
"doc_task_none": "Nessun documento collegato a questa attività.",
|
||||
"doc_link_existing": "Collega un documento…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registra un completamento (imposta l'ultima esecuzione) quando il trigger si risolve da solo — ad es. sale ricaricato, filtro sostituito.",
|
||||
"doc_search": "Cerca documenti…",
|
||||
"doc_search_none": "Nessun documento corrispondente",
|
||||
"doc_description": "Descrizione (facoltativa)",
|
||||
"doc_sort": "Ordina documenti",
|
||||
"doc_sort_newest": "Più recenti prima",
|
||||
"doc_sort_oldest": "Meno recenti prima",
|
||||
"doc_sort_title": "Per titolo (1, 2, 3 …)",
|
||||
"doc_sort_category": "Per categoria",
|
||||
"link_device_optional": "Collega a dispositivo esistente (opzionale)",
|
||||
"parent_object_optional": "Oggetto principale (opzionale)",
|
||||
"parent_none": "(Nessun principale)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notifica solo per la vista",
|
||||
"settings_notify_scope_all": "Tutte le attività",
|
||||
"settings_notify_scope_hint": "Solo le attività che corrispondono ai filtri etichetta/utente della vista scelta inviano promemoria. Stato, ordinamento e raggruppamento della vista qui vengono ignorati.",
|
||||
"settings_notify_completed": "Notifiche di completamento",
|
||||
"settings_notify_completed_hint": "Una nota per la famiglia quando un'attività viene completata, con il motivo e chi l'ha fatto. \"Solo automatici\" annuncia solo i completamenti che nessuno ha fatto sul posto: ripristino del sensore, spunta nella lista della spesa, chiamata di servizio di un'automazione. Solo al servizio della famiglia; ore di silenzio e limite giornaliero si applicano.",
|
||||
"notify_completed_off": "Disattivato",
|
||||
"notify_completed_automatic": "Solo completamenti automatici",
|
||||
"notify_completed_all": "Tutti i completamenti",
|
||||
"settings_notify_rule": "La tua regola di notifica",
|
||||
"settings_notify_rule_hint": "Ogni notifica genera anche l'evento maintenance_supporter_notification (tipo, stato, oggetto, attività, numeri di riferimento, priorità, link, payload). Instradalo con un'automazione — ad es. verso Ticker, Telegram o Pushover.",
|
||||
"settings_notify_event_only": "Genera solo l'evento, non inviare nulla",
|
||||
"settings_notify_event_only_hint": "La tua automazione gestisce l'intera consegna. Intervalli di ripetizione, ore di silenzio e limite giornaliero valgono ancora per quando scatta l'evento.",
|
||||
"settings_notify_extra_data": "Dati di notifica aggiuntivi (template)",
|
||||
"settings_notify_extra_data_hint": "JSON o YAML, Jinja consentito; unito ai data della chiamata notify (le tue chiavi vincono). Variabili: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Prova con il pulsante Invia test.",
|
||||
"settings_notify_extra_data_hint": "JSON o YAML, Jinja consentito; unito ai data della chiamata notify (le tue chiavi vincono). Variabili: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Prova con il pulsante Invia test.",
|
||||
"card_saved_view": "Vista salvata",
|
||||
"card_saved_view_none": "Nessuna",
|
||||
"card_saved_view_help": "Applica i filtri stato, utente ed etichetta della vista in aggiunta ai filtri sopra. Ordinamento e raggruppamento della vista sono impostazioni di visualizzazione del pannello e non si applicano alla scheda.",
|
||||
"panel_card_tab": "Apri sulla scheda",
|
||||
"panel_card_tab_default": "Scheda memorizzata (come nel pannello)",
|
||||
"panel_card_view": "Apri con la vista salvata",
|
||||
"panel_card_height": "Altezza",
|
||||
"panel_card_height_help": "Vuoto = riempie lo schermo sotto la scheda (vista pannello). Altrimenti una lunghezza CSS come 600px o 80vh per viste con altre schede.",
|
||||
"panel_card_not_registered": "Il pannello Maintenance Supporter non è disponibile — l'integrazione è configurata?",
|
||||
"panel_card_load_failed": "Impossibile caricare il pannello Maintenance Supporter — ricarica la pagina (dopo un aggiornamento: svuota la cache del browser).",
|
||||
"doc_part_none": "Nessun documento collegato a questo ricambio.",
|
||||
"settings_templates_toggle_group": "Attiva o disattiva tutti i modelli di questo gruppo",
|
||||
"setups_button": "Configurazioni suggerite",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "A posto",
|
||||
"battery_fleet_status_due": "In scadenza",
|
||||
"battery_fleet_predicted_on": "Previsto intorno al {date}",
|
||||
"battery_fleet_predicted_typical": "Previsto intorno al {date} — {months} mesi tipici per {type} ({source})",
|
||||
"lifetime_source_default": "valore predefinito integrato",
|
||||
"lifetime_source_table": "tabella integrata",
|
||||
"lifetime_source_learned": "appreso da {n} sostituzioni",
|
||||
"lifetime_source_override": "la tua impostazione",
|
||||
"lifetime_source_learned_device": "appreso da {n} sostituzioni di questo dispositivo",
|
||||
"lifetime_source_learned_model": "appreso da {n} sostituzioni su dispositivi di questo modello",
|
||||
"battery_fleet_predicted_trend": "Previsto dalla tendenza di scarica di questa batteria: intorno al {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Ricaricabile: da ricaricare anziché sostituire — mai nella lista della spesa",
|
||||
"battery_fleet_sort_name": "Ordina per nome",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Ricambi usati",
|
||||
"print_inc_photos": "Foto",
|
||||
"print_inc_documents": "Documenti collegati",
|
||||
"print_inc_doc_desc": "Descrizioni dei documenti",
|
||||
"print_inc_checklist": "Lista di controllo",
|
||||
"print_inc_notes": "Note",
|
||||
"print_inc_costs": "Costo e durata",
|
||||
"print_inc_person": "Eseguito da",
|
||||
"print_inc_refs": "Numeri di riferimento",
|
||||
"print_inc_bare": "Completamenti senza dettagli",
|
||||
"print_inc_qr": "Codice QR per attività",
|
||||
"print_button": "Stampa",
|
||||
"report_scan_hint": "Scansiona per aprire l'attività",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "稼働時間",
|
||||
"runtime_hours": "目標稼働時間(時間)",
|
||||
"target_value": "目標値",
|
||||
"baseline": "基準値",
|
||||
"target_changes": "変化回数",
|
||||
"for_minutes": "継続(分)",
|
||||
"time_based": "時間ベース",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "作成日",
|
||||
"report_times_done": "実施回数",
|
||||
"report_total_cost": "合計費用",
|
||||
"report_every": "{n} {unit} ごと",
|
||||
"report_notes": "メモ",
|
||||
"report_col_type": "種類",
|
||||
"report_col_status": "状態",
|
||||
"report_col_schedule": "スケジュール",
|
||||
"duration": "所要時間",
|
||||
"minutes_short": "分",
|
||||
"both": "両方",
|
||||
"trigger_val": "トリガー値",
|
||||
"complete_title": "完了: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: 型が違います(期待値: {type})",
|
||||
"err_invalid_choice": "{field}: 許可されていない値です",
|
||||
"err_invalid_value": "{field}: 無効な値です",
|
||||
"ws_err_not_found": "見つかりません — その間に削除された可能性があります",
|
||||
"ws_err_invalid_input": "入力が無効です",
|
||||
"ws_err_invalid_date": "日付が無効です",
|
||||
"ws_err_invalid_format": "形式が無効です",
|
||||
"ws_err_invalid_url": "リンクが無効です",
|
||||
"ws_err_not_configured": "まだ設定されていません",
|
||||
"ws_err_not_loaded": "まだ読み込まれていません — しばらくしてからもう一度お試しください",
|
||||
"ws_err_limit_reached": "上限に達しました",
|
||||
"ws_err_too_many": "項目が多すぎます",
|
||||
"ws_err_too_large": "サイズが大きすぎます",
|
||||
"ws_err_not_archived": "アーカイブされていません",
|
||||
"ws_err_invalid_target": "対象が無効です",
|
||||
"ws_err_create_failed": "作成できませんでした",
|
||||
"ws_err_archived": "アーカイブ済み — 先に復元してください",
|
||||
"ws_err_already_archived": "すでにアーカイブ済みです",
|
||||
"ws_err_unavailable": "現在利用できません",
|
||||
"ws_err_unauthorized": "このユーザーには許可されていません",
|
||||
"ws_err_too_many_views": "保存済みビューが多すぎます",
|
||||
"ws_err_storage_unavailable": "ストレージが利用できません",
|
||||
"ws_err_replace_failed": "置き換えに失敗しました",
|
||||
"ws_err_not_paused": "一時停止されていません",
|
||||
"ws_err_not_available": "利用できるものがありません",
|
||||
"ws_err_no_url": "リンクが指定されていません",
|
||||
"ws_err_no_phases": "このタスクにはフェーズがありません",
|
||||
"ws_err_invalid_view": "ビューが無効です",
|
||||
"ws_err_invalid_user": "不明なユーザーです",
|
||||
"ws_err_invalid_range": "範囲が無効です",
|
||||
"ws_err_invalid_parent": "親オブジェクトが無効です",
|
||||
"ws_err_invalid_device": "不明なデバイスです",
|
||||
"ws_err_empty_csv": "CSV ファイルが空です",
|
||||
"ws_err_empty": "インポートするものがありません",
|
||||
"ws_err_duplicate_failed": "複製できませんでした",
|
||||
"ws_err_already_paused": "すでに一時停止中です",
|
||||
"ws_err_invalid_cursor": "フェーズの位置が無効です",
|
||||
"ws_err_invalid_entity_slug": "エンティティ名が無効です",
|
||||
"ws_err_invalid_trigger_config": "トリガー設定が無効です",
|
||||
"ws_err_no_defaults": "クイック完了の既定値が設定されていません",
|
||||
"ws_err_self_link_device": "オブジェクトを自分自身にリンクすることはできません",
|
||||
"ws_err_too_early": "早すぎます — このタスクは期日が近づいてからのみ完了できます",
|
||||
"feat_schedule_time": "時刻指定スケジュール",
|
||||
"feat_schedule_time_desc": "タスクが深夜0時ではなく指定した時刻に期限超過となります。",
|
||||
"schedule_time_toggle": "特定の時刻に期限",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "値が一致したらトリガー(=)",
|
||||
"trigger_not_equals": "値が異なればトリガー(≠)",
|
||||
"for_at_least_minutes": "最低継続時間(分)",
|
||||
"safety_interval_days": "安全間隔(日、任意)",
|
||||
"safety_interval": "安全間隔(任意)",
|
||||
"trigger_combinator": "トリガーと間隔の組み合わせ",
|
||||
"trigger_combinator_any": "トリガーまたは間隔(先に満たした方)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "自分のタスク",
|
||||
"tab_calendar": "カレンダー",
|
||||
"cal_no_events": "メンテナンスなし",
|
||||
"cal_window_7": "7日間",
|
||||
"cal_window_14": "14日間",
|
||||
"cal_window_30": "30日間",
|
||||
"cal_window_365": "1年間",
|
||||
"cal_every_n_days": "{n}日ごと",
|
||||
"cal_source_time": "時間ベース",
|
||||
"cal_source_time_adaptive": "時間ベース(適応)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QRコードの生成に失敗しました。",
|
||||
"qr_error_no_url": "HAのURLが設定されていません。設定 → システム → ネットワークで外部または内部URLを設定してください。",
|
||||
"save_error": "保存に失敗しました。もう一度お試しください。",
|
||||
"subsave_warning": "保存しましたが、1 つの設定が拒否されました: {detail}",
|
||||
"qr_print": "印刷",
|
||||
"qr_download": "SVGをダウンロード",
|
||||
"qr_action": "スキャン時の動作",
|
||||
"qr_action_view": "メンテナンス情報を表示",
|
||||
"qr_action_complete": "メンテナンスを完了にする",
|
||||
"qr_url_mode": "リンク種別",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "ローカル(mDNS)",
|
||||
"qr_mode_server": "サーバーURL",
|
||||
"overview": "概要",
|
||||
"analysis": "分析",
|
||||
"recent_activities": "最近のアクティビティ",
|
||||
"search_notes": "メモを検索",
|
||||
"avg_cost": "平均費用",
|
||||
"no_advanced_features": "高度な機能が有効になっていません",
|
||||
"no_advanced_features_hint": "統合設定で「適応間隔」または「季節パターン」を有効にすると、ここに分析データが表示されます。",
|
||||
"analysis_not_enough_data": "分析に必要なデータがまだ不足しています。",
|
||||
"analysis_not_enough_data_hint": "ワイブル分析には完了済みメンテナンスが最低5件必要です。季節パターンは月あたり6件以上のデータポイントで表示されるようになります。",
|
||||
"analysis_manual_task_hint": "間隔のない手動タスクは分析データを生成しません。",
|
||||
"completions": "完了回数",
|
||||
"current": "現在",
|
||||
"shorter": "短く",
|
||||
"longer": "長く",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "過去の期間",
|
||||
"cal_forward_windows": "今後の期間",
|
||||
"history_edit_title": "履歴項目を編集",
|
||||
"history_delete_entry": "記録を削除",
|
||||
"history_delete_confirm": "この履歴記録を削除しますか?最終実施日は前回の完了に戻ります。写真はオブジェクトに残り、消費した部品は在庫に戻りません。",
|
||||
"history_edit_timestamp": "日時",
|
||||
"manufacturer": "メーカー",
|
||||
"model": "型番",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "既定の警告日数",
|
||||
"settings_consumable_threshold": "消耗品「残量少」しきい値(%)",
|
||||
"settings_battery_low_percent": "電池「残量少」しきい値(%)",
|
||||
"settings_battery_lifetimes": "電池の標準寿命",
|
||||
"settings_battery_lifetimes_hint": "パーセント表示のない電池にのみ使います:種類と交換日だけの Battery Notes メモや「残量低下」しか報告しないセンサーです。期日は最終交換日+この寿命。残量を報告する電池は実測の放電から予測します。同じ種類の交換を3回記録すると、中央値の間隔が内蔵値に代わります。自分で設定した値が常に優先されます。",
|
||||
"settings_battery_lifetime_months": "か月",
|
||||
"settings_battery_lifetime_reset": "既定値に戻す",
|
||||
"settings_battery_lifetime_in_fleet": "使用中",
|
||||
"settings_battery_lifetime_learned_models": "型番ごとの学習値:{list}",
|
||||
"settings_thresholds_hint": "新しい「推奨セットアップ」(残量パーセント)と、独自の Battery Notes しきい値を持たないバッテリー一覧の電池に適用される既定値です。既存のタスクは値を保持します。",
|
||||
"settings_value_out_of_range": "値は {min} から {max} の間で指定してください",
|
||||
"bn_summary": "{name}:{n} 個の電池に {pct} %",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "独自のしきい値を持つデバイス {n} 台:",
|
||||
"bn_more": "+ 他 {n} 件",
|
||||
"settings_row_actions": "タスク行の操作",
|
||||
"settings_ref_numbers_in_lists": "一覧に参照番号を表示",
|
||||
"settings_ref_numbers_in_lists_hint": "「今日」の一覧、タスク表、オブジェクトカード、オブジェクト表で、オブジェクト名とタスク名の前に #8 / #8.3 のチップを表示します。",
|
||||
"row_actions_buttons_compact": "ボタン(スマートフォンではアイコンのみ)",
|
||||
"row_actions_buttons": "テキスト付きボタン",
|
||||
"row_actions_icons": "アイコン(従来)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "スヌーズ時間(時間)",
|
||||
"settings_budget": "予算",
|
||||
"settings_currency": "通貨",
|
||||
"settings_currency_decimals": "金額の小数点以下の桁数",
|
||||
"settings_currency_decimals_hint": "各金額に表示する小数点以下の桁数:KPI、予算、リストやレポートの費用、予算アラート。0 = 整数。",
|
||||
"settings_budget_monthly": "月間予算",
|
||||
"settings_budget_yearly": "年間予算",
|
||||
"settings_budget_alerts": "予算アラート",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAMLをエクスポート",
|
||||
"settings_export_csv": "CSVをエクスポート",
|
||||
"settings_export_settings": "設定をエクスポート(JSON)",
|
||||
"settings_import_csv": "CSVをインポート",
|
||||
"settings_import_placeholder": "JSONまたはCSVの内容をここに貼り付け…",
|
||||
"settings_import_btn": "インポート",
|
||||
"settings_import_success": "{count}件の対象を正常にインポートしました。",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "タスクごとの完了時HAアクション+事前設定値付きのクイック完了QR。",
|
||||
"on_complete_action_title": "完了時: HAアクションを実行(任意)",
|
||||
"on_complete_action_desc": "タスク完了時にHAサービスを呼び出します — 例: デバイスのカウンターをリセット。",
|
||||
"on_complete_action_service": "サービス",
|
||||
"on_complete_action_target": "対象エンティティ",
|
||||
"on_complete_action_target_hint": "注意: エンティティのドメインはサービスと一致する必要があります — 例: 'button.press'はbutton.*のみ、'counter.increment'はcounter.*のみ、'input_button.press'はinput_button.*のみで動作します。不一致の場合、アクションは何も表示せずに失敗します(HAのログに「Referenced entities ... missing or not currently available」と記録されます)。",
|
||||
"on_complete_action_data": "データ(JSON、任意)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "写真を選択",
|
||||
"choose_photo": "写真を選択",
|
||||
"photos_android_hint": "Android アプリは1回の選択につき写真1枚を渡します。選択するたびに追加されます。",
|
||||
"add_photos": "写真を追加",
|
||||
"photos_limit": "1回の完了につき最大{max}枚",
|
||||
"history_edit_photos_hint": "ここで外した写真は対象のドキュメントに残ります。",
|
||||
"uploading": "アップロード中…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "ダウンロード",
|
||||
"doc_close": "閉じる",
|
||||
"doc_camera": "写真を撮る",
|
||||
"camera_capture_shoot": "撮影",
|
||||
"camera_switch_lens": "カメラを切り替え",
|
||||
"doc_drop_hint": "ここにファイルをドロップ",
|
||||
"doc_task_none": "このタスクに紐付いたドキュメントはありません。",
|
||||
"doc_link_existing": "ドキュメントを紐付け…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "トリガーが自然に解消されたとき(塩の補充、フィルター交換など)に完了を記録し、最終実施日を更新します。",
|
||||
"doc_search": "ドキュメントを検索…",
|
||||
"doc_search_none": "一致するドキュメントはありません",
|
||||
"doc_description": "説明(任意)",
|
||||
"doc_sort": "文書を並べ替え",
|
||||
"doc_sort_newest": "新しい順",
|
||||
"doc_sort_oldest": "古い順",
|
||||
"doc_sort_title": "タイトル順(1、2、3…)",
|
||||
"doc_sort_category": "カテゴリー順",
|
||||
"link_device_optional": "既存のデバイスにリンク(任意)",
|
||||
"parent_object_optional": "親オブジェクト(任意)",
|
||||
"parent_none": "(親なし)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "このビューのみ通知",
|
||||
"settings_notify_scope_all": "すべてのタスク",
|
||||
"settings_notify_scope_hint": "選択した保存済みビューのラベル/ユーザーフィルターに一致するタスクのみがリマインダーを送信します。ビューのステータス・並び順・グループ化はここでは無視されます。",
|
||||
"settings_notify_completed": "完了通知",
|
||||
"settings_notify_completed_hint": "タスクが完了したときに、理由と実行者を添えて家族に知らせます。「自動のみ」は、その場で誰も操作していない完了だけを通知します:センサーの回復、買い物リストのチェック、オートメーションのサービス呼び出し。家庭用サービスにのみ送信され、静音時間と 1 日の上限が適用されます。",
|
||||
"notify_completed_off": "オフ",
|
||||
"notify_completed_automatic": "自動完了のみ",
|
||||
"notify_completed_all": "すべての完了",
|
||||
"settings_notify_rule": "独自の通知ルール",
|
||||
"settings_notify_rule_hint": "各通知はイベント maintenance_supporter_notification(種類、状態、対象、タスク、参照番号、優先度、ディープリンク、ペイロード)も発火します。オートメーションで Ticker、Telegram、Pushover などへ転送してください。",
|
||||
"settings_notify_event_only": "イベントのみ発火し、自身では送信しない",
|
||||
"settings_notify_event_only_hint": "配信はすべてオートメーション側で行います。繰り返し間隔、静音時間、1日の上限は引き続きイベント発火の条件になります。",
|
||||
"settings_notify_extra_data": "追加の通知データ(テンプレート)",
|
||||
"settings_notify_extra_data_hint": "JSON または YAML、Jinja 可。notify 呼び出しの data にマージされます(あなたのキーが優先)。変数: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message。「テスト送信」ボタンで確認できます。",
|
||||
"settings_notify_extra_data_hint": "JSON または YAML、Jinja 可。notify 呼び出しの data にマージされます(あなたのキーが優先)。変数: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message。「テスト送信」ボタンで確認できます。",
|
||||
"card_saved_view": "保存済みビュー",
|
||||
"card_saved_view_none": "なし",
|
||||
"card_saved_view_help": "上記のフィルターに加えて、ビューのステータス・ユーザー・ラベルのフィルターを適用します。ビューの並び順とグループ化はパネルの表示設定であり、カードには適用されません。",
|
||||
"panel_card_tab": "開くタブ",
|
||||
"panel_card_tab_default": "記憶したタブ(パネルと同じ)",
|
||||
"panel_card_view": "保存済みビューで開く",
|
||||
"panel_card_height": "高さ",
|
||||
"panel_card_height_help": "空欄 = カードの下の画面を埋めます(パネルビュー)。それ以外は、他のカードがあるビュー向けに 600px や 80vh などの CSS の長さを指定します。",
|
||||
"panel_card_not_registered": "Maintenance Supporter パネルを利用できません。統合は設定済みですか?",
|
||||
"panel_card_load_failed": "Maintenance Supporter パネルを読み込めませんでした。ページを再読み込みしてください(更新後はブラウザーのキャッシュを消去)。",
|
||||
"doc_part_none": "この部品にリンクされた文書はありません。",
|
||||
"settings_templates_toggle_group": "このグループのテンプレートをすべて有効/無効にする",
|
||||
"setups_button": "おすすめセットアップ",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "良好",
|
||||
"battery_fleet_status_due": "交換時期",
|
||||
"battery_fleet_predicted_on": "{date} 頃の見込み",
|
||||
"battery_fleet_predicted_typical": "{date} 頃の見込み — {type} の標準 {months} か月({source})",
|
||||
"lifetime_source_default": "内蔵の既定値",
|
||||
"lifetime_source_table": "内蔵テーブル",
|
||||
"lifetime_source_learned": "{n} 回の交換から学習",
|
||||
"lifetime_source_override": "あなたの設定",
|
||||
"lifetime_source_learned_device": "この機器の {n} 回の交換から学習",
|
||||
"lifetime_source_learned_model": "同じ型番の機器の {n} 回の交換から学習",
|
||||
"battery_fleet_predicted_trend": "この電池の放電傾向から予測: {date} 頃 ({confidence})",
|
||||
"battery_fleet_rechargeable": "充電式:交換ではなく充電——買い物リストには載りません",
|
||||
"battery_fleet_sort_name": "名前順に並べ替え",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "使用部品",
|
||||
"print_inc_photos": "写真",
|
||||
"print_inc_documents": "関連文書",
|
||||
"print_inc_doc_desc": "ドキュメントの説明",
|
||||
"print_inc_checklist": "チェックリスト",
|
||||
"print_inc_notes": "メモ",
|
||||
"print_inc_costs": "費用と所要時間",
|
||||
"print_inc_person": "実施者",
|
||||
"print_inc_refs": "参照番号",
|
||||
"print_inc_bare": "詳細のない完了記録",
|
||||
"print_inc_qr": "タスクごとのQRコード",
|
||||
"print_button": "印刷",
|
||||
"report_scan_hint": "スキャンしてタスクを開く",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "가동 시간",
|
||||
"runtime_hours": "목표 가동 시간 (시간)",
|
||||
"target_value": "목표값",
|
||||
"baseline": "기준값",
|
||||
"target_changes": "목표 변경 횟수",
|
||||
"for_minutes": "지속 시간 (분)",
|
||||
"time_based": "시간 기반",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "생성일",
|
||||
"report_times_done": "수행 횟수",
|
||||
"report_total_cost": "총 비용",
|
||||
"report_every": "{n} {unit}마다",
|
||||
"report_notes": "메모",
|
||||
"report_col_type": "유형",
|
||||
"report_col_status": "상태",
|
||||
"report_col_schedule": "일정",
|
||||
"duration": "소요 시간",
|
||||
"minutes_short": "분",
|
||||
"both": "둘 다",
|
||||
"trigger_val": "트리거 값",
|
||||
"complete_title": "완료: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: 잘못된 형식입니다 (예상: {type})",
|
||||
"err_invalid_choice": "{field}: 허용되지 않는 값입니다",
|
||||
"err_invalid_value": "{field}: 유효하지 않은 값입니다",
|
||||
"ws_err_not_found": "찾을 수 없음 — 그 사이에 삭제되었을 수 있습니다",
|
||||
"ws_err_invalid_input": "잘못된 입력",
|
||||
"ws_err_invalid_date": "잘못된 날짜",
|
||||
"ws_err_invalid_format": "잘못된 형식",
|
||||
"ws_err_invalid_url": "잘못된 링크",
|
||||
"ws_err_not_configured": "아직 설정되지 않음",
|
||||
"ws_err_not_loaded": "아직 로드되지 않음 — 잠시 후 다시 시도하세요",
|
||||
"ws_err_limit_reached": "한도에 도달함",
|
||||
"ws_err_too_many": "항목이 너무 많음",
|
||||
"ws_err_too_large": "너무 큼",
|
||||
"ws_err_not_archived": "보관되지 않음",
|
||||
"ws_err_invalid_target": "잘못된 대상",
|
||||
"ws_err_create_failed": "생성할 수 없음",
|
||||
"ws_err_archived": "보관됨 — 먼저 복원하세요",
|
||||
"ws_err_already_archived": "이미 보관됨",
|
||||
"ws_err_unavailable": "현재 사용할 수 없음",
|
||||
"ws_err_unauthorized": "이 사용자에게는 허용되지 않음",
|
||||
"ws_err_too_many_views": "저장된 보기가 너무 많음",
|
||||
"ws_err_storage_unavailable": "저장소를 사용할 수 없음",
|
||||
"ws_err_replace_failed": "교체 실패",
|
||||
"ws_err_not_paused": "일시 중지되지 않음",
|
||||
"ws_err_not_available": "사용 가능한 항목 없음",
|
||||
"ws_err_no_url": "링크가 지정되지 않음",
|
||||
"ws_err_no_phases": "이 작업에는 단계가 없음",
|
||||
"ws_err_invalid_view": "잘못된 보기",
|
||||
"ws_err_invalid_user": "알 수 없는 사용자",
|
||||
"ws_err_invalid_range": "잘못된 범위",
|
||||
"ws_err_invalid_parent": "잘못된 상위 객체",
|
||||
"ws_err_invalid_device": "알 수 없는 장치",
|
||||
"ws_err_empty_csv": "CSV 파일이 비어 있음",
|
||||
"ws_err_empty": "가져올 항목 없음",
|
||||
"ws_err_duplicate_failed": "복제할 수 없음",
|
||||
"ws_err_already_paused": "이미 일시 중지됨",
|
||||
"ws_err_invalid_cursor": "잘못된 단계 위치",
|
||||
"ws_err_invalid_entity_slug": "잘못된 엔터티 이름",
|
||||
"ws_err_invalid_trigger_config": "잘못된 트리거 구성",
|
||||
"ws_err_no_defaults": "빠른 완료 기본값이 설정되지 않음",
|
||||
"ws_err_self_link_device": "객체는 자기 자신에게 연결할 수 없음",
|
||||
"ws_err_too_early": "너무 이름 — 이 작업은 기한에 가까워져야 완료할 수 있습니다",
|
||||
"feat_schedule_time": "시각 지정 일정",
|
||||
"feat_schedule_time_desc": "작업이 자정이 아닌 지정한 시각에 기한 초과로 전환됩니다.",
|
||||
"schedule_time_toggle": "특정 시각에 기한",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "값이 같으면 트리거 (=)",
|
||||
"trigger_not_equals": "값이 다르면 트리거 (≠)",
|
||||
"for_at_least_minutes": "최소 지속 시간 (분)",
|
||||
"safety_interval_days": "안전 주기 (일, 선택)",
|
||||
"safety_interval": "안전 주기 (선택)",
|
||||
"trigger_combinator": "트리거와 간격 결합",
|
||||
"trigger_combinator_any": "트리거 또는 간격 (먼저 충족)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "내 작업",
|
||||
"tab_calendar": "캘린더",
|
||||
"cal_no_events": "유지보수 없음",
|
||||
"cal_window_7": "7일",
|
||||
"cal_window_14": "14일",
|
||||
"cal_window_30": "30일",
|
||||
"cal_window_365": "1년",
|
||||
"cal_every_n_days": "{n}일마다",
|
||||
"cal_source_time": "시간 기반",
|
||||
"cal_source_time_adaptive": "시간 기반 (적응형)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR 코드 생성에 실패했습니다.",
|
||||
"qr_error_no_url": "설정된 HA URL이 없습니다. 설정 → 시스템 → 네트워크에서 외부 또는 내부 URL을 설정해 주세요.",
|
||||
"save_error": "저장에 실패했습니다. 다시 시도해 주세요.",
|
||||
"subsave_warning": "저장되었지만 설정 하나가 거부되었습니다: {detail}",
|
||||
"qr_print": "인쇄",
|
||||
"qr_download": "SVG 다운로드",
|
||||
"qr_action": "스캔 시 동작",
|
||||
"qr_action_view": "유지보수 정보 보기",
|
||||
"qr_action_complete": "유지보수 완료로 표시",
|
||||
"qr_url_mode": "링크 유형",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "로컬 (mDNS)",
|
||||
"qr_mode_server": "서버 URL",
|
||||
"overview": "개요",
|
||||
"analysis": "분석",
|
||||
"recent_activities": "최근 활동",
|
||||
"search_notes": "메모 검색",
|
||||
"avg_cost": "평균 비용",
|
||||
"no_advanced_features": "활성화된 고급 기능 없음",
|
||||
"no_advanced_features_hint": "통합구성요소 설정에서 “적응형 일정” 또는 “계절 패턴”을 활성화하면 여기에서 분석 데이터를 볼 수 있습니다.",
|
||||
"analysis_not_enough_data": "아직 분석할 데이터가 충분하지 않습니다.",
|
||||
"analysis_not_enough_data_hint": "Weibull 분석에는 최소 5회의 완료 기록이 필요하며, 계절 패턴은 월별 6개 이상의 데이터 포인트가 쌓이면 표시됩니다.",
|
||||
"analysis_manual_task_hint": "주기가 없는 수동 작업은 분석 데이터를 생성하지 않습니다.",
|
||||
"completions": "회 완료",
|
||||
"current": "현재",
|
||||
"shorter": "더 짧게",
|
||||
"longer": "더 길게",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "이전 기간",
|
||||
"cal_forward_windows": "이후 기간",
|
||||
"history_edit_title": "기록 항목 편집",
|
||||
"history_delete_entry": "기록 삭제",
|
||||
"history_delete_confirm": "이 기록을 삭제할까요? 마지막 수행일은 이전 완료로 되돌아가며, 사진은 개체에 남고 사용한 부품은 재고로 복구되지 않습니다.",
|
||||
"history_edit_timestamp": "타임스탬프",
|
||||
"manufacturer": "제조사",
|
||||
"model": "모델",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "기본 경고 일수",
|
||||
"settings_consumable_threshold": "소모품 „부족“ 임계값 (%)",
|
||||
"settings_battery_low_percent": "배터리 „부족“ 임계값 (%)",
|
||||
"settings_battery_lifetimes": "배터리 일반 수명",
|
||||
"settings_battery_lifetimes_hint": "백분율이 없는 배터리에만 사용됩니다: 종류와 교체일만 있는 Battery Notes 메모나 \"낮음\"만 알리는 센서입니다. 만기 = 마지막 교체 + 이 수명. 잔량을 보고하는 배터리는 실측 방전으로 예측합니다. 같은 종류의 교체를 3회 기록하면 중앙값 간격이 내장값을 대체합니다. 직접 설정한 값이 항상 우선합니다.",
|
||||
"settings_battery_lifetime_months": "개월",
|
||||
"settings_battery_lifetime_reset": "기본값 사용",
|
||||
"settings_battery_lifetime_in_fleet": "내 장치에 사용 중",
|
||||
"settings_battery_lifetime_learned_models": "모델별 학습값: {list}",
|
||||
"settings_thresholds_hint": "새 추천 설정(남은 비율)과 자체 Battery Notes 임계값이 없는 배터리 목록 배터리의 기본값입니다. 기존 작업은 값을 유지합니다.",
|
||||
"settings_value_out_of_range": "값은 {min}에서 {max} 사이여야 합니다",
|
||||
"bn_summary": "{name}: 배터리 {n}개에 {pct} %",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "자체 임계값이 있는 기기 {n}개:",
|
||||
"bn_more": "+ {n}개 더",
|
||||
"settings_row_actions": "작업 행 동작",
|
||||
"settings_ref_numbers_in_lists": "목록에 참조 번호 표시",
|
||||
"settings_ref_numbers_in_lists_hint": "오늘 목록, 작업 표, 개체 카드, 개체 표에서 개체 및 작업 이름 앞에 #8 / #8.3 칩을 표시합니다.",
|
||||
"row_actions_buttons_compact": "버튼 (휴대폰에서는 아이콘만)",
|
||||
"row_actions_buttons": "텍스트가 있는 버튼",
|
||||
"row_actions_icons": "아이콘 (기본)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "다시 알림 대기 시간 (시간)",
|
||||
"settings_budget": "예산",
|
||||
"settings_currency": "통화",
|
||||
"settings_currency_decimals": "금액 소수 자릿수",
|
||||
"settings_currency_decimals_hint": "모든 금액에 표시할 소수 자릿수: KPI, 예산, 목록과 보고서의 비용, 예산 알림. 0 = 정수.",
|
||||
"settings_budget_monthly": "월 예산",
|
||||
"settings_budget_yearly": "연 예산",
|
||||
"settings_budget_alerts": "예산 경고",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML 내보내기",
|
||||
"settings_export_csv": "CSV 내보내기",
|
||||
"settings_export_settings": "설정 내보내기 (JSON)",
|
||||
"settings_import_csv": "CSV 가져오기",
|
||||
"settings_import_placeholder": "JSON 또는 CSV 내용을 여기에 붙여넣으세요…",
|
||||
"settings_import_btn": "가져오기",
|
||||
"settings_import_success": "객체 {count}개를 성공적으로 가져왔습니다.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "완료 시 작업별 HA 동작 실행 + 사전 설정값이 있는 빠른 완료 QR.",
|
||||
"on_complete_action_title": "완료 시: HA 동작 실행 (선택)",
|
||||
"on_complete_action_desc": "작업이 완료되면 HA 서비스를 호출합니다 — 예: 기기의 카운터 초기화.",
|
||||
"on_complete_action_service": "서비스",
|
||||
"on_complete_action_target": "대상 엔티티",
|
||||
"on_complete_action_target_hint": "참고: 엔티티 도메인이 서비스와 일치해야 합니다 — 예: 'button.press'는 button.*에서만, 'counter.increment'는 counter.*에서만, 'input_button.press'는 input_button.*에서만 동작합니다. 불일치 시 동작이 조용히 실패합니다 (HA 로그에 'Referenced entities ... missing or not currently available' 기록).",
|
||||
"on_complete_action_data": "데이터 (JSON, 선택)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "사진 선택",
|
||||
"choose_photo": "사진 선택",
|
||||
"photos_android_hint": "Android 앱은 선택할 때마다 사진 한 장씩 전달합니다. 선택할 때마다 추가됩니다.",
|
||||
"add_photos": "사진 추가",
|
||||
"photos_limit": "완료당 최대 {max}장",
|
||||
"history_edit_photos_hint": "여기서 제거한 사진은 대상의 문서에 그대로 남습니다.",
|
||||
"uploading": "업로드 중…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "다운로드",
|
||||
"doc_close": "닫기",
|
||||
"doc_camera": "사진 촬영",
|
||||
"camera_capture_shoot": "촬영",
|
||||
"camera_switch_lens": "카메라 전환",
|
||||
"doc_drop_hint": "파일을 여기에 놓으세요",
|
||||
"doc_task_none": "이 작업에 연결된 문서가 없습니다.",
|
||||
"doc_link_existing": "문서 연결…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "트리거가 스스로 해제되면 완료를 기록합니다 (마지막 수행일 갱신) — 예: 소금 보충, 필터 교체.",
|
||||
"doc_search": "문서 검색…",
|
||||
"doc_search_none": "일치하는 문서 없음",
|
||||
"doc_description": "설명(선택)",
|
||||
"doc_sort": "문서 정렬",
|
||||
"doc_sort_newest": "최신순",
|
||||
"doc_sort_oldest": "오래된순",
|
||||
"doc_sort_title": "제목순(1, 2, 3 …)",
|
||||
"doc_sort_category": "카테고리순",
|
||||
"link_device_optional": "기존 기기와 연결 (선택)",
|
||||
"parent_object_optional": "상위 객체 (선택)",
|
||||
"parent_none": "(상위 없음)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "특정 보기만 알림",
|
||||
"settings_notify_scope_all": "모든 작업",
|
||||
"settings_notify_scope_hint": "선택한 저장된 보기의 라벨/사용자 필터와 일치하는 작업만 알림을 보냅니다. 보기의 상태·정렬·그룹화는 여기서 무시됩니다.",
|
||||
"settings_notify_completed": "완료 알림",
|
||||
"settings_notify_completed_hint": "작업이 완료되면 사유와 실행자를 함께 가족에게 알립니다. \"자동만\"은 현장에서 아무도 하지 않은 완료만 알립니다: 센서 복구, 쇼핑 목록 체크, 자동화의 서비스 호출. 가정용 서비스로만 전송되며 방해 금지 시간과 일일 한도가 적용됩니다.",
|
||||
"notify_completed_off": "끄기",
|
||||
"notify_completed_automatic": "자동 완료만",
|
||||
"notify_completed_all": "모든 완료",
|
||||
"settings_notify_rule": "나만의 알림 규칙",
|
||||
"settings_notify_rule_hint": "모든 알림은 maintenance_supporter_notification 이벤트도 발생시킵니다(종류, 상태, 대상, 작업, 참조 번호, 우선순위, 딥 링크, 페이로드). 자동화로 라우팅하세요 — 예: Ticker, Telegram, Pushover.",
|
||||
"settings_notify_event_only": "이벤트만 발생시키고 직접 보내지 않음",
|
||||
"settings_notify_event_only_hint": "전달은 전적으로 자동화가 담당합니다. 반복 간격, 방해 금지 시간, 일일 한도는 여전히 이벤트 발생 시점에 적용됩니다.",
|
||||
"settings_notify_extra_data": "추가 알림 데이터(템플릿)",
|
||||
"settings_notify_extra_data_hint": "JSON 또는 YAML, Jinja 사용 가능. notify 호출의 data에 병합됩니다(사용자 키 우선). 변수: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. 테스트 보내기 버튼으로 확인하세요.",
|
||||
"settings_notify_extra_data_hint": "JSON 또는 YAML, Jinja 사용 가능. notify 호출의 data에 병합됩니다(사용자 키 우선). 변수: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. 테스트 보내기 버튼으로 확인하세요.",
|
||||
"card_saved_view": "저장된 보기",
|
||||
"card_saved_view_none": "없음",
|
||||
"card_saved_view_help": "위 필터에 더해 보기의 상태·사용자·라벨 필터를 적용합니다. 보기의 정렬과 그룹화는 패널 표시 설정이며 카드에는 적용되지 않습니다.",
|
||||
"panel_card_tab": "열 때 탭",
|
||||
"panel_card_tab_default": "기억된 탭(패널과 동일)",
|
||||
"panel_card_view": "저장된 보기로 열기",
|
||||
"panel_card_height": "높이",
|
||||
"panel_card_height_help": "비워 두면 카드 아래 화면을 채웁니다(패널 보기). 그 외에는 다른 카드가 있는 보기를 위해 600px 또는 80vh 같은 CSS 길이를 지정합니다.",
|
||||
"panel_card_not_registered": "Maintenance Supporter 패널을 사용할 수 없습니다. 통합이 설정되어 있나요?",
|
||||
"panel_card_load_failed": "Maintenance Supporter 패널을 불러오지 못했습니다. 페이지를 새로 고치세요(업데이트 후에는 브라우저 캐시를 지우세요).",
|
||||
"doc_part_none": "이 부품에 연결된 문서가 없습니다.",
|
||||
"settings_templates_toggle_group": "이 그룹의 모든 템플릿을 켜거나 끄기",
|
||||
"setups_button": "추천 설정",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "정상",
|
||||
"battery_fleet_status_due": "교체 시기",
|
||||
"battery_fleet_predicted_on": "{date}쯤 예상",
|
||||
"battery_fleet_predicted_typical": "{date}경 예상 — {type} 기준 보통 {months}개월({source})",
|
||||
"lifetime_source_default": "기본 내장값",
|
||||
"lifetime_source_table": "기본 내장 표",
|
||||
"lifetime_source_learned": "{n}회 교체에서 학습",
|
||||
"lifetime_source_override": "사용자 설정",
|
||||
"lifetime_source_learned_device": "이 기기의 {n}회 교체에서 학습",
|
||||
"lifetime_source_learned_model": "같은 모델 기기의 {n}회 교체에서 학습",
|
||||
"battery_fleet_predicted_trend": "이 배터리의 방전 추세로 예측: 약 {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "충전식: 교체 대신 충전 — 쇼핑 목록에 오르지 않습니다",
|
||||
"battery_fleet_sort_name": "이름순 정렬",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "사용 부품",
|
||||
"print_inc_photos": "사진",
|
||||
"print_inc_documents": "연결된 문서",
|
||||
"print_inc_doc_desc": "문서 설명",
|
||||
"print_inc_checklist": "체크리스트",
|
||||
"print_inc_notes": "메모",
|
||||
"print_inc_costs": "비용 및 소요 시간",
|
||||
"print_inc_person": "완료자",
|
||||
"print_inc_refs": "참조 번호",
|
||||
"print_inc_bare": "세부 정보 없는 완료 기록",
|
||||
"print_inc_qr": "작업별 QR 코드",
|
||||
"print_button": "인쇄",
|
||||
"report_scan_hint": "스캔하여 작업 열기",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Driftstid",
|
||||
"runtime_hours": "Måldriftstid (timer)",
|
||||
"target_value": "Målverdi",
|
||||
"baseline": "Utgangspunkt",
|
||||
"target_changes": "Antall endringer",
|
||||
"for_minutes": "I (minutter)",
|
||||
"time_based": "Tidsbasert",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Generert",
|
||||
"report_times_done": "Utført",
|
||||
"report_total_cost": "Totalkostnad",
|
||||
"report_every": "hver {n}. {unit}",
|
||||
"report_notes": "Notater",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Tidsplan",
|
||||
"duration": "Varighet",
|
||||
"minutes_short": "min",
|
||||
"both": "Begge",
|
||||
"trigger_val": "Utløserverdi",
|
||||
"complete_title": "Fullfør: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: feil type (forventet: {type})",
|
||||
"err_invalid_choice": "{field}: ikke en tillatt verdi",
|
||||
"err_invalid_value": "{field}: ugyldig verdi",
|
||||
"ws_err_not_found": "Ikke funnet — det kan ha blitt slettet i mellomtiden",
|
||||
"ws_err_invalid_input": "Ugyldig inndata",
|
||||
"ws_err_invalid_date": "Ugyldig dato",
|
||||
"ws_err_invalid_format": "Ugyldig format",
|
||||
"ws_err_invalid_url": "Ugyldig lenke",
|
||||
"ws_err_not_configured": "Ikke satt opp ennå",
|
||||
"ws_err_not_loaded": "Ikke lastet ennå — prøv igjen om et øyeblikk",
|
||||
"ws_err_limit_reached": "Grensen er nådd",
|
||||
"ws_err_too_many": "For mange oppføringer",
|
||||
"ws_err_too_large": "For stort",
|
||||
"ws_err_not_archived": "Ikke arkivert",
|
||||
"ws_err_invalid_target": "Ugyldig mål",
|
||||
"ws_err_create_failed": "Kunne ikke opprettes",
|
||||
"ws_err_archived": "Arkivert — gjenopprett først",
|
||||
"ws_err_already_archived": "Allerede arkivert",
|
||||
"ws_err_unavailable": "Ikke tilgjengelig for øyeblikket",
|
||||
"ws_err_unauthorized": "Ikke tillatt for din bruker",
|
||||
"ws_err_too_many_views": "For mange lagrede visninger",
|
||||
"ws_err_storage_unavailable": "Lagringen er ikke tilgjengelig",
|
||||
"ws_err_replace_failed": "Utskifting mislyktes",
|
||||
"ws_err_not_paused": "Ikke satt på pause",
|
||||
"ws_err_not_available": "Ingenting tilgjengelig",
|
||||
"ws_err_no_url": "Ingen lenke oppgitt",
|
||||
"ws_err_no_phases": "Oppgaven har ingen faser",
|
||||
"ws_err_invalid_view": "Ugyldig visning",
|
||||
"ws_err_invalid_user": "Ukjent bruker",
|
||||
"ws_err_invalid_range": "Ugyldig område",
|
||||
"ws_err_invalid_parent": "Ugyldig overordnet objekt",
|
||||
"ws_err_invalid_device": "Ukjent enhet",
|
||||
"ws_err_empty_csv": "CSV-filen er tom",
|
||||
"ws_err_empty": "Ingenting å importere",
|
||||
"ws_err_duplicate_failed": "Kunne ikke dupliseres",
|
||||
"ws_err_already_paused": "Allerede satt på pause",
|
||||
"ws_err_invalid_cursor": "Ugyldig faseposisjon",
|
||||
"ws_err_invalid_entity_slug": "Ugyldig entitetsnavn",
|
||||
"ws_err_invalid_trigger_config": "Ugyldig utløserkonfigurasjon",
|
||||
"ws_err_no_defaults": "Ingen standardverdier for hurtigfullføring",
|
||||
"ws_err_self_link_device": "Et objekt kan ikke kobles til seg selv",
|
||||
"ws_err_too_early": "For tidlig — oppgaven kan først fullføres nærmere forfallsdatoen",
|
||||
"feat_schedule_time": "Planlegging med klokkeslett",
|
||||
"feat_schedule_time_desc": "Oppgaver forfaller på et bestemt klokkeslett i stedet for ved midnatt.",
|
||||
"schedule_time_toggle": "Forfaller på et bestemt klokkeslett",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Utløs ved lik (=)",
|
||||
"trigger_not_equals": "Utløs ved forskjellig fra (≠)",
|
||||
"for_at_least_minutes": "I minst (minutter)",
|
||||
"safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)",
|
||||
"safety_interval": "Sikkerhetsintervall (valgfritt)",
|
||||
"trigger_combinator": "Kombiner utløser og intervall",
|
||||
"trigger_combinator_any": "Utløser eller intervall (først oppfylt)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Mine oppgaver",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Ingen vedlikehold",
|
||||
"cal_window_7": "7 dager",
|
||||
"cal_window_14": "14 dager",
|
||||
"cal_window_30": "30 dager",
|
||||
"cal_window_365": "1 år",
|
||||
"cal_every_n_days": "hver {n}. dag",
|
||||
"cal_source_time": "Tidsbasert",
|
||||
"cal_source_time_adaptive": "Tidsbasert (adaptiv)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "Kunne ikke generere QR-kode.",
|
||||
"qr_error_no_url": "Ingen HA-URL konfigurert. Angi en ekstern eller intern URL under Innstillinger → System → Nettverk.",
|
||||
"save_error": "Kunne ikke lagre. Prøv igjen.",
|
||||
"subsave_warning": "Lagret, men én innstilling ble avvist: {detail}",
|
||||
"qr_print": "Skriv ut",
|
||||
"qr_download": "Last ned SVG",
|
||||
"qr_action": "Handling ved skanning",
|
||||
"qr_action_view": "Vis vedlikeholdsinfo",
|
||||
"qr_action_complete": "Merk vedlikehold som fullført",
|
||||
"qr_url_mode": "Lenketype",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Lokal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Oversikt",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Nylige aktiviteter",
|
||||
"search_notes": "Søk i notater",
|
||||
"avg_cost": "Gj.snittlig kostnad",
|
||||
"no_advanced_features": "Ingen avanserte funksjoner aktivert",
|
||||
"no_advanced_features_hint": "Aktiver «Adaptive intervaller» eller «Sesongmønstre» i integrasjonsinnstillingene for å se analysedata her.",
|
||||
"analysis_not_enough_data": "Ikke nok data for analyse ennå.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analyse krever minst 5 fullførte vedlikehold; sesongmønstre blir synlige etter 6+ datapunkter per måned.",
|
||||
"analysis_manual_task_hint": "Manuelle oppgaver uten intervall genererer ikke analysedata.",
|
||||
"completions": "fullføringer",
|
||||
"current": "Nåværende",
|
||||
"shorter": "Kortere",
|
||||
"longer": "Lengre",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Tidligere vinduer",
|
||||
"cal_forward_windows": "Fremtidige vinduer",
|
||||
"history_edit_title": "Rediger historikkoppføring",
|
||||
"history_delete_entry": "Slett oppføring",
|
||||
"history_delete_confirm": "Slette denne historikkoppføringen? Datoen for siste utførelse faller tilbake til den forrige; bilder blir hos objektet, og forbrukte deler legges ikke tilbake på lager.",
|
||||
"history_edit_timestamp": "Tidsstempel",
|
||||
"manufacturer": "Produsent",
|
||||
"model": "Modell",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Standard varslingsdager",
|
||||
"settings_consumable_threshold": "Terskel „forbruksvare lav“ (%)",
|
||||
"settings_battery_low_percent": "Terskel „batteri lavt“ (%)",
|
||||
"settings_battery_lifetimes": "Typiske batterilevetider",
|
||||
"settings_battery_lifetimes_hint": "Brukes bare for batterier uten prosent: et Battery Notes-notat med bare type og byttedato, eller en sensor som bare melder «lavt». Forfall = siste bytte + denne levetiden. Batterier som melder nivå får prognosen fra målt utlading. Når flåten har sett tre bytter av en type, overtar medianintervallet fra den innebygde verdien — din egen verdi vinner alltid.",
|
||||
"settings_battery_lifetime_months": "måneder",
|
||||
"settings_battery_lifetime_reset": "Bruk standard",
|
||||
"settings_battery_lifetime_in_fleet": "i flåten din",
|
||||
"settings_battery_lifetime_learned_models": "Lært per modell: {list}",
|
||||
"settings_thresholds_hint": "Standardverdier for nye Foreslåtte oppsett (gjenværende prosent) og for flåtebatterier uten egen Battery Notes-terskel. Eksisterende oppgaver beholder verdien sin.",
|
||||
"settings_value_out_of_range": "Verdien må være mellom {min} og {max}",
|
||||
"bn_summary": "{name}: {pct} % for {n} batterier",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} enheter med egen terskel:",
|
||||
"bn_more": "+ {n} til",
|
||||
"settings_row_actions": "Handlinger i oppgaverader",
|
||||
"settings_ref_numbers_in_lists": "Referansenumre i lister",
|
||||
"settings_ref_numbers_in_lists_hint": "Viser brikkene #8 / #8.3 foran objekt- og oppgavenavn i I dag-listen, oppgavetabellen, objektkortene og objekttabellen.",
|
||||
"row_actions_buttons_compact": "Knapper (bare ikoner på mobil)",
|
||||
"row_actions_buttons": "Knapper med tekst",
|
||||
"row_actions_icons": "Ikoner (klassisk)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Slumrevarighet (timer)",
|
||||
"settings_budget": "Budsjett",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Desimaler for beløp",
|
||||
"settings_currency_decimals_hint": "Hvor mange desimaler hvert beløp viser: nøkkeltall, budsjetter, kostnader i lister og rapporter, budsjettvarsler. 0 = hele tall.",
|
||||
"settings_budget_monthly": "Månedsbudsjett",
|
||||
"settings_budget_yearly": "Årsbudsjett",
|
||||
"settings_budget_alerts": "Budsjettvarsler",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Eksporter YAML",
|
||||
"settings_export_csv": "Eksporter CSV",
|
||||
"settings_export_settings": "Eksporter innstillinger (JSON)",
|
||||
"settings_import_csv": "Importer CSV",
|
||||
"settings_import_placeholder": "Lim inn JSON- eller CSV-innhold her…",
|
||||
"settings_import_btn": "Importer",
|
||||
"settings_import_success": "{count} objekter importert.",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "HA-handling per oppgave ved fullføring + hurtigfullføring med QR og forhåndsinnstilte verdier.",
|
||||
"on_complete_action_title": "Ved fullføring: utløs HA-handling (valgfritt)",
|
||||
"on_complete_action_desc": "Kaller en HA-tjeneste når oppgaven fullføres — f.eks. tilbakestille en teller på enheten.",
|
||||
"on_complete_action_service": "Tjeneste",
|
||||
"on_complete_action_target": "Målentitet",
|
||||
"on_complete_action_target_hint": "Merk: entitetsdomenet må samsvare med tjenesten — f.eks. fungerer 'button.press' kun på button.*, 'counter.increment' kun på counter.*, 'input_button.press' kun på input_button.* osv. Ved manglende samsvar mislykkes handlingen i det stille (HA logger 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Data (JSON, valgfritt)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Velg bilder",
|
||||
"choose_photo": "Velg bilde",
|
||||
"photos_android_hint": "Android-appen leverer ett bilde per valg — hvert valg legges til.",
|
||||
"add_photos": "Legg til bilder",
|
||||
"photos_limit": "Inntil {max} bilder per fullføring",
|
||||
"history_edit_photos_hint": "Et bilde som fjernes her, blir liggende i objektets dokumenter.",
|
||||
"uploading": "Laster opp…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Last ned",
|
||||
"doc_close": "Lukk",
|
||||
"doc_camera": "Ta bilde",
|
||||
"camera_capture_shoot": "Ta bilde",
|
||||
"camera_switch_lens": "Bytt kamera",
|
||||
"doc_drop_hint": "Slipp filer her",
|
||||
"doc_task_none": "Ingen dokumenter knyttet til denne oppgaven.",
|
||||
"doc_link_existing": "Knytt et dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registrerer en fullføring (setter sist utført) når utløseren løser seg selv — f.eks. salt etterfylt, filter byttet.",
|
||||
"doc_search": "Søk i dokumenter…",
|
||||
"doc_search_none": "Ingen samsvarende dokumenter",
|
||||
"doc_description": "Beskrivelse (valgfritt)",
|
||||
"doc_sort": "Sorter dokumenter",
|
||||
"doc_sort_newest": "Nyeste først",
|
||||
"doc_sort_oldest": "Eldste først",
|
||||
"doc_sort_title": "Etter tittel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Etter kategori",
|
||||
"link_device_optional": "Koble til eksisterende enhet (valgfritt)",
|
||||
"parent_object_optional": "Overordnet objekt (valgfritt)",
|
||||
"parent_none": "(Ingen overordnet)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Varsle kun for visning",
|
||||
"settings_notify_scope_all": "Alle oppgaver",
|
||||
"settings_notify_scope_hint": "Bare oppgaver som matcher etikett-/brukerfiltrene til valgt visning sender påminnelser. Visningens status, sortering og gruppering ignoreres her.",
|
||||
"settings_notify_completed": "Varsler om fullføring",
|
||||
"settings_notify_completed_hint": "En beskjed til husstanden når en oppgave er fullført, med årsak og hvem som gjorde det. «Bare automatiske» melder bare fullføringer ingen gjorde på stedet: sensorgjenoppretting, avkrysning i handlelisten, et tjenestekall fra en automasjon. Bare til husstandstjenesten; stilletimer og dagsgrensen gjelder.",
|
||||
"notify_completed_off": "Av",
|
||||
"notify_completed_automatic": "Bare automatiske fullføringer",
|
||||
"notify_completed_all": "Alle fullføringer",
|
||||
"settings_notify_rule": "Din egen varslingsregel",
|
||||
"settings_notify_rule_hint": "Hvert varsel utløser også hendelsen maintenance_supporter_notification (type, status, objekt, oppgave, referansenumre, prioritet, lenke, nyttelast). Rut den med en automasjon — f.eks. til Ticker, Telegram eller Pushover.",
|
||||
"settings_notify_event_only": "Bare utløs hendelsen, send ingenting selv",
|
||||
"settings_notify_event_only_hint": "Automasjonen din tar hele leveransen. Gjentakelsesintervaller, stille timer og dagstaket avgjør fortsatt når hendelsen utløses.",
|
||||
"settings_notify_extra_data": "Ekstra varslingsdata (mal)",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillatt; flettes inn i notify-kallets data (dine nøkler vinner). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test med knappen Send test.",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillatt; flettes inn i notify-kallets data (dine nøkler vinner). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test med knappen Send test.",
|
||||
"card_saved_view": "Lagret visning",
|
||||
"card_saved_view_none": "Ingen",
|
||||
"card_saved_view_help": "Bruker visningens status-, bruker- og etikettfiltre i tillegg til filtrene over. Visningens sortering og gruppering er panelvisningsinnstillinger og brukes ikke på kortet.",
|
||||
"panel_card_tab": "Åpne på fanen",
|
||||
"panel_card_tab_default": "Husket fane (som i panelet)",
|
||||
"panel_card_view": "Åpne med lagret visning",
|
||||
"panel_card_height": "Høyde",
|
||||
"panel_card_height_help": "Tom = fyller skjermen under kortet (panelvisning). Ellers en CSS-lengde som 600px eller 80vh for visninger med andre kort.",
|
||||
"panel_card_not_registered": "Maintenance Supporter-panelet er ikke tilgjengelig — er integrasjonen satt opp?",
|
||||
"panel_card_load_failed": "Maintenance Supporter-panelet kunne ikke lastes — last siden på nytt (etter en oppdatering: tøm nettleserens hurtigbuffer).",
|
||||
"doc_part_none": "Ingen dokumenter knyttet til denne delen.",
|
||||
"settings_templates_toggle_group": "Slå alle maler i denne gruppen på eller av",
|
||||
"setups_button": "Foreslåtte oppsett",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "I orden",
|
||||
"battery_fleet_status_due": "Forfalt",
|
||||
"battery_fleet_predicted_on": "Forventes rundt {date}",
|
||||
"battery_fleet_predicted_typical": "Forventet rundt {date} — {months} måneder typisk for {type} ({source})",
|
||||
"lifetime_source_default": "innebygd standardverdi",
|
||||
"lifetime_source_table": "innebygd tabell",
|
||||
"lifetime_source_learned": "lært fra {n} bytter",
|
||||
"lifetime_source_override": "din innstilling",
|
||||
"lifetime_source_learned_device": "lært fra {n} bytter på denne enheten",
|
||||
"lifetime_source_learned_model": "lært fra {n} bytter på enheter av denne modellen",
|
||||
"battery_fleet_predicted_trend": "Forutsagt ut fra batteriets utladingstrend: rundt {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Oppladbart: lad i stedet for å bytte — aldri på handlelisten",
|
||||
"battery_fleet_sort_name": "Sorter etter navn",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Brukte deler",
|
||||
"print_inc_photos": "Bilder",
|
||||
"print_inc_documents": "Tilknyttede dokumenter",
|
||||
"print_inc_doc_desc": "Dokumentbeskrivelser",
|
||||
"print_inc_checklist": "Sjekkliste",
|
||||
"print_inc_notes": "Notater",
|
||||
"print_inc_costs": "Kostnad og varighet",
|
||||
"print_inc_person": "Utført av",
|
||||
"print_inc_refs": "Referansenumre",
|
||||
"print_inc_bare": "Utførelser uten detaljer",
|
||||
"print_inc_qr": "QR-kode per oppgave",
|
||||
"print_button": "Skriv ut",
|
||||
"report_scan_hint": "Skann for å åpne oppgaven",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Looptijd",
|
||||
"runtime_hours": "Doellooptijd (uren)",
|
||||
"target_value": "Doelwaarde",
|
||||
"baseline": "Basislijn",
|
||||
"target_changes": "Doelwijzigingen",
|
||||
"for_minutes": "Voor (minuten)",
|
||||
"time_based": "Tijdgebaseerd",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Gegenereerd",
|
||||
"report_times_done": "Gedaan",
|
||||
"report_total_cost": "Totale kosten",
|
||||
"report_every": "elke {n} {unit}",
|
||||
"report_notes": "Notities",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Schema",
|
||||
"duration": "Duur",
|
||||
"minutes_short": "min",
|
||||
"both": "Beide",
|
||||
"trigger_val": "Triggerwaarde",
|
||||
"complete_title": "Voltooid: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: verkeerd type (verwacht: {type})",
|
||||
"err_invalid_choice": "{field}: niet-toegestane waarde",
|
||||
"err_invalid_value": "{field}: ongeldige waarde",
|
||||
"ws_err_not_found": "Niet gevonden — het is misschien intussen verwijderd",
|
||||
"ws_err_invalid_input": "Ongeldige invoer",
|
||||
"ws_err_invalid_date": "Ongeldige datum",
|
||||
"ws_err_invalid_format": "Ongeldig formaat",
|
||||
"ws_err_invalid_url": "Ongeldige link",
|
||||
"ws_err_not_configured": "Nog niet ingesteld",
|
||||
"ws_err_not_loaded": "Nog niet geladen — probeer het zo opnieuw",
|
||||
"ws_err_limit_reached": "Limiet bereikt",
|
||||
"ws_err_too_many": "Te veel items",
|
||||
"ws_err_too_large": "Te groot",
|
||||
"ws_err_not_archived": "Niet gearchiveerd",
|
||||
"ws_err_invalid_target": "Ongeldig doel",
|
||||
"ws_err_create_failed": "Kon niet worden aangemaakt",
|
||||
"ws_err_archived": "Gearchiveerd — herstel het eerst",
|
||||
"ws_err_already_archived": "Al gearchiveerd",
|
||||
"ws_err_unavailable": "Momenteel niet beschikbaar",
|
||||
"ws_err_unauthorized": "Niet toegestaan voor jouw gebruiker",
|
||||
"ws_err_too_many_views": "Te veel opgeslagen weergaven",
|
||||
"ws_err_storage_unavailable": "Opslag is niet beschikbaar",
|
||||
"ws_err_replace_failed": "Vervangen mislukt",
|
||||
"ws_err_not_paused": "Niet gepauzeerd",
|
||||
"ws_err_not_available": "Niets beschikbaar",
|
||||
"ws_err_no_url": "Geen link opgegeven",
|
||||
"ws_err_no_phases": "Deze taak heeft geen fasen",
|
||||
"ws_err_invalid_view": "Ongeldige weergave",
|
||||
"ws_err_invalid_user": "Onbekende gebruiker",
|
||||
"ws_err_invalid_range": "Ongeldig bereik",
|
||||
"ws_err_invalid_parent": "Ongeldig bovenliggend object",
|
||||
"ws_err_invalid_device": "Onbekend apparaat",
|
||||
"ws_err_empty_csv": "Het CSV-bestand is leeg",
|
||||
"ws_err_empty": "Niets om te importeren",
|
||||
"ws_err_duplicate_failed": "Kon niet worden gedupliceerd",
|
||||
"ws_err_already_paused": "Al gepauzeerd",
|
||||
"ws_err_invalid_cursor": "Ongeldige fasepositie",
|
||||
"ws_err_invalid_entity_slug": "Ongeldige entiteitsnaam",
|
||||
"ws_err_invalid_trigger_config": "Ongeldige triggerconfiguratie",
|
||||
"ws_err_no_defaults": "Geen standaardwaarden voor snel afronden ingesteld",
|
||||
"ws_err_self_link_device": "Een object kan niet aan zichzelf worden gekoppeld",
|
||||
"ws_err_too_early": "Te vroeg — deze taak kan pas dichter bij de vervaldatum worden afgerond",
|
||||
"feat_schedule_time": "Tijd-van-dag-planning",
|
||||
"feat_schedule_time_desc": "Taken vervallen op een specifieke tijd in plaats van middernacht.",
|
||||
"schedule_time_toggle": "Vervalt op een vast tijdstip",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Activeren bij gelijk aan (=)",
|
||||
"trigger_not_equals": "Activeren bij afwijkend van (≠)",
|
||||
"for_at_least_minutes": "Voor minstens (minuten)",
|
||||
"safety_interval_days": "Veiligheidsinterval (dagen, optioneel)",
|
||||
"safety_interval": "Veiligheidsinterval (optioneel)",
|
||||
"trigger_combinator": "Trigger en interval combineren",
|
||||
"trigger_combinator_any": "Trigger of interval (eerst vervuld)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Mijn taken",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Geen onderhoud",
|
||||
"cal_window_7": "7 dagen",
|
||||
"cal_window_14": "14 dagen",
|
||||
"cal_window_30": "30 dagen",
|
||||
"cal_window_365": "1 jaar",
|
||||
"cal_every_n_days": "elke {n} dagen",
|
||||
"cal_source_time": "Tijd-gebaseerd",
|
||||
"cal_source_time_adaptive": "Tijd-gebaseerd (adaptief)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "QR-code kon niet worden gegenereerd.",
|
||||
"qr_error_no_url": "Geen HA-URL geconfigureerd. Stel een externe of interne URL in via Instellingen → Systeem → Netwerk.",
|
||||
"save_error": "Opslaan mislukt. Probeer het opnieuw.",
|
||||
"subsave_warning": "Opgeslagen, maar één instelling is geweigerd: {detail}",
|
||||
"qr_print": "Afdrukken",
|
||||
"qr_download": "SVG downloaden",
|
||||
"qr_action": "Actie bij scannen",
|
||||
"qr_action_view": "Onderhoudsinfo bekijken",
|
||||
"qr_action_complete": "Onderhoud als voltooid markeren",
|
||||
"qr_url_mode": "Linktype",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Lokaal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Overzicht",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Recente activiteiten",
|
||||
"search_notes": "Notities doorzoeken",
|
||||
"avg_cost": "Ø Kosten",
|
||||
"no_advanced_features": "Geen geavanceerde functies ingeschakeld",
|
||||
"no_advanced_features_hint": "Schakel „Adaptieve Intervallen” of „Seizoenpatronen” in via de integratie-instellingen om hier analysegegevens te zien.",
|
||||
"analysis_not_enough_data": "Nog niet genoeg gegevens voor analyse.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analyse vereist minstens 5 voltooide onderhoudsbeurten; seizoenspatronen worden zichtbaar na 6+ datapunten per maand.",
|
||||
"analysis_manual_task_hint": "Handmatige taken zonder interval genereren geen analysegegevens.",
|
||||
"completions": "voltooiingen",
|
||||
"current": "Huidig",
|
||||
"shorter": "Korter",
|
||||
"longer": "Langer",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Eerdere vensters",
|
||||
"cal_forward_windows": "Toekomstige vensters",
|
||||
"history_edit_title": "Geschiedenisitem bewerken",
|
||||
"history_delete_entry": "Vermelding verwijderen",
|
||||
"history_delete_confirm": "Deze geschiedenisvermelding verwijderen? De datum van laatste uitvoering valt terug op de vorige voltooiing; foto's blijven bij het object en verbruikte onderdelen worden niet teruggeboekt.",
|
||||
"history_edit_timestamp": "Tijdstempel",
|
||||
"manufacturer": "Fabrikant",
|
||||
"model": "Model",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Standaard waarschuwingsdagen",
|
||||
"settings_consumable_threshold": "Drempel „verbruiksartikel laag“ (%)",
|
||||
"settings_battery_low_percent": "Drempel „batterij laag“ (%)",
|
||||
"settings_battery_lifetimes": "Gebruikelijke batterijlevensduur",
|
||||
"settings_battery_lifetimes_hint": "Alleen gebruikt voor batterijen zonder percentage: een Battery Notes-notitie met enkel een type en een vervangdatum, of een sensor die alleen „bijna leeg“ meldt. Hun vervaldatum is laatste vervanging + deze levensduur. Batterijen die een niveau melden krijgen hun voorspelling uit de gemeten ontlading. Zodra de vloot drie vervangingen van een type heeft gezien, vervangt het mediane interval de ingebouwde waarde — je eigen waarde wint altijd.",
|
||||
"settings_battery_lifetime_months": "maanden",
|
||||
"settings_battery_lifetime_reset": "Standaard gebruiken",
|
||||
"settings_battery_lifetime_in_fleet": "in je vloot",
|
||||
"settings_battery_lifetime_learned_models": "Geleerd per model: {list}",
|
||||
"settings_thresholds_hint": "Standaardwaarden voor nieuwe Voorgestelde installaties (resterend percentage) en voor vlootbatterijen zonder eigen Battery Notes-drempel. Bestaande taken behouden hun waarde.",
|
||||
"settings_value_out_of_range": "De waarde moet tussen {min} en {max} liggen",
|
||||
"bn_summary": "{name}: {pct} % voor {n} batterijen",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} apparaten met een eigen drempel:",
|
||||
"bn_more": "+ {n} meer",
|
||||
"settings_row_actions": "Acties in taakrijen",
|
||||
"settings_ref_numbers_in_lists": "Referentienummers in lijsten",
|
||||
"settings_ref_numbers_in_lists_hint": "Toont de chips #8 / #8.3 vóór object- en taaknamen in de lijst Vandaag, de takentabel, de objectkaarten en de objectentabel.",
|
||||
"row_actions_buttons_compact": "Knoppen (op telefoons alleen pictogrammen)",
|
||||
"row_actions_buttons": "Knoppen met tekst",
|
||||
"row_actions_icons": "Pictogrammen (klassiek)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Snoozeduur (uren)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Decimalen voor bedragen",
|
||||
"settings_currency_decimals_hint": "Hoeveel decimalen elk bedrag toont: KPI's, budgetten, kosten in lijsten en rapporten, budgetmeldingen. 0 = hele getallen.",
|
||||
"settings_budget_monthly": "Maandbudget",
|
||||
"settings_budget_yearly": "Jaarbudget",
|
||||
"settings_budget_alerts": "Budgetwaarschuwingen",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "YAML exporteren",
|
||||
"settings_export_csv": "CSV exporteren",
|
||||
"settings_export_settings": "Instellingen exporteren (JSON)",
|
||||
"settings_import_csv": "CSV importeren",
|
||||
"settings_import_placeholder": "Plak JSON- of CSV-inhoud hier…",
|
||||
"settings_import_btn": "Importeren",
|
||||
"settings_import_success": "{count} objecten succesvol geïmporteerd.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Per taak HA-actie bij voltooien + snel-voltooien-QR met vooraf ingestelde waarden.",
|
||||
"on_complete_action_title": "Bij voltooien: HA-actie uitvoeren (optioneel)",
|
||||
"on_complete_action_desc": "Roept een HA-service aan wanneer de taak is voltooid — bv. een teller op het apparaat resetten.",
|
||||
"on_complete_action_service": "Service",
|
||||
"on_complete_action_target": "Doel-entiteit",
|
||||
"on_complete_action_data": "Data (JSON, optioneel)",
|
||||
"on_complete_action_test": "Actie testen",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Foto's kiezen",
|
||||
"choose_photo": "Foto kiezen",
|
||||
"photos_android_hint": "De Android-app geeft één foto per keuze door — elke keuze wordt toegevoegd.",
|
||||
"add_photos": "Foto's toevoegen",
|
||||
"photos_limit": "Maximaal {max} foto's per afronding",
|
||||
"history_edit_photos_hint": "Een hier verwijderde foto blijft bewaard in de documenten van het object.",
|
||||
"uploading": "Uploaden…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Downloaden",
|
||||
"doc_close": "Sluiten",
|
||||
"doc_camera": "Foto maken",
|
||||
"camera_capture_shoot": "Vastleggen",
|
||||
"camera_switch_lens": "Camera wisselen",
|
||||
"doc_drop_hint": "Sleep bestanden hierheen",
|
||||
"doc_task_none": "Geen documenten aan deze taak gekoppeld.",
|
||||
"doc_link_existing": "Document koppelen…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registreert een voltooiing (stelt laatst uitgevoerd in) wanneer de trigger zichzelf oplost — bijv. zout bijgevuld, filter vervangen.",
|
||||
"doc_search": "Documenten zoeken…",
|
||||
"doc_search_none": "Geen overeenkomende documenten",
|
||||
"doc_description": "Beschrijving (optioneel)",
|
||||
"doc_sort": "Documenten sorteren",
|
||||
"doc_sort_newest": "Nieuwste eerst",
|
||||
"doc_sort_oldest": "Oudste eerst",
|
||||
"doc_sort_title": "Op titel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Op categorie",
|
||||
"link_device_optional": "Koppelen aan bestaand apparaat (optioneel)",
|
||||
"parent_object_optional": "Bovenliggend object (optioneel)",
|
||||
"parent_none": "(Geen bovenliggend object)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Alleen melden voor weergave",
|
||||
"settings_notify_scope_all": "Alle taken",
|
||||
"settings_notify_scope_hint": "Alleen taken die overeenkomen met de label-/gebruikersfilters van de gekozen weergave sturen herinneringen. Status, sortering en groepering van de weergave worden hier genegeerd.",
|
||||
"settings_notify_completed": "Voltooiingsmeldingen",
|
||||
"settings_notify_completed_hint": "Huishoudnieuws wanneer een taak is voltooid, met de reden en wie het deed. \"Alleen automatisch\" meldt alleen voltooiingen die niemand ter plekke deed: sensorherstel, afvinken op de boodschappenlijst, een service-aanroep van een automatisering. Alleen naar de huishouddienst; stille uren en het daglimiet gelden.",
|
||||
"notify_completed_off": "Uit",
|
||||
"notify_completed_automatic": "Alleen automatische voltooiingen",
|
||||
"notify_completed_all": "Alle voltooiingen",
|
||||
"settings_notify_rule": "Eigen meldingsregel",
|
||||
"settings_notify_rule_hint": "Elke melding vuurt ook het event maintenance_supporter_notification af (soort, status, object, taak, referentienummers, prioriteit, deep link, payload). Stuur het door met een automatisering — bijv. naar Ticker, Telegram of Pushover.",
|
||||
"settings_notify_event_only": "Alleen het event afvuren, zelf niets verzenden",
|
||||
"settings_notify_event_only_hint": "Je automatisering verzorgt de volledige bezorging. Herhaalintervallen, stille uren en het daglimiet bepalen nog steeds wanneer het event afgaat.",
|
||||
"settings_notify_extra_data": "Extra meldingsgegevens (sjabloon)",
|
||||
"settings_notify_extra_data_hint": "JSON of YAML, Jinja toegestaan; wordt samengevoegd met de data van de notify-aanroep (jouw sleutels winnen). Variabelen: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test met de knop Test verzenden.",
|
||||
"settings_notify_extra_data_hint": "JSON of YAML, Jinja toegestaan; wordt samengevoegd met de data van de notify-aanroep (jouw sleutels winnen). Variabelen: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test met de knop Test verzenden.",
|
||||
"card_saved_view": "Opgeslagen weergave",
|
||||
"card_saved_view_none": "Geen",
|
||||
"card_saved_view_help": "Past de status-, gebruikers- en labelfilters van de weergave toe bovenop de bovenstaande filters. Sortering en groepering van de weergave zijn paneelweergave-instellingen en gelden niet voor de kaart.",
|
||||
"panel_card_tab": "Openen op tabblad",
|
||||
"panel_card_tab_default": "Onthouden tabblad (zoals in het paneel)",
|
||||
"panel_card_view": "Openen met opgeslagen weergave",
|
||||
"panel_card_height": "Hoogte",
|
||||
"panel_card_height_help": "Leeg = vult het scherm onder de kaart (paneelweergave). Anders een CSS-lengte zoals 600px of 80vh voor weergaven met andere kaarten.",
|
||||
"panel_card_not_registered": "Het Maintenance Supporter-paneel is niet beschikbaar — is de integratie ingesteld?",
|
||||
"panel_card_load_failed": "Het Maintenance Supporter-paneel kon niet worden geladen — herlaad de pagina (na een update: browsercache wissen).",
|
||||
"doc_part_none": "Geen documenten gekoppeld aan dit onderdeel.",
|
||||
"settings_templates_toggle_group": "Alle sjablonen in deze groep in- of uitschakelen",
|
||||
"setups_button": "Voorgestelde setups",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "In orde",
|
||||
"battery_fleet_status_due": "Aan de beurt",
|
||||
"battery_fleet_predicted_on": "Verwacht rond {date}",
|
||||
"battery_fleet_predicted_typical": "Verwacht rond {date} — {months} maanden gebruikelijk voor {type} ({source})",
|
||||
"lifetime_source_default": "ingebouwde standaard",
|
||||
"lifetime_source_table": "ingebouwde tabel",
|
||||
"lifetime_source_learned": "geleerd uit {n} vervangingen",
|
||||
"lifetime_source_override": "jouw instelling",
|
||||
"lifetime_source_learned_device": "geleerd uit {n} vervangingen van dit apparaat",
|
||||
"lifetime_source_learned_model": "geleerd uit {n} vervangingen bij apparaten van dit model",
|
||||
"battery_fleet_predicted_trend": "Voorspeld uit de ontladingstrend van deze batterij: rond {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Oplaadbaar: opladen in plaats van vervangen — nooit op het boodschappenlijstje",
|
||||
"battery_fleet_sort_name": "Sorteren op naam",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Gebruikte onderdelen",
|
||||
"print_inc_photos": "Foto's",
|
||||
"print_inc_documents": "Gekoppelde documenten",
|
||||
"print_inc_doc_desc": "Documentbeschrijvingen",
|
||||
"print_inc_checklist": "Controlelijst",
|
||||
"print_inc_notes": "Notities",
|
||||
"print_inc_costs": "Kosten en duur",
|
||||
"print_inc_person": "Uitgevoerd door",
|
||||
"print_inc_refs": "Referentienummers",
|
||||
"print_inc_bare": "Voltooiingen zonder details",
|
||||
"print_inc_qr": "QR-code per taak",
|
||||
"print_button": "Afdrukken",
|
||||
"report_scan_hint": "Scan om de taak te openen",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Czas pracy",
|
||||
"runtime_hours": "Docelowy czas pracy (godziny)",
|
||||
"target_value": "Wartość docelowa",
|
||||
"baseline": "Wartość bazowa",
|
||||
"target_changes": "Docelowa liczba zmian",
|
||||
"for_minutes": "Przez (minuty)",
|
||||
"time_based": "Czasowy",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Wygenerowano",
|
||||
"report_times_done": "Wykonano",
|
||||
"report_total_cost": "Koszt całkowity",
|
||||
"report_every": "co {n} {unit}",
|
||||
"report_notes": "Notatki",
|
||||
"report_col_type": "Typ",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Harmonogram",
|
||||
"duration": "Czas trwania",
|
||||
"minutes_short": "min",
|
||||
"both": "Oba",
|
||||
"trigger_val": "Wartość wyzwalacza",
|
||||
"complete_title": "Wykonaj: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: zły typ (oczekiwano: {type})",
|
||||
"err_invalid_choice": "{field}: niedozwolona wartość",
|
||||
"err_invalid_value": "{field}: nieprawidłowa wartość",
|
||||
"ws_err_not_found": "Nie znaleziono — być może zostało w międzyczasie usunięte",
|
||||
"ws_err_invalid_input": "Nieprawidłowe dane",
|
||||
"ws_err_invalid_date": "Nieprawidłowa data",
|
||||
"ws_err_invalid_format": "Nieprawidłowy format",
|
||||
"ws_err_invalid_url": "Nieprawidłowy link",
|
||||
"ws_err_not_configured": "Jeszcze nie skonfigurowano",
|
||||
"ws_err_not_loaded": "Jeszcze nie załadowano — spróbuj ponownie za chwilę",
|
||||
"ws_err_limit_reached": "Osiągnięto limit",
|
||||
"ws_err_too_many": "Zbyt wiele wpisów",
|
||||
"ws_err_too_large": "Za duży rozmiar",
|
||||
"ws_err_not_archived": "Nie zarchiwizowano",
|
||||
"ws_err_invalid_target": "Nieprawidłowy cel",
|
||||
"ws_err_create_failed": "Nie udało się utworzyć",
|
||||
"ws_err_archived": "Zarchiwizowano — najpierw przywróć",
|
||||
"ws_err_already_archived": "Już zarchiwizowano",
|
||||
"ws_err_unavailable": "Obecnie niedostępne",
|
||||
"ws_err_unauthorized": "Niedozwolone dla Twojego użytkownika",
|
||||
"ws_err_too_many_views": "Zbyt wiele zapisanych widoków",
|
||||
"ws_err_storage_unavailable": "Magazyn danych jest niedostępny",
|
||||
"ws_err_replace_failed": "Zastąpienie nie powiodło się",
|
||||
"ws_err_not_paused": "Nie wstrzymano",
|
||||
"ws_err_not_available": "Brak dostępnych",
|
||||
"ws_err_no_url": "Nie podano linku",
|
||||
"ws_err_no_phases": "To zadanie nie ma faz",
|
||||
"ws_err_invalid_view": "Nieprawidłowy widok",
|
||||
"ws_err_invalid_user": "Nieznany użytkownik",
|
||||
"ws_err_invalid_range": "Nieprawidłowy zakres",
|
||||
"ws_err_invalid_parent": "Nieprawidłowy obiekt nadrzędny",
|
||||
"ws_err_invalid_device": "Nieznane urządzenie",
|
||||
"ws_err_empty_csv": "Plik CSV jest pusty",
|
||||
"ws_err_empty": "Nie ma nic do zaimportowania",
|
||||
"ws_err_duplicate_failed": "Nie udało się zduplikować",
|
||||
"ws_err_already_paused": "Już wstrzymano",
|
||||
"ws_err_invalid_cursor": "Nieprawidłowa pozycja fazy",
|
||||
"ws_err_invalid_entity_slug": "Nieprawidłowa nazwa encji",
|
||||
"ws_err_invalid_trigger_config": "Nieprawidłowa konfiguracja wyzwalacza",
|
||||
"ws_err_no_defaults": "Brak domyślnych wartości szybkiego ukończenia",
|
||||
"ws_err_self_link_device": "Obiekt nie może być powiązany sam ze sobą",
|
||||
"ws_err_too_early": "Za wcześnie — to zadanie można ukończyć dopiero bliżej terminu",
|
||||
"feat_schedule_time": "Harmonogram według pory dnia",
|
||||
"feat_schedule_time_desc": "Zadania stają się zaległe o określonej porze dnia zamiast o północy.",
|
||||
"schedule_time_toggle": "Termin o określonej godzinie",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Wyzwól przy równym (=)",
|
||||
"trigger_not_equals": "Wyzwól przy różnym od (≠)",
|
||||
"for_at_least_minutes": "Przez co najmniej (minuty)",
|
||||
"safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)",
|
||||
"safety_interval": "Interwał bezpieczeństwa (opcjonalny)",
|
||||
"trigger_combinator": "Połącz wyzwalacz i interwał",
|
||||
"trigger_combinator_any": "Wyzwalacz lub interwał (co pierwsze)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Moje zadania",
|
||||
"tab_calendar": "Kalendarz",
|
||||
"cal_no_events": "Brak konserwacji",
|
||||
"cal_window_7": "7 dni",
|
||||
"cal_window_14": "14 dni",
|
||||
"cal_window_30": "30 dni",
|
||||
"cal_window_365": "1 rok",
|
||||
"cal_every_n_days": "co {n} dni",
|
||||
"cal_source_time": "Czas",
|
||||
"cal_source_time_adaptive": "Czas (adaptacyjny)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Nie udało się wygenerować kodu QR.",
|
||||
"qr_error_no_url": "Brak skonfigurowanego URL HA. Ustaw zewnętrzny lub wewnętrzny URL w Ustawienia → System → Sieć.",
|
||||
"save_error": "Nie udało się zapisać. Spróbuj ponownie.",
|
||||
"subsave_warning": "Zapisano, ale jedno ustawienie zostało odrzucone: {detail}",
|
||||
"qr_print": "Drukuj",
|
||||
"qr_download": "Pobierz SVG",
|
||||
"qr_action": "Akcja przy skanowaniu",
|
||||
"qr_action_view": "Wyświetl informacje o konserwacji",
|
||||
"qr_action_complete": "Oznacz konserwację jako wykonaną",
|
||||
"qr_url_mode": "Typ linku",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Lokalny (mDNS)",
|
||||
"qr_mode_server": "URL serwera",
|
||||
"overview": "Przegląd",
|
||||
"analysis": "Analiza",
|
||||
"recent_activities": "Ostatnie aktywności",
|
||||
"search_notes": "Szukaj w notatkach",
|
||||
"avg_cost": "Śr. koszt",
|
||||
"no_advanced_features": "Brak włączonych funkcji zaawansowanych",
|
||||
"no_advanced_features_hint": "Włącz „Adaptacyjne interwały” lub „Wzorce sezonowe” w ustawieniach integracji, aby zobaczyć tutaj dane analityczne.",
|
||||
"analysis_not_enough_data": "Jeszcze za mało danych do analizy.",
|
||||
"analysis_not_enough_data_hint": "Analiza Weibulla wymaga co najmniej 5 wykonanych konserwacji; wzorce sezonowe stają się widoczne po 6+ punktach danych na miesiąc.",
|
||||
"analysis_manual_task_hint": "Zadania ręczne bez interwału nie generują danych analitycznych.",
|
||||
"completions": "wykonania",
|
||||
"current": "Bieżące",
|
||||
"shorter": "Krótsze",
|
||||
"longer": "Dłuższe",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Minione okna",
|
||||
"cal_forward_windows": "Przyszłe okna",
|
||||
"history_edit_title": "Edytuj wpis historii",
|
||||
"history_delete_entry": "Usuń wpis",
|
||||
"history_delete_confirm": "Usunąć ten wpis historii? Data ostatniego wykonania cofnie się do poprzedniego; zdjęcia pozostaną przy obiekcie, a zużyte części nie wrócą na stan.",
|
||||
"history_edit_timestamp": "Znacznik czasu",
|
||||
"manufacturer": "Producent",
|
||||
"model": "Model",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Domyślne dni ostrzeżenia",
|
||||
"settings_consumable_threshold": "Próg „materiał eksploatacyjny niski” (%)",
|
||||
"settings_battery_low_percent": "Próg „bateria słaba” (%)",
|
||||
"settings_battery_lifetimes": "Typowa żywotność baterii",
|
||||
"settings_battery_lifetimes_hint": "Używane tylko dla baterii bez procentu: notatka Battery Notes z samym typem i datą wymiany albo czujnik zgłaszający tylko „niski”. Termin = ostatnia wymiana + ta żywotność. Baterie raportujące poziom dostają prognozę z mierzonego rozładowania. Po trzech wymianach danego typu mediana odstępów zastępuje wbudowaną wartość — Twoja wartość zawsze wygrywa.",
|
||||
"settings_battery_lifetime_months": "mies.",
|
||||
"settings_battery_lifetime_reset": "Użyj domyślnej",
|
||||
"settings_battery_lifetime_in_fleet": "w Twojej flocie",
|
||||
"settings_battery_lifetime_learned_models": "Nauczone według modelu: {list}",
|
||||
"settings_thresholds_hint": "Wartości domyślne dla nowych Sugerowanych konfiguracji (procent pozostały) i dla baterii floty bez własnego progu Battery Notes. Istniejące zadania zachowują swoją wartość.",
|
||||
"settings_value_out_of_range": "Wartość musi mieścić się w zakresie od {min} do {max}",
|
||||
"bn_summary": "{name}: {pct} % dla {n} baterii",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} urządzeń z własnym progiem:",
|
||||
"bn_more": "+ {n} więcej",
|
||||
"settings_row_actions": "Akcje w wierszach zadań",
|
||||
"settings_ref_numbers_in_lists": "Numery referencyjne na listach",
|
||||
"settings_ref_numbers_in_lists_hint": "Pokazuje etykiety #8 / #8.3 przed nazwami obiektów i zadań na liście Dzisiaj, w tabeli zadań, na kartach obiektów i w tabeli obiektów.",
|
||||
"row_actions_buttons_compact": "Przyciski (na telefonie tylko ikony)",
|
||||
"row_actions_buttons": "Przyciski z tekstem",
|
||||
"row_actions_icons": "Ikony (klasyczne)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Czas drzemki (godziny)",
|
||||
"settings_budget": "Budżet",
|
||||
"settings_currency": "Waluta",
|
||||
"settings_currency_decimals": "Miejsca po przecinku w kwotach",
|
||||
"settings_currency_decimals_hint": "Ile miejsc po przecinku pokazuje każda kwota: KPI, budżety, koszty na listach i w raportach, alerty budżetowe. 0 = liczby całkowite.",
|
||||
"settings_budget_monthly": "Budżet miesięczny",
|
||||
"settings_budget_yearly": "Budżet roczny",
|
||||
"settings_budget_alerts": "Alerty budżetowe",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Eksportuj YAML",
|
||||
"settings_export_csv": "Eksportuj CSV",
|
||||
"settings_export_settings": "Eksportuj ustawienia (JSON)",
|
||||
"settings_import_csv": "Importuj CSV",
|
||||
"settings_import_placeholder": "Wklej tutaj zawartość JSON lub CSV…",
|
||||
"settings_import_btn": "Importuj",
|
||||
"settings_import_success": "{count} obiektów zaimportowanych pomyślnie.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Akcja HA per zadanie po zakończeniu + QR szybkiego zakończenia z predefiniowanymi wartościami.",
|
||||
"on_complete_action_title": "Po zakończeniu: wywołaj akcję HA (opcjonalnie)",
|
||||
"on_complete_action_desc": "Wywołuje usługę HA po zakończeniu zadania — np. zresetuj licznik urządzenia.",
|
||||
"on_complete_action_service": "Usługa",
|
||||
"on_complete_action_target": "Encja docelowa",
|
||||
"on_complete_action_data": "Dane (JSON, opcjonalnie)",
|
||||
"on_complete_action_test": "Testuj akcję",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Wybierz zdjęcia",
|
||||
"choose_photo": "Wybierz zdjęcie",
|
||||
"photos_android_hint": "Aplikacja na Androida przekazuje jedno zdjęcie na wybór — każdy wybór jest dodawany.",
|
||||
"add_photos": "Dodaj zdjęcia",
|
||||
"photos_limit": "Maksymalnie {max} zdjęć na wykonanie",
|
||||
"history_edit_photos_hint": "Zdjęcie usunięte tutaj pozostaje w dokumentach obiektu.",
|
||||
"uploading": "Przesyłanie…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Pobierz",
|
||||
"doc_close": "Zamknij",
|
||||
"doc_camera": "Zrób zdjęcie",
|
||||
"camera_capture_shoot": "Zrób zdjęcie",
|
||||
"camera_switch_lens": "Przełącz kamerę",
|
||||
"doc_drop_hint": "Upuść pliki tutaj",
|
||||
"doc_task_none": "Brak dokumentów powiązanych z tym zadaniem.",
|
||||
"doc_link_existing": "Powiąż dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Rejestruje wykonanie (ustawia ostatnie wykonanie), gdy wyzwalacz sam się rozwiąże — np. uzupełniono sól, wymieniono filtr.",
|
||||
"doc_search": "Szukaj dokumentów…",
|
||||
"doc_search_none": "Brak pasujących dokumentów",
|
||||
"doc_description": "Opis (opcjonalnie)",
|
||||
"doc_sort": "Sortuj dokumenty",
|
||||
"doc_sort_newest": "Najnowsze najpierw",
|
||||
"doc_sort_oldest": "Najstarsze najpierw",
|
||||
"doc_sort_title": "Według tytułu (1, 2, 3 …)",
|
||||
"doc_sort_category": "Według kategorii",
|
||||
"link_device_optional": "Połącz z istniejącym urządzeniem (opcjonalnie)",
|
||||
"parent_object_optional": "Obiekt nadrzędny (opcjonalnie)",
|
||||
"parent_none": "(Brak nadrzędnego)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Powiadamiaj tylko dla widoku",
|
||||
"settings_notify_scope_all": "Wszystkie zadania",
|
||||
"settings_notify_scope_hint": "Przypomnienia wysyłają tylko zadania pasujące do filtrów etykiety/użytkownika wybranego widoku. Status, sortowanie i grupowanie widoku są tu ignorowane.",
|
||||
"settings_notify_completed": "Powiadomienia o wykonaniu",
|
||||
"settings_notify_completed_hint": "Wiadomość dla domowników, gdy zadanie zostanie wykonane, z powodem i wykonawcą. „Tylko automatyczne” zgłasza wyłącznie wykonania, których nikt nie zrobił na miejscu: powrót czujnika, odhaczenie na liście zakupów, wywołanie usługi przez automatyzację. Tylko do usługi domowej; godziny ciszy i dzienny limit obowiązują.",
|
||||
"notify_completed_off": "Wył.",
|
||||
"notify_completed_automatic": "Tylko automatyczne wykonania",
|
||||
"notify_completed_all": "Wszystkie wykonania",
|
||||
"settings_notify_rule": "Własna reguła powiadomień",
|
||||
"settings_notify_rule_hint": "Każde powiadomienie wyzwala dodatkowo zdarzenie maintenance_supporter_notification (rodzaj, status, obiekt, zadanie, numery referencyjne, priorytet, link, dane). Przekieruj je automatyzacją — np. do Tickera, Telegrama lub Pushover.",
|
||||
"settings_notify_event_only": "Tylko zdarzenie, nic nie wysyłaj",
|
||||
"settings_notify_event_only_hint": "Twoja automatyzacja odpowiada za całe dostarczanie. Odstępy powtórzeń, ciche godziny i dzienny limit nadal decydują, kiedy zdarzenie się wyzwala.",
|
||||
"settings_notify_extra_data": "Dodatkowe dane powiadomienia (szablon)",
|
||||
"settings_notify_extra_data_hint": "JSON lub YAML, Jinja dozwolone; scalane z data wywołania notify (Twoje klucze wygrywają). Zmienne: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Sprawdź przyciskiem Wyślij test.",
|
||||
"settings_notify_extra_data_hint": "JSON lub YAML, Jinja dozwolone; scalane z data wywołania notify (Twoje klucze wygrywają). Zmienne: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Sprawdź przyciskiem Wyślij test.",
|
||||
"card_saved_view": "Zapisany widok",
|
||||
"card_saved_view_none": "Brak",
|
||||
"card_saved_view_help": "Stosuje filtry statusu, użytkownika i etykiety widoku oprócz powyższych filtrów. Sortowanie i grupowanie widoku to ustawienia wyświetlania panelu i nie są stosowane na karcie.",
|
||||
"panel_card_tab": "Otwieraj na karcie",
|
||||
"panel_card_tab_default": "Zapamiętana karta (jak w panelu)",
|
||||
"panel_card_view": "Otwieraj z zapisanym widokiem",
|
||||
"panel_card_height": "Wysokość",
|
||||
"panel_card_height_help": "Puste = wypełnia ekran pod kartą (widok panelu). W przeciwnym razie długość CSS, np. 600px lub 80vh, dla widoków z innymi kartami.",
|
||||
"panel_card_not_registered": "Panel Maintenance Supporter jest niedostępny — czy integracja jest skonfigurowana?",
|
||||
"panel_card_load_failed": "Nie udało się wczytać panelu Maintenance Supporter — odśwież stronę (po aktualizacji: wyczyść pamięć podręczną przeglądarki).",
|
||||
"doc_part_none": "Brak dokumentów powiązanych z tą częścią.",
|
||||
"settings_templates_toggle_group": "Włącz lub wyłącz wszystkie szablony w tej grupie",
|
||||
"setups_button": "Sugerowane konfiguracje",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "W porządku",
|
||||
"battery_fleet_status_due": "Do wymiany",
|
||||
"battery_fleet_predicted_on": "Przewidywane około {date}",
|
||||
"battery_fleet_predicted_typical": "Spodziewane około {date} — {months} mies. typowo dla {type} ({source})",
|
||||
"lifetime_source_default": "wbudowana wartość domyślna",
|
||||
"lifetime_source_table": "wbudowana tabela",
|
||||
"lifetime_source_learned": "nauczone z {n} wymian",
|
||||
"lifetime_source_override": "Twoje ustawienie",
|
||||
"lifetime_source_learned_device": "nauczone z {n} wymian tego urządzenia",
|
||||
"lifetime_source_learned_model": "nauczone z {n} wymian w urządzeniach tego modelu",
|
||||
"battery_fleet_predicted_trend": "Prognoza na podstawie trendu rozładowania tej baterii: około {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Akumulator: ładowanie zamiast wymiany — nigdy na liście zakupów",
|
||||
"battery_fleet_sort_name": "Sortuj według nazwy",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Zużyte części",
|
||||
"print_inc_photos": "Zdjęcia",
|
||||
"print_inc_documents": "Powiązane dokumenty",
|
||||
"print_inc_doc_desc": "Opisy dokumentów",
|
||||
"print_inc_checklist": "Lista kontrolna",
|
||||
"print_inc_notes": "Notatki",
|
||||
"print_inc_costs": "Koszt i czas",
|
||||
"print_inc_person": "Wykonał",
|
||||
"print_inc_refs": "Numery referencyjne",
|
||||
"print_inc_bare": "Wykonania bez szczegółów",
|
||||
"print_inc_qr": "Kod QR dla każdego zadania",
|
||||
"print_button": "Drukuj",
|
||||
"report_scan_hint": "Zeskanuj, aby otworzyć zadanie",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Tempo de operação",
|
||||
"runtime_hours": "Tempo de operação alvo (horas)",
|
||||
"target_value": "Valor alvo",
|
||||
"baseline": "Linha de base",
|
||||
"target_changes": "Mudanças alvo",
|
||||
"for_minutes": "Durante (minutos)",
|
||||
"time_based": "Baseado em tempo",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Gerado em",
|
||||
"report_times_done": "Concluídas",
|
||||
"report_total_cost": "Custo total",
|
||||
"report_every": "a cada {n} {unit}",
|
||||
"report_notes": "Observações",
|
||||
"report_col_type": "Tipo",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Agendamento",
|
||||
"duration": "Duração",
|
||||
"minutes_short": "min",
|
||||
"both": "Ambos",
|
||||
"trigger_val": "Valor do gatilho",
|
||||
"complete_title": "Concluir: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: tipo incorreto (esperado: {type})",
|
||||
"err_invalid_choice": "{field}: valor não permitido",
|
||||
"err_invalid_value": "{field}: valor inválido",
|
||||
"ws_err_not_found": "Não encontrado — pode ter sido excluído nesse meio-tempo",
|
||||
"ws_err_invalid_input": "Entrada inválida",
|
||||
"ws_err_invalid_date": "Data inválida",
|
||||
"ws_err_invalid_format": "Formato inválido",
|
||||
"ws_err_invalid_url": "Link inválido",
|
||||
"ws_err_not_configured": "Ainda não configurado",
|
||||
"ws_err_not_loaded": "Ainda não carregado — tente novamente em instantes",
|
||||
"ws_err_limit_reached": "Limite atingido",
|
||||
"ws_err_too_many": "Entradas demais",
|
||||
"ws_err_too_large": "Grande demais",
|
||||
"ws_err_not_archived": "Não arquivado",
|
||||
"ws_err_invalid_target": "Destino inválido",
|
||||
"ws_err_create_failed": "Não foi possível criar",
|
||||
"ws_err_archived": "Arquivado — restaure primeiro",
|
||||
"ws_err_already_archived": "Já arquivado",
|
||||
"ws_err_unavailable": "Indisponível no momento",
|
||||
"ws_err_unauthorized": "Não permitido para o seu usuário",
|
||||
"ws_err_too_many_views": "Visualizações salvas demais",
|
||||
"ws_err_storage_unavailable": "O armazenamento não está disponível",
|
||||
"ws_err_replace_failed": "Falha ao substituir",
|
||||
"ws_err_not_paused": "Não está pausado",
|
||||
"ws_err_not_available": "Nada disponível",
|
||||
"ws_err_no_url": "Nenhum link informado",
|
||||
"ws_err_no_phases": "Esta tarefa não tem fases",
|
||||
"ws_err_invalid_view": "Visualização inválida",
|
||||
"ws_err_invalid_user": "Usuário desconhecido",
|
||||
"ws_err_invalid_range": "Intervalo inválido",
|
||||
"ws_err_invalid_parent": "Objeto pai inválido",
|
||||
"ws_err_invalid_device": "Dispositivo desconhecido",
|
||||
"ws_err_empty_csv": "O arquivo CSV está vazio",
|
||||
"ws_err_empty": "Nada para importar",
|
||||
"ws_err_duplicate_failed": "Não foi possível duplicar",
|
||||
"ws_err_already_paused": "Já está pausado",
|
||||
"ws_err_invalid_cursor": "Posição de fase inválida",
|
||||
"ws_err_invalid_entity_slug": "Nome de entidade inválido",
|
||||
"ws_err_invalid_trigger_config": "Configuração do gatilho inválida",
|
||||
"ws_err_no_defaults": "Sem padrões para conclusão rápida",
|
||||
"ws_err_self_link_device": "Um objeto não pode ser vinculado a si mesmo",
|
||||
"ws_err_too_early": "Cedo demais — esta tarefa só pode ser concluída mais perto do vencimento",
|
||||
"feat_schedule_time": "Agendamento por horário",
|
||||
"feat_schedule_time_desc": "As tarefas ficam atrasadas em um horário específico do dia, em vez de à meia-noite.",
|
||||
"schedule_time_toggle": "Vence em um horário específico",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Acionar quando igual a (=)",
|
||||
"trigger_not_equals": "Acionar quando diferente de (≠)",
|
||||
"for_at_least_minutes": "Por pelo menos (minutos)",
|
||||
"safety_interval_days": "Intervalo de segurança (dias, opcional)",
|
||||
"safety_interval": "Intervalo de segurança (opcional)",
|
||||
"trigger_combinator": "Combinar gatilho e intervalo",
|
||||
"trigger_combinator_any": "Gatilho ou intervalo (o primeiro)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Minhas tarefas",
|
||||
"tab_calendar": "Calendário",
|
||||
"cal_no_events": "Sem manutenção",
|
||||
"cal_window_7": "7 dias",
|
||||
"cal_window_14": "14 dias",
|
||||
"cal_window_30": "30 dias",
|
||||
"cal_window_365": "1 ano",
|
||||
"cal_every_n_days": "a cada {n} dias",
|
||||
"cal_source_time": "Baseado em tempo",
|
||||
"cal_source_time_adaptive": "Baseado em tempo (adaptativo)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "Falha ao gerar o código QR.",
|
||||
"qr_error_no_url": "Nenhuma URL do HA configurada. Defina uma URL externa ou interna em Configurações → Sistema → Rede.",
|
||||
"save_error": "Falha ao salvar. Tente novamente.",
|
||||
"subsave_warning": "Salvo, mas uma configuração foi rejeitada: {detail}",
|
||||
"qr_print": "Imprimir",
|
||||
"qr_download": "Baixar SVG",
|
||||
"qr_action": "Ação ao escanear",
|
||||
"qr_action_view": "Ver informações de manutenção",
|
||||
"qr_action_complete": "Marcar a manutenção como concluída",
|
||||
"qr_url_mode": "Tipo de link",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "URL do servidor",
|
||||
"overview": "Visão geral",
|
||||
"analysis": "Análise",
|
||||
"recent_activities": "Atividades recentes",
|
||||
"search_notes": "Pesquisar observações",
|
||||
"avg_cost": "Custo médio",
|
||||
"no_advanced_features": "Nenhum recurso avançado ativado",
|
||||
"no_advanced_features_hint": "Ative “Intervalos adaptativos” ou “Padrões sazonais” nas configurações da integração para ver dados de análise aqui.",
|
||||
"analysis_not_enough_data": "Ainda não há dados suficientes para análise.",
|
||||
"analysis_not_enough_data_hint": "A análise de Weibull exige pelo menos 5 manutenções concluídas; padrões sazonais aparecem após 6+ pontos de dados por mês.",
|
||||
"analysis_manual_task_hint": "Tarefas manuais sem intervalo não geram dados de análise.",
|
||||
"completions": "conclusões",
|
||||
"current": "Atual",
|
||||
"shorter": "Mais curto",
|
||||
"longer": "Mais longo",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Janelas passadas",
|
||||
"cal_forward_windows": "Janelas futuras",
|
||||
"history_edit_title": "Editar entrada do histórico",
|
||||
"history_delete_entry": "Excluir entrada",
|
||||
"history_delete_confirm": "Excluir esta entrada do histórico? A data da última execução volta para a conclusão anterior; as fotos permanecem com o objeto e as peças consumidas não são repostas.",
|
||||
"history_edit_timestamp": "Data e hora",
|
||||
"manufacturer": "Fabricante",
|
||||
"model": "Modelo",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Dias de aviso padrão",
|
||||
"settings_consumable_threshold": "Limite „consumível baixo“ (%)",
|
||||
"settings_battery_low_percent": "Limite „pilha fraca“ (%)",
|
||||
"settings_battery_lifetimes": "Vida útil típica das pilhas",
|
||||
"settings_battery_lifetimes_hint": "Usado apenas para pilhas sem porcentagem: uma nota do Battery Notes só com tipo e data de troca, ou um sensor que apenas informa « fraca ». O vencimento é última troca + esta vida útil. Pilhas que informam nível têm a previsão pela descarga medida. Após três trocas de um tipo, o intervalo mediano substitui o valor integrado — seu valor sempre vence.",
|
||||
"settings_battery_lifetime_months": "meses",
|
||||
"settings_battery_lifetime_reset": "Usar padrão",
|
||||
"settings_battery_lifetime_in_fleet": "na sua frota",
|
||||
"settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}",
|
||||
"settings_thresholds_hint": "Padrões para novas Configurações sugeridas (porcentagem restante) e para pilhas da frota sem limite próprio do Battery Notes. As tarefas existentes mantêm seu valor.",
|
||||
"settings_value_out_of_range": "O valor deve estar entre {min} e {max}",
|
||||
"bn_summary": "{name}: {pct} % para {n} pilhas",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} dispositivos com limite próprio:",
|
||||
"bn_more": "+ {n} mais",
|
||||
"settings_row_actions": "Ações nas linhas de tarefas",
|
||||
"settings_ref_numbers_in_lists": "Números de referência nas listas",
|
||||
"settings_ref_numbers_in_lists_hint": "Mostra as etiquetas #8 / #8.3 antes dos nomes de objetos e tarefas na lista Hoje, na tabela de tarefas, nos cartões de objetos e na tabela de objetos.",
|
||||
"row_actions_buttons_compact": "Botões (só ícones no celular)",
|
||||
"row_actions_buttons": "Botões com texto",
|
||||
"row_actions_icons": "Ícones (clássico)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Duração da soneca (horas)",
|
||||
"settings_budget": "Orçamento",
|
||||
"settings_currency": "Moeda",
|
||||
"settings_currency_decimals": "Casas decimais dos valores",
|
||||
"settings_currency_decimals_hint": "Quantas casas decimais cada valor mostra: KPIs, orçamentos, custos em listas e relatórios, alertas de orçamento. 0 = números inteiros.",
|
||||
"settings_budget_monthly": "Orçamento mensal",
|
||||
"settings_budget_yearly": "Orçamento anual",
|
||||
"settings_budget_alerts": "Alertas de orçamento",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Exportar YAML",
|
||||
"settings_export_csv": "Exportar CSV",
|
||||
"settings_export_settings": "Exportar configurações (JSON)",
|
||||
"settings_import_csv": "Importar CSV",
|
||||
"settings_import_placeholder": "Cole o conteúdo JSON ou CSV aqui…",
|
||||
"settings_import_btn": "Importar",
|
||||
"settings_import_success": "{count} objetos importados com sucesso.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "Ação do HA por tarefa ao concluir + QR de conclusão rápida com valores predefinidos.",
|
||||
"on_complete_action_title": "Ao concluir: acionar ação do HA (opcional)",
|
||||
"on_complete_action_desc": "Chama um serviço do HA quando a tarefa é concluída — ex.: zerar um contador no dispositivo.",
|
||||
"on_complete_action_service": "Serviço",
|
||||
"on_complete_action_target": "Entidade alvo",
|
||||
"on_complete_action_target_hint": "Atenção: o domínio da entidade deve corresponder ao serviço — ex.: 'button.press' só funciona em button.*, 'counter.increment' só em counter.*, 'input_button.press' só em input_button.* etc. Em caso de divergência, a ação falhará silenciosamente (o HA registra 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Dados (JSON, opcional)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "Escolher fotos",
|
||||
"choose_photo": "Escolher foto",
|
||||
"photos_android_hint": "O app Android entrega uma foto por seleção — cada seleção é adicionada.",
|
||||
"add_photos": "Adicionar fotos",
|
||||
"photos_limit": "Até {max} fotos por conclusão",
|
||||
"history_edit_photos_hint": "Uma foto removida aqui continua nos documentos do objeto.",
|
||||
"uploading": "Enviando…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "Baixar",
|
||||
"doc_close": "Fechar",
|
||||
"doc_camera": "Tirar foto",
|
||||
"camera_capture_shoot": "Capturar",
|
||||
"camera_switch_lens": "Trocar de câmera",
|
||||
"doc_drop_hint": "Solte os arquivos aqui",
|
||||
"doc_task_none": "Nenhum documento vinculado a esta tarefa.",
|
||||
"doc_link_existing": "Vincular um documento…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "Registra uma conclusão (define a última execução) quando o gatilho se resolve sozinho — ex.: sal reposto, filtro trocado.",
|
||||
"doc_search": "Pesquisar documentos…",
|
||||
"doc_search_none": "Nenhum documento correspondente",
|
||||
"doc_description": "Descrição (opcional)",
|
||||
"doc_sort": "Ordenar documentos",
|
||||
"doc_sort_newest": "Mais recentes primeiro",
|
||||
"doc_sort_oldest": "Mais antigos primeiro",
|
||||
"doc_sort_title": "Por título (1, 2, 3 …)",
|
||||
"doc_sort_category": "Por categoria",
|
||||
"link_device_optional": "Vincular a um dispositivo existente (opcional)",
|
||||
"parent_object_optional": "Objeto pai (opcional)",
|
||||
"parent_none": "(Sem pai)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notificar apenas para a visualização",
|
||||
"settings_notify_scope_all": "Todas as tarefas",
|
||||
"settings_notify_scope_hint": "Apenas tarefas que correspondem aos filtros de etiqueta/usuário da visualização salva selecionada enviam lembretes. Status, ordenação e agrupamento da visualização são ignorados aqui.",
|
||||
"settings_notify_completed": "Notificações de conclusão",
|
||||
"settings_notify_completed_hint": "Aviso para a casa quando uma tarefa é concluída, com o motivo e quem fez. \"Só automáticas\" anuncia apenas as conclusões que ninguém fez na hora: recuperação do sensor, item marcado na lista de compras, chamada de serviço de uma automação. Só para o serviço da casa; horário de silêncio e limite diário se aplicam.",
|
||||
"notify_completed_off": "Desligado",
|
||||
"notify_completed_automatic": "Só conclusões automáticas",
|
||||
"notify_completed_all": "Todas as conclusões",
|
||||
"settings_notify_rule": "Sua regra de notificação",
|
||||
"settings_notify_rule_hint": "Cada notificação também dispara o evento maintenance_supporter_notification (tipo, status, objeto, tarefa, números de referência, prioridade, link, payload). Roteie-o com uma automação — por ex. para Ticker, Telegram ou Pushover.",
|
||||
"settings_notify_event_only": "Apenas disparar o evento, não enviar nada",
|
||||
"settings_notify_event_only_hint": "Sua automação cuida de toda a entrega. Intervalos de repetição, horário silencioso e o limite diário continuam valendo para quando o evento dispara.",
|
||||
"settings_notify_extra_data": "Dados extras de notificação (modelo)",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; mesclado nos data da chamada notify (suas chaves vencem). Variáveis: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Teste com o botão Enviar teste.",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; mesclado nos data da chamada notify (suas chaves vencem). Variáveis: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Teste com o botão Enviar teste.",
|
||||
"card_saved_view": "Visualização salva",
|
||||
"card_saved_view_none": "Nenhuma",
|
||||
"card_saved_view_help": "Aplica os filtros de status, usuário e etiqueta da visualização por cima dos filtros acima. A ordenação e o agrupamento da visualização são configurações de exibição do painel e não se aplicam ao cartão.",
|
||||
"panel_card_tab": "Abrir na aba",
|
||||
"panel_card_tab_default": "Aba lembrada (como no painel)",
|
||||
"panel_card_view": "Abrir com a visualização salva",
|
||||
"panel_card_height": "Altura",
|
||||
"panel_card_height_help": "Vazio = preenche a tela abaixo do cartão (visualização painel). Caso contrário, um comprimento CSS como 600px ou 80vh para visualizações com outros cartões.",
|
||||
"panel_card_not_registered": "O painel Maintenance Supporter não está disponível — a integração está configurada?",
|
||||
"panel_card_load_failed": "Não foi possível carregar o painel Maintenance Supporter — recarregue a página (após uma atualização: limpe o cache do navegador).",
|
||||
"doc_part_none": "Nenhum documento vinculado a esta peça.",
|
||||
"settings_templates_toggle_group": "Ativar ou desativar todos os modelos deste grupo",
|
||||
"setups_button": "Configurações sugeridas",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Em bom estado",
|
||||
"battery_fleet_status_due": "Vencida",
|
||||
"battery_fleet_predicted_on": "Previsto por volta de {date}",
|
||||
"battery_fleet_predicted_typical": "Previsto por volta de {date} — {months} meses típicos para {type} ({source})",
|
||||
"lifetime_source_default": "valor padrão integrado",
|
||||
"lifetime_source_table": "tabela integrada",
|
||||
"lifetime_source_learned": "aprendido de {n} trocas",
|
||||
"lifetime_source_override": "sua configuração",
|
||||
"lifetime_source_learned_device": "aprendido de {n} trocas deste dispositivo",
|
||||
"lifetime_source_learned_model": "aprendido de {n} trocas em dispositivos deste modelo",
|
||||
"battery_fleet_predicted_trend": "Previsto pela tendência de descarga desta bateria: por volta de {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Recarregável: recarregue em vez de substituir — nunca na lista de compras",
|
||||
"battery_fleet_sort_name": "Ordenar por nome",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Peças usadas",
|
||||
"print_inc_photos": "Fotos",
|
||||
"print_inc_documents": "Documentos vinculados",
|
||||
"print_inc_doc_desc": "Descrições dos documentos",
|
||||
"print_inc_checklist": "Lista de verificação",
|
||||
"print_inc_notes": "Notas",
|
||||
"print_inc_costs": "Custo e duração",
|
||||
"print_inc_person": "Concluído por",
|
||||
"print_inc_refs": "Números de referência",
|
||||
"print_inc_bare": "Conclusões sem detalhes",
|
||||
"print_inc_qr": "Código QR por tarefa",
|
||||
"print_button": "Imprimir",
|
||||
"report_scan_hint": "Escaneie para abrir a tarefa",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Tempo de funcionamento",
|
||||
"runtime_hours": "Duração alvo (horas)",
|
||||
"target_value": "Valor alvo",
|
||||
"baseline": "Linha de base",
|
||||
"target_changes": "Alterações alvo",
|
||||
"for_minutes": "Durante (minutos)",
|
||||
"time_based": "Temporal",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Gerado",
|
||||
"report_times_done": "Feito",
|
||||
"report_total_cost": "Custo total",
|
||||
"report_every": "a cada {n} {unit}",
|
||||
"report_notes": "Notas",
|
||||
"report_col_type": "Tipo",
|
||||
"report_col_status": "Estado",
|
||||
"report_col_schedule": "Agenda",
|
||||
"duration": "Duração",
|
||||
"minutes_short": "min",
|
||||
"both": "Ambos",
|
||||
"trigger_val": "Valor do acionador",
|
||||
"complete_title": "Concluída: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: tipo incorreto (esperado: {type})",
|
||||
"err_invalid_choice": "{field}: valor não permitido",
|
||||
"err_invalid_value": "{field}: valor inválido",
|
||||
"ws_err_not_found": "Não encontrado — pode ter sido eliminado entretanto",
|
||||
"ws_err_invalid_input": "Entrada inválida",
|
||||
"ws_err_invalid_date": "Data inválida",
|
||||
"ws_err_invalid_format": "Formato inválido",
|
||||
"ws_err_invalid_url": "Ligação inválida",
|
||||
"ws_err_not_configured": "Ainda não configurado",
|
||||
"ws_err_not_loaded": "Ainda não carregado — tente novamente daqui a pouco",
|
||||
"ws_err_limit_reached": "Limite atingido",
|
||||
"ws_err_too_many": "Demasiadas entradas",
|
||||
"ws_err_too_large": "Demasiado grande",
|
||||
"ws_err_not_archived": "Não arquivado",
|
||||
"ws_err_invalid_target": "Destino inválido",
|
||||
"ws_err_create_failed": "Não foi possível criar",
|
||||
"ws_err_archived": "Arquivado — restaure primeiro",
|
||||
"ws_err_already_archived": "Já arquivado",
|
||||
"ws_err_unavailable": "Atualmente indisponível",
|
||||
"ws_err_unauthorized": "Não permitido para o seu utilizador",
|
||||
"ws_err_too_many_views": "Demasiadas vistas guardadas",
|
||||
"ws_err_storage_unavailable": "O armazenamento não está disponível",
|
||||
"ws_err_replace_failed": "Falha ao substituir",
|
||||
"ws_err_not_paused": "Não está em pausa",
|
||||
"ws_err_not_available": "Nada disponível",
|
||||
"ws_err_no_url": "Nenhuma ligação indicada",
|
||||
"ws_err_no_phases": "Esta tarefa não tem fases",
|
||||
"ws_err_invalid_view": "Vista inválida",
|
||||
"ws_err_invalid_user": "Utilizador desconhecido",
|
||||
"ws_err_invalid_range": "Intervalo inválido",
|
||||
"ws_err_invalid_parent": "Objeto principal inválido",
|
||||
"ws_err_invalid_device": "Dispositivo desconhecido",
|
||||
"ws_err_empty_csv": "O ficheiro CSV está vazio",
|
||||
"ws_err_empty": "Nada para importar",
|
||||
"ws_err_duplicate_failed": "Não foi possível duplicar",
|
||||
"ws_err_already_paused": "Já está em pausa",
|
||||
"ws_err_invalid_cursor": "Posição de fase inválida",
|
||||
"ws_err_invalid_entity_slug": "Nome de entidade inválido",
|
||||
"ws_err_invalid_trigger_config": "Configuração do acionador inválida",
|
||||
"ws_err_no_defaults": "Sem predefinições para conclusão rápida",
|
||||
"ws_err_self_link_device": "Um objeto não pode ser ligado a si próprio",
|
||||
"ws_err_too_early": "Demasiado cedo — esta tarefa só pode ser concluída mais perto do prazo",
|
||||
"feat_schedule_time": "Agendamento por hora",
|
||||
"feat_schedule_time_desc": "Tarefas vencem em um horário específico em vez de meia-noite.",
|
||||
"schedule_time_toggle": "Vence a uma hora específica",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Acionar quando igual a (=)",
|
||||
"trigger_not_equals": "Acionar quando diferente de (≠)",
|
||||
"for_at_least_minutes": "Durante pelo menos (minutos)",
|
||||
"safety_interval_days": "Intervalo de segurança (dias, opcional)",
|
||||
"safety_interval": "Intervalo de segurança (opcional)",
|
||||
"trigger_combinator": "Combinar acionador e intervalo",
|
||||
"trigger_combinator_any": "Acionador ou intervalo (o primeiro)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "As minhas tarefas",
|
||||
"tab_calendar": "Calendário",
|
||||
"cal_no_events": "Sem manutenção",
|
||||
"cal_window_7": "7 dias",
|
||||
"cal_window_14": "14 dias",
|
||||
"cal_window_30": "30 dias",
|
||||
"cal_window_365": "1 ano",
|
||||
"cal_every_n_days": "a cada {n} dias",
|
||||
"cal_source_time": "Baseado em tempo",
|
||||
"cal_source_time_adaptive": "Baseado em tempo (adaptativo)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Não foi possível gerar o código QR.",
|
||||
"qr_error_no_url": "Nenhum URL do HA configurado. Defina um URL externo ou interno em Definições → Sistema → Rede.",
|
||||
"save_error": "Erro ao guardar. Tente novamente.",
|
||||
"subsave_warning": "Guardado, mas uma definição foi rejeitada: {detail}",
|
||||
"qr_print": "Imprimir",
|
||||
"qr_download": "Transferir SVG",
|
||||
"qr_action": "Ação ao digitalizar",
|
||||
"qr_action_view": "Ver informações de manutenção",
|
||||
"qr_action_complete": "Marcar manutenção como concluída",
|
||||
"qr_url_mode": "Tipo de ligação",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "URL do servidor",
|
||||
"overview": "Visão geral",
|
||||
"analysis": "Análise",
|
||||
"recent_activities": "Atividades recentes",
|
||||
"search_notes": "Pesquisar notas",
|
||||
"avg_cost": "Ø Custo",
|
||||
"no_advanced_features": "Sem funções avançadas ativadas",
|
||||
"no_advanced_features_hint": "Ative “Intervalos Adaptativos” ou “Padrões Sazonais” nas definições da integração para ver dados de análise aqui.",
|
||||
"analysis_not_enough_data": "Ainda não há dados suficientes para a análise.",
|
||||
"analysis_not_enough_data_hint": "A análise Weibull requer pelo menos 5 manutenções concluídas; os padrões sazonais tornam-se visíveis após 6+ pontos de dados por mês.",
|
||||
"analysis_manual_task_hint": "Tarefas manuais sem intervalo não geram dados de análise.",
|
||||
"completions": "conclusões",
|
||||
"current": "Atual",
|
||||
"shorter": "Mais curto",
|
||||
"longer": "Mais longo",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Janelas passadas",
|
||||
"cal_forward_windows": "Janelas futuras",
|
||||
"history_edit_title": "Editar entrada do histórico",
|
||||
"history_delete_entry": "Eliminar entrada",
|
||||
"history_delete_confirm": "Eliminar esta entrada do histórico? A data da última execução recua para a conclusão anterior; as fotos ficam com o objeto e as peças consumidas não são repostas.",
|
||||
"history_edit_timestamp": "Carimbo de data/hora",
|
||||
"manufacturer": "Fabricante",
|
||||
"model": "Modelo",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Dias de aviso predefinidos",
|
||||
"settings_consumable_threshold": "Limiar „consumível baixo“ (%)",
|
||||
"settings_battery_low_percent": "Limiar „pilha fraca“ (%)",
|
||||
"settings_battery_lifetimes": "Durações típicas das pilhas",
|
||||
"settings_battery_lifetimes_hint": "Usado apenas para pilhas sem percentagem: uma nota do Battery Notes só com tipo e data de substituição, ou um sensor que apenas reporta « fraca ». O vencimento é última substituição + esta duração. Pilhas que reportam nível obtêm a previsão da descarga medida. Após três substituições de um tipo, o intervalo mediano substitui o valor integrado — o seu valor ganha sempre.",
|
||||
"settings_battery_lifetime_months": "meses",
|
||||
"settings_battery_lifetime_reset": "Usar predefinição",
|
||||
"settings_battery_lifetime_in_fleet": "na sua frota",
|
||||
"settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}",
|
||||
"settings_thresholds_hint": "Predefinições para novas Configurações sugeridas (percentagem restante) e para pilhas da frota sem limiar Battery Notes próprio. As tarefas existentes mantêm o seu valor.",
|
||||
"settings_value_out_of_range": "O valor tem de estar entre {min} e {max}",
|
||||
"bn_summary": "{name}: {pct} % para {n} pilhas",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} dispositivos com limiar próprio:",
|
||||
"bn_more": "+ {n} mais",
|
||||
"settings_row_actions": "Ações nas linhas de tarefas",
|
||||
"settings_ref_numbers_in_lists": "Números de referência nas listas",
|
||||
"settings_ref_numbers_in_lists_hint": "Mostra as etiquetas #8 / #8.3 antes dos nomes de objetos e tarefas na lista Hoje, na tabela de tarefas, nos cartões de objetos e na tabela de objetos.",
|
||||
"row_actions_buttons_compact": "Botões (só ícones no telemóvel)",
|
||||
"row_actions_buttons": "Botões com texto",
|
||||
"row_actions_icons": "Ícones (clássico)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Duração do adiamento (horas)",
|
||||
"settings_budget": "Orçamento",
|
||||
"settings_currency": "Moeda",
|
||||
"settings_currency_decimals": "Casas decimais dos montantes",
|
||||
"settings_currency_decimals_hint": "Quantas casas decimais cada montante mostra: KPIs, orçamentos, custos em listas e relatórios, alertas de orçamento. 0 = números inteiros.",
|
||||
"settings_budget_monthly": "Orçamento mensal",
|
||||
"settings_budget_yearly": "Orçamento anual",
|
||||
"settings_budget_alerts": "Alertas de orçamento",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exportar YAML",
|
||||
"settings_export_csv": "Exportar CSV",
|
||||
"settings_export_settings": "Exportar definições (JSON)",
|
||||
"settings_import_csv": "Importar CSV",
|
||||
"settings_import_placeholder": "Cole o conteúdo JSON ou CSV aqui…",
|
||||
"settings_import_btn": "Importar",
|
||||
"settings_import_success": "{count} objetos importados com sucesso.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Ação HA por tarefa ao concluir + QR de conclusão rápida com valores predefinidos.",
|
||||
"on_complete_action_title": "Na conclusão: acionar ação HA (opcional)",
|
||||
"on_complete_action_desc": "Chama um serviço HA quando a tarefa é concluída — p. ex. reiniciar um contador no dispositivo.",
|
||||
"on_complete_action_service": "Serviço",
|
||||
"on_complete_action_target": "Entidade alvo",
|
||||
"on_complete_action_data": "Dados (JSON, opcional)",
|
||||
"on_complete_action_test": "Testar ação",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Escolher fotos",
|
||||
"choose_photo": "Escolher foto",
|
||||
"photos_android_hint": "A app Android entrega uma foto por seleção — cada seleção é adicionada.",
|
||||
"add_photos": "Adicionar fotos",
|
||||
"photos_limit": "Até {max} fotos por conclusão",
|
||||
"history_edit_photos_hint": "Uma foto removida aqui continua nos documentos do objeto.",
|
||||
"uploading": "A enviar…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Transferir",
|
||||
"doc_close": "Fechar",
|
||||
"doc_camera": "Tirar foto",
|
||||
"camera_capture_shoot": "Capturar",
|
||||
"camera_switch_lens": "Mudar de câmara",
|
||||
"doc_drop_hint": "Solte os ficheiros aqui",
|
||||
"doc_task_none": "Nenhum documento associado a esta tarefa.",
|
||||
"doc_link_existing": "Associar um documento…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Regista uma conclusão (define a última realização) quando o gatilho se resolve sozinho — p. ex., sal reabastecido, filtro substituído.",
|
||||
"doc_search": "Pesquisar documentos…",
|
||||
"doc_search_none": "Nenhum documento correspondente",
|
||||
"doc_description": "Descrição (opcional)",
|
||||
"doc_sort": "Ordenar documentos",
|
||||
"doc_sort_newest": "Mais recentes primeiro",
|
||||
"doc_sort_oldest": "Mais antigos primeiro",
|
||||
"doc_sort_title": "Por título (1, 2, 3 …)",
|
||||
"doc_sort_category": "Por categoria",
|
||||
"link_device_optional": "Associar a dispositivo existente (opcional)",
|
||||
"parent_object_optional": "Objeto principal (opcional)",
|
||||
"parent_none": "(Sem objeto principal)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notificar apenas para a vista",
|
||||
"settings_notify_scope_all": "Todas as tarefas",
|
||||
"settings_notify_scope_hint": "Apenas as tarefas que correspondem aos filtros de etiqueta/utilizador da vista escolhida enviam lembretes. O estado, a ordenação e o agrupamento da vista são ignorados aqui.",
|
||||
"settings_notify_completed": "Notificações de conclusão",
|
||||
"settings_notify_completed_hint": "Aviso para a casa quando uma tarefa é concluída, com o motivo e quem a fez. \"Só automáticas\" anuncia apenas as conclusões que ninguém fez no local: recuperação do sensor, item marcado na lista de compras, chamada de serviço de uma automação. Só para o serviço da casa; horas de silêncio e limite diário aplicam-se.",
|
||||
"notify_completed_off": "Desligado",
|
||||
"notify_completed_automatic": "Só conclusões automáticas",
|
||||
"notify_completed_all": "Todas as conclusões",
|
||||
"settings_notify_rule": "A sua regra de notificação",
|
||||
"settings_notify_rule_hint": "Cada notificação dispara também o evento maintenance_supporter_notification (tipo, estado, objeto, tarefa, números de referência, prioridade, ligação, conteúdo). Encaminhe-o com uma automação — p. ex. para o Ticker, Telegram ou Pushover.",
|
||||
"settings_notify_event_only": "Apenas disparar o evento, não enviar nada",
|
||||
"settings_notify_event_only_hint": "A sua automação trata de toda a entrega. Intervalos de repetição, horas de silêncio e o limite diário continuam a decidir quando o evento dispara.",
|
||||
"settings_notify_extra_data": "Dados de notificação adicionais (modelo)",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; é fundido nos data da chamada notify (as suas chaves ganham). Variáveis: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Teste com o botão Enviar teste.",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; é fundido nos data da chamada notify (as suas chaves ganham). Variáveis: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Teste com o botão Enviar teste.",
|
||||
"card_saved_view": "Vista guardada",
|
||||
"card_saved_view_none": "Nenhuma",
|
||||
"card_saved_view_help": "Aplica os filtros de estado, utilizador e etiqueta da vista além dos filtros acima. A ordenação e o agrupamento da vista são definições de apresentação do painel e não se aplicam ao cartão.",
|
||||
"panel_card_tab": "Abrir no separador",
|
||||
"panel_card_tab_default": "Separador memorizado (como no painel)",
|
||||
"panel_card_view": "Abrir com a vista guardada",
|
||||
"panel_card_height": "Altura",
|
||||
"panel_card_height_help": "Vazio = preenche o ecrã abaixo do cartão (vista painel). Caso contrário, um comprimento CSS como 600px ou 80vh para vistas com outros cartões.",
|
||||
"panel_card_not_registered": "O painel Maintenance Supporter não está disponível — a integração está configurada?",
|
||||
"panel_card_load_failed": "Não foi possível carregar o painel Maintenance Supporter — recarregue a página (após uma atualização: limpe a cache do browser).",
|
||||
"doc_part_none": "Nenhum documento associado a esta peça.",
|
||||
"settings_templates_toggle_group": "Ativar ou desativar todos os modelos deste grupo",
|
||||
"setups_button": "Configurações sugeridas",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Em bom estado",
|
||||
"battery_fleet_status_due": "A substituir",
|
||||
"battery_fleet_predicted_on": "Previsto por volta de {date}",
|
||||
"battery_fleet_predicted_typical": "Previsto por volta de {date} — {months} meses típicos para {type} ({source})",
|
||||
"lifetime_source_default": "valor predefinido integrado",
|
||||
"lifetime_source_table": "tabela integrada",
|
||||
"lifetime_source_learned": "aprendido de {n} substituições",
|
||||
"lifetime_source_override": "a sua definição",
|
||||
"lifetime_source_learned_device": "aprendido de {n} substituições deste dispositivo",
|
||||
"lifetime_source_learned_model": "aprendido de {n} substituições em dispositivos deste modelo",
|
||||
"battery_fleet_predicted_trend": "Previsto pela tendência de descarga desta pilha: por volta de {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Recarregável: carrega-se em vez de se substituir — nunca na lista de compras",
|
||||
"battery_fleet_sort_name": "Ordenar por nome",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Peças usadas",
|
||||
"print_inc_photos": "Fotografias",
|
||||
"print_inc_documents": "Documentos associados",
|
||||
"print_inc_doc_desc": "Descrições dos documentos",
|
||||
"print_inc_checklist": "Lista de verificação",
|
||||
"print_inc_notes": "Notas",
|
||||
"print_inc_costs": "Custo e duração",
|
||||
"print_inc_person": "Concluído por",
|
||||
"print_inc_refs": "Números de referência",
|
||||
"print_inc_bare": "Conclusões sem detalhes",
|
||||
"print_inc_qr": "Código QR por tarefa",
|
||||
"print_button": "Imprimir",
|
||||
"report_scan_hint": "Digitalize para abrir a tarefa",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Время работы",
|
||||
"runtime_hours": "Целевое время работы (часы)",
|
||||
"target_value": "Целевое значение",
|
||||
"baseline": "Базовое значение",
|
||||
"target_changes": "Целевые изменения",
|
||||
"for_minutes": "На (минут)",
|
||||
"time_based": "По времени",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Создан",
|
||||
"report_times_done": "Выполнено",
|
||||
"report_total_cost": "Итого",
|
||||
"report_every": "каждые {n} {unit}",
|
||||
"report_notes": "Заметки",
|
||||
"report_col_type": "Тип",
|
||||
"report_col_status": "Статус",
|
||||
"report_col_schedule": "Расписание",
|
||||
"duration": "Длительность",
|
||||
"minutes_short": "мин",
|
||||
"both": "Оба",
|
||||
"trigger_val": "Значение триггера",
|
||||
"complete_title": "Выполнить: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: неверный тип (ожидался: {type})",
|
||||
"err_invalid_choice": "{field}: недопустимое значение",
|
||||
"err_invalid_value": "{field}: неверное значение",
|
||||
"ws_err_not_found": "Не найдено — возможно, уже удалено",
|
||||
"ws_err_invalid_input": "Недопустимый ввод",
|
||||
"ws_err_invalid_date": "Недопустимая дата",
|
||||
"ws_err_invalid_format": "Недопустимый формат",
|
||||
"ws_err_invalid_url": "Недопустимая ссылка",
|
||||
"ws_err_not_configured": "Ещё не настроено",
|
||||
"ws_err_not_loaded": "Ещё не загружено — попробуйте чуть позже",
|
||||
"ws_err_limit_reached": "Достигнут предел",
|
||||
"ws_err_too_many": "Слишком много записей",
|
||||
"ws_err_too_large": "Слишком большой размер",
|
||||
"ws_err_not_archived": "Не в архиве",
|
||||
"ws_err_invalid_target": "Недопустимая цель",
|
||||
"ws_err_create_failed": "Не удалось создать",
|
||||
"ws_err_archived": "В архиве — сначала восстановите",
|
||||
"ws_err_already_archived": "Уже в архиве",
|
||||
"ws_err_unavailable": "Сейчас недоступно",
|
||||
"ws_err_unauthorized": "Недоступно для вашего пользователя",
|
||||
"ws_err_too_many_views": "Слишком много сохранённых представлений",
|
||||
"ws_err_storage_unavailable": "Хранилище недоступно",
|
||||
"ws_err_replace_failed": "Не удалось заменить",
|
||||
"ws_err_not_paused": "Не на паузе",
|
||||
"ws_err_not_available": "Ничего нет",
|
||||
"ws_err_no_url": "Ссылка не указана",
|
||||
"ws_err_no_phases": "У этой задачи нет фаз",
|
||||
"ws_err_invalid_view": "Недопустимое представление",
|
||||
"ws_err_invalid_user": "Неизвестный пользователь",
|
||||
"ws_err_invalid_range": "Недопустимый диапазон",
|
||||
"ws_err_invalid_parent": "Недопустимый родительский объект",
|
||||
"ws_err_invalid_device": "Неизвестное устройство",
|
||||
"ws_err_empty_csv": "CSV-файл пуст",
|
||||
"ws_err_empty": "Нечего импортировать",
|
||||
"ws_err_duplicate_failed": "Не удалось дублировать",
|
||||
"ws_err_already_paused": "Уже на паузе",
|
||||
"ws_err_invalid_cursor": "Недопустимая позиция фазы",
|
||||
"ws_err_invalid_entity_slug": "Недопустимое имя сущности",
|
||||
"ws_err_invalid_trigger_config": "Недопустимая настройка триггера",
|
||||
"ws_err_no_defaults": "Не заданы значения по умолчанию для быстрого завершения",
|
||||
"ws_err_self_link_device": "Объект нельзя связать с самим собой",
|
||||
"ws_err_too_early": "Слишком рано — эту задачу можно завершить только ближе к сроку",
|
||||
"feat_schedule_time": "Планирование по времени дня",
|
||||
"feat_schedule_time_desc": "Задачи становятся просроченными в определённое время дня, а не в полночь.",
|
||||
"schedule_time_toggle": "Срок в определённое время",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Срабатывать при равенстве (=)",
|
||||
"trigger_not_equals": "Срабатывать при отличии от (≠)",
|
||||
"for_at_least_minutes": "Не менее (минут)",
|
||||
"safety_interval_days": "Интервал безопасности (дни, опционально)",
|
||||
"safety_interval": "Интервал безопасности (опционально)",
|
||||
"trigger_combinator": "Совместить триггер и интервал",
|
||||
"trigger_combinator_any": "Триггер или интервал (что раньше)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Мои задачи",
|
||||
"tab_calendar": "Календарь",
|
||||
"cal_no_events": "Без обслуживания",
|
||||
"cal_window_7": "7 дней",
|
||||
"cal_window_14": "14 дней",
|
||||
"cal_window_30": "30 дней",
|
||||
"cal_window_365": "1 год",
|
||||
"cal_every_n_days": "каждые {n} дней",
|
||||
"cal_source_time": "По времени",
|
||||
"cal_source_time_adaptive": "По времени (адаптивно)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Не удалось сгенерировать QR-код.",
|
||||
"qr_error_no_url": "URL HA не настроен. Установите внешний или внутренний URL в Настройках → Система → Сеть.",
|
||||
"save_error": "Не удалось сохранить. Попробуйте ещё раз.",
|
||||
"subsave_warning": "Сохранено, но одна настройка отклонена: {detail}",
|
||||
"qr_print": "Печать",
|
||||
"qr_download": "Скачать SVG",
|
||||
"qr_action": "Действие при сканировании",
|
||||
"qr_action_view": "Просмотр",
|
||||
"qr_action_complete": "Отметить обслуживание как выполненное",
|
||||
"qr_url_mode": "Тип ссылки",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Локальный (mDNS)",
|
||||
"qr_mode_server": "URL сервера",
|
||||
"overview": "Обзор",
|
||||
"analysis": "Анализ",
|
||||
"recent_activities": "Недавние действия",
|
||||
"search_notes": "Поиск по заметкам",
|
||||
"avg_cost": "Ср. стоимость",
|
||||
"no_advanced_features": "Расширенные функции не включены",
|
||||
"no_advanced_features_hint": "Включите «Адаптивные интервалы» или «Сезонные паттерны» в настройках интеграции, чтобы увидеть здесь аналитику.",
|
||||
"analysis_not_enough_data": "Недостаточно данных для анализа.",
|
||||
"analysis_not_enough_data_hint": "Для анализа Вейбулла требуется минимум 5 выполненных обслуживаний; сезонные паттерны становятся видны после 6+ точек данных в месяц.",
|
||||
"analysis_manual_task_hint": "Ручные задачи без интервала не генерируют аналитику.",
|
||||
"completions": "выполнений",
|
||||
"current": "Текущий",
|
||||
"shorter": "Короче",
|
||||
"longer": "Длиннее",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Прошлые окна",
|
||||
"cal_forward_windows": "Будущие окна",
|
||||
"history_edit_title": "Редактировать запись истории",
|
||||
"history_delete_entry": "Удалить запись",
|
||||
"history_delete_confirm": "Удалить эту запись истории? Дата последнего выполнения вернётся к предыдущему; фотографии останутся у объекта, а израсходованные детали не будут возвращены на склад.",
|
||||
"history_edit_timestamp": "Метка времени",
|
||||
"manufacturer": "Производитель",
|
||||
"model": "Модель",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Дни предупреждения по умолчанию",
|
||||
"settings_consumable_threshold": "Порог «расходник заканчивается» (%)",
|
||||
"settings_battery_low_percent": "Порог «батарея разряжена» (%)",
|
||||
"settings_battery_lifetimes": "Типичный срок службы батарей",
|
||||
"settings_battery_lifetimes_hint": "Используется только для батарей без процента: заметка Battery Notes лишь с типом и датой замены или датчик, сообщающий только «низкий». Срок = последняя замена + этот срок службы. Для батарей с уровнем прогноз строится по измеренному разряду. После трёх замен одного типа медианный интервал заменяет встроенное значение — ваше значение всегда главнее.",
|
||||
"settings_battery_lifetime_months": "мес.",
|
||||
"settings_battery_lifetime_reset": "По умолчанию",
|
||||
"settings_battery_lifetime_in_fleet": "в вашем парке",
|
||||
"settings_battery_lifetime_learned_models": "Изучено по моделям: {list}",
|
||||
"settings_thresholds_hint": "Значения по умолчанию для новых «Предлагаемых настроек» (остаток в процентах) и для батарей парка без собственного порога Battery Notes. Существующие задачи сохраняют своё значение.",
|
||||
"settings_value_out_of_range": "Значение должно быть от {min} до {max}",
|
||||
"bn_summary": "{name}: {pct} % для {n} батарей",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} устройств с собственным порогом:",
|
||||
"bn_more": "+ ещё {n}",
|
||||
"settings_row_actions": "Действия в строках задач",
|
||||
"settings_ref_numbers_in_lists": "Номера ссылок в списках",
|
||||
"settings_ref_numbers_in_lists_hint": "Показывать метки #8 / #8.3 перед названиями объектов и задач в списке «Сегодня», таблице задач, карточках объектов и таблице объектов.",
|
||||
"row_actions_buttons_compact": "Кнопки (на телефоне только значки)",
|
||||
"row_actions_buttons": "Кнопки с текстом",
|
||||
"row_actions_icons": "Значки (классический вид)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Длительность откладывания (часы)",
|
||||
"settings_budget": "Бюджет",
|
||||
"settings_currency": "Валюта",
|
||||
"settings_currency_decimals": "Знаков после запятой в суммах",
|
||||
"settings_currency_decimals_hint": "Сколько знаков после запятой показывает каждая сумма: KPI, бюджеты, расходы в списках и отчётах, оповещения о бюджете. 0 = целые числа.",
|
||||
"settings_budget_monthly": "Месячный бюджет",
|
||||
"settings_budget_yearly": "Годовой бюджет",
|
||||
"settings_budget_alerts": "Оповещения о бюджете",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Экспорт YAML",
|
||||
"settings_export_csv": "Экспорт CSV",
|
||||
"settings_export_settings": "Экспорт настроек (JSON)",
|
||||
"settings_import_csv": "Импорт CSV",
|
||||
"settings_import_placeholder": "Вставьте содержимое JSON или CSV здесь…",
|
||||
"settings_import_btn": "Импортировать",
|
||||
"settings_import_success": "Импортировано объектов: {count}.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "HA-действие по задаче при завершении + QR быстрого завершения с предустановленными значениями.",
|
||||
"on_complete_action_title": "При завершении: запустить HA-действие (опционально)",
|
||||
"on_complete_action_desc": "Вызывает HA-сервис при завершении задачи — напр., сбросить счётчик на устройстве.",
|
||||
"on_complete_action_service": "Сервис",
|
||||
"on_complete_action_target": "Целевая сущность",
|
||||
"on_complete_action_data": "Данные (JSON, опционально)",
|
||||
"on_complete_action_test": "Тестировать действие",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Выбрать фото",
|
||||
"choose_photo": "Выбрать фото",
|
||||
"photos_android_hint": "Приложение для Android передаёт по одному фото за выбор — каждый выбор добавляется.",
|
||||
"add_photos": "Добавить фото",
|
||||
"photos_limit": "До {max} фото на одно выполнение",
|
||||
"history_edit_photos_hint": "Удалённое здесь фото остаётся в документах объекта.",
|
||||
"uploading": "Загрузка…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Скачать",
|
||||
"doc_close": "Закрыть",
|
||||
"doc_camera": "Сделать фото",
|
||||
"camera_capture_shoot": "Снять",
|
||||
"camera_switch_lens": "Сменить камеру",
|
||||
"doc_drop_hint": "Перетащите файлы сюда",
|
||||
"doc_task_none": "Нет документов, связанных с этой задачей.",
|
||||
"doc_link_existing": "Связать документ…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Записывает выполнение (обновляет дату последнего обслуживания), когда триггер устраняется сам — например, соль досыпана, фильтр заменён.",
|
||||
"doc_search": "Поиск документов…",
|
||||
"doc_search_none": "Нет подходящих документов",
|
||||
"doc_description": "Описание (необязательно)",
|
||||
"doc_sort": "Сортировать документы",
|
||||
"doc_sort_newest": "Сначала новые",
|
||||
"doc_sort_oldest": "Сначала старые",
|
||||
"doc_sort_title": "По названию (1, 2, 3 …)",
|
||||
"doc_sort_category": "По категории",
|
||||
"link_device_optional": "Привязать к существующему устройству (необязательно)",
|
||||
"parent_object_optional": "Родительский объект (необязательно)",
|
||||
"parent_none": "(Без родителя)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Уведомлять только для представления",
|
||||
"settings_notify_scope_all": "Все задачи",
|
||||
"settings_notify_scope_hint": "Напоминания отправляют только задачи, совпадающие с фильтрами метки/пользователя выбранного представления. Статус, сортировка и группировка представления здесь игнорируются.",
|
||||
"settings_notify_completed": "Уведомления о выполнении",
|
||||
"settings_notify_completed_hint": "Сообщение для всех домашних, когда задача выполнена, с причиной и исполнителем. «Только автоматические» сообщает лишь о выполнениях, которые никто не делал на месте: восстановление датчика, отметка в списке покупок, вызов службы автоматизацией. Только на общий сервис; тихие часы и дневной лимит действуют.",
|
||||
"notify_completed_off": "Выкл.",
|
||||
"notify_completed_automatic": "Только автоматические выполнения",
|
||||
"notify_completed_all": "Все выполнения",
|
||||
"settings_notify_rule": "Своё правило уведомлений",
|
||||
"settings_notify_rule_hint": "Каждое уведомление дополнительно вызывает событие maintenance_supporter_notification (вид, статус, объект, задача, номера ссылок, приоритет, ссылка, данные). Направьте его автоматизацией — например в Ticker, Telegram или Pushover.",
|
||||
"settings_notify_event_only": "Только событие, ничего не отправлять",
|
||||
"settings_notify_event_only_hint": "Доставку целиком выполняет ваша автоматизация. Интервалы повтора, тихие часы и дневной лимит по-прежнему определяют, когда срабатывает событие.",
|
||||
"settings_notify_extra_data": "Дополнительные данные уведомления (шаблон)",
|
||||
"settings_notify_extra_data_hint": "JSON или YAML, Jinja разрешён; добавляется в data вызова notify (ваши ключи важнее). Переменные: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Проверьте кнопкой «Отправить тест».",
|
||||
"settings_notify_extra_data_hint": "JSON или YAML, Jinja разрешён; добавляется в data вызова notify (ваши ключи важнее). Переменные: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Проверьте кнопкой «Отправить тест».",
|
||||
"card_saved_view": "Сохранённое представление",
|
||||
"card_saved_view_none": "Нет",
|
||||
"card_saved_view_help": "Применяет фильтры статуса, пользователя и метки представления в дополнение к фильтрам выше. Сортировка и группировка представления — настройки отображения панели и на карточке не применяются.",
|
||||
"panel_card_tab": "Открывать на вкладке",
|
||||
"panel_card_tab_default": "Запомненная вкладка (как в панели)",
|
||||
"panel_card_view": "Открывать с сохранённым видом",
|
||||
"panel_card_height": "Высота",
|
||||
"panel_card_height_help": "Пусто = заполнить экран под карточкой (вид «панель»). Иначе длина CSS, например 600px или 80vh, для видов с другими карточками.",
|
||||
"panel_card_not_registered": "Панель Maintenance Supporter недоступна — интеграция настроена?",
|
||||
"panel_card_load_failed": "Не удалось загрузить панель Maintenance Supporter — перезагрузите страницу (после обновления очистите кэш браузера).",
|
||||
"doc_part_none": "К этой запчасти не привязаны документы.",
|
||||
"settings_templates_toggle_group": "Включить или выключить все шаблоны этой группы",
|
||||
"setups_button": "Предлагаемые настройки",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "В норме",
|
||||
"battery_fleet_status_due": "Пора менять",
|
||||
"battery_fleet_predicted_on": "Ожидается примерно {date}",
|
||||
"battery_fleet_predicted_typical": "Ожидается около {date} — {months} мес. типично для {type} ({source})",
|
||||
"lifetime_source_default": "встроенное значение по умолчанию",
|
||||
"lifetime_source_table": "встроенная таблица",
|
||||
"lifetime_source_learned": "по {n} заменам",
|
||||
"lifetime_source_override": "ваша настройка",
|
||||
"lifetime_source_learned_device": "по {n} заменам этого устройства",
|
||||
"lifetime_source_learned_model": "по {n} заменам на устройствах этой модели",
|
||||
"battery_fleet_predicted_trend": "Прогноз по тренду разряда этой батареи: примерно {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Аккумулятор: заряжается, а не заменяется — никогда не попадает в список покупок",
|
||||
"battery_fleet_sort_name": "Сортировать по имени",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Использованные детали",
|
||||
"print_inc_photos": "Фото",
|
||||
"print_inc_documents": "Связанные документы",
|
||||
"print_inc_doc_desc": "Описания документов",
|
||||
"print_inc_checklist": "Чек-лист",
|
||||
"print_inc_notes": "Заметки",
|
||||
"print_inc_costs": "Стоимость и длительность",
|
||||
"print_inc_person": "Выполнил",
|
||||
"print_inc_refs": "Номера ссылок",
|
||||
"print_inc_bare": "Выполнения без подробностей",
|
||||
"print_inc_qr": "QR-код для каждой задачи",
|
||||
"print_button": "Печать",
|
||||
"report_scan_hint": "Отсканируйте, чтобы открыть задачу",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Körtid",
|
||||
"runtime_hours": "Måltid (timmar)",
|
||||
"target_value": "Målvärde",
|
||||
"baseline": "Baslinje",
|
||||
"target_changes": "Antal målförändringar",
|
||||
"for_minutes": "Under (minuter)",
|
||||
"time_based": "Tidsbaserad",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Genererad",
|
||||
"report_times_done": "Utfört",
|
||||
"report_total_cost": "Total kostnad",
|
||||
"report_every": "var {n}:e {unit}",
|
||||
"report_notes": "Anteckningar",
|
||||
"report_col_type": "Typ",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Schema",
|
||||
"duration": "Varaktighet",
|
||||
"minutes_short": "min",
|
||||
"both": "Båda",
|
||||
"trigger_val": "Utlösarvärde",
|
||||
"complete_title": "Slutför: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: fel typ (förväntad: {type})",
|
||||
"err_invalid_choice": "{field}: ej tillåtet värde",
|
||||
"err_invalid_value": "{field}: ogiltigt värde",
|
||||
"ws_err_not_found": "Hittades inte — det kan ha tagits bort under tiden",
|
||||
"ws_err_invalid_input": "Ogiltig inmatning",
|
||||
"ws_err_invalid_date": "Ogiltigt datum",
|
||||
"ws_err_invalid_format": "Ogiltigt format",
|
||||
"ws_err_invalid_url": "Ogiltig länk",
|
||||
"ws_err_not_configured": "Inte inställt ännu",
|
||||
"ws_err_not_loaded": "Inte inläst ännu — försök igen om en stund",
|
||||
"ws_err_limit_reached": "Gränsen nådd",
|
||||
"ws_err_too_many": "För många poster",
|
||||
"ws_err_too_large": "För stort",
|
||||
"ws_err_not_archived": "Inte arkiverad",
|
||||
"ws_err_invalid_target": "Ogiltigt mål",
|
||||
"ws_err_create_failed": "Kunde inte skapas",
|
||||
"ws_err_archived": "Arkiverad — återställ först",
|
||||
"ws_err_already_archived": "Redan arkiverad",
|
||||
"ws_err_unavailable": "För närvarande otillgänglig",
|
||||
"ws_err_unauthorized": "Inte tillåtet för din användare",
|
||||
"ws_err_too_many_views": "För många sparade vyer",
|
||||
"ws_err_storage_unavailable": "Lagringen är inte tillgänglig",
|
||||
"ws_err_replace_failed": "Ersättningen misslyckades",
|
||||
"ws_err_not_paused": "Inte pausad",
|
||||
"ws_err_not_available": "Inget tillgängligt",
|
||||
"ws_err_no_url": "Ingen länk angiven",
|
||||
"ws_err_no_phases": "Uppgiften har inga faser",
|
||||
"ws_err_invalid_view": "Ogiltig vy",
|
||||
"ws_err_invalid_user": "Okänd användare",
|
||||
"ws_err_invalid_range": "Ogiltigt intervall",
|
||||
"ws_err_invalid_parent": "Ogiltigt överordnat objekt",
|
||||
"ws_err_invalid_device": "Okänd enhet",
|
||||
"ws_err_empty_csv": "CSV-filen är tom",
|
||||
"ws_err_empty": "Inget att importera",
|
||||
"ws_err_duplicate_failed": "Kunde inte dupliceras",
|
||||
"ws_err_already_paused": "Redan pausad",
|
||||
"ws_err_invalid_cursor": "Ogiltig fasposition",
|
||||
"ws_err_invalid_entity_slug": "Ogiltigt entitetsnamn",
|
||||
"ws_err_invalid_trigger_config": "Ogiltig utlösarkonfiguration",
|
||||
"ws_err_no_defaults": "Inga standardvärden för snabbslutförande",
|
||||
"ws_err_self_link_device": "Ett objekt kan inte länkas till sig självt",
|
||||
"ws_err_too_early": "För tidigt — uppgiften kan bara slutföras närmare förfallodatumet",
|
||||
"feat_schedule_time": "Schemaläggning per tid på dygnet",
|
||||
"feat_schedule_time_desc": "Uppgifter blir försenade vid en specifik tid på dygnet istället för midnatt.",
|
||||
"schedule_time_toggle": "Förfaller vid en viss tid",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Utlös vid lika med (=)",
|
||||
"trigger_not_equals": "Utlös vid skilt från (≠)",
|
||||
"for_at_least_minutes": "Under minst (minuter)",
|
||||
"safety_interval_days": "Säkerhetsintervall (dagar, valfritt)",
|
||||
"safety_interval": "Säkerhetsintervall (valfritt)",
|
||||
"trigger_combinator": "Kombinera utlösare och intervall",
|
||||
"trigger_combinator_any": "Utlösare eller intervall (först uppfylld)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Mina uppgifter",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Inget underhåll",
|
||||
"cal_window_7": "7 dagar",
|
||||
"cal_window_14": "14 dagar",
|
||||
"cal_window_30": "30 dagar",
|
||||
"cal_window_365": "1 år",
|
||||
"cal_every_n_days": "var {n} dag",
|
||||
"cal_source_time": "Tidsbaserad",
|
||||
"cal_source_time_adaptive": "Tidsbaserad (adaptiv)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Kunde inte generera QR-kod.",
|
||||
"qr_error_no_url": "Ingen HA-URL konfigurerad. Ange en extern eller intern URL i Inställningar → System → Nätverk.",
|
||||
"save_error": "Kunde inte spara. Försök igen.",
|
||||
"subsave_warning": "Sparat, men en inställning avvisades: {detail}",
|
||||
"qr_print": "Skriv ut",
|
||||
"qr_download": "Ladda ner SVG",
|
||||
"qr_action": "Åtgärd vid skanning",
|
||||
"qr_action_view": "Visa underhållsinformation",
|
||||
"qr_action_complete": "Markera underhåll som slutfört",
|
||||
"qr_url_mode": "Länktyp",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Lokal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Översikt",
|
||||
"analysis": "Analys",
|
||||
"recent_activities": "Senaste aktiviteter",
|
||||
"search_notes": "Sök i anteckningar",
|
||||
"avg_cost": "Snittlig kostnad",
|
||||
"no_advanced_features": "Inga avancerade funktioner aktiverade",
|
||||
"no_advanced_features_hint": "Aktivera „Adaptiva intervall” eller „Säsongsmönster” i integrationsinställningar för att se analysdata här.",
|
||||
"analysis_not_enough_data": "Inte tillräckligt med data för analys ännu.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analys kräver minst 5 slutförda underhåll; säsongsmönster blir synliga efter 6+ datapunkter per månad.",
|
||||
"analysis_manual_task_hint": "Manuella uppgifter utan intervall genererar inte analysdata.",
|
||||
"completions": "slutföranden",
|
||||
"current": "Aktuell",
|
||||
"shorter": "Kortare",
|
||||
"longer": "Längre",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Tidigare fönster",
|
||||
"cal_forward_windows": "Kommande fönster",
|
||||
"history_edit_title": "Redigera historikpost",
|
||||
"history_delete_entry": "Ta bort post",
|
||||
"history_delete_confirm": "Ta bort den här historikposten? Datumet för senaste utförande faller tillbaka till det föregående; foton stannar hos objektet och förbrukade delar läggs inte tillbaka i lager.",
|
||||
"history_edit_timestamp": "Tidsstämpel",
|
||||
"manufacturer": "Tillverkare",
|
||||
"model": "Modell",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Standard varningsdagar",
|
||||
"settings_consumable_threshold": "Tröskel „förbrukningsartikel låg“ (%)",
|
||||
"settings_battery_low_percent": "Tröskel „batteri lågt“ (%)",
|
||||
"settings_battery_lifetimes": "Typiska batterilivslängder",
|
||||
"settings_battery_lifetimes_hint": "Används bara för batterier utan procent: en Battery Notes-anteckning med bara typ och bytesdatum, eller en sensor som bara rapporterar ”lågt”. Deras förfallodatum är senaste byte + denna livslängd. Batterier som rapporterar nivå får sin prognos från den uppmätta urladdningen. När flottan sett tre byten av en typ tar medianintervallet över från det inbyggda värdet — ditt eget värde vinner alltid.",
|
||||
"settings_battery_lifetime_months": "månader",
|
||||
"settings_battery_lifetime_reset": "Använd standard",
|
||||
"settings_battery_lifetime_in_fleet": "i din flotta",
|
||||
"settings_battery_lifetime_learned_models": "Inlärt per modell: {list}",
|
||||
"settings_thresholds_hint": "Standardvärden för nya Föreslagna inställningar (återstående procent) och för flottans batterier utan egen Battery Notes-tröskel. Befintliga uppgifter behåller sitt värde.",
|
||||
"settings_value_out_of_range": "Värdet måste vara mellan {min} och {max}",
|
||||
"bn_summary": "{name}: {pct} % för {n} batterier",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} enheter med egen tröskel:",
|
||||
"bn_more": "+ {n} till",
|
||||
"settings_row_actions": "Åtgärder i uppgiftsrader",
|
||||
"settings_ref_numbers_in_lists": "Referensnummer i listor",
|
||||
"settings_ref_numbers_in_lists_hint": "Visar brickorna #8 / #8.3 före objekt- och uppgiftsnamn i Idag-listan, uppgiftstabellen, objektkorten och objekttabellen.",
|
||||
"row_actions_buttons_compact": "Knappar (endast ikoner på mobilen)",
|
||||
"row_actions_buttons": "Knappar med text",
|
||||
"row_actions_icons": "Ikoner (klassiskt)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Snooza-tid (timmar)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Decimaler för belopp",
|
||||
"settings_currency_decimals_hint": "Hur många decimaler varje belopp visar: nyckeltal, budgetar, kostnader i listor och rapporter, budgetvarningar. 0 = hela tal.",
|
||||
"settings_budget_monthly": "Månatlig budget",
|
||||
"settings_budget_yearly": "Årlig budget",
|
||||
"settings_budget_alerts": "Budgetvarningar",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exportera YAML",
|
||||
"settings_export_csv": "Exportera CSV",
|
||||
"settings_export_settings": "Exportera inställningar (JSON)",
|
||||
"settings_import_csv": "Importera CSV",
|
||||
"settings_import_placeholder": "Klistra in JSON- eller CSV-innehåll här…",
|
||||
"settings_import_btn": "Importera",
|
||||
"settings_import_success": "{count} objekt importerade.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "HA-åtgärd per uppgift vid slutförande + snabb-slutför-QR med förinställda värden.",
|
||||
"on_complete_action_title": "Vid slutförande: utlös HA-åtgärd (valfritt)",
|
||||
"on_complete_action_desc": "Anropar en HA-tjänst när uppgiften slutförs — t.ex. återställ en räknare på enheten.",
|
||||
"on_complete_action_service": "Tjänst",
|
||||
"on_complete_action_target": "Målentitet",
|
||||
"on_complete_action_data": "Data (JSON, valfritt)",
|
||||
"on_complete_action_test": "Testa åtgärd",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Välj foton",
|
||||
"choose_photo": "Välj foto",
|
||||
"photos_android_hint": "Android-appen lämnar över ett foto per val — varje val läggs till.",
|
||||
"add_photos": "Lägg till foton",
|
||||
"photos_limit": "Högst {max} foton per slutförande",
|
||||
"history_edit_photos_hint": "Ett foto som tas bort här finns kvar bland objektets dokument.",
|
||||
"uploading": "Laddar upp…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Ladda ner",
|
||||
"doc_close": "Stäng",
|
||||
"doc_camera": "Ta foto",
|
||||
"camera_capture_shoot": "Ta bild",
|
||||
"camera_switch_lens": "Byt kamera",
|
||||
"doc_drop_hint": "Släpp filer här",
|
||||
"doc_task_none": "Inga dokument kopplade till denna uppgift.",
|
||||
"doc_link_existing": "Koppla ett dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registrerar ett slutförande (sätter senast utförd) när utlösaren löser sig själv — t.ex. salt påfyllt, filter bytt.",
|
||||
"doc_search": "Sök dokument…",
|
||||
"doc_search_none": "Inga matchande dokument",
|
||||
"doc_description": "Beskrivning (valfritt)",
|
||||
"doc_sort": "Sortera dokument",
|
||||
"doc_sort_newest": "Nyaste först",
|
||||
"doc_sort_oldest": "Äldsta först",
|
||||
"doc_sort_title": "Efter titel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Efter kategori",
|
||||
"link_device_optional": "Länka till befintlig enhet (valfritt)",
|
||||
"parent_object_optional": "Överordnat objekt (valfritt)",
|
||||
"parent_none": "(Inget överordnat)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Avisera endast för vy",
|
||||
"settings_notify_scope_all": "Alla uppgifter",
|
||||
"settings_notify_scope_hint": "Endast uppgifter som matchar den valda vyns etikett-/användarfilter skickar påminnelser. Vyns status, sortering och gruppering ignoreras här.",
|
||||
"settings_notify_completed": "Aviseringar om slutförande",
|
||||
"settings_notify_completed_hint": "Ett meddelande till hushållet när en uppgift slutförts, med orsak och vem som gjorde det. \"Endast automatiska\" meddelar bara slutföranden som ingen gjorde på plats: sensoråterhämtning, avbockning i inköpslistan, ett tjänsteanrop från en automation. Bara till hushållstjänsten; tysta timmar och dagsgränsen gäller.",
|
||||
"notify_completed_off": "Av",
|
||||
"notify_completed_automatic": "Endast automatiska slutföranden",
|
||||
"notify_completed_all": "Alla slutföranden",
|
||||
"settings_notify_rule": "Din egen aviseringsregel",
|
||||
"settings_notify_rule_hint": "Varje avisering utlöser även händelsen maintenance_supporter_notification (typ, status, objekt, uppgift, referensnummer, prioritet, länk, nyttolast). Styr den med en automation — t.ex. till Ticker, Telegram eller Pushover.",
|
||||
"settings_notify_event_only": "Utlös bara händelsen, skicka inget själv",
|
||||
"settings_notify_event_only_hint": "Din automation sköter hela leveransen. Upprepningsintervall, tysta timmar och dagstaket avgör fortfarande när händelsen utlöses.",
|
||||
"settings_notify_extra_data": "Extra aviseringsdata (mall)",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillåtet; slås ihop med notify-anropets data (dina nycklar vinner). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Testa med knappen Skicka test.",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillåtet; slås ihop med notify-anropets data (dina nycklar vinner). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Testa med knappen Skicka test.",
|
||||
"card_saved_view": "Sparad vy",
|
||||
"card_saved_view_none": "Ingen",
|
||||
"card_saved_view_help": "Tillämpar vyns status-, användar- och etikettfilter utöver filtren ovan. Vyns sortering och gruppering är panelvisningsinställningar och tillämpas inte på kortet.",
|
||||
"panel_card_tab": "Öppna på fliken",
|
||||
"panel_card_tab_default": "Ihågkommen flik (som i panelen)",
|
||||
"panel_card_view": "Öppna med sparad vy",
|
||||
"panel_card_height": "Höjd",
|
||||
"panel_card_height_help": "Tomt = fyller skärmen under kortet (panelvy). Annars en CSS-längd som 600px eller 80vh för vyer med andra kort.",
|
||||
"panel_card_not_registered": "Maintenance Supporter-panelen är inte tillgänglig — är integrationen konfigurerad?",
|
||||
"panel_card_load_failed": "Maintenance Supporter-panelen kunde inte läsas in — ladda om sidan (efter en uppdatering: rensa webbläsarens cache).",
|
||||
"doc_part_none": "Inga dokument länkade till denna del.",
|
||||
"settings_templates_toggle_group": "Aktivera eller inaktivera alla mallar i denna grupp",
|
||||
"setups_button": "Föreslagna uppsättningar",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "I ordning",
|
||||
"battery_fleet_status_due": "Dags",
|
||||
"battery_fleet_predicted_on": "Väntas omkring {date}",
|
||||
"battery_fleet_predicted_typical": "Väntas omkring {date} — {months} månader typiskt för {type} ({source})",
|
||||
"lifetime_source_default": "inbyggt standardvärde",
|
||||
"lifetime_source_table": "inbyggd tabell",
|
||||
"lifetime_source_learned": "inlärt från {n} byten",
|
||||
"lifetime_source_override": "din inställning",
|
||||
"lifetime_source_learned_device": "inlärt från {n} byten på den här enheten",
|
||||
"lifetime_source_learned_model": "inlärt från {n} byten på enheter av den här modellen",
|
||||
"battery_fleet_predicted_trend": "Förutspått utifrån batteriets urladdningstrend: omkring {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Uppladdningsbart: ladda i stället för att byta — aldrig på inköpslistan",
|
||||
"battery_fleet_sort_name": "Sortera efter namn",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Använda delar",
|
||||
"print_inc_photos": "Foton",
|
||||
"print_inc_documents": "Länkade dokument",
|
||||
"print_inc_doc_desc": "Dokumentbeskrivningar",
|
||||
"print_inc_checklist": "Checklista",
|
||||
"print_inc_notes": "Anteckningar",
|
||||
"print_inc_costs": "Kostnad och tid",
|
||||
"print_inc_person": "Utförd av",
|
||||
"print_inc_refs": "Referensnummer",
|
||||
"print_inc_bare": "Utföranden utan detaljer",
|
||||
"print_inc_qr": "QR-kod per uppgift",
|
||||
"print_button": "Skriv ut",
|
||||
"report_scan_hint": "Skanna för att öppna uppgiften",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Çalışma süresi",
|
||||
"runtime_hours": "Hedef çalışma süresi (saat)",
|
||||
"target_value": "Hedef değer",
|
||||
"baseline": "Başlangıç değeri",
|
||||
"target_changes": "Hedef değişiklik sayısı",
|
||||
"for_minutes": "Süre (dakika)",
|
||||
"time_based": "Zamana dayalı",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Oluşturulma",
|
||||
"report_times_done": "Yapıldı",
|
||||
"report_total_cost": "Toplam maliyet",
|
||||
"report_every": "her {n} {unit}",
|
||||
"report_notes": "Notlar",
|
||||
"report_col_type": "Tür",
|
||||
"report_col_status": "Durum",
|
||||
"report_col_schedule": "Zamanlama",
|
||||
"duration": "Süre",
|
||||
"minutes_short": "dk",
|
||||
"both": "Her ikisi",
|
||||
"trigger_val": "Tetikleme değeri",
|
||||
"complete_title": "Tamamla: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: yanlış tür (beklenen: {type})",
|
||||
"err_invalid_choice": "{field}: izin verilen bir değer değil",
|
||||
"err_invalid_value": "{field}: geçersiz değer",
|
||||
"ws_err_not_found": "Bulunamadı — bu arada silinmiş olabilir",
|
||||
"ws_err_invalid_input": "Geçersiz giriş",
|
||||
"ws_err_invalid_date": "Geçersiz tarih",
|
||||
"ws_err_invalid_format": "Geçersiz biçim",
|
||||
"ws_err_invalid_url": "Geçersiz bağlantı",
|
||||
"ws_err_not_configured": "Henüz ayarlanmadı",
|
||||
"ws_err_not_loaded": "Henüz yüklenmedi — birazdan tekrar deneyin",
|
||||
"ws_err_limit_reached": "Sınıra ulaşıldı",
|
||||
"ws_err_too_many": "Çok fazla kayıt",
|
||||
"ws_err_too_large": "Çok büyük",
|
||||
"ws_err_not_archived": "Arşivlenmemiş",
|
||||
"ws_err_invalid_target": "Geçersiz hedef",
|
||||
"ws_err_create_failed": "Oluşturulamadı",
|
||||
"ws_err_archived": "Arşivlenmiş — önce geri yükleyin",
|
||||
"ws_err_already_archived": "Zaten arşivlenmiş",
|
||||
"ws_err_unavailable": "Şu anda kullanılamıyor",
|
||||
"ws_err_unauthorized": "Kullanıcınız için izin verilmiyor",
|
||||
"ws_err_too_many_views": "Çok fazla kayıtlı görünüm",
|
||||
"ws_err_storage_unavailable": "Depolama kullanılamıyor",
|
||||
"ws_err_replace_failed": "Değiştirme başarısız",
|
||||
"ws_err_not_paused": "Duraklatılmamış",
|
||||
"ws_err_not_available": "Kullanılabilir bir şey yok",
|
||||
"ws_err_no_url": "Bağlantı verilmedi",
|
||||
"ws_err_no_phases": "Bu görevin aşaması yok",
|
||||
"ws_err_invalid_view": "Geçersiz görünüm",
|
||||
"ws_err_invalid_user": "Bilinmeyen kullanıcı",
|
||||
"ws_err_invalid_range": "Geçersiz aralık",
|
||||
"ws_err_invalid_parent": "Geçersiz üst nesne",
|
||||
"ws_err_invalid_device": "Bilinmeyen cihaz",
|
||||
"ws_err_empty_csv": "CSV dosyası boş",
|
||||
"ws_err_empty": "İçe aktarılacak bir şey yok",
|
||||
"ws_err_duplicate_failed": "Çoğaltılamadı",
|
||||
"ws_err_already_paused": "Zaten duraklatılmış",
|
||||
"ws_err_invalid_cursor": "Geçersiz aşama konumu",
|
||||
"ws_err_invalid_entity_slug": "Geçersiz varlık adı",
|
||||
"ws_err_invalid_trigger_config": "Geçersiz tetikleyici yapılandırması",
|
||||
"ws_err_no_defaults": "Hızlı tamamlama varsayılanları ayarlanmamış",
|
||||
"ws_err_self_link_device": "Bir nesne kendisine bağlanamaz",
|
||||
"ws_err_too_early": "Çok erken — bu görev ancak son tarihine yaklaşınca tamamlanabilir",
|
||||
"feat_schedule_time": "Gün içi saat zamanlaması",
|
||||
"feat_schedule_time_desc": "Görevler gece yarısı yerine günün belirli bir saatinde gecikmiş olur.",
|
||||
"schedule_time_toggle": "Belirli bir saatte vadesi dolar",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Şuna eşitse tetikle (=)",
|
||||
"trigger_not_equals": "Şundan farklıysa tetikle (≠)",
|
||||
"for_at_least_minutes": "En az (dakika)",
|
||||
"safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)",
|
||||
"safety_interval": "Güvenlik aralığı (isteğe bağlı)",
|
||||
"trigger_combinator": "Tetikleyici ve aralığı birleştir",
|
||||
"trigger_combinator_any": "Tetikleyici veya aralık (ilk gerçekleşen)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Görevlerim",
|
||||
"tab_calendar": "Takvim",
|
||||
"cal_no_events": "Bakım yok",
|
||||
"cal_window_7": "7 gün",
|
||||
"cal_window_14": "14 gün",
|
||||
"cal_window_30": "30 gün",
|
||||
"cal_window_365": "1 yıl",
|
||||
"cal_every_n_days": "her {n} günde bir",
|
||||
"cal_source_time": "Zamana dayalı",
|
||||
"cal_source_time_adaptive": "Zamana dayalı (uyarlanabilir)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR kodu oluşturulamadı.",
|
||||
"qr_error_no_url": "Yapılandırılmış bir HA URL'si yok. Lütfen Ayarlar → Sistem → Ağ bölümünden bir dış veya iç URL ayarlayın.",
|
||||
"save_error": "Kaydedilemedi. Lütfen tekrar deneyin.",
|
||||
"subsave_warning": "Kaydedildi, ancak bir ayar reddedildi: {detail}",
|
||||
"qr_print": "Yazdır",
|
||||
"qr_download": "SVG indir",
|
||||
"qr_action": "Tarama eylemi",
|
||||
"qr_action_view": "Bakım bilgisini görüntüle",
|
||||
"qr_action_complete": "Bakımı tamamlandı olarak işaretle",
|
||||
"qr_url_mode": "Bağlantı türü",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Yerel (mDNS)",
|
||||
"qr_mode_server": "Sunucu URL'si",
|
||||
"overview": "Genel Bakış",
|
||||
"analysis": "Analiz",
|
||||
"recent_activities": "Son Etkinlikler",
|
||||
"search_notes": "Notlarda ara",
|
||||
"avg_cost": "Ort. Maliyet",
|
||||
"no_advanced_features": "Etkin gelişmiş özellik yok",
|
||||
"no_advanced_features_hint": "Burada analiz verilerini görmek için entegrasyon ayarlarından “Uyarlanabilir Aralıklar” veya “Mevsimsel Desenler” özelliğini etkinleştirin.",
|
||||
"analysis_not_enough_data": "Analiz için henüz yeterli veri yok.",
|
||||
"analysis_not_enough_data_hint": "Weibull analizi en az 5 tamamlanmış bakım gerektirir; mevsimsel desenler ay başına 6+ veri noktasından sonra görünür olur.",
|
||||
"analysis_manual_task_hint": "Aralığı olmayan manuel görevler analiz verisi üretmez.",
|
||||
"completions": "tamamlama",
|
||||
"current": "Mevcut",
|
||||
"shorter": "Daha kısa",
|
||||
"longer": "Daha uzun",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Geçmiş dönemler",
|
||||
"cal_forward_windows": "Gelecek dönemler",
|
||||
"history_edit_title": "Geçmiş kaydını düzenle",
|
||||
"history_delete_entry": "Kaydı sil",
|
||||
"history_delete_confirm": "Bu geçmiş kaydı silinsin mi? Son yapılma tarihi bir önceki tamamlamaya döner; fotoğraflar nesnede kalır, kullanılan parçalar stoğa geri eklenmez.",
|
||||
"history_edit_timestamp": "Zaman damgası",
|
||||
"manufacturer": "Üretici",
|
||||
"model": "Model",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Varsayılan uyarı günleri",
|
||||
"settings_consumable_threshold": "„Sarf malzemesi düşük“ eşiği (%)",
|
||||
"settings_battery_low_percent": "„Pil zayıf“ eşiği (%)",
|
||||
"settings_battery_lifetimes": "Tipik pil ömürleri",
|
||||
"settings_battery_lifetimes_hint": "Yalnızca yüzdesi olmayan piller için kullanılır: sadece tür ve değişim tarihi olan bir Battery Notes notu ya da yalnızca \"düşük\" bildiren bir sensör. Vade = son değişim + bu ömür. Seviye bildiren pillerin tahmini ölçülen deşarjdan gelir. Filo bir türden üç değişim gördüğünde ortanca aralık yerleşik değerin yerini alır — kendi değerin her zaman kazanır.",
|
||||
"settings_battery_lifetime_months": "ay",
|
||||
"settings_battery_lifetime_reset": "Varsayılanı kullan",
|
||||
"settings_battery_lifetime_in_fleet": "filonda",
|
||||
"settings_battery_lifetime_learned_models": "Modele göre öğrenilen: {list}",
|
||||
"settings_thresholds_hint": "Yeni Önerilen kurulumlar (kalan yüzde) ve kendi Battery Notes eşiği olmayan filo pilleri için varsayılanlar. Mevcut görevler değerlerini korur.",
|
||||
"settings_value_out_of_range": "Değer {min} ile {max} arasında olmalıdır",
|
||||
"bn_summary": "{name}: {n} pil için % {pct}",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "Kendi eşiği olan {n} cihaz:",
|
||||
"bn_more": "+ {n} tane daha",
|
||||
"settings_row_actions": "Görev satırı eylemleri",
|
||||
"settings_ref_numbers_in_lists": "Listelerde referans numaraları",
|
||||
"settings_ref_numbers_in_lists_hint": "Bugün listesinde, görev tablosunda, nesne kartlarında ve nesne tablosunda nesne ve görev adlarının önünde #8 / #8.3 etiketlerini gösterir.",
|
||||
"row_actions_buttons_compact": "Düğmeler (telefonda yalnızca simgeler)",
|
||||
"row_actions_buttons": "Metinli düğmeler",
|
||||
"row_actions_icons": "Simgeler (klasik)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Erteleme süresi (saat)",
|
||||
"settings_budget": "Bütçe",
|
||||
"settings_currency": "Para birimi",
|
||||
"settings_currency_decimals": "Tutarlarda ondalık basamak",
|
||||
"settings_currency_decimals_hint": "Her tutarın kaç ondalık basamak göstereceği: KPI'lar, bütçeler, liste ve raporlardaki maliyetler, bütçe uyarıları. 0 = tam sayılar.",
|
||||
"settings_budget_monthly": "Aylık bütçe",
|
||||
"settings_budget_yearly": "Yıllık bütçe",
|
||||
"settings_budget_alerts": "Bütçe uyarıları",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML dışa aktar",
|
||||
"settings_export_csv": "CSV dışa aktar",
|
||||
"settings_export_settings": "Ayarları dışa aktar (JSON)",
|
||||
"settings_import_csv": "CSV içe aktar",
|
||||
"settings_import_placeholder": "JSON veya CSV içeriğini buraya yapıştırın…",
|
||||
"settings_import_btn": "İçe aktar",
|
||||
"settings_import_success": "{count} nesne başarıyla içe aktarıldı.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "Görev başına tamamlamada HA eylemi + önceden ayarlı değerlerle hızlı tamamlama QR'ı.",
|
||||
"on_complete_action_title": "Tamamlandığında: HA eylemi tetikle (isteğe bağlı)",
|
||||
"on_complete_action_desc": "Görev tamamlandığında bir HA servisi çağırır — örn. cihazdaki bir sayacı sıfırlar.",
|
||||
"on_complete_action_service": "Servis",
|
||||
"on_complete_action_target": "Hedef varlık",
|
||||
"on_complete_action_target_hint": "Not: varlık alan adı servisle eşleşmelidir — örn. 'button.press' yalnızca button.* üzerinde, 'counter.increment' yalnızca counter.* üzerinde, 'input_button.press' yalnızca input_button.* üzerinde çalışır. Eşleşmezse eylem sessizce başarısız olur (HA günlüğü: 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Veri (JSON, isteğe bağlı)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "Fotoğraf seç",
|
||||
"choose_photo": "Fotoğraf seç",
|
||||
"photos_android_hint": "Android uygulaması her seçimde bir fotoğraf iletir — her seçim eklenir.",
|
||||
"add_photos": "Fotoğraf ekle",
|
||||
"photos_limit": "Tamamlama başına en fazla {max} fotoğraf",
|
||||
"history_edit_photos_hint": "Burada kaldırılan fotoğraf nesnenin belgelerinde kalır.",
|
||||
"uploading": "Yükleniyor…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "İndir",
|
||||
"doc_close": "Kapat",
|
||||
"doc_camera": "Fotoğraf çek",
|
||||
"camera_capture_shoot": "Çek",
|
||||
"camera_switch_lens": "Kamerayı değiştir",
|
||||
"doc_drop_hint": "Dosyaları buraya bırakın",
|
||||
"doc_task_none": "Bu göreve bağlı belge yok.",
|
||||
"doc_link_existing": "Belge bağla…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "Tetikleyici kendiliğinden temizlendiğinde bir tamamlama kaydeder (son yapılmayı ayarlar) — örn. tuz dolduruldu, filtre değiştirildi.",
|
||||
"doc_search": "Belgelerde ara…",
|
||||
"doc_search_none": "Eşleşen belge yok",
|
||||
"doc_description": "Açıklama (isteğe bağlı)",
|
||||
"doc_sort": "Belgeleri sırala",
|
||||
"doc_sort_newest": "En yeni önce",
|
||||
"doc_sort_oldest": "En eski önce",
|
||||
"doc_sort_title": "Başlığa göre (1, 2, 3 …)",
|
||||
"doc_sort_category": "Kategoriye göre",
|
||||
"link_device_optional": "Mevcut bir cihaza bağla (isteğe bağlı)",
|
||||
"parent_object_optional": "Üst nesne (isteğe bağlı)",
|
||||
"parent_none": "(Üst nesne yok)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Yalnızca şu görünüm için bildir",
|
||||
"settings_notify_scope_all": "Tüm görevler",
|
||||
"settings_notify_scope_hint": "Yalnızca seçili kayıtlı görünümün etiket/kullanıcı filtreleriyle eşleşen görevler hatırlatma gönderir. Görünümün durum, sıralama ve gruplaması burada dikkate alınmaz.",
|
||||
"settings_notify_completed": "Tamamlama bildirimleri",
|
||||
"settings_notify_completed_hint": "Bir görev tamamlandığında ev halkına nedeni ve kimin yaptığıyla birlikte haber verir. \"Yalnızca otomatik\" sadece kimsenin yerinde yapmadığı tamamlamaları bildirir: sensör toparlanması, alışveriş listesinde işaretleme, bir otomasyonun servis çağrısı. Yalnızca ev servisine gider; sessiz saatler ve günlük sınır geçerlidir.",
|
||||
"notify_completed_off": "Kapalı",
|
||||
"notify_completed_automatic": "Yalnızca otomatik tamamlamalar",
|
||||
"notify_completed_all": "Tüm tamamlamalar",
|
||||
"settings_notify_rule": "Kendi bildirim kuralınız",
|
||||
"settings_notify_rule_hint": "Her bildirim ayrıca maintenance_supporter_notification olayını tetikler (tür, durum, nesne, görev, referans numaraları, öncelik, derin bağlantı, yük). Bir otomasyonla yönlendirin — ör. Ticker, Telegram veya Pushover'a.",
|
||||
"settings_notify_event_only": "Yalnızca olayı tetikle, kendin gönderme",
|
||||
"settings_notify_event_only_hint": "Teslimatın tamamını otomasyonunuz yapar. Tekrar aralıkları, sessiz saatler ve günlük sınır, olayın ne zaman tetikleneceği için geçerli kalır.",
|
||||
"settings_notify_extra_data": "Ek bildirim verileri (şablon)",
|
||||
"settings_notify_extra_data_hint": "JSON veya YAML, Jinja kullanılabilir; notify çağrısının data alanına birleştirilir (sizin anahtarlarınız öncelikli). Değişkenler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test gönder düğmesiyle deneyin.",
|
||||
"settings_notify_extra_data_hint": "JSON veya YAML, Jinja kullanılabilir; notify çağrısının data alanına birleştirilir (sizin anahtarlarınız öncelikli). Değişkenler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test gönder düğmesiyle deneyin.",
|
||||
"card_saved_view": "Kayıtlı görünüm",
|
||||
"card_saved_view_none": "Yok",
|
||||
"card_saved_view_help": "Görünümün durum, kullanıcı ve etiket filtrelerini yukarıdaki filtrelerin üzerine uygular. Görünümün sıralaması ve gruplaması panel görüntüleme ayarlarıdır ve kartta uygulanmaz.",
|
||||
"panel_card_tab": "Şu sekmede aç",
|
||||
"panel_card_tab_default": "Hatırlanan sekme (paneldeki gibi)",
|
||||
"panel_card_view": "Kayıtlı görünümle aç",
|
||||
"panel_card_height": "Yükseklik",
|
||||
"panel_card_height_help": "Boş = kartın altındaki ekranı doldurur (panel görünümü). Aksi halde başka kartlar içeren görünümler için 600px veya 80vh gibi bir CSS uzunluğu.",
|
||||
"panel_card_not_registered": "Maintenance Supporter paneli kullanılamıyor — entegrasyon kurulu mu?",
|
||||
"panel_card_load_failed": "Maintenance Supporter paneli yüklenemedi — sayfayı yenileyin (bir güncellemeden sonra: tarayıcı önbelleğini temizleyin).",
|
||||
"doc_part_none": "Bu parçaya bağlı belge yok.",
|
||||
"settings_templates_toggle_group": "Bu gruptaki tüm şablonları etkinleştir veya devre dışı bırak",
|
||||
"setups_button": "Önerilen kurulumlar",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "İyi durumda",
|
||||
"battery_fleet_status_due": "Süresi doldu",
|
||||
"battery_fleet_predicted_on": "Yaklaşık {date} tarihinde bekleniyor",
|
||||
"battery_fleet_predicted_typical": "Yaklaşık {date} — {type} için tipik {months} ay ({source})",
|
||||
"lifetime_source_default": "yerleşik varsayılan",
|
||||
"lifetime_source_table": "yerleşik tablo",
|
||||
"lifetime_source_learned": "{n} değişimden öğrenildi",
|
||||
"lifetime_source_override": "senin ayarın",
|
||||
"lifetime_source_learned_device": "bu cihazın {n} değişiminden öğrenildi",
|
||||
"lifetime_source_learned_model": "bu modeldeki cihazların {n} değişiminden öğrenildi",
|
||||
"battery_fleet_predicted_trend": "Bu pilin deşarj eğiliminden tahmin edildi: yaklaşık {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Şarj edilebilir: değiştirmek yerine şarj edin — alışveriş listesine asla girmez",
|
||||
"battery_fleet_sort_name": "Ada göre sırala",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Kullanılan parçalar",
|
||||
"print_inc_photos": "Fotoğraflar",
|
||||
"print_inc_documents": "Bağlı belgeler",
|
||||
"print_inc_doc_desc": "Belge açıklamaları",
|
||||
"print_inc_checklist": "Kontrol listesi",
|
||||
"print_inc_notes": "Notlar",
|
||||
"print_inc_costs": "Maliyet ve süre",
|
||||
"print_inc_person": "Tamamlayan",
|
||||
"print_inc_refs": "Referans numaraları",
|
||||
"print_inc_bare": "Ayrıntısız tamamlamalar",
|
||||
"print_inc_qr": "Görev başına QR kodu",
|
||||
"print_button": "Yazdır",
|
||||
"report_scan_hint": "Görevi açmak için tarayın",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Напрацювання",
|
||||
"runtime_hours": "Цільове напрацювання (години)",
|
||||
"target_value": "Цільове значення",
|
||||
"baseline": "Базове значення",
|
||||
"target_changes": "Цільова кількість змін",
|
||||
"for_minutes": "Протягом (хвилин)",
|
||||
"time_based": "За часом",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Створено",
|
||||
"report_times_done": "Виконано",
|
||||
"report_total_cost": "Загальна вартість",
|
||||
"report_every": "кожні {n} {unit}",
|
||||
"report_notes": "Нотатки",
|
||||
"report_col_type": "Тип",
|
||||
"report_col_status": "Статус",
|
||||
"report_col_schedule": "Розклад",
|
||||
"duration": "Тривалість",
|
||||
"minutes_short": "хв",
|
||||
"both": "Обидва",
|
||||
"trigger_val": "Значення тригера",
|
||||
"complete_title": "Виконати: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: невірний тип (очікувалось: {type})",
|
||||
"err_invalid_choice": "{field}: недопустиме значення",
|
||||
"err_invalid_value": "{field}: невірне значення",
|
||||
"ws_err_not_found": "Не знайдено — можливо, вже видалено",
|
||||
"ws_err_invalid_input": "Неприпустиме введення",
|
||||
"ws_err_invalid_date": "Неприпустима дата",
|
||||
"ws_err_invalid_format": "Неприпустимий формат",
|
||||
"ws_err_invalid_url": "Неприпустиме посилання",
|
||||
"ws_err_not_configured": "Ще не налаштовано",
|
||||
"ws_err_not_loaded": "Ще не завантажено — спробуйте трохи пізніше",
|
||||
"ws_err_limit_reached": "Досягнуто межу",
|
||||
"ws_err_too_many": "Забагато записів",
|
||||
"ws_err_too_large": "Занадто великий розмір",
|
||||
"ws_err_not_archived": "Не в архіві",
|
||||
"ws_err_invalid_target": "Неприпустима ціль",
|
||||
"ws_err_create_failed": "Не вдалося створити",
|
||||
"ws_err_archived": "В архіві — спочатку відновіть",
|
||||
"ws_err_already_archived": "Уже в архіві",
|
||||
"ws_err_unavailable": "Зараз недоступно",
|
||||
"ws_err_unauthorized": "Недоступно для вашого користувача",
|
||||
"ws_err_too_many_views": "Забагато збережених подань",
|
||||
"ws_err_storage_unavailable": "Сховище недоступне",
|
||||
"ws_err_replace_failed": "Не вдалося замінити",
|
||||
"ws_err_not_paused": "Не на паузі",
|
||||
"ws_err_not_available": "Нічого немає",
|
||||
"ws_err_no_url": "Посилання не вказано",
|
||||
"ws_err_no_phases": "У цього завдання немає фаз",
|
||||
"ws_err_invalid_view": "Неприпустиме подання",
|
||||
"ws_err_invalid_user": "Невідомий користувач",
|
||||
"ws_err_invalid_range": "Неприпустимий діапазон",
|
||||
"ws_err_invalid_parent": "Неприпустимий батьківський об'єкт",
|
||||
"ws_err_invalid_device": "Невідомий пристрій",
|
||||
"ws_err_empty_csv": "CSV-файл порожній",
|
||||
"ws_err_empty": "Нічого імпортувати",
|
||||
"ws_err_duplicate_failed": "Не вдалося дублювати",
|
||||
"ws_err_already_paused": "Уже на паузі",
|
||||
"ws_err_invalid_cursor": "Неприпустима позиція фази",
|
||||
"ws_err_invalid_entity_slug": "Неприпустима назва сутності",
|
||||
"ws_err_invalid_trigger_config": "Неприпустиме налаштування тригера",
|
||||
"ws_err_no_defaults": "Не задано значення за замовчуванням для швидкого завершення",
|
||||
"ws_err_self_link_device": "Об'єкт не можна пов'язати із самим собою",
|
||||
"ws_err_too_early": "Зарано — це завдання можна завершити лише ближче до терміну",
|
||||
"feat_schedule_time": "Планування за часом доби",
|
||||
"feat_schedule_time_desc": "Задачі стають простроченими у певний час доби, а не опівночі.",
|
||||
"schedule_time_toggle": "Термін у визначений час",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Спрацьовувати при рівності (=)",
|
||||
"trigger_not_equals": "Спрацьовувати при відмінності від (≠)",
|
||||
"for_at_least_minutes": "Протягом не менше (хвилин)",
|
||||
"safety_interval_days": "Страховий інтервал (дні, необов'язково)",
|
||||
"safety_interval": "Страховий інтервал (необов'язково)",
|
||||
"trigger_combinator": "Поєднати тригер та інтервал",
|
||||
"trigger_combinator_any": "Тригер або інтервал (що раніше)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Мої завдання",
|
||||
"tab_calendar": "Календар",
|
||||
"cal_no_events": "Без обслуговування",
|
||||
"cal_window_7": "7 днів",
|
||||
"cal_window_14": "14 днів",
|
||||
"cal_window_30": "30 днів",
|
||||
"cal_window_365": "1 рік",
|
||||
"cal_every_n_days": "кожні {n} днів",
|
||||
"cal_source_time": "За часом",
|
||||
"cal_source_time_adaptive": "За часом (адаптивно)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Не вдалося згенерувати QR-код.",
|
||||
"qr_error_no_url": "URL Home Assistant не налаштовано. Задайте зовнішню або внутрішню URL-адресу в Налаштування → Система → Мережа.",
|
||||
"save_error": "Не вдалося зберегти. Спробуйте ще раз.",
|
||||
"subsave_warning": "Збережено, але одне налаштування відхилено: {detail}",
|
||||
"qr_print": "Друкувати",
|
||||
"qr_download": "Завантажити SVG",
|
||||
"qr_action": "Дія при скануванні",
|
||||
"qr_action_view": "Переглянути",
|
||||
"qr_action_complete": "Позначити обслуговування виконаним",
|
||||
"qr_url_mode": "Тип посилання",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Локальний (mDNS)",
|
||||
"qr_mode_server": "URL сервера",
|
||||
"overview": "Огляд",
|
||||
"analysis": "Аналіз",
|
||||
"recent_activities": "Остання активність",
|
||||
"search_notes": "Пошук у примітках",
|
||||
"avg_cost": "Сер. вартість",
|
||||
"no_advanced_features": "Розширені функції не увімкнено",
|
||||
"no_advanced_features_hint": "Увімкніть «Адаптивні інтервали» або «Сезонні закономірності» в налаштуваннях інтеграції, щоб побачити тут дані аналізу.",
|
||||
"analysis_not_enough_data": "Недостатньо даних для аналізу.",
|
||||
"analysis_not_enough_data_hint": "Аналіз Вейбулла потребує щонайменше 5 виконаних обслуговувань; сезонні закономірності стають видимими після 6+ записів на місяць.",
|
||||
"analysis_manual_task_hint": "Ручні завдання без інтервалу не генерують дані аналізу.",
|
||||
"completions": "виконань",
|
||||
"current": "Поточний",
|
||||
"shorter": "Коротший",
|
||||
"longer": "Довший",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Минулі вікна",
|
||||
"cal_forward_windows": "Майбутні вікна",
|
||||
"history_edit_title": "Редагувати запис історії",
|
||||
"history_delete_entry": "Видалити запис",
|
||||
"history_delete_confirm": "Видалити цей запис історії? Дата останнього виконання повернеться до попереднього; фотографії залишаться в об'єкта, а витрачені деталі не повернуться на склад.",
|
||||
"history_edit_timestamp": "Позначка часу",
|
||||
"manufacturer": "Виробник",
|
||||
"model": "Модель",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Днів попередження за замовчуванням",
|
||||
"settings_consumable_threshold": "Поріг «витратний матеріал закінчується» (%)",
|
||||
"settings_battery_low_percent": "Поріг «батарея розряджена» (%)",
|
||||
"settings_battery_lifetimes": "Типовий строк служби батарей",
|
||||
"settings_battery_lifetimes_hint": "Використовується лише для батарей без відсотка: нотатка Battery Notes лише з типом і датою заміни або датчик, що повідомляє тільки «низький». Термін = остання заміна + цей строк служби. Для батарей з рівнем прогноз будується за виміряним розрядом. Після трьох замін одного типу медіанний інтервал замінює вбудоване значення — ваше значення завжди головніше.",
|
||||
"settings_battery_lifetime_months": "міс.",
|
||||
"settings_battery_lifetime_reset": "За замовчуванням",
|
||||
"settings_battery_lifetime_in_fleet": "у вашому парку",
|
||||
"settings_battery_lifetime_learned_models": "Вивчено за моделями: {list}",
|
||||
"settings_thresholds_hint": "Значення за замовчуванням для нових «Запропонованих налаштувань» (залишок у відсотках) і для батарей парку без власного порогу Battery Notes. Наявні завдання зберігають своє значення.",
|
||||
"settings_value_out_of_range": "Значення має бути від {min} до {max}",
|
||||
"bn_summary": "{name}: {pct} % для {n} батарей",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} пристроїв із власним порогом:",
|
||||
"bn_more": "+ ще {n}",
|
||||
"settings_row_actions": "Дії в рядках завдань",
|
||||
"settings_ref_numbers_in_lists": "Номери посилань у списках",
|
||||
"settings_ref_numbers_in_lists_hint": "Показувати мітки #8 / #8.3 перед назвами об'єктів і завдань у списку «Сьогодні», таблиці завдань, картках об'єктів і таблиці об'єктів.",
|
||||
"row_actions_buttons_compact": "Кнопки (на телефоні лише значки)",
|
||||
"row_actions_buttons": "Кнопки з текстом",
|
||||
"row_actions_icons": "Значки (класичний вигляд)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Тривалість відкладення (години)",
|
||||
"settings_budget": "Бюджет",
|
||||
"settings_currency": "Валюта",
|
||||
"settings_currency_decimals": "Знаків після коми в сумах",
|
||||
"settings_currency_decimals_hint": "Скільки знаків після коми показує кожна сума: KPI, бюджети, витрати у списках і звітах, сповіщення про бюджет. 0 = цілі числа.",
|
||||
"settings_budget_monthly": "Щомісячний бюджет",
|
||||
"settings_budget_yearly": "Щорічний бюджет",
|
||||
"settings_budget_alerts": "Сповіщення про бюджет",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Експортувати YAML",
|
||||
"settings_export_csv": "Експортувати CSV",
|
||||
"settings_export_settings": "Експорт налаштувань (JSON)",
|
||||
"settings_import_csv": "Імпортувати CSV",
|
||||
"settings_import_placeholder": "Вставте вміст JSON або CSV сюди…",
|
||||
"settings_import_btn": "Імпортувати",
|
||||
"settings_import_success": "{count} об'єктів успішно імпортовано.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Дія HA по завданню при завершенні + QR швидкого завершення з попередньо встановленими значеннями.",
|
||||
"on_complete_action_title": "При завершенні: викликати HA-дію (опційно)",
|
||||
"on_complete_action_desc": "Викликає HA-сервіс, коли завдання завершено — напр., скинути лічильник на пристрої.",
|
||||
"on_complete_action_service": "Сервіс",
|
||||
"on_complete_action_target": "Цільова сутність",
|
||||
"on_complete_action_data": "Дані (JSON, опційно)",
|
||||
"on_complete_action_test": "Тестувати дію",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Вибрати фото",
|
||||
"choose_photo": "Вибрати фото",
|
||||
"photos_android_hint": "Застосунок для Android передає по одному фото за вибір — кожен вибір додається.",
|
||||
"add_photos": "Додати фото",
|
||||
"photos_limit": "До {max} фото на одне виконання",
|
||||
"history_edit_photos_hint": "Видалене тут фото залишається в документах об'єкта.",
|
||||
"uploading": "Завантаження…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Завантажити",
|
||||
"doc_close": "Закрити",
|
||||
"doc_camera": "Зробити фото",
|
||||
"camera_capture_shoot": "Зняти",
|
||||
"camera_switch_lens": "Змінити камеру",
|
||||
"doc_drop_hint": "Перетягніть файли сюди",
|
||||
"doc_task_none": "Немає документів, пов'язаних із цим завданням.",
|
||||
"doc_link_existing": "Пов'язати документ…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Записує виконання (оновлює дату останнього обслуговування), коли тригер зникає сам — наприклад, сіль досипано, фільтр замінено.",
|
||||
"doc_search": "Пошук документів…",
|
||||
"doc_search_none": "Немає відповідних документів",
|
||||
"doc_description": "Опис (необов'язково)",
|
||||
"doc_sort": "Сортувати документи",
|
||||
"doc_sort_newest": "Спочатку нові",
|
||||
"doc_sort_oldest": "Спочатку старі",
|
||||
"doc_sort_title": "За назвою (1, 2, 3 …)",
|
||||
"doc_sort_category": "За категорією",
|
||||
"link_device_optional": "Прив'язати до наявного пристрою (необов'язково)",
|
||||
"parent_object_optional": "Батьківський об'єкт (необов'язково)",
|
||||
"parent_none": "(Без батьківського)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Сповіщати лише для подання",
|
||||
"settings_notify_scope_all": "Усі завдання",
|
||||
"settings_notify_scope_hint": "Нагадування надсилають лише завдання, що відповідають фільтрам мітки/користувача обраного подання. Статус, сортування та групування подання тут ігноруються.",
|
||||
"settings_notify_completed": "Сповіщення про виконання",
|
||||
"settings_notify_completed_hint": "Повідомлення для всіх домашніх, коли завдання виконано, з причиною та виконавцем. «Лише автоматичні» повідомляє тільки про виконання, які ніхто не робив на місці: відновлення датчика, позначка у списку покупок, виклик служби автоматизацією. Лише на спільний сервіс; тихі години та денний ліміт діють.",
|
||||
"notify_completed_off": "Вимк.",
|
||||
"notify_completed_automatic": "Лише автоматичні виконання",
|
||||
"notify_completed_all": "Усі виконання",
|
||||
"settings_notify_rule": "Власне правило сповіщень",
|
||||
"settings_notify_rule_hint": "Кожне сповіщення додатково викликає подію maintenance_supporter_notification (вид, статус, об'єкт, завдання, номери посилань, пріоритет, посилання, дані). Спрямуйте її автоматизацією — наприклад у Ticker, Telegram чи Pushover.",
|
||||
"settings_notify_event_only": "Лише подія, нічого не надсилати",
|
||||
"settings_notify_event_only_hint": "Доставку повністю виконує ваша автоматизація. Інтервали повтору, тихі години та денний ліміт і далі визначають, коли спрацьовує подія.",
|
||||
"settings_notify_extra_data": "Додаткові дані сповіщення (шаблон)",
|
||||
"settings_notify_extra_data_hint": "JSON або YAML, Jinja дозволено; додається до data виклику notify (ваші ключі мають перевагу). Змінні: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Перевірте кнопкою «Надіслати тест».",
|
||||
"settings_notify_extra_data_hint": "JSON або YAML, Jinja дозволено; додається до data виклику notify (ваші ключі мають перевагу). Змінні: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Перевірте кнопкою «Надіслати тест».",
|
||||
"card_saved_view": "Збережене подання",
|
||||
"card_saved_view_none": "Немає",
|
||||
"card_saved_view_help": "Застосовує фільтри статусу, користувача та мітки подання додатково до фільтрів вище. Сортування та групування подання — налаштування відображення панелі й на картці не застосовуються.",
|
||||
"panel_card_tab": "Відкривати на вкладці",
|
||||
"panel_card_tab_default": "Запам'ятована вкладка (як у панелі)",
|
||||
"panel_card_view": "Відкривати зі збереженим виглядом",
|
||||
"panel_card_height": "Висота",
|
||||
"panel_card_height_help": "Порожньо = заповнити екран під карткою (вигляд «панель»). Інакше довжина CSS, наприклад 600px або 80vh, для виглядів з іншими картками.",
|
||||
"panel_card_not_registered": "Панель Maintenance Supporter недоступна — інтеграцію налаштовано?",
|
||||
"panel_card_load_failed": "Не вдалося завантажити панель Maintenance Supporter — перезавантажте сторінку (після оновлення очистіть кеш браузера).",
|
||||
"doc_part_none": "До цієї запчастини не прив'язано документів.",
|
||||
"settings_templates_toggle_group": "Увімкнути або вимкнути всі шаблони цієї групи",
|
||||
"setups_button": "Пропоновані налаштування",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "У нормі",
|
||||
"battery_fleet_status_due": "Час замінити",
|
||||
"battery_fleet_predicted_on": "Очікується приблизно {date}",
|
||||
"battery_fleet_predicted_typical": "Очікується близько {date} — {months} міс. типово для {type} ({source})",
|
||||
"lifetime_source_default": "вбудоване значення за замовчуванням",
|
||||
"lifetime_source_table": "вбудована таблиця",
|
||||
"lifetime_source_learned": "за {n} замінами",
|
||||
"lifetime_source_override": "ваше налаштування",
|
||||
"lifetime_source_learned_device": "за {n} замінами цього пристрою",
|
||||
"lifetime_source_learned_model": "за {n} замінами на пристроях цієї моделі",
|
||||
"battery_fleet_predicted_trend": "Прогноз за трендом розряду цієї батареї: приблизно {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Акумулятор: заряджається, а не замінюється — ніколи не потрапляє до списку покупок",
|
||||
"battery_fleet_sort_name": "Сортувати за назвою",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Використані деталі",
|
||||
"print_inc_photos": "Фото",
|
||||
"print_inc_documents": "Пов'язані документи",
|
||||
"print_inc_doc_desc": "Описи документів",
|
||||
"print_inc_checklist": "Чек-лист",
|
||||
"print_inc_notes": "Нотатки",
|
||||
"print_inc_costs": "Вартість і тривалість",
|
||||
"print_inc_person": "Виконав",
|
||||
"print_inc_refs": "Номери посилань",
|
||||
"print_inc_bare": "Виконання без подробиць",
|
||||
"print_inc_qr": "QR-код для кожного завдання",
|
||||
"print_button": "Друк",
|
||||
"report_scan_hint": "Скануйте, щоб відкрити завдання",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "运行时间",
|
||||
"runtime_hours": "目标运行时长 (小时)",
|
||||
"target_value": "目标值",
|
||||
"baseline": "基准线",
|
||||
"target_changes": "目标变化次数",
|
||||
"for_minutes": "持续 (分钟)",
|
||||
"time_based": "基于时间",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "生成于",
|
||||
"report_times_done": "完成次数",
|
||||
"report_total_cost": "总费用",
|
||||
"report_every": "每 {n} {unit}",
|
||||
"report_notes": "备注",
|
||||
"report_col_type": "类型",
|
||||
"report_col_status": "状态",
|
||||
"report_col_schedule": "计划",
|
||||
"duration": "耗时",
|
||||
"minutes_short": "分钟",
|
||||
"both": "两者",
|
||||
"trigger_val": "触发值",
|
||||
"complete_title": "完成: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: 类型错误 (预期: {type})",
|
||||
"err_invalid_choice": "{field}: 无效选项",
|
||||
"err_invalid_value": "{field}: 无效数值",
|
||||
"ws_err_not_found": "未找到 — 可能已被删除",
|
||||
"ws_err_invalid_input": "输入无效",
|
||||
"ws_err_invalid_date": "日期无效",
|
||||
"ws_err_invalid_format": "格式无效",
|
||||
"ws_err_invalid_url": "链接无效",
|
||||
"ws_err_not_configured": "尚未设置",
|
||||
"ws_err_not_loaded": "尚未加载 — 请稍后重试",
|
||||
"ws_err_limit_reached": "已达上限",
|
||||
"ws_err_too_many": "条目过多",
|
||||
"ws_err_too_large": "内容过大",
|
||||
"ws_err_not_archived": "未归档",
|
||||
"ws_err_invalid_target": "目标无效",
|
||||
"ws_err_create_failed": "无法创建",
|
||||
"ws_err_archived": "已归档 — 请先恢复",
|
||||
"ws_err_already_archived": "已归档",
|
||||
"ws_err_unavailable": "当前不可用",
|
||||
"ws_err_unauthorized": "您的用户无权执行此操作",
|
||||
"ws_err_too_many_views": "保存的视图过多",
|
||||
"ws_err_storage_unavailable": "存储不可用",
|
||||
"ws_err_replace_failed": "替换失败",
|
||||
"ws_err_not_paused": "未暂停",
|
||||
"ws_err_not_available": "没有可用项",
|
||||
"ws_err_no_url": "未提供链接",
|
||||
"ws_err_no_phases": "此任务没有阶段",
|
||||
"ws_err_invalid_view": "视图无效",
|
||||
"ws_err_invalid_user": "未知用户",
|
||||
"ws_err_invalid_range": "范围无效",
|
||||
"ws_err_invalid_parent": "父级对象无效",
|
||||
"ws_err_invalid_device": "未知设备",
|
||||
"ws_err_empty_csv": "CSV 文件为空",
|
||||
"ws_err_empty": "没有可导入的内容",
|
||||
"ws_err_duplicate_failed": "无法复制",
|
||||
"ws_err_already_paused": "已暂停",
|
||||
"ws_err_invalid_cursor": "阶段位置无效",
|
||||
"ws_err_invalid_entity_slug": "实体名称无效",
|
||||
"ws_err_invalid_trigger_config": "触发器配置无效",
|
||||
"ws_err_no_defaults": "未设置快速完成默认值",
|
||||
"ws_err_self_link_device": "对象不能链接到自身",
|
||||
"ws_err_too_early": "为时过早 — 此任务只能在临近到期日时完成",
|
||||
"feat_schedule_time": "具体时间调度",
|
||||
"feat_schedule_time_desc": "任务将在特定时间点变为逾期,而非默认的午夜。",
|
||||
"schedule_time_toggle": "在特定时间到期",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "等于时触发(=)",
|
||||
"trigger_not_equals": "不等于时触发(≠)",
|
||||
"for_at_least_minutes": "持续至少 (分钟)",
|
||||
"safety_interval_days": "安全间隔 (天, 可选)",
|
||||
"safety_interval": "安全间隔 (可选)",
|
||||
"trigger_combinator": "组合触发器与间隔",
|
||||
"trigger_combinator_any": "触发器或间隔(先到者)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "我的任务",
|
||||
"tab_calendar": "日历",
|
||||
"cal_no_events": "无维护事项",
|
||||
"cal_window_7": "7 天",
|
||||
"cal_window_14": "14 天",
|
||||
"cal_window_30": "30 天",
|
||||
"cal_window_365": "1 年",
|
||||
"cal_every_n_days": "每 {n} 天",
|
||||
"cal_source_time": "基于时间",
|
||||
"cal_source_time_adaptive": "基于时间 (自适应)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "无法生成二维码。",
|
||||
"qr_error_no_url": "未配置 HA URL。请前往“设置 → 系统 → 网络”设置外部或内部 URL。",
|
||||
"save_error": "保存失败。请重试。",
|
||||
"subsave_warning": "已保存,但有一项设置被拒绝:{detail}",
|
||||
"qr_print": "打印",
|
||||
"qr_download": "下载 SVG",
|
||||
"qr_action": "扫码后动作",
|
||||
"qr_action_view": "查看维护信息",
|
||||
"qr_action_complete": "标记维护为已完成",
|
||||
"qr_url_mode": "链接类型",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "本地 (mDNS)",
|
||||
"qr_mode_server": "服务器 URL",
|
||||
"overview": "概览",
|
||||
"analysis": "分析",
|
||||
"recent_activities": "近期活动",
|
||||
"search_notes": "搜索备注",
|
||||
"avg_cost": "平均成本",
|
||||
"no_advanced_features": "未启用高级功能",
|
||||
"no_advanced_features_hint": "请在集成设置中启用“自适应间隔”或“季节性模式”以在此查看分析数据。",
|
||||
"analysis_not_enough_data": "暂无足够的分析数据。",
|
||||
"analysis_not_enough_data_hint": "韦伯分析需要至少 5 次完成记录;季节性模式在每月有 6 个以上数据点后可见。",
|
||||
"analysis_manual_task_hint": "无间隔的手动任务不会生成分析数据。",
|
||||
"completions": "次完成记录",
|
||||
"current": "当前",
|
||||
"shorter": "较短",
|
||||
"longer": "较长",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "过去的窗口",
|
||||
"cal_forward_windows": "未来的窗口",
|
||||
"history_edit_title": "编辑历史记录",
|
||||
"history_delete_entry": "删除记录",
|
||||
"history_delete_confirm": "删除此历史记录?上次完成日期将回退到前一次完成;照片仍保留在对象中,已消耗的配件不会退回库存。",
|
||||
"history_edit_timestamp": "时间戳",
|
||||
"manufacturer": "制造商",
|
||||
"model": "型号",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "默认预警天数",
|
||||
"settings_consumable_threshold": "耗材“不足”阈值(%)",
|
||||
"settings_battery_low_percent": "电池“电量低”阈值(%)",
|
||||
"settings_battery_lifetimes": "电池典型寿命",
|
||||
"settings_battery_lifetimes_hint": "仅用于没有百分比的电池:只有类型和更换日期的 Battery Notes 记录,或只报告“低电量”的传感器。其到期日 = 上次更换 + 此寿命。报告电量的电池改用实测放电来预测。同一类型出现三次更换后,中位间隔将取代内置值——你自己的值始终优先。",
|
||||
"settings_battery_lifetime_months": "个月",
|
||||
"settings_battery_lifetime_reset": "使用默认值",
|
||||
"settings_battery_lifetime_in_fleet": "在你的电池群中",
|
||||
"settings_battery_lifetime_learned_models": "按型号学习:{list}",
|
||||
"settings_thresholds_hint": "用于新的“建议设置”(剩余百分比)以及没有自身 Battery Notes 阈值的电池组电池。现有任务保留其值。",
|
||||
"settings_value_out_of_range": "数值必须介于 {min} 和 {max} 之间",
|
||||
"bn_summary": "{name}:{n} 块电池为 {pct}%",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} 台设备有自己的阈值:",
|
||||
"bn_more": "+ 另外 {n} 个",
|
||||
"settings_row_actions": "任务行操作",
|
||||
"settings_ref_numbers_in_lists": "在列表中显示编号",
|
||||
"settings_ref_numbers_in_lists_hint": "在“今天”列表、任务表、对象卡片和对象表中的对象与任务名称前显示 #8 / #8.3 编号标签。",
|
||||
"row_actions_buttons_compact": "按钮(手机上仅显示图标)",
|
||||
"row_actions_buttons": "带文字的按钮",
|
||||
"row_actions_icons": "图标(经典)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "稍后提醒间隔 (小时)",
|
||||
"settings_budget": "预算",
|
||||
"settings_currency": "货币单位",
|
||||
"settings_currency_decimals": "金额小数位数",
|
||||
"settings_currency_decimals_hint": "每个金额显示几位小数:KPI、预算、列表和报告中的费用、预算提醒。0 = 整数。",
|
||||
"settings_budget_monthly": "月度预算",
|
||||
"settings_budget_yearly": "年度预算",
|
||||
"settings_budget_alerts": "预算警报",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "导出 YAML",
|
||||
"settings_export_csv": "导出 CSV",
|
||||
"settings_export_settings": "导出设置(JSON)",
|
||||
"settings_import_csv": "导入 CSV",
|
||||
"settings_import_placeholder": "在此粘贴 JSON 或 CSV 内容…",
|
||||
"settings_import_btn": "导入",
|
||||
"settings_import_success": "成功导入 {count} 个维护项。",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "为每个任务配置完成时的 HA 动作 + 预设值的快速完成二维码。",
|
||||
"on_complete_action_title": "完成时:触发 HA 动作(可选)",
|
||||
"on_complete_action_desc": "任务完成时调用 HA 服务 — 例如重置设备上的物理计数器。",
|
||||
"on_complete_action_service": "服务",
|
||||
"on_complete_action_target": "目标实体",
|
||||
"on_complete_action_target_hint": "注意:实体的域必须与服务匹配 — 例如 'button.press' 仅适用于 button.* 实体,'counter.increment' 仅适用于 counter.* 实体等。如果不匹配,动作将静默失败。",
|
||||
"on_complete_action_data": "数据(JSON, 可选)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "选择照片",
|
||||
"choose_photo": "选择照片",
|
||||
"photos_android_hint": "Android 应用每次只传一张照片——每次选择都会添加。",
|
||||
"add_photos": "添加照片",
|
||||
"photos_limit": "每次完成最多 {max} 张照片",
|
||||
"history_edit_photos_hint": "在此移除的照片仍保留在对象的文档中。",
|
||||
"uploading": "上传中…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "下载",
|
||||
"doc_close": "关闭",
|
||||
"doc_camera": "拍照",
|
||||
"camera_capture_shoot": "拍摄",
|
||||
"camera_switch_lens": "切换摄像头",
|
||||
"doc_drop_hint": "将文件拖放到此处",
|
||||
"doc_task_none": "没有与此任务关联的文档。",
|
||||
"doc_link_existing": "关联文档…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "当触发条件自行解除时(如已加盐、已更换滤芯),记录一次完成并更新上次执行日期。",
|
||||
"doc_search": "搜索文档…",
|
||||
"doc_search_none": "没有匹配的文档",
|
||||
"doc_description": "说明(可选)",
|
||||
"doc_sort": "文档排序",
|
||||
"doc_sort_newest": "最新在前",
|
||||
"doc_sort_oldest": "最早在前",
|
||||
"doc_sort_title": "按标题(1、2、3…)",
|
||||
"doc_sort_category": "按类别",
|
||||
"link_device_optional": "关联到现有设备(可选)",
|
||||
"parent_object_optional": "父对象(可选)",
|
||||
"parent_none": "(无父对象)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "仅为该视图发送通知",
|
||||
"settings_notify_scope_all": "所有任务",
|
||||
"settings_notify_scope_hint": "只有匹配所选视图的标签/用户筛选的任务才会发送提醒。视图的状态、排序和分组在此处被忽略。",
|
||||
"settings_notify_completed": "完成通知",
|
||||
"settings_notify_completed_hint": "任务完成时通知家庭成员,附上原因和执行者。“仅自动”只通报无人现场操作的完成:传感器恢复、购物清单勾选、自动化的服务调用。仅发送到家庭通知服务;免打扰时段和每日上限有效。",
|
||||
"notify_completed_off": "关闭",
|
||||
"notify_completed_automatic": "仅自动完成",
|
||||
"notify_completed_all": "所有完成",
|
||||
"settings_notify_rule": "自定义通知规则",
|
||||
"settings_notify_rule_hint": "每条通知还会触发事件 maintenance_supporter_notification(类型、状态、对象、任务、参考编号、优先级、深层链接、载荷)。用自动化转发它——例如到 Ticker、Telegram 或 Pushover。",
|
||||
"settings_notify_event_only": "仅触发事件,自身不发送",
|
||||
"settings_notify_event_only_hint": "由你的自动化完成全部发送。重复间隔、免打扰时段和每日上限仍决定事件何时触发。",
|
||||
"settings_notify_extra_data": "附加通知数据(模板)",
|
||||
"settings_notify_extra_data_hint": "JSON 或 YAML,可用 Jinja;合并进 notify 调用的 data(你的键优先)。变量:kind、status、priority、object_name、task_name、object_ref、task_ref、days_until_due、next_due、responsible_user_id、url、target、title、message。用“发送测试”按钮验证。",
|
||||
"settings_notify_extra_data_hint": "JSON 或 YAML,可用 Jinja;合并进 notify 调用的 data(你的键优先)。变量:kind、status、priority、labels、notes、area_name、object_name、task_name、object_ref、task_ref、days_until_due、next_due、last_performed、responsible_user_id、sensor_entity_id、url、target、title、message。用“发送测试”按钮验证。",
|
||||
"card_saved_view": "已保存的视图",
|
||||
"card_saved_view_none": "无",
|
||||
"card_saved_view_help": "在上述筛选之外再应用该视图的状态、用户和标签筛选。视图的排序和分组属于面板显示设置,不会应用到卡片上。",
|
||||
"panel_card_tab": "打开时的标签页",
|
||||
"panel_card_tab_default": "记住的标签页(与面板一致)",
|
||||
"panel_card_view": "打开时使用已保存的视图",
|
||||
"panel_card_height": "高度",
|
||||
"panel_card_height_help": "留空 = 填满卡片下方的屏幕(面板视图)。否则填写 CSS 长度,如 600px 或 80vh,用于含其他卡片的视图。",
|
||||
"panel_card_not_registered": "Maintenance Supporter 面板不可用——集成是否已设置?",
|
||||
"panel_card_load_failed": "无法加载 Maintenance Supporter 面板——请重新加载页面(更新后请清除浏览器缓存)。",
|
||||
"doc_part_none": "此部件没有关联的文档。",
|
||||
"settings_templates_toggle_group": "启用或禁用此组中的所有模板",
|
||||
"setups_button": "推荐配置",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "正常",
|
||||
"battery_fleet_status_due": "已到期",
|
||||
"battery_fleet_predicted_on": "预计在 {date} 前后",
|
||||
"battery_fleet_predicted_typical": "预计约 {date} — {type} 通常 {months} 个月({source})",
|
||||
"lifetime_source_default": "内置默认值",
|
||||
"lifetime_source_table": "内置表",
|
||||
"lifetime_source_learned": "从 {n} 次更换中学习",
|
||||
"lifetime_source_override": "你的设置",
|
||||
"lifetime_source_learned_device": "从此设备的 {n} 次更换中学习",
|
||||
"lifetime_source_learned_model": "从同型号设备的 {n} 次更换中学习",
|
||||
"battery_fleet_predicted_trend": "根据此电池的放电趋势预测:约 {date}({confidence})",
|
||||
"battery_fleet_rechargeable": "可充电电池:充电即可,无需更换——不会出现在购物清单中",
|
||||
"battery_fleet_sort_name": "按名称排序",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "使用的零件",
|
||||
"print_inc_photos": "照片",
|
||||
"print_inc_documents": "关联文档",
|
||||
"print_inc_doc_desc": "文档说明",
|
||||
"print_inc_checklist": "检查清单",
|
||||
"print_inc_notes": "备注",
|
||||
"print_inc_costs": "费用和用时",
|
||||
"print_inc_person": "完成者",
|
||||
"print_inc_refs": "参考编号",
|
||||
"print_inc_bare": "无详情的完成记录",
|
||||
"print_inc_qr": "每个任务的二维码",
|
||||
"print_button": "打印",
|
||||
"report_scan_hint": "扫描以打开任务",
|
||||
|
||||
@@ -35,15 +35,13 @@ import {
|
||||
type CalendarEvent,
|
||||
} from "./helpers/calendar-bucket";
|
||||
import { calendarStyles } from "./calendar-styles";
|
||||
import { syncLocaleFromHass, sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, formatWeekday, formatMonth, langOf, formatCost } from "./styles";
|
||||
import { syncLocaleFromHass, sharedStyles, currencySymbolOf, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, formatWeekday, formatMonth, langOf, formatCost, syncCurrencyDecimals} from "./styles";
|
||||
import { registerCustomCard } from "./helpers/register-card";
|
||||
import { historyPhotoIds } from "./helpers/history-photos";
|
||||
import { entryReadingValues } from "./helpers/reading-slots";
|
||||
import { loadHistoryEntryDraft } from "./helpers/history-draft";
|
||||
import { openHistoryEditDialog, openTaskQuickActions } from "./dialog-mount";
|
||||
import type {
|
||||
HomeAssistant,
|
||||
MaintenanceObjectResponse,
|
||||
ReadingSlot,
|
||||
StatisticsResponse,
|
||||
} from "./types";
|
||||
|
||||
@@ -173,6 +171,7 @@ export class MaintenanceCalendarCard extends LitElement {
|
||||
]);
|
||||
this._objects = (objResult as { objects: MaintenanceObjectResponse[] }).objects;
|
||||
this._stats = statsResult as StatisticsResponse;
|
||||
syncCurrencyDecimals(this._stats.budget);
|
||||
} catch {
|
||||
// WS not available yet
|
||||
}
|
||||
@@ -228,38 +227,9 @@ export class MaintenanceCalendarCard extends LitElement {
|
||||
* (mirrors the strategy shim's ll-custom "edit-history" path). */
|
||||
private async _openHistoryEntry(ev: CalendarEvent): Promise<void> {
|
||||
try {
|
||||
const resp = await this.hass.connection.sendMessagePromise<{
|
||||
tasks?: Array<{
|
||||
id: string;
|
||||
type?: string;
|
||||
reading_unit?: string | null;
|
||||
readings?: ReadingSlot[] | null;
|
||||
history?: Array<Record<string, unknown>>;
|
||||
}>;
|
||||
}>({ type: "maintenance_supporter/object", entry_id: ev.entry_id });
|
||||
const histTask = resp.tasks?.find((tk) => tk.id === ev.task_id);
|
||||
const entry = histTask?.history?.find((h) => h.timestamp === ev.history_timestamp);
|
||||
if (!entry) return;
|
||||
const opened = openHistoryEditDialog({
|
||||
entry_id: ev.entry_id,
|
||||
task_id: ev.task_id,
|
||||
original_timestamp: ev.history_timestamp!,
|
||||
type: (entry.type as string) || "completed",
|
||||
timestamp: (entry.timestamp as string) || ev.history_timestamp!,
|
||||
notes: (entry.notes as string | null) ?? null,
|
||||
cost: (entry.cost as number | null) ?? null,
|
||||
duration: (entry.duration as number | null) ?? null,
|
||||
completed_by: (entry.completed_by as string | null) ?? null,
|
||||
used_parts:
|
||||
(entry.used_parts as Array<{ part_id: string; name?: string; quantity: number; entry_id?: string }> | null) ?? null,
|
||||
photo_doc_ids: historyPhotoIds(entry),
|
||||
reading_value: (entry.reading_value as number | null) ?? null,
|
||||
reading_values: entryReadingValues(entry),
|
||||
readings: histTask?.readings ?? [],
|
||||
task_type: histTask?.type ?? null,
|
||||
reading_unit: histTask?.reading_unit ?? null,
|
||||
});
|
||||
if (opened) return;
|
||||
const draft = await loadHistoryEntryDraft(this.hass, ev.entry_id, ev.task_id, ev.history_timestamp!);
|
||||
if (!draft) return;
|
||||
if (openHistoryEditDialog(draft)) return;
|
||||
} catch {
|
||||
/* fall through to the ll-custom fallback below */
|
||||
}
|
||||
@@ -362,7 +332,7 @@ export class MaintenanceCalendarCard extends LitElement {
|
||||
${t("cal_predicted", L)} · ${t(`cal_confidence_${ev.prediction_confidence}`, L)}
|
||||
</span>`
|
||||
: nothing;
|
||||
const currencySymbol = this._stats?.budget?.currency_symbol || DEFAULT_CURRENCY_SYMBOL;
|
||||
const currencySymbol = currencySymbolOf(this._stats?.budget);
|
||||
// Past mode: ev.history_type is the actual event ('completed', 'skipped',
|
||||
// 'reset', 'triggered', 'trigger_replaced'). Showing the derived status
|
||||
// ('OK' for a completion) was confusing — discussion #49 user-feedback:
|
||||
@@ -385,7 +355,7 @@ export class MaintenanceCalendarCard extends LitElement {
|
||||
${recurEvery}
|
||||
</div>
|
||||
${ev.avg_cost != null && ev.avg_cost > 0
|
||||
? html`<span class="cal-event-cost">${formatCost(ev.avg_cost, currencySymbol, L, 0)}</span>`
|
||||
? html`<span class="cal-event-cost">${formatCost(ev.avg_cost, currencySymbol, L)}</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
@@ -423,7 +393,7 @@ export class MaintenanceCalendarCard extends LitElement {
|
||||
<div class="cal-controls">
|
||||
${showChips
|
||||
? html`
|
||||
<div class="cal-window-chips cal-past-chips" title="${t("cal_past_windows", L) || "Past windows"}">
|
||||
<div class="cal-window-chips cal-past-chips" title="${t("cal_past_windows", L)}">
|
||||
${[30, 90].map((p) => html`
|
||||
<button class="cal-window-chip cal-past-chip ${this._pastDays === p ? "active" : ""}"
|
||||
@click=${() => {
|
||||
@@ -434,7 +404,7 @@ export class MaintenanceCalendarCard extends LitElement {
|
||||
`)}
|
||||
</div>
|
||||
<span class="cal-chip-separator" aria-hidden="true">●</span>
|
||||
<div class="cal-window-chips" title="${t("cal_forward_windows", L) || "Forward windows"}">
|
||||
<div class="cal-window-chips" title="${t("cal_forward_windows", L)}">
|
||||
${[7, 14, 30, 365].map((w) => html`
|
||||
<button class="cal-window-chip ${this._pastDays === 0 && this._windowDays === w ? "active" : ""}"
|
||||
@click=${() => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { LitElement, html, css, nothing } from "lit";
|
||||
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
|
||||
import { hydrateObjects } from "./helpers/hydrate-objects";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, langOf } from "./styles";
|
||||
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setProfilePrefs, formatDueDays, langOf, currencySymbolOf, syncCurrencyDecimals} from "./styles";
|
||||
import { openSignedDocument } from "./helpers/document-url";
|
||||
import { isSafeHttpUrl } from "./helpers/url";
|
||||
import { registerCustomCard } from "./helpers/register-card";
|
||||
@@ -173,6 +173,7 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
]);
|
||||
this._objects = hydrateObjects((objResult as { objects: MaintenanceObjectResponse[] }).objects);
|
||||
this._stats = statsResult as StatisticsResponse;
|
||||
syncCurrencyDecimals(this._stats.budget);
|
||||
} catch {
|
||||
// WS not available yet
|
||||
}
|
||||
@@ -468,7 +469,7 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
({ entry_id, object_name, task }) => html`
|
||||
<div class="task-item clickable"
|
||||
@click=${() => this._openTaskDetail(entry_id, task.id)}
|
||||
title="${t("open_task", L) || "Open task"}">
|
||||
title="${t("open_task", L)}">
|
||||
<div class="status-dot" style="background: ${STATUS_COLORS[task.status] || "#ccc"}"></div>
|
||||
<div class="task-info">
|
||||
<div class="task-name">
|
||||
@@ -477,7 +478,7 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
? html`<ha-icon
|
||||
class="postponed-icon"
|
||||
icon="mdi:calendar-clock"
|
||||
title="${t("postponed", L) || "Postponed"}"
|
||||
title="${t("postponed", L)}"
|
||||
></ha-icon>`
|
||||
: nothing}
|
||||
</div>
|
||||
@@ -541,7 +542,7 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
lang: L,
|
||||
checklist: task.checklist || [],
|
||||
adaptiveEnabled: !!task.adaptive_config?.enabled,
|
||||
currencySymbol: this._stats?.budget?.currency_symbol || "",
|
||||
currencySymbol: currencySymbolOf(this._stats?.budget),
|
||||
}),
|
||||
L,
|
||||
);
|
||||
@@ -574,7 +575,7 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
lang: L,
|
||||
checklist: task.checklist || [],
|
||||
adaptiveEnabled: !!task.adaptive_config?.enabled,
|
||||
currencySymbol: this._stats?.budget?.currency_symbol || "",
|
||||
currencySymbol: currencySymbolOf(this._stats?.budget),
|
||||
}),
|
||||
L,
|
||||
);
|
||||
@@ -748,6 +749,10 @@ if (!customElements.get("maintenance-supporter-card")) {
|
||||
customElements.define("maintenance-supporter-card", MaintenanceSupporterCard);
|
||||
}
|
||||
|
||||
// #174: the whole panel as a card — a tiny wrapper that lazy-imports the
|
||||
// panel bundle HA registered for the sidebar (same module, defined once).
|
||||
import "./maintenance-panel-card";
|
||||
|
||||
// Register as custom card so the Lovelace card picker lists it.
|
||||
registerCustomCard({
|
||||
type: "maintenance-supporter-card",
|
||||
|
||||
+4
-37
@@ -31,8 +31,7 @@
|
||||
*/
|
||||
|
||||
import { STATUS_ICONS } from "./status-constants";
|
||||
import { historyPhotoIds } from "./helpers/history-photos";
|
||||
import { entryReadingValues } from "./helpers/reading-slots";
|
||||
import { loadHistoryEntryDraft } from "./helpers/history-draft";
|
||||
|
||||
interface MaintenanceObjectResp {
|
||||
entry_id: string;
|
||||
@@ -1143,41 +1142,9 @@ function registerLlCustomHandler(): void {
|
||||
const hass = haRoot?.hass;
|
||||
if (!hass) return;
|
||||
try {
|
||||
const r = await hass.connection.sendMessagePromise<{
|
||||
tasks?: Array<{
|
||||
id?: string;
|
||||
type?: string;
|
||||
reading_unit?: string | null;
|
||||
readings?: Array<{ id: string; name: string; unit?: string | null }> | null;
|
||||
history?: Array<Record<string, unknown>>;
|
||||
}>;
|
||||
}>({
|
||||
type: "maintenance_supporter/object",
|
||||
entry_id: detail.entry_id,
|
||||
});
|
||||
const task = r.tasks?.find((t) => t.id === detail.task_id);
|
||||
const histEntry = task?.history?.find(
|
||||
(h) => h.timestamp === detail.original_timestamp,
|
||||
);
|
||||
if (!histEntry) return;
|
||||
dm.openHistoryEditDialog({
|
||||
entry_id: detail.entry_id as string,
|
||||
task_id: detail.task_id as string,
|
||||
original_timestamp: detail.original_timestamp as string,
|
||||
type: (histEntry.type as string) || "completed",
|
||||
timestamp: (histEntry.timestamp as string) || (detail.original_timestamp as string),
|
||||
notes: (histEntry.notes as string) ?? null,
|
||||
cost: (histEntry.cost as number | undefined) ?? null,
|
||||
duration: (histEntry.duration as number | undefined) ?? null,
|
||||
completed_by: (histEntry.completed_by as string) ?? null,
|
||||
used_parts: (histEntry.used_parts as Array<{ part_id: string; name?: string; quantity: number; entry_id?: string }> | null) ?? null,
|
||||
photo_doc_ids: historyPhotoIds(histEntry),
|
||||
reading_value: (histEntry.reading_value as number | null) ?? null,
|
||||
reading_values: entryReadingValues(histEntry),
|
||||
readings: task?.readings ?? [],
|
||||
task_type: task?.type ?? null,
|
||||
reading_unit: task?.reading_unit ?? null,
|
||||
});
|
||||
const draft = await loadHistoryEntryDraft(hass, detail.entry_id, detail.task_id, detail.original_timestamp);
|
||||
if (!draft) return;
|
||||
dm.openHistoryEditDialog(draft);
|
||||
} catch {
|
||||
deepLink("/maintenance-supporter");
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
import { LitElement, html, nothing, type TemplateResult } from "lit";
|
||||
import { isSafeHttpUrl } from "./helpers/url";
|
||||
import { queryTokens, scoreFields } from "./helpers/search-match";
|
||||
import { objectRef, parseRef, renderRefChip, taskRef } from "./helpers/reference";
|
||||
import { objectRef, parseRef, renderRefChip, taskRef, type HasRef } from "./helpers/reference";
|
||||
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
|
||||
import { isStaleBundle } from "./helpers/bundle-version";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, STATUS_ICONS, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, formatDate, formatDueDays, formatInterval, formatRecurrence, setProfilePrefs, langOf, formatNumber, formatCost } from "./styles";
|
||||
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, STATUS_ICONS, currencySymbolOf, t, ensureLocale, isLocaleLoaded, formatDate, formatDueDays, formatInterval, formatRecurrence, setProfilePrefs, langOf, formatCost, syncCurrencyDecimals} from "./styles";
|
||||
import { OVERVIEW_TABS, type OverviewTab } from "./helpers/overview-tabs";
|
||||
import { LS_KEYS, lsGet, lsSet } from "./helpers/storage-keys";
|
||||
import { openHtmlInNewTab, openSignedDocument, signApiPath } from "./helpers/document-url";
|
||||
import { readObjectsCache, writeObjectsCache } from "./helpers/objects-cache";
|
||||
@@ -119,8 +120,9 @@ import "./components/task-detail-view";
|
||||
import { computeWindow, VIRTUAL_MIN_ROWS } from "./helpers/virtual-window";
|
||||
import { INITIAL_STICKY, nextStickyState, stickyStateOnSelect, stickyTop, type StickyState } from "./helpers/sticky-pane";
|
||||
import { invalidateSettingsCache } from "./helpers/settings-cache";
|
||||
import { historyPhotoIds } from "./helpers/history-photos";
|
||||
import { entryReadingValues, readingSlotDelta } from "./helpers/reading-slots";
|
||||
import { buildHistoryEntryDraft } from "./helpers/history-draft";
|
||||
import { readingSlotDelta } from "./helpers/reading-slots";
|
||||
import { runWs } from "./helpers/ws-run";
|
||||
|
||||
type View = "overview" | "object" | "task" | "all_objects" | "all_parts";
|
||||
|
||||
@@ -151,14 +153,14 @@ interface PartsOverviewRow {
|
||||
type SortMode = "due_date" | "object" | "type" | "task_name" | "area" | "assigned_user" | "group";
|
||||
type ObjectSortMode = "alphabetical" | "due_soonest" | "task_count";
|
||||
type GroupByMode = "none" | "area" | "group" | "user" | "object";
|
||||
type OverviewTab = "today" | "dashboard" | "calendar" | "settings";
|
||||
// OverviewTab + OVERVIEW_TABS live in helpers/overview-tabs.ts — shared with the panel card's tab preset (#174).
|
||||
|
||||
// Value lists for everything that arrives as a free string — localStorage,
|
||||
// a saved view, a deep-link query — and must be validated before it lands in
|
||||
// state (an unknown mode would be persisted and silently break sorting).
|
||||
const SORT_MODES: readonly SortMode[] = ["due_date", "object", "type", "task_name", "area", "assigned_user", "group"];
|
||||
const GROUP_BY_MODES: readonly GroupByMode[] = ["none", "area", "group", "user", "object"];
|
||||
const OVERVIEW_TABS: readonly OverviewTab[] = ["today", "dashboard", "calendar", "settings"];
|
||||
// (OVERVIEW_TABS: imported from helpers/overview-tabs.ts.)
|
||||
/** The dashboard list's status filter options (the <select> in the toolbar). */
|
||||
const STATUS_FILTERS: readonly string[] = ["overdue", "due_soon", "triggered", "ok"];
|
||||
|
||||
@@ -179,6 +181,26 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
@property({ type: Boolean, reflect: true }) public split = false;
|
||||
private _tightObserver: ResizeObserver | null = null;
|
||||
@property({ attribute: false }) public panel: Record<string, unknown> = {};
|
||||
/** #174: mounted inside `maintenance-supporter-panel-card` rather than as
|
||||
* the sidebar panel — no hamburger (the dashboard has its own header), no
|
||||
* safe-area padding (the dashboard wrapper applies it), deep links read
|
||||
* from whatever path the dashboard has. Reflected for the CSS. */
|
||||
@property({ type: Boolean, reflect: true }) public embedded = false;
|
||||
/** #174: the card's opening tab / saved view — applied once on the initial
|
||||
* load when the URL carries no `tab` / `view` of its own, WITHOUT
|
||||
* persisting (the sidebar panel keeps its remembered tab). */
|
||||
@property({ attribute: false }) public presets: { tab?: string; view?: string } = {};
|
||||
/** Once per element instance — deliberately NOT reset on disconnect: HA
|
||||
* re-attaches the card element on edit-mode toggles and view switches,
|
||||
* and a reset flipped a user who had moved off the preset tab back onto
|
||||
* it every time (bug audit 2026-09-12). */
|
||||
private _presetsApplied = false;
|
||||
/** #174: the pathname the panel was mounted on. Embedded, deep links are
|
||||
* only ours while the page is still on that path — HA fires
|
||||
* `location-changed` before it swaps the DOM, so a card that is about to
|
||||
* be detached would otherwise consume another view's `?tab=` (bug audit
|
||||
* 2026-09-12). */
|
||||
private _mountPath: string | null = null;
|
||||
|
||||
@state() private _objects: MaintenanceObjectResponse[] = [];
|
||||
@state() private _stats: StatisticsResponse | null = null;
|
||||
@@ -212,7 +234,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
@state() private _budget: BudgetStatus | null = null;
|
||||
|
||||
private get _currencySymbol(): string {
|
||||
return this._budget?.currency_symbol || DEFAULT_CURRENCY_SYMBOL;
|
||||
return currencySymbolOf(this._budget);
|
||||
}
|
||||
|
||||
@state() private _groups: Record<string, MaintenanceGroup> = {};
|
||||
@@ -230,6 +252,8 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
@state() private _defaultWarningDays = 7;
|
||||
/** #145: global "Task row actions" style (buttons_compact | buttons | icons). */
|
||||
@state() private _rowActionStyle = "buttons_compact";
|
||||
/** #170: reference numbers in front of names in the lists (global setting). */
|
||||
@state() private _refsInLists = false;
|
||||
/** #145: one-time "rows look different now" notice for existing installs. */
|
||||
@state() private _rowActionNotice = false;
|
||||
@state() private _actionLoading = false;
|
||||
@@ -389,6 +413,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._mountPath = window.location.pathname;
|
||||
// Prefetch the lazy UI chunks AFTER the first paint settles (idle), not
|
||||
// here: an eager prefetch put 7 fetches + ~280 KB of parsing in direct
|
||||
// competition with the initial data load and measurably slowed first
|
||||
@@ -471,6 +496,14 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
this._initialLoadDone = false;
|
||||
this._lastConnection = null;
|
||||
this._deepLinkHandled = false;
|
||||
// `_presetsApplied` intentionally survives (see its declaration).
|
||||
// Pending timers must not fire into a detached element (bug audit
|
||||
// 2026-09-12): a debounced search would send a WS query for a closed
|
||||
// palette, and a toast timer would leave `_toastMessage` in whatever
|
||||
// state it was in when the element comes back.
|
||||
if (this._searchTimer) { clearTimeout(this._searchTimer); this._searchTimer = null; }
|
||||
if (this._toastTimer) { clearTimeout(this._toastTimer); this._toastTimer = null; }
|
||||
this._toastMessage = ""; this._toastUndo = null; this._toastActionLabel = "";
|
||||
this._statsService?.clearCache();
|
||||
this._statsService = null;
|
||||
}
|
||||
@@ -703,14 +736,14 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
});
|
||||
}
|
||||
if (statsResult) this._stats = statsResult as StatisticsResponse;
|
||||
if (budgetResult) this._budget = budgetResult as BudgetStatus;
|
||||
if (budgetResult) { this._budget = budgetResult as BudgetStatus; syncCurrencyDecimals(this._budget); }
|
||||
if (groupsResult) this._groups = (groupsResult as { groups: Record<string, MaintenanceGroup> }).groups || {};
|
||||
if (settingsResult) {
|
||||
const sr = settingsResult as {
|
||||
features: AdvancedFeatures;
|
||||
admin_panel_user_ids?: string[];
|
||||
operator_write_enabled?: boolean;
|
||||
general?: { default_warning_days?: number; row_action_style?: string; row_action_notice_pending?: boolean };
|
||||
general?: { default_warning_days?: number; row_action_style?: string; row_action_notice_pending?: boolean; ref_numbers_in_lists?: boolean };
|
||||
objects_table_columns?: string[];
|
||||
};
|
||||
this._features = sr.features;
|
||||
@@ -723,6 +756,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
const ras = sr.general?.row_action_style;
|
||||
this._rowActionStyle = ras === "icons" || ras === "buttons" ? ras : "buttons_compact";
|
||||
this._rowActionNotice = sr.general?.row_action_notice_pending === true;
|
||||
this._refsInLists = sr.general?.ref_numbers_in_lists === true;
|
||||
this._objectsTableColumns = sanitizeColumns(sr.objects_table_columns);
|
||||
}
|
||||
|
||||
@@ -751,9 +785,16 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
* params from it. Navigations away from the panel are not ours. */
|
||||
private _onLocationChanged(): void {
|
||||
if (!this._initialLoadDone || !window.location.search) return;
|
||||
const base = `/${typeof this.panel?.url_path === "string" ? this.panel.url_path : "maintenance-supporter"}`;
|
||||
const path = window.location.pathname;
|
||||
if (path !== base && !path.startsWith(`${base}/`)) return;
|
||||
// Embedded in a card (#174): the dashboard's path is ours, whatever it
|
||||
// is — but only the one we were mounted on. A navigation to ANOTHER
|
||||
// view carrying `?tab=` fires while this element is still attached and
|
||||
// must be left alone (bug audit 2026-09-12).
|
||||
if (this.embedded && this._mountPath !== null && window.location.pathname !== this._mountPath) return;
|
||||
if (!this.embedded) {
|
||||
const base = `/${typeof this.panel?.url_path === "string" ? this.panel.url_path : "maintenance-supporter"}`;
|
||||
const path = window.location.pathname;
|
||||
if (path !== base && !path.startsWith(`${base}/`)) return;
|
||||
}
|
||||
this._deepLinkHandled = false;
|
||||
// HA's navigate() already pushed the entry for this URL (state null), so
|
||||
// the show*() helpers must take that entry over instead of stacking a
|
||||
@@ -835,6 +876,25 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
// ignored. Like a tap, the tab (also the implied one) and the sort are
|
||||
// remembered for the next plain visit. Consumed once and stripped like
|
||||
// every other param.
|
||||
// #174: the card's presets open a tab / saved view once, without the
|
||||
// persistence a tap or a `?tab=` link has — a dashboard subview that
|
||||
// starts on Today must not rewrite the sidebar panel's remembered tab.
|
||||
if (this.embedded && !this._presetsApplied) {
|
||||
this._presetsApplied = true;
|
||||
const pt = this.presets?.tab ?? "";
|
||||
const pv = (this.presets?.view ?? "").trim();
|
||||
if (!params.has("tab") && (OVERVIEW_TABS as readonly string[]).includes(pt) && (pt !== "settings" || this.hass?.user?.is_admin)) {
|
||||
this._overviewTab = pt as OverviewTab;
|
||||
}
|
||||
if (!params.has("view") && pv) {
|
||||
const wanted = pv.toLowerCase();
|
||||
const match = this._savedViews.find((v) => v.id === pv) ?? this._savedViews.find((v) => v.name.trim().toLowerCase() === wanted);
|
||||
if (match) {
|
||||
this._overviewTab = "dashboard";
|
||||
this._applyView(match.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
const tab = params.get("tab");
|
||||
const view = params.get("view");
|
||||
const sort = params.get("sort");
|
||||
@@ -1046,7 +1106,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
]);
|
||||
if (!this.isConnected) return;
|
||||
if (statsResult) this._stats = statsResult as StatisticsResponse;
|
||||
if (budgetResult) this._budget = budgetResult as BudgetStatus;
|
||||
if (budgetResult) { this._budget = budgetResult as BudgetStatus; syncCurrencyDecimals(this._budget); }
|
||||
} while (this._kpiRefreshPending);
|
||||
} finally {
|
||||
this._kpiRefreshInFlight = false;
|
||||
@@ -1170,6 +1230,18 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
|
||||
// --- Navigation ---
|
||||
|
||||
/** #170: the "#8.3" chip in front of a list row's name, when the setting is on. */
|
||||
private _listRef(entryId: string, taskId: string) {
|
||||
if (!this._refsInLists) return nothing;
|
||||
const obj = this._getObject(entryId);
|
||||
return renderRefChip(taskRef(obj?.object, obj?.tasks.find((tk) => tk.id === taskId)));
|
||||
}
|
||||
|
||||
/** The "#8" chip in front of an object's name in cards / tables, when the setting is on. */
|
||||
private _objRef(obj: HasRef | null | undefined) {
|
||||
return this._refsInLists ? renderRefChip(objectRef(obj)) : nothing;
|
||||
}
|
||||
|
||||
/** Push a browser history entry so the back button navigates within the
|
||||
* panel. An in-app deep link arrives on the entry HA's navigate() just
|
||||
* pushed — that one is taken over instead (see _onLocationChanged). */
|
||||
@@ -1387,6 +1459,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
this._selectedTaskId = taskId;
|
||||
this._activeTab = "overview";
|
||||
this._historyFilter = null;
|
||||
this._historySearch = "";
|
||||
this._resetStickyPane();
|
||||
this._fetchFullHistory(entryId, taskId);
|
||||
const task = this._getTask(entryId, taskId);
|
||||
@@ -1401,6 +1474,10 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
this._selectedTaskId = taskId;
|
||||
this._activeTab = "overview";
|
||||
this._historyFilter = null;
|
||||
// The notes filter belongs to ONE task: the global search pre-fills it
|
||||
// for the history hit it opened (after this call), and it must not leak
|
||||
// into every task opened afterwards (bug audit 2026-09-12).
|
||||
this._historySearch = "";
|
||||
this._scrollContentToTop();
|
||||
// Payload diet: list responses carry only the most recent history
|
||||
// window — the detail's full timeline/charts load here, on demand.
|
||||
@@ -1513,7 +1590,15 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
}).then((res) => {
|
||||
if (seq !== this._searchSeq || !this._paletteOpen) return;
|
||||
this._searchRemote = { query: q, documents: res.documents || [], history: res.history || [] };
|
||||
}).catch(() => { /* the local groups still answer; the server groups just stay empty */ });
|
||||
}).catch(() => {
|
||||
// The local groups still answer; the server groups just stay empty.
|
||||
// Record the (empty) answer for this query — `waiting` is derived
|
||||
// from `_searchRemote.query === q`, so a swallowed error left the
|
||||
// "Searching…" footer / placeholder up forever (bug audit
|
||||
// 2026-09-12).
|
||||
if (seq !== this._searchSeq || !this._paletteOpen) return;
|
||||
this._searchRemote = { query: q, documents: [], history: [] };
|
||||
});
|
||||
}, SEARCH_DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
@@ -1540,12 +1625,16 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
const o = obj.object;
|
||||
if (o.ref_no !== ref.object) continue;
|
||||
const oref = objectRef(o)!;
|
||||
// A number on paper outlives the archive: an archived object or task
|
||||
// still answers to its reference (with an "Archived" note), while the
|
||||
// text search below keeps hiding archived things.
|
||||
const archivedNote = (flag: boolean | undefined, base: string) => (flag ? [base, t("archived", L)].filter(Boolean).join(" · ") : base);
|
||||
if (ref.task == null) {
|
||||
objects.push({ kind: "object", entryId: obj.entry_id, label: o.name || "", sub: t("object", L), score: 1000, icon: "mdi:package-variant-closed", ref: oref });
|
||||
objects.push({ kind: "object", entryId: obj.entry_id, label: o.name || "", sub: archivedNote(o.archived, t("object", L)), score: 1000, icon: "mdi:package-variant-closed", ref: oref });
|
||||
}
|
||||
for (const task of obj.tasks) {
|
||||
if (task.archived || (ref.task != null && task.ref_no !== ref.task)) continue;
|
||||
tasks.push({ kind: "task", entryId: obj.entry_id, taskId: task.id, label: task.name || "", sub: o.name || "", score: ref.task == null ? 900 : 1000, icon: "mdi:clipboard-check-outline", ref: taskRef(o, task) });
|
||||
if (ref.task != null && task.ref_no !== ref.task) continue;
|
||||
tasks.push({ kind: "task", entryId: obj.entry_id, taskId: task.id, label: task.name || "", sub: archivedNote(task.archived || o.archived, o.name || ""), score: ref.task == null ? 900 : 1000, icon: "mdi:clipboard-check-outline", ref: taskRef(o, task) });
|
||||
}
|
||||
}
|
||||
if (objects.length || tasks.length) {
|
||||
@@ -2636,7 +2725,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
${this.narrow ? html`<ha-menu-button .hass=${this.hass} .narrow=${this.narrow}></ha-menu-button>` : nothing}
|
||||
${this.narrow && !this.embedded ? html`<ha-menu-button .hass=${this.hass} .narrow=${this.narrow}></ha-menu-button>` : nothing}
|
||||
${this._view !== "overview"
|
||||
? html`<ha-icon-button
|
||||
.path=${"M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z"}
|
||||
@@ -2845,25 +2934,13 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<div class="today-row" @click=${() => this._showTask(row.entry_id, row.task_id)}>
|
||||
<span class="today-dot ${row.trigger_active ? "triggered" : row.status}"></span>
|
||||
<div class="today-main">
|
||||
<div class="today-task">${row.task_name}</div>
|
||||
<div class="today-task">${this._listRef(row.entry_id, row.task_id)}${row.task_name}</div>
|
||||
<div class="today-object">
|
||||
<span class="today-object-text">${row.object_name} · ${formatDueDays(row.days_until_due, L)}</span>
|
||||
${renderPersonChip(personOf(row), "today-person")}
|
||||
</div>
|
||||
</div>
|
||||
${this._actionStyle() === "icons"
|
||||
? html`
|
||||
<mwc-icon-button class="btn-complete" title="${t("complete", L)}"
|
||||
@click=${(e: Event) => { e.stopPropagation(); this._openCompleteDialogForRow(row); }}>
|
||||
<ha-icon icon="mdi:check"></ha-icon>
|
||||
</mwc-icon-button>`
|
||||
: html`
|
||||
<ha-button size="small" appearance="accent" variant="success" class="today-complete" title="${t("complete", L)}"
|
||||
@click=${(e: Event) => { e.stopPropagation(); this._openCompleteDialogForRow(row); }}>
|
||||
${this._actionStyle() === "buttons_compact" && (this.narrow || this.tight)
|
||||
? html`<ha-icon icon="mdi:check"></ha-icon>`
|
||||
: html`<ha-icon slot="start" icon="mdi:check"></ha-icon>${t("complete", L)}`}
|
||||
</ha-button>`}
|
||||
${this._renderRowActions(L, () => this._openCompleteDialogForRow(row), undefined, false)}
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
@@ -3327,7 +3404,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<div class="object-card${overdue ? ' object-card-overdue' : ''}" @click=${() => this._showObject(obj.entry_id)}>
|
||||
${overdue ? html`<span class="overdue-dot" title="${t("has_overdue", L)}"></span>` : nothing}
|
||||
<div class="object-card-header">
|
||||
<span class="object-card-name">${obj.object.name}</span>
|
||||
<span class="object-card-name">${this._objRef(obj.object)}${obj.object.name}</span>
|
||||
${obj.object.paused
|
||||
? html`<span class="paused-badge" title="${t("object_paused_badge", L)}${obj.object.paused_until ? ` — ${obj.object.paused_until}` : ""}">
|
||||
<ha-icon icon="mdi:pause-circle-outline"></ha-icon>
|
||||
@@ -3491,7 +3568,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
${rows.map((row) => html`
|
||||
<tr class="objects-table-row" @click=${() => this._showObject(row.entry_id)}>
|
||||
<td>
|
||||
<span class="objects-table-name">${row.name}</span>
|
||||
<span class="objects-table-name">${this._objRef(this._getObject(row.entry_id)?.object)}${row.name}</span>
|
||||
${row.low
|
||||
? html`<ha-icon class="part-low-icon" icon="mdi:cart-arrow-down"
|
||||
title="${t("part_reorder_threshold", L)}: ${row.reorder_threshold}"></ha-icon>`
|
||||
@@ -3500,7 +3577,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<td>${row.object_name || "—"}</td>
|
||||
<td>${row.stock !== null ? `${row.stock}${row.unit ? ` ${row.unit}` : ""}` : "—"}</td>
|
||||
<td>${row.reorder_threshold ?? "—"}</td>
|
||||
<td>${row.cost != null ? `${row.cost} ${currency}`.trim() : "—"}</td>
|
||||
<td>${row.cost != null ? formatCost(row.cost, currency, L) : "—"}</td>
|
||||
<td>${row.storage_location || "—"}</td>
|
||||
<td>
|
||||
${row.consumers.length === 0
|
||||
@@ -3544,21 +3621,16 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
esc(row.consumers.map((c) => `${c.object_name ?? ""}/${c.task_name ?? c.task_id}×${c.quantity}`).join(" | ")),
|
||||
].join(","));
|
||||
}
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
const ts = isoDateLocal(new Date());
|
||||
downloadTextFile(lines.join("\n"), `maintenance_parts_${ts}.csv`, "text/csv;charset=utf-8");
|
||||
}
|
||||
|
||||
// (#67 / Phase 3) Download all objects as a one-row-per-object CSV.
|
||||
private async _exportObjectsCsv(): Promise<void> {
|
||||
try {
|
||||
const result = await this.hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/objects/csv",
|
||||
}) as { csv: string };
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
downloadTextFile(result.csv, `maintenance_objects_${ts}.csv`, "text/csv;charset=utf-8");
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
}
|
||||
const result = await runWs<{ csv: string }>(this, { type: "maintenance_supporter/objects/csv" }, { onError: (m) => this._showToast(m) });
|
||||
if (!result) return;
|
||||
const ts = isoDateLocal(new Date());
|
||||
downloadTextFile(result.csv, `maintenance_objects_${ts}.csv`, "text/csv;charset=utf-8");
|
||||
}
|
||||
|
||||
// (#67) Tabular All-Objects view honouring the configured columns. Desktop
|
||||
@@ -3596,7 +3668,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
switch (key) {
|
||||
case "name":
|
||||
return html`<td class="oc-name">
|
||||
<span class="objects-table-name">${o.name}</span>
|
||||
<span class="objects-table-name">${this._objRef(o)}${o.name}</span>
|
||||
${o.document_count
|
||||
? html`<span class="doc-badge" title="${o.document_count} ${t("documents", L)}">
|
||||
<ha-icon icon="mdi:paperclip"></ha-icon>${o.document_count}
|
||||
@@ -3753,9 +3825,9 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
const pct = Math.min(100, Math.max(0, (spent / budget) * 100));
|
||||
const color = pct >= 100 ? "var(--error-color, #f44336)" : pct >= b.alert_threshold_pct ? "var(--warning-color, #ff9800)" : "var(--success-color, #4caf50)";
|
||||
return html`
|
||||
<div class="stat-item budget-tile" title="${label}: ${formatNumber(spent, L, 2)} / ${formatCost(budget, cs, L)}">
|
||||
<div class="stat-item budget-tile" title="${label}: ${formatCost(spent, cs, L)} / ${formatCost(budget, cs, L)}">
|
||||
<span class="stat-value budget-tile-value">${formatCost(spent, cs, L)}</span>
|
||||
<span class="budget-tile-max">/ ${formatCost(budget, cs, L, 0)}</span>
|
||||
<span class="budget-tile-max">/ ${formatCost(budget, cs, L)}</span>
|
||||
<div class="budget-tile-bar"><div style="width:${pct}%; background:${color}"></div></div>
|
||||
<span class="stat-label">${label}</span>
|
||||
</div>
|
||||
@@ -3810,7 +3882,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
</span>
|
||||
<span class="row-head">
|
||||
<span class="cell object-name" @click=${(e: Event) => { e.stopPropagation(); this._showObject(row.entry_id); }}>${row.object_name}</span>
|
||||
<span class="cell task-name" @click=${() => this._showTask(row.entry_id, row.task_id)}>${row.task_name}</span>
|
||||
<span class="cell task-name" @click=${() => this._showTask(row.entry_id, row.task_id)}>${this._listRef(row.entry_id, row.task_id)}${row.task_name}</span>
|
||||
</span>
|
||||
<span class="task-sub${hasSub ? '' : ' task-sub-empty'}">
|
||||
${row.group_names.length > 0 ? html`
|
||||
@@ -3858,20 +3930,17 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
private async _dismissRowActionNotice(backToIcons: boolean): Promise<void> {
|
||||
const settings: Record<string, unknown> = { row_action_notice_pending: false };
|
||||
if (backToIcons) settings.row_action_style = "icons";
|
||||
try {
|
||||
await this.hass.connection.sendMessagePromise({ type: "maintenance_supporter/global/update", settings });
|
||||
this._rowActionNotice = false;
|
||||
if (backToIcons) this._rowActionStyle = "icons";
|
||||
invalidateSettingsCache();
|
||||
} catch (err) {
|
||||
console.warn("[maintenance-supporter] row-action notice update failed", err);
|
||||
}
|
||||
const ok = await runWs(this, { type: "maintenance_supporter/global/update", settings }, { onError: (m) => this._showToast(m) });
|
||||
if (ok === undefined) return;
|
||||
this._rowActionNotice = false;
|
||||
if (backToIcons) this._rowActionStyle = "icons";
|
||||
invalidateSettingsCache();
|
||||
}
|
||||
|
||||
/** #145: row actions as HA buttons — labelled (accent/success + outlined
|
||||
* warning, the task-detail pairing) or, for "buttons_compact" on narrow screens,
|
||||
* icon-only — or the classic icon pair when the household chose "icons". */
|
||||
private _renderRowActions(L: string, onComplete: () => void, onSkip: () => void, allowSkip = true): TemplateResult {
|
||||
private _renderRowActions(L: string, onComplete: () => void, onSkip?: () => void, allowSkip = true): TemplateResult {
|
||||
const style = this._actionStyle();
|
||||
if (style === "buttons" || style === "buttons_compact") {
|
||||
const iconOnly = style === "buttons_compact" && (this.narrow || this.tight);
|
||||
@@ -3886,7 +3955,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<ha-icon icon="mdi:check"></ha-icon>
|
||||
</ha-button>
|
||||
${allowSkip ? html`
|
||||
<ha-button size="small" appearance="outlined" variant="warning" title="${t("skip", L)}" aria-label="${t("skip", L)}" ?disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<ha-button size="small" appearance="outlined" variant="warning" title="${t("skip", L)}" aria-label="${t("skip", L)}" ?disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip?.(); }}>
|
||||
<ha-icon icon="mdi:skip-next"></ha-icon>
|
||||
</ha-button>` : nothing}
|
||||
</span>`;
|
||||
@@ -3897,7 +3966,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<ha-icon slot="start" icon="mdi:check"></ha-icon>${t("complete", L)}
|
||||
</ha-button>
|
||||
${allowSkip ? html`
|
||||
<ha-button size="small" appearance="outlined" variant="warning" title="${t("skip", L)}" ?disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<ha-button size="small" appearance="outlined" variant="warning" title="${t("skip", L)}" ?disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip?.(); }}>
|
||||
<ha-icon slot="start" icon="mdi:skip-next"></ha-icon>${t("skip", L)}
|
||||
</ha-button>` : nothing}
|
||||
</span>`;
|
||||
@@ -3908,7 +3977,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<ha-icon icon="mdi:check"></ha-icon>
|
||||
</mwc-icon-button>
|
||||
${allowSkip ? html`
|
||||
<mwc-icon-button class="btn-skip" title="${t("skip", L)}" .disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<mwc-icon-button class="btn-skip" title="${t("skip", L)}" .disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip?.(); }}>
|
||||
<ha-icon icon="mdi:skip-next"></ha-icon>
|
||||
</mwc-icon-button>` : nothing}
|
||||
</span>`;
|
||||
@@ -4044,7 +4113,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
? html`<span class="doc-badge" title="${task.document_count} ${t("documents", L)}"><ha-icon icon="mdi:paperclip"></ha-icon>${task.document_count}</span>`
|
||||
: nothing}
|
||||
</span>
|
||||
<span class="cell task-name" @click=${() => this._showTask(obj.entry_id, task.id)}>${task.name}</span>
|
||||
<span class="cell task-name" @click=${() => this._showTask(obj.entry_id, task.id)}>${this._listRef(obj.entry_id, task.id)}${task.name}</span>
|
||||
<span class="task-sub${task.responsible_user_id ? '' : ' task-sub-empty'}">${renderUserBadge(task, (id) => this._userService?.getUserName(id) ?? null, (id) => this._userService?.getPerson(id) ?? null)}</span>
|
||||
<span class="cell type">${t(task.type, L)}</span>
|
||||
<span class="due-cell" @click=${() => this._showTask(obj.entry_id, task.id)}>
|
||||
@@ -4434,24 +4503,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
private _openHistoryEdit(entry: HistoryEntry): void {
|
||||
if (!this._selectedEntryId || !this._selectedTaskId) return;
|
||||
const editTask = this._getTask(this._selectedEntryId, this._selectedTaskId);
|
||||
const draft: HistoryEntryDraft = {
|
||||
entry_id: this._selectedEntryId,
|
||||
task_id: this._selectedTaskId,
|
||||
original_timestamp: entry.timestamp,
|
||||
type: entry.type,
|
||||
timestamp: entry.timestamp,
|
||||
notes: entry.notes ?? null,
|
||||
cost: entry.cost ?? null,
|
||||
duration: entry.duration ?? null,
|
||||
completed_by: entry.completed_by ?? null,
|
||||
used_parts: entry.used_parts ?? null,
|
||||
photo_doc_ids: historyPhotoIds(entry),
|
||||
reading_value: entry.reading_value ?? null,
|
||||
reading_values: entryReadingValues(entry),
|
||||
readings: editTask?.readings ?? [],
|
||||
task_type: editTask?.type ?? null,
|
||||
reading_unit: editTask?.reading_unit ?? null,
|
||||
};
|
||||
const draft: HistoryEntryDraft = buildHistoryEntryDraft(this._selectedEntryId, this._selectedTaskId, entry, editTask);
|
||||
this.shadowRoot
|
||||
?.querySelector<MaintenanceHistoryEditDialog>("maintenance-history-edit-dialog")
|
||||
?.openEdit(draft);
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
import { css } from "lit";
|
||||
|
||||
export const panelStyles = css`
|
||||
/* #170: reference chips in front of list names. */
|
||||
.task-name .ref-chip, .today-task .ref-chip, .object-card-name .ref-chip, .objects-table-name .ref-chip { margin-right: 6px; }
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
@@ -29,6 +32,9 @@ export const panelStyles = css`
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* #174: inside a card the dashboard wrapper owns the insets. */
|
||||
:host([embedded]) .panel { padding: 0; }
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -414,11 +420,12 @@ export const panelStyles = css`
|
||||
the object name keeps its room. */
|
||||
:host([narrow]) .today-object .today-person .person-name,
|
||||
:host([tight]) .today-object .today-person .person-name { display: none; }
|
||||
.today-row .btn-complete { color: var(--success-color, #4caf50); flex: none; }
|
||||
.today-row .today-complete { flex: none; --ha-button-font-size: 13px; white-space: nowrap; }
|
||||
.today-row .today-complete ha-icon { --mdc-icon-size: 18px; }
|
||||
:host([narrow]) .today-row .today-complete,
|
||||
:host([tight]) .today-row .today-complete { min-width: 0; --ha-button-height: 36px; }
|
||||
/* The row actions are the shared _renderRowActions markup (Complete only,
|
||||
no Skip on Today); .row-actions carries the icon/button sizing. */
|
||||
.today-row .row-actions { flex: none; }
|
||||
.today-row .btn-complete { color: var(--success-color, #4caf50); }
|
||||
:host([narrow]) .today-row .row-actions.as-buttons ha-button,
|
||||
:host([tight]) .today-row .row-actions.as-buttons ha-button { min-width: 0; --ha-button-height: 36px; }
|
||||
.today-empty {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 10px;
|
||||
padding: 48px 16px; color: var(--secondary-text-color); text-align: center;
|
||||
|
||||
@@ -4,28 +4,36 @@
|
||||
* the active filter, the search text, the currency symbol — plus the
|
||||
* callbacks that mutate it or open the edit dialog are passed in via
|
||||
* HistoryContext, keeping these functions pure of component internals.
|
||||
*
|
||||
* `renderHistoryEntry` is THE history row: the panel timeline, the
|
||||
* quick-actions dialog and the task-detail "recent activities" card all
|
||||
* render it (DRY round 2026-09 — the dialog and the card carried their
|
||||
* own subsets, without badges / ref chips / photos / per-slot deltas).
|
||||
*/
|
||||
|
||||
import { html, nothing } from "lit";
|
||||
import { t, formatDateTime, formatNumber, formatCost, STATUS_ICONS } from "../styles";
|
||||
import type { MaintenanceTask, HistoryEntry, HomeAssistant } from "../types";
|
||||
import { t, formatDateTime, formatNumber, formatCost, formatDuration, STATUS_ICONS } from "../styles";
|
||||
import type { MaintenanceTask, HistoryEntry, HistoryEntryType, HomeAssistant } from "../types";
|
||||
import "../components/history-photo";
|
||||
import { historyPhotoIds } from "../helpers/history-photos";
|
||||
import { renderRefChip } from "../helpers/reference";
|
||||
import { entryReadingValues } from "../helpers/reading-slots";
|
||||
import { matchesQuery } from "../helpers/search-match";
|
||||
|
||||
export interface HistoryContext {
|
||||
/** v2.2.0: only "lifecycle + cost-bearing" entries are user-editable.
|
||||
* Triggers are auto-generated by sensors and shouldn't be retroactively
|
||||
* rewritten. (helpers/object-history LIFECYCLE_TYPES = this + "missed",
|
||||
* a different concept: what belongs in the object's record.) */
|
||||
export const EDITABLE_HISTORY_TYPES: readonly HistoryEntryType[] = ["completed", "reset", "skipped"];
|
||||
|
||||
/** What one history row needs — the panel's HistoryContext is a superset;
|
||||
* the dialog and the overview card build just this. */
|
||||
export interface HistoryEntryContext {
|
||||
lang: string;
|
||||
/** Home Assistant object — used to sign completion-photo URLs. */
|
||||
hass: HomeAssistant;
|
||||
/** Active type filter, or null for "all". */
|
||||
filter: string | null;
|
||||
/** Free-text search over entry notes. */
|
||||
search: string;
|
||||
/** Currency symbol for cost display (defaults to €). */
|
||||
currencySymbol: string;
|
||||
setFilter: (filter: string | null) => void;
|
||||
setSearch: (search: string) => void;
|
||||
openEdit: (entry: HistoryEntry) => void;
|
||||
/** v2.20 (#83): unit + delta-vs-previous for reading-task entries. */
|
||||
readingUnit?: string | null;
|
||||
@@ -38,6 +46,26 @@ export interface HistoryContext {
|
||||
taskRef?: string | null;
|
||||
}
|
||||
|
||||
export interface HistoryContext extends HistoryEntryContext {
|
||||
/** Active type filter, or null for "all". */
|
||||
filter: string | null;
|
||||
/** Free-text search over entry notes. */
|
||||
search: string;
|
||||
setFilter: (filter: string | null) => void;
|
||||
setSearch: (search: string) => void;
|
||||
}
|
||||
|
||||
export interface HistoryEntryOptions {
|
||||
/** Dense row without the status-icon column (dialog / overview card). */
|
||||
compact?: boolean;
|
||||
/** The "#8.3-2" chip (default on; needs ctx.taskRef to show anything). */
|
||||
showRef?: boolean;
|
||||
/** Phase + "automatic" badges (default on). */
|
||||
showBadges?: boolean;
|
||||
/** The pencil on editable entries (default on). */
|
||||
showEdit?: boolean;
|
||||
}
|
||||
|
||||
// Every HistoryEntryType is filterable (drift audit 2026-08: the repair-flow
|
||||
// types were missing, so their entries could not be isolated). Chips only
|
||||
// render for types present in the task's history, so the two rare ones cost
|
||||
@@ -74,11 +102,15 @@ export function renderHistoryList(task: MaintenanceTask, ctx: HistoryContext) {
|
||||
? task.history.filter((h) => h.type === ctx.filter)
|
||||
: task.history;
|
||||
|
||||
// Apply search filter
|
||||
// Apply search filter. Notes match through the same tolerant matcher the
|
||||
// global search and the server use (fold, digraphs, AND over words in any
|
||||
// order) — the palette pre-fills this box from a SERVER history hit, and a
|
||||
// plain includes() landed on an empty tab for "spuelung" vs "Spülung
|
||||
// durchgeführt" (bug audit 2026-09-12).
|
||||
if (ctx.search) {
|
||||
const search = ctx.search.toLowerCase();
|
||||
// #170: "8.3-2" (or just "-2") in the notes filter finds the entry by number.
|
||||
filtered = filtered.filter((h) => h.notes?.toLowerCase().includes(search) || (entryRefOf(ctx, h) ?? "").toLowerCase().endsWith(search));
|
||||
filtered = filtered.filter((h) => matchesQuery(h.notes, ctx.search) || (entryRefOf(ctx, h) ?? "").toLowerCase().endsWith(search));
|
||||
}
|
||||
|
||||
if (filtered.length === 0) {
|
||||
@@ -93,30 +125,66 @@ export function renderHistoryList(task: MaintenanceTask, ctx: HistoryContext) {
|
||||
}
|
||||
|
||||
/** "8.3-2" for a numbered completion, null otherwise. */
|
||||
function entryRefOf(ctx: HistoryContext, entry: HistoryEntry): string | null {
|
||||
function entryRefOf(ctx: HistoryEntryContext, entry: HistoryEntry): string | null {
|
||||
return ctx.taskRef && entry.ref_no ? `${ctx.taskRef}-${entry.ref_no}` : null;
|
||||
}
|
||||
|
||||
export function renderHistoryEntry(entry: HistoryEntry, ctx: HistoryContext) {
|
||||
/** The `<maintenance-history-photo>` strip of an entry (#161: photo_doc_ids,
|
||||
* or the pre-multi-photo photo_doc_id scalar); `nothing` without photos. */
|
||||
export function renderHistoryPhotos(hass: HomeAssistant, entry: HistoryEntry | Record<string, unknown>) {
|
||||
const photos = historyPhotoIds(entry);
|
||||
return photos.length > 0
|
||||
? html`<div class="history-photos">
|
||||
${photos.map((docId) => html`<maintenance-history-photo .hass=${hass} .docId=${docId}></maintenance-history-photo>`)}
|
||||
</div>`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
/** The entry's readings — one line per recorded slot (#161 phase 2) with the
|
||||
* delta against the previous entry carrying that slot, or the single value
|
||||
* of a scalar reading task (v2.20, #83) with its delta. */
|
||||
function renderHistoryReadings(entry: HistoryEntry, ctx: HistoryEntryContext) {
|
||||
const L = ctx.lang;
|
||||
// v2.2.0: only "lifecycle + cost-bearing" entries are user-editable.
|
||||
// Triggers are auto-generated by sensors and shouldn't be retroactively
|
||||
// rewritten. Allow edits for completed / reset / skipped entries.
|
||||
const editable = ["completed", "reset", "skipped"].includes(entry.type);
|
||||
const fmt = (n: number) => formatNumber(n, L, { maximumFractionDigits: 3 });
|
||||
const delta = (d: number | null | undefined) => (d == null ? "" : ` (${d >= 0 ? "+" : ""}${fmt(d)})`);
|
||||
const values = entryReadingValues(entry);
|
||||
if (values.length > 0) {
|
||||
return html`<div class="history-readings">
|
||||
${values.map((v) => html`<span class="history-reading">
|
||||
<span class="history-reading-name">${v.name}</span>
|
||||
<span class="history-reading-value">${fmt(v.value)}${v.unit ? ` ${v.unit}` : ""}${delta(ctx.readingSlotDelta?.(entry, v.id))}</span>
|
||||
</span>`)}
|
||||
</div>`;
|
||||
}
|
||||
if (entry.reading_value != null) {
|
||||
return html`<div class="history-readings"><span class="history-reading">
|
||||
<span class="history-reading-name">${t("reading_label", L)}</span>
|
||||
<span class="history-reading-value">${fmt(entry.reading_value)}${ctx.readingUnit ? ` ${ctx.readingUnit}` : ""}${delta(ctx.readingDelta?.(entry))}</span>
|
||||
</span></div>`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
export function renderHistoryEntry(entry: HistoryEntry, ctx: HistoryEntryContext, opts: HistoryEntryOptions = {}) {
|
||||
const L = ctx.lang;
|
||||
const { compact = false, showRef = true, showBadges = true, showEdit = true } = opts;
|
||||
const editable = showEdit && EDITABLE_HISTORY_TYPES.includes(entry.type);
|
||||
return html`
|
||||
<div class="history-entry">
|
||||
<div class="history-icon ${entry.type}">
|
||||
<ha-icon .icon=${STATUS_ICONS[entry.type] || "mdi:circle"}></ha-icon>
|
||||
</div>
|
||||
<div class="history-entry${compact ? " compact" : ""}">
|
||||
${compact
|
||||
? nothing
|
||||
: html`<div class="history-icon ${entry.type}">
|
||||
<ha-icon .icon=${STATUS_ICONS[entry.type] || "mdi:circle"}></ha-icon>
|
||||
</div>`}
|
||||
<div class="history-content">
|
||||
<div class="history-row">
|
||||
<strong>${t(entry.type, L)}</strong>
|
||||
${renderRefChip(entryRefOf(ctx, entry), t("ref_number", L))}
|
||||
${entry.phase_id ? html`<span class="history-phase-badge">${ctx.phaseNames?.[entry.phase_id] || entry.phase_id}</span>` : nothing}
|
||||
${entry.auto ? html`<span class="history-auto-badge">${t("history_auto", L)}</span>` : nothing}
|
||||
${showRef ? renderRefChip(entryRefOf(ctx, entry), t("ref_number", L)) : nothing}
|
||||
${showBadges && entry.phase_id ? html`<span class="history-phase-badge">${ctx.phaseNames?.[entry.phase_id] || entry.phase_id}</span>` : nothing}
|
||||
${showBadges && entry.auto ? html`<span class="history-auto-badge">${t("history_auto", L)}</span>` : nothing}
|
||||
${editable
|
||||
? html`<button class="history-edit-btn"
|
||||
title=${t("history_edit_button", L) || "Edit entry"}
|
||||
title=${t("history_edit_button", L)}
|
||||
@click=${() => ctx.openEdit(entry)}>
|
||||
<ha-icon icon="mdi:pencil"></ha-icon>
|
||||
</button>`
|
||||
@@ -124,41 +192,15 @@ export function renderHistoryEntry(entry: HistoryEntry, ctx: HistoryContext) {
|
||||
</div>
|
||||
<div class="history-date">${formatDateTime(entry.timestamp, L)}</div>
|
||||
${entry.notes ? html`<div>${entry.notes}</div>` : nothing}
|
||||
${(() => {
|
||||
// #161: photo_doc_ids (new) or the pre-multi-photo photo_doc_id scalar.
|
||||
const photos = historyPhotoIds(entry);
|
||||
return photos.length > 0
|
||||
? html`<div class="history-photos">
|
||||
${photos.map((docId) => html`<maintenance-history-photo .hass=${ctx.hass} .docId=${docId}></maintenance-history-photo>`)}
|
||||
</div>`
|
||||
: nothing;
|
||||
})()}
|
||||
${(() => {
|
||||
// #161 phase 2: one line per recorded slot, delta vs. the previous
|
||||
// completion that carried the same slot.
|
||||
const values = entryReadingValues(entry);
|
||||
if (values.length === 0) return nothing;
|
||||
return html`<div class="history-readings">
|
||||
${values.map((v) => {
|
||||
const d = ctx.readingSlotDelta?.(entry, v.id);
|
||||
return html`<span class="history-reading">
|
||||
<span class="history-reading-name">${v.name}</span>
|
||||
<span class="history-reading-value">${formatNumber(v.value, L, { maximumFractionDigits: 3 })}${v.unit ? ` ${v.unit}` : ""}${d == null ? "" : ` (${d >= 0 ? "+" : ""}${formatNumber(d, L, { maximumFractionDigits: 3 })})`}</span>
|
||||
</span>`;
|
||||
})}
|
||||
</div>`;
|
||||
})()}
|
||||
<div class="history-details">
|
||||
${entry.cost != null ? html`<span>${t("cost", L)}: ${formatCost(entry.cost, ctx.currencySymbol, L)}</span>` : nothing}
|
||||
${entry.duration != null ? html`<span>${t("duration", L)}: ${entry.duration} min</span>` : nothing}
|
||||
${entry.trigger_value != null ? html`<span>${t("trigger_val", L)}: ${entry.trigger_value}</span>` : nothing}
|
||||
${entry.reading_value != null
|
||||
? html`<span>${t("reading_label", L)}: ${entry.reading_value}${ctx.readingUnit ? ` ${ctx.readingUnit}` : ""}${(() => {
|
||||
const d = ctx.readingDelta?.(entry);
|
||||
return d == null ? "" : ` (${d >= 0 ? "+" : ""}${formatNumber(d, L, { maximumFractionDigits: 3 })})`;
|
||||
})()}</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
${renderHistoryPhotos(ctx.hass, entry)}
|
||||
${renderHistoryReadings(entry, ctx)}
|
||||
${entry.cost != null || entry.duration != null || entry.trigger_value != null
|
||||
? html`<div class="history-details">
|
||||
${entry.cost != null ? html`<span>${t("cost", L)}: ${formatCost(entry.cost, ctx.currencySymbol, L)}</span>` : nothing}
|
||||
${entry.duration != null ? html`<span>${t("duration", L)}: ${formatDuration(entry.duration, L)}</span>` : nothing}
|
||||
${entry.trigger_value != null ? html`<span>${t("trigger_val", L)}: ${entry.trigger_value}</span>` : nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -45,9 +45,9 @@ export function renderSeasonalCardCompact(task: MaintenanceTask, lang: string, f
|
||||
})}
|
||||
</div>
|
||||
<div class="seasonal-legend">
|
||||
<span class="legend-item"><span class="dot low"></span> ${t("shorter", lang) || "Kürzer"}</span>
|
||||
<span class="legend-item"><span class="dot normal"></span> ${t("normal", lang) || "Normal"}</span>
|
||||
<span class="legend-item"><span class="dot high"></span> ${t("longer", lang) || "Länger"}</span>
|
||||
<span class="legend-item"><span class="dot low"></span> ${t("shorter", lang)}</span>
|
||||
<span class="legend-item"><span class="dot normal"></span> ${t("normal", lang)}</span>
|
||||
<span class="legend-item"><span class="dot high"></span> ${t("longer", lang)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { html, nothing } from "lit";
|
||||
import { isSafeHttpUrl } from "../helpers/url";
|
||||
import { renderNotesMarkdown } from "../helpers/notes-markdown";
|
||||
import { renderPersonAvatar, type PersonDisplay } from "../helpers/person";
|
||||
import { t, formatDate, formatDateTime, formatRecurrence, formatNumber, formatCost } from "../styles";
|
||||
import { t, formatDate, formatRecurrence, formatCost, formatDuration } from "../styles";
|
||||
import { renderRefChip } from "../helpers/reference";
|
||||
import type { AdvancedFeatures, HomeAssistant, MaintenanceTask, ManualDocRef } from "../types";
|
||||
import { renderTriggerSection, type SparklineContext } from "./sparkline";
|
||||
@@ -23,7 +23,7 @@ import { renderRecommendationBars } from "./recommendation";
|
||||
import { renderSeasonalCardCompact, renderSeasonalCardExpanded } from "./seasonal";
|
||||
import { renderCostDurationCard } from "./charts";
|
||||
import { renderDaysProgress } from "./progress";
|
||||
import { renderHistoryFilters, renderHistoryList, type HistoryContext } from "./history";
|
||||
import { renderHistoryEntry, renderHistoryFilters, renderHistoryList, type HistoryContext } from "./history";
|
||||
import { clampPhaseCursor, effectivePhase, hasPhases } from "../helpers/phases";
|
||||
import "../components/task-documents";
|
||||
|
||||
@@ -369,11 +369,11 @@ function renderKPIBar(task: MaintenanceTask, ctx: TaskDetailContext) {
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">${t("avg_cost", L)}</div>
|
||||
<div class="kpi-value">${formatCost(avgCost, ctx.currencySymbol, L, 0)}</div>
|
||||
<div class="kpi-value">${formatCost(avgCost, ctx.currencySymbol, L)}</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-label">${t("avg_duration", L)}</div>
|
||||
<div class="kpi-value">${task.average_duration ? formatNumber(task.average_duration, L, 0) : "—"} min</div>
|
||||
<div class="kpi-value">${task.average_duration ? formatDuration(Math.round(task.average_duration), L) : "—"}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -422,28 +422,13 @@ function renderRecentActivities(task: MaintenanceTask, ctx: TaskDetailContext) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const getIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case "completed": return "✓";
|
||||
case "triggered": return "⊗";
|
||||
case "skipped": return "↷";
|
||||
case "reset": return "↺";
|
||||
default: return "·";
|
||||
}
|
||||
};
|
||||
|
||||
// The shared history row (compact, no pencil — editing lives on the
|
||||
// History tab); the panel's history context carries the deltas, phase
|
||||
// names and the task ref for the "#8.3-2" chip.
|
||||
return html`
|
||||
<div class="recent-activities">
|
||||
<h3>${t("recent_activities", L)}</h3>
|
||||
${recent.map(entry => html`
|
||||
<div class="activity-item">
|
||||
<span class="activity-icon">${getIcon(entry.type)}</span>
|
||||
<span class="activity-date">${formatDateTime(entry.timestamp, L)}</span>
|
||||
<span class="activity-note">${entry.notes || "—"}</span>
|
||||
${entry.cost ? html`<span class="activity-badge">${formatCost(entry.cost, ctx.currencySymbol, L, 0)}</span>` : nothing}
|
||||
${entry.duration ? html`<span class="activity-badge">${entry.duration}min</span>` : nothing}
|
||||
</div>
|
||||
`)}
|
||||
${recent.map((entry) => renderHistoryEntry(entry, ctx.history, { compact: true, showEdit: false }))}
|
||||
<div class="activity-show-all">
|
||||
<ha-button appearance="plain" @click=${() => ctx.setActiveTab("history")}>${t("show_all", L)} →</ha-button>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,15 @@ import { BUNDLE_VERSION } from "./helpers/bundle-version";
|
||||
// derives the real symbol from const.BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY].
|
||||
export const DEFAULT_CURRENCY_SYMBOL = "€";
|
||||
|
||||
/** The display symbol of a currency-bearing response block (`budget` in
|
||||
* settings / stats, a BudgetStatus) — the ONE fallback rule. Seven call
|
||||
* sites had forked into "€" (panel) vs "" (cards, complete dialog), so the
|
||||
* cards rendered a bare "12" where the panel showed "12 €" (DRY audit
|
||||
* 2026-09-12). */
|
||||
export function currencySymbolOf(budget: { currency_symbol?: string | null } | null | undefined): string {
|
||||
return budget?.currency_symbol || DEFAULT_CURRENCY_SYMBOL;
|
||||
}
|
||||
|
||||
// Moved to the dependency-free status-constants.ts so the dashboard strategy
|
||||
// bundle can share them without pulling Lit in; re-exported here so every
|
||||
// existing `from "./styles"` import keeps working.
|
||||
@@ -208,6 +217,25 @@ interface ProfilePrefs {
|
||||
}
|
||||
const _w = window as unknown as { __msDateTimePrefs?: ProfilePrefs };
|
||||
const DT_PREFS: ProfilePrefs = (_w.__msDateTimePrefs ??= {});
|
||||
/** Currency display prefs shared across bundles (a window singleton like the
|
||||
* date/time prefs): the global `currency_decimals` setting. Synced from every
|
||||
* response that carries a currency block (settings, budget_status,
|
||||
* statistics) so every amount — KPIs, budgets, costs, printables — rounds
|
||||
* the same way. Default 0 = whole numbers. */
|
||||
const MONEY_PREFS: { decimals?: number } = ((_w as unknown as { __msMoneyPrefs?: { decimals?: number } }).__msMoneyPrefs ??= {});
|
||||
|
||||
export function setCurrencyDecimals(decimals: number | null | undefined): void {
|
||||
MONEY_PREFS.decimals = typeof decimals === "number" && Number.isInteger(decimals) && decimals >= 0 && decimals <= 3 ? decimals : 0;
|
||||
}
|
||||
|
||||
export function currencyDecimals(): number {
|
||||
return MONEY_PREFS.decimals ?? 0;
|
||||
}
|
||||
|
||||
/** Take the decimals from a currency-bearing response block, when present. */
|
||||
export function syncCurrencyDecimals(src: { currency_decimals?: number | null } | null | undefined): void {
|
||||
if (src && src.currency_decimals !== undefined && src.currency_decimals !== null) setCurrencyDecimals(Number(src.currency_decimals));
|
||||
}
|
||||
|
||||
/** Feed HA's per-user date/time/number formats into the formatters below.
|
||||
* `country` is the SERVER's configured country (#140): with the profile
|
||||
@@ -263,10 +291,12 @@ export function formatNumber(n: number, lang?: string, digits?: number | Intl.Nu
|
||||
}
|
||||
}
|
||||
|
||||
/** Amount + currency symbol ("12.50 €"; the symbol is optional) — every cost
|
||||
* figure in the UI and the printables goes through here. */
|
||||
export function formatCost(amount: number, symbol?: string, lang?: string, digits = 2): string {
|
||||
const num = formatNumber(amount, lang, digits);
|
||||
/** Amount + currency symbol ("13 €" / "12.50 €"; the symbol is optional) —
|
||||
* every cost figure in the UI and the printables goes through here. The
|
||||
* fraction digits come from the global `currency_decimals` setting (default
|
||||
* 0) unless a caller pins them. */
|
||||
export function formatCost(amount: number, symbol?: string, lang?: string, digits?: number): string {
|
||||
const num = formatNumber(amount, lang, digits ?? currencyDecimals());
|
||||
return symbol ? `${num} ${symbol}` : num;
|
||||
}
|
||||
|
||||
@@ -358,6 +388,14 @@ export function formatInterval(
|
||||
return `${intervalDays} ${t("unit_" + (unit || "days"), lang)}`;
|
||||
}
|
||||
|
||||
/** A duration in minutes, e.g. "45 min" — profile-aware number plus the
|
||||
* localised unit. Five surfaces carried their own `${x} min` / `${x}m`
|
||||
* (DRY round 2026-09; tripwired in profile-format-single-source.test.ts). */
|
||||
export function formatDuration(minutes: number | null | undefined, lang?: string): string {
|
||||
if (minutes === null || minutes === undefined) return "—";
|
||||
return `${formatNumber(minutes, lang, { maximumFractionDigits: 1 })} ${t("minutes_short", lang)}`;
|
||||
}
|
||||
|
||||
/** Localized weekday name (0=Mon … 6=Sun) via Intl — 2024-01-01 is a Monday.
|
||||
* Single source for the dialog selectors AND formatRecurrence (DRY).
|
||||
*
|
||||
@@ -713,6 +751,14 @@ export const sharedStyles = css`
|
||||
contain-intrinsic-size: auto 48px;
|
||||
}
|
||||
.history-entry:last-child { border-bottom: none; }
|
||||
/* Dense variant (quick-actions dialog, overview "recent activities"):
|
||||
no status-icon column, tighter padding, smaller type. */
|
||||
.history-entry.compact {
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
contain-intrinsic-size: auto 40px;
|
||||
}
|
||||
.history-entry.compact .history-readings { font-size: 12px; }
|
||||
|
||||
.history-icon {
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
/** TypeScript interfaces for the Maintenance Supporter frontend. */
|
||||
|
||||
// ── Closed vocabularies (mirror const.py's StrEnums; DRY round 2026-09 turned
|
||||
// the comment-only unions into real types). WS payloads arrive typed through
|
||||
// the sendMessagePromise<T> generic — no runtime cast, no per-comparison cast.
|
||||
|
||||
/** const.MaintenanceStatus — `paused` is coordinator-injected for a paused object. */
|
||||
export type MaintenanceStatus = "ok" | "due_soon" | "overdue" | "triggered" | "archived" | "paused";
|
||||
/** const.HistoryEntryType */
|
||||
export type HistoryEntryType = "completed" | "skipped" | "missed" | "reset" | "triggered" | "trigger_removed" | "trigger_replaced";
|
||||
/** const.MaintenanceTypeEnum */
|
||||
export type MaintenanceType = "cleaning" | "inspection" | "replacement" | "calibration" | "service" | "reading" | "custom";
|
||||
/** The nested calendar recurrence kinds (schedule-model v2). */
|
||||
export type CalendarKind = "weekdays" | "nth_weekday" | "day_of_month";
|
||||
/** const.ScheduleType plus the calendar kinds the dialog exposes as schedule types. */
|
||||
export type ScheduleType = "time_based" | "sensor_based" | "one_time" | "manual" | CalendarKind;
|
||||
/** TaskSchedule.kind — the flat kinds plus the calendar kinds. */
|
||||
export type ScheduleKind = "interval" | "one_time" | "manual" | CalendarKind;
|
||||
/** const.TriggerType */
|
||||
export type TriggerType = "threshold" | "counter" | "state_change" | "runtime" | "compound";
|
||||
export type IntervalUnit = "days" | "weeks" | "months" | "years";
|
||||
export type DocumentKind = "file" | "weblink";
|
||||
export type Confidence = "low" | "medium" | "high";
|
||||
export type DegradationTrend = "rising" | "falling" | "stable" | "insufficient_data";
|
||||
export type SeasonalReason = "learned" | "manual";
|
||||
|
||||
export interface MaintenanceObject {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -46,7 +70,7 @@ export interface MaintenanceObject {
|
||||
export interface ManualDocRef {
|
||||
id: string;
|
||||
title: string;
|
||||
kind: string; // "file" | "weblink"
|
||||
kind: DocumentKind;
|
||||
url?: string | null;
|
||||
}
|
||||
|
||||
@@ -55,7 +79,7 @@ export interface TriggerConfig {
|
||||
entity_ids?: string[];
|
||||
entity_logic?: "any" | "all";
|
||||
attribute?: string | null;
|
||||
type?: string; // "threshold" | "counter" | "state_change" | "runtime"
|
||||
type?: TriggerType;
|
||||
trigger_above?: number | null;
|
||||
trigger_below?: number | null;
|
||||
trigger_equals?: number | null;
|
||||
@@ -109,7 +133,7 @@ export interface HistoryEntry {
|
||||
timestamp: string;
|
||||
/** #170: completion number within the task ("8.3-2"); skips carry none. */
|
||||
ref_no?: number | null;
|
||||
type: string; // "completed" | "skipped" | "reset" | "triggered"
|
||||
type: HistoryEntryType;
|
||||
notes?: string | null;
|
||||
cost?: number | null;
|
||||
duration?: number | null;
|
||||
@@ -166,7 +190,7 @@ export interface IntervalAnalysis {
|
||||
reason?: string | null;
|
||||
seasonal_factor?: number | null;
|
||||
seasonal_factors?: number[] | null;
|
||||
seasonal_reason?: string | null; // "learned" | "manual"
|
||||
seasonal_reason?: SeasonalReason | null;
|
||||
confidence_interval_low?: number | null;
|
||||
confidence_interval_high?: number | null;
|
||||
}
|
||||
@@ -175,7 +199,7 @@ export interface IntervalAnalysis {
|
||||
* for the interval/one_time kinds; the calendar kinds (weekdays / nth_weekday /
|
||||
* day_of_month) can only be expressed here. weekday: 0=Mon … 6=Sun. */
|
||||
export interface TaskSchedule {
|
||||
kind: string; // interval | weekdays | nth_weekday | day_of_month | one_time | manual
|
||||
kind: ScheduleKind;
|
||||
every?: number | null;
|
||||
unit?: string;
|
||||
anchor?: string;
|
||||
@@ -200,15 +224,15 @@ export interface MaintenanceTask {
|
||||
name: string;
|
||||
/** #170: reference number within the object ("8.3" = object 8, task 3). */
|
||||
ref_no?: number | null;
|
||||
type: string; // "cleaning" | "inspection" | "replacement" | "calibration" | "service" | "reading" | "custom"
|
||||
type: MaintenanceType;
|
||||
enabled: boolean;
|
||||
/** #150: false = the skip lock — Skip hidden everywhere, server refuses. */
|
||||
allow_skip?: boolean;
|
||||
/** #173: false = this task sends no reminders (dashboard/entities unaffected). */
|
||||
notify_enabled?: boolean;
|
||||
schedule_type: string; // "time_based" | "sensor_based" | "one_time" | "manual" | calendar kind
|
||||
schedule_type: ScheduleType;
|
||||
interval_days?: number | null;
|
||||
interval_unit?: string; // "days" | "weeks" | "months" | "years"
|
||||
interval_unit?: IntervalUnit;
|
||||
due_date?: string | null; // one-time task due date (ISO)
|
||||
interval_anchor?: "completion" | "planned";
|
||||
schedule?: TaskSchedule; // nested recurrence (calendar kinds read this)
|
||||
@@ -250,7 +274,7 @@ export interface MaintenanceTask {
|
||||
/** Total entries that exist, including those beyond the list window. */
|
||||
history_count?: number;
|
||||
// Computed
|
||||
status: string; // "ok" | "due_soon" | "overdue" | "triggered" | "archived"
|
||||
status: MaintenanceStatus;
|
||||
/** True for a one-time task that has been completed (done; never re-arms). */
|
||||
is_done?: boolean;
|
||||
/** v2.10.0 archive: archived = archived_at != null; reason is manual|auto|object. */
|
||||
@@ -272,21 +296,21 @@ export interface MaintenanceTask {
|
||||
// Adaptive scheduling
|
||||
adaptive_config?: AdaptiveConfig | null;
|
||||
suggested_interval?: number | null;
|
||||
interval_confidence?: string | null;
|
||||
interval_confidence?: Confidence | null;
|
||||
interval_analysis?: IntervalAnalysis | null;
|
||||
// Seasonal scheduling (top-level convenience)
|
||||
seasonal_factor?: number | null;
|
||||
seasonal_factors?: number[] | null;
|
||||
// Sensor-driven predictions (Phase 3)
|
||||
degradation_rate?: number | null;
|
||||
degradation_trend?: string | null; // "rising" | "falling" | "stable" | "insufficient_data"
|
||||
degradation_trend?: DegradationTrend | null;
|
||||
degradation_r_squared?: number | null;
|
||||
/** How many prior service cycles the degradation rate learned from
|
||||
* (sawtooth sensors); 0 = current window only. */
|
||||
prediction_cycles?: number;
|
||||
days_until_threshold?: number | null;
|
||||
threshold_prediction_date?: string | null;
|
||||
threshold_prediction_confidence?: string | null; // "low" | "medium" | "high"
|
||||
threshold_prediction_confidence?: Confidence | null;
|
||||
environmental_factor?: number | null;
|
||||
environmental_entity?: string | null;
|
||||
environmental_correlation?: number | null;
|
||||
@@ -382,7 +406,7 @@ export interface StatisticsResponse {
|
||||
due_soon: number;
|
||||
triggered: number;
|
||||
total_cost: number;
|
||||
budget?: { currency_symbol?: string } | null;
|
||||
budget?: { currency?: string; currency_symbol?: string; currency_decimals?: number } | null;
|
||||
}
|
||||
|
||||
/** A saved filter view: the panel task-list's filter/sort/group state, named
|
||||
@@ -476,6 +500,8 @@ export interface BudgetStatus {
|
||||
yearly_spent: number;
|
||||
alert_threshold_pct: number;
|
||||
currency_symbol: string;
|
||||
/** Decimal places for every displayed amount (0 = whole numbers). */
|
||||
currency_decimals?: number;
|
||||
}
|
||||
|
||||
export interface AdvancedFeatures {
|
||||
@@ -612,6 +638,9 @@ export interface HomeAssistant {
|
||||
config?: { country?: string | null };
|
||||
localize(key: string, ...args: unknown[]): string;
|
||||
user?: { id: string; name: string; is_admin: boolean; is_owner: boolean };
|
||||
/** HA's registered panels; a custom panel carries its module URL under
|
||||
* `config._panel_custom` (the panel card imports ours from there, #174). */
|
||||
panels?: Record<string, { url_path?: string; title?: string | null; config?: { _panel_custom?: { name?: string; module_url?: string } } | null }>;
|
||||
/** Current access token — used for authenticated `fetch()` to our HTTP
|
||||
* document views (a WebSocket frame can't carry a multipart file upload). */
|
||||
auth?: { data?: { access_token?: string } };
|
||||
|
||||
@@ -5,15 +5,68 @@
|
||||
* The HA WS layer wraps voluptuous errors in
|
||||
* { code: "invalid_format", message: "<voluptuous text>" }
|
||||
* The voluptuous text is consistently shaped — we parse the common
|
||||
* patterns and emit a localized template string. Falls back to the raw
|
||||
* patterns and emit a localized template string. Our own handlers reply
|
||||
* with one of the codes in WS_ERROR_CODE_KEYS and a free-text message;
|
||||
* the code picks the localized headline, the message rides along in
|
||||
* parentheses when it says more than the code does. Falls back to the raw
|
||||
* message when nothing matches, so the user never sees a silent failure.
|
||||
*/
|
||||
import { t } from "./styles";
|
||||
|
||||
/**
|
||||
* Every error code the backend's `send_error(msg["id"], "<code>", …)` calls
|
||||
* produce (websocket/*.py) → the locale key of its headline. Tripwired by
|
||||
* tests/test_frontend_const_parity.py: a new backend code without a row
|
||||
* here fails CI, as does a row whose key is missing from en.json.
|
||||
*/
|
||||
export const WS_ERROR_CODE_KEYS: Record<string, string> = {
|
||||
already_archived: "ws_err_already_archived",
|
||||
already_paused: "ws_err_already_paused",
|
||||
archived: "ws_err_archived",
|
||||
create_failed: "ws_err_create_failed",
|
||||
duplicate_failed: "ws_err_duplicate_failed",
|
||||
empty: "ws_err_empty",
|
||||
empty_csv: "ws_err_empty_csv",
|
||||
invalid_cursor: "ws_err_invalid_cursor",
|
||||
invalid_date: "ws_err_invalid_date",
|
||||
invalid_device: "ws_err_invalid_device",
|
||||
invalid_entity_slug: "ws_err_invalid_entity_slug",
|
||||
invalid_format: "ws_err_invalid_format",
|
||||
invalid_input: "ws_err_invalid_input",
|
||||
invalid_parent: "ws_err_invalid_parent",
|
||||
invalid_range: "ws_err_invalid_range",
|
||||
invalid_target: "ws_err_invalid_target",
|
||||
invalid_trigger_config: "ws_err_invalid_trigger_config",
|
||||
invalid_url: "ws_err_invalid_url",
|
||||
invalid_user: "ws_err_invalid_user",
|
||||
invalid_view: "ws_err_invalid_view",
|
||||
limit_reached: "ws_err_limit_reached",
|
||||
no_defaults: "ws_err_no_defaults",
|
||||
no_phases: "ws_err_no_phases",
|
||||
no_url: "ws_err_no_url",
|
||||
not_archived: "ws_err_not_archived",
|
||||
not_available: "ws_err_not_available",
|
||||
not_configured: "ws_err_not_configured",
|
||||
not_found: "ws_err_not_found",
|
||||
not_loaded: "ws_err_not_loaded",
|
||||
not_paused: "ws_err_not_paused",
|
||||
replace_failed: "ws_err_replace_failed",
|
||||
self_link_device: "ws_err_self_link_device",
|
||||
storage_unavailable: "ws_err_storage_unavailable",
|
||||
too_early: "ws_err_too_early",
|
||||
too_large: "ws_err_too_large",
|
||||
too_many: "ws_err_too_many",
|
||||
too_many_views: "ws_err_too_many_views",
|
||||
unauthorized: "ws_err_unauthorized",
|
||||
unavailable: "ws_err_unavailable",
|
||||
};
|
||||
|
||||
/**
|
||||
* Map of internal field keys (as they appear in voluptuous error paths)
|
||||
* to existing i18n label keys. When a field has no entry here, we surface
|
||||
* the raw key as-is so the user can still tell which input was rejected.
|
||||
* to existing i18n label keys. A field without an entry here is humanised
|
||||
* (`interval_days` → "Interval days", `entry_id` → "Entry") so the user
|
||||
* can still tell which input was rejected. Only fields whose label key
|
||||
* differs from a plain humanisation need a row.
|
||||
*/
|
||||
const FIELD_LABEL_KEYS: Record<string, string> = {
|
||||
entry_id: "object",
|
||||
@@ -43,8 +96,6 @@ const FIELD_LABEL_KEYS: Record<string, string> = {
|
||||
cost: "cost",
|
||||
duration: "duration",
|
||||
description: "description_optional",
|
||||
group_name: "name",
|
||||
group_description: "description_optional",
|
||||
environmental_entity: "environmental_entity_optional",
|
||||
environmental_attribute: "environmental_attribute_optional",
|
||||
trigger_above: "trigger_above",
|
||||
@@ -54,13 +105,23 @@ const FIELD_LABEL_KEYS: Record<string, string> = {
|
||||
trigger_for_minutes: "trigger_for_minutes",
|
||||
};
|
||||
|
||||
/** `snake_case_id` → "Snake case": the label of a field no key covers. */
|
||||
export function humanizeField(field: string): string {
|
||||
const words = field.replace(/_ids?$/, "").split("_").filter(Boolean);
|
||||
if (words.length === 0) return field;
|
||||
const text = words.join(" ");
|
||||
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||
}
|
||||
|
||||
function _label(field: string, lang: string): string {
|
||||
const key = FIELD_LABEL_KEYS[field];
|
||||
if (!key) return field;
|
||||
// t() returns the key itself when missing — we'd rather show the raw
|
||||
// field name in that case than the i18n key with underscores.
|
||||
const translated = t(key, lang);
|
||||
return translated && translated !== key ? translated : field;
|
||||
if (key) {
|
||||
// t() returns the key itself when missing — humanise in that case
|
||||
// rather than show the i18n key with underscores.
|
||||
const translated = t(key, lang);
|
||||
if (translated && translated !== key) return translated;
|
||||
}
|
||||
return humanizeField(field);
|
||||
}
|
||||
|
||||
interface ParsedError {
|
||||
@@ -89,8 +150,10 @@ function _parse(message: string): ParsedError {
|
||||
// "expected int for dictionary value @ data['interval_days']"
|
||||
// "value must be one of ['completion', 'planned'] for dictionary value @ data['interval_anchor']"
|
||||
// "not a valid value for dictionary value @ data['date']"
|
||||
const fieldMatch = message.match(/data\['([^']+)'\]/);
|
||||
const field = fieldMatch?.[1];
|
||||
// Nested paths ("@ data['trigger_config']['trigger_above']") name the
|
||||
// innermost key — that is the input the user can fix.
|
||||
const path = [...message.matchAll(/\['([^']+)'\]/g)].map((m) => m[1]);
|
||||
const field = path.length ? path[path.length - 1] : undefined;
|
||||
|
||||
let m: RegExpMatchArray | null;
|
||||
if ((m = message.match(/length of value must be at most (\d+)/))) {
|
||||
@@ -120,19 +183,39 @@ function _parse(message: string): ParsedError {
|
||||
return { field, rule: "unknown" };
|
||||
}
|
||||
|
||||
const _words = (s: string): string[] => s.toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length > 1);
|
||||
|
||||
/**
|
||||
* True when the server message merely restates its code ("Not found" for
|
||||
* `not_found`): every word of the message is already a word of the code,
|
||||
* so echoing it after the translated headline would add nothing.
|
||||
*/
|
||||
function _restatesCode(message: string, code: string): boolean {
|
||||
const codeWords = new Set(_words(code));
|
||||
return _words(message).every((w) => codeWords.has(w));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a thrown WS-promise rejection into a localized one-line string.
|
||||
* Pass the lang code (e.g. `this._lang`) and optionally a fallback for
|
||||
* non-WS errors (defaults to the localized generic "action failed").
|
||||
*
|
||||
* Resolution order: a voluptuous validation message that parses to a
|
||||
* field rule wins (it names the input); otherwise a known error code gives
|
||||
* the translated headline, followed by the server's message in parentheses
|
||||
* when that message says more than the code itself; otherwise the raw
|
||||
* message; otherwise the fallback.
|
||||
*/
|
||||
export function describeWsError(e: unknown, lang: string, fallback?: string): string {
|
||||
fallback = fallback ?? t("action_error", lang);
|
||||
if (typeof e === "string") return e;
|
||||
if (typeof e !== "object" || e === null) return fallback;
|
||||
|
||||
const err = e as { message?: string; error?: { message?: string }; code?: string };
|
||||
const err = e as { message?: string; error?: { message?: string; code?: string }; code?: string };
|
||||
const raw = err.message || err.error?.message || "";
|
||||
if (!raw) return fallback;
|
||||
const code = err.code || err.error?.code || "";
|
||||
const codeKey = code ? WS_ERROR_CODE_KEYS[code] : undefined;
|
||||
if (!raw && !codeKey) return fallback;
|
||||
|
||||
const parsed = _parse(raw);
|
||||
const field = parsed.field ? _label(parsed.field, lang) : "";
|
||||
@@ -157,6 +240,10 @@ export function describeWsError(e: unknown, lang: string, fallback?: string): st
|
||||
case "invalid_value":
|
||||
return tpl("err_invalid_value");
|
||||
default:
|
||||
if (codeKey) {
|
||||
const headline = t(codeKey, lang);
|
||||
return raw && !_restatesCode(raw, code) ? `${headline} (${raw})` : headline;
|
||||
}
|
||||
// Unknown shape — show the raw message so debugging is possible
|
||||
return raw || fallback;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Doba běhu",
|
||||
"runtime_hours": "Cílová doba běhu (hodiny)",
|
||||
"target_value": "Cílová hodnota",
|
||||
"baseline": "Základní hodnota",
|
||||
"target_changes": "Cílový počet změn",
|
||||
"for_minutes": "Po dobu (minut)",
|
||||
"time_based": "Časový",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Vytvořeno",
|
||||
"report_times_done": "Provedeno",
|
||||
"report_total_cost": "Celkové náklady",
|
||||
"report_every": "každých {n} {unit}",
|
||||
"report_notes": "Poznámky",
|
||||
"report_col_type": "Typ",
|
||||
"report_col_status": "Stav",
|
||||
"report_col_schedule": "Plán",
|
||||
"duration": "Doba trvání",
|
||||
"minutes_short": "min",
|
||||
"both": "Obojí",
|
||||
"trigger_val": "Hodnota spouštěče",
|
||||
"complete_title": "Dokončit: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: špatný typ (očekáván: {type})",
|
||||
"err_invalid_choice": "{field}: nepovolená hodnota",
|
||||
"err_invalid_value": "{field}: neplatná hodnota",
|
||||
"ws_err_not_found": "Nenalezeno — mezitím to mohlo být smazáno",
|
||||
"ws_err_invalid_input": "Neplatný vstup",
|
||||
"ws_err_invalid_date": "Neplatné datum",
|
||||
"ws_err_invalid_format": "Neplatný formát",
|
||||
"ws_err_invalid_url": "Neplatný odkaz",
|
||||
"ws_err_not_configured": "Zatím nenastaveno",
|
||||
"ws_err_not_loaded": "Zatím nenačteno — zkuste to za chvíli znovu",
|
||||
"ws_err_limit_reached": "Dosažen limit",
|
||||
"ws_err_too_many": "Příliš mnoho položek",
|
||||
"ws_err_too_large": "Příliš velké",
|
||||
"ws_err_not_archived": "Není archivováno",
|
||||
"ws_err_invalid_target": "Neplatný cíl",
|
||||
"ws_err_create_failed": "Nepodařilo se vytvořit",
|
||||
"ws_err_archived": "Archivováno — nejprve obnovte",
|
||||
"ws_err_already_archived": "Již archivováno",
|
||||
"ws_err_unavailable": "Momentálně nedostupné",
|
||||
"ws_err_unauthorized": "Pro vašeho uživatele není povoleno",
|
||||
"ws_err_too_many_views": "Příliš mnoho uložených pohledů",
|
||||
"ws_err_storage_unavailable": "Úložiště není dostupné",
|
||||
"ws_err_replace_failed": "Nahrazení se nezdařilo",
|
||||
"ws_err_not_paused": "Není pozastaveno",
|
||||
"ws_err_not_available": "Nic k dispozici",
|
||||
"ws_err_no_url": "Nebyl zadán odkaz",
|
||||
"ws_err_no_phases": "Tento úkol nemá fáze",
|
||||
"ws_err_invalid_view": "Neplatný pohled",
|
||||
"ws_err_invalid_user": "Neznámý uživatel",
|
||||
"ws_err_invalid_range": "Neplatný rozsah",
|
||||
"ws_err_invalid_parent": "Neplatný nadřazený objekt",
|
||||
"ws_err_invalid_device": "Neznámé zařízení",
|
||||
"ws_err_empty_csv": "Soubor CSV je prázdný",
|
||||
"ws_err_empty": "Není co importovat",
|
||||
"ws_err_duplicate_failed": "Nepodařilo se duplikovat",
|
||||
"ws_err_already_paused": "Již pozastaveno",
|
||||
"ws_err_invalid_cursor": "Neplatná pozice fáze",
|
||||
"ws_err_invalid_entity_slug": "Neplatný název entity",
|
||||
"ws_err_invalid_trigger_config": "Neplatná konfigurace spouštěče",
|
||||
"ws_err_no_defaults": "Nejsou nastaveny výchozí hodnoty pro rychlé dokončení",
|
||||
"ws_err_self_link_device": "Objekt nelze propojit sám se sebou",
|
||||
"ws_err_too_early": "Příliš brzy — tento úkol lze dokončit až blíže k termínu",
|
||||
"feat_schedule_time": "Plánování podle denní doby",
|
||||
"feat_schedule_time_desc": "Úkoly se stanou po termínu v určenou denní dobu místo o půlnoci.",
|
||||
"schedule_time_toggle": "Termín v určitou hodinu",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Spustit při rovnosti (=)",
|
||||
"trigger_not_equals": "Spustit při odlišnosti od (≠)",
|
||||
"for_at_least_minutes": "Po dobu alespoň (minut)",
|
||||
"safety_interval_days": "Bezpečnostní interval (dny, volitelný)",
|
||||
"safety_interval": "Bezpečnostní interval (volitelný)",
|
||||
"trigger_combinator": "Kombinovat spouštěč a interval",
|
||||
"trigger_combinator_any": "Spouštěč nebo interval (co dřív)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Moje úkoly",
|
||||
"tab_calendar": "Kalendář",
|
||||
"cal_no_events": "Bez údržby",
|
||||
"cal_window_7": "7 dní",
|
||||
"cal_window_14": "14 dní",
|
||||
"cal_window_30": "30 dní",
|
||||
"cal_window_365": "1 rok",
|
||||
"cal_every_n_days": "každých {n} dní",
|
||||
"cal_source_time": "Čas",
|
||||
"cal_source_time_adaptive": "Čas (adaptivní)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Nepodařilo se vygenerovat QR kód.",
|
||||
"qr_error_no_url": "Není nakonfigurováno URL HA. Nastavte externí nebo interní URL v Nastavení → Systém → Síť.",
|
||||
"save_error": "Nepodařilo se uložit. Zkuste to znovu.",
|
||||
"subsave_warning": "Uloženo, ale jedno nastavení bylo odmítnuto: {detail}",
|
||||
"qr_print": "Tisk",
|
||||
"qr_download": "Stáhnout SVG",
|
||||
"qr_action": "Akce při skenování",
|
||||
"qr_action_view": "Zobrazit informace o údržbě",
|
||||
"qr_action_complete": "Označit údržbu jako dokončenou",
|
||||
"qr_url_mode": "Typ odkazu",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Lokální (mDNS)",
|
||||
"qr_mode_server": "URL serveru",
|
||||
"overview": "Přehled",
|
||||
"analysis": "Analýza",
|
||||
"recent_activities": "Nedávné aktivity",
|
||||
"search_notes": "Hledat v poznámkách",
|
||||
"avg_cost": "Prům. náklady",
|
||||
"no_advanced_features": "Žádné pokročilé funkce nejsou povoleny",
|
||||
"no_advanced_features_hint": "Povolte „Adaptivní intervaly” nebo „Sezónní vzory” v nastavení integrace pro zobrazení analytických dat.",
|
||||
"analysis_not_enough_data": "Zatím nedostatek dat pro analýzu.",
|
||||
"analysis_not_enough_data_hint": "Weibullova analýza vyžaduje alespoň 5 dokončených údržeb; sezónní vzory se stanou viditelné po 6+ datových bodech na měsíc.",
|
||||
"analysis_manual_task_hint": "Manuální úkoly bez intervalu negenerují analytická data.",
|
||||
"completions": "dokončení",
|
||||
"current": "Aktuální",
|
||||
"shorter": "Kratší",
|
||||
"longer": "Delší",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Minulá okna",
|
||||
"cal_forward_windows": "Budoucí okna",
|
||||
"history_edit_title": "Upravit záznam historie",
|
||||
"history_delete_entry": "Smazat záznam",
|
||||
"history_delete_confirm": "Smazat tento záznam historie? Datum posledního provedení se vrátí k předchozímu dokončení; fotografie zůstanou u objektu a spotřebované díly se nevrátí na sklad.",
|
||||
"history_edit_timestamp": "Časové razítko",
|
||||
"manufacturer": "Výrobce",
|
||||
"model": "Model",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Výchozí dny upozornění",
|
||||
"settings_consumable_threshold": "Práh „spotřební materiál nízký“ (%)",
|
||||
"settings_battery_low_percent": "Práh „baterie slabá“ (%)",
|
||||
"settings_battery_lifetimes": "Typická životnost baterií",
|
||||
"settings_battery_lifetimes_hint": "Použije se jen u baterií bez procent: poznámka Battery Notes jen s typem a datem výměny nebo senzor hlásící pouze „slabá“. Splatnost = poslední výměna + tato životnost. Baterie hlásící úroveň mají prognózu z naměřeného vybíjení. Po třech výměnách daného typu nahradí medián intervalů vestavěnou hodnotu — vaše hodnota má vždy přednost.",
|
||||
"settings_battery_lifetime_months": "měsíců",
|
||||
"settings_battery_lifetime_reset": "Použít výchozí",
|
||||
"settings_battery_lifetime_in_fleet": "ve vaší flotile",
|
||||
"settings_battery_lifetime_learned_models": "Naučeno podle modelu: {list}",
|
||||
"settings_thresholds_hint": "Výchozí hodnoty pro nová Navrhovaná nastavení (zbývající procenta) a pro baterie flotily bez vlastního prahu Battery Notes. Stávající úkoly si ponechají svou hodnotu.",
|
||||
"settings_value_out_of_range": "Hodnota musí být mezi {min} a {max}",
|
||||
"bn_summary": "{name}: {pct} % pro {n} baterií",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} zařízení s vlastním prahem:",
|
||||
"bn_more": "+ {n} dalších",
|
||||
"settings_row_actions": "Akce v řádcích úkolů",
|
||||
"settings_ref_numbers_in_lists": "Referenční čísla v seznamech",
|
||||
"settings_ref_numbers_in_lists_hint": "Zobrazí štítky #8 / #8.3 před názvy objektů a úkolů v seznamu Dnes, tabulce úkolů, kartách objektů a tabulce objektů.",
|
||||
"row_actions_buttons_compact": "Tlačítka (na telefonu jen ikony)",
|
||||
"row_actions_buttons": "Tlačítka s textem",
|
||||
"row_actions_icons": "Ikony (klasické)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Doba odložení (hodiny)",
|
||||
"settings_budget": "Rozpočet",
|
||||
"settings_currency": "Měna",
|
||||
"settings_currency_decimals": "Desetinná místa částek",
|
||||
"settings_currency_decimals_hint": "Kolik desetinných míst zobrazuje každá částka: KPI, rozpočty, náklady v seznamech a sestavách, rozpočtová upozornění. 0 = celá čísla.",
|
||||
"settings_budget_monthly": "Měsíční rozpočet",
|
||||
"settings_budget_yearly": "Roční rozpočet",
|
||||
"settings_budget_alerts": "Rozpočtová upozornění",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exportovat YAML",
|
||||
"settings_export_csv": "Exportovat CSV",
|
||||
"settings_export_settings": "Exportovat nastavení (JSON)",
|
||||
"settings_import_csv": "Importovat CSV",
|
||||
"settings_import_placeholder": "Vložte sem obsah JSON nebo CSV…",
|
||||
"settings_import_btn": "Importovat",
|
||||
"settings_import_success": "{count} objektů úspěšně importováno.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Akce HA na úlohu při dokončení + QR rychlého dokončení s předvolenými hodnotami.",
|
||||
"on_complete_action_title": "Při dokončení: spustit akci HA (volitelné)",
|
||||
"on_complete_action_desc": "Volá službu HA, když je úloha dokončena — např. resetuje čítač na zařízení.",
|
||||
"on_complete_action_service": "Služba",
|
||||
"on_complete_action_target": "Cílová entita",
|
||||
"on_complete_action_data": "Data (JSON, volitelné)",
|
||||
"on_complete_action_test": "Testovat akci",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Vybrat fotky",
|
||||
"choose_photo": "Vybrat fotku",
|
||||
"photos_android_hint": "Aplikace pro Android předá jednu fotku na výběr — každý výběr se přidá.",
|
||||
"add_photos": "Přidat fotky",
|
||||
"photos_limit": "Nejvýše {max} fotek na dokončení",
|
||||
"history_edit_photos_hint": "Zde odebraná fotka zůstává v dokumentech objektu.",
|
||||
"uploading": "Nahrávání…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Stáhnout",
|
||||
"doc_close": "Zavřít",
|
||||
"doc_camera": "Vyfotit",
|
||||
"camera_capture_shoot": "Vyfotit",
|
||||
"camera_switch_lens": "Přepnout kameru",
|
||||
"doc_drop_hint": "Přetáhněte soubory sem",
|
||||
"doc_task_none": "Žádné dokumenty spojené s tímto úkolem.",
|
||||
"doc_link_existing": "Připojit dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Zaznamená dokončení (nastaví poslední provedení), jakmile se spouštěč sám vyřeší — např. doplněná sůl, vyměněný filtr.",
|
||||
"doc_search": "Hledat dokumenty…",
|
||||
"doc_search_none": "Žádné odpovídající dokumenty",
|
||||
"doc_description": "Popis (volitelné)",
|
||||
"doc_sort": "Seřadit dokumenty",
|
||||
"doc_sort_newest": "Nejnovější první",
|
||||
"doc_sort_oldest": "Nejstarší první",
|
||||
"doc_sort_title": "Podle názvu (1, 2, 3 …)",
|
||||
"doc_sort_category": "Podle kategorie",
|
||||
"link_device_optional": "Propojit s existujícím zařízením (volitelné)",
|
||||
"parent_object_optional": "Nadřazený objekt (volitelné)",
|
||||
"parent_none": "(Žádný nadřazený)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Upozorňovat jen pro zobrazení",
|
||||
"settings_notify_scope_all": "Všechny úkoly",
|
||||
"settings_notify_scope_hint": "Připomenutí posílají jen úkoly odpovídající filtrům štítku/uživatele zvoleného zobrazení. Stav, řazení a seskupení zobrazení se zde ignorují.",
|
||||
"settings_notify_completed": "Oznámení o dokončení",
|
||||
"settings_notify_completed_hint": "Zpráva pro domácnost, když je úkol dokončen, s důvodem a tím, kdo to udělal. „Jen automatická“ hlásí pouze dokončení, která nikdo neudělal na místě: zotavení senzoru, odškrtnutí v nákupním seznamu, volání služby automatizací. Jen na službu domácnosti; tiché hodiny a denní limit platí.",
|
||||
"notify_completed_off": "Vypnuto",
|
||||
"notify_completed_automatic": "Jen automatická dokončení",
|
||||
"notify_completed_all": "Všechna dokončení",
|
||||
"settings_notify_rule": "Vlastní pravidlo oznámení",
|
||||
"settings_notify_rule_hint": "Každé oznámení navíc vyvolá událost maintenance_supporter_notification (druh, stav, objekt, úkol, referenční čísla, priorita, odkaz, data). Nasměrujte ji automatizací — např. do Tickeru, Telegramu nebo Pushoveru.",
|
||||
"settings_notify_event_only": "Jen událost, nic neposílat",
|
||||
"settings_notify_event_only_hint": "Doručení zajišťuje vaše automatizace. Intervaly opakování, tiché hodiny a denní limit dál rozhodují, kdy se událost vyvolá.",
|
||||
"settings_notify_extra_data": "Další data oznámení (šablona)",
|
||||
"settings_notify_extra_data_hint": "JSON nebo YAML, Jinja povoleno; sloučí se do data volání notify (vaše klíče mají přednost). Proměnné: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Ověřte tlačítkem Odeslat test.",
|
||||
"settings_notify_extra_data_hint": "JSON nebo YAML, Jinja povoleno; sloučí se do data volání notify (vaše klíče mají přednost). Proměnné: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Ověřte tlačítkem Odeslat test.",
|
||||
"card_saved_view": "Uložené zobrazení",
|
||||
"card_saved_view_none": "Žádné",
|
||||
"card_saved_view_help": "Použije filtry stavu, uživatele a štítku zobrazení navíc k výše uvedeným filtrům. Řazení a seskupení zobrazení jsou nastavení panelu a na kartě se nepoužijí.",
|
||||
"panel_card_tab": "Otevřít na kartě",
|
||||
"panel_card_tab_default": "Zapamatovaná karta (jako v panelu)",
|
||||
"panel_card_view": "Otevřít s uloženým pohledem",
|
||||
"panel_card_height": "Výška",
|
||||
"panel_card_height_help": "Prázdné = vyplní obrazovku pod kartou (zobrazení panel). Jinak délka CSS jako 600px nebo 80vh pro zobrazení s dalšími kartami.",
|
||||
"panel_card_not_registered": "Panel Maintenance Supporter není k dispozici — je integrace nastavená?",
|
||||
"panel_card_load_failed": "Panel Maintenance Supporter se nepodařilo načíst — obnovte stránku (po aktualizaci: vymažte mezipaměť prohlížeče).",
|
||||
"doc_part_none": "K tomuto dílu nejsou připojeny žádné dokumenty.",
|
||||
"settings_templates_toggle_group": "Zapnout nebo vypnout všechny šablony v této skupině",
|
||||
"setups_button": "Navrhovaná nastavení",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "V pořádku",
|
||||
"battery_fleet_status_due": "K výměně",
|
||||
"battery_fleet_predicted_on": "Očekáváno kolem {date}",
|
||||
"battery_fleet_predicted_typical": "Očekáváno kolem {date} — {months} měsíců typicky pro {type} ({source})",
|
||||
"lifetime_source_default": "vestavěná výchozí hodnota",
|
||||
"lifetime_source_table": "vestavěná tabulka",
|
||||
"lifetime_source_learned": "naučeno z {n} výměn",
|
||||
"lifetime_source_override": "vaše nastavení",
|
||||
"lifetime_source_learned_device": "naučeno z {n} výměn tohoto zařízení",
|
||||
"lifetime_source_learned_model": "naučeno z {n} výměn u zařízení tohoto modelu",
|
||||
"battery_fleet_predicted_trend": "Předpověď z trendu vybíjení této baterie: přibližně {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Akumulátor: nabíjí se místo výměny — nikdy na nákupním seznamu",
|
||||
"battery_fleet_sort_name": "Řadit podle názvu",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Použité díly",
|
||||
"print_inc_photos": "Fotky",
|
||||
"print_inc_documents": "Propojené dokumenty",
|
||||
"print_inc_doc_desc": "Popisy dokumentů",
|
||||
"print_inc_checklist": "Kontrolní seznam",
|
||||
"print_inc_notes": "Poznámky",
|
||||
"print_inc_costs": "Náklady a doba",
|
||||
"print_inc_person": "Provedl",
|
||||
"print_inc_refs": "Referenční čísla",
|
||||
"print_inc_bare": "Dokončení bez podrobností",
|
||||
"print_inc_qr": "QR kód pro každý úkol",
|
||||
"print_button": "Tisk",
|
||||
"report_scan_hint": "Naskenujte pro otevření úkolu",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Driftstid",
|
||||
"runtime_hours": "Måldriftstid (timer)",
|
||||
"target_value": "Målværdi",
|
||||
"baseline": "Referenceværdi",
|
||||
"target_changes": "Antal målændringer",
|
||||
"for_minutes": "I (minutter)",
|
||||
"time_based": "Tidsbaseret",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Genereret",
|
||||
"report_times_done": "Udført",
|
||||
"report_total_cost": "Samlede omkostninger",
|
||||
"report_every": "hver {n}. {unit}",
|
||||
"report_notes": "Noter",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Tidsplan",
|
||||
"duration": "Varighed",
|
||||
"minutes_short": "min",
|
||||
"both": "Begge",
|
||||
"trigger_val": "Udløserværdi",
|
||||
"complete_title": "Fuldfør: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: forkert type (forventet: {type})",
|
||||
"err_invalid_choice": "{field}: ikke en tilladt værdi",
|
||||
"err_invalid_value": "{field}: ugyldig værdi",
|
||||
"ws_err_not_found": "Ikke fundet — det er måske blevet slettet i mellemtiden",
|
||||
"ws_err_invalid_input": "Ugyldig input",
|
||||
"ws_err_invalid_date": "Ugyldig dato",
|
||||
"ws_err_invalid_format": "Ugyldigt format",
|
||||
"ws_err_invalid_url": "Ugyldigt link",
|
||||
"ws_err_not_configured": "Ikke sat op endnu",
|
||||
"ws_err_not_loaded": "Ikke indlæst endnu — prøv igen om lidt",
|
||||
"ws_err_limit_reached": "Grænsen er nået",
|
||||
"ws_err_too_many": "For mange poster",
|
||||
"ws_err_too_large": "For stort",
|
||||
"ws_err_not_archived": "Ikke arkiveret",
|
||||
"ws_err_invalid_target": "Ugyldigt mål",
|
||||
"ws_err_create_failed": "Kunne ikke oprettes",
|
||||
"ws_err_archived": "Arkiveret — gendan først",
|
||||
"ws_err_already_archived": "Allerede arkiveret",
|
||||
"ws_err_unavailable": "Ikke tilgængelig lige nu",
|
||||
"ws_err_unauthorized": "Ikke tilladt for din bruger",
|
||||
"ws_err_too_many_views": "For mange gemte visninger",
|
||||
"ws_err_storage_unavailable": "Lageret er ikke tilgængeligt",
|
||||
"ws_err_replace_failed": "Udskiftning mislykkedes",
|
||||
"ws_err_not_paused": "Ikke sat på pause",
|
||||
"ws_err_not_available": "Intet tilgængeligt",
|
||||
"ws_err_no_url": "Intet link angivet",
|
||||
"ws_err_no_phases": "Opgaven har ingen faser",
|
||||
"ws_err_invalid_view": "Ugyldig visning",
|
||||
"ws_err_invalid_user": "Ukendt bruger",
|
||||
"ws_err_invalid_range": "Ugyldigt interval",
|
||||
"ws_err_invalid_parent": "Ugyldigt overordnet objekt",
|
||||
"ws_err_invalid_device": "Ukendt enhed",
|
||||
"ws_err_empty_csv": "CSV-filen er tom",
|
||||
"ws_err_empty": "Intet at importere",
|
||||
"ws_err_duplicate_failed": "Kunne ikke duplikeres",
|
||||
"ws_err_already_paused": "Allerede sat på pause",
|
||||
"ws_err_invalid_cursor": "Ugyldig faseposition",
|
||||
"ws_err_invalid_entity_slug": "Ugyldigt entitetsnavn",
|
||||
"ws_err_invalid_trigger_config": "Ugyldig triggerkonfiguration",
|
||||
"ws_err_no_defaults": "Ingen standardværdier for hurtig fuldførelse",
|
||||
"ws_err_self_link_device": "Et objekt kan ikke knyttes til sig selv",
|
||||
"ws_err_too_early": "For tidligt — opgaven kan først fuldføres tættere på forfaldsdatoen",
|
||||
"feat_schedule_time": "Planlægning efter tidspunkt på dagen",
|
||||
"feat_schedule_time_desc": "Opgaver bliver forfaldne på et bestemt tidspunkt på dagen i stedet for midnat.",
|
||||
"schedule_time_toggle": "Forfalder på et bestemt tidspunkt",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Udløs ved lig med (=)",
|
||||
"trigger_not_equals": "Udløs ved forskellig fra (≠)",
|
||||
"for_at_least_minutes": "I mindst (minutter)",
|
||||
"safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)",
|
||||
"safety_interval": "Sikkerhedsinterval (valgfrit)",
|
||||
"trigger_combinator": "Kombinér trigger og interval",
|
||||
"trigger_combinator_any": "Trigger eller interval (først opfyldt)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Mine opgaver",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Ingen vedligeholdelse",
|
||||
"cal_window_7": "7 dage",
|
||||
"cal_window_14": "14 dage",
|
||||
"cal_window_30": "30 dage",
|
||||
"cal_window_365": "1 år",
|
||||
"cal_every_n_days": "hver {n}. dag",
|
||||
"cal_source_time": "Tidsbaseret",
|
||||
"cal_source_time_adaptive": "Tidsbaseret (adaptiv)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "Kunne ikke generere QR-kode.",
|
||||
"qr_error_no_url": "Ingen HA-URL konfigureret. Angiv en ekstern eller intern URL under Indstillinger → System → Netværk.",
|
||||
"save_error": "Kunne ikke gemme. Prøv igen.",
|
||||
"subsave_warning": "Gemt, men én indstilling blev afvist: {detail}",
|
||||
"qr_print": "Udskriv",
|
||||
"qr_download": "Download SVG",
|
||||
"qr_action": "Handling ved scanning",
|
||||
"qr_action_view": "Vis vedligeholdelsesoplysninger",
|
||||
"qr_action_complete": "Marker vedligeholdelse som fuldført",
|
||||
"qr_url_mode": "Linktype",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Lokal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Oversigt",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Seneste aktiviteter",
|
||||
"search_notes": "Søg i noter",
|
||||
"avg_cost": "Gns. omkostning",
|
||||
"no_advanced_features": "Ingen avancerede funktioner aktiveret",
|
||||
"no_advanced_features_hint": "Aktiver „Adaptive intervaller” eller „Sæsonmønstre” i integrationsindstillingerne for at se analysedata her.",
|
||||
"analysis_not_enough_data": "Ikke nok data til analyse endnu.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analyse kræver mindst 5 fuldførte vedligeholdelser; sæsonmønstre bliver synlige efter 6+ datapunkter pr. måned.",
|
||||
"analysis_manual_task_hint": "Manuelle opgaver uden et interval genererer ikke analysedata.",
|
||||
"completions": "fuldførelser",
|
||||
"current": "Nuværende",
|
||||
"shorter": "Kortere",
|
||||
"longer": "Længere",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Tidligere vinduer",
|
||||
"cal_forward_windows": "Kommende vinduer",
|
||||
"history_edit_title": "Rediger historikpost",
|
||||
"history_delete_entry": "Slet post",
|
||||
"history_delete_confirm": "Slet denne historikpost? Datoen for seneste udførelse falder tilbage til den forrige; fotos bliver hos objektet, og forbrugte dele lægges ikke tilbage på lager.",
|
||||
"history_edit_timestamp": "Tidsstempel",
|
||||
"manufacturer": "Producent",
|
||||
"model": "Model",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Standard advarselsdage",
|
||||
"settings_consumable_threshold": "Tærskel „forbrugsvare lav“ (%)",
|
||||
"settings_battery_low_percent": "Tærskel „batteri lavt“ (%)",
|
||||
"settings_battery_lifetimes": "Typiske batterilevetider",
|
||||
"settings_battery_lifetimes_hint": "Bruges kun til batterier uden procent: en Battery Notes-note med kun type og udskiftningsdato, eller en sensor der kun melder \"lavt\". Forfaldsdato = seneste udskiftning + denne levetid. Batterier der melder niveau får prognosen fra den målte afladning. Når flåden har set tre udskiftninger af en type, overtager medianintervallet fra den indbyggede værdi — din egen værdi vinder altid.",
|
||||
"settings_battery_lifetime_months": "måneder",
|
||||
"settings_battery_lifetime_reset": "Brug standard",
|
||||
"settings_battery_lifetime_in_fleet": "i din flåde",
|
||||
"settings_battery_lifetime_learned_models": "Lært pr. model: {list}",
|
||||
"settings_thresholds_hint": "Standardværdier for nye Foreslåede opsætninger (resterende procent) og for flådebatterier uden egen Battery Notes-tærskel. Eksisterende opgaver beholder deres værdi.",
|
||||
"settings_value_out_of_range": "Værdien skal være mellem {min} og {max}",
|
||||
"bn_summary": "{name}: {pct} % for {n} batterier",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} enheder med egen tærskel:",
|
||||
"bn_more": "+ {n} mere",
|
||||
"settings_row_actions": "Handlinger i opgaverækker",
|
||||
"settings_ref_numbers_in_lists": "Referencenumre i lister",
|
||||
"settings_ref_numbers_in_lists_hint": "Viser brikkerne #8 / #8.3 foran objekt- og opgavenavne i I dag-listen, opgavetabellen, objektkortene og objekttabellen.",
|
||||
"row_actions_buttons_compact": "Knapper (kun ikoner på telefonen)",
|
||||
"row_actions_buttons": "Knapper med tekst",
|
||||
"row_actions_icons": "Ikoner (klassisk)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Udsættelsesvarighed (timer)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Decimaler for beløb",
|
||||
"settings_currency_decimals_hint": "Hvor mange decimaler hvert beløb viser: nøgletal, budgetter, omkostninger i lister og rapporter, budgetadvarsler. 0 = hele tal.",
|
||||
"settings_budget_monthly": "Månedligt budget",
|
||||
"settings_budget_yearly": "Årligt budget",
|
||||
"settings_budget_alerts": "Budgetadvarsler",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Eksporter YAML",
|
||||
"settings_export_csv": "Eksporter CSV",
|
||||
"settings_export_settings": "Eksportér indstillinger (JSON)",
|
||||
"settings_import_csv": "Importer CSV",
|
||||
"settings_import_placeholder": "Indsæt JSON- eller CSV-indhold her…",
|
||||
"settings_import_btn": "Importer",
|
||||
"settings_import_success": "{count} objekter importeret.",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "HA-handling pr. opgave ved fuldførelse + hurtig-fuldfør-QR med forudindstillede værdier.",
|
||||
"on_complete_action_title": "Ved fuldførelse: udløs HA-handling (valgfrit)",
|
||||
"on_complete_action_desc": "Kalder en HA-tjeneste, når opgaven fuldføres — f.eks. nulstil en tæller på enheden.",
|
||||
"on_complete_action_service": "Tjeneste",
|
||||
"on_complete_action_target": "Målenhed",
|
||||
"on_complete_action_target_hint": "Bemærk: enhedens domæne skal matche tjenesten — f.eks. virker 'button.press' kun på button.*, 'counter.increment' kun på counter.*, 'input_button.press' kun på input_button.* osv. Ved et misforhold mislykkes handlingen lydløst (HA logger 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Data (JSON, valgfrit)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Vælg fotos",
|
||||
"choose_photo": "Vælg foto",
|
||||
"photos_android_hint": "Android-appen afleverer ét foto pr. valg — hvert valg tilføjes.",
|
||||
"add_photos": "Tilføj fotos",
|
||||
"photos_limit": "Højst {max} fotos pr. fuldførelse",
|
||||
"history_edit_photos_hint": "Et foto, der fjernes her, bliver i objektets dokumenter.",
|
||||
"uploading": "Uploader…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Download",
|
||||
"doc_close": "Luk",
|
||||
"doc_camera": "Tag billede",
|
||||
"camera_capture_shoot": "Tag billede",
|
||||
"camera_switch_lens": "Skift kamera",
|
||||
"doc_drop_hint": "Slip filer her",
|
||||
"doc_task_none": "Ingen dokumenter knyttet til denne opgave.",
|
||||
"doc_link_existing": "Knyt et dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registrerer en fuldførelse (sætter senest udført), når udløseren løser sig selv — f.eks. salt påfyldt, filter udskiftet.",
|
||||
"doc_search": "Søg dokumenter…",
|
||||
"doc_search_none": "Ingen matchende dokumenter",
|
||||
"doc_description": "Beskrivelse (valgfri)",
|
||||
"doc_sort": "Sortér dokumenter",
|
||||
"doc_sort_newest": "Nyeste først",
|
||||
"doc_sort_oldest": "Ældste først",
|
||||
"doc_sort_title": "Efter titel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Efter kategori",
|
||||
"link_device_optional": "Tilknyt eksisterende enhed (valgfrit)",
|
||||
"parent_object_optional": "Overordnet objekt (valgfrit)",
|
||||
"parent_none": "(Intet overordnet)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Underret kun for visning",
|
||||
"settings_notify_scope_all": "Alle opgaver",
|
||||
"settings_notify_scope_hint": "Kun opgaver, der matcher den valgte visnings etiket-/brugerfiltre, sender påmindelser. Visningens status, sortering og gruppering ignoreres her.",
|
||||
"settings_notify_completed": "Notifikationer om udførelse",
|
||||
"settings_notify_completed_hint": "En besked til husstanden, når en opgave er udført, med årsag og hvem der gjorde det. \"Kun automatiske\" melder kun udførelser, som ingen lavede på stedet: sensorgendannelse, afkrydsning på indkøbslisten, et servicekald fra en automatisering. Kun til husstandstjenesten; stilletimer og dagsgrænsen gælder.",
|
||||
"notify_completed_off": "Fra",
|
||||
"notify_completed_automatic": "Kun automatiske udførelser",
|
||||
"notify_completed_all": "Alle udførelser",
|
||||
"settings_notify_rule": "Din egen notifikationsregel",
|
||||
"settings_notify_rule_hint": "Hver notifikation udløser også hændelsen maintenance_supporter_notification (type, status, objekt, opgave, referencenumre, prioritet, link, nyttelast). Videresend den med en automatisering — fx til Ticker, Telegram eller Pushover.",
|
||||
"settings_notify_event_only": "Udløs kun hændelsen, send intet selv",
|
||||
"settings_notify_event_only_hint": "Din automatisering står for hele leveringen. Gentagelsesintervaller, stille timer og dagsloftet gælder stadig for, hvornår hændelsen udløses.",
|
||||
"settings_notify_extra_data": "Ekstra notifikationsdata (skabelon)",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tilladt; flettes ind i notify-kaldets data (dine nøgler vinder). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test med knappen Send test.",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tilladt; flettes ind i notify-kaldets data (dine nøgler vinder). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test med knappen Send test.",
|
||||
"card_saved_view": "Gemt visning",
|
||||
"card_saved_view_none": "Ingen",
|
||||
"card_saved_view_help": "Anvender visningens status-, bruger- og etiketfiltre oven på filtrene ovenfor. Visningens sortering og gruppering er panelvisningsindstillinger og anvendes ikke på kortet.",
|
||||
"panel_card_tab": "Åbn på fanen",
|
||||
"panel_card_tab_default": "Husket fane (som i panelet)",
|
||||
"panel_card_view": "Åbn med gemt visning",
|
||||
"panel_card_height": "Højde",
|
||||
"panel_card_height_help": "Tom = fylder skærmen under kortet (panelvisning). Ellers en CSS-længde som 600px eller 80vh til visninger med andre kort.",
|
||||
"panel_card_not_registered": "Maintenance Supporter-panelet er ikke tilgængeligt — er integrationen sat op?",
|
||||
"panel_card_load_failed": "Maintenance Supporter-panelet kunne ikke indlæses — genindlæs siden (efter en opdatering: ryd browserens cache).",
|
||||
"doc_part_none": "Ingen dokumenter knyttet til denne del.",
|
||||
"settings_templates_toggle_group": "Slå alle skabeloner i denne gruppe til eller fra",
|
||||
"setups_button": "Foreslåede opsætninger",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "I orden",
|
||||
"battery_fleet_status_due": "Forfalden",
|
||||
"battery_fleet_predicted_on": "Forventes omkring {date}",
|
||||
"battery_fleet_predicted_typical": "Forventes omkring {date} — {months} måneder typisk for {type} ({source})",
|
||||
"lifetime_source_default": "indbygget standardværdi",
|
||||
"lifetime_source_table": "indbygget tabel",
|
||||
"lifetime_source_learned": "lært fra {n} udskiftninger",
|
||||
"lifetime_source_override": "din indstilling",
|
||||
"lifetime_source_learned_device": "lært fra {n} udskiftninger på denne enhed",
|
||||
"lifetime_source_learned_model": "lært fra {n} udskiftninger på enheder af denne model",
|
||||
"battery_fleet_predicted_trend": "Forudsagt ud fra batteriets afladningstendens: omkring {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Genopladeligt: oplad i stedet for at udskifte — aldrig på indkøbslisten",
|
||||
"battery_fleet_sort_name": "Sortér efter navn",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Brugte dele",
|
||||
"print_inc_photos": "Fotos",
|
||||
"print_inc_documents": "Tilknyttede dokumenter",
|
||||
"print_inc_doc_desc": "Dokumentbeskrivelser",
|
||||
"print_inc_checklist": "Tjekliste",
|
||||
"print_inc_notes": "Noter",
|
||||
"print_inc_costs": "Omkostning og varighed",
|
||||
"print_inc_person": "Udført af",
|
||||
"print_inc_refs": "Referencenumre",
|
||||
"print_inc_bare": "Udførelser uden detaljer",
|
||||
"print_inc_qr": "QR-kode pr. opgave",
|
||||
"print_button": "Udskriv",
|
||||
"report_scan_hint": "Scan for at åbne opgaven",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Laufzeit",
|
||||
"runtime_hours": "Ziel-Laufzeit (Stunden)",
|
||||
"target_value": "Zielwert",
|
||||
"baseline": "Nulllinie",
|
||||
"target_changes": "Ziel-Änderungen",
|
||||
"for_minutes": "Für (Minuten)",
|
||||
"time_based": "Zeitbasiert",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Erstellt",
|
||||
"report_times_done": "Erledigt",
|
||||
"report_total_cost": "Gesamtkosten",
|
||||
"report_every": "alle {n} {unit}",
|
||||
"report_notes": "Notizen",
|
||||
"report_col_type": "Typ",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Zeitplan",
|
||||
"duration": "Dauer",
|
||||
"minutes_short": "Min.",
|
||||
"both": "Beides",
|
||||
"trigger_val": "Trigger-Wert",
|
||||
"complete_title": "Erledigt: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: falscher Typ (erwartet: {type})",
|
||||
"err_invalid_choice": "{field}: nicht erlaubter Wert",
|
||||
"err_invalid_value": "{field}: ungültiger Wert",
|
||||
"ws_err_not_found": "Nicht gefunden — es wurde vielleicht inzwischen gelöscht",
|
||||
"ws_err_invalid_input": "Ungültige Eingabe",
|
||||
"ws_err_invalid_date": "Ungültiges Datum",
|
||||
"ws_err_invalid_format": "Ungültiges Format",
|
||||
"ws_err_invalid_url": "Ungültiger Link",
|
||||
"ws_err_not_configured": "Noch nicht eingerichtet",
|
||||
"ws_err_not_loaded": "Noch nicht geladen — gleich noch einmal versuchen",
|
||||
"ws_err_limit_reached": "Limit erreicht",
|
||||
"ws_err_too_many": "Zu viele Einträge",
|
||||
"ws_err_too_large": "Zu groß",
|
||||
"ws_err_not_archived": "Nicht archiviert",
|
||||
"ws_err_invalid_target": "Ungültiges Ziel",
|
||||
"ws_err_create_failed": "Konnte nicht angelegt werden",
|
||||
"ws_err_archived": "Archiviert — zuerst wiederherstellen",
|
||||
"ws_err_already_archived": "Bereits archiviert",
|
||||
"ws_err_unavailable": "Derzeit nicht verfügbar",
|
||||
"ws_err_unauthorized": "Für deinen Benutzer nicht erlaubt",
|
||||
"ws_err_too_many_views": "Zu viele gespeicherte Ansichten",
|
||||
"ws_err_storage_unavailable": "Der Speicher ist nicht verfügbar",
|
||||
"ws_err_replace_failed": "Ersetzen fehlgeschlagen",
|
||||
"ws_err_not_paused": "Nicht pausiert",
|
||||
"ws_err_not_available": "Nichts verfügbar",
|
||||
"ws_err_no_url": "Kein Link angegeben",
|
||||
"ws_err_no_phases": "Diese Aufgabe hat keine Phasen",
|
||||
"ws_err_invalid_view": "Ungültige Ansicht",
|
||||
"ws_err_invalid_user": "Unbekannter Benutzer",
|
||||
"ws_err_invalid_range": "Ungültiger Zeitraum",
|
||||
"ws_err_invalid_parent": "Ungültiges übergeordnetes Objekt",
|
||||
"ws_err_invalid_device": "Unbekanntes Gerät",
|
||||
"ws_err_empty_csv": "Die CSV-Datei ist leer",
|
||||
"ws_err_empty": "Nichts zu importieren",
|
||||
"ws_err_duplicate_failed": "Konnte nicht dupliziert werden",
|
||||
"ws_err_already_paused": "Bereits pausiert",
|
||||
"ws_err_invalid_cursor": "Ungültige Phasenposition",
|
||||
"ws_err_invalid_entity_slug": "Ungültiger Entitätsname",
|
||||
"ws_err_invalid_trigger_config": "Ungültige Trigger-Konfiguration",
|
||||
"ws_err_no_defaults": "Keine Vorgaben für das Schnell-Erledigen hinterlegt",
|
||||
"ws_err_self_link_device": "Ein Objekt kann nicht mit sich selbst verknüpft werden",
|
||||
"ws_err_too_early": "Zu früh — diese Aufgabe kann erst näher am Fälligkeitsdatum erledigt werden",
|
||||
"feat_schedule_time": "Uhrzeit-Scheduling",
|
||||
"feat_schedule_time_desc": "Tasks werden zu einer festen Uhrzeit fällig statt um Mitternacht.",
|
||||
"schedule_time_toggle": "Zu einer bestimmten Uhrzeit fällig",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Auslösen bei genau (=)",
|
||||
"trigger_not_equals": "Auslösen bei abweichend von (≠)",
|
||||
"for_at_least_minutes": "Für mindestens (Minuten)",
|
||||
"safety_interval_days": "Sicherheitsintervall (Tage, optional)",
|
||||
"safety_interval": "Sicherheitsintervall (optional)",
|
||||
"trigger_combinator": "Trigger und Intervall kombinieren",
|
||||
"trigger_combinator_any": "Trigger oder Intervall (zuerst erfüllt)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Meine Aufgaben",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Keine Wartung",
|
||||
"cal_window_7": "7 Tage",
|
||||
"cal_window_14": "14 Tage",
|
||||
"cal_window_30": "30 Tage",
|
||||
"cal_window_365": "1 Jahr",
|
||||
"cal_every_n_days": "alle {n} Tage",
|
||||
"cal_source_time": "Zeit-basiert",
|
||||
"cal_source_time_adaptive": "Zeit-basiert (adaptiv)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR-Code konnte nicht generiert werden.",
|
||||
"qr_error_no_url": "Keine HA-URL konfiguriert. Bitte unter Einstellungen → System → Netzwerk eine externe oder interne URL setzen.",
|
||||
"save_error": "Fehler beim Speichern. Bitte erneut versuchen.",
|
||||
"subsave_warning": "Gespeichert, aber eine Einstellung wurde abgelehnt: {detail}",
|
||||
"qr_print": "Drucken",
|
||||
"qr_download": "SVG herunterladen",
|
||||
"qr_action": "Aktion beim Scannen",
|
||||
"qr_action_view": "Wartungsinfo anzeigen",
|
||||
"qr_action_complete": "Wartung als erledigt markieren",
|
||||
"qr_url_mode": "Link-Typ",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Lokal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Übersicht",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Letzte Aktivitäten",
|
||||
"search_notes": "Notizen durchsuchen",
|
||||
"avg_cost": "Ø Kosten",
|
||||
"no_advanced_features": "Keine erweiterten Funktionen aktiviert",
|
||||
"no_advanced_features_hint": "Aktiviere „Adaptive Intervalle“ oder „Saisonale Muster“ in den Integrationseinstellungen, um hier Analysedaten zu sehen.",
|
||||
"analysis_not_enough_data": "Noch nicht genügend Daten für die Analyse vorhanden.",
|
||||
"analysis_not_enough_data_hint": "Die Weibull-Analyse benötigt mindestens 5 abgeschlossene Wartungen, saisonale Muster werden nach 6+ Datenpunkten pro Monat sichtbar.",
|
||||
"analysis_manual_task_hint": "Manuelle Aufgaben ohne Intervall erzeugen keine Analysedaten.",
|
||||
"completions": "Abschlüsse",
|
||||
"current": "Aktuell",
|
||||
"shorter": "Kürzer",
|
||||
"longer": "Länger",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Vergangene Fenster",
|
||||
"cal_forward_windows": "Zukünftige Fenster",
|
||||
"history_edit_title": "Verlaufseintrag bearbeiten",
|
||||
"history_delete_entry": "Eintrag löschen",
|
||||
"history_delete_confirm": "Diesen Verlaufseintrag löschen? Das Datum der letzten Erledigung fällt auf die vorherige zurück; Fotos bleiben beim Objekt, verbrauchte Teile werden nicht zurückgebucht.",
|
||||
"history_edit_timestamp": "Zeitstempel",
|
||||
"manufacturer": "Hersteller",
|
||||
"model": "Modell",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Standard-Warntage",
|
||||
"settings_consumable_threshold": "Schwelle „Verbrauchsmaterial niedrig“ (%)",
|
||||
"settings_battery_low_percent": "Schwelle „Batterie niedrig“ (%)",
|
||||
"settings_battery_lifetimes": "Typische Batterie-Lebensdauern",
|
||||
"settings_battery_lifetimes_hint": "Wird nur für Batterien ohne Prozentanzeige verwendet: eine Battery-Notes-Notiz, die nur Typ und Austauschdatum kennt, oder ein Sensor, der nur „schwach“ meldet. Deren Fälligkeit ist letzter Wechsel + diese Lebensdauer. Batterien mit Füllstand bekommen ihre Prognose aus der gemessenen Entladung. Sobald die Flotte drei Wechsel eines Typs gesehen hat, ersetzt das mittlere Intervall den eingebauten Wert — dein eigener Wert gewinnt immer.",
|
||||
"settings_battery_lifetime_months": "Monate",
|
||||
"settings_battery_lifetime_reset": "Standard verwenden",
|
||||
"settings_battery_lifetime_in_fleet": "in deiner Flotte",
|
||||
"settings_battery_lifetime_learned_models": "Gelernt je Modell: {list}",
|
||||
"settings_thresholds_hint": "Vorgaben für neue Vorgeschlagene Einrichtungen (Prozent-Restwert) und für Flotten-Batterien ohne eigene Battery-Notes-Schwelle. Bestehende Aufgaben behalten ihren Wert.",
|
||||
"settings_value_out_of_range": "Der Wert muss zwischen {min} und {max} liegen",
|
||||
"bn_summary": "{name}: {pct} % für {n} Batterien",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} Geräte mit eigener Schwelle:",
|
||||
"bn_more": "+ {n} weitere",
|
||||
"settings_row_actions": "Aktionen in Aufgabenzeilen",
|
||||
"settings_ref_numbers_in_lists": "Referenznummern in Listen",
|
||||
"settings_ref_numbers_in_lists_hint": "Zeigt die Chips #8 / #8.3 vor Objekt- und Aufgabennamen in der Heute-Liste, der Aufgabentabelle, den Objektkarten und der Objekttabelle.",
|
||||
"row_actions_buttons_compact": "Buttons (auf dem Handy nur Symbole)",
|
||||
"row_actions_buttons": "Buttons mit Text",
|
||||
"row_actions_icons": "Symbole (klassisch)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Schlummerdauer (Stunden)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Währung",
|
||||
"settings_currency_decimals": "Nachkommastellen für Beträge",
|
||||
"settings_currency_decimals_hint": "Wie viele Nachkommastellen jeder Betrag zeigt: KPIs, Budgets, Kosten in Listen und Berichten, Budget-Alarme. 0 = ganze Zahlen.",
|
||||
"settings_budget_monthly": "Monatsbudget",
|
||||
"settings_budget_yearly": "Jahresbudget",
|
||||
"settings_budget_alerts": "Budget-Warnungen",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML exportieren",
|
||||
"settings_export_csv": "CSV exportieren",
|
||||
"settings_export_settings": "Einstellungen exportieren (JSON)",
|
||||
"settings_import_csv": "CSV importieren",
|
||||
"settings_import_placeholder": "JSON- oder CSV-Inhalt hier einfügen…",
|
||||
"settings_import_btn": "Importieren",
|
||||
"settings_import_success": "{count} Objekte erfolgreich importiert.",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "Pro Aufgabe HA-Action beim Abschluss konfigurieren + Quick-Complete-QR mit voreingestellten Werten.",
|
||||
"on_complete_action_title": "Beim Abschluss: HA-Action auslösen (optional)",
|
||||
"on_complete_action_desc": "Ruft beim Erledigen der Aufgabe einen HA-Service auf — z. B. einen Zähler am Gerät zurücksetzen.",
|
||||
"on_complete_action_service": "Service",
|
||||
"on_complete_action_target": "Ziel-Entität",
|
||||
"on_complete_action_target_hint": "Achtung: Domain der Entität muss zum Service passen — z. B. 'button.press' nur für button.*, 'counter.increment' nur für counter.*, 'input_button.press' nur für input_button.* etc. Bei Mismatch fired die Aktion nicht (HA loggt 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Daten (JSON, optional)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Fotos auswählen",
|
||||
"choose_photo": "Foto wählen",
|
||||
"photos_android_hint": "Die Android-App übergibt pro Auswahl ein Foto — jede Auswahl wird hinzugefügt.",
|
||||
"add_photos": "Fotos hinzufügen",
|
||||
"photos_limit": "Bis zu {max} Fotos pro Abschluss",
|
||||
"history_edit_photos_hint": "Ein hier entferntes Foto bleibt in den Dokumenten des Objekts erhalten.",
|
||||
"uploading": "Wird hochgeladen…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Herunterladen",
|
||||
"doc_close": "Schließen",
|
||||
"doc_camera": "Foto aufnehmen",
|
||||
"camera_capture_shoot": "Aufnehmen",
|
||||
"camera_switch_lens": "Kamera wechseln",
|
||||
"doc_drop_hint": "Dateien hier ablegen",
|
||||
"doc_task_none": "Keine Dokumente mit dieser Aufgabe verknüpft.",
|
||||
"doc_link_existing": "Dokument verknüpfen…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Verbucht eine Wartung (setzt „zuletzt durchgeführt“), sobald sich der Auslöser von selbst auflöst — z. B. Salz nachgefüllt, Filter getauscht.",
|
||||
"doc_search": "Dokumente durchsuchen…",
|
||||
"doc_search_none": "Keine passenden Dokumente",
|
||||
"doc_description": "Beschreibung (optional)",
|
||||
"doc_sort": "Dokumente sortieren",
|
||||
"doc_sort_newest": "Neueste zuerst",
|
||||
"doc_sort_oldest": "Älteste zuerst",
|
||||
"doc_sort_title": "Nach Titel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Nach Kategorie",
|
||||
"link_device_optional": "Mit bestehendem Gerät verknüpfen (optional)",
|
||||
"parent_object_optional": "Übergeordnetes Objekt (optional)",
|
||||
"parent_none": "(Kein übergeordnetes Objekt)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Nur benachrichtigen für Ansicht",
|
||||
"settings_notify_scope_all": "Alle Aufgaben",
|
||||
"settings_notify_scope_hint": "Nur Aufgaben, die zu den Label-/Nutzer-Filtern der gewählten Ansicht passen, senden Erinnerungen. Status, Sortierung und Gruppierung der Ansicht werden hier ignoriert.",
|
||||
"settings_notify_completed": "Erledigt-Benachrichtigungen",
|
||||
"settings_notify_completed_hint": "Haushalts-Info, wenn eine Aufgabe erledigt wurde, mit Grund und Person. „Nur automatische“ meldet nur Erledigungen, die niemand vor Ort ausgelöst hat: Sensor-Erholung, Einkaufslisten-Abhaken, Service-Aufruf einer Automation. Geht nur an den Haushalts-Dienst; Ruhezeiten und Tageslimit gelten.",
|
||||
"notify_completed_off": "Aus",
|
||||
"notify_completed_automatic": "Nur automatische Erledigungen",
|
||||
"notify_completed_all": "Alle Erledigungen",
|
||||
"settings_notify_rule": "Eigene Benachrichtigungsregel",
|
||||
"settings_notify_rule_hint": "Jede Benachrichtigung löst zusätzlich das Ereignis maintenance_supporter_notification aus (Art, Status, Objekt, Aufgabe, Referenznummern, Priorität, Deep-Link, Nutzlast). Leite es per Automation weiter — z. B. an Ticker, Telegram oder Pushover.",
|
||||
"settings_notify_event_only": "Nur das Ereignis auslösen, selbst nichts senden",
|
||||
"settings_notify_event_only_hint": "Deine Automation übernimmt den kompletten Versand. Wiederholintervalle, Ruhezeiten und Tageslimit gelten weiterhin dafür, wann das Ereignis ausgelöst wird.",
|
||||
"settings_notify_extra_data": "Zusätzliche Benachrichtigungsdaten (Template)",
|
||||
"settings_notify_extra_data_hint": "JSON oder YAML, Jinja erlaubt; wird in die data des Notify-Aufrufs gemischt (deine Schlüssel gewinnen). Variablen: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Prüfen mit „Test senden“.",
|
||||
"settings_notify_extra_data_hint": "JSON oder YAML, Jinja erlaubt; wird in die data des Notify-Aufrufs gemischt (deine Schlüssel gewinnen). Variablen: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Prüfen mit „Test senden“.",
|
||||
"card_saved_view": "Gespeicherte Ansicht",
|
||||
"card_saved_view_none": "Keine",
|
||||
"card_saved_view_help": "Wendet die Status-, Nutzer- und Label-Filter der Ansicht zusätzlich zu den obigen Filtern an. Sortierung und Gruppierung der Ansicht sind Panel-Darstellung und werden auf der Karte nicht angewendet.",
|
||||
"panel_card_tab": "Startet auf Tab",
|
||||
"panel_card_tab_default": "Gemerkter Tab (wie im Panel)",
|
||||
"panel_card_view": "Startet mit gespeicherter Ansicht",
|
||||
"panel_card_height": "Höhe",
|
||||
"panel_card_height_help": "Leer = füllt den Bildschirm unterhalb der Karte (Panel-Ansicht). Sonst eine CSS-Länge wie 600px oder 80vh für Ansichten mit weiteren Karten.",
|
||||
"panel_card_not_registered": "Das Maintenance-Supporter-Panel ist nicht verfügbar — ist die Integration eingerichtet?",
|
||||
"panel_card_load_failed": "Das Maintenance-Supporter-Panel konnte nicht geladen werden — Seite neu laden (nach einem Update: Browser-Cache leeren).",
|
||||
"doc_part_none": "Keine Dokumente mit diesem Ersatzteil verknüpft.",
|
||||
"settings_templates_toggle_group": "Alle Vorlagen dieser Gruppe ein- oder ausschalten",
|
||||
"setups_button": "Setup-Vorschläge",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "In Ordnung",
|
||||
"battery_fleet_status_due": "Fällig",
|
||||
"battery_fleet_predicted_on": "Voraussichtlich um {date}",
|
||||
"battery_fleet_predicted_typical": "Voraussichtlich um {date} — {months} Monate typisch für {type} ({source})",
|
||||
"lifetime_source_default": "eingebauter Standardwert",
|
||||
"lifetime_source_table": "eingebaute Tabelle",
|
||||
"lifetime_source_learned": "gelernt aus {n} Wechseln",
|
||||
"lifetime_source_override": "deine Einstellung",
|
||||
"lifetime_source_learned_device": "gelernt aus {n} Wechseln dieses Geräts",
|
||||
"lifetime_source_learned_model": "gelernt aus {n} Wechseln bei Geräten dieses Modells",
|
||||
"battery_fleet_predicted_trend": "Aus dem Entladetrend dieser Batterie vorhergesagt: etwa {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Wiederaufladbar: laden statt ersetzen — nie auf der Einkaufsliste",
|
||||
"battery_fleet_sort_name": "Nach Name sortieren",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Verbrauchte Teile",
|
||||
"print_inc_photos": "Fotos",
|
||||
"print_inc_documents": "Verknüpfte Dokumente",
|
||||
"print_inc_doc_desc": "Dokumentbeschreibungen",
|
||||
"print_inc_checklist": "Checkliste",
|
||||
"print_inc_notes": "Notizen",
|
||||
"print_inc_costs": "Kosten und Dauer",
|
||||
"print_inc_person": "Erledigt von",
|
||||
"print_inc_refs": "Referenznummern",
|
||||
"print_inc_bare": "Erledigungen ohne Details",
|
||||
"print_inc_qr": "QR-Code je Aufgabe",
|
||||
"print_button": "Drucken",
|
||||
"report_scan_hint": "Scannen öffnet die Aufgabe",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Runtime",
|
||||
"runtime_hours": "Target runtime (hours)",
|
||||
"target_value": "Target value",
|
||||
"baseline": "Baseline",
|
||||
"target_changes": "Target changes",
|
||||
"for_minutes": "For (minutes)",
|
||||
"time_based": "Time-based",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Generated",
|
||||
"report_times_done": "Done",
|
||||
"report_total_cost": "Total cost",
|
||||
"report_every": "every {n} {unit}",
|
||||
"report_notes": "Notes",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Schedule",
|
||||
"duration": "Duration",
|
||||
"minutes_short": "min",
|
||||
"both": "Both",
|
||||
"trigger_val": "Trigger value",
|
||||
"complete_title": "Complete: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: wrong type (expected: {type})",
|
||||
"err_invalid_choice": "{field}: not an allowed value",
|
||||
"err_invalid_value": "{field}: invalid value",
|
||||
"ws_err_not_found": "Not found — it may have been deleted meanwhile",
|
||||
"ws_err_invalid_input": "Invalid input",
|
||||
"ws_err_invalid_date": "Invalid date",
|
||||
"ws_err_invalid_format": "Invalid format",
|
||||
"ws_err_invalid_url": "Invalid link",
|
||||
"ws_err_not_configured": "Not set up yet",
|
||||
"ws_err_not_loaded": "Not loaded yet — try again in a moment",
|
||||
"ws_err_limit_reached": "Limit reached",
|
||||
"ws_err_too_many": "Too many entries",
|
||||
"ws_err_too_large": "Too large",
|
||||
"ws_err_not_archived": "Not archived",
|
||||
"ws_err_invalid_target": "Invalid target",
|
||||
"ws_err_create_failed": "Could not be created",
|
||||
"ws_err_archived": "Archived — restore it first",
|
||||
"ws_err_already_archived": "Already archived",
|
||||
"ws_err_unavailable": "Currently unavailable",
|
||||
"ws_err_unauthorized": "Not permitted for your user",
|
||||
"ws_err_too_many_views": "Too many saved views",
|
||||
"ws_err_storage_unavailable": "Storage is not available",
|
||||
"ws_err_replace_failed": "Replacement failed",
|
||||
"ws_err_not_paused": "Not paused",
|
||||
"ws_err_not_available": "Nothing available",
|
||||
"ws_err_no_url": "No link given",
|
||||
"ws_err_no_phases": "This task has no phases",
|
||||
"ws_err_invalid_view": "Invalid view",
|
||||
"ws_err_invalid_user": "Unknown user",
|
||||
"ws_err_invalid_range": "Invalid range",
|
||||
"ws_err_invalid_parent": "Invalid parent object",
|
||||
"ws_err_invalid_device": "Unknown device",
|
||||
"ws_err_empty_csv": "The CSV file is empty",
|
||||
"ws_err_empty": "Nothing to import",
|
||||
"ws_err_duplicate_failed": "Could not duplicate",
|
||||
"ws_err_already_paused": "Already paused",
|
||||
"ws_err_invalid_cursor": "Invalid phase position",
|
||||
"ws_err_invalid_entity_slug": "Invalid entity name",
|
||||
"ws_err_invalid_trigger_config": "Invalid trigger configuration",
|
||||
"ws_err_no_defaults": "No quick-complete defaults set",
|
||||
"ws_err_self_link_device": "An object cannot be linked to itself",
|
||||
"ws_err_too_early": "Too early — this task can only be completed closer to its due date",
|
||||
"feat_schedule_time": "Time-of-day scheduling",
|
||||
"feat_schedule_time_desc": "Tasks become overdue at a specific time of day instead of midnight.",
|
||||
"schedule_time_toggle": "Due at a specific time",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Trigger when equal to (=)",
|
||||
"trigger_not_equals": "Trigger when different from (≠)",
|
||||
"for_at_least_minutes": "For at least (minutes)",
|
||||
"safety_interval_days": "Safety interval (days, optional)",
|
||||
"safety_interval": "Safety interval (optional)",
|
||||
"trigger_combinator": "Combine trigger and interval",
|
||||
"trigger_combinator_any": "Trigger or interval (whichever first)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "My Tasks",
|
||||
"tab_calendar": "Calendar",
|
||||
"cal_no_events": "No maintenance",
|
||||
"cal_window_7": "7 days",
|
||||
"cal_window_14": "14 days",
|
||||
"cal_window_30": "30 days",
|
||||
"cal_window_365": "1 year",
|
||||
"cal_every_n_days": "every {n} days",
|
||||
"cal_source_time": "Time-based",
|
||||
"cal_source_time_adaptive": "Time-based (adaptive)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "Failed to generate QR code.",
|
||||
"qr_error_no_url": "No HA URL configured. Please set an external or internal URL in Settings → System → Network.",
|
||||
"save_error": "Failed to save. Please try again.",
|
||||
"subsave_warning": "Saved, but one setting was rejected: {detail}",
|
||||
"qr_print": "Print",
|
||||
"qr_download": "Download SVG",
|
||||
"qr_action": "Action on scan",
|
||||
"qr_action_view": "View maintenance info",
|
||||
"qr_action_complete": "Mark maintenance as complete",
|
||||
"qr_url_mode": "Link type",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "Server URL",
|
||||
"overview": "Overview",
|
||||
"analysis": "Analysis",
|
||||
"recent_activities": "Recent Activities",
|
||||
"search_notes": "Search notes",
|
||||
"avg_cost": "Avg Cost",
|
||||
"no_advanced_features": "No advanced features enabled",
|
||||
"no_advanced_features_hint": "Enable “Adaptive Intervals” or “Seasonal Patterns” in the integration settings to see analysis data here.",
|
||||
"analysis_not_enough_data": "Not enough data for analysis yet.",
|
||||
"analysis_not_enough_data_hint": "Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.",
|
||||
"analysis_manual_task_hint": "Manual tasks without an interval do not generate analysis data.",
|
||||
"completions": "completions",
|
||||
"current": "Current",
|
||||
"shorter": "Shorter",
|
||||
"longer": "Longer",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Past windows",
|
||||
"cal_forward_windows": "Forward windows",
|
||||
"history_edit_title": "Edit history entry",
|
||||
"history_delete_entry": "Delete entry",
|
||||
"history_delete_confirm": "Delete this history entry? The task's last-performed date falls back to the previous completion; photos stay with the object and consumed parts are not restocked.",
|
||||
"history_edit_timestamp": "Timestamp",
|
||||
"manufacturer": "Manufacturer",
|
||||
"model": "Model",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Default warning days",
|
||||
"settings_consumable_threshold": "Consumable low threshold (%)",
|
||||
"settings_battery_low_percent": "Battery low threshold (%)",
|
||||
"settings_battery_lifetimes": "Typical battery lifetimes",
|
||||
"settings_battery_lifetimes_hint": "Only used for batteries without a percentage: a Battery Notes note that has just a type and a last-replaced date, or a sensor that only reports \"low\". Their due date is last replaced + this lifetime. Batteries that report a level get their forecast from the measured discharge instead. Once the fleet has seen three replacements of a type, the median interval takes over from the built-in value — your own value always wins.",
|
||||
"settings_battery_lifetime_months": "months",
|
||||
"settings_battery_lifetime_reset": "Use default",
|
||||
"settings_battery_lifetime_in_fleet": "in your fleet",
|
||||
"settings_battery_lifetime_learned_models": "Learned per model: {list}",
|
||||
"settings_thresholds_hint": "Defaults for new Suggested setups (percent-remaining consumables) and for fleet batteries without their own Battery Notes threshold. Existing tasks keep their value.",
|
||||
"settings_value_out_of_range": "Value must be between {min} and {max}",
|
||||
"bn_summary": "{name}: {pct} % for {n} batteries",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} devices with their own threshold:",
|
||||
"bn_more": "+ {n} more",
|
||||
"settings_row_actions": "Task row actions",
|
||||
"settings_ref_numbers_in_lists": "Reference numbers in lists",
|
||||
"settings_ref_numbers_in_lists_hint": "Show the #8 / #8.3 chips in front of object and task names in the Today list, the task table, the object cards and the objects table.",
|
||||
"row_actions_buttons_compact": "Buttons (icons only on phones)",
|
||||
"row_actions_buttons": "Buttons with text",
|
||||
"row_actions_icons": "Icons (classic)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Snooze duration (hours)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Currency",
|
||||
"settings_currency_decimals": "Decimal places for amounts",
|
||||
"settings_currency_decimals_hint": "How many decimals every amount shows: KPIs, budgets, costs in lists and reports, budget alerts. 0 = whole numbers.",
|
||||
"settings_budget_monthly": "Monthly budget",
|
||||
"settings_budget_yearly": "Yearly budget",
|
||||
"settings_budget_alerts": "Budget alerts",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Export YAML",
|
||||
"settings_export_csv": "Export CSV",
|
||||
"settings_export_settings": "Export settings (JSON)",
|
||||
"settings_import_csv": "Import CSV",
|
||||
"settings_import_placeholder": "Paste JSON or CSV content here…",
|
||||
"settings_import_btn": "Import",
|
||||
"settings_import_success": "{count} objects imported successfully.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "Per-task HA action on complete + quick-complete QR with pre-set values.",
|
||||
"on_complete_action_title": "On complete: trigger HA action (optional)",
|
||||
"on_complete_action_desc": "Calls an HA service when the task is completed — e.g. reset a counter on the device.",
|
||||
"on_complete_action_service": "Service",
|
||||
"on_complete_action_target": "Target entity",
|
||||
"on_complete_action_target_hint": "Note: the entity domain must match the service — e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Data (JSON, optional)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "Choose photos",
|
||||
"choose_photo": "Choose photo",
|
||||
"photos_android_hint": "The Android app hands over one photo per pick — each pick is added.",
|
||||
"add_photos": "Add photos",
|
||||
"photos_limit": "Up to {max} photos per completion",
|
||||
"history_edit_photos_hint": "Removing a photo here keeps the file in the object's documents.",
|
||||
"uploading": "Uploading…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "Download",
|
||||
"doc_close": "Close",
|
||||
"doc_camera": "Take photo",
|
||||
"camera_capture_shoot": "Capture",
|
||||
"camera_switch_lens": "Switch camera",
|
||||
"doc_drop_hint": "Drop files here",
|
||||
"doc_task_none": "No documents linked to this task.",
|
||||
"doc_link_existing": "Link a document…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "Records a completion (sets last performed) when the trigger clears itself — e.g. salt refilled, filter replaced.",
|
||||
"doc_search": "Search documents…",
|
||||
"doc_search_none": "No matching documents",
|
||||
"doc_description": "Description (optional)",
|
||||
"doc_sort": "Sort documents",
|
||||
"doc_sort_newest": "Newest first",
|
||||
"doc_sort_oldest": "Oldest first",
|
||||
"doc_sort_title": "By title (1, 2, 3 …)",
|
||||
"doc_sort_category": "By category",
|
||||
"link_device_optional": "Link to existing device (optional)",
|
||||
"parent_object_optional": "Parent object (optional)",
|
||||
"parent_none": "(No parent)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notify only for view",
|
||||
"settings_notify_scope_all": "All tasks",
|
||||
"settings_notify_scope_hint": "Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",
|
||||
"settings_notify_completed": "Completion notifications",
|
||||
"settings_notify_completed_hint": "Household news when a task is completed, with the reason and who did it. \"Automatic only\" announces just the completions nobody made on the spot: sensor recovery, shopping-list check-off, an automation's service call. Sent to the household service only; quiet hours and the daily cap apply.",
|
||||
"notify_completed_off": "Off",
|
||||
"notify_completed_automatic": "Automatic completions only",
|
||||
"notify_completed_all": "All completions",
|
||||
"settings_notify_rule": "Your own notification rule",
|
||||
"settings_notify_rule_hint": "Every notification also fires the event maintenance_supporter_notification (kind, status, object, task, reference numbers, priority, deep link, the payload). Route it with an automation — e.g. into Ticker, Telegram or Pushover.",
|
||||
"settings_notify_event_only": "Only fire the event, send nothing myself",
|
||||
"settings_notify_event_only_hint": "Your automation is the whole delivery. Rate limits, quiet hours and the daily cap still apply to when the event fires.",
|
||||
"settings_notify_extra_data": "Extra notification data (template)",
|
||||
"settings_notify_extra_data_hint": "JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test it with the Send test button.",
|
||||
"settings_notify_extra_data_hint": "JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test it with the Send test button.",
|
||||
"card_saved_view": "Saved view",
|
||||
"card_saved_view_none": "None",
|
||||
"card_saved_view_help": "Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",
|
||||
"panel_card_tab": "Open on tab",
|
||||
"panel_card_tab_default": "Remembered tab (as in the panel)",
|
||||
"panel_card_view": "Open with saved view",
|
||||
"panel_card_height": "Height",
|
||||
"panel_card_height_help": "Empty = fill the screen below the card (panel view). Otherwise a CSS length such as 600px or 80vh for views with other cards.",
|
||||
"panel_card_not_registered": "The Maintenance Supporter panel is not available — is the integration set up?",
|
||||
"panel_card_load_failed": "The Maintenance Supporter panel could not be loaded — reload the page (after an update: clear the browser cache).",
|
||||
"doc_part_none": "No documents linked to this part.",
|
||||
"settings_templates_toggle_group": "Enable or disable all templates in this group",
|
||||
"setups_button": "Suggested setups",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Healthy",
|
||||
"battery_fleet_status_due": "Due",
|
||||
"battery_fleet_predicted_on": "Expected around {date}",
|
||||
"battery_fleet_predicted_typical": "Expected around {date} — {months} months typical for {type} ({source})",
|
||||
"lifetime_source_default": "built-in default",
|
||||
"lifetime_source_table": "built-in table",
|
||||
"lifetime_source_learned": "learned from {n} replacements",
|
||||
"lifetime_source_override": "your setting",
|
||||
"lifetime_source_learned_device": "learned from this device's {n} replacements",
|
||||
"lifetime_source_learned_model": "learned from {n} replacements on devices of this model",
|
||||
"battery_fleet_predicted_trend": "Predicted from this battery's discharge trend: around {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Rechargeable: charge instead of replacing — never on the shopping list",
|
||||
"battery_fleet_sort_name": "Sort by name",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Parts used",
|
||||
"print_inc_photos": "Photos",
|
||||
"print_inc_documents": "Linked documents",
|
||||
"print_inc_doc_desc": "Document descriptions",
|
||||
"print_inc_checklist": "Checklist",
|
||||
"print_inc_notes": "Notes",
|
||||
"print_inc_costs": "Cost and duration",
|
||||
"print_inc_person": "Completed by",
|
||||
"print_inc_refs": "Reference numbers",
|
||||
"print_inc_bare": "Completions without details",
|
||||
"print_inc_qr": "QR code per task",
|
||||
"print_button": "Print",
|
||||
"report_scan_hint": "Scan to open the task",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Tiempo de funcionamiento",
|
||||
"runtime_hours": "Duración objetivo (horas)",
|
||||
"target_value": "Valor objetivo",
|
||||
"baseline": "Línea base",
|
||||
"target_changes": "Cambios objetivo",
|
||||
"for_minutes": "Durante (minutos)",
|
||||
"time_based": "Temporal",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Generado",
|
||||
"report_times_done": "Hecho",
|
||||
"report_total_cost": "Coste total",
|
||||
"report_every": "cada {n} {unit}",
|
||||
"report_notes": "Notas",
|
||||
"report_col_type": "Tipo",
|
||||
"report_col_status": "Estado",
|
||||
"report_col_schedule": "Programación",
|
||||
"duration": "Duración",
|
||||
"minutes_short": "min",
|
||||
"both": "Ambos",
|
||||
"trigger_val": "Valor del disparador",
|
||||
"complete_title": "Completada: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: tipo incorrecto (esperado: {type})",
|
||||
"err_invalid_choice": "{field}: valor no permitido",
|
||||
"err_invalid_value": "{field}: valor inválido",
|
||||
"ws_err_not_found": "No encontrado — puede que se haya eliminado mientras tanto",
|
||||
"ws_err_invalid_input": "Entrada no válida",
|
||||
"ws_err_invalid_date": "Fecha no válida",
|
||||
"ws_err_invalid_format": "Formato no válido",
|
||||
"ws_err_invalid_url": "Enlace no válido",
|
||||
"ws_err_not_configured": "Aún no configurado",
|
||||
"ws_err_not_loaded": "Aún no cargado — inténtalo de nuevo en un momento",
|
||||
"ws_err_limit_reached": "Límite alcanzado",
|
||||
"ws_err_too_many": "Demasiadas entradas",
|
||||
"ws_err_too_large": "Demasiado grande",
|
||||
"ws_err_not_archived": "No archivado",
|
||||
"ws_err_invalid_target": "Destino no válido",
|
||||
"ws_err_create_failed": "No se pudo crear",
|
||||
"ws_err_archived": "Archivado — restáuralo primero",
|
||||
"ws_err_already_archived": "Ya archivado",
|
||||
"ws_err_unavailable": "No disponible actualmente",
|
||||
"ws_err_unauthorized": "No permitido para tu usuario",
|
||||
"ws_err_too_many_views": "Demasiadas vistas guardadas",
|
||||
"ws_err_storage_unavailable": "El almacenamiento no está disponible",
|
||||
"ws_err_replace_failed": "Error al reemplazar",
|
||||
"ws_err_not_paused": "No está en pausa",
|
||||
"ws_err_not_available": "Nada disponible",
|
||||
"ws_err_no_url": "No se indicó ningún enlace",
|
||||
"ws_err_no_phases": "Esta tarea no tiene fases",
|
||||
"ws_err_invalid_view": "Vista no válida",
|
||||
"ws_err_invalid_user": "Usuario desconocido",
|
||||
"ws_err_invalid_range": "Rango no válido",
|
||||
"ws_err_invalid_parent": "Objeto principal no válido",
|
||||
"ws_err_invalid_device": "Dispositivo desconocido",
|
||||
"ws_err_empty_csv": "El archivo CSV está vacío",
|
||||
"ws_err_empty": "Nada que importar",
|
||||
"ws_err_duplicate_failed": "No se pudo duplicar",
|
||||
"ws_err_already_paused": "Ya está en pausa",
|
||||
"ws_err_invalid_cursor": "Posición de fase no válida",
|
||||
"ws_err_invalid_entity_slug": "Nombre de entidad no válido",
|
||||
"ws_err_invalid_trigger_config": "Configuración del disparador no válida",
|
||||
"ws_err_no_defaults": "No hay valores predeterminados para completar rápido",
|
||||
"ws_err_self_link_device": "Un objeto no puede vincularse a sí mismo",
|
||||
"ws_err_too_early": "Demasiado pronto — esta tarea solo puede completarse más cerca de su vencimiento",
|
||||
"feat_schedule_time": "Programación por hora",
|
||||
"feat_schedule_time_desc": "Las tareas vencen a una hora específica en lugar de medianoche.",
|
||||
"schedule_time_toggle": "Vence a una hora concreta",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Activar cuando sea igual a (=)",
|
||||
"trigger_not_equals": "Activar cuando sea distinto de (≠)",
|
||||
"for_at_least_minutes": "Durante al menos (minutos)",
|
||||
"safety_interval_days": "Intervalo de seguridad (días, opcional)",
|
||||
"safety_interval": "Intervalo de seguridad (opcional)",
|
||||
"trigger_combinator": "Combinar disparador e intervalo",
|
||||
"trigger_combinator_any": "Disparador o intervalo (el primero)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Mis tareas",
|
||||
"tab_calendar": "Calendario",
|
||||
"cal_no_events": "Sin mantenimiento",
|
||||
"cal_window_7": "7 días",
|
||||
"cal_window_14": "14 días",
|
||||
"cal_window_30": "30 días",
|
||||
"cal_window_365": "1 año",
|
||||
"cal_every_n_days": "cada {n} días",
|
||||
"cal_source_time": "Basado en tiempo",
|
||||
"cal_source_time_adaptive": "Basado en tiempo (adaptativo)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "No se pudo generar el código QR.",
|
||||
"qr_error_no_url": "No hay URL de HA configurada. Establezca una URL externa o interna en Ajustes → Sistema → Red.",
|
||||
"save_error": "Error al guardar. Inténtelo de nuevo.",
|
||||
"subsave_warning": "Guardado, pero se rechazó un ajuste: {detail}",
|
||||
"qr_print": "Imprimir",
|
||||
"qr_download": "Descargar SVG",
|
||||
"qr_action": "Acción al escanear",
|
||||
"qr_action_view": "Ver info de mantenimiento",
|
||||
"qr_action_complete": "Marcar mantenimiento como completado",
|
||||
"qr_url_mode": "Tipo de enlace",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "URL del servidor",
|
||||
"overview": "Resumen",
|
||||
"analysis": "Análisis",
|
||||
"recent_activities": "Actividades recientes",
|
||||
"search_notes": "Buscar en notas",
|
||||
"avg_cost": "Ø Coste",
|
||||
"no_advanced_features": "Sin funciones avanzadas activadas",
|
||||
"no_advanced_features_hint": "Active “Intervalos Adaptativos” o “Patrones Estacionales” en la configuración de la integración para ver datos de análisis aquí.",
|
||||
"analysis_not_enough_data": "Aún no hay suficientes datos para el análisis.",
|
||||
"analysis_not_enough_data_hint": "El análisis Weibull requiere al menos 5 mantenimientos completados; los patrones estacionales son visibles tras 6+ puntos de datos por mes.",
|
||||
"analysis_manual_task_hint": "Las tareas manuales sin intervalo no generan datos de análisis.",
|
||||
"completions": "finalizaciones",
|
||||
"current": "Actual",
|
||||
"shorter": "Más corto",
|
||||
"longer": "Más largo",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Ventanas pasadas",
|
||||
"cal_forward_windows": "Ventanas futuras",
|
||||
"history_edit_title": "Editar entrada del historial",
|
||||
"history_delete_entry": "Eliminar entrada",
|
||||
"history_delete_confirm": "¿Eliminar esta entrada del historial? La fecha de última realización vuelve a la anterior; las fotos permanecen con el objeto y las piezas consumidas no se reponen.",
|
||||
"history_edit_timestamp": "Marca de tiempo",
|
||||
"manufacturer": "Fabricante",
|
||||
"model": "Modelo",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Días de aviso predeterminados",
|
||||
"settings_consumable_threshold": "Umbral „consumible bajo“ (%)",
|
||||
"settings_battery_low_percent": "Umbral „pila baja“ (%)",
|
||||
"settings_battery_lifetimes": "Duraciones típicas de las pilas",
|
||||
"settings_battery_lifetimes_hint": "Solo se usa para pilas sin porcentaje: una nota de Battery Notes que solo tiene tipo y fecha de sustitución, o un sensor que solo avisa de « baja ». Su vencimiento es última sustitución + esta duración. Las pilas que informan de un nivel obtienen su previsión de la descarga medida. Cuando la flota ha visto tres sustituciones de un tipo, el intervalo mediano sustituye al valor integrado — tu valor siempre gana.",
|
||||
"settings_battery_lifetime_months": "meses",
|
||||
"settings_battery_lifetime_reset": "Usar predeterminado",
|
||||
"settings_battery_lifetime_in_fleet": "en tu flota",
|
||||
"settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}",
|
||||
"settings_thresholds_hint": "Valores predeterminados para nuevas Configuraciones sugeridas (porcentaje restante) y para pilas de la flota sin umbral propio de Battery Notes. Las tareas existentes conservan su valor.",
|
||||
"settings_value_out_of_range": "El valor debe estar entre {min} y {max}",
|
||||
"bn_summary": "{name}: {pct} % para {n} pilas",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} dispositivos con umbral propio:",
|
||||
"bn_more": "+ {n} más",
|
||||
"settings_row_actions": "Acciones en las filas de tareas",
|
||||
"settings_ref_numbers_in_lists": "Números de referencia en las listas",
|
||||
"settings_ref_numbers_in_lists_hint": "Muestra las etiquetas #8 / #8.3 delante de los nombres de objetos y tareas en la lista Hoy, la tabla de tareas, las tarjetas de objetos y la tabla de objetos.",
|
||||
"row_actions_buttons_compact": "Botones (solo iconos en el móvil)",
|
||||
"row_actions_buttons": "Botones con texto",
|
||||
"row_actions_icons": "Iconos (clásico)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Duración de posposición (horas)",
|
||||
"settings_budget": "Presupuesto",
|
||||
"settings_currency": "Moneda",
|
||||
"settings_currency_decimals": "Decimales de los importes",
|
||||
"settings_currency_decimals_hint": "Cuántos decimales muestra cada importe: KPI, presupuestos, costes en listas e informes, alertas de presupuesto. 0 = números enteros.",
|
||||
"settings_budget_monthly": "Presupuesto mensual",
|
||||
"settings_budget_yearly": "Presupuesto anual",
|
||||
"settings_budget_alerts": "Alertas de presupuesto",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exportar YAML",
|
||||
"settings_export_csv": "Exportar CSV",
|
||||
"settings_export_settings": "Exportar ajustes (JSON)",
|
||||
"settings_import_csv": "Importar CSV",
|
||||
"settings_import_placeholder": "Pegue el contenido JSON o CSV aquí…",
|
||||
"settings_import_btn": "Importar",
|
||||
"settings_import_success": "{count} objetos importados correctamente.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Acción HA por tarea al completar + QR de finalización rápida con valores prefijados.",
|
||||
"on_complete_action_title": "Al completar: ejecutar acción HA (opcional)",
|
||||
"on_complete_action_desc": "Llama un servicio HA cuando se completa la tarea — p. ej. reiniciar un contador del dispositivo.",
|
||||
"on_complete_action_service": "Servicio",
|
||||
"on_complete_action_target": "Entidad objetivo",
|
||||
"on_complete_action_data": "Datos (JSON, opcional)",
|
||||
"on_complete_action_test": "Probar acción",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Elegir fotos",
|
||||
"choose_photo": "Elegir foto",
|
||||
"photos_android_hint": "La app de Android entrega una foto por selección — cada selección se añade.",
|
||||
"add_photos": "Añadir fotos",
|
||||
"photos_limit": "Hasta {max} fotos por finalización",
|
||||
"history_edit_photos_hint": "Una foto quitada aquí se conserva en los documentos del objeto.",
|
||||
"uploading": "Subiendo…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Descargar",
|
||||
"doc_close": "Cerrar",
|
||||
"doc_camera": "Tomar foto",
|
||||
"camera_capture_shoot": "Capturar",
|
||||
"camera_switch_lens": "Cambiar de cámara",
|
||||
"doc_drop_hint": "Suelta los archivos aquí",
|
||||
"doc_task_none": "Ningún documento vinculado a esta tarea.",
|
||||
"doc_link_existing": "Vincular un documento…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registra una finalización (fija la última realización) cuando el disparador se resuelve solo — p. ej., sal rellenada, filtro cambiado.",
|
||||
"doc_search": "Buscar documentos…",
|
||||
"doc_search_none": "Ningún documento coincidente",
|
||||
"doc_description": "Descripción (opcional)",
|
||||
"doc_sort": "Ordenar documentos",
|
||||
"doc_sort_newest": "Más recientes primero",
|
||||
"doc_sort_oldest": "Más antiguos primero",
|
||||
"doc_sort_title": "Por título (1, 2, 3 …)",
|
||||
"doc_sort_category": "Por categoría",
|
||||
"link_device_optional": "Vincular a dispositivo existente (opcional)",
|
||||
"parent_object_optional": "Objeto principal (opcional)",
|
||||
"parent_none": "(Sin objeto principal)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notificar solo para la vista",
|
||||
"settings_notify_scope_all": "Todas las tareas",
|
||||
"settings_notify_scope_hint": "Solo las tareas que coinciden con los filtros de etiqueta/usuario de la vista seleccionada envían recordatorios. El estado, el orden y la agrupación de la vista se ignoran aquí.",
|
||||
"settings_notify_completed": "Notificaciones de finalización",
|
||||
"settings_notify_completed_hint": "Aviso para el hogar cuando se completa una tarea, con el motivo y quién lo hizo. «Solo automáticas» anuncia solo las finalizaciones que nadie hizo en el sitio: recuperación del sensor, marcado en la lista de la compra, llamada de servicio de una automatización. Solo al servicio del hogar; se aplican las horas de silencio y el límite diario.",
|
||||
"notify_completed_off": "Desactivado",
|
||||
"notify_completed_automatic": "Solo finalizaciones automáticas",
|
||||
"notify_completed_all": "Todas las finalizaciones",
|
||||
"settings_notify_rule": "Tu propia regla de notificación",
|
||||
"settings_notify_rule_hint": "Cada notificación dispara además el evento maintenance_supporter_notification (tipo, estado, objeto, tarea, números de referencia, prioridad, enlace, carga útil). Enrútalo con una automatización — p. ej. a Ticker, Telegram o Pushover.",
|
||||
"settings_notify_event_only": "Solo disparar el evento, no enviar nada",
|
||||
"settings_notify_event_only_hint": "Tu automatización se encarga de toda la entrega. Los intervalos de repetición, las horas de silencio y el límite diario siguen decidiendo cuándo se dispara el evento.",
|
||||
"settings_notify_extra_data": "Datos de notificación adicionales (plantilla)",
|
||||
"settings_notify_extra_data_hint": "JSON o YAML, se permite Jinja; se fusiona en los data de la llamada notify (tus claves ganan). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Pruébalo con el botón Enviar prueba.",
|
||||
"settings_notify_extra_data_hint": "JSON o YAML, se permite Jinja; se fusiona en los data de la llamada notify (tus claves ganan). Variables: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Pruébalo con el botón Enviar prueba.",
|
||||
"card_saved_view": "Vista guardada",
|
||||
"card_saved_view_none": "Ninguna",
|
||||
"card_saved_view_help": "Aplica los filtros de estado, usuario y etiqueta de la vista además de los filtros anteriores. El orden y la agrupación de la vista son ajustes de visualización del panel y no se aplican en la tarjeta.",
|
||||
"panel_card_tab": "Abrir en la pestaña",
|
||||
"panel_card_tab_default": "Pestaña recordada (como en el panel)",
|
||||
"panel_card_view": "Abrir con la vista guardada",
|
||||
"panel_card_height": "Altura",
|
||||
"panel_card_height_help": "Vacío = llena la pantalla debajo de la tarjeta (vista panel). Si no, una longitud CSS como 600px u 80vh para vistas con otras tarjetas.",
|
||||
"panel_card_not_registered": "El panel de Maintenance Supporter no está disponible — ¿está configurada la integración?",
|
||||
"panel_card_load_failed": "No se pudo cargar el panel de Maintenance Supporter — recarga la página (tras una actualización: vacía la caché del navegador).",
|
||||
"doc_part_none": "No hay documentos vinculados a esta pieza.",
|
||||
"settings_templates_toggle_group": "Activar o desactivar todas las plantillas de este grupo",
|
||||
"setups_button": "Configuraciones sugeridas",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Correcta",
|
||||
"battery_fleet_status_due": "Pendiente",
|
||||
"battery_fleet_predicted_on": "Previsto hacia {date}",
|
||||
"battery_fleet_predicted_typical": "Previsto hacia el {date} — {months} meses habituales para {type} ({source})",
|
||||
"lifetime_source_default": "valor predeterminado integrado",
|
||||
"lifetime_source_table": "tabla integrada",
|
||||
"lifetime_source_learned": "aprendido de {n} sustituciones",
|
||||
"lifetime_source_override": "tu ajuste",
|
||||
"lifetime_source_learned_device": "aprendido de {n} sustituciones de este dispositivo",
|
||||
"lifetime_source_learned_model": "aprendido de {n} sustituciones en dispositivos de este modelo",
|
||||
"battery_fleet_predicted_trend": "Predicción según la tendencia de descarga de esta pila: hacia {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Recargable: se recarga en lugar de sustituirse — nunca en la lista de compra",
|
||||
"battery_fleet_sort_name": "Ordenar por nombre",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Piezas usadas",
|
||||
"print_inc_photos": "Fotos",
|
||||
"print_inc_documents": "Documentos vinculados",
|
||||
"print_inc_doc_desc": "Descripciones de los documentos",
|
||||
"print_inc_checklist": "Lista de verificación",
|
||||
"print_inc_notes": "Notas",
|
||||
"print_inc_costs": "Coste y duración",
|
||||
"print_inc_person": "Realizado por",
|
||||
"print_inc_refs": "Números de referencia",
|
||||
"print_inc_bare": "Realizaciones sin detalles",
|
||||
"print_inc_qr": "Código QR por tarea",
|
||||
"print_button": "Imprimir",
|
||||
"report_scan_hint": "Escanea para abrir la tarea",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Käyttöaika",
|
||||
"runtime_hours": "Tavoitekäyttöaika (tuntia)",
|
||||
"target_value": "Tavoitearvo",
|
||||
"baseline": "Lähtötaso",
|
||||
"target_changes": "Tavoitemuutokset",
|
||||
"for_minutes": "Vähintään (minuuttia)",
|
||||
"time_based": "Aikaperusteinen",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Luotu",
|
||||
"report_times_done": "Tehty",
|
||||
"report_total_cost": "Kokonaiskustannus",
|
||||
"report_every": "joka {n}. {unit}",
|
||||
"report_notes": "Muistiinpanot",
|
||||
"report_col_type": "Tyyppi",
|
||||
"report_col_status": "Tila",
|
||||
"report_col_schedule": "Aikataulu",
|
||||
"duration": "Kesto",
|
||||
"minutes_short": "min",
|
||||
"both": "Molemmat",
|
||||
"trigger_val": "Laukaisuarvo",
|
||||
"complete_title": "Suorita: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: väärä tyyppi (odotettiin: {type})",
|
||||
"err_invalid_choice": "{field}: ei sallittu arvo",
|
||||
"err_invalid_value": "{field}: virheellinen arvo",
|
||||
"ws_err_not_found": "Ei löytynyt — se on ehkä poistettu sillä välin",
|
||||
"ws_err_invalid_input": "Virheellinen syöte",
|
||||
"ws_err_invalid_date": "Virheellinen päivämäärä",
|
||||
"ws_err_invalid_format": "Virheellinen muoto",
|
||||
"ws_err_invalid_url": "Virheellinen linkki",
|
||||
"ws_err_not_configured": "Ei vielä määritetty",
|
||||
"ws_err_not_loaded": "Ei vielä ladattu — yritä hetken kuluttua uudelleen",
|
||||
"ws_err_limit_reached": "Raja saavutettu",
|
||||
"ws_err_too_many": "Liian monta merkintää",
|
||||
"ws_err_too_large": "Liian suuri",
|
||||
"ws_err_not_archived": "Ei arkistoitu",
|
||||
"ws_err_invalid_target": "Virheellinen kohde",
|
||||
"ws_err_create_failed": "Luominen epäonnistui",
|
||||
"ws_err_archived": "Arkistoitu — palauta ensin",
|
||||
"ws_err_already_archived": "On jo arkistoitu",
|
||||
"ws_err_unavailable": "Ei tällä hetkellä käytettävissä",
|
||||
"ws_err_unauthorized": "Ei sallittu käyttäjällesi",
|
||||
"ws_err_too_many_views": "Liian monta tallennettua näkymää",
|
||||
"ws_err_storage_unavailable": "Tallennustila ei ole käytettävissä",
|
||||
"ws_err_replace_failed": "Korvaaminen epäonnistui",
|
||||
"ws_err_not_paused": "Ei keskeytetty",
|
||||
"ws_err_not_available": "Ei mitään saatavilla",
|
||||
"ws_err_no_url": "Linkkiä ei annettu",
|
||||
"ws_err_no_phases": "Tällä tehtävällä ei ole vaiheita",
|
||||
"ws_err_invalid_view": "Virheellinen näkymä",
|
||||
"ws_err_invalid_user": "Tuntematon käyttäjä",
|
||||
"ws_err_invalid_range": "Virheellinen aikaväli",
|
||||
"ws_err_invalid_parent": "Virheellinen yläobjekti",
|
||||
"ws_err_invalid_device": "Tuntematon laite",
|
||||
"ws_err_empty_csv": "CSV-tiedosto on tyhjä",
|
||||
"ws_err_empty": "Ei mitään tuotavaa",
|
||||
"ws_err_duplicate_failed": "Monistaminen epäonnistui",
|
||||
"ws_err_already_paused": "On jo keskeytetty",
|
||||
"ws_err_invalid_cursor": "Virheellinen vaiheen sijainti",
|
||||
"ws_err_invalid_entity_slug": "Virheellinen entiteetin nimi",
|
||||
"ws_err_invalid_trigger_config": "Virheellinen laukaisimen määritys",
|
||||
"ws_err_no_defaults": "Pikavalmistumisen oletuksia ei ole asetettu",
|
||||
"ws_err_self_link_device": "Objektia ei voi linkittää itseensä",
|
||||
"ws_err_too_early": "Liian aikaista — tehtävän voi merkitä tehdyksi vasta lähempänä eräpäivää",
|
||||
"feat_schedule_time": "Kellonajan ajoitus",
|
||||
"feat_schedule_time_desc": "Tehtävät erääntyvät tiettyyn kellonaikaan keskiyön sijaan.",
|
||||
"schedule_time_toggle": "Erääntyy tiettyyn kellonaikaan",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Laukaise kun yhtä suuri kuin (=)",
|
||||
"trigger_not_equals": "Laukaise kun eri kuin (≠)",
|
||||
"for_at_least_minutes": "Vähintään (minuuttia)",
|
||||
"safety_interval_days": "Turvaväli (päivää, valinnainen)",
|
||||
"safety_interval": "Turvaväli (valinnainen)",
|
||||
"trigger_combinator": "Yhdistä laukaisin ja väli",
|
||||
"trigger_combinator_any": "Laukaisin tai väli (ensin täyttyvä)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Omat tehtävät",
|
||||
"tab_calendar": "Kalenteri",
|
||||
"cal_no_events": "Ei huoltoja",
|
||||
"cal_window_7": "7 päivää",
|
||||
"cal_window_14": "14 päivää",
|
||||
"cal_window_30": "30 päivää",
|
||||
"cal_window_365": "1 vuosi",
|
||||
"cal_every_n_days": "{n} päivän välein",
|
||||
"cal_source_time": "Aikaperusteinen",
|
||||
"cal_source_time_adaptive": "Aikaperusteinen (mukautuva)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR-koodin luonti epäonnistui.",
|
||||
"qr_error_no_url": "HA:n URL-osoitetta ei ole määritetty. Aseta ulkoinen tai sisäinen URL kohdassa Asetukset → Järjestelmä → Verkko.",
|
||||
"save_error": "Tallennus epäonnistui. Yritä uudelleen.",
|
||||
"subsave_warning": "Tallennettu, mutta yksi asetus hylättiin: {detail}",
|
||||
"qr_print": "Tulosta",
|
||||
"qr_download": "Lataa SVG",
|
||||
"qr_action": "Toiminto skannattaessa",
|
||||
"qr_action_view": "Näytä huoltotiedot",
|
||||
"qr_action_complete": "Merkitse huolto valmiiksi",
|
||||
"qr_url_mode": "Linkin tyyppi",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Paikallinen (mDNS)",
|
||||
"qr_mode_server": "Palvelimen URL",
|
||||
"overview": "Yleiskatsaus",
|
||||
"analysis": "Analyysi",
|
||||
"recent_activities": "Viimeaikaiset toimet",
|
||||
"search_notes": "Hae muistiinpanoja",
|
||||
"avg_cost": "Keskim. kustannus",
|
||||
"no_advanced_features": "Lisäominaisuuksia ei ole otettu käyttöön",
|
||||
"no_advanced_features_hint": "Ota käyttöön ”Mukautuvat välit” tai ”Kausivaihtelut” integraation asetuksissa nähdäksesi analyysitiedot täällä.",
|
||||
"analysis_not_enough_data": "Ei vielä tarpeeksi tietoja analyysiin.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analyysi vaatii vähintään 5 suoritettua huoltoa; kausivaihtelut näkyvät kun kuukautta kohden on yli 6 tietopistettä.",
|
||||
"analysis_manual_task_hint": "Manuaaliset tehtävät ilman väliä eivät tuota analyysitietoja.",
|
||||
"completions": "suoritusta",
|
||||
"current": "Nykyinen",
|
||||
"shorter": "Lyhyempi",
|
||||
"longer": "Pidempi",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Menneet jaksot",
|
||||
"cal_forward_windows": "Tulevat jaksot",
|
||||
"history_edit_title": "Muokkaa historiamerkintää",
|
||||
"history_delete_entry": "Poista merkintä",
|
||||
"history_delete_confirm": "Poistetaanko tämä historiamerkintä? Viimeisimmän suorituksen päivä palautuu edelliseen; kuvat jäävät kohteelle eikä kulutettuja osia palauteta varastoon.",
|
||||
"history_edit_timestamp": "Aikaleima",
|
||||
"manufacturer": "Valmistaja",
|
||||
"model": "Malli",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Oletusvaroituspäivät",
|
||||
"settings_consumable_threshold": "Kynnys „kulutustarvike vähissä“ (%)",
|
||||
"settings_battery_low_percent": "Kynnys „paristo vähissä“ (%)",
|
||||
"settings_battery_lifetimes": "Paristojen tyypilliset käyttöiät",
|
||||
"settings_battery_lifetimes_hint": "Käytetään vain paristoille ilman prosenttilukua: Battery Notes -merkintä, jolla on vain tyyppi ja vaihtopäivä, tai anturi, joka ilmoittaa vain ”vähissä”. Erääntyminen = viimeisin vaihto + tämä käyttöikä. Tasoa raportoivien paristojen ennuste tulee mitatusta purkautumisesta. Kun laivasto on nähnyt kolme saman tyypin vaihtoa, mediaaniväli korvaa sisäänrakennetun arvon — oma arvosi voittaa aina.",
|
||||
"settings_battery_lifetime_months": "kuukautta",
|
||||
"settings_battery_lifetime_reset": "Käytä oletusta",
|
||||
"settings_battery_lifetime_in_fleet": "laivastossasi",
|
||||
"settings_battery_lifetime_learned_models": "Opittu mallikohtaisesti: {list}",
|
||||
"settings_thresholds_hint": "Oletukset uusille Ehdotetuille asetuksille (jäljellä oleva prosentti) ja paristokannan paristoille ilman omaa Battery Notes -kynnystä. Olemassa olevat tehtävät säilyttävät arvonsa.",
|
||||
"settings_value_out_of_range": "Arvon on oltava välillä {min}–{max}",
|
||||
"bn_summary": "{name}: {pct} % {n} paristolle",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} laitetta, joilla on oma kynnys:",
|
||||
"bn_more": "+ {n} lisää",
|
||||
"settings_row_actions": "Tehtävärivien toiminnot",
|
||||
"settings_ref_numbers_in_lists": "Viitenumerot luetteloissa",
|
||||
"settings_ref_numbers_in_lists_hint": "Näyttää tunnisteet #8 / #8.3 kohteiden ja tehtävien nimien edessä Tänään-luettelossa, tehtävätaulukossa, kohdekorteissa ja kohdetaulukossa.",
|
||||
"row_actions_buttons_compact": "Painikkeet (puhelimessa vain kuvakkeet)",
|
||||
"row_actions_buttons": "Painikkeet tekstillä",
|
||||
"row_actions_icons": "Kuvakkeet (perinteinen)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Torkun kesto (tuntia)",
|
||||
"settings_budget": "Budjetti",
|
||||
"settings_currency": "Valuutta",
|
||||
"settings_currency_decimals": "Summien desimaalit",
|
||||
"settings_currency_decimals_hint": "Montako desimaalia jokainen summa näyttää: tunnusluvut, budjetit, kustannukset listoissa ja raporteissa, budjettihälytykset. 0 = kokonaisluvut.",
|
||||
"settings_budget_monthly": "Kuukausibudjetti",
|
||||
"settings_budget_yearly": "Vuosibudjetti",
|
||||
"settings_budget_alerts": "Budjettihälytykset",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Vie YAML",
|
||||
"settings_export_csv": "Vie CSV",
|
||||
"settings_export_settings": "Vie asetukset (JSON)",
|
||||
"settings_import_csv": "Tuo CSV",
|
||||
"settings_import_placeholder": "Liitä JSON- tai CSV-sisältö tähän…",
|
||||
"settings_import_btn": "Tuo",
|
||||
"settings_import_success": "{count} kohdetta tuotu onnistuneesti.",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "Tehtäväkohtainen HA-toiminto suorituksen yhteydessä + pikasuoritus-QR esiasetetuilla arvoilla.",
|
||||
"on_complete_action_title": "Suorituksen yhteydessä: laukaise HA-toiminto (valinnainen)",
|
||||
"on_complete_action_desc": "Kutsuu HA-palvelua, kun tehtävä suoritetaan — esim. nollaa laitteen laskuri.",
|
||||
"on_complete_action_service": "Palvelu",
|
||||
"on_complete_action_target": "Kohde-entiteetti",
|
||||
"on_complete_action_target_hint": "Huom: entiteetin toimialueen on vastattava palvelua — esim. 'button.press' toimii vain button.*-entiteeteillä, 'counter.increment' vain counter.*-entiteeteillä, 'input_button.press' vain input_button.*-entiteeteillä jne. Epäsopivuuden sattuessa toiminto epäonnistuu hiljaisesti (HA kirjaa 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Tiedot (JSON, valinnainen)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Valitse kuvat",
|
||||
"choose_photo": "Valitse kuva",
|
||||
"photos_android_hint": "Android-sovellus välittää yhden kuvan per valinta — jokainen valinta lisätään.",
|
||||
"add_photos": "Lisää kuvia",
|
||||
"photos_limit": "Enintään {max} kuvaa suoritusta kohti",
|
||||
"history_edit_photos_hint": "Tästä poistettu kuva säilyy kohteen asiakirjoissa.",
|
||||
"uploading": "Ladataan…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Lataa",
|
||||
"doc_close": "Sulje",
|
||||
"doc_camera": "Ota kuva",
|
||||
"camera_capture_shoot": "Ota kuva",
|
||||
"camera_switch_lens": "Vaihda kameraa",
|
||||
"doc_drop_hint": "Pudota tiedostot tähän",
|
||||
"doc_task_none": "Tähän tehtävään ei ole liitetty asiakirjoja.",
|
||||
"doc_link_existing": "Liitä asiakirja…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Kirjaa suorituksen (asettaa viimeksi tehdyn), kun laukaisin poistuu itsestään — esim. suola lisätty, suodatin vaihdettu.",
|
||||
"doc_search": "Hae asiakirjoja…",
|
||||
"doc_search_none": "Ei vastaavia asiakirjoja",
|
||||
"doc_description": "Kuvaus (valinnainen)",
|
||||
"doc_sort": "Lajittele asiakirjat",
|
||||
"doc_sort_newest": "Uusimmat ensin",
|
||||
"doc_sort_oldest": "Vanhimmat ensin",
|
||||
"doc_sort_title": "Otsikon mukaan (1, 2, 3 …)",
|
||||
"doc_sort_category": "Luokan mukaan",
|
||||
"link_device_optional": "Liitä olemassa olevaan laitteeseen (valinnainen)",
|
||||
"parent_object_optional": "Yläobjekti (valinnainen)",
|
||||
"parent_none": "(Ei yläobjektia)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Ilmoita vain näkymälle",
|
||||
"settings_notify_scope_all": "Kaikki tehtävät",
|
||||
"settings_notify_scope_hint": "Vain valitun näkymän tunniste-/käyttäjäsuodattimiin osuvat tehtävät lähettävät muistutuksia. Näkymän tila, lajittelu ja ryhmittely ohitetaan tässä.",
|
||||
"settings_notify_completed": "Ilmoitukset suorituksista",
|
||||
"settings_notify_completed_hint": "Tieto taloudelle, kun tehtävä on suoritettu, syyn ja tekijän kera. ”Vain automaattiset” ilmoittaa vain suoritukset, joita kukaan ei tehnyt paikan päällä: anturin palautuminen, ostoslistan rastitus, automaation palvelukutsu. Vain talouden palveluun; hiljaiset tunnit ja päiväraja pätevät.",
|
||||
"notify_completed_off": "Pois",
|
||||
"notify_completed_automatic": "Vain automaattiset suoritukset",
|
||||
"notify_completed_all": "Kaikki suoritukset",
|
||||
"settings_notify_rule": "Oma ilmoitussääntö",
|
||||
"settings_notify_rule_hint": "Jokainen ilmoitus laukaisee myös tapahtuman maintenance_supporter_notification (laji, tila, kohde, tehtävä, viitenumerot, prioriteetti, linkki, sisältö). Ohjaa se automaatiolla — esim. Tickeriin, Telegramiin tai Pushoveriin.",
|
||||
"settings_notify_event_only": "Laukaise vain tapahtuma, älä lähetä itse mitään",
|
||||
"settings_notify_event_only_hint": "Automaatiosi hoitaa koko toimituksen. Toistovälit, hiljaiset tunnit ja päiväraja määräävät yhä, milloin tapahtuma laukeaa.",
|
||||
"settings_notify_extra_data": "Ilmoituksen lisätiedot (malli)",
|
||||
"settings_notify_extra_data_hint": "JSON tai YAML, Jinja sallittu; yhdistetään notify-kutsun data-kenttään (omat avaimesi voittavat). Muuttujat: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Kokeile Lähetä testi -painikkeella.",
|
||||
"settings_notify_extra_data_hint": "JSON tai YAML, Jinja sallittu; yhdistetään notify-kutsun data-kenttään (omat avaimesi voittavat). Muuttujat: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Kokeile Lähetä testi -painikkeella.",
|
||||
"card_saved_view": "Tallennettu näkymä",
|
||||
"card_saved_view_none": "Ei mitään",
|
||||
"card_saved_view_help": "Soveltaa näkymän tila-, käyttäjä- ja tunnistesuodattimet yllä olevien suodattimien lisäksi. Näkymän lajittelu ja ryhmittely ovat paneelin näyttöasetuksia eivätkä vaikuta korttiin.",
|
||||
"panel_card_tab": "Avaa välilehdellä",
|
||||
"panel_card_tab_default": "Muistettu välilehti (kuten paneelissa)",
|
||||
"panel_card_view": "Avaa tallennetulla näkymällä",
|
||||
"panel_card_height": "Korkeus",
|
||||
"panel_card_height_help": "Tyhjä = täyttää näytön kortin alapuolella (paneelinäkymä). Muuten CSS-pituus, kuten 600px tai 80vh, näkymille, joissa on muita kortteja.",
|
||||
"panel_card_not_registered": "Maintenance Supporter -paneeli ei ole käytettävissä — onko integraatio määritetty?",
|
||||
"panel_card_load_failed": "Maintenance Supporter -paneelia ei voitu ladata — lataa sivu uudelleen (päivityksen jälkeen: tyhjennä selaimen välimuisti).",
|
||||
"doc_part_none": "Tähän osaan ei ole linkitetty asiakirjoja.",
|
||||
"settings_templates_toggle_group": "Ota kaikki tämän ryhmän mallit käyttöön tai pois",
|
||||
"setups_button": "Ehdotetut asennukset",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Kunnossa",
|
||||
"battery_fleet_status_due": "Erääntynyt",
|
||||
"battery_fleet_predicted_on": "Odotettavissa noin {date}",
|
||||
"battery_fleet_predicted_typical": "Odotettavissa noin {date} — {months} kuukautta tyypillisesti tyypille {type} ({source})",
|
||||
"lifetime_source_default": "sisäänrakennettu oletus",
|
||||
"lifetime_source_table": "sisäänrakennettu taulukko",
|
||||
"lifetime_source_learned": "opittu {n} vaihdosta",
|
||||
"lifetime_source_override": "oma asetuksesi",
|
||||
"lifetime_source_learned_device": "opittu tämän laitteen {n} vaihdosta",
|
||||
"lifetime_source_learned_model": "opittu tämän mallin laitteiden {n} vaihdosta",
|
||||
"battery_fleet_predicted_trend": "Ennustettu tämän pariston purkautumistrendistä: noin {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Ladattava: lataa vaihtamisen sijaan — ei koskaan ostoslistalle",
|
||||
"battery_fleet_sort_name": "Lajittele nimen mukaan",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Käytetyt osat",
|
||||
"print_inc_photos": "Kuvat",
|
||||
"print_inc_documents": "Liitetyt asiakirjat",
|
||||
"print_inc_doc_desc": "Asiakirjojen kuvaukset",
|
||||
"print_inc_checklist": "Tarkistuslista",
|
||||
"print_inc_notes": "Muistiinpanot",
|
||||
"print_inc_costs": "Kustannus ja kesto",
|
||||
"print_inc_person": "Suorittaja",
|
||||
"print_inc_refs": "Viitenumerot",
|
||||
"print_inc_bare": "Suoritukset ilman tietoja",
|
||||
"print_inc_qr": "QR-koodi tehtävää kohti",
|
||||
"print_button": "Tulosta",
|
||||
"report_scan_hint": "Skannaa avataksesi tehtävän",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Durée de fonctionnement",
|
||||
"runtime_hours": "Durée cible (heures)",
|
||||
"target_value": "Valeur cible",
|
||||
"baseline": "Ligne de base",
|
||||
"target_changes": "Changements cibles",
|
||||
"for_minutes": "Pendant (minutes)",
|
||||
"time_based": "Temporel",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Généré",
|
||||
"report_times_done": "Effectué",
|
||||
"report_total_cost": "Coût total",
|
||||
"report_every": "tous les {n} {unit}",
|
||||
"report_notes": "Notes",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Statut",
|
||||
"report_col_schedule": "Planning",
|
||||
"duration": "Durée",
|
||||
"minutes_short": "min",
|
||||
"both": "Les deux",
|
||||
"trigger_val": "Valeur du déclencheur",
|
||||
"complete_title": "Terminé : ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field} : mauvais type (attendu : {type})",
|
||||
"err_invalid_choice": "{field} : valeur non autorisée",
|
||||
"err_invalid_value": "{field} : valeur invalide",
|
||||
"ws_err_not_found": "Introuvable — il a peut-être été supprimé entre-temps",
|
||||
"ws_err_invalid_input": "Saisie non valide",
|
||||
"ws_err_invalid_date": "Date non valide",
|
||||
"ws_err_invalid_format": "Format non valide",
|
||||
"ws_err_invalid_url": "Lien non valide",
|
||||
"ws_err_not_configured": "Pas encore configuré",
|
||||
"ws_err_not_loaded": "Pas encore chargé — réessayez dans un instant",
|
||||
"ws_err_limit_reached": "Limite atteinte",
|
||||
"ws_err_too_many": "Trop d'entrées",
|
||||
"ws_err_too_large": "Trop volumineux",
|
||||
"ws_err_not_archived": "Non archivé",
|
||||
"ws_err_invalid_target": "Cible non valide",
|
||||
"ws_err_create_failed": "Impossible de créer",
|
||||
"ws_err_archived": "Archivé — restaurez-le d'abord",
|
||||
"ws_err_already_archived": "Déjà archivé",
|
||||
"ws_err_unavailable": "Actuellement indisponible",
|
||||
"ws_err_unauthorized": "Non autorisé pour votre utilisateur",
|
||||
"ws_err_too_many_views": "Trop de vues enregistrées",
|
||||
"ws_err_storage_unavailable": "Le stockage n'est pas disponible",
|
||||
"ws_err_replace_failed": "Échec du remplacement",
|
||||
"ws_err_not_paused": "Pas en pause",
|
||||
"ws_err_not_available": "Rien de disponible",
|
||||
"ws_err_no_url": "Aucun lien indiqué",
|
||||
"ws_err_no_phases": "Cette tâche n'a pas de phases",
|
||||
"ws_err_invalid_view": "Vue non valide",
|
||||
"ws_err_invalid_user": "Utilisateur inconnu",
|
||||
"ws_err_invalid_range": "Plage non valide",
|
||||
"ws_err_invalid_parent": "Objet parent non valide",
|
||||
"ws_err_invalid_device": "Appareil inconnu",
|
||||
"ws_err_empty_csv": "Le fichier CSV est vide",
|
||||
"ws_err_empty": "Rien à importer",
|
||||
"ws_err_duplicate_failed": "Impossible de dupliquer",
|
||||
"ws_err_already_paused": "Déjà en pause",
|
||||
"ws_err_invalid_cursor": "Position de phase non valide",
|
||||
"ws_err_invalid_entity_slug": "Nom d'entité non valide",
|
||||
"ws_err_invalid_trigger_config": "Configuration du déclencheur non valide",
|
||||
"ws_err_no_defaults": "Aucune valeur par défaut pour la validation rapide",
|
||||
"ws_err_self_link_device": "Un objet ne peut pas être lié à lui-même",
|
||||
"ws_err_too_early": "Trop tôt — cette tâche ne peut être terminée que plus près de son échéance",
|
||||
"feat_schedule_time": "Planification à l'heure",
|
||||
"feat_schedule_time_desc": "Les tâches arrivent à échéance à une heure précise plutôt qu'à minuit.",
|
||||
"schedule_time_toggle": "Échéance à une heure précise",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Déclencher si égal à (=)",
|
||||
"trigger_not_equals": "Déclencher si différent de (≠)",
|
||||
"for_at_least_minutes": "Pendant au moins (minutes)",
|
||||
"safety_interval_days": "Intervalle de sécurité (jours, optionnel)",
|
||||
"safety_interval": "Intervalle de sécurité (optionnel)",
|
||||
"trigger_combinator": "Combiner déclencheur et intervalle",
|
||||
"trigger_combinator_any": "Déclencheur ou intervalle (premier atteint)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Mes tâches",
|
||||
"tab_calendar": "Calendrier",
|
||||
"cal_no_events": "Aucun entretien",
|
||||
"cal_window_7": "7 jours",
|
||||
"cal_window_14": "14 jours",
|
||||
"cal_window_30": "30 jours",
|
||||
"cal_window_365": "1 an",
|
||||
"cal_every_n_days": "tous les {n} jours",
|
||||
"cal_source_time": "Basé sur le temps",
|
||||
"cal_source_time_adaptive": "Basé sur le temps (adaptatif)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Impossible de générer le QR code.",
|
||||
"qr_error_no_url": "Aucune URL HA configurée. Veuillez définir une URL externe ou interne dans Paramètres → Système → Réseau.",
|
||||
"save_error": "Échec de l'enregistrement. Veuillez réessayer.",
|
||||
"subsave_warning": "Enregistré, mais un réglage a été refusé : {detail}",
|
||||
"qr_print": "Imprimer",
|
||||
"qr_download": "Télécharger SVG",
|
||||
"qr_action": "Action au scan",
|
||||
"qr_action_view": "Afficher les infos de maintenance",
|
||||
"qr_action_complete": "Marquer la maintenance comme terminée",
|
||||
"qr_url_mode": "Type de lien",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "URL serveur",
|
||||
"overview": "Aperçu",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Activités récentes",
|
||||
"search_notes": "Rechercher dans les notes",
|
||||
"avg_cost": "Ø Coût",
|
||||
"no_advanced_features": "Aucune fonction avancée activée",
|
||||
"no_advanced_features_hint": "Activez « Intervalles adaptatifs » ou « Tendances saisonnières » dans les paramètres de l'intégration pour voir les données d'analyse ici.",
|
||||
"analysis_not_enough_data": "Pas encore assez de données pour l'analyse.",
|
||||
"analysis_not_enough_data_hint": "L'analyse Weibull nécessite au moins 5 maintenances terminées ; les tendances saisonnières apparaissent après 6+ points par mois.",
|
||||
"analysis_manual_task_hint": "Les tâches manuelles sans intervalle ne génèrent pas de données d'analyse.",
|
||||
"completions": "réalisations",
|
||||
"current": "Actuel",
|
||||
"shorter": "Plus court",
|
||||
"longer": "Plus long",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Fenêtres passées",
|
||||
"cal_forward_windows": "Fenêtres à venir",
|
||||
"history_edit_title": "Modifier l'entrée d'historique",
|
||||
"history_delete_entry": "Supprimer l'entrée",
|
||||
"history_delete_confirm": "Supprimer cette entrée d'historique ? La date de dernière réalisation revient à la précédente ; les photos restent avec l'objet et les pièces consommées ne sont pas remises en stock.",
|
||||
"history_edit_timestamp": "Horodatage",
|
||||
"manufacturer": "Fabricant",
|
||||
"model": "Modèle",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Jours d'avertissement par défaut",
|
||||
"settings_consumable_threshold": "Seuil « consommable bas » (%)",
|
||||
"settings_battery_low_percent": "Seuil « pile faible » (%)",
|
||||
"settings_battery_lifetimes": "Durées de vie typiques des piles",
|
||||
"settings_battery_lifetimes_hint": "Utilisé uniquement pour les piles sans pourcentage : une note Battery Notes qui ne connaît qu'un type et une date de remplacement, ou un capteur qui ne signale que « faible ». Leur échéance est dernier remplacement + cette durée. Les piles qui remontent un niveau sont prévues d'après la décharge mesurée. Dès que le parc a vu trois remplacements d'un type, l'intervalle médian remplace la valeur intégrée — votre valeur l'emporte toujours.",
|
||||
"settings_battery_lifetime_months": "mois",
|
||||
"settings_battery_lifetime_reset": "Valeur par défaut",
|
||||
"settings_battery_lifetime_in_fleet": "dans votre parc",
|
||||
"settings_battery_lifetime_learned_models": "Appris par modèle : {list}",
|
||||
"settings_thresholds_hint": "Valeurs par défaut pour les nouvelles Configurations suggérées (pourcentage restant) et pour les piles de la flotte sans seuil Battery Notes propre. Les tâches existantes gardent leur valeur.",
|
||||
"settings_value_out_of_range": "La valeur doit être comprise entre {min} et {max}",
|
||||
"bn_summary": "{name} : {pct} % pour {n} piles",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} appareils avec leur propre seuil :",
|
||||
"bn_more": "+ {n} autres",
|
||||
"settings_row_actions": "Actions des lignes de tâches",
|
||||
"settings_ref_numbers_in_lists": "Numéros de référence dans les listes",
|
||||
"settings_ref_numbers_in_lists_hint": "Affiche les puces #8 / #8.3 devant les noms d'objets et de tâches dans la liste Aujourd'hui, le tableau des tâches, les cartes d'objets et le tableau des objets.",
|
||||
"row_actions_buttons_compact": "Boutons (icônes seules sur téléphone)",
|
||||
"row_actions_buttons": "Boutons avec texte",
|
||||
"row_actions_icons": "Icônes (classique)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Durée de report (heures)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Devise",
|
||||
"settings_currency_decimals": "Décimales des montants",
|
||||
"settings_currency_decimals_hint": "Nombre de décimales affichées pour chaque montant : indicateurs, budgets, coûts dans les listes et rapports, alertes budget. 0 = nombres entiers.",
|
||||
"settings_budget_monthly": "Budget mensuel",
|
||||
"settings_budget_yearly": "Budget annuel",
|
||||
"settings_budget_alerts": "Alertes budgétaires",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exporter YAML",
|
||||
"settings_export_csv": "Exporter CSV",
|
||||
"settings_export_settings": "Exporter les réglages (JSON)",
|
||||
"settings_import_csv": "Importer CSV",
|
||||
"settings_import_placeholder": "Collez le contenu JSON ou CSV ici…",
|
||||
"settings_import_btn": "Importer",
|
||||
"settings_import_success": "{count} objets importés avec succès.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Action HA par tâche lors de la finalisation + QR de finalisation rapide avec valeurs pré-définies.",
|
||||
"on_complete_action_title": "À la finalisation : déclencher une action HA (optionnel)",
|
||||
"on_complete_action_desc": "Appelle un service HA quand la tâche est terminée — p. ex. réinitialiser un compteur sur l'appareil.",
|
||||
"on_complete_action_service": "Service",
|
||||
"on_complete_action_target": "Entité cible",
|
||||
"on_complete_action_data": "Données (JSON, optionnel)",
|
||||
"on_complete_action_test": "Tester l'action",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Choisir des photos",
|
||||
"choose_photo": "Choisir une photo",
|
||||
"photos_android_hint": "L'application Android ne transmet qu'une photo par sélection — chaque sélection est ajoutée.",
|
||||
"add_photos": "Ajouter des photos",
|
||||
"photos_limit": "Jusqu'à {max} photos par fin de tâche",
|
||||
"history_edit_photos_hint": "Une photo retirée ici reste dans les documents de l'objet.",
|
||||
"uploading": "Téléversement…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Télécharger",
|
||||
"doc_close": "Fermer",
|
||||
"doc_camera": "Prendre une photo",
|
||||
"camera_capture_shoot": "Prendre la photo",
|
||||
"camera_switch_lens": "Changer de caméra",
|
||||
"doc_drop_hint": "Déposez les fichiers ici",
|
||||
"doc_task_none": "Aucun document lié à cette tâche.",
|
||||
"doc_link_existing": "Lier un document…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Enregistre une exécution (met à jour la dernière réalisation) lorsque le déclencheur se résout de lui-même — p. ex. sel rechargé, filtre remplacé.",
|
||||
"doc_search": "Rechercher des documents…",
|
||||
"doc_search_none": "Aucun document correspondant",
|
||||
"doc_description": "Description (facultatif)",
|
||||
"doc_sort": "Trier les documents",
|
||||
"doc_sort_newest": "Plus récents d'abord",
|
||||
"doc_sort_oldest": "Plus anciens d'abord",
|
||||
"doc_sort_title": "Par titre (1, 2, 3 …)",
|
||||
"doc_sort_category": "Par catégorie",
|
||||
"link_device_optional": "Lier à un appareil existant (optionnel)",
|
||||
"parent_object_optional": "Objet parent (optionnel)",
|
||||
"parent_none": "(Aucun parent)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notifier uniquement pour la vue",
|
||||
"settings_notify_scope_all": "Toutes les tâches",
|
||||
"settings_notify_scope_hint": "Seules les tâches correspondant aux filtres étiquette/utilisateur de la vue choisie envoient des rappels. Le statut, le tri et le regroupement de la vue sont ignorés ici.",
|
||||
"settings_notify_completed": "Notifications d'achèvement",
|
||||
"settings_notify_completed_hint": "Une info pour le foyer quand une tâche est terminée, avec la raison et l'auteur. « Automatiques seulement » n'annonce que les achèvements que personne n'a faits sur place : retour du capteur, case cochée dans la liste de courses, appel de service d'une automatisation. Envoyé au service du foyer seulement ; heures calmes et plafond quotidien s'appliquent.",
|
||||
"notify_completed_off": "Désactivé",
|
||||
"notify_completed_automatic": "Achèvements automatiques seulement",
|
||||
"notify_completed_all": "Tous les achèvements",
|
||||
"settings_notify_rule": "Votre propre règle de notification",
|
||||
"settings_notify_rule_hint": "Chaque notification déclenche aussi l'événement maintenance_supporter_notification (type, statut, objet, tâche, numéros de référence, priorité, lien, charge utile). Routez-le avec une automatisation — vers Ticker, Telegram ou Pushover par exemple.",
|
||||
"settings_notify_event_only": "Déclencher seulement l'événement, ne rien envoyer",
|
||||
"settings_notify_event_only_hint": "Votre automatisation assure toute la livraison. Intervalles de répétition, heures calmes et plafond quotidien s'appliquent toujours au déclenchement de l'événement.",
|
||||
"settings_notify_extra_data": "Données de notification supplémentaires (modèle)",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja autorisé ; fusionné dans les data de l'appel notify (vos clés l'emportent). Variables : kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Vérifiez avec le bouton Envoyer un test.",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja autorisé ; fusionné dans les data de l'appel notify (vos clés l'emportent). Variables : kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Vérifiez avec le bouton Envoyer un test.",
|
||||
"card_saved_view": "Vue enregistrée",
|
||||
"card_saved_view_none": "Aucune",
|
||||
"card_saved_view_help": "Applique les filtres statut, utilisateur et étiquette de la vue en plus des filtres ci-dessus. Le tri et le regroupement de la vue sont des réglages d'affichage du panneau et ne s'appliquent pas à la carte.",
|
||||
"panel_card_tab": "Ouvrir sur l'onglet",
|
||||
"panel_card_tab_default": "Onglet mémorisé (comme dans le panneau)",
|
||||
"panel_card_view": "Ouvrir avec la vue enregistrée",
|
||||
"panel_card_height": "Hauteur",
|
||||
"panel_card_height_help": "Vide = remplit l'écran sous la carte (vue panneau). Sinon une longueur CSS comme 600px ou 80vh pour les vues avec d'autres cartes.",
|
||||
"panel_card_not_registered": "Le panneau Maintenance Supporter n'est pas disponible — l'intégration est-elle configurée ?",
|
||||
"panel_card_load_failed": "Le panneau Maintenance Supporter n'a pas pu être chargé — rechargez la page (après une mise à jour : videz le cache du navigateur).",
|
||||
"doc_part_none": "Aucun document lié à cette pièce.",
|
||||
"settings_templates_toggle_group": "Activer ou désactiver tous les modèles de ce groupe",
|
||||
"setups_button": "Configurations suggérées",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Bon état",
|
||||
"battery_fleet_status_due": "À remplacer",
|
||||
"battery_fleet_predicted_on": "Prévu vers {date}",
|
||||
"battery_fleet_predicted_typical": "Prévu vers le {date} — {months} mois typiques pour {type} ({source})",
|
||||
"lifetime_source_default": "valeur par défaut intégrée",
|
||||
"lifetime_source_table": "table intégrée",
|
||||
"lifetime_source_learned": "appris de {n} remplacements",
|
||||
"lifetime_source_override": "votre réglage",
|
||||
"lifetime_source_learned_device": "appris des {n} remplacements de cet appareil",
|
||||
"lifetime_source_learned_model": "appris de {n} remplacements sur des appareils de ce modèle",
|
||||
"battery_fleet_predicted_trend": "Prédit à partir de la tendance de décharge de cette pile : vers {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Rechargeable : à recharger plutôt qu'à remplacer — jamais sur la liste d'achats",
|
||||
"battery_fleet_sort_name": "Trier par nom",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Pièces utilisées",
|
||||
"print_inc_photos": "Photographies",
|
||||
"print_inc_documents": "Documents liés",
|
||||
"print_inc_doc_desc": "Descriptions des documents",
|
||||
"print_inc_checklist": "Liste de contrôle",
|
||||
"print_inc_notes": "Remarques",
|
||||
"print_inc_costs": "Coût et durée",
|
||||
"print_inc_person": "Réalisé par",
|
||||
"print_inc_refs": "Numéros de référence",
|
||||
"print_inc_bare": "Réalisations sans détails",
|
||||
"print_inc_qr": "QR code par tâche",
|
||||
"print_button": "Imprimer",
|
||||
"report_scan_hint": "Scanner pour ouvrir la tâche",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "रनटाइम",
|
||||
"runtime_hours": "लक्ष्य रनटाइम (घंटे)",
|
||||
"target_value": "लक्ष्य मान",
|
||||
"baseline": "आधार रेखा",
|
||||
"target_changes": "लक्ष्य परिवर्तन",
|
||||
"for_minutes": "के लिए (मिनट)",
|
||||
"time_based": "समय-आधारित",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "जनरेट किया गया",
|
||||
"report_times_done": "पूर्ण",
|
||||
"report_total_cost": "कुल लागत",
|
||||
"report_every": "हर {n} {unit}",
|
||||
"report_notes": "नोट्स",
|
||||
"report_col_type": "प्रकार",
|
||||
"report_col_status": "स्थिति",
|
||||
"report_col_schedule": "शेड्यूल",
|
||||
"duration": "अवधि",
|
||||
"minutes_short": "मिनट",
|
||||
"both": "दोनों",
|
||||
"trigger_val": "ट्रिगर मान",
|
||||
"complete_title": "पूर्ण करें: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: गलत प्रकार (अपेक्षित: {type})",
|
||||
"err_invalid_choice": "{field}: अनुमत मान नहीं है",
|
||||
"err_invalid_value": "{field}: अमान्य मान",
|
||||
"ws_err_not_found": "नहीं मिला — शायद इस बीच हटा दिया गया है",
|
||||
"ws_err_invalid_input": "अमान्य इनपुट",
|
||||
"ws_err_invalid_date": "अमान्य तारीख",
|
||||
"ws_err_invalid_format": "अमान्य प्रारूप",
|
||||
"ws_err_invalid_url": "अमान्य लिंक",
|
||||
"ws_err_not_configured": "अभी सेट नहीं किया गया",
|
||||
"ws_err_not_loaded": "अभी लोड नहीं हुआ — कुछ देर बाद फिर से कोशिश करें",
|
||||
"ws_err_limit_reached": "सीमा पूरी हो गई",
|
||||
"ws_err_too_many": "बहुत अधिक प्रविष्टियाँ",
|
||||
"ws_err_too_large": "बहुत बड़ा",
|
||||
"ws_err_not_archived": "संग्रहीत नहीं",
|
||||
"ws_err_invalid_target": "अमान्य लक्ष्य",
|
||||
"ws_err_create_failed": "बनाया नहीं जा सका",
|
||||
"ws_err_archived": "संग्रहीत — पहले पुनर्स्थापित करें",
|
||||
"ws_err_already_archived": "पहले से संग्रहीत",
|
||||
"ws_err_unavailable": "फ़िलहाल उपलब्ध नहीं",
|
||||
"ws_err_unauthorized": "आपके उपयोगकर्ता के लिए अनुमति नहीं",
|
||||
"ws_err_too_many_views": "बहुत अधिक सहेजे गए दृश्य",
|
||||
"ws_err_storage_unavailable": "स्टोरेज उपलब्ध नहीं है",
|
||||
"ws_err_replace_failed": "बदलना विफल रहा",
|
||||
"ws_err_not_paused": "रोका नहीं गया",
|
||||
"ws_err_not_available": "कुछ उपलब्ध नहीं",
|
||||
"ws_err_no_url": "कोई लिंक नहीं दिया गया",
|
||||
"ws_err_no_phases": "इस कार्य में कोई चरण नहीं है",
|
||||
"ws_err_invalid_view": "अमान्य दृश्य",
|
||||
"ws_err_invalid_user": "अज्ञात उपयोगकर्ता",
|
||||
"ws_err_invalid_range": "अमान्य सीमा",
|
||||
"ws_err_invalid_parent": "अमान्य मूल ऑब्जेक्ट",
|
||||
"ws_err_invalid_device": "अज्ञात डिवाइस",
|
||||
"ws_err_empty_csv": "CSV फ़ाइल खाली है",
|
||||
"ws_err_empty": "आयात करने के लिए कुछ नहीं",
|
||||
"ws_err_duplicate_failed": "डुप्लिकेट नहीं किया जा सका",
|
||||
"ws_err_already_paused": "पहले से रोका गया",
|
||||
"ws_err_invalid_cursor": "अमान्य चरण स्थिति",
|
||||
"ws_err_invalid_entity_slug": "अमान्य एंटिटी नाम",
|
||||
"ws_err_invalid_trigger_config": "अमान्य ट्रिगर कॉन्फ़िगरेशन",
|
||||
"ws_err_no_defaults": "त्वरित पूर्णता के लिए डिफ़ॉल्ट सेट नहीं",
|
||||
"ws_err_self_link_device": "कोई ऑब्जेक्ट स्वयं से लिंक नहीं हो सकता",
|
||||
"ws_err_too_early": "बहुत जल्दी — यह कार्य केवल नियत तारीख के करीब ही पूरा किया जा सकता है",
|
||||
"feat_schedule_time": "दिन का समय निर्धारण",
|
||||
"feat_schedule_time_desc": "कार्य मध्यरात्रि के बजाय दिन के एक विशिष्ट समय पर अतिदेय हो जाते हैं।",
|
||||
"schedule_time_toggle": "किसी निश्चित समय पर देय",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "बराबर होने पर ट्रिगर करें (=)",
|
||||
"trigger_not_equals": "भिन्न होने पर ट्रिगर करें (≠)",
|
||||
"for_at_least_minutes": "कम से कम (मिनट)",
|
||||
"safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)",
|
||||
"safety_interval": "सुरक्षा अंतराल (वैकल्पिक)",
|
||||
"trigger_combinator": "ट्रिगर और अंतराल संयोजित करें",
|
||||
"trigger_combinator_any": "ट्रिगर या अंतराल (जो पहले हो)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "मेरे कार्य",
|
||||
"tab_calendar": "कैलेंडर",
|
||||
"cal_no_events": "कोई रखरखाव नहीं",
|
||||
"cal_window_7": "7 दिन",
|
||||
"cal_window_14": "14 दिन",
|
||||
"cal_window_30": "30 दिन",
|
||||
"cal_window_365": "1 वर्ष",
|
||||
"cal_every_n_days": "हर {n} दिन",
|
||||
"cal_source_time": "समय-आधारित",
|
||||
"cal_source_time_adaptive": "समय-आधारित (अनुकूली)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR कोड बनाने में विफल।",
|
||||
"qr_error_no_url": "कोई HA URL कॉन्फ़िगर नहीं है। कृपया सेटिंग्स → सिस्टम → नेटवर्क में एक बाहरी या आंतरिक URL सेट करें।",
|
||||
"save_error": "सहेजने में विफल। कृपया पुनः प्रयास करें।",
|
||||
"subsave_warning": "सहेजा गया, लेकिन एक सेटिंग अस्वीकृत हुई: {detail}",
|
||||
"qr_print": "प्रिंट करें",
|
||||
"qr_download": "SVG डाउनलोड करें",
|
||||
"qr_action": "स्कैन पर क्रिया",
|
||||
"qr_action_view": "रखरखाव जानकारी देखें",
|
||||
"qr_action_complete": "रखरखाव को पूर्ण के रूप में चिह्नित करें",
|
||||
"qr_url_mode": "लिंक प्रकार",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "लोकल (mDNS)",
|
||||
"qr_mode_server": "सर्वर URL",
|
||||
"overview": "अवलोकन",
|
||||
"analysis": "विश्लेषण",
|
||||
"recent_activities": "हाल की गतिविधियाँ",
|
||||
"search_notes": "टिप्पणियाँ खोजें",
|
||||
"avg_cost": "औसत लागत",
|
||||
"no_advanced_features": "कोई उन्नत सुविधा सक्षम नहीं",
|
||||
"no_advanced_features_hint": "यहाँ विश्लेषण डेटा देखने के लिए इंटीग्रेशन सेटिंग्स में “अनुकूली अंतराल” या “मौसमी पैटर्न” सक्षम करें।",
|
||||
"analysis_not_enough_data": "अभी विश्लेषण के लिए पर्याप्त डेटा नहीं।",
|
||||
"analysis_not_enough_data_hint": "वीबुल विश्लेषण के लिए कम से कम 5 पूर्ण रखरखाव आवश्यक हैं; मौसमी पैटर्न प्रति माह 6+ डेटा बिंदुओं के बाद दिखाई देते हैं।",
|
||||
"analysis_manual_task_hint": "अंतराल के बिना मैनुअल कार्य विश्लेषण डेटा उत्पन्न नहीं करते।",
|
||||
"completions": "पूर्णताएँ",
|
||||
"current": "वर्तमान",
|
||||
"shorter": "छोटा",
|
||||
"longer": "लंबा",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "पिछली अवधियाँ",
|
||||
"cal_forward_windows": "आगामी अवधियाँ",
|
||||
"history_edit_title": "इतिहास प्रविष्टि संपादित करें",
|
||||
"history_delete_entry": "प्रविष्टि हटाएँ",
|
||||
"history_delete_confirm": "क्या यह इतिहास प्रविष्टि हटाएँ? अंतिम निष्पादन की तिथि पिछली पूर्णता पर लौट जाएगी; फ़ोटो ऑब्जेक्ट के पास रहेंगे और उपयोग किए गए पुर्जे स्टॉक में वापस नहीं जोड़े जाएँगे।",
|
||||
"history_edit_timestamp": "टाइमस्टैम्प",
|
||||
"manufacturer": "निर्माता",
|
||||
"model": "मॉडल",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "डिफ़ॉल्ट चेतावनी दिन",
|
||||
"settings_consumable_threshold": "उपभोग्य „कम“ सीमा (%)",
|
||||
"settings_battery_low_percent": "बैटरी „कम“ सीमा (%)",
|
||||
"settings_battery_lifetimes": "बैटरी की सामान्य आयु",
|
||||
"settings_battery_lifetimes_hint": "केवल बिना प्रतिशत वाली बैटरियों के लिए: सिर्फ़ प्रकार और बदलने की तारीख वाला Battery Notes नोट, या केवल \"कम\" बताने वाला सेंसर। नियत तिथि = अंतिम बदलाव + यह आयु। स्तर बताने वाली बैटरियों का पूर्वानुमान मापे गए डिस्चार्ज से बनता है। एक प्रकार के तीन बदलाव देखने के बाद माध्यिका अंतराल अंतर्निहित मान की जगह लेता है — आपका अपना मान हमेशा प्राथमिकता पाता है।",
|
||||
"settings_battery_lifetime_months": "महीने",
|
||||
"settings_battery_lifetime_reset": "डिफ़ॉल्ट उपयोग करें",
|
||||
"settings_battery_lifetime_in_fleet": "आपके बेड़े में",
|
||||
"settings_battery_lifetime_learned_models": "मॉडल अनुसार सीखा: {list}",
|
||||
"settings_thresholds_hint": "नए सुझाए गए सेटअप (शेष प्रतिशत) और अपनी Battery Notes सीमा न रखने वाली बेड़े की बैटरियों के लिए डिफ़ॉल्ट। मौजूदा कार्य अपना मान रखते हैं।",
|
||||
"settings_value_out_of_range": "मान {min} और {max} के बीच होना चाहिए",
|
||||
"bn_summary": "{name}: {n} बैटरियों के लिए {pct} %",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} डिवाइस अपनी सीमा के साथ:",
|
||||
"bn_more": "+ {n} और",
|
||||
"settings_row_actions": "कार्य पंक्ति क्रियाएँ",
|
||||
"settings_ref_numbers_in_lists": "सूचियों में संदर्भ संख्या",
|
||||
"settings_ref_numbers_in_lists_hint": "आज सूची, कार्य तालिका, ऑब्जेक्ट कार्ड और ऑब्जेक्ट तालिका में ऑब्जेक्ट और कार्य नामों के आगे #8 / #8.3 चिप दिखाता है।",
|
||||
"row_actions_buttons_compact": "बटन (फ़ोन पर केवल आइकन)",
|
||||
"row_actions_buttons": "टेक्स्ट वाले बटन",
|
||||
"row_actions_icons": "आइकन (क्लासिक)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "स्नूज़ अवधि (घंटे)",
|
||||
"settings_budget": "बजट",
|
||||
"settings_currency": "मुद्रा",
|
||||
"settings_currency_decimals": "राशियों के दशमलव स्थान",
|
||||
"settings_currency_decimals_hint": "हर राशि कितने दशमलव दिखाए: KPI, बजट, सूचियों और रिपोर्टों में लागत, बजट अलर्ट। 0 = पूर्ण संख्याएँ।",
|
||||
"settings_budget_monthly": "मासिक बजट",
|
||||
"settings_budget_yearly": "वार्षिक बजट",
|
||||
"settings_budget_alerts": "बजट अलर्ट",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML निर्यात करें",
|
||||
"settings_export_csv": "CSV निर्यात करें",
|
||||
"settings_export_settings": "सेटिंग्स निर्यात करें (JSON)",
|
||||
"settings_import_csv": "CSV आयात करें",
|
||||
"settings_import_placeholder": "JSON या CSV सामग्री यहाँ चिपकाएँ…",
|
||||
"settings_import_btn": "आयात करें",
|
||||
"settings_import_success": "{count} वस्तुएँ सफलतापूर्वक आयात की गईं।",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "पूर्ण होने पर प्रति-कार्य HA क्रिया + पूर्व-निर्धारित मानों के साथ त्वरित-पूर्ण QR।",
|
||||
"on_complete_action_title": "पूर्ण होने पर: HA क्रिया ट्रिगर करें (वैकल्पिक)",
|
||||
"on_complete_action_desc": "कार्य पूर्ण होने पर एक HA सेवा कॉल करता है — उदा. डिवाइस पर काउंटर रीसेट करना।",
|
||||
"on_complete_action_service": "सेवा",
|
||||
"on_complete_action_target": "लक्ष्य एंटिटी",
|
||||
"on_complete_action_target_hint": "ध्यान दें: एंटिटी डोमेन सेवा से मेल खाना चाहिए — उदा. 'button.press' केवल button.* पर काम करता है, 'counter.increment' केवल counter.* पर, 'input_button.press' केवल input_button.* पर आदि। मेल न खाने पर क्रिया चुपचाप विफल हो जाएगी (HA लॉग करता है 'Referenced entities ... missing or not currently available')।",
|
||||
"on_complete_action_data": "डेटा (JSON, वैकल्पिक)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "तस्वीरें चुनें",
|
||||
"choose_photo": "फ़ोटो चुनें",
|
||||
"photos_android_hint": "Android ऐप प्रति चयन एक फ़ोटो देता है — हर चयन जोड़ा जाता है।",
|
||||
"add_photos": "तस्वीरें जोड़ें",
|
||||
"photos_limit": "प्रति पूर्णता अधिकतम {max} तस्वीरें",
|
||||
"history_edit_photos_hint": "यहाँ हटाई गई तस्वीर ऑब्जेक्ट के दस्तावेज़ों में बनी रहती है।",
|
||||
"uploading": "अपलोड हो रहा है…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "डाउनलोड करें",
|
||||
"doc_close": "बंद करें",
|
||||
"doc_camera": "फ़ोटो लें",
|
||||
"camera_capture_shoot": "कैप्चर करें",
|
||||
"camera_switch_lens": "कैमरा बदलें",
|
||||
"doc_drop_hint": "फ़ाइलें यहाँ छोड़ें",
|
||||
"doc_task_none": "इस कार्य से कोई दस्तावेज़ संबद्ध नहीं है।",
|
||||
"doc_link_existing": "दस्तावेज़ जोड़ें…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "जब ट्रिगर स्वयं हल हो जाता है तो एक पूर्णता दर्ज करता है (अंतिम निष्पादन सेट करता है) — जैसे नमक भरा गया, फ़िल्टर बदला गया।",
|
||||
"doc_search": "दस्तावेज़ खोजें…",
|
||||
"doc_search_none": "कोई मेल खाता दस्तावेज़ नहीं",
|
||||
"doc_description": "विवरण (वैकल्पिक)",
|
||||
"doc_sort": "दस्तावेज़ क्रमबद्ध करें",
|
||||
"doc_sort_newest": "नवीनतम पहले",
|
||||
"doc_sort_oldest": "सबसे पुराने पहले",
|
||||
"doc_sort_title": "शीर्षक के अनुसार (1, 2, 3 …)",
|
||||
"doc_sort_category": "श्रेणी के अनुसार",
|
||||
"link_device_optional": "मौजूदा डिवाइस से लिंक करें (वैकल्पिक)",
|
||||
"parent_object_optional": "मूल ऑब्जेक्ट (वैकल्पिक)",
|
||||
"parent_none": "(कोई मूल नहीं)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "केवल इस व्यू के लिए सूचित करें",
|
||||
"settings_notify_scope_all": "सभी कार्य",
|
||||
"settings_notify_scope_hint": "केवल वे कार्य अनुस्मारक भेजते हैं जो चुने गए व्यू के लेबल/उपयोगकर्ता फ़िल्टर से मेल खाते हैं। व्यू की स्थिति, क्रम और समूहन यहाँ अनदेखे रहते हैं।",
|
||||
"settings_notify_completed": "पूर्णता सूचनाएँ",
|
||||
"settings_notify_completed_hint": "किसी कार्य के पूरा होने पर परिवार को कारण और करने वाले सहित सूचना। \"केवल स्वचालित\" सिर्फ़ वे पूर्णताएँ बताता है जो किसी ने मौके पर नहीं कीं: सेंसर रिकवरी, खरीदारी सूची में टिक, ऑटोमेशन का सर्विस कॉल। केवल परिवार की सेवा पर; शांत घंटे और दैनिक सीमा लागू।",
|
||||
"notify_completed_off": "बंद",
|
||||
"notify_completed_automatic": "केवल स्वचालित पूर्णताएँ",
|
||||
"notify_completed_all": "सभी पूर्णताएँ",
|
||||
"settings_notify_rule": "आपका अपना सूचना नियम",
|
||||
"settings_notify_rule_hint": "हर सूचना maintenance_supporter_notification इवेंट भी ट्रिगर करती है (प्रकार, स्थिति, ऑब्जेक्ट, कार्य, संदर्भ संख्याएँ, प्राथमिकता, डीप लिंक, पेलोड)। इसे ऑटोमेशन से रूट करें — जैसे Ticker, Telegram या Pushover में।",
|
||||
"settings_notify_event_only": "केवल इवेंट ट्रिगर करें, स्वयं कुछ न भेजें",
|
||||
"settings_notify_event_only_hint": "आपका ऑटोमेशन पूरी डिलीवरी संभालता है। दोहराव अंतराल, शांत घंटे और दैनिक सीमा अभी भी तय करते हैं कि इवेंट कब ट्रिगर हो।",
|
||||
"settings_notify_extra_data": "अतिरिक्त सूचना डेटा (टेम्पलेट)",
|
||||
"settings_notify_extra_data_hint": "JSON या YAML, Jinja की अनुमति; notify कॉल के data में मिलाया जाता है (आपकी कुंजियाँ प्राथमिक)। वेरिएबल: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message। परीक्षण भेजें बटन से जाँचें।",
|
||||
"settings_notify_extra_data_hint": "JSON या YAML, Jinja की अनुमति; notify कॉल के data में मिलाया जाता है (आपकी कुंजियाँ प्राथमिक)। वेरिएबल: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message। परीक्षण भेजें बटन से जाँचें।",
|
||||
"card_saved_view": "सहेजा गया व्यू",
|
||||
"card_saved_view_none": "कोई नहीं",
|
||||
"card_saved_view_help": "ऊपर दिए फ़िल्टरों के अतिरिक्त व्यू के स्थिति, उपयोगकर्ता और लेबल फ़िल्टर लागू करता है। व्यू का क्रम और समूहन पैनल की प्रदर्शन सेटिंग हैं और कार्ड पर लागू नहीं होते।",
|
||||
"panel_card_tab": "इस टैब पर खोलें",
|
||||
"panel_card_tab_default": "याद रखा गया टैब (पैनल जैसा)",
|
||||
"panel_card_view": "सहेजे गए दृश्य के साथ खोलें",
|
||||
"panel_card_height": "ऊँचाई",
|
||||
"panel_card_height_help": "खाली = कार्ड के नीचे की स्क्रीन भरता है (पैनल दृश्य)। अन्यथा अन्य कार्डों वाले दृश्यों के लिए 600px या 80vh जैसी CSS लंबाई।",
|
||||
"panel_card_not_registered": "Maintenance Supporter पैनल उपलब्ध नहीं है — क्या इंटीग्रेशन सेट है?",
|
||||
"panel_card_load_failed": "Maintenance Supporter पैनल लोड नहीं हो सका — पेज फिर से लोड करें (अपडेट के बाद: ब्राउज़र कैश साफ़ करें)।",
|
||||
"doc_part_none": "इस पुर्ज़े से कोई दस्तावेज़ जुड़ा नहीं है।",
|
||||
"settings_templates_toggle_group": "इस समूह के सभी टेम्पलेट चालू या बंद करें",
|
||||
"setups_button": "सुझाए गए सेटअप",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "ठीक",
|
||||
"battery_fleet_status_due": "देय",
|
||||
"battery_fleet_predicted_on": "{date} के आसपास अपेक्षित",
|
||||
"battery_fleet_predicted_typical": "लगभग {date} अपेक्षित — {type} के लिए सामान्यतः {months} महीने ({source})",
|
||||
"lifetime_source_default": "अंतर्निहित डिफ़ॉल्ट",
|
||||
"lifetime_source_table": "अंतर्निहित तालिका",
|
||||
"lifetime_source_learned": "{n} बदलावों से सीखा",
|
||||
"lifetime_source_override": "आपकी सेटिंग",
|
||||
"lifetime_source_learned_device": "इस डिवाइस के {n} बदलावों से सीखा",
|
||||
"lifetime_source_learned_model": "इस मॉडल के डिवाइसों के {n} बदलावों से सीखा",
|
||||
"battery_fleet_predicted_trend": "इस बैटरी के डिस्चार्ज रुझान से अनुमानित: लगभग {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "रिचार्जेबल: बदलने के बजाय चार्ज करें — खरीदारी सूची में कभी नहीं",
|
||||
"battery_fleet_sort_name": "नाम के अनुसार क्रमबद्ध करें",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "प्रयुक्त पुर्जे",
|
||||
"print_inc_photos": "फ़ोटो",
|
||||
"print_inc_documents": "लिंक किए गए दस्तावेज़",
|
||||
"print_inc_doc_desc": "दस्तावेज़ विवरण",
|
||||
"print_inc_checklist": "चेकलिस्ट",
|
||||
"print_inc_notes": "नोट्स",
|
||||
"print_inc_costs": "लागत और अवधि",
|
||||
"print_inc_person": "पूर्ण किया",
|
||||
"print_inc_refs": "संदर्भ संख्याएँ",
|
||||
"print_inc_bare": "बिना विवरण के पूर्ण किए गए कार्य",
|
||||
"print_inc_qr": "प्रति कार्य QR कोड",
|
||||
"print_button": "प्रिंट",
|
||||
"report_scan_hint": "कार्य खोलने के लिए स्कैन करें",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Üzemidő",
|
||||
"runtime_hours": "Cél üzemidő (óra)",
|
||||
"target_value": "Célérték",
|
||||
"baseline": "Kiindulási érték",
|
||||
"target_changes": "Cél változásszám",
|
||||
"for_minutes": "Ennyi ideig (perc)",
|
||||
"time_based": "Időalapú",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Létrehozva",
|
||||
"report_times_done": "Elvégezve",
|
||||
"report_total_cost": "Összköltség",
|
||||
"report_every": "minden {n}. {unit}",
|
||||
"report_notes": "Megjegyzések",
|
||||
"report_col_type": "Típus",
|
||||
"report_col_status": "Állapot",
|
||||
"report_col_schedule": "Ütemezés",
|
||||
"duration": "Időtartam",
|
||||
"minutes_short": "perc",
|
||||
"both": "Mindkettő",
|
||||
"trigger_val": "Kiváltó érték",
|
||||
"complete_title": "Elvégzés: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: hibás típus (várt: {type})",
|
||||
"err_invalid_choice": "{field}: nem megengedett érték",
|
||||
"err_invalid_value": "{field}: érvénytelen érték",
|
||||
"ws_err_not_found": "Nem található — időközben törölhették",
|
||||
"ws_err_invalid_input": "Érvénytelen bevitel",
|
||||
"ws_err_invalid_date": "Érvénytelen dátum",
|
||||
"ws_err_invalid_format": "Érvénytelen formátum",
|
||||
"ws_err_invalid_url": "Érvénytelen hivatkozás",
|
||||
"ws_err_not_configured": "Még nincs beállítva",
|
||||
"ws_err_not_loaded": "Még nincs betöltve — próbálja újra egy pillanat múlva",
|
||||
"ws_err_limit_reached": "Elérte a korlátot",
|
||||
"ws_err_too_many": "Túl sok bejegyzés",
|
||||
"ws_err_too_large": "Túl nagy",
|
||||
"ws_err_not_archived": "Nincs archiválva",
|
||||
"ws_err_invalid_target": "Érvénytelen cél",
|
||||
"ws_err_create_failed": "Nem sikerült létrehozni",
|
||||
"ws_err_archived": "Archiválva — először állítsa vissza",
|
||||
"ws_err_already_archived": "Már archiválva",
|
||||
"ws_err_unavailable": "Jelenleg nem elérhető",
|
||||
"ws_err_unauthorized": "A felhasználója számára nem engedélyezett",
|
||||
"ws_err_too_many_views": "Túl sok mentett nézet",
|
||||
"ws_err_storage_unavailable": "A tároló nem elérhető",
|
||||
"ws_err_replace_failed": "A csere sikertelen",
|
||||
"ws_err_not_paused": "Nincs szüneteltetve",
|
||||
"ws_err_not_available": "Semmi sem elérhető",
|
||||
"ws_err_no_url": "Nincs megadva hivatkozás",
|
||||
"ws_err_no_phases": "Ennek a feladatnak nincsenek fázisai",
|
||||
"ws_err_invalid_view": "Érvénytelen nézet",
|
||||
"ws_err_invalid_user": "Ismeretlen felhasználó",
|
||||
"ws_err_invalid_range": "Érvénytelen tartomány",
|
||||
"ws_err_invalid_parent": "Érvénytelen szülőobjektum",
|
||||
"ws_err_invalid_device": "Ismeretlen eszköz",
|
||||
"ws_err_empty_csv": "A CSV-fájl üres",
|
||||
"ws_err_empty": "Nincs mit importálni",
|
||||
"ws_err_duplicate_failed": "Nem sikerült duplikálni",
|
||||
"ws_err_already_paused": "Már szünetel",
|
||||
"ws_err_invalid_cursor": "Érvénytelen fázispozíció",
|
||||
"ws_err_invalid_entity_slug": "Érvénytelen entitásnév",
|
||||
"ws_err_invalid_trigger_config": "Érvénytelen eseményindító-beállítás",
|
||||
"ws_err_no_defaults": "Nincs beállítva gyors befejezési alapérték",
|
||||
"ws_err_self_link_device": "Egy objektum nem kapcsolható önmagához",
|
||||
"ws_err_too_early": "Túl korai — ez a feladat csak a határidőhöz közelebb fejezhető be",
|
||||
"feat_schedule_time": "Napszakhoz kötött ütemezés",
|
||||
"feat_schedule_time_desc": "A feladatok nem éjfélkor, hanem egy megadott napszakban válnak lejárttá.",
|
||||
"schedule_time_toggle": "Adott időpontban esedékes",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Aktiválás ha egyenlő (=)",
|
||||
"trigger_not_equals": "Aktiválás ha eltér ettől (≠)",
|
||||
"for_at_least_minutes": "Legalább ennyi ideig (perc)",
|
||||
"safety_interval_days": "Biztonsági intervallum (nap, opcionális)",
|
||||
"safety_interval": "Biztonsági intervallum (opcionális)",
|
||||
"trigger_combinator": "Trigger és időköz kombinálása",
|
||||
"trigger_combinator_any": "Trigger vagy időköz (amelyik előbb)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Saját feladataim",
|
||||
"tab_calendar": "Naptár",
|
||||
"cal_no_events": "Nincs karbantartás",
|
||||
"cal_window_7": "7 nap",
|
||||
"cal_window_14": "14 nap",
|
||||
"cal_window_30": "30 nap",
|
||||
"cal_window_365": "1 év",
|
||||
"cal_every_n_days": "{n} naponta",
|
||||
"cal_source_time": "Időalapú",
|
||||
"cal_source_time_adaptive": "Időalapú (adaptív)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "A QR-kód generálása nem sikerült.",
|
||||
"qr_error_no_url": "Nincs HA URL beállítva. Adjon meg külső vagy belső URL-t a Beállítások → Rendszer → Hálózat alatt.",
|
||||
"save_error": "A mentés nem sikerült. Próbálja újra.",
|
||||
"subsave_warning": "Mentve, de egy beállítást elutasított a rendszer: {detail}",
|
||||
"qr_print": "Nyomtatás",
|
||||
"qr_download": "SVG letöltése",
|
||||
"qr_action": "Művelet beolvasáskor",
|
||||
"qr_action_view": "Karbantartási adatok megtekintése",
|
||||
"qr_action_complete": "Karbantartás elvégzettként jelölése",
|
||||
"qr_url_mode": "Hivatkozás típusa",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Helyi (mDNS)",
|
||||
"qr_mode_server": "Szerver URL",
|
||||
"overview": "Áttekintés",
|
||||
"analysis": "Elemzés",
|
||||
"recent_activities": "Legutóbbi tevékenységek",
|
||||
"search_notes": "Keresés a megjegyzésekben",
|
||||
"avg_cost": "Átl. költség",
|
||||
"no_advanced_features": "Nincsenek engedélyezett speciális funkciók",
|
||||
"no_advanced_features_hint": "Az elemzési adatok megjelenítéséhez engedélyezze az „Adaptív ütemezés” vagy a „Szezonális minták” funkciót az integráció beállításaiban.",
|
||||
"analysis_not_enough_data": "Még nincs elég adat az elemzéshez.",
|
||||
"analysis_not_enough_data_hint": "A Weibull-elemzéshez legalább 5 elvégzett karbantartás szükséges; a szezonális minták havi 6+ adatpont után válnak láthatóvá.",
|
||||
"analysis_manual_task_hint": "Az intervallum nélküli kézi feladatok nem termelnek elemzési adatot.",
|
||||
"completions": "elvégzés",
|
||||
"current": "Jelenlegi",
|
||||
"shorter": "Rövidebb",
|
||||
"longer": "Hosszabb",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Korábbi időszakok",
|
||||
"cal_forward_windows": "Jövőbeli időszakok",
|
||||
"history_edit_title": "Előzménybejegyzés szerkesztése",
|
||||
"history_delete_entry": "Bejegyzés törlése",
|
||||
"history_delete_confirm": "Törli ezt az előzménybejegyzést? Az utolsó elvégzés dátuma az előzőre áll vissza; a fotók az objektumnál maradnak, a felhasznált alkatrészek nem kerülnek vissza a készletbe.",
|
||||
"history_edit_timestamp": "Időbélyeg",
|
||||
"manufacturer": "Gyártó",
|
||||
"model": "Modell",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Alapértelmezett figyelmeztetési napok",
|
||||
"settings_consumable_threshold": "„Fogyóeszköz alacsony” küszöb (%)",
|
||||
"settings_battery_low_percent": "„Alacsony akkumulátor” küszöb (%)",
|
||||
"settings_battery_lifetimes": "Jellemző elem-élettartamok",
|
||||
"settings_battery_lifetimes_hint": "Csak százalék nélküli elemeknél használjuk: olyan Battery Notes-bejegyzésnél, amelynek csak típusa és cseredátuma van, vagy csak „alacsony”-t jelentő érzékelőnél. Esedékesség = utolsó csere + ez az élettartam. A szintet jelentő elemek előrejelzése a mért kisülésből jön. Ha a flotta már három cserét látott egy típusból, a medián időköz veszi át a beépített értéket — a saját értéked mindig nyer.",
|
||||
"settings_battery_lifetime_months": "hónap",
|
||||
"settings_battery_lifetime_reset": "Alapérték",
|
||||
"settings_battery_lifetime_in_fleet": "a flottádban",
|
||||
"settings_battery_lifetime_learned_models": "Modellenként tanulva: {list}",
|
||||
"settings_thresholds_hint": "Alapértékek az új Javasolt beállításokhoz (százalékos maradék) és a saját Battery Notes-küszöb nélküli flottaelemekhez. A meglévő feladatok megtartják az értéküket.",
|
||||
"settings_value_out_of_range": "Az értéknek {min} és {max} között kell lennie",
|
||||
"bn_summary": "{name}: {pct} % {n} elemhez",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} eszköz saját küszöbbel:",
|
||||
"bn_more": "+ még {n}",
|
||||
"settings_row_actions": "Műveletek a feladatsorokban",
|
||||
"settings_ref_numbers_in_lists": "Hivatkozási számok a listákban",
|
||||
"settings_ref_numbers_in_lists_hint": "Megjeleníti a #8 / #8.3 címkéket az objektum- és feladatnevek előtt a Ma listában, a feladattáblában, az objektumkártyákon és az objektumtáblában.",
|
||||
"row_actions_buttons_compact": "Gombok (telefonon csak ikonok)",
|
||||
"row_actions_buttons": "Gombok szöveggel",
|
||||
"row_actions_icons": "Ikonok (klasszikus)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Szundi időtartama (óra)",
|
||||
"settings_budget": "Költségkeret",
|
||||
"settings_currency": "Pénznem",
|
||||
"settings_currency_decimals": "Összegek tizedesjegyei",
|
||||
"settings_currency_decimals_hint": "Hány tizedesjegyet mutasson minden összeg: KPI-k, költségvetések, költségek a listákban és jelentésekben, költségvetési riasztások. 0 = egész számok.",
|
||||
"settings_budget_monthly": "Havi költségkeret",
|
||||
"settings_budget_yearly": "Éves költségkeret",
|
||||
"settings_budget_alerts": "Költségkeret-riasztások",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML exportálása",
|
||||
"settings_export_csv": "CSV exportálása",
|
||||
"settings_export_settings": "Beállítások exportálása (JSON)",
|
||||
"settings_import_csv": "CSV importálása",
|
||||
"settings_import_placeholder": "Illessze be ide a JSON vagy CSV tartalmat…",
|
||||
"settings_import_btn": "Importálás",
|
||||
"settings_import_success": "{count} objektum sikeresen importálva.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "Feladatonkénti HA művelet elvégzéskor + gyorselvégző QR előre beállított értékekkel.",
|
||||
"on_complete_action_title": "Elvégzéskor: HA művelet indítása (opcionális)",
|
||||
"on_complete_action_desc": "HA szolgáltatást hív meg a feladat elvégzésekor — pl. számláló nullázása az eszközön.",
|
||||
"on_complete_action_service": "Szolgáltatás",
|
||||
"on_complete_action_target": "Cél entitás",
|
||||
"on_complete_action_target_hint": "Megjegyzés: az entitás doménjének egyeznie kell a szolgáltatással — pl. a „button.press” csak button.* entitáson működik, a „counter.increment” csak counter.* entitáson, az „input_button.press” csak input_button.* entitáson stb. Eltérés esetén a művelet csendben meghiúsul (a HA naplóban: „Referenced entities ... missing or not currently available”).",
|
||||
"on_complete_action_data": "Adatok (JSON, opcionális)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "Fotók kiválasztása",
|
||||
"choose_photo": "Fotó kiválasztása",
|
||||
"photos_android_hint": "Az Android-alkalmazás választásonként egy fotót ad át — minden választás hozzáadódik.",
|
||||
"add_photos": "Fotók hozzáadása",
|
||||
"photos_limit": "Legfeljebb {max} fotó elvégzésenként",
|
||||
"history_edit_photos_hint": "Az itt eltávolított fotó megmarad az objektum dokumentumai között.",
|
||||
"uploading": "Feltöltés…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "Letöltés",
|
||||
"doc_close": "Bezárás",
|
||||
"doc_camera": "Fotó készítése",
|
||||
"camera_capture_shoot": "Felvétel",
|
||||
"camera_switch_lens": "Kamera váltása",
|
||||
"doc_drop_hint": "Húzza ide a fájlokat",
|
||||
"doc_task_none": "Ehhez a feladathoz nincs dokumentum csatolva.",
|
||||
"doc_link_existing": "Dokumentum hozzárendelése…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "Elvégzést rögzít (beállítja az utolsó elvégzést), amikor a kiváltás magától megszűnik — pl. só utántöltve, szűrő kicserélve.",
|
||||
"doc_search": "Dokumentumok keresése…",
|
||||
"doc_search_none": "Nincs egyező dokumentum",
|
||||
"doc_description": "Leírás (opcionális)",
|
||||
"doc_sort": "Dokumentumok rendezése",
|
||||
"doc_sort_newest": "Legújabb elöl",
|
||||
"doc_sort_oldest": "Legrégebbi elöl",
|
||||
"doc_sort_title": "Cím szerint (1, 2, 3 …)",
|
||||
"doc_sort_category": "Kategória szerint",
|
||||
"link_device_optional": "Meglévő eszközhöz kapcsolás (opcionális)",
|
||||
"parent_object_optional": "Szülőobjektum (opcionális)",
|
||||
"parent_none": "(Nincs szülő)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Értesítés csak erre a nézetre",
|
||||
"settings_notify_scope_all": "Minden feladat",
|
||||
"settings_notify_scope_hint": "Csak a kiválasztott mentett nézet címke-/felhasználószűrőinek megfelelő feladatok küldenek emlékeztetőt. A nézet állapot-, rendezési és csoportosítási beállításait itt figyelmen kívül hagyjuk.",
|
||||
"settings_notify_completed": "Elvégzési értesítések",
|
||||
"settings_notify_completed_hint": "Üzenet a háztartásnak, ha egy feladat elkészült, az okkal és a végrehajtóval. A „Csak automatikus” csak azokat jelenti, amelyeket senki nem végzett el a helyszínen: érzékelő helyreállása, kipipálás a bevásárlólistán, automatizálás szolgáltatáshívása. Csak a háztartási szolgáltatásra; a csendes órák és a napi korlát érvényes.",
|
||||
"notify_completed_off": "Ki",
|
||||
"notify_completed_automatic": "Csak automatikus elvégzések",
|
||||
"notify_completed_all": "Minden elvégzés",
|
||||
"settings_notify_rule": "Saját értesítési szabály",
|
||||
"settings_notify_rule_hint": "Minden értesítés kiváltja a maintenance_supporter_notification eseményt is (típus, állapot, tárgy, feladat, hivatkozási számok, prioritás, mélylink, adatok). Irányítsa automatizálással — pl. a Tickerbe, Telegramba vagy Pushoverbe.",
|
||||
"settings_notify_event_only": "Csak az esemény, saját küldés nélkül",
|
||||
"settings_notify_event_only_hint": "A kézbesítést teljes egészében az automatizálás végzi. Az ismétlési időközök, a csendes órák és a napi korlát továbbra is meghatározzák, mikor váltódik ki az esemény.",
|
||||
"settings_notify_extra_data": "További értesítési adatok (sablon)",
|
||||
"settings_notify_extra_data_hint": "JSON vagy YAML, Jinja engedélyezett; beolvad a notify hívás data mezőjébe (a saját kulcsai nyernek). Változók: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Ellenőrizze a Teszt küldése gombbal.",
|
||||
"settings_notify_extra_data_hint": "JSON vagy YAML, Jinja engedélyezett; beolvad a notify hívás data mezőjébe (a saját kulcsai nyernek). Változók: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Ellenőrizze a Teszt küldése gombbal.",
|
||||
"card_saved_view": "Mentett nézet",
|
||||
"card_saved_view_none": "Nincs",
|
||||
"card_saved_view_help": "A nézet állapot-, felhasználó- és címkeszűrőit a fenti szűrőkön felül alkalmazza. A nézet rendezése és csoportosítása a panel megjelenítési beállítása, a kártyán nem érvényesül.",
|
||||
"panel_card_tab": "Megnyitás ezen a lapon",
|
||||
"panel_card_tab_default": "Megjegyzett lap (mint a panelen)",
|
||||
"panel_card_view": "Megnyitás mentett nézettel",
|
||||
"panel_card_height": "Magasság",
|
||||
"panel_card_height_help": "Üres = kitölti a képernyőt a kártya alatt (panelnézet). Egyébként egy CSS-hossz, például 600px vagy 80vh, más kártyákat is tartalmazó nézetekhez.",
|
||||
"panel_card_not_registered": "A Maintenance Supporter panel nem érhető el — be van állítva az integráció?",
|
||||
"panel_card_load_failed": "A Maintenance Supporter panelt nem sikerült betölteni — töltse újra az oldalt (frissítés után: ürítse a böngésző gyorsítótárát).",
|
||||
"doc_part_none": "Ehhez az alkatrészhez nincs dokumentum csatolva.",
|
||||
"settings_templates_toggle_group": "A csoport összes sablonjának be- vagy kikapcsolása",
|
||||
"setups_button": "Javasolt beállítások",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Rendben",
|
||||
"battery_fleet_status_due": "Esedékes",
|
||||
"battery_fleet_predicted_on": "Várhatóan {date} körül",
|
||||
"battery_fleet_predicted_typical": "Várhatóan {date} körül — {type} esetén jellemzően {months} hónap ({source})",
|
||||
"lifetime_source_default": "beépített alapérték",
|
||||
"lifetime_source_table": "beépített táblázat",
|
||||
"lifetime_source_learned": "{n} cseréből tanulva",
|
||||
"lifetime_source_override": "saját beállításod",
|
||||
"lifetime_source_learned_device": "ennek az eszköznek {n} cseréjéből tanulva",
|
||||
"lifetime_source_learned_model": "ennek a modellnek {n} cseréjéből tanulva",
|
||||
"battery_fleet_predicted_trend": "Az elem merülési trendjéből előrejelezve: kb. {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Újratölthető: cserélés helyett töltse fel — soha nem kerül a bevásárlólistára",
|
||||
"battery_fleet_sort_name": "Rendezés név szerint",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Felhasznált alkatrészek",
|
||||
"print_inc_photos": "Fotók",
|
||||
"print_inc_documents": "Csatolt dokumentumok",
|
||||
"print_inc_doc_desc": "Dokumentumleírások",
|
||||
"print_inc_checklist": "Ellenőrzőlista",
|
||||
"print_inc_notes": "Jegyzetek",
|
||||
"print_inc_costs": "Költség és időtartam",
|
||||
"print_inc_person": "Elvégezte",
|
||||
"print_inc_refs": "Hivatkozási számok",
|
||||
"print_inc_bare": "Részletek nélküli elvégzések",
|
||||
"print_inc_qr": "QR-kód feladatonként",
|
||||
"print_button": "Nyomtatás",
|
||||
"report_scan_hint": "Olvassa be a feladat megnyitásához",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Tempo di funzionamento",
|
||||
"runtime_hours": "Durata obiettivo (ore)",
|
||||
"target_value": "Valore obiettivo",
|
||||
"baseline": "Linea di base",
|
||||
"target_changes": "Modifiche obiettivo",
|
||||
"for_minutes": "Per (minuti)",
|
||||
"time_based": "Temporale",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Generato",
|
||||
"report_times_done": "Fatto",
|
||||
"report_total_cost": "Costo totale",
|
||||
"report_every": "ogni {n} {unit}",
|
||||
"report_notes": "Note",
|
||||
"report_col_type": "Tipo",
|
||||
"report_col_status": "Stato",
|
||||
"report_col_schedule": "Pianificazione",
|
||||
"duration": "Durata",
|
||||
"minutes_short": "min",
|
||||
"both": "Entrambi",
|
||||
"trigger_val": "Valore trigger",
|
||||
"complete_title": "Completato: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: tipo errato (atteso: {type})",
|
||||
"err_invalid_choice": "{field}: valore non consentito",
|
||||
"err_invalid_value": "{field}: valore non valido",
|
||||
"ws_err_not_found": "Non trovato — potrebbe essere stato eliminato nel frattempo",
|
||||
"ws_err_invalid_input": "Input non valido",
|
||||
"ws_err_invalid_date": "Data non valida",
|
||||
"ws_err_invalid_format": "Formato non valido",
|
||||
"ws_err_invalid_url": "Link non valido",
|
||||
"ws_err_not_configured": "Non ancora configurato",
|
||||
"ws_err_not_loaded": "Non ancora caricato — riprova tra un istante",
|
||||
"ws_err_limit_reached": "Limite raggiunto",
|
||||
"ws_err_too_many": "Troppe voci",
|
||||
"ws_err_too_large": "Troppo grande",
|
||||
"ws_err_not_archived": "Non archiviato",
|
||||
"ws_err_invalid_target": "Destinazione non valida",
|
||||
"ws_err_create_failed": "Impossibile creare",
|
||||
"ws_err_archived": "Archiviato — ripristinalo prima",
|
||||
"ws_err_already_archived": "Già archiviato",
|
||||
"ws_err_unavailable": "Al momento non disponibile",
|
||||
"ws_err_unauthorized": "Non consentito al tuo utente",
|
||||
"ws_err_too_many_views": "Troppe viste salvate",
|
||||
"ws_err_storage_unavailable": "Archiviazione non disponibile",
|
||||
"ws_err_replace_failed": "Sostituzione non riuscita",
|
||||
"ws_err_not_paused": "Non in pausa",
|
||||
"ws_err_not_available": "Niente di disponibile",
|
||||
"ws_err_no_url": "Nessun link indicato",
|
||||
"ws_err_no_phases": "Questa attività non ha fasi",
|
||||
"ws_err_invalid_view": "Vista non valida",
|
||||
"ws_err_invalid_user": "Utente sconosciuto",
|
||||
"ws_err_invalid_range": "Intervallo non valido",
|
||||
"ws_err_invalid_parent": "Oggetto principale non valido",
|
||||
"ws_err_invalid_device": "Dispositivo sconosciuto",
|
||||
"ws_err_empty_csv": "Il file CSV è vuoto",
|
||||
"ws_err_empty": "Niente da importare",
|
||||
"ws_err_duplicate_failed": "Impossibile duplicare",
|
||||
"ws_err_already_paused": "Già in pausa",
|
||||
"ws_err_invalid_cursor": "Posizione della fase non valida",
|
||||
"ws_err_invalid_entity_slug": "Nome entità non valido",
|
||||
"ws_err_invalid_trigger_config": "Configurazione del trigger non valida",
|
||||
"ws_err_no_defaults": "Nessun valore predefinito per il completamento rapido",
|
||||
"ws_err_self_link_device": "Un oggetto non può essere collegato a sé stesso",
|
||||
"ws_err_too_early": "Troppo presto — questa attività può essere completata solo più vicino alla scadenza",
|
||||
"feat_schedule_time": "Pianificazione oraria",
|
||||
"feat_schedule_time_desc": "Le attività scadono a un'ora specifica anziché a mezzanotte.",
|
||||
"schedule_time_toggle": "Scadenza a un orario preciso",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Attiva quando uguale a (=)",
|
||||
"trigger_not_equals": "Attiva quando diverso da (≠)",
|
||||
"for_at_least_minutes": "Per almeno (minuti)",
|
||||
"safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)",
|
||||
"safety_interval": "Intervallo di sicurezza (opzionale)",
|
||||
"trigger_combinator": "Combina trigger e intervallo",
|
||||
"trigger_combinator_any": "Trigger o intervallo (il primo)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Le mie attività",
|
||||
"tab_calendar": "Calendario",
|
||||
"cal_no_events": "Nessuna manutenzione",
|
||||
"cal_window_7": "7 giorni",
|
||||
"cal_window_14": "14 giorni",
|
||||
"cal_window_30": "30 giorni",
|
||||
"cal_window_365": "1 anno",
|
||||
"cal_every_n_days": "ogni {n} giorni",
|
||||
"cal_source_time": "Basato sul tempo",
|
||||
"cal_source_time_adaptive": "Basato sul tempo (adattivo)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Impossibile generare il codice QR.",
|
||||
"qr_error_no_url": "Nessun URL HA configurato. Impostare un URL esterno o interno in Impostazioni → Sistema → Rete.",
|
||||
"save_error": "Salvataggio non riuscito. Riprovare.",
|
||||
"subsave_warning": "Salvato, ma un'impostazione è stata rifiutata: {detail}",
|
||||
"qr_print": "Stampa",
|
||||
"qr_download": "Scarica SVG",
|
||||
"qr_action": "Azione alla scansione",
|
||||
"qr_action_view": "Visualizza info manutenzione",
|
||||
"qr_action_complete": "Segna manutenzione come completata",
|
||||
"qr_url_mode": "Tipo di link",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Locale (mDNS)",
|
||||
"qr_mode_server": "URL server",
|
||||
"overview": "Panoramica",
|
||||
"analysis": "Analisi",
|
||||
"recent_activities": "Attività recenti",
|
||||
"search_notes": "Cerca nelle note",
|
||||
"avg_cost": "Ø Costo",
|
||||
"no_advanced_features": "Nessuna funzione avanzata attivata",
|
||||
"no_advanced_features_hint": "Attiva “Intervalli Adattivi” o “Modelli Stagionali” nelle impostazioni dell'integrazione per vedere i dati di analisi qui.",
|
||||
"analysis_not_enough_data": "Non ci sono ancora abbastanza dati per l'analisi.",
|
||||
"analysis_not_enough_data_hint": "L'analisi Weibull richiede almeno 5 manutenzioni completate; i modelli stagionali diventano visibili dopo 6+ punti dati al mese.",
|
||||
"analysis_manual_task_hint": "Le attività manuali senza intervallo non generano dati di analisi.",
|
||||
"completions": "completamenti",
|
||||
"current": "Attuale",
|
||||
"shorter": "Più breve",
|
||||
"longer": "Più lungo",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Finestre passate",
|
||||
"cal_forward_windows": "Finestre future",
|
||||
"history_edit_title": "Modifica voce dello storico",
|
||||
"history_delete_entry": "Elimina voce",
|
||||
"history_delete_confirm": "Eliminare questa voce della cronologia? La data dell'ultima esecuzione torna al completamento precedente; le foto restano con l'oggetto e le parti consumate non vengono rimesse a magazzino.",
|
||||
"history_edit_timestamp": "Data e ora",
|
||||
"manufacturer": "Produttore",
|
||||
"model": "Modello",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Giorni di avviso predefiniti",
|
||||
"settings_consumable_threshold": "Soglia „consumabile basso“ (%)",
|
||||
"settings_battery_low_percent": "Soglia „batteria scarica“ (%)",
|
||||
"settings_battery_lifetimes": "Durate tipiche delle batterie",
|
||||
"settings_battery_lifetimes_hint": "Usato solo per batterie senza percentuale: una nota Battery Notes con solo tipo e data di sostituzione, o un sensore che segnala solo « scarica ». La scadenza è ultima sostituzione + questa durata. Le batterie che riportano un livello ricavano la previsione dalla scarica misurata. Dopo tre sostituzioni dello stesso tipo, l'intervallo mediano sostituisce il valore integrato — il tuo valore vince sempre.",
|
||||
"settings_battery_lifetime_months": "mesi",
|
||||
"settings_battery_lifetime_reset": "Usa predefinito",
|
||||
"settings_battery_lifetime_in_fleet": "nel tuo parco",
|
||||
"settings_battery_lifetime_learned_models": "Appreso per modello: {list}",
|
||||
"settings_thresholds_hint": "Valori predefiniti per le nuove Configurazioni suggerite (percentuale residua) e per le batterie della flotta senza soglia Battery Notes propria. Le attività esistenti mantengono il loro valore.",
|
||||
"settings_value_out_of_range": "Il valore deve essere compreso tra {min} e {max}",
|
||||
"bn_summary": "{name}: {pct} % per {n} batterie",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} dispositivi con soglia propria:",
|
||||
"bn_more": "+ altri {n}",
|
||||
"settings_row_actions": "Azioni nelle righe delle attività",
|
||||
"settings_ref_numbers_in_lists": "Numeri di riferimento negli elenchi",
|
||||
"settings_ref_numbers_in_lists_hint": "Mostra le etichette #8 / #8.3 davanti ai nomi di oggetti e attività nell'elenco Oggi, nella tabella delle attività, nelle schede degli oggetti e nella tabella degli oggetti.",
|
||||
"row_actions_buttons_compact": "Pulsanti (solo icone sul telefono)",
|
||||
"row_actions_buttons": "Pulsanti con testo",
|
||||
"row_actions_icons": "Icone (classico)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Durata posticipo (ore)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Decimali degli importi",
|
||||
"settings_currency_decimals_hint": "Quanti decimali mostra ogni importo: KPI, budget, costi in elenchi e report, avvisi di budget. 0 = numeri interi.",
|
||||
"settings_budget_monthly": "Budget mensile",
|
||||
"settings_budget_yearly": "Budget annuale",
|
||||
"settings_budget_alerts": "Avvisi budget",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Esporta YAML",
|
||||
"settings_export_csv": "Esporta CSV",
|
||||
"settings_export_settings": "Esporta impostazioni (JSON)",
|
||||
"settings_import_csv": "Importa CSV",
|
||||
"settings_import_placeholder": "Incolla il contenuto JSON o CSV qui…",
|
||||
"settings_import_btn": "Importa",
|
||||
"settings_import_success": "{count} oggetti importati con successo.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Azione HA per attività al completamento + QR completamento rapido con valori predefiniti.",
|
||||
"on_complete_action_title": "Al completamento: attiva azione HA (opzionale)",
|
||||
"on_complete_action_desc": "Chiama un servizio HA quando l'attività viene completata — es. azzerare un contatore sul dispositivo.",
|
||||
"on_complete_action_service": "Servizio",
|
||||
"on_complete_action_target": "Entità target",
|
||||
"on_complete_action_data": "Dati (JSON, opzionale)",
|
||||
"on_complete_action_test": "Testa azione",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Scegli foto",
|
||||
"choose_photo": "Scegli foto",
|
||||
"photos_android_hint": "L'app Android passa una foto per selezione — ogni selezione viene aggiunta.",
|
||||
"add_photos": "Aggiungi foto",
|
||||
"photos_limit": "Fino a {max} foto per completamento",
|
||||
"history_edit_photos_hint": "Una foto rimossa qui resta nei documenti dell'oggetto.",
|
||||
"uploading": "Caricamento…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Scarica",
|
||||
"doc_close": "Chiudi",
|
||||
"doc_camera": "Scatta foto",
|
||||
"camera_capture_shoot": "Scatta",
|
||||
"camera_switch_lens": "Cambia fotocamera",
|
||||
"doc_drop_hint": "Trascina qui i file",
|
||||
"doc_task_none": "Nessun documento collegato a questa attività.",
|
||||
"doc_link_existing": "Collega un documento…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registra un completamento (imposta l'ultima esecuzione) quando il trigger si risolve da solo — ad es. sale ricaricato, filtro sostituito.",
|
||||
"doc_search": "Cerca documenti…",
|
||||
"doc_search_none": "Nessun documento corrispondente",
|
||||
"doc_description": "Descrizione (facoltativa)",
|
||||
"doc_sort": "Ordina documenti",
|
||||
"doc_sort_newest": "Più recenti prima",
|
||||
"doc_sort_oldest": "Meno recenti prima",
|
||||
"doc_sort_title": "Per titolo (1, 2, 3 …)",
|
||||
"doc_sort_category": "Per categoria",
|
||||
"link_device_optional": "Collega a dispositivo esistente (opzionale)",
|
||||
"parent_object_optional": "Oggetto principale (opzionale)",
|
||||
"parent_none": "(Nessun principale)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notifica solo per la vista",
|
||||
"settings_notify_scope_all": "Tutte le attività",
|
||||
"settings_notify_scope_hint": "Solo le attività che corrispondono ai filtri etichetta/utente della vista scelta inviano promemoria. Stato, ordinamento e raggruppamento della vista qui vengono ignorati.",
|
||||
"settings_notify_completed": "Notifiche di completamento",
|
||||
"settings_notify_completed_hint": "Una nota per la famiglia quando un'attività viene completata, con il motivo e chi l'ha fatto. \"Solo automatici\" annuncia solo i completamenti che nessuno ha fatto sul posto: ripristino del sensore, spunta nella lista della spesa, chiamata di servizio di un'automazione. Solo al servizio della famiglia; ore di silenzio e limite giornaliero si applicano.",
|
||||
"notify_completed_off": "Disattivato",
|
||||
"notify_completed_automatic": "Solo completamenti automatici",
|
||||
"notify_completed_all": "Tutti i completamenti",
|
||||
"settings_notify_rule": "La tua regola di notifica",
|
||||
"settings_notify_rule_hint": "Ogni notifica genera anche l'evento maintenance_supporter_notification (tipo, stato, oggetto, attività, numeri di riferimento, priorità, link, payload). Instradalo con un'automazione — ad es. verso Ticker, Telegram o Pushover.",
|
||||
"settings_notify_event_only": "Genera solo l'evento, non inviare nulla",
|
||||
"settings_notify_event_only_hint": "La tua automazione gestisce l'intera consegna. Intervalli di ripetizione, ore di silenzio e limite giornaliero valgono ancora per quando scatta l'evento.",
|
||||
"settings_notify_extra_data": "Dati di notifica aggiuntivi (template)",
|
||||
"settings_notify_extra_data_hint": "JSON o YAML, Jinja consentito; unito ai data della chiamata notify (le tue chiavi vincono). Variabili: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Prova con il pulsante Invia test.",
|
||||
"settings_notify_extra_data_hint": "JSON o YAML, Jinja consentito; unito ai data della chiamata notify (le tue chiavi vincono). Variabili: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Prova con il pulsante Invia test.",
|
||||
"card_saved_view": "Vista salvata",
|
||||
"card_saved_view_none": "Nessuna",
|
||||
"card_saved_view_help": "Applica i filtri stato, utente ed etichetta della vista in aggiunta ai filtri sopra. Ordinamento e raggruppamento della vista sono impostazioni di visualizzazione del pannello e non si applicano alla scheda.",
|
||||
"panel_card_tab": "Apri sulla scheda",
|
||||
"panel_card_tab_default": "Scheda memorizzata (come nel pannello)",
|
||||
"panel_card_view": "Apri con la vista salvata",
|
||||
"panel_card_height": "Altezza",
|
||||
"panel_card_height_help": "Vuoto = riempie lo schermo sotto la scheda (vista pannello). Altrimenti una lunghezza CSS come 600px o 80vh per viste con altre schede.",
|
||||
"panel_card_not_registered": "Il pannello Maintenance Supporter non è disponibile — l'integrazione è configurata?",
|
||||
"panel_card_load_failed": "Impossibile caricare il pannello Maintenance Supporter — ricarica la pagina (dopo un aggiornamento: svuota la cache del browser).",
|
||||
"doc_part_none": "Nessun documento collegato a questo ricambio.",
|
||||
"settings_templates_toggle_group": "Attiva o disattiva tutti i modelli di questo gruppo",
|
||||
"setups_button": "Configurazioni suggerite",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "A posto",
|
||||
"battery_fleet_status_due": "In scadenza",
|
||||
"battery_fleet_predicted_on": "Previsto intorno al {date}",
|
||||
"battery_fleet_predicted_typical": "Previsto intorno al {date} — {months} mesi tipici per {type} ({source})",
|
||||
"lifetime_source_default": "valore predefinito integrato",
|
||||
"lifetime_source_table": "tabella integrata",
|
||||
"lifetime_source_learned": "appreso da {n} sostituzioni",
|
||||
"lifetime_source_override": "la tua impostazione",
|
||||
"lifetime_source_learned_device": "appreso da {n} sostituzioni di questo dispositivo",
|
||||
"lifetime_source_learned_model": "appreso da {n} sostituzioni su dispositivi di questo modello",
|
||||
"battery_fleet_predicted_trend": "Previsto dalla tendenza di scarica di questa batteria: intorno al {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Ricaricabile: da ricaricare anziché sostituire — mai nella lista della spesa",
|
||||
"battery_fleet_sort_name": "Ordina per nome",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Ricambi usati",
|
||||
"print_inc_photos": "Foto",
|
||||
"print_inc_documents": "Documenti collegati",
|
||||
"print_inc_doc_desc": "Descrizioni dei documenti",
|
||||
"print_inc_checklist": "Lista di controllo",
|
||||
"print_inc_notes": "Note",
|
||||
"print_inc_costs": "Costo e durata",
|
||||
"print_inc_person": "Eseguito da",
|
||||
"print_inc_refs": "Numeri di riferimento",
|
||||
"print_inc_bare": "Completamenti senza dettagli",
|
||||
"print_inc_qr": "Codice QR per attività",
|
||||
"print_button": "Stampa",
|
||||
"report_scan_hint": "Scansiona per aprire l'attività",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "稼働時間",
|
||||
"runtime_hours": "目標稼働時間(時間)",
|
||||
"target_value": "目標値",
|
||||
"baseline": "基準値",
|
||||
"target_changes": "変化回数",
|
||||
"for_minutes": "継続(分)",
|
||||
"time_based": "時間ベース",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "作成日",
|
||||
"report_times_done": "実施回数",
|
||||
"report_total_cost": "合計費用",
|
||||
"report_every": "{n} {unit} ごと",
|
||||
"report_notes": "メモ",
|
||||
"report_col_type": "種類",
|
||||
"report_col_status": "状態",
|
||||
"report_col_schedule": "スケジュール",
|
||||
"duration": "所要時間",
|
||||
"minutes_short": "分",
|
||||
"both": "両方",
|
||||
"trigger_val": "トリガー値",
|
||||
"complete_title": "完了: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: 型が違います(期待値: {type})",
|
||||
"err_invalid_choice": "{field}: 許可されていない値です",
|
||||
"err_invalid_value": "{field}: 無効な値です",
|
||||
"ws_err_not_found": "見つかりません — その間に削除された可能性があります",
|
||||
"ws_err_invalid_input": "入力が無効です",
|
||||
"ws_err_invalid_date": "日付が無効です",
|
||||
"ws_err_invalid_format": "形式が無効です",
|
||||
"ws_err_invalid_url": "リンクが無効です",
|
||||
"ws_err_not_configured": "まだ設定されていません",
|
||||
"ws_err_not_loaded": "まだ読み込まれていません — しばらくしてからもう一度お試しください",
|
||||
"ws_err_limit_reached": "上限に達しました",
|
||||
"ws_err_too_many": "項目が多すぎます",
|
||||
"ws_err_too_large": "サイズが大きすぎます",
|
||||
"ws_err_not_archived": "アーカイブされていません",
|
||||
"ws_err_invalid_target": "対象が無効です",
|
||||
"ws_err_create_failed": "作成できませんでした",
|
||||
"ws_err_archived": "アーカイブ済み — 先に復元してください",
|
||||
"ws_err_already_archived": "すでにアーカイブ済みです",
|
||||
"ws_err_unavailable": "現在利用できません",
|
||||
"ws_err_unauthorized": "このユーザーには許可されていません",
|
||||
"ws_err_too_many_views": "保存済みビューが多すぎます",
|
||||
"ws_err_storage_unavailable": "ストレージが利用できません",
|
||||
"ws_err_replace_failed": "置き換えに失敗しました",
|
||||
"ws_err_not_paused": "一時停止されていません",
|
||||
"ws_err_not_available": "利用できるものがありません",
|
||||
"ws_err_no_url": "リンクが指定されていません",
|
||||
"ws_err_no_phases": "このタスクにはフェーズがありません",
|
||||
"ws_err_invalid_view": "ビューが無効です",
|
||||
"ws_err_invalid_user": "不明なユーザーです",
|
||||
"ws_err_invalid_range": "範囲が無効です",
|
||||
"ws_err_invalid_parent": "親オブジェクトが無効です",
|
||||
"ws_err_invalid_device": "不明なデバイスです",
|
||||
"ws_err_empty_csv": "CSV ファイルが空です",
|
||||
"ws_err_empty": "インポートするものがありません",
|
||||
"ws_err_duplicate_failed": "複製できませんでした",
|
||||
"ws_err_already_paused": "すでに一時停止中です",
|
||||
"ws_err_invalid_cursor": "フェーズの位置が無効です",
|
||||
"ws_err_invalid_entity_slug": "エンティティ名が無効です",
|
||||
"ws_err_invalid_trigger_config": "トリガー設定が無効です",
|
||||
"ws_err_no_defaults": "クイック完了の既定値が設定されていません",
|
||||
"ws_err_self_link_device": "オブジェクトを自分自身にリンクすることはできません",
|
||||
"ws_err_too_early": "早すぎます — このタスクは期日が近づいてからのみ完了できます",
|
||||
"feat_schedule_time": "時刻指定スケジュール",
|
||||
"feat_schedule_time_desc": "タスクが深夜0時ではなく指定した時刻に期限超過となります。",
|
||||
"schedule_time_toggle": "特定の時刻に期限",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "値が一致したらトリガー(=)",
|
||||
"trigger_not_equals": "値が異なればトリガー(≠)",
|
||||
"for_at_least_minutes": "最低継続時間(分)",
|
||||
"safety_interval_days": "安全間隔(日、任意)",
|
||||
"safety_interval": "安全間隔(任意)",
|
||||
"trigger_combinator": "トリガーと間隔の組み合わせ",
|
||||
"trigger_combinator_any": "トリガーまたは間隔(先に満たした方)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "自分のタスク",
|
||||
"tab_calendar": "カレンダー",
|
||||
"cal_no_events": "メンテナンスなし",
|
||||
"cal_window_7": "7日間",
|
||||
"cal_window_14": "14日間",
|
||||
"cal_window_30": "30日間",
|
||||
"cal_window_365": "1年間",
|
||||
"cal_every_n_days": "{n}日ごと",
|
||||
"cal_source_time": "時間ベース",
|
||||
"cal_source_time_adaptive": "時間ベース(適応)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QRコードの生成に失敗しました。",
|
||||
"qr_error_no_url": "HAのURLが設定されていません。設定 → システム → ネットワークで外部または内部URLを設定してください。",
|
||||
"save_error": "保存に失敗しました。もう一度お試しください。",
|
||||
"subsave_warning": "保存しましたが、1 つの設定が拒否されました: {detail}",
|
||||
"qr_print": "印刷",
|
||||
"qr_download": "SVGをダウンロード",
|
||||
"qr_action": "スキャン時の動作",
|
||||
"qr_action_view": "メンテナンス情報を表示",
|
||||
"qr_action_complete": "メンテナンスを完了にする",
|
||||
"qr_url_mode": "リンク種別",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "ローカル(mDNS)",
|
||||
"qr_mode_server": "サーバーURL",
|
||||
"overview": "概要",
|
||||
"analysis": "分析",
|
||||
"recent_activities": "最近のアクティビティ",
|
||||
"search_notes": "メモを検索",
|
||||
"avg_cost": "平均費用",
|
||||
"no_advanced_features": "高度な機能が有効になっていません",
|
||||
"no_advanced_features_hint": "統合設定で「適応間隔」または「季節パターン」を有効にすると、ここに分析データが表示されます。",
|
||||
"analysis_not_enough_data": "分析に必要なデータがまだ不足しています。",
|
||||
"analysis_not_enough_data_hint": "ワイブル分析には完了済みメンテナンスが最低5件必要です。季節パターンは月あたり6件以上のデータポイントで表示されるようになります。",
|
||||
"analysis_manual_task_hint": "間隔のない手動タスクは分析データを生成しません。",
|
||||
"completions": "完了回数",
|
||||
"current": "現在",
|
||||
"shorter": "短く",
|
||||
"longer": "長く",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "過去の期間",
|
||||
"cal_forward_windows": "今後の期間",
|
||||
"history_edit_title": "履歴項目を編集",
|
||||
"history_delete_entry": "記録を削除",
|
||||
"history_delete_confirm": "この履歴記録を削除しますか?最終実施日は前回の完了に戻ります。写真はオブジェクトに残り、消費した部品は在庫に戻りません。",
|
||||
"history_edit_timestamp": "日時",
|
||||
"manufacturer": "メーカー",
|
||||
"model": "型番",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "既定の警告日数",
|
||||
"settings_consumable_threshold": "消耗品「残量少」しきい値(%)",
|
||||
"settings_battery_low_percent": "電池「残量少」しきい値(%)",
|
||||
"settings_battery_lifetimes": "電池の標準寿命",
|
||||
"settings_battery_lifetimes_hint": "パーセント表示のない電池にのみ使います:種類と交換日だけの Battery Notes メモや「残量低下」しか報告しないセンサーです。期日は最終交換日+この寿命。残量を報告する電池は実測の放電から予測します。同じ種類の交換を3回記録すると、中央値の間隔が内蔵値に代わります。自分で設定した値が常に優先されます。",
|
||||
"settings_battery_lifetime_months": "か月",
|
||||
"settings_battery_lifetime_reset": "既定値に戻す",
|
||||
"settings_battery_lifetime_in_fleet": "使用中",
|
||||
"settings_battery_lifetime_learned_models": "型番ごとの学習値:{list}",
|
||||
"settings_thresholds_hint": "新しい「推奨セットアップ」(残量パーセント)と、独自の Battery Notes しきい値を持たないバッテリー一覧の電池に適用される既定値です。既存のタスクは値を保持します。",
|
||||
"settings_value_out_of_range": "値は {min} から {max} の間で指定してください",
|
||||
"bn_summary": "{name}:{n} 個の電池に {pct} %",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "独自のしきい値を持つデバイス {n} 台:",
|
||||
"bn_more": "+ 他 {n} 件",
|
||||
"settings_row_actions": "タスク行の操作",
|
||||
"settings_ref_numbers_in_lists": "一覧に参照番号を表示",
|
||||
"settings_ref_numbers_in_lists_hint": "「今日」の一覧、タスク表、オブジェクトカード、オブジェクト表で、オブジェクト名とタスク名の前に #8 / #8.3 のチップを表示します。",
|
||||
"row_actions_buttons_compact": "ボタン(スマートフォンではアイコンのみ)",
|
||||
"row_actions_buttons": "テキスト付きボタン",
|
||||
"row_actions_icons": "アイコン(従来)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "スヌーズ時間(時間)",
|
||||
"settings_budget": "予算",
|
||||
"settings_currency": "通貨",
|
||||
"settings_currency_decimals": "金額の小数点以下の桁数",
|
||||
"settings_currency_decimals_hint": "各金額に表示する小数点以下の桁数:KPI、予算、リストやレポートの費用、予算アラート。0 = 整数。",
|
||||
"settings_budget_monthly": "月間予算",
|
||||
"settings_budget_yearly": "年間予算",
|
||||
"settings_budget_alerts": "予算アラート",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAMLをエクスポート",
|
||||
"settings_export_csv": "CSVをエクスポート",
|
||||
"settings_export_settings": "設定をエクスポート(JSON)",
|
||||
"settings_import_csv": "CSVをインポート",
|
||||
"settings_import_placeholder": "JSONまたはCSVの内容をここに貼り付け…",
|
||||
"settings_import_btn": "インポート",
|
||||
"settings_import_success": "{count}件の対象を正常にインポートしました。",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "タスクごとの完了時HAアクション+事前設定値付きのクイック完了QR。",
|
||||
"on_complete_action_title": "完了時: HAアクションを実行(任意)",
|
||||
"on_complete_action_desc": "タスク完了時にHAサービスを呼び出します — 例: デバイスのカウンターをリセット。",
|
||||
"on_complete_action_service": "サービス",
|
||||
"on_complete_action_target": "対象エンティティ",
|
||||
"on_complete_action_target_hint": "注意: エンティティのドメインはサービスと一致する必要があります — 例: 'button.press'はbutton.*のみ、'counter.increment'はcounter.*のみ、'input_button.press'はinput_button.*のみで動作します。不一致の場合、アクションは何も表示せずに失敗します(HAのログに「Referenced entities ... missing or not currently available」と記録されます)。",
|
||||
"on_complete_action_data": "データ(JSON、任意)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "写真を選択",
|
||||
"choose_photo": "写真を選択",
|
||||
"photos_android_hint": "Android アプリは1回の選択につき写真1枚を渡します。選択するたびに追加されます。",
|
||||
"add_photos": "写真を追加",
|
||||
"photos_limit": "1回の完了につき最大{max}枚",
|
||||
"history_edit_photos_hint": "ここで外した写真は対象のドキュメントに残ります。",
|
||||
"uploading": "アップロード中…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "ダウンロード",
|
||||
"doc_close": "閉じる",
|
||||
"doc_camera": "写真を撮る",
|
||||
"camera_capture_shoot": "撮影",
|
||||
"camera_switch_lens": "カメラを切り替え",
|
||||
"doc_drop_hint": "ここにファイルをドロップ",
|
||||
"doc_task_none": "このタスクに紐付いたドキュメントはありません。",
|
||||
"doc_link_existing": "ドキュメントを紐付け…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "トリガーが自然に解消されたとき(塩の補充、フィルター交換など)に完了を記録し、最終実施日を更新します。",
|
||||
"doc_search": "ドキュメントを検索…",
|
||||
"doc_search_none": "一致するドキュメントはありません",
|
||||
"doc_description": "説明(任意)",
|
||||
"doc_sort": "文書を並べ替え",
|
||||
"doc_sort_newest": "新しい順",
|
||||
"doc_sort_oldest": "古い順",
|
||||
"doc_sort_title": "タイトル順(1、2、3…)",
|
||||
"doc_sort_category": "カテゴリー順",
|
||||
"link_device_optional": "既存のデバイスにリンク(任意)",
|
||||
"parent_object_optional": "親オブジェクト(任意)",
|
||||
"parent_none": "(親なし)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "このビューのみ通知",
|
||||
"settings_notify_scope_all": "すべてのタスク",
|
||||
"settings_notify_scope_hint": "選択した保存済みビューのラベル/ユーザーフィルターに一致するタスクのみがリマインダーを送信します。ビューのステータス・並び順・グループ化はここでは無視されます。",
|
||||
"settings_notify_completed": "完了通知",
|
||||
"settings_notify_completed_hint": "タスクが完了したときに、理由と実行者を添えて家族に知らせます。「自動のみ」は、その場で誰も操作していない完了だけを通知します:センサーの回復、買い物リストのチェック、オートメーションのサービス呼び出し。家庭用サービスにのみ送信され、静音時間と 1 日の上限が適用されます。",
|
||||
"notify_completed_off": "オフ",
|
||||
"notify_completed_automatic": "自動完了のみ",
|
||||
"notify_completed_all": "すべての完了",
|
||||
"settings_notify_rule": "独自の通知ルール",
|
||||
"settings_notify_rule_hint": "各通知はイベント maintenance_supporter_notification(種類、状態、対象、タスク、参照番号、優先度、ディープリンク、ペイロード)も発火します。オートメーションで Ticker、Telegram、Pushover などへ転送してください。",
|
||||
"settings_notify_event_only": "イベントのみ発火し、自身では送信しない",
|
||||
"settings_notify_event_only_hint": "配信はすべてオートメーション側で行います。繰り返し間隔、静音時間、1日の上限は引き続きイベント発火の条件になります。",
|
||||
"settings_notify_extra_data": "追加の通知データ(テンプレート)",
|
||||
"settings_notify_extra_data_hint": "JSON または YAML、Jinja 可。notify 呼び出しの data にマージされます(あなたのキーが優先)。変数: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message。「テスト送信」ボタンで確認できます。",
|
||||
"settings_notify_extra_data_hint": "JSON または YAML、Jinja 可。notify 呼び出しの data にマージされます(あなたのキーが優先)。変数: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message。「テスト送信」ボタンで確認できます。",
|
||||
"card_saved_view": "保存済みビュー",
|
||||
"card_saved_view_none": "なし",
|
||||
"card_saved_view_help": "上記のフィルターに加えて、ビューのステータス・ユーザー・ラベルのフィルターを適用します。ビューの並び順とグループ化はパネルの表示設定であり、カードには適用されません。",
|
||||
"panel_card_tab": "開くタブ",
|
||||
"panel_card_tab_default": "記憶したタブ(パネルと同じ)",
|
||||
"panel_card_view": "保存済みビューで開く",
|
||||
"panel_card_height": "高さ",
|
||||
"panel_card_height_help": "空欄 = カードの下の画面を埋めます(パネルビュー)。それ以外は、他のカードがあるビュー向けに 600px や 80vh などの CSS の長さを指定します。",
|
||||
"panel_card_not_registered": "Maintenance Supporter パネルを利用できません。統合は設定済みですか?",
|
||||
"panel_card_load_failed": "Maintenance Supporter パネルを読み込めませんでした。ページを再読み込みしてください(更新後はブラウザーのキャッシュを消去)。",
|
||||
"doc_part_none": "この部品にリンクされた文書はありません。",
|
||||
"settings_templates_toggle_group": "このグループのテンプレートをすべて有効/無効にする",
|
||||
"setups_button": "おすすめセットアップ",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "良好",
|
||||
"battery_fleet_status_due": "交換時期",
|
||||
"battery_fleet_predicted_on": "{date} 頃の見込み",
|
||||
"battery_fleet_predicted_typical": "{date} 頃の見込み — {type} の標準 {months} か月({source})",
|
||||
"lifetime_source_default": "内蔵の既定値",
|
||||
"lifetime_source_table": "内蔵テーブル",
|
||||
"lifetime_source_learned": "{n} 回の交換から学習",
|
||||
"lifetime_source_override": "あなたの設定",
|
||||
"lifetime_source_learned_device": "この機器の {n} 回の交換から学習",
|
||||
"lifetime_source_learned_model": "同じ型番の機器の {n} 回の交換から学習",
|
||||
"battery_fleet_predicted_trend": "この電池の放電傾向から予測: {date} 頃 ({confidence})",
|
||||
"battery_fleet_rechargeable": "充電式:交換ではなく充電——買い物リストには載りません",
|
||||
"battery_fleet_sort_name": "名前順に並べ替え",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "使用部品",
|
||||
"print_inc_photos": "写真",
|
||||
"print_inc_documents": "関連文書",
|
||||
"print_inc_doc_desc": "ドキュメントの説明",
|
||||
"print_inc_checklist": "チェックリスト",
|
||||
"print_inc_notes": "メモ",
|
||||
"print_inc_costs": "費用と所要時間",
|
||||
"print_inc_person": "実施者",
|
||||
"print_inc_refs": "参照番号",
|
||||
"print_inc_bare": "詳細のない完了記録",
|
||||
"print_inc_qr": "タスクごとのQRコード",
|
||||
"print_button": "印刷",
|
||||
"report_scan_hint": "スキャンしてタスクを開く",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "가동 시간",
|
||||
"runtime_hours": "목표 가동 시간 (시간)",
|
||||
"target_value": "목표값",
|
||||
"baseline": "기준값",
|
||||
"target_changes": "목표 변경 횟수",
|
||||
"for_minutes": "지속 시간 (분)",
|
||||
"time_based": "시간 기반",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "생성일",
|
||||
"report_times_done": "수행 횟수",
|
||||
"report_total_cost": "총 비용",
|
||||
"report_every": "{n} {unit}마다",
|
||||
"report_notes": "메모",
|
||||
"report_col_type": "유형",
|
||||
"report_col_status": "상태",
|
||||
"report_col_schedule": "일정",
|
||||
"duration": "소요 시간",
|
||||
"minutes_short": "분",
|
||||
"both": "둘 다",
|
||||
"trigger_val": "트리거 값",
|
||||
"complete_title": "완료: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: 잘못된 형식입니다 (예상: {type})",
|
||||
"err_invalid_choice": "{field}: 허용되지 않는 값입니다",
|
||||
"err_invalid_value": "{field}: 유효하지 않은 값입니다",
|
||||
"ws_err_not_found": "찾을 수 없음 — 그 사이에 삭제되었을 수 있습니다",
|
||||
"ws_err_invalid_input": "잘못된 입력",
|
||||
"ws_err_invalid_date": "잘못된 날짜",
|
||||
"ws_err_invalid_format": "잘못된 형식",
|
||||
"ws_err_invalid_url": "잘못된 링크",
|
||||
"ws_err_not_configured": "아직 설정되지 않음",
|
||||
"ws_err_not_loaded": "아직 로드되지 않음 — 잠시 후 다시 시도하세요",
|
||||
"ws_err_limit_reached": "한도에 도달함",
|
||||
"ws_err_too_many": "항목이 너무 많음",
|
||||
"ws_err_too_large": "너무 큼",
|
||||
"ws_err_not_archived": "보관되지 않음",
|
||||
"ws_err_invalid_target": "잘못된 대상",
|
||||
"ws_err_create_failed": "생성할 수 없음",
|
||||
"ws_err_archived": "보관됨 — 먼저 복원하세요",
|
||||
"ws_err_already_archived": "이미 보관됨",
|
||||
"ws_err_unavailable": "현재 사용할 수 없음",
|
||||
"ws_err_unauthorized": "이 사용자에게는 허용되지 않음",
|
||||
"ws_err_too_many_views": "저장된 보기가 너무 많음",
|
||||
"ws_err_storage_unavailable": "저장소를 사용할 수 없음",
|
||||
"ws_err_replace_failed": "교체 실패",
|
||||
"ws_err_not_paused": "일시 중지되지 않음",
|
||||
"ws_err_not_available": "사용 가능한 항목 없음",
|
||||
"ws_err_no_url": "링크가 지정되지 않음",
|
||||
"ws_err_no_phases": "이 작업에는 단계가 없음",
|
||||
"ws_err_invalid_view": "잘못된 보기",
|
||||
"ws_err_invalid_user": "알 수 없는 사용자",
|
||||
"ws_err_invalid_range": "잘못된 범위",
|
||||
"ws_err_invalid_parent": "잘못된 상위 객체",
|
||||
"ws_err_invalid_device": "알 수 없는 장치",
|
||||
"ws_err_empty_csv": "CSV 파일이 비어 있음",
|
||||
"ws_err_empty": "가져올 항목 없음",
|
||||
"ws_err_duplicate_failed": "복제할 수 없음",
|
||||
"ws_err_already_paused": "이미 일시 중지됨",
|
||||
"ws_err_invalid_cursor": "잘못된 단계 위치",
|
||||
"ws_err_invalid_entity_slug": "잘못된 엔터티 이름",
|
||||
"ws_err_invalid_trigger_config": "잘못된 트리거 구성",
|
||||
"ws_err_no_defaults": "빠른 완료 기본값이 설정되지 않음",
|
||||
"ws_err_self_link_device": "객체는 자기 자신에게 연결할 수 없음",
|
||||
"ws_err_too_early": "너무 이름 — 이 작업은 기한에 가까워져야 완료할 수 있습니다",
|
||||
"feat_schedule_time": "시각 지정 일정",
|
||||
"feat_schedule_time_desc": "작업이 자정이 아닌 지정한 시각에 기한 초과로 전환됩니다.",
|
||||
"schedule_time_toggle": "특정 시각에 기한",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "값이 같으면 트리거 (=)",
|
||||
"trigger_not_equals": "값이 다르면 트리거 (≠)",
|
||||
"for_at_least_minutes": "최소 지속 시간 (분)",
|
||||
"safety_interval_days": "안전 주기 (일, 선택)",
|
||||
"safety_interval": "안전 주기 (선택)",
|
||||
"trigger_combinator": "트리거와 간격 결합",
|
||||
"trigger_combinator_any": "트리거 또는 간격 (먼저 충족)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "내 작업",
|
||||
"tab_calendar": "캘린더",
|
||||
"cal_no_events": "유지보수 없음",
|
||||
"cal_window_7": "7일",
|
||||
"cal_window_14": "14일",
|
||||
"cal_window_30": "30일",
|
||||
"cal_window_365": "1년",
|
||||
"cal_every_n_days": "{n}일마다",
|
||||
"cal_source_time": "시간 기반",
|
||||
"cal_source_time_adaptive": "시간 기반 (적응형)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR 코드 생성에 실패했습니다.",
|
||||
"qr_error_no_url": "설정된 HA URL이 없습니다. 설정 → 시스템 → 네트워크에서 외부 또는 내부 URL을 설정해 주세요.",
|
||||
"save_error": "저장에 실패했습니다. 다시 시도해 주세요.",
|
||||
"subsave_warning": "저장되었지만 설정 하나가 거부되었습니다: {detail}",
|
||||
"qr_print": "인쇄",
|
||||
"qr_download": "SVG 다운로드",
|
||||
"qr_action": "스캔 시 동작",
|
||||
"qr_action_view": "유지보수 정보 보기",
|
||||
"qr_action_complete": "유지보수 완료로 표시",
|
||||
"qr_url_mode": "링크 유형",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "로컬 (mDNS)",
|
||||
"qr_mode_server": "서버 URL",
|
||||
"overview": "개요",
|
||||
"analysis": "분석",
|
||||
"recent_activities": "최근 활동",
|
||||
"search_notes": "메모 검색",
|
||||
"avg_cost": "평균 비용",
|
||||
"no_advanced_features": "활성화된 고급 기능 없음",
|
||||
"no_advanced_features_hint": "통합구성요소 설정에서 “적응형 일정” 또는 “계절 패턴”을 활성화하면 여기에서 분석 데이터를 볼 수 있습니다.",
|
||||
"analysis_not_enough_data": "아직 분석할 데이터가 충분하지 않습니다.",
|
||||
"analysis_not_enough_data_hint": "Weibull 분석에는 최소 5회의 완료 기록이 필요하며, 계절 패턴은 월별 6개 이상의 데이터 포인트가 쌓이면 표시됩니다.",
|
||||
"analysis_manual_task_hint": "주기가 없는 수동 작업은 분석 데이터를 생성하지 않습니다.",
|
||||
"completions": "회 완료",
|
||||
"current": "현재",
|
||||
"shorter": "더 짧게",
|
||||
"longer": "더 길게",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "이전 기간",
|
||||
"cal_forward_windows": "이후 기간",
|
||||
"history_edit_title": "기록 항목 편집",
|
||||
"history_delete_entry": "기록 삭제",
|
||||
"history_delete_confirm": "이 기록을 삭제할까요? 마지막 수행일은 이전 완료로 되돌아가며, 사진은 개체에 남고 사용한 부품은 재고로 복구되지 않습니다.",
|
||||
"history_edit_timestamp": "타임스탬프",
|
||||
"manufacturer": "제조사",
|
||||
"model": "모델",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "기본 경고 일수",
|
||||
"settings_consumable_threshold": "소모품 „부족“ 임계값 (%)",
|
||||
"settings_battery_low_percent": "배터리 „부족“ 임계값 (%)",
|
||||
"settings_battery_lifetimes": "배터리 일반 수명",
|
||||
"settings_battery_lifetimes_hint": "백분율이 없는 배터리에만 사용됩니다: 종류와 교체일만 있는 Battery Notes 메모나 \"낮음\"만 알리는 센서입니다. 만기 = 마지막 교체 + 이 수명. 잔량을 보고하는 배터리는 실측 방전으로 예측합니다. 같은 종류의 교체를 3회 기록하면 중앙값 간격이 내장값을 대체합니다. 직접 설정한 값이 항상 우선합니다.",
|
||||
"settings_battery_lifetime_months": "개월",
|
||||
"settings_battery_lifetime_reset": "기본값 사용",
|
||||
"settings_battery_lifetime_in_fleet": "내 장치에 사용 중",
|
||||
"settings_battery_lifetime_learned_models": "모델별 학습값: {list}",
|
||||
"settings_thresholds_hint": "새 추천 설정(남은 비율)과 자체 Battery Notes 임계값이 없는 배터리 목록 배터리의 기본값입니다. 기존 작업은 값을 유지합니다.",
|
||||
"settings_value_out_of_range": "값은 {min}에서 {max} 사이여야 합니다",
|
||||
"bn_summary": "{name}: 배터리 {n}개에 {pct} %",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "자체 임계값이 있는 기기 {n}개:",
|
||||
"bn_more": "+ {n}개 더",
|
||||
"settings_row_actions": "작업 행 동작",
|
||||
"settings_ref_numbers_in_lists": "목록에 참조 번호 표시",
|
||||
"settings_ref_numbers_in_lists_hint": "오늘 목록, 작업 표, 개체 카드, 개체 표에서 개체 및 작업 이름 앞에 #8 / #8.3 칩을 표시합니다.",
|
||||
"row_actions_buttons_compact": "버튼 (휴대폰에서는 아이콘만)",
|
||||
"row_actions_buttons": "텍스트가 있는 버튼",
|
||||
"row_actions_icons": "아이콘 (기본)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "다시 알림 대기 시간 (시간)",
|
||||
"settings_budget": "예산",
|
||||
"settings_currency": "통화",
|
||||
"settings_currency_decimals": "금액 소수 자릿수",
|
||||
"settings_currency_decimals_hint": "모든 금액에 표시할 소수 자릿수: KPI, 예산, 목록과 보고서의 비용, 예산 알림. 0 = 정수.",
|
||||
"settings_budget_monthly": "월 예산",
|
||||
"settings_budget_yearly": "연 예산",
|
||||
"settings_budget_alerts": "예산 경고",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML 내보내기",
|
||||
"settings_export_csv": "CSV 내보내기",
|
||||
"settings_export_settings": "설정 내보내기 (JSON)",
|
||||
"settings_import_csv": "CSV 가져오기",
|
||||
"settings_import_placeholder": "JSON 또는 CSV 내용을 여기에 붙여넣으세요…",
|
||||
"settings_import_btn": "가져오기",
|
||||
"settings_import_success": "객체 {count}개를 성공적으로 가져왔습니다.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "완료 시 작업별 HA 동작 실행 + 사전 설정값이 있는 빠른 완료 QR.",
|
||||
"on_complete_action_title": "완료 시: HA 동작 실행 (선택)",
|
||||
"on_complete_action_desc": "작업이 완료되면 HA 서비스를 호출합니다 — 예: 기기의 카운터 초기화.",
|
||||
"on_complete_action_service": "서비스",
|
||||
"on_complete_action_target": "대상 엔티티",
|
||||
"on_complete_action_target_hint": "참고: 엔티티 도메인이 서비스와 일치해야 합니다 — 예: 'button.press'는 button.*에서만, 'counter.increment'는 counter.*에서만, 'input_button.press'는 input_button.*에서만 동작합니다. 불일치 시 동작이 조용히 실패합니다 (HA 로그에 'Referenced entities ... missing or not currently available' 기록).",
|
||||
"on_complete_action_data": "데이터 (JSON, 선택)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "사진 선택",
|
||||
"choose_photo": "사진 선택",
|
||||
"photos_android_hint": "Android 앱은 선택할 때마다 사진 한 장씩 전달합니다. 선택할 때마다 추가됩니다.",
|
||||
"add_photos": "사진 추가",
|
||||
"photos_limit": "완료당 최대 {max}장",
|
||||
"history_edit_photos_hint": "여기서 제거한 사진은 대상의 문서에 그대로 남습니다.",
|
||||
"uploading": "업로드 중…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "다운로드",
|
||||
"doc_close": "닫기",
|
||||
"doc_camera": "사진 촬영",
|
||||
"camera_capture_shoot": "촬영",
|
||||
"camera_switch_lens": "카메라 전환",
|
||||
"doc_drop_hint": "파일을 여기에 놓으세요",
|
||||
"doc_task_none": "이 작업에 연결된 문서가 없습니다.",
|
||||
"doc_link_existing": "문서 연결…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "트리거가 스스로 해제되면 완료를 기록합니다 (마지막 수행일 갱신) — 예: 소금 보충, 필터 교체.",
|
||||
"doc_search": "문서 검색…",
|
||||
"doc_search_none": "일치하는 문서 없음",
|
||||
"doc_description": "설명(선택)",
|
||||
"doc_sort": "문서 정렬",
|
||||
"doc_sort_newest": "최신순",
|
||||
"doc_sort_oldest": "오래된순",
|
||||
"doc_sort_title": "제목순(1, 2, 3 …)",
|
||||
"doc_sort_category": "카테고리순",
|
||||
"link_device_optional": "기존 기기와 연결 (선택)",
|
||||
"parent_object_optional": "상위 객체 (선택)",
|
||||
"parent_none": "(상위 없음)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "특정 보기만 알림",
|
||||
"settings_notify_scope_all": "모든 작업",
|
||||
"settings_notify_scope_hint": "선택한 저장된 보기의 라벨/사용자 필터와 일치하는 작업만 알림을 보냅니다. 보기의 상태·정렬·그룹화는 여기서 무시됩니다.",
|
||||
"settings_notify_completed": "완료 알림",
|
||||
"settings_notify_completed_hint": "작업이 완료되면 사유와 실행자를 함께 가족에게 알립니다. \"자동만\"은 현장에서 아무도 하지 않은 완료만 알립니다: 센서 복구, 쇼핑 목록 체크, 자동화의 서비스 호출. 가정용 서비스로만 전송되며 방해 금지 시간과 일일 한도가 적용됩니다.",
|
||||
"notify_completed_off": "끄기",
|
||||
"notify_completed_automatic": "자동 완료만",
|
||||
"notify_completed_all": "모든 완료",
|
||||
"settings_notify_rule": "나만의 알림 규칙",
|
||||
"settings_notify_rule_hint": "모든 알림은 maintenance_supporter_notification 이벤트도 발생시킵니다(종류, 상태, 대상, 작업, 참조 번호, 우선순위, 딥 링크, 페이로드). 자동화로 라우팅하세요 — 예: Ticker, Telegram, Pushover.",
|
||||
"settings_notify_event_only": "이벤트만 발생시키고 직접 보내지 않음",
|
||||
"settings_notify_event_only_hint": "전달은 전적으로 자동화가 담당합니다. 반복 간격, 방해 금지 시간, 일일 한도는 여전히 이벤트 발생 시점에 적용됩니다.",
|
||||
"settings_notify_extra_data": "추가 알림 데이터(템플릿)",
|
||||
"settings_notify_extra_data_hint": "JSON 또는 YAML, Jinja 사용 가능. notify 호출의 data에 병합됩니다(사용자 키 우선). 변수: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. 테스트 보내기 버튼으로 확인하세요.",
|
||||
"settings_notify_extra_data_hint": "JSON 또는 YAML, Jinja 사용 가능. notify 호출의 data에 병합됩니다(사용자 키 우선). 변수: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. 테스트 보내기 버튼으로 확인하세요.",
|
||||
"card_saved_view": "저장된 보기",
|
||||
"card_saved_view_none": "없음",
|
||||
"card_saved_view_help": "위 필터에 더해 보기의 상태·사용자·라벨 필터를 적용합니다. 보기의 정렬과 그룹화는 패널 표시 설정이며 카드에는 적용되지 않습니다.",
|
||||
"panel_card_tab": "열 때 탭",
|
||||
"panel_card_tab_default": "기억된 탭(패널과 동일)",
|
||||
"panel_card_view": "저장된 보기로 열기",
|
||||
"panel_card_height": "높이",
|
||||
"panel_card_height_help": "비워 두면 카드 아래 화면을 채웁니다(패널 보기). 그 외에는 다른 카드가 있는 보기를 위해 600px 또는 80vh 같은 CSS 길이를 지정합니다.",
|
||||
"panel_card_not_registered": "Maintenance Supporter 패널을 사용할 수 없습니다. 통합이 설정되어 있나요?",
|
||||
"panel_card_load_failed": "Maintenance Supporter 패널을 불러오지 못했습니다. 페이지를 새로 고치세요(업데이트 후에는 브라우저 캐시를 지우세요).",
|
||||
"doc_part_none": "이 부품에 연결된 문서가 없습니다.",
|
||||
"settings_templates_toggle_group": "이 그룹의 모든 템플릿을 켜거나 끄기",
|
||||
"setups_button": "추천 설정",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "정상",
|
||||
"battery_fleet_status_due": "교체 시기",
|
||||
"battery_fleet_predicted_on": "{date}쯤 예상",
|
||||
"battery_fleet_predicted_typical": "{date}경 예상 — {type} 기준 보통 {months}개월({source})",
|
||||
"lifetime_source_default": "기본 내장값",
|
||||
"lifetime_source_table": "기본 내장 표",
|
||||
"lifetime_source_learned": "{n}회 교체에서 학습",
|
||||
"lifetime_source_override": "사용자 설정",
|
||||
"lifetime_source_learned_device": "이 기기의 {n}회 교체에서 학습",
|
||||
"lifetime_source_learned_model": "같은 모델 기기의 {n}회 교체에서 학습",
|
||||
"battery_fleet_predicted_trend": "이 배터리의 방전 추세로 예측: 약 {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "충전식: 교체 대신 충전 — 쇼핑 목록에 오르지 않습니다",
|
||||
"battery_fleet_sort_name": "이름순 정렬",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "사용 부품",
|
||||
"print_inc_photos": "사진",
|
||||
"print_inc_documents": "연결된 문서",
|
||||
"print_inc_doc_desc": "문서 설명",
|
||||
"print_inc_checklist": "체크리스트",
|
||||
"print_inc_notes": "메모",
|
||||
"print_inc_costs": "비용 및 소요 시간",
|
||||
"print_inc_person": "완료자",
|
||||
"print_inc_refs": "참조 번호",
|
||||
"print_inc_bare": "세부 정보 없는 완료 기록",
|
||||
"print_inc_qr": "작업별 QR 코드",
|
||||
"print_button": "인쇄",
|
||||
"report_scan_hint": "스캔하여 작업 열기",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Driftstid",
|
||||
"runtime_hours": "Måldriftstid (timer)",
|
||||
"target_value": "Målverdi",
|
||||
"baseline": "Utgangspunkt",
|
||||
"target_changes": "Antall endringer",
|
||||
"for_minutes": "I (minutter)",
|
||||
"time_based": "Tidsbasert",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Generert",
|
||||
"report_times_done": "Utført",
|
||||
"report_total_cost": "Totalkostnad",
|
||||
"report_every": "hver {n}. {unit}",
|
||||
"report_notes": "Notater",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Tidsplan",
|
||||
"duration": "Varighet",
|
||||
"minutes_short": "min",
|
||||
"both": "Begge",
|
||||
"trigger_val": "Utløserverdi",
|
||||
"complete_title": "Fullfør: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: feil type (forventet: {type})",
|
||||
"err_invalid_choice": "{field}: ikke en tillatt verdi",
|
||||
"err_invalid_value": "{field}: ugyldig verdi",
|
||||
"ws_err_not_found": "Ikke funnet — det kan ha blitt slettet i mellomtiden",
|
||||
"ws_err_invalid_input": "Ugyldig inndata",
|
||||
"ws_err_invalid_date": "Ugyldig dato",
|
||||
"ws_err_invalid_format": "Ugyldig format",
|
||||
"ws_err_invalid_url": "Ugyldig lenke",
|
||||
"ws_err_not_configured": "Ikke satt opp ennå",
|
||||
"ws_err_not_loaded": "Ikke lastet ennå — prøv igjen om et øyeblikk",
|
||||
"ws_err_limit_reached": "Grensen er nådd",
|
||||
"ws_err_too_many": "For mange oppføringer",
|
||||
"ws_err_too_large": "For stort",
|
||||
"ws_err_not_archived": "Ikke arkivert",
|
||||
"ws_err_invalid_target": "Ugyldig mål",
|
||||
"ws_err_create_failed": "Kunne ikke opprettes",
|
||||
"ws_err_archived": "Arkivert — gjenopprett først",
|
||||
"ws_err_already_archived": "Allerede arkivert",
|
||||
"ws_err_unavailable": "Ikke tilgjengelig for øyeblikket",
|
||||
"ws_err_unauthorized": "Ikke tillatt for din bruker",
|
||||
"ws_err_too_many_views": "For mange lagrede visninger",
|
||||
"ws_err_storage_unavailable": "Lagringen er ikke tilgjengelig",
|
||||
"ws_err_replace_failed": "Utskifting mislyktes",
|
||||
"ws_err_not_paused": "Ikke satt på pause",
|
||||
"ws_err_not_available": "Ingenting tilgjengelig",
|
||||
"ws_err_no_url": "Ingen lenke oppgitt",
|
||||
"ws_err_no_phases": "Oppgaven har ingen faser",
|
||||
"ws_err_invalid_view": "Ugyldig visning",
|
||||
"ws_err_invalid_user": "Ukjent bruker",
|
||||
"ws_err_invalid_range": "Ugyldig område",
|
||||
"ws_err_invalid_parent": "Ugyldig overordnet objekt",
|
||||
"ws_err_invalid_device": "Ukjent enhet",
|
||||
"ws_err_empty_csv": "CSV-filen er tom",
|
||||
"ws_err_empty": "Ingenting å importere",
|
||||
"ws_err_duplicate_failed": "Kunne ikke dupliseres",
|
||||
"ws_err_already_paused": "Allerede satt på pause",
|
||||
"ws_err_invalid_cursor": "Ugyldig faseposisjon",
|
||||
"ws_err_invalid_entity_slug": "Ugyldig entitetsnavn",
|
||||
"ws_err_invalid_trigger_config": "Ugyldig utløserkonfigurasjon",
|
||||
"ws_err_no_defaults": "Ingen standardverdier for hurtigfullføring",
|
||||
"ws_err_self_link_device": "Et objekt kan ikke kobles til seg selv",
|
||||
"ws_err_too_early": "For tidlig — oppgaven kan først fullføres nærmere forfallsdatoen",
|
||||
"feat_schedule_time": "Planlegging med klokkeslett",
|
||||
"feat_schedule_time_desc": "Oppgaver forfaller på et bestemt klokkeslett i stedet for ved midnatt.",
|
||||
"schedule_time_toggle": "Forfaller på et bestemt klokkeslett",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Utløs ved lik (=)",
|
||||
"trigger_not_equals": "Utløs ved forskjellig fra (≠)",
|
||||
"for_at_least_minutes": "I minst (minutter)",
|
||||
"safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)",
|
||||
"safety_interval": "Sikkerhetsintervall (valgfritt)",
|
||||
"trigger_combinator": "Kombiner utløser og intervall",
|
||||
"trigger_combinator_any": "Utløser eller intervall (først oppfylt)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Mine oppgaver",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Ingen vedlikehold",
|
||||
"cal_window_7": "7 dager",
|
||||
"cal_window_14": "14 dager",
|
||||
"cal_window_30": "30 dager",
|
||||
"cal_window_365": "1 år",
|
||||
"cal_every_n_days": "hver {n}. dag",
|
||||
"cal_source_time": "Tidsbasert",
|
||||
"cal_source_time_adaptive": "Tidsbasert (adaptiv)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "Kunne ikke generere QR-kode.",
|
||||
"qr_error_no_url": "Ingen HA-URL konfigurert. Angi en ekstern eller intern URL under Innstillinger → System → Nettverk.",
|
||||
"save_error": "Kunne ikke lagre. Prøv igjen.",
|
||||
"subsave_warning": "Lagret, men én innstilling ble avvist: {detail}",
|
||||
"qr_print": "Skriv ut",
|
||||
"qr_download": "Last ned SVG",
|
||||
"qr_action": "Handling ved skanning",
|
||||
"qr_action_view": "Vis vedlikeholdsinfo",
|
||||
"qr_action_complete": "Merk vedlikehold som fullført",
|
||||
"qr_url_mode": "Lenketype",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Lokal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Oversikt",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Nylige aktiviteter",
|
||||
"search_notes": "Søk i notater",
|
||||
"avg_cost": "Gj.snittlig kostnad",
|
||||
"no_advanced_features": "Ingen avanserte funksjoner aktivert",
|
||||
"no_advanced_features_hint": "Aktiver «Adaptive intervaller» eller «Sesongmønstre» i integrasjonsinnstillingene for å se analysedata her.",
|
||||
"analysis_not_enough_data": "Ikke nok data for analyse ennå.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analyse krever minst 5 fullførte vedlikehold; sesongmønstre blir synlige etter 6+ datapunkter per måned.",
|
||||
"analysis_manual_task_hint": "Manuelle oppgaver uten intervall genererer ikke analysedata.",
|
||||
"completions": "fullføringer",
|
||||
"current": "Nåværende",
|
||||
"shorter": "Kortere",
|
||||
"longer": "Lengre",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Tidligere vinduer",
|
||||
"cal_forward_windows": "Fremtidige vinduer",
|
||||
"history_edit_title": "Rediger historikkoppføring",
|
||||
"history_delete_entry": "Slett oppføring",
|
||||
"history_delete_confirm": "Slette denne historikkoppføringen? Datoen for siste utførelse faller tilbake til den forrige; bilder blir hos objektet, og forbrukte deler legges ikke tilbake på lager.",
|
||||
"history_edit_timestamp": "Tidsstempel",
|
||||
"manufacturer": "Produsent",
|
||||
"model": "Modell",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Standard varslingsdager",
|
||||
"settings_consumable_threshold": "Terskel „forbruksvare lav“ (%)",
|
||||
"settings_battery_low_percent": "Terskel „batteri lavt“ (%)",
|
||||
"settings_battery_lifetimes": "Typiske batterilevetider",
|
||||
"settings_battery_lifetimes_hint": "Brukes bare for batterier uten prosent: et Battery Notes-notat med bare type og byttedato, eller en sensor som bare melder «lavt». Forfall = siste bytte + denne levetiden. Batterier som melder nivå får prognosen fra målt utlading. Når flåten har sett tre bytter av en type, overtar medianintervallet fra den innebygde verdien — din egen verdi vinner alltid.",
|
||||
"settings_battery_lifetime_months": "måneder",
|
||||
"settings_battery_lifetime_reset": "Bruk standard",
|
||||
"settings_battery_lifetime_in_fleet": "i flåten din",
|
||||
"settings_battery_lifetime_learned_models": "Lært per modell: {list}",
|
||||
"settings_thresholds_hint": "Standardverdier for nye Foreslåtte oppsett (gjenværende prosent) og for flåtebatterier uten egen Battery Notes-terskel. Eksisterende oppgaver beholder verdien sin.",
|
||||
"settings_value_out_of_range": "Verdien må være mellom {min} og {max}",
|
||||
"bn_summary": "{name}: {pct} % for {n} batterier",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} enheter med egen terskel:",
|
||||
"bn_more": "+ {n} til",
|
||||
"settings_row_actions": "Handlinger i oppgaverader",
|
||||
"settings_ref_numbers_in_lists": "Referansenumre i lister",
|
||||
"settings_ref_numbers_in_lists_hint": "Viser brikkene #8 / #8.3 foran objekt- og oppgavenavn i I dag-listen, oppgavetabellen, objektkortene og objekttabellen.",
|
||||
"row_actions_buttons_compact": "Knapper (bare ikoner på mobil)",
|
||||
"row_actions_buttons": "Knapper med tekst",
|
||||
"row_actions_icons": "Ikoner (klassisk)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Slumrevarighet (timer)",
|
||||
"settings_budget": "Budsjett",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Desimaler for beløp",
|
||||
"settings_currency_decimals_hint": "Hvor mange desimaler hvert beløp viser: nøkkeltall, budsjetter, kostnader i lister og rapporter, budsjettvarsler. 0 = hele tall.",
|
||||
"settings_budget_monthly": "Månedsbudsjett",
|
||||
"settings_budget_yearly": "Årsbudsjett",
|
||||
"settings_budget_alerts": "Budsjettvarsler",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Eksporter YAML",
|
||||
"settings_export_csv": "Eksporter CSV",
|
||||
"settings_export_settings": "Eksporter innstillinger (JSON)",
|
||||
"settings_import_csv": "Importer CSV",
|
||||
"settings_import_placeholder": "Lim inn JSON- eller CSV-innhold her…",
|
||||
"settings_import_btn": "Importer",
|
||||
"settings_import_success": "{count} objekter importert.",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "HA-handling per oppgave ved fullføring + hurtigfullføring med QR og forhåndsinnstilte verdier.",
|
||||
"on_complete_action_title": "Ved fullføring: utløs HA-handling (valgfritt)",
|
||||
"on_complete_action_desc": "Kaller en HA-tjeneste når oppgaven fullføres — f.eks. tilbakestille en teller på enheten.",
|
||||
"on_complete_action_service": "Tjeneste",
|
||||
"on_complete_action_target": "Målentitet",
|
||||
"on_complete_action_target_hint": "Merk: entitetsdomenet må samsvare med tjenesten — f.eks. fungerer 'button.press' kun på button.*, 'counter.increment' kun på counter.*, 'input_button.press' kun på input_button.* osv. Ved manglende samsvar mislykkes handlingen i det stille (HA logger 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Data (JSON, valgfritt)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Velg bilder",
|
||||
"choose_photo": "Velg bilde",
|
||||
"photos_android_hint": "Android-appen leverer ett bilde per valg — hvert valg legges til.",
|
||||
"add_photos": "Legg til bilder",
|
||||
"photos_limit": "Inntil {max} bilder per fullføring",
|
||||
"history_edit_photos_hint": "Et bilde som fjernes her, blir liggende i objektets dokumenter.",
|
||||
"uploading": "Laster opp…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Last ned",
|
||||
"doc_close": "Lukk",
|
||||
"doc_camera": "Ta bilde",
|
||||
"camera_capture_shoot": "Ta bilde",
|
||||
"camera_switch_lens": "Bytt kamera",
|
||||
"doc_drop_hint": "Slipp filer her",
|
||||
"doc_task_none": "Ingen dokumenter knyttet til denne oppgaven.",
|
||||
"doc_link_existing": "Knytt et dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registrerer en fullføring (setter sist utført) når utløseren løser seg selv — f.eks. salt etterfylt, filter byttet.",
|
||||
"doc_search": "Søk i dokumenter…",
|
||||
"doc_search_none": "Ingen samsvarende dokumenter",
|
||||
"doc_description": "Beskrivelse (valgfritt)",
|
||||
"doc_sort": "Sorter dokumenter",
|
||||
"doc_sort_newest": "Nyeste først",
|
||||
"doc_sort_oldest": "Eldste først",
|
||||
"doc_sort_title": "Etter tittel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Etter kategori",
|
||||
"link_device_optional": "Koble til eksisterende enhet (valgfritt)",
|
||||
"parent_object_optional": "Overordnet objekt (valgfritt)",
|
||||
"parent_none": "(Ingen overordnet)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Varsle kun for visning",
|
||||
"settings_notify_scope_all": "Alle oppgaver",
|
||||
"settings_notify_scope_hint": "Bare oppgaver som matcher etikett-/brukerfiltrene til valgt visning sender påminnelser. Visningens status, sortering og gruppering ignoreres her.",
|
||||
"settings_notify_completed": "Varsler om fullføring",
|
||||
"settings_notify_completed_hint": "En beskjed til husstanden når en oppgave er fullført, med årsak og hvem som gjorde det. «Bare automatiske» melder bare fullføringer ingen gjorde på stedet: sensorgjenoppretting, avkrysning i handlelisten, et tjenestekall fra en automasjon. Bare til husstandstjenesten; stilletimer og dagsgrensen gjelder.",
|
||||
"notify_completed_off": "Av",
|
||||
"notify_completed_automatic": "Bare automatiske fullføringer",
|
||||
"notify_completed_all": "Alle fullføringer",
|
||||
"settings_notify_rule": "Din egen varslingsregel",
|
||||
"settings_notify_rule_hint": "Hvert varsel utløser også hendelsen maintenance_supporter_notification (type, status, objekt, oppgave, referansenumre, prioritet, lenke, nyttelast). Rut den med en automasjon — f.eks. til Ticker, Telegram eller Pushover.",
|
||||
"settings_notify_event_only": "Bare utløs hendelsen, send ingenting selv",
|
||||
"settings_notify_event_only_hint": "Automasjonen din tar hele leveransen. Gjentakelsesintervaller, stille timer og dagstaket avgjør fortsatt når hendelsen utløses.",
|
||||
"settings_notify_extra_data": "Ekstra varslingsdata (mal)",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillatt; flettes inn i notify-kallets data (dine nøkler vinner). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test med knappen Send test.",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillatt; flettes inn i notify-kallets data (dine nøkler vinner). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test med knappen Send test.",
|
||||
"card_saved_view": "Lagret visning",
|
||||
"card_saved_view_none": "Ingen",
|
||||
"card_saved_view_help": "Bruker visningens status-, bruker- og etikettfiltre i tillegg til filtrene over. Visningens sortering og gruppering er panelvisningsinnstillinger og brukes ikke på kortet.",
|
||||
"panel_card_tab": "Åpne på fanen",
|
||||
"panel_card_tab_default": "Husket fane (som i panelet)",
|
||||
"panel_card_view": "Åpne med lagret visning",
|
||||
"panel_card_height": "Høyde",
|
||||
"panel_card_height_help": "Tom = fyller skjermen under kortet (panelvisning). Ellers en CSS-lengde som 600px eller 80vh for visninger med andre kort.",
|
||||
"panel_card_not_registered": "Maintenance Supporter-panelet er ikke tilgjengelig — er integrasjonen satt opp?",
|
||||
"panel_card_load_failed": "Maintenance Supporter-panelet kunne ikke lastes — last siden på nytt (etter en oppdatering: tøm nettleserens hurtigbuffer).",
|
||||
"doc_part_none": "Ingen dokumenter knyttet til denne delen.",
|
||||
"settings_templates_toggle_group": "Slå alle maler i denne gruppen på eller av",
|
||||
"setups_button": "Foreslåtte oppsett",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "I orden",
|
||||
"battery_fleet_status_due": "Forfalt",
|
||||
"battery_fleet_predicted_on": "Forventes rundt {date}",
|
||||
"battery_fleet_predicted_typical": "Forventet rundt {date} — {months} måneder typisk for {type} ({source})",
|
||||
"lifetime_source_default": "innebygd standardverdi",
|
||||
"lifetime_source_table": "innebygd tabell",
|
||||
"lifetime_source_learned": "lært fra {n} bytter",
|
||||
"lifetime_source_override": "din innstilling",
|
||||
"lifetime_source_learned_device": "lært fra {n} bytter på denne enheten",
|
||||
"lifetime_source_learned_model": "lært fra {n} bytter på enheter av denne modellen",
|
||||
"battery_fleet_predicted_trend": "Forutsagt ut fra batteriets utladingstrend: rundt {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Oppladbart: lad i stedet for å bytte — aldri på handlelisten",
|
||||
"battery_fleet_sort_name": "Sorter etter navn",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Brukte deler",
|
||||
"print_inc_photos": "Bilder",
|
||||
"print_inc_documents": "Tilknyttede dokumenter",
|
||||
"print_inc_doc_desc": "Dokumentbeskrivelser",
|
||||
"print_inc_checklist": "Sjekkliste",
|
||||
"print_inc_notes": "Notater",
|
||||
"print_inc_costs": "Kostnad og varighet",
|
||||
"print_inc_person": "Utført av",
|
||||
"print_inc_refs": "Referansenumre",
|
||||
"print_inc_bare": "Utførelser uten detaljer",
|
||||
"print_inc_qr": "QR-kode per oppgave",
|
||||
"print_button": "Skriv ut",
|
||||
"report_scan_hint": "Skann for å åpne oppgaven",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Looptijd",
|
||||
"runtime_hours": "Doellooptijd (uren)",
|
||||
"target_value": "Doelwaarde",
|
||||
"baseline": "Basislijn",
|
||||
"target_changes": "Doelwijzigingen",
|
||||
"for_minutes": "Voor (minuten)",
|
||||
"time_based": "Tijdgebaseerd",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Gegenereerd",
|
||||
"report_times_done": "Gedaan",
|
||||
"report_total_cost": "Totale kosten",
|
||||
"report_every": "elke {n} {unit}",
|
||||
"report_notes": "Notities",
|
||||
"report_col_type": "Type",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Schema",
|
||||
"duration": "Duur",
|
||||
"minutes_short": "min",
|
||||
"both": "Beide",
|
||||
"trigger_val": "Triggerwaarde",
|
||||
"complete_title": "Voltooid: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: verkeerd type (verwacht: {type})",
|
||||
"err_invalid_choice": "{field}: niet-toegestane waarde",
|
||||
"err_invalid_value": "{field}: ongeldige waarde",
|
||||
"ws_err_not_found": "Niet gevonden — het is misschien intussen verwijderd",
|
||||
"ws_err_invalid_input": "Ongeldige invoer",
|
||||
"ws_err_invalid_date": "Ongeldige datum",
|
||||
"ws_err_invalid_format": "Ongeldig formaat",
|
||||
"ws_err_invalid_url": "Ongeldige link",
|
||||
"ws_err_not_configured": "Nog niet ingesteld",
|
||||
"ws_err_not_loaded": "Nog niet geladen — probeer het zo opnieuw",
|
||||
"ws_err_limit_reached": "Limiet bereikt",
|
||||
"ws_err_too_many": "Te veel items",
|
||||
"ws_err_too_large": "Te groot",
|
||||
"ws_err_not_archived": "Niet gearchiveerd",
|
||||
"ws_err_invalid_target": "Ongeldig doel",
|
||||
"ws_err_create_failed": "Kon niet worden aangemaakt",
|
||||
"ws_err_archived": "Gearchiveerd — herstel het eerst",
|
||||
"ws_err_already_archived": "Al gearchiveerd",
|
||||
"ws_err_unavailable": "Momenteel niet beschikbaar",
|
||||
"ws_err_unauthorized": "Niet toegestaan voor jouw gebruiker",
|
||||
"ws_err_too_many_views": "Te veel opgeslagen weergaven",
|
||||
"ws_err_storage_unavailable": "Opslag is niet beschikbaar",
|
||||
"ws_err_replace_failed": "Vervangen mislukt",
|
||||
"ws_err_not_paused": "Niet gepauzeerd",
|
||||
"ws_err_not_available": "Niets beschikbaar",
|
||||
"ws_err_no_url": "Geen link opgegeven",
|
||||
"ws_err_no_phases": "Deze taak heeft geen fasen",
|
||||
"ws_err_invalid_view": "Ongeldige weergave",
|
||||
"ws_err_invalid_user": "Onbekende gebruiker",
|
||||
"ws_err_invalid_range": "Ongeldig bereik",
|
||||
"ws_err_invalid_parent": "Ongeldig bovenliggend object",
|
||||
"ws_err_invalid_device": "Onbekend apparaat",
|
||||
"ws_err_empty_csv": "Het CSV-bestand is leeg",
|
||||
"ws_err_empty": "Niets om te importeren",
|
||||
"ws_err_duplicate_failed": "Kon niet worden gedupliceerd",
|
||||
"ws_err_already_paused": "Al gepauzeerd",
|
||||
"ws_err_invalid_cursor": "Ongeldige fasepositie",
|
||||
"ws_err_invalid_entity_slug": "Ongeldige entiteitsnaam",
|
||||
"ws_err_invalid_trigger_config": "Ongeldige triggerconfiguratie",
|
||||
"ws_err_no_defaults": "Geen standaardwaarden voor snel afronden ingesteld",
|
||||
"ws_err_self_link_device": "Een object kan niet aan zichzelf worden gekoppeld",
|
||||
"ws_err_too_early": "Te vroeg — deze taak kan pas dichter bij de vervaldatum worden afgerond",
|
||||
"feat_schedule_time": "Tijd-van-dag-planning",
|
||||
"feat_schedule_time_desc": "Taken vervallen op een specifieke tijd in plaats van middernacht.",
|
||||
"schedule_time_toggle": "Vervalt op een vast tijdstip",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Activeren bij gelijk aan (=)",
|
||||
"trigger_not_equals": "Activeren bij afwijkend van (≠)",
|
||||
"for_at_least_minutes": "Voor minstens (minuten)",
|
||||
"safety_interval_days": "Veiligheidsinterval (dagen, optioneel)",
|
||||
"safety_interval": "Veiligheidsinterval (optioneel)",
|
||||
"trigger_combinator": "Trigger en interval combineren",
|
||||
"trigger_combinator_any": "Trigger of interval (eerst vervuld)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Mijn taken",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Geen onderhoud",
|
||||
"cal_window_7": "7 dagen",
|
||||
"cal_window_14": "14 dagen",
|
||||
"cal_window_30": "30 dagen",
|
||||
"cal_window_365": "1 jaar",
|
||||
"cal_every_n_days": "elke {n} dagen",
|
||||
"cal_source_time": "Tijd-gebaseerd",
|
||||
"cal_source_time_adaptive": "Tijd-gebaseerd (adaptief)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "QR-code kon niet worden gegenereerd.",
|
||||
"qr_error_no_url": "Geen HA-URL geconfigureerd. Stel een externe of interne URL in via Instellingen → Systeem → Netwerk.",
|
||||
"save_error": "Opslaan mislukt. Probeer het opnieuw.",
|
||||
"subsave_warning": "Opgeslagen, maar één instelling is geweigerd: {detail}",
|
||||
"qr_print": "Afdrukken",
|
||||
"qr_download": "SVG downloaden",
|
||||
"qr_action": "Actie bij scannen",
|
||||
"qr_action_view": "Onderhoudsinfo bekijken",
|
||||
"qr_action_complete": "Onderhoud als voltooid markeren",
|
||||
"qr_url_mode": "Linktype",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Lokaal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Overzicht",
|
||||
"analysis": "Analyse",
|
||||
"recent_activities": "Recente activiteiten",
|
||||
"search_notes": "Notities doorzoeken",
|
||||
"avg_cost": "Ø Kosten",
|
||||
"no_advanced_features": "Geen geavanceerde functies ingeschakeld",
|
||||
"no_advanced_features_hint": "Schakel „Adaptieve Intervallen” of „Seizoenpatronen” in via de integratie-instellingen om hier analysegegevens te zien.",
|
||||
"analysis_not_enough_data": "Nog niet genoeg gegevens voor analyse.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analyse vereist minstens 5 voltooide onderhoudsbeurten; seizoenspatronen worden zichtbaar na 6+ datapunten per maand.",
|
||||
"analysis_manual_task_hint": "Handmatige taken zonder interval genereren geen analysegegevens.",
|
||||
"completions": "voltooiingen",
|
||||
"current": "Huidig",
|
||||
"shorter": "Korter",
|
||||
"longer": "Langer",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Eerdere vensters",
|
||||
"cal_forward_windows": "Toekomstige vensters",
|
||||
"history_edit_title": "Geschiedenisitem bewerken",
|
||||
"history_delete_entry": "Vermelding verwijderen",
|
||||
"history_delete_confirm": "Deze geschiedenisvermelding verwijderen? De datum van laatste uitvoering valt terug op de vorige voltooiing; foto's blijven bij het object en verbruikte onderdelen worden niet teruggeboekt.",
|
||||
"history_edit_timestamp": "Tijdstempel",
|
||||
"manufacturer": "Fabrikant",
|
||||
"model": "Model",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Standaard waarschuwingsdagen",
|
||||
"settings_consumable_threshold": "Drempel „verbruiksartikel laag“ (%)",
|
||||
"settings_battery_low_percent": "Drempel „batterij laag“ (%)",
|
||||
"settings_battery_lifetimes": "Gebruikelijke batterijlevensduur",
|
||||
"settings_battery_lifetimes_hint": "Alleen gebruikt voor batterijen zonder percentage: een Battery Notes-notitie met enkel een type en een vervangdatum, of een sensor die alleen „bijna leeg“ meldt. Hun vervaldatum is laatste vervanging + deze levensduur. Batterijen die een niveau melden krijgen hun voorspelling uit de gemeten ontlading. Zodra de vloot drie vervangingen van een type heeft gezien, vervangt het mediane interval de ingebouwde waarde — je eigen waarde wint altijd.",
|
||||
"settings_battery_lifetime_months": "maanden",
|
||||
"settings_battery_lifetime_reset": "Standaard gebruiken",
|
||||
"settings_battery_lifetime_in_fleet": "in je vloot",
|
||||
"settings_battery_lifetime_learned_models": "Geleerd per model: {list}",
|
||||
"settings_thresholds_hint": "Standaardwaarden voor nieuwe Voorgestelde installaties (resterend percentage) en voor vlootbatterijen zonder eigen Battery Notes-drempel. Bestaande taken behouden hun waarde.",
|
||||
"settings_value_out_of_range": "De waarde moet tussen {min} en {max} liggen",
|
||||
"bn_summary": "{name}: {pct} % voor {n} batterijen",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} apparaten met een eigen drempel:",
|
||||
"bn_more": "+ {n} meer",
|
||||
"settings_row_actions": "Acties in taakrijen",
|
||||
"settings_ref_numbers_in_lists": "Referentienummers in lijsten",
|
||||
"settings_ref_numbers_in_lists_hint": "Toont de chips #8 / #8.3 vóór object- en taaknamen in de lijst Vandaag, de takentabel, de objectkaarten en de objectentabel.",
|
||||
"row_actions_buttons_compact": "Knoppen (op telefoons alleen pictogrammen)",
|
||||
"row_actions_buttons": "Knoppen met tekst",
|
||||
"row_actions_icons": "Pictogrammen (klassiek)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Snoozeduur (uren)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Decimalen voor bedragen",
|
||||
"settings_currency_decimals_hint": "Hoeveel decimalen elk bedrag toont: KPI's, budgetten, kosten in lijsten en rapporten, budgetmeldingen. 0 = hele getallen.",
|
||||
"settings_budget_monthly": "Maandbudget",
|
||||
"settings_budget_yearly": "Jaarbudget",
|
||||
"settings_budget_alerts": "Budgetwaarschuwingen",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "YAML exporteren",
|
||||
"settings_export_csv": "CSV exporteren",
|
||||
"settings_export_settings": "Instellingen exporteren (JSON)",
|
||||
"settings_import_csv": "CSV importeren",
|
||||
"settings_import_placeholder": "Plak JSON- of CSV-inhoud hier…",
|
||||
"settings_import_btn": "Importeren",
|
||||
"settings_import_success": "{count} objecten succesvol geïmporteerd.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Per taak HA-actie bij voltooien + snel-voltooien-QR met vooraf ingestelde waarden.",
|
||||
"on_complete_action_title": "Bij voltooien: HA-actie uitvoeren (optioneel)",
|
||||
"on_complete_action_desc": "Roept een HA-service aan wanneer de taak is voltooid — bv. een teller op het apparaat resetten.",
|
||||
"on_complete_action_service": "Service",
|
||||
"on_complete_action_target": "Doel-entiteit",
|
||||
"on_complete_action_data": "Data (JSON, optioneel)",
|
||||
"on_complete_action_test": "Actie testen",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Foto's kiezen",
|
||||
"choose_photo": "Foto kiezen",
|
||||
"photos_android_hint": "De Android-app geeft één foto per keuze door — elke keuze wordt toegevoegd.",
|
||||
"add_photos": "Foto's toevoegen",
|
||||
"photos_limit": "Maximaal {max} foto's per afronding",
|
||||
"history_edit_photos_hint": "Een hier verwijderde foto blijft bewaard in de documenten van het object.",
|
||||
"uploading": "Uploaden…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Downloaden",
|
||||
"doc_close": "Sluiten",
|
||||
"doc_camera": "Foto maken",
|
||||
"camera_capture_shoot": "Vastleggen",
|
||||
"camera_switch_lens": "Camera wisselen",
|
||||
"doc_drop_hint": "Sleep bestanden hierheen",
|
||||
"doc_task_none": "Geen documenten aan deze taak gekoppeld.",
|
||||
"doc_link_existing": "Document koppelen…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registreert een voltooiing (stelt laatst uitgevoerd in) wanneer de trigger zichzelf oplost — bijv. zout bijgevuld, filter vervangen.",
|
||||
"doc_search": "Documenten zoeken…",
|
||||
"doc_search_none": "Geen overeenkomende documenten",
|
||||
"doc_description": "Beschrijving (optioneel)",
|
||||
"doc_sort": "Documenten sorteren",
|
||||
"doc_sort_newest": "Nieuwste eerst",
|
||||
"doc_sort_oldest": "Oudste eerst",
|
||||
"doc_sort_title": "Op titel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Op categorie",
|
||||
"link_device_optional": "Koppelen aan bestaand apparaat (optioneel)",
|
||||
"parent_object_optional": "Bovenliggend object (optioneel)",
|
||||
"parent_none": "(Geen bovenliggend object)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Alleen melden voor weergave",
|
||||
"settings_notify_scope_all": "Alle taken",
|
||||
"settings_notify_scope_hint": "Alleen taken die overeenkomen met de label-/gebruikersfilters van de gekozen weergave sturen herinneringen. Status, sortering en groepering van de weergave worden hier genegeerd.",
|
||||
"settings_notify_completed": "Voltooiingsmeldingen",
|
||||
"settings_notify_completed_hint": "Huishoudnieuws wanneer een taak is voltooid, met de reden en wie het deed. \"Alleen automatisch\" meldt alleen voltooiingen die niemand ter plekke deed: sensorherstel, afvinken op de boodschappenlijst, een service-aanroep van een automatisering. Alleen naar de huishouddienst; stille uren en het daglimiet gelden.",
|
||||
"notify_completed_off": "Uit",
|
||||
"notify_completed_automatic": "Alleen automatische voltooiingen",
|
||||
"notify_completed_all": "Alle voltooiingen",
|
||||
"settings_notify_rule": "Eigen meldingsregel",
|
||||
"settings_notify_rule_hint": "Elke melding vuurt ook het event maintenance_supporter_notification af (soort, status, object, taak, referentienummers, prioriteit, deep link, payload). Stuur het door met een automatisering — bijv. naar Ticker, Telegram of Pushover.",
|
||||
"settings_notify_event_only": "Alleen het event afvuren, zelf niets verzenden",
|
||||
"settings_notify_event_only_hint": "Je automatisering verzorgt de volledige bezorging. Herhaalintervallen, stille uren en het daglimiet bepalen nog steeds wanneer het event afgaat.",
|
||||
"settings_notify_extra_data": "Extra meldingsgegevens (sjabloon)",
|
||||
"settings_notify_extra_data_hint": "JSON of YAML, Jinja toegestaan; wordt samengevoegd met de data van de notify-aanroep (jouw sleutels winnen). Variabelen: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test met de knop Test verzenden.",
|
||||
"settings_notify_extra_data_hint": "JSON of YAML, Jinja toegestaan; wordt samengevoegd met de data van de notify-aanroep (jouw sleutels winnen). Variabelen: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test met de knop Test verzenden.",
|
||||
"card_saved_view": "Opgeslagen weergave",
|
||||
"card_saved_view_none": "Geen",
|
||||
"card_saved_view_help": "Past de status-, gebruikers- en labelfilters van de weergave toe bovenop de bovenstaande filters. Sortering en groepering van de weergave zijn paneelweergave-instellingen en gelden niet voor de kaart.",
|
||||
"panel_card_tab": "Openen op tabblad",
|
||||
"panel_card_tab_default": "Onthouden tabblad (zoals in het paneel)",
|
||||
"panel_card_view": "Openen met opgeslagen weergave",
|
||||
"panel_card_height": "Hoogte",
|
||||
"panel_card_height_help": "Leeg = vult het scherm onder de kaart (paneelweergave). Anders een CSS-lengte zoals 600px of 80vh voor weergaven met andere kaarten.",
|
||||
"panel_card_not_registered": "Het Maintenance Supporter-paneel is niet beschikbaar — is de integratie ingesteld?",
|
||||
"panel_card_load_failed": "Het Maintenance Supporter-paneel kon niet worden geladen — herlaad de pagina (na een update: browsercache wissen).",
|
||||
"doc_part_none": "Geen documenten gekoppeld aan dit onderdeel.",
|
||||
"settings_templates_toggle_group": "Alle sjablonen in deze groep in- of uitschakelen",
|
||||
"setups_button": "Voorgestelde setups",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "In orde",
|
||||
"battery_fleet_status_due": "Aan de beurt",
|
||||
"battery_fleet_predicted_on": "Verwacht rond {date}",
|
||||
"battery_fleet_predicted_typical": "Verwacht rond {date} — {months} maanden gebruikelijk voor {type} ({source})",
|
||||
"lifetime_source_default": "ingebouwde standaard",
|
||||
"lifetime_source_table": "ingebouwde tabel",
|
||||
"lifetime_source_learned": "geleerd uit {n} vervangingen",
|
||||
"lifetime_source_override": "jouw instelling",
|
||||
"lifetime_source_learned_device": "geleerd uit {n} vervangingen van dit apparaat",
|
||||
"lifetime_source_learned_model": "geleerd uit {n} vervangingen bij apparaten van dit model",
|
||||
"battery_fleet_predicted_trend": "Voorspeld uit de ontladingstrend van deze batterij: rond {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Oplaadbaar: opladen in plaats van vervangen — nooit op het boodschappenlijstje",
|
||||
"battery_fleet_sort_name": "Sorteren op naam",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Gebruikte onderdelen",
|
||||
"print_inc_photos": "Foto's",
|
||||
"print_inc_documents": "Gekoppelde documenten",
|
||||
"print_inc_doc_desc": "Documentbeschrijvingen",
|
||||
"print_inc_checklist": "Controlelijst",
|
||||
"print_inc_notes": "Notities",
|
||||
"print_inc_costs": "Kosten en duur",
|
||||
"print_inc_person": "Uitgevoerd door",
|
||||
"print_inc_refs": "Referentienummers",
|
||||
"print_inc_bare": "Voltooiingen zonder details",
|
||||
"print_inc_qr": "QR-code per taak",
|
||||
"print_button": "Afdrukken",
|
||||
"report_scan_hint": "Scan om de taak te openen",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Czas pracy",
|
||||
"runtime_hours": "Docelowy czas pracy (godziny)",
|
||||
"target_value": "Wartość docelowa",
|
||||
"baseline": "Wartość bazowa",
|
||||
"target_changes": "Docelowa liczba zmian",
|
||||
"for_minutes": "Przez (minuty)",
|
||||
"time_based": "Czasowy",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Wygenerowano",
|
||||
"report_times_done": "Wykonano",
|
||||
"report_total_cost": "Koszt całkowity",
|
||||
"report_every": "co {n} {unit}",
|
||||
"report_notes": "Notatki",
|
||||
"report_col_type": "Typ",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Harmonogram",
|
||||
"duration": "Czas trwania",
|
||||
"minutes_short": "min",
|
||||
"both": "Oba",
|
||||
"trigger_val": "Wartość wyzwalacza",
|
||||
"complete_title": "Wykonaj: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: zły typ (oczekiwano: {type})",
|
||||
"err_invalid_choice": "{field}: niedozwolona wartość",
|
||||
"err_invalid_value": "{field}: nieprawidłowa wartość",
|
||||
"ws_err_not_found": "Nie znaleziono — być może zostało w międzyczasie usunięte",
|
||||
"ws_err_invalid_input": "Nieprawidłowe dane",
|
||||
"ws_err_invalid_date": "Nieprawidłowa data",
|
||||
"ws_err_invalid_format": "Nieprawidłowy format",
|
||||
"ws_err_invalid_url": "Nieprawidłowy link",
|
||||
"ws_err_not_configured": "Jeszcze nie skonfigurowano",
|
||||
"ws_err_not_loaded": "Jeszcze nie załadowano — spróbuj ponownie za chwilę",
|
||||
"ws_err_limit_reached": "Osiągnięto limit",
|
||||
"ws_err_too_many": "Zbyt wiele wpisów",
|
||||
"ws_err_too_large": "Za duży rozmiar",
|
||||
"ws_err_not_archived": "Nie zarchiwizowano",
|
||||
"ws_err_invalid_target": "Nieprawidłowy cel",
|
||||
"ws_err_create_failed": "Nie udało się utworzyć",
|
||||
"ws_err_archived": "Zarchiwizowano — najpierw przywróć",
|
||||
"ws_err_already_archived": "Już zarchiwizowano",
|
||||
"ws_err_unavailable": "Obecnie niedostępne",
|
||||
"ws_err_unauthorized": "Niedozwolone dla Twojego użytkownika",
|
||||
"ws_err_too_many_views": "Zbyt wiele zapisanych widoków",
|
||||
"ws_err_storage_unavailable": "Magazyn danych jest niedostępny",
|
||||
"ws_err_replace_failed": "Zastąpienie nie powiodło się",
|
||||
"ws_err_not_paused": "Nie wstrzymano",
|
||||
"ws_err_not_available": "Brak dostępnych",
|
||||
"ws_err_no_url": "Nie podano linku",
|
||||
"ws_err_no_phases": "To zadanie nie ma faz",
|
||||
"ws_err_invalid_view": "Nieprawidłowy widok",
|
||||
"ws_err_invalid_user": "Nieznany użytkownik",
|
||||
"ws_err_invalid_range": "Nieprawidłowy zakres",
|
||||
"ws_err_invalid_parent": "Nieprawidłowy obiekt nadrzędny",
|
||||
"ws_err_invalid_device": "Nieznane urządzenie",
|
||||
"ws_err_empty_csv": "Plik CSV jest pusty",
|
||||
"ws_err_empty": "Nie ma nic do zaimportowania",
|
||||
"ws_err_duplicate_failed": "Nie udało się zduplikować",
|
||||
"ws_err_already_paused": "Już wstrzymano",
|
||||
"ws_err_invalid_cursor": "Nieprawidłowa pozycja fazy",
|
||||
"ws_err_invalid_entity_slug": "Nieprawidłowa nazwa encji",
|
||||
"ws_err_invalid_trigger_config": "Nieprawidłowa konfiguracja wyzwalacza",
|
||||
"ws_err_no_defaults": "Brak domyślnych wartości szybkiego ukończenia",
|
||||
"ws_err_self_link_device": "Obiekt nie może być powiązany sam ze sobą",
|
||||
"ws_err_too_early": "Za wcześnie — to zadanie można ukończyć dopiero bliżej terminu",
|
||||
"feat_schedule_time": "Harmonogram według pory dnia",
|
||||
"feat_schedule_time_desc": "Zadania stają się zaległe o określonej porze dnia zamiast o północy.",
|
||||
"schedule_time_toggle": "Termin o określonej godzinie",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Wyzwól przy równym (=)",
|
||||
"trigger_not_equals": "Wyzwól przy różnym od (≠)",
|
||||
"for_at_least_minutes": "Przez co najmniej (minuty)",
|
||||
"safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)",
|
||||
"safety_interval": "Interwał bezpieczeństwa (opcjonalny)",
|
||||
"trigger_combinator": "Połącz wyzwalacz i interwał",
|
||||
"trigger_combinator_any": "Wyzwalacz lub interwał (co pierwsze)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Moje zadania",
|
||||
"tab_calendar": "Kalendarz",
|
||||
"cal_no_events": "Brak konserwacji",
|
||||
"cal_window_7": "7 dni",
|
||||
"cal_window_14": "14 dni",
|
||||
"cal_window_30": "30 dni",
|
||||
"cal_window_365": "1 rok",
|
||||
"cal_every_n_days": "co {n} dni",
|
||||
"cal_source_time": "Czas",
|
||||
"cal_source_time_adaptive": "Czas (adaptacyjny)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Nie udało się wygenerować kodu QR.",
|
||||
"qr_error_no_url": "Brak skonfigurowanego URL HA. Ustaw zewnętrzny lub wewnętrzny URL w Ustawienia → System → Sieć.",
|
||||
"save_error": "Nie udało się zapisać. Spróbuj ponownie.",
|
||||
"subsave_warning": "Zapisano, ale jedno ustawienie zostało odrzucone: {detail}",
|
||||
"qr_print": "Drukuj",
|
||||
"qr_download": "Pobierz SVG",
|
||||
"qr_action": "Akcja przy skanowaniu",
|
||||
"qr_action_view": "Wyświetl informacje o konserwacji",
|
||||
"qr_action_complete": "Oznacz konserwację jako wykonaną",
|
||||
"qr_url_mode": "Typ linku",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Lokalny (mDNS)",
|
||||
"qr_mode_server": "URL serwera",
|
||||
"overview": "Przegląd",
|
||||
"analysis": "Analiza",
|
||||
"recent_activities": "Ostatnie aktywności",
|
||||
"search_notes": "Szukaj w notatkach",
|
||||
"avg_cost": "Śr. koszt",
|
||||
"no_advanced_features": "Brak włączonych funkcji zaawansowanych",
|
||||
"no_advanced_features_hint": "Włącz „Adaptacyjne interwały” lub „Wzorce sezonowe” w ustawieniach integracji, aby zobaczyć tutaj dane analityczne.",
|
||||
"analysis_not_enough_data": "Jeszcze za mało danych do analizy.",
|
||||
"analysis_not_enough_data_hint": "Analiza Weibulla wymaga co najmniej 5 wykonanych konserwacji; wzorce sezonowe stają się widoczne po 6+ punktach danych na miesiąc.",
|
||||
"analysis_manual_task_hint": "Zadania ręczne bez interwału nie generują danych analitycznych.",
|
||||
"completions": "wykonania",
|
||||
"current": "Bieżące",
|
||||
"shorter": "Krótsze",
|
||||
"longer": "Dłuższe",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Minione okna",
|
||||
"cal_forward_windows": "Przyszłe okna",
|
||||
"history_edit_title": "Edytuj wpis historii",
|
||||
"history_delete_entry": "Usuń wpis",
|
||||
"history_delete_confirm": "Usunąć ten wpis historii? Data ostatniego wykonania cofnie się do poprzedniego; zdjęcia pozostaną przy obiekcie, a zużyte części nie wrócą na stan.",
|
||||
"history_edit_timestamp": "Znacznik czasu",
|
||||
"manufacturer": "Producent",
|
||||
"model": "Model",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Domyślne dni ostrzeżenia",
|
||||
"settings_consumable_threshold": "Próg „materiał eksploatacyjny niski” (%)",
|
||||
"settings_battery_low_percent": "Próg „bateria słaba” (%)",
|
||||
"settings_battery_lifetimes": "Typowa żywotność baterii",
|
||||
"settings_battery_lifetimes_hint": "Używane tylko dla baterii bez procentu: notatka Battery Notes z samym typem i datą wymiany albo czujnik zgłaszający tylko „niski”. Termin = ostatnia wymiana + ta żywotność. Baterie raportujące poziom dostają prognozę z mierzonego rozładowania. Po trzech wymianach danego typu mediana odstępów zastępuje wbudowaną wartość — Twoja wartość zawsze wygrywa.",
|
||||
"settings_battery_lifetime_months": "mies.",
|
||||
"settings_battery_lifetime_reset": "Użyj domyślnej",
|
||||
"settings_battery_lifetime_in_fleet": "w Twojej flocie",
|
||||
"settings_battery_lifetime_learned_models": "Nauczone według modelu: {list}",
|
||||
"settings_thresholds_hint": "Wartości domyślne dla nowych Sugerowanych konfiguracji (procent pozostały) i dla baterii floty bez własnego progu Battery Notes. Istniejące zadania zachowują swoją wartość.",
|
||||
"settings_value_out_of_range": "Wartość musi mieścić się w zakresie od {min} do {max}",
|
||||
"bn_summary": "{name}: {pct} % dla {n} baterii",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} urządzeń z własnym progiem:",
|
||||
"bn_more": "+ {n} więcej",
|
||||
"settings_row_actions": "Akcje w wierszach zadań",
|
||||
"settings_ref_numbers_in_lists": "Numery referencyjne na listach",
|
||||
"settings_ref_numbers_in_lists_hint": "Pokazuje etykiety #8 / #8.3 przed nazwami obiektów i zadań na liście Dzisiaj, w tabeli zadań, na kartach obiektów i w tabeli obiektów.",
|
||||
"row_actions_buttons_compact": "Przyciski (na telefonie tylko ikony)",
|
||||
"row_actions_buttons": "Przyciski z tekstem",
|
||||
"row_actions_icons": "Ikony (klasyczne)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Czas drzemki (godziny)",
|
||||
"settings_budget": "Budżet",
|
||||
"settings_currency": "Waluta",
|
||||
"settings_currency_decimals": "Miejsca po przecinku w kwotach",
|
||||
"settings_currency_decimals_hint": "Ile miejsc po przecinku pokazuje każda kwota: KPI, budżety, koszty na listach i w raportach, alerty budżetowe. 0 = liczby całkowite.",
|
||||
"settings_budget_monthly": "Budżet miesięczny",
|
||||
"settings_budget_yearly": "Budżet roczny",
|
||||
"settings_budget_alerts": "Alerty budżetowe",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Eksportuj YAML",
|
||||
"settings_export_csv": "Eksportuj CSV",
|
||||
"settings_export_settings": "Eksportuj ustawienia (JSON)",
|
||||
"settings_import_csv": "Importuj CSV",
|
||||
"settings_import_placeholder": "Wklej tutaj zawartość JSON lub CSV…",
|
||||
"settings_import_btn": "Importuj",
|
||||
"settings_import_success": "{count} obiektów zaimportowanych pomyślnie.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Akcja HA per zadanie po zakończeniu + QR szybkiego zakończenia z predefiniowanymi wartościami.",
|
||||
"on_complete_action_title": "Po zakończeniu: wywołaj akcję HA (opcjonalnie)",
|
||||
"on_complete_action_desc": "Wywołuje usługę HA po zakończeniu zadania — np. zresetuj licznik urządzenia.",
|
||||
"on_complete_action_service": "Usługa",
|
||||
"on_complete_action_target": "Encja docelowa",
|
||||
"on_complete_action_data": "Dane (JSON, opcjonalnie)",
|
||||
"on_complete_action_test": "Testuj akcję",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Wybierz zdjęcia",
|
||||
"choose_photo": "Wybierz zdjęcie",
|
||||
"photos_android_hint": "Aplikacja na Androida przekazuje jedno zdjęcie na wybór — każdy wybór jest dodawany.",
|
||||
"add_photos": "Dodaj zdjęcia",
|
||||
"photos_limit": "Maksymalnie {max} zdjęć na wykonanie",
|
||||
"history_edit_photos_hint": "Zdjęcie usunięte tutaj pozostaje w dokumentach obiektu.",
|
||||
"uploading": "Przesyłanie…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Pobierz",
|
||||
"doc_close": "Zamknij",
|
||||
"doc_camera": "Zrób zdjęcie",
|
||||
"camera_capture_shoot": "Zrób zdjęcie",
|
||||
"camera_switch_lens": "Przełącz kamerę",
|
||||
"doc_drop_hint": "Upuść pliki tutaj",
|
||||
"doc_task_none": "Brak dokumentów powiązanych z tym zadaniem.",
|
||||
"doc_link_existing": "Powiąż dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Rejestruje wykonanie (ustawia ostatnie wykonanie), gdy wyzwalacz sam się rozwiąże — np. uzupełniono sól, wymieniono filtr.",
|
||||
"doc_search": "Szukaj dokumentów…",
|
||||
"doc_search_none": "Brak pasujących dokumentów",
|
||||
"doc_description": "Opis (opcjonalnie)",
|
||||
"doc_sort": "Sortuj dokumenty",
|
||||
"doc_sort_newest": "Najnowsze najpierw",
|
||||
"doc_sort_oldest": "Najstarsze najpierw",
|
||||
"doc_sort_title": "Według tytułu (1, 2, 3 …)",
|
||||
"doc_sort_category": "Według kategorii",
|
||||
"link_device_optional": "Połącz z istniejącym urządzeniem (opcjonalnie)",
|
||||
"parent_object_optional": "Obiekt nadrzędny (opcjonalnie)",
|
||||
"parent_none": "(Brak nadrzędnego)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Powiadamiaj tylko dla widoku",
|
||||
"settings_notify_scope_all": "Wszystkie zadania",
|
||||
"settings_notify_scope_hint": "Przypomnienia wysyłają tylko zadania pasujące do filtrów etykiety/użytkownika wybranego widoku. Status, sortowanie i grupowanie widoku są tu ignorowane.",
|
||||
"settings_notify_completed": "Powiadomienia o wykonaniu",
|
||||
"settings_notify_completed_hint": "Wiadomość dla domowników, gdy zadanie zostanie wykonane, z powodem i wykonawcą. „Tylko automatyczne” zgłasza wyłącznie wykonania, których nikt nie zrobił na miejscu: powrót czujnika, odhaczenie na liście zakupów, wywołanie usługi przez automatyzację. Tylko do usługi domowej; godziny ciszy i dzienny limit obowiązują.",
|
||||
"notify_completed_off": "Wył.",
|
||||
"notify_completed_automatic": "Tylko automatyczne wykonania",
|
||||
"notify_completed_all": "Wszystkie wykonania",
|
||||
"settings_notify_rule": "Własna reguła powiadomień",
|
||||
"settings_notify_rule_hint": "Każde powiadomienie wyzwala dodatkowo zdarzenie maintenance_supporter_notification (rodzaj, status, obiekt, zadanie, numery referencyjne, priorytet, link, dane). Przekieruj je automatyzacją — np. do Tickera, Telegrama lub Pushover.",
|
||||
"settings_notify_event_only": "Tylko zdarzenie, nic nie wysyłaj",
|
||||
"settings_notify_event_only_hint": "Twoja automatyzacja odpowiada za całe dostarczanie. Odstępy powtórzeń, ciche godziny i dzienny limit nadal decydują, kiedy zdarzenie się wyzwala.",
|
||||
"settings_notify_extra_data": "Dodatkowe dane powiadomienia (szablon)",
|
||||
"settings_notify_extra_data_hint": "JSON lub YAML, Jinja dozwolone; scalane z data wywołania notify (Twoje klucze wygrywają). Zmienne: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Sprawdź przyciskiem Wyślij test.",
|
||||
"settings_notify_extra_data_hint": "JSON lub YAML, Jinja dozwolone; scalane z data wywołania notify (Twoje klucze wygrywają). Zmienne: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Sprawdź przyciskiem Wyślij test.",
|
||||
"card_saved_view": "Zapisany widok",
|
||||
"card_saved_view_none": "Brak",
|
||||
"card_saved_view_help": "Stosuje filtry statusu, użytkownika i etykiety widoku oprócz powyższych filtrów. Sortowanie i grupowanie widoku to ustawienia wyświetlania panelu i nie są stosowane na karcie.",
|
||||
"panel_card_tab": "Otwieraj na karcie",
|
||||
"panel_card_tab_default": "Zapamiętana karta (jak w panelu)",
|
||||
"panel_card_view": "Otwieraj z zapisanym widokiem",
|
||||
"panel_card_height": "Wysokość",
|
||||
"panel_card_height_help": "Puste = wypełnia ekran pod kartą (widok panelu). W przeciwnym razie długość CSS, np. 600px lub 80vh, dla widoków z innymi kartami.",
|
||||
"panel_card_not_registered": "Panel Maintenance Supporter jest niedostępny — czy integracja jest skonfigurowana?",
|
||||
"panel_card_load_failed": "Nie udało się wczytać panelu Maintenance Supporter — odśwież stronę (po aktualizacji: wyczyść pamięć podręczną przeglądarki).",
|
||||
"doc_part_none": "Brak dokumentów powiązanych z tą częścią.",
|
||||
"settings_templates_toggle_group": "Włącz lub wyłącz wszystkie szablony w tej grupie",
|
||||
"setups_button": "Sugerowane konfiguracje",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "W porządku",
|
||||
"battery_fleet_status_due": "Do wymiany",
|
||||
"battery_fleet_predicted_on": "Przewidywane około {date}",
|
||||
"battery_fleet_predicted_typical": "Spodziewane około {date} — {months} mies. typowo dla {type} ({source})",
|
||||
"lifetime_source_default": "wbudowana wartość domyślna",
|
||||
"lifetime_source_table": "wbudowana tabela",
|
||||
"lifetime_source_learned": "nauczone z {n} wymian",
|
||||
"lifetime_source_override": "Twoje ustawienie",
|
||||
"lifetime_source_learned_device": "nauczone z {n} wymian tego urządzenia",
|
||||
"lifetime_source_learned_model": "nauczone z {n} wymian w urządzeniach tego modelu",
|
||||
"battery_fleet_predicted_trend": "Prognoza na podstawie trendu rozładowania tej baterii: około {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Akumulator: ładowanie zamiast wymiany — nigdy na liście zakupów",
|
||||
"battery_fleet_sort_name": "Sortuj według nazwy",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Zużyte części",
|
||||
"print_inc_photos": "Zdjęcia",
|
||||
"print_inc_documents": "Powiązane dokumenty",
|
||||
"print_inc_doc_desc": "Opisy dokumentów",
|
||||
"print_inc_checklist": "Lista kontrolna",
|
||||
"print_inc_notes": "Notatki",
|
||||
"print_inc_costs": "Koszt i czas",
|
||||
"print_inc_person": "Wykonał",
|
||||
"print_inc_refs": "Numery referencyjne",
|
||||
"print_inc_bare": "Wykonania bez szczegółów",
|
||||
"print_inc_qr": "Kod QR dla każdego zadania",
|
||||
"print_button": "Drukuj",
|
||||
"report_scan_hint": "Zeskanuj, aby otworzyć zadanie",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Tempo de operação",
|
||||
"runtime_hours": "Tempo de operação alvo (horas)",
|
||||
"target_value": "Valor alvo",
|
||||
"baseline": "Linha de base",
|
||||
"target_changes": "Mudanças alvo",
|
||||
"for_minutes": "Durante (minutos)",
|
||||
"time_based": "Baseado em tempo",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Gerado em",
|
||||
"report_times_done": "Concluídas",
|
||||
"report_total_cost": "Custo total",
|
||||
"report_every": "a cada {n} {unit}",
|
||||
"report_notes": "Observações",
|
||||
"report_col_type": "Tipo",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Agendamento",
|
||||
"duration": "Duração",
|
||||
"minutes_short": "min",
|
||||
"both": "Ambos",
|
||||
"trigger_val": "Valor do gatilho",
|
||||
"complete_title": "Concluir: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: tipo incorreto (esperado: {type})",
|
||||
"err_invalid_choice": "{field}: valor não permitido",
|
||||
"err_invalid_value": "{field}: valor inválido",
|
||||
"ws_err_not_found": "Não encontrado — pode ter sido excluído nesse meio-tempo",
|
||||
"ws_err_invalid_input": "Entrada inválida",
|
||||
"ws_err_invalid_date": "Data inválida",
|
||||
"ws_err_invalid_format": "Formato inválido",
|
||||
"ws_err_invalid_url": "Link inválido",
|
||||
"ws_err_not_configured": "Ainda não configurado",
|
||||
"ws_err_not_loaded": "Ainda não carregado — tente novamente em instantes",
|
||||
"ws_err_limit_reached": "Limite atingido",
|
||||
"ws_err_too_many": "Entradas demais",
|
||||
"ws_err_too_large": "Grande demais",
|
||||
"ws_err_not_archived": "Não arquivado",
|
||||
"ws_err_invalid_target": "Destino inválido",
|
||||
"ws_err_create_failed": "Não foi possível criar",
|
||||
"ws_err_archived": "Arquivado — restaure primeiro",
|
||||
"ws_err_already_archived": "Já arquivado",
|
||||
"ws_err_unavailable": "Indisponível no momento",
|
||||
"ws_err_unauthorized": "Não permitido para o seu usuário",
|
||||
"ws_err_too_many_views": "Visualizações salvas demais",
|
||||
"ws_err_storage_unavailable": "O armazenamento não está disponível",
|
||||
"ws_err_replace_failed": "Falha ao substituir",
|
||||
"ws_err_not_paused": "Não está pausado",
|
||||
"ws_err_not_available": "Nada disponível",
|
||||
"ws_err_no_url": "Nenhum link informado",
|
||||
"ws_err_no_phases": "Esta tarefa não tem fases",
|
||||
"ws_err_invalid_view": "Visualização inválida",
|
||||
"ws_err_invalid_user": "Usuário desconhecido",
|
||||
"ws_err_invalid_range": "Intervalo inválido",
|
||||
"ws_err_invalid_parent": "Objeto pai inválido",
|
||||
"ws_err_invalid_device": "Dispositivo desconhecido",
|
||||
"ws_err_empty_csv": "O arquivo CSV está vazio",
|
||||
"ws_err_empty": "Nada para importar",
|
||||
"ws_err_duplicate_failed": "Não foi possível duplicar",
|
||||
"ws_err_already_paused": "Já está pausado",
|
||||
"ws_err_invalid_cursor": "Posição de fase inválida",
|
||||
"ws_err_invalid_entity_slug": "Nome de entidade inválido",
|
||||
"ws_err_invalid_trigger_config": "Configuração do gatilho inválida",
|
||||
"ws_err_no_defaults": "Sem padrões para conclusão rápida",
|
||||
"ws_err_self_link_device": "Um objeto não pode ser vinculado a si mesmo",
|
||||
"ws_err_too_early": "Cedo demais — esta tarefa só pode ser concluída mais perto do vencimento",
|
||||
"feat_schedule_time": "Agendamento por horário",
|
||||
"feat_schedule_time_desc": "As tarefas ficam atrasadas em um horário específico do dia, em vez de à meia-noite.",
|
||||
"schedule_time_toggle": "Vence em um horário específico",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Acionar quando igual a (=)",
|
||||
"trigger_not_equals": "Acionar quando diferente de (≠)",
|
||||
"for_at_least_minutes": "Por pelo menos (minutos)",
|
||||
"safety_interval_days": "Intervalo de segurança (dias, opcional)",
|
||||
"safety_interval": "Intervalo de segurança (opcional)",
|
||||
"trigger_combinator": "Combinar gatilho e intervalo",
|
||||
"trigger_combinator_any": "Gatilho ou intervalo (o primeiro)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Minhas tarefas",
|
||||
"tab_calendar": "Calendário",
|
||||
"cal_no_events": "Sem manutenção",
|
||||
"cal_window_7": "7 dias",
|
||||
"cal_window_14": "14 dias",
|
||||
"cal_window_30": "30 dias",
|
||||
"cal_window_365": "1 ano",
|
||||
"cal_every_n_days": "a cada {n} dias",
|
||||
"cal_source_time": "Baseado em tempo",
|
||||
"cal_source_time_adaptive": "Baseado em tempo (adaptativo)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "Falha ao gerar o código QR.",
|
||||
"qr_error_no_url": "Nenhuma URL do HA configurada. Defina uma URL externa ou interna em Configurações → Sistema → Rede.",
|
||||
"save_error": "Falha ao salvar. Tente novamente.",
|
||||
"subsave_warning": "Salvo, mas uma configuração foi rejeitada: {detail}",
|
||||
"qr_print": "Imprimir",
|
||||
"qr_download": "Baixar SVG",
|
||||
"qr_action": "Ação ao escanear",
|
||||
"qr_action_view": "Ver informações de manutenção",
|
||||
"qr_action_complete": "Marcar a manutenção como concluída",
|
||||
"qr_url_mode": "Tipo de link",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "URL do servidor",
|
||||
"overview": "Visão geral",
|
||||
"analysis": "Análise",
|
||||
"recent_activities": "Atividades recentes",
|
||||
"search_notes": "Pesquisar observações",
|
||||
"avg_cost": "Custo médio",
|
||||
"no_advanced_features": "Nenhum recurso avançado ativado",
|
||||
"no_advanced_features_hint": "Ative “Intervalos adaptativos” ou “Padrões sazonais” nas configurações da integração para ver dados de análise aqui.",
|
||||
"analysis_not_enough_data": "Ainda não há dados suficientes para análise.",
|
||||
"analysis_not_enough_data_hint": "A análise de Weibull exige pelo menos 5 manutenções concluídas; padrões sazonais aparecem após 6+ pontos de dados por mês.",
|
||||
"analysis_manual_task_hint": "Tarefas manuais sem intervalo não geram dados de análise.",
|
||||
"completions": "conclusões",
|
||||
"current": "Atual",
|
||||
"shorter": "Mais curto",
|
||||
"longer": "Mais longo",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Janelas passadas",
|
||||
"cal_forward_windows": "Janelas futuras",
|
||||
"history_edit_title": "Editar entrada do histórico",
|
||||
"history_delete_entry": "Excluir entrada",
|
||||
"history_delete_confirm": "Excluir esta entrada do histórico? A data da última execução volta para a conclusão anterior; as fotos permanecem com o objeto e as peças consumidas não são repostas.",
|
||||
"history_edit_timestamp": "Data e hora",
|
||||
"manufacturer": "Fabricante",
|
||||
"model": "Modelo",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Dias de aviso padrão",
|
||||
"settings_consumable_threshold": "Limite „consumível baixo“ (%)",
|
||||
"settings_battery_low_percent": "Limite „pilha fraca“ (%)",
|
||||
"settings_battery_lifetimes": "Vida útil típica das pilhas",
|
||||
"settings_battery_lifetimes_hint": "Usado apenas para pilhas sem porcentagem: uma nota do Battery Notes só com tipo e data de troca, ou um sensor que apenas informa « fraca ». O vencimento é última troca + esta vida útil. Pilhas que informam nível têm a previsão pela descarga medida. Após três trocas de um tipo, o intervalo mediano substitui o valor integrado — seu valor sempre vence.",
|
||||
"settings_battery_lifetime_months": "meses",
|
||||
"settings_battery_lifetime_reset": "Usar padrão",
|
||||
"settings_battery_lifetime_in_fleet": "na sua frota",
|
||||
"settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}",
|
||||
"settings_thresholds_hint": "Padrões para novas Configurações sugeridas (porcentagem restante) e para pilhas da frota sem limite próprio do Battery Notes. As tarefas existentes mantêm seu valor.",
|
||||
"settings_value_out_of_range": "O valor deve estar entre {min} e {max}",
|
||||
"bn_summary": "{name}: {pct} % para {n} pilhas",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} dispositivos com limite próprio:",
|
||||
"bn_more": "+ {n} mais",
|
||||
"settings_row_actions": "Ações nas linhas de tarefas",
|
||||
"settings_ref_numbers_in_lists": "Números de referência nas listas",
|
||||
"settings_ref_numbers_in_lists_hint": "Mostra as etiquetas #8 / #8.3 antes dos nomes de objetos e tarefas na lista Hoje, na tabela de tarefas, nos cartões de objetos e na tabela de objetos.",
|
||||
"row_actions_buttons_compact": "Botões (só ícones no celular)",
|
||||
"row_actions_buttons": "Botões com texto",
|
||||
"row_actions_icons": "Ícones (clássico)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Duração da soneca (horas)",
|
||||
"settings_budget": "Orçamento",
|
||||
"settings_currency": "Moeda",
|
||||
"settings_currency_decimals": "Casas decimais dos valores",
|
||||
"settings_currency_decimals_hint": "Quantas casas decimais cada valor mostra: KPIs, orçamentos, custos em listas e relatórios, alertas de orçamento. 0 = números inteiros.",
|
||||
"settings_budget_monthly": "Orçamento mensal",
|
||||
"settings_budget_yearly": "Orçamento anual",
|
||||
"settings_budget_alerts": "Alertas de orçamento",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "Exportar YAML",
|
||||
"settings_export_csv": "Exportar CSV",
|
||||
"settings_export_settings": "Exportar configurações (JSON)",
|
||||
"settings_import_csv": "Importar CSV",
|
||||
"settings_import_placeholder": "Cole o conteúdo JSON ou CSV aqui…",
|
||||
"settings_import_btn": "Importar",
|
||||
"settings_import_success": "{count} objetos importados com sucesso.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "Ação do HA por tarefa ao concluir + QR de conclusão rápida com valores predefinidos.",
|
||||
"on_complete_action_title": "Ao concluir: acionar ação do HA (opcional)",
|
||||
"on_complete_action_desc": "Chama um serviço do HA quando a tarefa é concluída — ex.: zerar um contador no dispositivo.",
|
||||
"on_complete_action_service": "Serviço",
|
||||
"on_complete_action_target": "Entidade alvo",
|
||||
"on_complete_action_target_hint": "Atenção: o domínio da entidade deve corresponder ao serviço — ex.: 'button.press' só funciona em button.*, 'counter.increment' só em counter.*, 'input_button.press' só em input_button.* etc. Em caso de divergência, a ação falhará silenciosamente (o HA registra 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Dados (JSON, opcional)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "Escolher fotos",
|
||||
"choose_photo": "Escolher foto",
|
||||
"photos_android_hint": "O app Android entrega uma foto por seleção — cada seleção é adicionada.",
|
||||
"add_photos": "Adicionar fotos",
|
||||
"photos_limit": "Até {max} fotos por conclusão",
|
||||
"history_edit_photos_hint": "Uma foto removida aqui continua nos documentos do objeto.",
|
||||
"uploading": "Enviando…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "Baixar",
|
||||
"doc_close": "Fechar",
|
||||
"doc_camera": "Tirar foto",
|
||||
"camera_capture_shoot": "Capturar",
|
||||
"camera_switch_lens": "Trocar de câmera",
|
||||
"doc_drop_hint": "Solte os arquivos aqui",
|
||||
"doc_task_none": "Nenhum documento vinculado a esta tarefa.",
|
||||
"doc_link_existing": "Vincular um documento…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "Registra uma conclusão (define a última execução) quando o gatilho se resolve sozinho — ex.: sal reposto, filtro trocado.",
|
||||
"doc_search": "Pesquisar documentos…",
|
||||
"doc_search_none": "Nenhum documento correspondente",
|
||||
"doc_description": "Descrição (opcional)",
|
||||
"doc_sort": "Ordenar documentos",
|
||||
"doc_sort_newest": "Mais recentes primeiro",
|
||||
"doc_sort_oldest": "Mais antigos primeiro",
|
||||
"doc_sort_title": "Por título (1, 2, 3 …)",
|
||||
"doc_sort_category": "Por categoria",
|
||||
"link_device_optional": "Vincular a um dispositivo existente (opcional)",
|
||||
"parent_object_optional": "Objeto pai (opcional)",
|
||||
"parent_none": "(Sem pai)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notificar apenas para a visualização",
|
||||
"settings_notify_scope_all": "Todas as tarefas",
|
||||
"settings_notify_scope_hint": "Apenas tarefas que correspondem aos filtros de etiqueta/usuário da visualização salva selecionada enviam lembretes. Status, ordenação e agrupamento da visualização são ignorados aqui.",
|
||||
"settings_notify_completed": "Notificações de conclusão",
|
||||
"settings_notify_completed_hint": "Aviso para a casa quando uma tarefa é concluída, com o motivo e quem fez. \"Só automáticas\" anuncia apenas as conclusões que ninguém fez na hora: recuperação do sensor, item marcado na lista de compras, chamada de serviço de uma automação. Só para o serviço da casa; horário de silêncio e limite diário se aplicam.",
|
||||
"notify_completed_off": "Desligado",
|
||||
"notify_completed_automatic": "Só conclusões automáticas",
|
||||
"notify_completed_all": "Todas as conclusões",
|
||||
"settings_notify_rule": "Sua regra de notificação",
|
||||
"settings_notify_rule_hint": "Cada notificação também dispara o evento maintenance_supporter_notification (tipo, status, objeto, tarefa, números de referência, prioridade, link, payload). Roteie-o com uma automação — por ex. para Ticker, Telegram ou Pushover.",
|
||||
"settings_notify_event_only": "Apenas disparar o evento, não enviar nada",
|
||||
"settings_notify_event_only_hint": "Sua automação cuida de toda a entrega. Intervalos de repetição, horário silencioso e o limite diário continuam valendo para quando o evento dispara.",
|
||||
"settings_notify_extra_data": "Dados extras de notificação (modelo)",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; mesclado nos data da chamada notify (suas chaves vencem). Variáveis: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Teste com o botão Enviar teste.",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; mesclado nos data da chamada notify (suas chaves vencem). Variáveis: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Teste com o botão Enviar teste.",
|
||||
"card_saved_view": "Visualização salva",
|
||||
"card_saved_view_none": "Nenhuma",
|
||||
"card_saved_view_help": "Aplica os filtros de status, usuário e etiqueta da visualização por cima dos filtros acima. A ordenação e o agrupamento da visualização são configurações de exibição do painel e não se aplicam ao cartão.",
|
||||
"panel_card_tab": "Abrir na aba",
|
||||
"panel_card_tab_default": "Aba lembrada (como no painel)",
|
||||
"panel_card_view": "Abrir com a visualização salva",
|
||||
"panel_card_height": "Altura",
|
||||
"panel_card_height_help": "Vazio = preenche a tela abaixo do cartão (visualização painel). Caso contrário, um comprimento CSS como 600px ou 80vh para visualizações com outros cartões.",
|
||||
"panel_card_not_registered": "O painel Maintenance Supporter não está disponível — a integração está configurada?",
|
||||
"panel_card_load_failed": "Não foi possível carregar o painel Maintenance Supporter — recarregue a página (após uma atualização: limpe o cache do navegador).",
|
||||
"doc_part_none": "Nenhum documento vinculado a esta peça.",
|
||||
"settings_templates_toggle_group": "Ativar ou desativar todos os modelos deste grupo",
|
||||
"setups_button": "Configurações sugeridas",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Em bom estado",
|
||||
"battery_fleet_status_due": "Vencida",
|
||||
"battery_fleet_predicted_on": "Previsto por volta de {date}",
|
||||
"battery_fleet_predicted_typical": "Previsto por volta de {date} — {months} meses típicos para {type} ({source})",
|
||||
"lifetime_source_default": "valor padrão integrado",
|
||||
"lifetime_source_table": "tabela integrada",
|
||||
"lifetime_source_learned": "aprendido de {n} trocas",
|
||||
"lifetime_source_override": "sua configuração",
|
||||
"lifetime_source_learned_device": "aprendido de {n} trocas deste dispositivo",
|
||||
"lifetime_source_learned_model": "aprendido de {n} trocas em dispositivos deste modelo",
|
||||
"battery_fleet_predicted_trend": "Previsto pela tendência de descarga desta bateria: por volta de {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Recarregável: recarregue em vez de substituir — nunca na lista de compras",
|
||||
"battery_fleet_sort_name": "Ordenar por nome",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Peças usadas",
|
||||
"print_inc_photos": "Fotos",
|
||||
"print_inc_documents": "Documentos vinculados",
|
||||
"print_inc_doc_desc": "Descrições dos documentos",
|
||||
"print_inc_checklist": "Lista de verificação",
|
||||
"print_inc_notes": "Notas",
|
||||
"print_inc_costs": "Custo e duração",
|
||||
"print_inc_person": "Concluído por",
|
||||
"print_inc_refs": "Números de referência",
|
||||
"print_inc_bare": "Conclusões sem detalhes",
|
||||
"print_inc_qr": "Código QR por tarefa",
|
||||
"print_button": "Imprimir",
|
||||
"report_scan_hint": "Escaneie para abrir a tarefa",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Tempo de funcionamento",
|
||||
"runtime_hours": "Duração alvo (horas)",
|
||||
"target_value": "Valor alvo",
|
||||
"baseline": "Linha de base",
|
||||
"target_changes": "Alterações alvo",
|
||||
"for_minutes": "Durante (minutos)",
|
||||
"time_based": "Temporal",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Gerado",
|
||||
"report_times_done": "Feito",
|
||||
"report_total_cost": "Custo total",
|
||||
"report_every": "a cada {n} {unit}",
|
||||
"report_notes": "Notas",
|
||||
"report_col_type": "Tipo",
|
||||
"report_col_status": "Estado",
|
||||
"report_col_schedule": "Agenda",
|
||||
"duration": "Duração",
|
||||
"minutes_short": "min",
|
||||
"both": "Ambos",
|
||||
"trigger_val": "Valor do acionador",
|
||||
"complete_title": "Concluída: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: tipo incorreto (esperado: {type})",
|
||||
"err_invalid_choice": "{field}: valor não permitido",
|
||||
"err_invalid_value": "{field}: valor inválido",
|
||||
"ws_err_not_found": "Não encontrado — pode ter sido eliminado entretanto",
|
||||
"ws_err_invalid_input": "Entrada inválida",
|
||||
"ws_err_invalid_date": "Data inválida",
|
||||
"ws_err_invalid_format": "Formato inválido",
|
||||
"ws_err_invalid_url": "Ligação inválida",
|
||||
"ws_err_not_configured": "Ainda não configurado",
|
||||
"ws_err_not_loaded": "Ainda não carregado — tente novamente daqui a pouco",
|
||||
"ws_err_limit_reached": "Limite atingido",
|
||||
"ws_err_too_many": "Demasiadas entradas",
|
||||
"ws_err_too_large": "Demasiado grande",
|
||||
"ws_err_not_archived": "Não arquivado",
|
||||
"ws_err_invalid_target": "Destino inválido",
|
||||
"ws_err_create_failed": "Não foi possível criar",
|
||||
"ws_err_archived": "Arquivado — restaure primeiro",
|
||||
"ws_err_already_archived": "Já arquivado",
|
||||
"ws_err_unavailable": "Atualmente indisponível",
|
||||
"ws_err_unauthorized": "Não permitido para o seu utilizador",
|
||||
"ws_err_too_many_views": "Demasiadas vistas guardadas",
|
||||
"ws_err_storage_unavailable": "O armazenamento não está disponível",
|
||||
"ws_err_replace_failed": "Falha ao substituir",
|
||||
"ws_err_not_paused": "Não está em pausa",
|
||||
"ws_err_not_available": "Nada disponível",
|
||||
"ws_err_no_url": "Nenhuma ligação indicada",
|
||||
"ws_err_no_phases": "Esta tarefa não tem fases",
|
||||
"ws_err_invalid_view": "Vista inválida",
|
||||
"ws_err_invalid_user": "Utilizador desconhecido",
|
||||
"ws_err_invalid_range": "Intervalo inválido",
|
||||
"ws_err_invalid_parent": "Objeto principal inválido",
|
||||
"ws_err_invalid_device": "Dispositivo desconhecido",
|
||||
"ws_err_empty_csv": "O ficheiro CSV está vazio",
|
||||
"ws_err_empty": "Nada para importar",
|
||||
"ws_err_duplicate_failed": "Não foi possível duplicar",
|
||||
"ws_err_already_paused": "Já está em pausa",
|
||||
"ws_err_invalid_cursor": "Posição de fase inválida",
|
||||
"ws_err_invalid_entity_slug": "Nome de entidade inválido",
|
||||
"ws_err_invalid_trigger_config": "Configuração do acionador inválida",
|
||||
"ws_err_no_defaults": "Sem predefinições para conclusão rápida",
|
||||
"ws_err_self_link_device": "Um objeto não pode ser ligado a si próprio",
|
||||
"ws_err_too_early": "Demasiado cedo — esta tarefa só pode ser concluída mais perto do prazo",
|
||||
"feat_schedule_time": "Agendamento por hora",
|
||||
"feat_schedule_time_desc": "Tarefas vencem em um horário específico em vez de meia-noite.",
|
||||
"schedule_time_toggle": "Vence a uma hora específica",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Acionar quando igual a (=)",
|
||||
"trigger_not_equals": "Acionar quando diferente de (≠)",
|
||||
"for_at_least_minutes": "Durante pelo menos (minutos)",
|
||||
"safety_interval_days": "Intervalo de segurança (dias, opcional)",
|
||||
"safety_interval": "Intervalo de segurança (opcional)",
|
||||
"trigger_combinator": "Combinar acionador e intervalo",
|
||||
"trigger_combinator_any": "Acionador ou intervalo (o primeiro)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "As minhas tarefas",
|
||||
"tab_calendar": "Calendário",
|
||||
"cal_no_events": "Sem manutenção",
|
||||
"cal_window_7": "7 dias",
|
||||
"cal_window_14": "14 dias",
|
||||
"cal_window_30": "30 dias",
|
||||
"cal_window_365": "1 ano",
|
||||
"cal_every_n_days": "a cada {n} dias",
|
||||
"cal_source_time": "Baseado em tempo",
|
||||
"cal_source_time_adaptive": "Baseado em tempo (adaptativo)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Não foi possível gerar o código QR.",
|
||||
"qr_error_no_url": "Nenhum URL do HA configurado. Defina um URL externo ou interno em Definições → Sistema → Rede.",
|
||||
"save_error": "Erro ao guardar. Tente novamente.",
|
||||
"subsave_warning": "Guardado, mas uma definição foi rejeitada: {detail}",
|
||||
"qr_print": "Imprimir",
|
||||
"qr_download": "Transferir SVG",
|
||||
"qr_action": "Ação ao digitalizar",
|
||||
"qr_action_view": "Ver informações de manutenção",
|
||||
"qr_action_complete": "Marcar manutenção como concluída",
|
||||
"qr_url_mode": "Tipo de ligação",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Local (mDNS)",
|
||||
"qr_mode_server": "URL do servidor",
|
||||
"overview": "Visão geral",
|
||||
"analysis": "Análise",
|
||||
"recent_activities": "Atividades recentes",
|
||||
"search_notes": "Pesquisar notas",
|
||||
"avg_cost": "Ø Custo",
|
||||
"no_advanced_features": "Sem funções avançadas ativadas",
|
||||
"no_advanced_features_hint": "Ative “Intervalos Adaptativos” ou “Padrões Sazonais” nas definições da integração para ver dados de análise aqui.",
|
||||
"analysis_not_enough_data": "Ainda não há dados suficientes para a análise.",
|
||||
"analysis_not_enough_data_hint": "A análise Weibull requer pelo menos 5 manutenções concluídas; os padrões sazonais tornam-se visíveis após 6+ pontos de dados por mês.",
|
||||
"analysis_manual_task_hint": "Tarefas manuais sem intervalo não geram dados de análise.",
|
||||
"completions": "conclusões",
|
||||
"current": "Atual",
|
||||
"shorter": "Mais curto",
|
||||
"longer": "Mais longo",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Janelas passadas",
|
||||
"cal_forward_windows": "Janelas futuras",
|
||||
"history_edit_title": "Editar entrada do histórico",
|
||||
"history_delete_entry": "Eliminar entrada",
|
||||
"history_delete_confirm": "Eliminar esta entrada do histórico? A data da última execução recua para a conclusão anterior; as fotos ficam com o objeto e as peças consumidas não são repostas.",
|
||||
"history_edit_timestamp": "Carimbo de data/hora",
|
||||
"manufacturer": "Fabricante",
|
||||
"model": "Modelo",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Dias de aviso predefinidos",
|
||||
"settings_consumable_threshold": "Limiar „consumível baixo“ (%)",
|
||||
"settings_battery_low_percent": "Limiar „pilha fraca“ (%)",
|
||||
"settings_battery_lifetimes": "Durações típicas das pilhas",
|
||||
"settings_battery_lifetimes_hint": "Usado apenas para pilhas sem percentagem: uma nota do Battery Notes só com tipo e data de substituição, ou um sensor que apenas reporta « fraca ». O vencimento é última substituição + esta duração. Pilhas que reportam nível obtêm a previsão da descarga medida. Após três substituições de um tipo, o intervalo mediano substitui o valor integrado — o seu valor ganha sempre.",
|
||||
"settings_battery_lifetime_months": "meses",
|
||||
"settings_battery_lifetime_reset": "Usar predefinição",
|
||||
"settings_battery_lifetime_in_fleet": "na sua frota",
|
||||
"settings_battery_lifetime_learned_models": "Aprendido por modelo: {list}",
|
||||
"settings_thresholds_hint": "Predefinições para novas Configurações sugeridas (percentagem restante) e para pilhas da frota sem limiar Battery Notes próprio. As tarefas existentes mantêm o seu valor.",
|
||||
"settings_value_out_of_range": "O valor tem de estar entre {min} e {max}",
|
||||
"bn_summary": "{name}: {pct} % para {n} pilhas",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} dispositivos com limiar próprio:",
|
||||
"bn_more": "+ {n} mais",
|
||||
"settings_row_actions": "Ações nas linhas de tarefas",
|
||||
"settings_ref_numbers_in_lists": "Números de referência nas listas",
|
||||
"settings_ref_numbers_in_lists_hint": "Mostra as etiquetas #8 / #8.3 antes dos nomes de objetos e tarefas na lista Hoje, na tabela de tarefas, nos cartões de objetos e na tabela de objetos.",
|
||||
"row_actions_buttons_compact": "Botões (só ícones no telemóvel)",
|
||||
"row_actions_buttons": "Botões com texto",
|
||||
"row_actions_icons": "Ícones (clássico)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Duração do adiamento (horas)",
|
||||
"settings_budget": "Orçamento",
|
||||
"settings_currency": "Moeda",
|
||||
"settings_currency_decimals": "Casas decimais dos montantes",
|
||||
"settings_currency_decimals_hint": "Quantas casas decimais cada montante mostra: KPIs, orçamentos, custos em listas e relatórios, alertas de orçamento. 0 = números inteiros.",
|
||||
"settings_budget_monthly": "Orçamento mensal",
|
||||
"settings_budget_yearly": "Orçamento anual",
|
||||
"settings_budget_alerts": "Alertas de orçamento",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exportar YAML",
|
||||
"settings_export_csv": "Exportar CSV",
|
||||
"settings_export_settings": "Exportar definições (JSON)",
|
||||
"settings_import_csv": "Importar CSV",
|
||||
"settings_import_placeholder": "Cole o conteúdo JSON ou CSV aqui…",
|
||||
"settings_import_btn": "Importar",
|
||||
"settings_import_success": "{count} objetos importados com sucesso.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Ação HA por tarefa ao concluir + QR de conclusão rápida com valores predefinidos.",
|
||||
"on_complete_action_title": "Na conclusão: acionar ação HA (opcional)",
|
||||
"on_complete_action_desc": "Chama um serviço HA quando a tarefa é concluída — p. ex. reiniciar um contador no dispositivo.",
|
||||
"on_complete_action_service": "Serviço",
|
||||
"on_complete_action_target": "Entidade alvo",
|
||||
"on_complete_action_data": "Dados (JSON, opcional)",
|
||||
"on_complete_action_test": "Testar ação",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Escolher fotos",
|
||||
"choose_photo": "Escolher foto",
|
||||
"photos_android_hint": "A app Android entrega uma foto por seleção — cada seleção é adicionada.",
|
||||
"add_photos": "Adicionar fotos",
|
||||
"photos_limit": "Até {max} fotos por conclusão",
|
||||
"history_edit_photos_hint": "Uma foto removida aqui continua nos documentos do objeto.",
|
||||
"uploading": "A enviar…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Transferir",
|
||||
"doc_close": "Fechar",
|
||||
"doc_camera": "Tirar foto",
|
||||
"camera_capture_shoot": "Capturar",
|
||||
"camera_switch_lens": "Mudar de câmara",
|
||||
"doc_drop_hint": "Solte os ficheiros aqui",
|
||||
"doc_task_none": "Nenhum documento associado a esta tarefa.",
|
||||
"doc_link_existing": "Associar um documento…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Regista uma conclusão (define a última realização) quando o gatilho se resolve sozinho — p. ex., sal reabastecido, filtro substituído.",
|
||||
"doc_search": "Pesquisar documentos…",
|
||||
"doc_search_none": "Nenhum documento correspondente",
|
||||
"doc_description": "Descrição (opcional)",
|
||||
"doc_sort": "Ordenar documentos",
|
||||
"doc_sort_newest": "Mais recentes primeiro",
|
||||
"doc_sort_oldest": "Mais antigos primeiro",
|
||||
"doc_sort_title": "Por título (1, 2, 3 …)",
|
||||
"doc_sort_category": "Por categoria",
|
||||
"link_device_optional": "Associar a dispositivo existente (opcional)",
|
||||
"parent_object_optional": "Objeto principal (opcional)",
|
||||
"parent_none": "(Sem objeto principal)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Notificar apenas para a vista",
|
||||
"settings_notify_scope_all": "Todas as tarefas",
|
||||
"settings_notify_scope_hint": "Apenas as tarefas que correspondem aos filtros de etiqueta/utilizador da vista escolhida enviam lembretes. O estado, a ordenação e o agrupamento da vista são ignorados aqui.",
|
||||
"settings_notify_completed": "Notificações de conclusão",
|
||||
"settings_notify_completed_hint": "Aviso para a casa quando uma tarefa é concluída, com o motivo e quem a fez. \"Só automáticas\" anuncia apenas as conclusões que ninguém fez no local: recuperação do sensor, item marcado na lista de compras, chamada de serviço de uma automação. Só para o serviço da casa; horas de silêncio e limite diário aplicam-se.",
|
||||
"notify_completed_off": "Desligado",
|
||||
"notify_completed_automatic": "Só conclusões automáticas",
|
||||
"notify_completed_all": "Todas as conclusões",
|
||||
"settings_notify_rule": "A sua regra de notificação",
|
||||
"settings_notify_rule_hint": "Cada notificação dispara também o evento maintenance_supporter_notification (tipo, estado, objeto, tarefa, números de referência, prioridade, ligação, conteúdo). Encaminhe-o com uma automação — p. ex. para o Ticker, Telegram ou Pushover.",
|
||||
"settings_notify_event_only": "Apenas disparar o evento, não enviar nada",
|
||||
"settings_notify_event_only_hint": "A sua automação trata de toda a entrega. Intervalos de repetição, horas de silêncio e o limite diário continuam a decidir quando o evento dispara.",
|
||||
"settings_notify_extra_data": "Dados de notificação adicionais (modelo)",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; é fundido nos data da chamada notify (as suas chaves ganham). Variáveis: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Teste com o botão Enviar teste.",
|
||||
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; é fundido nos data da chamada notify (as suas chaves ganham). Variáveis: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Teste com o botão Enviar teste.",
|
||||
"card_saved_view": "Vista guardada",
|
||||
"card_saved_view_none": "Nenhuma",
|
||||
"card_saved_view_help": "Aplica os filtros de estado, utilizador e etiqueta da vista além dos filtros acima. A ordenação e o agrupamento da vista são definições de apresentação do painel e não se aplicam ao cartão.",
|
||||
"panel_card_tab": "Abrir no separador",
|
||||
"panel_card_tab_default": "Separador memorizado (como no painel)",
|
||||
"panel_card_view": "Abrir com a vista guardada",
|
||||
"panel_card_height": "Altura",
|
||||
"panel_card_height_help": "Vazio = preenche o ecrã abaixo do cartão (vista painel). Caso contrário, um comprimento CSS como 600px ou 80vh para vistas com outros cartões.",
|
||||
"panel_card_not_registered": "O painel Maintenance Supporter não está disponível — a integração está configurada?",
|
||||
"panel_card_load_failed": "Não foi possível carregar o painel Maintenance Supporter — recarregue a página (após uma atualização: limpe a cache do browser).",
|
||||
"doc_part_none": "Nenhum documento associado a esta peça.",
|
||||
"settings_templates_toggle_group": "Ativar ou desativar todos os modelos deste grupo",
|
||||
"setups_button": "Configurações sugeridas",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "Em bom estado",
|
||||
"battery_fleet_status_due": "A substituir",
|
||||
"battery_fleet_predicted_on": "Previsto por volta de {date}",
|
||||
"battery_fleet_predicted_typical": "Previsto por volta de {date} — {months} meses típicos para {type} ({source})",
|
||||
"lifetime_source_default": "valor predefinido integrado",
|
||||
"lifetime_source_table": "tabela integrada",
|
||||
"lifetime_source_learned": "aprendido de {n} substituições",
|
||||
"lifetime_source_override": "a sua definição",
|
||||
"lifetime_source_learned_device": "aprendido de {n} substituições deste dispositivo",
|
||||
"lifetime_source_learned_model": "aprendido de {n} substituições em dispositivos deste modelo",
|
||||
"battery_fleet_predicted_trend": "Previsto pela tendência de descarga desta pilha: por volta de {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Recarregável: carrega-se em vez de se substituir — nunca na lista de compras",
|
||||
"battery_fleet_sort_name": "Ordenar por nome",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Peças usadas",
|
||||
"print_inc_photos": "Fotografias",
|
||||
"print_inc_documents": "Documentos associados",
|
||||
"print_inc_doc_desc": "Descrições dos documentos",
|
||||
"print_inc_checklist": "Lista de verificação",
|
||||
"print_inc_notes": "Notas",
|
||||
"print_inc_costs": "Custo e duração",
|
||||
"print_inc_person": "Concluído por",
|
||||
"print_inc_refs": "Números de referência",
|
||||
"print_inc_bare": "Conclusões sem detalhes",
|
||||
"print_inc_qr": "Código QR por tarefa",
|
||||
"print_button": "Imprimir",
|
||||
"report_scan_hint": "Digitalize para abrir a tarefa",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Время работы",
|
||||
"runtime_hours": "Целевое время работы (часы)",
|
||||
"target_value": "Целевое значение",
|
||||
"baseline": "Базовое значение",
|
||||
"target_changes": "Целевые изменения",
|
||||
"for_minutes": "На (минут)",
|
||||
"time_based": "По времени",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Создан",
|
||||
"report_times_done": "Выполнено",
|
||||
"report_total_cost": "Итого",
|
||||
"report_every": "каждые {n} {unit}",
|
||||
"report_notes": "Заметки",
|
||||
"report_col_type": "Тип",
|
||||
"report_col_status": "Статус",
|
||||
"report_col_schedule": "Расписание",
|
||||
"duration": "Длительность",
|
||||
"minutes_short": "мин",
|
||||
"both": "Оба",
|
||||
"trigger_val": "Значение триггера",
|
||||
"complete_title": "Выполнить: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: неверный тип (ожидался: {type})",
|
||||
"err_invalid_choice": "{field}: недопустимое значение",
|
||||
"err_invalid_value": "{field}: неверное значение",
|
||||
"ws_err_not_found": "Не найдено — возможно, уже удалено",
|
||||
"ws_err_invalid_input": "Недопустимый ввод",
|
||||
"ws_err_invalid_date": "Недопустимая дата",
|
||||
"ws_err_invalid_format": "Недопустимый формат",
|
||||
"ws_err_invalid_url": "Недопустимая ссылка",
|
||||
"ws_err_not_configured": "Ещё не настроено",
|
||||
"ws_err_not_loaded": "Ещё не загружено — попробуйте чуть позже",
|
||||
"ws_err_limit_reached": "Достигнут предел",
|
||||
"ws_err_too_many": "Слишком много записей",
|
||||
"ws_err_too_large": "Слишком большой размер",
|
||||
"ws_err_not_archived": "Не в архиве",
|
||||
"ws_err_invalid_target": "Недопустимая цель",
|
||||
"ws_err_create_failed": "Не удалось создать",
|
||||
"ws_err_archived": "В архиве — сначала восстановите",
|
||||
"ws_err_already_archived": "Уже в архиве",
|
||||
"ws_err_unavailable": "Сейчас недоступно",
|
||||
"ws_err_unauthorized": "Недоступно для вашего пользователя",
|
||||
"ws_err_too_many_views": "Слишком много сохранённых представлений",
|
||||
"ws_err_storage_unavailable": "Хранилище недоступно",
|
||||
"ws_err_replace_failed": "Не удалось заменить",
|
||||
"ws_err_not_paused": "Не на паузе",
|
||||
"ws_err_not_available": "Ничего нет",
|
||||
"ws_err_no_url": "Ссылка не указана",
|
||||
"ws_err_no_phases": "У этой задачи нет фаз",
|
||||
"ws_err_invalid_view": "Недопустимое представление",
|
||||
"ws_err_invalid_user": "Неизвестный пользователь",
|
||||
"ws_err_invalid_range": "Недопустимый диапазон",
|
||||
"ws_err_invalid_parent": "Недопустимый родительский объект",
|
||||
"ws_err_invalid_device": "Неизвестное устройство",
|
||||
"ws_err_empty_csv": "CSV-файл пуст",
|
||||
"ws_err_empty": "Нечего импортировать",
|
||||
"ws_err_duplicate_failed": "Не удалось дублировать",
|
||||
"ws_err_already_paused": "Уже на паузе",
|
||||
"ws_err_invalid_cursor": "Недопустимая позиция фазы",
|
||||
"ws_err_invalid_entity_slug": "Недопустимое имя сущности",
|
||||
"ws_err_invalid_trigger_config": "Недопустимая настройка триггера",
|
||||
"ws_err_no_defaults": "Не заданы значения по умолчанию для быстрого завершения",
|
||||
"ws_err_self_link_device": "Объект нельзя связать с самим собой",
|
||||
"ws_err_too_early": "Слишком рано — эту задачу можно завершить только ближе к сроку",
|
||||
"feat_schedule_time": "Планирование по времени дня",
|
||||
"feat_schedule_time_desc": "Задачи становятся просроченными в определённое время дня, а не в полночь.",
|
||||
"schedule_time_toggle": "Срок в определённое время",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Срабатывать при равенстве (=)",
|
||||
"trigger_not_equals": "Срабатывать при отличии от (≠)",
|
||||
"for_at_least_minutes": "Не менее (минут)",
|
||||
"safety_interval_days": "Интервал безопасности (дни, опционально)",
|
||||
"safety_interval": "Интервал безопасности (опционально)",
|
||||
"trigger_combinator": "Совместить триггер и интервал",
|
||||
"trigger_combinator_any": "Триггер или интервал (что раньше)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Мои задачи",
|
||||
"tab_calendar": "Календарь",
|
||||
"cal_no_events": "Без обслуживания",
|
||||
"cal_window_7": "7 дней",
|
||||
"cal_window_14": "14 дней",
|
||||
"cal_window_30": "30 дней",
|
||||
"cal_window_365": "1 год",
|
||||
"cal_every_n_days": "каждые {n} дней",
|
||||
"cal_source_time": "По времени",
|
||||
"cal_source_time_adaptive": "По времени (адаптивно)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Не удалось сгенерировать QR-код.",
|
||||
"qr_error_no_url": "URL HA не настроен. Установите внешний или внутренний URL в Настройках → Система → Сеть.",
|
||||
"save_error": "Не удалось сохранить. Попробуйте ещё раз.",
|
||||
"subsave_warning": "Сохранено, но одна настройка отклонена: {detail}",
|
||||
"qr_print": "Печать",
|
||||
"qr_download": "Скачать SVG",
|
||||
"qr_action": "Действие при сканировании",
|
||||
"qr_action_view": "Просмотр",
|
||||
"qr_action_complete": "Отметить обслуживание как выполненное",
|
||||
"qr_url_mode": "Тип ссылки",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Локальный (mDNS)",
|
||||
"qr_mode_server": "URL сервера",
|
||||
"overview": "Обзор",
|
||||
"analysis": "Анализ",
|
||||
"recent_activities": "Недавние действия",
|
||||
"search_notes": "Поиск по заметкам",
|
||||
"avg_cost": "Ср. стоимость",
|
||||
"no_advanced_features": "Расширенные функции не включены",
|
||||
"no_advanced_features_hint": "Включите «Адаптивные интервалы» или «Сезонные паттерны» в настройках интеграции, чтобы увидеть здесь аналитику.",
|
||||
"analysis_not_enough_data": "Недостаточно данных для анализа.",
|
||||
"analysis_not_enough_data_hint": "Для анализа Вейбулла требуется минимум 5 выполненных обслуживаний; сезонные паттерны становятся видны после 6+ точек данных в месяц.",
|
||||
"analysis_manual_task_hint": "Ручные задачи без интервала не генерируют аналитику.",
|
||||
"completions": "выполнений",
|
||||
"current": "Текущий",
|
||||
"shorter": "Короче",
|
||||
"longer": "Длиннее",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Прошлые окна",
|
||||
"cal_forward_windows": "Будущие окна",
|
||||
"history_edit_title": "Редактировать запись истории",
|
||||
"history_delete_entry": "Удалить запись",
|
||||
"history_delete_confirm": "Удалить эту запись истории? Дата последнего выполнения вернётся к предыдущему; фотографии останутся у объекта, а израсходованные детали не будут возвращены на склад.",
|
||||
"history_edit_timestamp": "Метка времени",
|
||||
"manufacturer": "Производитель",
|
||||
"model": "Модель",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Дни предупреждения по умолчанию",
|
||||
"settings_consumable_threshold": "Порог «расходник заканчивается» (%)",
|
||||
"settings_battery_low_percent": "Порог «батарея разряжена» (%)",
|
||||
"settings_battery_lifetimes": "Типичный срок службы батарей",
|
||||
"settings_battery_lifetimes_hint": "Используется только для батарей без процента: заметка Battery Notes лишь с типом и датой замены или датчик, сообщающий только «низкий». Срок = последняя замена + этот срок службы. Для батарей с уровнем прогноз строится по измеренному разряду. После трёх замен одного типа медианный интервал заменяет встроенное значение — ваше значение всегда главнее.",
|
||||
"settings_battery_lifetime_months": "мес.",
|
||||
"settings_battery_lifetime_reset": "По умолчанию",
|
||||
"settings_battery_lifetime_in_fleet": "в вашем парке",
|
||||
"settings_battery_lifetime_learned_models": "Изучено по моделям: {list}",
|
||||
"settings_thresholds_hint": "Значения по умолчанию для новых «Предлагаемых настроек» (остаток в процентах) и для батарей парка без собственного порога Battery Notes. Существующие задачи сохраняют своё значение.",
|
||||
"settings_value_out_of_range": "Значение должно быть от {min} до {max}",
|
||||
"bn_summary": "{name}: {pct} % для {n} батарей",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} устройств с собственным порогом:",
|
||||
"bn_more": "+ ещё {n}",
|
||||
"settings_row_actions": "Действия в строках задач",
|
||||
"settings_ref_numbers_in_lists": "Номера ссылок в списках",
|
||||
"settings_ref_numbers_in_lists_hint": "Показывать метки #8 / #8.3 перед названиями объектов и задач в списке «Сегодня», таблице задач, карточках объектов и таблице объектов.",
|
||||
"row_actions_buttons_compact": "Кнопки (на телефоне только значки)",
|
||||
"row_actions_buttons": "Кнопки с текстом",
|
||||
"row_actions_icons": "Значки (классический вид)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Длительность откладывания (часы)",
|
||||
"settings_budget": "Бюджет",
|
||||
"settings_currency": "Валюта",
|
||||
"settings_currency_decimals": "Знаков после запятой в суммах",
|
||||
"settings_currency_decimals_hint": "Сколько знаков после запятой показывает каждая сумма: KPI, бюджеты, расходы в списках и отчётах, оповещения о бюджете. 0 = целые числа.",
|
||||
"settings_budget_monthly": "Месячный бюджет",
|
||||
"settings_budget_yearly": "Годовой бюджет",
|
||||
"settings_budget_alerts": "Оповещения о бюджете",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Экспорт YAML",
|
||||
"settings_export_csv": "Экспорт CSV",
|
||||
"settings_export_settings": "Экспорт настроек (JSON)",
|
||||
"settings_import_csv": "Импорт CSV",
|
||||
"settings_import_placeholder": "Вставьте содержимое JSON или CSV здесь…",
|
||||
"settings_import_btn": "Импортировать",
|
||||
"settings_import_success": "Импортировано объектов: {count}.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "HA-действие по задаче при завершении + QR быстрого завершения с предустановленными значениями.",
|
||||
"on_complete_action_title": "При завершении: запустить HA-действие (опционально)",
|
||||
"on_complete_action_desc": "Вызывает HA-сервис при завершении задачи — напр., сбросить счётчик на устройстве.",
|
||||
"on_complete_action_service": "Сервис",
|
||||
"on_complete_action_target": "Целевая сущность",
|
||||
"on_complete_action_data": "Данные (JSON, опционально)",
|
||||
"on_complete_action_test": "Тестировать действие",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Выбрать фото",
|
||||
"choose_photo": "Выбрать фото",
|
||||
"photos_android_hint": "Приложение для Android передаёт по одному фото за выбор — каждый выбор добавляется.",
|
||||
"add_photos": "Добавить фото",
|
||||
"photos_limit": "До {max} фото на одно выполнение",
|
||||
"history_edit_photos_hint": "Удалённое здесь фото остаётся в документах объекта.",
|
||||
"uploading": "Загрузка…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Скачать",
|
||||
"doc_close": "Закрыть",
|
||||
"doc_camera": "Сделать фото",
|
||||
"camera_capture_shoot": "Снять",
|
||||
"camera_switch_lens": "Сменить камеру",
|
||||
"doc_drop_hint": "Перетащите файлы сюда",
|
||||
"doc_task_none": "Нет документов, связанных с этой задачей.",
|
||||
"doc_link_existing": "Связать документ…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Записывает выполнение (обновляет дату последнего обслуживания), когда триггер устраняется сам — например, соль досыпана, фильтр заменён.",
|
||||
"doc_search": "Поиск документов…",
|
||||
"doc_search_none": "Нет подходящих документов",
|
||||
"doc_description": "Описание (необязательно)",
|
||||
"doc_sort": "Сортировать документы",
|
||||
"doc_sort_newest": "Сначала новые",
|
||||
"doc_sort_oldest": "Сначала старые",
|
||||
"doc_sort_title": "По названию (1, 2, 3 …)",
|
||||
"doc_sort_category": "По категории",
|
||||
"link_device_optional": "Привязать к существующему устройству (необязательно)",
|
||||
"parent_object_optional": "Родительский объект (необязательно)",
|
||||
"parent_none": "(Без родителя)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Уведомлять только для представления",
|
||||
"settings_notify_scope_all": "Все задачи",
|
||||
"settings_notify_scope_hint": "Напоминания отправляют только задачи, совпадающие с фильтрами метки/пользователя выбранного представления. Статус, сортировка и группировка представления здесь игнорируются.",
|
||||
"settings_notify_completed": "Уведомления о выполнении",
|
||||
"settings_notify_completed_hint": "Сообщение для всех домашних, когда задача выполнена, с причиной и исполнителем. «Только автоматические» сообщает лишь о выполнениях, которые никто не делал на месте: восстановление датчика, отметка в списке покупок, вызов службы автоматизацией. Только на общий сервис; тихие часы и дневной лимит действуют.",
|
||||
"notify_completed_off": "Выкл.",
|
||||
"notify_completed_automatic": "Только автоматические выполнения",
|
||||
"notify_completed_all": "Все выполнения",
|
||||
"settings_notify_rule": "Своё правило уведомлений",
|
||||
"settings_notify_rule_hint": "Каждое уведомление дополнительно вызывает событие maintenance_supporter_notification (вид, статус, объект, задача, номера ссылок, приоритет, ссылка, данные). Направьте его автоматизацией — например в Ticker, Telegram или Pushover.",
|
||||
"settings_notify_event_only": "Только событие, ничего не отправлять",
|
||||
"settings_notify_event_only_hint": "Доставку целиком выполняет ваша автоматизация. Интервалы повтора, тихие часы и дневной лимит по-прежнему определяют, когда срабатывает событие.",
|
||||
"settings_notify_extra_data": "Дополнительные данные уведомления (шаблон)",
|
||||
"settings_notify_extra_data_hint": "JSON или YAML, Jinja разрешён; добавляется в data вызова notify (ваши ключи важнее). Переменные: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Проверьте кнопкой «Отправить тест».",
|
||||
"settings_notify_extra_data_hint": "JSON или YAML, Jinja разрешён; добавляется в data вызова notify (ваши ключи важнее). Переменные: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Проверьте кнопкой «Отправить тест».",
|
||||
"card_saved_view": "Сохранённое представление",
|
||||
"card_saved_view_none": "Нет",
|
||||
"card_saved_view_help": "Применяет фильтры статуса, пользователя и метки представления в дополнение к фильтрам выше. Сортировка и группировка представления — настройки отображения панели и на карточке не применяются.",
|
||||
"panel_card_tab": "Открывать на вкладке",
|
||||
"panel_card_tab_default": "Запомненная вкладка (как в панели)",
|
||||
"panel_card_view": "Открывать с сохранённым видом",
|
||||
"panel_card_height": "Высота",
|
||||
"panel_card_height_help": "Пусто = заполнить экран под карточкой (вид «панель»). Иначе длина CSS, например 600px или 80vh, для видов с другими карточками.",
|
||||
"panel_card_not_registered": "Панель Maintenance Supporter недоступна — интеграция настроена?",
|
||||
"panel_card_load_failed": "Не удалось загрузить панель Maintenance Supporter — перезагрузите страницу (после обновления очистите кэш браузера).",
|
||||
"doc_part_none": "К этой запчасти не привязаны документы.",
|
||||
"settings_templates_toggle_group": "Включить или выключить все шаблоны этой группы",
|
||||
"setups_button": "Предлагаемые настройки",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "В норме",
|
||||
"battery_fleet_status_due": "Пора менять",
|
||||
"battery_fleet_predicted_on": "Ожидается примерно {date}",
|
||||
"battery_fleet_predicted_typical": "Ожидается около {date} — {months} мес. типично для {type} ({source})",
|
||||
"lifetime_source_default": "встроенное значение по умолчанию",
|
||||
"lifetime_source_table": "встроенная таблица",
|
||||
"lifetime_source_learned": "по {n} заменам",
|
||||
"lifetime_source_override": "ваша настройка",
|
||||
"lifetime_source_learned_device": "по {n} заменам этого устройства",
|
||||
"lifetime_source_learned_model": "по {n} заменам на устройствах этой модели",
|
||||
"battery_fleet_predicted_trend": "Прогноз по тренду разряда этой батареи: примерно {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Аккумулятор: заряжается, а не заменяется — никогда не попадает в список покупок",
|
||||
"battery_fleet_sort_name": "Сортировать по имени",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Использованные детали",
|
||||
"print_inc_photos": "Фото",
|
||||
"print_inc_documents": "Связанные документы",
|
||||
"print_inc_doc_desc": "Описания документов",
|
||||
"print_inc_checklist": "Чек-лист",
|
||||
"print_inc_notes": "Заметки",
|
||||
"print_inc_costs": "Стоимость и длительность",
|
||||
"print_inc_person": "Выполнил",
|
||||
"print_inc_refs": "Номера ссылок",
|
||||
"print_inc_bare": "Выполнения без подробностей",
|
||||
"print_inc_qr": "QR-код для каждой задачи",
|
||||
"print_button": "Печать",
|
||||
"report_scan_hint": "Отсканируйте, чтобы открыть задачу",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Körtid",
|
||||
"runtime_hours": "Måltid (timmar)",
|
||||
"target_value": "Målvärde",
|
||||
"baseline": "Baslinje",
|
||||
"target_changes": "Antal målförändringar",
|
||||
"for_minutes": "Under (minuter)",
|
||||
"time_based": "Tidsbaserad",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Genererad",
|
||||
"report_times_done": "Utfört",
|
||||
"report_total_cost": "Total kostnad",
|
||||
"report_every": "var {n}:e {unit}",
|
||||
"report_notes": "Anteckningar",
|
||||
"report_col_type": "Typ",
|
||||
"report_col_status": "Status",
|
||||
"report_col_schedule": "Schema",
|
||||
"duration": "Varaktighet",
|
||||
"minutes_short": "min",
|
||||
"both": "Båda",
|
||||
"trigger_val": "Utlösarvärde",
|
||||
"complete_title": "Slutför: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: fel typ (förväntad: {type})",
|
||||
"err_invalid_choice": "{field}: ej tillåtet värde",
|
||||
"err_invalid_value": "{field}: ogiltigt värde",
|
||||
"ws_err_not_found": "Hittades inte — det kan ha tagits bort under tiden",
|
||||
"ws_err_invalid_input": "Ogiltig inmatning",
|
||||
"ws_err_invalid_date": "Ogiltigt datum",
|
||||
"ws_err_invalid_format": "Ogiltigt format",
|
||||
"ws_err_invalid_url": "Ogiltig länk",
|
||||
"ws_err_not_configured": "Inte inställt ännu",
|
||||
"ws_err_not_loaded": "Inte inläst ännu — försök igen om en stund",
|
||||
"ws_err_limit_reached": "Gränsen nådd",
|
||||
"ws_err_too_many": "För många poster",
|
||||
"ws_err_too_large": "För stort",
|
||||
"ws_err_not_archived": "Inte arkiverad",
|
||||
"ws_err_invalid_target": "Ogiltigt mål",
|
||||
"ws_err_create_failed": "Kunde inte skapas",
|
||||
"ws_err_archived": "Arkiverad — återställ först",
|
||||
"ws_err_already_archived": "Redan arkiverad",
|
||||
"ws_err_unavailable": "För närvarande otillgänglig",
|
||||
"ws_err_unauthorized": "Inte tillåtet för din användare",
|
||||
"ws_err_too_many_views": "För många sparade vyer",
|
||||
"ws_err_storage_unavailable": "Lagringen är inte tillgänglig",
|
||||
"ws_err_replace_failed": "Ersättningen misslyckades",
|
||||
"ws_err_not_paused": "Inte pausad",
|
||||
"ws_err_not_available": "Inget tillgängligt",
|
||||
"ws_err_no_url": "Ingen länk angiven",
|
||||
"ws_err_no_phases": "Uppgiften har inga faser",
|
||||
"ws_err_invalid_view": "Ogiltig vy",
|
||||
"ws_err_invalid_user": "Okänd användare",
|
||||
"ws_err_invalid_range": "Ogiltigt intervall",
|
||||
"ws_err_invalid_parent": "Ogiltigt överordnat objekt",
|
||||
"ws_err_invalid_device": "Okänd enhet",
|
||||
"ws_err_empty_csv": "CSV-filen är tom",
|
||||
"ws_err_empty": "Inget att importera",
|
||||
"ws_err_duplicate_failed": "Kunde inte dupliceras",
|
||||
"ws_err_already_paused": "Redan pausad",
|
||||
"ws_err_invalid_cursor": "Ogiltig fasposition",
|
||||
"ws_err_invalid_entity_slug": "Ogiltigt entitetsnamn",
|
||||
"ws_err_invalid_trigger_config": "Ogiltig utlösarkonfiguration",
|
||||
"ws_err_no_defaults": "Inga standardvärden för snabbslutförande",
|
||||
"ws_err_self_link_device": "Ett objekt kan inte länkas till sig självt",
|
||||
"ws_err_too_early": "För tidigt — uppgiften kan bara slutföras närmare förfallodatumet",
|
||||
"feat_schedule_time": "Schemaläggning per tid på dygnet",
|
||||
"feat_schedule_time_desc": "Uppgifter blir försenade vid en specifik tid på dygnet istället för midnatt.",
|
||||
"schedule_time_toggle": "Förfaller vid en viss tid",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Utlös vid lika med (=)",
|
||||
"trigger_not_equals": "Utlös vid skilt från (≠)",
|
||||
"for_at_least_minutes": "Under minst (minuter)",
|
||||
"safety_interval_days": "Säkerhetsintervall (dagar, valfritt)",
|
||||
"safety_interval": "Säkerhetsintervall (valfritt)",
|
||||
"trigger_combinator": "Kombinera utlösare och intervall",
|
||||
"trigger_combinator_any": "Utlösare eller intervall (först uppfylld)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Mina uppgifter",
|
||||
"tab_calendar": "Kalender",
|
||||
"cal_no_events": "Inget underhåll",
|
||||
"cal_window_7": "7 dagar",
|
||||
"cal_window_14": "14 dagar",
|
||||
"cal_window_30": "30 dagar",
|
||||
"cal_window_365": "1 år",
|
||||
"cal_every_n_days": "var {n} dag",
|
||||
"cal_source_time": "Tidsbaserad",
|
||||
"cal_source_time_adaptive": "Tidsbaserad (adaptiv)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Kunde inte generera QR-kod.",
|
||||
"qr_error_no_url": "Ingen HA-URL konfigurerad. Ange en extern eller intern URL i Inställningar → System → Nätverk.",
|
||||
"save_error": "Kunde inte spara. Försök igen.",
|
||||
"subsave_warning": "Sparat, men en inställning avvisades: {detail}",
|
||||
"qr_print": "Skriv ut",
|
||||
"qr_download": "Ladda ner SVG",
|
||||
"qr_action": "Åtgärd vid skanning",
|
||||
"qr_action_view": "Visa underhållsinformation",
|
||||
"qr_action_complete": "Markera underhåll som slutfört",
|
||||
"qr_url_mode": "Länktyp",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Lokal (mDNS)",
|
||||
"qr_mode_server": "Server-URL",
|
||||
"overview": "Översikt",
|
||||
"analysis": "Analys",
|
||||
"recent_activities": "Senaste aktiviteter",
|
||||
"search_notes": "Sök i anteckningar",
|
||||
"avg_cost": "Snittlig kostnad",
|
||||
"no_advanced_features": "Inga avancerade funktioner aktiverade",
|
||||
"no_advanced_features_hint": "Aktivera „Adaptiva intervall” eller „Säsongsmönster” i integrationsinställningar för att se analysdata här.",
|
||||
"analysis_not_enough_data": "Inte tillräckligt med data för analys ännu.",
|
||||
"analysis_not_enough_data_hint": "Weibull-analys kräver minst 5 slutförda underhåll; säsongsmönster blir synliga efter 6+ datapunkter per månad.",
|
||||
"analysis_manual_task_hint": "Manuella uppgifter utan intervall genererar inte analysdata.",
|
||||
"completions": "slutföranden",
|
||||
"current": "Aktuell",
|
||||
"shorter": "Kortare",
|
||||
"longer": "Längre",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Tidigare fönster",
|
||||
"cal_forward_windows": "Kommande fönster",
|
||||
"history_edit_title": "Redigera historikpost",
|
||||
"history_delete_entry": "Ta bort post",
|
||||
"history_delete_confirm": "Ta bort den här historikposten? Datumet för senaste utförande faller tillbaka till det föregående; foton stannar hos objektet och förbrukade delar läggs inte tillbaka i lager.",
|
||||
"history_edit_timestamp": "Tidsstämpel",
|
||||
"manufacturer": "Tillverkare",
|
||||
"model": "Modell",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Standard varningsdagar",
|
||||
"settings_consumable_threshold": "Tröskel „förbrukningsartikel låg“ (%)",
|
||||
"settings_battery_low_percent": "Tröskel „batteri lågt“ (%)",
|
||||
"settings_battery_lifetimes": "Typiska batterilivslängder",
|
||||
"settings_battery_lifetimes_hint": "Används bara för batterier utan procent: en Battery Notes-anteckning med bara typ och bytesdatum, eller en sensor som bara rapporterar ”lågt”. Deras förfallodatum är senaste byte + denna livslängd. Batterier som rapporterar nivå får sin prognos från den uppmätta urladdningen. När flottan sett tre byten av en typ tar medianintervallet över från det inbyggda värdet — ditt eget värde vinner alltid.",
|
||||
"settings_battery_lifetime_months": "månader",
|
||||
"settings_battery_lifetime_reset": "Använd standard",
|
||||
"settings_battery_lifetime_in_fleet": "i din flotta",
|
||||
"settings_battery_lifetime_learned_models": "Inlärt per modell: {list}",
|
||||
"settings_thresholds_hint": "Standardvärden för nya Föreslagna inställningar (återstående procent) och för flottans batterier utan egen Battery Notes-tröskel. Befintliga uppgifter behåller sitt värde.",
|
||||
"settings_value_out_of_range": "Värdet måste vara mellan {min} och {max}",
|
||||
"bn_summary": "{name}: {pct} % för {n} batterier",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} enheter med egen tröskel:",
|
||||
"bn_more": "+ {n} till",
|
||||
"settings_row_actions": "Åtgärder i uppgiftsrader",
|
||||
"settings_ref_numbers_in_lists": "Referensnummer i listor",
|
||||
"settings_ref_numbers_in_lists_hint": "Visar brickorna #8 / #8.3 före objekt- och uppgiftsnamn i Idag-listan, uppgiftstabellen, objektkorten och objekttabellen.",
|
||||
"row_actions_buttons_compact": "Knappar (endast ikoner på mobilen)",
|
||||
"row_actions_buttons": "Knappar med text",
|
||||
"row_actions_icons": "Ikoner (klassiskt)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Snooza-tid (timmar)",
|
||||
"settings_budget": "Budget",
|
||||
"settings_currency": "Valuta",
|
||||
"settings_currency_decimals": "Decimaler för belopp",
|
||||
"settings_currency_decimals_hint": "Hur många decimaler varje belopp visar: nyckeltal, budgetar, kostnader i listor och rapporter, budgetvarningar. 0 = hela tal.",
|
||||
"settings_budget_monthly": "Månatlig budget",
|
||||
"settings_budget_yearly": "Årlig budget",
|
||||
"settings_budget_alerts": "Budgetvarningar",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Exportera YAML",
|
||||
"settings_export_csv": "Exportera CSV",
|
||||
"settings_export_settings": "Exportera inställningar (JSON)",
|
||||
"settings_import_csv": "Importera CSV",
|
||||
"settings_import_placeholder": "Klistra in JSON- eller CSV-innehåll här…",
|
||||
"settings_import_btn": "Importera",
|
||||
"settings_import_success": "{count} objekt importerade.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "HA-åtgärd per uppgift vid slutförande + snabb-slutför-QR med förinställda värden.",
|
||||
"on_complete_action_title": "Vid slutförande: utlös HA-åtgärd (valfritt)",
|
||||
"on_complete_action_desc": "Anropar en HA-tjänst när uppgiften slutförs — t.ex. återställ en räknare på enheten.",
|
||||
"on_complete_action_service": "Tjänst",
|
||||
"on_complete_action_target": "Målentitet",
|
||||
"on_complete_action_data": "Data (JSON, valfritt)",
|
||||
"on_complete_action_test": "Testa åtgärd",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Välj foton",
|
||||
"choose_photo": "Välj foto",
|
||||
"photos_android_hint": "Android-appen lämnar över ett foto per val — varje val läggs till.",
|
||||
"add_photos": "Lägg till foton",
|
||||
"photos_limit": "Högst {max} foton per slutförande",
|
||||
"history_edit_photos_hint": "Ett foto som tas bort här finns kvar bland objektets dokument.",
|
||||
"uploading": "Laddar upp…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Ladda ner",
|
||||
"doc_close": "Stäng",
|
||||
"doc_camera": "Ta foto",
|
||||
"camera_capture_shoot": "Ta bild",
|
||||
"camera_switch_lens": "Byt kamera",
|
||||
"doc_drop_hint": "Släpp filer här",
|
||||
"doc_task_none": "Inga dokument kopplade till denna uppgift.",
|
||||
"doc_link_existing": "Koppla ett dokument…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Registrerar ett slutförande (sätter senast utförd) när utlösaren löser sig själv — t.ex. salt påfyllt, filter bytt.",
|
||||
"doc_search": "Sök dokument…",
|
||||
"doc_search_none": "Inga matchande dokument",
|
||||
"doc_description": "Beskrivning (valfritt)",
|
||||
"doc_sort": "Sortera dokument",
|
||||
"doc_sort_newest": "Nyaste först",
|
||||
"doc_sort_oldest": "Äldsta först",
|
||||
"doc_sort_title": "Efter titel (1, 2, 3 …)",
|
||||
"doc_sort_category": "Efter kategori",
|
||||
"link_device_optional": "Länka till befintlig enhet (valfritt)",
|
||||
"parent_object_optional": "Överordnat objekt (valfritt)",
|
||||
"parent_none": "(Inget överordnat)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Avisera endast för vy",
|
||||
"settings_notify_scope_all": "Alla uppgifter",
|
||||
"settings_notify_scope_hint": "Endast uppgifter som matchar den valda vyns etikett-/användarfilter skickar påminnelser. Vyns status, sortering och gruppering ignoreras här.",
|
||||
"settings_notify_completed": "Aviseringar om slutförande",
|
||||
"settings_notify_completed_hint": "Ett meddelande till hushållet när en uppgift slutförts, med orsak och vem som gjorde det. \"Endast automatiska\" meddelar bara slutföranden som ingen gjorde på plats: sensoråterhämtning, avbockning i inköpslistan, ett tjänsteanrop från en automation. Bara till hushållstjänsten; tysta timmar och dagsgränsen gäller.",
|
||||
"notify_completed_off": "Av",
|
||||
"notify_completed_automatic": "Endast automatiska slutföranden",
|
||||
"notify_completed_all": "Alla slutföranden",
|
||||
"settings_notify_rule": "Din egen aviseringsregel",
|
||||
"settings_notify_rule_hint": "Varje avisering utlöser även händelsen maintenance_supporter_notification (typ, status, objekt, uppgift, referensnummer, prioritet, länk, nyttolast). Styr den med en automation — t.ex. till Ticker, Telegram eller Pushover.",
|
||||
"settings_notify_event_only": "Utlös bara händelsen, skicka inget själv",
|
||||
"settings_notify_event_only_hint": "Din automation sköter hela leveransen. Upprepningsintervall, tysta timmar och dagstaket avgör fortfarande när händelsen utlöses.",
|
||||
"settings_notify_extra_data": "Extra aviseringsdata (mall)",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillåtet; slås ihop med notify-anropets data (dina nycklar vinner). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Testa med knappen Skicka test.",
|
||||
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillåtet; slås ihop med notify-anropets data (dina nycklar vinner). Variabler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Testa med knappen Skicka test.",
|
||||
"card_saved_view": "Sparad vy",
|
||||
"card_saved_view_none": "Ingen",
|
||||
"card_saved_view_help": "Tillämpar vyns status-, användar- och etikettfilter utöver filtren ovan. Vyns sortering och gruppering är panelvisningsinställningar och tillämpas inte på kortet.",
|
||||
"panel_card_tab": "Öppna på fliken",
|
||||
"panel_card_tab_default": "Ihågkommen flik (som i panelen)",
|
||||
"panel_card_view": "Öppna med sparad vy",
|
||||
"panel_card_height": "Höjd",
|
||||
"panel_card_height_help": "Tomt = fyller skärmen under kortet (panelvy). Annars en CSS-längd som 600px eller 80vh för vyer med andra kort.",
|
||||
"panel_card_not_registered": "Maintenance Supporter-panelen är inte tillgänglig — är integrationen konfigurerad?",
|
||||
"panel_card_load_failed": "Maintenance Supporter-panelen kunde inte läsas in — ladda om sidan (efter en uppdatering: rensa webbläsarens cache).",
|
||||
"doc_part_none": "Inga dokument länkade till denna del.",
|
||||
"settings_templates_toggle_group": "Aktivera eller inaktivera alla mallar i denna grupp",
|
||||
"setups_button": "Föreslagna uppsättningar",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "I ordning",
|
||||
"battery_fleet_status_due": "Dags",
|
||||
"battery_fleet_predicted_on": "Väntas omkring {date}",
|
||||
"battery_fleet_predicted_typical": "Väntas omkring {date} — {months} månader typiskt för {type} ({source})",
|
||||
"lifetime_source_default": "inbyggt standardvärde",
|
||||
"lifetime_source_table": "inbyggd tabell",
|
||||
"lifetime_source_learned": "inlärt från {n} byten",
|
||||
"lifetime_source_override": "din inställning",
|
||||
"lifetime_source_learned_device": "inlärt från {n} byten på den här enheten",
|
||||
"lifetime_source_learned_model": "inlärt från {n} byten på enheter av den här modellen",
|
||||
"battery_fleet_predicted_trend": "Förutspått utifrån batteriets urladdningstrend: omkring {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Uppladdningsbart: ladda i stället för att byta — aldrig på inköpslistan",
|
||||
"battery_fleet_sort_name": "Sortera efter namn",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Använda delar",
|
||||
"print_inc_photos": "Foton",
|
||||
"print_inc_documents": "Länkade dokument",
|
||||
"print_inc_doc_desc": "Dokumentbeskrivningar",
|
||||
"print_inc_checklist": "Checklista",
|
||||
"print_inc_notes": "Anteckningar",
|
||||
"print_inc_costs": "Kostnad och tid",
|
||||
"print_inc_person": "Utförd av",
|
||||
"print_inc_refs": "Referensnummer",
|
||||
"print_inc_bare": "Utföranden utan detaljer",
|
||||
"print_inc_qr": "QR-kod per uppgift",
|
||||
"print_button": "Skriv ut",
|
||||
"report_scan_hint": "Skanna för att öppna uppgiften",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "Çalışma süresi",
|
||||
"runtime_hours": "Hedef çalışma süresi (saat)",
|
||||
"target_value": "Hedef değer",
|
||||
"baseline": "Başlangıç değeri",
|
||||
"target_changes": "Hedef değişiklik sayısı",
|
||||
"for_minutes": "Süre (dakika)",
|
||||
"time_based": "Zamana dayalı",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "Oluşturulma",
|
||||
"report_times_done": "Yapıldı",
|
||||
"report_total_cost": "Toplam maliyet",
|
||||
"report_every": "her {n} {unit}",
|
||||
"report_notes": "Notlar",
|
||||
"report_col_type": "Tür",
|
||||
"report_col_status": "Durum",
|
||||
"report_col_schedule": "Zamanlama",
|
||||
"duration": "Süre",
|
||||
"minutes_short": "dk",
|
||||
"both": "Her ikisi",
|
||||
"trigger_val": "Tetikleme değeri",
|
||||
"complete_title": "Tamamla: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: yanlış tür (beklenen: {type})",
|
||||
"err_invalid_choice": "{field}: izin verilen bir değer değil",
|
||||
"err_invalid_value": "{field}: geçersiz değer",
|
||||
"ws_err_not_found": "Bulunamadı — bu arada silinmiş olabilir",
|
||||
"ws_err_invalid_input": "Geçersiz giriş",
|
||||
"ws_err_invalid_date": "Geçersiz tarih",
|
||||
"ws_err_invalid_format": "Geçersiz biçim",
|
||||
"ws_err_invalid_url": "Geçersiz bağlantı",
|
||||
"ws_err_not_configured": "Henüz ayarlanmadı",
|
||||
"ws_err_not_loaded": "Henüz yüklenmedi — birazdan tekrar deneyin",
|
||||
"ws_err_limit_reached": "Sınıra ulaşıldı",
|
||||
"ws_err_too_many": "Çok fazla kayıt",
|
||||
"ws_err_too_large": "Çok büyük",
|
||||
"ws_err_not_archived": "Arşivlenmemiş",
|
||||
"ws_err_invalid_target": "Geçersiz hedef",
|
||||
"ws_err_create_failed": "Oluşturulamadı",
|
||||
"ws_err_archived": "Arşivlenmiş — önce geri yükleyin",
|
||||
"ws_err_already_archived": "Zaten arşivlenmiş",
|
||||
"ws_err_unavailable": "Şu anda kullanılamıyor",
|
||||
"ws_err_unauthorized": "Kullanıcınız için izin verilmiyor",
|
||||
"ws_err_too_many_views": "Çok fazla kayıtlı görünüm",
|
||||
"ws_err_storage_unavailable": "Depolama kullanılamıyor",
|
||||
"ws_err_replace_failed": "Değiştirme başarısız",
|
||||
"ws_err_not_paused": "Duraklatılmamış",
|
||||
"ws_err_not_available": "Kullanılabilir bir şey yok",
|
||||
"ws_err_no_url": "Bağlantı verilmedi",
|
||||
"ws_err_no_phases": "Bu görevin aşaması yok",
|
||||
"ws_err_invalid_view": "Geçersiz görünüm",
|
||||
"ws_err_invalid_user": "Bilinmeyen kullanıcı",
|
||||
"ws_err_invalid_range": "Geçersiz aralık",
|
||||
"ws_err_invalid_parent": "Geçersiz üst nesne",
|
||||
"ws_err_invalid_device": "Bilinmeyen cihaz",
|
||||
"ws_err_empty_csv": "CSV dosyası boş",
|
||||
"ws_err_empty": "İçe aktarılacak bir şey yok",
|
||||
"ws_err_duplicate_failed": "Çoğaltılamadı",
|
||||
"ws_err_already_paused": "Zaten duraklatılmış",
|
||||
"ws_err_invalid_cursor": "Geçersiz aşama konumu",
|
||||
"ws_err_invalid_entity_slug": "Geçersiz varlık adı",
|
||||
"ws_err_invalid_trigger_config": "Geçersiz tetikleyici yapılandırması",
|
||||
"ws_err_no_defaults": "Hızlı tamamlama varsayılanları ayarlanmamış",
|
||||
"ws_err_self_link_device": "Bir nesne kendisine bağlanamaz",
|
||||
"ws_err_too_early": "Çok erken — bu görev ancak son tarihine yaklaşınca tamamlanabilir",
|
||||
"feat_schedule_time": "Gün içi saat zamanlaması",
|
||||
"feat_schedule_time_desc": "Görevler gece yarısı yerine günün belirli bir saatinde gecikmiş olur.",
|
||||
"schedule_time_toggle": "Belirli bir saatte vadesi dolar",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "Şuna eşitse tetikle (=)",
|
||||
"trigger_not_equals": "Şundan farklıysa tetikle (≠)",
|
||||
"for_at_least_minutes": "En az (dakika)",
|
||||
"safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)",
|
||||
"safety_interval": "Güvenlik aralığı (isteğe bağlı)",
|
||||
"trigger_combinator": "Tetikleyici ve aralığı birleştir",
|
||||
"trigger_combinator_any": "Tetikleyici veya aralık (ilk gerçekleşen)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "Görevlerim",
|
||||
"tab_calendar": "Takvim",
|
||||
"cal_no_events": "Bakım yok",
|
||||
"cal_window_7": "7 gün",
|
||||
"cal_window_14": "14 gün",
|
||||
"cal_window_30": "30 gün",
|
||||
"cal_window_365": "1 yıl",
|
||||
"cal_every_n_days": "her {n} günde bir",
|
||||
"cal_source_time": "Zamana dayalı",
|
||||
"cal_source_time_adaptive": "Zamana dayalı (uyarlanabilir)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "QR kodu oluşturulamadı.",
|
||||
"qr_error_no_url": "Yapılandırılmış bir HA URL'si yok. Lütfen Ayarlar → Sistem → Ağ bölümünden bir dış veya iç URL ayarlayın.",
|
||||
"save_error": "Kaydedilemedi. Lütfen tekrar deneyin.",
|
||||
"subsave_warning": "Kaydedildi, ancak bir ayar reddedildi: {detail}",
|
||||
"qr_print": "Yazdır",
|
||||
"qr_download": "SVG indir",
|
||||
"qr_action": "Tarama eylemi",
|
||||
"qr_action_view": "Bakım bilgisini görüntüle",
|
||||
"qr_action_complete": "Bakımı tamamlandı olarak işaretle",
|
||||
"qr_url_mode": "Bağlantı türü",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "Yerel (mDNS)",
|
||||
"qr_mode_server": "Sunucu URL'si",
|
||||
"overview": "Genel Bakış",
|
||||
"analysis": "Analiz",
|
||||
"recent_activities": "Son Etkinlikler",
|
||||
"search_notes": "Notlarda ara",
|
||||
"avg_cost": "Ort. Maliyet",
|
||||
"no_advanced_features": "Etkin gelişmiş özellik yok",
|
||||
"no_advanced_features_hint": "Burada analiz verilerini görmek için entegrasyon ayarlarından “Uyarlanabilir Aralıklar” veya “Mevsimsel Desenler” özelliğini etkinleştirin.",
|
||||
"analysis_not_enough_data": "Analiz için henüz yeterli veri yok.",
|
||||
"analysis_not_enough_data_hint": "Weibull analizi en az 5 tamamlanmış bakım gerektirir; mevsimsel desenler ay başına 6+ veri noktasından sonra görünür olur.",
|
||||
"analysis_manual_task_hint": "Aralığı olmayan manuel görevler analiz verisi üretmez.",
|
||||
"completions": "tamamlama",
|
||||
"current": "Mevcut",
|
||||
"shorter": "Daha kısa",
|
||||
"longer": "Daha uzun",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "Geçmiş dönemler",
|
||||
"cal_forward_windows": "Gelecek dönemler",
|
||||
"history_edit_title": "Geçmiş kaydını düzenle",
|
||||
"history_delete_entry": "Kaydı sil",
|
||||
"history_delete_confirm": "Bu geçmiş kaydı silinsin mi? Son yapılma tarihi bir önceki tamamlamaya döner; fotoğraflar nesnede kalır, kullanılan parçalar stoğa geri eklenmez.",
|
||||
"history_edit_timestamp": "Zaman damgası",
|
||||
"manufacturer": "Üretici",
|
||||
"model": "Model",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "Varsayılan uyarı günleri",
|
||||
"settings_consumable_threshold": "„Sarf malzemesi düşük“ eşiği (%)",
|
||||
"settings_battery_low_percent": "„Pil zayıf“ eşiği (%)",
|
||||
"settings_battery_lifetimes": "Tipik pil ömürleri",
|
||||
"settings_battery_lifetimes_hint": "Yalnızca yüzdesi olmayan piller için kullanılır: sadece tür ve değişim tarihi olan bir Battery Notes notu ya da yalnızca \"düşük\" bildiren bir sensör. Vade = son değişim + bu ömür. Seviye bildiren pillerin tahmini ölçülen deşarjdan gelir. Filo bir türden üç değişim gördüğünde ortanca aralık yerleşik değerin yerini alır — kendi değerin her zaman kazanır.",
|
||||
"settings_battery_lifetime_months": "ay",
|
||||
"settings_battery_lifetime_reset": "Varsayılanı kullan",
|
||||
"settings_battery_lifetime_in_fleet": "filonda",
|
||||
"settings_battery_lifetime_learned_models": "Modele göre öğrenilen: {list}",
|
||||
"settings_thresholds_hint": "Yeni Önerilen kurulumlar (kalan yüzde) ve kendi Battery Notes eşiği olmayan filo pilleri için varsayılanlar. Mevcut görevler değerlerini korur.",
|
||||
"settings_value_out_of_range": "Değer {min} ile {max} arasında olmalıdır",
|
||||
"bn_summary": "{name}: {n} pil için % {pct}",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "Kendi eşiği olan {n} cihaz:",
|
||||
"bn_more": "+ {n} tane daha",
|
||||
"settings_row_actions": "Görev satırı eylemleri",
|
||||
"settings_ref_numbers_in_lists": "Listelerde referans numaraları",
|
||||
"settings_ref_numbers_in_lists_hint": "Bugün listesinde, görev tablosunda, nesne kartlarında ve nesne tablosunda nesne ve görev adlarının önünde #8 / #8.3 etiketlerini gösterir.",
|
||||
"row_actions_buttons_compact": "Düğmeler (telefonda yalnızca simgeler)",
|
||||
"row_actions_buttons": "Metinli düğmeler",
|
||||
"row_actions_icons": "Simgeler (klasik)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "Erteleme süresi (saat)",
|
||||
"settings_budget": "Bütçe",
|
||||
"settings_currency": "Para birimi",
|
||||
"settings_currency_decimals": "Tutarlarda ondalık basamak",
|
||||
"settings_currency_decimals_hint": "Her tutarın kaç ondalık basamak göstereceği: KPI'lar, bütçeler, liste ve raporlardaki maliyetler, bütçe uyarıları. 0 = tam sayılar.",
|
||||
"settings_budget_monthly": "Aylık bütçe",
|
||||
"settings_budget_yearly": "Yıllık bütçe",
|
||||
"settings_budget_alerts": "Bütçe uyarıları",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "YAML dışa aktar",
|
||||
"settings_export_csv": "CSV dışa aktar",
|
||||
"settings_export_settings": "Ayarları dışa aktar (JSON)",
|
||||
"settings_import_csv": "CSV içe aktar",
|
||||
"settings_import_placeholder": "JSON veya CSV içeriğini buraya yapıştırın…",
|
||||
"settings_import_btn": "İçe aktar",
|
||||
"settings_import_success": "{count} nesne başarıyla içe aktarıldı.",
|
||||
@@ -644,7 +681,6 @@
|
||||
"feat_completion_actions_desc": "Görev başına tamamlamada HA eylemi + önceden ayarlı değerlerle hızlı tamamlama QR'ı.",
|
||||
"on_complete_action_title": "Tamamlandığında: HA eylemi tetikle (isteğe bağlı)",
|
||||
"on_complete_action_desc": "Görev tamamlandığında bir HA servisi çağırır — örn. cihazdaki bir sayacı sıfırlar.",
|
||||
"on_complete_action_service": "Servis",
|
||||
"on_complete_action_target": "Hedef varlık",
|
||||
"on_complete_action_target_hint": "Not: varlık alan adı servisle eşleşmelidir — örn. 'button.press' yalnızca button.* üzerinde, 'counter.increment' yalnızca counter.* üzerinde, 'input_button.press' yalnızca input_button.* üzerinde çalışır. Eşleşmezse eylem sessizce başarısız olur (HA günlüğü: 'Referenced entities ... missing or not currently available').",
|
||||
"on_complete_action_data": "Veri (JSON, isteğe bağlı)",
|
||||
@@ -710,7 +746,6 @@
|
||||
"choose_photos": "Fotoğraf seç",
|
||||
"choose_photo": "Fotoğraf seç",
|
||||
"photos_android_hint": "Android uygulaması her seçimde bir fotoğraf iletir — her seçim eklenir.",
|
||||
"add_photos": "Fotoğraf ekle",
|
||||
"photos_limit": "Tamamlama başına en fazla {max} fotoğraf",
|
||||
"history_edit_photos_hint": "Burada kaldırılan fotoğraf nesnenin belgelerinde kalır.",
|
||||
"uploading": "Yükleniyor…",
|
||||
@@ -731,6 +766,8 @@
|
||||
"doc_download": "İndir",
|
||||
"doc_close": "Kapat",
|
||||
"doc_camera": "Fotoğraf çek",
|
||||
"camera_capture_shoot": "Çek",
|
||||
"camera_switch_lens": "Kamerayı değiştir",
|
||||
"doc_drop_hint": "Dosyaları buraya bırakın",
|
||||
"doc_task_none": "Bu göreve bağlı belge yok.",
|
||||
"doc_link_existing": "Belge bağla…",
|
||||
@@ -747,6 +784,12 @@
|
||||
"auto_complete_on_recovery_help": "Tetikleyici kendiliğinden temizlendiğinde bir tamamlama kaydeder (son yapılmayı ayarlar) — örn. tuz dolduruldu, filtre değiştirildi.",
|
||||
"doc_search": "Belgelerde ara…",
|
||||
"doc_search_none": "Eşleşen belge yok",
|
||||
"doc_description": "Açıklama (isteğe bağlı)",
|
||||
"doc_sort": "Belgeleri sırala",
|
||||
"doc_sort_newest": "En yeni önce",
|
||||
"doc_sort_oldest": "En eski önce",
|
||||
"doc_sort_title": "Başlığa göre (1, 2, 3 …)",
|
||||
"doc_sort_category": "Kategoriye göre",
|
||||
"link_device_optional": "Mevcut bir cihaza bağla (isteğe bağlı)",
|
||||
"parent_object_optional": "Üst nesne (isteğe bağlı)",
|
||||
"parent_none": "(Üst nesne yok)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Yalnızca şu görünüm için bildir",
|
||||
"settings_notify_scope_all": "Tüm görevler",
|
||||
"settings_notify_scope_hint": "Yalnızca seçili kayıtlı görünümün etiket/kullanıcı filtreleriyle eşleşen görevler hatırlatma gönderir. Görünümün durum, sıralama ve gruplaması burada dikkate alınmaz.",
|
||||
"settings_notify_completed": "Tamamlama bildirimleri",
|
||||
"settings_notify_completed_hint": "Bir görev tamamlandığında ev halkına nedeni ve kimin yaptığıyla birlikte haber verir. \"Yalnızca otomatik\" sadece kimsenin yerinde yapmadığı tamamlamaları bildirir: sensör toparlanması, alışveriş listesinde işaretleme, bir otomasyonun servis çağrısı. Yalnızca ev servisine gider; sessiz saatler ve günlük sınır geçerlidir.",
|
||||
"notify_completed_off": "Kapalı",
|
||||
"notify_completed_automatic": "Yalnızca otomatik tamamlamalar",
|
||||
"notify_completed_all": "Tüm tamamlamalar",
|
||||
"settings_notify_rule": "Kendi bildirim kuralınız",
|
||||
"settings_notify_rule_hint": "Her bildirim ayrıca maintenance_supporter_notification olayını tetikler (tür, durum, nesne, görev, referans numaraları, öncelik, derin bağlantı, yük). Bir otomasyonla yönlendirin — ör. Ticker, Telegram veya Pushover'a.",
|
||||
"settings_notify_event_only": "Yalnızca olayı tetikle, kendin gönderme",
|
||||
"settings_notify_event_only_hint": "Teslimatın tamamını otomasyonunuz yapar. Tekrar aralıkları, sessiz saatler ve günlük sınır, olayın ne zaman tetikleneceği için geçerli kalır.",
|
||||
"settings_notify_extra_data": "Ek bildirim verileri (şablon)",
|
||||
"settings_notify_extra_data_hint": "JSON veya YAML, Jinja kullanılabilir; notify çağrısının data alanına birleştirilir (sizin anahtarlarınız öncelikli). Değişkenler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test gönder düğmesiyle deneyin.",
|
||||
"settings_notify_extra_data_hint": "JSON veya YAML, Jinja kullanılabilir; notify çağrısının data alanına birleştirilir (sizin anahtarlarınız öncelikli). Değişkenler: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Test gönder düğmesiyle deneyin.",
|
||||
"card_saved_view": "Kayıtlı görünüm",
|
||||
"card_saved_view_none": "Yok",
|
||||
"card_saved_view_help": "Görünümün durum, kullanıcı ve etiket filtrelerini yukarıdaki filtrelerin üzerine uygular. Görünümün sıralaması ve gruplaması panel görüntüleme ayarlarıdır ve kartta uygulanmaz.",
|
||||
"panel_card_tab": "Şu sekmede aç",
|
||||
"panel_card_tab_default": "Hatırlanan sekme (paneldeki gibi)",
|
||||
"panel_card_view": "Kayıtlı görünümle aç",
|
||||
"panel_card_height": "Yükseklik",
|
||||
"panel_card_height_help": "Boş = kartın altındaki ekranı doldurur (panel görünümü). Aksi halde başka kartlar içeren görünümler için 600px veya 80vh gibi bir CSS uzunluğu.",
|
||||
"panel_card_not_registered": "Maintenance Supporter paneli kullanılamıyor — entegrasyon kurulu mu?",
|
||||
"panel_card_load_failed": "Maintenance Supporter paneli yüklenemedi — sayfayı yenileyin (bir güncellemeden sonra: tarayıcı önbelleğini temizleyin).",
|
||||
"doc_part_none": "Bu parçaya bağlı belge yok.",
|
||||
"settings_templates_toggle_group": "Bu gruptaki tüm şablonları etkinleştir veya devre dışı bırak",
|
||||
"setups_button": "Önerilen kurulumlar",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "İyi durumda",
|
||||
"battery_fleet_status_due": "Süresi doldu",
|
||||
"battery_fleet_predicted_on": "Yaklaşık {date} tarihinde bekleniyor",
|
||||
"battery_fleet_predicted_typical": "Yaklaşık {date} — {type} için tipik {months} ay ({source})",
|
||||
"lifetime_source_default": "yerleşik varsayılan",
|
||||
"lifetime_source_table": "yerleşik tablo",
|
||||
"lifetime_source_learned": "{n} değişimden öğrenildi",
|
||||
"lifetime_source_override": "senin ayarın",
|
||||
"lifetime_source_learned_device": "bu cihazın {n} değişiminden öğrenildi",
|
||||
"lifetime_source_learned_model": "bu modeldeki cihazların {n} değişiminden öğrenildi",
|
||||
"battery_fleet_predicted_trend": "Bu pilin deşarj eğiliminden tahmin edildi: yaklaşık {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Şarj edilebilir: değiştirmek yerine şarj edin — alışveriş listesine asla girmez",
|
||||
"battery_fleet_sort_name": "Ada göre sırala",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Kullanılan parçalar",
|
||||
"print_inc_photos": "Fotoğraflar",
|
||||
"print_inc_documents": "Bağlı belgeler",
|
||||
"print_inc_doc_desc": "Belge açıklamaları",
|
||||
"print_inc_checklist": "Kontrol listesi",
|
||||
"print_inc_notes": "Notlar",
|
||||
"print_inc_costs": "Maliyet ve süre",
|
||||
"print_inc_person": "Tamamlayan",
|
||||
"print_inc_refs": "Referans numaraları",
|
||||
"print_inc_bare": "Ayrıntısız tamamlamalar",
|
||||
"print_inc_qr": "Görev başına QR kodu",
|
||||
"print_button": "Yazdır",
|
||||
"report_scan_hint": "Görevi açmak için tarayın",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"runtime": "Напрацювання",
|
||||
"runtime_hours": "Цільове напрацювання (години)",
|
||||
"target_value": "Цільове значення",
|
||||
"baseline": "Базове значення",
|
||||
"target_changes": "Цільова кількість змін",
|
||||
"for_minutes": "Протягом (хвилин)",
|
||||
"time_based": "За часом",
|
||||
@@ -103,12 +102,12 @@
|
||||
"report_generated": "Створено",
|
||||
"report_times_done": "Виконано",
|
||||
"report_total_cost": "Загальна вартість",
|
||||
"report_every": "кожні {n} {unit}",
|
||||
"report_notes": "Нотатки",
|
||||
"report_col_type": "Тип",
|
||||
"report_col_status": "Статус",
|
||||
"report_col_schedule": "Розклад",
|
||||
"duration": "Тривалість",
|
||||
"minutes_short": "хв",
|
||||
"both": "Обидва",
|
||||
"trigger_val": "Значення тригера",
|
||||
"complete_title": "Виконати: ",
|
||||
@@ -125,6 +124,45 @@
|
||||
"err_wrong_type": "{field}: невірний тип (очікувалось: {type})",
|
||||
"err_invalid_choice": "{field}: недопустиме значення",
|
||||
"err_invalid_value": "{field}: невірне значення",
|
||||
"ws_err_not_found": "Не знайдено — можливо, вже видалено",
|
||||
"ws_err_invalid_input": "Неприпустиме введення",
|
||||
"ws_err_invalid_date": "Неприпустима дата",
|
||||
"ws_err_invalid_format": "Неприпустимий формат",
|
||||
"ws_err_invalid_url": "Неприпустиме посилання",
|
||||
"ws_err_not_configured": "Ще не налаштовано",
|
||||
"ws_err_not_loaded": "Ще не завантажено — спробуйте трохи пізніше",
|
||||
"ws_err_limit_reached": "Досягнуто межу",
|
||||
"ws_err_too_many": "Забагато записів",
|
||||
"ws_err_too_large": "Занадто великий розмір",
|
||||
"ws_err_not_archived": "Не в архіві",
|
||||
"ws_err_invalid_target": "Неприпустима ціль",
|
||||
"ws_err_create_failed": "Не вдалося створити",
|
||||
"ws_err_archived": "В архіві — спочатку відновіть",
|
||||
"ws_err_already_archived": "Уже в архіві",
|
||||
"ws_err_unavailable": "Зараз недоступно",
|
||||
"ws_err_unauthorized": "Недоступно для вашого користувача",
|
||||
"ws_err_too_many_views": "Забагато збережених подань",
|
||||
"ws_err_storage_unavailable": "Сховище недоступне",
|
||||
"ws_err_replace_failed": "Не вдалося замінити",
|
||||
"ws_err_not_paused": "Не на паузі",
|
||||
"ws_err_not_available": "Нічого немає",
|
||||
"ws_err_no_url": "Посилання не вказано",
|
||||
"ws_err_no_phases": "У цього завдання немає фаз",
|
||||
"ws_err_invalid_view": "Неприпустиме подання",
|
||||
"ws_err_invalid_user": "Невідомий користувач",
|
||||
"ws_err_invalid_range": "Неприпустимий діапазон",
|
||||
"ws_err_invalid_parent": "Неприпустимий батьківський об'єкт",
|
||||
"ws_err_invalid_device": "Невідомий пристрій",
|
||||
"ws_err_empty_csv": "CSV-файл порожній",
|
||||
"ws_err_empty": "Нічого імпортувати",
|
||||
"ws_err_duplicate_failed": "Не вдалося дублювати",
|
||||
"ws_err_already_paused": "Уже на паузі",
|
||||
"ws_err_invalid_cursor": "Неприпустима позиція фази",
|
||||
"ws_err_invalid_entity_slug": "Неприпустима назва сутності",
|
||||
"ws_err_invalid_trigger_config": "Неприпустиме налаштування тригера",
|
||||
"ws_err_no_defaults": "Не задано значення за замовчуванням для швидкого завершення",
|
||||
"ws_err_self_link_device": "Об'єкт не можна пов'язати із самим собою",
|
||||
"ws_err_too_early": "Зарано — це завдання можна завершити лише ближче до терміну",
|
||||
"feat_schedule_time": "Планування за часом доби",
|
||||
"feat_schedule_time_desc": "Задачі стають простроченими у певний час доби, а не опівночі.",
|
||||
"schedule_time_toggle": "Термін у визначений час",
|
||||
@@ -205,7 +243,6 @@
|
||||
"trigger_equals": "Спрацьовувати при рівності (=)",
|
||||
"trigger_not_equals": "Спрацьовувати при відмінності від (≠)",
|
||||
"for_at_least_minutes": "Протягом не менше (хвилин)",
|
||||
"safety_interval_days": "Страховий інтервал (дні, необов'язково)",
|
||||
"safety_interval": "Страховий інтервал (необов'язково)",
|
||||
"trigger_combinator": "Поєднати тригер та інтервал",
|
||||
"trigger_combinator_any": "Тригер або інтервал (що раніше)",
|
||||
@@ -245,10 +282,6 @@
|
||||
"my_tasks": "Мої завдання",
|
||||
"tab_calendar": "Календар",
|
||||
"cal_no_events": "Без обслуговування",
|
||||
"cal_window_7": "7 днів",
|
||||
"cal_window_14": "14 днів",
|
||||
"cal_window_30": "30 днів",
|
||||
"cal_window_365": "1 рік",
|
||||
"cal_every_n_days": "кожні {n} днів",
|
||||
"cal_source_time": "За часом",
|
||||
"cal_source_time_adaptive": "За часом (адаптивно)",
|
||||
@@ -346,9 +379,9 @@
|
||||
"qr_error": "Не вдалося згенерувати QR-код.",
|
||||
"qr_error_no_url": "URL Home Assistant не налаштовано. Задайте зовнішню або внутрішню URL-адресу в Налаштування → Система → Мережа.",
|
||||
"save_error": "Не вдалося зберегти. Спробуйте ще раз.",
|
||||
"subsave_warning": "Збережено, але одне налаштування відхилено: {detail}",
|
||||
"qr_print": "Друкувати",
|
||||
"qr_download": "Завантажити SVG",
|
||||
"qr_action": "Дія при скануванні",
|
||||
"qr_action_view": "Переглянути",
|
||||
"qr_action_complete": "Позначити обслуговування виконаним",
|
||||
"qr_url_mode": "Тип посилання",
|
||||
@@ -356,16 +389,9 @@
|
||||
"qr_mode_local": "Локальний (mDNS)",
|
||||
"qr_mode_server": "URL сервера",
|
||||
"overview": "Огляд",
|
||||
"analysis": "Аналіз",
|
||||
"recent_activities": "Остання активність",
|
||||
"search_notes": "Пошук у примітках",
|
||||
"avg_cost": "Сер. вартість",
|
||||
"no_advanced_features": "Розширені функції не увімкнено",
|
||||
"no_advanced_features_hint": "Увімкніть «Адаптивні інтервали» або «Сезонні закономірності» в налаштуваннях інтеграції, щоб побачити тут дані аналізу.",
|
||||
"analysis_not_enough_data": "Недостатньо даних для аналізу.",
|
||||
"analysis_not_enough_data_hint": "Аналіз Вейбулла потребує щонайменше 5 виконаних обслуговувань; сезонні закономірності стають видимими після 6+ записів на місяць.",
|
||||
"analysis_manual_task_hint": "Ручні завдання без інтервалу не генерують дані аналізу.",
|
||||
"completions": "виконань",
|
||||
"current": "Поточний",
|
||||
"shorter": "Коротший",
|
||||
"longer": "Довший",
|
||||
@@ -411,6 +437,8 @@
|
||||
"cal_past_windows": "Минулі вікна",
|
||||
"cal_forward_windows": "Майбутні вікна",
|
||||
"history_edit_title": "Редагувати запис історії",
|
||||
"history_delete_entry": "Видалити запис",
|
||||
"history_delete_confirm": "Видалити цей запис історії? Дата останнього виконання повернеться до попереднього; фотографії залишаться в об'єкта, а витрачені деталі не повернуться на склад.",
|
||||
"history_edit_timestamp": "Позначка часу",
|
||||
"manufacturer": "Виробник",
|
||||
"model": "Модель",
|
||||
@@ -468,6 +496,12 @@
|
||||
"settings_default_warning": "Днів попередження за замовчуванням",
|
||||
"settings_consumable_threshold": "Поріг «витратний матеріал закінчується» (%)",
|
||||
"settings_battery_low_percent": "Поріг «батарея розряджена» (%)",
|
||||
"settings_battery_lifetimes": "Типовий строк служби батарей",
|
||||
"settings_battery_lifetimes_hint": "Використовується лише для батарей без відсотка: нотатка Battery Notes лише з типом і датою заміни або датчик, що повідомляє тільки «низький». Термін = остання заміна + цей строк служби. Для батарей з рівнем прогноз будується за виміряним розрядом. Після трьох замін одного типу медіанний інтервал замінює вбудоване значення — ваше значення завжди головніше.",
|
||||
"settings_battery_lifetime_months": "міс.",
|
||||
"settings_battery_lifetime_reset": "За замовчуванням",
|
||||
"settings_battery_lifetime_in_fleet": "у вашому парку",
|
||||
"settings_battery_lifetime_learned_models": "Вивчено за моделями: {list}",
|
||||
"settings_thresholds_hint": "Значення за замовчуванням для нових «Запропонованих налаштувань» (залишок у відсотках) і для батарей парку без власного порогу Battery Notes. Наявні завдання зберігають своє значення.",
|
||||
"settings_value_out_of_range": "Значення має бути від {min} до {max}",
|
||||
"bn_summary": "{name}: {pct} % для {n} батарей",
|
||||
@@ -476,6 +510,8 @@
|
||||
"bn_overrides": "{n} пристроїв із власним порогом:",
|
||||
"bn_more": "+ ще {n}",
|
||||
"settings_row_actions": "Дії в рядках завдань",
|
||||
"settings_ref_numbers_in_lists": "Номери посилань у списках",
|
||||
"settings_ref_numbers_in_lists_hint": "Показувати мітки #8 / #8.3 перед назвами об'єктів і завдань у списку «Сьогодні», таблиці завдань, картках об'єктів і таблиці об'єктів.",
|
||||
"row_actions_buttons_compact": "Кнопки (на телефоні лише значки)",
|
||||
"row_actions_buttons": "Кнопки з текстом",
|
||||
"row_actions_icons": "Значки (класичний вигляд)",
|
||||
@@ -525,6 +561,8 @@
|
||||
"settings_snooze_hours": "Тривалість відкладення (години)",
|
||||
"settings_budget": "Бюджет",
|
||||
"settings_currency": "Валюта",
|
||||
"settings_currency_decimals": "Знаків після коми в сумах",
|
||||
"settings_currency_decimals_hint": "Скільки знаків після коми показує кожна сума: KPI, бюджети, витрати у списках і звітах, сповіщення про бюджет. 0 = цілі числа.",
|
||||
"settings_budget_monthly": "Щомісячний бюджет",
|
||||
"settings_budget_yearly": "Щорічний бюджет",
|
||||
"settings_budget_alerts": "Сповіщення про бюджет",
|
||||
@@ -534,7 +572,6 @@
|
||||
"settings_export_yaml": "Експортувати YAML",
|
||||
"settings_export_csv": "Експортувати CSV",
|
||||
"settings_export_settings": "Експорт налаштувань (JSON)",
|
||||
"settings_import_csv": "Імпортувати CSV",
|
||||
"settings_import_placeholder": "Вставте вміст JSON або CSV сюди…",
|
||||
"settings_import_btn": "Імпортувати",
|
||||
"settings_import_success": "{count} об'єктів успішно імпортовано.",
|
||||
@@ -612,7 +649,6 @@
|
||||
"feat_completion_actions_desc": "Дія HA по завданню при завершенні + QR швидкого завершення з попередньо встановленими значеннями.",
|
||||
"on_complete_action_title": "При завершенні: викликати HA-дію (опційно)",
|
||||
"on_complete_action_desc": "Викликає HA-сервіс, коли завдання завершено — напр., скинути лічильник на пристрої.",
|
||||
"on_complete_action_service": "Сервіс",
|
||||
"on_complete_action_target": "Цільова сутність",
|
||||
"on_complete_action_data": "Дані (JSON, опційно)",
|
||||
"on_complete_action_test": "Тестувати дію",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "Вибрати фото",
|
||||
"choose_photo": "Вибрати фото",
|
||||
"photos_android_hint": "Застосунок для Android передає по одному фото за вибір — кожен вибір додається.",
|
||||
"add_photos": "Додати фото",
|
||||
"photos_limit": "До {max} фото на одне виконання",
|
||||
"history_edit_photos_hint": "Видалене тут фото залишається в документах об'єкта.",
|
||||
"uploading": "Завантаження…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "Завантажити",
|
||||
"doc_close": "Закрити",
|
||||
"doc_camera": "Зробити фото",
|
||||
"camera_capture_shoot": "Зняти",
|
||||
"camera_switch_lens": "Змінити камеру",
|
||||
"doc_drop_hint": "Перетягніть файли сюди",
|
||||
"doc_task_none": "Немає документів, пов'язаних із цим завданням.",
|
||||
"doc_link_existing": "Пов'язати документ…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "Записує виконання (оновлює дату останнього обслуговування), коли тригер зникає сам — наприклад, сіль досипано, фільтр замінено.",
|
||||
"doc_search": "Пошук документів…",
|
||||
"doc_search_none": "Немає відповідних документів",
|
||||
"doc_description": "Опис (необов'язково)",
|
||||
"doc_sort": "Сортувати документи",
|
||||
"doc_sort_newest": "Спочатку нові",
|
||||
"doc_sort_oldest": "Спочатку старі",
|
||||
"doc_sort_title": "За назвою (1, 2, 3 …)",
|
||||
"doc_sort_category": "За категорією",
|
||||
"link_device_optional": "Прив'язати до наявного пристрою (необов'язково)",
|
||||
"parent_object_optional": "Батьківський об'єкт (необов'язково)",
|
||||
"parent_none": "(Без батьківського)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "Сповіщати лише для подання",
|
||||
"settings_notify_scope_all": "Усі завдання",
|
||||
"settings_notify_scope_hint": "Нагадування надсилають лише завдання, що відповідають фільтрам мітки/користувача обраного подання. Статус, сортування та групування подання тут ігноруються.",
|
||||
"settings_notify_completed": "Сповіщення про виконання",
|
||||
"settings_notify_completed_hint": "Повідомлення для всіх домашніх, коли завдання виконано, з причиною та виконавцем. «Лише автоматичні» повідомляє тільки про виконання, які ніхто не робив на місці: відновлення датчика, позначка у списку покупок, виклик служби автоматизацією. Лише на спільний сервіс; тихі години та денний ліміт діють.",
|
||||
"notify_completed_off": "Вимк.",
|
||||
"notify_completed_automatic": "Лише автоматичні виконання",
|
||||
"notify_completed_all": "Усі виконання",
|
||||
"settings_notify_rule": "Власне правило сповіщень",
|
||||
"settings_notify_rule_hint": "Кожне сповіщення додатково викликає подію maintenance_supporter_notification (вид, статус, об'єкт, завдання, номери посилань, пріоритет, посилання, дані). Спрямуйте її автоматизацією — наприклад у Ticker, Telegram чи Pushover.",
|
||||
"settings_notify_event_only": "Лише подія, нічого не надсилати",
|
||||
"settings_notify_event_only_hint": "Доставку повністю виконує ваша автоматизація. Інтервали повтору, тихі години та денний ліміт і далі визначають, коли спрацьовує подія.",
|
||||
"settings_notify_extra_data": "Додаткові дані сповіщення (шаблон)",
|
||||
"settings_notify_extra_data_hint": "JSON або YAML, Jinja дозволено; додається до data виклику notify (ваші ключі мають перевагу). Змінні: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Перевірте кнопкою «Надіслати тест».",
|
||||
"settings_notify_extra_data_hint": "JSON або YAML, Jinja дозволено; додається до data виклику notify (ваші ключі мають перевагу). Змінні: kind, status, priority, labels, notes, area_name, object_name, task_name, object_ref, task_ref, days_until_due, next_due, last_performed, responsible_user_id, sensor_entity_id, url, target, title, message. Перевірте кнопкою «Надіслати тест».",
|
||||
"card_saved_view": "Збережене подання",
|
||||
"card_saved_view_none": "Немає",
|
||||
"card_saved_view_help": "Застосовує фільтри статусу, користувача та мітки подання додатково до фільтрів вище. Сортування та групування подання — налаштування відображення панелі й на картці не застосовуються.",
|
||||
"panel_card_tab": "Відкривати на вкладці",
|
||||
"panel_card_tab_default": "Запам'ятована вкладка (як у панелі)",
|
||||
"panel_card_view": "Відкривати зі збереженим виглядом",
|
||||
"panel_card_height": "Висота",
|
||||
"panel_card_height_help": "Порожньо = заповнити екран під карткою (вигляд «панель»). Інакше довжина CSS, наприклад 600px або 80vh, для виглядів з іншими картками.",
|
||||
"panel_card_not_registered": "Панель Maintenance Supporter недоступна — інтеграцію налаштовано?",
|
||||
"panel_card_load_failed": "Не вдалося завантажити панель Maintenance Supporter — перезавантажте сторінку (після оновлення очистіть кеш браузера).",
|
||||
"doc_part_none": "До цієї запчастини не прив'язано документів.",
|
||||
"settings_templates_toggle_group": "Увімкнути або вимкнути всі шаблони цієї групи",
|
||||
"setups_button": "Пропоновані налаштування",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "У нормі",
|
||||
"battery_fleet_status_due": "Час замінити",
|
||||
"battery_fleet_predicted_on": "Очікується приблизно {date}",
|
||||
"battery_fleet_predicted_typical": "Очікується близько {date} — {months} міс. типово для {type} ({source})",
|
||||
"lifetime_source_default": "вбудоване значення за замовчуванням",
|
||||
"lifetime_source_table": "вбудована таблиця",
|
||||
"lifetime_source_learned": "за {n} замінами",
|
||||
"lifetime_source_override": "ваше налаштування",
|
||||
"lifetime_source_learned_device": "за {n} замінами цього пристрою",
|
||||
"lifetime_source_learned_model": "за {n} замінами на пристроях цієї моделі",
|
||||
"battery_fleet_predicted_trend": "Прогноз за трендом розряду цієї батареї: приблизно {date} ({confidence})",
|
||||
"battery_fleet_rechargeable": "Акумулятор: заряджається, а не замінюється — ніколи не потрапляє до списку покупок",
|
||||
"battery_fleet_sort_name": "Сортувати за назвою",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "Використані деталі",
|
||||
"print_inc_photos": "Фото",
|
||||
"print_inc_documents": "Пов'язані документи",
|
||||
"print_inc_doc_desc": "Описи документів",
|
||||
"print_inc_checklist": "Чек-лист",
|
||||
"print_inc_notes": "Нотатки",
|
||||
"print_inc_costs": "Вартість і тривалість",
|
||||
"print_inc_person": "Виконав",
|
||||
"print_inc_refs": "Номери посилань",
|
||||
"print_inc_bare": "Виконання без подробиць",
|
||||
"print_inc_qr": "QR-код для кожного завдання",
|
||||
"print_button": "Друк",
|
||||
"report_scan_hint": "Скануйте, щоб відкрити завдання",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"runtime": "运行时间",
|
||||
"runtime_hours": "目标运行时长 (小时)",
|
||||
"target_value": "目标值",
|
||||
"baseline": "基准线",
|
||||
"target_changes": "目标变化次数",
|
||||
"for_minutes": "持续 (分钟)",
|
||||
"time_based": "基于时间",
|
||||
@@ -105,12 +104,12 @@
|
||||
"report_generated": "生成于",
|
||||
"report_times_done": "完成次数",
|
||||
"report_total_cost": "总费用",
|
||||
"report_every": "每 {n} {unit}",
|
||||
"report_notes": "备注",
|
||||
"report_col_type": "类型",
|
||||
"report_col_status": "状态",
|
||||
"report_col_schedule": "计划",
|
||||
"duration": "耗时",
|
||||
"minutes_short": "分钟",
|
||||
"both": "两者",
|
||||
"trigger_val": "触发值",
|
||||
"complete_title": "完成: ",
|
||||
@@ -127,6 +126,45 @@
|
||||
"err_wrong_type": "{field}: 类型错误 (预期: {type})",
|
||||
"err_invalid_choice": "{field}: 无效选项",
|
||||
"err_invalid_value": "{field}: 无效数值",
|
||||
"ws_err_not_found": "未找到 — 可能已被删除",
|
||||
"ws_err_invalid_input": "输入无效",
|
||||
"ws_err_invalid_date": "日期无效",
|
||||
"ws_err_invalid_format": "格式无效",
|
||||
"ws_err_invalid_url": "链接无效",
|
||||
"ws_err_not_configured": "尚未设置",
|
||||
"ws_err_not_loaded": "尚未加载 — 请稍后重试",
|
||||
"ws_err_limit_reached": "已达上限",
|
||||
"ws_err_too_many": "条目过多",
|
||||
"ws_err_too_large": "内容过大",
|
||||
"ws_err_not_archived": "未归档",
|
||||
"ws_err_invalid_target": "目标无效",
|
||||
"ws_err_create_failed": "无法创建",
|
||||
"ws_err_archived": "已归档 — 请先恢复",
|
||||
"ws_err_already_archived": "已归档",
|
||||
"ws_err_unavailable": "当前不可用",
|
||||
"ws_err_unauthorized": "您的用户无权执行此操作",
|
||||
"ws_err_too_many_views": "保存的视图过多",
|
||||
"ws_err_storage_unavailable": "存储不可用",
|
||||
"ws_err_replace_failed": "替换失败",
|
||||
"ws_err_not_paused": "未暂停",
|
||||
"ws_err_not_available": "没有可用项",
|
||||
"ws_err_no_url": "未提供链接",
|
||||
"ws_err_no_phases": "此任务没有阶段",
|
||||
"ws_err_invalid_view": "视图无效",
|
||||
"ws_err_invalid_user": "未知用户",
|
||||
"ws_err_invalid_range": "范围无效",
|
||||
"ws_err_invalid_parent": "父级对象无效",
|
||||
"ws_err_invalid_device": "未知设备",
|
||||
"ws_err_empty_csv": "CSV 文件为空",
|
||||
"ws_err_empty": "没有可导入的内容",
|
||||
"ws_err_duplicate_failed": "无法复制",
|
||||
"ws_err_already_paused": "已暂停",
|
||||
"ws_err_invalid_cursor": "阶段位置无效",
|
||||
"ws_err_invalid_entity_slug": "实体名称无效",
|
||||
"ws_err_invalid_trigger_config": "触发器配置无效",
|
||||
"ws_err_no_defaults": "未设置快速完成默认值",
|
||||
"ws_err_self_link_device": "对象不能链接到自身",
|
||||
"ws_err_too_early": "为时过早 — 此任务只能在临近到期日时完成",
|
||||
"feat_schedule_time": "具体时间调度",
|
||||
"feat_schedule_time_desc": "任务将在特定时间点变为逾期,而非默认的午夜。",
|
||||
"schedule_time_toggle": "在特定时间到期",
|
||||
@@ -207,7 +245,6 @@
|
||||
"trigger_equals": "等于时触发(=)",
|
||||
"trigger_not_equals": "不等于时触发(≠)",
|
||||
"for_at_least_minutes": "持续至少 (分钟)",
|
||||
"safety_interval_days": "安全间隔 (天, 可选)",
|
||||
"safety_interval": "安全间隔 (可选)",
|
||||
"trigger_combinator": "组合触发器与间隔",
|
||||
"trigger_combinator_any": "触发器或间隔(先到者)",
|
||||
@@ -247,10 +284,6 @@
|
||||
"my_tasks": "我的任务",
|
||||
"tab_calendar": "日历",
|
||||
"cal_no_events": "无维护事项",
|
||||
"cal_window_7": "7 天",
|
||||
"cal_window_14": "14 天",
|
||||
"cal_window_30": "30 天",
|
||||
"cal_window_365": "1 年",
|
||||
"cal_every_n_days": "每 {n} 天",
|
||||
"cal_source_time": "基于时间",
|
||||
"cal_source_time_adaptive": "基于时间 (自适应)",
|
||||
@@ -348,9 +381,9 @@
|
||||
"qr_error": "无法生成二维码。",
|
||||
"qr_error_no_url": "未配置 HA URL。请前往“设置 → 系统 → 网络”设置外部或内部 URL。",
|
||||
"save_error": "保存失败。请重试。",
|
||||
"subsave_warning": "已保存,但有一项设置被拒绝:{detail}",
|
||||
"qr_print": "打印",
|
||||
"qr_download": "下载 SVG",
|
||||
"qr_action": "扫码后动作",
|
||||
"qr_action_view": "查看维护信息",
|
||||
"qr_action_complete": "标记维护为已完成",
|
||||
"qr_url_mode": "链接类型",
|
||||
@@ -358,16 +391,9 @@
|
||||
"qr_mode_local": "本地 (mDNS)",
|
||||
"qr_mode_server": "服务器 URL",
|
||||
"overview": "概览",
|
||||
"analysis": "分析",
|
||||
"recent_activities": "近期活动",
|
||||
"search_notes": "搜索备注",
|
||||
"avg_cost": "平均成本",
|
||||
"no_advanced_features": "未启用高级功能",
|
||||
"no_advanced_features_hint": "请在集成设置中启用“自适应间隔”或“季节性模式”以在此查看分析数据。",
|
||||
"analysis_not_enough_data": "暂无足够的分析数据。",
|
||||
"analysis_not_enough_data_hint": "韦伯分析需要至少 5 次完成记录;季节性模式在每月有 6 个以上数据点后可见。",
|
||||
"analysis_manual_task_hint": "无间隔的手动任务不会生成分析数据。",
|
||||
"completions": "次完成记录",
|
||||
"current": "当前",
|
||||
"shorter": "较短",
|
||||
"longer": "较长",
|
||||
@@ -413,6 +439,8 @@
|
||||
"cal_past_windows": "过去的窗口",
|
||||
"cal_forward_windows": "未来的窗口",
|
||||
"history_edit_title": "编辑历史记录",
|
||||
"history_delete_entry": "删除记录",
|
||||
"history_delete_confirm": "删除此历史记录?上次完成日期将回退到前一次完成;照片仍保留在对象中,已消耗的配件不会退回库存。",
|
||||
"history_edit_timestamp": "时间戳",
|
||||
"manufacturer": "制造商",
|
||||
"model": "型号",
|
||||
@@ -470,6 +498,12 @@
|
||||
"settings_default_warning": "默认预警天数",
|
||||
"settings_consumable_threshold": "耗材“不足”阈值(%)",
|
||||
"settings_battery_low_percent": "电池“电量低”阈值(%)",
|
||||
"settings_battery_lifetimes": "电池典型寿命",
|
||||
"settings_battery_lifetimes_hint": "仅用于没有百分比的电池:只有类型和更换日期的 Battery Notes 记录,或只报告“低电量”的传感器。其到期日 = 上次更换 + 此寿命。报告电量的电池改用实测放电来预测。同一类型出现三次更换后,中位间隔将取代内置值——你自己的值始终优先。",
|
||||
"settings_battery_lifetime_months": "个月",
|
||||
"settings_battery_lifetime_reset": "使用默认值",
|
||||
"settings_battery_lifetime_in_fleet": "在你的电池群中",
|
||||
"settings_battery_lifetime_learned_models": "按型号学习:{list}",
|
||||
"settings_thresholds_hint": "用于新的“建议设置”(剩余百分比)以及没有自身 Battery Notes 阈值的电池组电池。现有任务保留其值。",
|
||||
"settings_value_out_of_range": "数值必须介于 {min} 和 {max} 之间",
|
||||
"bn_summary": "{name}:{n} 块电池为 {pct}%",
|
||||
@@ -478,6 +512,8 @@
|
||||
"bn_overrides": "{n} 台设备有自己的阈值:",
|
||||
"bn_more": "+ 另外 {n} 个",
|
||||
"settings_row_actions": "任务行操作",
|
||||
"settings_ref_numbers_in_lists": "在列表中显示编号",
|
||||
"settings_ref_numbers_in_lists_hint": "在“今天”列表、任务表、对象卡片和对象表中的对象与任务名称前显示 #8 / #8.3 编号标签。",
|
||||
"row_actions_buttons_compact": "按钮(手机上仅显示图标)",
|
||||
"row_actions_buttons": "带文字的按钮",
|
||||
"row_actions_icons": "图标(经典)",
|
||||
@@ -527,6 +563,8 @@
|
||||
"settings_snooze_hours": "稍后提醒间隔 (小时)",
|
||||
"settings_budget": "预算",
|
||||
"settings_currency": "货币单位",
|
||||
"settings_currency_decimals": "金额小数位数",
|
||||
"settings_currency_decimals_hint": "每个金额显示几位小数:KPI、预算、列表和报告中的费用、预算提醒。0 = 整数。",
|
||||
"settings_budget_monthly": "月度预算",
|
||||
"settings_budget_yearly": "年度预算",
|
||||
"settings_budget_alerts": "预算警报",
|
||||
@@ -536,7 +574,6 @@
|
||||
"settings_export_yaml": "导出 YAML",
|
||||
"settings_export_csv": "导出 CSV",
|
||||
"settings_export_settings": "导出设置(JSON)",
|
||||
"settings_import_csv": "导入 CSV",
|
||||
"settings_import_placeholder": "在此粘贴 JSON 或 CSV 内容…",
|
||||
"settings_import_btn": "导入",
|
||||
"settings_import_success": "成功导入 {count} 个维护项。",
|
||||
@@ -638,7 +675,6 @@
|
||||
"feat_completion_actions_desc": "为每个任务配置完成时的 HA 动作 + 预设值的快速完成二维码。",
|
||||
"on_complete_action_title": "完成时:触发 HA 动作(可选)",
|
||||
"on_complete_action_desc": "任务完成时调用 HA 服务 — 例如重置设备上的物理计数器。",
|
||||
"on_complete_action_service": "服务",
|
||||
"on_complete_action_target": "目标实体",
|
||||
"on_complete_action_target_hint": "注意:实体的域必须与服务匹配 — 例如 'button.press' 仅适用于 button.* 实体,'counter.increment' 仅适用于 counter.* 实体等。如果不匹配,动作将静默失败。",
|
||||
"on_complete_action_data": "数据(JSON, 可选)",
|
||||
@@ -704,7 +740,6 @@
|
||||
"choose_photos": "选择照片",
|
||||
"choose_photo": "选择照片",
|
||||
"photos_android_hint": "Android 应用每次只传一张照片——每次选择都会添加。",
|
||||
"add_photos": "添加照片",
|
||||
"photos_limit": "每次完成最多 {max} 张照片",
|
||||
"history_edit_photos_hint": "在此移除的照片仍保留在对象的文档中。",
|
||||
"uploading": "上传中…",
|
||||
@@ -725,6 +760,8 @@
|
||||
"doc_download": "下载",
|
||||
"doc_close": "关闭",
|
||||
"doc_camera": "拍照",
|
||||
"camera_capture_shoot": "拍摄",
|
||||
"camera_switch_lens": "切换摄像头",
|
||||
"doc_drop_hint": "将文件拖放到此处",
|
||||
"doc_task_none": "没有与此任务关联的文档。",
|
||||
"doc_link_existing": "关联文档…",
|
||||
@@ -741,6 +778,12 @@
|
||||
"auto_complete_on_recovery_help": "当触发条件自行解除时(如已加盐、已更换滤芯),记录一次完成并更新上次执行日期。",
|
||||
"doc_search": "搜索文档…",
|
||||
"doc_search_none": "没有匹配的文档",
|
||||
"doc_description": "说明(可选)",
|
||||
"doc_sort": "文档排序",
|
||||
"doc_sort_newest": "最新在前",
|
||||
"doc_sort_oldest": "最早在前",
|
||||
"doc_sort_title": "按标题(1、2、3…)",
|
||||
"doc_sort_category": "按类别",
|
||||
"link_device_optional": "关联到现有设备(可选)",
|
||||
"parent_object_optional": "父对象(可选)",
|
||||
"parent_none": "(无父对象)",
|
||||
@@ -854,15 +897,27 @@
|
||||
"settings_notify_scope": "仅为该视图发送通知",
|
||||
"settings_notify_scope_all": "所有任务",
|
||||
"settings_notify_scope_hint": "只有匹配所选视图的标签/用户筛选的任务才会发送提醒。视图的状态、排序和分组在此处被忽略。",
|
||||
"settings_notify_completed": "完成通知",
|
||||
"settings_notify_completed_hint": "任务完成时通知家庭成员,附上原因和执行者。“仅自动”只通报无人现场操作的完成:传感器恢复、购物清单勾选、自动化的服务调用。仅发送到家庭通知服务;免打扰时段和每日上限有效。",
|
||||
"notify_completed_off": "关闭",
|
||||
"notify_completed_automatic": "仅自动完成",
|
||||
"notify_completed_all": "所有完成",
|
||||
"settings_notify_rule": "自定义通知规则",
|
||||
"settings_notify_rule_hint": "每条通知还会触发事件 maintenance_supporter_notification(类型、状态、对象、任务、参考编号、优先级、深层链接、载荷)。用自动化转发它——例如到 Ticker、Telegram 或 Pushover。",
|
||||
"settings_notify_event_only": "仅触发事件,自身不发送",
|
||||
"settings_notify_event_only_hint": "由你的自动化完成全部发送。重复间隔、免打扰时段和每日上限仍决定事件何时触发。",
|
||||
"settings_notify_extra_data": "附加通知数据(模板)",
|
||||
"settings_notify_extra_data_hint": "JSON 或 YAML,可用 Jinja;合并进 notify 调用的 data(你的键优先)。变量:kind、status、priority、object_name、task_name、object_ref、task_ref、days_until_due、next_due、responsible_user_id、url、target、title、message。用“发送测试”按钮验证。",
|
||||
"settings_notify_extra_data_hint": "JSON 或 YAML,可用 Jinja;合并进 notify 调用的 data(你的键优先)。变量:kind、status、priority、labels、notes、area_name、object_name、task_name、object_ref、task_ref、days_until_due、next_due、last_performed、responsible_user_id、sensor_entity_id、url、target、title、message。用“发送测试”按钮验证。",
|
||||
"card_saved_view": "已保存的视图",
|
||||
"card_saved_view_none": "无",
|
||||
"card_saved_view_help": "在上述筛选之外再应用该视图的状态、用户和标签筛选。视图的排序和分组属于面板显示设置,不会应用到卡片上。",
|
||||
"panel_card_tab": "打开时的标签页",
|
||||
"panel_card_tab_default": "记住的标签页(与面板一致)",
|
||||
"panel_card_view": "打开时使用已保存的视图",
|
||||
"panel_card_height": "高度",
|
||||
"panel_card_height_help": "留空 = 填满卡片下方的屏幕(面板视图)。否则填写 CSS 长度,如 600px 或 80vh,用于含其他卡片的视图。",
|
||||
"panel_card_not_registered": "Maintenance Supporter 面板不可用——集成是否已设置?",
|
||||
"panel_card_load_failed": "无法加载 Maintenance Supporter 面板——请重新加载页面(更新后请清除浏览器缓存)。",
|
||||
"doc_part_none": "此部件没有关联的文档。",
|
||||
"settings_templates_toggle_group": "启用或禁用此组中的所有模板",
|
||||
"setups_button": "推荐配置",
|
||||
@@ -920,6 +975,13 @@
|
||||
"battery_fleet_status_ok": "正常",
|
||||
"battery_fleet_status_due": "已到期",
|
||||
"battery_fleet_predicted_on": "预计在 {date} 前后",
|
||||
"battery_fleet_predicted_typical": "预计约 {date} — {type} 通常 {months} 个月({source})",
|
||||
"lifetime_source_default": "内置默认值",
|
||||
"lifetime_source_table": "内置表",
|
||||
"lifetime_source_learned": "从 {n} 次更换中学习",
|
||||
"lifetime_source_override": "你的设置",
|
||||
"lifetime_source_learned_device": "从此设备的 {n} 次更换中学习",
|
||||
"lifetime_source_learned_model": "从同型号设备的 {n} 次更换中学习",
|
||||
"battery_fleet_predicted_trend": "根据此电池的放电趋势预测:约 {date}({confidence})",
|
||||
"battery_fleet_rechargeable": "可充电电池:充电即可,无需更换——不会出现在购物清单中",
|
||||
"battery_fleet_sort_name": "按名称排序",
|
||||
@@ -973,11 +1035,13 @@
|
||||
"print_inc_parts": "使用的零件",
|
||||
"print_inc_photos": "照片",
|
||||
"print_inc_documents": "关联文档",
|
||||
"print_inc_doc_desc": "文档说明",
|
||||
"print_inc_checklist": "检查清单",
|
||||
"print_inc_notes": "备注",
|
||||
"print_inc_costs": "费用和用时",
|
||||
"print_inc_person": "完成者",
|
||||
"print_inc_refs": "参考编号",
|
||||
"print_inc_bare": "无详情的完成记录",
|
||||
"print_inc_qr": "每个任务的二维码",
|
||||
"print_button": "打印",
|
||||
"report_scan_hint": "扫描以打开任务",
|
||||
|
||||
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
File diff suppressed because one or more lines are too long
+250
@@ -0,0 +1,250 @@
|
||||
/*! maintenance_supporter frontend 2.86.0 */
|
||||
import{e as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZLVS6B3E.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FE4LECIX.js";import{a as o,b as _,c as i,f as l,h,l as g,m as n,q as t,s as u,u as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WG3MZHOS.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=s=>{let a=new Set(this._selected);a.has(s)?a.delete(s):a.add(s),this._selected=a};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0,for_minutes:parseInt(this._forMinutes,10)>0?parseInt(this._forMinutes,10):void 0})),a=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:a})),this._open=!1}catch(s){this._error=p(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return u(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="",this._forMinutes="0";try{this._userService?this._userService.updateHass(this.hass):this._userService=new f(this.hass);let[s,a]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=a}catch(s){this._error=p(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let s=this._lang,a=this._sensors.length>0&&this._selected.size===this._sensors.length;return i`
|
||||
<div class="overlay" @click=${this._close}>
|
||||
<div class="card" @click=${e=>e.stopPropagation()}>
|
||||
<div class="title">${t("adopt_problem_title",s)}</div>
|
||||
<div class="hint">${t("adopt_problem_hint",s)}</div>
|
||||
${this._error?i`<div class="error">${this._error}</div>`:l}
|
||||
|
||||
${this._loading?i`<div class="loading">…</div>`:this._sensors.length===0?i`<div class="empty">${t("adopt_problem_none",s)}</div>`:i`
|
||||
<label class="select-all">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${a}
|
||||
@change=${this._toggleAll}
|
||||
/>
|
||||
<span>${t("selected",s)}: ${this._selected.size} / ${this._sensors.length}</span>
|
||||
</label>
|
||||
<div class="list">
|
||||
${this._sensors.map(e=>{let m=this._selected.has(e.entity_id),d=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return i`
|
||||
<label class="row">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${m}
|
||||
@change=${()=>this._toggle(e.entity_id)}
|
||||
/>
|
||||
<div class="row-main">
|
||||
<div class="row-top">
|
||||
<span class="row-name">${e.name}</span>
|
||||
<span class="chip ${d?"chip-active":"chip-ok"}">
|
||||
${d?t("adopt_problem_active",s):t("adopt_problem_ok",s)}
|
||||
</span>
|
||||
</div>
|
||||
${c?i`<div class="row-sub">${c}</div>`:l}
|
||||
<div class="row-target">
|
||||
→ ${e.suggested_object_name}${e.suggested_entry_id?l:i` <span class="new-tag">${t("adopt_problem_new_object",s)}</span>`}
|
||||
</div>
|
||||
${e.suggested_part_name?i`<div class="row-part">
|
||||
<ha-icon icon="mdi:package-variant-closed"></ha-icon>
|
||||
${t("adopt_problem_part",s).replace("{name}",e.suggested_part_name)}
|
||||
</div>`:l}
|
||||
</div>
|
||||
</label>
|
||||
`})}
|
||||
</div>
|
||||
`}
|
||||
|
||||
${!this._loading&&this._sensors.length>0?i`
|
||||
<label class="responsible">
|
||||
<span>${t("for_at_least_minutes",s)}</span>
|
||||
<input
|
||||
class="for-input"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1440"
|
||||
.value=${this._forMinutes}
|
||||
@input=${e=>this._forMinutes=e.target.value}
|
||||
/>
|
||||
</label>
|
||||
<div class="for-hint">${t("adopt_for_minutes_hint",s)}</div>
|
||||
`:l}
|
||||
|
||||
${!this._loading&&this._sensors.length>0&&this._users.length>0?i`
|
||||
<label class="responsible">
|
||||
<span>${t("adopt_problem_responsible",s)}</span>
|
||||
<select
|
||||
.value=${this._responsible}
|
||||
@change=${e=>{this._responsible=e.target.value}}
|
||||
>
|
||||
<option value="" ?selected=${!this._responsible}>${t("no_user_assigned",s)}</option>
|
||||
${this._users.map(e=>i`<option value=${e.id} ?selected=${e.id===this._responsible}>${e.name}</option>`)}
|
||||
</select>
|
||||
</label>
|
||||
`:l}
|
||||
|
||||
<div class="actions">
|
||||
<ha-button appearance="plain" @click=${this._close}>
|
||||
${t("cancel",s)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
@click=${this._adopt}
|
||||
.disabled=${this._selected.size===0||this._adopting}
|
||||
>
|
||||
${t("adopt_problem_adopt",s)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`}};r.styles=_`
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-background-color, #fff);
|
||||
color: var(--primary-text-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-width: min(360px, calc(100vw - 24px));
|
||||
max-width: 560px;
|
||||
width: 90vw;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.hint {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color, #f44336);
|
||||
font-size: 13px;
|
||||
}
|
||||
.loading,
|
||||
.empty {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 14px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.select-all input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
overflow-y: auto;
|
||||
max-height: 50vh;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.row input {
|
||||
margin-top: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.row-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.row-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.row-name {
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
.row-sub {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
.row-target {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
.row-part {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.row-part ha-icon {
|
||||
--mdc-icon-size: 14px;
|
||||
}
|
||||
.new-tag {
|
||||
font-style: italic;
|
||||
}
|
||||
.chip {
|
||||
font-size: 11px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chip-active {
|
||||
background: var(--error-color, #f44336);
|
||||
color: #fff;
|
||||
}
|
||||
.chip-ok {
|
||||
background: var(--divider-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.responsible {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.for-input {
|
||||
width: 70px;
|
||||
background: var(--card-background-color);
|
||||
color: var(--primary-text-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
.for-hint {
|
||||
font-size: 11px;
|
||||
color: var(--secondary-text-color);
|
||||
margin: -4px 0 2px;
|
||||
}
|
||||
.responsible select {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
padding: 4px 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--divider-color);
|
||||
background: var(--card-background-color, #fff);
|
||||
color: var(--primary-text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
`,o([g({attribute:!1})],r.prototype,"hass",2),o([n()],r.prototype,"_open",2),o([n()],r.prototype,"_loading",2),o([n()],r.prototype,"_adopting",2),o([n()],r.prototype,"_error",2),o([n()],r.prototype,"_sensors",2),o([n()],r.prototype,"_selected",2),o([n()],r.prototype,"_users",2),o([n()],r.prototype,"_responsible",2),o([n()],r.prototype,"_forMinutes",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user