187 files
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Constants for TaskMate integration."""
|
||||
|
||||
import re
|
||||
from typing import Final
|
||||
|
||||
DOMAIN: Final = "taskmate"
|
||||
@@ -76,6 +77,28 @@ RECURRENCE_PERIOD_DAYS: Final = {
|
||||
"every_6_months": 182,
|
||||
}
|
||||
|
||||
# Custom "every N days" recurrences (#1038) are stored as ``every_<N>_days``;
|
||||
# the original every_2_days is simply the N=2 case. N is bounded so a typo
|
||||
# can't park a chore for years.
|
||||
MIN_RECURRENCE_INTERVAL_DAYS: Final = 2
|
||||
MAX_RECURRENCE_INTERVAL_DAYS: Final = 365
|
||||
_EVERY_N_DAYS_RE: Final = re.compile(r"every_(\d{1,3})_days")
|
||||
|
||||
|
||||
def recurrence_interval_days(recurrence: str | None) -> int | None:
|
||||
"""N for an ``every_<N>_days`` recurrence, else None (also when N is out of range)."""
|
||||
match = _EVERY_N_DAYS_RE.fullmatch(recurrence) if isinstance(recurrence, str) else None
|
||||
if not match:
|
||||
return None
|
||||
days = int(match.group(1))
|
||||
return days if MIN_RECURRENCE_INTERVAL_DAYS <= days <= MAX_RECURRENCE_INTERVAL_DAYS else None
|
||||
|
||||
|
||||
def recurrence_period_days(recurrence: str | None) -> int:
|
||||
"""Nominal period of a Mode-B recurrence in days (weekly when unknown)."""
|
||||
return recurrence_interval_days(recurrence) or RECURRENCE_PERIOD_DAYS.get(recurrence or "", 7)
|
||||
|
||||
|
||||
# Badge tiers, lowest to highest.
|
||||
BADGE_TIERS: Final = ["bronze", "silver", "gold", "platinum"]
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from .const import (
|
||||
MAX_CALENDAR_PROJECTION_DAYS,
|
||||
MIN_CALENDAR_PROJECTION_DAYS,
|
||||
TIME_CATEGORY_ICONS,
|
||||
recurrence_interval_days,
|
||||
)
|
||||
from .models import Chore
|
||||
|
||||
@@ -245,11 +246,12 @@ class CalendarMixin:
|
||||
pass
|
||||
return True
|
||||
|
||||
if recurrence == "every_2_days" and anchor_iso:
|
||||
interval_days = recurrence_interval_days(recurrence)
|
||||
if interval_days and anchor_iso:
|
||||
try:
|
||||
anchor = date.fromisoformat(anchor_iso)
|
||||
diff = (day - anchor).days
|
||||
return diff >= 0 and diff % 2 == 0
|
||||
return diff >= 0 and diff % interval_days == 0
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from datetime import timedelta
|
||||
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import notify_strings
|
||||
from .models import Challenge, PointsTransaction
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -173,7 +174,11 @@ class ChallengesMixin:
|
||||
await self._celebrate(
|
||||
child,
|
||||
"challenge_completed",
|
||||
f"{child.name} completed the challenge '{challenge.name}'!",
|
||||
notify_strings.render(
|
||||
self.hass,
|
||||
"celebrate_challenge_completed",
|
||||
{"child_name": child.name, "challenge_name": challenge.name},
|
||||
),
|
||||
tier=2,
|
||||
extra={"challenge_id": challenge.id, "bonus": bonus},
|
||||
)
|
||||
|
||||
@@ -9,9 +9,15 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import images, photos
|
||||
from . import images, notify_strings, photos
|
||||
from .chore_undo import child_can_undo, undo_window_seconds
|
||||
from .const import CHORE_NOTE_MAX_LENGTH, CHORE_SUGGESTED_POINTS_MAX, DAILY_PROGRESS_KEEP_DAYS, QUALITY_RATINGS
|
||||
from .const import (
|
||||
CHORE_NOTE_MAX_LENGTH,
|
||||
CHORE_SUGGESTED_POINTS_MAX,
|
||||
DAILY_PROGRESS_KEEP_DAYS,
|
||||
QUALITY_RATINGS,
|
||||
recurrence_interval_days,
|
||||
)
|
||||
from .coord_rejections import clean_reject_reason
|
||||
from .coord_teamwork import teamwork_config_error
|
||||
from .models import Chore, ChoreCompletion, PointsTransaction
|
||||
@@ -592,6 +598,7 @@ class ChoresMixin:
|
||||
self.storage.remove_chore(chore_id)
|
||||
self.storage.remove_completions_for_chore(chore_id)
|
||||
self.storage.remove_last_completed_for_chore(chore_id)
|
||||
self.storage.remove_weekly_target_bonuses(chore_id=chore_id)
|
||||
# Strip chore from any task group it belonged to.
|
||||
self.storage.remove_chore_from_task_groups(chore_id)
|
||||
# Drop queued scheduled changes (#675) — nothing left to apply them to.
|
||||
@@ -1025,6 +1032,7 @@ class ChoresMixin:
|
||||
completion.approved_at = dt_util.now()
|
||||
completion.points_awarded = total_awarded
|
||||
self.storage.update_completion(completion)
|
||||
await self._async_pay_weekly_target_bonus(chore, child, completion)
|
||||
|
||||
self.hass.bus.async_fire(
|
||||
"taskmate_chore_completed",
|
||||
@@ -1356,6 +1364,7 @@ class ChoresMixin:
|
||||
self.storage.update_completion(completion)
|
||||
if getattr(completion, "bounty_id", ""):
|
||||
self._bounty_on_approved(completion)
|
||||
await self._async_pay_weekly_target_bonus(chore, child, completion)
|
||||
|
||||
# Dismiss the mobile approval push now this completion is
|
||||
# reviewed (covers single approve AND "approve all", which
|
||||
@@ -1410,7 +1419,7 @@ class ChoresMixin:
|
||||
await self._celebrate(
|
||||
child,
|
||||
"all_chores_done",
|
||||
f"{child.name} finished every chore today!",
|
||||
notify_strings.render(self.hass, "celebrate_all_chores_done", {"child_name": child.name}),
|
||||
tier=1,
|
||||
)
|
||||
else:
|
||||
@@ -1499,6 +1508,8 @@ class ChoresMixin:
|
||||
)
|
||||
)
|
||||
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)
|
||||
|
||||
bonus_completions: list = []
|
||||
is_parent = not target_completion.bonus_subtask_id
|
||||
@@ -1823,11 +1834,8 @@ class ChoresMixin:
|
||||
now = dt_util.now()
|
||||
today = dt_util.as_local(now).date()
|
||||
|
||||
window_days = {
|
||||
"every_2_days": 2,
|
||||
"weekly": 7,
|
||||
"every_2_weeks": 14,
|
||||
}.get(recurrence, 7)
|
||||
interval_days = recurrence_interval_days(recurrence)
|
||||
window_days = interval_days or {"weekly": 7, "every_2_weeks": 14}.get(recurrence, 7)
|
||||
|
||||
record = self.storage.get_last_completed(chore.id, child_id)
|
||||
current_iso = record.get("current")
|
||||
@@ -1863,14 +1871,14 @@ class ChoresMixin:
|
||||
# from, so moving one week's chore doesn't shift every week after it.
|
||||
last_dt = self.occurrence_origin(chore, last_dt)
|
||||
|
||||
# every_2_days with anchor — check alignment
|
||||
if recurrence == "every_2_days" and recurrence_start:
|
||||
# every_<N>_days with anchor — check alignment
|
||||
if interval_days and recurrence_start:
|
||||
try:
|
||||
anchor = date.fromisoformat(recurrence_start)
|
||||
days_since_anchor = (today - anchor).days
|
||||
if days_since_anchor < 0:
|
||||
return False
|
||||
if days_since_anchor % 2 != 0:
|
||||
if days_since_anchor % interval_days != 0:
|
||||
return False
|
||||
return last_dt < today
|
||||
except ValueError:
|
||||
@@ -1920,6 +1928,125 @@ class ChoresMixin:
|
||||
return False
|
||||
return self.weekly_completion_count(chore.id, child_id) >= target
|
||||
|
||||
@staticmethod
|
||||
def _week_key(when) -> str:
|
||||
"""Monday (ISO date) of the local week ``when`` falls in."""
|
||||
day = dt_util.as_local(when).date()
|
||||
return (day - timedelta(days=day.weekday())).isoformat()
|
||||
|
||||
def _weekly_approved_count(self, chore_id: str, child_id: str, week: str, exclude_id: str = "") -> int:
|
||||
"""Approved, non-bonus completions of a chore by a child in ``week``.
|
||||
|
||||
Unlike ``weekly_completion_count`` this ignores pending work: the target
|
||||
bonus (#1044) is points, and points are only paid on approval.
|
||||
"""
|
||||
count = 0
|
||||
for comp in self.storage.get_completions():
|
||||
if (
|
||||
comp.id == exclude_id
|
||||
or comp.chore_id != chore_id
|
||||
or comp.child_id != child_id
|
||||
or not comp.approved
|
||||
or comp.bonus_subtask_id
|
||||
):
|
||||
continue
|
||||
try:
|
||||
if self._week_key(comp.completed_at) == week:
|
||||
count += 1
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
continue
|
||||
return count
|
||||
|
||||
async def _async_pay_weekly_target_bonus(self, chore, child, completion) -> None:
|
||||
"""Pay the chore's weekly target bonus once its week's quota is approved (#1044).
|
||||
|
||||
Paid once per chore, child and week — the week the completions belong
|
||||
to, so approving last Sunday's work on a Monday still settles last
|
||||
week. ``_reverse_weekly_target_bonus`` takes it back if the week drops
|
||||
below the target again.
|
||||
"""
|
||||
bonus = int(getattr(chore, "weekly_target_bonus", 0) or 0)
|
||||
target = int(getattr(chore, "weekly_target", 0) or 0)
|
||||
if bonus <= 0 or target <= 0 or completion.bonus_subtask_id:
|
||||
return
|
||||
week = self._week_key(completion.completed_at)
|
||||
paid = dict(self.storage.get_weekly_target_bonus(chore.id, child.id))
|
||||
if week in paid:
|
||||
return
|
||||
if self._weekly_approved_count(chore.id, child.id, week) < target:
|
||||
return
|
||||
# Claim the week before anything below can suspend, so two approvals
|
||||
# landing together can't both pay it. Only the last few weeks are kept.
|
||||
paid[week] = bonus
|
||||
self.storage.set_weekly_target_bonus(chore.id, child.id, dict(sorted(paid.items())[-4:]))
|
||||
child.points += bonus
|
||||
child.total_points_earned += bonus
|
||||
child.career_score = child.total_points_earned - child.total_penalties_received
|
||||
self.storage.add_points_transaction(
|
||||
PointsTransaction(
|
||||
child_id=child.id,
|
||||
points=bonus,
|
||||
reason=f"Weekly target bonus: {chore.name}",
|
||||
created_at=dt_util.now(),
|
||||
)
|
||||
)
|
||||
await self._maybe_level_up(child)
|
||||
self.storage.update_child(child)
|
||||
_LOGGER.info("Weekly target for '%s' reached by %s (+%d)", chore.name, child.name, bonus)
|
||||
self.hass.bus.async_fire(
|
||||
"taskmate_weekly_target_reached",
|
||||
{
|
||||
"child_id": child.id,
|
||||
"child_name": child.name,
|
||||
"chore_id": chore.id,
|
||||
"chore_name": chore.name,
|
||||
"target": target,
|
||||
"bonus": bonus,
|
||||
"timestamp": dt_util.now().isoformat(),
|
||||
},
|
||||
)
|
||||
await self._celebrate(
|
||||
child,
|
||||
"weekly_target_reached",
|
||||
notify_strings.render(
|
||||
self.hass,
|
||||
"celebrate_weekly_target",
|
||||
{"child_name": child.name, "chore_name": chore.name, "bonus": bonus},
|
||||
),
|
||||
tier=2,
|
||||
extra={"chore_id": chore.id, "bonus": bonus},
|
||||
)
|
||||
|
||||
def _reverse_weekly_target_bonus(self, completion, child) -> None:
|
||||
"""Refund a paid weekly target bonus the approved ``completion`` no
|
||||
longer leaves standing (#1044). The caller syncs the career score."""
|
||||
if completion.bonus_subtask_id or not child:
|
||||
return
|
||||
week = self._week_key(completion.completed_at)
|
||||
paid = dict(self.storage.get_weekly_target_bonus(completion.chore_id, child.id))
|
||||
if week not in paid:
|
||||
return
|
||||
chore = self.get_chore(completion.chore_id)
|
||||
target = int(getattr(chore, "weekly_target", 0) or 0)
|
||||
if target > 0 and (
|
||||
self._weekly_approved_count(completion.chore_id, child.id, week, exclude_id=completion.id) >= target
|
||||
):
|
||||
return
|
||||
refund = int(paid.pop(week) or 0)
|
||||
self.storage.set_weekly_target_bonus(completion.chore_id, child.id, paid or None)
|
||||
if refund <= 0:
|
||||
return
|
||||
child.points = max(0, child.points - refund)
|
||||
child.total_points_earned = max(0, child.total_points_earned - refund)
|
||||
self.storage.add_points_transaction(
|
||||
PointsTransaction(
|
||||
child_id=child.id,
|
||||
points=-refund,
|
||||
reason=f"Weekly target bonus reversed: {getattr(chore, 'name', '')}".rstrip(": "),
|
||||
created_at=dt_util.now(),
|
||||
)
|
||||
)
|
||||
|
||||
def _is_chore_completable_by_child(self, chore, child_id: str) -> bool:
|
||||
"""Whether ``child_id`` may complete ``chore`` right now (card parity).
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@ from __future__ import annotations
|
||||
import contextlib
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.event import async_track_state_change_event, async_track_time_change
|
||||
|
||||
from . import authz
|
||||
from . import authz, notify_strings
|
||||
from .const import (
|
||||
DEFAULT_NOTIFICATION_GROUP,
|
||||
DEFAULT_NOTIFICATION_NAV_URL,
|
||||
@@ -64,24 +64,24 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_MORNING_NOTIFY_TIME = "08:00"
|
||||
|
||||
# Appended to actionable notifications sent to non-mobile_app backends, which
|
||||
# silently ignore tap actions. Gives those recipients a way to act.
|
||||
_APPROVE_IN_PANEL_HINT = "Open the TaskMate panel to approve or reject."
|
||||
|
||||
# Mobile action id prefix for "approve with an N-star quality rating" (#927):
|
||||
# TASKMATE_RATE_<n>_<completion id>. Distinct from TASKMATE_APPROVE_ so the
|
||||
# original Approve/Reject ids keep working unchanged when ratings are off.
|
||||
_RATE_ACTION_PREFIX = "TASKMATE_RATE_"
|
||||
|
||||
# Stand-in names for a test push, translated like the rest (notify.sample_*).
|
||||
_SAMPLE_KEYS = ("chore", "reward", "badge", "goal", "goal_reward", "wish", "pledger", "bounty", "reason", "note")
|
||||
|
||||
def _reject_action(entry_id: str) -> dict[str, str]:
|
||||
|
||||
def _reject_action(hass: HomeAssistant, entry_id: str) -> dict[str, str]:
|
||||
"""The Reject button of an approval push, with a reply box for the reason (#976)."""
|
||||
reject = notify_strings.text(hass, "action_reject")
|
||||
return {
|
||||
"action": f"TASKMATE_REJECT_{entry_id}",
|
||||
"title": "Reject",
|
||||
"title": reject,
|
||||
"behavior": "textInput",
|
||||
"textInputButtonTitle": "Reject",
|
||||
"textInputPlaceholder": "Reason (optional)",
|
||||
"textInputButtonTitle": reject,
|
||||
"textInputPlaceholder": notify_strings.text(hass, "action_reject_placeholder"),
|
||||
}
|
||||
|
||||
|
||||
@@ -255,11 +255,7 @@ _parse_hhmm = parse_hhmm
|
||||
_is_within_quiet_hours = is_within_window
|
||||
|
||||
|
||||
class _SafeDict(dict):
|
||||
"""str.format_map dict that leaves missing keys as `{key}` literal."""
|
||||
|
||||
def __missing__(self, key: str) -> str:
|
||||
return "{" + key + "}"
|
||||
_SafeDict = notify_strings.SafeDict
|
||||
|
||||
|
||||
class NotificationCoordinator:
|
||||
@@ -300,6 +296,7 @@ class NotificationCoordinator:
|
||||
return
|
||||
|
||||
recipients_fired: list[str] = []
|
||||
await notify_strings.async_load(self.hass)
|
||||
message = self._render_template(meta, context)
|
||||
nav_url = self._resolve_nav_url(cfg)
|
||||
group = self._resolve_group(cfg)
|
||||
@@ -416,38 +413,54 @@ class NotificationCoordinator:
|
||||
meta = NOTIFICATION_TYPES_BY_ID.get(type_id)
|
||||
if meta is None:
|
||||
raise ValueError(f"Unknown notification type {type_id}")
|
||||
await notify_strings.async_load(self.hass)
|
||||
points_name = self.storage.get_points_name()
|
||||
sample_month = date(2026, 1, 1)
|
||||
sample = {key: notify_strings.text(self.hass, f"sample_{key}") for key in _SAMPLE_KEYS}
|
||||
ctx = {
|
||||
"child_name": "Alex",
|
||||
"chore_name": "Tidy room",
|
||||
"reward_name": "Movie night",
|
||||
"badge_name": "Star Helper",
|
||||
"chore_name": sample["chore"],
|
||||
"reward_name": sample["reward"],
|
||||
"badge_name": sample["badge"],
|
||||
"points": 10,
|
||||
"cost": 50,
|
||||
"streak": 7,
|
||||
"days": 7,
|
||||
"level": 5,
|
||||
"tier": 3,
|
||||
"message": "Alex reached level 5!",
|
||||
"summary": "• Alex: 5 chores, 50 Stars earned",
|
||||
"month": "January 2026",
|
||||
"goal_name": "Movie night fund",
|
||||
"goal_reward": "a family movie night",
|
||||
"message": notify_strings.render(self.hass, "celebrate_level_up", {"child_name": "Alex", "level": 5}),
|
||||
"summary": notify_strings.render(
|
||||
self.hass,
|
||||
"weekly_digest_line",
|
||||
{"child_name": "Alex", "chores": 5, "points": 50, "points_name": points_name},
|
||||
),
|
||||
"month": notify_strings.month_year(self.hass, sample_month),
|
||||
"goal_name": sample["goal"],
|
||||
"goal_reward": sample["goal_reward"],
|
||||
"multiplier": "2",
|
||||
"count": 3,
|
||||
"wish_name": "Lego set",
|
||||
"freezes_left": 2,
|
||||
"wish_name": sample["wish"],
|
||||
"target": 600,
|
||||
"pledger": "Grandma",
|
||||
"bounty_name": "Wash the car",
|
||||
"pledger": sample["pledger"],
|
||||
"bounty_name": sample["bounty"],
|
||||
"minutes": 15,
|
||||
"period": "January",
|
||||
"item_name": "Tidy room",
|
||||
"reason": "Not finished",
|
||||
"reason_text": ": Not finished",
|
||||
"period": notify_strings.month_name(self.hass, sample_month.month),
|
||||
"item_name": sample["chore"],
|
||||
"reason": sample["reason"],
|
||||
"reason_text": f": {sample['reason']}",
|
||||
"date": notify_strings.short_date(self.hass, sample_month),
|
||||
"max_points": 20,
|
||||
"until": "18:20",
|
||||
"bonus": 10,
|
||||
"note_text": ": Brilliant job!",
|
||||
"points_name": self.storage.get_points_name(),
|
||||
"note_text": f": {sample['note']}",
|
||||
"points_name": points_name,
|
||||
}
|
||||
ctx["result_text"] = notify_strings.render(
|
||||
self.hass,
|
||||
"auction_won",
|
||||
{**ctx, "points": 12},
|
||||
)
|
||||
message = "[TEST] " + self._render_template(meta, ctx)
|
||||
cfg = self.storage.get_notification_config(type_id)
|
||||
nav_url = self._resolve_nav_url(cfg)
|
||||
@@ -506,47 +519,17 @@ class NotificationCoordinator:
|
||||
return ""
|
||||
|
||||
def _render_template(self, meta: "NotificationTypeMeta", context: dict[str, Any]) -> str:
|
||||
# Built-in types use a baked-in default; will be replaced by translations
|
||||
# in a later task. For now use a safe English fallback so dispatch works.
|
||||
templates = {
|
||||
NOTIF_TYPE_BEDTIME_REMINDER: "{child_name}, you still have chores to do before bedtime.",
|
||||
NOTIF_TYPE_STREAK_AT_RISK: "{child_name}, complete a chore today to keep your {streak}-day streak!",
|
||||
NOTIF_TYPE_ALL_CHORES_DONE: "{child_name} finished every chore today!",
|
||||
NOTIF_TYPE_BADGE_EARNED: "{child_name} earned the {badge_name} badge!",
|
||||
NOTIF_TYPE_PENDING_CHORE_APPROVAL: "{child_name} completed '{chore_name}' (+{points} {points_name}) — awaiting approval.",
|
||||
NOTIF_TYPE_PENDING_REWARD_CLAIM: "{child_name} claimed '{reward_name}' ({cost} {points_name}) — awaiting approval.",
|
||||
NOTIF_TYPE_STREAK_MILESTONE: "{child_name} hit a {days}-day streak — +{points} {points_name}!",
|
||||
NOTIF_TYPE_LEVEL_UP: "{child_name} reached level {level}! 🎉",
|
||||
NOTIF_TYPE_WEEKLY_DIGEST: "TaskMate weekly digest:\n{summary}",
|
||||
NOTIF_TYPE_CELEBRATION: "🎉 {message}",
|
||||
NOTIF_TYPE_MANDATORY_REMINDER: "{child_name}, you still need to do '{chore_name}'.",
|
||||
NOTIF_TYPE_MANDATORY_PARENT_ALERT: "{child_name} still hasn't done the mandatory chore '{chore_name}'.",
|
||||
NOTIF_TYPE_MONTHLY_REPORT: "TaskMate {month} report:\n{summary}",
|
||||
NOTIF_TYPE_SEASON_CHAMPION: "🏆 {child_name} won the {month} leaderboard with {points} {points_name}!",
|
||||
NOTIF_TYPE_FAMILY_GOAL_REACHED: "🎉 Family goal reached: {goal_name}! Time for {goal_reward}.",
|
||||
NOTIF_TYPE_BIRTHDAY: "🎂 Happy birthday, {child_name}! Every chore pays {multiplier}× today.",
|
||||
NOTIF_TYPE_STREAK_FREEZE_USED: "❄️ A streak freeze saved {child_name}'s {streak}-day streak ({freezes_left} left).",
|
||||
NOTIF_TYPE_PRESENCE_ARRIVAL: "🏠 You're home, {child_name} — {count} chores left today.",
|
||||
NOTIF_TYPE_WISH_REQUESTED: "{child_name} wished for '{wish_name}' ({target} {points_name}) — waiting for your approval.",
|
||||
NOTIF_TYPE_WISH_PLEDGED: "💝 {pledger} added {points} {points_name} to your wish '{wish_name}'!",
|
||||
NOTIF_TYPE_BOUNTY_POSTED: "🏁 New bounty: {bounty_name} — {points} {points_name}. First to claim it gets it!",
|
||||
NOTIF_TYPE_BOUNTY_CLAIM_LAPSING: "⏳ {child_name}, {minutes} minutes left to finish '{bounty_name}' before it goes back on the board.",
|
||||
NOTIF_TYPE_RECAP_READY: "✨ Your {period} recap is ready, {child_name}! Tap to watch it.",
|
||||
NOTIF_TYPE_ITEM_REJECTED: "↩️ {child_name}, '{item_name}' was sent back{reason_text}",
|
||||
NOTIF_TYPE_AUCTION_OPENED: "🔨 New auction: {chore_name} on {date}, up to {max_points} {points_name}. Lowest bid wins!",
|
||||
NOTIF_TYPE_AUCTION_CLOSING: "⏳ {child_name}, bidding on '{chore_name}' closes in {minutes} minutes.",
|
||||
NOTIF_TYPE_AUCTION_RESULT: "🔨 Auction closed: {result_text}",
|
||||
NOTIF_TYPE_INSPECTION_STARTED: "🔍 {child_name}, a grown-up is coming to inspect '{chore_name}' before {until}. Keep it looking great for +{bonus} {points_name}!",
|
||||
NOTIF_TYPE_INSPECTION_PASSED: "🌟 Inspection passed, {child_name}! '{chore_name}' looked great: +{bonus} {points_name}{note_text}",
|
||||
NOTIF_TYPE_INSPECTION_REMINDER: "🔍 {child_name}'s '{chore_name}' inspection closes in {minutes} minutes — pass or fail it before {until}.",
|
||||
}
|
||||
tpl = context.get("message_template") or templates.get(meta.id, "")
|
||||
# Built-in types read their wording from the locale catalogue in HA's
|
||||
# language (#1037); a custom message_template is the user's own text.
|
||||
custom = context.get("message_template")
|
||||
if not custom:
|
||||
return notify_strings.render(self.hass, meta.id, context)
|
||||
try:
|
||||
return tpl.format_map(_SafeDict(context))
|
||||
return custom.format_map(_SafeDict(context))
|
||||
except (ValueError, IndexError, KeyError):
|
||||
# Malformed user template (e.g. stray '{') — fall back to raw text
|
||||
_LOGGER.warning("Malformed notification template %r, sending raw", tpl)
|
||||
return tpl
|
||||
_LOGGER.warning("Malformed notification template %r, sending raw", custom)
|
||||
return custom
|
||||
|
||||
async def _send_to(
|
||||
self,
|
||||
@@ -587,7 +570,7 @@ class NotificationCoordinator:
|
||||
push["tag"] = _approval_tag(entry_id)
|
||||
push["actions"] = self._approval_actions(meta.id, entry_id)
|
||||
else:
|
||||
data["message"] = f"{message} {_APPROVE_IN_PANEL_HINT}"
|
||||
data["message"] = f"{message} {notify_strings.text(self.hass, 'approve_in_panel_hint')}"
|
||||
|
||||
if attach_photo:
|
||||
# "image" renders inline on Android; iOS needs it under
|
||||
@@ -647,11 +630,11 @@ class NotificationCoordinator:
|
||||
if rated:
|
||||
return [
|
||||
*({"action": f"{_RATE_ACTION_PREFIX}{n}_{entry_id}", "title": "★" * n} for n in QUALITY_RATINGS),
|
||||
_reject_action(entry_id),
|
||||
_reject_action(self.hass, entry_id),
|
||||
]
|
||||
return [
|
||||
{"action": f"TASKMATE_APPROVE_{entry_id}", "title": "Approve"},
|
||||
_reject_action(entry_id),
|
||||
{"action": f"TASKMATE_APPROVE_{entry_id}", "title": notify_strings.text(self.hass, "action_approve")},
|
||||
_reject_action(self.hass, entry_id),
|
||||
]
|
||||
|
||||
async def clear_approval(self, type_id: str, entry_id: str) -> None:
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import photos
|
||||
from . import notify_strings, photos
|
||||
from .models import Bonus, Child, Penalty, PointsTransaction, generate_id
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -143,7 +143,11 @@ class PointsMixin:
|
||||
await self._celebrate(
|
||||
child,
|
||||
"perfect_week",
|
||||
f"{child.name} earned a perfect week — +{perfect_week_bonus}!",
|
||||
notify_strings.render(
|
||||
self.hass,
|
||||
"celebrate_perfect_week",
|
||||
{"child_name": child.name, "bonus": perfect_week_bonus},
|
||||
),
|
||||
tier=3,
|
||||
extra={"bonus": perfect_week_bonus},
|
||||
)
|
||||
@@ -489,6 +493,8 @@ class PointsMixin:
|
||||
"Weekend bonus",
|
||||
"Streak milestone bonus",
|
||||
"Perfect week bonus",
|
||||
# Paid and refunded alongside the completions that reach the target.
|
||||
"Weekly target bonus",
|
||||
"Allocated to pool:",
|
||||
"Pool refund",
|
||||
"Points decay",
|
||||
@@ -657,7 +663,7 @@ class PointsMixin:
|
||||
await self._celebrate(
|
||||
child,
|
||||
"level_up",
|
||||
f"{child.name} reached level {lvl}!",
|
||||
notify_strings.render(self.hass, "celebrate_level_up", {"child_name": child.name, "level": lvl}),
|
||||
tier=3 if lvl % 5 == 0 else 2,
|
||||
extra={"level": lvl},
|
||||
)
|
||||
@@ -1130,7 +1136,9 @@ class PointsMixin:
|
||||
await self._celebrate(
|
||||
child,
|
||||
"streak_milestone",
|
||||
f"{child.name} hit a {days}-day streak!",
|
||||
notify_strings.render(
|
||||
self.hass, "celebrate_streak_milestone", {"child_name": child.name, "days": days}
|
||||
),
|
||||
tier=3 if days >= 30 else 2,
|
||||
extra={"days": days},
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import logging
|
||||
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import notify_strings
|
||||
from .models import PointsTransaction, Quest
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -223,7 +224,11 @@ class QuestsMixin:
|
||||
await self._celebrate(
|
||||
child,
|
||||
"quest_completed",
|
||||
f"{child.name} completed the quest '{quest.name}'!",
|
||||
notify_strings.render(
|
||||
self.hass,
|
||||
"celebrate_quest_completed",
|
||||
{"child_name": child.name, "quest_name": quest.name},
|
||||
),
|
||||
tier=3,
|
||||
extra={"quest_id": quest.id, "bonus": bonus},
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.helpers.event import async_track_time_change
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import photos
|
||||
from . import notify_strings, photos
|
||||
from .const import DOMAIN
|
||||
from .coord_assignments import AssignmentsMixin
|
||||
from .coord_auctions import AuctionsMixin
|
||||
@@ -136,6 +136,7 @@ class TaskMateCoordinator(
|
||||
self.external_state_version: int = 0
|
||||
self._unsub_surprise: Callable[[], None] | None = None
|
||||
self._unsub_weekly: Callable[[], None] | None = None
|
||||
self._unsub_language: Callable[[], None] | None = None
|
||||
self._unsub_mandatory: list[Callable[[], None]] = []
|
||||
# Per-build memo for the availability matrix (PERF-1). Non-None only
|
||||
# inside `availability_build_scope()`; see coord_chores/coord_assignments.
|
||||
@@ -392,6 +393,10 @@ class TaskMateCoordinator(
|
||||
"""Initialize the coordinator."""
|
||||
await self.storage.async_load()
|
||||
self.notifications.coordinator = self
|
||||
# Notification wording follows HA's language (#1037): load it now, and
|
||||
# again whenever the language is changed.
|
||||
await notify_strings.async_load(self.hass)
|
||||
self._unsub_language = notify_strings.async_watch_language(self.hass)
|
||||
# Wire existing parent recipients to default-on parent-audience types
|
||||
# that have no routes yet (e.g. a parent added after the one-time seed),
|
||||
# so pending-approval notifications actually reach a parent.
|
||||
@@ -470,6 +475,7 @@ class TaskMateCoordinator(
|
||||
|
||||
async def _async_send_weekly_digest(self) -> None:
|
||||
"""Build and send the weekly digest to parents (opt-in)."""
|
||||
await notify_strings.async_load(self.hass)
|
||||
summary = self._build_weekly_digest()
|
||||
if not summary:
|
||||
return
|
||||
@@ -492,7 +498,14 @@ class TaskMateCoordinator(
|
||||
done[comp.child_id] = done.get(comp.child_id, 0) + 1
|
||||
earned[comp.child_id] = earned.get(comp.child_id, 0) + (comp.points_awarded or 0)
|
||||
pts = self.storage.get_points_name()
|
||||
lines = [f"• {c.name}: {done.get(c.id, 0)} chores, {earned.get(c.id, 0)} {pts} earned" for c in children]
|
||||
lines = [
|
||||
notify_strings.render(
|
||||
self.hass,
|
||||
"weekly_digest_line",
|
||||
{"child_name": c.name, "chores": done.get(c.id, 0), "points": earned.get(c.id, 0), "points_name": pts},
|
||||
)
|
||||
for c in children
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
async def _async_send_monthly_report(self) -> None:
|
||||
@@ -500,6 +513,7 @@ class TaskMateCoordinator(
|
||||
today = dt_util.now().date()
|
||||
month_end = today.replace(day=1) - timedelta(days=1)
|
||||
month_start = month_end.replace(day=1)
|
||||
await notify_strings.async_load(self.hass)
|
||||
summary = self._build_monthly_report(month_start, month_end)
|
||||
if not summary:
|
||||
return
|
||||
@@ -507,7 +521,7 @@ class TaskMateCoordinator(
|
||||
"monthly_report",
|
||||
{
|
||||
"summary": summary,
|
||||
"month": month_start.strftime("%B %Y"),
|
||||
"month": notify_strings.month_year(self.hass, month_start),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -528,8 +542,18 @@ class TaskMateCoordinator(
|
||||
earned[comp.child_id] = earned.get(comp.child_id, 0) + (comp.points_awarded or 0)
|
||||
pts = self.storage.get_points_name()
|
||||
lines = [
|
||||
f"• {c.name}: {done.get(c.id, 0)} chores, {earned.get(c.id, 0)} {pts}, "
|
||||
f"level {getattr(c, 'level', 1)}, best streak {getattr(c, 'best_streak', 0)}"
|
||||
notify_strings.render(
|
||||
self.hass,
|
||||
"monthly_report_line",
|
||||
{
|
||||
"child_name": c.name,
|
||||
"chores": done.get(c.id, 0),
|
||||
"points": earned.get(c.id, 0),
|
||||
"points_name": pts,
|
||||
"level": getattr(c, "level", 1),
|
||||
"streak": getattr(c, "best_streak", 0),
|
||||
},
|
||||
)
|
||||
for c in children
|
||||
]
|
||||
return "\n".join(lines)
|
||||
@@ -688,7 +712,7 @@ class TaskMateCoordinator(
|
||||
{
|
||||
"child_name": top["name"],
|
||||
"points": top["points"],
|
||||
"month": prev_end.strftime("%B %Y"),
|
||||
"month": notify_strings.month_year(self.hass, prev_end),
|
||||
"points_name": self.storage.get_points_name(),
|
||||
},
|
||||
)
|
||||
@@ -809,6 +833,7 @@ class TaskMateCoordinator(
|
||||
"_unsub_tag_scanned",
|
||||
"_unsub_surprise",
|
||||
"_unsub_weekly",
|
||||
"_unsub_language",
|
||||
"_unsub_auction_timer",
|
||||
):
|
||||
if unsub := getattr(self, attr, None):
|
||||
@@ -1011,6 +1036,7 @@ class TaskMateCoordinator(
|
||||
self.storage.remove_daily_progress_for_child(child_id)
|
||||
self.storage.remove_quest_progress_for_child(child_id)
|
||||
self.storage.remove_challenge_progress_for_child(child_id)
|
||||
self.storage.remove_weekly_target_bonuses(child_id=child_id)
|
||||
# Drop pending swap requests either side of this child (#785) — a
|
||||
# handover to or from a deleted child can never complete, and the
|
||||
# approval queue would render them as "?".
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
"iot_class": "calculated",
|
||||
"issue_tracker": "https://github.com/tempus2016/taskmate/issues",
|
||||
"requirements": [],
|
||||
"version": "6.1.0"
|
||||
"version": "6.2.0"
|
||||
}
|
||||
|
||||
@@ -346,6 +346,9 @@ class Chore:
|
||||
# and counts pending completions too, so a slow approval never hands the
|
||||
# child a spare go.
|
||||
weekly_target: int = 0
|
||||
# Extra points paid once a week when the weekly target is reached (#1044),
|
||||
# on top of each completion's own points. 0 = no bonus.
|
||||
weekly_target_bonus: int = 0
|
||||
completion_sound: str = "coin" # Sound to play on completion
|
||||
# Optional picture for the chore. Text-free pre-reader mode (#683) needs
|
||||
# one per chore; everything else falls back to the time-of-day icon.
|
||||
@@ -362,9 +365,11 @@ class Chore:
|
||||
schedule_mode: str = "specific_days"
|
||||
due_days: list[str] = field(default_factory=list) # Mode A: days to show chore
|
||||
# Mode B fields
|
||||
recurrence: str = "weekly" # every_2_days | weekly | every_2_weeks | monthly | every_3_months | every_6_months
|
||||
recurrence: str = (
|
||||
"weekly" # every_<N>_days (N 2-365) | weekly | every_2_weeks | monthly | every_3_months | every_6_months
|
||||
)
|
||||
recurrence_day: str = "" # optional: which day of week for weekly/every_2_weeks
|
||||
recurrence_start: str = "" # optional: ISO date anchor for every_2_days
|
||||
recurrence_start: str = "" # optional: ISO date anchor for every_<N>_days
|
||||
first_occurrence_mode: str = "available_immediately" # available_immediately | wait_for_first_occurrence
|
||||
# Dynamic visibility
|
||||
visibility_entity: str = "" # optional: entity_id to check for visibility
|
||||
@@ -479,6 +484,7 @@ class Chore:
|
||||
claim_allowance_minutes=max(0, int(data.get("claim_allowance_minutes", 0) or 0)),
|
||||
daily_limit=data.get("daily_limit", 1),
|
||||
weekly_target=int(data.get("weekly_target", 0) or 0),
|
||||
weekly_target_bonus=max(0, int(data.get("weekly_target_bonus", 0) or 0)),
|
||||
completion_sound=data.get("completion_sound", "coin"),
|
||||
icon=str(data.get("icon", "") or ""),
|
||||
image_url=str(data.get("image_url", "") or ""),
|
||||
@@ -552,6 +558,7 @@ class Chore:
|
||||
"claim_allowance_minutes": self.claim_allowance_minutes,
|
||||
"daily_limit": self.daily_limit,
|
||||
"weekly_target": self.weekly_target,
|
||||
"weekly_target_bonus": self.weekly_target_bonus,
|
||||
"completion_sound": self.completion_sound,
|
||||
"icon": self.icon,
|
||||
"image_url": self.image_url,
|
||||
|
||||
@@ -307,6 +307,9 @@ def _build_chores_list(coordinator: TaskMateCoordinator, common: dict) -> list[d
|
||||
weekly_target = int(getattr(c, "weekly_target", 0) or 0)
|
||||
if weekly_target:
|
||||
record["weekly_target"] = weekly_target
|
||||
weekly_target_bonus = int(getattr(c, "weekly_target_bonus", 0) or 0)
|
||||
if weekly_target_bonus:
|
||||
record["weekly_target_bonus"] = weekly_target_bonus
|
||||
claim_allowance_minutes = getattr(c, "claim_allowance_minutes", 0) or 0
|
||||
if claim_allowance_minutes:
|
||||
record["claim_allowance_minutes"] = claim_allowance_minutes
|
||||
|
||||
@@ -1523,6 +1523,33 @@ class TaskMateStorage:
|
||||
def set_challenge_child_progress(self, challenge_id: str, child_id: str, progress: dict) -> None:
|
||||
self.get_challenge_progress().setdefault(challenge_id, {})[child_id] = progress
|
||||
|
||||
def _weekly_target_bonuses(self) -> dict:
|
||||
"""Weekly-target bonuses paid (#1044): {"<chore_id>:<child_id>": {week, points}}."""
|
||||
bonuses = self._data.get("weekly_target_bonuses")
|
||||
if not isinstance(bonuses, dict):
|
||||
bonuses = self._data["weekly_target_bonuses"] = {}
|
||||
return bonuses
|
||||
|
||||
def get_weekly_target_bonus(self, chore_id: str, child_id: str) -> dict:
|
||||
record = self._weekly_target_bonuses().get(f"{chore_id}:{child_id}")
|
||||
return record if isinstance(record, dict) else {}
|
||||
|
||||
def set_weekly_target_bonus(self, chore_id: str, child_id: str, record: dict | None) -> None:
|
||||
"""Record (or with ``None``, forget) the bonus paid for a chore/child."""
|
||||
key = f"{chore_id}:{child_id}"
|
||||
if record is None:
|
||||
self._weekly_target_bonuses().pop(key, None)
|
||||
else:
|
||||
self._weekly_target_bonuses()[key] = record
|
||||
|
||||
def remove_weekly_target_bonuses(self, *, chore_id: str = "", child_id: str = "") -> None:
|
||||
"""Forget the bonuses paid for a removed chore or child."""
|
||||
bonuses = self._weekly_target_bonuses()
|
||||
for key in list(bonuses):
|
||||
chore, _, child = key.partition(":")
|
||||
if chore == chore_id or child == child_id:
|
||||
del bonuses[key]
|
||||
|
||||
def remove_challenge_progress_for_child(self, child_id: str) -> None:
|
||||
for child_map in self.get_challenge_progress().values():
|
||||
child_map.pop(child_id, None)
|
||||
@@ -1577,6 +1604,8 @@ class TaskMateStorage:
|
||||
self._data["quest_progress"] = {}
|
||||
if not isinstance(self._data.get("challenge_progress"), dict):
|
||||
self._data["challenge_progress"] = {}
|
||||
if not isinstance(self._data.get("weekly_target_bonuses"), dict):
|
||||
self._data["weekly_target_bonuses"] = {}
|
||||
self._sanitize_imported_records()
|
||||
|
||||
def _sanitize_imported_records(self) -> None:
|
||||
|
||||
@@ -75,13 +75,16 @@ from .const import (
|
||||
DIFFICULTY_TIERS,
|
||||
DOMAIN,
|
||||
MAX_CHORE_TAGS,
|
||||
MAX_RECURRENCE_INTERVAL_DAYS,
|
||||
MAX_TIME_PERIODS,
|
||||
MIN_RECURRENCE_INTERVAL_DAYS,
|
||||
SCHEDULE_MODES,
|
||||
TAG_ID_MAX_LENGTH,
|
||||
TEAM_POINTS_MODES,
|
||||
TEAM_SIZE_MAX,
|
||||
TIME_CATEGORY_ICONS,
|
||||
is_valid_completion_sound,
|
||||
recurrence_interval_days,
|
||||
)
|
||||
from .coord_birthdays import normalize_birthday
|
||||
from .coord_inspections import INSPECTION_BONUS_MAX, INSPECTION_FAIL_MODES, INSPECTION_WINDOWS
|
||||
@@ -673,6 +676,7 @@ _CHORE_EDITABLE_FIELDS = {
|
||||
"claim_allowance_minutes",
|
||||
"daily_limit",
|
||||
"weekly_target",
|
||||
"weekly_target_bonus",
|
||||
"completion_sound",
|
||||
"icon",
|
||||
"image_url",
|
||||
@@ -718,6 +722,16 @@ _CHORE_EDITABLE_FIELDS = {
|
||||
}
|
||||
|
||||
|
||||
def _recurrence(value):
|
||||
"""A recurrence name; a custom every_<N>_days interval (#1038) must have N in range."""
|
||||
text = str(value)
|
||||
if text.startswith("every_") and text.endswith("_days") and recurrence_interval_days(text) is None:
|
||||
raise vol.Invalid(
|
||||
f"Custom recurrence must be every {MIN_RECURRENCE_INTERVAL_DAYS}-{MAX_RECURRENCE_INTERVAL_DAYS} days"
|
||||
)
|
||||
return text
|
||||
|
||||
|
||||
def _tag_id_list(value):
|
||||
"""Validate the NFC / QR tag ids linked to a chore (#923).
|
||||
|
||||
@@ -749,13 +763,14 @@ def _chore_payload_schema(*, require_name: bool):
|
||||
# Weekly target (#883): 0 = no quota, which is the case for almost
|
||||
# every chore, so it has to be a valid value rather than a floor of 1.
|
||||
vol.Optional("weekly_target"): vol.All(int, vol.Range(min=0)),
|
||||
vol.Optional("weekly_target_bonus"): vol.All(int, vol.Range(min=0)),
|
||||
vol.Optional("completion_sound"): _completion_sound,
|
||||
vol.Optional("icon"): str,
|
||||
vol.Optional("image_url"): _image_url_or_blank,
|
||||
vol.Optional("difficulty"): vol.In(DIFFICULTY_TIERS),
|
||||
vol.Optional("schedule_mode"): vol.In(SCHEDULE_MODES),
|
||||
vol.Optional("due_days"): [str],
|
||||
vol.Optional("recurrence"): str,
|
||||
vol.Optional("recurrence"): _recurrence,
|
||||
vol.Optional("recurrence_day"): str,
|
||||
vol.Optional("recurrence_start"): str,
|
||||
vol.Optional("first_occurrence_mode"): str,
|
||||
@@ -2500,7 +2515,7 @@ async def _ws_templates_get(hass, connection, msg, coordinator):
|
||||
vol.Optional("completion_sound"): _completion_sound,
|
||||
vol.Optional("schedule_mode"): vol.In(SCHEDULE_MODES),
|
||||
vol.Optional("due_days"): [str],
|
||||
vol.Optional("recurrence"): str,
|
||||
vol.Optional("recurrence"): _recurrence,
|
||||
vol.Optional("recurrence_day"): str,
|
||||
vol.Optional("recurrence_start"): str,
|
||||
vol.Optional("first_occurrence_mode"): str,
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Giv straf",
|
||||
"approvals.postpone": "Udskyd",
|
||||
"approvals.dismiss": "Afvis",
|
||||
"approvals.penalty_points": "Straf: −{points} point",
|
||||
"approvals.penalty_points": "Straf: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "Ingen straf angivet",
|
||||
"approvals.timed_duration": "{count} min.",
|
||||
"badge.criteria_and": "OG",
|
||||
@@ -259,6 +259,7 @@
|
||||
"child.open_ended_tag": "Beskriv det",
|
||||
"child.weekly_target_done": "Klaret for denne uge",
|
||||
"child.weekly_target_progress": "{done}/{target} denne uge",
|
||||
"child.weekly_target_bonus_reached": "🎉 Ugens mål er nået! +{points}",
|
||||
"child.team_progress": "{joined} / {size} med",
|
||||
"child.team_you_joined": "Du er med",
|
||||
"child.team_join": "Vær med",
|
||||
@@ -332,8 +333,8 @@
|
||||
"child.paused": "På pause",
|
||||
"child.please_define_child_id": "Angiv et child_id",
|
||||
"child.please_define_entity": "Angiv en entitet (TaskMate-oversigtssensor)",
|
||||
"child.pts_per_min": "point/min.",
|
||||
"child.pts_per_n_min": "point/{count} min.",
|
||||
"child.pts_per_min": "{pointsName}/min.",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min.",
|
||||
"child.recurring": "Gentagende",
|
||||
"child.resume": "Fortsæt",
|
||||
"child.start": "Start",
|
||||
@@ -364,6 +365,8 @@
|
||||
"common.editor.header_colour_helper": "Baggrundsfarve på kortets overskrift",
|
||||
"common.editor.overview_entity": "Oversigtsentitet",
|
||||
"common.editor.overview_entity_helper": "TaskMates oversigtssensor",
|
||||
"common.editor.show_parent_actions": "Vis forældrehandlinger",
|
||||
"common.editor.show_parent_actions_helper": "Slå fra på et delt børnedashboard, så kortet vises præcis som et barn ser det, også når en forælder er logget ind. Kun visning — rettighederne ændres ikke.",
|
||||
"common.entity": "Entitet",
|
||||
"common.entity_not_found": "Entiteten blev ikke fundet: {entity}",
|
||||
"common.error": "Fejl",
|
||||
@@ -822,6 +825,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/uge",
|
||||
"panel.chore_weekly_target_hint": "Hvor mange gange om ugen den skal klares, på en af de valgte dage. Den tilbydes ikke længere, når antallet er nået, og nulstilles mandag.",
|
||||
"panel.chore_weekly_target_label": "Ugemål (0 = fra)",
|
||||
"panel.chore_weekly_target_bonus_label": "Bonus for ugens mål",
|
||||
"panel.chore_weekly_target_bonus_hint": "Ekstra point, når ugens mål er nået, oven i pointene for hver udførelse. Udbetales én gang om ugen, når den sidste udførelse er godkendt. 0 = ingen bonus.",
|
||||
"panel.chore_team_section": "Holdarbejde (valgfrit)",
|
||||
"panel.chore_team_hint": "En holdpligt tæller først, når så mange forskellige børn er med. Hvert barns tryk på Færdig melder dem til, og når holdet er fuldt, indsendes pligten for dem alle. Tilmeldinger nulstilles hver dag. Kræver tildelingen \"Alle\" og kan ikke være en åben opgave.",
|
||||
"panel.chore_team_size_label": "Holdstørrelse (0 = fra)",
|
||||
@@ -841,6 +846,9 @@
|
||||
"panel.chore_points_label": "Point",
|
||||
"panel.chore_points_per_window_label": "Point pr. periode",
|
||||
"panel.chore_recurrence_day_hint": "f.eks. monday — lad feltet være tomt for en hvilken som helst dag",
|
||||
"panel.chore_recurrence_interval_label": "Gentag hver (dage)",
|
||||
"panel.chore_recurrence_interval_hint": "Helt antal dage, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Angiv et antal dage mellem {min} og {max}",
|
||||
"panel.chore_recurrence_day_label": "Ugedag (kun ugentligt)",
|
||||
"panel.chore_recurrence_label": "Gentagelse",
|
||||
"panel.chore_recurrence_start_hint": "Startdato — tom = i dag",
|
||||
@@ -1277,8 +1285,10 @@
|
||||
"panel.penbon_table_name": "Navn",
|
||||
"panel.penbon_table_points": "Point",
|
||||
"panel.pending_tooltip": "{count} afventer — klik for at gennemgå",
|
||||
"panel.pts_display": "{count} point",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "Hver 2. dag",
|
||||
"panel.recurrence_every_n_days": "Hver N. dag (brugerdefineret)",
|
||||
"panel.recurrence_every_count_days": "Hver {count}. dag",
|
||||
"panel.recurrence_every_2_weeks": "Hver 2. uge",
|
||||
"panel.recurrence_every_3_months": "Hver 3. måned",
|
||||
"panel.recurrence_every_6_months": "Hver 6. måned",
|
||||
@@ -1528,7 +1538,7 @@
|
||||
"panel.template_builtin_template_packs": "Indbyggede skabelonpakker",
|
||||
"panel.template_builtin_templates": "Indbyggede skabeloner",
|
||||
"panel.template_chores_in_template": "Pligter i denne skabelon",
|
||||
"panel.template_confirm_bar": "{count} pligt(er) oprettes · {points} point i alt",
|
||||
"panel.template_confirm_bar": "{count} pligt(er) oprettes · {points} {points_name} i alt",
|
||||
"panel.template_create_btn": "Opret {count} pligt(er)",
|
||||
"panel.template_create_title": "Opret skabelon",
|
||||
"panel.template_custom": "Egne",
|
||||
@@ -1540,7 +1550,7 @@
|
||||
"panel.template_name_label": "Navn",
|
||||
"panel.template_picker_title": "Tilføj fra skabelon",
|
||||
"panel.template_preview_hint": "Gennemse og tilpas hver pligt, før den oprettes. Fold ud for at redigere alle felter, eller fjern med ✕.",
|
||||
"panel.template_pts_total": "{count} pligt(er) · {points} point i alt",
|
||||
"panel.template_pts_total": "{count} pligt(er) · {points} {points_name} i alt",
|
||||
"panel.template_requires_approval_label": "Kræver godkendelse",
|
||||
"panel.template_requires_approval_no": "Nej",
|
||||
"panel.template_requires_approval_yes": "Ja",
|
||||
@@ -1752,7 +1762,7 @@
|
||||
"points_display.this_week": "Denne uge",
|
||||
"points_display.total_points": "Point i alt",
|
||||
"points_display.weekly_plus": "+{count} denne uge",
|
||||
"points_display.weekly_pts": "{count} point",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Tilføj først pligter til dette barn",
|
||||
"reorder.card_description": "Et kort til at sortere hvert barns pligter efter tidspunkt",
|
||||
"reorder.card_name": "TaskMate Sorteringskort",
|
||||
@@ -1857,9 +1867,9 @@
|
||||
"streak.achievement.hundred_desc": "Klar 100 pligter",
|
||||
"streak.achievement.hundred_name": "100 klaret",
|
||||
"streak.achievement.points100_desc": "Optjen 100 point i alt",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Optjen 50 point i alt",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "14 dages streak",
|
||||
"streak.achievement.streak14_name": "2 uger",
|
||||
"streak.achievement.streak30_desc": "30 dages streak",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Indstillinger for overraskelsesbonus",
|
||||
"panel.settings_surprise_params_hint": "Daglig chance pr. barn og det tilfældige interval af point, der gives ved et hit.",
|
||||
"panel.settings_surprise_chance": "Chance %",
|
||||
"panel.settings_surprise_min": "Min. point",
|
||||
"panel.settings_surprise_max": "Maks. point",
|
||||
"panel.settings_surprise_min": "Min. {points_name}",
|
||||
"panel.settings_surprise_max": "Maks. {points_name}",
|
||||
"panel.reward_restock_label": "Automatisk genopfyldning",
|
||||
"panel.reward_restock_hint": "Nulstil jævnligt belønningens lager til et fast antal.",
|
||||
"panel.reward_restock_amount": "Fyld op til",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Begræns hvor mange point hvert barn kan bruge på belønninger pr. periode.",
|
||||
"panel.settings_spendcap_params_label": "Indstillinger for forbrugsloft",
|
||||
"panel.settings_spendcap_params_hint": "Maks. point der kan bruges pr. periode (0 = ingen grænse).",
|
||||
"panel.settings_spendcap_amount": "Loft (point)",
|
||||
"panel.settings_spendcap_amount": "Loft ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Opsparingsrente",
|
||||
"panel.settings_interest_enabled_hint": "Giv jævnligt hvert barn rente af deres disponible saldo (belønner opsparing).",
|
||||
"panel.settings_interest_params_label": "Indstillinger for opsparingsrente",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "{time} tilbage",
|
||||
"bounty.no_expiry": "Udløber ikke",
|
||||
"bounty.photo_tag": "foto",
|
||||
"bounty.pts": "P",
|
||||
"bounty.claim": "Tag den",
|
||||
"bounty.claim_hint": "Du får {hours} t til at blive færdig",
|
||||
"bounty.finish_first": "Gør din dusør færdig først",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Giv tilbage",
|
||||
"bounty.sibling_on_it": "{name} er i gang",
|
||||
"bounty.sibling_back_in": "tilbage på tavlen om {time}, hvis den ikke bliver færdig",
|
||||
"bounty.waiting": "Venter på, at en voksen tjekker · +{points} ★ afventer",
|
||||
"bounty.waiting": "Venter på, at en voksen tjekker · +{points} {pointsName} afventer",
|
||||
"bounty.undo": "Fortryd",
|
||||
"bounty.sibling_checking": "{name} er færdig — bliver tjekket",
|
||||
"bounty.you_earned": "Du tjente +{points} ★",
|
||||
"bounty.you_earned": "Du tjente +{points} {pointsName}",
|
||||
"bounty.done_by": "Lavet af {name}",
|
||||
"bounty.recent": "For nylig fuldført",
|
||||
"bounty.claim_title": "Tag \"{title}\"?",
|
||||
"bounty.claim_body": "Den er din i {hours} timer. Er den ikke lavet inden da, går den tilbage på tavlen til alle.",
|
||||
"bounty.not_now": "Ikke nu",
|
||||
"bounty.claim_it": "Tag den — {points} ★",
|
||||
"bounty.claim_it": "Tag den — {points} {pointsName}",
|
||||
"bounty.photo_title": "Vis os den færdige opgave",
|
||||
"bounty.photo_body": "\"{title}\" skal have et foto",
|
||||
"bounty.cancel": "Annuller",
|
||||
"bounty.take_photo": "Tag foto og send",
|
||||
"bounty.uploading": "Sender…",
|
||||
"bounty.yay_title": "Godt gået!",
|
||||
"bounty.yay_body": "En voksen tjekker \"{title}\", og så får du {points} ★.",
|
||||
"bounty.yay_body": "En voksen tjekker \"{title}\", og så får du {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d} d {h} t",
|
||||
"bounty.dur_hours": "{h} t {m} min",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Fuldført",
|
||||
"panel.bounty_status_expired": "Udløbet",
|
||||
"panel.bounty_claimed_meta": "{name} · {time} tilbage",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Ingen tog den · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Udløbet efter en reservation udløb · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Taget, men aldrig gjort færdig · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Vælg et barn i korteditoren for at se barnets auktioner.",
|
||||
"auction.results": "Resultater",
|
||||
"auction.load_failed": "Kunne ikke hente auktionerne: {error}",
|
||||
"auction.up_to": "op til {points} ★",
|
||||
"auction.up_to": "op til {points} {pointsName}",
|
||||
"auction.lowest_wins": "Laveste bud vinder",
|
||||
"auction.gauge_max": "maks. {points} ★",
|
||||
"auction.gauge_hint": "Normalt {points} ★ værd · byd det færreste antal point, du vil gøre det for",
|
||||
"auction.gauge_max": "maks. {points} {pointsName}",
|
||||
"auction.gauge_hint": "Normalt {points} {pointsName} værd · byd det færreste antal point, du vil gøre det for",
|
||||
"auction.your_bid": "Dit hemmelige bud",
|
||||
"auction.change": "Ændr",
|
||||
"auction.withdraw": "Træk tilbage",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Slet sidste ciffer",
|
||||
"auction.rule_secret": "Hemmeligt: ingen kan se dit tal.",
|
||||
"auction.rule_lowest": "Laveste bud vinder. Ved uafgjort vinder den, der bød først.",
|
||||
"auction.rule_min": "Buddene starter ved {points} ★.",
|
||||
"auction.rule_min": "Buddene starter ved {points} {pointsName}.",
|
||||
"auction.rule_win": "Hvis du vinder, gør du det {date} og får dit bud, når det er godkendt.",
|
||||
"auction.cancel": "Annuller",
|
||||
"auction.seal": "Forsegl mit bud · {points} ★",
|
||||
"auction.you_won": "Du vandt! {points} ★ er dine",
|
||||
"auction.seal": "Forsegl mit bud · {points} {pointsName}",
|
||||
"auction.you_won": "Du vandt! {points} {pointsName} er dine",
|
||||
"auction.you_won_sub": "når du har gjort det {date}, og en voksen har godkendt det",
|
||||
"auction.in_your_chores": "Den står i dine pligter {date}",
|
||||
"auction.sibling_won": "{name} vandt denne",
|
||||
"auction.sibling_won_sub": "for {points} ★. Laveste bud vinder, så prøv lidt lavere næste gang!",
|
||||
"auction.sibling_won_sub_bid": "for {points} ★ · du bød {bid} ★. Laveste bud vinder, så prøv lidt lavere næste gang!",
|
||||
"auction.tied_sub": "I bød begge {points} ★. Det blev uafgjort, og {name} nåede at byde først.",
|
||||
"auction.sibling_won_sub": "for {points} {pointsName}. Laveste bud vinder, så prøv lidt lavere næste gang!",
|
||||
"auction.sibling_won_sub_bid": "for {points} {pointsName} · du bød {bid} {pointsName}. Laveste bud vinder, så prøv lidt lavere næste gang!",
|
||||
"auction.tied_sub": "I bød begge {points} {pointsName}. Det blev uafgjort, og {name} nåede at byde først.",
|
||||
"auction.no_bids": "Ingen bud denne gang",
|
||||
"auction.no_bids_sub": "{chore} går tilbage til den normale fordeling {date}.",
|
||||
"auction.closed_at": "Sluttede {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Slutter",
|
||||
"panel.auction_col_result": "Resultat",
|
||||
"panel.auction_col_status": "Pligtens status",
|
||||
"panel.auction_normally": "{date} · normalt {points} ★",
|
||||
"panel.auction_min_meta": "min. {points} ★",
|
||||
"panel.auction_normally": "{date} · normalt {points} {points_name}",
|
||||
"panel.auction_min_meta": "min. {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "Ingen bud endnu",
|
||||
"panel.auction_no_bids": "Ingen bud",
|
||||
"panel.auction_sealed": "forseglet",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "Ingen kommende gang af denne pligt kan sættes på auktion inden for de næste 60 dage.",
|
||||
"panel.auction_no_chores": "Ingen pligt kan sættes på auktion endnu. Holdpligter, tidsbaserede, åbne og ikke-tildelte pligter kan ikke.",
|
||||
"panel.auction_max_label": "Højeste bud",
|
||||
"panel.auction_normally_worth": "normalt {points} ★ værd",
|
||||
"panel.auction_normally_worth": "normalt {points} {points_name} værd",
|
||||
"panel.auction_max_hint": "Børnene kan byde fra minimumsbuddet op til dette. Laveste vinder; ved uafgjort vinder det tidligste bud.",
|
||||
"panel.auction_min_label": "Mindste bud",
|
||||
"panel.auction_min_hint": "Fra: ethvert bud fra 1 tæller. Til: stopper et kapløb mod bunden.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Fremskridt mod næste belønning",
|
||||
"kiosk.editor.show_picker_progress": "Fremskridt i vælgeren",
|
||||
"kiosk.editor.show_picker_progress_helper": "“3 af 6 klaret” under hvert ansigt",
|
||||
"kiosk.editor.play_sounds": "Lyde",
|
||||
"kiosk.editor.play_sounds_helper": "Hver opgaves færdig-lyd og fortryd-lyden",
|
||||
"kiosk.editor.show_confetti": "Konfetti",
|
||||
"kiosk.editor.show_completion_popup": "Fejrings-popup",
|
||||
"kiosk.editor.show_completion_popup_helper": "Et kort “Super!” med point efter hver opgave",
|
||||
"kiosk.editor.children_heading": "Viste børn og PIN-koder",
|
||||
"kiosk.editor.children_hint": "Fjern fluebenet for at skjule et barn. Brug pilene til at bestemme rækkefølgen i vælgeren.",
|
||||
"kiosk.editor.require_pin": "Kræv PIN-kode",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Slå fra for en tavle, der kun kan ses",
|
||||
"chore_board.editor.show_legend": "Vis forklaring",
|
||||
"chore_board.editor.hide_done_periods": "Skjul færdige tidspunkter",
|
||||
"chore_board.editor.hide_done_periods_helper": "Fjern et tidspunkt på dagen, når alle børn har klaret deres pligter"
|
||||
"chore_board.editor.hide_done_periods_helper": "Fjern et tidspunkt på dagen, når alle børn har klaret deres pligter",
|
||||
"notify.bedtime_reminder": "{child_name}, du har stadig opgaver at lave før sengetid.",
|
||||
"notify.streak_at_risk": "{child_name}, lav en opgave i dag for at holde din stime på {streak} dage!",
|
||||
"notify.all_chores_done": "{child_name} har klaret alle dagens opgaver!",
|
||||
"notify.badge_earned": "{child_name} har fået mærket \"{badge_name}\"!",
|
||||
"notify.pending_chore_approval": "{child_name} har lavet \"{chore_name}\" (+{points} {points_name}) — venter på godkendelse.",
|
||||
"notify.pending_reward_claim": "{child_name} har indløst \"{reward_name}\" ({cost} {points_name}) — venter på godkendelse.",
|
||||
"notify.streak_milestone": "{child_name} har nået en stime på {days} dage — +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} er nået til niveau {level}! 🎉",
|
||||
"notify.weekly_digest": "TaskMate ugeoversigt:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, du mangler stadig at lave \"{chore_name}\".",
|
||||
"notify.mandatory_parent_alert": "{child_name} har stadig ikke lavet den obligatoriske opgave \"{chore_name}\".",
|
||||
"notify.monthly_report": "TaskMate rapport for {month}:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} vandt ranglisten for {month} med {points} {points_name}!",
|
||||
"notify.family_goal_reached": "🎉 Familiemål nået: {goal_name}! Tid til {goal_reward}.",
|
||||
"notify.birthday": "🎂 Tillykke med fødselsdagen, {child_name}! Alle opgaver giver {multiplier}× i dag.",
|
||||
"notify.streak_freeze_used": "❄️ En stimefrysning reddede {child_name}s stime på {streak} dage ({freezes_left} tilbage).",
|
||||
"notify.presence_arrival": "🏠 Du er hjemme, {child_name} — {count} opgaver tilbage i dag.",
|
||||
"notify.wish_requested": "{child_name} ønsker sig \"{wish_name}\" ({target} {points_name}) — venter på din godkendelse.",
|
||||
"notify.wish_pledged": "💝 {pledger} har lagt {points} {points_name} i dit ønske \"{wish_name}\"!",
|
||||
"notify.bounty_posted": "🏁 Ny dusør: {bounty_name} — {points} {points_name}. Den første, der tager den, får den!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, {minutes} minutter tilbage til at gøre \"{bounty_name}\" færdig, før den kommer tilbage på tavlen.",
|
||||
"notify.recap_ready": "✨ Dit tilbageblik er klar ({period}), {child_name}! Tryk for at se det.",
|
||||
"notify.item_rejected": "↩️ {child_name}, \"{item_name}\" blev sendt tilbage{reason_text}",
|
||||
"notify.auction_opened": "🔨 Ny auktion: {chore_name} den {date}, op til {max_points} {points_name}. Laveste bud vinder!",
|
||||
"notify.auction_closing": "⏳ {child_name}, budrunden på \"{chore_name}\" lukker om {minutes} minutter.",
|
||||
"notify.auction_result": "🔨 Auktion afsluttet: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, en voksen kommer og tjekker \"{chore_name}\" før {until}. Hold det pænt for +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Inspektion bestået, {child_name}! \"{chore_name}\" så flot ud: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 Inspektionen af {child_name}s \"{chore_name}\" lukker om {minutes} minutter — godkend eller afvis den før {until}.",
|
||||
"notify.auction_won": "{child_name} vandt \"{chore_name}\" den {date} for {points} {points_name}.",
|
||||
"notify.auction_no_bids": "ingen bud på \"{chore_name}\", så den går tilbage til den normale tur.",
|
||||
"notify.auction_called_off": "auktionen over \"{chore_name}\" blev aflyst. Den går tilbage til den normale tur.",
|
||||
"notify.weekly_digest_line": "• {child_name}: {chores} opgaver, {points} {points_name} optjent",
|
||||
"notify.monthly_report_line": "• {child_name}: {chores} opgaver, {points} {points_name}, niveau {level}, bedste stime {streak}",
|
||||
"notify.action_approve": "Godkend",
|
||||
"notify.action_reject": "Afvis",
|
||||
"notify.action_reject_placeholder": "Begrundelse (valgfri)",
|
||||
"notify.approve_in_panel_hint": "Åbn TaskMate-panelet for at godkende eller afvise.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} har klaret alle dagens opgaver!",
|
||||
"notify.celebrate_birthday": "Tillykke med fødselsdagen, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} har klaret udfordringen \"{challenge_name}\"!",
|
||||
"notify.celebrate_inspection_passed": "{child_name}s \"{chore_name}\" bestod inspektionen!",
|
||||
"notify.celebrate_level_up": "{child_name} er nået til niveau {level}!",
|
||||
"notify.celebrate_perfect_week": "{child_name} har haft en perfekt uge — +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} har nået ugens mål for \"{chore_name}\" — +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} har gennemført missionen \"{quest_name}\"!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} har nået en stime på {days} dage!",
|
||||
"notify.recap_ready_many": "✨ {count} nye tilbageblik er klar, {child_name}! Tryk for at se dem.",
|
||||
"notify.recap_parent_one": "✨ Nye tilbageblik ({period}) er klar til {names}. Tryk for at se dem.",
|
||||
"notify.recap_parent_many": "✨ Nye tilbageblik er klar til {names}. Tryk for at se dem.",
|
||||
"notify.period_weekly": "uge",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} og {last}",
|
||||
"notify.month_year": "{month} {year}",
|
||||
"notify.short_date": "{weekday} d. {day}. {month}",
|
||||
"notify.month_1": "januar",
|
||||
"notify.month_2": "februar",
|
||||
"notify.month_3": "marts",
|
||||
"notify.month_4": "april",
|
||||
"notify.month_5": "maj",
|
||||
"notify.month_6": "juni",
|
||||
"notify.month_7": "juli",
|
||||
"notify.month_8": "august",
|
||||
"notify.month_9": "september",
|
||||
"notify.month_10": "oktober",
|
||||
"notify.month_11": "november",
|
||||
"notify.month_12": "december",
|
||||
"notify.month_short_1": "jan.",
|
||||
"notify.month_short_2": "feb.",
|
||||
"notify.month_short_3": "mar.",
|
||||
"notify.month_short_4": "apr.",
|
||||
"notify.month_short_5": "maj",
|
||||
"notify.month_short_6": "jun.",
|
||||
"notify.month_short_7": "jul.",
|
||||
"notify.month_short_8": "aug.",
|
||||
"notify.month_short_9": "sep.",
|
||||
"notify.month_short_10": "okt.",
|
||||
"notify.month_short_11": "nov.",
|
||||
"notify.month_short_12": "dec.",
|
||||
"notify.weekday_short_1": "man.",
|
||||
"notify.weekday_short_2": "tir.",
|
||||
"notify.weekday_short_3": "ons.",
|
||||
"notify.weekday_short_4": "tor.",
|
||||
"notify.weekday_short_5": "fre.",
|
||||
"notify.weekday_short_6": "lør.",
|
||||
"notify.weekday_short_7": "søn.",
|
||||
"notify.sample_chore": "Ryd op på værelset",
|
||||
"notify.sample_reward": "Filmaften",
|
||||
"notify.sample_badge": "Superhjælper",
|
||||
"notify.sample_goal": "Filmaftenskasse",
|
||||
"notify.sample_goal_reward": "en filmaften for hele familien",
|
||||
"notify.sample_wish": "Lego-sæt",
|
||||
"notify.sample_pledger": "Mormor",
|
||||
"notify.sample_bounty": "Vask bilen",
|
||||
"notify.sample_reason": "Ikke færdig",
|
||||
"notify.sample_note": "Flot klaret!"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Strafe anwenden",
|
||||
"approvals.postpone": "Verschieben",
|
||||
"approvals.dismiss": "Verwerfen",
|
||||
"approvals.penalty_points": "Strafe: −{points} Pkt.",
|
||||
"approvals.penalty_points": "Strafe: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "Keine Strafe festgelegt",
|
||||
"approvals.timed_duration": "{count} Min.",
|
||||
"badge.criteria_and": "UND",
|
||||
@@ -253,6 +253,7 @@
|
||||
"child.open_ended_tag": "Beschreiben",
|
||||
"child.weekly_target_done": "Für diese Woche erledigt",
|
||||
"child.weekly_target_progress": "{done}/{target} diese Woche",
|
||||
"child.weekly_target_bonus_reached": "🎉 Wochenziel erreicht! +{points}",
|
||||
"child.team_progress": "{joined} / {size} dabei",
|
||||
"child.team_you_joined": "Du bist dabei",
|
||||
"child.team_join": "Mitmachen",
|
||||
@@ -326,8 +327,8 @@
|
||||
"child.paused": "Pausiert",
|
||||
"child.please_define_child_id": "Bitte definieren Sie eine child_id",
|
||||
"child.please_define_entity": "Bitte definieren Sie eine Entität (Taskmate-Übersichtssensor)",
|
||||
"child.pts_per_min": "Pkt./Min.",
|
||||
"child.pts_per_n_min": "Pkt./{count} Min.",
|
||||
"child.pts_per_min": "{pointsName}/Min.",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} Min.",
|
||||
"child.recurring": "Wiederkehrend",
|
||||
"child.resume": "Fortsetzen",
|
||||
"child.start": "Starten",
|
||||
@@ -354,6 +355,8 @@
|
||||
"common.editor.header_colour_helper": "Hintergrundfarbe der Kartenkopfzeile",
|
||||
"common.editor.overview_entity": "Übersichtseinheit",
|
||||
"common.editor.overview_entity_helper": "Die TaskMate-Übersichtssensoreinheit",
|
||||
"common.editor.show_parent_actions": "Elternaktionen anzeigen",
|
||||
"common.editor.show_parent_actions_helper": "Auf einem gemeinsamen Kinder-Dashboard ausschalten, damit die Karte genau wie für ein Kind angezeigt wird, auch wenn ein Elternteil angemeldet ist. Nur die Anzeige — Berechtigungen bleiben unverändert.",
|
||||
"common.entity": "Entität",
|
||||
"common.entity_not_found": "Entität nicht gefunden: {entity}",
|
||||
"common.evening": "Abend",
|
||||
@@ -772,6 +775,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/Woche",
|
||||
"panel.chore_weekly_target_hint": "Wie oft pro Woche die Aufgabe erledigt werden muss, an beliebigen der gewählten Tage. Ist die Anzahl erreicht, wird sie nicht mehr angezeigt; montags beginnt die Zählung neu.",
|
||||
"panel.chore_weekly_target_label": "Wochenziel (0 = aus)",
|
||||
"panel.chore_weekly_target_bonus_label": "Bonus für das Wochenziel",
|
||||
"panel.chore_weekly_target_bonus_hint": "Zusätzliche Punkte, sobald das Wochenziel erreicht ist – zusätzlich zu den Punkten jeder Erledigung. Wird einmal pro Woche gutgeschrieben, wenn die letzte Erledigung genehmigt ist. 0 = kein Bonus.",
|
||||
"panel.chore_team_section": "Teamarbeit (optional)",
|
||||
"panel.chore_team_hint": "Eine Teamaufgabe zählt erst, wenn so viele verschiedene Kinder mitmachen. Jedes Tippen auf Fertig ist ein Beitritt, und sobald das Team voll ist, wird die Aufgabe für alle eingereicht. Beitritte werden jeden Tag zurückgesetzt. Erfordert die Zuweisung „Alle“ und kann keine offene Aufgabe sein.",
|
||||
"panel.chore_team_size_label": "Teamgröße (0 = aus)",
|
||||
@@ -791,6 +796,9 @@
|
||||
"panel.chore_points_label": "Punkte",
|
||||
"panel.chore_points_per_window_label": "Punkte pro Zeitfenster",
|
||||
"panel.chore_recurrence_day_hint": "z.B. Montag — leer lassen für beliebigen Tag",
|
||||
"panel.chore_recurrence_interval_label": "Wiederholen alle (Tage)",
|
||||
"panel.chore_recurrence_interval_hint": "Ganze Anzahl Tage, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Gib eine Anzahl Tage zwischen {min} und {max} ein",
|
||||
"panel.chore_recurrence_day_label": "Wochentag (nur wöchentlich)",
|
||||
"panel.chore_recurrence_label": "Wiederholung",
|
||||
"panel.chore_recurrence_start_hint": "Ankerdatum — leer = heute",
|
||||
@@ -1227,8 +1235,10 @@
|
||||
"panel.penbon_table_name": "Name",
|
||||
"panel.penbon_table_points": "Punkte",
|
||||
"panel.pending_tooltip": "{count} ausstehend — klicken zum Überprüfen",
|
||||
"panel.pts_display": "{count} Pkt.",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "Alle 2 Tage",
|
||||
"panel.recurrence_every_n_days": "Alle N Tage (benutzerdefiniert)",
|
||||
"panel.recurrence_every_count_days": "Alle {count} Tage",
|
||||
"panel.recurrence_every_2_weeks": "Alle 2 Wochen",
|
||||
"panel.recurrence_every_3_months": "Alle 3 Monate",
|
||||
"panel.recurrence_every_6_months": "Alle 6 Monate",
|
||||
@@ -1464,7 +1474,7 @@
|
||||
"panel.template_builtin_template_packs": "Integrierte Vorlagenpakete",
|
||||
"panel.template_builtin_templates": "Integrierte Vorlagen",
|
||||
"panel.template_chores_in_template": "Aufgaben in dieser Vorlage",
|
||||
"panel.template_confirm_bar": "{count} Aufgabe(n) werden erstellt · {points} Pkt. gesamt",
|
||||
"panel.template_confirm_bar": "{count} Aufgabe(n) werden erstellt · {points} {points_name} gesamt",
|
||||
"panel.template_create_btn": "{count} Aufgabe(n) erstellen",
|
||||
"panel.template_create_title": "Vorlage erstellen",
|
||||
"panel.template_custom": "Benutzerdefiniert",
|
||||
@@ -1476,7 +1486,7 @@
|
||||
"panel.template_name_label": "Name",
|
||||
"panel.template_picker_title": "Aus Vorlage hinzufügen",
|
||||
"panel.template_preview_hint": "Überprüfe und passe jede Aufgabe vor dem Erstellen an. Zum Bearbeiten aufklappen oder mit ✕ entfernen.",
|
||||
"panel.template_pts_total": "{count} Aufgabe(n) · {points} Pkt. gesamt",
|
||||
"panel.template_pts_total": "{count} Aufgabe(n) · {points} {points_name} gesamt",
|
||||
"panel.template_requires_approval_label": "Erfordert Genehmigung",
|
||||
"panel.template_requires_approval_no": "Nein",
|
||||
"panel.template_requires_approval_yes": "Ja",
|
||||
@@ -1687,7 +1697,7 @@
|
||||
"points_display.this_week": "Diese Woche",
|
||||
"points_display.total_points": "Gesamtpunktzahl",
|
||||
"points_display.weekly_plus": "+{count} diese Woche",
|
||||
"points_display.weekly_pts": "{count} Punkte",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Fügen Sie diesem Kind zuerst Aufgaben hinzu",
|
||||
"reorder.card_description": "Eine Karte zum Neuordnen der Aufgaben pro Kind, geordnet nach Zeitkategorie",
|
||||
"reorder.card_name": "TaskMate-Nachbestellungskarte",
|
||||
@@ -1792,9 +1802,9 @@
|
||||
"streak.achievement.hundred_desc": "Erledige 100 Aufgaben",
|
||||
"streak.achievement.hundred_name": "100 Fertig",
|
||||
"streak.achievement.points100_desc": "Verdienen Sie insgesamt 100 Punkte",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Verdienen Sie insgesamt 50 Punkte",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "14-tägige Serie",
|
||||
"streak.achievement.streak14_name": "2 Wochen",
|
||||
"streak.achievement.streak30_desc": "30-Tage-Strecke",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Überraschungsbonus-Einstellungen",
|
||||
"panel.settings_surprise_params_hint": "Tägliche Chance pro Kind und der zufällige Punktebereich bei einem Treffer.",
|
||||
"panel.settings_surprise_chance": "Chance %",
|
||||
"panel.settings_surprise_min": "Min. Pkt.",
|
||||
"panel.settings_surprise_max": "Max. Pkt.",
|
||||
"panel.settings_surprise_min": "Min. {points_name}",
|
||||
"panel.settings_surprise_max": "Max. {points_name}",
|
||||
"panel.reward_restock_label": "Auto-Auffüllen",
|
||||
"panel.reward_restock_hint": "Den Bestand dieser Belohnung regelmäßig auf einen festen Wert zurücksetzen.",
|
||||
"panel.reward_restock_amount": "Auffüllen auf",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Begrenzt, wie viele Punkte jedes Kind pro Zeitraum für Belohnungen ausgeben kann.",
|
||||
"panel.settings_spendcap_params_label": "Einstellungen für Ausgabenlimit",
|
||||
"panel.settings_spendcap_params_hint": "Maximal ausgebbare Punkte pro Zeitraum (0 = kein Limit).",
|
||||
"panel.settings_spendcap_amount": "Limit (Pkt)",
|
||||
"panel.settings_spendcap_amount": "Limit ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Sparzinsen",
|
||||
"panel.settings_interest_enabled_hint": "Zahlt jedem Kind regelmäßig Zinsen auf sein verfügbares Guthaben (belohnt Sparen).",
|
||||
"panel.settings_interest_params_label": "Einstellungen für Sparzinsen",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "noch {time}",
|
||||
"bounty.no_expiry": "Kein Ablauf",
|
||||
"bounty.photo_tag": "Foto",
|
||||
"bounty.pts": "PKT",
|
||||
"bounty.claim": "Übernehmen",
|
||||
"bounty.claim_hint": "Du hast dann {hours} Std. Zeit",
|
||||
"bounty.finish_first": "Erledige erst deinen Sonderjob",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Zurückgeben",
|
||||
"bounty.sibling_on_it": "{name} ist dran",
|
||||
"bounty.sibling_back_in": "wieder frei in {time}, falls nicht erledigt",
|
||||
"bounty.waiting": "Wartet auf Prüfung durch Erwachsene · +{points} ★ ausstehend",
|
||||
"bounty.waiting": "Wartet auf Prüfung durch Erwachsene · +{points} {pointsName} ausstehend",
|
||||
"bounty.undo": "Rückgängig",
|
||||
"bounty.sibling_checking": "{name} ist fertig — wird geprüft",
|
||||
"bounty.you_earned": "Du hast +{points} ★ verdient",
|
||||
"bounty.you_earned": "Du hast +{points} {pointsName} verdient",
|
||||
"bounty.done_by": "Erledigt von {name}",
|
||||
"bounty.recent": "Kürzlich erledigt",
|
||||
"bounty.claim_title": "„{title}“ übernehmen?",
|
||||
"bounty.claim_body": "Der Job gehört {hours} Stunden lang dir. Ist er bis dahin nicht erledigt, kommt er zurück an die Tafel für alle.",
|
||||
"bounty.not_now": "Jetzt nicht",
|
||||
"bounty.claim_it": "Übernehmen — {points} ★",
|
||||
"bounty.claim_it": "Übernehmen — {points} {pointsName}",
|
||||
"bounty.photo_title": "Zeig uns den fertigen Job",
|
||||
"bounty.photo_body": "„{title}“ braucht ein Foto",
|
||||
"bounty.cancel": "Abbrechen",
|
||||
"bounty.take_photo": "Foto machen & senden",
|
||||
"bounty.uploading": "Wird gesendet…",
|
||||
"bounty.yay_title": "Super gemacht!",
|
||||
"bounty.yay_body": "Ein Erwachsener prüft „{title}“ und dann bekommst du {points} ★.",
|
||||
"bounty.yay_body": "Ein Erwachsener prüft „{title}“ und dann bekommst du {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d} T. {h} Std.",
|
||||
"bounty.dur_hours": "{h} Std. {m} Min.",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Erledigt",
|
||||
"panel.bounty_status_expired": "Abgelaufen",
|
||||
"panel.bounty_claimed_meta": "{name} · noch {time}",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Niemand hat ihn übernommen · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Abgelaufen nach verfallener Reservierung · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Übernommen, aber nie erledigt · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Wähle im Karteneditor ein Kind aus, um seine Auktionen zu sehen.",
|
||||
"auction.results": "Ergebnisse",
|
||||
"auction.load_failed": "Auktionen konnten nicht geladen werden: {error}",
|
||||
"auction.up_to": "bis {points} ★",
|
||||
"auction.up_to": "bis {points} {pointsName}",
|
||||
"auction.lowest_wins": "Niedrigstes Gebot gewinnt",
|
||||
"auction.gauge_max": "max. {points} ★",
|
||||
"auction.gauge_hint": "Normalerweise {points} ★ wert · biete die wenigsten Punkte, für die du es machen würdest",
|
||||
"auction.gauge_max": "max. {points} {pointsName}",
|
||||
"auction.gauge_hint": "Normalerweise {points} {pointsName} wert · biete die wenigsten Punkte, für die du es machen würdest",
|
||||
"auction.your_bid": "Dein geheimes Gebot",
|
||||
"auction.change": "Ändern",
|
||||
"auction.withdraw": "Zurückziehen",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Letzte Ziffer löschen",
|
||||
"auction.rule_secret": "Geheim: Niemand sieht deine Zahl.",
|
||||
"auction.rule_lowest": "Das niedrigste Gebot gewinnt. Bei Gleichstand gewinnt, wer zuerst geboten hat.",
|
||||
"auction.rule_min": "Gebote beginnen bei {points} ★.",
|
||||
"auction.rule_min": "Gebote beginnen bei {points} {pointsName}.",
|
||||
"auction.rule_win": "Wenn du gewinnst, machst du es am {date} und bekommst dein Gebot, sobald es genehmigt ist.",
|
||||
"auction.cancel": "Abbrechen",
|
||||
"auction.seal": "Gebot versiegeln · {points} ★",
|
||||
"auction.you_won": "Gewonnen! {points} ★ gehören dir",
|
||||
"auction.seal": "Gebot versiegeln · {points} {pointsName}",
|
||||
"auction.you_won": "Gewonnen! {points} {pointsName} gehören dir",
|
||||
"auction.you_won_sub": "sobald du es am {date} erledigt hast und ein Erwachsener es genehmigt",
|
||||
"auction.in_your_chores": "Es steht am {date} in deinen Aufgaben",
|
||||
"auction.sibling_won": "{name} hat diese gewonnen",
|
||||
"auction.sibling_won_sub": "für {points} ★. Das niedrigste Gebot gewinnt – versuch es nächstes Mal etwas niedriger!",
|
||||
"auction.sibling_won_sub_bid": "für {points} ★ · du hast {bid} ★ geboten. Das niedrigste Gebot gewinnt – versuch es nächstes Mal etwas niedriger!",
|
||||
"auction.tied_sub": "Ihr habt beide {points} ★ geboten. Gleichstand – und {name} hat zuerst geboten.",
|
||||
"auction.sibling_won_sub": "für {points} {pointsName}. Das niedrigste Gebot gewinnt – versuch es nächstes Mal etwas niedriger!",
|
||||
"auction.sibling_won_sub_bid": "für {points} {pointsName} · du hast {bid} {pointsName} geboten. Das niedrigste Gebot gewinnt – versuch es nächstes Mal etwas niedriger!",
|
||||
"auction.tied_sub": "Ihr habt beide {points} {pointsName} geboten. Gleichstand – und {name} hat zuerst geboten.",
|
||||
"auction.no_bids": "Diesmal keine Gebote",
|
||||
"auction.no_bids_sub": "{chore} geht am {date} zurück an die normale Zuteilung.",
|
||||
"auction.closed_at": "Beendet {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Endet",
|
||||
"panel.auction_col_result": "Ergebnis",
|
||||
"panel.auction_col_status": "Aufgabenstatus",
|
||||
"panel.auction_normally": "{date} · normalerweise {points} ★",
|
||||
"panel.auction_min_meta": "mind. {points} ★",
|
||||
"panel.auction_normally": "{date} · normalerweise {points} {points_name}",
|
||||
"panel.auction_min_meta": "mind. {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "Noch keine Gebote",
|
||||
"panel.auction_no_bids": "Keine Gebote",
|
||||
"panel.auction_sealed": "versiegelt",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "In den nächsten 60 Tagen gibt es keinen Termin dieser Aufgabe, der versteigert werden kann.",
|
||||
"panel.auction_no_chores": "Noch keine Aufgabe kann versteigert werden. Teamwork-, Zeit-, offene und nicht zugeteilte Aufgaben gehen nicht.",
|
||||
"panel.auction_max_label": "Höchstgebot",
|
||||
"panel.auction_normally_worth": "normalerweise {points} ★ wert",
|
||||
"panel.auction_normally_worth": "normalerweise {points} {points_name} wert",
|
||||
"panel.auction_max_hint": "Kinder können vom Mindestgebot bis zu diesem Wert bieten. Das niedrigste gewinnt; bei Gleichstand das früheste Gebot.",
|
||||
"panel.auction_min_label": "Mindestgebot",
|
||||
"panel.auction_min_hint": "Aus: Jedes Gebot ab 1 zählt. An: verhindert ein Unterbieten bis ganz nach unten.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Fortschritt zur nächsten Belohnung",
|
||||
"kiosk.editor.show_picker_progress": "Fortschritt in der Auswahl",
|
||||
"kiosk.editor.show_picker_progress_helper": "„3 von 6 erledigt“ unter jedem Gesicht",
|
||||
"kiosk.editor.play_sounds": "Töne",
|
||||
"kiosk.editor.play_sounds_helper": "Der Abschluss-Ton jeder Aufgabe und der Rückgängig-Ton",
|
||||
"kiosk.editor.show_confetti": "Konfetti",
|
||||
"kiosk.editor.show_completion_popup": "Jubel-Pop-up",
|
||||
"kiosk.editor.show_completion_popup_helper": "Ein kurzes „Super!“ mit den Punkten nach jeder Aufgabe",
|
||||
"kiosk.editor.children_heading": "Angezeigte Kinder und PINs",
|
||||
"kiosk.editor.children_hint": "Entferne den Haken, um ein Kind auszublenden. Mit den Pfeilen legst du die Reihenfolge in der Auswahl fest.",
|
||||
"kiosk.editor.require_pin": "PIN verlangen",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Ausschalten für eine reine Anzeige",
|
||||
"chore_board.editor.show_legend": "Legende anzeigen",
|
||||
"chore_board.editor.hide_done_periods": "Erledigte Tageszeiten ausblenden",
|
||||
"chore_board.editor.hide_done_periods_helper": "Eine Tageszeit ausblenden, sobald alle Kinder ihre Aufgaben erledigt haben"
|
||||
"chore_board.editor.hide_done_periods_helper": "Eine Tageszeit ausblenden, sobald alle Kinder ihre Aufgaben erledigt haben",
|
||||
"notify.bedtime_reminder": "{child_name}, du hast vor dem Schlafengehen noch Aufgaben zu erledigen.",
|
||||
"notify.streak_at_risk": "{child_name}, erledige heute eine Aufgabe, um deine {streak}-Tage-Serie zu halten!",
|
||||
"notify.all_chores_done": "{child_name} hat heute alle Aufgaben erledigt!",
|
||||
"notify.badge_earned": "{child_name} hat das Abzeichen „{badge_name}“ verdient!",
|
||||
"notify.pending_chore_approval": "{child_name} hat „{chore_name}“ erledigt (+{points} {points_name}) – wartet auf Freigabe.",
|
||||
"notify.pending_reward_claim": "{child_name} hat „{reward_name}“ eingelöst ({cost} {points_name}) – wartet auf Freigabe.",
|
||||
"notify.streak_milestone": "{child_name} hat eine {days}-Tage-Serie geschafft – +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} hat Level {level} erreicht! 🎉",
|
||||
"notify.weekly_digest": "TaskMate-Wochenübersicht:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, du musst noch „{chore_name}“ erledigen.",
|
||||
"notify.mandatory_parent_alert": "{child_name} hat die Pflichtaufgabe „{chore_name}“ immer noch nicht erledigt.",
|
||||
"notify.monthly_report": "TaskMate-Bericht {month}:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} hat die Bestenliste im {month} mit {points} {points_name} gewonnen!",
|
||||
"notify.family_goal_reached": "🎉 Familienziel erreicht: {goal_name}! Zeit für {goal_reward}.",
|
||||
"notify.birthday": "🎂 Alles Gute zum Geburtstag, {child_name}! Heute zählt jede Aufgabe {multiplier}×.",
|
||||
"notify.streak_freeze_used": "❄️ Ein Serienschutz hat die {streak}-Tage-Serie von {child_name} gerettet (noch {freezes_left}).",
|
||||
"notify.presence_arrival": "🏠 Du bist zu Hause, {child_name} – heute sind noch {count} Aufgaben offen.",
|
||||
"notify.wish_requested": "{child_name} wünscht sich „{wish_name}“ ({target} {points_name}) – wartet auf deine Freigabe.",
|
||||
"notify.wish_pledged": "💝 {pledger} hat {points} {points_name} zu deinem Wunsch „{wish_name}“ beigesteuert!",
|
||||
"notify.bounty_posted": "🏁 Neue Prämie: {bounty_name} – {points} {points_name}. Wer sie zuerst beansprucht, bekommt sie!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, noch {minutes} Minuten, um „{bounty_name}“ zu erledigen, bevor sie wieder frei wird.",
|
||||
"notify.recap_ready": "✨ Dein Rückblick ist fertig ({period}), {child_name}! Tippe, um ihn anzusehen.",
|
||||
"notify.item_rejected": "↩️ {child_name}, „{item_name}“ wurde zurückgegeben{reason_text}",
|
||||
"notify.auction_opened": "🔨 Neue Auktion: {chore_name} am {date}, bis zu {max_points} {points_name}. Das niedrigste Gebot gewinnt!",
|
||||
"notify.auction_closing": "⏳ {child_name}, die Gebote für „{chore_name}“ enden in {minutes} Minuten.",
|
||||
"notify.auction_result": "🔨 Auktion beendet: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, ein Erwachsener prüft „{chore_name}“ vor {until}. Halte es schön ordentlich für +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Prüfung bestanden, {child_name}! „{chore_name}“ sah toll aus: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 Die Prüfung von „{chore_name}“ bei {child_name} endet in {minutes} Minuten – bestätige oder lehne sie vor {until} ab.",
|
||||
"notify.auction_won": "{child_name} hat „{chore_name}“ am {date} für {points} {points_name} gewonnen.",
|
||||
"notify.auction_no_bids": "keine Gebote für „{chore_name}“, die Aufgabe geht zurück in den normalen Plan.",
|
||||
"notify.auction_called_off": "die Auktion für „{chore_name}“ wurde abgesagt. Die Aufgabe geht zurück in den normalen Plan.",
|
||||
"notify.weekly_digest_line": "• {child_name}: {chores} Aufgaben, {points} {points_name} verdient",
|
||||
"notify.monthly_report_line": "• {child_name}: {chores} Aufgaben, {points} {points_name}, Level {level}, beste Serie {streak}",
|
||||
"notify.action_approve": "Genehmigen",
|
||||
"notify.action_reject": "Ablehnen",
|
||||
"notify.action_reject_placeholder": "Grund (optional)",
|
||||
"notify.approve_in_panel_hint": "Öffne das TaskMate-Panel, um zu genehmigen oder abzulehnen.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} hat heute alle Aufgaben erledigt!",
|
||||
"notify.celebrate_birthday": "Alles Gute zum Geburtstag, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} hat die Challenge „{challenge_name}“ geschafft!",
|
||||
"notify.celebrate_inspection_passed": "„{chore_name}“ von {child_name} hat die Prüfung bestanden!",
|
||||
"notify.celebrate_level_up": "{child_name} hat Level {level} erreicht!",
|
||||
"notify.celebrate_perfect_week": "{child_name} hat eine perfekte Woche geschafft – +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} hat das Wochenziel für „{chore_name}“ erreicht – +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} hat das Abenteuer „{quest_name}“ geschafft!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} hat eine {days}-Tage-Serie geschafft!",
|
||||
"notify.recap_ready_many": "✨ {count} neue Rückblicke sind fertig, {child_name}! Tippe, um sie anzusehen.",
|
||||
"notify.recap_parent_one": "✨ Neue Rückblicke ({period}) sind fertig für {names}. Tippe, um sie anzusehen.",
|
||||
"notify.recap_parent_many": "✨ Neue Rückblicke sind fertig für {names}. Tippe, um sie anzusehen.",
|
||||
"notify.period_weekly": "Woche",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} und {last}",
|
||||
"notify.month_year": "{month} {year}",
|
||||
"notify.short_date": "{weekday}, {day}. {month}",
|
||||
"notify.month_1": "Januar",
|
||||
"notify.month_2": "Februar",
|
||||
"notify.month_3": "März",
|
||||
"notify.month_4": "April",
|
||||
"notify.month_5": "Mai",
|
||||
"notify.month_6": "Juni",
|
||||
"notify.month_7": "Juli",
|
||||
"notify.month_8": "August",
|
||||
"notify.month_9": "September",
|
||||
"notify.month_10": "Oktober",
|
||||
"notify.month_11": "November",
|
||||
"notify.month_12": "Dezember",
|
||||
"notify.month_short_1": "Jan",
|
||||
"notify.month_short_2": "Feb",
|
||||
"notify.month_short_3": "Mär",
|
||||
"notify.month_short_4": "Apr",
|
||||
"notify.month_short_5": "Mai",
|
||||
"notify.month_short_6": "Jun",
|
||||
"notify.month_short_7": "Jul",
|
||||
"notify.month_short_8": "Aug",
|
||||
"notify.month_short_9": "Sep",
|
||||
"notify.month_short_10": "Okt",
|
||||
"notify.month_short_11": "Nov",
|
||||
"notify.month_short_12": "Dez",
|
||||
"notify.weekday_short_1": "Mo",
|
||||
"notify.weekday_short_2": "Di",
|
||||
"notify.weekday_short_3": "Mi",
|
||||
"notify.weekday_short_4": "Do",
|
||||
"notify.weekday_short_5": "Fr",
|
||||
"notify.weekday_short_6": "Sa",
|
||||
"notify.weekday_short_7": "So",
|
||||
"notify.sample_chore": "Zimmer aufräumen",
|
||||
"notify.sample_reward": "Filmabend",
|
||||
"notify.sample_badge": "Super-Helfer",
|
||||
"notify.sample_goal": "Filmabend-Kasse",
|
||||
"notify.sample_goal_reward": "einen Familien-Filmabend",
|
||||
"notify.sample_wish": "Lego-Set",
|
||||
"notify.sample_pledger": "Oma",
|
||||
"notify.sample_bounty": "Auto waschen",
|
||||
"notify.sample_reason": "Nicht fertig",
|
||||
"notify.sample_note": "Super gemacht!"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Apply penalty",
|
||||
"approvals.postpone": "Postpone",
|
||||
"approvals.dismiss": "Dismiss",
|
||||
"approvals.penalty_points": "Penalty: −{points} pts",
|
||||
"approvals.penalty_points": "Penalty: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "No penalty set",
|
||||
"approvals.timed_duration": "{count} min",
|
||||
"badge.criteria_and": "AND",
|
||||
@@ -259,6 +259,7 @@
|
||||
"child.open_ended_tag": "Describe it",
|
||||
"child.weekly_target_done": "Done for this week",
|
||||
"child.weekly_target_progress": "{done}/{target} this week",
|
||||
"child.weekly_target_bonus_reached": "🎉 Weekly target reached! +{points}",
|
||||
"child.team_progress": "{joined} / {size} joined",
|
||||
"child.team_you_joined": "You're in",
|
||||
"child.team_join": "Join",
|
||||
@@ -332,8 +333,8 @@
|
||||
"child.paused": "Paused",
|
||||
"child.please_define_child_id": "Please define a child_id",
|
||||
"child.please_define_entity": "Please define an entity (taskmate overview sensor)",
|
||||
"child.pts_per_min": "pts/min",
|
||||
"child.pts_per_n_min": "pts/{count} min",
|
||||
"child.pts_per_min": "{pointsName}/min",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min",
|
||||
"child.recurring": "Recurring",
|
||||
"child.resume": "Resume",
|
||||
"child.start": "Start",
|
||||
@@ -364,6 +365,8 @@
|
||||
"common.editor.header_colour_helper": "Card header background colour",
|
||||
"common.editor.overview_entity": "Overview Entity",
|
||||
"common.editor.overview_entity_helper": "The TaskMate overview sensor entity",
|
||||
"common.editor.show_parent_actions": "Show parent actions",
|
||||
"common.editor.show_parent_actions_helper": "Turn off on a shared kids' dashboard to show this card exactly as a child sees it, even while a parent is signed in. Display only — permissions are unchanged.",
|
||||
"common.entity": "Entity",
|
||||
"common.entity_not_found": "Entity not found: {entity}",
|
||||
"common.error": "Error",
|
||||
@@ -822,6 +825,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/week",
|
||||
"panel.chore_weekly_target_hint": "How many times a week this has to be done, on any of the chosen days. It stops being offered once the count is reached, and resets on Monday.",
|
||||
"panel.chore_weekly_target_label": "Weekly target (0 = off)",
|
||||
"panel.chore_weekly_target_bonus_label": "Weekly target bonus",
|
||||
"panel.chore_weekly_target_bonus_hint": "Extra points paid once the weekly target is reached, on top of each completion's points. Paid once a week, when the last completion is approved. 0 = no bonus.",
|
||||
"panel.chore_team_section": "Teamwork (optional)",
|
||||
"panel.chore_team_hint": "A teamwork chore only counts once this many different children have joined it. Each child's Done tap joins, and when the team is full it's submitted for all of them. Joins reset each day. Needs the \"Everyone\" assignment and can't be open-ended.",
|
||||
"panel.chore_team_size_label": "Team size (0 = off)",
|
||||
@@ -841,6 +846,9 @@
|
||||
"panel.chore_points_label": "Points",
|
||||
"panel.chore_points_per_window_label": "Points per window",
|
||||
"panel.chore_recurrence_day_hint": "e.g. monday — leave blank for any day",
|
||||
"panel.chore_recurrence_interval_label": "Repeat every (days)",
|
||||
"panel.chore_recurrence_interval_hint": "Whole number of days, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Enter a number of days between {min} and {max}",
|
||||
"panel.chore_recurrence_day_label": "Day of week (weekly only)",
|
||||
"panel.chore_recurrence_label": "Recurrence",
|
||||
"panel.chore_recurrence_start_hint": "Anchor date — blank = today",
|
||||
@@ -1277,8 +1285,10 @@
|
||||
"panel.penbon_table_name": "Name",
|
||||
"panel.penbon_table_points": "Points",
|
||||
"panel.pending_tooltip": "{count} pending — click to review",
|
||||
"panel.pts_display": "{count} pts",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "Every 2 days",
|
||||
"panel.recurrence_every_n_days": "Every N days (custom)",
|
||||
"panel.recurrence_every_count_days": "Every {count} days",
|
||||
"panel.recurrence_every_2_weeks": "Every 2 weeks",
|
||||
"panel.recurrence_every_3_months": "Every 3 months",
|
||||
"panel.recurrence_every_6_months": "Every 6 months",
|
||||
@@ -1528,7 +1538,7 @@
|
||||
"panel.template_builtin_template_packs": "Built-in template packs",
|
||||
"panel.template_builtin_templates": "Built-in templates",
|
||||
"panel.template_chores_in_template": "Chores in this template",
|
||||
"panel.template_confirm_bar": "{count} chore(s) will be created · {points} pts total",
|
||||
"panel.template_confirm_bar": "{count} chore(s) will be created · {points} {points_name} total",
|
||||
"panel.template_create_btn": "Create {count} chore(s)",
|
||||
"panel.template_create_title": "Create Template",
|
||||
"panel.template_custom": "Custom",
|
||||
@@ -1540,7 +1550,7 @@
|
||||
"panel.template_name_label": "Name",
|
||||
"panel.template_picker_title": "Add from Template",
|
||||
"panel.template_preview_hint": "Review and customise each chore before creating. Expand to edit all fields, or remove with ✕.",
|
||||
"panel.template_pts_total": "{count} chore(s) · {points} pts total",
|
||||
"panel.template_pts_total": "{count} chore(s) · {points} {points_name} total",
|
||||
"panel.template_requires_approval_label": "Requires approval",
|
||||
"panel.template_requires_approval_no": "No",
|
||||
"panel.template_requires_approval_yes": "Yes",
|
||||
@@ -1752,7 +1762,7 @@
|
||||
"points_display.this_week": "This Week",
|
||||
"points_display.total_points": "Total Points",
|
||||
"points_display.weekly_plus": "+{count} this week",
|
||||
"points_display.weekly_pts": "{count} pts",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Add chores to this child first",
|
||||
"reorder.card_description": "A card for reordering chores per child, organized by time category",
|
||||
"reorder.card_name": "TaskMate Reorder Card",
|
||||
@@ -1857,9 +1867,9 @@
|
||||
"streak.achievement.hundred_desc": "Complete 100 chores",
|
||||
"streak.achievement.hundred_name": "100 Done",
|
||||
"streak.achievement.points100_desc": "Earn 100 points total",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Earn 50 points total",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "14 day streak",
|
||||
"streak.achievement.streak14_name": "2 Weeks",
|
||||
"streak.achievement.streak30_desc": "30 day streak",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Surprise bonus settings",
|
||||
"panel.settings_surprise_params_hint": "Daily chance per child, and the random points range awarded on a hit.",
|
||||
"panel.settings_surprise_chance": "Chance %",
|
||||
"panel.settings_surprise_min": "Min pts",
|
||||
"panel.settings_surprise_max": "Max pts",
|
||||
"panel.settings_surprise_min": "Min {points_name}",
|
||||
"panel.settings_surprise_max": "Max {points_name}",
|
||||
"panel.reward_restock_label": "Auto-restock",
|
||||
"panel.reward_restock_hint": "Periodically reset this reward's stock back to a set amount.",
|
||||
"panel.reward_restock_amount": "Restock to",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Limit how many points each child can spend on rewards per period.",
|
||||
"panel.settings_spendcap_params_label": "Spending cap settings",
|
||||
"panel.settings_spendcap_params_hint": "Max points spendable per period (0 = no limit).",
|
||||
"panel.settings_spendcap_amount": "Cap (pts)",
|
||||
"panel.settings_spendcap_amount": "Cap ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Savings interest",
|
||||
"panel.settings_interest_enabled_hint": "Periodically pay each child interest on their spendable balance (rewards saving).",
|
||||
"panel.settings_interest_params_label": "Savings interest settings",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "{time} left",
|
||||
"bounty.no_expiry": "No expiry",
|
||||
"bounty.photo_tag": "photo",
|
||||
"bounty.pts": "PTS",
|
||||
"bounty.claim": "Claim",
|
||||
"bounty.claim_hint": "You'll have {hours}h to finish",
|
||||
"bounty.finish_first": "Finish your bounty first",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Give back",
|
||||
"bounty.sibling_on_it": "{name} is on it",
|
||||
"bounty.sibling_back_in": "back on the board in {time} if not finished",
|
||||
"bounty.waiting": "Waiting for a grown-up to check · +{points} ★ pending",
|
||||
"bounty.waiting": "Waiting for a grown-up to check · +{points} {pointsName} pending",
|
||||
"bounty.undo": "Undo",
|
||||
"bounty.sibling_checking": "{name} finished it — being checked",
|
||||
"bounty.you_earned": "You earned +{points} ★",
|
||||
"bounty.you_earned": "You earned +{points} {pointsName}",
|
||||
"bounty.done_by": "Done by {name}",
|
||||
"bounty.recent": "Recently completed",
|
||||
"bounty.claim_title": "Claim \"{title}\"?",
|
||||
"bounty.claim_body": "It's yours for {hours} hours. If it's not done by then, it goes back on the board for everyone.",
|
||||
"bounty.not_now": "Not now",
|
||||
"bounty.claim_it": "Claim it — {points} ★",
|
||||
"bounty.claim_it": "Claim it — {points} {pointsName}",
|
||||
"bounty.photo_title": "Show us the finished job",
|
||||
"bounty.photo_body": "\"{title}\" needs a photo",
|
||||
"bounty.cancel": "Cancel",
|
||||
"bounty.take_photo": "Take photo & send",
|
||||
"bounty.uploading": "Sending…",
|
||||
"bounty.yay_title": "Nice work!",
|
||||
"bounty.yay_body": "A grown-up will check \"{title}\" and then you get {points} ★.",
|
||||
"bounty.yay_body": "A grown-up will check \"{title}\" and then you get {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d}d {h}h",
|
||||
"bounty.dur_hours": "{h}h {m}m",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Completed",
|
||||
"panel.bounty_status_expired": "Expired",
|
||||
"panel.bounty_claimed_meta": "{name} · {time} left",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Nobody claimed · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Expired after a claim lapsed · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Claimed but never finished · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Pick a child in the card editor to see their auctions.",
|
||||
"auction.results": "Results",
|
||||
"auction.load_failed": "Couldn't load the auctions: {error}",
|
||||
"auction.up_to": "up to {points} ★",
|
||||
"auction.up_to": "up to {points} {pointsName}",
|
||||
"auction.lowest_wins": "Lowest bid wins",
|
||||
"auction.gauge_max": "max {points} ★",
|
||||
"auction.gauge_hint": "Normally worth {points} ★ · bid the fewest points you'd do it for",
|
||||
"auction.gauge_max": "max {points} {pointsName}",
|
||||
"auction.gauge_hint": "Normally worth {points} {pointsName} · bid the fewest points you'd do it for",
|
||||
"auction.your_bid": "Your sealed bid",
|
||||
"auction.change": "Change",
|
||||
"auction.withdraw": "Withdraw",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Delete last digit",
|
||||
"auction.rule_secret": "Secret: nobody sees your number.",
|
||||
"auction.rule_lowest": "Lowest bid wins. If it's a tie, whoever bid first wins.",
|
||||
"auction.rule_min": "Bids start at {points} ★.",
|
||||
"auction.rule_min": "Bids start at {points} {pointsName}.",
|
||||
"auction.rule_win": "If you win, you do it on {date} and get your bid when it's approved.",
|
||||
"auction.cancel": "Cancel",
|
||||
"auction.seal": "Seal my bid · {points} ★",
|
||||
"auction.you_won": "You won! {points} ★ is yours",
|
||||
"auction.seal": "Seal my bid · {points} {pointsName}",
|
||||
"auction.you_won": "You won! {points} {pointsName} is yours",
|
||||
"auction.you_won_sub": "when you've done it on {date} and a grown-up approves it",
|
||||
"auction.in_your_chores": "It's in your chores for {date}",
|
||||
"auction.sibling_won": "{name} won this one",
|
||||
"auction.sibling_won_sub": "at {points} ★. Lowest bid wins, so try a little lower next time!",
|
||||
"auction.sibling_won_sub_bid": "at {points} ★ · you bid {bid} ★. Lowest bid wins, so try a little lower next time!",
|
||||
"auction.tied_sub": "You both bid {points} ★. It was a tie, and {name} got their bid in first.",
|
||||
"auction.sibling_won_sub": "at {points} {pointsName}. Lowest bid wins, so try a little lower next time!",
|
||||
"auction.sibling_won_sub_bid": "at {points} {pointsName} · you bid {bid} {pointsName}. Lowest bid wins, so try a little lower next time!",
|
||||
"auction.tied_sub": "You both bid {points} {pointsName}. It was a tie, and {name} got their bid in first.",
|
||||
"auction.no_bids": "No bids this time",
|
||||
"auction.no_bids_sub": "{chore} goes back to its normal assignment on {date}.",
|
||||
"auction.closed_at": "Closed {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Closes",
|
||||
"panel.auction_col_result": "Result",
|
||||
"panel.auction_col_status": "Chore status",
|
||||
"panel.auction_normally": "{date} · normally {points} ★",
|
||||
"panel.auction_min_meta": "min {points} ★",
|
||||
"panel.auction_normally": "{date} · normally {points} {points_name}",
|
||||
"panel.auction_min_meta": "min {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "No bids yet",
|
||||
"panel.auction_no_bids": "No bids",
|
||||
"panel.auction_sealed": "sealed",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "No upcoming occurrence of this chore can be auctioned in the next 60 days.",
|
||||
"panel.auction_no_chores": "No chore can be auctioned yet. Teamwork, timed, open-ended and unassigned chores can't be.",
|
||||
"panel.auction_max_label": "Maximum bid",
|
||||
"panel.auction_normally_worth": "normally worth {points} ★",
|
||||
"panel.auction_normally_worth": "normally worth {points} {points_name}",
|
||||
"panel.auction_max_hint": "Children can bid anything from the minimum up to this. Lowest wins; ties go to the earliest bid.",
|
||||
"panel.auction_min_label": "Minimum bid",
|
||||
"panel.auction_min_hint": "Off: any bid from 1 counts. On: stops a race to the bottom.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Next reward progress",
|
||||
"kiosk.editor.show_picker_progress": "Progress on the picker",
|
||||
"kiosk.editor.show_picker_progress_helper": "“3 of 6 done” under each face",
|
||||
"kiosk.editor.play_sounds": "Sounds",
|
||||
"kiosk.editor.play_sounds_helper": "Each chore's completion sound, and the undo sound",
|
||||
"kiosk.editor.show_confetti": "Confetti",
|
||||
"kiosk.editor.show_completion_popup": "Celebration popup",
|
||||
"kiosk.editor.show_completion_popup_helper": "A short “Awesome!” with the points after each chore",
|
||||
"kiosk.editor.children_heading": "Children shown and PINs",
|
||||
"kiosk.editor.children_hint": "Untick a child to hide them. Use the arrows to set the order on the picker.",
|
||||
"kiosk.editor.require_pin": "Require PIN",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Turn off for a view-only board",
|
||||
"chore_board.editor.show_legend": "Show the legend",
|
||||
"chore_board.editor.hide_done_periods": "Hide finished time periods",
|
||||
"chore_board.editor.hide_done_periods_helper": "Drop a time of day once every child has finished its chores"
|
||||
"chore_board.editor.hide_done_periods_helper": "Drop a time of day once every child has finished its chores",
|
||||
"notify.bedtime_reminder": "{child_name}, you still have chores to do before bedtime.",
|
||||
"notify.streak_at_risk": "{child_name}, complete a chore today to keep your {streak}-day streak!",
|
||||
"notify.all_chores_done": "{child_name} finished every chore today!",
|
||||
"notify.badge_earned": "{child_name} earned the {badge_name} badge!",
|
||||
"notify.pending_chore_approval": "{child_name} completed '{chore_name}' (+{points} {points_name}) — awaiting approval.",
|
||||
"notify.pending_reward_claim": "{child_name} claimed '{reward_name}' ({cost} {points_name}) — awaiting approval.",
|
||||
"notify.streak_milestone": "{child_name} hit a {days}-day streak — +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} reached level {level}! 🎉",
|
||||
"notify.weekly_digest": "TaskMate weekly digest:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, you still need to do '{chore_name}'.",
|
||||
"notify.mandatory_parent_alert": "{child_name} still hasn't done the mandatory chore '{chore_name}'.",
|
||||
"notify.monthly_report": "TaskMate {month} report:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} won the {month} leaderboard with {points} {points_name}!",
|
||||
"notify.family_goal_reached": "🎉 Family goal reached: {goal_name}! Time for {goal_reward}.",
|
||||
"notify.birthday": "🎂 Happy birthday, {child_name}! Every chore pays {multiplier}× today.",
|
||||
"notify.streak_freeze_used": "❄️ A streak freeze saved {child_name}'s {streak}-day streak ({freezes_left} left).",
|
||||
"notify.presence_arrival": "🏠 You're home, {child_name} — {count} chores left today.",
|
||||
"notify.wish_requested": "{child_name} wished for '{wish_name}' ({target} {points_name}) — waiting for your approval.",
|
||||
"notify.wish_pledged": "💝 {pledger} added {points} {points_name} to your wish '{wish_name}'!",
|
||||
"notify.bounty_posted": "🏁 New bounty: {bounty_name} — {points} {points_name}. First to claim it gets it!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, {minutes} minutes left to finish '{bounty_name}' before it goes back on the board.",
|
||||
"notify.recap_ready": "✨ Your {period} recap is ready, {child_name}! Tap to watch it.",
|
||||
"notify.item_rejected": "↩️ {child_name}, '{item_name}' was sent back{reason_text}",
|
||||
"notify.auction_opened": "🔨 New auction: {chore_name} on {date}, up to {max_points} {points_name}. Lowest bid wins!",
|
||||
"notify.auction_closing": "⏳ {child_name}, bidding on '{chore_name}' closes in {minutes} minutes.",
|
||||
"notify.auction_result": "🔨 Auction closed: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, a grown-up is coming to inspect '{chore_name}' before {until}. Keep it looking great for +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Inspection passed, {child_name}! '{chore_name}' looked great: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 {child_name}'s '{chore_name}' inspection closes in {minutes} minutes — pass or fail it before {until}.",
|
||||
"notify.auction_won": "{child_name} won '{chore_name}' on {date} for {points} {points_name}.",
|
||||
"notify.auction_no_bids": "no bids for '{chore_name}', so it goes back to the normal rota.",
|
||||
"notify.auction_called_off": "the auction for '{chore_name}' was called off. It goes back to the normal rota.",
|
||||
"notify.weekly_digest_line": "• {child_name}: {chores} chores, {points} {points_name} earned",
|
||||
"notify.monthly_report_line": "• {child_name}: {chores} chores, {points} {points_name}, level {level}, best streak {streak}",
|
||||
"notify.action_approve": "Approve",
|
||||
"notify.action_reject": "Reject",
|
||||
"notify.action_reject_placeholder": "Reason (optional)",
|
||||
"notify.approve_in_panel_hint": "Open the TaskMate panel to approve or reject.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} finished every chore today!",
|
||||
"notify.celebrate_birthday": "Happy birthday, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} completed the challenge '{challenge_name}'!",
|
||||
"notify.celebrate_inspection_passed": "{child_name}'s {chore_name} passed inspection!",
|
||||
"notify.celebrate_level_up": "{child_name} reached level {level}!",
|
||||
"notify.celebrate_perfect_week": "{child_name} earned a perfect week — +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} reached the weekly target for '{chore_name}' — +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} completed the quest '{quest_name}'!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} hit a {days}-day streak!",
|
||||
"notify.recap_ready_many": "✨ {count} new recaps are ready, {child_name}! Tap to watch them.",
|
||||
"notify.recap_parent_one": "✨ New {period} recaps are ready for {names}. Tap to see them.",
|
||||
"notify.recap_parent_many": "✨ New recaps are ready for {names}. Tap to see them.",
|
||||
"notify.period_weekly": "weekly",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} and {last}",
|
||||
"notify.month_year": "{month} {year}",
|
||||
"notify.short_date": "{weekday} {day} {month}",
|
||||
"notify.month_1": "January",
|
||||
"notify.month_2": "February",
|
||||
"notify.month_3": "March",
|
||||
"notify.month_4": "April",
|
||||
"notify.month_5": "May",
|
||||
"notify.month_6": "June",
|
||||
"notify.month_7": "July",
|
||||
"notify.month_8": "August",
|
||||
"notify.month_9": "September",
|
||||
"notify.month_10": "October",
|
||||
"notify.month_11": "November",
|
||||
"notify.month_12": "December",
|
||||
"notify.month_short_1": "Jan",
|
||||
"notify.month_short_2": "Feb",
|
||||
"notify.month_short_3": "Mar",
|
||||
"notify.month_short_4": "Apr",
|
||||
"notify.month_short_5": "May",
|
||||
"notify.month_short_6": "Jun",
|
||||
"notify.month_short_7": "Jul",
|
||||
"notify.month_short_8": "Aug",
|
||||
"notify.month_short_9": "Sep",
|
||||
"notify.month_short_10": "Oct",
|
||||
"notify.month_short_11": "Nov",
|
||||
"notify.month_short_12": "Dec",
|
||||
"notify.weekday_short_1": "Mon",
|
||||
"notify.weekday_short_2": "Tue",
|
||||
"notify.weekday_short_3": "Wed",
|
||||
"notify.weekday_short_4": "Thu",
|
||||
"notify.weekday_short_5": "Fri",
|
||||
"notify.weekday_short_6": "Sat",
|
||||
"notify.weekday_short_7": "Sun",
|
||||
"notify.sample_chore": "Tidy room",
|
||||
"notify.sample_reward": "Movie night",
|
||||
"notify.sample_badge": "Star Helper",
|
||||
"notify.sample_goal": "Movie night fund",
|
||||
"notify.sample_goal_reward": "a family movie night",
|
||||
"notify.sample_wish": "Lego set",
|
||||
"notify.sample_pledger": "Grandma",
|
||||
"notify.sample_bounty": "Wash the car",
|
||||
"notify.sample_reason": "Not finished",
|
||||
"notify.sample_note": "Brilliant job!"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Apply penalty",
|
||||
"approvals.postpone": "Postpone",
|
||||
"approvals.dismiss": "Dismiss",
|
||||
"approvals.penalty_points": "Penalty: −{points} pts",
|
||||
"approvals.penalty_points": "Penalty: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "No penalty set",
|
||||
"approvals.timed_duration": "{count} min",
|
||||
"badge.criteria_and": "AND",
|
||||
@@ -259,6 +259,7 @@
|
||||
"child.open_ended_tag": "Describe it",
|
||||
"child.weekly_target_done": "Done for this week",
|
||||
"child.weekly_target_progress": "{done}/{target} this week",
|
||||
"child.weekly_target_bonus_reached": "🎉 Weekly target reached! +{points}",
|
||||
"child.team_progress": "{joined} / {size} joined",
|
||||
"child.team_you_joined": "You're in",
|
||||
"child.team_join": "Join",
|
||||
@@ -332,8 +333,8 @@
|
||||
"child.paused": "Paused",
|
||||
"child.please_define_child_id": "Please define a child_id",
|
||||
"child.please_define_entity": "Please define an entity (taskmate overview sensor)",
|
||||
"child.pts_per_min": "pts/min",
|
||||
"child.pts_per_n_min": "pts/{count} min",
|
||||
"child.pts_per_min": "{pointsName}/min",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min",
|
||||
"child.recurring": "Recurring",
|
||||
"child.resume": "Resume",
|
||||
"child.start": "Start",
|
||||
@@ -364,6 +365,8 @@
|
||||
"common.editor.header_colour_helper": "Card header background colour",
|
||||
"common.editor.overview_entity": "Overview Entity",
|
||||
"common.editor.overview_entity_helper": "The TaskMate overview sensor entity",
|
||||
"common.editor.show_parent_actions": "Show parent actions",
|
||||
"common.editor.show_parent_actions_helper": "Turn off on a shared kids' dashboard to show this card exactly as a child sees it, even while a parent is signed in. Display only — permissions are unchanged.",
|
||||
"common.entity": "Entity",
|
||||
"common.entity_not_found": "Entity not found: {entity}",
|
||||
"common.error": "Error",
|
||||
@@ -822,6 +825,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/week",
|
||||
"panel.chore_weekly_target_hint": "How many times a week this has to be done, on any of the chosen days. It stops being offered once the count is reached, and resets on Monday.",
|
||||
"panel.chore_weekly_target_label": "Weekly target (0 = off)",
|
||||
"panel.chore_weekly_target_bonus_label": "Weekly target bonus",
|
||||
"panel.chore_weekly_target_bonus_hint": "Extra points paid once the weekly target is reached, on top of each completion's points. Paid once a week, when the last completion is approved. 0 = no bonus.",
|
||||
"panel.chore_team_section": "Teamwork (optional)",
|
||||
"panel.chore_team_hint": "A teamwork chore only counts once this many different children have joined it. Each child's Done tap joins, and when the team is full it's submitted for all of them. Joins reset each day. Needs the \"Everyone\" assignment and can't be open-ended.",
|
||||
"panel.chore_team_size_label": "Team size (0 = off)",
|
||||
@@ -841,6 +846,9 @@
|
||||
"panel.chore_points_label": "Points",
|
||||
"panel.chore_points_per_window_label": "Points per window",
|
||||
"panel.chore_recurrence_day_hint": "e.g. monday — leave blank for any day",
|
||||
"panel.chore_recurrence_interval_label": "Repeat every (days)",
|
||||
"panel.chore_recurrence_interval_hint": "Whole number of days, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Enter a number of days between {min} and {max}",
|
||||
"panel.chore_recurrence_day_label": "Day of week (weekly only)",
|
||||
"panel.chore_recurrence_label": "Recurrence",
|
||||
"panel.chore_recurrence_start_hint": "Anchor date — blank = today",
|
||||
@@ -1277,8 +1285,10 @@
|
||||
"panel.penbon_table_name": "Name",
|
||||
"panel.penbon_table_points": "Points",
|
||||
"panel.pending_tooltip": "{count} pending — click to review",
|
||||
"panel.pts_display": "{count} pts",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "Every 2 days",
|
||||
"panel.recurrence_every_n_days": "Every N days (custom)",
|
||||
"panel.recurrence_every_count_days": "Every {count} days",
|
||||
"panel.recurrence_every_2_weeks": "Every 2 weeks",
|
||||
"panel.recurrence_every_3_months": "Every 3 months",
|
||||
"panel.recurrence_every_6_months": "Every 6 months",
|
||||
@@ -1528,7 +1538,7 @@
|
||||
"panel.template_builtin_template_packs": "Built-in template packs",
|
||||
"panel.template_builtin_templates": "Built-in templates",
|
||||
"panel.template_chores_in_template": "Chores in this template",
|
||||
"panel.template_confirm_bar": "{count} chore(s) will be created · {points} pts total",
|
||||
"panel.template_confirm_bar": "{count} chore(s) will be created · {points} {points_name} total",
|
||||
"panel.template_create_btn": "Create {count} chore(s)",
|
||||
"panel.template_create_title": "Create Template",
|
||||
"panel.template_custom": "Custom",
|
||||
@@ -1540,7 +1550,7 @@
|
||||
"panel.template_name_label": "Name",
|
||||
"panel.template_picker_title": "Add from Template",
|
||||
"panel.template_preview_hint": "Review and customise each chore before creating. Expand to edit all fields, or remove with ✕.",
|
||||
"panel.template_pts_total": "{count} chore(s) · {points} pts total",
|
||||
"panel.template_pts_total": "{count} chore(s) · {points} {points_name} total",
|
||||
"panel.template_requires_approval_label": "Requires approval",
|
||||
"panel.template_requires_approval_no": "No",
|
||||
"panel.template_requires_approval_yes": "Yes",
|
||||
@@ -1752,7 +1762,7 @@
|
||||
"points_display.this_week": "This Week",
|
||||
"points_display.total_points": "Total Points",
|
||||
"points_display.weekly_plus": "+{count} this week",
|
||||
"points_display.weekly_pts": "{count} pts",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Add chores to this child first",
|
||||
"reorder.card_description": "A card for reordering chores per child, organized by time category",
|
||||
"reorder.card_name": "TaskMate Reorder Card",
|
||||
@@ -1857,9 +1867,9 @@
|
||||
"streak.achievement.hundred_desc": "Complete 100 chores",
|
||||
"streak.achievement.hundred_name": "100 Done",
|
||||
"streak.achievement.points100_desc": "Earn 100 points total",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Earn 50 points total",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "14 day streak",
|
||||
"streak.achievement.streak14_name": "2 Weeks",
|
||||
"streak.achievement.streak30_desc": "30 day streak",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Surprise bonus settings",
|
||||
"panel.settings_surprise_params_hint": "Daily chance per child, and the random points range awarded on a hit.",
|
||||
"panel.settings_surprise_chance": "Chance %",
|
||||
"panel.settings_surprise_min": "Min pts",
|
||||
"panel.settings_surprise_max": "Max pts",
|
||||
"panel.settings_surprise_min": "Min {points_name}",
|
||||
"panel.settings_surprise_max": "Max {points_name}",
|
||||
"panel.reward_restock_label": "Auto-restock",
|
||||
"panel.reward_restock_hint": "Periodically reset this reward's stock back to a set amount.",
|
||||
"panel.reward_restock_amount": "Restock to",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Limit how many points each child can spend on rewards per period.",
|
||||
"panel.settings_spendcap_params_label": "Spending cap settings",
|
||||
"panel.settings_spendcap_params_hint": "Max points spendable per period (0 = no limit).",
|
||||
"panel.settings_spendcap_amount": "Cap (pts)",
|
||||
"panel.settings_spendcap_amount": "Cap ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Savings interest",
|
||||
"panel.settings_interest_enabled_hint": "Periodically pay each child interest on their spendable balance (rewards saving).",
|
||||
"panel.settings_interest_params_label": "Savings interest settings",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "{time} left",
|
||||
"bounty.no_expiry": "No expiry",
|
||||
"bounty.photo_tag": "photo",
|
||||
"bounty.pts": "PTS",
|
||||
"bounty.claim": "Claim",
|
||||
"bounty.claim_hint": "You'll have {hours}h to finish",
|
||||
"bounty.finish_first": "Finish your bounty first",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Give back",
|
||||
"bounty.sibling_on_it": "{name} is on it",
|
||||
"bounty.sibling_back_in": "back on the board in {time} if not finished",
|
||||
"bounty.waiting": "Waiting for a grown-up to check · +{points} ★ pending",
|
||||
"bounty.waiting": "Waiting for a grown-up to check · +{points} {pointsName} pending",
|
||||
"bounty.undo": "Undo",
|
||||
"bounty.sibling_checking": "{name} finished it — being checked",
|
||||
"bounty.you_earned": "You earned +{points} ★",
|
||||
"bounty.you_earned": "You earned +{points} {pointsName}",
|
||||
"bounty.done_by": "Done by {name}",
|
||||
"bounty.recent": "Recently completed",
|
||||
"bounty.claim_title": "Claim \"{title}\"?",
|
||||
"bounty.claim_body": "It's yours for {hours} hours. If it's not done by then, it goes back on the board for everyone.",
|
||||
"bounty.not_now": "Not now",
|
||||
"bounty.claim_it": "Claim it — {points} ★",
|
||||
"bounty.claim_it": "Claim it — {points} {pointsName}",
|
||||
"bounty.photo_title": "Show us the finished job",
|
||||
"bounty.photo_body": "\"{title}\" needs a photo",
|
||||
"bounty.cancel": "Cancel",
|
||||
"bounty.take_photo": "Take photo & send",
|
||||
"bounty.uploading": "Sending…",
|
||||
"bounty.yay_title": "Nice work!",
|
||||
"bounty.yay_body": "A grown-up will check \"{title}\" and then you get {points} ★.",
|
||||
"bounty.yay_body": "A grown-up will check \"{title}\" and then you get {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d}d {h}h",
|
||||
"bounty.dur_hours": "{h}h {m}m",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Completed",
|
||||
"panel.bounty_status_expired": "Expired",
|
||||
"panel.bounty_claimed_meta": "{name} · {time} left",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Nobody claimed · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Expired after a claim lapsed · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Claimed but never finished · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Pick a child in the card editor to see their auctions.",
|
||||
"auction.results": "Results",
|
||||
"auction.load_failed": "Couldn't load the auctions: {error}",
|
||||
"auction.up_to": "up to {points} ★",
|
||||
"auction.up_to": "up to {points} {pointsName}",
|
||||
"auction.lowest_wins": "Lowest bid wins",
|
||||
"auction.gauge_max": "max {points} ★",
|
||||
"auction.gauge_hint": "Normally worth {points} ★ · bid the fewest points you'd do it for",
|
||||
"auction.gauge_max": "max {points} {pointsName}",
|
||||
"auction.gauge_hint": "Normally worth {points} {pointsName} · bid the fewest points you'd do it for",
|
||||
"auction.your_bid": "Your sealed bid",
|
||||
"auction.change": "Change",
|
||||
"auction.withdraw": "Withdraw",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Delete last digit",
|
||||
"auction.rule_secret": "Secret: nobody sees your number.",
|
||||
"auction.rule_lowest": "Lowest bid wins. If it's a tie, whoever bid first wins.",
|
||||
"auction.rule_min": "Bids start at {points} ★.",
|
||||
"auction.rule_min": "Bids start at {points} {pointsName}.",
|
||||
"auction.rule_win": "If you win, you do it on {date} and get your bid when it's approved.",
|
||||
"auction.cancel": "Cancel",
|
||||
"auction.seal": "Seal my bid · {points} ★",
|
||||
"auction.you_won": "You won! {points} ★ is yours",
|
||||
"auction.seal": "Seal my bid · {points} {pointsName}",
|
||||
"auction.you_won": "You won! {points} {pointsName} is yours",
|
||||
"auction.you_won_sub": "when you've done it on {date} and a grown-up approves it",
|
||||
"auction.in_your_chores": "It's in your chores for {date}",
|
||||
"auction.sibling_won": "{name} won this one",
|
||||
"auction.sibling_won_sub": "at {points} ★. Lowest bid wins, so try a little lower next time!",
|
||||
"auction.sibling_won_sub_bid": "at {points} ★ · you bid {bid} ★. Lowest bid wins, so try a little lower next time!",
|
||||
"auction.tied_sub": "You both bid {points} ★. It was a tie, and {name} got their bid in first.",
|
||||
"auction.sibling_won_sub": "at {points} {pointsName}. Lowest bid wins, so try a little lower next time!",
|
||||
"auction.sibling_won_sub_bid": "at {points} {pointsName} · you bid {bid} {pointsName}. Lowest bid wins, so try a little lower next time!",
|
||||
"auction.tied_sub": "You both bid {points} {pointsName}. It was a tie, and {name} got their bid in first.",
|
||||
"auction.no_bids": "No bids this time",
|
||||
"auction.no_bids_sub": "{chore} goes back to its normal assignment on {date}.",
|
||||
"auction.closed_at": "Closed {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Closes",
|
||||
"panel.auction_col_result": "Result",
|
||||
"panel.auction_col_status": "Chore status",
|
||||
"panel.auction_normally": "{date} · normally {points} ★",
|
||||
"panel.auction_min_meta": "min {points} ★",
|
||||
"panel.auction_normally": "{date} · normally {points} {points_name}",
|
||||
"panel.auction_min_meta": "min {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "No bids yet",
|
||||
"panel.auction_no_bids": "No bids",
|
||||
"panel.auction_sealed": "sealed",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "No upcoming occurrence of this chore can be auctioned in the next 60 days.",
|
||||
"panel.auction_no_chores": "No chore can be auctioned yet. Teamwork, timed, open-ended and unassigned chores can't be.",
|
||||
"panel.auction_max_label": "Maximum bid",
|
||||
"panel.auction_normally_worth": "normally worth {points} ★",
|
||||
"panel.auction_normally_worth": "normally worth {points} {points_name}",
|
||||
"panel.auction_max_hint": "Children can bid anything from the minimum up to this. Lowest wins; ties go to the earliest bid.",
|
||||
"panel.auction_min_label": "Minimum bid",
|
||||
"panel.auction_min_hint": "Off: any bid from 1 counts. On: stops a race to the bottom.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Next reward progress",
|
||||
"kiosk.editor.show_picker_progress": "Progress on the picker",
|
||||
"kiosk.editor.show_picker_progress_helper": "“3 of 6 done” under each face",
|
||||
"kiosk.editor.play_sounds": "Sounds",
|
||||
"kiosk.editor.play_sounds_helper": "Each chore's completion sound, and the undo sound",
|
||||
"kiosk.editor.show_confetti": "Confetti",
|
||||
"kiosk.editor.show_completion_popup": "Celebration popup",
|
||||
"kiosk.editor.show_completion_popup_helper": "A short “Awesome!” with the points after each chore",
|
||||
"kiosk.editor.children_heading": "Children shown and PINs",
|
||||
"kiosk.editor.children_hint": "Untick a child to hide them. Use the arrows to set the order on the picker.",
|
||||
"kiosk.editor.require_pin": "Require PIN",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Turn off for a view-only board",
|
||||
"chore_board.editor.show_legend": "Show the legend",
|
||||
"chore_board.editor.hide_done_periods": "Hide finished time periods",
|
||||
"chore_board.editor.hide_done_periods_helper": "Drop a time of day once every child has finished its chores"
|
||||
"chore_board.editor.hide_done_periods_helper": "Drop a time of day once every child has finished its chores",
|
||||
"notify.bedtime_reminder": "{child_name}, you still have chores to do before bedtime.",
|
||||
"notify.streak_at_risk": "{child_name}, complete a chore today to keep your {streak}-day streak!",
|
||||
"notify.all_chores_done": "{child_name} finished every chore today!",
|
||||
"notify.badge_earned": "{child_name} earned the {badge_name} badge!",
|
||||
"notify.pending_chore_approval": "{child_name} completed '{chore_name}' (+{points} {points_name}) — awaiting approval.",
|
||||
"notify.pending_reward_claim": "{child_name} claimed '{reward_name}' ({cost} {points_name}) — awaiting approval.",
|
||||
"notify.streak_milestone": "{child_name} hit a {days}-day streak — +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} reached level {level}! 🎉",
|
||||
"notify.weekly_digest": "TaskMate weekly digest:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, you still need to do '{chore_name}'.",
|
||||
"notify.mandatory_parent_alert": "{child_name} still hasn't done the mandatory chore '{chore_name}'.",
|
||||
"notify.monthly_report": "TaskMate {month} report:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} won the {month} leaderboard with {points} {points_name}!",
|
||||
"notify.family_goal_reached": "🎉 Family goal reached: {goal_name}! Time for {goal_reward}.",
|
||||
"notify.birthday": "🎂 Happy birthday, {child_name}! Every chore pays {multiplier}× today.",
|
||||
"notify.streak_freeze_used": "❄️ A streak freeze saved {child_name}'s {streak}-day streak ({freezes_left} left).",
|
||||
"notify.presence_arrival": "🏠 You're home, {child_name} — {count} chores left today.",
|
||||
"notify.wish_requested": "{child_name} wished for '{wish_name}' ({target} {points_name}) — waiting for your approval.",
|
||||
"notify.wish_pledged": "💝 {pledger} added {points} {points_name} to your wish '{wish_name}'!",
|
||||
"notify.bounty_posted": "🏁 New bounty: {bounty_name} — {points} {points_name}. First to claim it gets it!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, {minutes} minutes left to finish '{bounty_name}' before it goes back on the board.",
|
||||
"notify.recap_ready": "✨ Your {period} recap is ready, {child_name}! Tap to watch it.",
|
||||
"notify.item_rejected": "↩️ {child_name}, '{item_name}' was sent back{reason_text}",
|
||||
"notify.auction_opened": "🔨 New auction: {chore_name} on {date}, up to {max_points} {points_name}. Lowest bid wins!",
|
||||
"notify.auction_closing": "⏳ {child_name}, bidding on '{chore_name}' closes in {minutes} minutes.",
|
||||
"notify.auction_result": "🔨 Auction closed: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, a grown-up is coming to inspect '{chore_name}' before {until}. Keep it looking great for +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Inspection passed, {child_name}! '{chore_name}' looked great: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 {child_name}'s '{chore_name}' inspection closes in {minutes} minutes — pass or fail it before {until}.",
|
||||
"notify.auction_won": "{child_name} won '{chore_name}' on {date} for {points} {points_name}.",
|
||||
"notify.auction_no_bids": "no bids for '{chore_name}', so it goes back to the normal rota.",
|
||||
"notify.auction_called_off": "the auction for '{chore_name}' was called off. It goes back to the normal rota.",
|
||||
"notify.weekly_digest_line": "• {child_name}: {chores} chores, {points} {points_name} earned",
|
||||
"notify.monthly_report_line": "• {child_name}: {chores} chores, {points} {points_name}, level {level}, best streak {streak}",
|
||||
"notify.action_approve": "Approve",
|
||||
"notify.action_reject": "Reject",
|
||||
"notify.action_reject_placeholder": "Reason (optional)",
|
||||
"notify.approve_in_panel_hint": "Open the TaskMate panel to approve or reject.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} finished every chore today!",
|
||||
"notify.celebrate_birthday": "Happy birthday, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} completed the challenge '{challenge_name}'!",
|
||||
"notify.celebrate_inspection_passed": "{child_name}'s {chore_name} passed inspection!",
|
||||
"notify.celebrate_level_up": "{child_name} reached level {level}!",
|
||||
"notify.celebrate_perfect_week": "{child_name} earned a perfect week — +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} reached the weekly target for '{chore_name}' — +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} completed the quest '{quest_name}'!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} hit a {days}-day streak!",
|
||||
"notify.recap_ready_many": "✨ {count} new recaps are ready, {child_name}! Tap to watch them.",
|
||||
"notify.recap_parent_one": "✨ New {period} recaps are ready for {names}. Tap to see them.",
|
||||
"notify.recap_parent_many": "✨ New recaps are ready for {names}. Tap to see them.",
|
||||
"notify.period_weekly": "weekly",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} and {last}",
|
||||
"notify.month_year": "{month} {year}",
|
||||
"notify.short_date": "{weekday} {day} {month}",
|
||||
"notify.month_1": "January",
|
||||
"notify.month_2": "February",
|
||||
"notify.month_3": "March",
|
||||
"notify.month_4": "April",
|
||||
"notify.month_5": "May",
|
||||
"notify.month_6": "June",
|
||||
"notify.month_7": "July",
|
||||
"notify.month_8": "August",
|
||||
"notify.month_9": "September",
|
||||
"notify.month_10": "October",
|
||||
"notify.month_11": "November",
|
||||
"notify.month_12": "December",
|
||||
"notify.month_short_1": "Jan",
|
||||
"notify.month_short_2": "Feb",
|
||||
"notify.month_short_3": "Mar",
|
||||
"notify.month_short_4": "Apr",
|
||||
"notify.month_short_5": "May",
|
||||
"notify.month_short_6": "Jun",
|
||||
"notify.month_short_7": "Jul",
|
||||
"notify.month_short_8": "Aug",
|
||||
"notify.month_short_9": "Sep",
|
||||
"notify.month_short_10": "Oct",
|
||||
"notify.month_short_11": "Nov",
|
||||
"notify.month_short_12": "Dec",
|
||||
"notify.weekday_short_1": "Mon",
|
||||
"notify.weekday_short_2": "Tue",
|
||||
"notify.weekday_short_3": "Wed",
|
||||
"notify.weekday_short_4": "Thu",
|
||||
"notify.weekday_short_5": "Fri",
|
||||
"notify.weekday_short_6": "Sat",
|
||||
"notify.weekday_short_7": "Sun",
|
||||
"notify.sample_chore": "Tidy room",
|
||||
"notify.sample_reward": "Movie night",
|
||||
"notify.sample_badge": "Star Helper",
|
||||
"notify.sample_goal": "Movie night fund",
|
||||
"notify.sample_goal_reward": "a family movie night",
|
||||
"notify.sample_wish": "Lego set",
|
||||
"notify.sample_pledger": "Grandma",
|
||||
"notify.sample_bounty": "Wash the car",
|
||||
"notify.sample_reason": "Not finished",
|
||||
"notify.sample_note": "Brilliant job!"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Appliquer la pénalité",
|
||||
"approvals.postpone": "Reporter",
|
||||
"approvals.dismiss": "Ignorer",
|
||||
"approvals.penalty_points": "Pénalité : −{points} pts",
|
||||
"approvals.penalty_points": "Pénalité : −{points} {pointsName}",
|
||||
"approvals.no_penalty": "Aucune pénalité définie",
|
||||
"approvals.timed_duration": "{count} min",
|
||||
"badge.criteria_and": "ET",
|
||||
@@ -253,6 +253,7 @@
|
||||
"child.open_ended_tag": "À décrire",
|
||||
"child.weekly_target_done": "Terminé pour cette semaine",
|
||||
"child.weekly_target_progress": "{done}/{target} cette semaine",
|
||||
"child.weekly_target_bonus_reached": "🎉 Objectif de la semaine atteint ! +{points}",
|
||||
"child.team_progress": "{joined} / {size} inscrits",
|
||||
"child.team_you_joined": "Tu en fais partie",
|
||||
"child.team_join": "Rejoindre",
|
||||
@@ -326,8 +327,8 @@
|
||||
"child.paused": "En pause",
|
||||
"child.please_define_child_id": "Veuillez définir un child_id",
|
||||
"child.please_define_entity": "Veuillez définir une entité (capteur de vue d'ensemble TaskMate)",
|
||||
"child.pts_per_min": "pts/min",
|
||||
"child.pts_per_n_min": "pts/{count} min",
|
||||
"child.pts_per_min": "{pointsName}/min",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min",
|
||||
"child.recurring": "Récurrent",
|
||||
"child.resume": "Reprendre",
|
||||
"child.start": "Démarrer",
|
||||
@@ -354,6 +355,8 @@
|
||||
"common.editor.header_colour_helper": "Couleur d'arrière-plan de l'en-tête de la carte",
|
||||
"common.editor.overview_entity": "Entité de vue d'ensemble",
|
||||
"common.editor.overview_entity_helper": "L'entité du capteur de vue d'ensemble TaskMate",
|
||||
"common.editor.show_parent_actions": "Afficher les actions parentales",
|
||||
"common.editor.show_parent_actions_helper": "Désactivez sur un tableau de bord partagé des enfants pour afficher cette carte exactement comme un enfant la voit, même lorsqu'un parent est connecté. Affichage uniquement — les autorisations ne changent pas.",
|
||||
"common.entity": "Entité",
|
||||
"common.entity_not_found": "Entité non trouvée : {entity}",
|
||||
"common.evening": "Soir",
|
||||
@@ -772,6 +775,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/semaine",
|
||||
"panel.chore_weekly_target_hint": "Combien de fois par semaine la tâche doit être faite, n'importe lesquels des jours choisis. Elle n'est plus proposée une fois le compte atteint et repart le lundi.",
|
||||
"panel.chore_weekly_target_label": "Objectif hebdomadaire (0 = désactivé)",
|
||||
"panel.chore_weekly_target_bonus_label": "Bonus d’objectif hebdomadaire",
|
||||
"panel.chore_weekly_target_bonus_hint": "Points supplémentaires versés une fois l’objectif de la semaine atteint, en plus des points de chaque réalisation. Versé une fois par semaine, à la validation de la dernière réalisation. 0 = pas de bonus.",
|
||||
"panel.chore_team_section": "Travail d'équipe (facultatif)",
|
||||
"panel.chore_team_hint": "Une tâche d'équipe ne compte qu'une fois que ce nombre d'enfants différents l'ont rejointe. Chaque appui sur Terminé est une inscription, et quand l'équipe est complète, la tâche est soumise pour tous. Les inscriptions sont remises à zéro chaque jour. Nécessite l'attribution « Tous » et ne peut pas être une tâche ouverte.",
|
||||
"panel.chore_team_size_label": "Taille de l'équipe (0 = désactivé)",
|
||||
@@ -791,6 +796,9 @@
|
||||
"panel.chore_points_label": "Points",
|
||||
"panel.chore_points_per_window_label": "Points par période",
|
||||
"panel.chore_recurrence_day_hint": "ex. lundi — laisser vide pour n'importe quel jour",
|
||||
"panel.chore_recurrence_interval_label": "Répéter tous les (jours)",
|
||||
"panel.chore_recurrence_interval_hint": "Nombre entier de jours, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Saisissez un nombre de jours entre {min} et {max}",
|
||||
"panel.chore_recurrence_day_label": "Jour de la semaine (hebdomadaire uniquement)",
|
||||
"panel.chore_recurrence_label": "Récurrence",
|
||||
"panel.chore_recurrence_start_hint": "Date de début — vide = aujourd'hui",
|
||||
@@ -1227,8 +1235,10 @@
|
||||
"panel.penbon_table_name": "Nom",
|
||||
"panel.penbon_table_points": "Points",
|
||||
"panel.pending_tooltip": "{count} en attente — cliquer pour vérifier",
|
||||
"panel.pts_display": "{count} pts",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "Tous les 2 jours",
|
||||
"panel.recurrence_every_n_days": "Tous les N jours (personnalisé)",
|
||||
"panel.recurrence_every_count_days": "Tous les {count} jours",
|
||||
"panel.recurrence_every_2_weeks": "Toutes les 2 semaines",
|
||||
"panel.recurrence_every_3_months": "Tous les 3 mois",
|
||||
"panel.recurrence_every_6_months": "Tous les 6 mois",
|
||||
@@ -1464,7 +1474,7 @@
|
||||
"panel.template_builtin_template_packs": "Packs de modèles intégrés",
|
||||
"panel.template_builtin_templates": "Modèles intégrés",
|
||||
"panel.template_chores_in_template": "Tâches dans ce modèle",
|
||||
"panel.template_confirm_bar": "{count} tâche(s) seront créées · {points} pts au total",
|
||||
"panel.template_confirm_bar": "{count} tâche(s) seront créées · {points} {points_name} au total",
|
||||
"panel.template_create_btn": "Créer {count} tâche(s)",
|
||||
"panel.template_create_title": "Créer un modèle",
|
||||
"panel.template_custom": "Personnalisé",
|
||||
@@ -1476,7 +1486,7 @@
|
||||
"panel.template_name_label": "Nom",
|
||||
"panel.template_picker_title": "Ajouter depuis un modèle",
|
||||
"panel.template_preview_hint": "Vérifiez et personnalisez chaque tâche avant de créer. Développez pour modifier tous les champs, ou retirez avec ✕.",
|
||||
"panel.template_pts_total": "{count} tâche(s) · {points} pts au total",
|
||||
"panel.template_pts_total": "{count} tâche(s) · {points} {points_name} au total",
|
||||
"panel.template_requires_approval_label": "Nécessite approbation",
|
||||
"panel.template_requires_approval_no": "Non",
|
||||
"panel.template_requires_approval_yes": "Oui",
|
||||
@@ -1687,7 +1697,7 @@
|
||||
"points_display.this_week": "Cette semaine",
|
||||
"points_display.total_points": "Points totaux",
|
||||
"points_display.weekly_plus": "+{count} cette semaine",
|
||||
"points_display.weekly_pts": "{count} pts",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Ajoutez d'abord des tâches à cet enfant",
|
||||
"reorder.card_description": "Une carte pour réorganiser les tâches par enfant, organisées par catégorie de temps",
|
||||
"reorder.card_name": "Carte de réorganisation TaskMate",
|
||||
@@ -1792,9 +1802,9 @@
|
||||
"streak.achievement.hundred_desc": "Accomplis 100 tâches",
|
||||
"streak.achievement.hundred_name": "100 faites",
|
||||
"streak.achievement.points100_desc": "Gagne 100 points au total",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Gagne 50 points au total",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "Série de 14 jours",
|
||||
"streak.achievement.streak14_name": "2 semaines",
|
||||
"streak.achievement.streak30_desc": "Série de 30 jours",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Réglages du bonus surprise",
|
||||
"panel.settings_surprise_params_hint": "Chance quotidienne par enfant et plage de points aléatoire en cas de réussite.",
|
||||
"panel.settings_surprise_chance": "Chance %",
|
||||
"panel.settings_surprise_min": "Pts min",
|
||||
"panel.settings_surprise_max": "Pts max",
|
||||
"panel.settings_surprise_min": "{points_name} min",
|
||||
"panel.settings_surprise_max": "{points_name} max",
|
||||
"panel.reward_restock_label": "Réapprovisionnement auto",
|
||||
"panel.reward_restock_hint": "Réinitialiser périodiquement le stock de cette récompense à une quantité fixe.",
|
||||
"panel.reward_restock_amount": "Réapprovisionner à",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Limite le nombre de points que chaque enfant peut dépenser en récompenses par période.",
|
||||
"panel.settings_spendcap_params_label": "Réglages du plafond de dépenses",
|
||||
"panel.settings_spendcap_params_hint": "Points maximum dépensables par période (0 = aucune limite).",
|
||||
"panel.settings_spendcap_amount": "Plafond (pts)",
|
||||
"panel.settings_spendcap_amount": "Plafond ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Intérêts d'épargne",
|
||||
"panel.settings_interest_enabled_hint": "Verse régulièrement des intérêts à chaque enfant sur son solde disponible (récompense l'épargne).",
|
||||
"panel.settings_interest_params_label": "Réglages des intérêts d'épargne",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "encore {time}",
|
||||
"bounty.no_expiry": "Sans limite",
|
||||
"bounty.photo_tag": "photo",
|
||||
"bounty.pts": "PTS",
|
||||
"bounty.claim": "Réserver",
|
||||
"bounty.claim_hint": "Tu auras {hours} h pour la faire",
|
||||
"bounty.finish_first": "Termine d'abord ta prime",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Rendre",
|
||||
"bounty.sibling_on_it": "{name} s'en occupe",
|
||||
"bounty.sibling_back_in": "de retour sur le tableau dans {time} si ce n'est pas fini",
|
||||
"bounty.waiting": "En attente de vérification par un adulte · +{points} ★ en attente",
|
||||
"bounty.waiting": "En attente de vérification par un adulte · +{points} {pointsName} en attente",
|
||||
"bounty.undo": "Annuler",
|
||||
"bounty.sibling_checking": "{name} a fini — en cours de vérification",
|
||||
"bounty.you_earned": "Tu as gagné +{points} ★",
|
||||
"bounty.you_earned": "Tu as gagné +{points} {pointsName}",
|
||||
"bounty.done_by": "Faite par {name}",
|
||||
"bounty.recent": "Terminées récemment",
|
||||
"bounty.claim_title": "Réserver « {title} » ?",
|
||||
"bounty.claim_body": "Elle est à toi pendant {hours} heures. Si elle n'est pas faite d'ici là, elle revient sur le tableau pour tout le monde.",
|
||||
"bounty.not_now": "Pas maintenant",
|
||||
"bounty.claim_it": "La réserver — {points} ★",
|
||||
"bounty.claim_it": "La réserver — {points} {pointsName}",
|
||||
"bounty.photo_title": "Montre-nous le travail fini",
|
||||
"bounty.photo_body": "« {title} » demande une photo",
|
||||
"bounty.cancel": "Annuler",
|
||||
"bounty.take_photo": "Prendre une photo et envoyer",
|
||||
"bounty.uploading": "Envoi…",
|
||||
"bounty.yay_title": "Bravo !",
|
||||
"bounty.yay_body": "Un adulte va vérifier « {title} » et tu recevras ensuite {points} ★.",
|
||||
"bounty.yay_body": "Un adulte va vérifier « {title} » et tu recevras ensuite {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d} j {h} h",
|
||||
"bounty.dur_hours": "{h} h {m} min",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Terminée",
|
||||
"panel.bounty_status_expired": "Expirée",
|
||||
"panel.bounty_claimed_meta": "{name} · encore {time}",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Personne ne l'a réservée · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Expirée après une réservation expirée · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Réservée mais jamais terminée · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Choisis un enfant dans l'éditeur de la carte pour voir ses enchères.",
|
||||
"auction.results": "Résultats",
|
||||
"auction.load_failed": "Impossible de charger les enchères : {error}",
|
||||
"auction.up_to": "jusqu'à {points} ★",
|
||||
"auction.up_to": "jusqu'à {points} {pointsName}",
|
||||
"auction.lowest_wins": "L'offre la plus basse gagne",
|
||||
"auction.gauge_max": "max. {points} ★",
|
||||
"auction.gauge_hint": "Vaut normalement {points} ★ · propose le moins de points pour lequel tu le ferais",
|
||||
"auction.gauge_max": "max. {points} {pointsName}",
|
||||
"auction.gauge_hint": "Vaut normalement {points} {pointsName} · propose le moins de points pour lequel tu le ferais",
|
||||
"auction.your_bid": "Ton offre secrète",
|
||||
"auction.change": "Modifier",
|
||||
"auction.withdraw": "Retirer",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Effacer le dernier chiffre",
|
||||
"auction.rule_secret": "Secret : personne ne voit ton nombre.",
|
||||
"auction.rule_lowest": "L'offre la plus basse gagne. En cas d'égalité, la première offre gagne.",
|
||||
"auction.rule_min": "Les offres commencent à {points} ★.",
|
||||
"auction.rule_min": "Les offres commencent à {points} {pointsName}.",
|
||||
"auction.rule_win": "Si tu gagnes, tu le fais le {date} et tu reçois ton offre une fois validée.",
|
||||
"auction.cancel": "Annuler",
|
||||
"auction.seal": "Sceller mon offre · {points} ★",
|
||||
"auction.you_won": "Gagné ! {points} ★ sont pour toi",
|
||||
"auction.seal": "Sceller mon offre · {points} {pointsName}",
|
||||
"auction.you_won": "Gagné ! {points} {pointsName} sont pour toi",
|
||||
"auction.you_won_sub": "quand tu l'auras fait le {date} et qu'un adulte l'aura validé",
|
||||
"auction.in_your_chores": "C'est dans tes tâches du {date}",
|
||||
"auction.sibling_won": "{name} a remporté celle-ci",
|
||||
"auction.sibling_won_sub": "pour {points} ★. L'offre la plus basse gagne, alors vise un peu plus bas la prochaine fois !",
|
||||
"auction.sibling_won_sub_bid": "pour {points} ★ · tu as proposé {bid} ★. L'offre la plus basse gagne, alors vise un peu plus bas la prochaine fois !",
|
||||
"auction.tied_sub": "Vous avez proposé {points} ★ tous les deux. Égalité, et {name} a fait son offre en premier.",
|
||||
"auction.sibling_won_sub": "pour {points} {pointsName}. L'offre la plus basse gagne, alors vise un peu plus bas la prochaine fois !",
|
||||
"auction.sibling_won_sub_bid": "pour {points} {pointsName} · tu as proposé {bid} {pointsName}. L'offre la plus basse gagne, alors vise un peu plus bas la prochaine fois !",
|
||||
"auction.tied_sub": "Vous avez proposé {points} {pointsName} tous les deux. Égalité, et {name} a fait son offre en premier.",
|
||||
"auction.no_bids": "Aucune offre cette fois",
|
||||
"auction.no_bids_sub": "{chore} revient à son attribution habituelle le {date}.",
|
||||
"auction.closed_at": "Terminée {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Fin",
|
||||
"panel.auction_col_result": "Résultat",
|
||||
"panel.auction_col_status": "État de la tâche",
|
||||
"panel.auction_normally": "{date} · normalement {points} ★",
|
||||
"panel.auction_min_meta": "min. {points} ★",
|
||||
"panel.auction_normally": "{date} · normalement {points} {points_name}",
|
||||
"panel.auction_min_meta": "min. {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "Pas encore d'offre",
|
||||
"panel.auction_no_bids": "Aucune offre",
|
||||
"panel.auction_sealed": "scellée",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "Aucune occurrence de cette tâche ne peut être mise aux enchères dans les 60 prochains jours.",
|
||||
"panel.auction_no_chores": "Aucune tâche ne peut encore être mise aux enchères. Les tâches en équipe, chronométrées, ouvertes et non attribuées ne le peuvent pas.",
|
||||
"panel.auction_max_label": "Offre maximale",
|
||||
"panel.auction_normally_worth": "vaut normalement {points} ★",
|
||||
"panel.auction_normally_worth": "vaut normalement {points} {points_name}",
|
||||
"panel.auction_max_hint": "Les enfants peuvent proposer de l'offre minimale jusqu'à ce montant. La plus basse gagne ; en cas d'égalité, la plus ancienne.",
|
||||
"panel.auction_min_label": "Offre minimale",
|
||||
"panel.auction_min_hint": "Désactivé : toute offre à partir de 1 compte. Activé : évite une course vers le bas.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Progression vers la prochaine récompense",
|
||||
"kiosk.editor.show_picker_progress": "Progression sur l'écran de choix",
|
||||
"kiosk.editor.show_picker_progress_helper": "« 3 sur 6 faites » sous chaque visage",
|
||||
"kiosk.editor.play_sounds": "Sons",
|
||||
"kiosk.editor.play_sounds_helper": "Le son de fin de chaque tâche, et le son d'annulation",
|
||||
"kiosk.editor.show_confetti": "Confettis",
|
||||
"kiosk.editor.show_completion_popup": "Message de fête",
|
||||
"kiosk.editor.show_completion_popup_helper": "Un court « Génial ! » avec les points après chaque tâche",
|
||||
"kiosk.editor.children_heading": "Enfants affichés et codes PIN",
|
||||
"kiosk.editor.children_hint": "Décoche un enfant pour le masquer. Utilise les flèches pour choisir l'ordre sur l'écran de choix.",
|
||||
"kiosk.editor.require_pin": "Exiger le code PIN",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Désactivez pour un tableau en lecture seule",
|
||||
"chore_board.editor.show_legend": "Afficher la légende",
|
||||
"chore_board.editor.hide_done_periods": "Masquer les moments terminés",
|
||||
"chore_board.editor.hide_done_periods_helper": "Retirer un moment de la journée dès que tous les enfants ont fini leurs tâches"
|
||||
"chore_board.editor.hide_done_periods_helper": "Retirer un moment de la journée dès que tous les enfants ont fini leurs tâches",
|
||||
"notify.bedtime_reminder": "{child_name}, tu as encore des tâches à faire avant d'aller au lit.",
|
||||
"notify.streak_at_risk": "{child_name}, fais une tâche aujourd'hui pour garder ta série de {streak} jours !",
|
||||
"notify.all_chores_done": "{child_name} a terminé toutes ses tâches aujourd'hui !",
|
||||
"notify.badge_earned": "{child_name} a gagné le badge « {badge_name} » !",
|
||||
"notify.pending_chore_approval": "{child_name} a terminé « {chore_name} » (+{points} {points_name}) — en attente de validation.",
|
||||
"notify.pending_reward_claim": "{child_name} a demandé « {reward_name} » ({cost} {points_name}) — en attente de validation.",
|
||||
"notify.streak_milestone": "{child_name} a atteint une série de {days} jours — +{points} {points_name} !",
|
||||
"notify.level_up": "{child_name} a atteint le niveau {level} ! 🎉",
|
||||
"notify.weekly_digest": "Résumé hebdomadaire TaskMate :\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, tu dois encore faire « {chore_name} ».",
|
||||
"notify.mandatory_parent_alert": "{child_name} n'a toujours pas fait la tâche obligatoire « {chore_name} ».",
|
||||
"notify.monthly_report": "Rapport TaskMate de {month} :\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} a remporté le classement de {month} avec {points} {points_name} !",
|
||||
"notify.family_goal_reached": "🎉 Objectif familial atteint : {goal_name} ! C'est l'heure de {goal_reward}.",
|
||||
"notify.birthday": "🎂 Joyeux anniversaire, {child_name} ! Chaque tâche rapporte {multiplier}× aujourd'hui.",
|
||||
"notify.streak_freeze_used": "❄️ Un gel de série a sauvé la série de {streak} jours de {child_name} (il en reste {freezes_left}).",
|
||||
"notify.presence_arrival": "🏠 Te voilà à la maison, {child_name} — encore {count} tâches aujourd'hui.",
|
||||
"notify.wish_requested": "{child_name} souhaite « {wish_name} » ({target} {points_name}) — en attente de ta validation.",
|
||||
"notify.wish_pledged": "💝 {pledger} a ajouté {points} {points_name} à ton souhait « {wish_name} » !",
|
||||
"notify.bounty_posted": "🏁 Nouvelle prime : {bounty_name} — {points} {points_name}. Le premier qui la réclame la remporte !",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, plus que {minutes} minutes pour finir « {bounty_name} » avant qu'elle ne retourne au tableau.",
|
||||
"notify.recap_ready": "✨ Ton récap est prêt ({period}), {child_name} ! Touche pour le regarder.",
|
||||
"notify.item_rejected": "↩️ {child_name}, « {item_name} » a été renvoyé{reason_text}",
|
||||
"notify.auction_opened": "🔨 Nouvelle enchère : {chore_name} le {date}, jusqu'à {max_points} {points_name}. L'offre la plus basse gagne !",
|
||||
"notify.auction_closing": "⏳ {child_name}, les enchères pour « {chore_name} » se terminent dans {minutes} minutes.",
|
||||
"notify.auction_result": "🔨 Enchère terminée : {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, un adulte va inspecter « {chore_name} » avant {until}. Garde-le impeccable pour +{bonus} {points_name} !",
|
||||
"notify.inspection_passed": "🌟 Inspection réussie, {child_name} ! « {chore_name} » était parfait : +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 L'inspection de « {chore_name} » de {child_name} se termine dans {minutes} minutes — valide-la ou refuse-la avant {until}.",
|
||||
"notify.auction_won": "{child_name} a remporté « {chore_name} » le {date} pour {points} {points_name}.",
|
||||
"notify.auction_no_bids": "aucune offre pour « {chore_name} », la tâche retourne dans la rotation normale.",
|
||||
"notify.auction_called_off": "l'enchère pour « {chore_name} » a été annulée. La tâche retourne dans la rotation normale.",
|
||||
"notify.weekly_digest_line": "• {child_name} : {chores} tâches, {points} {points_name} gagnés",
|
||||
"notify.monthly_report_line": "• {child_name} : {chores} tâches, {points} {points_name}, niveau {level}, meilleure série {streak}",
|
||||
"notify.action_approve": "Valider",
|
||||
"notify.action_reject": "Refuser",
|
||||
"notify.action_reject_placeholder": "Raison (facultatif)",
|
||||
"notify.approve_in_panel_hint": "Ouvre le panneau TaskMate pour valider ou refuser.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} a terminé toutes ses tâches aujourd'hui !",
|
||||
"notify.celebrate_birthday": "Joyeux anniversaire, {child_name} !",
|
||||
"notify.celebrate_challenge_completed": "{child_name} a réussi le défi « {challenge_name} » !",
|
||||
"notify.celebrate_inspection_passed": "« {chore_name} » de {child_name} a réussi l'inspection !",
|
||||
"notify.celebrate_level_up": "{child_name} a atteint le niveau {level} !",
|
||||
"notify.celebrate_perfect_week": "{child_name} a fait une semaine parfaite — +{bonus} !",
|
||||
"notify.celebrate_weekly_target": "{child_name} a atteint l’objectif de la semaine pour « {chore_name} » — +{bonus} !",
|
||||
"notify.celebrate_quest_completed": "{child_name} a terminé la quête « {quest_name} » !",
|
||||
"notify.celebrate_streak_milestone": "{child_name} a atteint une série de {days} jours !",
|
||||
"notify.recap_ready_many": "✨ {count} nouveaux récaps sont prêts, {child_name} ! Touche pour les regarder.",
|
||||
"notify.recap_parent_one": "✨ Nouveaux récaps prêts ({period}) pour {names}. Touche pour les voir.",
|
||||
"notify.recap_parent_many": "✨ Nouveaux récaps prêts pour {names}. Touche pour les voir.",
|
||||
"notify.period_weekly": "semaine",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} et {last}",
|
||||
"notify.month_year": "{month} {year}",
|
||||
"notify.short_date": "{weekday} {day} {month}",
|
||||
"notify.month_1": "janvier",
|
||||
"notify.month_2": "février",
|
||||
"notify.month_3": "mars",
|
||||
"notify.month_4": "avril",
|
||||
"notify.month_5": "mai",
|
||||
"notify.month_6": "juin",
|
||||
"notify.month_7": "juillet",
|
||||
"notify.month_8": "août",
|
||||
"notify.month_9": "septembre",
|
||||
"notify.month_10": "octobre",
|
||||
"notify.month_11": "novembre",
|
||||
"notify.month_12": "décembre",
|
||||
"notify.month_short_1": "janv.",
|
||||
"notify.month_short_2": "févr.",
|
||||
"notify.month_short_3": "mars",
|
||||
"notify.month_short_4": "avr.",
|
||||
"notify.month_short_5": "mai",
|
||||
"notify.month_short_6": "juin",
|
||||
"notify.month_short_7": "juil.",
|
||||
"notify.month_short_8": "août",
|
||||
"notify.month_short_9": "sept.",
|
||||
"notify.month_short_10": "oct.",
|
||||
"notify.month_short_11": "nov.",
|
||||
"notify.month_short_12": "déc.",
|
||||
"notify.weekday_short_1": "lun.",
|
||||
"notify.weekday_short_2": "mar.",
|
||||
"notify.weekday_short_3": "mer.",
|
||||
"notify.weekday_short_4": "jeu.",
|
||||
"notify.weekday_short_5": "ven.",
|
||||
"notify.weekday_short_6": "sam.",
|
||||
"notify.weekday_short_7": "dim.",
|
||||
"notify.sample_chore": "Ranger la chambre",
|
||||
"notify.sample_reward": "Soirée cinéma",
|
||||
"notify.sample_badge": "Super assistant",
|
||||
"notify.sample_goal": "Cagnotte soirée cinéma",
|
||||
"notify.sample_goal_reward": "une soirée cinéma en famille",
|
||||
"notify.sample_wish": "Boîte de Lego",
|
||||
"notify.sample_pledger": "Mamie",
|
||||
"notify.sample_bounty": "Laver la voiture",
|
||||
"notify.sample_reason": "Pas terminé",
|
||||
"notify.sample_note": "Bravo, super travail !"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Gi straff",
|
||||
"approvals.postpone": "Utsett",
|
||||
"approvals.dismiss": "Avvis",
|
||||
"approvals.penalty_points": "Straff: −{points} poeng",
|
||||
"approvals.penalty_points": "Straff: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "Ingen straff angitt",
|
||||
"approvals.timed_duration": "{count} min",
|
||||
"badge.criteria_and": "OG",
|
||||
@@ -253,6 +253,7 @@
|
||||
"child.open_ended_tag": "Beskriv",
|
||||
"child.weekly_target_done": "Ferdig for denne uka",
|
||||
"child.weekly_target_progress": "{done}/{target} denne uka",
|
||||
"child.weekly_target_bonus_reached": "🎉 Ukemålet er nådd! +{points}",
|
||||
"child.team_progress": "{joined} / {size} med",
|
||||
"child.team_you_joined": "Du er med",
|
||||
"child.team_join": "Bli med",
|
||||
@@ -326,8 +327,8 @@
|
||||
"child.paused": "Pauset",
|
||||
"child.please_define_child_id": "Vennligst definer en child_id",
|
||||
"child.please_define_entity": "Vennligst definer en entitet (TaskMate oversiktssensor)",
|
||||
"child.pts_per_min": "poeng/min",
|
||||
"child.pts_per_n_min": "poeng/{count} min",
|
||||
"child.pts_per_min": "{pointsName}/min",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min",
|
||||
"child.recurring": "Gjentagende",
|
||||
"child.resume": "Fortsett",
|
||||
"child.start": "Start",
|
||||
@@ -354,6 +355,8 @@
|
||||
"common.editor.header_colour_helper": "Bakgrunnsfarge for korttoppen",
|
||||
"common.editor.overview_entity": "Oversiktsentitet",
|
||||
"common.editor.overview_entity_helper": "TaskMate oversiktssensor-entiteten",
|
||||
"common.editor.show_parent_actions": "Vis foreldrehandlinger",
|
||||
"common.editor.show_parent_actions_helper": "Slå av på et delt barnedashbord for å vise kortet nøyaktig slik et barn ser det, selv når en forelder er logget inn. Kun visning — tillatelsene endres ikke.",
|
||||
"common.entity": "Entitet",
|
||||
"common.entity_not_found": "Entitet ikke funnet: {entity}",
|
||||
"common.evening": "Kveld",
|
||||
@@ -772,6 +775,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/uke",
|
||||
"panel.chore_weekly_target_hint": "Hvor mange ganger i uka oppgaven må gjøres, på hvilke som helst av de valgte dagene. Den vises ikke lenger når antallet er nådd, og nullstilles på mandag.",
|
||||
"panel.chore_weekly_target_label": "Ukesmål (0 = av)",
|
||||
"panel.chore_weekly_target_bonus_label": "Bonus for ukemål",
|
||||
"panel.chore_weekly_target_bonus_hint": "Ekstra poeng når ukemålet er nådd, i tillegg til poengene for hver gjennomføring. Utbetales én gang i uken, når den siste gjennomføringen er godkjent. 0 = ingen bonus.",
|
||||
"panel.chore_team_section": "Lagarbeid (valgfritt)",
|
||||
"panel.chore_team_hint": "En lagoppgave teller først når så mange ulike barn er med. Hvert barns trykk på Ferdig melder dem på, og når laget er fullt, sendes oppgaven inn for alle. Påmeldinger nullstilles hver dag. Krever tildelingen «Alle» og kan ikke være en åpen oppgave.",
|
||||
"panel.chore_team_size_label": "Lagstørrelse (0 = av)",
|
||||
@@ -791,6 +796,9 @@
|
||||
"panel.chore_points_label": "Poeng",
|
||||
"panel.chore_points_per_window_label": "Poeng per vindu",
|
||||
"panel.chore_recurrence_day_hint": "f.eks. mandag — la stå tomt for hvilken som helst dag",
|
||||
"panel.chore_recurrence_interval_label": "Gjenta hver (dager)",
|
||||
"panel.chore_recurrence_interval_hint": "Helt antall dager, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Skriv inn et antall dager mellom {min} og {max}",
|
||||
"panel.chore_recurrence_day_label": "Ukedag (kun ukentlig)",
|
||||
"panel.chore_recurrence_label": "Gjentakelse",
|
||||
"panel.chore_recurrence_start_hint": "Ankerdato — tomt = i dag",
|
||||
@@ -1227,8 +1235,10 @@
|
||||
"panel.penbon_table_name": "Navn",
|
||||
"panel.penbon_table_points": "Poeng",
|
||||
"panel.pending_tooltip": "{count} ventende — klikk for å gjennomgå",
|
||||
"panel.pts_display": "{count} poeng",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "Annenhver dag",
|
||||
"panel.recurrence_every_n_days": "Hver N. dag (egendefinert)",
|
||||
"panel.recurrence_every_count_days": "Hver {count}. dag",
|
||||
"panel.recurrence_every_2_weeks": "Annenhver uke",
|
||||
"panel.recurrence_every_3_months": "Hver 3. måned",
|
||||
"panel.recurrence_every_6_months": "Hvert halvår",
|
||||
@@ -1464,7 +1474,7 @@
|
||||
"panel.template_builtin_template_packs": "Innebygde malpakker",
|
||||
"panel.template_builtin_templates": "Innebygde maler",
|
||||
"panel.template_chores_in_template": "Oppgaver i denne malen",
|
||||
"panel.template_confirm_bar": "{count} oppgave(r) opprettes · {points} poeng totalt",
|
||||
"panel.template_confirm_bar": "{count} oppgave(r) opprettes · {points} {points_name} totalt",
|
||||
"panel.template_create_btn": "Opprett {count} oppgave(r)",
|
||||
"panel.template_create_title": "Opprett mal",
|
||||
"panel.template_custom": "Egendefinert",
|
||||
@@ -1476,7 +1486,7 @@
|
||||
"panel.template_name_label": "Navn",
|
||||
"panel.template_picker_title": "Legg til fra mal",
|
||||
"panel.template_preview_hint": "Se gjennom og tilpass hver oppgave før oppretting. Utvid for å redigere alle felt, eller fjern med ✕.",
|
||||
"panel.template_pts_total": "{count} oppgave(r) · {points} poeng totalt",
|
||||
"panel.template_pts_total": "{count} oppgave(r) · {points} {points_name} totalt",
|
||||
"panel.template_requires_approval_label": "Krever godkjenning",
|
||||
"panel.template_requires_approval_no": "Nei",
|
||||
"panel.template_requires_approval_yes": "Ja",
|
||||
@@ -1687,7 +1697,7 @@
|
||||
"points_display.this_week": "Denne uken",
|
||||
"points_display.total_points": "Totale poeng",
|
||||
"points_display.weekly_plus": "+{count} denne uken",
|
||||
"points_display.weekly_pts": "{count} poe.",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Legg til oppgaver for dette barnet først",
|
||||
"reorder.card_description": "Et kort for å omorganisere oppgaver per barn, organisert etter tidskategori",
|
||||
"reorder.card_name": "TaskMate Omorganiseringskort",
|
||||
@@ -1792,9 +1802,9 @@
|
||||
"streak.achievement.hundred_desc": "Fullfør 100 oppgaver",
|
||||
"streak.achievement.hundred_name": "100 gjort",
|
||||
"streak.achievement.points100_desc": "Tjen 100 poeng totalt",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Tjen 50 poeng totalt",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "14 dagers rekke",
|
||||
"streak.achievement.streak14_name": "2 uker",
|
||||
"streak.achievement.streak30_desc": "30 dagers rekke",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Innstillinger for overraskelsesbonus",
|
||||
"panel.settings_surprise_params_hint": "Daglig sjanse per barn, og det tilfeldige poengspennet ved treff.",
|
||||
"panel.settings_surprise_chance": "Sjanse %",
|
||||
"panel.settings_surprise_min": "Min poeng",
|
||||
"panel.settings_surprise_max": "Maks poeng",
|
||||
"panel.settings_surprise_min": "Min {points_name}",
|
||||
"panel.settings_surprise_max": "Maks {points_name}",
|
||||
"panel.reward_restock_label": "Auto-påfyll",
|
||||
"panel.reward_restock_hint": "Tilbakestill belønningens lager til et fast antall med jevne mellomrom.",
|
||||
"panel.reward_restock_amount": "Fyll opp til",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Begrens hvor mange poeng hvert barn kan bruke på belønninger per periode.",
|
||||
"panel.settings_spendcap_params_label": "Innstillinger for forbruksgrense",
|
||||
"panel.settings_spendcap_params_hint": "Maks poeng som kan brukes per periode (0 = ingen grense).",
|
||||
"panel.settings_spendcap_amount": "Grense (poeng)",
|
||||
"panel.settings_spendcap_amount": "Grense ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Sparerente",
|
||||
"panel.settings_interest_enabled_hint": "Betal jevnlig hvert barn rente på den disponible saldoen (belønner sparing).",
|
||||
"panel.settings_interest_params_label": "Innstillinger for sparerente",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "{time} igjen",
|
||||
"bounty.no_expiry": "Utløper ikke",
|
||||
"bounty.photo_tag": "bilde",
|
||||
"bounty.pts": "P",
|
||||
"bounty.claim": "Ta den",
|
||||
"bounty.claim_hint": "Du får {hours} t på å gjøre den ferdig",
|
||||
"bounty.finish_first": "Gjør ferdig dusøren din først",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Gi tilbake",
|
||||
"bounty.sibling_on_it": "{name} holder på",
|
||||
"bounty.sibling_back_in": "tilbake på tavlen om {time} hvis den ikke blir ferdig",
|
||||
"bounty.waiting": "Venter på at en voksen sjekker · +{points} ★ venter",
|
||||
"bounty.waiting": "Venter på at en voksen sjekker · +{points} {pointsName} venter",
|
||||
"bounty.undo": "Angre",
|
||||
"bounty.sibling_checking": "{name} er ferdig — blir sjekket",
|
||||
"bounty.you_earned": "Du tjente +{points} ★",
|
||||
"bounty.you_earned": "Du tjente +{points} {pointsName}",
|
||||
"bounty.done_by": "Gjort av {name}",
|
||||
"bounty.recent": "Nylig fullført",
|
||||
"bounty.claim_title": "Ta «{title}»?",
|
||||
"bounty.claim_body": "Den er din i {hours} timer. Er den ikke gjort innen da, går den tilbake på tavlen for alle.",
|
||||
"bounty.not_now": "Ikke nå",
|
||||
"bounty.claim_it": "Ta den — {points} ★",
|
||||
"bounty.claim_it": "Ta den — {points} {pointsName}",
|
||||
"bounty.photo_title": "Vis oss den ferdige jobben",
|
||||
"bounty.photo_body": "«{title}» trenger et bilde",
|
||||
"bounty.cancel": "Avbryt",
|
||||
"bounty.take_photo": "Ta bilde og send",
|
||||
"bounty.uploading": "Sender…",
|
||||
"bounty.yay_title": "Godt jobbet!",
|
||||
"bounty.yay_body": "En voksen sjekker «{title}», og så får du {points} ★.",
|
||||
"bounty.yay_body": "En voksen sjekker «{title}», og så får du {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d} d {h} t",
|
||||
"bounty.dur_hours": "{h} t {m} min",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Fullført",
|
||||
"panel.bounty_status_expired": "Utløpt",
|
||||
"panel.bounty_claimed_meta": "{name} · {time} igjen",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Ingen tok den · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Utløpt etter at en reservasjon gikk ut · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Tatt, men aldri gjort ferdig · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Velg et barn i kortredigereren for å se auksjonene deres.",
|
||||
"auction.results": "Resultater",
|
||||
"auction.load_failed": "Kunne ikke hente auksjonene: {error}",
|
||||
"auction.up_to": "opptil {points} ★",
|
||||
"auction.up_to": "opptil {points} {pointsName}",
|
||||
"auction.lowest_wins": "Laveste bud vinner",
|
||||
"auction.gauge_max": "maks {points} ★",
|
||||
"auction.gauge_hint": "Vanligvis verdt {points} ★ · by det minste antallet poeng du vil gjøre det for",
|
||||
"auction.gauge_max": "maks {points} {pointsName}",
|
||||
"auction.gauge_hint": "Vanligvis verdt {points} {pointsName} · by det minste antallet poeng du vil gjøre det for",
|
||||
"auction.your_bid": "Ditt hemmelige bud",
|
||||
"auction.change": "Endre",
|
||||
"auction.withdraw": "Trekk tilbake",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Slett siste siffer",
|
||||
"auction.rule_secret": "Hemmelig: ingen ser tallet ditt.",
|
||||
"auction.rule_lowest": "Laveste bud vinner. Ved likt bud vinner den som bød først.",
|
||||
"auction.rule_min": "Budene starter på {points} ★.",
|
||||
"auction.rule_min": "Budene starter på {points} {pointsName}.",
|
||||
"auction.rule_win": "Hvis du vinner, gjør du det {date} og får budet ditt når det er godkjent.",
|
||||
"auction.cancel": "Avbryt",
|
||||
"auction.seal": "Forsegl budet mitt · {points} ★",
|
||||
"auction.you_won": "Du vant! {points} ★ er dine",
|
||||
"auction.seal": "Forsegl budet mitt · {points} {pointsName}",
|
||||
"auction.you_won": "Du vant! {points} {pointsName} er dine",
|
||||
"auction.you_won_sub": "når du har gjort det {date} og en voksen har godkjent det",
|
||||
"auction.in_your_chores": "Den står i oppgavene dine {date}",
|
||||
"auction.sibling_won": "{name} vant denne",
|
||||
"auction.sibling_won_sub": "for {points} ★. Laveste bud vinner, så prøv litt lavere neste gang!",
|
||||
"auction.sibling_won_sub_bid": "for {points} ★ · du bød {bid} ★. Laveste bud vinner, så prøv litt lavere neste gang!",
|
||||
"auction.tied_sub": "Dere bød begge {points} ★. Det ble uavgjort, og {name} bød først.",
|
||||
"auction.sibling_won_sub": "for {points} {pointsName}. Laveste bud vinner, så prøv litt lavere neste gang!",
|
||||
"auction.sibling_won_sub_bid": "for {points} {pointsName} · du bød {bid} {pointsName}. Laveste bud vinner, så prøv litt lavere neste gang!",
|
||||
"auction.tied_sub": "Dere bød begge {points} {pointsName}. Det ble uavgjort, og {name} bød først.",
|
||||
"auction.no_bids": "Ingen bud denne gangen",
|
||||
"auction.no_bids_sub": "{chore} går tilbake til vanlig fordeling {date}.",
|
||||
"auction.closed_at": "Stengte {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Stenger",
|
||||
"panel.auction_col_result": "Resultat",
|
||||
"panel.auction_col_status": "Oppgavestatus",
|
||||
"panel.auction_normally": "{date} · vanligvis {points} ★",
|
||||
"panel.auction_min_meta": "min. {points} ★",
|
||||
"panel.auction_normally": "{date} · vanligvis {points} {points_name}",
|
||||
"panel.auction_min_meta": "min. {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "Ingen bud ennå",
|
||||
"panel.auction_no_bids": "Ingen bud",
|
||||
"panel.auction_sealed": "forseglet",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "Ingen kommende gang av denne oppgaven kan legges ut på auksjon de neste 60 dagene.",
|
||||
"panel.auction_no_chores": "Ingen oppgave kan legges ut på auksjon ennå. Lagoppgaver, tidsoppgaver, åpne og ikke-tildelte oppgaver kan ikke det.",
|
||||
"panel.auction_max_label": "Høyeste bud",
|
||||
"panel.auction_normally_worth": "vanligvis verdt {points} ★",
|
||||
"panel.auction_normally_worth": "vanligvis verdt {points} {points_name}",
|
||||
"panel.auction_max_hint": "Barna kan by fra minstebudet og opp til dette. Laveste vinner; ved likt bud vinner det tidligste.",
|
||||
"panel.auction_min_label": "Minstebud",
|
||||
"panel.auction_min_hint": "Av: alle bud fra 1 teller. På: hindrer et kappløp mot bunnen.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Fremgang mot neste belønning",
|
||||
"kiosk.editor.show_picker_progress": "Fremgang i velgeren",
|
||||
"kiosk.editor.show_picker_progress_helper": "«3 av 6 ferdig» under hvert ansikt",
|
||||
"kiosk.editor.play_sounds": "Lyder",
|
||||
"kiosk.editor.play_sounds_helper": "Fullført-lyden til hver oppgave, og angre-lyden",
|
||||
"kiosk.editor.show_confetti": "Konfetti",
|
||||
"kiosk.editor.show_completion_popup": "Feiringsvindu",
|
||||
"kiosk.editor.show_completion_popup_helper": "Et kort «Kjempebra!» med poengene etter hver oppgave",
|
||||
"kiosk.editor.children_heading": "Viste barn og PIN-koder",
|
||||
"kiosk.editor.children_hint": "Fjern haken for å skjule et barn. Bruk pilene til å bestemme rekkefølgen i velgeren.",
|
||||
"kiosk.editor.require_pin": "Krev PIN-kode",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Slå av for en tavle som bare kan vises",
|
||||
"chore_board.editor.show_legend": "Vis forklaring",
|
||||
"chore_board.editor.hide_done_periods": "Skjul ferdige tidspunkter",
|
||||
"chore_board.editor.hide_done_periods_helper": "Fjern en tid på dagen når alle barna har gjort oppgavene sine"
|
||||
"chore_board.editor.hide_done_periods_helper": "Fjern en tid på dagen når alle barna har gjort oppgavene sine",
|
||||
"notify.bedtime_reminder": "{child_name}, du har fortsatt oppgaver å gjøre før leggetid.",
|
||||
"notify.streak_at_risk": "{child_name}, gjør en oppgave i dag for å holde rekken på {streak} dager!",
|
||||
"notify.all_chores_done": "{child_name} har gjort alle dagens oppgaver!",
|
||||
"notify.badge_earned": "{child_name} har fått merket «{badge_name}»!",
|
||||
"notify.pending_chore_approval": "{child_name} har gjort «{chore_name}» (+{points} {points_name}) — venter på godkjenning.",
|
||||
"notify.pending_reward_claim": "{child_name} har løst inn «{reward_name}» ({cost} {points_name}) — venter på godkjenning.",
|
||||
"notify.streak_milestone": "{child_name} har nådd en rekke på {days} dager — +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} har nådd nivå {level}! 🎉",
|
||||
"notify.weekly_digest": "TaskMate ukesoversikt:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, du må fortsatt gjøre «{chore_name}».",
|
||||
"notify.mandatory_parent_alert": "{child_name} har fortsatt ikke gjort den obligatoriske oppgaven «{chore_name}».",
|
||||
"notify.monthly_report": "TaskMate-rapport for {month}:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} vant ledertavla for {month} med {points} {points_name}!",
|
||||
"notify.family_goal_reached": "🎉 Familiemålet er nådd: {goal_name}! Tid for {goal_reward}.",
|
||||
"notify.birthday": "🎂 Gratulerer med dagen, {child_name}! Alle oppgaver gir {multiplier}× i dag.",
|
||||
"notify.streak_freeze_used": "❄️ En rekkefrys reddet {child_name} sin rekke på {streak} dager ({freezes_left} igjen).",
|
||||
"notify.presence_arrival": "🏠 Du er hjemme, {child_name} — {count} oppgaver igjen i dag.",
|
||||
"notify.wish_requested": "{child_name} ønsker seg «{wish_name}» ({target} {points_name}) — venter på din godkjenning.",
|
||||
"notify.wish_pledged": "💝 {pledger} har lagt {points} {points_name} i ønsket ditt «{wish_name}»!",
|
||||
"notify.bounty_posted": "🏁 Ny dusør: {bounty_name} — {points} {points_name}. Førstemann som tar den, får den!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, {minutes} minutter igjen til å fullføre «{bounty_name}» før den går tilbake på tavla.",
|
||||
"notify.recap_ready": "✨ Tilbakeblikket ditt er klart ({period}), {child_name}! Trykk for å se det.",
|
||||
"notify.item_rejected": "↩️ {child_name}, «{item_name}» ble sendt tilbake{reason_text}",
|
||||
"notify.auction_opened": "🔨 Ny auksjon: {chore_name} den {date}, opptil {max_points} {points_name}. Laveste bud vinner!",
|
||||
"notify.auction_closing": "⏳ {child_name}, budrunden på «{chore_name}» stenger om {minutes} minutter.",
|
||||
"notify.auction_result": "🔨 Auksjonen er avsluttet: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, en voksen kommer for å sjekke «{chore_name}» før {until}. Hold det fint for +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Inspeksjonen er bestått, {child_name}! «{chore_name}» så flott ut: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 Inspeksjonen av «{chore_name}» til {child_name} stenger om {minutes} minutter — godkjenn eller avvis den før {until}.",
|
||||
"notify.auction_won": "{child_name} vant «{chore_name}» den {date} for {points} {points_name}.",
|
||||
"notify.auction_no_bids": "ingen bud på «{chore_name}», så den går tilbake til vanlig tur.",
|
||||
"notify.auction_called_off": "auksjonen for «{chore_name}» ble avlyst. Den går tilbake til vanlig tur.",
|
||||
"notify.weekly_digest_line": "• {child_name}: {chores} oppgaver, {points} {points_name} opptjent",
|
||||
"notify.monthly_report_line": "• {child_name}: {chores} oppgaver, {points} {points_name}, nivå {level}, beste rekke {streak}",
|
||||
"notify.action_approve": "Godkjenn",
|
||||
"notify.action_reject": "Avvis",
|
||||
"notify.action_reject_placeholder": "Grunn (valgfritt)",
|
||||
"notify.approve_in_panel_hint": "Åpne TaskMate-panelet for å godkjenne eller avvise.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} har gjort alle dagens oppgaver!",
|
||||
"notify.celebrate_birthday": "Gratulerer med dagen, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} har fullført utfordringen «{challenge_name}»!",
|
||||
"notify.celebrate_inspection_passed": "«{chore_name}» til {child_name} besto inspeksjonen!",
|
||||
"notify.celebrate_level_up": "{child_name} har nådd nivå {level}!",
|
||||
"notify.celebrate_perfect_week": "{child_name} har hatt en perfekt uke — +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} har nådd ukemålet for «{chore_name}» — +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} har fullført oppdraget «{quest_name}»!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} har nådd en rekke på {days} dager!",
|
||||
"notify.recap_ready_many": "✨ {count} nye tilbakeblikk er klare, {child_name}! Trykk for å se dem.",
|
||||
"notify.recap_parent_one": "✨ Nye tilbakeblikk ({period}) er klare for {names}. Trykk for å se dem.",
|
||||
"notify.recap_parent_many": "✨ Nye tilbakeblikk er klare for {names}. Trykk for å se dem.",
|
||||
"notify.period_weekly": "uke",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} og {last}",
|
||||
"notify.month_year": "{month} {year}",
|
||||
"notify.short_date": "{weekday} {day}. {month}",
|
||||
"notify.month_1": "januar",
|
||||
"notify.month_2": "februar",
|
||||
"notify.month_3": "mars",
|
||||
"notify.month_4": "april",
|
||||
"notify.month_5": "mai",
|
||||
"notify.month_6": "juni",
|
||||
"notify.month_7": "juli",
|
||||
"notify.month_8": "august",
|
||||
"notify.month_9": "september",
|
||||
"notify.month_10": "oktober",
|
||||
"notify.month_11": "november",
|
||||
"notify.month_12": "desember",
|
||||
"notify.month_short_1": "jan.",
|
||||
"notify.month_short_2": "feb.",
|
||||
"notify.month_short_3": "mar.",
|
||||
"notify.month_short_4": "apr.",
|
||||
"notify.month_short_5": "mai",
|
||||
"notify.month_short_6": "jun.",
|
||||
"notify.month_short_7": "jul.",
|
||||
"notify.month_short_8": "aug.",
|
||||
"notify.month_short_9": "sep.",
|
||||
"notify.month_short_10": "okt.",
|
||||
"notify.month_short_11": "nov.",
|
||||
"notify.month_short_12": "des.",
|
||||
"notify.weekday_short_1": "man.",
|
||||
"notify.weekday_short_2": "tir.",
|
||||
"notify.weekday_short_3": "ons.",
|
||||
"notify.weekday_short_4": "tor.",
|
||||
"notify.weekday_short_5": "fre.",
|
||||
"notify.weekday_short_6": "lør.",
|
||||
"notify.weekday_short_7": "søn.",
|
||||
"notify.sample_chore": "Rydd rommet",
|
||||
"notify.sample_reward": "Filmkveld",
|
||||
"notify.sample_badge": "Superhjelper",
|
||||
"notify.sample_goal": "Filmkveldskasse",
|
||||
"notify.sample_goal_reward": "en filmkveld for hele familien",
|
||||
"notify.sample_wish": "Lego-sett",
|
||||
"notify.sample_pledger": "Bestemor",
|
||||
"notify.sample_bounty": "Vask bilen",
|
||||
"notify.sample_reason": "Ikke ferdig",
|
||||
"notify.sample_note": "Kjempebra jobba!"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Gje straff",
|
||||
"approvals.postpone": "Utset",
|
||||
"approvals.dismiss": "Avvis",
|
||||
"approvals.penalty_points": "Straff: −{points} poeng",
|
||||
"approvals.penalty_points": "Straff: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "Ingen straff angjeven",
|
||||
"approvals.timed_duration": "{count} min",
|
||||
"badge.criteria_and": "OG",
|
||||
@@ -253,6 +253,7 @@
|
||||
"child.open_ended_tag": "Beskriv",
|
||||
"child.weekly_target_done": "Ferdig for denne veka",
|
||||
"child.weekly_target_progress": "{done}/{target} denne veka",
|
||||
"child.weekly_target_bonus_reached": "🎉 Vekemålet er nådd! +{points}",
|
||||
"child.team_progress": "{joined} / {size} med",
|
||||
"child.team_you_joined": "Du er med",
|
||||
"child.team_join": "Bli med",
|
||||
@@ -326,8 +327,8 @@
|
||||
"child.paused": "Pausa",
|
||||
"child.please_define_child_id": "Ver venleg og definer ein child_id",
|
||||
"child.please_define_entity": "Ver venleg og definer ein entitet (TaskMate oversiktssensor)",
|
||||
"child.pts_per_min": "poeng/min",
|
||||
"child.pts_per_n_min": "poeng/{count} min",
|
||||
"child.pts_per_min": "{pointsName}/min",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min",
|
||||
"child.recurring": "Gjentakande",
|
||||
"child.resume": "Fortset",
|
||||
"child.start": "Start",
|
||||
@@ -354,6 +355,8 @@
|
||||
"common.editor.header_colour_helper": "Bakgrunnsfarge for korttoppen",
|
||||
"common.editor.overview_entity": "Oversiktsentitet",
|
||||
"common.editor.overview_entity_helper": "TaskMate oversiktssensor-entiteten",
|
||||
"common.editor.show_parent_actions": "Vis foreldrehandlingar",
|
||||
"common.editor.show_parent_actions_helper": "Slå av på eit delt barnedashbord for å vise kortet nøyaktig slik eit barn ser det, sjølv når ein forelder er logga inn. Berre vising — løyva vert ikkje endra.",
|
||||
"common.entity": "Entitet",
|
||||
"common.entity_not_found": "Entitet ikkje funnen: {entity}",
|
||||
"common.evening": "Kveld",
|
||||
@@ -772,6 +775,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/veke",
|
||||
"panel.chore_weekly_target_hint": "Kor mange gonger i veka oppgåva må gjerast, på kva som helst av dei valde dagane. Ho blir ikkje vist meir når talet er nådd, og blir nullstilt på måndag.",
|
||||
"panel.chore_weekly_target_label": "Vekemål (0 = av)",
|
||||
"panel.chore_weekly_target_bonus_label": "Bonus for vekemål",
|
||||
"panel.chore_weekly_target_bonus_hint": "Ekstra poeng når vekemålet er nådd, i tillegg til poenga for kvar gjennomføring. Blir utbetalt éin gong i veka, når den siste gjennomføringa er godkjend. 0 = ingen bonus.",
|
||||
"panel.chore_team_section": "Lagarbeid (valfritt)",
|
||||
"panel.chore_team_hint": "Ei lagoppgåve tel først når så mange ulike born er med. Kvart born sitt trykk på Ferdig melder dei på, og når laget er fullt, vert oppgåva send inn for alle. Påmeldingar vert nullstilte kvar dag. Krev tildelinga «Alle» og kan ikkje vere ei open oppgåve.",
|
||||
"panel.chore_team_size_label": "Lagstorleik (0 = av)",
|
||||
@@ -791,6 +796,9 @@
|
||||
"panel.chore_points_label": "Poeng",
|
||||
"panel.chore_points_per_window_label": "Poeng per vindauge",
|
||||
"panel.chore_recurrence_day_hint": "t.d. måndag — la stå tomt for kva dag som helst",
|
||||
"panel.chore_recurrence_interval_label": "Gjenta kvar (dagar)",
|
||||
"panel.chore_recurrence_interval_hint": "Heilt tal dagar, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Skriv inn eit tal dagar mellom {min} og {max}",
|
||||
"panel.chore_recurrence_day_label": "Vekedag (berre vekentleg)",
|
||||
"panel.chore_recurrence_label": "Gjentaking",
|
||||
"panel.chore_recurrence_start_hint": "Ankerdato — tomt = i dag",
|
||||
@@ -1227,8 +1235,10 @@
|
||||
"panel.penbon_table_name": "Namn",
|
||||
"panel.penbon_table_points": "Poeng",
|
||||
"panel.pending_tooltip": "{count} ventande — klikk for å gjennomgå",
|
||||
"panel.pts_display": "{count} poeng",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "Kvar 2. dag",
|
||||
"panel.recurrence_every_n_days": "Kvar N. dag (eigendefinert)",
|
||||
"panel.recurrence_every_count_days": "Kvar {count}. dag",
|
||||
"panel.recurrence_every_2_weeks": "Kvar 2. veke",
|
||||
"panel.recurrence_every_3_months": "Kvar 3. månad",
|
||||
"panel.recurrence_every_6_months": "Kvar 6. månad",
|
||||
@@ -1464,7 +1474,7 @@
|
||||
"panel.template_builtin_template_packs": "Innebygde malpakkar",
|
||||
"panel.template_builtin_templates": "Innebygde malar",
|
||||
"panel.template_chores_in_template": "Oppgåver i denne malen",
|
||||
"panel.template_confirm_bar": "{count} oppgåve(r) vert oppretta · {points} poeng totalt",
|
||||
"panel.template_confirm_bar": "{count} oppgåve(r) vert oppretta · {points} {points_name} totalt",
|
||||
"panel.template_create_btn": "Opprett {count} oppgåve(r)",
|
||||
"panel.template_create_title": "Opprett mal",
|
||||
"panel.template_custom": "Eigendefinert",
|
||||
@@ -1476,7 +1486,7 @@
|
||||
"panel.template_name_label": "Namn",
|
||||
"panel.template_picker_title": "Legg til frå mal",
|
||||
"panel.template_preview_hint": "Gå gjennom og tilpass kvar oppgåve før oppretting. Utvid for å redigere alle felt, eller fjern med ✕.",
|
||||
"panel.template_pts_total": "{count} oppgåve(r) · {points} poeng totalt",
|
||||
"panel.template_pts_total": "{count} oppgåve(r) · {points} {points_name} totalt",
|
||||
"panel.template_requires_approval_label": "Krev godkjenning",
|
||||
"panel.template_requires_approval_no": "Nei",
|
||||
"panel.template_requires_approval_yes": "Ja",
|
||||
@@ -1687,7 +1697,7 @@
|
||||
"points_display.this_week": "Denne veka",
|
||||
"points_display.total_points": "Totale poeng",
|
||||
"points_display.weekly_plus": "+{count} denne veka",
|
||||
"points_display.weekly_pts": "{count} poe.",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Legg til oppgåver for dette bornet fyrst",
|
||||
"reorder.card_description": "Eit kort for å omorganisere oppgåver per born, organisert etter tidskategori",
|
||||
"reorder.card_name": "TaskMate Omorganiseringskort",
|
||||
@@ -1792,9 +1802,9 @@
|
||||
"streak.achievement.hundred_desc": "Fullfør 100 oppgåver",
|
||||
"streak.achievement.hundred_name": "100 gjort",
|
||||
"streak.achievement.points100_desc": "Ten 100 poeng totalt",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Ten 50 poeng totalt",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "14 dagars rekke",
|
||||
"streak.achievement.streak14_name": "2 veker",
|
||||
"streak.achievement.streak30_desc": "30 dagars rekke",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Innstillingar for overraskingsbonus",
|
||||
"panel.settings_surprise_params_hint": "Dagleg sjanse per barn, og det tilfeldige poengspennet ved treff.",
|
||||
"panel.settings_surprise_chance": "Sjanse %",
|
||||
"panel.settings_surprise_min": "Min poeng",
|
||||
"panel.settings_surprise_max": "Maks poeng",
|
||||
"panel.settings_surprise_min": "Min {points_name}",
|
||||
"panel.settings_surprise_max": "Maks {points_name}",
|
||||
"panel.reward_restock_label": "Auto-påfyll",
|
||||
"panel.reward_restock_hint": "Tilbakestill løninga sitt lager til eit fast tal med jamne mellomrom.",
|
||||
"panel.reward_restock_amount": "Fyll opp til",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Avgrens kor mange poeng kvart barn kan bruke på belønningar per periode.",
|
||||
"panel.settings_spendcap_params_label": "Innstillingar for forbruksgrense",
|
||||
"panel.settings_spendcap_params_hint": "Maks poeng som kan brukast per periode (0 = inga grense).",
|
||||
"panel.settings_spendcap_amount": "Grense (poeng)",
|
||||
"panel.settings_spendcap_amount": "Grense ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Sparerente",
|
||||
"panel.settings_interest_enabled_hint": "Betal jamleg kvart barn rente på den disponible saldoen (belønnar sparing).",
|
||||
"panel.settings_interest_params_label": "Innstillingar for sparerente",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "{time} att",
|
||||
"bounty.no_expiry": "Går ikkje ut",
|
||||
"bounty.photo_tag": "bilete",
|
||||
"bounty.pts": "P",
|
||||
"bounty.claim": "Ta han",
|
||||
"bounty.claim_hint": "Du får {hours} t på å gjere han ferdig",
|
||||
"bounty.finish_first": "Gjer ferdig dusøren din først",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Gje tilbake",
|
||||
"bounty.sibling_on_it": "{name} held på",
|
||||
"bounty.sibling_back_in": "tilbake på tavla om {time} viss han ikkje vert ferdig",
|
||||
"bounty.waiting": "Ventar på at ein vaksen sjekkar · +{points} ★ ventar",
|
||||
"bounty.waiting": "Ventar på at ein vaksen sjekkar · +{points} {pointsName} ventar",
|
||||
"bounty.undo": "Angre",
|
||||
"bounty.sibling_checking": "{name} er ferdig — vert sjekka",
|
||||
"bounty.you_earned": "Du tente +{points} ★",
|
||||
"bounty.you_earned": "Du tente +{points} {pointsName}",
|
||||
"bounty.done_by": "Gjort av {name}",
|
||||
"bounty.recent": "Nyleg fullført",
|
||||
"bounty.claim_title": "Ta «{title}»?",
|
||||
"bounty.claim_body": "Han er din i {hours} timar. Er han ikkje gjord innan då, går han tilbake på tavla for alle.",
|
||||
"bounty.not_now": "Ikkje no",
|
||||
"bounty.claim_it": "Ta han — {points} ★",
|
||||
"bounty.claim_it": "Ta han — {points} {pointsName}",
|
||||
"bounty.photo_title": "Vis oss den ferdige jobben",
|
||||
"bounty.photo_body": "«{title}» treng eit bilete",
|
||||
"bounty.cancel": "Avbryt",
|
||||
"bounty.take_photo": "Ta bilete og send",
|
||||
"bounty.uploading": "Sender…",
|
||||
"bounty.yay_title": "Godt jobba!",
|
||||
"bounty.yay_body": "Ein vaksen sjekkar «{title}», og så får du {points} ★.",
|
||||
"bounty.yay_body": "Ein vaksen sjekkar «{title}», og så får du {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d} d {h} t",
|
||||
"bounty.dur_hours": "{h} t {m} min",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Fullført",
|
||||
"panel.bounty_status_expired": "Gått ut",
|
||||
"panel.bounty_claimed_meta": "{name} · {time} att",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Ingen tok han · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Gått ut etter at ei reservasjon gjekk ut · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Teken, men aldri gjort ferdig · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Vel eit barn i kortredigeraren for å sjå auksjonane til barnet.",
|
||||
"auction.results": "Resultat",
|
||||
"auction.load_failed": "Kunne ikkje hente auksjonane: {error}",
|
||||
"auction.up_to": "opptil {points} ★",
|
||||
"auction.up_to": "opptil {points} {pointsName}",
|
||||
"auction.lowest_wins": "Lågaste bod vinn",
|
||||
"auction.gauge_max": "maks {points} ★",
|
||||
"auction.gauge_hint": "Vanlegvis verdt {points} ★ · by det minste talet på poeng du vil gjere det for",
|
||||
"auction.gauge_max": "maks {points} {pointsName}",
|
||||
"auction.gauge_hint": "Vanlegvis verdt {points} {pointsName} · by det minste talet på poeng du vil gjere det for",
|
||||
"auction.your_bid": "Det løynde bodet ditt",
|
||||
"auction.change": "Endre",
|
||||
"auction.withdraw": "Trekk tilbake",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Slett siste siffer",
|
||||
"auction.rule_secret": "Løynt: ingen ser talet ditt.",
|
||||
"auction.rule_lowest": "Lågaste bod vinn. Ved likt bod vinn den som baud først.",
|
||||
"auction.rule_min": "Boda startar på {points} ★.",
|
||||
"auction.rule_min": "Boda startar på {points} {pointsName}.",
|
||||
"auction.rule_win": "Viss du vinn, gjer du det {date} og får bodet ditt når det er godkjent.",
|
||||
"auction.cancel": "Avbryt",
|
||||
"auction.seal": "Forsegl bodet mitt · {points} ★",
|
||||
"auction.you_won": "Du vann! {points} ★ er dine",
|
||||
"auction.seal": "Forsegl bodet mitt · {points} {pointsName}",
|
||||
"auction.you_won": "Du vann! {points} {pointsName} er dine",
|
||||
"auction.you_won_sub": "når du har gjort det {date} og ein vaksen har godkjent det",
|
||||
"auction.in_your_chores": "Ho står i oppgåvene dine {date}",
|
||||
"auction.sibling_won": "{name} vann denne",
|
||||
"auction.sibling_won_sub": "for {points} ★. Lågaste bod vinn, så prøv litt lågare neste gong!",
|
||||
"auction.sibling_won_sub_bid": "for {points} ★ · du baud {bid} ★. Lågaste bod vinn, så prøv litt lågare neste gong!",
|
||||
"auction.tied_sub": "De baud begge {points} ★. Det vart uavgjort, og {name} baud først.",
|
||||
"auction.sibling_won_sub": "for {points} {pointsName}. Lågaste bod vinn, så prøv litt lågare neste gong!",
|
||||
"auction.sibling_won_sub_bid": "for {points} {pointsName} · du baud {bid} {pointsName}. Lågaste bod vinn, så prøv litt lågare neste gong!",
|
||||
"auction.tied_sub": "De baud begge {points} {pointsName}. Det vart uavgjort, og {name} baud først.",
|
||||
"auction.no_bids": "Ingen bod denne gongen",
|
||||
"auction.no_bids_sub": "{chore} går tilbake til vanleg fordeling {date}.",
|
||||
"auction.closed_at": "Stengde {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Stengjer",
|
||||
"panel.auction_col_result": "Resultat",
|
||||
"panel.auction_col_status": "Oppgåvestatus",
|
||||
"panel.auction_normally": "{date} · vanlegvis {points} ★",
|
||||
"panel.auction_min_meta": "min. {points} ★",
|
||||
"panel.auction_normally": "{date} · vanlegvis {points} {points_name}",
|
||||
"panel.auction_min_meta": "min. {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "Ingen bod enno",
|
||||
"panel.auction_no_bids": "Ingen bod",
|
||||
"panel.auction_sealed": "forsegla",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "Ingen komande gong av denne oppgåva kan leggjast ut på auksjon dei neste 60 dagane.",
|
||||
"panel.auction_no_chores": "Inga oppgåve kan leggjast ut på auksjon enno. Lagoppgåver, tidsoppgåver, opne og ikkje-tildelte oppgåver kan ikkje det.",
|
||||
"panel.auction_max_label": "Høgaste bod",
|
||||
"panel.auction_normally_worth": "vanlegvis verdt {points} ★",
|
||||
"panel.auction_normally_worth": "vanlegvis verdt {points} {points_name}",
|
||||
"panel.auction_max_hint": "Borna kan by frå minstebodet og opp til dette. Lågaste vinn; ved likt bod vinn det tidlegaste.",
|
||||
"panel.auction_min_label": "Minstebod",
|
||||
"panel.auction_min_hint": "Av: alle bod frå 1 tel. På: hindrar eit kapplaup mot botnen.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Framgang mot neste løning",
|
||||
"kiosk.editor.show_picker_progress": "Framgang i veljaren",
|
||||
"kiosk.editor.show_picker_progress_helper": "«3 av 6 ferdig» under kvart andlet",
|
||||
"kiosk.editor.play_sounds": "Lydar",
|
||||
"kiosk.editor.play_sounds_helper": "Fullført-lyden til kvar oppgåve, og angre-lyden",
|
||||
"kiosk.editor.show_confetti": "Konfetti",
|
||||
"kiosk.editor.show_completion_popup": "Feiringsvindauge",
|
||||
"kiosk.editor.show_completion_popup_helper": "Eit kort «Kjempebra!» med poenga etter kvar oppgåve",
|
||||
"kiosk.editor.children_heading": "Viste born og PIN-kodar",
|
||||
"kiosk.editor.children_hint": "Fjern haken for å skjule eit barn. Bruk pilene til å bestemme rekkjefølgja i veljaren.",
|
||||
"kiosk.editor.require_pin": "Krev PIN-kode",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Slå av for ei tavle som berre kan visast",
|
||||
"chore_board.editor.show_legend": "Vis forklaring",
|
||||
"chore_board.editor.hide_done_periods": "Skjul ferdige tidspunkt",
|
||||
"chore_board.editor.hide_done_periods_helper": "Fjern ei tid på dagen når alle borna har gjort oppgåvene sine"
|
||||
"chore_board.editor.hide_done_periods_helper": "Fjern ei tid på dagen når alle borna har gjort oppgåvene sine",
|
||||
"notify.bedtime_reminder": "{child_name}, du har framleis oppgåver å gjere før leggjetid.",
|
||||
"notify.streak_at_risk": "{child_name}, gjer ei oppgåve i dag for å halde rekkja på {streak} dagar!",
|
||||
"notify.all_chores_done": "{child_name} har gjort alle oppgåvene i dag!",
|
||||
"notify.badge_earned": "{child_name} har fått merket «{badge_name}»!",
|
||||
"notify.pending_chore_approval": "{child_name} har gjort «{chore_name}» (+{points} {points_name}) — ventar på godkjenning.",
|
||||
"notify.pending_reward_claim": "{child_name} har løyst inn «{reward_name}» ({cost} {points_name}) — ventar på godkjenning.",
|
||||
"notify.streak_milestone": "{child_name} har nådd ei rekkje på {days} dagar — +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} har nådd nivå {level}! 🎉",
|
||||
"notify.weekly_digest": "TaskMate vekeoversikt:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, du må framleis gjere «{chore_name}».",
|
||||
"notify.mandatory_parent_alert": "{child_name} har framleis ikkje gjort den obligatoriske oppgåva «{chore_name}».",
|
||||
"notify.monthly_report": "TaskMate-rapport for {month}:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} vann leiartavla for {month} med {points} {points_name}!",
|
||||
"notify.family_goal_reached": "🎉 Familiemålet er nådd: {goal_name}! Tid for {goal_reward}.",
|
||||
"notify.birthday": "🎂 Gratulerer med dagen, {child_name}! Alle oppgåver gir {multiplier}× i dag.",
|
||||
"notify.streak_freeze_used": "❄️ Ein rekkjefrys redda rekkja til {child_name} på {streak} dagar ({freezes_left} att).",
|
||||
"notify.presence_arrival": "🏠 Du er heime, {child_name} — {count} oppgåver att i dag.",
|
||||
"notify.wish_requested": "{child_name} ønskjer seg «{wish_name}» ({target} {points_name}) — ventar på godkjenninga di.",
|
||||
"notify.wish_pledged": "💝 {pledger} har lagt {points} {points_name} i ønsket ditt «{wish_name}»!",
|
||||
"notify.bounty_posted": "🏁 Ny dusør: {bounty_name} — {points} {points_name}. Den første som tek ho, får ho!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, {minutes} minutt att til å fullføre «{bounty_name}» før ho går tilbake på tavla.",
|
||||
"notify.recap_ready": "✨ Tilbakeblikket ditt er klart ({period}), {child_name}! Trykk for å sjå det.",
|
||||
"notify.item_rejected": "↩️ {child_name}, «{item_name}» vart sendt tilbake{reason_text}",
|
||||
"notify.auction_opened": "🔨 Ny auksjon: {chore_name} den {date}, opptil {max_points} {points_name}. Lågaste bod vinn!",
|
||||
"notify.auction_closing": "⏳ {child_name}, bodrunden på «{chore_name}» stengjer om {minutes} minutt.",
|
||||
"notify.auction_result": "🔨 Auksjonen er avslutta: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, ein vaksen kjem for å sjekke «{chore_name}» før {until}. Hald det fint for +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Inspeksjonen er bestått, {child_name}! «{chore_name}» såg flott ut: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 Inspeksjonen av «{chore_name}» til {child_name} stengjer om {minutes} minutt — godkjenn eller avvis ho før {until}.",
|
||||
"notify.auction_won": "{child_name} vann «{chore_name}» den {date} for {points} {points_name}.",
|
||||
"notify.auction_no_bids": "ingen bod på «{chore_name}», så ho går tilbake til vanleg tur.",
|
||||
"notify.auction_called_off": "auksjonen for «{chore_name}» vart avlyst. Ho går tilbake til vanleg tur.",
|
||||
"notify.weekly_digest_line": "• {child_name}: {chores} oppgåver, {points} {points_name} tente",
|
||||
"notify.monthly_report_line": "• {child_name}: {chores} oppgåver, {points} {points_name}, nivå {level}, beste rekkje {streak}",
|
||||
"notify.action_approve": "Godkjenn",
|
||||
"notify.action_reject": "Avvis",
|
||||
"notify.action_reject_placeholder": "Grunn (valfritt)",
|
||||
"notify.approve_in_panel_hint": "Opne TaskMate-panelet for å godkjenne eller avvise.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} har gjort alle oppgåvene i dag!",
|
||||
"notify.celebrate_birthday": "Gratulerer med dagen, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} har fullført utfordringa «{challenge_name}»!",
|
||||
"notify.celebrate_inspection_passed": "«{chore_name}» til {child_name} bestod inspeksjonen!",
|
||||
"notify.celebrate_level_up": "{child_name} har nådd nivå {level}!",
|
||||
"notify.celebrate_perfect_week": "{child_name} har hatt ei perfekt veke — +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} har nådd vekemålet for «{chore_name}» — +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} har fullført oppdraget «{quest_name}»!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} har nådd ei rekkje på {days} dagar!",
|
||||
"notify.recap_ready_many": "✨ {count} nye tilbakeblikk er klare, {child_name}! Trykk for å sjå dei.",
|
||||
"notify.recap_parent_one": "✨ Nye tilbakeblikk ({period}) er klare for {names}. Trykk for å sjå dei.",
|
||||
"notify.recap_parent_many": "✨ Nye tilbakeblikk er klare for {names}. Trykk for å sjå dei.",
|
||||
"notify.period_weekly": "veke",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} og {last}",
|
||||
"notify.month_year": "{month} {year}",
|
||||
"notify.short_date": "{weekday} {day}. {month}",
|
||||
"notify.month_1": "januar",
|
||||
"notify.month_2": "februar",
|
||||
"notify.month_3": "mars",
|
||||
"notify.month_4": "april",
|
||||
"notify.month_5": "mai",
|
||||
"notify.month_6": "juni",
|
||||
"notify.month_7": "juli",
|
||||
"notify.month_8": "august",
|
||||
"notify.month_9": "september",
|
||||
"notify.month_10": "oktober",
|
||||
"notify.month_11": "november",
|
||||
"notify.month_12": "desember",
|
||||
"notify.month_short_1": "jan.",
|
||||
"notify.month_short_2": "feb.",
|
||||
"notify.month_short_3": "mar.",
|
||||
"notify.month_short_4": "apr.",
|
||||
"notify.month_short_5": "mai",
|
||||
"notify.month_short_6": "jun.",
|
||||
"notify.month_short_7": "jul.",
|
||||
"notify.month_short_8": "aug.",
|
||||
"notify.month_short_9": "sep.",
|
||||
"notify.month_short_10": "okt.",
|
||||
"notify.month_short_11": "nov.",
|
||||
"notify.month_short_12": "des.",
|
||||
"notify.weekday_short_1": "mån.",
|
||||
"notify.weekday_short_2": "tys.",
|
||||
"notify.weekday_short_3": "ons.",
|
||||
"notify.weekday_short_4": "tor.",
|
||||
"notify.weekday_short_5": "fre.",
|
||||
"notify.weekday_short_6": "lau.",
|
||||
"notify.weekday_short_7": "sun.",
|
||||
"notify.sample_chore": "Rydd rommet",
|
||||
"notify.sample_reward": "Filmkveld",
|
||||
"notify.sample_badge": "Superhjelpar",
|
||||
"notify.sample_goal": "Filmkveldskasse",
|
||||
"notify.sample_goal_reward": "ein filmkveld for heile familien",
|
||||
"notify.sample_wish": "Lego-sett",
|
||||
"notify.sample_pledger": "Bestemor",
|
||||
"notify.sample_bounty": "Vask bilen",
|
||||
"notify.sample_reason": "Ikkje ferdig",
|
||||
"notify.sample_note": "Kjempebra jobba!"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Nałóż karę",
|
||||
"approvals.postpone": "Odłóż",
|
||||
"approvals.dismiss": "Odrzuć bez kary",
|
||||
"approvals.penalty_points": "Kara: −{points} pkt",
|
||||
"approvals.penalty_points": "Kara: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "Brak ustawionej kary",
|
||||
"approvals.timed_duration": "{count} min",
|
||||
"badge.criteria_and": "ORAZ",
|
||||
@@ -259,6 +259,7 @@
|
||||
"child.open_ended_tag": "Opisz to",
|
||||
"child.weekly_target_done": "Zrobione w tym tygodniu",
|
||||
"child.weekly_target_progress": "{done}/{target} w tym tygodniu",
|
||||
"child.weekly_target_bonus_reached": "🎉 Cel tygodniowy osiągnięty! +{points}",
|
||||
"child.team_progress": "Dołączyło: {joined} / {size}",
|
||||
"child.team_you_joined": "Jesteś w zespole",
|
||||
"child.team_join": "Dołącz",
|
||||
@@ -332,8 +333,8 @@
|
||||
"child.paused": "Wstrzymane",
|
||||
"child.please_define_child_id": "Wskaż child_id",
|
||||
"child.please_define_entity": "Wskaż encję (sensor przeglądu taskmate)",
|
||||
"child.pts_per_min": "pkt/min",
|
||||
"child.pts_per_n_min": "pkt/{count} min",
|
||||
"child.pts_per_min": "{pointsName}/min",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min",
|
||||
"child.recurring": "Cykliczne",
|
||||
"child.resume": "Wznów",
|
||||
"child.start": "Start",
|
||||
@@ -364,6 +365,8 @@
|
||||
"common.editor.header_colour_helper": "Kolor tła nagłówka karty",
|
||||
"common.editor.overview_entity": "Encja przeglądu",
|
||||
"common.editor.overview_entity_helper": "Encja sensora przeglądu TaskMate",
|
||||
"common.editor.show_parent_actions": "Pokaż działania rodzica",
|
||||
"common.editor.show_parent_actions_helper": "Wyłącz na wspólnym pulpicie dzieci, aby karta wyglądała dokładnie tak, jak widzi ją dziecko, nawet gdy zalogowany jest rodzic. Tylko wyświetlanie — uprawnienia się nie zmieniają.",
|
||||
"common.entity": "Encja",
|
||||
"common.entity_not_found": "Nie znaleziono encji: {entity}",
|
||||
"common.error": "Błąd",
|
||||
@@ -822,6 +825,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/tydz.",
|
||||
"panel.chore_weekly_target_hint": "Ile razy w tygodniu trzeba to zrobić, w dowolnym z wybranych dni. Po osiągnięciu liczby przestaje być proponowane; resetuje się w poniedziałek.",
|
||||
"panel.chore_weekly_target_label": "Cel tygodniowy (0 = wył.)",
|
||||
"panel.chore_weekly_target_bonus_label": "Premia za cel tygodniowy",
|
||||
"panel.chore_weekly_target_bonus_hint": "Dodatkowe punkty po osiągnięciu celu tygodniowego, oprócz punktów za każde wykonanie. Przyznawana raz w tygodniu, gdy ostatnie wykonanie zostanie zatwierdzone. 0 = brak premii.",
|
||||
"panel.chore_team_section": "Praca zespołowa (opcjonalnie)",
|
||||
"panel.chore_team_hint": "Obowiązek zespołowy liczy się dopiero, gdy dołączy do niego tyle różnych dzieci. Każde dotknięcie „Zrobione” to dołączenie, a gdy zespół jest pełny, obowiązek zostaje zgłoszony za wszystkich. Dołączenia resetują się codziennie. Wymaga przydziału „Wszyscy” i nie może być obowiązkiem otwartym.",
|
||||
"panel.chore_team_size_label": "Wielkość zespołu (0 = wył.)",
|
||||
@@ -841,6 +846,9 @@
|
||||
"panel.chore_points_label": "Punkty",
|
||||
"panel.chore_points_per_window_label": "Punkty za okres",
|
||||
"panel.chore_recurrence_day_hint": "np. monday — pozostaw puste dla dowolnego dnia",
|
||||
"panel.chore_recurrence_interval_label": "Powtarzaj co (dni)",
|
||||
"panel.chore_recurrence_interval_hint": "Całkowita liczba dni, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Podaj liczbę dni od {min} do {max}",
|
||||
"panel.chore_recurrence_day_label": "Dzień tygodnia (tylko co tydzień)",
|
||||
"panel.chore_recurrence_label": "Powtarzanie",
|
||||
"panel.chore_recurrence_start_hint": "Data początkowa — puste = dziś",
|
||||
@@ -1277,8 +1285,10 @@
|
||||
"panel.penbon_table_name": "Nazwa",
|
||||
"panel.penbon_table_points": "Punkty",
|
||||
"panel.pending_tooltip": "Oczekujące: {count} — kliknij, aby przejrzeć",
|
||||
"panel.pts_display": "{count} pkt",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "Co 2 dni",
|
||||
"panel.recurrence_every_n_days": "Co N dni (własne)",
|
||||
"panel.recurrence_every_count_days": "Co {count} dni",
|
||||
"panel.recurrence_every_2_weeks": "Co 2 tygodnie",
|
||||
"panel.recurrence_every_3_months": "Co 3 miesiące",
|
||||
"panel.recurrence_every_6_months": "Co 6 miesięcy",
|
||||
@@ -1528,7 +1538,7 @@
|
||||
"panel.template_builtin_template_packs": "Wbudowane pakiety szablonów",
|
||||
"panel.template_builtin_templates": "Wbudowane szablony",
|
||||
"panel.template_chores_in_template": "Obowiązki w tym szablonie",
|
||||
"panel.template_confirm_bar": "Zostanie utworzonych obowiązków: {count} · łącznie {points} pkt",
|
||||
"panel.template_confirm_bar": "Zostanie utworzonych obowiązków: {count} · łącznie {points} {points_name}",
|
||||
"panel.template_create_btn": "Utwórz obowiązki ({count})",
|
||||
"panel.template_create_title": "Utwórz szablon",
|
||||
"panel.template_custom": "Własny",
|
||||
@@ -1540,7 +1550,7 @@
|
||||
"panel.template_name_label": "Nazwa",
|
||||
"panel.template_picker_title": "Dodaj z szablonu",
|
||||
"panel.template_preview_hint": "Przejrzyj i dostosuj każdy obowiązek przed utworzeniem. Rozwiń, aby edytować wszystkie pola, lub usuń za pomocą ✕.",
|
||||
"panel.template_pts_total": "Obowiązki: {count} · łącznie {points} pkt",
|
||||
"panel.template_pts_total": "Obowiązki: {count} · łącznie {points} {points_name}",
|
||||
"panel.template_requires_approval_label": "Wymaga zatwierdzenia",
|
||||
"panel.template_requires_approval_no": "Nie",
|
||||
"panel.template_requires_approval_yes": "Tak",
|
||||
@@ -1752,7 +1762,7 @@
|
||||
"points_display.this_week": "Ten tydzień",
|
||||
"points_display.total_points": "Łącznie punktów",
|
||||
"points_display.weekly_plus": "+{count} w tym tygodniu",
|
||||
"points_display.weekly_pts": "{count} pkt",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Najpierw dodaj obowiązki temu dziecku",
|
||||
"reorder.card_description": "Karta do zmiany kolejności obowiązków każdego dziecka, pogrupowanych według pory dnia",
|
||||
"reorder.card_name": "TaskMate – Kolejność obowiązków",
|
||||
@@ -1857,9 +1867,9 @@
|
||||
"streak.achievement.hundred_desc": "Wykonaj 100 obowiązków",
|
||||
"streak.achievement.hundred_name": "100 zrobione",
|
||||
"streak.achievement.points100_desc": "Zdobądź łącznie 100 punktów",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Zdobądź łącznie 50 punktów",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "Seria 14 dni",
|
||||
"streak.achievement.streak14_name": "2 tygodnie",
|
||||
"streak.achievement.streak30_desc": "Seria 30 dni",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Ustawienia premii-niespodzianek",
|
||||
"panel.settings_surprise_params_hint": "Dzienna szansa na dziecko oraz losowy zakres punktów przyznawanych przy trafieniu.",
|
||||
"panel.settings_surprise_chance": "Szansa %",
|
||||
"panel.settings_surprise_min": "Min. pkt",
|
||||
"panel.settings_surprise_max": "Maks. pkt",
|
||||
"panel.settings_surprise_min": "Min. {points_name}",
|
||||
"panel.settings_surprise_max": "Maks. {points_name}",
|
||||
"panel.reward_restock_label": "Automatyczne uzupełnianie",
|
||||
"panel.reward_restock_hint": "Okresowo przywracaj zapas tej nagrody do ustalonej liczby.",
|
||||
"panel.reward_restock_amount": "Uzupełnij do",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Ogranicz, ile punktów każde dziecko może wydać na nagrody w danym okresie.",
|
||||
"panel.settings_spendcap_params_label": "Ustawienia limitu wydatków",
|
||||
"panel.settings_spendcap_params_hint": "Maksymalna liczba punktów do wydania w okresie (0 = bez limitu).",
|
||||
"panel.settings_spendcap_amount": "Limit (pkt)",
|
||||
"panel.settings_spendcap_amount": "Limit ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Odsetki od oszczędności",
|
||||
"panel.settings_interest_enabled_hint": "Okresowo wypłacaj każdemu dziecku odsetki od dostępnego salda (nagroda za oszczędzanie).",
|
||||
"panel.settings_interest_params_label": "Ustawienia odsetek od oszczędności",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "Zostało {time}",
|
||||
"bounty.no_expiry": "Bez terminu",
|
||||
"bounty.photo_tag": "zdjęcie",
|
||||
"bounty.pts": "PKT",
|
||||
"bounty.claim": "Biorę",
|
||||
"bounty.claim_hint": "Będziesz mieć {hours} godz. na wykonanie",
|
||||
"bounty.finish_first": "Najpierw dokończ swoje zlecenie",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Oddaj",
|
||||
"bounty.sibling_on_it": "{name} się tym zajmuje",
|
||||
"bounty.sibling_back_in": "wróci na tablicę za {time}, jeśli nie zostanie wykonane",
|
||||
"bounty.waiting": "Czeka na sprawdzenie przez dorosłego · +{points} ★ w drodze",
|
||||
"bounty.waiting": "Czeka na sprawdzenie przez dorosłego · +{points} {pointsName} w drodze",
|
||||
"bounty.undo": "Cofnij",
|
||||
"bounty.sibling_checking": "{name} skończył(a) — trwa sprawdzanie",
|
||||
"bounty.you_earned": "Zdobywasz +{points} ★",
|
||||
"bounty.you_earned": "Zdobywasz +{points} {pointsName}",
|
||||
"bounty.done_by": "Zrobione przez: {name}",
|
||||
"bounty.recent": "Ostatnio wykonane",
|
||||
"bounty.claim_title": "Wziąć „{title}”?",
|
||||
"bounty.claim_body": "Jest Twoje przez {hours} godz. Jeśli do tego czasu nie będzie zrobione, wróci na tablicę dla wszystkich.",
|
||||
"bounty.not_now": "Nie teraz",
|
||||
"bounty.claim_it": "Biorę — {points} ★",
|
||||
"bounty.claim_it": "Biorę — {points} {pointsName}",
|
||||
"bounty.photo_title": "Pokaż wykonane zadanie",
|
||||
"bounty.photo_body": "„{title}” wymaga zdjęcia",
|
||||
"bounty.cancel": "Anuluj",
|
||||
"bounty.take_photo": "Zrób zdjęcie i wyślij",
|
||||
"bounty.uploading": "Wysyłanie…",
|
||||
"bounty.yay_title": "Dobra robota!",
|
||||
"bounty.yay_body": "Dorosły sprawdzi „{title}”, a potem dostaniesz {points} ★.",
|
||||
"bounty.yay_body": "Dorosły sprawdzi „{title}”, a potem dostaniesz {points} {pointsName}.",
|
||||
"bounty.ok": "Dobrze",
|
||||
"bounty.dur_days": "{d} d. {h} godz.",
|
||||
"bounty.dur_hours": "{h} godz. {m} min",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Wykonane",
|
||||
"panel.bounty_status_expired": "Wygasłe",
|
||||
"panel.bounty_claimed_meta": "{name} · zostało {time}",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Nikt nie wziął · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Wygasło po upływie rezerwacji · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Zajęte, ale niedokończone · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Wybierz dziecko w edytorze karty, aby zobaczyć jego licytacje.",
|
||||
"auction.results": "Wyniki",
|
||||
"auction.load_failed": "Nie udało się wczytać licytacji: {error}",
|
||||
"auction.up_to": "do {points} ★",
|
||||
"auction.up_to": "do {points} {pointsName}",
|
||||
"auction.lowest_wins": "Wygrywa najniższa oferta",
|
||||
"auction.gauge_max": "maks. {points} ★",
|
||||
"auction.gauge_hint": "Zwykle warte {points} ★ · zaproponuj najmniej punktów, za które to zrobisz",
|
||||
"auction.gauge_max": "maks. {points} {pointsName}",
|
||||
"auction.gauge_hint": "Zwykle warte {points} {pointsName} · zaproponuj najmniej punktów, za które to zrobisz",
|
||||
"auction.your_bid": "Twoja tajna oferta",
|
||||
"auction.change": "Zmień",
|
||||
"auction.withdraw": "Wycofaj",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Usuń ostatnią cyfrę",
|
||||
"auction.rule_secret": "Tajemnica: nikt nie widzi Twojej liczby.",
|
||||
"auction.rule_lowest": "Wygrywa najniższa oferta. Przy remisie wygrywa ten, kto złożył ją pierwszy.",
|
||||
"auction.rule_min": "Oferty zaczynają się od {points} ★.",
|
||||
"auction.rule_min": "Oferty zaczynają się od {points} {pointsName}.",
|
||||
"auction.rule_win": "Jeśli wygrasz, robisz to {date} i dostajesz swoją ofertę po zatwierdzeniu.",
|
||||
"auction.cancel": "Anuluj",
|
||||
"auction.seal": "Zatwierdź ofertę · {points} ★",
|
||||
"auction.you_won": "Wygrywasz! {points} ★ jest Twoje",
|
||||
"auction.seal": "Zatwierdź ofertę · {points} {pointsName}",
|
||||
"auction.you_won": "Wygrywasz! {points} {pointsName} jest Twoje",
|
||||
"auction.you_won_sub": "gdy zrobisz to {date}, a dorosły to zatwierdzi",
|
||||
"auction.in_your_chores": "Jest w Twoich obowiązkach na {date}",
|
||||
"auction.sibling_won": "Tym razem wygrywa: {name}",
|
||||
"auction.sibling_won_sub": "za {points} ★. Wygrywa najniższa oferta, więc następnym razem spróbuj trochę niżej!",
|
||||
"auction.sibling_won_sub_bid": "za {points} ★ · Twoja oferta: {bid} ★. Wygrywa najniższa oferta, więc następnym razem spróbuj trochę niżej!",
|
||||
"auction.tied_sub": "Oboje zaproponowaliście {points} ★. Był remis, a {name} złożył(a) ofertę pierwszy(-a).",
|
||||
"auction.sibling_won_sub": "za {points} {pointsName}. Wygrywa najniższa oferta, więc następnym razem spróbuj trochę niżej!",
|
||||
"auction.sibling_won_sub_bid": "za {points} {pointsName} · Twoja oferta: {bid} {pointsName}. Wygrywa najniższa oferta, więc następnym razem spróbuj trochę niżej!",
|
||||
"auction.tied_sub": "Oboje zaproponowaliście {points} {pointsName}. Był remis, a {name} złożył(a) ofertę pierwszy(-a).",
|
||||
"auction.no_bids": "Tym razem brak ofert",
|
||||
"auction.no_bids_sub": "{chore} wraca do zwykłego przydziału na {date}.",
|
||||
"auction.closed_at": "Zakończona {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Koniec",
|
||||
"panel.auction_col_result": "Wynik",
|
||||
"panel.auction_col_status": "Status obowiązku",
|
||||
"panel.auction_normally": "{date} · zwykle {points} ★",
|
||||
"panel.auction_min_meta": "min. {points} ★",
|
||||
"panel.auction_normally": "{date} · zwykle {points} {points_name}",
|
||||
"panel.auction_min_meta": "min. {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "Brak ofert",
|
||||
"panel.auction_no_bids": "Brak ofert",
|
||||
"panel.auction_sealed": "tajna",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "Żadnego nadchodzącego terminu tego obowiązku nie można wystawić na licytację w ciągu najbliższych 60 dni.",
|
||||
"panel.auction_no_chores": "Żaden obowiązek nie może jeszcze trafić na licytację. Nie można licytować obowiązków zespołowych, na czas, otwartych ani nieprzypisanych.",
|
||||
"panel.auction_max_label": "Maksymalna oferta",
|
||||
"panel.auction_normally_worth": "zwykle warte {points} ★",
|
||||
"panel.auction_normally_worth": "zwykle warte {points} {points_name}",
|
||||
"panel.auction_max_hint": "Dzieci mogą zaproponować dowolną kwotę od minimum do tej wartości. Wygrywa najniższa; przy remisie — najwcześniejsza oferta.",
|
||||
"panel.auction_min_label": "Minimalna oferta",
|
||||
"panel.auction_min_hint": "Wył.: liczy się każda oferta od 1. Wł.: zapobiega licytacji w dół do zera.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Postęp do następnej nagrody",
|
||||
"kiosk.editor.show_picker_progress": "Postęp na ekranie wyboru",
|
||||
"kiosk.editor.show_picker_progress_helper": "„Zrobione: 3 z 6” pod każdą buzią",
|
||||
"kiosk.editor.play_sounds": "Dźwięki",
|
||||
"kiosk.editor.play_sounds_helper": "Dźwięk ukończenia każdego zadania i dźwięk cofnięcia",
|
||||
"kiosk.editor.show_confetti": "Konfetti",
|
||||
"kiosk.editor.show_completion_popup": "Okienko z gratulacjami",
|
||||
"kiosk.editor.show_completion_popup_helper": "Krótkie „Super!” z punktami po każdym zadaniu",
|
||||
"kiosk.editor.children_heading": "Widoczne dzieci i PIN-y",
|
||||
"kiosk.editor.children_hint": "Odznacz dziecko, aby je ukryć. Strzałkami ustaw kolejność na ekranie wyboru.",
|
||||
"kiosk.editor.require_pin": "Wymagaj PIN-u",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Wyłącz, aby tablica służyła tylko do podglądu",
|
||||
"chore_board.editor.show_legend": "Pokazuj legendę",
|
||||
"chore_board.editor.hide_done_periods": "Ukrywaj ukończone pory dnia",
|
||||
"chore_board.editor.hide_done_periods_helper": "Ukryj porę dnia, gdy każde dziecko wykona jej obowiązki"
|
||||
"chore_board.editor.hide_done_periods_helper": "Ukryj porę dnia, gdy każde dziecko wykona jej obowiązki",
|
||||
"notify.bedtime_reminder": "{child_name}, przed snem masz jeszcze zadania do zrobienia.",
|
||||
"notify.streak_at_risk": "{child_name}, wykonaj dziś zadanie, aby utrzymać serię {streak} dni!",
|
||||
"notify.all_chores_done": "{child_name} wykonał(a) dziś wszystkie zadania!",
|
||||
"notify.badge_earned": "{child_name} zdobył(a) odznakę „{badge_name}”!",
|
||||
"notify.pending_chore_approval": "{child_name} wykonał(a) „{chore_name}” (+{points} {points_name}) — czeka na zatwierdzenie.",
|
||||
"notify.pending_reward_claim": "{child_name} odebrał(a) „{reward_name}” ({cost} {points_name}) — czeka na zatwierdzenie.",
|
||||
"notify.streak_milestone": "{child_name} osiągnął(-ęła) serię {days} dni — +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} osiągnął(-ęła) poziom {level}! 🎉",
|
||||
"notify.weekly_digest": "Tygodniowe podsumowanie TaskMate:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, musisz jeszcze zrobić „{chore_name}”.",
|
||||
"notify.mandatory_parent_alert": "{child_name} wciąż nie wykonał(a) obowiązkowego zadania „{chore_name}”.",
|
||||
"notify.monthly_report": "Raport TaskMate za {month}:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} wygrał(a) ranking za {month} z wynikiem {points} {points_name}!",
|
||||
"notify.family_goal_reached": "🎉 Cel rodzinny osiągnięty: {goal_name}! Czas na {goal_reward}.",
|
||||
"notify.birthday": "🎂 Wszystkiego najlepszego, {child_name}! Dziś każde zadanie liczy się {multiplier}×.",
|
||||
"notify.streak_freeze_used": "❄️ Zamrożenie serii uratowało serię {streak} dni ({child_name}, pozostało: {freezes_left}).",
|
||||
"notify.presence_arrival": "🏠 Jesteś w domu, {child_name} — na dziś zostało zadań: {count}.",
|
||||
"notify.wish_requested": "{child_name} chce „{wish_name}” ({target} {points_name}) — czeka na Twoje zatwierdzenie.",
|
||||
"notify.wish_pledged": "💝 {pledger} dodał(a) {points} {points_name} do Twojego życzenia „{wish_name}”!",
|
||||
"notify.bounty_posted": "🏁 Nowa nagroda specjalna: {bounty_name} — {points} {points_name}. Kto pierwszy, ten lepszy!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, zostało {minutes} min na dokończenie „{bounty_name}”, zanim wróci na tablicę.",
|
||||
"notify.recap_ready": "✨ Twoje podsumowanie ({period}) jest gotowe, {child_name}! Dotknij, aby je obejrzeć.",
|
||||
"notify.item_rejected": "↩️ {child_name}, „{item_name}” zostało odesłane{reason_text}",
|
||||
"notify.auction_opened": "🔨 Nowa aukcja: {chore_name} w dniu {date}, do {max_points} {points_name}. Wygrywa najniższa oferta!",
|
||||
"notify.auction_closing": "⏳ {child_name}, licytacja „{chore_name}” kończy się za {minutes} min.",
|
||||
"notify.auction_result": "🔨 Aukcja zakończona: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, dorosły sprawdzi „{chore_name}” przed {until}. Utrzymaj porządek, a dostaniesz +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Kontrola zaliczona, {child_name}! „{chore_name}” wyglądało świetnie: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 Kontrola „{chore_name}” ({child_name}) kończy się za {minutes} min — zatwierdź lub odrzuć ją przed {until}.",
|
||||
"notify.auction_won": "{child_name} wygrał(a) „{chore_name}” w dniu {date} za {points} {points_name}.",
|
||||
"notify.auction_no_bids": "brak ofert na „{chore_name}”, więc zadanie wraca do zwykłej kolejki.",
|
||||
"notify.auction_called_off": "aukcja „{chore_name}” została odwołana. Zadanie wraca do zwykłej kolejki.",
|
||||
"notify.weekly_digest_line": "• {child_name}: zadania: {chores}, zdobyte {points_name}: {points}",
|
||||
"notify.monthly_report_line": "• {child_name}: zadania: {chores}, {points} {points_name}, poziom {level}, najlepsza seria {streak}",
|
||||
"notify.action_approve": "Zatwierdź",
|
||||
"notify.action_reject": "Odrzuć",
|
||||
"notify.action_reject_placeholder": "Powód (opcjonalnie)",
|
||||
"notify.approve_in_panel_hint": "Otwórz panel TaskMate, aby zatwierdzić lub odrzucić.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} wykonał(a) dziś wszystkie zadania!",
|
||||
"notify.celebrate_birthday": "Wszystkiego najlepszego, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} ukończył(a) wyzwanie „{challenge_name}”!",
|
||||
"notify.celebrate_inspection_passed": "„{chore_name}” ({child_name}) przeszło kontrolę!",
|
||||
"notify.celebrate_level_up": "{child_name} osiągnął(-ęła) poziom {level}!",
|
||||
"notify.celebrate_perfect_week": "{child_name} ma za sobą idealny tydzień — +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} osiąga tygodniowy cel dla „{chore_name}” — +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} ukończył(a) misję „{quest_name}”!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} osiągnął(-ęła) serię {days} dni!",
|
||||
"notify.recap_ready_many": "✨ Nowe podsumowania są gotowe ({count}), {child_name}! Dotknij, aby je obejrzeć.",
|
||||
"notify.recap_parent_one": "✨ Nowe podsumowania ({period}) są gotowe dla: {names}. Dotknij, aby je zobaczyć.",
|
||||
"notify.recap_parent_many": "✨ Nowe podsumowania są gotowe dla: {names}. Dotknij, aby je zobaczyć.",
|
||||
"notify.period_weekly": "tydzień",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} i {last}",
|
||||
"notify.month_year": "{month} {year}",
|
||||
"notify.short_date": "{weekday} {day} {month}",
|
||||
"notify.month_1": "styczeń",
|
||||
"notify.month_2": "luty",
|
||||
"notify.month_3": "marzec",
|
||||
"notify.month_4": "kwiecień",
|
||||
"notify.month_5": "maj",
|
||||
"notify.month_6": "czerwiec",
|
||||
"notify.month_7": "lipiec",
|
||||
"notify.month_8": "sierpień",
|
||||
"notify.month_9": "wrzesień",
|
||||
"notify.month_10": "październik",
|
||||
"notify.month_11": "listopad",
|
||||
"notify.month_12": "grudzień",
|
||||
"notify.month_short_1": "sty",
|
||||
"notify.month_short_2": "lut",
|
||||
"notify.month_short_3": "mar",
|
||||
"notify.month_short_4": "kwi",
|
||||
"notify.month_short_5": "maj",
|
||||
"notify.month_short_6": "cze",
|
||||
"notify.month_short_7": "lip",
|
||||
"notify.month_short_8": "sie",
|
||||
"notify.month_short_9": "wrz",
|
||||
"notify.month_short_10": "paź",
|
||||
"notify.month_short_11": "lis",
|
||||
"notify.month_short_12": "gru",
|
||||
"notify.weekday_short_1": "pon.",
|
||||
"notify.weekday_short_2": "wt.",
|
||||
"notify.weekday_short_3": "śr.",
|
||||
"notify.weekday_short_4": "czw.",
|
||||
"notify.weekday_short_5": "pt.",
|
||||
"notify.weekday_short_6": "sob.",
|
||||
"notify.weekday_short_7": "niedz.",
|
||||
"notify.sample_chore": "Posprzątaj pokój",
|
||||
"notify.sample_reward": "Wieczór filmowy",
|
||||
"notify.sample_badge": "Super pomocnik",
|
||||
"notify.sample_goal": "Skarbonka na wieczór filmowy",
|
||||
"notify.sample_goal_reward": "rodzinny wieczór filmowy",
|
||||
"notify.sample_wish": "Zestaw Lego",
|
||||
"notify.sample_pledger": "Babcia",
|
||||
"notify.sample_bounty": "Umyj samochód",
|
||||
"notify.sample_reason": "Niedokończone",
|
||||
"notify.sample_note": "Świetna robota!"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Aplicar penalidade",
|
||||
"approvals.postpone": "Adiar",
|
||||
"approvals.dismiss": "Dispensar",
|
||||
"approvals.penalty_points": "Penalidade: −{points} pts",
|
||||
"approvals.penalty_points": "Penalidade: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "Sem penalidade definida",
|
||||
"approvals.timed_duration": "{count} min",
|
||||
"badge.criteria_and": "E",
|
||||
@@ -253,6 +253,7 @@
|
||||
"child.open_ended_tag": "Descrever",
|
||||
"child.weekly_target_done": "Concluída esta semana",
|
||||
"child.weekly_target_progress": "{done}/{target} esta semana",
|
||||
"child.weekly_target_bonus_reached": "🎉 Meta semanal atingida! +{points}",
|
||||
"child.team_progress": "{joined} / {size} participando",
|
||||
"child.team_you_joined": "Você está na equipe",
|
||||
"child.team_join": "Participar",
|
||||
@@ -326,8 +327,8 @@
|
||||
"child.paused": "Pausado",
|
||||
"child.please_define_child_id": "Por favor define um child_id",
|
||||
"child.please_define_entity": "Por favor define uma entidade (sensor de visão geral do taskmate)",
|
||||
"child.pts_per_min": "pts/min",
|
||||
"child.pts_per_n_min": "pts/{count} min",
|
||||
"child.pts_per_min": "{pointsName}/min",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min",
|
||||
"child.recurring": "Recorrente",
|
||||
"child.resume": "Retomar",
|
||||
"child.start": "Iniciar",
|
||||
@@ -354,6 +355,8 @@
|
||||
"common.editor.header_colour_helper": "Cor de fundo do cabeçalho do cartão",
|
||||
"common.editor.overview_entity": "Entidade de Visão Geral",
|
||||
"common.editor.overview_entity_helper": "A entidade sensor de visão geral do TaskMate",
|
||||
"common.editor.show_parent_actions": "Mostrar ações dos pais",
|
||||
"common.editor.show_parent_actions_helper": "Desative em um painel compartilhado das crianças para mostrar este cartão exatamente como uma criança o vê, mesmo com um pai ou mãe conectado. Apenas exibição — as permissões não mudam.",
|
||||
"common.entity": "Entidade",
|
||||
"common.entity_not_found": "Entidade não encontrada: {entity}",
|
||||
"common.evening": "Noite",
|
||||
@@ -772,6 +775,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/semana",
|
||||
"panel.chore_weekly_target_hint": "Quantas vezes por semana a tarefa precisa ser feita, em qualquer um dos dias escolhidos. Ela deixa de aparecer quando a contagem é atingida e reinicia na segunda-feira.",
|
||||
"panel.chore_weekly_target_label": "Meta semanal (0 = desligado)",
|
||||
"panel.chore_weekly_target_bonus_label": "Bônus da meta semanal",
|
||||
"panel.chore_weekly_target_bonus_hint": "Pontos extras pagos quando a meta semanal é atingida, além dos pontos de cada conclusão. Pago uma vez por semana, quando a última conclusão é aprovada. 0 = sem bônus.",
|
||||
"panel.chore_team_section": "Trabalho em equipe (opcional)",
|
||||
"panel.chore_team_hint": "Uma tarefa em equipe só conta quando esse número de crianças diferentes participar. Cada toque em Concluído é uma entrada e, quando a equipe está completa, a tarefa é enviada para todas. As entradas são zeradas todo dia. Requer a atribuição \"Todos\" e não pode ser uma tarefa aberta.",
|
||||
"panel.chore_team_size_label": "Tamanho da equipe (0 = desligado)",
|
||||
@@ -791,6 +796,9 @@
|
||||
"panel.chore_points_label": "Pontos",
|
||||
"panel.chore_points_per_window_label": "Pontos por janela",
|
||||
"panel.chore_recurrence_day_hint": "ex: segunda — deixe em branco para qualquer dia",
|
||||
"panel.chore_recurrence_interval_label": "Repetir a cada (dias)",
|
||||
"panel.chore_recurrence_interval_hint": "Número inteiro de dias, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Informe um número de dias entre {min} e {max}",
|
||||
"panel.chore_recurrence_day_label": "Dia da semana (somente semanal)",
|
||||
"panel.chore_recurrence_label": "Recorrência",
|
||||
"panel.chore_recurrence_start_hint": "Data âncora — em branco = hoje",
|
||||
@@ -1227,8 +1235,10 @@
|
||||
"panel.penbon_table_name": "Nome",
|
||||
"panel.penbon_table_points": "Pontos",
|
||||
"panel.pending_tooltip": "{count} pendente(s) — clique para revisar",
|
||||
"panel.pts_display": "{count} pts",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "A cada 2 dias",
|
||||
"panel.recurrence_every_n_days": "A cada N dias (personalizado)",
|
||||
"panel.recurrence_every_count_days": "A cada {count} dias",
|
||||
"panel.recurrence_every_2_weeks": "A cada 2 semanas",
|
||||
"panel.recurrence_every_3_months": "A cada 3 meses",
|
||||
"panel.recurrence_every_6_months": "A cada 6 meses",
|
||||
@@ -1464,7 +1474,7 @@
|
||||
"panel.template_builtin_template_packs": "Pacotes de modelos integrados",
|
||||
"panel.template_builtin_templates": "Modelos integrados",
|
||||
"panel.template_chores_in_template": "Tarefas neste modelo",
|
||||
"panel.template_confirm_bar": "{count} tarefa(s) serão criadas · {points} pts no total",
|
||||
"panel.template_confirm_bar": "{count} tarefa(s) serão criadas · {points} {points_name} no total",
|
||||
"panel.template_create_btn": "Criar {count} tarefa(s)",
|
||||
"panel.template_create_title": "Criar Modelo",
|
||||
"panel.template_custom": "Personalizado",
|
||||
@@ -1476,7 +1486,7 @@
|
||||
"panel.template_name_label": "Nome",
|
||||
"panel.template_picker_title": "Adicionar de Modelo",
|
||||
"panel.template_preview_hint": "Revise e personalize cada tarefa antes de criar. Expanda para editar todos os campos ou remova com ✕.",
|
||||
"panel.template_pts_total": "{count} tarefa(s) · {points} pts no total",
|
||||
"panel.template_pts_total": "{count} tarefa(s) · {points} {points_name} no total",
|
||||
"panel.template_requires_approval_label": "Requer aprovação",
|
||||
"panel.template_requires_approval_no": "Não",
|
||||
"panel.template_requires_approval_yes": "Sim",
|
||||
@@ -1687,7 +1697,7 @@
|
||||
"points_display.this_week": "Esta Semana",
|
||||
"points_display.total_points": "Total de Pontos",
|
||||
"points_display.weekly_plus": "+{count} esta semana",
|
||||
"points_display.weekly_pts": "{count} pts",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Adiciona tarefas a esta criança primeiro",
|
||||
"reorder.card_description": "Cartão para reordenar tarefas por criança, organizado por categoria de hora",
|
||||
"reorder.card_name": "Cartão de Reordenação TaskMate",
|
||||
@@ -1792,9 +1802,9 @@
|
||||
"streak.achievement.hundred_desc": "Conclui 100 tarefas",
|
||||
"streak.achievement.hundred_name": "100 Feitas",
|
||||
"streak.achievement.points100_desc": "Ganha 100 pontos no total",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Ganha 50 pontos no total",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "14 dias seguidos",
|
||||
"streak.achievement.streak14_name": "2 Semanas",
|
||||
"streak.achievement.streak30_desc": "30 dias seguidos",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Configurações do bônus surpresa",
|
||||
"panel.settings_surprise_params_hint": "Chance diária por criança e o intervalo de pontos aleatório num acerto.",
|
||||
"panel.settings_surprise_chance": "Chance %",
|
||||
"panel.settings_surprise_min": "Pts mín",
|
||||
"panel.settings_surprise_max": "Pts máx",
|
||||
"panel.settings_surprise_min": "{points_name} mín",
|
||||
"panel.settings_surprise_max": "{points_name} máx",
|
||||
"panel.reward_restock_label": "Reposição automática",
|
||||
"panel.reward_restock_hint": "Repor periodicamente o estoque desta recompensa para um valor fixo.",
|
||||
"panel.reward_restock_amount": "Repor para",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Limita quantos pontos cada criança pode gastar em recompensas por período.",
|
||||
"panel.settings_spendcap_params_label": "Configurações do limite de gastos",
|
||||
"panel.settings_spendcap_params_hint": "Máx. de pontos gastáveis por período (0 = sem limite).",
|
||||
"panel.settings_spendcap_amount": "Limite (pts)",
|
||||
"panel.settings_spendcap_amount": "Limite ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Juros de poupança",
|
||||
"panel.settings_interest_enabled_hint": "Paga periodicamente juros a cada criança sobre o saldo disponível (recompensa a poupança).",
|
||||
"panel.settings_interest_params_label": "Configurações dos juros de poupança",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "faltam {time}",
|
||||
"bounty.no_expiry": "Sem prazo",
|
||||
"bounty.photo_tag": "foto",
|
||||
"bounty.pts": "PTS",
|
||||
"bounty.claim": "Pegar",
|
||||
"bounty.claim_hint": "Você terá {hours} h para terminar",
|
||||
"bounty.finish_first": "Termine sua tarefa extra primeiro",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Devolver",
|
||||
"bounty.sibling_on_it": "{name} está fazendo",
|
||||
"bounty.sibling_back_in": "volta ao quadro em {time} se não terminar",
|
||||
"bounty.waiting": "Esperando um adulto conferir · +{points} ★ pendentes",
|
||||
"bounty.waiting": "Esperando um adulto conferir · +{points} {pointsName} pendentes",
|
||||
"bounty.undo": "Desfazer",
|
||||
"bounty.sibling_checking": "{name} terminou — está sendo conferida",
|
||||
"bounty.you_earned": "Você ganhou +{points} ★",
|
||||
"bounty.you_earned": "Você ganhou +{points} {pointsName}",
|
||||
"bounty.done_by": "Feita por {name}",
|
||||
"bounty.recent": "Feitas recentemente",
|
||||
"bounty.claim_title": "Pegar \"{title}\"?",
|
||||
"bounty.claim_body": "Ela é sua por {hours} horas. Se não estiver pronta até lá, volta ao quadro para todos.",
|
||||
"bounty.not_now": "Agora não",
|
||||
"bounty.claim_it": "Pegar — {points} ★",
|
||||
"bounty.claim_it": "Pegar — {points} {pointsName}",
|
||||
"bounty.photo_title": "Mostre o trabalho pronto",
|
||||
"bounty.photo_body": "\"{title}\" precisa de uma foto",
|
||||
"bounty.cancel": "Cancelar",
|
||||
"bounty.take_photo": "Tirar foto e enviar",
|
||||
"bounty.uploading": "Enviando…",
|
||||
"bounty.yay_title": "Mandou bem!",
|
||||
"bounty.yay_body": "Um adulto vai conferir \"{title}\" e depois você ganha {points} ★.",
|
||||
"bounty.yay_body": "Um adulto vai conferir \"{title}\" e depois você ganha {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d} d {h} h",
|
||||
"bounty.dur_hours": "{h} h {m} min",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Concluída",
|
||||
"panel.bounty_status_expired": "Expirada",
|
||||
"panel.bounty_claimed_meta": "{name} · faltam {time}",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Ninguém pegou · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Expirou depois que uma reserva venceu · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Pega mas nunca concluída · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Escolha uma criança no editor do cartão para ver os leilões dela.",
|
||||
"auction.results": "Resultados",
|
||||
"auction.load_failed": "Não foi possível carregar os leilões: {error}",
|
||||
"auction.up_to": "até {points} ★",
|
||||
"auction.up_to": "até {points} {pointsName}",
|
||||
"auction.lowest_wins": "O lance mais baixo ganha",
|
||||
"auction.gauge_max": "máx. {points} ★",
|
||||
"auction.gauge_hint": "Normalmente vale {points} ★ · dê o menor número de pontos pelo qual você faria",
|
||||
"auction.gauge_max": "máx. {points} {pointsName}",
|
||||
"auction.gauge_hint": "Normalmente vale {points} {pointsName} · dê o menor número de pontos pelo qual você faria",
|
||||
"auction.your_bid": "Seu lance secreto",
|
||||
"auction.change": "Alterar",
|
||||
"auction.withdraw": "Retirar",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Apagar o último dígito",
|
||||
"auction.rule_secret": "Secreto: ninguém vê o seu número.",
|
||||
"auction.rule_lowest": "O lance mais baixo ganha. Em caso de empate, ganha quem deu o lance primeiro.",
|
||||
"auction.rule_min": "Os lances começam em {points} ★.",
|
||||
"auction.rule_min": "Os lances começam em {points} {pointsName}.",
|
||||
"auction.rule_win": "Se você ganhar, faz a tarefa em {date} e recebe o seu lance quando for aprovada.",
|
||||
"auction.cancel": "Cancelar",
|
||||
"auction.seal": "Selar meu lance · {points} ★",
|
||||
"auction.you_won": "Você ganhou! {points} ★ são seus",
|
||||
"auction.seal": "Selar meu lance · {points} {pointsName}",
|
||||
"auction.you_won": "Você ganhou! {points} {pointsName} são seus",
|
||||
"auction.you_won_sub": "quando você fizer em {date} e um adulto aprovar",
|
||||
"auction.in_your_chores": "Está nas suas tarefas de {date}",
|
||||
"auction.sibling_won": "{name} ganhou esta",
|
||||
"auction.sibling_won_sub": "por {points} ★. O lance mais baixo ganha, então tente um pouco mais baixo da próxima vez!",
|
||||
"auction.sibling_won_sub_bid": "por {points} ★ · você deu {bid} ★. O lance mais baixo ganha, então tente um pouco mais baixo da próxima vez!",
|
||||
"auction.tied_sub": "Vocês dois deram {points} ★. Deu empate, e {name} deu o lance primeiro.",
|
||||
"auction.sibling_won_sub": "por {points} {pointsName}. O lance mais baixo ganha, então tente um pouco mais baixo da próxima vez!",
|
||||
"auction.sibling_won_sub_bid": "por {points} {pointsName} · você deu {bid} {pointsName}. O lance mais baixo ganha, então tente um pouco mais baixo da próxima vez!",
|
||||
"auction.tied_sub": "Vocês dois deram {points} {pointsName}. Deu empate, e {name} deu o lance primeiro.",
|
||||
"auction.no_bids": "Nenhum lance desta vez",
|
||||
"auction.no_bids_sub": "{chore} volta à atribuição normal em {date}.",
|
||||
"auction.closed_at": "Fechou {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Fecha",
|
||||
"panel.auction_col_result": "Resultado",
|
||||
"panel.auction_col_status": "Status da tarefa",
|
||||
"panel.auction_normally": "{date} · normalmente {points} ★",
|
||||
"panel.auction_min_meta": "mín. {points} ★",
|
||||
"panel.auction_normally": "{date} · normalmente {points} {points_name}",
|
||||
"panel.auction_min_meta": "mín. {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "Nenhum lance ainda",
|
||||
"panel.auction_no_bids": "Sem lances",
|
||||
"panel.auction_sealed": "selado",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "Nenhuma ocorrência desta tarefa pode ir a leilão nos próximos 60 dias.",
|
||||
"panel.auction_no_chores": "Nenhuma tarefa pode ir a leilão ainda. Tarefas em equipe, cronometradas, abertas e sem atribuição não podem.",
|
||||
"panel.auction_max_label": "Lance máximo",
|
||||
"panel.auction_normally_worth": "normalmente vale {points} ★",
|
||||
"panel.auction_normally_worth": "normalmente vale {points} {points_name}",
|
||||
"panel.auction_max_hint": "As crianças podem dar lances do mínimo até este valor. O mais baixo ganha; em caso de empate, o mais antigo.",
|
||||
"panel.auction_min_label": "Lance mínimo",
|
||||
"panel.auction_min_hint": "Desligado: vale qualquer lance a partir de 1. Ligado: evita uma disputa para baixo.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Progresso até a próxima recompensa",
|
||||
"kiosk.editor.show_picker_progress": "Progresso na tela de escolha",
|
||||
"kiosk.editor.show_picker_progress_helper": "“3 de 6 feitas” embaixo de cada rosto",
|
||||
"kiosk.editor.play_sounds": "Sons",
|
||||
"kiosk.editor.play_sounds_helper": "O som de conclusão de cada tarefa e o som de desfazer",
|
||||
"kiosk.editor.show_confetti": "Confete",
|
||||
"kiosk.editor.show_completion_popup": "Janela de comemoração",
|
||||
"kiosk.editor.show_completion_popup_helper": "Um rápido “Incrível!” com os pontos depois de cada tarefa",
|
||||
"kiosk.editor.children_heading": "Crianças exibidas e PINs",
|
||||
"kiosk.editor.children_hint": "Desmarque uma criança para ocultá-la. Use as setas para definir a ordem na tela de escolha.",
|
||||
"kiosk.editor.require_pin": "Exigir PIN",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Desative para um quadro só de visualização",
|
||||
"chore_board.editor.show_legend": "Mostrar a legenda",
|
||||
"chore_board.editor.hide_done_periods": "Ocultar períodos concluídos",
|
||||
"chore_board.editor.hide_done_periods_helper": "Remover um período do dia quando todas as crianças terminarem as tarefas"
|
||||
"chore_board.editor.hide_done_periods_helper": "Remover um período do dia quando todas as crianças terminarem as tarefas",
|
||||
"notify.bedtime_reminder": "{child_name}, você ainda tem tarefas para fazer antes de dormir.",
|
||||
"notify.streak_at_risk": "{child_name}, faça uma tarefa hoje para manter sua sequência de {streak} dias!",
|
||||
"notify.all_chores_done": "{child_name} terminou todas as tarefas de hoje!",
|
||||
"notify.badge_earned": "{child_name} ganhou a medalha \"{badge_name}\"!",
|
||||
"notify.pending_chore_approval": "{child_name} concluiu \"{chore_name}\" (+{points} {points_name}) — aguardando aprovação.",
|
||||
"notify.pending_reward_claim": "{child_name} resgatou \"{reward_name}\" ({cost} {points_name}) — aguardando aprovação.",
|
||||
"notify.streak_milestone": "{child_name} chegou a uma sequência de {days} dias — +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} chegou ao nível {level}! 🎉",
|
||||
"notify.weekly_digest": "Resumo semanal do TaskMate:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, você ainda precisa fazer \"{chore_name}\".",
|
||||
"notify.mandatory_parent_alert": "{child_name} ainda não fez a tarefa obrigatória \"{chore_name}\".",
|
||||
"notify.monthly_report": "Relatório TaskMate de {month}:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} venceu o ranking de {month} com {points} {points_name}!",
|
||||
"notify.family_goal_reached": "🎉 Meta da família alcançada: {goal_name}! Hora de {goal_reward}.",
|
||||
"notify.birthday": "🎂 Feliz aniversário, {child_name}! Hoje cada tarefa vale {multiplier}×.",
|
||||
"notify.streak_freeze_used": "❄️ Um congelamento salvou a sequência de {streak} dias de {child_name} (restam {freezes_left}).",
|
||||
"notify.presence_arrival": "🏠 Você chegou em casa, {child_name} — faltam {count} tarefas hoje.",
|
||||
"notify.wish_requested": "{child_name} deseja \"{wish_name}\" ({target} {points_name}) — aguardando sua aprovação.",
|
||||
"notify.wish_pledged": "💝 {pledger} colocou {points} {points_name} no seu desejo \"{wish_name}\"!",
|
||||
"notify.bounty_posted": "🏁 Nova recompensa extra: {bounty_name} — {points} {points_name}. Quem pegar primeiro leva!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, faltam {minutes} minutos para terminar \"{bounty_name}\" antes que volte para o quadro.",
|
||||
"notify.recap_ready": "✨ Sua retrospectiva está pronta ({period}), {child_name}! Toque para assistir.",
|
||||
"notify.item_rejected": "↩️ {child_name}, \"{item_name}\" foi devolvido{reason_text}",
|
||||
"notify.auction_opened": "🔨 Novo leilão: {chore_name} em {date}, até {max_points} {points_name}. O menor lance vence!",
|
||||
"notify.auction_closing": "⏳ {child_name}, os lances para \"{chore_name}\" terminam em {minutes} minutos.",
|
||||
"notify.auction_result": "🔨 Leilão encerrado: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, um adulto vai inspecionar \"{chore_name}\" antes das {until}. Mantenha tudo caprichado para ganhar +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Inspeção aprovada, {child_name}! \"{chore_name}\" ficou ótimo: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 A inspeção de \"{chore_name}\" de {child_name} termina em {minutes} minutos — aprove ou reprove antes das {until}.",
|
||||
"notify.auction_won": "{child_name} ganhou \"{chore_name}\" em {date} por {points} {points_name}.",
|
||||
"notify.auction_no_bids": "nenhum lance para \"{chore_name}\", então ela volta para o rodízio normal.",
|
||||
"notify.auction_called_off": "o leilão de \"{chore_name}\" foi cancelado. Ela volta para o rodízio normal.",
|
||||
"notify.weekly_digest_line": "• {child_name}: {chores} tarefas, {points} {points_name} ganhos",
|
||||
"notify.monthly_report_line": "• {child_name}: {chores} tarefas, {points} {points_name}, nível {level}, melhor sequência {streak}",
|
||||
"notify.action_approve": "Aprovar",
|
||||
"notify.action_reject": "Rejeitar",
|
||||
"notify.action_reject_placeholder": "Motivo (opcional)",
|
||||
"notify.approve_in_panel_hint": "Abra o painel do TaskMate para aprovar ou rejeitar.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} terminou todas as tarefas de hoje!",
|
||||
"notify.celebrate_birthday": "Feliz aniversário, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} completou o desafio \"{challenge_name}\"!",
|
||||
"notify.celebrate_inspection_passed": "\"{chore_name}\" de {child_name} passou na inspeção!",
|
||||
"notify.celebrate_level_up": "{child_name} chegou ao nível {level}!",
|
||||
"notify.celebrate_perfect_week": "{child_name} fez uma semana perfeita — +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} atingiu a meta semanal de \"{chore_name}\" — +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} completou a missão \"{quest_name}\"!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} chegou a uma sequência de {days} dias!",
|
||||
"notify.recap_ready_many": "✨ {count} novas retrospectivas estão prontas, {child_name}! Toque para assistir.",
|
||||
"notify.recap_parent_one": "✨ Novas retrospectivas ({period}) estão prontas para {names}. Toque para ver.",
|
||||
"notify.recap_parent_many": "✨ Novas retrospectivas estão prontas para {names}. Toque para ver.",
|
||||
"notify.period_weekly": "semana",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} e {last}",
|
||||
"notify.month_year": "{month} de {year}",
|
||||
"notify.short_date": "{weekday}, {day} {month}",
|
||||
"notify.month_1": "janeiro",
|
||||
"notify.month_2": "fevereiro",
|
||||
"notify.month_3": "março",
|
||||
"notify.month_4": "abril",
|
||||
"notify.month_5": "maio",
|
||||
"notify.month_6": "junho",
|
||||
"notify.month_7": "julho",
|
||||
"notify.month_8": "agosto",
|
||||
"notify.month_9": "setembro",
|
||||
"notify.month_10": "outubro",
|
||||
"notify.month_11": "novembro",
|
||||
"notify.month_12": "dezembro",
|
||||
"notify.month_short_1": "jan",
|
||||
"notify.month_short_2": "fev",
|
||||
"notify.month_short_3": "mar",
|
||||
"notify.month_short_4": "abr",
|
||||
"notify.month_short_5": "mai",
|
||||
"notify.month_short_6": "jun",
|
||||
"notify.month_short_7": "jul",
|
||||
"notify.month_short_8": "ago",
|
||||
"notify.month_short_9": "set",
|
||||
"notify.month_short_10": "out",
|
||||
"notify.month_short_11": "nov",
|
||||
"notify.month_short_12": "dez",
|
||||
"notify.weekday_short_1": "seg",
|
||||
"notify.weekday_short_2": "ter",
|
||||
"notify.weekday_short_3": "qua",
|
||||
"notify.weekday_short_4": "qui",
|
||||
"notify.weekday_short_5": "sex",
|
||||
"notify.weekday_short_6": "sáb",
|
||||
"notify.weekday_short_7": "dom",
|
||||
"notify.sample_chore": "Arrumar o quarto",
|
||||
"notify.sample_reward": "Noite de cinema",
|
||||
"notify.sample_badge": "Super ajudante",
|
||||
"notify.sample_goal": "Cofrinho da noite de cinema",
|
||||
"notify.sample_goal_reward": "uma noite de cinema em família",
|
||||
"notify.sample_wish": "Kit de Lego",
|
||||
"notify.sample_pledger": "Vovó",
|
||||
"notify.sample_bounty": "Lavar o carro",
|
||||
"notify.sample_reason": "Não terminou",
|
||||
"notify.sample_note": "Mandou muito bem!"
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
"approvals.apply_penalty": "Aplicar penalização",
|
||||
"approvals.postpone": "Adiar",
|
||||
"approvals.dismiss": "Dispensar",
|
||||
"approvals.penalty_points": "Penalização: −{points} pts",
|
||||
"approvals.penalty_points": "Penalização: −{points} {pointsName}",
|
||||
"approvals.no_penalty": "Sem penalização definida",
|
||||
"approvals.timed_duration": "{count} min",
|
||||
"badge.criteria_and": "E",
|
||||
@@ -253,6 +253,7 @@
|
||||
"child.open_ended_tag": "Descrever",
|
||||
"child.weekly_target_done": "Concluída esta semana",
|
||||
"child.weekly_target_progress": "{done}/{target} esta semana",
|
||||
"child.weekly_target_bonus_reached": "🎉 Objetivo semanal atingido! +{points}",
|
||||
"child.team_progress": "{joined} / {size} juntaram-se",
|
||||
"child.team_you_joined": "Estás na equipa",
|
||||
"child.team_join": "Juntar-me",
|
||||
@@ -326,8 +327,8 @@
|
||||
"child.paused": "Pausado",
|
||||
"child.please_define_child_id": "Por favor define um child_id",
|
||||
"child.please_define_entity": "Por favor define uma entidade (sensor de visão geral do taskmate)",
|
||||
"child.pts_per_min": "pts/min",
|
||||
"child.pts_per_n_min": "pts/{count} min",
|
||||
"child.pts_per_min": "{pointsName}/min",
|
||||
"child.pts_per_n_min": "{pointsName}/{count} min",
|
||||
"child.recurring": "Recorrente",
|
||||
"child.resume": "Retomar",
|
||||
"child.start": "Iniciar",
|
||||
@@ -358,6 +359,8 @@
|
||||
"common.editor.header_colour_helper": "Cor de fundo do cabeçalho do cartão",
|
||||
"common.editor.overview_entity": "Entidade de Visão Geral",
|
||||
"common.editor.overview_entity_helper": "A entidade sensor de visão geral do TaskMate",
|
||||
"common.editor.show_parent_actions": "Mostrar ações dos pais",
|
||||
"common.editor.show_parent_actions_helper": "Desative num painel partilhado das crianças para mostrar este cartão exatamente como uma criança o vê, mesmo com um pai ou mãe com sessão iniciada. Apenas visualização — as permissões não mudam.",
|
||||
"common.entity": "Entidade",
|
||||
"common.entity_not_found": "Entidade não encontrada: {entity}",
|
||||
"common.error": "Erro",
|
||||
@@ -777,6 +780,8 @@
|
||||
"panel.chore_weekly_target_pill": "{count}×/semana",
|
||||
"panel.chore_weekly_target_hint": "Quantas vezes por semana a tarefa tem de ser feita, em qualquer um dos dias escolhidos. Deixa de ser apresentada quando a contagem é atingida e reinicia à segunda-feira.",
|
||||
"panel.chore_weekly_target_label": "Meta semanal (0 = desligado)",
|
||||
"panel.chore_weekly_target_bonus_label": "Bónus do objetivo semanal",
|
||||
"panel.chore_weekly_target_bonus_hint": "Pontos extra atribuídos quando o objetivo semanal é atingido, além dos pontos de cada conclusão. Atribuído uma vez por semana, quando a última conclusão é aprovada. 0 = sem bónus.",
|
||||
"panel.chore_team_section": "Trabalho de equipa (opcional)",
|
||||
"panel.chore_team_hint": "Uma tarefa de equipa só conta quando este número de crianças diferentes se juntar. Cada toque em Concluído é uma adesão e, quando a equipa está completa, a tarefa é submetida para todas. As adesões reiniciam todos os dias. Requer a atribuição \"Todos\" e não pode ser uma tarefa aberta.",
|
||||
"panel.chore_team_size_label": "Tamanho da equipa (0 = desligado)",
|
||||
@@ -796,6 +801,9 @@
|
||||
"panel.chore_points_label": "Pontos",
|
||||
"panel.chore_points_per_window_label": "Pontos por janela",
|
||||
"panel.chore_recurrence_day_hint": "ex. segunda-feira — deixe em branco para qualquer dia",
|
||||
"panel.chore_recurrence_interval_label": "Repetir a cada (dias)",
|
||||
"panel.chore_recurrence_interval_hint": "Número inteiro de dias, {min}–{max}",
|
||||
"panel.chore_recurrence_interval_invalid": "Introduza um número de dias entre {min} e {max}",
|
||||
"panel.chore_recurrence_day_label": "Dia da semana (apenas semanal)",
|
||||
"panel.chore_recurrence_label": "Recorrência",
|
||||
"panel.chore_recurrence_start_hint": "Data âncora — em branco = hoje",
|
||||
@@ -1232,8 +1240,10 @@
|
||||
"panel.penbon_table_name": "Nome",
|
||||
"panel.penbon_table_points": "Pontos",
|
||||
"panel.pending_tooltip": "{count} pendente(s) — clique para revisar",
|
||||
"panel.pts_display": "{count} pts",
|
||||
"panel.pts_display": "{count} {points_name}",
|
||||
"panel.recurrence_every_2_days": "A cada 2 dias",
|
||||
"panel.recurrence_every_n_days": "A cada N dias (personalizado)",
|
||||
"panel.recurrence_every_count_days": "A cada {count} dias",
|
||||
"panel.recurrence_every_2_weeks": "A cada 2 semanas",
|
||||
"panel.recurrence_every_3_months": "A cada 3 meses",
|
||||
"panel.recurrence_every_6_months": "A cada 6 meses",
|
||||
@@ -1469,7 +1479,7 @@
|
||||
"panel.template_builtin_template_packs": "Pacotes de modelos incorporados",
|
||||
"panel.template_builtin_templates": "Modelos incorporados",
|
||||
"panel.template_chores_in_template": "Tarefas neste modelo",
|
||||
"panel.template_confirm_bar": "{count} tarefa(s) serão criadas · {points} pts no total",
|
||||
"panel.template_confirm_bar": "{count} tarefa(s) serão criadas · {points} {points_name} no total",
|
||||
"panel.template_create_btn": "Criar {count} tarefa(s)",
|
||||
"panel.template_create_title": "Criar Modelo",
|
||||
"panel.template_custom": "Personalizado",
|
||||
@@ -1481,7 +1491,7 @@
|
||||
"panel.template_name_label": "Nome",
|
||||
"panel.template_picker_title": "Adicionar a partir de Modelo",
|
||||
"panel.template_preview_hint": "Reveja e personalize cada tarefa antes de criar. Expanda para editar todos os campos ou remova com ✕.",
|
||||
"panel.template_pts_total": "{count} tarefa(s) · {points} pts no total",
|
||||
"panel.template_pts_total": "{count} tarefa(s) · {points} {points_name} no total",
|
||||
"panel.template_requires_approval_label": "Requer aprovação",
|
||||
"panel.template_requires_approval_no": "Não",
|
||||
"panel.template_requires_approval_yes": "Sim",
|
||||
@@ -1692,7 +1702,7 @@
|
||||
"points_display.this_week": "Esta Semana",
|
||||
"points_display.total_points": "Total de Pontos",
|
||||
"points_display.weekly_plus": "+{count} esta semana",
|
||||
"points_display.weekly_pts": "{count} pts",
|
||||
"points_display.weekly_pts": "{count} {pointsName}",
|
||||
"reorder.add_chores_first": "Adiciona tarefas a esta criança primeiro",
|
||||
"reorder.card_description": "Cartão para reordenar tarefas por criança, organizado por categoria de hora",
|
||||
"reorder.card_name": "Cartão de Reordenação TaskMate",
|
||||
@@ -1797,9 +1807,9 @@
|
||||
"streak.achievement.hundred_desc": "Conclui 100 tarefas",
|
||||
"streak.achievement.hundred_name": "100 Feitas",
|
||||
"streak.achievement.points100_desc": "Ganha 100 pontos no total",
|
||||
"streak.achievement.points100_name": "100 ⭐",
|
||||
"streak.achievement.points100_name": "100 {pointsName}",
|
||||
"streak.achievement.points50_desc": "Ganha 50 pontos no total",
|
||||
"streak.achievement.points50_name": "50 ⭐",
|
||||
"streak.achievement.points50_name": "50 {pointsName}",
|
||||
"streak.achievement.streak14_desc": "14 dias seguidos",
|
||||
"streak.achievement.streak14_name": "2 Semanas",
|
||||
"streak.achievement.streak30_desc": "30 dias seguidos",
|
||||
@@ -1948,8 +1958,8 @@
|
||||
"panel.settings_surprise_params_label": "Definições do bónus surpresa",
|
||||
"panel.settings_surprise_params_hint": "Hipótese diária por criança e o intervalo de pontos aleatório num acerto.",
|
||||
"panel.settings_surprise_chance": "Hipótese %",
|
||||
"panel.settings_surprise_min": "Pts mín",
|
||||
"panel.settings_surprise_max": "Pts máx",
|
||||
"panel.settings_surprise_min": "{points_name} mín",
|
||||
"panel.settings_surprise_max": "{points_name} máx",
|
||||
"panel.reward_restock_label": "Reposição automática",
|
||||
"panel.reward_restock_hint": "Repor periodicamente o stock desta recompensa para um valor fixo.",
|
||||
"panel.reward_restock_amount": "Repor para",
|
||||
@@ -1995,7 +2005,7 @@
|
||||
"panel.settings_spendcap_enabled_hint": "Limita quantos pontos cada criança pode gastar em recompensas por período.",
|
||||
"panel.settings_spendcap_params_label": "Configurações do limite de gastos",
|
||||
"panel.settings_spendcap_params_hint": "Máx. de pontos gastáveis por período (0 = sem limite).",
|
||||
"panel.settings_spendcap_amount": "Limite (pts)",
|
||||
"panel.settings_spendcap_amount": "Limite ({points_name})",
|
||||
"panel.settings_interest_enabled_label": "Juros de poupança",
|
||||
"panel.settings_interest_enabled_hint": "Paga periodicamente juros a cada criança sobre o saldo disponível (recompensa a poupança).",
|
||||
"panel.settings_interest_params_label": "Configurações dos juros de poupança",
|
||||
@@ -2229,7 +2239,6 @@
|
||||
"bounty.left": "faltam {time}",
|
||||
"bounty.no_expiry": "Sem prazo",
|
||||
"bounty.photo_tag": "foto",
|
||||
"bounty.pts": "PTS",
|
||||
"bounty.claim": "Ficar com ela",
|
||||
"bounty.claim_hint": "Vais ter {hours} h para a acabar",
|
||||
"bounty.finish_first": "Acaba primeiro a tua tarefa extra",
|
||||
@@ -2238,23 +2247,23 @@
|
||||
"bounty.give_back": "Devolver",
|
||||
"bounty.sibling_on_it": "{name} está a tratar disto",
|
||||
"bounty.sibling_back_in": "volta ao quadro daqui a {time} se não estiver feita",
|
||||
"bounty.waiting": "À espera que um adulto verifique · +{points} ★ pendentes",
|
||||
"bounty.waiting": "À espera que um adulto verifique · +{points} {pointsName} pendentes",
|
||||
"bounty.undo": "Anular",
|
||||
"bounty.sibling_checking": "{name} acabou — está a ser verificada",
|
||||
"bounty.you_earned": "Ganhaste +{points} ★",
|
||||
"bounty.you_earned": "Ganhaste +{points} {pointsName}",
|
||||
"bounty.done_by": "Feita por {name}",
|
||||
"bounty.recent": "Feitas recentemente",
|
||||
"bounty.claim_title": "Ficar com \"{title}\"?",
|
||||
"bounty.claim_body": "É tua durante {hours} horas. Se não estiver feita até lá, volta ao quadro para todos.",
|
||||
"bounty.not_now": "Agora não",
|
||||
"bounty.claim_it": "Ficar com ela — {points} ★",
|
||||
"bounty.claim_it": "Ficar com ela — {points} {pointsName}",
|
||||
"bounty.photo_title": "Mostra-nos o trabalho feito",
|
||||
"bounty.photo_body": "\"{title}\" precisa de uma foto",
|
||||
"bounty.cancel": "Cancelar",
|
||||
"bounty.take_photo": "Tirar foto e enviar",
|
||||
"bounty.uploading": "A enviar…",
|
||||
"bounty.yay_title": "Bom trabalho!",
|
||||
"bounty.yay_body": "Um adulto vai verificar \"{title}\" e depois recebes {points} ★.",
|
||||
"bounty.yay_body": "Um adulto vai verificar \"{title}\" e depois recebes {points} {pointsName}.",
|
||||
"bounty.ok": "OK",
|
||||
"bounty.dur_days": "{d} d {h} h",
|
||||
"bounty.dur_hours": "{h} h {m} min",
|
||||
@@ -2279,7 +2288,7 @@
|
||||
"panel.bounty_status_completed": "Concluída",
|
||||
"panel.bounty_status_expired": "Expirada",
|
||||
"panel.bounty_claimed_meta": "{name} · faltam {time}",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} ★",
|
||||
"panel.bounty_completed_meta": "{name} · +{points} {points_name}",
|
||||
"panel.bounty_expired_meta": "Ninguém ficou com ela · {date}",
|
||||
"panel.bounty_expired_lapsed_meta": "Expirou depois de uma reserva ter caducado · {date}",
|
||||
"panel.bounty_expired_unfinished_meta": "Reservada mas nunca concluída · {date}",
|
||||
@@ -2332,10 +2341,10 @@
|
||||
"auction.no_child": "Escolhe uma criança no editor do cartão para ver os leilões dela.",
|
||||
"auction.results": "Resultados",
|
||||
"auction.load_failed": "Não foi possível carregar os leilões: {error}",
|
||||
"auction.up_to": "até {points} ★",
|
||||
"auction.up_to": "até {points} {pointsName}",
|
||||
"auction.lowest_wins": "A oferta mais baixa ganha",
|
||||
"auction.gauge_max": "máx. {points} ★",
|
||||
"auction.gauge_hint": "Normalmente vale {points} ★ · oferece o mínimo de pontos por que a farias",
|
||||
"auction.gauge_max": "máx. {points} {pointsName}",
|
||||
"auction.gauge_hint": "Normalmente vale {points} {pointsName} · oferece o mínimo de pontos por que a farias",
|
||||
"auction.your_bid": "A tua oferta secreta",
|
||||
"auction.change": "Alterar",
|
||||
"auction.withdraw": "Retirar",
|
||||
@@ -2352,17 +2361,17 @@
|
||||
"auction.backspace": "Apagar o último algarismo",
|
||||
"auction.rule_secret": "Secreto: ninguém vê o teu número.",
|
||||
"auction.rule_lowest": "A oferta mais baixa ganha. Em caso de empate, ganha quem ofereceu primeiro.",
|
||||
"auction.rule_min": "As ofertas começam em {points} ★.",
|
||||
"auction.rule_min": "As ofertas começam em {points} {pointsName}.",
|
||||
"auction.rule_win": "Se ganhares, fazes a tarefa em {date} e recebes a tua oferta quando for aprovada.",
|
||||
"auction.cancel": "Cancelar",
|
||||
"auction.seal": "Selar a minha oferta · {points} ★",
|
||||
"auction.you_won": "Ganhaste! {points} ★ são teus",
|
||||
"auction.seal": "Selar a minha oferta · {points} {pointsName}",
|
||||
"auction.you_won": "Ganhaste! {points} {pointsName} são teus",
|
||||
"auction.you_won_sub": "quando a tiveres feito em {date} e um adulto a aprovar",
|
||||
"auction.in_your_chores": "Está nas tuas tarefas de {date}",
|
||||
"auction.sibling_won": "{name} ganhou esta",
|
||||
"auction.sibling_won_sub": "por {points} ★. A oferta mais baixa ganha, por isso tenta um pouco mais baixo da próxima vez!",
|
||||
"auction.sibling_won_sub_bid": "por {points} ★ · ofereceste {bid} ★. A oferta mais baixa ganha, por isso tenta um pouco mais baixo da próxima vez!",
|
||||
"auction.tied_sub": "Ambos ofereceram {points} ★. Houve empate, e {name} fez a oferta primeiro.",
|
||||
"auction.sibling_won_sub": "por {points} {pointsName}. A oferta mais baixa ganha, por isso tenta um pouco mais baixo da próxima vez!",
|
||||
"auction.sibling_won_sub_bid": "por {points} {pointsName} · ofereceste {bid} {pointsName}. A oferta mais baixa ganha, por isso tenta um pouco mais baixo da próxima vez!",
|
||||
"auction.tied_sub": "Ambos ofereceram {points} {pointsName}. Houve empate, e {name} fez a oferta primeiro.",
|
||||
"auction.no_bids": "Desta vez não houve ofertas",
|
||||
"auction.no_bids_sub": "{chore} volta à atribuição habitual em {date}.",
|
||||
"auction.closed_at": "Fechou {when}",
|
||||
@@ -2389,8 +2398,8 @@
|
||||
"panel.auction_col_closes": "Fecha",
|
||||
"panel.auction_col_result": "Resultado",
|
||||
"panel.auction_col_status": "Estado da tarefa",
|
||||
"panel.auction_normally": "{date} · normalmente {points} ★",
|
||||
"panel.auction_min_meta": "mín. {points} ★",
|
||||
"panel.auction_normally": "{date} · normalmente {points} {points_name}",
|
||||
"panel.auction_min_meta": "mín. {points} {points_name}",
|
||||
"panel.auction_no_bids_yet": "Ainda sem ofertas",
|
||||
"panel.auction_no_bids": "Sem ofertas",
|
||||
"panel.auction_sealed": "selada",
|
||||
@@ -2419,7 +2428,7 @@
|
||||
"panel.auction_no_occurrences": "Nenhuma ocorrência desta tarefa pode ir a leilão nos próximos 60 dias.",
|
||||
"panel.auction_no_chores": "Ainda nenhuma tarefa pode ir a leilão. Tarefas de equipa, cronometradas, abertas e sem atribuição não podem.",
|
||||
"panel.auction_max_label": "Oferta máxima",
|
||||
"panel.auction_normally_worth": "normalmente vale {points} ★",
|
||||
"panel.auction_normally_worth": "normalmente vale {points} {points_name}",
|
||||
"panel.auction_max_hint": "As crianças podem oferecer desde a oferta mínima até este valor. Ganha a mais baixa; em caso de empate, a mais antiga.",
|
||||
"panel.auction_min_label": "Oferta mínima",
|
||||
"panel.auction_min_hint": "Desligada: conta qualquer oferta a partir de 1. Ligada: evita uma corrida para o fundo.",
|
||||
@@ -2591,6 +2600,11 @@
|
||||
"kiosk.editor.show_reward_progress": "Progresso para a próxima recompensa",
|
||||
"kiosk.editor.show_picker_progress": "Progresso no ecrã de escolha",
|
||||
"kiosk.editor.show_picker_progress_helper": "“3 de 6 feitas” debaixo de cada cara",
|
||||
"kiosk.editor.play_sounds": "Sons",
|
||||
"kiosk.editor.play_sounds_helper": "O som de conclusão de cada tarefa e o som de anular",
|
||||
"kiosk.editor.show_confetti": "Confetes",
|
||||
"kiosk.editor.show_completion_popup": "Janela de festa",
|
||||
"kiosk.editor.show_completion_popup_helper": "Um breve “Incrível!” com os pontos depois de cada tarefa",
|
||||
"kiosk.editor.children_heading": "Crianças mostradas e PINs",
|
||||
"kiosk.editor.children_hint": "Desmarca uma criança para a esconder. Usa as setas para definir a ordem no ecrã de escolha.",
|
||||
"kiosk.editor.require_pin": "Exigir PIN",
|
||||
@@ -2883,5 +2897,102 @@
|
||||
"chore_board.editor.tap_to_complete_helper": "Desative para um quadro só de visualização",
|
||||
"chore_board.editor.show_legend": "Mostrar a legenda",
|
||||
"chore_board.editor.hide_done_periods": "Ocultar períodos concluídos",
|
||||
"chore_board.editor.hide_done_periods_helper": "Retirar um período do dia quando todas as crianças terminaram as tarefas"
|
||||
"chore_board.editor.hide_done_periods_helper": "Retirar um período do dia quando todas as crianças terminaram as tarefas",
|
||||
"notify.bedtime_reminder": "{child_name}, ainda tens tarefas para fazer antes de ir para a cama.",
|
||||
"notify.streak_at_risk": "{child_name}, faz uma tarefa hoje para manter a tua sequência de {streak} dias!",
|
||||
"notify.all_chores_done": "{child_name} terminou todas as tarefas de hoje!",
|
||||
"notify.badge_earned": "{child_name} ganhou o distintivo \"{badge_name}\"!",
|
||||
"notify.pending_chore_approval": "{child_name} concluiu \"{chore_name}\" (+{points} {points_name}) — a aguardar aprovação.",
|
||||
"notify.pending_reward_claim": "{child_name} pediu \"{reward_name}\" ({cost} {points_name}) — a aguardar aprovação.",
|
||||
"notify.streak_milestone": "{child_name} chegou a uma sequência de {days} dias — +{points} {points_name}!",
|
||||
"notify.level_up": "{child_name} chegou ao nível {level}! 🎉",
|
||||
"notify.weekly_digest": "Resumo semanal do TaskMate:\n{summary}",
|
||||
"notify.celebration": "🎉 {message}",
|
||||
"notify.mandatory_reminder": "{child_name}, ainda tens de fazer \"{chore_name}\".",
|
||||
"notify.mandatory_parent_alert": "{child_name} ainda não fez a tarefa obrigatória \"{chore_name}\".",
|
||||
"notify.monthly_report": "Relatório TaskMate de {month}:\n{summary}",
|
||||
"notify.season_champion": "🏆 {child_name} venceu a classificação de {month} com {points} {points_name}!",
|
||||
"notify.family_goal_reached": "🎉 Objetivo da família alcançado: {goal_name}! Está na hora de {goal_reward}.",
|
||||
"notify.birthday": "🎂 Feliz aniversário, {child_name}! Hoje cada tarefa vale {multiplier}×.",
|
||||
"notify.streak_freeze_used": "❄️ Um congelamento salvou a sequência de {streak} dias de {child_name} (restam {freezes_left}).",
|
||||
"notify.presence_arrival": "🏠 Estás em casa, {child_name} — faltam {count} tarefas hoje.",
|
||||
"notify.wish_requested": "{child_name} deseja \"{wish_name}\" ({target} {points_name}) — a aguardar a tua aprovação.",
|
||||
"notify.wish_pledged": "💝 {pledger} juntou {points} {points_name} ao teu desejo \"{wish_name}\"!",
|
||||
"notify.bounty_posted": "🏁 Novo prémio: {bounty_name} — {points} {points_name}. Quem o pedir primeiro fica com ele!",
|
||||
"notify.bounty_claim_lapsing": "⏳ {child_name}, faltam {minutes} minutos para terminares \"{bounty_name}\" antes de voltar ao quadro.",
|
||||
"notify.recap_ready": "✨ O teu resumo está pronto ({period}), {child_name}! Toca para o ver.",
|
||||
"notify.item_rejected": "↩️ {child_name}, \"{item_name}\" foi devolvido{reason_text}",
|
||||
"notify.auction_opened": "🔨 Novo leilão: {chore_name} em {date}, até {max_points} {points_name}. Ganha a oferta mais baixa!",
|
||||
"notify.auction_closing": "⏳ {child_name}, as licitações para \"{chore_name}\" terminam dentro de {minutes} minutos.",
|
||||
"notify.auction_result": "🔨 Leilão terminado: {result_text}",
|
||||
"notify.inspection_started": "🔍 {child_name}, um adulto vai inspecionar \"{chore_name}\" antes das {until}. Mantém tudo impecável para ganhares +{bonus} {points_name}!",
|
||||
"notify.inspection_passed": "🌟 Inspeção aprovada, {child_name}! \"{chore_name}\" estava ótimo: +{bonus} {points_name}{note_text}",
|
||||
"notify.inspection_reminder": "🔍 A inspeção de \"{chore_name}\" de {child_name} termina dentro de {minutes} minutos — aprova ou reprova antes das {until}.",
|
||||
"notify.auction_won": "{child_name} ganhou \"{chore_name}\" em {date} por {points} {points_name}.",
|
||||
"notify.auction_no_bids": "não houve ofertas para \"{chore_name}\", por isso volta à rotação normal.",
|
||||
"notify.auction_called_off": "o leilão de \"{chore_name}\" foi cancelado. Volta à rotação normal.",
|
||||
"notify.weekly_digest_line": "• {child_name}: {chores} tarefas, {points} {points_name} ganhos",
|
||||
"notify.monthly_report_line": "• {child_name}: {chores} tarefas, {points} {points_name}, nível {level}, melhor sequência {streak}",
|
||||
"notify.action_approve": "Aprovar",
|
||||
"notify.action_reject": "Rejeitar",
|
||||
"notify.action_reject_placeholder": "Motivo (opcional)",
|
||||
"notify.approve_in_panel_hint": "Abre o painel do TaskMate para aprovar ou rejeitar.",
|
||||
"notify.celebrate_all_chores_done": "{child_name} terminou todas as tarefas de hoje!",
|
||||
"notify.celebrate_birthday": "Feliz aniversário, {child_name}!",
|
||||
"notify.celebrate_challenge_completed": "{child_name} completou o desafio \"{challenge_name}\"!",
|
||||
"notify.celebrate_inspection_passed": "\"{chore_name}\" de {child_name} passou na inspeção!",
|
||||
"notify.celebrate_level_up": "{child_name} chegou ao nível {level}!",
|
||||
"notify.celebrate_perfect_week": "{child_name} fez uma semana perfeita — +{bonus}!",
|
||||
"notify.celebrate_weekly_target": "{child_name} atingiu o objetivo semanal de \"{chore_name}\" — +{bonus}!",
|
||||
"notify.celebrate_quest_completed": "{child_name} completou a missão \"{quest_name}\"!",
|
||||
"notify.celebrate_streak_milestone": "{child_name} chegou a uma sequência de {days} dias!",
|
||||
"notify.recap_ready_many": "✨ {count} novos resumos estão prontos, {child_name}! Toca para os ver.",
|
||||
"notify.recap_parent_one": "✨ Novos resumos ({period}) estão prontos para {names}. Toca para os ver.",
|
||||
"notify.recap_parent_many": "✨ Novos resumos estão prontos para {names}. Toca para os ver.",
|
||||
"notify.period_weekly": "semana",
|
||||
"notify.period_range": "{start}–{end}",
|
||||
"notify.names_and": "{names} e {last}",
|
||||
"notify.month_year": "{month} de {year}",
|
||||
"notify.short_date": "{weekday}, {day} {month}",
|
||||
"notify.month_1": "janeiro",
|
||||
"notify.month_2": "fevereiro",
|
||||
"notify.month_3": "março",
|
||||
"notify.month_4": "abril",
|
||||
"notify.month_5": "maio",
|
||||
"notify.month_6": "junho",
|
||||
"notify.month_7": "julho",
|
||||
"notify.month_8": "agosto",
|
||||
"notify.month_9": "setembro",
|
||||
"notify.month_10": "outubro",
|
||||
"notify.month_11": "novembro",
|
||||
"notify.month_12": "dezembro",
|
||||
"notify.month_short_1": "jan",
|
||||
"notify.month_short_2": "fev",
|
||||
"notify.month_short_3": "mar",
|
||||
"notify.month_short_4": "abr",
|
||||
"notify.month_short_5": "mai",
|
||||
"notify.month_short_6": "jun",
|
||||
"notify.month_short_7": "jul",
|
||||
"notify.month_short_8": "ago",
|
||||
"notify.month_short_9": "set",
|
||||
"notify.month_short_10": "out",
|
||||
"notify.month_short_11": "nov",
|
||||
"notify.month_short_12": "dez",
|
||||
"notify.weekday_short_1": "seg",
|
||||
"notify.weekday_short_2": "ter",
|
||||
"notify.weekday_short_3": "qua",
|
||||
"notify.weekday_short_4": "qui",
|
||||
"notify.weekday_short_5": "sex",
|
||||
"notify.weekday_short_6": "sáb",
|
||||
"notify.weekday_short_7": "dom",
|
||||
"notify.sample_chore": "Arrumar o quarto",
|
||||
"notify.sample_reward": "Noite de cinema",
|
||||
"notify.sample_badge": "Super ajudante",
|
||||
"notify.sample_goal": "Mealheiro da noite de cinema",
|
||||
"notify.sample_goal_reward": "uma noite de cinema em família",
|
||||
"notify.sample_wish": "Conjunto Lego",
|
||||
"notify.sample_pledger": "Avó",
|
||||
"notify.sample_bounty": "Lavar o carro",
|
||||
"notify.sample_reason": "Não terminado",
|
||||
"notify.sample_note": "Excelente trabalho!"
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class TaskMateActivityCard extends LitElement {
|
||||
// Everything else (penalty, bonus, gift, manual add/remove) is reversible.
|
||||
static get _UNDO_DENY_PREFIXES() {
|
||||
return [
|
||||
"Weekend bonus", "Streak milestone bonus", "Perfect week bonus",
|
||||
"Weekend bonus", "Streak milestone bonus", "Perfect week bonus", "Weekly target bonus",
|
||||
"Allocated to pool:", "Pool refund", "Points decay",
|
||||
"Savings interest", "Badge", "Streak freeze",
|
||||
"Wish savings", "Wish refund", "Wish pledge", "Wish redeemed",
|
||||
@@ -1099,7 +1099,9 @@ class TaskMateActivityCard extends LitElement {
|
||||
const verb = isAdd ? this._t('activity.received') : isSpend ? this._t('activity.spent') : this._t('activity.lost');
|
||||
const sign = isAdd ? '+' : '−';
|
||||
const tone = isAdd ? 'good' : (isSpend ? 'accent' : 'bad');
|
||||
const emoji = reason.startsWith('Inspection passed:') ? '🔍' : isAdd ? '⭐' : (isPenalty ? '⚠️' : '➖');
|
||||
const emoji = reason.startsWith('Inspection passed:') ? '🔍'
|
||||
: isAdd ? html`<ha-icon style="--mdc-icon-size:1.1em" icon="${pointsIcon}"></ha-icon>`
|
||||
: (isPenalty ? '⚠️' : '➖');
|
||||
return {
|
||||
childName, tone, emoji, sign, pts,
|
||||
text: html`<strong>${childName}</strong> ${verb} <strong>${pts}</strong>${item.reason ? html` <span class="reason">— ${displayReason}</span>` : ''}`,
|
||||
|
||||
@@ -808,6 +808,19 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
return child ? child.avatar : null;
|
||||
}
|
||||
|
||||
_pointsAttrs() {
|
||||
return (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity))
|
||||
|| this.hass?.states?.[this.config.entity]?.attributes || {};
|
||||
}
|
||||
|
||||
_pointsIcon() {
|
||||
return this._pointsAttrs().points_icon || "mdi:star";
|
||||
}
|
||||
|
||||
_pointsName() {
|
||||
return this._pointsAttrs().points_name || this._t("common.points");
|
||||
}
|
||||
|
||||
// Flatten all pending items into a single ordered, day/time-grouped list so
|
||||
// the designed views can render approvals, reward claims and mandatory misses
|
||||
// together while reusing every existing handler.
|
||||
@@ -1066,7 +1079,7 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
<div class="muted ap-pl-sub">${it.childName}</div>
|
||||
</div>
|
||||
${this._apPhotoDesigned(it, "ap-pl-photo")}
|
||||
<div class="chip ap-gold">+${it.points} ⭐</div>
|
||||
<div class="chip ap-gold">+${it.points} <ha-icon style="--mdc-icon-size:1.1em;vertical-align:-0.15em" icon="${this._pointsIcon()}"></ha-icon></div>
|
||||
</div>
|
||||
<div class="row" style="margin-top:11px;gap:8px">
|
||||
${this._designActions(it, "full")}
|
||||
@@ -1163,7 +1176,7 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
${childName}
|
||||
</span>
|
||||
<span class="points-badge">
|
||||
<ha-icon icon="mdi:star"></ha-icon>
|
||||
<ha-icon icon="${this._pointsIcon()}"></ha-icon>
|
||||
${cost}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1253,7 +1266,7 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
</span>
|
||||
</div>
|
||||
${penalty > 0
|
||||
? html`<span class="penalty-note">${this._t('approvals.penalty_points', { points: penalty })}</span>`
|
||||
? html`<span class="penalty-note">${this._t('approvals.penalty_points', { points: penalty, pointsName: this._pointsName() })}</span>`
|
||||
: html`<span class="penalty-note none">${this._t('approvals.no_penalty')}</span>`}
|
||||
</div>
|
||||
<div class="action-buttons right">
|
||||
@@ -1547,7 +1560,7 @@ class TaskMateApprovalsCard extends LitElement {
|
||||
</span>
|
||||
` : ''}
|
||||
<span class="points-badge">
|
||||
<ha-icon icon="mdi:star"></ha-icon>
|
||||
<ha-icon icon="${this._pointsIcon()}"></ha-icon>
|
||||
${completion.points}
|
||||
</span>
|
||||
${this._renderStars(completion)}
|
||||
|
||||
@@ -126,6 +126,14 @@ class TaskMateAuctionCard extends LitElement {
|
||||
return this._attrs().children || [];
|
||||
}
|
||||
|
||||
_pointsName() {
|
||||
return this._attrs().points_name || this._t("common.points");
|
||||
}
|
||||
|
||||
_pointsIcon() {
|
||||
return this._attrs().points_icon || "mdi:star";
|
||||
}
|
||||
|
||||
_child() {
|
||||
return this._children().find(c => String(c.id) === String(this.config.child_id));
|
||||
}
|
||||
@@ -314,10 +322,10 @@ class TaskMateAuctionCard extends LitElement {
|
||||
return html`
|
||||
<div class="ac-gauge">
|
||||
<div class="ac-gauge-row">
|
||||
<span>${a.min_points} ★</span><span class="ac-caps">${this._t("auction.lowest_wins")}</span><span>${this._t("auction.gauge_max", { points: a.max_points })}</span>
|
||||
<span>${a.min_points} <ha-icon class="ac-inl-ic" icon="${this._pointsIcon()}"></ha-icon></span><span class="ac-caps">${this._t("auction.lowest_wins")}</span><span>${this._t("auction.gauge_max", { points: a.max_points, pointsName: this._pointsName() })}</span>
|
||||
</div>
|
||||
<div class="ac-gauge-bar"></div>
|
||||
<div class="ac-gauge-hint">${this._t("auction.gauge_hint", { points: a.normal_points })}</div>
|
||||
<div class="ac-gauge-hint">${this._t("auction.gauge_hint", { points: a.normal_points, pointsName: this._pointsName() })}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -330,7 +338,7 @@ class TaskMateAuctionCard extends LitElement {
|
||||
<div class="ac-lot-t">${a.chore_name}</div>
|
||||
<div class="ac-lot-when"><ha-icon icon="mdi:calendar-blank-outline"></ha-icon>${this._date(a.occurrence)}</div>
|
||||
</div>
|
||||
${a.status === "open" ? html`<span class="ac-tag ac-tag-max">${this._t("auction.up_to", { points: a.max_points })}</span>` : ""}
|
||||
${a.status === "open" ? html`<span class="ac-tag ac-tag-max">${this._t("auction.up_to", { points: a.max_points, pointsName: this._pointsName() })}</span>` : ""}
|
||||
</div>`;
|
||||
|
||||
if (a.status === "open") {
|
||||
@@ -341,7 +349,7 @@ class TaskMateAuctionCard extends LitElement {
|
||||
${mine ? html`
|
||||
<div class="ac-sealed">
|
||||
<span class="ac-env"><ha-icon icon="mdi:email-lock"></ha-icon></span>
|
||||
<div class="ac-grow"><small>${this._t("auction.your_bid")}</small><b>${a.my_bid} ★</b></div>
|
||||
<div class="ac-grow"><small>${this._t("auction.your_bid")}</small><b>${a.my_bid} <ha-icon class="ac-inl-ic" icon="${this._pointsIcon()}"></ha-icon></b></div>
|
||||
<button class="ac-btn ac-ghost ac-sm" ?disabled=${this._busy} @click=${() => this._openSheet(a)}>${this._t("auction.change")}</button>
|
||||
</div>` : this._gauge(a)}
|
||||
<div class="ac-lot-foot">
|
||||
@@ -373,13 +381,13 @@ class TaskMateAuctionCard extends LitElement {
|
||||
${head}
|
||||
<div class="ac-result ac-win">
|
||||
<span class="ac-ri"><ha-icon icon="mdi:trophy"></ha-icon></span>
|
||||
<div>${this._t("auction.you_won", { points: a.price })}<small>${this._t("auction.you_won_sub", { date: this._date(a.occurrence, false) })}</small></div>
|
||||
<div>${this._t("auction.you_won", { points: a.price, pointsName: this._pointsName() })}<small>${this._t("auction.you_won_sub", { date: this._date(a.occurrence, false) })}</small></div>
|
||||
</div>
|
||||
<div class="ac-won-chore">
|
||||
<ha-icon icon="${a.icon || "mdi:clipboard-check-outline"}"></ha-icon>
|
||||
<span class="ac-grow ac-won-nm">${a.chore_name}</span>
|
||||
<span class="ac-tag ac-tag-won"><ha-icon icon="mdi:gavel"></ha-icon>${this._t("child.won_at_auction")}</span>
|
||||
<span class="ac-pts"><ha-icon icon="mdi:star"></ha-icon>${a.price}</span>
|
||||
<span class="ac-pts"><ha-icon icon="${this._pointsIcon()}"></ha-icon>${a.price}</span>
|
||||
</div>
|
||||
<div class="ac-lot-foot"><span class="ac-faint">${this._t("auction.in_your_chores", { date: this._date(a.occurrence, false) })}</span></div>
|
||||
</div>`;
|
||||
@@ -387,9 +395,9 @@ class TaskMateAuctionCard extends LitElement {
|
||||
// Lowest bid wins and the earliest breaks a tie, so a losing bid equal to
|
||||
// the price lost on timing, not on the amount.
|
||||
const winner = this._childName(a.winner_id);
|
||||
const sub = a.my_bid == null ? this._t("auction.sibling_won_sub", { points: a.price })
|
||||
: Number(a.my_bid) === Number(a.price) ? this._t("auction.tied_sub", { points: a.price, name: winner })
|
||||
: this._t("auction.sibling_won_sub_bid", { points: a.price, bid: a.my_bid });
|
||||
const sub = a.my_bid == null ? this._t("auction.sibling_won_sub", { points: a.price, pointsName: this._pointsName() })
|
||||
: Number(a.my_bid) === Number(a.price) ? this._t("auction.tied_sub", { points: a.price, name: winner, pointsName: this._pointsName() })
|
||||
: this._t("auction.sibling_won_sub_bid", { points: a.price, bid: a.my_bid, pointsName: this._pointsName() });
|
||||
return html`
|
||||
<div class="ac-lot ac-lost">
|
||||
${head}
|
||||
@@ -413,7 +421,7 @@ class TaskMateAuctionCard extends LitElement {
|
||||
<div class="ac-grab"></div>
|
||||
<h3>${a.chore_name}</h3>
|
||||
<div class="ac-sheet-sub">${this._t("auction.sheet_sub", { date: this._date(a.occurrence), time: this._fmtDur(this._left(a)) })}</div>
|
||||
<div class="ac-bigbid">${v} <ha-icon icon="mdi:star"></ha-icon></div>
|
||||
<div class="ac-bigbid">${v} <ha-icon icon="${this._pointsIcon()}"></ha-icon></div>
|
||||
<div class="ac-bidlbl">${this._t("auction.bid_label")}</div>
|
||||
<input type="range" class="ac-range" min="${a.min_points}" max="${a.max_points}" .value=${String(Math.max(a.min_points, v))}
|
||||
aria-label="${this._t("auction.bid_label")}"
|
||||
@@ -428,13 +436,13 @@ class TaskMateAuctionCard extends LitElement {
|
||||
<div class="ac-rules">
|
||||
<div><ha-icon icon="mdi:lock-outline"></ha-icon><span>${this._t("auction.rule_secret")}${others ? ` ${others === 1 ? this._t("auction.other_bids_one") : this._t("auction.other_bids", { count: others })}.` : ""}</span></div>
|
||||
<div><ha-icon icon="mdi:trophy-outline"></ha-icon><span>${this._t("auction.rule_lowest")}</span></div>
|
||||
${a.min_points > 1 ? html`<div><ha-icon icon="mdi:arrow-collapse-down"></ha-icon><span>${this._t("auction.rule_min", { points: a.min_points })}</span></div>` : ""}
|
||||
${a.min_points > 1 ? html`<div><ha-icon icon="mdi:arrow-collapse-down"></ha-icon><span>${this._t("auction.rule_min", { points: a.min_points, pointsName: this._pointsName() })}</span></div>` : ""}
|
||||
<div><ha-icon icon="mdi:check"></ha-icon><span>${this._t("auction.rule_win", { date: this._date(a.occurrence, false) })}</span></div>
|
||||
</div>
|
||||
<div class="ac-sheet-acts">
|
||||
<button type="button" class="ac-btn ac-ghost" @click=${close}>${this._t("auction.cancel")}</button>
|
||||
<button type="button" class="ac-btn ac-bid ac-seal" ?disabled=${!valid || this._busy} @click=${() => this._seal(a)}>
|
||||
<ha-icon icon="mdi:email-lock"></ha-icon>${this._t("auction.seal", { points: v })}
|
||||
<ha-icon icon="mdi:email-lock"></ha-icon>${this._t("auction.seal", { points: v, pointsName: this._pointsName() })}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -608,6 +616,7 @@ class TaskMateAuctionCard extends LitElement {
|
||||
font-weight: 700; color: var(--tmd-warn, #e67e22);
|
||||
}
|
||||
.ac-pts ha-icon { --mdc-icon-size: 16px; }
|
||||
.ac-inl-ic { --mdc-icon-size: 1.1em; vertical-align: -0.15em; }
|
||||
.ac-av {
|
||||
width: var(--ac-s, 30px); height: var(--ac-s, 30px); flex: none;
|
||||
border-radius: 50%;
|
||||
|
||||
@@ -110,6 +110,14 @@ class TaskMateBountyCard extends LitElement {
|
||||
return this._attrs().children || [];
|
||||
}
|
||||
|
||||
_pointsName() {
|
||||
return this._attrs().points_name || this._t("common.points");
|
||||
}
|
||||
|
||||
_pointsIcon() {
|
||||
return this._attrs().points_icon || "mdi:star";
|
||||
}
|
||||
|
||||
_child() {
|
||||
return this._children().find(c => String(c.id) === String(this.config.child_id));
|
||||
}
|
||||
@@ -378,7 +386,7 @@ class TaskMateBountyCard extends LitElement {
|
||||
} else if (b.status === "pending" && mine) {
|
||||
body = html`
|
||||
<div class="bb-act">
|
||||
<span class="bb-tag warn bb-wait"><ha-icon icon="mdi:timer-sand"></ha-icon>${this._t("bounty.waiting", { points: b.points })}</span>
|
||||
<span class="bb-tag warn bb-wait"><ha-icon icon="mdi:timer-sand"></ha-icon>${this._t("bounty.waiting", { points: b.points, pointsName: this._pointsName() })}</span>
|
||||
${b.undo && b.completion_id ? html`
|
||||
<button class="bb-btn bb-text" ?disabled=${this._busy} @click=${() => this._undo(b)}>
|
||||
<ha-icon class="tm-rtl-flip" icon="mdi:undo-variant"></ha-icon>${this._t("bounty.undo")}
|
||||
@@ -389,13 +397,13 @@ class TaskMateBountyCard extends LitElement {
|
||||
body = html`<div class="bb-sib">${this._avatar(b.claimed_by, 20)}<span>${this._t("bounty.sibling_checking", { name: this._childName(b.claimed_by) })}</span></div>`;
|
||||
} else if (b.status === "completed") {
|
||||
body = mine
|
||||
? html`<div class="bb-act"><span class="bb-tag good"><ha-icon icon="mdi:check"></ha-icon>${this._t("bounty.you_earned", { points: b.points_awarded ?? b.points })}</span></div>`
|
||||
? html`<div class="bb-act"><span class="bb-tag good"><ha-icon icon="mdi:check"></ha-icon>${this._t("bounty.you_earned", { points: b.points_awarded ?? b.points, pointsName: this._pointsName() })}</span></div>`
|
||||
: html`<div class="bb-sib">${this._avatar(b.claimed_by, 20)}<span>${this._t("bounty.done_by", { name: this._childName(b.claimed_by) })}</span></div>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="bb-row ${cls}">
|
||||
<div class="bb-cost"><b>${b.points}</b><small>★ ${this._t("bounty.pts")}</small></div>
|
||||
<div class="bb-cost" title="${b.points} ${this._pointsName()}"><b>${b.points}</b><ha-icon class="bb-cost-ic" icon="${this._pointsIcon()}"></ha-icon></div>
|
||||
<div class="bb-main">
|
||||
<div class="bb-title-row">
|
||||
<span class="bb-ic"><ha-icon icon="${b.icon || "mdi:flag-outline"}"></ha-icon></span>
|
||||
@@ -427,7 +435,7 @@ class TaskMateBountyCard extends LitElement {
|
||||
<p>${this._t("bounty.claim_body", { hours: b.claim_hours })}</p>
|
||||
<div class="bb-dlg-acts">
|
||||
<button class="bb-btn bb-ghost" @click=${close}>${this._t("bounty.not_now")}</button>
|
||||
<button class="bb-btn bb-claim" ?disabled=${this._busy} @click=${() => this._claim(b)}>${this._t("bounty.claim_it", { points: b.points })}</button>
|
||||
<button class="bb-btn bb-claim" ?disabled=${this._busy} @click=${() => this._claim(b)}>${this._t("bounty.claim_it", { points: b.points, pointsName: this._pointsName() })}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
} else if (d.kind === "photo") {
|
||||
@@ -451,7 +459,7 @@ class TaskMateBountyCard extends LitElement {
|
||||
<div class="bb-dlg" style="--bb-dlg:var(--tmd-good, #2ecc71)">
|
||||
<div class="bb-big-ic"><ha-icon icon="mdi:check"></ha-icon></div>
|
||||
<h3>${this._t("bounty.yay_title")}</h3>
|
||||
<p>${this._t("bounty.yay_body", { title: b.title, points: b.points })}</p>
|
||||
<p>${this._t("bounty.yay_body", { title: b.title, points: b.points, pointsName: this._pointsName() })}</p>
|
||||
<div class="bb-dlg-acts"><button class="bb-btn bb-done" @click=${close}>${this._t("bounty.ok")}</button></div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -527,7 +535,7 @@ class TaskMateBountyCard extends LitElement {
|
||||
font-family: var(--tmd-font-display, inherit);
|
||||
font-size: 1.35rem; line-height: 1; text-shadow: 0 1px 2px rgba(0, 0, 0, .2);
|
||||
}
|
||||
.bb-cost small { font-size: .62rem; font-weight: 700; letter-spacing: .5px; margin-top: 3px; opacity: .95; }
|
||||
.bb-cost-ic { --mdc-icon-size: 14px; margin-top: 2px; opacity: .95; }
|
||||
:host([data-tm-design="accessible"]) .bb-cost { color: #111; box-shadow: none; border: 2px solid var(--tmd-border, #111); }
|
||||
:host([data-tm-design="accessible"]) .bb-cost b { text-shadow: none; }
|
||||
.bb-main { flex: 1; min-width: 0; }
|
||||
|
||||
@@ -31,6 +31,15 @@ const WINDOW_DAYS = {
|
||||
every_6_months: 180,
|
||||
};
|
||||
|
||||
// Custom "every N days" recurrences (#1038) are stored as every_<N>_days
|
||||
// (every_2_days included); bounds match recurrence_interval_days() in const.py.
|
||||
function recurrenceIntervalDays(recurrence) {
|
||||
const m = /^every_(\d{1,3})_days$/.exec(recurrence || "");
|
||||
if (!m) return null;
|
||||
const n = Number(m[1]);
|
||||
return n >= 2 && n <= 365 ? n : null;
|
||||
}
|
||||
|
||||
function ymd(date, tz) {
|
||||
return date.toLocaleDateString("en-CA", { timeZone: tz });
|
||||
}
|
||||
@@ -366,12 +375,13 @@ class TaskMateCalendarCard extends LitElement {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recurrence === "every_2_days" && anchorStr) {
|
||||
const intervalDays = recurrenceIntervalDays(recurrence);
|
||||
if (intervalDays && anchorStr) {
|
||||
try {
|
||||
const anchor = new Date(anchorStr + "T00:00:00");
|
||||
const diff = diffDays(dayDate, anchor);
|
||||
if (diff < 0) return false;
|
||||
return diff % 2 === 0;
|
||||
return diff % intervalDays === 0;
|
||||
} catch (e) { return false; }
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ class TaskMateChildCard extends LitElement {
|
||||
super();
|
||||
this._loading = {};
|
||||
this._celebrating = null;
|
||||
this._celebrationWeeklyBonus = 0;
|
||||
this._confetti = [];
|
||||
// Optimistic completions: track chores that were just completed
|
||||
// These are used to immediately hide the DONE button before the server confirms
|
||||
@@ -1462,6 +1463,13 @@ class TaskMateChildCard extends LitElement {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.celebration-weekly-bonus {
|
||||
margin-top: 8px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
color: var(--fun-purple);
|
||||
}
|
||||
|
||||
.celebration-points ha-icon {
|
||||
--mdc-icon-size: 28px;
|
||||
color: var(--fun-yellow);
|
||||
@@ -2996,7 +3004,7 @@ class TaskMateChildCard extends LitElement {
|
||||
<span class="ch-emoji">${r.glyph}</span>
|
||||
<div class="ch-mid">
|
||||
<div class="ch-name">${r.chore.name}</div>
|
||||
${r.done ? "" : html`<div class="chip soft" style="margin-top:3px">+${r.points} ⭐</div>`}
|
||||
${r.done ? "" : html`<div class="chip soft" style="margin-top:3px">+${r.points} <ha-icon style="--mdc-icon-size:1.1em;vertical-align:-0.15em" icon="${this._pointsIcon()}"></ha-icon></div>`}
|
||||
${this._designChoreMeta(r)}
|
||||
</div>
|
||||
${r.done
|
||||
@@ -4141,9 +4149,10 @@ class TaskMateChildCard extends LitElement {
|
||||
const timeStr = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||
|
||||
// Rate display
|
||||
const pointsName = attrs.points_name || this._t('common.stars');
|
||||
const rateLabel = chore.timed_rate_minutes === 1
|
||||
? `${ratePoints} ${this._t('child.pts_per_min')}`
|
||||
: `${ratePoints} ${this._t('child.pts_per_n_min', {count: chore.timed_rate_minutes})}`;
|
||||
? `${ratePoints} ${this._t('child.pts_per_min', {pointsName})}`
|
||||
: `${ratePoints} ${this._t('child.pts_per_n_min', {count: chore.timed_rate_minutes, pointsName})}`;
|
||||
|
||||
// Daily cap info
|
||||
const maxMin = chore.timed_max_daily_minutes || 0;
|
||||
@@ -4373,7 +4382,7 @@ class TaskMateChildCard extends LitElement {
|
||||
// for non-parents with one friendly message instead of a doomed service call.
|
||||
// The one exception is a child's own tick still inside the undo window
|
||||
// (#918), which goes through the restricted undo_chore service.
|
||||
if (!window.__taskmate_is_parent(this.hass)) {
|
||||
if (!this._isParent()) {
|
||||
const own = (todaysCompletions || []).find(
|
||||
c => c.chore_id === chore.id && c.child_id === child.id && c.bonus_subtask_id === subtask.id
|
||||
);
|
||||
@@ -4454,6 +4463,9 @@ class TaskMateChildCard extends LitElement {
|
||||
<ha-icon icon="${pointsIcon}"></ha-icon>
|
||||
+${points}
|
||||
</div>
|
||||
${this._celebrationWeeklyBonus ? html`
|
||||
<div class="celebration-weekly-bonus">${this._t('child.weekly_target_bonus_reached', { points: this._celebrationWeeklyBonus })}</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -4559,6 +4571,17 @@ class TaskMateChildCard extends LitElement {
|
||||
},
|
||||
};
|
||||
|
||||
// Weekly target bonus (#1044): this tap fills the week's quota, and the
|
||||
// chore pays without approval, so the bonus lands right now — say so in
|
||||
// the celebration. Read before the call, while progress is still pre-tap.
|
||||
const weeklyTarget = Number(chore.weekly_target) || 0;
|
||||
const weeklyBonus = Number(chore.weekly_target_bonus) || 0;
|
||||
const weeklyProgress = Number((child.weekly_chore_progress || {})[chore.id] || 0);
|
||||
this._celebrationWeeklyBonus = weeklyTarget > 0 && weeklyBonus > 0
|
||||
&& chore.requires_approval === false && !chore.require_photo && !chore.open_ended
|
||||
&& weeklyProgress + 1 === weeklyTarget
|
||||
? weeklyBonus : 0;
|
||||
|
||||
this._loading = { ...this._loading, [chore.id]: true };
|
||||
this.requestUpdate();
|
||||
|
||||
@@ -4925,6 +4948,16 @@ class TaskMateChildCard extends LitElement {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent-only controls are shown to admins and TaskMate parents unless the
|
||||
* card sets show_parent_actions: false (#1032) — for a shared kids' dashboard
|
||||
* where a parent account stays signed in. The card then behaves exactly as it
|
||||
* does for a child. Display only; the services keep their own permission checks.
|
||||
*/
|
||||
_isParent() {
|
||||
return this.config?.show_parent_actions !== false && window.__taskmate_is_parent(this.hass);
|
||||
}
|
||||
|
||||
/** True if an error from reject_chore is an authorization/admin rejection. */
|
||||
_isUnauthorized(error) {
|
||||
const text = `${error?.message || ""} ${error?.code || ""}`.toLowerCase();
|
||||
@@ -5049,7 +5082,7 @@ class TaskMateChildCard extends LitElement {
|
||||
// with one clear, friendly message and never fire the doomed call.
|
||||
// The one exception is a child's own completion still inside the undo
|
||||
// window (#918), which goes through the restricted undo_chore service.
|
||||
if (!window.__taskmate_is_parent(this.hass)) {
|
||||
if (!this._isParent()) {
|
||||
const latest = this._latestCompletion(childCompletionsToday);
|
||||
if (this._childCanUndo(latest)) {
|
||||
await this._childUndo(latest, chore, child);
|
||||
@@ -5181,12 +5214,18 @@ class TaskMateChildCard extends LitElement {
|
||||
return i.status === "open" ? "tm-insp-row-open" : i.status === "passed" ? "tm-insp-row-passed" : "";
|
||||
}
|
||||
|
||||
_pointsIcon() {
|
||||
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config?.entity))
|
||||
|| this.hass?.states?.[this.config?.entity]?.attributes || {};
|
||||
return attrs.points_icon || "mdi:star";
|
||||
}
|
||||
|
||||
_renderInspectionTag(chore, child) {
|
||||
const i = this._inspectionFor(chore, child);
|
||||
if (!i) return "";
|
||||
const tags = {
|
||||
open: ["mdi:magnify-scan", this._t("inspection.tag_open")],
|
||||
passed: ["mdi:star", this._t("inspection.tag_passed", { bonus: i.bonus })],
|
||||
passed: [this._pointsIcon(), this._t("inspection.tag_passed", { bonus: i.bonus })],
|
||||
redo: ["mdi:restore", this._t("inspection.tag_redo")],
|
||||
failed: ["mdi:magnify-scan", this._t("inspection.tag_failed")],
|
||||
};
|
||||
@@ -5289,6 +5328,7 @@ class TaskMateChildCard extends LitElement {
|
||||
|
||||
_closeCelebration() {
|
||||
this._celebrating = null;
|
||||
this._celebrationWeeklyBonus = 0;
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
@@ -5527,6 +5567,7 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
{ name: 'pre_reader', selector: { boolean: {} } },
|
||||
{ name: 'pre_reader_labels', selector: { boolean: {} } },
|
||||
{ name: 'pre_reader_points', selector: { boolean: {} } },
|
||||
{ name: 'show_parent_actions', selector: { boolean: {} } },
|
||||
{ name: 'debug', selector: { boolean: {} } },
|
||||
];
|
||||
}
|
||||
@@ -5548,6 +5589,7 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
pre_reader: this._t('child.editor.pre_reader'),
|
||||
pre_reader_labels: this._t('child.editor.pre_reader_labels'),
|
||||
pre_reader_points: this._t('child.editor.pre_reader_points'),
|
||||
show_parent_actions: this._t('common.editor.show_parent_actions'),
|
||||
debug: this._t('child.editor.show_debug'),
|
||||
};
|
||||
return labels[entry.name] ?? entry.name;
|
||||
@@ -5564,6 +5606,7 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
dependency_mode: this._t('child.editor.dependency_helper'),
|
||||
elapsed_time_mode: this._t('child.editor.elapsed_helper'),
|
||||
include_anytime_chores: this._t('child.editor.include_anytime_chores_helper'),
|
||||
show_parent_actions: this._t('common.editor.show_parent_actions_helper'),
|
||||
};
|
||||
return helpers[entry.name] ?? '';
|
||||
};
|
||||
@@ -5587,6 +5630,7 @@ class TaskMateChildCardEditor extends LitElement {
|
||||
pre_reader: this.config.pre_reader === true,
|
||||
pre_reader_labels: this.config.pre_reader_labels === true,
|
||||
pre_reader_points: this.config.pre_reader_points === true,
|
||||
show_parent_actions: this.config.show_parent_actions !== false,
|
||||
debug: this.config.debug === true,
|
||||
};
|
||||
|
||||
|
||||
@@ -245,7 +245,8 @@ class TaskMateChoreBoardCard extends LitElement {
|
||||
return;
|
||||
}
|
||||
let service;
|
||||
if (window.__taskmate_is_parent && window.__taskmate_is_parent(this.hass)) {
|
||||
if (this.config?.show_parent_actions !== false
|
||||
&& window.__taskmate_is_parent && window.__taskmate_is_parent(this.hass)) {
|
||||
service = "reject_chore";
|
||||
} else {
|
||||
// A child's own tick, inside the undo window the server publishes (#918).
|
||||
@@ -664,6 +665,7 @@ class TaskMateChoreBoardCardEditor extends LitElement {
|
||||
{ name: "tap_to_complete", selector: { boolean: {} } },
|
||||
{ name: "show_legend", selector: { boolean: {} } },
|
||||
{ name: "hide_done_periods", selector: { boolean: {} } },
|
||||
{ name: "show_parent_actions", selector: { boolean: {} } },
|
||||
{
|
||||
name: "card_design",
|
||||
selector: {
|
||||
@@ -687,6 +689,7 @@ class TaskMateChoreBoardCardEditor extends LitElement {
|
||||
tap_to_complete: this._t("chore_board.editor.tap_to_complete"),
|
||||
show_legend: this._t("chore_board.editor.show_legend"),
|
||||
hide_done_periods: this._t("chore_board.editor.hide_done_periods"),
|
||||
show_parent_actions: this._t("common.editor.show_parent_actions"),
|
||||
card_design: this._t("common.design.field_label"),
|
||||
}[entry.name] ?? entry.name);
|
||||
|
||||
@@ -695,6 +698,7 @@ class TaskMateChoreBoardCardEditor extends LitElement {
|
||||
children: this._t("chore_board.editor.children_helper"),
|
||||
tap_to_complete: this._t("chore_board.editor.tap_to_complete_helper"),
|
||||
hide_done_periods: this._t("chore_board.editor.hide_done_periods_helper"),
|
||||
show_parent_actions: this._t("common.editor.show_parent_actions_helper"),
|
||||
}[entry.name] ?? "");
|
||||
|
||||
render() {
|
||||
@@ -708,6 +712,7 @@ class TaskMateChoreBoardCardEditor extends LitElement {
|
||||
tap_to_complete: this.config.tap_to_complete !== false,
|
||||
show_legend: this.config.show_legend !== false,
|
||||
hide_done_periods: this.config.hide_done_periods === true,
|
||||
show_parent_actions: this.config.show_parent_actions !== false,
|
||||
card_design: this.config.card_design || "global",
|
||||
};
|
||||
return html`
|
||||
@@ -745,7 +750,7 @@ class TaskMateChoreBoardCardEditor extends LitElement {
|
||||
if (key === "card_design") return value === "global";
|
||||
if (key === "default_view") return value === "today";
|
||||
if (key === "children") return Array.isArray(value) && value.length === 0;
|
||||
if (["show_week", "tap_to_complete", "show_legend"].includes(key)) return value === true;
|
||||
if (["show_week", "tap_to_complete", "show_legend", "show_parent_actions"].includes(key)) return value === true;
|
||||
if (key === "hide_done_periods") return value === false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -449,13 +449,19 @@
|
||||
* page). In LTR, and for text with no number run that doesn't open with a
|
||||
* number, the value comes back untouched. Works on plain strings: Lit text,
|
||||
* the panel's HTML strings and translated messages alike.
|
||||
*
|
||||
* "3-day" mustn't follow a letter or hyphen ("re-3-day" stays as is). That
|
||||
* is matched as a captured lead character, not a lookbehind: Safari before
|
||||
* 16.4 (iOS 15) can't compile a lookbehind, and the SyntaxError took this
|
||||
* whole file down with it, blanking the child card (#1034).
|
||||
*/
|
||||
const _NUM = String.raw`\p{Nd}+(?:[.,\u066B\u066C]\p{Nd}+)*`;
|
||||
const _NUM_RUN = new RegExp(String.raw`${_NUM}(?:\s*\/\s*${_NUM})+|(?<![\p{L}-])${_NUM}-\p{L}+`, "gu");
|
||||
const _NUM_RUN = new RegExp(String.raw`${_NUM}(?:\s*\/\s*${_NUM})+|(^|[^\p{L}-])(${_NUM}-\p{L}+)`, "gu");
|
||||
function ltrNums(text) {
|
||||
if (text === null || text === undefined || _lastDir !== "rtl") return text;
|
||||
const s = String(text);
|
||||
const out = s.replace(_NUM_RUN, (run) => "\u2066" + run + "\u2069");
|
||||
const out = s.replace(_NUM_RUN, (run, lead, word) =>
|
||||
word === undefined ? "\u2066" + run + "\u2069" : lead + "\u2066" + word + "\u2069");
|
||||
return out === s && !/^\p{Nd}/u.test(s) ? s : "\u2068" + out + "\u2069";
|
||||
}
|
||||
|
||||
|
||||
@@ -471,14 +471,15 @@ export function createIncentiveCard(P) {
|
||||
admins *and* TaskMate parents get the Apply buttons. Defining one is
|
||||
structural config, so the manage controls (pencil, add/edit/delete) are
|
||||
admin-only — matching the `_parent` / `_admin` service gates. A child on
|
||||
a shared tablet sees the list with no buttons. */
|
||||
a shared tablet sees the list with no buttons. show_parent_actions: false
|
||||
(#1032) gives a parent who stays signed in on that tablet the same view. */
|
||||
|
||||
_canApply() {
|
||||
return window.__taskmate_is_parent(this.hass);
|
||||
return this.config?.show_parent_actions !== false && window.__taskmate_is_parent(this.hass);
|
||||
}
|
||||
|
||||
_canManage() {
|
||||
return !!this.hass?.user?.is_admin;
|
||||
return this.config?.show_parent_actions !== false && !!this.hass?.user?.is_admin;
|
||||
}
|
||||
|
||||
_toggleEditMode() {
|
||||
@@ -1065,6 +1066,7 @@ export function createIncentiveCard(P) {
|
||||
return [
|
||||
{ name: 'entity', selector: { entity: { domain: 'sensor' } } },
|
||||
{ name: 'title', selector: { text: {} } },
|
||||
{ name: 'show_parent_actions', selector: { boolean: {} } },
|
||||
{
|
||||
name: 'card_design',
|
||||
selector: {
|
||||
@@ -1083,6 +1085,7 @@ export function createIncentiveCard(P) {
|
||||
const labels = {
|
||||
entity: this._t('common.editor.overview_entity'),
|
||||
title: this._t('common.editor.card_title'),
|
||||
show_parent_actions: this._t('common.editor.show_parent_actions'),
|
||||
card_design: this._t('common.design.field_label'),
|
||||
};
|
||||
return labels[entry.name] ?? entry.name;
|
||||
@@ -1091,6 +1094,7 @@ export function createIncentiveCard(P) {
|
||||
_computeHelper = (entry) => {
|
||||
const helpers = {
|
||||
entity: this._t('common.editor.overview_entity_helper'),
|
||||
show_parent_actions: this._t('common.editor.show_parent_actions_helper'),
|
||||
};
|
||||
return helpers[entry.name] ?? '';
|
||||
};
|
||||
@@ -1100,6 +1104,7 @@ export function createIncentiveCard(P) {
|
||||
const data = {
|
||||
entity: this.config.entity || '',
|
||||
title: this.config.title || '',
|
||||
show_parent_actions: this.config.show_parent_actions !== false,
|
||||
card_design: this.config.card_design || 'global',
|
||||
};
|
||||
return html`
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
* no complete-on-behalf, no points adjustments, no reward claiming, no
|
||||
* settings — there is no option to turn them on.
|
||||
*
|
||||
* Each completion gets the child card's feedback (#1039): the chore's
|
||||
* completion sound, confetti and a short popup — each can be switched off.
|
||||
*
|
||||
* PINs are set by a parent in the admin panel and checked on the server
|
||||
* (taskmate/kiosk/verify_pin, rate-limited); the card never sees a PIN or its
|
||||
* hash, and only remembers which child is unlocked until it returns to the
|
||||
@@ -52,6 +55,10 @@ const MAX_TIMEOUT = 600;
|
||||
const WARN_SECONDS = 10;
|
||||
// How long the all-done celebration stays up before the picker comes back.
|
||||
const CELEBRATE_MS = 4000;
|
||||
// How long the per-chore popup and its confetti stay up (#1039), as on the child card.
|
||||
const POPUP_MS = 2500;
|
||||
const CONFETTI_MS = 3500;
|
||||
const CONFETTI_COLOURS = ["#ff6b9d", "#9b59b6", "#3498db", "#2ecc71", "#f1c40f", "#e67e22"];
|
||||
const PIN_LENGTH = 4;
|
||||
// The same six-colour ring the child card uses for per-child tones.
|
||||
const CHILD_FALLBACK = ["#ff7043", "#42a5f5", "#66bb6a", "#ab47bc", "#ffa726", "#26c6da"];
|
||||
@@ -76,6 +83,8 @@ class TaskMateKioskCard extends LitElement {
|
||||
_idle: { type: Number },
|
||||
_busy: { type: String },
|
||||
_celebrating: { type: Boolean },
|
||||
_popup: { type: Object },
|
||||
_confetti: { type: Array },
|
||||
_fullscreen: { type: Boolean },
|
||||
};
|
||||
}
|
||||
@@ -94,6 +103,8 @@ class TaskMateKioskCard extends LitElement {
|
||||
this._busy = "";
|
||||
this._celebrating = false;
|
||||
this._armCelebrate = false;
|
||||
this._popup = null;
|
||||
this._confetti = [];
|
||||
this._fullscreen = false;
|
||||
this._now = new Date();
|
||||
// Any touch or key press counts as activity and restarts the idle timer.
|
||||
@@ -137,6 +148,8 @@ class TaskMateKioskCard extends LitElement {
|
||||
this._ticker = null;
|
||||
clearTimeout(this._statusTimer);
|
||||
clearTimeout(this._celebrateTimer);
|
||||
clearTimeout(this._popupTimer);
|
||||
clearTimeout(this._confettiTimer);
|
||||
clearTimeout(this._undoExpiryTimer);
|
||||
if (typeof document !== "undefined") document.removeEventListener?.("fullscreenchange", this._onFullscreen);
|
||||
// Leaving the dashboard locks the kiosk again.
|
||||
@@ -404,6 +417,10 @@ class TaskMateKioskCard extends LitElement {
|
||||
|
||||
_resetToPicker() {
|
||||
clearTimeout(this._celebrateTimer);
|
||||
clearTimeout(this._popupTimer);
|
||||
clearTimeout(this._confettiTimer);
|
||||
this._popup = null;
|
||||
this._confetti = [];
|
||||
this._screen = "picker";
|
||||
this._kid = null;
|
||||
this._pin = "";
|
||||
@@ -476,6 +493,7 @@ class TaskMateKioskCard extends LitElement {
|
||||
} else {
|
||||
await this.hass.callService("taskmate", "complete_chore", { chore_id: chore.id, child_id: child.id });
|
||||
}
|
||||
this._cheer(chore);
|
||||
} catch (err) {
|
||||
this._notify(String(err?.message || err));
|
||||
} finally {
|
||||
@@ -492,6 +510,7 @@ class TaskMateKioskCard extends LitElement {
|
||||
await this.hass.callService("taskmate", "undo_chore", {
|
||||
completion_id: completion.completion_id || completion.id,
|
||||
});
|
||||
if (this.config.play_sounds !== false) this._playSound(this.config.undo_sound || "undo");
|
||||
} catch (err) {
|
||||
this._notify(String(err?.message || err));
|
||||
} finally {
|
||||
@@ -500,6 +519,40 @@ class TaskMateKioskCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Completion feedback (#1039) ──────────────────────────────────────────
|
||||
|
||||
/** The child card's reward for a tap: the chore's sound, confetti, a popup. */
|
||||
_cheer(chore) {
|
||||
if (this.config.play_sounds !== false) {
|
||||
this._playSound(chore.completion_sound || this.config.default_sound || "coin");
|
||||
}
|
||||
if (this.config.show_confetti !== false) this._spawnConfetti();
|
||||
if (this.config.show_completion_popup !== false) {
|
||||
this._popup = { points: this._pointsFor(chore) };
|
||||
clearTimeout(this._popupTimer);
|
||||
this._popupTimer = setTimeout(() => { this._popup = null; }, POPUP_MS);
|
||||
}
|
||||
}
|
||||
|
||||
/** Shared engine in taskmate-sounds.js; custom sounds (#856) resolve
|
||||
* through the custom_sounds table the attr resolver merges in. */
|
||||
_playSound(name) {
|
||||
const engine = window.__taskmate_sounds;
|
||||
if (!engine) return;
|
||||
engine.play(name, this._attrs().custom_sounds || []);
|
||||
}
|
||||
|
||||
_spawnConfetti() {
|
||||
this._confetti = Array.from({ length: 50 }, () => ({
|
||||
x: Math.random() * 100,
|
||||
delay: Math.random() * 0.5,
|
||||
size: Math.random() * 8 + 6,
|
||||
round: Math.random() > 0.5,
|
||||
}));
|
||||
clearTimeout(this._confettiTimer);
|
||||
this._confettiTimer = setTimeout(() => { this._confetti = []; }, CONFETTI_MS);
|
||||
}
|
||||
|
||||
/** Straight back to the picker once a child finishes their last chore here. */
|
||||
_maybeCelebrate() {
|
||||
if (!this._armCelebrate || this._celebrating || this._screen !== "child") return;
|
||||
@@ -509,6 +562,9 @@ class TaskMateKioskCard extends LitElement {
|
||||
if (!total || done < total) return;
|
||||
this._armCelebrate = false;
|
||||
this._celebrating = true;
|
||||
// The all-done overlay is the bigger party: it replaces the chore popup.
|
||||
clearTimeout(this._popupTimer);
|
||||
this._popup = null;
|
||||
clearTimeout(this._celebrateTimer);
|
||||
this._celebrateTimer = setTimeout(() => this._toPicker(), CELEBRATE_MS);
|
||||
}
|
||||
@@ -581,6 +637,10 @@ class TaskMateKioskCard extends LitElement {
|
||||
</button>` : ""}
|
||||
</div>
|
||||
<div class="km-main">${body}</div>
|
||||
${this._confetti.length ? html`
|
||||
<div class="km-confetti" aria-hidden="true">
|
||||
${this._confetti.map((p, i) => html`<i style="inset-inline-start:${p.x}%;animation-delay:${p.delay}s;background:${CONFETTI_COLOURS[i % CONFETTI_COLOURS.length]};border-radius:${p.round ? "50%" : "0"};width:${p.size}px;height:${p.size}px"></i>`)}
|
||||
</div>` : ""}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
@@ -684,7 +744,7 @@ class TaskMateKioskCard extends LitElement {
|
||||
const showPoints = this.config.show_points !== false;
|
||||
const showStreak = this.config.show_streak !== false;
|
||||
const showReward = this.config.show_reward_progress !== false;
|
||||
const warn = this.config.warn_before_return !== false && left <= WARN_SECONDS && left > 0 && !this._celebrating;
|
||||
const warn = this.config.warn_before_return !== false && left <= WARN_SECONDS && left > 0 && !this._celebrating && !this._popup;
|
||||
const earnedToday = (attrs.todays_completions || [])
|
||||
.filter(c => String(c.child_id) === String(child.id))
|
||||
.reduce((sum, c) => sum + (Number(c.points) || 0), 0);
|
||||
@@ -756,6 +816,15 @@ class TaskMateKioskCard extends LitElement {
|
||||
<ha-icon icon="mdi:hand-wave"></ha-icon> ${this._t("kiosk.still_here")}
|
||||
</button>
|
||||
</div>` : ""}
|
||||
${this._popup && !this._celebrating ? html`
|
||||
<div class="km-idle km-pop" role="status" @click=${() => { this._popup = null; }}>
|
||||
<div class="km-pop-card">
|
||||
<div class="km-pop-stars">🌟🌟🌟</div>
|
||||
<h2>${this._t("child.celebration_title")}</h2>
|
||||
<p>${this._t("child.celebration_message")}</p>
|
||||
${showPoints ? html`<div class="km-pop-pts"><ha-icon icon="${icon}"></ha-icon> +${this._popup.points}</div>` : ""}
|
||||
</div>
|
||||
</div>` : ""}
|
||||
${this._celebrating ? html`
|
||||
<div class="km-idle km-party">
|
||||
<div class="km-cheer">🎉</div>
|
||||
@@ -1148,6 +1217,36 @@ class TaskMateKioskCard extends LitElement {
|
||||
}
|
||||
.km-cheer { font-size: 96px; line-height: 1; }
|
||||
|
||||
/* Per-chore popup + confetti (#1039), after the child card's */
|
||||
.km-pop { cursor: pointer; animation: km-fade 0.3s ease; }
|
||||
.km-pop-card {
|
||||
background: var(--tmd-surface, var(--card-background-color, #fff));
|
||||
border-radius: calc(var(--tmd-radius, 16px) + 14px); padding: 36px 48px;
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
|
||||
animation: km-pop-in 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
.km-pop-stars { font-size: 3.5rem; line-height: 1; margin-bottom: 12px; animation: km-star 0.6s ease infinite; }
|
||||
.km-pop h2 { color: var(--tmd-accent, #9b59b6); font-size: 2.4rem; }
|
||||
.km-pop p { margin: 8px 0 14px; }
|
||||
.km-pop-pts {
|
||||
display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
font-size: 1.8rem; font-weight: 800; color: var(--tmd-gold, #e67e22);
|
||||
}
|
||||
.km-pop-pts ha-icon { --mdc-icon-size: 28px; }
|
||||
.km-confetti { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: 4; }
|
||||
.km-confetti i { position: absolute; top: 0; animation: km-confetti 3s linear forwards; }
|
||||
@keyframes km-fade { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes km-pop-in { from { transform: scale(0); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
||||
@keyframes km-star { 0%, 100% { transform: scale(1) rotate(0deg); } 50% { transform: scale(1.2) rotate(10deg); } }
|
||||
@keyframes km-confetti {
|
||||
0% { transform: translateY(-100px) rotate(0deg); opacity: 1; }
|
||||
100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.km-confetti { display: none; }
|
||||
.km-pop, .km-pop-card, .km-pop-stars { animation: none; }
|
||||
}
|
||||
|
||||
.km-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px; text-align: center; color: var(--tmd-dim, var(--secondary-text-color)); }
|
||||
.km-empty ha-icon { --mdc-icon-size: 56px; opacity: 0.5; }
|
||||
|
||||
@@ -1217,6 +1316,9 @@ class TaskMateKioskCardEditor extends LitElement {
|
||||
{ name: "show_streak", selector: { boolean: {} } },
|
||||
{ name: "show_reward_progress", selector: { boolean: {} } },
|
||||
{ name: "show_picker_progress", selector: { boolean: {} } },
|
||||
{ name: "play_sounds", selector: { boolean: {} } },
|
||||
{ name: "show_confetti", selector: { boolean: {} } },
|
||||
{ name: "show_completion_popup", selector: { boolean: {} } },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1230,6 +1332,9 @@ class TaskMateKioskCardEditor extends LitElement {
|
||||
show_streak: this._t("kiosk.editor.show_streak"),
|
||||
show_reward_progress: this._t("kiosk.editor.show_reward_progress"),
|
||||
show_picker_progress: this._t("kiosk.editor.show_picker_progress"),
|
||||
play_sounds: this._t("kiosk.editor.play_sounds"),
|
||||
show_confetti: this._t("kiosk.editor.show_confetti"),
|
||||
show_completion_popup: this._t("kiosk.editor.show_completion_popup"),
|
||||
})[entry.name] ?? entry.name;
|
||||
|
||||
_computeHelper = (entry) => ({
|
||||
@@ -1237,6 +1342,8 @@ class TaskMateKioskCardEditor extends LitElement {
|
||||
timeout: this._t("kiosk.editor.timeout_helper"),
|
||||
warn_before_return: this._t("kiosk.editor.warn_before_return_helper"),
|
||||
show_picker_progress: this._t("kiosk.editor.show_picker_progress_helper"),
|
||||
play_sounds: this._t("kiosk.editor.play_sounds_helper"),
|
||||
show_completion_popup: this._t("kiosk.editor.show_completion_popup_helper"),
|
||||
})[entry.name] ?? "";
|
||||
|
||||
/** Every child, configured ones first in their order, each with shown/require_pin. */
|
||||
@@ -1295,6 +1402,9 @@ class TaskMateKioskCardEditor extends LitElement {
|
||||
show_streak: this.config.show_streak !== false,
|
||||
show_reward_progress: this.config.show_reward_progress !== false,
|
||||
show_picker_progress: this.config.show_picker_progress !== false,
|
||||
play_sounds: this.config.play_sounds !== false,
|
||||
show_confetti: this.config.show_confetti !== false,
|
||||
show_completion_popup: this.config.show_completion_popup !== false,
|
||||
};
|
||||
const rows = this._childRows();
|
||||
const pins = this._pins || {};
|
||||
|
||||
@@ -249,8 +249,8 @@ class TaskMateLeaderboardCard extends LitElement {
|
||||
.lb-pod.win .lb-pod-name { font-size: 14px; }
|
||||
.lb-pod-score { font-size: 22px; color: var(--tmd-accent); margin-top: 2px; }
|
||||
.lb-pod.win .lb-pod-score { font-size: 28px; }
|
||||
.lb-pod-score span { font-size: 11px; }
|
||||
.lb-pod.win .lb-pod-score span { font-size: 13px; }
|
||||
.lb-pod-score ha-icon { --mdc-icon-size: 16px; vertical-align: -2px; margin-inline-start: 2px; }
|
||||
.lb-pod.win .lb-pod-score ha-icon { --mdc-icon-size: 20px; }
|
||||
.lb-list { display: flex; flex-direction: column; gap: 9px; }
|
||||
.lb-list .lb-row { background: var(--tmd-surface-2); border-radius: 18px; padding: 11px 13px; }
|
||||
|
||||
@@ -278,6 +278,7 @@ class TaskMateLeaderboardCard extends LitElement {
|
||||
.lb-cp-mid { flex: 1; min-width: 0; }
|
||||
.lb-cp-name { font-weight: 600; }
|
||||
.lb-cp-meta { font-size: 11.5px; gap: 10px; margin-top: 2px; }
|
||||
.lb-cn-meta ha-icon, .lb-cp-meta ha-icon { --mdc-icon-size: 1.2em; vertical-align: -0.2em; margin-inline-end: 2px; }
|
||||
.lb-cp-right { text-align: end; }
|
||||
.lb-cp-score { font-size: 19px; }
|
||||
.lb-cp-unit { font-size: 10.5px; }
|
||||
@@ -594,6 +595,7 @@ class TaskMateLeaderboardCard extends LitElement {
|
||||
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity)) || entity.attributes || {};
|
||||
const children = [...(attrs.children || [])].filter(c => !c.is_guest);
|
||||
const pointsName = attrs.points_name || this._t('common.points');
|
||||
const pointsIcon = attrs.points_icon || "mdi:star";
|
||||
|
||||
const tz = this.hass?.config?.time_zone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const weeklyPoints = this._buildWeeklyPoints(attrs, tz);
|
||||
@@ -635,7 +637,7 @@ class TaskMateLeaderboardCard extends LitElement {
|
||||
career: child.career_score || 0,
|
||||
}));
|
||||
|
||||
const ctx = { sortBy, pointsName };
|
||||
const ctx = { sortBy, pointsName, pointsIcon };
|
||||
const body =
|
||||
design === "playroom" ? this._lbPlayroom(rows, scoreUnit, ctx) :
|
||||
design === "console" ? this._lbConsole(rows, scoreUnit, ctx) :
|
||||
@@ -663,12 +665,12 @@ class TaskMateLeaderboardCard extends LitElement {
|
||||
// icons there. active() returns the real design id (apply() hands the
|
||||
// render path the layout alias, which for graphite is "cleanpro").
|
||||
const mono = window.__taskmate_design?.active?.(this) === "graphite";
|
||||
const stat = (icon, emoji, value) => (mono
|
||||
const stat = (icon, emoji, value) => (mono || !emoji
|
||||
? html`<span><ha-icon icon="${icon}"></ha-icon>${value}</span>`
|
||||
: html`<span>${emoji} ${value}</span>`);
|
||||
if (this.config.show_streak !== false && sortBy !== "streak") chips.push(stat("mdi:fire", "🔥", r.streak));
|
||||
if (this.config.show_weekly !== false && sortBy !== "weekly") chips.push(stat("mdi:calendar-blank-outline", "📅", r.weekly));
|
||||
if (sortBy !== "points") chips.push(stat("mdi:star-outline", "⭐", r.points));
|
||||
if (sortBy !== "points") chips.push(stat(ctx.pointsIcon, null, r.points));
|
||||
if (this.config.show_career !== false && sortBy !== "career") chips.push(stat("mdi:trophy-outline", "🏆", r.career));
|
||||
if (!chips.length) return "";
|
||||
return html`<div class="row muted ${cls}">${chips}</div>`;
|
||||
@@ -685,7 +687,7 @@ class TaskMateLeaderboardCard extends LitElement {
|
||||
<div class="medal">${MEDAL[r.idx]}</div>
|
||||
${this._av(r.child, r.tone, win ? 58 : 46)}
|
||||
<div class="lb-pod-name">${r.child.name}</div>
|
||||
<div class="big lb-pod-score">${r.score.toLocaleString()}<span>⭐</span></div>
|
||||
<div class="big lb-pod-score">${r.score.toLocaleString()}<ha-icon icon="${ctx.sortBy === "streak" ? "mdi:fire" : ctx.sortBy === "career" ? "mdi:trophy-outline" : ctx.pointsIcon}"></ha-icon></div>
|
||||
${win && this.config.show_streak !== false && r.streak
|
||||
? html`<div class="chip soft" style="margin-top:7px">🔥 ${this._t('common.d_streak', { count: r.streak })}</div>` : ""}
|
||||
</div>`;
|
||||
|
||||
@@ -502,6 +502,17 @@ class TaskMateOverviewCard extends LitElement {
|
||||
return html`<div class="av" style="--av:${size}px;--ac:${tone}">${inner}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent-only controls are shown to admins and TaskMate parents unless the
|
||||
* card sets show_parent_actions: false (#1032) — for a shared kids' dashboard
|
||||
* where a parent account stays signed in. The card then behaves exactly as it
|
||||
* does for a child (no complete-on-behalf). Display only; the services keep
|
||||
* their own permission checks.
|
||||
*/
|
||||
_isParent() {
|
||||
return this.config?.show_parent_actions !== false && window.__taskmate_is_parent(this.hass);
|
||||
}
|
||||
|
||||
/** Per-child today progress, mirroring the classic tile filtering exactly. */
|
||||
_childProgress(child, chores, completions, attrs) {
|
||||
const childChores = this._childChoresToday(child, chores, attrs);
|
||||
@@ -535,7 +546,7 @@ class TaskMateOverviewCard extends LitElement {
|
||||
pendingApprovals = (attrs.chore_completions || completions.filter(c => !c.approved)).length;
|
||||
}
|
||||
|
||||
const isParent = window.__taskmate_is_parent(this.hass);
|
||||
const isParent = this._isParent();
|
||||
|
||||
// Aggregate today's progress across all children
|
||||
let doneTotal = 0;
|
||||
@@ -759,7 +770,7 @@ class TaskMateOverviewCard extends LitElement {
|
||||
// Pending approvals for this child
|
||||
const childPending = childCompletions.filter(c => !c.approved).length;
|
||||
|
||||
const isParent = window.__taskmate_is_parent(this.hass);
|
||||
const isParent = this._isParent();
|
||||
const outstanding = childChores.filter(c => {
|
||||
const doneToday = completions.filter(
|
||||
x => x.child_id === child.id && x.chore_id === c.id && !x.bonus_subtask_id
|
||||
@@ -903,6 +914,7 @@ class TaskMateOverviewCardEditor extends LitElement {
|
||||
{ name: 'entity', selector: { entity: { domain: 'sensor' } } },
|
||||
{ name: 'title', selector: { text: {} } },
|
||||
{ name: 'approvals_entity', selector: { entity: { domain: 'sensor' } } },
|
||||
{ name: 'show_parent_actions', selector: { boolean: {} } },
|
||||
{
|
||||
name: 'card_design',
|
||||
selector: {
|
||||
@@ -922,6 +934,7 @@ class TaskMateOverviewCardEditor extends LitElement {
|
||||
entity: this._t('overview.editor.entity_label'),
|
||||
title: this._t('overview.editor.title_label'),
|
||||
approvals_entity: this._t('overview.editor.approvals_entity_label'),
|
||||
show_parent_actions: this._t('common.editor.show_parent_actions'),
|
||||
card_design: this._t('common.design.field_label'),
|
||||
};
|
||||
return labels[entry.name] ?? entry.name;
|
||||
@@ -931,6 +944,7 @@ class TaskMateOverviewCardEditor extends LitElement {
|
||||
const helpers = {
|
||||
entity: this._t('overview.editor.entity_helper'),
|
||||
approvals_entity: this._t('overview.editor.approvals_entity_helper'),
|
||||
show_parent_actions: this._t('common.editor.show_parent_actions_helper'),
|
||||
};
|
||||
return helpers[entry.name] ?? '';
|
||||
};
|
||||
@@ -941,6 +955,7 @@ class TaskMateOverviewCardEditor extends LitElement {
|
||||
entity: this.config.entity || '',
|
||||
title: this.config.title || '',
|
||||
approvals_entity: this.config.approvals_entity || '',
|
||||
show_parent_actions: this.config.show_parent_actions !== false,
|
||||
card_design: this.config.card_design || 'global',
|
||||
};
|
||||
return html`
|
||||
|
||||
@@ -52,6 +52,7 @@ const SCHEDULE_MODES = [
|
||||
|
||||
const RECURRENCES = [
|
||||
{ v: "every_2_days", lk: "panel.recurrence_every_2_days" },
|
||||
{ v: "every_n_days", lk: "panel.recurrence_every_n_days" },
|
||||
{ v: "weekly", lk: "panel.recurrence_weekly" },
|
||||
{ v: "every_2_weeks", lk: "panel.recurrence_every_2_weeks" },
|
||||
{ v: "monthly", lk: "panel.recurrence_monthly" },
|
||||
@@ -59,6 +60,18 @@ const RECURRENCES = [
|
||||
{ v: "every_6_months", lk: "panel.recurrence_every_6_months" },
|
||||
];
|
||||
|
||||
// Custom "every N days" recurrences (#1038) are stored as every_<N>_days; the
|
||||
// editor shows them as the "every_n_days" option plus a number field. Bounds
|
||||
// match recurrence_interval_days() in const.py.
|
||||
const RECURRENCE_INTERVAL_MIN = 2;
|
||||
const RECURRENCE_INTERVAL_MAX = 365;
|
||||
function recurrenceIntervalDays(recurrence) {
|
||||
const m = /^every_(\d{1,3})_days$/.exec(recurrence || "");
|
||||
if (!m) return null;
|
||||
const n = Number(m[1]);
|
||||
return n >= RECURRENCE_INTERVAL_MIN && n <= RECURRENCE_INTERVAL_MAX ? n : null;
|
||||
}
|
||||
|
||||
const FIRST_OCCURRENCE = [
|
||||
{ v: "available_immediately", lk: "panel.first_occ_available_immediately" },
|
||||
{ v: "wait_for_first_occurrence", lk: "panel.first_occ_wait" },
|
||||
@@ -363,7 +376,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
// tests/test_panel_undo_deny_list.py pins all three copies together.
|
||||
static get _UNDO_DENY_PREFIXES() {
|
||||
return [
|
||||
"Weekend bonus", "Streak milestone bonus", "Perfect week bonus",
|
||||
"Weekend bonus", "Streak milestone bonus", "Perfect week bonus", "Weekly target bonus",
|
||||
"Allocated to pool:", "Pool refund", "Points decay",
|
||||
"Savings interest", "Badge", "Streak freeze",
|
||||
"Wish savings", "Wish refund", "Wish pledge", "Wish redeemed",
|
||||
@@ -2182,10 +2195,11 @@ class TaskMatePanel extends HTMLElement {
|
||||
assigned_to: [], requires_approval: true,
|
||||
time_category: "anytime", completion_sound: "coin", daily_limit: 1,
|
||||
weekly_target: 0,
|
||||
weekly_target_bonus: 0,
|
||||
difficulty: "medium",
|
||||
claim_allowance_minutes: 0,
|
||||
schedule_mode: "specific_days",
|
||||
due_days: [], recurrence: "weekly", recurrence_day: "", recurrence_start: "",
|
||||
due_days: [], recurrence: "weekly", recurrence_interval: 3, recurrence_day: "", recurrence_start: "",
|
||||
first_occurrence_mode: "available_immediately",
|
||||
assignment_mode: "everyone", assignment_rotation_anchor: "",
|
||||
manual_start_child_id: "",
|
||||
@@ -2229,6 +2243,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
weather_temp_max: c.weather_temp_max ?? "",
|
||||
weather_wind_max: c.weather_wind_max ?? "",
|
||||
manual_start_child_id: "",
|
||||
...this._recurrenceDraft(c.recurrence),
|
||||
} });
|
||||
} else {
|
||||
this._openDialog({ kind: "chore", mode: "add", data: blank });
|
||||
@@ -2423,6 +2438,16 @@ class TaskMatePanel extends HTMLElement {
|
||||
this._showToast("err", this._t("panel.chore_team_conflict"));
|
||||
return;
|
||||
}
|
||||
let recurrence = d.recurrence || "weekly";
|
||||
if (recurrence === "every_n_days") {
|
||||
const n = Number(d.recurrence_interval);
|
||||
if (!Number.isInteger(n) || n < RECURRENCE_INTERVAL_MIN || n > RECURRENCE_INTERVAL_MAX) {
|
||||
this._showToast("err", this._t("panel.chore_recurrence_interval_invalid",
|
||||
{ min: RECURRENCE_INTERVAL_MIN, max: RECURRENCE_INTERVAL_MAX }));
|
||||
return;
|
||||
}
|
||||
recurrence = `every_${n}_days`;
|
||||
}
|
||||
const base = {
|
||||
name: d.name.trim(),
|
||||
description: d.description || "",
|
||||
@@ -2441,9 +2466,10 @@ class TaskMatePanel extends HTMLElement {
|
||||
difficulty: d.difficulty || "medium",
|
||||
daily_limit: Number(d.daily_limit) || 1,
|
||||
weekly_target: Math.max(0, Number(d.weekly_target) || 0),
|
||||
weekly_target_bonus: Math.max(0, Number(d.weekly_target_bonus) || 0),
|
||||
schedule_mode: d.schedule_mode || "specific_days",
|
||||
due_days: d.due_days || [],
|
||||
recurrence: d.recurrence || "weekly",
|
||||
recurrence,
|
||||
recurrence_day: d.recurrence_day || "",
|
||||
recurrence_start: d.recurrence_start || "",
|
||||
first_occurrence_mode: d.first_occurrence_mode || "available_immediately",
|
||||
@@ -6474,7 +6500,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
? `<strong>${this._esc(childById[curChild].name)}</strong>`
|
||||
: isRotation ? `<span class="tm-text-muted">—</span>` : "";
|
||||
const schedLabel = c.schedule_mode === "recurring"
|
||||
? this._labelOf(RECURRENCES, c.recurrence) + (c.recurrence_day && c.recurrence_day !== "any_day" ? ` · ${this._labelOf(DAYS, c.recurrence_day)}` : "")
|
||||
? this._recurrenceLabel(c.recurrence) + (c.recurrence_day && c.recurrence_day !== "any_day" ? ` · ${this._labelOf(DAYS, c.recurrence_day)}` : "")
|
||||
: c.schedule_mode === "one_shot"
|
||||
? this._t("panel.common_one_shot")
|
||||
: ((c.due_days || []).length === 0 ? this._t("panel.common_daily") : (c.due_days || []).map(d => this._labelOf(DAYS, d)).join(" · "));
|
||||
@@ -7076,7 +7102,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<div class="tm-meta">${this._esc(name(b.claimed_by))}${comp ? ` · ${this._timeAgo(comp.completed_at)}` : ""}</div>`;
|
||||
} else if (b.status === "completed") {
|
||||
status = `<span class="tm-pill tm-pill-success">${this._t("panel.bounty_status_completed")}</span>
|
||||
<div class="tm-meta">${this._t("panel.bounty_completed_meta", { name: this._esc(name(b.claimed_by)), points: this._num(b.points_awarded || b.points) })}</div>`;
|
||||
<div class="tm-meta">${this._t("panel.bounty_completed_meta", { name: this._esc(name(b.claimed_by)), points: this._num(b.points_awarded || b.points), points_name: this._esc(this._pointsName()) })}</div>`;
|
||||
} else if (b.status === "expired") {
|
||||
status = `<span class="tm-pill tm-pill-muted">${this._t("panel.bounty_status_expired")}</span>
|
||||
<div class="tm-meta">${this._t(b.lapse_count > 0 ? "panel.bounty_expired_lapsed_meta" : b.claim_count > 0 ? "panel.bounty_expired_unfinished_meta" : "panel.bounty_expired_meta", { date: b.closed_at ? this._esc(new Date(b.closed_at).toLocaleDateString([], { day: "numeric", month: "short" })) : "" })}</div>`;
|
||||
@@ -7383,7 +7409,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
const child = childById[b.child_id];
|
||||
return `<span class="tm-auc-chip" title="${this._esc(child ? child.name : "?")}">
|
||||
${child ? this._childAvatar(child) : ""}
|
||||
${show ? `<b>${this._num(b.points)} ★</b>` : `<span class="tm-text-muted">${this._t("panel.auction_sealed")}</span>`}
|
||||
${show ? `<b>${this._num(b.points)} ${this._pointsIconHtml()}</b>` : `<span class="tm-text-muted">${this._t("panel.auction_sealed")}</span>`}
|
||||
${i === 0 && a.status === "open" && show ? `<span class="tm-meta">· ${this._t("panel.auction_leading")}</span>` : ""}
|
||||
</span>`;
|
||||
}).join("");
|
||||
@@ -7394,9 +7420,9 @@ class TaskMatePanel extends HTMLElement {
|
||||
const chore = `
|
||||
<div class="tm-name-main tm-bounty-cell">
|
||||
<span class="tm-avatar tm-auc-ic">${this._mdi(a.icon || "mdi:gavel")}</span>
|
||||
<div><strong>${this._esc(a.chore_name)}</strong><div class="tm-meta">${this._esc(this._t("panel.auction_normally", { date: this._auctionDate(a.occurrence), points: this._num(a.normal_points) }))}</div></div>
|
||||
<div><strong>${this._esc(a.chore_name)}</strong><div class="tm-meta">${this._esc(this._t("panel.auction_normally", { date: this._auctionDate(a.occurrence), points: this._num(a.normal_points), points_name: this._pointsName() }))}</div></div>
|
||||
</div>`;
|
||||
const max = `<strong class="tm-numeric tm-auc-gold">${this._num(a.max_points)} ★</strong>${a.min_points > 1 ? `<div class="tm-meta">${this._t("panel.auction_min_meta", { points: this._num(a.min_points) })}</div>` : ""}`;
|
||||
const max = `<strong class="tm-numeric tm-auc-gold">${this._num(a.max_points)} ${this._pointsIconHtml()}</strong>${a.min_points > 1 ? `<div class="tm-meta">${this._t("panel.auction_min_meta", { points: this._num(a.min_points), points_name: this._esc(this._pointsName()) })}</div>` : ""}`;
|
||||
if (a.status === "open") {
|
||||
const left = Date.parse(a.closes_at || "") - Date.now();
|
||||
const who = (a.eligible_child_ids || []).map(cid => childById[cid]).filter(Boolean)
|
||||
@@ -7424,7 +7450,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
result = `<span class="tm-pill tm-pill-muted">${this._t("panel.auction_status_cancelled")}</span>`;
|
||||
status = `<span class="tm-pill tm-pill-muted">${this._t("panel.auction_status_normal")}</span>`;
|
||||
} else if (a.winner_id) {
|
||||
result = `<div class="tm-auc-win">${winner ? this._childAvatar(winner) : ""}<div><strong>${this._esc(winner ? winner.name : "?")}</strong> ${this._t("panel.auction_won_at", { points: `<strong class="tm-auc-gold">${this._num(a.price)} ★</strong>` })}
|
||||
result = `<div class="tm-auc-win">${winner ? this._childAvatar(winner) : ""}<div><strong>${this._esc(winner ? winner.name : "?")}</strong> ${this._t("panel.auction_won_at", { points: `<strong class="tm-auc-gold">${this._num(a.price)} ${this._pointsIconHtml()}</strong>` })}
|
||||
<div class="tm-meta">${this._esc(this._t("panel.auction_assigned_for", { date: this._auctionDate(a.occurrence) }))}</div></div></div>`;
|
||||
const st = a.chore_status || "todo";
|
||||
status = st === "done"
|
||||
@@ -7572,10 +7598,10 @@ class TaskMatePanel extends HTMLElement {
|
||||
<div class="tm-auc-steprow">
|
||||
<div class="tm-auc-stepper">
|
||||
<button type="button" data-act="auction-max-step" data-id="-1" aria-label="−">−</button>
|
||||
<span>${this._num(d.max_points)} ★</span>
|
||||
<span>${this._num(d.max_points)} ${this._pointsIconHtml()}</span>
|
||||
<button type="button" data-act="auction-max-step" data-id="1" aria-label="+">+</button>
|
||||
</div>
|
||||
<span class="tm-meta">${this._t("panel.auction_normally_worth", { points: normal })}</span>
|
||||
<span class="tm-meta">${this._t("panel.auction_normally_worth", { points: normal, points_name: this._esc(this._pointsName()) })}</span>
|
||||
</div>
|
||||
<div class="tm-chip-row" style="margin-top:8px">
|
||||
${multiples.map(([m, n]) => chip("auction-max-set", n, Number(d.max_points) === n, `${m}× · ${n}`)).join("")}
|
||||
@@ -8148,7 +8174,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<div class="tm-tpl-icon"><ha-icon icon="${this._esc(tpl.icon || "mdi:clipboard-list")}"></ha-icon></div>
|
||||
<div class="tm-manage-tpl-info">
|
||||
<div class="tm-manage-tpl-name">${this._esc(tpl.name)}${locked ? ' <span class="tm-text-vfaint" style="font-size:12px">🔒</span>' : ""}</div>
|
||||
<div class="tm-meta">${this._t("panel.template_pts_total", {count, points: pts})}</div>
|
||||
<div class="tm-meta">${this._t("panel.template_pts_total", {count, points: pts, points_name: this._esc(this._pointsName())})}</div>
|
||||
</div>
|
||||
<div class="tm-row-actions">
|
||||
${locked ? "" : `<button type="button" class="tm-btn tm-btn-sm" data-act="tpl-edit" data-id="${this._esc(tpl.id)}">${this._t("panel.btn_edit")}</button>`}
|
||||
@@ -8190,7 +8216,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<div class="tm-tpl-icon"><ha-icon icon="${this._esc(tpl.icon || "mdi:clipboard-list")}"></ha-icon></div>
|
||||
<div style="flex:1">
|
||||
<div class="tm-tpl-picker-name">${this._esc(tpl.name)}</div>
|
||||
<div class="tm-meta">${this._t("panel.template_pts_total", {count, points: pts})}</div>
|
||||
<div class="tm-meta">${this._t("panel.template_pts_total", {count, points: pts, points_name: this._esc(this._pointsName())})}</div>
|
||||
</div>
|
||||
<span class="tm-pill ${tpl.builtin ? "tm-pill-accent" : "tm-pill-success"}">${tpl.builtin ? this._t("panel.template_builtin") : this._t("panel.template_custom")}</span>
|
||||
</div>
|
||||
@@ -8217,7 +8243,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
${chores.map((c, i) => this._renderTemplateChoreCard(c, i)).join("")}
|
||||
${chores.length > 0 ? `
|
||||
<div class="tm-tpl-confirm-bar">
|
||||
<div>${this._t("panel.template_confirm_bar", {count: chores.length, points: totalPts})}</div>
|
||||
<div>${this._t("panel.template_confirm_bar", {count: chores.length, points: totalPts, points_name: this._esc(this._pointsName())})}</div>
|
||||
<button type="button" class="tm-btn tm-btn-raised" data-act="tpl-apply">${this._t("panel.template_create_btn", {count: chores.length})}</button>
|
||||
</div>
|
||||
` : `<div class="tm-card tm-empty"><p>${this._t("panel.empty_template_all_removed")}</p></div>`}
|
||||
@@ -8233,7 +8259,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<span class="tm-tpl-expand ${expanded ? "open" : ""}">▶</span>
|
||||
<span class="tm-tpl-preview-name">${this._esc(chore.name)}</span>
|
||||
<span class="tm-tpl-preview-summary">
|
||||
<span>${this._t("panel.pts_display", {count: chore.points || 0})}</span>
|
||||
<span>${this._t("panel.pts_display", {count: chore.points || 0, points_name: this._esc(this._pointsName())})}</span>
|
||||
<span>${schedLabel}</span>
|
||||
<span>${this._esc(this._timeCategoryLabel(chore.time_category))}</span>
|
||||
</span>
|
||||
@@ -8298,7 +8324,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<label class="tm-tpl-check-row">
|
||||
<input type="checkbox" data-tpl-chore-check="${this._esc(c.id)}">
|
||||
<span>${this._esc(c.name)}</span>
|
||||
<span class="tm-text-muted" style="margin-inline-start:auto">${this._t("panel.pts_display", {count: c.points})}</span>
|
||||
<span class="tm-text-muted" style="margin-inline-start:auto">${this._t("panel.pts_display", {count: c.points, points_name: this._esc(this._pointsName())})}</span>
|
||||
</label>
|
||||
`).join("")}
|
||||
</div>
|
||||
@@ -8639,8 +8665,8 @@ class TaskMatePanel extends HTMLElement {
|
||||
<div class="tm-setting-label">${this._t("panel.settings_surprise_params_label")}<small>${this._t("panel.settings_surprise_params_hint")}</small></div>
|
||||
<div class="tm-difficulty-mults">
|
||||
<label>${this._t("panel.settings_surprise_chance")}<input type="number" class="tm-input" step="1" min="0" max="100" data-setting="surprise_bonus_chance" value="${this._num(s.surprise_bonus_chance, 15)}"></label>
|
||||
<label>${this._t("panel.settings_surprise_min")}<input type="number" class="tm-input" step="1" min="0" data-setting="surprise_bonus_min" value="${this._num(s.surprise_bonus_min, 5)}"></label>
|
||||
<label>${this._t("panel.settings_surprise_max")}<input type="number" class="tm-input" step="1" min="0" data-setting="surprise_bonus_max" value="${this._num(s.surprise_bonus_max, 20)}"></label>
|
||||
<label>${this._t("panel.settings_surprise_min", { points_name: this._esc(this._pointsName()) })}<input type="number" class="tm-input" step="1" min="0" data-setting="surprise_bonus_min" value="${this._num(s.surprise_bonus_min, 5)}"></label>
|
||||
<label>${this._t("panel.settings_surprise_max", { points_name: this._esc(this._pointsName()) })}<input type="number" class="tm-input" step="1" min="0" data-setting="surprise_bonus_max" value="${this._num(s.surprise_bonus_max, 20)}"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tm-setting-row tm-setting-stack">
|
||||
@@ -8701,7 +8727,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<div class="tm-setting-row">
|
||||
<div class="tm-setting-label">${this._t("panel.settings_spendcap_params_label")}<small>${this._t("panel.settings_spendcap_params_hint")}</small></div>
|
||||
<div class="tm-difficulty-mults">
|
||||
<label>${this._t("panel.settings_spendcap_amount")}<input type="number" class="tm-input" step="1" min="0" data-setting="spend_cap_amount" value="${this._num(s.spend_cap_amount, 0)}"></label>
|
||||
<label>${this._t("panel.settings_spendcap_amount", { points_name: this._esc(this._pointsName()) })}<input type="number" class="tm-input" step="1" min="0" data-setting="spend_cap_amount" value="${this._num(s.spend_cap_amount, 0)}"></label>
|
||||
<label>${this._t("panel.settings_decay_period")}
|
||||
<select class="tm-select" data-setting="spend_cap_period">
|
||||
<option value="weekly" ${s.spend_cap_period === "weekly" ? "selected" : ""}>${this._t("panel.reward_restock_weekly")}</option>
|
||||
@@ -9339,7 +9365,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<div class="tm-tpl-preview-card" style="margin-bottom:8px">
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:10px 14px">
|
||||
<span style="font-weight:600;flex:1">${this._esc(c.name || this._t("panel.template_unnamed"))}</span>
|
||||
<span class="tm-meta">${this._t("panel.pts_display", {count: c.points || 0})}</span>
|
||||
<span class="tm-meta">${this._t("panel.pts_display", {count: c.points || 0, points_name: this._esc(this._pointsName())})}</span>
|
||||
<button type="button" class="tm-tpl-remove" data-act="tpl-dialog-remove-chore" data-idx="${i}">✕</button>
|
||||
</div>
|
||||
<div style="padding:0 14px 12px;border-top:1px solid var(--tm-border-soft)">
|
||||
@@ -9514,13 +9540,20 @@ class TaskMatePanel extends HTMLElement {
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
${this._field(this._t("panel.chore_weekly_target_label"), "weekly_target", d.weekly_target || 0, "number",
|
||||
this._t("panel.chore_weekly_target_hint"))}
|
||||
<div class="tm-field-row">
|
||||
${this._field(this._t("panel.chore_weekly_target_label"), "weekly_target", d.weekly_target || 0, "number",
|
||||
this._t("panel.chore_weekly_target_hint"))}
|
||||
${this._field(this._t("panel.chore_weekly_target_bonus_label"), "weekly_target_bonus", d.weekly_target_bonus || 0, "number",
|
||||
this._t("panel.chore_weekly_target_bonus_hint"))}
|
||||
</div>
|
||||
` : "",
|
||||
showRecurring ? `
|
||||
<div class="tm-field-row">
|
||||
${this._select(this._t("panel.chore_recurrence_label"), "recurrence", d.recurrence, RECURRENCES)}
|
||||
${this._field(this._t("panel.chore_recurrence_day_label"), "recurrence_day", d.recurrence_day, "text", this._t("panel.chore_recurrence_day_hint"))}
|
||||
${this._select(this._t("panel.chore_recurrence_label"), "recurrence", d.recurrence, RECURRENCES, "", true)}
|
||||
${d.recurrence === "every_n_days"
|
||||
? this._field(this._t("panel.chore_recurrence_interval_label"), "recurrence_interval", d.recurrence_interval, "number",
|
||||
this._t("panel.chore_recurrence_interval_hint", { min: RECURRENCE_INTERVAL_MIN, max: RECURRENCE_INTERVAL_MAX }))
|
||||
: this._field(this._t("panel.chore_recurrence_day_label"), "recurrence_day", d.recurrence_day, "text", this._t("panel.chore_recurrence_day_hint"))}
|
||||
</div>
|
||||
<div class="tm-field-row">
|
||||
${this._dateField(this._t("panel.chore_recurrence_start_label"), "recurrence_start", d.recurrence_start, this._t("panel.chore_recurrence_start_hint"))}
|
||||
@@ -10630,6 +10663,18 @@ class TaskMatePanel extends HTMLElement {
|
||||
return `<span class="tm-id-badge"><code>${this._esc(id)}</code><button type="button" class="tm-id-copy" data-act="copy-id" data-id="${this._esc(id)}" title="${this._t("panel.toast_copied")}"><ha-icon icon="mdi:content-copy"></ha-icon></button></span>`;
|
||||
}
|
||||
|
||||
/** Editor fields for a stored recurrence: every_<N>_days opens as the custom option. */
|
||||
_recurrenceDraft(recurrence) {
|
||||
const n = recurrenceIntervalDays(recurrence);
|
||||
return n && n !== 2 ? { recurrence: "every_n_days", recurrence_interval: n } : {};
|
||||
}
|
||||
|
||||
_recurrenceLabel(recurrence) {
|
||||
const n = recurrenceIntervalDays(recurrence);
|
||||
if (n && n !== 2) return this._t("panel.recurrence_every_count_days", { count: n });
|
||||
return this._labelOf(RECURRENCES, recurrence);
|
||||
}
|
||||
|
||||
_labelOf(arr, val) {
|
||||
const item = arr.find(x => x.v === val);
|
||||
if (!item) return val || "";
|
||||
@@ -10698,6 +10743,15 @@ class TaskMatePanel extends HTMLElement {
|
||||
return `<ha-icon icon="${this._esc(name || "mdi:account-circle")}"></ha-icon>`;
|
||||
}
|
||||
|
||||
_pointsName() {
|
||||
return this._state.settings?.points_name || this._t("common.points");
|
||||
}
|
||||
|
||||
/** The configured points icon, sized to sit inline after a number. */
|
||||
_pointsIconHtml() {
|
||||
return `<ha-icon class="tm-pts-ic" icon="${this._esc(this._state.settings?.points_icon || "mdi:star")}"></ha-icon>`;
|
||||
}
|
||||
|
||||
_styles() {
|
||||
return `<style>
|
||||
/* Fill the panel area without depending on a percentage-height chain.
|
||||
@@ -11161,6 +11215,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
.tm-auc-ic { width: 34px; height: 34px; border-radius: 10px; color: #fff; background: linear-gradient(135deg, #7e57c2, #5e35b1); }
|
||||
.tm-auc-ic ha-icon { --mdc-icon-size: 18px; }
|
||||
.tm-auc-gold { color: var(--tm-gold, #d4ac0d); }
|
||||
.tm-pts-ic { --mdc-icon-size: 1.1em; vertical-align: -0.15em; }
|
||||
.tm-auc-head { flex: 1; min-width: 0; }
|
||||
.tm-auc-chips .tm-chip-btn { display: inline-flex; flex-direction: row; align-items: center; gap: 6px; }
|
||||
.tm-auc-chips .tm-chip-btn > ha-icon { --mdc-icon-size: 15px; }
|
||||
|
||||
@@ -530,6 +530,7 @@ class TaskMateParentDashboardCard extends LitElement {
|
||||
.pd-name-cp { font-weight: 600; }
|
||||
.pd-sub { font-size: 12.5px; }
|
||||
.pd-pts { font-size: 20px; color: var(--tmd-accent); }
|
||||
.pd-pts ha-icon { --mdc-icon-size: 18px; vertical-align: -2px; margin-inline-start: 2px; }
|
||||
.pd-pts-cn { font-family: var(--tmd-font-mono); font-weight: 800; font-size: 18px; color: var(--tmd-accent); }
|
||||
.pd-mini { font-family: var(--tmd-font-mono); font-size: 10px; }
|
||||
|
||||
@@ -823,7 +824,7 @@ class TaskMateParentDashboardCard extends LitElement {
|
||||
|
||||
_dEmpty(msg) { return html`<div class="tmd-empty">${msg}</div>`; }
|
||||
|
||||
_dOverview(design, children, chores, completions, attrs, _pointsIcon) {
|
||||
_dOverview(design, children, chores, completions, attrs, pointsIcon) {
|
||||
if (!children.length) return this._dEmpty(this._t('dashboard.empty_no_children'));
|
||||
return html`${children.map((child, i) => {
|
||||
const tone = this._designTone(i);
|
||||
@@ -866,7 +867,7 @@ class TaskMateParentDashboardCard extends LitElement {
|
||||
<div class="pd-name">${child.name}</div>
|
||||
<div class="muted pd-sub">${_ltrNums(this._t('dashboard.chores_done', { done: approved, total }, `${approved} / ${total} chores done`))}</div>
|
||||
</div>
|
||||
<div class="big pd-pts">${child.points}⭐</div>
|
||||
<div class="big pd-pts">${child.points}<ha-icon icon="${pointsIcon}"></ha-icon></div>
|
||||
</div>
|
||||
<div class="bar" style="margin-top:9px"><i style="width:${pct}%"></i></div>
|
||||
</div>`;
|
||||
|
||||
@@ -609,7 +609,7 @@ class TaskMatePointsCard extends LitElement {
|
||||
.pc-mid { flex: 1; min-width: 0; }
|
||||
.pc-name { font-weight: 800; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.pc-pts { font-family: var(--tmd-font-display); font-weight: 800; font-size: 22px; line-height: 1; color: var(--tmd-accent); }
|
||||
.pc-pts span { font-size: 12px; margin-inline-start: 2px; }
|
||||
.pc-pts ha-icon { --mdc-icon-size: 16px; vertical-align: -1px; margin-inline-start: 2px; }
|
||||
.pc-sub { font-size: 11px; }
|
||||
|
||||
/* Console list variant */
|
||||
@@ -877,7 +877,7 @@ class TaskMatePointsCard extends LitElement {
|
||||
${this._av(child, tone, 46)}
|
||||
<div class="pc-mid">
|
||||
<div class="pc-name">${child.name}</div>
|
||||
<div class="pc-pts">${child.points}<span>⭐</span></div>
|
||||
<div class="pc-pts">${child.points}<ha-icon icon="${pointsIcon}"></ha-icon></div>
|
||||
</div>
|
||||
${this._designQuickActions(child, pointsName, pointsIcon, isLoading)}
|
||||
</div>`;
|
||||
|
||||
@@ -226,9 +226,8 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.big-points::before {
|
||||
content: "\u2B50";
|
||||
font-size: 7rem;
|
||||
.pd-wm {
|
||||
--mdc-icon-size: 7rem;
|
||||
position: absolute;
|
||||
inset-inline-end: -12px;
|
||||
bottom: -18px;
|
||||
@@ -253,7 +252,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.points-star {
|
||||
font-size: 2rem;
|
||||
--mdc-icon-size: 2rem;
|
||||
animation: star-spin 3s linear infinite;
|
||||
display: inline-block;
|
||||
margin-inline-end: 6px;
|
||||
@@ -351,7 +350,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.child-tile .tile-star {
|
||||
font-size: 1rem;
|
||||
--mdc-icon-size: 1rem;
|
||||
vertical-align: middle;
|
||||
margin-inline-end: 2px;
|
||||
display: inline-block;
|
||||
@@ -412,14 +411,11 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cumulative-total::before {
|
||||
content: "\u{1F31F}";
|
||||
font-size: 6rem;
|
||||
position: absolute;
|
||||
.cumulative-total .pd-wm {
|
||||
--mdc-icon-size: 6rem;
|
||||
inset-inline-end: -10px;
|
||||
bottom: -14px;
|
||||
opacity: 0.07;
|
||||
pointer-events: none;
|
||||
}
|
||||
.cumulative-total .points-label { margin-bottom: 6px; }
|
||||
.cumulative-total .points-number { font-size: 3.8rem; }
|
||||
@@ -473,7 +469,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cumul-row .cumul-star {
|
||||
font-size: 0.85rem;
|
||||
--mdc-icon-size: 0.85rem;
|
||||
animation: star-spin 5s linear infinite;
|
||||
display: inline-block;
|
||||
margin-inline-end: 2px;
|
||||
@@ -537,13 +533,14 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
/* Shared .tmd kit + design tokens are provided by taskmate-design.js styles(). */
|
||||
|
||||
/* Points Display — Playroom */
|
||||
.tmd-hd .ic ha-icon { --mdc-icon-size: 18px; }
|
||||
.pl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 11px; }
|
||||
.pl-tile { background: var(--tmd-surface-2); border-radius: 18px; padding: 22px 12px 15px; text-align: center;
|
||||
position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; }
|
||||
.pl-medal { position: absolute; top: 9px; inset-inline-start: 11px; font-size: 16px; line-height: 1; }
|
||||
.pl-pts { font-family: var(--tmd-font-display); font-weight: 800; font-size: 1.85rem; line-height: 1;
|
||||
color: var(--tmd-accent); }
|
||||
.pl-pts span { font-size: 0.9rem; margin-inline-start: 2px; }
|
||||
.pl-pts ha-icon { --mdc-icon-size: 0.9rem; margin-inline-start: 2px; vertical-align: 0.1em; }
|
||||
.pl-name { font-weight: 800; font-size: 0.95rem; }
|
||||
.pl-chips { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; }
|
||||
|
||||
@@ -571,7 +568,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
/* Points Display — Cumulative (designed) */
|
||||
.cumul-total-d { text-align: center; padding: 8px 0 14px; }
|
||||
.cumul-big { font-size: 2.3rem; color: var(--tmd-accent); margin-top: 2px; }
|
||||
.cumul-big-star { font-size: 1rem; }
|
||||
.cumul-big-star { --mdc-icon-size: 1rem; vertical-align: 0.15em; }
|
||||
.cumul-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.cumul-row-d { display: flex; align-items: center; gap: 11px; background: var(--tmd-surface-2);
|
||||
border: 1px solid var(--tmd-border); border-radius: 14px; padding: 11px; }
|
||||
@@ -614,6 +611,18 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
return this.hass?.states?.[this.config.entity];
|
||||
}
|
||||
|
||||
_pointsIcon() {
|
||||
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity))
|
||||
|| this._stateObj()?.attributes || {};
|
||||
return attrs.points_icon || "mdi:star";
|
||||
}
|
||||
|
||||
_pointsName() {
|
||||
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity))
|
||||
|| this._stateObj()?.attributes || {};
|
||||
return attrs.points_name || this._t("common.points");
|
||||
}
|
||||
|
||||
_allChildren() {
|
||||
const s = this._stateObj();
|
||||
return s ? getChildren(s) : [];
|
||||
@@ -697,9 +706,10 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="big-points">
|
||||
<ha-icon class="pd-wm" icon="${this._pointsIcon()}"></ha-icon>
|
||||
<div class="points-label">${this._primaryLabel()}</div>
|
||||
<div class="points-number" style="color:${colour}">
|
||||
<span class="points-star">\u2B50</span>${primary.toLocaleString()}
|
||||
<ha-icon class="points-star" icon="${this._pointsIcon()}"></ha-icon>${primary.toLocaleString()}
|
||||
</div>
|
||||
<div class="secondary-info">
|
||||
${this._secondaryLabel()}: <span class="secondary-value">${secondary.toLocaleString()}</span>
|
||||
@@ -711,7 +721,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
<div class="stat-pill">
|
||||
<ha-icon icon="mdi:calendar-week"></ha-icon>
|
||||
<div>
|
||||
<div>${this._t("points_display.weekly_pts", { count: weekly })}</div>
|
||||
<div>${this._t("points_display.weekly_pts", { count: weekly, pointsName: this._pointsName() })}</div>
|
||||
<div class="pill-label">${this._t("points_display.this_week")}</div>
|
||||
</div>
|
||||
</div>` : ""}
|
||||
@@ -766,7 +776,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
${childAvatar(child, colour)}
|
||||
<div class="child-name">${child.name}</div>
|
||||
<div class="tile-points" style="color:${colour}">
|
||||
<span class="tile-star">\u2B50</span>${primary.toLocaleString()}
|
||||
<ha-icon class="tile-star" icon="${this._pointsIcon()}"></ha-icon>${primary.toLocaleString()}
|
||||
</div>
|
||||
<div class="tile-label">${this._isCareerMode()
|
||||
? this._t("points_display.career_score_label")
|
||||
@@ -801,9 +811,10 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
return html`
|
||||
<div class="cumulative-wrap">
|
||||
<div class="cumulative-total">
|
||||
<ha-icon class="pd-wm" icon="${this._pointsIcon()}"></ha-icon>
|
||||
<div class="points-label">${this._t("points_display.combined_family_total")}</div>
|
||||
<div class="points-number" style="color:${_safeColor(this.config.header_color, DEFAULT_HEADER)}">
|
||||
<span class="points-star">\u{1F31F}</span>${total.toLocaleString()}
|
||||
<ha-icon class="points-star" icon="${this._pointsIcon()}"></ha-icon>${total.toLocaleString()}
|
||||
</div>
|
||||
<div class="secondary-info">
|
||||
${this._secondaryLabel()}: <span class="secondary-value">${secTotal.toLocaleString()}</span>
|
||||
@@ -825,7 +836,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:5px;">
|
||||
<span class="child-name">${child.name}</span>
|
||||
<span class="cumul-points" style="color:${colour}">
|
||||
<span class="cumul-star">\u2B50</span>${primary.toLocaleString()}
|
||||
<ha-icon class="cumul-star" icon="${this._pointsIcon()}"></ha-icon>${primary.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div class="secondary-info" style="margin-top:0;margin-bottom:4px;">
|
||||
@@ -973,7 +984,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
|
||||
const header = html`
|
||||
<div class="tmd-hd">
|
||||
<span class="ic">⭐</span>
|
||||
<span class="ic"><ha-icon icon="${this._pointsIcon()}"></ha-icon></span>
|
||||
<span class="tt">${title}</span>
|
||||
${mode === "multi" ? html`<span class="pill">${this._t("points_display.mode_multi")}</span>` : ""}
|
||||
</div>`;
|
||||
@@ -1017,7 +1028,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
<div class="pl-tile" style="--ac:${r.tone}">
|
||||
${showRank && r.rank < 3 ? html`<div class="pl-medal">${RANK_MEDAL[r.rank]}</div>` : ""}
|
||||
${this._av(r.child, r.tone, 52)}
|
||||
<div class="pl-pts">${r.points.toLocaleString()}<span>⭐</span></div>
|
||||
<div class="pl-pts">${r.points.toLocaleString()}<ha-icon icon="${this._pointsIcon()}"></ha-icon></div>
|
||||
<div class="pl-name">${r.child.name}</div>
|
||||
${showWeekly || (showStreak && r.streak) ? html`
|
||||
<div class="pl-chips">
|
||||
@@ -1082,7 +1093,7 @@ class TaskMatePointsDisplayCard extends LitElement {
|
||||
return html`
|
||||
<div class="cumul-total-d">
|
||||
<div class="muted">${this._t("points_display.combined_family_total")}</div>
|
||||
<div class="big cumul-big">${total.toLocaleString()} <span class="cumul-big-star">⭐</span></div>
|
||||
<div class="big cumul-big">${total.toLocaleString()} <ha-icon class="cumul-big-star" icon="${this._pointsIcon()}"></ha-icon></div>
|
||||
</div>
|
||||
<div class="cumul-list">
|
||||
${rows.map((r) => html`
|
||||
|
||||
@@ -450,7 +450,7 @@ class TaskMateRewardProgressCard extends LitElement {
|
||||
flex-direction: column; align-items: center; gap: 4px; }
|
||||
.rp-kid .av { margin: 0 auto 2px; }
|
||||
.rp-kid-pts { font-size: 20px; color: var(--tmd-accent); }
|
||||
.rp-kid-pts span { font-size: 12px; margin-inline-start: 1px; }
|
||||
.rp-kid-pts ha-icon { --mdc-icon-size: 16px; vertical-align: -1px; margin-inline-start: 1px; }
|
||||
.rp-kid-name { font-size: 12px; font-weight: 800; }
|
||||
.rp-kid-bar { width: 100%; height: 7px; margin-top: 3px; }
|
||||
`;
|
||||
@@ -745,6 +745,7 @@ class TaskMateRewardProgressCard extends LitElement {
|
||||
const rewards = attrs.rewards || [];
|
||||
const children = attrs.children || [];
|
||||
const pointsName = attrs.points_name || this._t("common.points");
|
||||
const pointsIcon = attrs.points_icon || "mdi:star";
|
||||
|
||||
let reward = this.config.reward_id
|
||||
? rewards.find(r => r.id === this.config.reward_id)
|
||||
@@ -803,10 +804,10 @@ class TaskMateRewardProgressCard extends LitElement {
|
||||
availabilityTone = 'warn';
|
||||
}
|
||||
|
||||
return wrap(html`${this._designBody(reward, design, cost, have, pct, remaining, contributors, pointsName, availabilityLabel, availabilityTone)}`);
|
||||
return wrap(html`${this._designBody(reward, design, cost, have, pct, remaining, contributors, pointsName, pointsIcon, availabilityLabel, availabilityTone)}`);
|
||||
}
|
||||
|
||||
_designBody(reward, design, cost, have, pct, remaining, contributors, pointsName, availabilityLabel, availabilityTone) {
|
||||
_designBody(reward, design, cost, have, pct, remaining, contributors, pointsName, pointsIcon, availabilityLabel, availabilityTone) {
|
||||
const icon = reward.icon || "mdi:gift";
|
||||
const isIcon = typeof icon === "string" && icon.startsWith("mdi:");
|
||||
const hero = isIcon
|
||||
@@ -820,7 +821,7 @@ class TaskMateRewardProgressCard extends LitElement {
|
||||
${reward.description ? html`<div class="muted rp-desc">${reward.description}</div>` : ''}
|
||||
${reward.is_jackpot ? html`<div class="chip rp-jackpot">🎰 ${this._t('reward_progress.jackpot_reward')}</div>` : ''}
|
||||
${availabilityLabel ? html`<div class="chip rp-avail rp-avail-${availabilityTone}">${availabilityLabel}</div>` : ''}
|
||||
<div class="chip rp-cost">⭐ ${cost} ${pointsName}</div>
|
||||
<div class="chip rp-cost"><ha-icon style="--mdc-icon-size:1.1em;vertical-align:-0.15em" icon="${pointsIcon}"></ha-icon> ${cost} ${pointsName}</div>
|
||||
<div class="bar rp-bar"><i style="width:${pct}%;${pct >= 100 ? 'background:var(--tmd-good)' : ''}"></i></div>
|
||||
${design === "cleanpro"
|
||||
? html`<div class="row rp-stat">
|
||||
@@ -840,7 +841,7 @@ class TaskMateRewardProgressCard extends LitElement {
|
||||
${contributors.map((c) => html`
|
||||
<div class="rp-kid" style="--ac:${c.tone}">
|
||||
${this._av(c.child, c.tone, 36)}
|
||||
<div class="big rp-kid-pts">${c.points}<span>⭐</span></div>
|
||||
<div class="big rp-kid-pts">${c.points}<ha-icon icon="${pointsIcon}"></ha-icon></div>
|
||||
<div class="rp-kid-name">${c.child.name}</div>
|
||||
${design === "cleanpro" && !reward.is_jackpot && c.cost > 0
|
||||
? html`<div class="bar rp-kid-bar"><i style="width:${Math.min(100, Math.round((c.points / c.cost) * 100))}%;background:${c.tone}"></i></div>` : ''}
|
||||
|
||||
@@ -2030,7 +2030,7 @@ class TaskMateRewardsCard extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
_designChildSelector(child, _pointsIcon, _pointsName, _design) {
|
||||
_designChildSelector(child, pointsIcon, _pointsName, _design) {
|
||||
const spendable = typeof child.spendable_balance === 'number'
|
||||
? child.spendable_balance : (child.points || 0);
|
||||
return html`
|
||||
@@ -2038,7 +2038,7 @@ class TaskMateRewardsCard extends LitElement {
|
||||
${this._av(child, this._designTone(0), 38)}
|
||||
<div class="rw-sel-name">${child.name}</div>
|
||||
<span class="chip soft">
|
||||
<span style="color:var(--tmd-gold)">⭐</span>
|
||||
<ha-icon style="color:var(--tmd-gold);--mdc-icon-size:1.1em;vertical-align:-0.15em" icon="${pointsIcon}"></ha-icon>
|
||||
${this._t('rewards.spendable_balance')}: ${spendable}
|
||||
</span>
|
||||
</div>`;
|
||||
@@ -2063,7 +2063,7 @@ class TaskMateRewardsCard extends LitElement {
|
||||
: assignedTo.map((cid) => html`<span class="chip rw-badge">${childMap[cid] || cid}</span>`))
|
||||
: '';
|
||||
|
||||
const costBadge = html`<span class="chip rw-cost">⭐ ${d.displayCost} ${pointsName}</span>`;
|
||||
const costBadge = html`<span class="chip rw-cost"><ha-icon style="--mdc-icon-size:1.1em;vertical-align:-0.15em" icon="${pointsIcon}"></ha-icon> ${d.displayCost} ${pointsName}</span>`;
|
||||
|
||||
const bar = d.isJackpot
|
||||
? html`<div class="bar rw-pool-bar">
|
||||
|
||||
@@ -462,6 +462,9 @@ class TaskMateStreakCard extends LitElement {
|
||||
: (entity_ref?.attributes?.total_completions_all_time || completions.filter(comp => comp.child_id === child.id).length);
|
||||
const totalPoints = (child.total_points_earned !== undefined ? child.total_points_earned : child.points) || 0;
|
||||
const bestStreak = child.best_streak || streak || 0;
|
||||
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity))
|
||||
|| entity_ref?.attributes || {};
|
||||
const pointsName = attrs.points_name || this._t('common.points');
|
||||
|
||||
const milestones = [
|
||||
{ id: "first", name: this._t('streak.achievement.first_name'), emoji: "🌟", description: this._t('streak.achievement.first_desc'), earned: totalCompletions >= 1 },
|
||||
@@ -472,8 +475,8 @@ class TaskMateStreakCard extends LitElement {
|
||||
{ id: "streak7", name: this._t('streak.achievement.streak7_name'), emoji: "🔥", description: this._t('streak.achievement.streak7_desc'), earned: bestStreak >= 7 },
|
||||
{ id: "streak14", name: this._t('streak.achievement.streak14_name'), emoji: "🔥🔥", description: this._t('streak.achievement.streak14_desc'), earned: bestStreak >= 14 },
|
||||
{ id: "streak30", name: this._t('streak.achievement.streak30_name'), emoji: "💎", description: this._t('streak.achievement.streak30_desc'), earned: bestStreak >= 30 },
|
||||
{ id: "points50", name: this._t('streak.achievement.points50_name'), emoji: "🎯", description: this._t('streak.achievement.points50_desc'), earned: totalPoints >= 50 },
|
||||
{ id: "points100", name: this._t('streak.achievement.points100_name'), emoji: "💰", description: this._t('streak.achievement.points100_desc'), earned: totalPoints >= 100 },
|
||||
{ id: "points50", name: this._t('streak.achievement.points50_name', { pointsName }), emoji: "🎯", description: this._t('streak.achievement.points50_desc'), earned: totalPoints >= 50 },
|
||||
{ id: "points100", name: this._t('streak.achievement.points100_name', { pointsName }), emoji: "💰", description: this._t('streak.achievement.points100_desc'), earned: totalPoints >= 100 },
|
||||
];
|
||||
|
||||
// Show earned ones + next locked milestone
|
||||
|
||||
@@ -671,6 +671,9 @@ class TaskMateWeeklyCard extends LitElement {
|
||||
|
||||
_wkKids(design, d) {
|
||||
if (!d.kids.length) return "";
|
||||
const attrs = (window.__taskmate_attrs && window.__taskmate_attrs(this.hass, this.config.entity))
|
||||
|| this.hass?.states?.[this.config.entity]?.attributes || {};
|
||||
const pointsIcon = attrs.points_icon || "mdi:star";
|
||||
const wrapClass = design === "cleanpro" ? "wk-kids cp" : "wk-kids";
|
||||
const valColor = design === "cleanpro" ? "var(--tmd-good)" : "var(--tmd-accent)";
|
||||
const av = design === "playroom" ? 34 : design === "console" ? 30 : 28;
|
||||
@@ -684,7 +687,7 @@ class TaskMateWeeklyCard extends LitElement {
|
||||
${this._av(r.child, tone, av)}
|
||||
<div class="name">${r.child.name}</div>
|
||||
${design === "playroom"
|
||||
? html`<div class="chip soft">+${r.points} ⭐</div>`
|
||||
? html`<div class="chip soft">+${r.points} <ha-icon style="--mdc-icon-size:1.1em;vertical-align:-0.15em" icon="${pointsIcon}"></ha-icon></div>`
|
||||
: html`<div class="num" style="color:${valColor}">+${r.points}</div>`}
|
||||
</div>`;
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user