348 files

This commit is contained in:
Home Assistant Version Control
2026-07-23 19:02:12 +00:00
parent 0ed1687503
commit 016af2e013
348 changed files with 12368 additions and 11689 deletions
+175 -1
View File
@@ -1538,5 +1538,179 @@
"panel.family_goal_reward": "Belohnung",
"panel.family_goal_reward_ph": "ein Familienfilmabend",
"notification.family_goal_reached.name": "Familienziel erreicht",
"notification.family_goal_reached.description": "Feiert, wenn die kombinierten Punkte der Familie das gemeinsame Ziel erreichen. Standardmäßig aus."
"notification.family_goal_reached.description": "Feiert, wenn die kombinierten Punkte der Familie das gemeinsame Ziel erreichen. Standardmäßig aus.",
"panel.chore_advanced_weather": "Erweitert — Wetterbedingungen",
"panel.chore_weather_intro": "Diese Aufgabe ausblenden, solange das Wetter ungeeignet ist. Praktisch für Arbeiten im Freien — eine wetterbedingt ausgefallene Aufgabe gilt nicht als versäumte Pflichtaufgabe und unterbricht keine Serie.",
"panel.chore_weather_entity_label": "Wetter-Entität",
"panel.chore_weather_entity_hint": "Leer lassen, um das Wetter komplett zu ignorieren.",
"panel.chore_weather_conditions_label": "Ausblenden bei folgendem Wetter",
"panel.chore_weather_conditions_hint": "Wähle die Bedingungen, bei denen die Aufgabe ausgeblendet wird.",
"panel.chore_weather_temp_min_label": "Mindesttemperatur",
"panel.chore_weather_temp_min_hint": "Darunter ausblenden. Leer lassen für keine Grenze.",
"panel.chore_weather_temp_max_label": "Höchsttemperatur",
"panel.chore_weather_temp_max_hint": "Darüber ausblenden. Leer lassen für keine Grenze.",
"panel.chore_weather_wind_max_label": "Maximale Windgeschwindigkeit",
"panel.chore_weather_wind_max_hint": "Darüber ausblenden. Leer lassen für keine Grenze.",
"panel.chore_weather_failopen_hint": "Die Grenzwerte verwenden die Einheiten deiner Wetter-Entität. Fehlt die Entität oder ist sie nicht verfügbar, bleibt die Aufgabe sichtbar.",
"weather.blocked_condition": "Ausgeblendet — ungeeignetes Wetter",
"weather.blocked_temp_low": "Ausgeblendet — zu kalt",
"weather.blocked_temp_high": "Ausgeblendet — zu heiß",
"weather.blocked_wind": "Ausgeblendet — zu windig",
"weather.condition_rainy": "Regnerisch",
"weather.condition_pouring": "Starkregen",
"weather.condition_snowy": "Schnee",
"weather.condition_snowy_rainy": "Schneeregen",
"weather.condition_hail": "Hagel",
"weather.condition_lightning": "Blitze",
"weather.condition_lightning_rainy": "Gewitter",
"weather.condition_fog": "Nebel",
"weather.condition_windy": "Windig",
"weather.condition_windy_variant": "Sehr windig",
"weather.condition_cloudy": "Bewölkt",
"weather.condition_exceptional": "Unwetter",
"child.deadline_minutes": "noch {mins} Min.",
"child.deadline_hours": "noch {hours} Std. {mins} Min.",
"child.deadline_title": "Erledige das, bevor die Zeit abläuft",
"panel.chore_advanced_scheduled": "Erweitert — geplante Änderungen",
"panel.chore_scheduled_intro": "Plane eine Änderung für ein späteres Datum — „ab 1. September 20 Punkte wert“. Wird um Mitternacht angewendet; war Home Assistant aus, wird es beim nächsten Start nachgeholt.",
"panel.chore_scheduled_none": "Keine Änderungen geplant.",
"panel.chore_scheduled_history": "Bereits angewendet",
"panel.chore_scheduled_applied": "Angewendet",
"panel.sched_date_label": "Anwenden am",
"panel.sched_field_label": "Änderung",
"panel.sched_value_label": "Neuer Wert",
"panel.sched_note_label": "Notiz (optional)",
"panel.sched_note_hint": "Eine Erinnerung an den Grund, neben der geplanten Änderung angezeigt.",
"panel.btn_add_scheduled": "Änderung planen",
"panel.sched_value_yes": "Ja",
"panel.sched_value_no": "Nein",
"panel.sched_value_nobody": "Niemand",
"panel.toast_sched_added": "Änderung geplant",
"panel.toast_sched_removed": "Änderung entfernt",
"panel.toast_sched_date_required": "Wähle ein Datum für die Änderung",
"panel.toast_sched_value_required": "Gib einen Wert für die Änderung ein",
"panel.sched_field_points": "Punkte",
"panel.sched_field_assigned_to": "Zugewiesen an",
"panel.sched_field_enabled": "Aktiviert",
"panel.sched_field_requires_approval": "Bestätigung nötig",
"panel.sched_field_daily_limit": "Tageslimit",
"panel.sched_field_due_days": "Tage",
"panel.sched_field_mandatory": "Pflicht",
"panel.sched_field_mandatory_penalty_points": "Strafpunkte",
"panel.sched_field_expires_on": "Läuft ab am",
"panel.sched_field_description": "Beschreibung",
"panel.sched_field_time_category": "Tageszeit",
"panel.sched_field_difficulty": "Schwierigkeit",
"routine.title": "{name}s Routine",
"routine.no_child": "Wähle ein Kind für diese Karte.",
"routine.task_of": "Aufgabe {current} von {total}",
"routine.all_done_count": "Alle {total} erledigt",
"routine.done": "Fertig ✓",
"routine.completed": "Erledigt",
"routine.sent_for_checking": "Zur Prüfung gesendet",
"routine.skip": "Später",
"routine.next": "Weiter →",
"routine.back": "← Zurück",
"routine.waiting_for_parent": "⏳ Warte auf die Prüfung durch einen Erwachsenen",
"routine.photo_note": "Hierfür ist ein Foto nötig — tippe es auf deiner Karte an.",
"routine.finished_title": "Routine geschafft!",
"routine.finished_body": "Gut gemacht, {name}. Das war alles.",
"routine.earned": "+{points} verdient",
"routine.awaiting_points": "+{points}, sobald ein Erwachsener prüft",
"routine.skipped_note": "{count} für später übrig.",
"routine.start_again": "Neu starten",
"routine.empty_title": "Nichts zu tun",
"routine.empty_body": "Diese Routine ist schon erledigt.",
"child.roulette_spin": "Für eine ×{multiplier}-Aufgabe drehen",
"child.roulette_respin": "Nochmal drehen (noch {count})",
"child.roulette_no_spins": "Heute keine Drehungen mehr",
"panel.settings_roulette_section": "Aufgaben-Roulette",
"panel.settings_roulette_enabled": "Aufgaben-Roulette aktivieren",
"panel.settings_roulette_enabled_hint": "Ein Kind kann für eine zufällige offene Aufgabe mit Extrapunkten drehen.",
"panel.settings_roulette_multiplier": "Punkte-Multiplikator",
"panel.settings_roulette_spins": "Drehungen pro Tag",
"panel.settings_unlock_allowlist": "Freischalt-Positivliste",
"panel.settings_unlock_allowlist_hint": "Nur diese Entitäten dürfen von einer Belohnung freigeschaltet werden. Bis du hier etwas hinzufügst, ist nichts erlaubt.",
"panel.settings_unlock_allowlist_empty": "Noch nichts erlaubt.",
"panel.settings_unlock_allowlist_add": "Entität zum Erlauben suchen",
"panel.reward_unlock_section": "Erweitert — zeitlich begrenzte Freischaltung",
"panel.reward_unlock_intro": "Schaltet etwas ein, wenn diese Belohnung genehmigt wird, und nach einer festgelegten Zeit wieder aus.",
"panel.reward_unlock_none": "Nichts",
"panel.reward_unlock_entity": "Freischalten",
"panel.reward_unlock_minutes": "Wie lange (Minuten)",
"panel.reward_unlock_minutes_hint": "0 = eingeschaltet lassen; du musst selbst ausschalten.",
"panel.reward_unlock_no_allowlist": "Füge zuerst in den Einstellungen eine Entität zur Freischalt-Positivliste hinzu.",
"panel.tab_insights": "Einblicke",
"panel.insights_loading": "Wird berechnet…",
"panel.insights_fairness_title": "Wer macht die Arbeit?",
"panel.insights_fairness_intro": "{start} bis {end}. Gleichmäßig verteilt wären {fair}% pro Kind.",
"panel.insights_last_days": "Letzte {days} Tage",
"panel.insights_no_data": "In diesem Zeitraum noch keine bestätigten Aufgaben.",
"panel.insights_balanced": "Wirkt recht ausgewogen.",
"panel.insights_unbalanced": "Die Arbeit ist ungleich verteilt.",
"panel.insights_chores": "Aufgaben",
"panel.insights_status_over": "Macht mehr",
"panel.insights_status_under": "Macht weniger",
"panel.insights_status_balanced": "Ausgewogen",
"panel.insights_status_idle": "Noch nichts",
"panel.insights_fairness_note": "Bewertet nach Anzahl der Aufgaben, nicht nach Punkten, damit eine teurere Aufgabe keine ungleiche Verteilung verdeckt. Markiert, wenn ein Kind mehr als {tolerance} Punkte von einem gleichen Anteil abweicht.",
"panel.insights_chore": "Aufgabe",
"panel.insights_view_fairness": "Fairness",
"panel.insights_view_friction": "Reibung",
"panel.insights_friction_title": "Was funktioniert nicht?",
"panel.insights_friction_intro": "{start} bis {end}. Aufgaben werden danach bewertet, wie oft sie tatsächlich erledigt wurden im Verhältnis dazu, wie oft sie anstanden.",
"panel.insights_friction_none": "Keine aktiven Aufgaben vorhanden.",
"panel.insights_friction_healthy": "Es wird alles erledigt.",
"panel.insights_friction_problems": "{count} Aufgabe(n) genauer ansehen.",
"panel.insights_col_chore": "Aufgabe",
"panel.insights_col_done": "Erledigt",
"panel.insights_col_rate": "Quote",
"panel.insights_col_last": "Zuletzt",
"panel.insights_col_verdict": "Urteil",
"panel.insights_col_suggestion": "Vorschlag",
"panel.insights_never": "Nie",
"panel.insights_days_ago": "vor {days} T.",
"panel.insights_verdict_never": "Nie erledigt",
"panel.insights_verdict_stalling": "Bleibt liegen",
"panel.insights_verdict_struggling": "Unregelmäßig",
"panel.insights_verdict_fine": "In Ordnung",
"panel.insights_verdict_unknown": "Unklar",
"panel.insights_suggest_retire": "Abschaffen",
"panel.insights_suggest_reprice": "Punkte erhöhen",
"panel.insights_suggest_reassign": "Jemand anderem geben",
"panel.insights_suggest_keep": "So lassen",
"panel.insights_outstanding_misses": "Offene versäumte Pflichtaufgaben",
"panel.insights_friction_note": "Ablehnungen sind nicht aufgeführt: TaskMate löscht eine Erledigung, wenn du sie ablehnst, es gibt also keinen Verlauf dazu. Die erwarteten Werte sind Näherungen — Rotation, Abhängigkeiten und Wetter werden nicht nachgestellt.",
"panel.insights_view_projection": "Kommende Woche",
"panel.insights_projection_title": "Was steht an?",
"panel.insights_projection_intro": "{start} bis {end}, basierend auf dem Zeitplan jeder Aufgabe.",
"panel.insights_next_days": "Nächste {days} Tage",
"panel.insights_col_day": "Tag",
"panel.insights_projected_earn": "bis zu {points} {name}",
"panel.insights_projected_total": "käme auf {total}",
"panel.insights_projection_unassigned": "{points} Punkte sind niemandem zugewiesen.",
"panel.insights_projection_note": "Eine Obergrenze, keine Prognose: Aufgaben für alle werden jedem berechtigten Kind angerechnet, da der Zeitplan nicht wissen kann, wer zuerst da ist. Wetter, Abhängigkeiten und Verfügbarkeit werden nicht vorhergesagt — sie hängen vom Tag ab.",
"panel.insights_view_health": "Zustand",
"panel.health_title": "Ist etwas kaputt?",
"panel.health_recheck": "Erneut prüfen",
"panel.health_all_good": "Nichts gefunden.",
"panel.health_issues": "{count} Sache(n) brauchen Aufmerksamkeit.",
"panel.health_sev_error": "Fehler",
"panel.health_sev_warning": "Warnung",
"panel.health_sev_info": "Hinweis",
"panel.health_goto": "Anzeigen",
"panel.health_count_children": "Kinder",
"panel.health_count_chores": "Aufgaben",
"panel.health_count_enabled_chores": "Aktiv",
"panel.health_count_rewards": "Belohnungen",
"panel.health_count_completions": "Erledigungen",
"panel.health_count_badges": "Abzeichen",
"panel.health_count_scheduled_changes": "Geplant",
"panel.health_count_active_unlocks": "Freischaltungen",
"panel.health_count_mandatory_misses": "Versäumt",
"panel.health_count_storage": "Speicher",
"panel.chore_icon_label": "Bild",
"child.editor.pre_reader": "Nur-Bilder-Modus (für kleine Kinder)",
"child.editor.pre_reader_labels": "Namen im Bildmodus anzeigen",
"common.design.accessible": "Barrierefrei"
}
@@ -1538,5 +1538,179 @@
"panel.family_goal_reward": "Reward",
"panel.family_goal_reward_ph": "a family movie night",
"notification.family_goal_reached.name": "Family goal reached",
"notification.family_goal_reached.description": "Celebrates when the family's combined points reach the shared goal. Off by default."
"notification.family_goal_reached.description": "Celebrates when the family's combined points reach the shared goal. Off by default.",
"panel.chore_advanced_weather": "Advanced — weather conditions",
"panel.chore_weather_intro": "Hide this chore while the weather is unsuitable. Useful for outdoor jobs — a rained-off chore doesn't count as a mandatory miss and won't break a streak.",
"panel.chore_weather_entity_label": "Weather entity",
"panel.chore_weather_entity_hint": "Leave empty to ignore the weather entirely.",
"panel.chore_weather_conditions_label": "Hide when the weather is",
"panel.chore_weather_conditions_hint": "Pick any conditions that should hide the chore.",
"panel.chore_weather_temp_min_label": "Minimum temperature",
"panel.chore_weather_temp_min_hint": "Hide below this. Leave empty for no limit.",
"panel.chore_weather_temp_max_label": "Maximum temperature",
"panel.chore_weather_temp_max_hint": "Hide above this. Leave empty for no limit.",
"panel.chore_weather_wind_max_label": "Maximum wind speed",
"panel.chore_weather_wind_max_hint": "Hide above this. Leave empty for no limit.",
"panel.chore_weather_failopen_hint": "Limits use whatever units your weather entity reports. If the entity is missing or unavailable, the chore stays visible.",
"weather.blocked_condition": "Hidden — unsuitable weather",
"weather.blocked_temp_low": "Hidden — too cold",
"weather.blocked_temp_high": "Hidden — too hot",
"weather.blocked_wind": "Hidden — too windy",
"weather.condition_rainy": "Rainy",
"weather.condition_pouring": "Pouring",
"weather.condition_snowy": "Snowy",
"weather.condition_snowy_rainy": "Sleet",
"weather.condition_hail": "Hail",
"weather.condition_lightning": "Lightning",
"weather.condition_lightning_rainy": "Thunderstorm",
"weather.condition_fog": "Fog",
"weather.condition_windy": "Windy",
"weather.condition_windy_variant": "Very windy",
"weather.condition_cloudy": "Cloudy",
"weather.condition_exceptional": "Severe",
"child.deadline_minutes": "{mins} min left",
"child.deadline_hours": "{hours}h {mins}m left",
"child.deadline_title": "Do this before the time runs out",
"panel.chore_advanced_scheduled": "Advanced — scheduled changes",
"panel.chore_scheduled_intro": "Queue a change to take effect on a future date — \"from 1 September this is worth 20 points\". Applied at midnight; if Home Assistant was off, it catches up on the next start.",
"panel.chore_scheduled_none": "No changes queued.",
"panel.chore_scheduled_history": "Already applied",
"panel.chore_scheduled_applied": "Applied",
"panel.sched_date_label": "Apply on",
"panel.sched_field_label": "Change",
"panel.sched_value_label": "New value",
"panel.sched_note_label": "Note (optional)",
"panel.sched_note_hint": "A reminder of why, shown beside the queued change.",
"panel.btn_add_scheduled": "Queue change",
"panel.sched_value_yes": "Yes",
"panel.sched_value_no": "No",
"panel.sched_value_nobody": "Nobody",
"panel.toast_sched_added": "Change queued",
"panel.toast_sched_removed": "Change removed",
"panel.toast_sched_date_required": "Pick a date for the change",
"panel.toast_sched_value_required": "Enter a value for the change",
"panel.sched_field_points": "Points",
"panel.sched_field_assigned_to": "Assigned to",
"panel.sched_field_enabled": "Enabled",
"panel.sched_field_requires_approval": "Requires approval",
"panel.sched_field_daily_limit": "Daily limit",
"panel.sched_field_due_days": "Days",
"panel.sched_field_mandatory": "Mandatory",
"panel.sched_field_mandatory_penalty_points": "Penalty points",
"panel.sched_field_expires_on": "Expires on",
"panel.sched_field_description": "Description",
"panel.sched_field_time_category": "Time of day",
"panel.sched_field_difficulty": "Difficulty",
"routine.title": "{name}'s routine",
"routine.no_child": "Pick a child for this card.",
"routine.task_of": "Task {current} of {total}",
"routine.all_done_count": "All {total} done",
"routine.done": "Done ✓",
"routine.completed": "Completed",
"routine.sent_for_checking": "Sent for checking",
"routine.skip": "Skip for now",
"routine.next": "Next →",
"routine.back": "← Back",
"routine.waiting_for_parent": "⏳ Waiting for a grown-up to check",
"routine.photo_note": "This one needs a photo — tap it on your card.",
"routine.finished_title": "Routine done!",
"routine.finished_body": "Nice one, {name}. That's everything.",
"routine.earned": "+{points} earned",
"routine.awaiting_points": "+{points} once a grown-up checks",
"routine.skipped_note": "{count} left for later.",
"routine.start_again": "Start again",
"routine.empty_title": "Nothing to do",
"routine.empty_body": "This routine is already finished.",
"child.roulette_spin": "Spin for a ×{multiplier} chore",
"child.roulette_respin": "Spin again ({count} left)",
"child.roulette_no_spins": "No spins left today",
"panel.settings_roulette_section": "Chore roulette",
"panel.settings_roulette_enabled": "Enable chore roulette",
"panel.settings_roulette_enabled_hint": "Lets a child spin for a random outstanding chore worth extra points.",
"panel.settings_roulette_multiplier": "Points multiplier",
"panel.settings_roulette_spins": "Spins per day",
"panel.settings_unlock_allowlist": "Unlock allowlist",
"panel.settings_unlock_allowlist_hint": "Only these entities may be unlocked by a reward. Nothing is allowed until you add something here.",
"panel.settings_unlock_allowlist_empty": "Nothing allowed yet.",
"panel.settings_unlock_allowlist_add": "Search for an entity to allow",
"panel.reward_unlock_section": "Advanced — timed unlock",
"panel.reward_unlock_intro": "Turn something on when this reward is approved, and back off again after a set time.",
"panel.reward_unlock_none": "Nothing",
"panel.reward_unlock_entity": "Unlock",
"panel.reward_unlock_minutes": "For how long (minutes)",
"panel.reward_unlock_minutes_hint": "0 = leave it on; you'll need to turn it off yourself.",
"panel.reward_unlock_no_allowlist": "Add an entity to the unlock allowlist in Settings first.",
"panel.tab_insights": "Insights",
"panel.insights_loading": "Working it out…",
"panel.insights_fairness_title": "Who's doing the work?",
"panel.insights_fairness_intro": "{start} to {end}. An even split would be {fair}% each.",
"panel.insights_last_days": "Last {days} days",
"panel.insights_no_data": "No approved chores in this period yet.",
"panel.insights_balanced": "Looks fairly balanced.",
"panel.insights_unbalanced": "The workload is uneven.",
"panel.insights_chores": "chores",
"panel.insights_status_over": "Doing more",
"panel.insights_status_under": "Doing less",
"panel.insights_status_balanced": "Balanced",
"panel.insights_status_idle": "Nothing yet",
"panel.insights_fairness_note": "Judged on chore count, not points, so a pricier chore doesn't hide an uneven split. Flagged when a child is more than {tolerance} points off an even share.",
"panel.insights_chore": "chore",
"panel.insights_view_fairness": "Fairness",
"panel.insights_view_friction": "Friction",
"panel.insights_friction_title": "What isn't working?",
"panel.insights_friction_intro": "{start} to {end}. Chores are judged on how often they actually get done versus how often they came up.",
"panel.insights_friction_none": "No active chores to look at.",
"panel.insights_friction_healthy": "Everything is getting done.",
"panel.insights_friction_problems": "{count} chore(s) worth a look.",
"panel.insights_col_chore": "Chore",
"panel.insights_col_done": "Done",
"panel.insights_col_rate": "Rate",
"panel.insights_col_last": "Last done",
"panel.insights_col_verdict": "Verdict",
"panel.insights_col_suggestion": "Try",
"panel.insights_never": "Never",
"panel.insights_days_ago": "{days}d ago",
"panel.insights_verdict_never": "Never done",
"panel.insights_verdict_stalling": "Stalling",
"panel.insights_verdict_struggling": "Patchy",
"panel.insights_verdict_fine": "Fine",
"panel.insights_verdict_unknown": "Unclear",
"panel.insights_suggest_retire": "Retire it",
"panel.insights_suggest_reprice": "Raise the points",
"panel.insights_suggest_reassign": "Give it to someone else",
"panel.insights_suggest_keep": "Leave it alone",
"panel.insights_outstanding_misses": "Outstanding mandatory misses",
"panel.insights_friction_note": "Rejections aren't listed: TaskMate deletes a completion when you reject it, so there's no rejection history to count. Expected counts are approximate — they don't replay rotation, dependencies or weather.",
"panel.insights_view_projection": "Week ahead",
"panel.insights_projection_title": "What's coming up?",
"panel.insights_projection_intro": "{start} to {end}, based on each chore's schedule.",
"panel.insights_next_days": "Next {days} days",
"panel.insights_col_day": "Day",
"panel.insights_projected_earn": "up to {points} {name}",
"panel.insights_projected_total": "would reach {total}",
"panel.insights_projection_unassigned": "{points} points aren't assigned to anyone.",
"panel.insights_projection_note": "A ceiling, not a forecast: chores open to everyone are counted for each eligible child, since the schedule can't know who'll get there first. Weather, dependencies and availability aren't projected — they depend on the day.",
"panel.insights_view_health": "Health",
"panel.health_title": "Is anything broken?",
"panel.health_recheck": "Check again",
"panel.health_all_good": "Nothing wrong found.",
"panel.health_issues": "{count} thing(s) need attention.",
"panel.health_sev_error": "Error",
"panel.health_sev_warning": "Warning",
"panel.health_sev_info": "Note",
"panel.health_goto": "Show me",
"panel.health_count_children": "Children",
"panel.health_count_chores": "Chores",
"panel.health_count_enabled_chores": "Active",
"panel.health_count_rewards": "Rewards",
"panel.health_count_completions": "Completions",
"panel.health_count_badges": "Badges",
"panel.health_count_scheduled_changes": "Scheduled",
"panel.health_count_active_unlocks": "Unlocks",
"panel.health_count_mandatory_misses": "Misses",
"panel.health_count_storage": "Storage",
"panel.chore_icon_label": "Picture",
"child.editor.pre_reader": "Picture-only mode (for young children)",
"child.editor.pre_reader_labels": "Show names in picture mode",
"common.design.accessible": "Accessible"
}
+175 -1
View File
@@ -1538,5 +1538,179 @@
"panel.family_goal_reward": "Reward",
"panel.family_goal_reward_ph": "a family movie night",
"notification.family_goal_reached.name": "Family goal reached",
"notification.family_goal_reached.description": "Celebrates when the family's combined points reach the shared goal. Off by default."
"notification.family_goal_reached.description": "Celebrates when the family's combined points reach the shared goal. Off by default.",
"panel.chore_advanced_weather": "Advanced — weather conditions",
"panel.chore_weather_intro": "Hide this chore while the weather is unsuitable. Useful for outdoor jobs — a rained-off chore doesn't count as a mandatory miss and won't break a streak.",
"panel.chore_weather_entity_label": "Weather entity",
"panel.chore_weather_entity_hint": "Leave empty to ignore the weather entirely.",
"panel.chore_weather_conditions_label": "Hide when the weather is",
"panel.chore_weather_conditions_hint": "Pick any conditions that should hide the chore.",
"panel.chore_weather_temp_min_label": "Minimum temperature",
"panel.chore_weather_temp_min_hint": "Hide below this. Leave empty for no limit.",
"panel.chore_weather_temp_max_label": "Maximum temperature",
"panel.chore_weather_temp_max_hint": "Hide above this. Leave empty for no limit.",
"panel.chore_weather_wind_max_label": "Maximum wind speed",
"panel.chore_weather_wind_max_hint": "Hide above this. Leave empty for no limit.",
"panel.chore_weather_failopen_hint": "Limits use whatever units your weather entity reports. If the entity is missing or unavailable, the chore stays visible.",
"weather.blocked_condition": "Hidden — unsuitable weather",
"weather.blocked_temp_low": "Hidden — too cold",
"weather.blocked_temp_high": "Hidden — too hot",
"weather.blocked_wind": "Hidden — too windy",
"weather.condition_rainy": "Rainy",
"weather.condition_pouring": "Pouring",
"weather.condition_snowy": "Snowy",
"weather.condition_snowy_rainy": "Sleet",
"weather.condition_hail": "Hail",
"weather.condition_lightning": "Lightning",
"weather.condition_lightning_rainy": "Thunderstorm",
"weather.condition_fog": "Fog",
"weather.condition_windy": "Windy",
"weather.condition_windy_variant": "Very windy",
"weather.condition_cloudy": "Cloudy",
"weather.condition_exceptional": "Severe",
"child.deadline_minutes": "{mins} min left",
"child.deadline_hours": "{hours}h {mins}m left",
"child.deadline_title": "Do this before the time runs out",
"panel.chore_advanced_scheduled": "Advanced — scheduled changes",
"panel.chore_scheduled_intro": "Queue a change to take effect on a future date — \"from 1 September this is worth 20 points\". Applied at midnight; if Home Assistant was off, it catches up on the next start.",
"panel.chore_scheduled_none": "No changes queued.",
"panel.chore_scheduled_history": "Already applied",
"panel.chore_scheduled_applied": "Applied",
"panel.sched_date_label": "Apply on",
"panel.sched_field_label": "Change",
"panel.sched_value_label": "New value",
"panel.sched_note_label": "Note (optional)",
"panel.sched_note_hint": "A reminder of why, shown beside the queued change.",
"panel.btn_add_scheduled": "Queue change",
"panel.sched_value_yes": "Yes",
"panel.sched_value_no": "No",
"panel.sched_value_nobody": "Nobody",
"panel.toast_sched_added": "Change queued",
"panel.toast_sched_removed": "Change removed",
"panel.toast_sched_date_required": "Pick a date for the change",
"panel.toast_sched_value_required": "Enter a value for the change",
"panel.sched_field_points": "Points",
"panel.sched_field_assigned_to": "Assigned to",
"panel.sched_field_enabled": "Enabled",
"panel.sched_field_requires_approval": "Requires approval",
"panel.sched_field_daily_limit": "Daily limit",
"panel.sched_field_due_days": "Days",
"panel.sched_field_mandatory": "Mandatory",
"panel.sched_field_mandatory_penalty_points": "Penalty points",
"panel.sched_field_expires_on": "Expires on",
"panel.sched_field_description": "Description",
"panel.sched_field_time_category": "Time of day",
"panel.sched_field_difficulty": "Difficulty",
"routine.title": "{name}'s routine",
"routine.no_child": "Pick a child for this card.",
"routine.task_of": "Task {current} of {total}",
"routine.all_done_count": "All {total} done",
"routine.done": "Done ✓",
"routine.completed": "Completed",
"routine.sent_for_checking": "Sent for checking",
"routine.skip": "Skip for now",
"routine.next": "Next →",
"routine.back": "← Back",
"routine.waiting_for_parent": "⏳ Waiting for a grown-up to check",
"routine.photo_note": "This one needs a photo — tap it on your card.",
"routine.finished_title": "Routine done!",
"routine.finished_body": "Nice one, {name}. That's everything.",
"routine.earned": "+{points} earned",
"routine.awaiting_points": "+{points} once a grown-up checks",
"routine.skipped_note": "{count} left for later.",
"routine.start_again": "Start again",
"routine.empty_title": "Nothing to do",
"routine.empty_body": "This routine is already finished.",
"child.roulette_spin": "Spin for a ×{multiplier} chore",
"child.roulette_respin": "Spin again ({count} left)",
"child.roulette_no_spins": "No spins left today",
"panel.settings_roulette_section": "Chore roulette",
"panel.settings_roulette_enabled": "Enable chore roulette",
"panel.settings_roulette_enabled_hint": "Lets a child spin for a random outstanding chore worth extra points.",
"panel.settings_roulette_multiplier": "Points multiplier",
"panel.settings_roulette_spins": "Spins per day",
"panel.settings_unlock_allowlist": "Unlock allowlist",
"panel.settings_unlock_allowlist_hint": "Only these entities may be unlocked by a reward. Nothing is allowed until you add something here.",
"panel.settings_unlock_allowlist_empty": "Nothing allowed yet.",
"panel.settings_unlock_allowlist_add": "Search for an entity to allow",
"panel.reward_unlock_section": "Advanced — timed unlock",
"panel.reward_unlock_intro": "Turn something on when this reward is approved, and back off again after a set time.",
"panel.reward_unlock_none": "Nothing",
"panel.reward_unlock_entity": "Unlock",
"panel.reward_unlock_minutes": "For how long (minutes)",
"panel.reward_unlock_minutes_hint": "0 = leave it on; you'll need to turn it off yourself.",
"panel.reward_unlock_no_allowlist": "Add an entity to the unlock allowlist in Settings first.",
"panel.tab_insights": "Insights",
"panel.insights_loading": "Working it out…",
"panel.insights_fairness_title": "Who's doing the work?",
"panel.insights_fairness_intro": "{start} to {end}. An even split would be {fair}% each.",
"panel.insights_last_days": "Last {days} days",
"panel.insights_no_data": "No approved chores in this period yet.",
"panel.insights_balanced": "Looks fairly balanced.",
"panel.insights_unbalanced": "The workload is uneven.",
"panel.insights_chores": "chores",
"panel.insights_status_over": "Doing more",
"panel.insights_status_under": "Doing less",
"panel.insights_status_balanced": "Balanced",
"panel.insights_status_idle": "Nothing yet",
"panel.insights_fairness_note": "Judged on chore count, not points, so a pricier chore doesn't hide an uneven split. Flagged when a child is more than {tolerance} points off an even share.",
"panel.insights_chore": "chore",
"panel.insights_view_fairness": "Fairness",
"panel.insights_view_friction": "Friction",
"panel.insights_friction_title": "What isn't working?",
"panel.insights_friction_intro": "{start} to {end}. Chores are judged on how often they actually get done versus how often they came up.",
"panel.insights_friction_none": "No active chores to look at.",
"panel.insights_friction_healthy": "Everything is getting done.",
"panel.insights_friction_problems": "{count} chore(s) worth a look.",
"panel.insights_col_chore": "Chore",
"panel.insights_col_done": "Done",
"panel.insights_col_rate": "Rate",
"panel.insights_col_last": "Last done",
"panel.insights_col_verdict": "Verdict",
"panel.insights_col_suggestion": "Try",
"panel.insights_never": "Never",
"panel.insights_days_ago": "{days}d ago",
"panel.insights_verdict_never": "Never done",
"panel.insights_verdict_stalling": "Stalling",
"panel.insights_verdict_struggling": "Patchy",
"panel.insights_verdict_fine": "Fine",
"panel.insights_verdict_unknown": "Unclear",
"panel.insights_suggest_retire": "Retire it",
"panel.insights_suggest_reprice": "Raise the points",
"panel.insights_suggest_reassign": "Give it to someone else",
"panel.insights_suggest_keep": "Leave it alone",
"panel.insights_outstanding_misses": "Outstanding mandatory misses",
"panel.insights_friction_note": "Rejections aren't listed: TaskMate deletes a completion when you reject it, so there's no rejection history to count. Expected counts are approximate — they don't replay rotation, dependencies or weather.",
"panel.insights_view_projection": "Week ahead",
"panel.insights_projection_title": "What's coming up?",
"panel.insights_projection_intro": "{start} to {end}, based on each chore's schedule.",
"panel.insights_next_days": "Next {days} days",
"panel.insights_col_day": "Day",
"panel.insights_projected_earn": "up to {points} {name}",
"panel.insights_projected_total": "would reach {total}",
"panel.insights_projection_unassigned": "{points} points aren't assigned to anyone.",
"panel.insights_projection_note": "A ceiling, not a forecast: chores open to everyone are counted for each eligible child, since the schedule can't know who'll get there first. Weather, dependencies and availability aren't projected — they depend on the day.",
"panel.insights_view_health": "Health",
"panel.health_title": "Is anything broken?",
"panel.health_recheck": "Check again",
"panel.health_all_good": "Nothing wrong found.",
"panel.health_issues": "{count} thing(s) need attention.",
"panel.health_sev_error": "Error",
"panel.health_sev_warning": "Warning",
"panel.health_sev_info": "Note",
"panel.health_goto": "Show me",
"panel.health_count_children": "Children",
"panel.health_count_chores": "Chores",
"panel.health_count_enabled_chores": "Active",
"panel.health_count_rewards": "Rewards",
"panel.health_count_completions": "Completions",
"panel.health_count_badges": "Badges",
"panel.health_count_scheduled_changes": "Scheduled",
"panel.health_count_active_unlocks": "Unlocks",
"panel.health_count_mandatory_misses": "Misses",
"panel.health_count_storage": "Storage",
"panel.chore_icon_label": "Picture",
"child.editor.pre_reader": "Picture-only mode (for young children)",
"child.editor.pre_reader_labels": "Show names in picture mode",
"common.design.accessible": "Accessible"
}
+175 -1
View File
@@ -1538,5 +1538,179 @@
"panel.family_goal_reward": "Récompense",
"panel.family_goal_reward_ph": "une soirée cinéma en famille",
"notification.family_goal_reached.name": "Objectif familial atteint",
"notification.family_goal_reached.description": "Célèbre lorsque les points cumulés de la famille atteignent l'objectif commun. Désactivé par défaut."
"notification.family_goal_reached.description": "Célèbre lorsque les points cumulés de la famille atteignent l'objectif commun. Désactivé par défaut.",
"panel.chore_advanced_weather": "Avancé — conditions météo",
"panel.chore_weather_intro": "Masquer cette tâche tant que la météo est défavorable. Utile pour les travaux extérieurs — une tâche annulée par la pluie ne compte pas comme un manquement obligatoire et ne casse aucune série.",
"panel.chore_weather_entity_label": "Entité météo",
"panel.chore_weather_entity_hint": "Laisser vide pour ignorer complètement la météo.",
"panel.chore_weather_conditions_label": "Masquer quand la météo est",
"panel.chore_weather_conditions_hint": "Choisissez les conditions qui doivent masquer la tâche.",
"panel.chore_weather_temp_min_label": "Température minimale",
"panel.chore_weather_temp_min_hint": "Masquer en dessous. Laisser vide pour aucune limite.",
"panel.chore_weather_temp_max_label": "Température maximale",
"panel.chore_weather_temp_max_hint": "Masquer au-dessus. Laisser vide pour aucune limite.",
"panel.chore_weather_wind_max_label": "Vitesse de vent maximale",
"panel.chore_weather_wind_max_hint": "Masquer au-dessus. Laisser vide pour aucune limite.",
"panel.chore_weather_failopen_hint": "Les limites utilisent les unités de votre entité météo. Si l'entité est absente ou indisponible, la tâche reste visible.",
"weather.blocked_condition": "Masquée — météo défavorable",
"weather.blocked_temp_low": "Masquée — trop froid",
"weather.blocked_temp_high": "Masquée — trop chaud",
"weather.blocked_wind": "Masquée — trop venteux",
"weather.condition_rainy": "Pluvieux",
"weather.condition_pouring": "Averses",
"weather.condition_snowy": "Neigeux",
"weather.condition_snowy_rainy": "Neige fondue",
"weather.condition_hail": "Grêle",
"weather.condition_lightning": "Éclairs",
"weather.condition_lightning_rainy": "Orage",
"weather.condition_fog": "Brouillard",
"weather.condition_windy": "Venteux",
"weather.condition_windy_variant": "Très venteux",
"weather.condition_cloudy": "Nuageux",
"weather.condition_exceptional": "Conditions extrêmes",
"child.deadline_minutes": "{mins} min restantes",
"child.deadline_hours": "{hours} h {mins} min restantes",
"child.deadline_title": "À faire avant la fin du temps imparti",
"panel.chore_advanced_scheduled": "Avancé — modifications planifiées",
"panel.chore_scheduled_intro": "Planifiez une modification pour une date future — « à partir du 1er septembre, elle vaut 20 points ». Appliquée à minuit ; si Home Assistant était éteint, elle se rattrape au démarrage suivant.",
"panel.chore_scheduled_none": "Aucune modification planifiée.",
"panel.chore_scheduled_history": "Déjà appliquées",
"panel.chore_scheduled_applied": "Appliquée",
"panel.sched_date_label": "Appliquer le",
"panel.sched_field_label": "Modification",
"panel.sched_value_label": "Nouvelle valeur",
"panel.sched_note_label": "Note (facultatif)",
"panel.sched_note_hint": "Un rappel du pourquoi, affiché à côté de la modification planifiée.",
"panel.btn_add_scheduled": "Planifier",
"panel.sched_value_yes": "Oui",
"panel.sched_value_no": "Non",
"panel.sched_value_nobody": "Personne",
"panel.toast_sched_added": "Modification planifiée",
"panel.toast_sched_removed": "Modification supprimée",
"panel.toast_sched_date_required": "Choisissez une date pour la modification",
"panel.toast_sched_value_required": "Saisissez une valeur pour la modification",
"panel.sched_field_points": "Points",
"panel.sched_field_assigned_to": "Attribuée à",
"panel.sched_field_enabled": "Activée",
"panel.sched_field_requires_approval": "Validation requise",
"panel.sched_field_daily_limit": "Limite quotidienne",
"panel.sched_field_due_days": "Jours",
"panel.sched_field_mandatory": "Obligatoire",
"panel.sched_field_mandatory_penalty_points": "Points de pénalité",
"panel.sched_field_expires_on": "Expire le",
"panel.sched_field_description": "Description",
"panel.sched_field_time_category": "Moment de la journée",
"panel.sched_field_difficulty": "Difficulté",
"routine.title": "Routine de {name}",
"routine.no_child": "Choisissez un enfant pour cette carte.",
"routine.task_of": "Tâche {current} sur {total}",
"routine.all_done_count": "Les {total} sont faites",
"routine.done": "Terminé ✓",
"routine.completed": "Terminée",
"routine.sent_for_checking": "Envoyé pour vérification",
"routine.skip": "Plus tard",
"routine.next": "Suivant →",
"routine.back": "← Retour",
"routine.waiting_for_parent": "⏳ En attente de la vérification d'un adulte",
"routine.photo_note": "Celle-ci demande une photo — touchez-la sur votre carte.",
"routine.finished_title": "Routine terminée !",
"routine.finished_body": "Bravo, {name}. C'est tout.",
"routine.earned": "+{points} gagnés",
"routine.awaiting_points": "+{points} après vérification",
"routine.skipped_note": "{count} laissée(s) pour plus tard.",
"routine.start_again": "Recommencer",
"routine.empty_title": "Rien à faire",
"routine.empty_body": "Cette routine est déjà terminée.",
"child.roulette_spin": "Tourner pour une tâche ×{multiplier}",
"child.roulette_respin": "Tourner encore ({count} restant)",
"child.roulette_no_spins": "Plus de tours aujourd'hui",
"panel.settings_roulette_section": "Roulette des tâches",
"panel.settings_roulette_enabled": "Activer la roulette des tâches",
"panel.settings_roulette_enabled_hint": "Permet à un enfant de tirer une tâche au hasard valant plus de points.",
"panel.settings_roulette_multiplier": "Multiplicateur de points",
"panel.settings_roulette_spins": "Tours par jour",
"panel.settings_unlock_allowlist": "Liste d'autorisation de déverrouillage",
"panel.settings_unlock_allowlist_hint": "Seules ces entités peuvent être déverrouillées par une récompense. Rien n'est autorisé tant que vous n'ajoutez rien ici.",
"panel.settings_unlock_allowlist_empty": "Rien d'autorisé pour l'instant.",
"panel.settings_unlock_allowlist_add": "Rechercher une entité à autoriser",
"panel.reward_unlock_section": "Avancé — déverrouillage temporaire",
"panel.reward_unlock_intro": "Allume quelque chose quand cette récompense est validée, puis l'éteint après un temps donné.",
"panel.reward_unlock_none": "Rien",
"panel.reward_unlock_entity": "Déverrouiller",
"panel.reward_unlock_minutes": "Pendant combien de temps (minutes)",
"panel.reward_unlock_minutes_hint": "0 = laisser allumé ; vous devrez l'éteindre vous-même.",
"panel.reward_unlock_no_allowlist": "Ajoutez d'abord une entité à la liste d'autorisation dans les Réglages.",
"panel.tab_insights": "Analyses",
"panel.insights_loading": "Calcul en cours…",
"panel.insights_fairness_title": "Qui fait le travail ?",
"panel.insights_fairness_intro": "Du {start} au {end}. Une répartition égale serait de {fair}% chacun.",
"panel.insights_last_days": "{days} derniers jours",
"panel.insights_no_data": "Aucune tâche validée sur cette période.",
"panel.insights_balanced": "Cela semble équilibré.",
"panel.insights_unbalanced": "La charge est déséquilibrée.",
"panel.insights_chores": "tâches",
"panel.insights_status_over": "En fait plus",
"panel.insights_status_under": "En fait moins",
"panel.insights_status_balanced": "Équilibré",
"panel.insights_status_idle": "Rien encore",
"panel.insights_fairness_note": "Évalué sur le nombre de tâches, pas les points, pour qu'une tâche mieux payée ne masque pas un déséquilibre. Signalé au-delà de {tolerance} points d'écart avec une part égale.",
"panel.insights_chore": "tâche",
"panel.insights_view_fairness": "Équité",
"panel.insights_view_friction": "Frictions",
"panel.insights_friction_title": "Qu'est-ce qui ne marche pas ?",
"panel.insights_friction_intro": "Du {start} au {end}. Les tâches sont jugées sur la fréquence réelle d'exécution par rapport à leur fréquence d'apparition.",
"panel.insights_friction_none": "Aucune tâche active à examiner.",
"panel.insights_friction_healthy": "Tout se fait.",
"panel.insights_friction_problems": "{count} tâche(s) à regarder.",
"panel.insights_col_chore": "Tâche",
"panel.insights_col_done": "Faites",
"panel.insights_col_rate": "Taux",
"panel.insights_col_last": "Dernière fois",
"panel.insights_col_verdict": "Verdict",
"panel.insights_col_suggestion": "Essayez",
"panel.insights_never": "Jamais",
"panel.insights_days_ago": "il y a {days} j",
"panel.insights_verdict_never": "Jamais faite",
"panel.insights_verdict_stalling": "À la traîne",
"panel.insights_verdict_struggling": "Irrégulière",
"panel.insights_verdict_fine": "Correcte",
"panel.insights_verdict_unknown": "Incertain",
"panel.insights_suggest_retire": "La supprimer",
"panel.insights_suggest_reprice": "Augmenter les points",
"panel.insights_suggest_reassign": "La confier à un autre",
"panel.insights_suggest_keep": "Ne rien changer",
"panel.insights_outstanding_misses": "Manquements obligatoires en attente",
"panel.insights_friction_note": "Les refus ne sont pas listés : TaskMate supprime une validation quand vous la refusez, il n'y a donc pas d'historique. Les valeurs attendues sont approximatives — rotation, dépendances et météo ne sont pas rejouées.",
"panel.insights_view_projection": "Semaine à venir",
"panel.insights_projection_title": "Qu'est-ce qui arrive ?",
"panel.insights_projection_intro": "Du {start} au {end}, d'après le planning de chaque tâche.",
"panel.insights_next_days": "{days} prochains jours",
"panel.insights_col_day": "Jour",
"panel.insights_projected_earn": "jusqu'à {points} {name}",
"panel.insights_projected_total": "atteindrait {total}",
"panel.insights_projection_unassigned": "{points} points ne sont attribués à personne.",
"panel.insights_projection_note": "Un plafond, pas une prévision : les tâches ouvertes à tous sont comptées pour chaque enfant éligible, le planning ne pouvant savoir qui arrivera en premier. Météo, dépendances et disponibilité ne sont pas projetées — elles dépendent du jour.",
"panel.insights_view_health": "Santé",
"panel.health_title": "Quelque chose ne va pas ?",
"panel.health_recheck": "Revérifier",
"panel.health_all_good": "Rien d'anormal.",
"panel.health_issues": "{count} point(s) à regarder.",
"panel.health_sev_error": "Erreur",
"panel.health_sev_warning": "Avertissement",
"panel.health_sev_info": "Note",
"panel.health_goto": "Voir",
"panel.health_count_children": "Enfants",
"panel.health_count_chores": "Tâches",
"panel.health_count_enabled_chores": "Actives",
"panel.health_count_rewards": "Récompenses",
"panel.health_count_completions": "Validations",
"panel.health_count_badges": "Badges",
"panel.health_count_scheduled_changes": "Planifiées",
"panel.health_count_active_unlocks": "Déverrouillages",
"panel.health_count_mandatory_misses": "Manquements",
"panel.health_count_storage": "Stockage",
"panel.chore_icon_label": "Image",
"child.editor.pre_reader": "Mode images seules (jeunes enfants)",
"child.editor.pre_reader_labels": "Afficher les noms en mode images",
"common.design.accessible": "Accessible"
}
+175 -1
View File
@@ -1538,5 +1538,179 @@
"panel.family_goal_reward": "Belønning",
"panel.family_goal_reward_ph": "en familiefilmkveld",
"notification.family_goal_reached.name": "Familiemål nådd",
"notification.family_goal_reached.description": "Feirer når familiens samlede poeng når det felles målet. Av som standard."
"notification.family_goal_reached.description": "Feirer når familiens samlede poeng når det felles målet. Av som standard.",
"panel.chore_advanced_weather": "Avansert — værforhold",
"panel.chore_weather_intro": "Skjul denne oppgaven mens været er uegnet. Nyttig for utendørsarbeid — en oppgave som regner bort teller ikke som en glemt obligatorisk oppgave og bryter ingen rekke.",
"panel.chore_weather_entity_label": "Vær-entitet",
"panel.chore_weather_entity_hint": "La stå tom for å ignorere været helt.",
"panel.chore_weather_conditions_label": "Skjul når været er",
"panel.chore_weather_conditions_hint": "Velg forholdene som skal skjule oppgaven.",
"panel.chore_weather_temp_min_label": "Minimumstemperatur",
"panel.chore_weather_temp_min_hint": "Skjul under dette. La stå tom for ingen grense.",
"panel.chore_weather_temp_max_label": "Maksimumstemperatur",
"panel.chore_weather_temp_max_hint": "Skjul over dette. La stå tom for ingen grense.",
"panel.chore_weather_wind_max_label": "Maksimal vindstyrke",
"panel.chore_weather_wind_max_hint": "Skjul over dette. La stå tom for ingen grense.",
"panel.chore_weather_failopen_hint": "Grensene bruker enhetene vær-entiteten din rapporterer. Hvis entiteten mangler eller er utilgjengelig, forblir oppgaven synlig.",
"weather.blocked_condition": "Skjult — uegnet vær",
"weather.blocked_temp_low": "Skjult — for kaldt",
"weather.blocked_temp_high": "Skjult — for varmt",
"weather.blocked_wind": "Skjult — for mye vind",
"weather.condition_rainy": "Regn",
"weather.condition_pouring": "Kraftig regn",
"weather.condition_snowy": "Snø",
"weather.condition_snowy_rainy": "Sludd",
"weather.condition_hail": "Hagl",
"weather.condition_lightning": "Lyn",
"weather.condition_lightning_rainy": "Tordenvær",
"weather.condition_fog": "Tåke",
"weather.condition_windy": "Vindfullt",
"weather.condition_windy_variant": "Svært vindfullt",
"weather.condition_cloudy": "Skyet",
"weather.condition_exceptional": "Ekstremvær",
"child.deadline_minutes": "{mins} min igjen",
"child.deadline_hours": "{hours}t {mins}m igjen",
"child.deadline_title": "Gjør dette før tiden renner ut",
"panel.chore_advanced_scheduled": "Avansert — planlagte endringer",
"panel.chore_scheduled_intro": "Planlegg en endring til en framtidig dato — «fra 1. september er den verdt 20 poeng». Utføres ved midnatt; var Home Assistant av, tas den igjen ved neste oppstart.",
"panel.chore_scheduled_none": "Ingen endringer planlagt.",
"panel.chore_scheduled_history": "Allerede utført",
"panel.chore_scheduled_applied": "Utført",
"panel.sched_date_label": "Utfør den",
"panel.sched_field_label": "Endring",
"panel.sched_value_label": "Ny verdi",
"panel.sched_note_label": "Notat (valgfritt)",
"panel.sched_note_hint": "En påminnelse om hvorfor, vist ved siden av den planlagte endringen.",
"panel.btn_add_scheduled": "Planlegg endring",
"panel.sched_value_yes": "Ja",
"panel.sched_value_no": "Nei",
"panel.sched_value_nobody": "Ingen",
"panel.toast_sched_added": "Endring planlagt",
"panel.toast_sched_removed": "Endring fjernet",
"panel.toast_sched_date_required": "Velg en dato for endringen",
"panel.toast_sched_value_required": "Skriv inn en verdi for endringen",
"panel.sched_field_points": "Poeng",
"panel.sched_field_assigned_to": "Tildelt",
"panel.sched_field_enabled": "Aktivert",
"panel.sched_field_requires_approval": "Krever godkjenning",
"panel.sched_field_daily_limit": "Daglig grense",
"panel.sched_field_due_days": "Dager",
"panel.sched_field_mandatory": "Obligatorisk",
"panel.sched_field_mandatory_penalty_points": "Strafferpoeng",
"panel.sched_field_expires_on": "Utløper",
"panel.sched_field_description": "Beskrivelse",
"panel.sched_field_time_category": "Tid på dagen",
"panel.sched_field_difficulty": "Vanskelighetsgrad",
"routine.title": "{name}s rutine",
"routine.no_child": "Velg et barn for dette kortet.",
"routine.task_of": "Oppgave {current} av {total}",
"routine.all_done_count": "Alle {total} ferdige",
"routine.done": "Ferdig ✓",
"routine.completed": "Fullført",
"routine.sent_for_checking": "Sendt til sjekk",
"routine.skip": "Senere",
"routine.next": "Neste →",
"routine.back": "← Tilbake",
"routine.waiting_for_parent": "⏳ Venter på at en voksen sjekker",
"routine.photo_note": "Denne trenger et bilde — trykk på den på kortet ditt.",
"routine.finished_title": "Rutinen er ferdig!",
"routine.finished_body": "Bra jobba, {name}. Det var alt.",
"routine.earned": "+{points} tjent",
"routine.awaiting_points": "+{points} når en voksen har sjekket",
"routine.skipped_note": "{count} igjen til senere.",
"routine.start_again": "Start på nytt",
"routine.empty_title": "Ingenting å gjøre",
"routine.empty_body": "Denne rutinen er allerede ferdig.",
"child.roulette_spin": "Spinn for en ×{multiplier}-oppgave",
"child.roulette_respin": "Spinn igjen ({count} igjen)",
"child.roulette_no_spins": "Ingen spinn igjen i dag",
"panel.settings_roulette_section": "Oppgaveroulette",
"panel.settings_roulette_enabled": "Slå på oppgaveroulette",
"panel.settings_roulette_enabled_hint": "Lar et barn spinne for en tilfeldig gjenstående oppgave verdt ekstra poeng.",
"panel.settings_roulette_multiplier": "Poengmultiplikator",
"panel.settings_roulette_spins": "Spinn per dag",
"panel.settings_unlock_allowlist": "Tillatelsesliste for opplåsing",
"panel.settings_unlock_allowlist_hint": "Bare disse entitetene kan låses opp av en belønning. Ingenting er tillatt før du legger til noe her.",
"panel.settings_unlock_allowlist_empty": "Ingenting tillatt ennå.",
"panel.settings_unlock_allowlist_add": "Søk etter en entitet å tillate",
"panel.reward_unlock_section": "Avansert — tidsbegrenset opplåsing",
"panel.reward_unlock_intro": "Slår på noe når denne belønningen godkjennes, og av igjen etter en angitt tid.",
"panel.reward_unlock_none": "Ingenting",
"panel.reward_unlock_entity": "Lås opp",
"panel.reward_unlock_minutes": "Hvor lenge (minutter)",
"panel.reward_unlock_minutes_hint": "0 = la den stå på; du må slå av selv.",
"panel.reward_unlock_no_allowlist": "Legg først til en entitet i tillatelseslisten under Innstillinger.",
"panel.tab_insights": "Innsikt",
"panel.insights_loading": "Regner ut…",
"panel.insights_fairness_title": "Hvem gjør jobben?",
"panel.insights_fairness_intro": "{start} til {end}. En jevn fordeling ville vært {fair}% hver.",
"panel.insights_last_days": "Siste {days} dager",
"panel.insights_no_data": "Ingen godkjente oppgaver i denne perioden ennå.",
"panel.insights_balanced": "Ser ganske jevnt fordelt ut.",
"panel.insights_unbalanced": "Arbeidet er ujevnt fordelt.",
"panel.insights_chores": "oppgaver",
"panel.insights_status_over": "Gjør mer",
"panel.insights_status_under": "Gjør mindre",
"panel.insights_status_balanced": "Jevnt",
"panel.insights_status_idle": "Ingenting ennå",
"panel.insights_fairness_note": "Vurdert på antall oppgaver, ikke poeng, så en dyrere oppgave ikke skjuler en skjev fordeling. Markert når et barn er mer enn {tolerance} poeng unna en lik andel.",
"panel.insights_chore": "oppgave",
"panel.insights_view_fairness": "Rettferdighet",
"panel.insights_view_friction": "Friksjon",
"panel.insights_friction_title": "Hva fungerer ikke?",
"panel.insights_friction_intro": "{start} til {end}. Oppgaver vurderes etter hvor ofte de faktisk blir gjort mot hvor ofte de dukket opp.",
"panel.insights_friction_none": "Ingen aktive oppgaver å se på.",
"panel.insights_friction_healthy": "Alt blir gjort.",
"panel.insights_friction_problems": "{count} oppgave(r) verdt et blikk.",
"panel.insights_col_chore": "Oppgave",
"panel.insights_col_done": "Gjort",
"panel.insights_col_rate": "Andel",
"panel.insights_col_last": "Sist gjort",
"panel.insights_col_verdict": "Dom",
"panel.insights_col_suggestion": "Prøv",
"panel.insights_never": "Aldri",
"panel.insights_days_ago": "for {days}d siden",
"panel.insights_verdict_never": "Aldri gjort",
"panel.insights_verdict_stalling": "Stopper opp",
"panel.insights_verdict_struggling": "Ujevn",
"panel.insights_verdict_fine": "Greit",
"panel.insights_verdict_unknown": "Uklart",
"panel.insights_suggest_retire": "Fjern den",
"panel.insights_suggest_reprice": "Øk poengene",
"panel.insights_suggest_reassign": "Gi den til noen andre",
"panel.insights_suggest_keep": "La den være",
"panel.insights_outstanding_misses": "Uavklarte glemte pliktoppgaver",
"panel.insights_friction_note": "Avvisninger er ikke listet: TaskMate sletter en fullføring når du avviser den, så det finnes ingen historikk. Forventede tall er omtrentlige — rotasjon, avhengigheter og vær spilles ikke av på nytt.",
"panel.insights_view_projection": "Uka som kommer",
"panel.insights_projection_title": "Hva står for tur?",
"panel.insights_projection_intro": "{start} til {end}, basert på timeplanen til hver oppgave.",
"panel.insights_next_days": "Neste {days} dager",
"panel.insights_col_day": "Dag",
"panel.insights_projected_earn": "opptil {points} {name}",
"panel.insights_projected_total": "ville nå {total}",
"panel.insights_projection_unassigned": "{points} poeng er ikke tildelt noen.",
"panel.insights_projection_note": "Et tak, ikke en prognose: oppgaver åpne for alle telles for hvert aktuelt barn, siden timeplanen ikke kan vite hvem som rekker først. Vær, avhengigheter og tilgjengelighet framskrives ikke — de avhenger av dagen.",
"panel.insights_view_health": "Tilstand",
"panel.health_title": "Er noe ødelagt?",
"panel.health_recheck": "Sjekk igjen",
"panel.health_all_good": "Fant ingenting galt.",
"panel.health_issues": "{count} ting trenger oppmerksomhet.",
"panel.health_sev_error": "Feil",
"panel.health_sev_warning": "Advarsel",
"panel.health_sev_info": "Merk",
"panel.health_goto": "Vis meg",
"panel.health_count_children": "Barn",
"panel.health_count_chores": "Oppgaver",
"panel.health_count_enabled_chores": "Aktive",
"panel.health_count_rewards": "Belønninger",
"panel.health_count_completions": "Fullføringer",
"panel.health_count_badges": "Merker",
"panel.health_count_scheduled_changes": "Planlagte",
"panel.health_count_active_unlocks": "Opplåsinger",
"panel.health_count_mandatory_misses": "Glemt",
"panel.health_count_storage": "Lagring",
"panel.chore_icon_label": "Bilde",
"child.editor.pre_reader": "Kun bilder (for små barn)",
"child.editor.pre_reader_labels": "Vis navn i bildemodus",
"common.design.accessible": "Tilgjengelig"
}
+175 -1
View File
@@ -1538,5 +1538,179 @@
"panel.family_goal_reward": "Beløning",
"panel.family_goal_reward_ph": "ein familiefilmkveld",
"notification.family_goal_reached.name": "Familiemål nådd",
"notification.family_goal_reached.description": "Feirar når dei samla poenga til familien når det felles målet. Av som standard."
"notification.family_goal_reached.description": "Feirar når dei samla poenga til familien når det felles målet. Av som standard.",
"panel.chore_advanced_weather": "Avansert — vertilhøve",
"panel.chore_weather_intro": "Skjul denne oppgåva medan veret er uegna. Nyttig for utandørsarbeid — ei oppgåve som regnar bort tel ikkje som ei gløymd obligatorisk oppgåve og bryt ingen rekkje.",
"panel.chore_weather_entity_label": "Ver-entitet",
"panel.chore_weather_entity_hint": "La stå tom for å ignorere veret heilt.",
"panel.chore_weather_conditions_label": "Skjul når veret er",
"panel.chore_weather_conditions_hint": "Vel tilhøva som skal skjule oppgåva.",
"panel.chore_weather_temp_min_label": "Minimumstemperatur",
"panel.chore_weather_temp_min_hint": "Skjul under dette. La stå tom for inga grense.",
"panel.chore_weather_temp_max_label": "Maksimumstemperatur",
"panel.chore_weather_temp_max_hint": "Skjul over dette. La stå tom for inga grense.",
"panel.chore_weather_wind_max_label": "Maksimal vindstyrke",
"panel.chore_weather_wind_max_hint": "Skjul over dette. La stå tom for inga grense.",
"panel.chore_weather_failopen_hint": "Grensene bruker einingane ver-entiteten din rapporterer. Om entiteten manglar eller er utilgjengeleg, held oppgåva fram med å vere synleg.",
"weather.blocked_condition": "Skjult — uegna ver",
"weather.blocked_temp_low": "Skjult — for kaldt",
"weather.blocked_temp_high": "Skjult — for varmt",
"weather.blocked_wind": "Skjult — for mykje vind",
"weather.condition_rainy": "Regn",
"weather.condition_pouring": "Kraftig regn",
"weather.condition_snowy": "Snø",
"weather.condition_snowy_rainy": "Sludd",
"weather.condition_hail": "Hagl",
"weather.condition_lightning": "Lyn",
"weather.condition_lightning_rainy": "Tordenver",
"weather.condition_fog": "Skodde",
"weather.condition_windy": "Vindfullt",
"weather.condition_windy_variant": "Svært vindfullt",
"weather.condition_cloudy": "Skya",
"weather.condition_exceptional": "Ekstremver",
"child.deadline_minutes": "{mins} min att",
"child.deadline_hours": "{hours}t {mins}m att",
"child.deadline_title": "Gjer dette før tida renn ut",
"panel.chore_advanced_scheduled": "Avansert — planlagde endringar",
"panel.chore_scheduled_intro": "Planlegg ei endring til ein framtidig dato — «frå 1. september er ho verdt 20 poeng». Vert utført ved midnatt; var Home Assistant av, vert ho teken att ved neste oppstart.",
"panel.chore_scheduled_none": "Ingen endringar planlagde.",
"panel.chore_scheduled_history": "Alt utført",
"panel.chore_scheduled_applied": "Utført",
"panel.sched_date_label": "Utfør den",
"panel.sched_field_label": "Endring",
"panel.sched_value_label": "Ny verdi",
"panel.sched_note_label": "Notat (valfritt)",
"panel.sched_note_hint": "Ei påminning om kvifor, vist ved sida av den planlagde endringa.",
"panel.btn_add_scheduled": "Planlegg endring",
"panel.sched_value_yes": "Ja",
"panel.sched_value_no": "Nei",
"panel.sched_value_nobody": "Ingen",
"panel.toast_sched_added": "Endring planlagd",
"panel.toast_sched_removed": "Endring fjerna",
"panel.toast_sched_date_required": "Vel ein dato for endringa",
"panel.toast_sched_value_required": "Skriv inn ein verdi for endringa",
"panel.sched_field_points": "Poeng",
"panel.sched_field_assigned_to": "Tildelt",
"panel.sched_field_enabled": "Aktivert",
"panel.sched_field_requires_approval": "Krev godkjenning",
"panel.sched_field_daily_limit": "Dagleg grense",
"panel.sched_field_due_days": "Dagar",
"panel.sched_field_mandatory": "Obligatorisk",
"panel.sched_field_mandatory_penalty_points": "Straffepoeng",
"panel.sched_field_expires_on": "Går ut",
"panel.sched_field_description": "Skildring",
"panel.sched_field_time_category": "Tid på dagen",
"panel.sched_field_difficulty": "Vanskegrad",
"routine.title": "{name} si rutine",
"routine.no_child": "Vel eit barn for dette kortet.",
"routine.task_of": "Oppgåve {current} av {total}",
"routine.all_done_count": "Alle {total} ferdige",
"routine.done": "Ferdig ✓",
"routine.completed": "Fullført",
"routine.sent_for_checking": "Sendt til sjekk",
"routine.skip": "Seinare",
"routine.next": "Neste →",
"routine.back": "← Tilbake",
"routine.waiting_for_parent": "⏳ Ventar på at ein vaksen sjekkar",
"routine.photo_note": "Denne treng eit bilete — trykk på han på kortet ditt.",
"routine.finished_title": "Rutinen er ferdig!",
"routine.finished_body": "Bra jobba, {name}. Det var alt.",
"routine.earned": "+{points} tent",
"routine.awaiting_points": "+{points} når ein vaksen har sjekka",
"routine.skipped_note": "{count} att til seinare.",
"routine.start_again": "Start på nytt",
"routine.empty_title": "Ingenting å gjere",
"routine.empty_body": "Denne rutinen er alt ferdig.",
"child.roulette_spin": "Spinn for ei ×{multiplier}-oppgåve",
"child.roulette_respin": "Spinn igjen ({count} att)",
"child.roulette_no_spins": "Ingen spinn att i dag",
"panel.settings_roulette_section": "Oppgåveroulette",
"panel.settings_roulette_enabled": "Slå på oppgåveroulette",
"panel.settings_roulette_enabled_hint": "Lèt eit barn spinne for ei tilfeldig gjenståande oppgåve verdt ekstra poeng.",
"panel.settings_roulette_multiplier": "Poengmultiplikator",
"panel.settings_roulette_spins": "Spinn per dag",
"panel.settings_unlock_allowlist": "Løyveliste for opplåsing",
"panel.settings_unlock_allowlist_hint": "Berre desse entitetane kan låsast opp av ei belønning. Ingenting er tillate før du legg til noko her.",
"panel.settings_unlock_allowlist_empty": "Ingenting tillate enno.",
"panel.settings_unlock_allowlist_add": "Søk etter ein entitet å tillate",
"panel.reward_unlock_section": "Avansert — tidsavgrensa opplåsing",
"panel.reward_unlock_intro": "Slår på noko når denne belønninga vert godkjend, og av igjen etter ei fastsett tid.",
"panel.reward_unlock_none": "Ingenting",
"panel.reward_unlock_entity": "Lås opp",
"panel.reward_unlock_minutes": "Kor lenge (minutt)",
"panel.reward_unlock_minutes_hint": "0 = la han stå på; du må slå av sjølv.",
"panel.reward_unlock_no_allowlist": "Legg først til ein entitet i løyvelista under Innstillingar.",
"panel.tab_insights": "Innsikt",
"panel.insights_loading": "Reknar ut…",
"panel.insights_fairness_title": "Kven gjer jobben?",
"panel.insights_fairness_intro": "{start} til {end}. Ei jamn fordeling ville vore {fair}% kvar.",
"panel.insights_last_days": "Siste {days} dagar",
"panel.insights_no_data": "Ingen godkjende oppgåver i denne perioden enno.",
"panel.insights_balanced": "Ser ganske jamt fordelt ut.",
"panel.insights_unbalanced": "Arbeidet er ujamt fordelt.",
"panel.insights_chores": "oppgåver",
"panel.insights_status_over": "Gjer meir",
"panel.insights_status_under": "Gjer mindre",
"panel.insights_status_balanced": "Jamt",
"panel.insights_status_idle": "Ingenting enno",
"panel.insights_fairness_note": "Vurdert på tal oppgåver, ikkje poeng, så ei dyrare oppgåve ikkje skjuler ei skeiv fordeling. Markert når eit barn er meir enn {tolerance} poeng unna ein lik del.",
"panel.insights_chore": "oppgåve",
"panel.insights_view_fairness": "Rettferd",
"panel.insights_view_friction": "Friksjon",
"panel.insights_friction_title": "Kva fungerer ikkje?",
"panel.insights_friction_intro": "{start} til {end}. Oppgåver vert vurderte etter kor ofte dei faktisk vert gjorde mot kor ofte dei dukka opp.",
"panel.insights_friction_none": "Ingen aktive oppgåver å sjå på.",
"panel.insights_friction_healthy": "Alt vert gjort.",
"panel.insights_friction_problems": "{count} oppgåve(r) verdt eit blikk.",
"panel.insights_col_chore": "Oppgåve",
"panel.insights_col_done": "Gjort",
"panel.insights_col_rate": "Del",
"panel.insights_col_last": "Sist gjort",
"panel.insights_col_verdict": "Dom",
"panel.insights_col_suggestion": "Prøv",
"panel.insights_never": "Aldri",
"panel.insights_days_ago": "for {days}d sidan",
"panel.insights_verdict_never": "Aldri gjort",
"panel.insights_verdict_stalling": "Stoppar opp",
"panel.insights_verdict_struggling": "Ujamn",
"panel.insights_verdict_fine": "Greitt",
"panel.insights_verdict_unknown": "Uklart",
"panel.insights_suggest_retire": "Fjern henne",
"panel.insights_suggest_reprice": "Auk poenga",
"panel.insights_suggest_reassign": "Gje henne til nokon andre",
"panel.insights_suggest_keep": "La henne vere",
"panel.insights_outstanding_misses": "Uavklarte gløymde pliktoppgåver",
"panel.insights_friction_note": "Avvisingar er ikkje lista: TaskMate slettar ei fullføring når du avviser henne, så det finst ingen historikk. Venta tal er omtrentlege — rotasjon, avhengnader og ver vert ikkje spelte av på nytt.",
"panel.insights_view_projection": "Veka som kjem",
"panel.insights_projection_title": "Kva står for tur?",
"panel.insights_projection_intro": "{start} til {end}, basert på timeplanen til kvar oppgåve.",
"panel.insights_next_days": "Neste {days} dagar",
"panel.insights_col_day": "Dag",
"panel.insights_projected_earn": "opptil {points} {name}",
"panel.insights_projected_total": "ville nå {total}",
"panel.insights_projection_unassigned": "{points} poeng er ikkje tildelte nokon.",
"panel.insights_projection_note": "Eit tak, ikkje ein prognose: oppgåver opne for alle vert talde for kvart aktuelt barn, sidan timeplanen ikkje kan vite kven som rekk først. Ver, avhengnader og tilgjenge vert ikkje framskrivne — dei kjem an på dagen.",
"panel.insights_view_health": "Tilstand",
"panel.health_title": "Er noko øydelagt?",
"panel.health_recheck": "Sjekk igjen",
"panel.health_all_good": "Fann ingenting gale.",
"panel.health_issues": "{count} ting treng merksemd.",
"panel.health_sev_error": "Feil",
"panel.health_sev_warning": "Åtvaring",
"panel.health_sev_info": "Merk",
"panel.health_goto": "Vis meg",
"panel.health_count_children": "Born",
"panel.health_count_chores": "Oppgåver",
"panel.health_count_enabled_chores": "Aktive",
"panel.health_count_rewards": "Belønningar",
"panel.health_count_completions": "Fullføringar",
"panel.health_count_badges": "Merke",
"panel.health_count_scheduled_changes": "Planlagde",
"panel.health_count_active_unlocks": "Opplåsingar",
"panel.health_count_mandatory_misses": "Gløymt",
"panel.health_count_storage": "Lagring",
"panel.chore_icon_label": "Bilete",
"child.editor.pre_reader": "Berre bilete (for små born)",
"child.editor.pre_reader_labels": "Vis namn i biletemodus",
"common.design.accessible": "Tilgjengeleg"
}
@@ -1538,5 +1538,179 @@
"panel.family_goal_reward": "Recompensa",
"panel.family_goal_reward_ph": "uma noite de cinema em família",
"notification.family_goal_reached.name": "Meta da família alcançada",
"notification.family_goal_reached.description": "Comemora quando os pontos combinados da família atingem a meta compartilhada. Desativado por padrão."
"notification.family_goal_reached.description": "Comemora quando os pontos combinados da família atingem a meta compartilhada. Desativado por padrão.",
"panel.chore_advanced_weather": "Avançado — condições do tempo",
"panel.chore_weather_intro": "Ocultar esta tarefa enquanto o tempo não estiver adequado. Útil para trabalhos ao ar livre — uma tarefa cancelada pela chuva não conta como falha obrigatória nem quebra uma sequência.",
"panel.chore_weather_entity_label": "Entidade de clima",
"panel.chore_weather_entity_hint": "Deixe vazio para ignorar o tempo completamente.",
"panel.chore_weather_conditions_label": "Ocultar quando o tempo estiver",
"panel.chore_weather_conditions_hint": "Escolha as condições que devem ocultar a tarefa.",
"panel.chore_weather_temp_min_label": "Temperatura mínima",
"panel.chore_weather_temp_min_hint": "Ocultar abaixo disso. Deixe vazio para não ter limite.",
"panel.chore_weather_temp_max_label": "Temperatura máxima",
"panel.chore_weather_temp_max_hint": "Ocultar acima disso. Deixe vazio para não ter limite.",
"panel.chore_weather_wind_max_label": "Velocidade máxima do vento",
"panel.chore_weather_wind_max_hint": "Ocultar acima disso. Deixe vazio para não ter limite.",
"panel.chore_weather_failopen_hint": "Os limites usam as unidades informadas pela sua entidade de clima. Se a entidade estiver ausente ou indisponível, a tarefa continua visível.",
"weather.blocked_condition": "Oculta — tempo inadequado",
"weather.blocked_temp_low": "Oculta — frio demais",
"weather.blocked_temp_high": "Oculta — calor demais",
"weather.blocked_wind": "Oculta — vento demais",
"weather.condition_rainy": "Chuvoso",
"weather.condition_pouring": "Chuva forte",
"weather.condition_snowy": "Neve",
"weather.condition_snowy_rainy": "Chuva com neve",
"weather.condition_hail": "Granizo",
"weather.condition_lightning": "Relâmpagos",
"weather.condition_lightning_rainy": "Tempestade",
"weather.condition_fog": "Neblina",
"weather.condition_windy": "Ventando",
"weather.condition_windy_variant": "Muito vento",
"weather.condition_cloudy": "Nublado",
"weather.condition_exceptional": "Tempo severo",
"child.deadline_minutes": "faltam {mins} min",
"child.deadline_hours": "faltam {hours}h {mins}m",
"child.deadline_title": "Faça isso antes de o tempo acabar",
"panel.chore_advanced_scheduled": "Avançado — alterações agendadas",
"panel.chore_scheduled_intro": "Agende uma alteração para uma data futura — \"a partir de 1º de setembro vale 20 pontos\". Aplicada à meia-noite; se o Home Assistant estiver desligado, é recuperada na próxima inicialização.",
"panel.chore_scheduled_none": "Nenhuma alteração agendada.",
"panel.chore_scheduled_history": "Já aplicadas",
"panel.chore_scheduled_applied": "Aplicada",
"panel.sched_date_label": "Aplicar em",
"panel.sched_field_label": "Alteração",
"panel.sched_value_label": "Novo valor",
"panel.sched_note_label": "Observação (opcional)",
"panel.sched_note_hint": "Um lembrete do motivo, mostrado ao lado da alteração agendada.",
"panel.btn_add_scheduled": "Agendar alteração",
"panel.sched_value_yes": "Sim",
"panel.sched_value_no": "Não",
"panel.sched_value_nobody": "Ninguém",
"panel.toast_sched_added": "Alteração agendada",
"panel.toast_sched_removed": "Alteração removida",
"panel.toast_sched_date_required": "Escolha uma data para a alteração",
"panel.toast_sched_value_required": "Digite um valor para a alteração",
"panel.sched_field_points": "Pontos",
"panel.sched_field_assigned_to": "Atribuída a",
"panel.sched_field_enabled": "Ativada",
"panel.sched_field_requires_approval": "Requer aprovação",
"panel.sched_field_daily_limit": "Limite diário",
"panel.sched_field_due_days": "Dias",
"panel.sched_field_mandatory": "Obrigatória",
"panel.sched_field_mandatory_penalty_points": "Pontos de penalidade",
"panel.sched_field_expires_on": "Expira em",
"panel.sched_field_description": "Descrição",
"panel.sched_field_time_category": "Período do dia",
"panel.sched_field_difficulty": "Dificuldade",
"routine.title": "Rotina de {name}",
"routine.no_child": "Escolha uma criança para este card.",
"routine.task_of": "Tarefa {current} de {total}",
"routine.all_done_count": "Todas as {total} feitas",
"routine.done": "Feito ✓",
"routine.completed": "Concluída",
"routine.sent_for_checking": "Enviado para verificação",
"routine.skip": "Mais tarde",
"routine.next": "Próxima →",
"routine.back": "← Voltar",
"routine.waiting_for_parent": "⏳ Aguardando a verificação de um adulto",
"routine.photo_note": "Esta precisa de uma foto — toque nela no seu card.",
"routine.finished_title": "Rotina concluída!",
"routine.finished_body": "Boa, {name}. É tudo.",
"routine.earned": "+{points} ganhos",
"routine.awaiting_points": "+{points} assim que um adulto verificar",
"routine.skipped_note": "{count} deixada(s) para depois.",
"routine.start_again": "Recomeçar",
"routine.empty_title": "Nada a fazer",
"routine.empty_body": "Esta rotina já está concluída.",
"child.roulette_spin": "Girar para uma tarefa ×{multiplier}",
"child.roulette_respin": "Girar de novo ({count} restante)",
"child.roulette_no_spins": "Sem giros hoje",
"panel.settings_roulette_section": "Roleta de tarefas",
"panel.settings_roulette_enabled": "Ativar roleta de tarefas",
"panel.settings_roulette_enabled_hint": "Permite que uma criança gire por uma tarefa pendente aleatória que vale pontos extras.",
"panel.settings_roulette_multiplier": "Multiplicador de pontos",
"panel.settings_roulette_spins": "Giros por dia",
"panel.settings_unlock_allowlist": "Lista de permissões de desbloqueio",
"panel.settings_unlock_allowlist_hint": "Só estas entidades podem ser desbloqueadas por uma recompensa. Nada é permitido até você adicionar algo aqui.",
"panel.settings_unlock_allowlist_empty": "Nada permitido ainda.",
"panel.settings_unlock_allowlist_add": "Procurar uma entidade para permitir",
"panel.reward_unlock_section": "Avançado — desbloqueio temporário",
"panel.reward_unlock_intro": "Liga algo quando esta recompensa for aprovada e desliga de novo após um tempo definido.",
"panel.reward_unlock_none": "Nada",
"panel.reward_unlock_entity": "Desbloquear",
"panel.reward_unlock_minutes": "Por quanto tempo (minutos)",
"panel.reward_unlock_minutes_hint": "0 = deixar ligado; você terá de desligar.",
"panel.reward_unlock_no_allowlist": "Adicione primeiro uma entidade à lista de permissões nas Configurações.",
"panel.tab_insights": "Análises",
"panel.insights_loading": "Calculando…",
"panel.insights_fairness_title": "Quem está fazendo o trabalho?",
"panel.insights_fairness_intro": "{start} a {end}. Uma divisão igual seria {fair}% para cada.",
"panel.insights_last_days": "Últimos {days} dias",
"panel.insights_no_data": "Ainda sem tarefas aprovadas neste período.",
"panel.insights_balanced": "Parece bem equilibrado.",
"panel.insights_unbalanced": "A carga está desequilibrada.",
"panel.insights_chores": "tarefas",
"panel.insights_status_over": "Faz mais",
"panel.insights_status_under": "Faz menos",
"panel.insights_status_balanced": "Equilibrado",
"panel.insights_status_idle": "Ainda nada",
"panel.insights_fairness_note": "Avaliado pelo número de tarefas, não por pontos, para que uma tarefa mais cara não esconda uma divisão desigual. Sinalizado quando uma criança está a mais de {tolerance} pontos de uma parte igual.",
"panel.insights_chore": "tarefa",
"panel.insights_view_fairness": "Equidade",
"panel.insights_view_friction": "Atrito",
"panel.insights_friction_title": "O que não está funcionando?",
"panel.insights_friction_intro": "{start} a {end}. As tarefas são avaliadas pela frequência com que são feitas em relação à frequência com que surgiram.",
"panel.insights_friction_none": "Sem tarefas ativas para analisar.",
"panel.insights_friction_healthy": "Está tudo sendo feito.",
"panel.insights_friction_problems": "{count} tarefa(s) merecendo atenção.",
"panel.insights_col_chore": "Tarefa",
"panel.insights_col_done": "Feitas",
"panel.insights_col_rate": "Taxa",
"panel.insights_col_last": "Última vez",
"panel.insights_col_verdict": "Veredito",
"panel.insights_col_suggestion": "Tente",
"panel.insights_never": "Nunca",
"panel.insights_days_ago": "há {days}d",
"panel.insights_verdict_never": "Nunca feita",
"panel.insights_verdict_stalling": "Parada",
"panel.insights_verdict_struggling": "Irregular",
"panel.insights_verdict_fine": "Bem",
"panel.insights_verdict_unknown": "Incerto",
"panel.insights_suggest_retire": "Remover",
"panel.insights_suggest_reprice": "Aumentar os pontos",
"panel.insights_suggest_reassign": "Dar para outra pessoa",
"panel.insights_suggest_keep": "Deixar como está",
"panel.insights_outstanding_misses": "Falhas obrigatórias pendentes",
"panel.insights_friction_note": "As rejeições não são listadas: o TaskMate apaga a conclusão quando você a rejeita, então não há histórico. Os valores esperados são aproximados — rotação, dependências e clima não são reproduzidos.",
"panel.insights_view_projection": "Semana seguinte",
"panel.insights_projection_title": "O que vem por aí?",
"panel.insights_projection_intro": "{start} a {end}, com base na programação de cada tarefa.",
"panel.insights_next_days": "Próximos {days} dias",
"panel.insights_col_day": "Dia",
"panel.insights_projected_earn": "até {points} {name}",
"panel.insights_projected_total": "chegaria a {total}",
"panel.insights_projection_unassigned": "{points} pontos não estão atribuídos a ninguém.",
"panel.insights_projection_note": "Um teto, não uma previsão: tarefas abertas a todos são contadas para cada criança elegível, já que a programação não sabe quem chega primeiro. Clima, dependências e disponibilidade não são projetados — dependem do dia.",
"panel.insights_view_health": "Saúde",
"panel.health_title": "Tem algo quebrado?",
"panel.health_recheck": "Verificar de novo",
"panel.health_all_good": "Nada de errado.",
"panel.health_issues": "{count} coisa(s) precisando de atenção.",
"panel.health_sev_error": "Erro",
"panel.health_sev_warning": "Aviso",
"panel.health_sev_info": "Nota",
"panel.health_goto": "Mostrar",
"panel.health_count_children": "Crianças",
"panel.health_count_chores": "Tarefas",
"panel.health_count_enabled_chores": "Ativas",
"panel.health_count_rewards": "Recompensas",
"panel.health_count_completions": "Conclusões",
"panel.health_count_badges": "Emblemas",
"panel.health_count_scheduled_changes": "Agendadas",
"panel.health_count_active_unlocks": "Desbloqueios",
"panel.health_count_mandatory_misses": "Falhas",
"panel.health_count_storage": "Armazenamento",
"panel.chore_icon_label": "Imagem",
"child.editor.pre_reader": "Modo só imagens (crianças pequenas)",
"child.editor.pre_reader_labels": "Mostrar nomes no modo imagens",
"common.design.accessible": "Acessível"
}
+175 -1
View File
@@ -1538,5 +1538,179 @@
"panel.family_goal_reward": "Recompensa",
"panel.family_goal_reward_ph": "uma noite de cinema em família",
"notification.family_goal_reached.name": "Objetivo da família alcançado",
"notification.family_goal_reached.description": "Comemora quando os pontos combinados da família atingem o objetivo partilhado. Desativado por predefinição."
"notification.family_goal_reached.description": "Comemora quando os pontos combinados da família atingem o objetivo partilhado. Desativado por predefinição.",
"panel.chore_advanced_weather": "Avançado — condições meteorológicas",
"panel.chore_weather_intro": "Ocultar esta tarefa enquanto o tempo não for adequado. Útil para trabalhos no exterior — uma tarefa cancelada pela chuva não conta como falha obrigatória nem quebra uma sequência.",
"panel.chore_weather_entity_label": "Entidade meteorológica",
"panel.chore_weather_entity_hint": "Deixe vazio para ignorar completamente o tempo.",
"panel.chore_weather_conditions_label": "Ocultar quando o tempo estiver",
"panel.chore_weather_conditions_hint": "Escolha as condições que devem ocultar a tarefa.",
"panel.chore_weather_temp_min_label": "Temperatura mínima",
"panel.chore_weather_temp_min_hint": "Ocultar abaixo disto. Deixe vazio para não ter limite.",
"panel.chore_weather_temp_max_label": "Temperatura máxima",
"panel.chore_weather_temp_max_hint": "Ocultar acima disto. Deixe vazio para não ter limite.",
"panel.chore_weather_wind_max_label": "Velocidade máxima do vento",
"panel.chore_weather_wind_max_hint": "Ocultar acima disto. Deixe vazio para não ter limite.",
"panel.chore_weather_failopen_hint": "Os limites usam as unidades comunicadas pela sua entidade meteorológica. Se a entidade estiver em falta ou indisponível, a tarefa permanece visível.",
"weather.blocked_condition": "Oculta — tempo inadequado",
"weather.blocked_temp_low": "Oculta — demasiado frio",
"weather.blocked_temp_high": "Oculta — demasiado calor",
"weather.blocked_wind": "Oculta — demasiado vento",
"weather.condition_rainy": "Chuvoso",
"weather.condition_pouring": "Chuva forte",
"weather.condition_snowy": "Neve",
"weather.condition_snowy_rainy": "Chuva com neve",
"weather.condition_hail": "Granizo",
"weather.condition_lightning": "Relâmpagos",
"weather.condition_lightning_rainy": "Trovoada",
"weather.condition_fog": "Nevoeiro",
"weather.condition_windy": "Ventoso",
"weather.condition_windy_variant": "Muito ventoso",
"weather.condition_cloudy": "Nublado",
"weather.condition_exceptional": "Tempo severo",
"child.deadline_minutes": "faltam {mins} min",
"child.deadline_hours": "faltam {hours}h {mins}m",
"child.deadline_title": "Faz isto antes de o tempo acabar",
"panel.chore_advanced_scheduled": "Avançado — alterações agendadas",
"panel.chore_scheduled_intro": "Agende uma alteração para uma data futura — «a partir de 1 de setembro vale 20 pontos». Aplicada à meia-noite; se o Home Assistant estiver desligado, é recuperada no arranque seguinte.",
"panel.chore_scheduled_none": "Sem alterações agendadas.",
"panel.chore_scheduled_history": "Já aplicadas",
"panel.chore_scheduled_applied": "Aplicada",
"panel.sched_date_label": "Aplicar em",
"panel.sched_field_label": "Alteração",
"panel.sched_value_label": "Novo valor",
"panel.sched_note_label": "Nota (opcional)",
"panel.sched_note_hint": "Um lembrete do porquê, mostrado junto à alteração agendada.",
"panel.btn_add_scheduled": "Agendar alteração",
"panel.sched_value_yes": "Sim",
"panel.sched_value_no": "Não",
"panel.sched_value_nobody": "Ninguém",
"panel.toast_sched_added": "Alteração agendada",
"panel.toast_sched_removed": "Alteração removida",
"panel.toast_sched_date_required": "Escolha uma data para a alteração",
"panel.toast_sched_value_required": "Introduza um valor para a alteração",
"panel.sched_field_points": "Pontos",
"panel.sched_field_assigned_to": "Atribuída a",
"panel.sched_field_enabled": "Ativada",
"panel.sched_field_requires_approval": "Requer aprovação",
"panel.sched_field_daily_limit": "Limite diário",
"panel.sched_field_due_days": "Dias",
"panel.sched_field_mandatory": "Obrigatória",
"panel.sched_field_mandatory_penalty_points": "Pontos de penalização",
"panel.sched_field_expires_on": "Expira em",
"panel.sched_field_description": "Descrição",
"panel.sched_field_time_category": "Altura do dia",
"panel.sched_field_difficulty": "Dificuldade",
"routine.title": "Rotina de {name}",
"routine.no_child": "Escolhe uma criança para este cartão.",
"routine.task_of": "Tarefa {current} de {total}",
"routine.all_done_count": "Todas as {total} feitas",
"routine.done": "Feito ✓",
"routine.completed": "Concluída",
"routine.sent_for_checking": "Enviado para verificação",
"routine.skip": "Mais tarde",
"routine.next": "Seguinte →",
"routine.back": "← Voltar",
"routine.waiting_for_parent": "⏳ À espera da verificação de um adulto",
"routine.photo_note": "Esta precisa de uma foto — toca nela no teu cartão.",
"routine.finished_title": "Rotina concluída!",
"routine.finished_body": "Boa, {name}. É tudo.",
"routine.earned": "+{points} ganhos",
"routine.awaiting_points": "+{points} assim que um adulto verificar",
"routine.skipped_note": "{count} deixada(s) para depois.",
"routine.start_again": "Recomeçar",
"routine.empty_title": "Nada a fazer",
"routine.empty_body": "Esta rotina já está concluída.",
"child.roulette_spin": "Girar para uma tarefa ×{multiplier}",
"child.roulette_respin": "Girar de novo ({count} restante)",
"child.roulette_no_spins": "Sem giros hoje",
"panel.settings_roulette_section": "Roleta de tarefas",
"panel.settings_roulette_enabled": "Ativar roleta de tarefas",
"panel.settings_roulette_enabled_hint": "Permite a uma criança girar por uma tarefa pendente aleatória que vale pontos extra.",
"panel.settings_roulette_multiplier": "Multiplicador de pontos",
"panel.settings_roulette_spins": "Giros por dia",
"panel.settings_unlock_allowlist": "Lista de permissões de desbloqueio",
"panel.settings_unlock_allowlist_hint": "Só estas entidades podem ser desbloqueadas por uma recompensa. Nada é permitido até adicionares algo aqui.",
"panel.settings_unlock_allowlist_empty": "Nada permitido ainda.",
"panel.settings_unlock_allowlist_add": "Procurar uma entidade para permitir",
"panel.reward_unlock_section": "Avançado — desbloqueio temporário",
"panel.reward_unlock_intro": "Liga algo quando esta recompensa for aprovada e desliga de novo após um tempo definido.",
"panel.reward_unlock_none": "Nada",
"panel.reward_unlock_entity": "Desbloquear",
"panel.reward_unlock_minutes": "Durante quanto tempo (minutos)",
"panel.reward_unlock_minutes_hint": "0 = deixar ligado; terás de desligar tu.",
"panel.reward_unlock_no_allowlist": "Adiciona primeiro uma entidade à lista de permissões nas Definições.",
"panel.tab_insights": "Análises",
"panel.insights_loading": "A calcular…",
"panel.insights_fairness_title": "Quem está a fazer o trabalho?",
"panel.insights_fairness_intro": "{start} a {end}. Uma divisão igual seria {fair}% para cada.",
"panel.insights_last_days": "Últimos {days} dias",
"panel.insights_no_data": "Ainda sem tarefas aprovadas neste período.",
"panel.insights_balanced": "Parece bastante equilibrado.",
"panel.insights_unbalanced": "A carga está desequilibrada.",
"panel.insights_chores": "tarefas",
"panel.insights_status_over": "Faz mais",
"panel.insights_status_under": "Faz menos",
"panel.insights_status_balanced": "Equilibrado",
"panel.insights_status_idle": "Ainda nada",
"panel.insights_fairness_note": "Avaliado pelo número de tarefas, não por pontos, para que uma tarefa mais cara não esconda uma divisão desigual. Assinalado quando uma criança está a mais de {tolerance} pontos de uma parte igual.",
"panel.insights_chore": "tarefa",
"panel.insights_view_fairness": "Equidade",
"panel.insights_view_friction": "Atrito",
"panel.insights_friction_title": "O que não está a funcionar?",
"panel.insights_friction_intro": "{start} a {end}. As tarefas são avaliadas pela frequência com que são feitas face à frequência com que surgiram.",
"panel.insights_friction_none": "Sem tarefas ativas para analisar.",
"panel.insights_friction_healthy": "Está tudo a ser feito.",
"panel.insights_friction_problems": "{count} tarefa(s) a merecer atenção.",
"panel.insights_col_chore": "Tarefa",
"panel.insights_col_done": "Feitas",
"panel.insights_col_rate": "Taxa",
"panel.insights_col_last": "Última vez",
"panel.insights_col_verdict": "Veredito",
"panel.insights_col_suggestion": "Tenta",
"panel.insights_never": "Nunca",
"panel.insights_days_ago": "há {days}d",
"panel.insights_verdict_never": "Nunca feita",
"panel.insights_verdict_stalling": "Parada",
"panel.insights_verdict_struggling": "Irregular",
"panel.insights_verdict_fine": "Bem",
"panel.insights_verdict_unknown": "Incerto",
"panel.insights_suggest_retire": "Remover",
"panel.insights_suggest_reprice": "Aumentar os pontos",
"panel.insights_suggest_reassign": "Dar a outra pessoa",
"panel.insights_suggest_keep": "Deixar como está",
"panel.insights_outstanding_misses": "Falhas obrigatórias por resolver",
"panel.insights_friction_note": "As rejeições não são listadas: o TaskMate apaga a conclusão quando a rejeitas, por isso não há histórico. Os valores esperados são aproximados — rotação, dependências e meteorologia não são reproduzidas.",
"panel.insights_view_projection": "Semana seguinte",
"panel.insights_projection_title": "O que vem aí?",
"panel.insights_projection_intro": "{start} a {end}, com base no horário de cada tarefa.",
"panel.insights_next_days": "Próximos {days} dias",
"panel.insights_col_day": "Dia",
"panel.insights_projected_earn": "até {points} {name}",
"panel.insights_projected_total": "chegaria a {total}",
"panel.insights_projection_unassigned": "{points} pontos não estão atribuídos a ninguém.",
"panel.insights_projection_note": "Um teto, não uma previsão: tarefas abertas a todos são contadas para cada criança elegível, já que o horário não sabe quem chega primeiro. Meteorologia, dependências e disponibilidade não são projetadas — dependem do dia.",
"panel.insights_view_health": "Estado",
"panel.health_title": "Está algo avariado?",
"panel.health_recheck": "Verificar de novo",
"panel.health_all_good": "Nada de errado.",
"panel.health_issues": "{count} coisa(s) a precisar de atenção.",
"panel.health_sev_error": "Erro",
"panel.health_sev_warning": "Aviso",
"panel.health_sev_info": "Nota",
"panel.health_goto": "Mostrar",
"panel.health_count_children": "Crianças",
"panel.health_count_chores": "Tarefas",
"panel.health_count_enabled_chores": "Ativas",
"panel.health_count_rewards": "Recompensas",
"panel.health_count_completions": "Conclusões",
"panel.health_count_badges": "Emblemas",
"panel.health_count_scheduled_changes": "Agendadas",
"panel.health_count_active_unlocks": "Desbloqueios",
"panel.health_count_mandatory_misses": "Falhas",
"panel.health_count_storage": "Armazenamento",
"panel.chore_icon_label": "Imagem",
"child.editor.pre_reader": "Modo só imagens (crianças pequenas)",
"child.editor.pre_reader_labels": "Mostrar nomes no modo imagens",
"common.design.accessible": "Acessível"
}
@@ -91,9 +91,17 @@ class TaskMateChildCard extends LitElement {
|| this.hass?.states?.[this.config?.entity]?.attributes || {};
const sessions = attrs.active_timed_sessions || [];
const hasRunning = sessions.some(s => s.state === 'running' && s.child_id === this.config?.child_id);
if (hasRunning && !this._timerInterval) {
// A reactive chore's countdown (#674) has to keep ticking too, otherwise it
// sits on a stale minute until the next coordinator push.
const now = Date.now();
const hasDeadline = (attrs.chores || []).some(c => {
if (!c.deadline_at) return false;
const at = new Date(c.deadline_at).getTime();
return !Number.isNaN(at) && at > now;
});
if ((hasRunning || hasDeadline) && !this._timerInterval) {
this._timerInterval = setInterval(() => { this._timerTick++; this.requestUpdate(); }, 1000);
} else if (!hasRunning && this._timerInterval) {
} else if (!hasRunning && !hasDeadline && this._timerInterval) {
this._stopTimerTick();
}
}
@@ -600,6 +608,23 @@ class TaskMateChildCard extends LitElement {
width: 18px; height: 18px; border-radius: 50%;
background: rgba(0,0,0,0.45); display: flex; align-items: center; justify-content: center;
}
/* Designed-header avatar wrapper: mirror of the classic avatar-container
so the picker works under every design, not just classic. The picker
itself (.avatar-picker) is absolute, so the header is the positioned
anchor. Base styles here win over the shared kit's .tmd-hd because the
card includes them after the kit in static styles(). */
.tmd-hd { position: relative; }
.tmd-av-wrap { position: relative; display: inline-flex; align-items: center; flex: none; }
.tmd-av-wrap.avatar-clickable { cursor: pointer; }
.tmd-av-edit {
position: absolute; bottom: -2px; right: -2px;
width: 15px; height: 15px; border-radius: 50%;
background: var(--tmd-accent, #7c3aed); color: #fff;
display: grid; place-items: center;
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.tmd-av-edit ha-icon { --mdc-icon-size: 10px; color: #fff; }
.avatar-edit-dot ha-icon { --mdc-icon-size: 12px; color: white; }
.avatar-picker {
position: absolute; z-index: 20; margin-top: 56px;
@@ -1185,6 +1210,121 @@ class TaskMateChildCard extends LitElement {
width: 6px;
background: var(--fun-red);
}
/* Pre-reader mode (#683) — picture-only, huge targets */
.pre-reader-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
gap: 14px;
padding: 4px 0 8px;
}
.pre-tile {
position: relative;
display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 8px;
min-height: 140px; padding: 16px 10px;
border: 3px solid var(--divider-color, #e0e0e0);
border-radius: 26px;
background: var(--card-background-color, #fff);
font-family: inherit; cursor: pointer;
transition: transform 0.12s ease, border-color 0.12s ease;
}
.pre-tile:active { transform: scale(0.96); }
.pre-tile:focus-visible { outline: 4px solid var(--primary-color); outline-offset: 3px; }
.pre-tile-icon ha-icon { --mdc-icon-size: 64px; color: var(--primary-color); }
.pre-tile-stars { display: flex; gap: 2px; }
.pre-tile-stars ha-icon { --mdc-icon-size: 18px; color: #f1c40f; }
.pre-tile-label {
font-size: 0.85rem; font-weight: 700; text-align: center;
color: var(--secondary-text-color); line-height: 1.2;
}
.pre-tile.done {
border-color: var(--success-color, #2ecc71);
background: rgba(46, 204, 113, 0.10);
}
.pre-tile.done .pre-tile-icon ha-icon { opacity: 0.35; }
.pre-tile.done .pre-tile-stars { opacity: 0.35; }
.pre-tile-tick {
position: absolute; inset: 0;
display: grid; place-items: center;
pointer-events: none;
}
.pre-tile-tick ha-icon {
--mdc-icon-size: 72px;
color: var(--success-color, #2ecc71);
}
.pre-tile.locked { opacity: 0.4; cursor: default; }
.pre-tile.loading { opacity: 0.6; }
/* Chore roulette (#677) */
.roulette {
display: flex; flex-direction: column; gap: 8px;
margin: 4px 0 12px;
}
/* The --tmd-* tokens only resolve under a designed style; the fallbacks
are the original classic values, so classic is unchanged. */
.roulette-btn {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
background: var(--tmd-accent, linear-gradient(135deg, #8e44ad, #c0392b));
color: #fff; border: 0;
border-radius: var(--tmd-radius-sm, 14px);
padding: 12px 16px;
font-family: var(--tmd-font-display, inherit); font-size: 0.95rem;
font-weight: 800; cursor: pointer; width: 100%;
}
.roulette-btn:disabled { opacity: 0.55; cursor: default; }
.roulette-btn.is-spinning ha-icon { animation: tm-roulette-spin 0.6s linear infinite; }
@keyframes tm-roulette-spin { to { transform: rotate(360deg); } }
.roulette-result {
display: flex; align-items: center; gap: 8px;
background: color-mix(in srgb, var(--tmd-accent, #8e44ad) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--tmd-accent, #8e44ad) 35%, transparent);
border-radius: var(--tmd-radius-sm, 12px); padding: 8px 12px;
}
.roulette-mult {
background: var(--tmd-accent, #8e44ad); color: #fff;
border-radius: var(--tmd-radius-sm, 8px); padding: 2px 8px;
font-weight: 900; font-size: 0.85rem;
}
.roulette-name { flex: 1; font-weight: 700; font-size: 0.95rem; }
.roulette-worth {
display: inline-flex; align-items: center; gap: 4px;
font-weight: 800; color: var(--tmd-accent, #8e44ad);
}
.roulette-worth ha-icon { --mdc-icon-size: 16px; }
.roulette-spent {
text-align: center; font-size: 0.8rem;
color: var(--tmd-dim, var(--secondary-text-color)); opacity: 0.8;
}
/* Reactive-chore countdown (#674). Amber by default, red under 5 min.
On its own line, not inline in .chore-name — that name ellipsises, and
a long one clipped the badge down to just the bonus. */
.deadline-badge {
display: inline-flex;
align-items: center;
gap: 4px;
margin-top: 4px;
align-self: flex-start;
background: rgba(255, 152, 0, 0.18);
color: #ef6c00;
font-size: 0.62rem;
font-weight: 800;
letter-spacing: 0.02em;
text-transform: uppercase;
border-radius: 8px;
padding: 2px 7px;
white-space: nowrap;
width: fit-content;
}
.deadline-badge.deadline-urgent {
background: var(--fun-red);
color: #fff;
}
.deadline-bonus {
opacity: 0.85;
font-weight: 900;
}
.mandatory-badge {
display: inline-flex;
align-items: center;
@@ -2091,7 +2231,13 @@ class TaskMateChildCard extends LitElement {
` : '';
})() : ''}
</div>
${childChores.map((chore, index) => html`
${this._renderRoulette(child, childChores, pointsIcon)}
${this.config.pre_reader === true ? html`
<div class="pre-reader-grid">
${childChores.map((chore, index) =>
this._renderPreReaderTile(chore, child, todaysCompletions, index))}
</div>
` : childChores.map((chore, index) => html`
${chore.task_type === 'timed'
? this._renderTimedChoreCard(chore, child, pointsIcon, todaysCompletions, index)
: html`
@@ -2292,10 +2438,20 @@ class TaskMateChildCard extends LitElement {
<ha-icon icon="mdi:clock-outline"></ha-icon>${countdown.label}</span>` : ""}
</div>`;
const body =
design === "playroom" ? this._designPlayroom(child, rows, remaining, tone) :
design === "console" ? this._designConsole(child, rows, remaining, tone) :
this._designCleanpro(child, rows, remaining, tone);
// Pre-reader mode replaces the chore list with picture tiles. It has to be
// handled here as well as in the classic path, or `pre_reader: true` is
// silently ignored under every designed style — including accessible,
// which is the one a child who needs picture tiles is most likely on.
// The tiles keep the designed shell (header, tokens) around them.
const body = this.config.pre_reader === true
? html`
<div class="pre-reader-grid">
${childChores.map((chore, index) =>
this._renderPreReaderTile(chore, child, todaysCompletions, index))}
</div>`
: design === "playroom" ? this._designPlayroom(child, rows, remaining, tone) :
design === "console" ? this._designConsole(child, rows, remaining, tone) :
this._designCleanpro(child, rows, remaining, tone);
return html`<ha-card class="tmd" style="--hd:${hd}">
${this._designHeaderFull(child, design, remaining, rows.length, tone, pendingPoints, pointsIcon)}
@@ -2316,6 +2472,7 @@ class TaskMateChildCard extends LitElement {
${earnedBadges.length > 5 ? html`<span class="more">+${earnedBadges.length - 5} →</span>` : ""}
</div>` : ""}
${sectionLine}
${this._renderRoulette(child, childChores, pointsIcon)}
${body}
${this._renderSwappable(allChores, child, pointsIcon)}
</div>
@@ -2336,13 +2493,25 @@ class TaskMateChildCard extends LitElement {
? (child.level ? `${this._t("child.level_label", { level: child.level })} · ${remaining} ACTIVE` : `${remaining} ACTIVE`)
: design === "cleanpro" ? `${remaining} / ${total}`
: `${remaining} · ${this._t("child.todays_chores")}`;
// Avatar picker: classic makes the avatar tappable; the designed header
// must too, or the picker only ever works on classic. Same eligibility
// rule (allow_avatar_change + more than one unlocked option).
const opts = child.avatar_options || [];
const canChange = this.config.allow_avatar_change !== false
&& opts.filter(o => o.unlocked).length > 1;
return html`
<div class="tmd-hd">
${this._av(child, tone, 34)}
<div class="tmd-av-wrap ${canChange ? "avatar-clickable" : ""}"
@click=${canChange ? () => this._toggleAvatarPicker() : null}
title="${canChange ? this._t("child.avatar_change") : ""}">
${this._av(child, tone, 34)}
${canChange ? html`<span class="tmd-av-edit"><ha-icon icon="mdi:pencil"></ha-icon></span>` : ""}
</div>
<span class="tt">${title}<small>${sub}</small></span>
${remaining === 0 && total > 0 ? html`<span class="pill">🎉</span>` : ""}
${pendingPoints > 0 ? html`<span class="tmd-pending">
<ha-icon icon="mdi:timer-sand"></ha-icon>+${pendingPoints}</span>` : ""}
${canChange && this._avatarPickerOpen ? this._renderAvatarPicker(child, opts) : ""}
</div>`;
}
@@ -2982,6 +3151,144 @@ class TaskMateChildCard extends LitElement {
`;
}
/**
* Countdown badge for a reactive chore (#674) — "empty the washing machine
* within 30 minutes". Reads the deadline fresh on every render, so it ticks
* down with the coordinator's updates. Hidden once the chore is done.
*/
_renderDeadlineBadge(chore, isCompletedForToday) {
if (!chore.deadline_at || isCompletedForToday) return '';
const deadline = new Date(chore.deadline_at);
if (Number.isNaN(deadline.getTime())) return '';
const msLeft = deadline.getTime() - Date.now();
if (msLeft <= 0) return '';
const minsLeft = Math.ceil(msLeft / 60000);
const label = minsLeft >= 60
? this._t('child.deadline_hours', { hours: Math.floor(minsLeft / 60), mins: minsLeft % 60 })
: this._t('child.deadline_minutes', { mins: minsLeft });
// Under five minutes is the point at which it's worth shouting about.
const urgent = minsLeft <= 5;
const bonus = chore.speed_bonus_points || 0;
return html`
<span class="deadline-badge ${urgent ? 'deadline-urgent' : ''}"
title="${this._t('child.deadline_title')}">
${label}${bonus ? html` <span class="deadline-bonus">+${bonus}</span>` : ''}
</span>
`;
}
/**
* Chore roulette (#677) — an opt-in nudge for the child who has stalled.
* Only renders when the parent enabled it in settings AND the card opts in,
* so an existing dashboard never sprouts a new button unasked.
*/
_renderRoulette(child, childChores, pointsIcon) {
if (this.config.show_roulette !== true) return '';
const roulette = child.roulette;
if (!roulette) return ''; // switched off in settings
const picked = roulette.chore_id
? childChores.find(c => String(c.id) === String(roulette.chore_id))
: null;
const spinsLeft = roulette.spins_left ?? 0;
const multiplier = roulette.multiplier || 2;
const spinning = this._spinning === child.id;
return html`
<div class="roulette">
${picked ? html`
<div class="roulette-result">
<span class="roulette-mult">×${multiplier}</span>
<span class="roulette-name">${picked.name}</span>
<span class="roulette-worth">
<ha-icon icon="${pointsIcon}"></ha-icon>
${Math.round((picked.effective_points ?? picked.points ?? 0) * multiplier)}
</span>
</div>
` : ''}
${spinsLeft > 0 ? html`
<button class="roulette-btn ${spinning ? 'is-spinning' : ''}"
?disabled=${spinning || childChores.length === 0}
@click=${() => this._spinRoulette(child)}>
<ha-icon icon="mdi:dice-multiple"></ha-icon>
${picked
? this._t('child.roulette_respin', { count: spinsLeft })
: this._t('child.roulette_spin', { multiplier })}
</button>
` : html`
<div class="roulette-spent">${this._t('child.roulette_no_spins')}</div>
`}
</div>
`;
}
async _spinRoulette(child) {
if (this._spinning) return;
this._spinning = child.id;
this.requestUpdate();
try {
await this.hass.callService('taskmate', 'spin_roulette', { child_id: child.id });
} catch (err) {
// "No spins left" / "Nothing left to spin for" arrive here as
// ServiceValidationError — show them rather than failing silently.
this.dispatchEvent(new CustomEvent('hass-notification', {
detail: { message: String(err?.message || err) }, bubbles: true, composed: true,
}));
} finally {
this._spinning = null;
this.requestUpdate();
}
}
/**
* Pre-reader mode (#683): a picture-only tile for children who can't read
* yet. No chore name, no numbers — a big icon, a row of stars for the
* points, and a huge tick when it's done.
*/
_renderPreReaderTile(chore, child, todaysCompletions = [], choreIndex = 0) {
const dailyLimit = chore.daily_limit || 1;
// Counted the same way the standard row does: pending completions hold a
// place too, and a parent completing on behalf lands under "__parent__".
const childCompletionsToday = todaysCompletions.filter(
(comp) => comp.chore_id === chore.id
&& (comp.child_id === child.id || comp.child_id === "__parent__")
&& !comp.bonus_subtask_id
);
const isDone = childCompletionsToday.length >= dailyLimit;
const isLoading = this._loading[chore.id];
const available = chore._isAvailableForChild !== false && !chore._isLockedPreview;
// Stars, not digits: a 4-year-old can count pictures, not read "+3".
const points = chore.effective_points ?? chore.points ?? 0;
const stars = Math.max(1, Math.min(5, Math.round(points / 2) || 1));
const icon = chore.icon
|| this._getTimeCategoryIcon(chore.time_category)
|| 'mdi:checkbox-marked-circle-outline';
return html`
<button
class="pre-tile ${isDone ? 'done' : ''} ${isLoading ? 'loading' : ''} ${available ? '' : 'locked'}"
?disabled=${isLoading || !available}
aria-label="${chore.name}"
title="${chore.name}"
@click=${() => (isDone
? this._handleUndo(chore, child, childCompletionsToday)
: this._handleComplete(chore, child))}
>
<div class="pre-tile-icon"><ha-icon icon="${icon}"></ha-icon></div>
${isDone ? html`<div class="pre-tile-tick"><ha-icon icon="mdi:check-bold"></ha-icon></div>` : ''}
<div class="pre-tile-stars">
${Array.from({ length: stars }, () => html`<ha-icon icon="mdi:star"></ha-icon>`)}
</div>
${this.config.pre_reader_labels === true
? html`<div class="pre-tile-label">${chore.name}</div>` : ''}
</button>
`;
}
_renderChoreCard(chore, child, pointsIcon, todaysCompletions = [], choreIndex = 0) {
const isLoading = this._loading[chore.id];
const isCelebrating = this._celebrating === chore.id;
@@ -3095,6 +3402,7 @@ class TaskMateChildCard extends LitElement {
</div>
<div class="chore-details">
<div class="chore-name">${chore.name}${chore.mandatory ? html`<span class="mandatory-badge">⚠ ${this._t('child.mandatory')}</span>` : ''}</div>
${this._renderDeadlineBadge(chore, isCompletedForToday)}
${chore.difficulty ? html`<span class="difficulty-badge difficulty-${chore.difficulty}">${this._t('child.difficulty_' + chore.difficulty) || chore.difficulty}</span>` : ''}
${this.config.show_description && chore.description ? html`
<div class="chore-description">${chore.description}</div>
@@ -4133,6 +4441,8 @@ class TaskMateChildCardEditor extends LitElement {
},
{ name: 'show_countdown', selector: { boolean: {} } },
{ name: 'show_description', selector: { boolean: {} } },
{ name: 'pre_reader', selector: { boolean: {} } },
{ name: 'pre_reader_labels', selector: { boolean: {} } },
{ name: 'debug', selector: { boolean: {} } },
];
}
@@ -4148,6 +4458,8 @@ class TaskMateChildCardEditor extends LitElement {
elapsed_time_mode: this._t('child.editor.missed_time_chores'),
show_countdown: this._t('child.editor.show_countdown'),
show_description: this._t('child.editor.show_description'),
pre_reader: this._t('child.editor.pre_reader'),
pre_reader_labels: this._t('child.editor.pre_reader_labels'),
debug: this._t('child.editor.show_debug'),
};
return labels[entry.name] ?? entry.name;
@@ -4178,6 +4490,8 @@ class TaskMateChildCardEditor extends LitElement {
elapsed_time_mode: this.config.elapsed_time_mode || 'dim',
show_countdown: this.config.show_countdown !== false,
show_description: this.config.show_description === true,
pre_reader: this.config.pre_reader === true,
pre_reader_labels: this.config.pre_reader_labels === true,
debug: this.config.debug === true,
};
@@ -12,16 +12,17 @@
* playroom — warm, rounded, picture-book
* console — dark gamified HUD
* cleanpro — calm productivity / SaaS
* accessible — high contrast, colour-blind safe, dyslexia-friendly type
*
* Mirrors window.__taskmate_localize: exposed globally, no ES module imports.
*/
(function () {
const IDS = ["classic", "playroom", "console", "cleanpro"];
const IDS = ["classic", "playroom", "console", "cleanpro", "accessible"];
// Fonts must be loaded at the document level (an @import inside a shadow
// stylesheet is honoured, but loading once globally avoids N duplicate fetches).
const FONT_IMPORT =
"@import url('https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700;800&family=Nunito:wght@400;600;700;800;900&family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700;800&family=Manrope:wght@500;600;700;800&display=swap');";
"@import url('https://fonts.googleapis.com/css2?family=Baloo+2:wght@500;600;700;800&family=Nunito:wght@400;600;700;800;900&family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700;800&family=Manrope:wght@500;600;700;800&family=Atkinson+Hyperlegible:wght@400;700&display=swap');";
// Design tokens. IMPORTANT: these are :host-scoped so they can be applied
// INSIDE each card's shadow root. A document-level [data-tm-design] rule
@@ -59,6 +60,22 @@
Cards stamp data-tm-dark on the host when HA is in dark mode. Each design has
a light base (above) and a dark override here, so all three follow HA's
light/dark setting. */
:host([data-tm-design="accessible"]),[data-tm-design="accessible"]{
/* Colours are the Okabe-Ito colour-blind-safe palette: distinguishable
under protanopia, deuteranopia and tritanopia. Text is near-black on
white (~19:1, comfortably past WCAG AAA) and borders are heavy enough to
carry meaning without relying on hue. Atkinson Hyperlegible is drawn for
low vision — its letterforms stay distinct where similar glyphs (I/l/1,
O/0) usually collapse. */
--tmd-bg:#FFFFFF;--tmd-surface:#FFFFFF;--tmd-surface-2:#F2F2F2;--tmd-border:#111111;
--tmd-text:#111111;--tmd-dim:#3D3D3D;
--tmd-accent:#0072B2;--tmd-accent2:#D55E00;--tmd-good:#009E73;--tmd-warn:#E69F00;--tmd-bad:#D55E00;--tmd-gold:#E69F00;
--tmd-radius:10px;--tmd-radius-sm:6px;--tmd-shadow:0 0 0 2px #111111;--tmd-hd-text:#fff;
--tmd-font-display:"Atkinson Hyperlegible",sans-serif;
--tmd-font-body:"Atkinson Hyperlegible",sans-serif;
--tmd-font-mono:"Atkinson Hyperlegible",monospace;
--tmd-c1:#0072B2;--tmd-c2:#D55E00;--tmd-c3:#009E73;--tmd-c4:#CC79A7;--tmd-c5:#56B4E9;--tmd-c6:#E69F00;
}
:host([data-tm-design="console"][data-tm-dark]),[data-tm-design="console"][data-tm-dark]{
--tmd-bg:#0E1320;--tmd-surface:#161D2E;--tmd-surface-2:#1E2740;--tmd-border:#2A3550;
--tmd-text:#EAF0FF;--tmd-dim:#8A97B8;
@@ -75,6 +92,16 @@
--tmd-bg:#0F141A;--tmd-surface:#181D25;--tmd-surface-2:#212733;--tmd-border:#2C3340;
--tmd-text:#E6EAF1;--tmd-dim:#97A1B0;--tmd-accent:#6E86F5;--tmd-accent2:#1FC7B8;
--tmd-shadow:0 1px 2px rgba(0,0,0,.4),0 8px 18px rgba(0,0,0,.35);
}
:host([data-tm-design="accessible"][data-tm-dark]),[data-tm-design="accessible"][data-tm-dark]{
/* Pure black would bloom on OLED and is harsh for astigmatism; #0A0A0A with
near-white text still clears AAA. Hues are the Okabe-Ito light variants,
which stay distinguishable against a dark ground. */
--tmd-bg:#0A0A0A;--tmd-surface:#0A0A0A;--tmd-surface-2:#1C1C1C;--tmd-border:#F5F5F5;
--tmd-text:#F5F5F5;--tmd-dim:#CFCFCF;
--tmd-accent:#56B4E9;--tmd-accent2:#E69F00;--tmd-good:#009E73;--tmd-warn:#F0E442;--tmd-bad:#E69F00;--tmd-gold:#F0E442;
--tmd-shadow:0 0 0 2px #F5F5F5;
--tmd-c1:#56B4E9;--tmd-c2:#E69F00;--tmd-c3:#009E73;--tmd-c4:#CC79A7;--tmd-c5:#F0E442;--tmd-c6:#0072B2;
}`;
// Shared component kit, themed by the tokens above. Every designed card
@@ -234,6 +261,7 @@
{ value: "playroom", label: label("playroom", "Playroom") },
{ value: "console", label: label("console", "Console") },
{ value: "cleanpro", label: label("cleanpro", "Clean Pro") },
{ value: "accessible", label: label("accessible", "Accessible") },
];
}
@@ -3,6 +3,11 @@
* Shows the shared family co-op goal (combined points across all children) with
* a progress bar toward the target and the reward. Reads the overview sensor's
* `family_goal` attribute.
*
* Single layout, so it consumes the design tokens directly (like the routine
* card) rather than carrying a second _renderDesigned() template. Every
* --tmd-* reference has the card's original value as its fallback, so classic
* — which defines no tokens — renders exactly as it did before.
*/
const LitElement = customElements.get("hui-masonry-view")
@@ -13,6 +18,7 @@ const html = LitElement.prototype.html;
const css = LitElement.prototype.css;
const _safeColor = (c, d) => (typeof c === "string" && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : d);
const DEFAULT_ACCENT = "#16a085";
class TaskMateFamilyGoalCard extends LitElement {
static get properties() {
@@ -39,18 +45,38 @@ class TaskMateFamilyGoalCard extends LitElement {
return fn ? fn(this.hass, key, params) : key;
}
/**
* Resolve the active design, stamp data-tm-design on the host (which is what
* makes the :host-scoped token block apply inside this shadow root), and set
* the accent. The accent is set as an inline host property, not from a
* <style> in the template: a shadow tree's <style> is ordered before
* adoptedStyleSheets, so it loses to the :host default in static styles().
*/
_applyDesign() {
if (window.__taskmate_design) {
window.__taskmate_design.apply(this, this.hass, this.config, this.config.entity);
}
const configured = this.config.header_color;
if (typeof configured === "string" && /^#[0-9a-fA-F]{3,8}$/.test(configured)) {
this.style.setProperty("--fg-accent", _safeColor(configured, DEFAULT_ACCENT));
} else {
this.style.removeProperty("--fg-accent");
}
}
render() {
if (!this.hass || !this.config) return html``;
this._applyDesign();
const entity = this.hass.states[this.config.entity];
if (!entity) {
return html`<ha-card><div class="empty"><ha-icon icon="mdi:alert-circle"></ha-icon><div>${this._t("common.entity_not_found", { entity: this.config.entity })}</div></div></ha-card>`;
return html`<ha-card><div class="fg-empty"><ha-icon icon="mdi:alert-circle"></ha-icon><div>${this._t("common.entity_not_found", { entity: this.config.entity })}</div></div></ha-card>`;
}
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity)) || entity.attributes || {};
const goal = attrs.family_goal || {};
const pointsName = attrs.points_name || this._t("common.points");
if (!goal.enabled || !goal.target) {
return html`<ha-card><div class="empty"><ha-icon icon="mdi:flag-checkered"></ha-icon><div>${this._t("family_goal.disabled")}</div></div></ha-card>`;
return html`<ha-card><div class="fg-empty"><ha-icon icon="mdi:flag-checkered"></ha-icon><div>${this._t("family_goal.disabled")}</div></div></ha-card>`;
}
const progress = Number(goal.progress) || 0;
@@ -60,21 +86,20 @@ class TaskMateFamilyGoalCard extends LitElement {
return html`
<ha-card>
<style>:host { --tm-goal-accent: ${_safeColor(this.config.header_color, "#16a085")}; }</style>
<div class="head">
<div class="fg-head">
<ha-icon icon="${achieved ? "mdi:trophy" : "mdi:flag-checkered"}"></ha-icon>
<span class="title">${this.config.title || goal.name || this._t("family_goal.default_title")}</span>
<span class="fg-title">${this.config.title || goal.name || this._t("family_goal.default_title")}</span>
</div>
<div class="body">
<div class="bar"><div class="fill ${achieved ? "done" : ""}" style="width:${pct}%"></div></div>
<div class="stats">
<div class="fg-body">
<div class="fg-bar"><div class="fg-fill ${achieved ? "done" : ""}" style="width:${pct}%"></div></div>
<div class="fg-stats">
<span>${progress} / ${target} ${pointsName}</span>
<span>${pct}%</span>
</div>
${achieved
? html`<div class="reward done">🎉 ${this._t("family_goal.reached", { reward: goal.reward || "" })}</div>`
? html`<div class="fg-reward done">🎉 ${this._t("family_goal.reached", { reward: goal.reward || "" })}</div>`
: goal.reward
? html`<div class="reward">${this._t("family_goal.reward_label", { reward: goal.reward })}</div>`
? html`<div class="fg-reward">${this._t("family_goal.reward_label", { reward: goal.reward })}</div>`
: ""}
</div>
</ha-card>
@@ -82,18 +107,48 @@ class TaskMateFamilyGoalCard extends LitElement {
}
static get styles() {
return css`
.head { display: flex; align-items: center; gap: 8px; padding: 14px 16px 6px; font-weight: 600; }
.head ha-icon { color: var(--tm-goal-accent); }
.body { padding: 4px 16px 16px; }
.bar { height: 14px; border-radius: 8px; background: var(--secondary-background-color, #eee); overflow: hidden; }
.fill { height: 100%; background: var(--tm-goal-accent); transition: width .4s ease; }
.fill.done { background: #d4ac0d; }
.stats { display: flex; justify-content: space-between; font-size: 0.82rem; color: var(--secondary-text-color); margin-top: 6px; }
.reward { margin-top: 8px; font-size: 0.85rem; color: var(--primary-text-color); }
.reward.done { font-weight: 600; color: #d4ac0d; }
.empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 28px 16px; color: var(--secondary-text-color); }
// Layout classes carry an fg- prefix because the shared design kit defines
// its own .bar (and .stat / .grid / …); a bare .bar here would pick up the
// kit's rules under a designed style.
const base = css`
:host { --fg-accent: var(--tmd-accent, #16a085); }
ha-card {
overflow: hidden;
background: var(--tmd-surface, var(--ha-card-background, var(--card-background-color, #fff)));
color: var(--tmd-text, var(--primary-text-color));
border-radius: var(--tmd-radius, var(--ha-card-border-radius, 12px));
font-family: var(--tmd-font-body, inherit);
}
.fg-head {
display: flex; align-items: center; gap: 8px;
padding: 14px 16px 6px; font-weight: 600;
font-family: var(--tmd-font-display, inherit);
}
.fg-head ha-icon { color: var(--fg-accent); }
.fg-body { padding: 4px 16px 16px; }
.fg-bar {
height: 14px; border-radius: var(--tmd-radius-sm, 8px);
background: var(--tmd-surface-2, var(--secondary-background-color, #eee));
border: 1px solid var(--tmd-border, transparent);
box-sizing: border-box;
overflow: hidden;
}
.fg-fill { height: 100%; background: var(--fg-accent); transition: width .4s ease; }
.fg-fill.done { background: var(--tmd-gold, #d4ac0d); }
.fg-stats {
display: flex; justify-content: space-between;
font-size: 0.82rem; color: var(--tmd-dim, var(--secondary-text-color)); margin-top: 6px;
}
.fg-reward { margin-top: 8px; font-size: 0.85rem; color: var(--tmd-text, var(--primary-text-color)); }
.fg-reward.done { font-weight: 600; color: var(--tmd-gold, #d4ac0d); }
.fg-empty {
display: flex; flex-direction: column; align-items: center; gap: 8px;
padding: 28px 16px; color: var(--tmd-dim, var(--secondary-text-color));
}
`;
const tokens = window.__taskmate_design && window.__taskmate_design.styles
? window.__taskmate_design.styles() : null;
return tokens ? [tokens, base] : base;
}
}
@@ -319,7 +319,7 @@ class TaskMateLeaderboardCard extends LitElement {
if (entity.state === "unavailable" || entity.state === "unknown") return html`<ha-card><div class="error-state"><ha-icon icon="mdi:alert-circle"></ha-icon><div>${this._t('common.unavailable')}</div></div></ha-card>`;
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity)) || entity.attributes || {};
const children = [...(attrs.children || [])];
const children = [...(attrs.children || [])].filter(c => !c.is_guest);
const pointsIcon = attrs.points_icon || "mdi:star";
const pointsName = attrs.points_name || this._t('common.points');
@@ -593,7 +593,7 @@ class TaskMateLeaderboardCard extends LitElement {
}
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity)) || entity.attributes || {};
const children = [...(attrs.children || [])];
const children = [...(attrs.children || [])].filter(c => !c.is_guest);
const pointsName = attrs.points_name || this._t('common.points');
const tz = this.hass?.config?.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -411,7 +411,7 @@ class TaskMateOverviewCard extends LitElement {
// full pending list (resolved via companion) so the count keeps including
// completions left pending from a previous day; fall back to today's
// completions only on older backends.
let pendingApprovals = 0;
let pendingApprovals;
if (this.config.approvals_entity) {
const appEntity = this.hass.states[this.config.approvals_entity];
pendingApprovals = appEntity?.attributes?.chore_completions?.length || 0;
@@ -517,7 +517,7 @@ class TaskMateOverviewCard extends LitElement {
</ha-card>`;
}
let pendingApprovals = 0;
let pendingApprovals;
if (this.config.approvals_entity) {
const appEntity = this.hass.states[this.config.approvals_entity];
pendingApprovals = appEntity?.attributes?.chore_completions?.length || 0;
File diff suppressed because it is too large Load Diff
@@ -109,10 +109,19 @@ class TaskMateParentDashboardCard extends LitElement {
display: flex;
border-bottom: 1px solid var(--divider-color, #e0e0e0);
background: var(--secondary-background-color, #f5f5f5);
/* Flex items refuse to shrink below their content, so on a narrow card
the last tab used to be clipped off the edge with no way to reach it.
Wrapping keeps every tab visible without asking the parent to
discover a horizontal swipe on a card that doesn't look scrollable. */
flex-wrap: wrap;
}
.tab-btn {
flex: 1; padding: 10px 8px;
/* Grow to fill a wide card, but keep the label intact when narrow. */
flex: 1 0 auto;
min-width: 0;
white-space: nowrap;
padding: 10px 8px;
background: none; border: none;
font-size: 0.78rem; font-weight: 600;
color: var(--secondary-text-color);
@@ -4,6 +4,10 @@
* `photo_gallery` attribute and renders thumbnails. A card's <img> can't send a
* bearer token, so each photo path is signed via the `auth/sign_path` WS command
* before use.
*
* Single layout, so it consumes the design tokens directly (like the routine
* card). Every --tmd-* reference has the card's original value as its fallback,
* so classic — which defines no tokens — renders exactly as it did before.
*/
const LitElement = customElements.get("hui-masonry-view")
@@ -14,6 +18,7 @@ const html = LitElement.prototype.html;
const css = LitElement.prototype.css;
const _safeColor = (c, d) => (typeof c === "string" && /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : d);
const DEFAULT_ACCENT = "#5d6d7e";
class TaskMatePhotoGalleryCard extends LitElement {
static get properties() {
@@ -46,6 +51,25 @@ class TaskMatePhotoGalleryCard extends LitElement {
return fn ? fn(this.hass, key, params) : key;
}
/**
* Resolve the active design, stamp data-tm-design on the host, and set the
* header accent. The accent is an inline host property, not a template
* <style>: a shadow tree's <style> is ordered before adoptedStyleSheets, so
* it loses to the :host default in static styles(). Header stays full-colour
* in every style per the house rule.
*/
_applyDesign() {
if (window.__taskmate_design) {
window.__taskmate_design.apply(this, this.hass, this.config, this.config.entity);
}
const configured = this.config.header_color;
if (typeof configured === "string" && /^#[0-9a-fA-F]{3,8}$/.test(configured)) {
this.style.setProperty("--pg-accent", _safeColor(configured, DEFAULT_ACCENT));
} else {
this.style.removeProperty("--pg-accent");
}
}
// Open the shared in-page lightbox; fall through to the href if it's absent.
_openPhoto(e, url, caption) {
if (window.__taskmate_lightbox) {
@@ -85,22 +109,22 @@ class TaskMatePhotoGalleryCard extends LitElement {
render() {
if (!this.hass || !this.config) return html``;
this._applyDesign();
const entity = this.hass.states[this.config.entity];
if (!entity) {
return html`<ha-card><div class="empty"><ha-icon icon="mdi:alert-circle"></ha-icon><div>${this._t("common.entity_not_found", { entity: this.config.entity })}</div></div></ha-card>`;
return html`<ha-card><div class="pg-empty"><ha-icon icon="mdi:alert-circle"></ha-icon><div>${this._t("common.entity_not_found", { entity: this.config.entity })}</div></div></ha-card>`;
}
const items = this._gallery();
return html`
<ha-card>
<style>:host { --tm-gallery-header: ${_safeColor(this.config.header_color, "#5d6d7e")}; }</style>
<div class="card-header" style="background: var(--tm-gallery-header);">
<div class="pg-head">
<ha-icon icon="mdi:image-multiple"></ha-icon>
<span>${this.config.title || this._t("gallery.default_title")}</span>
</div>
${items.length === 0
? html`<div class="empty"><ha-icon icon="mdi:camera-off"></ha-icon><div>${this._t("gallery.empty")}</div></div>`
? html`<div class="pg-empty"><ha-icon icon="mdi:camera-off"></ha-icon><div>${this._t("gallery.empty")}</div></div>`
: html`
<div class="grid">
<div class="pg-grid">
${items.map((it) => this._tile(it))}
</div>`}
</ha-card>
@@ -111,12 +135,12 @@ class TaskMatePhotoGalleryCard extends LitElement {
const src = this._signed[it.photo_url];
const when = this._formatDate(it.completed_at);
return html`
<div class="tile" title="${it.chore_name}${it.child_name}">
<div class="pg-tile" title="${it.chore_name}${it.child_name}">
${src
? html`<a href="${src}" target="_blank" rel="noopener"
@click="${(e) => this._openPhoto(e, src, [it.chore_name, it.child_name, when].filter(Boolean).join(" · "))}"><img src="${src}" alt="${it.chore_name}" loading="lazy"></a>`
: html`<div class="ph"><ha-icon icon="mdi:image"></ha-icon></div>`}
<div class="cap">
: html`<div class="pg-ph"><ha-icon icon="mdi:image"></ha-icon></div>`}
<div class="pg-cap">
<span class="who">${it.child_name}</span>
<span class="what">${it.chore_name}</span>
<span class="when">${when}${it.approved ? "" : " · " + this._t("gallery.pending")}</span>
@@ -134,27 +158,51 @@ class TaskMatePhotoGalleryCard extends LitElement {
}
static get styles() {
return css`
ha-card { overflow: hidden; }
.card-header {
display: flex; align-items: center; gap: 8px;
padding: 12px 16px; color: #fff; font-weight: 600;
// Layout classes carry a pg- prefix because the shared design kit defines
// its own .grid; a bare .grid here would pick up the kit's rules under a
// designed style. Base styles come after the kit in static styles(), so
// they win at equal specificity.
const base = css`
:host { --pg-accent: var(--tmd-accent, #5d6d7e); }
ha-card {
overflow: hidden;
background: var(--tmd-surface, var(--ha-card-background, var(--card-background-color, #fff)));
color: var(--tmd-text, var(--primary-text-color));
border-radius: var(--tmd-radius, var(--ha-card-border-radius, 12px));
font-family: var(--tmd-font-body, inherit);
}
.grid {
.pg-head {
display: flex; align-items: center; gap: 8px;
padding: 12px 16px; color: var(--tmd-hd-text, #fff); font-weight: 600;
background: var(--pg-accent);
font-family: var(--tmd-font-display, inherit);
}
.pg-grid {
display: grid; gap: 10px; padding: 14px;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
}
.tile { display: flex; flex-direction: column; border-radius: 8px; overflow: hidden; background: var(--secondary-background-color, #f5f5f5); }
.tile img, .tile .ph {
.pg-tile {
display: flex; flex-direction: column;
border-radius: var(--tmd-radius-sm, 8px); overflow: hidden;
background: var(--tmd-surface-2, var(--secondary-background-color, #f5f5f5));
border: 1px solid var(--tmd-border, transparent);
}
.pg-tile img, .pg-tile .pg-ph {
width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block;
}
.tile .ph { display: flex; align-items: center; justify-content: center; color: var(--secondary-text-color); }
.cap { padding: 6px 8px; display: flex; flex-direction: column; gap: 1px; font-size: 0.72rem; }
.cap .who { font-weight: 600; }
.cap .what { color: var(--primary-text-color); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cap .when { color: var(--secondary-text-color); }
.empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 32px 16px; color: var(--secondary-text-color); }
.pg-tile .pg-ph { display: flex; align-items: center; justify-content: center; color: var(--tmd-dim, var(--secondary-text-color)); }
.pg-cap { padding: 6px 8px; display: flex; flex-direction: column; gap: 1px; font-size: 0.72rem; }
.pg-cap .who { font-weight: 600; }
.pg-cap .what { color: var(--tmd-text, var(--primary-text-color)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pg-cap .when { color: var(--tmd-dim, var(--secondary-text-color)); }
.pg-empty {
display: flex; flex-direction: column; align-items: center; gap: 8px;
padding: 32px 16px; color: var(--tmd-dim, var(--secondary-text-color));
}
`;
const tokens = window.__taskmate_design && window.__taskmate_design.styles
? window.__taskmate_design.styles() : null;
return tokens ? [tokens, base] : base;
}
}
@@ -1725,7 +1725,7 @@ class TaskMateRewardsCard extends LitElement {
// Calculate weighted segments - each segment width = (child's share of goal) * (their progress %)
// This way, if a child has 50% share and is at 100% progress, they fill 50% of the bar
const segments = childContributions.map((contrib) => {
let width = 0;
let width;
if (hasWeightedData && contrib.shareOfGoal > 0) {
// Weighted: segment width = share of goal * progress percentage
// e.g., 40% share at 50% progress = 20% of bar filled