Add family members and chores to see the leaderboard.
@@ -109,7 +111,10 @@ export function Leaderboard({ open, onClose }: LeaderboardProps) {
{entry.member.name}
- {entry.earnings.chore_count} chore{entry.earnings.chore_count !== 1 ? 's' : ''}
+ {entry.earnings.chore_count === 1
+ ? t('leaderboard.choreCompletedSingular', { count: entry.earnings.chore_count })
+ : t('leaderboard.choresCompleted', { count: entry.earnings.chore_count })
+ }
diff --git a/beacon/src/components/MusicView.tsx b/beacon/src/components/MusicView.tsx
index ad4ac6e1..c73c1683 100644
--- a/beacon/src/components/MusicView.tsx
+++ b/beacon/src/components/MusicView.tsx
@@ -10,6 +10,7 @@ import {
ChevronDown,
} from 'lucide-react';
import { MediaPlayer } from '../types/music';
+import { useTranslation } from '../i18n';
interface MusicViewProps {
players: MediaPlayer[];
@@ -44,14 +45,15 @@ export function MusicView({
onPrevious,
onSetVolume,
}: MusicViewProps) {
+ const { t } = useTranslation();
// Empty state when no media players found
if (players.length === 0) {
return (
🎵
-
No Media Players
-
Connect a media player in Home Assistant to control music from here.
+
{t('music.noPlayers')}
+
{t('music.noPlayersDesc')}
);
@@ -104,7 +106,7 @@ export function MusicView({
className="music-player-selector-btn"
onClick={() => setShowPlayerDropdown(!showPlayerDropdown)}
>
-
{player?.friendly_name || 'Select Player'}
+
{player?.friendly_name || t('music.selectPlayer')}
{showPlayerDropdown && (
@@ -123,7 +125,7 @@ export function MusicView({
>
{p.friendly_name}
{p.state === 'playing' && (
-
Playing
+
{t('music.nowPlaying')}
)}
))}
@@ -138,7 +140,7 @@ export function MusicView({

) : (
@@ -150,7 +152,7 @@ export function MusicView({
{/* Track info */}
- {player?.media_title || player?.app_name || 'Nothing Playing'}
+ {player?.media_title || player?.app_name || t('music.nowPlaying')}
{player?.media_artist || ''}
@@ -180,7 +182,7 @@ export function MusicView({
type="button"
className="music-control-btn"
onClick={() => onPrevious(player?.entity_id)}
- aria-label="Previous track"
+ aria-label={t('music.previousTrack')}
>
@@ -188,7 +190,7 @@ export function MusicView({
type="button"
className="music-control-btn music-control-btn--play"
onClick={() => (isPlaying ? onPause : onPlay)(player?.entity_id)}
- aria-label={isPlaying ? 'Pause' : 'Play'}
+ aria-label={isPlaying ? t('music.pause') : t('music.play')}
>
{isPlaying ? : }
@@ -196,7 +198,7 @@ export function MusicView({
type="button"
className="music-control-btn"
onClick={() => onNext(player?.entity_id)}
- aria-label="Next track"
+ aria-label={t('music.nextTrack')}
>
@@ -213,7 +215,7 @@ export function MusicView({
player?.entity_id,
)
}
- aria-label={player?.is_volume_muted ? 'Unmute' : 'Mute'}
+ aria-label={player?.is_volume_muted ? t('music.unmute') : t('music.mute')}
>
{player?.is_volume_muted ? : }
@@ -225,7 +227,7 @@ export function MusicView({
step={0.02}
value={player?.is_volume_muted ? 0 : (player?.volume_level ?? 0.5)}
onChange={(e) => onSetVolume(parseFloat(e.target.value), player?.entity_id)}
- aria-label="Volume"
+ aria-label={t('music.volume')}
/>
diff --git a/beacon/src/components/NowPlayingBar.tsx b/beacon/src/components/NowPlayingBar.tsx
index b5bf512a..ef2e2b3a 100644
--- a/beacon/src/components/NowPlayingBar.tsx
+++ b/beacon/src/components/NowPlayingBar.tsx
@@ -9,6 +9,7 @@ import {
} from 'lucide-react';
import { MediaPlayer } from '../types/music';
import { getConfig } from '../config';
+import { useTranslation } from '../i18n';
interface NowPlayingBarProps {
player: MediaPlayer | null;
@@ -29,6 +30,7 @@ export function NowPlayingBar({
onSetVolume,
onExpand,
}: NowPlayingBarProps) {
+ const { t } = useTranslation();
// Local progress tracking
const [position, setPosition] = useState(player?.media_position ?? 0);
const animRef = useRef
| null>(null);
@@ -59,7 +61,7 @@ export function NowPlayingBar({
: null;
return (
-
+
{/* Thin progress bar across the top */}
{duration > 0 && (
@@ -71,13 +73,13 @@ export function NowPlayingBar({
type="button"
className="now-playing-info"
onClick={onExpand}
- aria-label="Open music view"
+ aria-label={t('music.openMusicView')}
>
{artSrc ? (

) : (
@@ -98,7 +100,7 @@ export function NowPlayingBar({
type="button"
className="now-playing-btn"
onClick={onPrevious}
- aria-label="Previous track"
+ aria-label={t('music.previousTrack')}
>
@@ -106,7 +108,7 @@ export function NowPlayingBar({
type="button"
className="now-playing-btn now-playing-btn--play"
onClick={isPlaying ? onPause : onPlay}
- aria-label={isPlaying ? 'Pause' : 'Play'}
+ aria-label={isPlaying ? t('music.pause') : t('music.play')}
>
{isPlaying ?
:
}
@@ -114,7 +116,7 @@ export function NowPlayingBar({
type="button"
className="now-playing-btn"
onClick={onNext}
- aria-label="Next track"
+ aria-label={t('music.nextTrack')}
>
@@ -126,7 +128,7 @@ export function NowPlayingBar({
type="button"
className="now-playing-btn"
onClick={() => onSetVolume(player.is_volume_muted ? (player.volume_level || 0.5) : 0)}
- aria-label={player.is_volume_muted ? 'Unmute' : 'Mute'}
+ aria-label={player.is_volume_muted ? t('music.unmute') : t('music.mute')}
>
{player.is_volume_muted ?
:
}
@@ -138,7 +140,7 @@ export function NowPlayingBar({
step={0.02}
value={player.is_volume_muted ? 0 : (player.volume_level ?? 0.5)}
onChange={(e) => onSetVolume(parseFloat(e.target.value))}
- aria-label="Volume"
+ aria-label={t('music.volume')}
/>
diff --git a/beacon/src/components/OnboardingView.tsx b/beacon/src/components/OnboardingView.tsx
index a944a630..711f79c3 100644
--- a/beacon/src/components/OnboardingView.tsx
+++ b/beacon/src/components/OnboardingView.tsx
@@ -1,6 +1,7 @@
import { useState, useCallback } from 'react';
import { ArrowRight, Check, ExternalLink, Key, LogIn } from 'lucide-react';
import beaconLogo from '../assets/beacon-app-icon.svg';
+import { useTranslation } from '../i18n';
// ---------------------------------------------------------------------------
// Types
@@ -197,6 +198,7 @@ const styles = {
// ---------------------------------------------------------------------------
export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingViewProps) {
+ const { t } = useTranslation();
const [step, setStep] = useState
(1);
const [haUrl, setHaUrl] = useState('');
const [urlError, setUrlError] = useState('');
@@ -216,7 +218,7 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
const testConnection = useCallback(async () => {
const trimmed = haUrl.trim().replace(/\/+$/, '');
if (!isValidUrl(trimmed)) {
- setUrlError('URL must start with http:// or https://');
+ setUrlError(t('onboarding.urlError'));
return;
}
setUrlError('');
@@ -228,24 +230,24 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
setHaUrl(trimmed);
setStep(3);
} else {
- setUrlError(`Connection failed (HTTP ${response.status}). Check the URL and try again.`);
+ setUrlError(t('onboarding.connectionFailed', { status: response.status }));
}
} catch {
- setUrlError('Could not reach Home Assistant. Verify the URL and ensure it is accessible.');
+ setUrlError(t('onboarding.notReachable'));
} finally {
setConnecting(false);
}
- }, [haUrl, isValidUrl]);
+ }, [haUrl, isValidUrl, t]);
// ---- Complete setup ----
const handleComplete = useCallback(() => {
if (!token.trim()) {
- setTokenError('Please enter your access token.');
+ setTokenError(t('onboarding.tokenRequired'));
return;
}
setTokenError('');
onComplete(haUrl, token.trim());
- }, [haUrl, token, onComplete]);
+ }, [haUrl, token, onComplete, t]);
// ---- Render steps ----
@@ -263,13 +265,13 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
const renderStep1 = () => (

-
Welcome to Beacon
-
Connect your Home Assistant to get started.
+
{t('onboarding.welcome')}
+
{t('onboarding.welcomeDesc')}
@@ -277,16 +279,16 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
const renderStep2 = () => (
-
Connect to Home Assistant
+
{t('onboarding.connectTitle')}
- Enter the URL of your Home Assistant instance.
+ {t('onboarding.connectDesc')}
-
+
{
setHaUrl(e.target.value);
@@ -311,7 +313,7 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
disabled={connecting || !haUrl.trim()}
onClick={testConnection}
>
- {connecting ? 'Connecting...' : 'Connect'}
+ {connecting ? t('onboarding.connecting') : t('onboarding.connect')}
{!connecting &&
}
@@ -319,9 +321,9 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
const renderStep3 = () => (
-
Authentication
+
{t('onboarding.authTitle')}
- Choose how to authenticate with Home Assistant.
+ {t('onboarding.authDesc')}
@@ -332,17 +334,17 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
>
- Long-lived Access Token
+ {t('onboarding.tokenMethod')}
- Use a token generated from your Home Assistant profile.
+ {t('onboarding.tokenMethodDesc')}
{authMethod === 'token' && (
{
setToken(e.target.value);
@@ -356,9 +358,9 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
/>
{tokenError &&
{tokenError}
}
- How to create a token:
+ {t('onboarding.tokenHint')}
- Go to your HA Profile → Long-lived tokens → Create Token
+ {t('onboarding.tokenHintDetail')}
)}
@@ -371,10 +373,10 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
>
- Sign in with Home Assistant
+ {t('onboarding.oauthMethod')}
- Authenticate using your Home Assistant credentials via OAuth2.
+ {t('onboarding.oauthMethodDesc')}
{authMethod === 'oauth' && (
@@ -386,7 +388,7 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
onOAuthStart?.(haUrl);
}}
>
- Open Home Assistant
+ {t('onboarding.openHA')}
@@ -403,7 +405,7 @@ export default function OnboardingView({ onComplete, onOAuthStart }: OnboardingV
disabled={!token.trim()}
onClick={handleComplete}
>
- Complete Setup
+ {t('onboarding.completeSetup')}
)}
diff --git a/beacon/src/components/StreakBadge.tsx b/beacon/src/components/StreakBadge.tsx
index 1010ecd0..ac66de92 100644
--- a/beacon/src/components/StreakBadge.tsx
+++ b/beacon/src/components/StreakBadge.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { Streak } from '../types/family';
+import { useTranslation } from '../i18n';
interface StreakBadgeProps {
streak: Streak;
@@ -7,6 +8,7 @@ interface StreakBadgeProps {
}
export function StreakBadge({ streak, size = 'md' }: StreakBadgeProps) {
+ const { t } = useTranslation();
const [showLongest, setShowLongest] = useState(false);
if (streak.current === 0 && streak.longest === 0) return null;
@@ -20,13 +22,13 @@ export function StreakBadge({ streak, size = 'md' }: StreakBadgeProps) {
type="button"
className={`streak-badge ${sizeClass} ${hotClass}`}
onClick={() => setShowLongest((v) => !v)}
- title={`Longest streak: ${streak.longest} days`}
+ title={t('streak.longestTooltip', { count: streak.longest })}
>
🔥
{showLongest ? (
<>
- Best: {streak.longest}
+ {t('streak.best')} {streak.longest}
>
) : (
streak.current
diff --git a/beacon/src/components/cards/CardPickerModal.tsx b/beacon/src/components/cards/CardPickerModal.tsx
index e91abab8..e3882d77 100644
--- a/beacon/src/components/cards/CardPickerModal.tsx
+++ b/beacon/src/components/cards/CardPickerModal.tsx
@@ -17,7 +17,7 @@ export function CardPickerModal({ region, onPick, onClose }: CardPickerModalProp
e.stopPropagation()}>
-
Add Card
+ {t('cardPicker.title')}
@@ -34,7 +34,7 @@ export function CardPickerModal({ region, onPick, onClose }: CardPickerModalProp
))}
{options.length === 0 && (
-
No card types available for this area.
+
{t('cardPicker.empty')}
)}
diff --git a/beacon/src/components/cards/ViewNameModal.tsx b/beacon/src/components/cards/ViewNameModal.tsx
index e2566474..41f1637a 100644
--- a/beacon/src/components/cards/ViewNameModal.tsx
+++ b/beacon/src/components/cards/ViewNameModal.tsx
@@ -1,4 +1,5 @@
import { useState } from 'react';
+import { useTranslation } from '../../i18n';
interface ViewNameModalProps {
title: string;
@@ -9,6 +10,7 @@ interface ViewNameModalProps {
/** Small text-input modal used for adding/renaming a dashboard view (tab). */
export function ViewNameModal({ title, initialValue, onSave, onClose }: ViewNameModalProps) {
+ const { t } = useTranslation();
const [name, setName] = useState(initialValue);
const handleSave = () => {
@@ -25,7 +27,7 @@ export function ViewNameModal({ title, initialValue, onSave, onClose }: ViewName
diff --git a/beacon/src/components/cards/registry.tsx b/beacon/src/components/cards/registry.tsx
index 1d7e8a16..d5ae6125 100644
--- a/beacon/src/components/cards/registry.tsx
+++ b/beacon/src/components/cards/registry.tsx
@@ -104,7 +104,7 @@ export const cardRegistry: Record
= {
displayNameKey: 'cards.haEntitiesList.displayName',
icon: '📶',
component: HaEntitiesListCard,
- defaultConfig: { title: 'Entities', subtitle: '', entity_ids: [] },
+ defaultConfig: { title: '', subtitle: '', entity_ids: [] },
defaultSize: 'sm',
configFields: [
{ type: 'text', key: 'title', labelKey: 'cards.common.titleLabel' },
diff --git a/beacon/src/i18n/translations/de.ts b/beacon/src/i18n/translations/de.ts
index 184ecd0e..0c7061f7 100644
--- a/beacon/src/i18n/translations/de.ts
+++ b/beacon/src/i18n/translations/de.ts
@@ -431,4 +431,108 @@ export const de: Record = {
'photoFrame.pauseSlideshow': 'Diashow pausieren',
'photoFrame.resumeSlideshow': 'Diashow fortsetzen',
'photoFrame.nextPhoto': 'Nächstes Foto',
+
+ // ---- chores view ----
+ 'chores.title': 'Aufgaben',
+ 'chores.addChore': 'Aufgabe hinzufügen',
+ 'chores.editChore': 'Aufgabe bearbeiten',
+ 'chores.newChore': 'Neue Aufgabe',
+ 'chores.claim': 'Übernehmen',
+ 'chores.complete': 'Erledigen',
+ 'chores.whoClaims': 'Wer übernimmt dies?',
+ 'chores.whoCompleted': 'Wer hat das erledigt?',
+ 'chores.editChoreAria': '{name} bearbeiten',
+ 'chores.deleteChoreAria': '{name} löschen',
+ 'chores.name': 'Name',
+ 'chores.value': 'Wert',
+ 'chores.frequency': 'Häufigkeit',
+ 'chores.assignedTo': 'Zugewiesen an',
+ 'chores.assignTo': 'Zuweisen an',
+ 'chores.icon': 'Symbol',
+ 'chores.daily': 'Täglich',
+ 'chores.weekly': 'Wöchentlich',
+ 'chores.monthly': 'Monatlich',
+ 'chores.oneTime': 'Einmalig',
+ 'chores.namePlaceholder': 'z.B. Wohnzimmer saugen',
+ 'chores.anyone': 'Jeder',
+ 'chores.empty': 'Noch keine Aufgaben — füge eine hinzu',
+ 'chores.addMembersFirst': 'Füge erst Familienmitglieder hinzu, um Aufgaben zuzuweisen.',
+ 'chores.openChores': 'Offene Aufgaben',
+ 'chores.noChoresAssigned': 'Keine Aufgaben zugewiesen',
+ 'chores.addChoreFor': 'Aufgabe für {name} hinzufügen',
+ 'chores.saveChanges': 'Änderungen speichern',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': 'Bestenliste',
+ 'leaderboard.thisWeek': 'Diese Woche',
+ 'leaderboard.thisMonth': 'Dieser Monat',
+ 'leaderboard.empty': 'Füge Familienmitglieder und Aufgaben hinzu, um die Bestenliste zu sehen.',
+ 'leaderboard.choresCompleted': '{count} Aufgaben',
+ 'leaderboard.choreCompletedSingular': '{count} Aufgabe',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': 'Längste Serie: {count} Tage',
+ 'streak.best': 'Beste:',
+
+ // ---- music view ----
+ 'music.noPlayers': 'Keine Mediaplayer',
+ 'music.noPlayersDesc': 'Verbinde einen Mediaplayer in Home Assistant, um hier Musik zu steuern.',
+ 'music.selectPlayer': 'Player auswählen',
+ 'music.nowPlaying': 'Läuft gerade',
+ 'music.volume': 'Lautstärke',
+ 'music.openMusicView': 'Musikansicht öffnen',
+ 'music.previousTrack': 'Vorheriger Titel',
+ 'music.nextTrack': 'Nächster Titel',
+ 'music.play': 'Abspielen',
+ 'music.pause': 'Pausieren',
+ 'music.mute': 'Stumm',
+ 'music.unmute': 'Laut',
+ 'music.albumArt': 'Albumcover',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': 'Keine Listen verfügbar',
+ 'grocery.addItem': 'Artikel hinzufügen',
+ 'grocery.addItemPlaceholder': 'Artikel hinzufügen...',
+ 'grocery.loadingItems': 'Artikel werden geladen...',
+ 'grocery.empty': 'Noch keine Artikel — füge oben einen hinzu',
+ 'grocery.items': 'Artikel ({count})',
+ 'grocery.completed': 'Erledigt ({count})',
+ 'grocery.deleteItem': 'Artikel löschen',
+ 'grocery.haList': '{name} (HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': 'Karte hinzufügen',
+ 'cardPicker.empty': 'Keine Kartentypen für diesen Bereich verfügbar.',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': 'Entitäten',
+
+ // ---- view name modal ----
+ 'viewName.name': 'Name',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Willkommen bei Beacon',
+ 'onboarding.welcomeDesc': 'Verbinde dein Home Assistant, um loszulegen.',
+ 'onboarding.getStarted': 'Los geht\'s',
+ 'onboarding.connectTitle': 'Mit Home Assistant verbinden',
+ 'onboarding.connectDesc': 'Gib die URL deiner Home Assistant-Instanz ein.',
+ 'onboarding.haUrl': 'Home Assistant URL',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': 'Verbinden',
+ 'onboarding.connecting': 'Verbindung wird hergestellt...',
+ 'onboarding.authTitle': 'Authentifizierung',
+ 'onboarding.authDesc': 'Wähle, wie du dich bei Home Assistant authentifizieren möchtest.',
+ 'onboarding.tokenMethod': 'Langlebiges Zugriffstoken',
+ 'onboarding.tokenMethodDesc': 'Verwende ein Token aus deinem Home Assistant-Profil.',
+ 'onboarding.tokenPlaceholder': 'Token hier einfügen',
+ 'onboarding.tokenHint': 'So erstellst du ein Token:',
+ 'onboarding.tokenHintDetail': 'Gehe zu deinem HA-Profil → Langlebige Zugriffstoken → Token erstellen',
+ 'onboarding.oauthMethod': 'Mit Home Assistant anmelden',
+ 'onboarding.oauthMethodDesc': 'Authentifiziere dich mit deinen Home Assistant-Zugangsdaten via OAuth2.',
+ 'onboarding.openHA': 'Home Assistant öffnen',
+ 'onboarding.completeSetup': 'Einrichtung abschließen',
+ 'onboarding.urlError': 'URL muss mit http:// oder https:// beginnen',
+ 'onboarding.connectionFailed': 'Verbindung fehlgeschlagen (HTTP {status}). Überprüfe die URL und versuche es erneut.',
+ 'onboarding.notReachable': 'Home Assistant konnte nicht erreicht werden. Überprüfe die URL und stelle sicher, dass sie erreichbar ist.',
+ 'onboarding.tokenRequired': 'Bitte gib dein Zugriffstoken ein.',
};
diff --git a/beacon/src/i18n/translations/en.ts b/beacon/src/i18n/translations/en.ts
index 19e92bc3..d7b8889d 100644
--- a/beacon/src/i18n/translations/en.ts
+++ b/beacon/src/i18n/translations/en.ts
@@ -445,6 +445,110 @@ export const en = {
'photoFrame.pauseSlideshow': 'Pause slideshow',
'photoFrame.resumeSlideshow': 'Resume slideshow',
'photoFrame.nextPhoto': 'Next photo',
+
+ // ---- chores view ----
+ 'chores.title': 'Chores',
+ 'chores.addChore': 'Add Chore',
+ 'chores.editChore': 'Edit Chore',
+ 'chores.newChore': 'New Chore',
+ 'chores.claim': 'Claim',
+ 'chores.complete': 'Complete',
+ 'chores.whoClaims': 'Who claims this?',
+ 'chores.whoCompleted': 'Who completed this?',
+ 'chores.editChoreAria': 'Edit {name}',
+ 'chores.deleteChoreAria': 'Delete {name}',
+ 'chores.name': 'Name',
+ 'chores.value': 'Value',
+ 'chores.frequency': 'Frequency',
+ 'chores.assignedTo': 'Assign To',
+ 'chores.assignTo': 'Assign To',
+ 'chores.icon': 'Icon',
+ 'chores.daily': 'Daily',
+ 'chores.weekly': 'Weekly',
+ 'chores.monthly': 'Monthly',
+ 'chores.oneTime': 'One-time',
+ 'chores.namePlaceholder': 'e.g., Vacuum living room',
+ 'chores.anyone': 'Anyone',
+ 'chores.empty': 'No chores yet — add one to get started',
+ 'chores.addMembersFirst': 'Add family members first to assign chores.',
+ 'chores.openChores': 'Open Chores',
+ 'chores.noChoresAssigned': 'No chores assigned',
+ 'chores.addChoreFor': 'Add chore for {name}',
+ 'chores.saveChanges': 'Save Changes',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': 'Leaderboard',
+ 'leaderboard.thisWeek': 'This Week',
+ 'leaderboard.thisMonth': 'This Month',
+ 'leaderboard.empty': 'Add family members and chores to see the leaderboard.',
+ 'leaderboard.choresCompleted': '{count} chores',
+ 'leaderboard.choreCompletedSingular': '{count} chore',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': 'Longest streak: {count} days',
+ 'streak.best': 'Best:',
+
+ // ---- music view ----
+ 'music.noPlayers': 'No Media Players',
+ 'music.noPlayersDesc': 'Connect a media player in Home Assistant to control music from here.',
+ 'music.selectPlayer': 'Select Player',
+ 'music.nowPlaying': 'Now Playing',
+ 'music.volume': 'Volume',
+ 'music.openMusicView': 'Open music view',
+ 'music.previousTrack': 'Previous track',
+ 'music.nextTrack': 'Next track',
+ 'music.play': 'Play',
+ 'music.pause': 'Pause',
+ 'music.mute': 'Mute',
+ 'music.unmute': 'Unmute',
+ 'music.albumArt': 'Album art',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': 'No lists available',
+ 'grocery.addItem': 'Add item',
+ 'grocery.addItemPlaceholder': 'Add an item...',
+ 'grocery.loadingItems': 'Loading items...',
+ 'grocery.empty': 'No items yet — add one above',
+ 'grocery.items': 'Items ({count})',
+ 'grocery.completed': 'Completed ({count})',
+ 'grocery.deleteItem': 'Delete item',
+ 'grocery.haList': '{name} (HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': 'Add Card',
+ 'cardPicker.empty': 'No card types available for this area.',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': 'Entities',
+
+ // ---- view name modal ----
+ 'viewName.name': 'Name',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Welcome to Beacon',
+ 'onboarding.welcomeDesc': 'Connect your Home Assistant to get started.',
+ 'onboarding.getStarted': 'Get Started',
+ 'onboarding.connectTitle': 'Connect to Home Assistant',
+ 'onboarding.connectDesc': 'Enter the URL of your Home Assistant instance.',
+ 'onboarding.haUrl': 'Home Assistant URL',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': 'Connect',
+ 'onboarding.connecting': 'Connecting...',
+ 'onboarding.authTitle': 'Authentication',
+ 'onboarding.authDesc': 'Choose how to authenticate with Home Assistant.',
+ 'onboarding.tokenMethod': 'Long-lived Access Token',
+ 'onboarding.tokenMethodDesc': 'Use a token generated from your Home Assistant profile.',
+ 'onboarding.tokenPlaceholder': 'Paste your token here',
+ 'onboarding.tokenHint': 'How to create a token:',
+ 'onboarding.tokenHintDetail': 'Go to your HA Profile → Long-lived tokens → Create Token',
+ 'onboarding.oauthMethod': 'Sign in with Home Assistant',
+ 'onboarding.oauthMethodDesc': 'Authenticate using your Home Assistant credentials via OAuth2.',
+ 'onboarding.openHA': 'Open Home Assistant',
+ 'onboarding.completeSetup': 'Complete Setup',
+ 'onboarding.urlError': 'URL must start with http:// or https://',
+ 'onboarding.connectionFailed': 'Connection failed (HTTP {status}). Check the URL and try again.',
+ 'onboarding.notReachable': 'Could not reach Home Assistant. Verify the URL and ensure it is accessible.',
+ 'onboarding.tokenRequired': 'Please enter your access token.',
} as const;
export type TranslationKey = keyof typeof en;
diff --git a/beacon/src/i18n/translations/es.ts b/beacon/src/i18n/translations/es.ts
index 5e981d9d..047c7dbe 100644
--- a/beacon/src/i18n/translations/es.ts
+++ b/beacon/src/i18n/translations/es.ts
@@ -431,4 +431,108 @@ export const es: Record = {
'photoFrame.pauseSlideshow': 'Pausar presentación',
'photoFrame.resumeSlideshow': 'Reanudar presentación',
'photoFrame.nextPhoto': 'Siguiente foto',
+
+ // ---- chores view ----
+ 'chores.title': 'Tareas',
+ 'chores.addChore': 'Agregar tarea',
+ 'chores.editChore': 'Editar tarea',
+ 'chores.newChore': 'Nueva tarea',
+ 'chores.claim': 'Reclamar',
+ 'chores.complete': 'Completar',
+ 'chores.whoClaims': '¿Quién reclama esto?',
+ 'chores.whoCompleted': '¿Quién completó esto?',
+ 'chores.editChoreAria': 'Editar {name}',
+ 'chores.deleteChoreAria': 'Eliminar {name}',
+ 'chores.name': 'Nombre',
+ 'chores.value': 'Valor',
+ 'chores.frequency': 'Frecuencia',
+ 'chores.assignedTo': 'Asignado a',
+ 'chores.assignTo': 'Asignar a',
+ 'chores.icon': 'Icono',
+ 'chores.daily': 'Diario',
+ 'chores.weekly': 'Semanal',
+ 'chores.monthly': 'Mensual',
+ 'chores.oneTime': 'Una vez',
+ 'chores.namePlaceholder': 'p. ej., Aspirar la sala',
+ 'chores.anyone': 'Cualquiera',
+ 'chores.empty': 'Aún no hay tareas — agrega una',
+ 'chores.addMembersFirst': 'Agrega miembros de la familia primero para asignar tareas.',
+ 'chores.openChores': 'Tareas abiertas',
+ 'chores.noChoresAssigned': 'No hay tareas asignadas',
+ 'chores.addChoreFor': 'Agregar tarea para {name}',
+ 'chores.saveChanges': 'Guardar cambios',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': 'Tabla de clasificación',
+ 'leaderboard.thisWeek': 'Esta semana',
+ 'leaderboard.thisMonth': 'Este mes',
+ 'leaderboard.empty': 'Agrega miembros de la familia y tareas para ver la tabla de clasificación.',
+ 'leaderboard.choresCompleted': '{count} tareas',
+ 'leaderboard.choreCompletedSingular': '{count} tarea',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': 'Racha más larga: {count} días',
+ 'streak.best': 'Mejor:',
+
+ // ---- music view ----
+ 'music.noPlayers': 'Sin reproductores multimedia',
+ 'music.noPlayersDesc': 'Conecta un reproductor multimedia en Home Assistant para controlar música desde aquí.',
+ 'music.selectPlayer': 'Seleccionar reproductor',
+ 'music.nowPlaying': 'Reproduciendo ahora',
+ 'music.volume': 'Volumen',
+ 'music.openMusicView': 'Abrir vista de música',
+ 'music.previousTrack': 'Pista anterior',
+ 'music.nextTrack': 'Siguiente pista',
+ 'music.play': 'Reproducir',
+ 'music.pause': 'Pausar',
+ 'music.mute': 'Silenciar',
+ 'music.unmute': 'Activar sonido',
+ 'music.albumArt': 'Arte del álbum',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': 'No hay listas disponibles',
+ 'grocery.addItem': 'Agregar artículo',
+ 'grocery.addItemPlaceholder': 'Agregar un artículo...',
+ 'grocery.loadingItems': 'Cargando artículos...',
+ 'grocery.empty': 'Aún no hay artículos — agrega uno arriba',
+ 'grocery.items': 'Artículos ({count})',
+ 'grocery.completed': 'Completados ({count})',
+ 'grocery.deleteItem': 'Eliminar artículo',
+ 'grocery.haList': '{name} (HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': 'Agregar tarjeta',
+ 'cardPicker.empty': 'No hay tipos de tarjetas disponibles para esta área.',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': 'Entidades',
+
+ // ---- view name modal ----
+ 'viewName.name': 'Nombre',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Bienvenido a Beacon',
+ 'onboarding.welcomeDesc': 'Conecta tu Home Assistant para comenzar.',
+ 'onboarding.getStarted': 'Comenzar',
+ 'onboarding.connectTitle': 'Conectar a Home Assistant',
+ 'onboarding.connectDesc': 'Ingresa la URL de tu instancia de Home Assistant.',
+ 'onboarding.haUrl': 'URL de Home Assistant',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': 'Conectar',
+ 'onboarding.connecting': 'Conectando...',
+ 'onboarding.authTitle': 'Autenticación',
+ 'onboarding.authDesc': 'Elige cómo autenticarte con Home Assistant.',
+ 'onboarding.tokenMethod': 'Token de acceso de larga duración',
+ 'onboarding.tokenMethodDesc': 'Usa un token generado desde tu perfil de Home Assistant.',
+ 'onboarding.tokenPlaceholder': 'Pega tu token aquí',
+ 'onboarding.tokenHint': 'Cómo crear un token:',
+ 'onboarding.tokenHintDetail': 'Ve a tu Perfil de HA → Tokens de larga duración → Crear token',
+ 'onboarding.oauthMethod': 'Iniciar sesión con Home Assistant',
+ 'onboarding.oauthMethodDesc': 'Autentícate usando tus credenciales de Home Assistant vía OAuth2.',
+ 'onboarding.openHA': 'Abrir Home Assistant',
+ 'onboarding.completeSetup': 'Completar configuración',
+ 'onboarding.urlError': 'La URL debe comenzar con http:// o https://',
+ 'onboarding.connectionFailed': 'Conexión fallida (HTTP {status}). Verifica la URL e intenta nuevamente.',
+ 'onboarding.notReachable': 'No se pudo alcanzar Home Assistant. Verifica la URL y asegúrate de que sea accesible.',
+ 'onboarding.tokenRequired': 'Por favor ingresa tu token de acceso.',
};
diff --git a/beacon/src/i18n/translations/fr.ts b/beacon/src/i18n/translations/fr.ts
index 0aba3a56..f55d0549 100644
--- a/beacon/src/i18n/translations/fr.ts
+++ b/beacon/src/i18n/translations/fr.ts
@@ -431,4 +431,108 @@ export const fr: Record = {
'photoFrame.pauseSlideshow': 'Mettre le diaporama en pause',
'photoFrame.resumeSlideshow': 'Reprendre le diaporama',
'photoFrame.nextPhoto': 'Photo suivante',
+
+ // ---- chores view ----
+ 'chores.title': 'Tâches',
+ 'chores.addChore': 'Ajouter une tâche',
+ 'chores.editChore': 'Modifier la tâche',
+ 'chores.newChore': 'Nouvelle tâche',
+ 'chores.claim': 'Revendiquer',
+ 'chores.complete': 'Terminer',
+ 'chores.whoClaims': 'Qui revendique cela ?',
+ 'chores.whoCompleted': 'Qui a terminé cela ?',
+ 'chores.editChoreAria': 'Modifier {name}',
+ 'chores.deleteChoreAria': 'Supprimer {name}',
+ 'chores.name': 'Nom',
+ 'chores.value': 'Valeur',
+ 'chores.frequency': 'Fréquence',
+ 'chores.assignedTo': 'Assigné à',
+ 'chores.assignTo': 'Assigner à',
+ 'chores.icon': 'Icône',
+ 'chores.daily': 'Quotidien',
+ 'chores.weekly': 'Hebdomadaire',
+ 'chores.monthly': 'Mensuel',
+ 'chores.oneTime': 'Une fois',
+ 'chores.namePlaceholder': 'p. ex., Passer l\'aspirateur',
+ 'chores.anyone': 'N\'importe qui',
+ 'chores.empty': 'Pas encore de tâches — ajoutez-en une',
+ 'chores.addMembersFirst': 'Ajoutez d\'abord des membres de la famille pour assigner des tâches.',
+ 'chores.openChores': 'Tâches ouvertes',
+ 'chores.noChoresAssigned': 'Aucune tâche assignée',
+ 'chores.addChoreFor': 'Ajouter une tâche pour {name}',
+ 'chores.saveChanges': 'Enregistrer les modifications',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': 'Classement',
+ 'leaderboard.thisWeek': 'Cette semaine',
+ 'leaderboard.thisMonth': 'Ce mois-ci',
+ 'leaderboard.empty': 'Ajoutez des membres de la famille et des tâches pour voir le classement.',
+ 'leaderboard.choresCompleted': '{count} tâches',
+ 'leaderboard.choreCompletedSingular': '{count} tâche',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': 'Série la plus longue : {count} jours',
+ 'streak.best': 'Meilleur :',
+
+ // ---- music view ----
+ 'music.noPlayers': 'Aucun lecteur multimédia',
+ 'music.noPlayersDesc': 'Connectez un lecteur multimédia dans Home Assistant pour contrôler la musique ici.',
+ 'music.selectPlayer': 'Sélectionner un lecteur',
+ 'music.nowPlaying': 'En cours de lecture',
+ 'music.volume': 'Volume',
+ 'music.openMusicView': 'Ouvrir la vue musicale',
+ 'music.previousTrack': 'Piste précédente',
+ 'music.nextTrack': 'Piste suivante',
+ 'music.play': 'Lecture',
+ 'music.pause': 'Pause',
+ 'music.mute': 'Muet',
+ 'music.unmute': 'Rétablir le son',
+ 'music.albumArt': 'Pochette d\'album',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': 'Aucune liste disponible',
+ 'grocery.addItem': 'Ajouter un article',
+ 'grocery.addItemPlaceholder': 'Ajouter un article...',
+ 'grocery.loadingItems': 'Chargement des articles...',
+ 'grocery.empty': 'Pas encore d\'articles — ajoutez-en un ci-dessus',
+ 'grocery.items': 'Articles ({count})',
+ 'grocery.completed': 'Terminés ({count})',
+ 'grocery.deleteItem': 'Supprimer l\'article',
+ 'grocery.haList': '{name} (HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': 'Ajouter une carte',
+ 'cardPicker.empty': 'Aucun type de carte disponible pour cette zone.',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': 'Entités',
+
+ // ---- view name modal ----
+ 'viewName.name': 'Nom',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Bienvenue sur Beacon',
+ 'onboarding.welcomeDesc': 'Connectez votre Home Assistant pour commencer.',
+ 'onboarding.getStarted': 'Commencer',
+ 'onboarding.connectTitle': 'Se connecter à Home Assistant',
+ 'onboarding.connectDesc': 'Entrez l\'URL de votre instance Home Assistant.',
+ 'onboarding.haUrl': 'URL de Home Assistant',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': 'Connecter',
+ 'onboarding.connecting': 'Connexion en cours...',
+ 'onboarding.authTitle': 'Authentification',
+ 'onboarding.authDesc': 'Choisissez comment vous authentifier avec Home Assistant.',
+ 'onboarding.tokenMethod': 'Jeton d\'accès de longue durée',
+ 'onboarding.tokenMethodDesc': 'Utilisez un jeton généré depuis votre profil Home Assistant.',
+ 'onboarding.tokenPlaceholder': 'Collez votre jeton ici',
+ 'onboarding.tokenHint': 'Comment créer un jeton :',
+ 'onboarding.tokenHintDetail': 'Allez dans votre Profil HA → Jetons de longue durée → Créer un jeton',
+ 'onboarding.oauthMethod': 'Se connecter avec Home Assistant',
+ 'onboarding.oauthMethodDesc': 'Authentifiez-vous en utilisant vos identifiants Home Assistant via OAuth2.',
+ 'onboarding.openHA': 'Ouvrir Home Assistant',
+ 'onboarding.completeSetup': 'Terminer la configuration',
+ 'onboarding.urlError': 'L\'URL doit commencer par http:// ou https://',
+ 'onboarding.connectionFailed': 'Échec de la connexion (HTTP {status}). Vérifiez l\'URL et réessayez.',
+ 'onboarding.notReachable': 'Impossible d\'atteindre Home Assistant. Vérifiez l\'URL et assurez-vous qu\'elle est accessible.',
+ 'onboarding.tokenRequired': 'Veuillez entrer votre jeton d\'accès.',
};
diff --git a/beacon/src/i18n/translations/it.ts b/beacon/src/i18n/translations/it.ts
index 5fb614f9..b70f375e 100644
--- a/beacon/src/i18n/translations/it.ts
+++ b/beacon/src/i18n/translations/it.ts
@@ -431,4 +431,108 @@ export const it: Record = {
'photoFrame.pauseSlideshow': 'Metti in pausa la presentazione',
'photoFrame.resumeSlideshow': 'Riprendi la presentazione',
'photoFrame.nextPhoto': 'Foto successiva',
+
+ // ---- chores view ----
+ 'chores.title': 'Faccende',
+ 'chores.addChore': 'Aggiungi faccenda',
+ 'chores.editChore': 'Modifica faccenda',
+ 'chores.newChore': 'Nuova faccenda',
+ 'chores.claim': 'Rivendica',
+ 'chores.complete': 'Completa',
+ 'chores.whoClaims': 'Chi rivendica questo?',
+ 'chores.whoCompleted': 'Chi ha completato questo?',
+ 'chores.editChoreAria': 'Modifica {name}',
+ 'chores.deleteChoreAria': 'Elimina {name}',
+ 'chores.name': 'Nome',
+ 'chores.value': 'Valore',
+ 'chores.frequency': 'Frequenza',
+ 'chores.assignedTo': 'Assegnato a',
+ 'chores.assignTo': 'Assegna a',
+ 'chores.icon': 'Icona',
+ 'chores.daily': 'Giornaliero',
+ 'chores.weekly': 'Settimanale',
+ 'chores.monthly': 'Mensile',
+ 'chores.oneTime': 'Una volta',
+ 'chores.namePlaceholder': 'es., Aspirare il soggiorno',
+ 'chores.anyone': 'Chiunque',
+ 'chores.empty': 'Nessuna faccenda ancora — aggiungine una',
+ 'chores.addMembersFirst': 'Aggiungi prima i membri della famiglia per assegnare le faccende.',
+ 'chores.openChores': 'Faccende aperte',
+ 'chores.noChoresAssigned': 'Nessuna faccenda assegnata',
+ 'chores.addChoreFor': 'Aggiungi faccenda per {name}',
+ 'chores.saveChanges': 'Salva modifiche',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': 'Classifica',
+ 'leaderboard.thisWeek': 'Questa settimana',
+ 'leaderboard.thisMonth': 'Questo mese',
+ 'leaderboard.empty': 'Aggiungi membri della famiglia e faccende per vedere la classifica.',
+ 'leaderboard.choresCompleted': '{count} faccende',
+ 'leaderboard.choreCompletedSingular': '{count} faccenda',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': 'Serie più lunga: {count} giorni',
+ 'streak.best': 'Migliore:',
+
+ // ---- music view ----
+ 'music.noPlayers': 'Nessun lettore multimediale',
+ 'music.noPlayersDesc': 'Collega un lettore multimediale in Home Assistant per controllare la musica da qui.',
+ 'music.selectPlayer': 'Seleziona lettore',
+ 'music.nowPlaying': 'In riproduzione',
+ 'music.volume': 'Volume',
+ 'music.openMusicView': 'Apri vista musica',
+ 'music.previousTrack': 'Traccia precedente',
+ 'music.nextTrack': 'Traccia successiva',
+ 'music.play': 'Riproduci',
+ 'music.pause': 'Pausa',
+ 'music.mute': 'Muto',
+ 'music.unmute': 'Riattiva audio',
+ 'music.albumArt': 'Copertina album',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': 'Nessuna lista disponibile',
+ 'grocery.addItem': 'Aggiungi articolo',
+ 'grocery.addItemPlaceholder': 'Aggiungi un articolo...',
+ 'grocery.loadingItems': 'Caricamento articoli...',
+ 'grocery.empty': 'Nessun articolo ancora — aggiungine uno sopra',
+ 'grocery.items': 'Articoli ({count})',
+ 'grocery.completed': 'Completati ({count})',
+ 'grocery.deleteItem': 'Elimina articolo',
+ 'grocery.haList': '{name} (HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': 'Aggiungi scheda',
+ 'cardPicker.empty': 'Nessun tipo di scheda disponibile per quest\'area.',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': 'Entità',
+
+ // ---- view name modal ----
+ 'viewName.name': 'Nome',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Benvenuto su Beacon',
+ 'onboarding.welcomeDesc': 'Collega il tuo Home Assistant per iniziare.',
+ 'onboarding.getStarted': 'Inizia',
+ 'onboarding.connectTitle': 'Collega a Home Assistant',
+ 'onboarding.connectDesc': 'Inserisci l\'URL della tua istanza Home Assistant.',
+ 'onboarding.haUrl': 'URL di Home Assistant',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': 'Collega',
+ 'onboarding.connecting': 'Connessione in corso...',
+ 'onboarding.authTitle': 'Autenticazione',
+ 'onboarding.authDesc': 'Scegli come autenticarti con Home Assistant.',
+ 'onboarding.tokenMethod': 'Token di accesso a lungo termine',
+ 'onboarding.tokenMethodDesc': 'Usa un token generato dal tuo profilo Home Assistant.',
+ 'onboarding.tokenPlaceholder': 'Incolla il tuo token qui',
+ 'onboarding.tokenHint': 'Come creare un token:',
+ 'onboarding.tokenHintDetail': 'Vai al tuo Profilo HA → Token a lungo termine → Crea token',
+ 'onboarding.oauthMethod': 'Accedi con Home Assistant',
+ 'onboarding.oauthMethodDesc': 'Autenticati utilizzando le tue credenziali Home Assistant tramite OAuth2.',
+ 'onboarding.openHA': 'Apri Home Assistant',
+ 'onboarding.completeSetup': 'Completa configurazione',
+ 'onboarding.urlError': 'L\'URL deve iniziare con http:// o https://',
+ 'onboarding.connectionFailed': 'Connessione fallita (HTTP {status}). Controlla l\'URL e riprova.',
+ 'onboarding.notReachable': 'Impossibile raggiungere Home Assistant. Verifica l\'URL e assicurati che sia accessibile.',
+ 'onboarding.tokenRequired': 'Inserisci il tuo token di accesso.',
};
diff --git a/beacon/src/i18n/translations/ja.ts b/beacon/src/i18n/translations/ja.ts
index 9ed214b8..d22a8a87 100644
--- a/beacon/src/i18n/translations/ja.ts
+++ b/beacon/src/i18n/translations/ja.ts
@@ -431,4 +431,108 @@ export const ja: Record = {
'photoFrame.pauseSlideshow': 'スライドショーを一時停止',
'photoFrame.resumeSlideshow': 'スライドショーを再開',
'photoFrame.nextPhoto': '次の写真',
+
+ // ---- chores view ----
+ 'chores.title': '家事',
+ 'chores.addChore': '家事を追加',
+ 'chores.editChore': '家事を編集',
+ 'chores.newChore': '新しい家事',
+ 'chores.claim': '引き受ける',
+ 'chores.complete': '完了する',
+ 'chores.whoClaims': '誰が引き受けますか?',
+ 'chores.whoCompleted': '誰が完了しましたか?',
+ 'chores.editChoreAria': '{name}を編集',
+ 'chores.deleteChoreAria': '{name}を削除',
+ 'chores.name': '名前',
+ 'chores.value': '価値',
+ 'chores.frequency': '頻度',
+ 'chores.assignedTo': '担当者',
+ 'chores.assignTo': '割り当て先',
+ 'chores.icon': 'アイコン',
+ 'chores.daily': '毎日',
+ 'chores.weekly': '毎週',
+ 'chores.monthly': '毎月',
+ 'chores.oneTime': '一回のみ',
+ 'chores.namePlaceholder': '例:リビングを掃除機で掃除',
+ 'chores.anyone': '誰でも',
+ 'chores.empty': 'まだ家事がありません — 追加してください',
+ 'chores.addMembersFirst': '家事を割り当てるには、まず家族を追加してください。',
+ 'chores.openChores': '未完了の家事',
+ 'chores.noChoresAssigned': '割り当てられた家事はありません',
+ 'chores.addChoreFor': '{name}の家事を追加',
+ 'chores.saveChanges': '変更を保存',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': 'リーダーボード',
+ 'leaderboard.thisWeek': '今週',
+ 'leaderboard.thisMonth': '今月',
+ 'leaderboard.empty': '家族と家事を追加して、リーダーボードを表示してください。',
+ 'leaderboard.choresCompleted': '{count}件の家事',
+ 'leaderboard.choreCompletedSingular': '{count}件の家事',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': '最長連続記録:{count}日',
+ 'streak.best': '最高:',
+
+ // ---- music view ----
+ 'music.noPlayers': 'メディアプレーヤーがありません',
+ 'music.noPlayersDesc': 'Home Assistantでメディアプレーヤーを接続して、ここから音楽を制御してください。',
+ 'music.selectPlayer': 'プレーヤーを選択',
+ 'music.nowPlaying': '再生中',
+ 'music.volume': '音量',
+ 'music.openMusicView': '音楽ビューを開く',
+ 'music.previousTrack': '前のトラック',
+ 'music.nextTrack': '次のトラック',
+ 'music.play': '再生',
+ 'music.pause': '一時停止',
+ 'music.mute': 'ミュート',
+ 'music.unmute': 'ミュート解除',
+ 'music.albumArt': 'アルバムアート',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': '利用可能なリストがありません',
+ 'grocery.addItem': 'アイテムを追加',
+ 'grocery.addItemPlaceholder': 'アイテムを追加...',
+ 'grocery.loadingItems': 'アイテムを読み込み中...',
+ 'grocery.empty': 'まだアイテムがありません — 上に追加してください',
+ 'grocery.items': 'アイテム({count})',
+ 'grocery.completed': '完了済み({count})',
+ 'grocery.deleteItem': 'アイテムを削除',
+ 'grocery.haList': '{name}(HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': 'カードを追加',
+ 'cardPicker.empty': 'このエリアに利用可能なカードタイプはありません。',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': 'エンティティ',
+
+ // ---- view name modal ----
+ 'viewName.name': '名前',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Beaconへようこそ',
+ 'onboarding.welcomeDesc': 'Home Assistantを接続して始めましょう。',
+ 'onboarding.getStarted': '始める',
+ 'onboarding.connectTitle': 'Home Assistantに接続',
+ 'onboarding.connectDesc': 'Home AssistantインスタンスのURLを入力してください。',
+ 'onboarding.haUrl': 'Home Assistant URL',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': '接続',
+ 'onboarding.connecting': '接続中...',
+ 'onboarding.authTitle': '認証',
+ 'onboarding.authDesc': 'Home Assistantへの認証方法を選択してください。',
+ 'onboarding.tokenMethod': '長期アクセストークン',
+ 'onboarding.tokenMethodDesc': 'Home Assistantプロファイルから生成されたトークンを使用します。',
+ 'onboarding.tokenPlaceholder': 'トークンをここに貼り付けてください',
+ 'onboarding.tokenHint': 'トークンの作成方法:',
+ 'onboarding.tokenHintDetail': 'HAプロファイル → 長期トークン → トークンを作成',
+ 'onboarding.oauthMethod': 'Home Assistantでサインイン',
+ 'onboarding.oauthMethodDesc': 'OAuth2を介してHome Assistant認証情報を使用して認証します。',
+ 'onboarding.openHA': 'Home Assistantを開く',
+ 'onboarding.completeSetup': 'セットアップを完了',
+ 'onboarding.urlError': 'URLはhttp://またはhttps://で始まる必要があります',
+ 'onboarding.connectionFailed': '接続に失敗しました(HTTP {status})。URLを確認して再試行してください。',
+ 'onboarding.notReachable': 'Home Assistantに到達できませんでした。URLを確認し、アクセス可能であることを確認してください。',
+ 'onboarding.tokenRequired': 'アクセストークンを入力してください。',
};
diff --git a/beacon/src/i18n/translations/ko.ts b/beacon/src/i18n/translations/ko.ts
index c695306f..35d6cc93 100644
--- a/beacon/src/i18n/translations/ko.ts
+++ b/beacon/src/i18n/translations/ko.ts
@@ -431,4 +431,108 @@ export const ko: Record = {
'photoFrame.pauseSlideshow': '슬라이드쇼 일시정지',
'photoFrame.resumeSlideshow': '슬라이드쇼 재개',
'photoFrame.nextPhoto': '다음 사진',
+
+ // ---- chores view ----
+ 'chores.title': '집안일',
+ 'chores.addChore': '집안일 추가',
+ 'chores.editChore': '집안일 수정',
+ 'chores.newChore': '새 집안일',
+ 'chores.claim': '맡기',
+ 'chores.complete': '완료',
+ 'chores.whoClaims': '누가 이것을 맡나요?',
+ 'chores.whoCompleted': '누가 이것을 완료했나요?',
+ 'chores.editChoreAria': '{name} 수정',
+ 'chores.deleteChoreAria': '{name} 삭제',
+ 'chores.name': '이름',
+ 'chores.value': '가치',
+ 'chores.frequency': '빈도',
+ 'chores.assignedTo': '담당자',
+ 'chores.assignTo': '배정',
+ 'chores.icon': '아이콘',
+ 'chores.daily': '매일',
+ 'chores.weekly': '매주',
+ 'chores.monthly': '매월',
+ 'chores.oneTime': '한 번',
+ 'chores.namePlaceholder': '예: 거실 청소',
+ 'chores.anyone': '누구나',
+ 'chores.empty': '아직 집안일이 없습니다 — 추가하세요',
+ 'chores.addMembersFirst': '집안일을 배정하려면 먼저 가족 구성원을 추가하세요.',
+ 'chores.openChores': '미완료 집안일',
+ 'chores.noChoresAssigned': '배정된 집안일이 없습니다',
+ 'chores.addChoreFor': '{name}의 집안일 추가',
+ 'chores.saveChanges': '변경사항 저장',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': '리더보드',
+ 'leaderboard.thisWeek': '이번 주',
+ 'leaderboard.thisMonth': '이번 달',
+ 'leaderboard.empty': '가족 구성원과 집안일을 추가하여 리더보드를 확인하세요.',
+ 'leaderboard.choresCompleted': '{count}개 집안일',
+ 'leaderboard.choreCompletedSingular': '{count}개 집안일',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': '최장 연속 기록: {count}일',
+ 'streak.best': '최고:',
+
+ // ---- music view ----
+ 'music.noPlayers': '미디어 플레이어 없음',
+ 'music.noPlayersDesc': 'Home Assistant에서 미디어 플레이어를 연결하여 여기에서 음악을 제어하세요.',
+ 'music.selectPlayer': '플레이어 선택',
+ 'music.nowPlaying': '재생 중',
+ 'music.volume': '볼륨',
+ 'music.openMusicView': '음악 보기 열기',
+ 'music.previousTrack': '이전 트랙',
+ 'music.nextTrack': '다음 트랙',
+ 'music.play': '재생',
+ 'music.pause': '일시 정지',
+ 'music.mute': '음소거',
+ 'music.unmute': '음소거 해제',
+ 'music.albumArt': '앨범 아트',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': '사용 가능한 목록이 없습니다',
+ 'grocery.addItem': '항목 추가',
+ 'grocery.addItemPlaceholder': '항목 추가...',
+ 'grocery.loadingItems': '항목 로딩 중...',
+ 'grocery.empty': '아직 항목이 없습니다 — 위에 추가하세요',
+ 'grocery.items': '항목 ({count})',
+ 'grocery.completed': '완료 ({count})',
+ 'grocery.deleteItem': '항목 삭제',
+ 'grocery.haList': '{name} (HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': '카드 추가',
+ 'cardPicker.empty': '이 영역에 사용 가능한 카드 유형이 없습니다.',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': '엔티티',
+
+ // ---- view name modal ----
+ 'viewName.name': '이름',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Beacon에 오신 것을 환영합니다',
+ 'onboarding.welcomeDesc': 'Home Assistant를 연결하여 시작하세요.',
+ 'onboarding.getStarted': '시작하기',
+ 'onboarding.connectTitle': 'Home Assistant에 연결',
+ 'onboarding.connectDesc': 'Home Assistant 인스턴스의 URL을 입력하세요.',
+ 'onboarding.haUrl': 'Home Assistant URL',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': '연결',
+ 'onboarding.connecting': '연결 중...',
+ 'onboarding.authTitle': '인증',
+ 'onboarding.authDesc': 'Home Assistant 인증 방법을 선택하세요.',
+ 'onboarding.tokenMethod': '장기 액세스 토큰',
+ 'onboarding.tokenMethodDesc': 'Home Assistant 프로필에서 생성된 토큰을 사용하세요.',
+ 'onboarding.tokenPlaceholder': '여기에 토큰을 붙여넣으세요',
+ 'onboarding.tokenHint': '토큰 생성 방법:',
+ 'onboarding.tokenHintDetail': 'HA 프로필 → 장기 토큰 → 토큰 생성',
+ 'onboarding.oauthMethod': 'Home Assistant로 로그인',
+ 'onboarding.oauthMethodDesc': 'OAuth2를 통해 Home Assistant 자격 증명을 사용하여 인증하세요.',
+ 'onboarding.openHA': 'Home Assistant 열기',
+ 'onboarding.completeSetup': '설정 완료',
+ 'onboarding.urlError': 'URL은 http:// 또는 https://로 시작해야 합니다',
+ 'onboarding.connectionFailed': '연결 실패 (HTTP {status}). URL을 확인하고 다시 시도하세요.',
+ 'onboarding.notReachable': 'Home Assistant에 연결할 수 없습니다. URL을 확인하고 접근 가능한지 확인하세요.',
+ 'onboarding.tokenRequired': '액세스 토큰을 입력하세요.',
};
diff --git a/beacon/src/i18n/translations/nl.ts b/beacon/src/i18n/translations/nl.ts
index 7ceb5248..865e410e 100644
--- a/beacon/src/i18n/translations/nl.ts
+++ b/beacon/src/i18n/translations/nl.ts
@@ -431,4 +431,108 @@ export const nl: Record = {
'photoFrame.pauseSlideshow': 'Diavoorstelling pauzeren',
'photoFrame.resumeSlideshow': 'Diavoorstelling hervatten',
'photoFrame.nextPhoto': 'Volgende foto',
+
+ // ---- chores view ----
+ 'chores.title': 'Karweitjes',
+ 'chores.addChore': 'Karweitje toevoegen',
+ 'chores.editChore': 'Karweitje bewerken',
+ 'chores.newChore': 'Nieuw karweitje',
+ 'chores.claim': 'Claimen',
+ 'chores.complete': 'Voltooien',
+ 'chores.whoClaims': 'Wie claimt dit?',
+ 'chores.whoCompleted': 'Wie heeft dit voltooid?',
+ 'chores.editChoreAria': '{name} bewerken',
+ 'chores.deleteChoreAria': '{name} verwijderen',
+ 'chores.name': 'Naam',
+ 'chores.value': 'Waarde',
+ 'chores.frequency': 'Frequentie',
+ 'chores.assignedTo': 'Toegewezen aan',
+ 'chores.assignTo': 'Toewijzen aan',
+ 'chores.icon': 'Pictogram',
+ 'chores.daily': 'Dagelijks',
+ 'chores.weekly': 'Wekelijks',
+ 'chores.monthly': 'Maandelijks',
+ 'chores.oneTime': 'Eenmalig',
+ 'chores.namePlaceholder': 'bijv. Woonkamer stofzuigen',
+ 'chores.anyone': 'Iedereen',
+ 'chores.empty': 'Nog geen karweitjes — voeg er een toe',
+ 'chores.addMembersFirst': 'Voeg eerst gezinsleden toe om karweitjes toe te wijzen.',
+ 'chores.openChores': 'Open karweitjes',
+ 'chores.noChoresAssigned': 'Geen karweitjes toegewezen',
+ 'chores.addChoreFor': 'Karweitje toevoegen voor {name}',
+ 'chores.saveChanges': 'Wijzigingen opslaan',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': 'Klassement',
+ 'leaderboard.thisWeek': 'Deze week',
+ 'leaderboard.thisMonth': 'Deze maand',
+ 'leaderboard.empty': 'Voeg gezinsleden en karweitjes toe om het klassement te zien.',
+ 'leaderboard.choresCompleted': '{count} karweitjes',
+ 'leaderboard.choreCompletedSingular': '{count} karweitje',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': 'Langste reeks: {count} dagen',
+ 'streak.best': 'Beste:',
+
+ // ---- music view ----
+ 'music.noPlayers': 'Geen mediaspelers',
+ 'music.noPlayersDesc': 'Verbind een mediaspeler in Home Assistant om hier muziek te bedienen.',
+ 'music.selectPlayer': 'Speler selecteren',
+ 'music.nowPlaying': 'Nu bezig',
+ 'music.volume': 'Volume',
+ 'music.openMusicView': 'Muziekweergave openen',
+ 'music.previousTrack': 'Vorig nummer',
+ 'music.nextTrack': 'Volgend nummer',
+ 'music.play': 'Afspelen',
+ 'music.pause': 'Pauzeren',
+ 'music.mute': 'Dempen',
+ 'music.unmute': 'Geluid aan',
+ 'music.albumArt': 'Albumhoes',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': 'Geen lijsten beschikbaar',
+ 'grocery.addItem': 'Item toevoegen',
+ 'grocery.addItemPlaceholder': 'Voeg een item toe...',
+ 'grocery.loadingItems': 'Items laden...',
+ 'grocery.empty': 'Nog geen items — voeg er een toe hierboven',
+ 'grocery.items': 'Items ({count})',
+ 'grocery.completed': 'Voltooid ({count})',
+ 'grocery.deleteItem': 'Item verwijderen',
+ 'grocery.haList': '{name} (HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': 'Kaart toevoegen',
+ 'cardPicker.empty': 'Geen kaarttypen beschikbaar voor dit gebied.',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': 'Entiteiten',
+
+ // ---- view name modal ----
+ 'viewName.name': 'Naam',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Welkom bij Beacon',
+ 'onboarding.welcomeDesc': 'Verbind je Home Assistant om te beginnen.',
+ 'onboarding.getStarted': 'Aan de slag',
+ 'onboarding.connectTitle': 'Verbinden met Home Assistant',
+ 'onboarding.connectDesc': 'Voer de URL van je Home Assistant-instantie in.',
+ 'onboarding.haUrl': 'Home Assistant URL',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': 'Verbinden',
+ 'onboarding.connecting': 'Verbinden...',
+ 'onboarding.authTitle': 'Authenticatie',
+ 'onboarding.authDesc': 'Kies hoe je wilt authenticeren met Home Assistant.',
+ 'onboarding.tokenMethod': 'Langdurig toegangstoken',
+ 'onboarding.tokenMethodDesc': 'Gebruik een token gegenereerd vanuit je Home Assistant-profiel.',
+ 'onboarding.tokenPlaceholder': 'Plak je token hier',
+ 'onboarding.tokenHint': 'Hoe maak je een token:',
+ 'onboarding.tokenHintDetail': 'Ga naar je HA-profiel → Langdurige tokens → Token aanmaken',
+ 'onboarding.oauthMethod': 'Inloggen met Home Assistant',
+ 'onboarding.oauthMethodDesc': 'Authenticeer met je Home Assistant-referenties via OAuth2.',
+ 'onboarding.openHA': 'Home Assistant openen',
+ 'onboarding.completeSetup': 'Installatie voltooien',
+ 'onboarding.urlError': 'URL moet beginnen met http:// of https://',
+ 'onboarding.connectionFailed': 'Verbinding mislukt (HTTP {status}). Controleer de URL en probeer opnieuw.',
+ 'onboarding.notReachable': 'Kan Home Assistant niet bereiken. Controleer de URL en zorg ervoor dat het toegankelijk is.',
+ 'onboarding.tokenRequired': 'Voer je toegangstoken in.',
};
diff --git a/beacon/src/i18n/translations/pt.ts b/beacon/src/i18n/translations/pt.ts
index 06b8b67b..352a1af4 100644
--- a/beacon/src/i18n/translations/pt.ts
+++ b/beacon/src/i18n/translations/pt.ts
@@ -431,4 +431,108 @@ export const pt: Record = {
'photoFrame.pauseSlideshow': 'Pausar apresentação',
'photoFrame.resumeSlideshow': 'Retomar apresentação',
'photoFrame.nextPhoto': 'Próxima foto',
+
+ // ---- chores view ----
+ 'chores.title': 'Tarefas',
+ 'chores.addChore': 'Adicionar tarefa',
+ 'chores.editChore': 'Editar tarefa',
+ 'chores.newChore': 'Nova tarefa',
+ 'chores.claim': 'Reivindicar',
+ 'chores.complete': 'Concluir',
+ 'chores.whoClaims': 'Quem reivindica isto?',
+ 'chores.whoCompleted': 'Quem concluiu isto?',
+ 'chores.editChoreAria': 'Editar {name}',
+ 'chores.deleteChoreAria': 'Excluir {name}',
+ 'chores.name': 'Nome',
+ 'chores.value': 'Valor',
+ 'chores.frequency': 'Frequência',
+ 'chores.assignedTo': 'Atribuído a',
+ 'chores.assignTo': 'Atribuir a',
+ 'chores.icon': 'Ícone',
+ 'chores.daily': 'Diário',
+ 'chores.weekly': 'Semanal',
+ 'chores.monthly': 'Mensal',
+ 'chores.oneTime': 'Uma vez',
+ 'chores.namePlaceholder': 'ex: Aspirar a sala',
+ 'chores.anyone': 'Qualquer um',
+ 'chores.empty': 'Ainda não há tarefas — adicione uma',
+ 'chores.addMembersFirst': 'Adicione membros da família primeiro para atribuir tarefas.',
+ 'chores.openChores': 'Tarefas abertas',
+ 'chores.noChoresAssigned': 'Nenhuma tarefa atribuída',
+ 'chores.addChoreFor': 'Adicionar tarefa para {name}',
+ 'chores.saveChanges': 'Salvar alterações',
+
+ // ---- leaderboard ----
+ 'leaderboard.title': 'Classificação',
+ 'leaderboard.thisWeek': 'Esta semana',
+ 'leaderboard.thisMonth': 'Este mês',
+ 'leaderboard.empty': 'Adicione membros da família e tarefas para ver a classificação.',
+ 'leaderboard.choresCompleted': '{count} tarefas',
+ 'leaderboard.choreCompletedSingular': '{count} tarefa',
+
+ // ---- streak badge ----
+ 'streak.longestTooltip': 'Sequência mais longa: {count} dias',
+ 'streak.best': 'Melhor:',
+
+ // ---- music view ----
+ 'music.noPlayers': 'Nenhum reprodutor de mídia',
+ 'music.noPlayersDesc': 'Conecte um reprodutor de mídia no Home Assistant para controlar música aqui.',
+ 'music.selectPlayer': 'Selecionar reprodutor',
+ 'music.nowPlaying': 'Tocando agora',
+ 'music.volume': 'Volume',
+ 'music.openMusicView': 'Abrir visualização de música',
+ 'music.previousTrack': 'Faixa anterior',
+ 'music.nextTrack': 'Próxima faixa',
+ 'music.play': 'Reproduzir',
+ 'music.pause': 'Pausar',
+ 'music.mute': 'Silenciar',
+ 'music.unmute': 'Ativar som',
+ 'music.albumArt': 'Capa do álbum',
+
+ // ---- grocery / lists view ----
+ 'grocery.noListsAvailable': 'Nenhuma lista disponível',
+ 'grocery.addItem': 'Adicionar item',
+ 'grocery.addItemPlaceholder': 'Adicionar um item...',
+ 'grocery.loadingItems': 'Carregando itens...',
+ 'grocery.empty': 'Ainda não há itens — adicione um acima',
+ 'grocery.items': 'Itens ({count})',
+ 'grocery.completed': 'Concluídos ({count})',
+ 'grocery.deleteItem': 'Excluir item',
+ 'grocery.haList': '{name} (HA)',
+
+ // ---- card picker modal ----
+ 'cardPicker.title': 'Adicionar cartão',
+ 'cardPicker.empty': 'Nenhum tipo de cartão disponível para esta área.',
+
+ // ---- card config modal (common) ----
+ 'cardConfig.entitiesPlaceholder': 'Entidades',
+
+ // ---- view name modal ----
+ 'viewName.name': 'Nome',
+
+ // ---- onboarding ----
+ 'onboarding.welcome': 'Bem-vindo ao Beacon',
+ 'onboarding.welcomeDesc': 'Conecte seu Home Assistant para começar.',
+ 'onboarding.getStarted': 'Começar',
+ 'onboarding.connectTitle': 'Conectar ao Home Assistant',
+ 'onboarding.connectDesc': 'Digite a URL da sua instância do Home Assistant.',
+ 'onboarding.haUrl': 'URL do Home Assistant',
+ 'onboarding.haUrlPlaceholder': 'https://homeassistant.local:8123',
+ 'onboarding.connect': 'Conectar',
+ 'onboarding.connecting': 'Conectando...',
+ 'onboarding.authTitle': 'Autenticação',
+ 'onboarding.authDesc': 'Escolha como autenticar com o Home Assistant.',
+ 'onboarding.tokenMethod': 'Token de acesso de longa duração',
+ 'onboarding.tokenMethodDesc': 'Use um token gerado do seu perfil do Home Assistant.',
+ 'onboarding.tokenPlaceholder': 'Cole seu token aqui',
+ 'onboarding.tokenHint': 'Como criar um token:',
+ 'onboarding.tokenHintDetail': 'Vá para seu Perfil HA → Tokens de longa duração → Criar token',
+ 'onboarding.oauthMethod': 'Entrar com Home Assistant',
+ 'onboarding.oauthMethodDesc': 'Autentique usando suas credenciais do Home Assistant via OAuth2.',
+ 'onboarding.openHA': 'Abrir Home Assistant',
+ 'onboarding.completeSetup': 'Concluir configuração',
+ 'onboarding.urlError': 'URL deve começar com http:// ou https://',
+ 'onboarding.connectionFailed': 'Conexão falhou (HTTP {status}). Verifique a URL e tente novamente.',
+ 'onboarding.notReachable': 'Não foi possível alcançar o Home Assistant. Verifique a URL e certifique-se de que esteja acessível.',
+ 'onboarding.tokenRequired': 'Por favor, insira seu token de acesso.',
};
diff --git a/src/components/ChoresView.tsx b/src/components/ChoresView.tsx
index 3485c92b..c1d4218d 100644
--- a/src/components/ChoresView.tsx
+++ b/src/components/ChoresView.tsx
@@ -6,6 +6,7 @@ import { StreakBadge } from './StreakBadge';
import { useChores } from '../hooks/useChores';
import { useFamily } from '../hooks/useFamily';
import { useSettings } from '../hooks/useSettings';
+import { useTranslation } from '../i18n';
const CHORE_ICONS = ['🧹', '🍽️', '🐕', '🛏️', '📚', '🗑️', '👕', '🧺', '🪥', '🚿', '🧼', '💪'];
@@ -36,6 +37,7 @@ function UnassignedChoreCard({
onDelete,
currencySymbol = '$',
}: UnassignedChoreCardProps) {
+ const { t } = useTranslation();
const [showMemberPicker, setShowMemberPicker] = useState(false);
const [pickerAction, setPickerAction] = useState<'claim' | 'complete'>('claim');
const [confirmDelete, setConfirmDelete] = useState(false);
@@ -80,7 +82,7 @@ function UnassignedChoreCard({
setShowMemberPicker(true);
}}
>
- Claim
+ {t('chores.claim')}
@@ -104,10 +106,10 @@ function UnassignedChoreCard({
type="button"
className="chore-card-action-btn chore-card-action-btn--danger"
onClick={handleDeleteClick}
- aria-label={`Delete ${chore.name}`}
+ aria-label={t('chores.deleteChoreAria', { name: chore.name })}
>
{confirmDelete ? (
- Sure?
+ {t('common.sure')}
) : (
)}
@@ -119,7 +121,7 @@ function UnassignedChoreCard({
e.stopPropagation()}>
- {pickerAction === 'claim' ? 'Who claims this?' : 'Who completed this?'}
+ {pickerAction === 'claim' ? t('chores.whoClaims') : t('chores.whoCompleted')}