diff --git a/README.md b/README.md
index 39e9d2c7..342cdec8 100755
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@
3. **Parents approve** — chores marked "requires approval" land in a pending queue
4. **Kids claim rewards** — points are only deducted once a parent approves the claim
-Everything is managed from the **TaskMate panel** in the Home Assistant sidebar, and surfaced on your dashboards through 27 Lovelace cards.
+Everything is managed from the **TaskMate panel** in the Home Assistant sidebar, and surfaced on your dashboards through 28 Lovelace cards.
All data stays inside your Home Assistant instance. Nothing is sent to any external service.
@@ -89,7 +89,7 @@ Lovelace resources register automatically on startup. Edit your dashboard → **
| For kids | For parents |
|----------|-------------|
-| Child · Rewards · Reward Progress · Points Display · Routine · Kiosk · Streak · Badges · Bounty Board · Wishlist · Recap · Chore Auction | Approvals · Parent Dashboard · Overview · Chore Board · Activity · Points · Reorder · Penalties · Bonuses · Weekly · Graph · Leaderboard · Calendar · Family Goal · Photo Gallery |
+| Child · Sticker Chart · Rewards · Reward Progress · Points Display · Routine · Kiosk · Streak · Badges · Bounty Board · Wishlist · Recap · Chore Auction | Approvals · Parent Dashboard · Overview · Chore Board · Activity · Points · Reorder · Penalties · Bonuses · Weekly · Graph · Leaderboard · Calendar · Family Goal · Photo Gallery |
Every card takes a `header_color` and a `card_design` option. See [Dashboard Cards](https://github.com/tempus2016/taskmate/wiki/Dashboard-Cards) for each card's full configuration.
@@ -108,7 +108,7 @@ Every card takes a `header_color` and a `card_design` option. See [Dashboard Car
| [Getting Started](https://github.com/tempus2016/taskmate/wiki/Getting-Started) | First-time setup walkthrough |
| [Admin Panel](https://github.com/tempus2016/taskmate/wiki/Admin-Panel) | The management hub in the HA sidebar |
| [Settings](https://github.com/tempus2016/taskmate/wiki/Settings) | Every configurable setting in one place |
-| [Dashboard Cards](https://github.com/tempus2016/taskmate/wiki/Dashboard-Cards) | All 27 Lovelace cards and their options |
+| [Dashboard Cards](https://github.com/tempus2016/taskmate/wiki/Dashboard-Cards) | All 28 Lovelace cards and their options |
| [Services](https://github.com/tempus2016/taskmate/wiki/Services) | Every callable service, with examples |
| [Sensor Reference](https://github.com/tempus2016/taskmate/wiki/Sensor-Reference) | All entities and their attributes |
| [Automations](https://github.com/tempus2016/taskmate/wiki/Automations) | Bus events, blueprints, and examples |
@@ -134,4 +134,4 @@ Bug reports and feature requests are welcome in [Issues](https://github.com/temp
MIT — see [LICENSE](LICENSE).
-> Originally created by [vinnybad/choremander](https://github.com/vinnybad/choremander). This fork adds 27 Lovelace cards, an admin panel, a bonus points system, streak tracking, reward approval flow, a penalty system, and much more.
+> Originally created by [vinnybad/choremander](https://github.com/vinnybad/choremander). This fork adds 28 Lovelace cards, an admin panel, a bonus points system, streak tracking, reward approval flow, a penalty system, and much more.
diff --git a/custom_components/taskmate/__init__.py b/custom_components/taskmate/__init__.py
index a501f8fd..9f6352b9 100644
--- a/custom_components/taskmate/__init__.py
+++ b/custom_components/taskmate/__init__.py
@@ -545,6 +545,16 @@ async def handle_complete_chore(call: ServiceCall) -> None:
chore_id = call.data[ATTR_CHORE_ID]
child_id = call.data[ATTR_CHILD_ID]
as_parent = call.data.get(ATTR_AS_PARENT, False)
+ completed_date = call.data.get("completed_date")
+ if completed_date is not None:
+ # Logging a job for an earlier day is a grown-up's correction, never
+ # a child's: it is always parent-gated and always approved.
+ await _async_require_parent(hass, call)
+ try:
+ await coordinator.async_complete_chore_on_date(chore_id, child_id, completed_date)
+ except ValueError as err:
+ raise ServiceValidationError(str(err)) from err
+ return
if as_parent:
# Completing on behalf of a child (auto-approve + instant award) is a
# parent privilege. Enforce it on the backend, not just by hiding the
@@ -1302,6 +1312,7 @@ async def handle_rebuild_badges(call: ServiceCall) -> None:
vol.Required(ATTR_CHILD_ID): cv.string,
vol.Optional(ATTR_AS_PARENT, default=False): cv.boolean,
vol.Optional("photo_url"): cv.string,
+ vol.Optional("completed_date"): cv.date,
vol.Optional("note"): cv.string,
vol.Optional("suggested_points"): vol.All(
vol.Coerce(int), vol.Range(min=0, max=CHORE_SUGGESTED_POINTS_MAX)
diff --git a/custom_components/taskmate/coord_assignments.py b/custom_components/taskmate/coord_assignments.py
index fe304e78..893289db 100644
--- a/custom_components/taskmate/coord_assignments.py
+++ b/custom_components/taskmate/coord_assignments.py
@@ -53,6 +53,14 @@ def _refresh_tracked_availability_entities(self) -> None:
visibility.add(chore.weather_entity)
self._tracked_visibility_entities = visibility
+ # A child's photo is read from their picture entity, so a photo changed
+ # there has to reach the sensors without waiting for a TaskMate edit.
+ # Trimmed the same way child_avatar_image reads it, so a stray space in
+ # a stored id can't leave the photo untracked.
+ self._tracked_picture_entities = {
+ entity for c in self.storage.get_children() if (entity := (getattr(c, "picture_entity", "") or "").strip())
+ }
+
@callback
def _availability_state_changed(self, event: Any) -> None:
"""Cheap bus-filter: react only when a tracked entity changes.
@@ -66,6 +74,9 @@ def _availability_state_changed(self, event: Any) -> None:
if not entity_id:
return
+ if entity_id in getattr(self, "_tracked_picture_entities", set()):
+ self._picture_entity_changed(data)
+
is_availability = entity_id in getattr(self, "_tracked_availability_entities", set())
is_visibility = entity_id in getattr(self, "_tracked_visibility_entities", set())
if not (is_availability or is_visibility):
@@ -79,6 +90,22 @@ def _availability_state_changed(self, event: Any) -> None:
if is_availability:
self.hass.async_create_task(self._async_reevaluate_availability())
+ def _picture_entity_changed(self, data: dict) -> None:
+ """Push a child's new photo out as soon as the entity's picture changes.
+
+ A person's state flips between home and away all day without touching
+ its picture, so only a real change of ``entity_picture`` (or the entity
+ appearing or vanishing) is worth invalidating the sensors for.
+ """
+
+ def picture(state: Any) -> str:
+ return str((getattr(state, "attributes", None) or {}).get("entity_picture") or "") if state else ""
+
+ if picture(data.get("old_state")) == picture(data.get("new_state")):
+ return
+ self.external_state_version += 1
+ self.async_update_listeners()
+
async def _async_reevaluate_availability(self) -> None:
"""Re-run assignment for require_availability chores when availability flips.
diff --git a/custom_components/taskmate/coord_avatars.py b/custom_components/taskmate/coord_avatars.py
index ff0be77b..589d09e7 100644
--- a/custom_components/taskmate/coord_avatars.py
+++ b/custom_components/taskmate/coord_avatars.py
@@ -10,6 +10,10 @@
import logging
+from homeassistant.helpers import entity_registry as er
+
+from .const import DOMAIN
+
_LOGGER = logging.getLogger(__name__)
# Shipped defaults so the feature is useful out of the box. Parents can replace
@@ -36,6 +40,31 @@ def avatar_catalog(self) -> list[dict]:
return [a for a in raw if isinstance(a, dict) and a.get("icon")]
return list(DEFAULT_AVATAR_CATALOG)
+ def child_avatar_image(self, child) -> str:
+ """The picture of the entity chosen for a child's face, or ``""``.
+
+ Read from that entity on every update rather than stored, so a photo
+ changed in HA appears here with nothing to re-save, and a deleted
+ entity simply falls back to the MDI avatar instead of a broken image.
+
+ Empty when no entity is chosen, it no longer exists, or it has no
+ picture — every call site then shows the MDI avatar, so a child is
+ never left with a blank face.
+
+ TaskMate's own child sensors are refused: they show this very photo,
+ so choosing one would make the face its own source and leave it stale.
+ """
+ entity_id = (getattr(child, "picture_entity", "") or "").strip()
+ if not entity_id:
+ return ""
+ entry = er.async_get(self.hass).async_get(entity_id)
+ if entry is not None and entry.platform == DOMAIN:
+ return ""
+ state = self.hass.states.get(entity_id)
+ if state is None:
+ return ""
+ return str(state.attributes.get("entity_picture") or "")
+
def _avatar_unlocked(self, entry: dict, child) -> bool:
kind = entry.get("unlock_type", "free")
try:
diff --git a/custom_components/taskmate/coord_chores.py b/custom_components/taskmate/coord_chores.py
index 00390ac8..082f60d5 100644
--- a/custom_components/taskmate/coord_chores.py
+++ b/custom_components/taskmate/coord_chores.py
@@ -4,7 +4,7 @@
import logging
from calendar import monthrange
-from datetime import date, datetime, timedelta
+from datetime import date, datetime, time, timedelta
from typing import TYPE_CHECKING
from homeassistant.util import dt as dt_util
@@ -20,7 +20,7 @@
)
from .coord_rejections import clean_reject_reason
from .coord_teamwork import teamwork_config_error
-from .models import Chore, ChoreCompletion, PointsTransaction
+from .models import Chore, ChoreCompletion, PointsTransaction, parse_datetime
if TYPE_CHECKING:
pass
@@ -996,12 +996,18 @@ async def _async_record_completion(
photo_url: str = "",
note: str = "",
suggested_points: int = 0,
+ backdated: bool = False,
) -> ChoreCompletion:
"""Store one completion and, when auto-approved, pay it.
The part of completing a chore that is per child. The caller has already
run every eligibility gate and then calls _async_after_completions once
for the whole batch (one child normally, a whole team for #928).
+
+ ``backdated`` is a parent logging a job for an earlier day: ``now`` is
+ then that day's timestamp. It is paid for that day (weekend and birthday
+ bonuses follow the day) but leaves the streak alone, and only moves the
+ recurrence window forward, never back.
"""
completion = ChoreCompletion(
chore_id=chore.id,
@@ -1017,6 +1023,7 @@ async def _async_record_completion(
# window (#918); one a parent made on their behalf may not, and
# nothing made while the window is 0 (off) ever can.
child_undo_allowed=not as_parent and undo_window_seconds(self.storage) > 0,
+ backdated=backdated,
)
# Record the completion before awarding anything. Awarding can suspend
@@ -1027,7 +1034,16 @@ async def _async_record_completion(
self.storage.add_completion(completion)
if auto_approve:
- total_awarded = await self._award_points(child, points, chore_id=chore.id)
+ if backdated:
+ total_awarded = await self._award_points(
+ child,
+ points,
+ completion_date=dt_util.as_local(now).date(),
+ skip_streak=True,
+ chore_id=chore.id,
+ )
+ else:
+ total_awarded = await self._award_points(child, points, chore_id=chore.id)
completion.approved = True
completion.approved_at = dt_util.now()
completion.points_awarded = total_awarded
@@ -1043,12 +1059,17 @@ async def _async_record_completion(
"chore_name": chore.name,
"points": points,
"difficulty": getattr(chore, "difficulty", "medium"),
- "timestamp": dt_util.now().isoformat(),
+ "timestamp": now.isoformat() if backdated else dt_util.now().isoformat(),
+ **({"backdated": True} if backdated else {}),
},
)
- # Update last_completed store (window starts at completion time, midnight-rounded)
- self.storage.set_last_completed(chore.id, child.id, now.isoformat())
+ # Update last_completed store (window starts at completion time, midnight-rounded).
+ # A job logged for an earlier day must not pull the window back behind a
+ # completion the child made since, so it only records when it is the latest.
+ current = parse_datetime((self.storage.get_last_completed(chore.id, child.id) or {}).get("current"))
+ if not backdated or current is None or now > current:
+ self.storage.set_last_completed(chore.id, child.id, now.isoformat())
# One-shot: if auto-approved, disable for this child immediately
if getattr(chore, "schedule_mode", "specific_days") == "one_shot" and auto_approve:
@@ -1097,6 +1118,175 @@ async def _async_after_completions(self, chore, recorded: list, *, auto_approve:
if hasattr(self, "_async_evaluate_challenges"):
await self._async_evaluate_challenges(child.id)
+ # How far back a grown-up may log a job that was done but not ticked.
+ BACKDATE_MAX_DAYS = 7
+
+ async def async_complete_chore_on_date(self, chore_id: str, child_id: str, day: date) -> ChoreCompletion:
+ """Log a job a child did on an earlier day but nobody ticked at the time.
+
+ Parent-only (the service gates it). The completion is stamped midday
+ on ``day`` so it lands in that day's (and that week's) totals, and it
+ is approved straight away — the grown-up logging it is the approver. It
+ is recorded and followed up by the same code a parent ticking a job
+ for a child uses, so badges, quest steps, challenges, the recurrence
+ window and one-shot disabling all behave as they do for any approval.
+
+ It must be a day in the last ``BACKDATE_MAX_DAYS`` days, not today
+ (today is ticked the normal way), for a chore that was assigned to the
+ child and due that day, with that day's daily limit — and, for a
+ weekly-target chore, that week's quota — not yet used up.
+
+ Refused, because they can't be reconstructed or priced after the fact:
+ rotation and teamwork chores (whose turn it was), a day won at auction,
+ and open-ended chores (they need the child's note and the parent's price).
+
+ Points are the chore's base points (with its difficulty) plus the
+ weekend multiplier when ``day`` was a Saturday or Sunday; the time-of-day,
+ speed and roulette adjustments are left out, as there was no live tap.
+ The streak is left alone: rewriting ``last_completion_date`` to a past
+ day would break a streak the child is on today.
+
+ Every refusal raises ValueError with a message meant for the parent.
+ """
+ chore = self.get_chore(chore_id)
+ if not chore:
+ raise ValueError(f"Chore {chore_id} not found")
+ child = self.get_child(child_id)
+ if not child:
+ raise ValueError(f"Child {child_id} not found")
+
+ today = dt_util.as_local(dt_util.now()).date()
+ if not (today - timedelta(days=self.BACKDATE_MAX_DAYS) <= day < today):
+ raise ValueError(f"Pick a day in the last {self.BACKDATE_MAX_DAYS} days, before today.")
+
+ refusal = self._backdate_refusal(chore, child, day)
+ if refusal:
+ raise ValueError(refusal)
+
+ points = self.effective_chore_points(chore)
+ completed_at = datetime.combine(day, time(12, 0), tzinfo=dt_util.DEFAULT_TIME_ZONE)
+ completion = await self._async_record_completion(
+ chore,
+ child,
+ completed_at,
+ auto_approve=True,
+ points=points,
+ as_parent=True,
+ backdated=True,
+ )
+ await self._async_after_completions(chore, [(child, completion, points)], auto_approve=True)
+ return completion
+
+ def completions_on_date(self, day: date) -> list[dict]:
+ """Every chore completion made on ``day`` (local time), sub-tasks excluded.
+
+ Shaped like the sensors' ``todays_completions`` rows, so the sticker
+ chart can show a past day with the same code it uses for today.
+ """
+ return [
+ {
+ "completion_id": comp.id,
+ "chore_id": comp.chore_id,
+ "child_id": comp.child_id,
+ "approved": comp.approved,
+ "points": comp.points_awarded,
+ }
+ for comp in self.storage.get_completions()
+ if not comp.bonus_subtask_id and dt_util.as_local(comp.completed_at).date() == day
+ ]
+
+ def _backdate_refusal(self, chore, child, day: date, *, quota: bool = True) -> str | None:
+ """Why ``chore`` can't be logged for ``child`` on ``day``, or None if it can.
+
+ The one place the past-day rules live: logging a job refuses with this
+ message, and the panel's day lookup offers exactly the chores it lets
+ through, so the dialog never offers a tap TaskMate would then refuse.
+
+ ``quota=False`` leaves out what that day and week have already used
+ (the daily limit and a weekly target's quota). The sticker chart shows
+ those itself, as done tiles that can still be undone.
+ """
+ child_id = child.id
+ if not getattr(chore, "enabled", True):
+ return f"'{chore.name}' is switched off."
+ assigned = chore.assigned_to or []
+ if (assigned and child_id not in assigned) or child_id in (getattr(chore, "disabled_for", None) or []):
+ return f"'{chore.name}' isn't one of {child.name}'s jobs."
+ if (getattr(chore, "assignment_mode", "everyone") or "everyone") != "everyone" or self.teamwork_size(chore):
+ return f"'{chore.name}' rotates or needs a team, so it can't be logged for a past day."
+ if getattr(chore, "open_ended", False):
+ return f"'{chore.name}' is open-ended, so it needs the child's note and a price. Use the child's card."
+ if self.is_vacation_day(day):
+ return f"{day.strftime('%A')} was a holiday, so no jobs were due."
+ # A birthday with "chores off" is a day off for everything but mandatory
+ # chores, the same rule today's chore list follows.
+ if not getattr(chore, "mandatory", False) and self.is_birthday_day_off(child, day):
+ return f"{day.strftime('%A')} was {child.name}'s birthday, a day off from jobs."
+ if self.auction_winner(chore, day):
+ return f"'{chore.name}' was won at auction that day, so it can't be logged afterwards."
+
+ # A weekly-target chore (#883) has no fixed days — the child picks them —
+ # so it isn't "due" on any one day; its quota is the gate instead.
+ weekly_target = int(getattr(chore, "weekly_target", 0) or 0)
+ if weekly_target:
+ if quota and self.weekly_target_met(chore, child_id, on=day):
+ return f"'{chore.name}' already reached its {weekly_target} for that week."
+ elif not self._is_chore_scheduled_for_date(chore, day):
+ return f"'{chore.name}' wasn't due on {day.strftime('%A')}."
+
+ if not quota:
+ return None
+ redo_ids = self.inspection_redo_completion_ids()
+ done_that_day = sum(
+ 1
+ for comp in self.storage.get_completions()
+ if comp.id not in redo_ids
+ and comp.chore_id == chore.id
+ and comp.child_id == child_id
+ and not comp.bonus_subtask_id
+ and dt_util.as_local(comp.completed_at).date() == day
+ )
+ daily_limit = max(1, int(getattr(chore, "daily_limit", 1) or 1))
+ if done_that_day >= daily_limit:
+ return f"'{chore.name}' was already done {done_that_day}/{daily_limit} times on {day.strftime('%A')}."
+ return None
+
+ def loggable_on(self, day: date) -> dict[str, list[str]]:
+ """The chores each child could have had logged for ``day``.
+
+ Everything ``_backdate_refusal`` allows apart from the day's and week's
+ quotas, which the sticker chart shows itself. The panel uses it so its
+ list and the past-day chart follow every schedule TaskMate knows —
+ recurrences, calendar moves, holidays — not just fixed weekdays.
+ """
+ loggable: dict[str, list[str]] = {}
+ for child in self.storage.get_children():
+ ids = [
+ chore.id
+ for chore in self.storage.get_chores()
+ if self._backdate_refusal(chore, child, day, quota=False) is None
+ ]
+ if ids:
+ loggable[child.id] = ids
+ return loggable
+
+ def week_progress_on(self, day: date) -> dict[str, dict[str, int]]:
+ """Each child's count for every weekly-target chore, in the week of ``day``.
+
+ The past-day sticker chart caps a weekly-target chore at what is left
+ of that week's quota, the way today's chart reads ``weekly_chore_progress``,
+ so it never offers a tick the backend would refuse.
+ """
+ progress: dict[str, dict[str, int]] = {}
+ for chore in self.storage.get_chores():
+ if int(getattr(chore, "weekly_target", 0) or 0) <= 0:
+ continue
+ for child in self.storage.get_children():
+ count = self.weekly_completion_count(chore.id, child.id, on=day)
+ if count:
+ progress.setdefault(child.id, {})[chore.id] = count
+ return progress
+
async def async_parent_complete_chore(self, chore_id: str) -> ChoreCompletion:
"""Mark a chore as completed by the parent — zero points, advances recurrence."""
chore = self.get_chore(chore_id)
@@ -1455,8 +1645,9 @@ def _reverse_completion_awards(self, target_completion, completions) -> list:
# Only reverse streak for parent completions, and only
# when this was the child's sole completion that day —
- # streaks are day-based, not per-completion
- if not completion.bonus_subtask_id:
+ # streaks are day-based, not per-completion. A backdated one
+ # never advanced the streak, so it has nothing to give back.
+ if not completion.bonus_subtask_id and not completion.backdated:
reject_date = dt_util.as_local(completion.completed_at).date()
other_same_day = any(
c.id != completion.id
@@ -1508,8 +1699,10 @@ def _reverse_completion_awards(self, target_completion, completions) -> list:
)
)
child.streak_milestones_achieved = sorted(d for d in achieved if d <= child.current_streak)
- # The weekly target bonus this completion helped earn (#1044).
- self._reverse_weekly_target_bonus(completion, child)
+ # The weekly target bonus this completion helped earn (#1044).
+ # Outside the streak block: a backdated job never moved the
+ # streak, but it can complete a week's target like any other.
+ self._reverse_weekly_target_bonus(completion, child)
bonus_completions: list = []
is_parent = not target_completion.bonus_subtask_id
@@ -1532,8 +1725,17 @@ def _reverse_completion_awards(self, target_completion, completions) -> list:
child.total_points_earned = max(0, child.total_points_earned - bc.points_awarded)
child.total_chores_completed = max(0, child.total_chores_completed - 1)
- # Undo last_completed store so recurrence window resets correctly
- self.storage.undo_last_completed(target_completion.chore_id, target_completion.child_id)
+ # Undo last_completed store so recurrence window resets correctly.
+ # A backdated completion only owns the current entry if it was the
+ # latest when logged; otherwise a later completion does, and popping
+ # it would reopen that chore's window.
+ lc_current = parse_datetime(
+ (self.storage.get_last_completed(target_completion.chore_id, target_completion.child_id) or {}).get(
+ "current"
+ )
+ )
+ if not target_completion.backdated or lc_current == target_completion.completed_at:
+ self.storage.undo_last_completed(target_completion.chore_id, target_completion.child_id)
# One-shot: re-enable for this child
chore = self.get_chore(target_completion.chore_id)
@@ -1900,33 +2102,40 @@ def is_chore_available_for_child(self, chore, child_id: str) -> bool:
days_since = (today - last_dt).days
return days_since >= window_days
- def weekly_completion_count(self, chore_id: str, child_id: str) -> int:
+ def weekly_completion_count(self, chore_id: str, child_id: str, on: date | None = None) -> int:
"""How many times ``child_id`` has completed ``chore_id`` this week.
Monday-anchored, matching the Challenges period convention. Bonus
sub-tasks don't count; pending completions do, so a chore can't be
submitted a fourth time just because the parent hasn't got round to
approving the third yet.
+
+ ``on`` asks about the week containing that day instead of this one, for
+ a job being logged against a past week.
"""
- today = dt_util.as_local(dt_util.now()).date()
- week_start = today - timedelta(days=today.weekday())
+ anchor = on or dt_util.as_local(dt_util.now()).date()
+ week_start = anchor - timedelta(days=anchor.weekday())
+ week_end = week_start + timedelta(days=6)
count = 0
for comp in self._cached_completions_for_chore(chore_id):
if comp.child_id != child_id or getattr(comp, "bonus_subtask_id", ""):
continue
try:
- if dt_util.as_local(comp.completed_at).date() >= week_start:
+ if week_start <= dt_util.as_local(comp.completed_at).date() <= week_end:
count += 1
except (AttributeError, TypeError, ValueError):
continue
return count
- def weekly_target_met(self, chore, child_id: str) -> bool:
- """True when a weekly-target chore has had its quota filled this week."""
+ def weekly_target_met(self, chore, child_id: str, on: date | None = None) -> bool:
+ """True when a weekly-target chore has had its quota filled this week.
+
+ ``on`` checks the week containing that day (see ``weekly_completion_count``).
+ """
target = int(getattr(chore, "weekly_target", 0) or 0)
if target <= 0:
return False
- return self.weekly_completion_count(chore.id, child_id) >= target
+ return self.weekly_completion_count(chore.id, child_id, on) >= target
@staticmethod
def _week_key(when) -> str:
diff --git a/custom_components/taskmate/coord_points.py b/custom_components/taskmate/coord_points.py
index 626db246..474325c1 100644
--- a/custom_components/taskmate/coord_points.py
+++ b/custom_components/taskmate/coord_points.py
@@ -20,6 +20,84 @@
class PointsMixin:
"""Mixin providing points, streaks, penalties, bonuses, and notifications."""
+ def week_progress(self, children, chores, completions) -> dict[str, dict[str, int]]:
+ """Points each child has earned this week, and the most they could have.
+
+ The week runs Monday to Sunday, like the weekly digest. ``earned`` is
+ the approved chore points since Monday (bonus sub-tasks excluded, as
+ they are extras on top of the jobs). ``available`` is every scheduled
+ job's points at its daily limit across all seven days, with the weekend
+ multiplier on Saturday and Sunday and the family's planned vacation days
+ left out (a child's live away-sensor is not used: it says nothing about
+ the rest of the week). Rotation
+ chores are skipped — who is active on a future day can't be known — so
+ ``earned`` may run past ``available``; cards cap the display.
+
+ A weekly-target chore (#883) is picked up on whichever days the child
+ chooses, so it adds its quota (target × points) once rather than a slot
+ per scheduled day. An open-ended chore has no price of its own, so it
+ adds nothing to ``available`` (what was paid for it still counts in
+ ``earned``). A teamwork chore counts each member's share, as paid.
+ """
+ today = dt_util.now().date()
+ week_start = today - timedelta(days=today.weekday())
+ days = [week_start + timedelta(days=i) for i in range(7)]
+ try:
+ multiplier = float(self.storage.get_setting("weekend_multiplier", "2.0"))
+ except (ValueError, TypeError):
+ multiplier = 2.0
+
+ earned: dict[str, int] = {}
+ for comp in completions:
+ if not comp.approved or comp.bonus_subtask_id:
+ continue
+ if dt_util.as_local(comp.completed_at).date() < week_start:
+ continue
+ earned[comp.child_id] = earned.get(comp.child_id, 0) + (comp.points_awarded or 0)
+
+ celebration_multiplier = self.birthday_multiplier()
+
+ progress: dict[str, dict[str, int]] = {}
+ for child in children:
+ available = 0
+ for chore in chores:
+ if not getattr(chore, "enabled", True):
+ continue
+ assigned = chore.assigned_to or []
+ if assigned and child.id not in assigned:
+ continue
+ if child.id in (getattr(chore, "disabled_for", None) or []):
+ continue
+ mode = getattr(chore, "assignment_mode", "everyone") or "everyone"
+ if mode not in ("everyone", "first_come"):
+ continue
+ if getattr(chore, "open_ended", False):
+ continue
+ limit = 1 if mode == "first_come" else max(1, int(getattr(chore, "daily_limit", 1) or 1))
+ # A teamwork chore (#928) pays each member their share (plus any
+ # team bonus), so that is what it can add to a child's week.
+ points = self.team_share_points(chore)
+ weekly_target = int(getattr(chore, "weekly_target", 0) or 0)
+ if weekly_target:
+ available += weekly_target * points
+ continue
+ for day in days:
+ if self.is_vacation_day(day) or not self._is_chore_scheduled_for_date(chore, day):
+ continue
+ # A birthday off (#924) only excuses non-mandatory chores.
+ birthday = self.is_birthday(child, day)
+ if birthday and not getattr(chore, "mandatory", False) and self.birthday_chores_off():
+ continue
+ per = points
+ if day.weekday() in (5, 6) and multiplier > 1.0:
+ per += round(points * (multiplier - 1.0))
+ # The birthday multiplier stacks on the weekend one, as paid.
+ if birthday and celebration_multiplier > 1.0:
+ per += round(per * (celebration_multiplier - 1.0))
+ available += per * limit
+ progress[child.id] = {"earned": earned.get(child.id, 0), "available": available}
+ return progress
+
async def _async_check_perfect_week(self) -> None:
"""Award perfect week bonus to children who completed at least one chore every day last week."""
perfect_week_enabled = self.storage.get_setting("perfect_week_enabled", "true") == "true"
diff --git a/custom_components/taskmate/coordinator.py b/custom_components/taskmate/coordinator.py
index b9deff90..616b4049 100644
--- a/custom_components/taskmate/coordinator.py
+++ b/custom_components/taskmate/coordinator.py
@@ -986,6 +986,7 @@ async def async_add_child(
unavailability_entity: str = "",
pause_streak_when_unavailable: bool = False,
linked_user_id: str = "",
+ picture_entity: str = "",
birthday: str = "",
presence_entity: str = "",
age_group: str = "",
@@ -1004,6 +1005,7 @@ async def async_add_child(
unavailability_entity=unavailability_entity,
pause_streak_when_unavailable=pause_streak_when_unavailable,
linked_user_id=linked_user_id,
+ picture_entity=picture_entity,
birthday=birthday,
presence_entity=presence_entity,
age_group=age_group,
diff --git a/custom_components/taskmate/frontend.py b/custom_components/taskmate/frontend.py
index 12dc0f02..69402f8a 100644
--- a/custom_components/taskmate/frontend.py
+++ b/custom_components/taskmate/frontend.py
@@ -49,6 +49,7 @@
"taskmate-calendar-card.js",
"taskmate-photo-gallery-card.js",
"taskmate-family-goal-card.js",
+ "taskmate-sticker-chart-card.js",
"taskmate-wishlist-card.js",
"taskmate-kiosk-card.js",
"taskmate-bounty-card.js",
diff --git a/custom_components/taskmate/models.py b/custom_components/taskmate/models.py
index b620ccb9..13ae9d26 100644
--- a/custom_components/taskmate/models.py
+++ b/custom_components/taskmate/models.py
@@ -235,6 +235,12 @@ class Child:
total_penalties_received: int = 0
notify_service: str | None = None
linked_user_id: str = "" # HA user id; when set, only that user (or an admin) may self-serve as this child
+ # Entity whose picture replaces the MDI avatar — normally the child's
+ # person.* entity. Deliberately independent of linked_user_id: a young
+ # child often has a person (with a photo) but no HA login of their own.
+ # Only the entity id is stored; the picture is read from it on every
+ # update, so a new photo set in HA shows up with nothing to re-save here.
+ picture_entity: str = ""
quiet_hours_start: str = "" # "HH:MM" — start of do-not-disturb window; empty = no quiet hours
quiet_hours_end: str = "" # "HH:MM" — end of do-not-disturb window; start>end means overnight
presence_entity: str = "" # person./device_tracker.; while not home, reminders wait for arrival (#926)
@@ -282,6 +288,7 @@ def from_dict(cls, data: dict[str, Any]) -> Child:
total_penalties_received=data.get("total_penalties_received", 0),
notify_service=data.get("notify_service"),
linked_user_id=data.get("linked_user_id", ""),
+ picture_entity=str(data.get("picture_entity", "") or ""),
quiet_hours_start=data.get("quiet_hours_start", ""),
quiet_hours_end=data.get("quiet_hours_end", ""),
presence_entity=str(data.get("presence_entity", "") or ""),
@@ -320,6 +327,7 @@ def to_dict(self) -> dict[str, Any]:
"total_penalties_received": self.total_penalties_received,
"notify_service": self.notify_service,
"linked_user_id": self.linked_user_id,
+ "picture_entity": self.picture_entity,
"quiet_hours_start": self.quiet_hours_start,
"quiet_hours_end": self.quiet_hours_end,
"presence_entity": self.presence_entity,
@@ -1112,6 +1120,11 @@ class ChoreCompletion:
# Set when this completion is a bounty's (#931). chore_id then carries the
# bounty id too, so nothing that groups completions by chore mixes the two.
bounty_id: str = ""
+ # True on a completion a parent logged afterwards for an earlier day. It was
+ # paid without touching the child's streak (rewriting last_completion_date
+ # to a past day would break a streak they are on today), so undoing it must
+ # not take a day off the streak either.
+ backdated: bool = False
@classmethod
def from_dict(cls, data: dict[str, Any]) -> ChoreCompletion:
@@ -1136,6 +1149,7 @@ def from_dict(cls, data: dict[str, Any]) -> ChoreCompletion:
quality_rating=parse_quality_rating(data.get("quality_rating")),
child_undo_allowed=data.get("child_undo_allowed") is True,
bounty_id=str(data.get("bounty_id", "") or ""),
+ backdated=data.get("backdated") is True,
)
def to_dict(self) -> dict[str, Any]:
@@ -1162,6 +1176,8 @@ def to_dict(self) -> dict[str, Any]:
data["child_undo_allowed"] = True
if self.bounty_id:
data["bounty_id"] = self.bounty_id
+ if self.backdated:
+ data["backdated"] = True
return data
diff --git a/custom_components/taskmate/sensor.py b/custom_components/taskmate/sensor.py
index 40d51760..9b243e54 100644
--- a/custom_components/taskmate/sensor.py
+++ b/custom_components/taskmate/sensor.py
@@ -68,8 +68,11 @@ def _compute_common(coordinator: TaskMateCoordinator) -> dict:
"""
data = coordinator.data
data_id = id(data)
+ # External entities TaskMate reads (a child's photo among them) are not part
+ # of ``data``, so their version is part of the key.
+ external = getattr(coordinator, "external_state_version", 0)
cached = getattr(coordinator, _COMMON_CACHE_ATTR, None)
- if cached and cached.get("data_id") == data_id:
+ if cached and cached.get("data_id") == data_id and cached.get("external") == external:
return cached["common"]
children = data.get("children", [])
@@ -142,10 +145,15 @@ def _compute_common(coordinator: TaskMateCoordinator) -> dict:
"pool_by_child_reward": pool_by_child_reward,
"pool_total_by_reward": pool_total_by_reward,
"total_allocated_by_child": total_allocated_by_child,
+ # Each child's photo (from their chosen picture entity), resolved once
+ # per update: every call site that draws a child's face reads this.
+ "child_avatar_images": {c.id: coordinator.child_avatar_image(c) for c in children},
+ # This week's points earned vs. the most on offer, per child.
+ "child_week_progress": coordinator.week_progress(children, chores, all_completions),
# Sent back to redo by an inspection (#981): not "done" on the card.
"inspection_redo_ids": _inspection_redo_ids(coordinator),
}
- setattr(coordinator, _COMMON_CACHE_ATTR, {"data_id": data_id, "common": common})
+ setattr(coordinator, _COMMON_CACHE_ATTR, {"data_id": data_id, "external": external, "common": common})
return common
@@ -217,6 +225,10 @@ def _build_children_summary(coordinator: TaskMateCoordinator, common: dict) -> l
"total_points_earned": getattr(c, "total_points_earned", 0) or 0,
"total_chores_completed": getattr(c, "total_chores_completed", 0) or 0,
"avatar": getattr(c, "avatar", "mdi:account-circle") or "mdi:account-circle",
+ # The child's photo, or "" — cards fall back to `avatar`.
+ "avatar_image": common.get("child_avatar_images", {}).get(c.id, ""),
+ "week_points_earned": common.get("child_week_progress", {}).get(c.id, {}).get("earned", 0),
+ "week_points_available": common.get("child_week_progress", {}).get(c.id, {}).get("available", 0),
"last_completion_date": getattr(c, "last_completion_date", None),
"streak_paused": getattr(c, "streak_paused", False),
"on_vacation": coordinator._is_child_on_vacation(c),
@@ -698,6 +710,9 @@ def _build_pending_reward_claims(common: dict) -> list[dict]:
"reward_id": rc.reward_id,
"child_id": rc.child_id,
"child_name": child.name,
+ # No avatar_image here on purpose: the cards resolve a claim's
+ # face from the overview sensor's children[], and a copy per
+ # claim pushed this sensor past the 16KB recorder limit.
"child_avatar": getattr(child, "avatar", "mdi:account-circle") or "mdi:account-circle",
"reward_name": reward.name,
"reward_icon": reward.icon or "mdi:gift",
@@ -1575,6 +1590,7 @@ def extra_state_attributes(self) -> dict:
"child_id": child.id,
"child_name": child.name,
"avatar": child.avatar,
+ "avatar_image": self.coordinator.child_avatar_image(child),
"total_points_earned": child.total_points_earned,
"total_chores_completed": child.total_chores_completed,
"current_streak": child.current_streak,
@@ -1615,6 +1631,18 @@ def icon(self) -> str:
child = self.coordinator.get_child(self.child_id)
return child.avatar if child else "mdi:account-circle"
+ @property
+ def entity_picture(self) -> str | None:
+ """The child's photo, so HA's own UI shows their face too.
+
+ HA prefers a picture over ``icon`` when both are set, so returning
+ None (the default) leaves the MDI avatar above in charge.
+ """
+ child = self.coordinator.get_child(self.child_id)
+ if not child:
+ return None
+ return self.coordinator.child_avatar_image(child) or None
+
@property
def extra_state_attributes(self) -> dict:
"""Return additional attributes."""
@@ -1650,6 +1678,7 @@ def _included(c):
"child_id": child.id,
"child_name": child.name,
"avatar": child.avatar,
+ "avatar_image": self.coordinator.child_avatar_image(child),
"points": child.points,
"total_points_earned": child.total_points_earned,
"total_chores_completed": child.total_chores_completed,
@@ -1869,6 +1898,10 @@ def extra_state_attributes(self) -> dict:
earned.sort(key=lambda e: e.get("earned_at") or "", reverse=True)
return {
+ # Who the badges belong to, so the card can put a face to them.
+ "child_name": child.name,
+ "child_avatar": getattr(child, "avatar", "mdi:account-circle") or "mdi:account-circle",
+ "avatar_image": self.coordinator.child_avatar_image(child),
"earned": earned,
"available": available,
"total_badges": len(applicable),
diff --git a/custom_components/taskmate/services.yaml b/custom_components/taskmate/services.yaml
index 74a4a529..578f9a50 100644
--- a/custom_components/taskmate/services.yaml
+++ b/custom_components/taskmate/services.yaml
@@ -27,6 +27,12 @@ complete_chore:
required: false
selector:
text:
+ completed_date:
+ name: Done on
+ description: Log the job for an earlier day (up to 7 days back, not today) that was done but not ticked. Parents only; approved straight away. The day's daily limit still applies.
+ required: false
+ selector:
+ date:
note:
name: Note
description: What the child did, in their own words. Required when the chore is open-ended, and shown to the parent during approval.
diff --git a/custom_components/taskmate/websocket.py b/custom_components/taskmate/websocket.py
index c591a7b7..63c10fcc 100644
--- a/custom_components/taskmate/websocket.py
+++ b/custom_components/taskmate/websocket.py
@@ -173,6 +173,7 @@
WS_SET_GLOBAL_CHORE_ORDER: Final = "taskmate/set_global_chore_order"
WS_ADD_CHORES_BULK: Final = "taskmate/add_chores_bulk"
WS_PARENT_COMPLETE_CHORE: Final = "taskmate/parent_complete_chore"
+WS_DAY_COMPLETIONS: Final = "taskmate/day_completions"
# Templates
WS_TEMPLATES_LIST: Final = "taskmate/templates/list"
@@ -490,6 +491,7 @@ async def _ws_get_state(hass, connection, msg, coordinator):
vol.Optional("unavailability_entity", default=""): str,
vol.Optional("pause_streak_when_unavailable", default=False): bool,
vol.Optional("linked_user_id", default=""): str,
+ vol.Optional("picture_entity", default=""): str,
vol.Optional("birthday", default=""): vol.All(str, vol.Length(max=10)),
vol.Optional("presence_entity", default=""): _validate_presence_entity,
vol.Optional("age_group", default=""): vol.In(("", *AGE_GROUPS)),
@@ -511,6 +513,7 @@ async def _ws_add_child(hass, connection, msg, coordinator):
unavailability_entity=_opt_str(msg.get("unavailability_entity")),
pause_streak_when_unavailable=bool(msg.get("pause_streak_when_unavailable", False)),
linked_user_id=_opt_str(msg.get("linked_user_id")),
+ picture_entity=_opt_str(msg.get("picture_entity")),
birthday=birthday,
presence_entity=msg.get("presence_entity", ""),
age_group=msg.get("age_group", ""),
@@ -529,6 +532,7 @@ async def _ws_add_child(hass, connection, msg, coordinator):
vol.Optional("unavailability_entity"): str,
vol.Optional("pause_streak_when_unavailable"): bool,
vol.Optional("linked_user_id"): str,
+ vol.Optional("picture_entity"): str,
vol.Optional("presence_entity"): _validate_presence_entity,
vol.Optional("is_guest"): bool,
vol.Optional("guest_expires_on"): str,
@@ -557,6 +561,8 @@ async def _ws_update_child(hass, connection, msg, coordinator):
existing.pause_streak_when_unavailable = bool(msg["pause_streak_when_unavailable"])
if "linked_user_id" in msg:
existing.linked_user_id = _opt_str(msg["linked_user_id"])
+ if "picture_entity" in msg:
+ existing.picture_entity = _opt_str(msg["picture_entity"])
if "birthday" in msg:
try:
existing.birthday = normalize_birthday(msg["birthday"])
@@ -2410,6 +2416,26 @@ async def _ws_parent_complete_chore(hass, connection, msg, coordinator):
connection.send_result(msg["id"], {"ok": True})
+@websocket_api.websocket_command(
+ {
+ vol.Required("type"): WS_DAY_COMPLETIONS,
+ vol.Required("date"): vol.All(str, vol.Coerce(date.fromisoformat)),
+ }
+)
+@websocket_api.async_response
+@_admin_only
+async def _ws_day_completions(hass, connection, msg, coordinator):
+ """What was done on one day — for logging past jobs from the panel."""
+ connection.send_result(
+ msg["id"],
+ {
+ "completions": coordinator.completions_on_date(msg["date"]),
+ "week_progress": coordinator.week_progress_on(msg["date"]),
+ "loggable": coordinator.loggable_on(msg["date"]),
+ },
+ )
+
+
@websocket_api.websocket_command(
{
vol.Required("type"): WS_SET_CHORE_ORDER,
@@ -3183,6 +3209,7 @@ async def _ws_config_import(hass, connection, msg, coordinator):
_ws_approve_reward,
_ws_reject_reward,
_ws_parent_complete_chore,
+ _ws_day_completions,
_ws_set_chore_order,
_ws_set_global_chore_order,
_ws_add_chores_bulk,
diff --git a/custom_components/taskmate/www/locales/da.json b/custom_components/taskmate/www/locales/da.json
index 5f54fb73..760d0b74 100644
--- a/custom_components/taskmate/www/locales/da.json
+++ b/custom_components/taskmate/www/locales/da.json
@@ -778,6 +778,8 @@
"panel.child_inverted": "(omvendt)",
"panel.child_link_user_label": "Knyt til Home Assistant-bruger (valgfrit)",
"panel.child_link_user_hint": "Når det er sat, kan kun denne Home Assistant-bruger (eller en admin) fuldføre pligter, indløse belønninger eller køre timere som dette barn via service-kald. Vælg \"Alle\" til en fælles familie-tablet.",
+ "panel.child_picture_entity_label": "Foto fra",
+ "panel.child_picture_entity_hint": "Vælg barnets person for at vise deres foto i stedet for ikonet ovenfor. Fotoet indstilles under Indstillinger › Personer; uden foto vises ikonet.",
"panel.child_kiosk_pin_label": "Kiosk-PIN-kode",
"panel.child_kiosk_pin_hint": "Valgfri. 4 cifre, som barnet taster på en delt kiosk-tablet. Gemmes som hash og kan ikke læses igen — lad feltet stå tomt for at beholde den nuværende kode.",
"panel.child_kiosk_pin_set": "Der er sat en PIN-kode.",
@@ -3055,5 +3057,66 @@
"notify.sample_pledger": "Mormor",
"notify.sample_bounty": "Vask bilen",
"notify.sample_reason": "Ikke færdig",
- "notify.sample_note": "Flot klaret!"
+ "notify.sample_note": "Flot klaret!",
+ "sticker_chart.error.entity_required": "Angiv en entitet",
+ "sticker_chart.default_title": "{name}s klistermærkeskema",
+ "sticker_chart.today": "I dag: {done} / {total} opgaver klaret",
+ "sticker_chart.all_done": "Alt klaret, {name}!",
+ "sticker_chart.day_done_message": "Alle opgaver er klaret i dag!",
+ "sticker_chart.no_stickers": "Ingen klistermærker endnu - lav en opgave for at få dit første!",
+ "sticker_chart.stickers_alt": "{count} {points} optjent",
+ "sticker_chart.goal": "{points} / {goal} {unit} til belønningen",
+ "sticker_chart.goal_reached": "MÅLET ER NÅET! Tid til din belønning!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} denne uge",
+ "sticker_chart.week_perfect": "PERFEKT UGE! Alle stjerner er optjent!",
+ "sticker_chart.block_key": "Hver blok = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} opgaver klaret",
+ "sticker_chart.no_jobs_day": "Ingen opgaver skulle laves {day}.",
+ "sticker_chart.editor.goal": "Hvad klistermærkerne tæller mod",
+ "sticker_chart.editor.goal_reward": "Belønningen",
+ "sticker_chart.editor.goal_week": "Ugens opgaver",
+ "sticker_chart.editor.goal_helper": "Ugens opgaver: blokkene fyldes med de stjerner, der er optjent siden mandag, ud af alle de stjerner, ugens opgaver kan give. Belønningen og knappen til at kræve den forbliver uændrede.",
+ "sticker_chart.no_reward": "Ingen belønning at spare op til endnu - vælg en i kortets indstillinger.",
+ "sticker_chart.no_child": "Ingen børn fundet på denne entitet",
+ "sticker_chart.editor.title": "Titel",
+ "sticker_chart.editor.reward": "Belønning at spare op til",
+ "sticker_chart.editor.reward_helper": "Belønningens pris bliver målet. Lad stå på automatisk for at sigte mod den billigste, de endnu ikke har optjent.",
+ "sticker_chart.editor.reward_auto": "Automatisk (den næste, der kan optjenes)",
+ "sticker_chart.editor.show_today": "Vis dagens opgaver",
+ "sticker_chart.editor.show_today_helper": "Vis linjen \"I dag: x / y opgaver klaret\" over klistermærkerne.",
+ "sticker_chart.claim": "Kræv {reward}",
+ "sticker_chart.claim_confirm": "Tryk igen for at kræve {reward}",
+ "sticker_chart.claim_pending": "Venter på, at en voksen siger ja",
+ "sticker_chart.claim_short": "{count} mere {unit} igen",
+ "sticker_chart.claimed": "{reward} krævet for {name}!",
+ "sticker_chart.complete_failed": "Det virkede ikke: {error}",
+ "sticker_chart.editor.allow_undo": "Hold et klistermærke nede for at fortryde",
+ "sticker_chart.editor.allow_undo_helper": "Tryk og hold på et klistermærke for at tage det tilbage. TaskMate lader kun en forælder gøre dette.",
+ "sticker_chart.editor.child": "Barn",
+ "sticker_chart.editor.child_all": "Alle børn, side om side",
+ "sticker_chart.editor.columns": "Klistermærker pr. række",
+ "sticker_chart.editor.show_claim": "Vis knappen til at kræve belønningen",
+ "sticker_chart.editor.show_claim_helper": "En knap under målet, der kræver belønningen, når der er optjent nok (tryk to gange for at bekræfte).",
+ "sticker_chart.editor.sound": "Afspil opgavens lyd",
+ "sticker_chart.editor.title_helper": "Lad stå tomt for at bruge hvert barns navn.",
+ "sticker_chart.goal_tile": "Mål",
+ "sticker_chart.needs_photo": "Denne opgave kræver et foto - åbn den på TaskMates børnekort.",
+ "sticker_chart.no_jobs_today": "Ingen opgaver i dag - nyd din dag!",
+ "sticker_chart.state.done": "klaret",
+ "sticker_chart.state.partial": "påbegyndt",
+ "sticker_chart.state.pending": "venter på en voksen",
+ "sticker_chart.state.todo": "skal gøres",
+ "sticker_chart.not_allowed": "Denne tablet må ikke sætte flueben ved {name}s opgaver. En voksen kan rette det i TaskMate: knyt {name} til den konto, tabletten bruger, eller tilføj kontoen som forælder.",
+ "sticker_chart.undo_denied": "Kun en voksen kan fortryde et klistermærke.",
+ "sticker_chart.undone": "Klistermærket er taget tilbage.",
+ "panel.backdate_button": "Registrer en tidligere opgave",
+ "panel.backdate_title": "Registrer en tidligere opgave for {name}",
+ "panel.backdate_hint": "Vælg dagen, og tryk derefter på de opgaver, der blev lavet, ligesom på klistermærkeskemaet. De tæller for den dag og uge og godkendes med det samme. Hold et klistermærke nede for at tage det tilbage.",
+ "panel.backdate_hint_list": "Til en opgave, der blev lavet en tidligere dag, men ikke blev afkrydset. Den tæller for den dag og uge og godkendes med det samme.",
+ "panel.backdate_finish": "Færdig",
+ "panel.backdate_day": "Dagen den blev lavet",
+ "panel.backdate_chore": "Opgave",
+ "panel.backdate_none": "Ingen opgaver skulle laves for {name} den dag.",
+ "panel.backdate_save": "Registrer",
+ "panel.backdate_done": "{chore} registreret for {name} d. {day}"
}
diff --git a/custom_components/taskmate/www/locales/de.json b/custom_components/taskmate/www/locales/de.json
index 9445097b..067dd29b 100644
--- a/custom_components/taskmate/www/locales/de.json
+++ b/custom_components/taskmate/www/locales/de.json
@@ -1886,6 +1886,57 @@
"streak.editor.days_shown_helper": "Wie viele Tage der Punkte werden angezeigt (Standard: 14)",
"streak.editor.title": "Titel",
"streak.error.entity_required": "Bitte definieren Sie eine Entität",
+ "sticker_chart.error.entity_required": "Bitte definieren Sie eine Entität",
+ "sticker_chart.default_title": "{name}s Stickertafel",
+ "sticker_chart.today": "Heute: {done} / {total} Aufgaben erledigt",
+ "sticker_chart.all_done": "Alles geschafft, {name}!",
+ "sticker_chart.day_done_message": "Heute ist jede Aufgabe erledigt!",
+ "sticker_chart.no_stickers": "Noch keine Sticker - erledige eine Aufgabe für den ersten!",
+ "sticker_chart.stickers_alt": "{count} {points} gesammelt",
+ "sticker_chart.goal": "{points} / {goal} {unit} bis zur Belohnung",
+ "sticker_chart.goal_reached": "ZIEL ERREICHT! Zeit für die Belohnung!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} diese Woche",
+ "sticker_chart.week_perfect": "PERFEKTE WOCHE! Jeden Stern verdient!",
+ "sticker_chart.block_key": "Jedes Kästchen = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} Aufgaben erledigt",
+ "sticker_chart.no_jobs_day": "Am {day} war keine Aufgabe fällig.",
+ "sticker_chart.editor.goal": "Worauf die Sticker zählen",
+ "sticker_chart.editor.goal_reward": "Die Belohnung",
+ "sticker_chart.editor.goal_week": "Die Aufgaben dieser Woche",
+ "sticker_chart.editor.goal_helper": "Aufgaben dieser Woche: Die Kästchen füllen sich mit den seit Montag verdienten Sternen, von allen Sternen, die die Aufgaben der Woche bringen können. Belohnung und Einlöse-Knopf bleiben, wie sie sind.",
+ "sticker_chart.no_reward": "Noch keine Belohnung zum Sparen - wähle eine in den Karteneinstellungen.",
+ "sticker_chart.no_child": "Keine Kinder für diese Entität gefunden",
+ "sticker_chart.editor.title": "Titel",
+ "sticker_chart.editor.reward": "Belohnung zum Sparen",
+ "sticker_chart.editor.reward_helper": "Die Kosten der Belohnung werden zum Ziel. Auf automatisch belassen, um die günstigste noch nicht verdiente anzupeilen.",
+ "sticker_chart.editor.reward_auto": "Automatisch (die nächste)",
+ "sticker_chart.editor.show_today": "Heutige Aufgaben anzeigen",
+ "sticker_chart.editor.show_today_helper": "Zeigt die Zeile \"Heute: x / y Aufgaben erledigt\" über den Stickern.",
+ "sticker_chart.claim": "{reward} einlösen",
+ "sticker_chart.claim_confirm": "Nochmal tippen, um {reward} einzulösen",
+ "sticker_chart.claim_pending": "Warte auf das OK von einem Erwachsenen",
+ "sticker_chart.claim_short": "Noch {count} {unit}",
+ "sticker_chart.claimed": "{reward} für {name} eingelöst!",
+ "sticker_chart.complete_failed": "Das hat nicht geklappt: {error}",
+ "sticker_chart.editor.allow_undo": "Sticker gedrückt halten zum Rückgängigmachen",
+ "sticker_chart.editor.allow_undo_helper": "Einen Sticker gedrückt halten, um ihn zurückzunehmen. TaskMate erlaubt das nur Eltern.",
+ "sticker_chart.editor.child": "Kind",
+ "sticker_chart.editor.child_all": "Alle Kinder nebeneinander",
+ "sticker_chart.editor.columns": "Sticker pro Reihe",
+ "sticker_chart.editor.show_claim": "Einlöse-Knopf anzeigen",
+ "sticker_chart.editor.show_claim_helper": "Ein Knopf unter dem Ziel, der die Belohnung einlöst, sobald genug gesammelt ist (zum Bestätigen zweimal tippen).",
+ "sticker_chart.editor.sound": "Ton der Aufgabe abspielen",
+ "sticker_chart.editor.title_helper": "Leer lassen, um den Namen jedes Kindes zu verwenden.",
+ "sticker_chart.goal_tile": "Ziel",
+ "sticker_chart.needs_photo": "Diese Aufgabe braucht ein Foto - öffne sie auf der TaskMate-Kinderkarte.",
+ "sticker_chart.no_jobs_today": "Heute keine Aufgaben - genieß den Tag!",
+ "sticker_chart.state.done": "erledigt",
+ "sticker_chart.state.partial": "angefangen",
+ "sticker_chart.state.pending": "wartet auf einen Erwachsenen",
+ "sticker_chart.state.todo": "zu erledigen",
+ "sticker_chart.not_allowed": "Dieses Tablet darf {name}s Aufgaben nicht abhaken. Ein Erwachsener kann das in TaskMate ändern: {name} mit dem Konto dieses Tablets verknüpfen oder dieses Konto als Elternteil eintragen.",
+ "sticker_chart.undo_denied": "Nur ein Erwachsener kann einen Sticker zurücknehmen.",
+ "sticker_chart.undone": "Sticker zurückgenommen.",
"visibility.description_hidden": "Durch Sichtbarkeitsbedingung ausgeblendet",
"visibility.description_shown": "Wird nach Sichtbarkeitsbedingung angezeigt",
"visibility.label_entity": "Sichtbarkeitseinheit",
@@ -1980,6 +2031,8 @@
"panel.btn_clone_chore": "Duplizieren",
"panel.child_link_user_label": "Mit Home-Assistant-Benutzer verknüpfen (optional)",
"panel.child_link_user_hint": "Wenn gesetzt, kann nur dieser Home-Assistant-Benutzer (oder ein Admin) per Dienstaufruf Aufgaben erledigen, Belohnungen einlösen oder Timer für dieses Kind starten. Für ein gemeinsames Familien-Tablet auf \"Beliebig\" lassen.",
+ "panel.child_picture_entity_label": "Foto von",
+ "panel.child_picture_entity_hint": "Wähle die Person des Kindes, um ihr Foto anstelle des Symbols oben zu zeigen. Das Foto wird unter Einstellungen › Personen gesetzt; ohne Foto wird das Symbol angezeigt.",
"panel.child_kiosk_pin_label": "Kiosk-PIN",
"panel.child_kiosk_pin_hint": "Optional. 4 Ziffern, die das Kind auf einem gemeinsamen Kiosk-Tablet eintippt. Wird als Hash gespeichert und kann nicht ausgelesen werden — leer lassen, um die aktuelle PIN zu behalten.",
"panel.child_kiosk_pin_set": "Eine PIN ist gesetzt.",
@@ -2085,6 +2138,16 @@
"panel.adjust_custom_title": "Punkte von {child} um einen eigenen Betrag anpassen",
"panel.adjust_dialog_title": "Punkte anpassen — {name}",
"panel.adjust_done": "{sign}{amount} {points} für {child}",
+ "panel.backdate_button": "Erledigte Aufgabe nachtragen",
+ "panel.backdate_title": "Aufgabe für {name} nachtragen",
+ "panel.backdate_hint": "Wähle den Tag und tippe dann die erledigten Aufgaben an, genau wie auf dem Stickerplan. Sie zählen für diesen Tag und diese Woche und sind sofort bestätigt. Einen Sticker gedrückt halten, um ihn zurückzunehmen.",
+ "panel.backdate_hint_list": "Für eine Aufgabe, die an einem früheren Tag erledigt, aber nicht abgehakt wurde. Sie zählt für diesen Tag und diese Woche und ist sofort bestätigt.",
+ "panel.backdate_finish": "Fertig",
+ "panel.backdate_day": "Tag der Erledigung",
+ "panel.backdate_chore": "Aufgabe",
+ "panel.backdate_none": "An diesem Tag war für {name} keine Aufgabe fällig.",
+ "panel.backdate_save": "Nachtragen",
+ "panel.backdate_done": "{chore} für {name} am {day} nachgetragen",
"panel.adjust_err_amount": "Gib einen Betrag zwischen 1 und 10000 ein.",
"panel.adjust_reason": "Grund (optional)",
"panel.adjust_remove": "Abziehen",
diff --git a/custom_components/taskmate/www/locales/en-GB.json b/custom_components/taskmate/www/locales/en-GB.json
index 4fd7dc37..c95db8d9 100644
--- a/custom_components/taskmate/www/locales/en-GB.json
+++ b/custom_components/taskmate/www/locales/en-GB.json
@@ -778,6 +778,8 @@
"panel.child_inverted": "(inverted)",
"panel.child_link_user_label": "Link to Home Assistant user (optional)",
"panel.child_link_user_hint": "When set, only this Home Assistant user (or an admin) can complete chores, claim rewards, or run timers as this child via service calls. Leave as \"Anyone\" for a shared family tablet.",
+ "panel.child_picture_entity_label": "Photo from",
+ "panel.child_picture_entity_hint": "Pick the child's person to show their photo instead of the icon above. Set the photo under Settings › People; with none set, the icon is shown.",
"panel.child_kiosk_pin_label": "Kiosk PIN",
"panel.child_kiosk_pin_hint": "Optional. 4 digits this child types on a shared kiosk tablet. Stored hashed, so it can't be read back — leave blank to keep the current PIN.",
"panel.child_kiosk_pin_set": "A PIN is set.",
@@ -1951,6 +1953,57 @@
"streak.editor.days_shown_helper": "How many days of dots to show (default: 14)",
"streak.editor.title": "Title",
"streak.error.entity_required": "Please define an entity",
+ "sticker_chart.error.entity_required": "Please define an entity",
+ "sticker_chart.default_title": "{name}'s Sticker Chart",
+ "sticker_chart.today": "Today: {done} / {total} jobs done",
+ "sticker_chart.all_done": "All done, {name}!",
+ "sticker_chart.day_done_message": "Every job finished today!",
+ "sticker_chart.no_stickers": "No stickers yet - do a chore to earn your first!",
+ "sticker_chart.stickers_alt": "{count} {points} earned",
+ "sticker_chart.goal": "{points} / {goal} {unit} to the reward",
+ "sticker_chart.goal_reached": "GOAL REACHED! Time for your reward!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} this week",
+ "sticker_chart.week_perfect": "PERFECT WEEK! Every star earned!",
+ "sticker_chart.block_key": "Each block = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} jobs done",
+ "sticker_chart.no_jobs_day": "No jobs were due on {day}.",
+ "sticker_chart.editor.goal": "What the stickers count towards",
+ "sticker_chart.editor.goal_reward": "The reward",
+ "sticker_chart.editor.goal_week": "This week's jobs",
+ "sticker_chart.editor.goal_helper": "This week's jobs: the blocks fill with the stars earned since Monday, out of every star the week's jobs can give. The reward and claim button stay as they are.",
+ "sticker_chart.no_reward": "No reward to save up for yet - pick one in the card settings.",
+ "sticker_chart.no_child": "No children found on this entity",
+ "sticker_chart.editor.title": "Title",
+ "sticker_chart.editor.reward": "Reward to save up for",
+ "sticker_chart.editor.reward_helper": "The reward's cost becomes the goal. Leave on automatic to aim at the cheapest one they haven't earned yet.",
+ "sticker_chart.editor.reward_auto": "Automatic (next one to earn)",
+ "sticker_chart.editor.show_today": "Show today's jobs",
+ "sticker_chart.editor.show_today_helper": "Show the \"Today: x / y jobs done\" line above the stickers.",
+ "sticker_chart.claim": "Claim {reward}",
+ "sticker_chart.claim_confirm": "Tap again to claim {reward}",
+ "sticker_chart.claim_pending": "Waiting for a grown-up to say yes",
+ "sticker_chart.claim_short": "{count} more {unit} to go",
+ "sticker_chart.claimed": "{reward} claimed for {name}!",
+ "sticker_chart.complete_failed": "That didn't work: {error}",
+ "sticker_chart.editor.allow_undo": "Hold a sticker to undo it",
+ "sticker_chart.editor.allow_undo_helper": "Press and hold a sticker to take it back. TaskMate only lets a parent do this.",
+ "sticker_chart.editor.child": "Child",
+ "sticker_chart.editor.child_all": "All children, side by side",
+ "sticker_chart.editor.columns": "Stickers per row",
+ "sticker_chart.editor.show_claim": "Show the claim button",
+ "sticker_chart.editor.show_claim_helper": "A button under the goal that claims the reward once enough is earned (tap twice to confirm).",
+ "sticker_chart.editor.sound": "Play the chore's sound",
+ "sticker_chart.editor.title_helper": "Leave empty to use each child's name.",
+ "sticker_chart.goal_tile": "Goal",
+ "sticker_chart.needs_photo": "This job needs a photo - open it on the TaskMate child card.",
+ "sticker_chart.no_jobs_today": "No jobs today - enjoy your day!",
+ "sticker_chart.state.done": "done",
+ "sticker_chart.state.partial": "started",
+ "sticker_chart.state.pending": "waiting for a grown-up",
+ "sticker_chart.state.todo": "to do",
+ "sticker_chart.not_allowed": "This tablet isn't allowed to tick {name}'s jobs. A grown-up can fix that in TaskMate: link {name} to the account this tablet uses, or add that account as a parent.",
+ "sticker_chart.undo_denied": "Only a grown-up can undo a sticker.",
+ "sticker_chart.undone": "Sticker taken back.",
"visibility.description_hidden": "Hidden by visibility condition",
"visibility.description_shown": "Shown by visibility condition",
"visibility.label_entity": "Visibility Entity",
@@ -2085,6 +2138,16 @@
"panel.adjust_custom_title": "Adjust {child}'s points by a custom amount",
"panel.adjust_dialog_title": "Adjust points — {name}",
"panel.adjust_done": "{sign}{amount} {points} for {child}",
+ "panel.backdate_button": "Log a past job",
+ "panel.backdate_title": "Log a past job for {name}",
+ "panel.backdate_hint": "Pick the day, then tap the jobs that were done, just like on the sticker chart. They count for that day and week and are approved straight away. Hold a sticker to take it back.",
+ "panel.backdate_hint_list": "For a job done on an earlier day that nobody ticked. It counts for that day and week, and it's approved straight away.",
+ "panel.backdate_finish": "Done",
+ "panel.backdate_day": "Day it was done",
+ "panel.backdate_chore": "Job",
+ "panel.backdate_none": "No jobs were due for {name} that day.",
+ "panel.backdate_save": "Log it",
+ "panel.backdate_done": "{chore} logged for {name} on {day}",
"panel.adjust_err_amount": "Enter an amount between 1 and 10000.",
"panel.adjust_reason": "Reason (optional)",
"panel.adjust_remove": "Remove",
diff --git a/custom_components/taskmate/www/locales/en.json b/custom_components/taskmate/www/locales/en.json
index 4fd7dc37..bb03b191 100644
--- a/custom_components/taskmate/www/locales/en.json
+++ b/custom_components/taskmate/www/locales/en.json
@@ -778,6 +778,8 @@
"panel.child_inverted": "(inverted)",
"panel.child_link_user_label": "Link to Home Assistant user (optional)",
"panel.child_link_user_hint": "When set, only this Home Assistant user (or an admin) can complete chores, claim rewards, or run timers as this child via service calls. Leave as \"Anyone\" for a shared family tablet.",
+ "panel.child_picture_entity_label": "Photo from",
+ "panel.child_picture_entity_hint": "Pick any entity with a picture, usually the child's person, to show it instead of the icon above. A person's photo is set under Settings › People; with no picture, the icon is shown.",
"panel.child_kiosk_pin_label": "Kiosk PIN",
"panel.child_kiosk_pin_hint": "Optional. 4 digits this child types on a shared kiosk tablet. Stored hashed, so it can't be read back — leave blank to keep the current PIN.",
"panel.child_kiosk_pin_set": "A PIN is set.",
@@ -1951,6 +1953,57 @@
"streak.editor.days_shown_helper": "How many days of dots to show (default: 14)",
"streak.editor.title": "Title",
"streak.error.entity_required": "Please define an entity",
+ "sticker_chart.error.entity_required": "Please define an entity",
+ "sticker_chart.default_title": "{name}'s Sticker Chart",
+ "sticker_chart.today": "Today: {done} / {total} jobs done",
+ "sticker_chart.all_done": "All done, {name}!",
+ "sticker_chart.day_done_message": "Every job finished today!",
+ "sticker_chart.no_stickers": "No stickers yet - do a chore to earn your first!",
+ "sticker_chart.stickers_alt": "{count} {points} earned",
+ "sticker_chart.goal": "{points} / {goal} {unit} to the reward",
+ "sticker_chart.goal_reached": "GOAL REACHED! Time for your reward!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} this week",
+ "sticker_chart.week_perfect": "PERFECT WEEK! Every star earned!",
+ "sticker_chart.block_key": "Each block = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} jobs done",
+ "sticker_chart.no_jobs_day": "No jobs were due on {day}.",
+ "sticker_chart.editor.goal": "What the stickers count towards",
+ "sticker_chart.editor.goal_reward": "The reward",
+ "sticker_chart.editor.goal_week": "This week's jobs",
+ "sticker_chart.editor.goal_helper": "This week's jobs: the blocks fill with the stars earned since Monday, out of every star the week's jobs can give. The reward and claim button stay as they are.",
+ "sticker_chart.no_reward": "No reward to save up for yet - pick one in the card settings.",
+ "sticker_chart.no_child": "No children found on this entity",
+ "sticker_chart.editor.title": "Title",
+ "sticker_chart.editor.reward": "Reward to save up for",
+ "sticker_chart.editor.reward_helper": "The reward's cost becomes the goal. Leave on automatic to aim at the cheapest one they haven't earned yet.",
+ "sticker_chart.editor.reward_auto": "Automatic (next one to earn)",
+ "sticker_chart.editor.show_today": "Show today's jobs",
+ "sticker_chart.editor.show_today_helper": "Show the \"Today: x / y jobs done\" line above the stickers.",
+ "sticker_chart.claim": "Claim {reward}",
+ "sticker_chart.claim_confirm": "Tap again to claim {reward}",
+ "sticker_chart.claim_pending": "Waiting for a grown-up to say yes",
+ "sticker_chart.claim_short": "{count} more {unit} to go",
+ "sticker_chart.claimed": "{reward} claimed for {name}!",
+ "sticker_chart.complete_failed": "That didn't work: {error}",
+ "sticker_chart.editor.allow_undo": "Hold a sticker to undo it",
+ "sticker_chart.editor.allow_undo_helper": "Press and hold a sticker to take it back. TaskMate only lets a parent do this.",
+ "sticker_chart.editor.child": "Child",
+ "sticker_chart.editor.child_all": "All children, side by side",
+ "sticker_chart.editor.columns": "Stickers per row",
+ "sticker_chart.editor.show_claim": "Show the claim button",
+ "sticker_chart.editor.show_claim_helper": "A button under the goal that claims the reward once enough is earned (tap twice to confirm).",
+ "sticker_chart.editor.sound": "Play the chore's sound",
+ "sticker_chart.editor.title_helper": "Leave empty to use each child's name.",
+ "sticker_chart.goal_tile": "Goal",
+ "sticker_chart.needs_photo": "This job needs a photo - open it on the TaskMate child card.",
+ "sticker_chart.no_jobs_today": "No jobs today - enjoy your day!",
+ "sticker_chart.state.done": "done",
+ "sticker_chart.state.partial": "started",
+ "sticker_chart.state.pending": "waiting for a grown-up",
+ "sticker_chart.state.todo": "to do",
+ "sticker_chart.not_allowed": "This tablet isn't allowed to tick {name}'s jobs. A grown-up can fix that in TaskMate: link {name} to the account this tablet uses, or add that account as a parent.",
+ "sticker_chart.undo_denied": "Only a grown-up can undo a sticker.",
+ "sticker_chart.undone": "Sticker taken back.",
"visibility.description_hidden": "Hidden by visibility condition",
"visibility.description_shown": "Shown by visibility condition",
"visibility.label_entity": "Visibility Entity",
@@ -2085,6 +2138,16 @@
"panel.adjust_custom_title": "Adjust {child}'s points by a custom amount",
"panel.adjust_dialog_title": "Adjust points — {name}",
"panel.adjust_done": "{sign}{amount} {points} for {child}",
+ "panel.backdate_button": "Log a past job",
+ "panel.backdate_title": "Log a past job for {name}",
+ "panel.backdate_hint": "Pick the day, then tap the jobs that were done, just like on the sticker chart. They count for that day and week and are approved straight away. Hold a sticker to take it back.",
+ "panel.backdate_hint_list": "For a job done on an earlier day that nobody ticked. It counts for that day and week, and it's approved straight away.",
+ "panel.backdate_finish": "Done",
+ "panel.backdate_day": "Day it was done",
+ "panel.backdate_chore": "Job",
+ "panel.backdate_none": "No jobs were due for {name} that day.",
+ "panel.backdate_save": "Log it",
+ "panel.backdate_done": "{chore} logged for {name} on {day}",
"panel.adjust_err_amount": "Enter an amount between 1 and 10000.",
"panel.adjust_reason": "Reason (optional)",
"panel.adjust_remove": "Remove",
diff --git a/custom_components/taskmate/www/locales/fr.json b/custom_components/taskmate/www/locales/fr.json
index 8d8af266..303e0b10 100644
--- a/custom_components/taskmate/www/locales/fr.json
+++ b/custom_components/taskmate/www/locales/fr.json
@@ -1886,6 +1886,57 @@
"streak.editor.days_shown_helper": "Combien de jours de points afficher (par défaut : 14)",
"streak.editor.title": "Titre",
"streak.error.entity_required": "Veuillez définir une entité",
+ "sticker_chart.error.entity_required": "Veuillez définir une entité",
+ "sticker_chart.default_title": "Tableau d'autocollants de {name}",
+ "sticker_chart.today": "Aujourd'hui : {done} / {total} tâches faites",
+ "sticker_chart.all_done": "Tout est fait, {name} !",
+ "sticker_chart.day_done_message": "Toutes les tâches du jour sont faites !",
+ "sticker_chart.no_stickers": "Pas encore d'autocollants - fais une tâche pour gagner le premier !",
+ "sticker_chart.stickers_alt": "{count} {points} gagnées",
+ "sticker_chart.goal": "{points} / {goal} {unit} avant la récompense",
+ "sticker_chart.goal_reached": "OBJECTIF ATTEINT ! C'est l'heure de la récompense !",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} cette semaine",
+ "sticker_chart.week_perfect": "SEMAINE PARFAITE ! Toutes les étoiles gagnées !",
+ "sticker_chart.block_key": "Chaque case = {count} {unit}",
+ "sticker_chart.day_count": "{day} : {done} / {total} tâches faites",
+ "sticker_chart.no_jobs_day": "Aucune tâche n'était prévue le {day}.",
+ "sticker_chart.editor.goal": "Ce que comptent les autocollants",
+ "sticker_chart.editor.goal_reward": "La récompense",
+ "sticker_chart.editor.goal_week": "Les tâches de la semaine",
+ "sticker_chart.editor.goal_helper": "Tâches de la semaine : les cases se remplissent avec les étoiles gagnées depuis lundi, sur toutes celles que les tâches de la semaine peuvent rapporter. La récompense et son bouton ne changent pas.",
+ "sticker_chart.no_reward": "Aucune récompense à viser pour l'instant - choisissez-en une dans les réglages de la carte.",
+ "sticker_chart.no_child": "Aucun enfant trouvé pour cette entité",
+ "sticker_chart.editor.title": "Titre",
+ "sticker_chart.editor.reward": "Récompense à viser",
+ "sticker_chart.editor.reward_helper": "Le coût de la récompense devient l'objectif. Laissez sur automatique pour viser la moins chère pas encore gagnée.",
+ "sticker_chart.editor.reward_auto": "Automatique (la prochaine à gagner)",
+ "sticker_chart.editor.show_today": "Afficher les tâches du jour",
+ "sticker_chart.editor.show_today_helper": "Affiche la ligne « Aujourd'hui : x / y tâches faites » au-dessus des autocollants.",
+ "sticker_chart.claim": "Obtenir {reward}",
+ "sticker_chart.claim_confirm": "Touchez encore pour obtenir {reward}",
+ "sticker_chart.claim_pending": "En attente de l'accord d'un adulte",
+ "sticker_chart.claim_short": "Encore {count} {unit}",
+ "sticker_chart.claimed": "{reward} obtenu pour {name} !",
+ "sticker_chart.complete_failed": "Ça n'a pas marché : {error}",
+ "sticker_chart.editor.allow_undo": "Maintenir un autocollant pour l'annuler",
+ "sticker_chart.editor.allow_undo_helper": "Maintenez un autocollant appuyé pour le reprendre. TaskMate ne l'autorise qu'aux parents.",
+ "sticker_chart.editor.child": "Enfant",
+ "sticker_chart.editor.child_all": "Tous les enfants côte à côte",
+ "sticker_chart.editor.columns": "Autocollants par ligne",
+ "sticker_chart.editor.show_claim": "Afficher le bouton de récompense",
+ "sticker_chart.editor.show_claim_helper": "Un bouton sous l'objectif qui obtient la récompense une fois assez gagné (touchez deux fois pour confirmer).",
+ "sticker_chart.editor.sound": "Jouer le son de la tâche",
+ "sticker_chart.editor.title_helper": "Laissez vide pour utiliser le prénom de chaque enfant.",
+ "sticker_chart.goal_tile": "Objectif",
+ "sticker_chart.needs_photo": "Cette tâche demande une photo - ouvrez-la sur la carte enfant TaskMate.",
+ "sticker_chart.no_jobs_today": "Pas de tâches aujourd'hui - profite de ta journée !",
+ "sticker_chart.state.done": "fait",
+ "sticker_chart.state.partial": "commencé",
+ "sticker_chart.state.pending": "attend un adulte",
+ "sticker_chart.state.todo": "à faire",
+ "sticker_chart.not_allowed": "Cette tablette n'a pas le droit de cocher les tâches de {name}. Un adulte peut le corriger dans TaskMate : associer {name} au compte de cette tablette, ou ajouter ce compte comme parent.",
+ "sticker_chart.undo_denied": "Seul un adulte peut reprendre un autocollant.",
+ "sticker_chart.undone": "Autocollant repris.",
"visibility.description_hidden": "Masqué par la condition de visibilité",
"visibility.description_shown": "Affiché par la condition de visibilité",
"visibility.label_entity": "Entité de visibilité",
@@ -1980,6 +2031,8 @@
"panel.btn_clone_chore": "Dupliquer",
"panel.child_link_user_label": "Lier à un utilisateur Home Assistant (facultatif)",
"panel.child_link_user_hint": "Si défini, seul cet utilisateur Home Assistant (ou un admin) peut terminer des tâches, échanger des récompenses ou lancer des minuteurs pour cet enfant via les services. Laissez sur « N'importe qui » pour une tablette familiale partagée.",
+ "panel.child_picture_entity_label": "Photo de",
+ "panel.child_picture_entity_hint": "Choisissez la personne de l'enfant pour afficher sa photo à la place de l'icône ci-dessus. La photo se définit dans Paramètres › Personnes ; sans photo, l'icône est affichée.",
"panel.child_kiosk_pin_label": "Code PIN du kiosque",
"panel.child_kiosk_pin_hint": "Facultatif. 4 chiffres que l'enfant tape sur une tablette kiosque partagée. Stocké haché, il ne peut pas être relu — laissez vide pour garder le code actuel.",
"panel.child_kiosk_pin_set": "Un code PIN est défini.",
@@ -2085,6 +2138,16 @@
"panel.adjust_custom_title": "Ajuster les points de {child} d'un montant personnalisé",
"panel.adjust_dialog_title": "Ajuster les points — {name}",
"panel.adjust_done": "{sign}{amount} {points} pour {child}",
+ "panel.backdate_button": "Ajouter une tâche passée",
+ "panel.backdate_title": "Ajouter une tâche passée pour {name}",
+ "panel.backdate_hint": "Choisissez le jour, puis touchez les tâches faites, comme sur le tableau d'autocollants. Elles comptent pour ce jour et cette semaine, et sont validées tout de suite. Maintenez un autocollant pour l'annuler.",
+ "panel.backdate_hint_list": "Pour une tâche faite un jour précédent mais jamais cochée. Elle compte pour ce jour et cette semaine, et elle est validée tout de suite.",
+ "panel.backdate_finish": "Terminé",
+ "panel.backdate_day": "Jour où elle a été faite",
+ "panel.backdate_chore": "Tâche",
+ "panel.backdate_none": "Aucune tâche n'était prévue pour {name} ce jour-là.",
+ "panel.backdate_save": "Ajouter",
+ "panel.backdate_done": "{chore} ajoutée pour {name} le {day}",
"panel.adjust_err_amount": "Saisissez un montant entre 1 et 10000.",
"panel.adjust_reason": "Motif (facultatif)",
"panel.adjust_remove": "Retirer",
diff --git a/custom_components/taskmate/www/locales/nb.json b/custom_components/taskmate/www/locales/nb.json
index e739d6f8..369cf35d 100644
--- a/custom_components/taskmate/www/locales/nb.json
+++ b/custom_components/taskmate/www/locales/nb.json
@@ -1886,6 +1886,57 @@
"streak.editor.days_shown_helper": "Hvor mange dager med prikker som skal vises (standard: 14)",
"streak.editor.title": "Tittel",
"streak.error.entity_required": "Vennligst definer en entitet",
+ "sticker_chart.error.entity_required": "Vennligst definer en entitet",
+ "sticker_chart.default_title": "{name} sin klistremerketavle",
+ "sticker_chart.today": "I dag: {done} / {total} oppgaver gjort",
+ "sticker_chart.all_done": "Alt er gjort, {name}!",
+ "sticker_chart.day_done_message": "Alle dagens oppgaver er gjort!",
+ "sticker_chart.no_stickers": "Ingen klistremerker ennå - gjør en oppgave for å få det første!",
+ "sticker_chart.stickers_alt": "{count} {points} opptjent",
+ "sticker_chart.goal": "{points} / {goal} {unit} til belønningen",
+ "sticker_chart.goal_reached": "MÅLET ER NÅDD! På tide med belønning!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} denne uken",
+ "sticker_chart.week_perfect": "PERFEKT UKE! Hver eneste stjerne tjent!",
+ "sticker_chart.block_key": "Hver rute = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} oppgaver gjort",
+ "sticker_chart.no_jobs_day": "Ingen oppgaver den {day}.",
+ "sticker_chart.editor.goal": "Hva klistremerkene teller mot",
+ "sticker_chart.editor.goal_reward": "Belønningen",
+ "sticker_chart.editor.goal_week": "Ukens oppgaver",
+ "sticker_chart.editor.goal_helper": "Ukens oppgaver: rutene fylles med stjernene tjent siden mandag, av alle stjernene ukens oppgaver kan gi. Belønningen og knappen for å hente den er som før.",
+ "sticker_chart.no_reward": "Ingen belønning å spare til ennå - velg en i kortinnstillingene.",
+ "sticker_chart.no_child": "Fant ingen barn på denne entiteten",
+ "sticker_chart.editor.title": "Tittel",
+ "sticker_chart.editor.reward": "Belønning å spare til",
+ "sticker_chart.editor.reward_helper": "Prisen på belønningen blir målet. La stå på automatisk for å sikte mot den billigste som ikke er opptjent ennå.",
+ "sticker_chart.editor.reward_auto": "Automatisk (den neste å tjene)",
+ "sticker_chart.editor.show_today": "Vis dagens oppgaver",
+ "sticker_chart.editor.show_today_helper": "Viser linjen «I dag: x / y oppgaver gjort» over klistremerkene.",
+ "sticker_chart.claim": "Hent {reward}",
+ "sticker_chart.claim_confirm": "Trykk igjen for å hente {reward}",
+ "sticker_chart.claim_pending": "Venter på at en voksen sier ja",
+ "sticker_chart.claim_short": "{count} {unit} igjen",
+ "sticker_chart.claimed": "{reward} hentet for {name}!",
+ "sticker_chart.complete_failed": "Det gikk ikke: {error}",
+ "sticker_chart.editor.allow_undo": "Hold inne et klistremerke for å angre",
+ "sticker_chart.editor.allow_undo_helper": "Hold inne et klistremerke for å ta det tilbake. TaskMate lar bare foreldre gjøre dette.",
+ "sticker_chart.editor.child": "Barn",
+ "sticker_chart.editor.child_all": "Alle barn side om side",
+ "sticker_chart.editor.columns": "Klistremerker per rad",
+ "sticker_chart.editor.show_claim": "Vis hent-knappen",
+ "sticker_chart.editor.show_claim_helper": "En knapp under målet som henter belønningen når nok er opptjent (trykk to ganger for å bekrefte).",
+ "sticker_chart.editor.sound": "Spill oppgavens lyd",
+ "sticker_chart.editor.title_helper": "La stå tom for å bruke navnet til hvert barn.",
+ "sticker_chart.goal_tile": "Mål",
+ "sticker_chart.needs_photo": "Denne oppgaven trenger et bilde - åpne den på TaskMate-barnekortet.",
+ "sticker_chart.no_jobs_today": "Ingen oppgaver i dag - kos deg!",
+ "sticker_chart.state.done": "gjort",
+ "sticker_chart.state.partial": "påbegynt",
+ "sticker_chart.state.pending": "venter på en voksen",
+ "sticker_chart.state.todo": "ikke gjort",
+ "sticker_chart.not_allowed": "Dette nettbrettet får ikke krysse av oppgavene til {name}. En voksen kan fikse det i TaskMate: koble {name} til kontoen nettbrettet bruker, eller legge til den kontoen som forelder.",
+ "sticker_chart.undo_denied": "Bare en voksen kan ta tilbake et klistremerke.",
+ "sticker_chart.undone": "Klistremerket er tatt tilbake.",
"visibility.description_hidden": "Skjult av synlighetsbetingelse",
"visibility.description_shown": "Vist av synlighetsbetingelse",
"visibility.label_entity": "Synlighetsentitet",
@@ -1980,6 +2031,8 @@
"panel.btn_clone_chore": "Dupliser",
"panel.child_link_user_label": "Koble til Home Assistant-bruker (valgfritt)",
"panel.child_link_user_hint": "Når satt, kan bare denne Home Assistant-brukeren (eller en admin) fullføre gjøremål, løse inn belønninger eller starte tidtakere for dette barnet via tjenestekall. La stå på «Hvem som helst» for et delt familiebrett.",
+ "panel.child_picture_entity_label": "Bilde fra",
+ "panel.child_picture_entity_hint": "Velg barnets person for å vise bildet i stedet for ikonet over. Bildet settes under Innstillinger › Personer; uten bilde vises ikonet.",
"panel.child_kiosk_pin_label": "Kiosk-PIN",
"panel.child_kiosk_pin_hint": "Valgfritt. 4 sifre barnet taster på et delt kiosk-nettbrett. Lagres som hash og kan ikke leses tilbake — la feltet stå tomt for å beholde gjeldende kode.",
"panel.child_kiosk_pin_set": "En PIN-kode er satt.",
@@ -2085,6 +2138,16 @@
"panel.adjust_custom_title": "Juster poengene til {child} med et eget beløp",
"panel.adjust_dialog_title": "Juster poeng — {name}",
"panel.adjust_done": "{sign}{amount} {points} til {child}",
+ "panel.backdate_button": "Før opp en tidligere oppgave",
+ "panel.backdate_title": "Før opp en tidligere oppgave for {name}",
+ "panel.backdate_hint": "Velg dagen, og trykk så på oppgavene som ble gjort, akkurat som på klistremerketavla. De teller for den dagen og uken, og godkjennes med en gang. Hold inne et klistremerke for å ta det tilbake.",
+ "panel.backdate_hint_list": "For en oppgave som ble gjort en tidligere dag, men ikke krysset av. Den teller for den dagen og uken, og godkjennes med en gang.",
+ "panel.backdate_finish": "Ferdig",
+ "panel.backdate_day": "Dagen den ble gjort",
+ "panel.backdate_chore": "Oppgave",
+ "panel.backdate_none": "{name} hadde ingen oppgaver den dagen.",
+ "panel.backdate_save": "Før opp",
+ "panel.backdate_done": "{chore} ført opp for {name} {day}",
"panel.adjust_err_amount": "Skriv inn et beløp mellom 1 og 10000.",
"panel.adjust_reason": "Årsak (valgfritt)",
"panel.adjust_remove": "Trekk fra",
diff --git a/custom_components/taskmate/www/locales/nn.json b/custom_components/taskmate/www/locales/nn.json
index 56a1b693..0b1ef0e4 100644
--- a/custom_components/taskmate/www/locales/nn.json
+++ b/custom_components/taskmate/www/locales/nn.json
@@ -1886,6 +1886,57 @@
"streak.editor.days_shown_helper": "Kor mange dagar med prikkar som skal visast (standard: 14)",
"streak.editor.title": "Tittel",
"streak.error.entity_required": "Ver venleg og definer ein entitet",
+ "sticker_chart.error.entity_required": "Ver venleg og definer ein entitet",
+ "sticker_chart.default_title": "{name} si klistremerketavle",
+ "sticker_chart.today": "I dag: {done} / {total} oppgåver gjorde",
+ "sticker_chart.all_done": "Alt er gjort, {name}!",
+ "sticker_chart.day_done_message": "Alle oppgåvene i dag er gjorde!",
+ "sticker_chart.no_stickers": "Ingen klistremerke enno - gjer ei oppgåve for å få det første!",
+ "sticker_chart.stickers_alt": "{count} {points} opptente",
+ "sticker_chart.goal": "{points} / {goal} {unit} til løninga",
+ "sticker_chart.goal_reached": "MÅLET ER NÅDD! På tide med løn!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} denne veka",
+ "sticker_chart.week_perfect": "PERFEKT VEKE! Kvar einaste stjerne tent!",
+ "sticker_chart.block_key": "Kvar rute = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} oppgåver gjorde",
+ "sticker_chart.no_jobs_day": "Ingen oppgåver den {day}.",
+ "sticker_chart.editor.goal": "Kva klistremerka tel mot",
+ "sticker_chart.editor.goal_reward": "Løna",
+ "sticker_chart.editor.goal_week": "Oppgåvene denne veka",
+ "sticker_chart.editor.goal_helper": "Oppgåvene denne veka: rutene blir fylte med stjernene tente sidan måndag, av alle stjernene oppgåvene i veka kan gi. Løna og knappen for å hente henne er som før.",
+ "sticker_chart.no_reward": "Inga løn å spare til enno - vel ei i kortinnstillingane.",
+ "sticker_chart.no_child": "Fann ingen born på denne entiteten",
+ "sticker_chart.editor.title": "Tittel",
+ "sticker_chart.editor.reward": "Løn å spare til",
+ "sticker_chart.editor.reward_helper": "Prisen på løna blir målet. Lat stå på automatisk for å sikte mot den billegaste som ikkje er opptent enno.",
+ "sticker_chart.editor.reward_auto": "Automatisk (den neste å tene)",
+ "sticker_chart.editor.show_today": "Vis oppgåvene i dag",
+ "sticker_chart.editor.show_today_helper": "Viser linja «I dag: x / y oppgåver gjorde» over klistremerka.",
+ "sticker_chart.claim": "Hent {reward}",
+ "sticker_chart.claim_confirm": "Trykk igjen for å hente {reward}",
+ "sticker_chart.claim_pending": "Ventar på at ein vaksen seier ja",
+ "sticker_chart.claim_short": "{count} {unit} att",
+ "sticker_chart.claimed": "{reward} henta for {name}!",
+ "sticker_chart.complete_failed": "Det gjekk ikkje: {error}",
+ "sticker_chart.editor.allow_undo": "Hald inne eit klistremerke for å angre",
+ "sticker_chart.editor.allow_undo_helper": "Hald inne eit klistremerke for å ta det tilbake. TaskMate lèt berre foreldre gjere dette.",
+ "sticker_chart.editor.child": "Barn",
+ "sticker_chart.editor.child_all": "Alle born side om side",
+ "sticker_chart.editor.columns": "Klistremerke per rad",
+ "sticker_chart.editor.show_claim": "Vis hent-knappen",
+ "sticker_chart.editor.show_claim_helper": "Ein knapp under målet som hentar løna når nok er opptent (trykk to gonger for å stadfeste).",
+ "sticker_chart.editor.sound": "Spel lyden til oppgåva",
+ "sticker_chart.editor.title_helper": "La stå tom for å bruke namnet til kvart barn.",
+ "sticker_chart.goal_tile": "Mål",
+ "sticker_chart.needs_photo": "Denne oppgåva treng eit bilete - opne ho på TaskMate-barnekortet.",
+ "sticker_chart.no_jobs_today": "Ingen oppgåver i dag - kos deg!",
+ "sticker_chart.state.done": "gjort",
+ "sticker_chart.state.partial": "byrja",
+ "sticker_chart.state.pending": "ventar på ein vaksen",
+ "sticker_chart.state.todo": "ikkje gjort",
+ "sticker_chart.not_allowed": "Dette nettbrettet får ikkje krysse av oppgåvene til {name}. Ein vaksen kan fikse det i TaskMate: kople {name} til kontoen nettbrettet brukar, eller leggje til den kontoen som forelder.",
+ "sticker_chart.undo_denied": "Berre ein vaksen kan ta tilbake eit klistremerke.",
+ "sticker_chart.undone": "Klistremerket er teke tilbake.",
"visibility.description_hidden": "Skjult av synlegheitskondisjon",
"visibility.description_shown": "Vist av synlegheitskondisjon",
"visibility.label_entity": "Synlegheitsentitet",
@@ -1980,6 +2031,8 @@
"panel.btn_clone_chore": "Dupliser",
"panel.child_link_user_label": "Kople til Home Assistant-brukar (valfritt)",
"panel.child_link_user_hint": "Når sett, kan berre denne Home Assistant-brukaren (eller ein admin) fullføre gjeremål, løyse inn løningar eller starte tidtakarar for dette barnet via tenestekall. Lat stå på «Kven som helst» for eit delt familiebrett.",
+ "panel.child_picture_entity_label": "Bilete frå",
+ "panel.child_picture_entity_hint": "Vel personen til barnet for å vise biletet i staden for ikonet over. Biletet vert sett under Innstillingar › Personar; utan bilete vert ikonet vist.",
"panel.child_kiosk_pin_label": "Kiosk-PIN",
"panel.child_kiosk_pin_hint": "Valfritt. 4 siffer barnet tastar på eit delt kiosk-nettbrett. Vert lagra som hash og kan ikkje lesast tilbake — la feltet stå tomt for å halde på gjeldande kode.",
"panel.child_kiosk_pin_set": "Ein PIN-kode er sett.",
@@ -2085,6 +2138,16 @@
"panel.adjust_custom_title": "Juster poenga til {child} med eit eige beløp",
"panel.adjust_dialog_title": "Juster poeng — {name}",
"panel.adjust_done": "{sign}{amount} {points} til {child}",
+ "panel.backdate_button": "Før opp ei tidlegare oppgåve",
+ "panel.backdate_title": "Før opp ei tidlegare oppgåve for {name}",
+ "panel.backdate_hint": "Vel dagen, og trykk så på oppgåvene som vart gjorde, akkurat som på klistremerketavla. Dei tel for den dagen og veka, og blir godkjende med ein gong. Hald inne eit klistremerke for å ta det tilbake.",
+ "panel.backdate_hint_list": "For ei oppgåve som vart gjord ein tidlegare dag, men ikkje kryssa av. Ho tel for den dagen og veka, og blir godkjend med ein gong.",
+ "panel.backdate_finish": "Ferdig",
+ "panel.backdate_day": "Dagen ho vart gjord",
+ "panel.backdate_chore": "Oppgåve",
+ "panel.backdate_none": "{name} hadde ingen oppgåver den dagen.",
+ "panel.backdate_save": "Før opp",
+ "panel.backdate_done": "{chore} ført opp for {name} {day}",
"panel.adjust_err_amount": "Skriv inn eit beløp mellom 1 og 10000.",
"panel.adjust_reason": "Årsak (valfritt)",
"panel.adjust_remove": "Trekk frå",
diff --git a/custom_components/taskmate/www/locales/pl.json b/custom_components/taskmate/www/locales/pl.json
index 8ef97537..ddbbe43d 100644
--- a/custom_components/taskmate/www/locales/pl.json
+++ b/custom_components/taskmate/www/locales/pl.json
@@ -778,6 +778,8 @@
"panel.child_inverted": "(odwrócone)",
"panel.child_link_user_label": "Powiąż z użytkownikiem Home Assistant (opcjonalnie)",
"panel.child_link_user_hint": "Po ustawieniu tylko ten użytkownik Home Assistant (lub administrator) może wykonywać obowiązki, odbierać nagrody i uruchamiać stopery jako to dziecko przez wywołania usług. Pozostaw „Każdy” dla wspólnego tabletu rodzinnego.",
+ "panel.child_picture_entity_label": "Zdjęcie z",
+ "panel.child_picture_entity_hint": "Wybierz osobę dziecka, aby pokazać jego zdjęcie zamiast ikony powyżej. Zdjęcie ustawisz w Ustawienia › Osoby; bez zdjęcia pokazywana jest ikona.",
"panel.child_kiosk_pin_label": "PIN kiosku",
"panel.child_kiosk_pin_hint": "Opcjonalny. 4 cyfry, które dziecko wpisuje na wspólnym tablecie w trybie kiosku. Przechowywany jako hash, więc nie da się go odczytać — pozostaw puste, aby zachować obecny PIN.",
"panel.child_kiosk_pin_set": "PIN jest ustawiony.",
@@ -3055,5 +3057,66 @@
"notify.sample_pledger": "Babcia",
"notify.sample_bounty": "Umyj samochód",
"notify.sample_reason": "Niedokończone",
- "notify.sample_note": "Świetna robota!"
+ "notify.sample_note": "Świetna robota!",
+ "sticker_chart.error.entity_required": "Zdefiniuj encję",
+ "sticker_chart.default_title": "Tabela naklejek: {name}",
+ "sticker_chart.today": "Dziś: {done} / {total} zadań zrobionych",
+ "sticker_chart.all_done": "Wszystko zrobione, {name}!",
+ "sticker_chart.day_done_message": "Wszystkie zadania na dziś gotowe!",
+ "sticker_chart.no_stickers": "Jeszcze nie ma naklejek – zrób obowiązek, aby zdobyć pierwszą!",
+ "sticker_chart.stickers_alt": "Zdobyte: {count} {points}",
+ "sticker_chart.goal": "{points} / {goal} {unit} do nagrody",
+ "sticker_chart.goal_reached": "CEL OSIĄGNIĘTY! Czas na nagrodę!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} w tym tygodniu",
+ "sticker_chart.week_perfect": "IDEALNY TYDZIEŃ! Wszystkie gwiazdki zdobyte!",
+ "sticker_chart.block_key": "Każdy blok = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} zadań zrobionych",
+ "sticker_chart.no_jobs_day": "W dniu {day} nie było żadnych zadań.",
+ "sticker_chart.editor.goal": "Do czego liczą się naklejki",
+ "sticker_chart.editor.goal_reward": "Nagroda",
+ "sticker_chart.editor.goal_week": "Zadania z tego tygodnia",
+ "sticker_chart.editor.goal_helper": "Zadania z tego tygodnia: bloki wypełniają się gwiazdkami zdobytymi od poniedziałku, z wszystkich gwiazdek, jakie mogą dać zadania tygodnia. Nagroda i przycisk odbioru pozostają bez zmian.",
+ "sticker_chart.no_reward": "Nie ma jeszcze nagrody do zbierania – wybierz ją w ustawieniach karty.",
+ "sticker_chart.no_child": "Nie znaleziono dzieci dla tej encji",
+ "sticker_chart.editor.title": "Tytuł",
+ "sticker_chart.editor.reward": "Nagroda, na którą się zbiera",
+ "sticker_chart.editor.reward_helper": "Koszt nagrody staje się celem. Zostaw automatycznie, aby celować w najtańszą, jeszcze niezdobytą nagrodę.",
+ "sticker_chart.editor.reward_auto": "Automatycznie (następna do zdobycia)",
+ "sticker_chart.editor.show_today": "Pokaż dzisiejsze zadania",
+ "sticker_chart.editor.show_today_helper": "Pokaż wiersz „Dziś: x / y zadań zrobionych” nad naklejkami.",
+ "sticker_chart.claim": "Odbierz: {reward}",
+ "sticker_chart.claim_confirm": "Dotknij ponownie, aby odebrać: {reward}",
+ "sticker_chart.claim_pending": "Czeka na zgodę dorosłego",
+ "sticker_chart.claim_short": "Jeszcze {count} {unit}",
+ "sticker_chart.claimed": "Odebrano {reward} dla {name}!",
+ "sticker_chart.complete_failed": "Nie udało się: {error}",
+ "sticker_chart.editor.allow_undo": "Przytrzymaj naklejkę, aby cofnąć",
+ "sticker_chart.editor.allow_undo_helper": "Naciśnij i przytrzymaj naklejkę, aby ją zabrać. TaskMate pozwala na to tylko rodzicowi.",
+ "sticker_chart.editor.child": "Dziecko",
+ "sticker_chart.editor.child_all": "Wszystkie dzieci obok siebie",
+ "sticker_chart.editor.columns": "Naklejek w rzędzie",
+ "sticker_chart.editor.show_claim": "Pokaż przycisk odbioru",
+ "sticker_chart.editor.show_claim_helper": "Przycisk pod celem, który odbiera nagrodę po zdobyciu wystarczającej liczby (dotknij dwa razy, aby potwierdzić).",
+ "sticker_chart.editor.sound": "Odtwarzaj dźwięk obowiązku",
+ "sticker_chart.editor.title_helper": "Zostaw puste, aby użyć imienia każdego dziecka.",
+ "sticker_chart.goal_tile": "Cel",
+ "sticker_chart.needs_photo": "To zadanie wymaga zdjęcia – otwórz je na karcie dziecka TaskMate.",
+ "sticker_chart.no_jobs_today": "Dziś brak zadań – miłego dnia!",
+ "sticker_chart.state.done": "zrobione",
+ "sticker_chart.state.partial": "rozpoczęte",
+ "sticker_chart.state.pending": "czeka na dorosłego",
+ "sticker_chart.state.todo": "do zrobienia",
+ "sticker_chart.not_allowed": "Ten tablet nie może odznaczać zadań dziecka {name}. Dorosły może to zmienić w TaskMate: połącz {name} z kontem używanym na tym tablecie lub dodaj to konto jako rodzica.",
+ "sticker_chart.undo_denied": "Tylko dorosły może cofnąć naklejkę.",
+ "sticker_chart.undone": "Naklejka zabrana.",
+ "panel.backdate_button": "Zapisz minione zadanie",
+ "panel.backdate_title": "Zapisz minione zadanie dla: {name}",
+ "panel.backdate_hint": "Wybierz dzień, a potem dotknij zadań, które zostały zrobione – tak jak w tabeli naklejek. Liczą się do tego dnia i tygodnia i są zatwierdzane od razu. Przytrzymaj naklejkę, aby ją cofnąć.",
+ "panel.backdate_hint_list": "Dla zadania zrobionego wcześniej, którego nikt nie odznaczył. Liczy się do tamtego dnia i tygodnia i jest zatwierdzane od razu.",
+ "panel.backdate_finish": "Gotowe",
+ "panel.backdate_day": "Dzień wykonania",
+ "panel.backdate_chore": "Zadanie",
+ "panel.backdate_none": "W tym dniu nie było zadań dla: {name}.",
+ "panel.backdate_save": "Zapisz",
+ "panel.backdate_done": "Zapisano {chore} dla: {name} w dniu {day}"
}
diff --git a/custom_components/taskmate/www/locales/pt-BR.json b/custom_components/taskmate/www/locales/pt-BR.json
index 8053106b..71f098c1 100644
--- a/custom_components/taskmate/www/locales/pt-BR.json
+++ b/custom_components/taskmate/www/locales/pt-BR.json
@@ -1886,6 +1886,57 @@
"streak.editor.days_shown_helper": "Quantos dias de pontos mostrar (padrão: 14)",
"streak.editor.title": "Título",
"streak.error.entity_required": "Por favor define uma entidade",
+ "sticker_chart.error.entity_required": "Por favor define uma entidade",
+ "sticker_chart.default_title": "Quadro de adesivos da {name}",
+ "sticker_chart.today": "Hoje: {done} / {total} tarefas feitas",
+ "sticker_chart.all_done": "Tudo pronto, {name}!",
+ "sticker_chart.day_done_message": "Todas as tarefas de hoje foram feitas!",
+ "sticker_chart.no_stickers": "Ainda sem adesivos - faça uma tarefa para ganhar o primeiro!",
+ "sticker_chart.stickers_alt": "{count} {points} ganhas",
+ "sticker_chart.goal": "{points} / {goal} {unit} até a recompensa",
+ "sticker_chart.goal_reached": "OBJETIVO ALCANÇADO! Hora da recompensa!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} nesta semana",
+ "sticker_chart.week_perfect": "SEMANA PERFEITA! Todas as estrelas conquistadas!",
+ "sticker_chart.block_key": "Cada bloco = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} tarefas feitas",
+ "sticker_chart.no_jobs_day": "Não havia tarefas em {day}.",
+ "sticker_chart.editor.goal": "Para onde contam os adesivos",
+ "sticker_chart.editor.goal_reward": "A recompensa",
+ "sticker_chart.editor.goal_week": "As tarefas desta semana",
+ "sticker_chart.editor.goal_helper": "Tarefas desta semana: os blocos se enchem com as estrelas ganhas desde segunda-feira, de todas as que as tarefas da semana podem dar. A recompensa e o botão de resgate continuam iguais.",
+ "sticker_chart.no_reward": "Ainda não há recompensa para juntar - escolha uma nas configurações do cartão.",
+ "sticker_chart.no_child": "Nenhuma criança encontrada nesta entidade",
+ "sticker_chart.editor.title": "Título",
+ "sticker_chart.editor.reward": "Recompensa a ser conquistada",
+ "sticker_chart.editor.reward_helper": "O custo da recompensa vira o objetivo. Deixe em automático para mirar a mais barata ainda não conquistada.",
+ "sticker_chart.editor.reward_auto": "Automático (a próxima a conquistar)",
+ "sticker_chart.editor.show_today": "Mostrar tarefas de hoje",
+ "sticker_chart.editor.show_today_helper": "Mostra a linha \"Hoje: x / y tarefas feitas\" acima dos adesivos.",
+ "sticker_chart.claim": "Resgatar {reward}",
+ "sticker_chart.claim_confirm": "Toque de novo para resgatar {reward}",
+ "sticker_chart.claim_pending": "Esperando um adulto dizer sim",
+ "sticker_chart.claim_short": "Faltam {count} {unit}",
+ "sticker_chart.claimed": "{reward} resgatado por {name}!",
+ "sticker_chart.complete_failed": "Não deu certo: {error}",
+ "sticker_chart.editor.allow_undo": "Segurar um adesivo para desfazer",
+ "sticker_chart.editor.allow_undo_helper": "Segure um adesivo para retirá-lo. O TaskMate só deixa os pais fazerem isso.",
+ "sticker_chart.editor.child": "Criança",
+ "sticker_chart.editor.child_all": "Todas as crianças lado a lado",
+ "sticker_chart.editor.columns": "Adesivos por linha",
+ "sticker_chart.editor.show_claim": "Mostrar o botão de resgate",
+ "sticker_chart.editor.show_claim_helper": "Um botão abaixo do objetivo que resgata a recompensa quando já ganhou o suficiente (toque duas vezes para confirmar).",
+ "sticker_chart.editor.sound": "Tocar o som da tarefa",
+ "sticker_chart.editor.title_helper": "Deixe vazio para usar o nome de cada criança.",
+ "sticker_chart.goal_tile": "Objetivo",
+ "sticker_chart.needs_photo": "Esta tarefa precisa de uma foto - abra no cartão da criança do TaskMate.",
+ "sticker_chart.no_jobs_today": "Nenhuma tarefa hoje - aproveite o dia!",
+ "sticker_chart.state.done": "feito",
+ "sticker_chart.state.partial": "começado",
+ "sticker_chart.state.pending": "esperando um adulto",
+ "sticker_chart.state.todo": "a fazer",
+ "sticker_chart.not_allowed": "Este tablet não pode marcar as tarefas de {name}. Um adulto pode resolver no TaskMate: vincular {name} à conta que este tablet usa, ou adicionar essa conta como pai/mãe.",
+ "sticker_chart.undo_denied": "Só um adulto pode retirar um adesivo.",
+ "sticker_chart.undone": "Adesivo retirado.",
"visibility.description_hidden": "Ocultado por condição de visibilidade",
"visibility.description_shown": "Mostrado por condição de visibilidade",
"visibility.label_entity": "Entidade de Visibilidade",
@@ -1980,6 +2031,8 @@
"panel.btn_clone_chore": "Duplicar",
"panel.child_link_user_label": "Vincular a um usuário do Home Assistant (opcional)",
"panel.child_link_user_hint": "Quando definido, apenas este usuário do Home Assistant (ou um admin) pode concluir tarefas, resgatar recompensas ou iniciar timers por esta criança via serviços. Deixe em \"Qualquer pessoa\" para um tablet familiar compartilhado.",
+ "panel.child_picture_entity_label": "Foto de",
+ "panel.child_picture_entity_hint": "Escolha a pessoa da criança para mostrar a foto em vez do ícone acima. A foto é definida em Configurações › Pessoas; sem foto, o ícone é exibido.",
"panel.child_kiosk_pin_label": "PIN do quiosque",
"panel.child_kiosk_pin_hint": "Opcional. 4 dígitos que a criança digita em um tablet de quiosque compartilhado. Salvo com hash, não pode ser lido de volta — deixe em branco para manter o PIN atual.",
"panel.child_kiosk_pin_set": "Há um PIN definido.",
@@ -2085,6 +2138,16 @@
"panel.adjust_custom_title": "Ajustar os pontos de {child} com um valor personalizado",
"panel.adjust_dialog_title": "Ajustar pontos — {name}",
"panel.adjust_done": "{sign}{amount} {points} para {child}",
+ "panel.backdate_button": "Registrar uma tarefa passada",
+ "panel.backdate_title": "Registrar uma tarefa passada para {name}",
+ "panel.backdate_hint": "Escolha o dia e toque nas tarefas que foram feitas, igual ao quadro de adesivos. Elas contam para esse dia e essa semana, e já ficam aprovadas. Segure um adesivo para desfazê-lo.",
+ "panel.backdate_hint_list": "Para uma tarefa feita em um dia anterior que ninguém marcou. Conta para esse dia e essa semana, e já fica aprovada.",
+ "panel.backdate_finish": "Concluir",
+ "panel.backdate_day": "Dia em que foi feita",
+ "panel.backdate_chore": "Tarefa",
+ "panel.backdate_none": "Não havia tarefas para {name} nesse dia.",
+ "panel.backdate_save": "Registrar",
+ "panel.backdate_done": "{chore} registrada para {name} em {day}",
"panel.adjust_err_amount": "Digite um valor entre 1 e 10000.",
"panel.adjust_reason": "Motivo (opcional)",
"panel.adjust_remove": "Remover",
diff --git a/custom_components/taskmate/www/locales/pt.json b/custom_components/taskmate/www/locales/pt.json
index 8be54547..72aa5a43 100644
--- a/custom_components/taskmate/www/locales/pt.json
+++ b/custom_components/taskmate/www/locales/pt.json
@@ -1891,6 +1891,57 @@
"streak.editor.days_shown_helper": "Quantos dias de pontos mostrar (padrão: 14)",
"streak.editor.title": "Título",
"streak.error.entity_required": "Por favor define uma entidade",
+ "sticker_chart.error.entity_required": "Por favor define uma entidade",
+ "sticker_chart.default_title": "Quadro de autocolantes da {name}",
+ "sticker_chart.today": "Hoje: {done} / {total} tarefas feitas",
+ "sticker_chart.all_done": "Está tudo feito, {name}!",
+ "sticker_chart.day_done_message": "Todas as tarefas de hoje estão feitas!",
+ "sticker_chart.no_stickers": "Ainda sem autocolantes - faz uma tarefa para ganhar o primeiro!",
+ "sticker_chart.stickers_alt": "{count} {points} ganhas",
+ "sticker_chart.goal": "{points} / {goal} {unit} até à recompensa",
+ "sticker_chart.goal_reached": "OBJETIVO ALCANÇADO! Hora da recompensa!",
+ "sticker_chart.week_goal": "{points} / {goal} {unit} esta semana",
+ "sticker_chart.week_perfect": "SEMANA PERFEITA! Todas as estrelas ganhas!",
+ "sticker_chart.block_key": "Cada bloco = {count} {unit}",
+ "sticker_chart.day_count": "{day}: {done} / {total} tarefas feitas",
+ "sticker_chart.no_jobs_day": "Não havia tarefas em {day}.",
+ "sticker_chart.editor.goal": "Para onde contam os autocolantes",
+ "sticker_chart.editor.goal_reward": "A recompensa",
+ "sticker_chart.editor.goal_week": "As tarefas desta semana",
+ "sticker_chart.editor.goal_helper": "Tarefas desta semana: os blocos enchem-se com as estrelas ganhas desde segunda-feira, de todas as que as tarefas da semana podem dar. A recompensa e o botão para a levantar ficam como estão.",
+ "sticker_chart.no_reward": "Ainda não há recompensa para juntar - escolhe uma nas definições do cartão.",
+ "sticker_chart.no_child": "Não foram encontradas crianças nesta entidade",
+ "sticker_chart.editor.title": "Título",
+ "sticker_chart.editor.reward": "Recompensa a que se destina",
+ "sticker_chart.editor.reward_helper": "O custo da recompensa passa a ser o objetivo. Deixa em automático para apontar à mais barata ainda não ganha.",
+ "sticker_chart.editor.reward_auto": "Automático (a próxima a ganhar)",
+ "sticker_chart.editor.show_today": "Mostrar tarefas de hoje",
+ "sticker_chart.editor.show_today_helper": "Mostra a linha \"Hoje: x / y tarefas feitas\" por cima dos autocolantes.",
+ "sticker_chart.claim": "Levantar {reward}",
+ "sticker_chart.claim_confirm": "Toca outra vez para levantar {reward}",
+ "sticker_chart.claim_pending": "À espera que um adulto diga que sim",
+ "sticker_chart.claim_short": "Faltam {count} {unit}",
+ "sticker_chart.claimed": "{reward} levantado por {name}!",
+ "sticker_chart.complete_failed": "Não resultou: {error}",
+ "sticker_chart.editor.allow_undo": "Manter premido um autocolante para anular",
+ "sticker_chart.editor.allow_undo_helper": "Mantém premido um autocolante para o retirar. O TaskMate só permite isto aos pais.",
+ "sticker_chart.editor.child": "Criança",
+ "sticker_chart.editor.child_all": "Todas as crianças lado a lado",
+ "sticker_chart.editor.columns": "Autocolantes por linha",
+ "sticker_chart.editor.show_claim": "Mostrar o botão de levantar",
+ "sticker_chart.editor.show_claim_helper": "Um botão por baixo do objetivo que levanta a recompensa quando já se ganhou o suficiente (toca duas vezes para confirmar).",
+ "sticker_chart.editor.sound": "Tocar o som da tarefa",
+ "sticker_chart.editor.title_helper": "Deixa vazio para usar o nome de cada criança.",
+ "sticker_chart.goal_tile": "Objetivo",
+ "sticker_chart.needs_photo": "Esta tarefa precisa de uma fotografia - abre-a no cartão da criança do TaskMate.",
+ "sticker_chart.no_jobs_today": "Hoje não há tarefas - aproveita o dia!",
+ "sticker_chart.state.done": "feito",
+ "sticker_chart.state.partial": "começado",
+ "sticker_chart.state.pending": "à espera de um adulto",
+ "sticker_chart.state.todo": "por fazer",
+ "sticker_chart.not_allowed": "Este tablet não pode marcar as tarefas de {name}. Um adulto pode resolver no TaskMate: associar {name} à conta que este tablet usa, ou adicionar essa conta como pai/mãe.",
+ "sticker_chart.undo_denied": "Só um adulto pode retirar um autocolante.",
+ "sticker_chart.undone": "Autocolante retirado.",
"visibility.description_hidden": "Ocultado por condição de visibilidade",
"visibility.description_shown": "Mostrado por condição de visibilidade",
"visibility.label_entity": "Entidade de Visibilidade",
@@ -1980,6 +2031,8 @@
"panel.btn_clone_chore": "Duplicar",
"panel.child_link_user_label": "Associar a um utilizador do Home Assistant (opcional)",
"panel.child_link_user_hint": "Quando definido, apenas este utilizador do Home Assistant (ou um admin) pode concluir tarefas, resgatar recompensas ou iniciar temporizadores por esta criança via serviços. Deixe em \"Qualquer pessoa\" para um tablet familiar partilhado.",
+ "panel.child_picture_entity_label": "Fotografia de",
+ "panel.child_picture_entity_hint": "Escolhe a pessoa da criança para mostrar a fotografia em vez do ícone acima. A fotografia define-se em Definições › Pessoas; sem fotografia, é mostrado o ícone.",
"panel.child_kiosk_pin_label": "PIN do quiosque",
"panel.child_kiosk_pin_hint": "Opcional. 4 dígitos que a criança escreve num tablet de quiosque partilhado. Guardado com hash, não pode ser lido de volta — deixe em branco para manter o PIN atual.",
"panel.child_kiosk_pin_set": "Há um PIN definido.",
@@ -2085,6 +2138,16 @@
"panel.adjust_custom_title": "Ajustar os pontos de {child} com um valor personalizado",
"panel.adjust_dialog_title": "Ajustar pontos — {name}",
"panel.adjust_done": "{sign}{amount} {points} para {child}",
+ "panel.backdate_button": "Registar uma tarefa passada",
+ "panel.backdate_title": "Registar uma tarefa passada para {name}",
+ "panel.backdate_hint": "Escolhe o dia e toca nas tarefas que foram feitas, tal como no quadro de autocolantes. Contam para esse dia e essa semana, e ficam logo aprovadas. Mantém um autocolante premido para o retirar.",
+ "panel.backdate_hint_list": "Para uma tarefa feita num dia anterior que ninguém marcou. Conta para esse dia e essa semana, e fica logo aprovada.",
+ "panel.backdate_finish": "Concluído",
+ "panel.backdate_day": "Dia em que foi feita",
+ "panel.backdate_chore": "Tarefa",
+ "panel.backdate_none": "Não havia tarefas para {name} nesse dia.",
+ "panel.backdate_save": "Registar",
+ "panel.backdate_done": "{chore} registada para {name} em {day}",
"panel.adjust_err_amount": "Introduza um valor entre 1 e 10000.",
"panel.adjust_reason": "Motivo (opcional)",
"panel.adjust_remove": "Retirar",
diff --git a/custom_components/taskmate/www/taskmate-approvals-card.js b/custom_components/taskmate/www/taskmate-approvals-card.js
index c451ab6a..849a9f51 100644
--- a/custom_components/taskmate/www/taskmate-approvals-card.js
+++ b/custom_components/taskmate/www/taskmate-approvals-card.js
@@ -789,23 +789,26 @@ class TaskMateApprovalsCard extends LitElement {
_designTone(i) { return `var(--tmd-c${(i % 6) + 1})`; }
- _av(name, avatar, tone, size) {
- const a = avatar || "";
- const inner = a.startsWith("mdi:")
- ? html``
+ // `source` is whatever _childAvatar returned: a child object, or a bare
+ // avatar string from an older payload. The shared helper accepts either.
+ _av(name, source, tone, size) {
+ const face = window.__taskmate_child_visual(source);
+ const inner = face.kind === "icon"
+ ? html`
`
: (name || "?").split(" ").map(w => w[0]).slice(0, 2).join("").toUpperCase();
return html`