348 files
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user