@@ -57,17 +60,17 @@ export default function SharedAssessmentView({ assessment }) {
@@ -78,11 +81,11 @@ export default function SharedAssessmentView({ assessment }) {
@@ -121,7 +124,7 @@ export default function SharedAssessmentView({ assessment }) {
- Ergebnis
+ {t('shared.col_result')}
{totalPoints.toFixed(0)}
diff --git a/src/hooks/useUserProfile.js b/src/hooks/useUserProfile.js
index 1c94027..5e9171e 100644
--- a/src/hooks/useUserProfile.js
+++ b/src/hooks/useUserProfile.js
@@ -7,6 +7,7 @@ const buildDefaultProfile = (email = '') => ({
firstName: '',
lastName: '',
country: '',
+ language: 'de',
testsVisible: false,
email
});
@@ -15,6 +16,7 @@ const mapFromDb = (row, email = '') => ({
firstName: row?.first_name || '',
lastName: row?.last_name || '',
country: row?.country || '',
+ language: row?.language || 'de',
testsVisible: Boolean(row?.tests_visible),
email
});
@@ -24,6 +26,7 @@ const mapToDb = (profile, userId) => ({
first_name: profile.firstName.trim(),
last_name: profile.lastName.trim(),
country: profile.country.trim(),
+ language: profile.language || 'de',
tests_visible: Boolean(profile.testsVisible)
});
@@ -62,7 +65,7 @@ export const useUserProfile = (session) => {
try {
const { data, error: loadError } = await supabase
.from('user_profiles')
- .select('first_name, last_name, country, tests_visible')
+ .select('first_name, last_name, country, language, tests_visible')
.eq('user_id', userId)
.maybeSingle();
@@ -112,7 +115,7 @@ export const useUserProfile = (session) => {
const { data, error: saveError } = await supabase
.from('user_profiles')
.upsert(mapToDb(nextValues, userId), { onConflict: 'user_id' })
- .select('first_name, last_name, country, tests_visible')
+ .select('first_name, last_name, country, language, tests_visible')
.single();
if (saveError) throw saveError;
diff --git a/src/i18n/LanguageContext.jsx b/src/i18n/LanguageContext.jsx
new file mode 100644
index 0000000..a14f3a2
--- /dev/null
+++ b/src/i18n/LanguageContext.jsx
@@ -0,0 +1,18 @@
+import React, { createContext, useContext, useMemo } from 'react';
+import { getTranslator } from './translations';
+
+const LanguageContext = createContext(() => (key) => key);
+
+export function LanguageProvider({ language, children }) {
+ const t = useMemo(() => getTranslator(language), [language]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTranslation() {
+ return useContext(LanguageContext);
+}
diff --git a/src/i18n/translations.js b/src/i18n/translations.js
new file mode 100644
index 0000000..1b9836c
--- /dev/null
+++ b/src/i18n/translations.js
@@ -0,0 +1,1164 @@
+const translations = {
+ de: {
+ // ── Navigation / Header ──────────────────────────────────────────────────
+ 'nav.logged_in': 'Angemeldet',
+ 'nav.assessments': 'Bewertungen',
+ 'nav.analysis': 'Analyse',
+ 'nav.profile': 'Profil',
+ 'nav.sign_out': 'Abmelden',
+ 'nav.theme_dark': 'Dark Mode',
+ 'nav.theme_light': 'Light Mode',
+ 'nav.analysis_disabled': 'Analyse ist während der Bearbeitung deaktiviert',
+ 'nav.analysis_open': 'Analyse öffnen',
+ 'nav.profile_disabled': 'Profil ist während der Bearbeitung deaktiviert',
+ 'nav.profile_open': 'Profil öffnen',
+
+ // ── Profil ───────────────────────────────────────────────────────────────
+ 'profile.title_label': 'Benutzerprofil',
+ 'profile.title': 'Profil verwalten',
+ 'profile.description': 'Name, Vorname, Mailadresse, Land und die Sichtbarkeit deiner Testfreigaben werden hier gepflegt.',
+ 'profile.loading': 'Profil wird geladen...',
+ 'profile.first_name': 'Vorname',
+ 'profile.first_name_placeholder': 'Max',
+ 'profile.last_name': 'Nachname',
+ 'profile.last_name_placeholder': 'Mustermann',
+ 'profile.email': 'Mailadresse',
+ 'profile.email_hint': 'Wenn du die Mailadresse änderst, wird dir eine Bestätigung an die neue Adresse gesendet.',
+ 'profile.country': 'Land',
+ 'profile.country_none': 'Kein Land ausgewählt',
+ 'profile.country_hint': 'Dein Land',
+ 'profile.display': 'Anzeige',
+ 'profile.display_hint': 'Sprache, in der die Anwendung angezeigt wird.',
+ 'profile.rank': 'Rang Anzeige',
+ 'profile.rank_placeholder': 'Platzhalter',
+ 'profile.rank_hint': 'Hier kann später der Ranglistenplatz des Benutzers angezeigt werden.',
+ 'profile.tests_visible_title': 'Tests sichtbar',
+ 'profile.tests_visible_desc': 'Ist dieser Schalter aktiv, funktionieren deine Freigabelinks. Wenn du ihn deaktivierst, bleiben vorhandene Links gespeichert, sind aber nicht mehr öffentlich sichtbar.',
+ 'profile.visible': 'Sichtbar',
+ 'profile.not_visible': 'Nicht sichtbar',
+ 'profile.account_id': 'Konto-ID',
+ 'profile.delete': 'Profil löschen',
+ 'profile.deleting': 'Löscht...',
+ 'profile.save': 'Profil speichern',
+ 'profile.saving': 'Speichert...',
+ 'profile.saved': 'Profil gespeichert.',
+ 'profile.saved_email': 'Profil gespeichert. Die neue Mailadresse muss ggf. noch per E-Mail bestätigt werden.',
+ 'profile.error_save': 'Profil konnte nicht gespeichert werden.',
+ 'profile.error_delete': 'Profil konnte nicht gelöscht werden.',
+ 'profile.delete_confirm': 'Profil wirklich löschen?\n\nDabei werden alle Tests dieses Benutzers sowie vorhandene Analysepläne dauerhaft entfernt.',
+ 'profile.deleted': 'Profil und alle Tests wurden gelöscht. Das Konto bleibt bestehen und kann neu eingerichtet werden.',
+
+ // ── PAT Test Manager (Toast-Meldungen) ───────────────────────────────────
+ 'patmgr.saved': 'Erfolgreich gespeichert!',
+ 'patmgr.finalized': 'Bewertung wurde final abgeschlossen und ist jetzt nur noch lesbar.',
+ 'patmgr.fill_name_date': 'Bitte Name und Datum ausfüllen bevor Sie speichern',
+ 'patmgr.share_copied': 'Freigabelink für "{label}" wurde in die Zwischenablage kopiert.',
+ 'patmgr.share_localhost_hint': 'Hinweis: Der Link zeigt aktuell auf localhost und ist nur erreichbar, wenn die App von außen erreichbar ist.',
+ 'patmgr.share_link_label': 'Freigabelink für "{label}"',
+ 'patmgr.share_error': 'Freigabelink konnte nicht erstellt werden: {error}',
+ 'patmgr.unshare_success': 'Freigabe für "{label}" wurde aufgehoben.',
+ 'patmgr.unshare_error': 'Freigabe konnte nicht aufgehoben werden: {error}',
+ 'patmgr.profile_loading': 'Profil wird noch geladen. Bitte versuche es gleich noch einmal.',
+ 'patmgr.enable_visible': 'Aktiviere im Profil zuerst "Tests sichtbar", bevor du Tests teilst.',
+ 'patmgr.save_before_share': 'Bitte den Test zuerst speichern, bevor du ihn teilen kannst.',
+ 'patmgr.save_for_share': 'Bitte erst speichern, damit der aktuelle Stand geteilt wird.',
+ 'patmgr.save_first': 'Bitte zuerst speichern',
+ 'patmgr.profile_loading_btn': 'Profil wird geladen',
+ 'patmgr.enable_visible_btn': 'Aktiviere im Profil "Tests sichtbar"',
+
+ // ── PAT Liste ────────────────────────────────────────────────────────────
+ 'patlist.title': 'PAT Test Manager',
+ 'patlist.new_assessment': 'Neue Bewertung',
+ 'patlist.filter_name': 'Nach Name filtern',
+ 'patlist.filter_date': 'Nach Datum filtern',
+ 'patlist.col_name': 'Name',
+ 'patlist.col_date': 'Datum',
+ 'patlist.col_result': 'Ergebnis',
+ 'patlist.date_placeholder': 'tt.mm.jjjj',
+ 'patlist.prev_month': 'Vorheriger Monat',
+ 'patlist.next_month': 'Nächster Monat',
+ 'patlist.today': 'Heute',
+ 'patlist.delete': 'Löschen',
+ 'patlist.open_assessments': 'Noch offene Bewertungen',
+ 'patlist.no_open': 'Keine offenen Bewertungen mit den aktuellen Suchfiltern gefunden',
+ 'patlist.closed_assessments': 'Abgeschlossene Bewertungen',
+ 'patlist.no_closed': 'Keine abgeschlossenen Bewertungen mit den aktuellen Suchfiltern gefunden',
+ 'patlist.no_assessments': 'Noch keine Bewertungen vorhanden',
+ 'patlist.no_assessments_hint': 'Lege oben eine neue Bewertung an und wähle dabei PAT Start, PAT 1, PAT 2 oder PAT 3',
+ 'patlist.share_active': 'Freigabe aktiv',
+ 'patlist.share_paused': 'Freigabe pausiert',
+ 'patlist.finalized': 'Final abgeschlossen',
+ 'patlist.open': 'Öffnen',
+ 'patlist.count': 'Anzahl: {count}',
+ 'patlist.days': 'Mo Di Mi Do Fr Sa So',
+
+ // ── PAT Detail ───────────────────────────────────────────────────────────
+ 'detail.back': '← Zurück zur Übersicht',
+ 'detail.unsaved': '● Ungespeicherte Änderungen',
+ 'detail.finalized': 'Final abgeschlossen',
+ 'detail.share_active': 'Freigabe aktiv',
+ 'detail.share_paused': 'Freigabe pausiert',
+ 'detail.save': 'Speichern',
+ 'detail.finalize': 'Final Abschließen',
+ 'detail.pdf': 'PDF',
+ 'detail.share': 'Teilen',
+ 'detail.unshare': 'Freigabe aufheben',
+ 'detail.readonly': 'Nur Lese-Modus',
+ 'detail.readonly_hint': 'Diese Bewertung wurde final abgeschlossen und kann nicht mehr bearbeitet werden.',
+ 'detail.date_name': 'Datum / Name',
+ 'detail.name_placeholder': 'Name',
+ 'detail.col_avg_target': 'Durchschnitt Soll {soll}',
+ 'detail.col_actual': 'Ist',
+ 'detail.col_factor': 'Faktor',
+ 'detail.col_points': 'Points',
+ 'detail.col_result': 'Ergebnis',
+
+ // ── Speichern-Dialog ─────────────────────────────────────────────────────
+ 'savedialog.title': 'Ungespeicherte Änderungen',
+ 'savedialog.message': 'Sie haben ungespeicherte Änderungen. Möchten Sie diese speichern?',
+ 'savedialog.yes': 'Ja, speichern',
+ 'savedialog.no': 'Nein, verwerfen',
+ 'savedialog.cancel': 'Abbrechen',
+
+ // ── Analyse ──────────────────────────────────────────────────────────────
+ 'analysis.title': 'Analyse',
+ 'analysis.priority_title': 'Wo solltest du besser werden?',
+ 'analysis.priority_desc': 'Priorisierung nach Gap × Faktor × Konstanz basierend auf den letzten 5 Assessments.',
+ 'analysis.assessments_count': 'Assessments: {count}',
+ 'analysis.window': 'Analysefenster: {count}/5',
+ 'analysis.limited_data': 'Begrenzte Datenbasis',
+ 'analysis.no_data': 'Noch keine Daten für die Analyse',
+ 'analysis.no_data_hint': 'Lege zuerst eine Bewertung im Bereich "Bewertungen" an.',
+ 'analysis.no_type_data': 'Keine Bewertungen für {patType}',
+ 'analysis.no_type_hint': 'Wähle einen anderen PAT-Typ oder erstelle eine neue Bewertung.',
+
+ // ── Schwächen ────────────────────────────────────────────────────────────
+ 'weakness.no_data': 'Keine Schwächen-Daten verfügbar.',
+ 'weakness.title': 'Top-3 Schwächen',
+ 'weakness.gap': 'Gap: {score}',
+ 'weakness.consistency': 'Konstanz: {score}',
+ 'weakness.trend': 'Trend: {score}',
+
+ // ── Stärken ──────────────────────────────────────────────────────────────
+ 'strength.no_data': 'Keine Stärken-Daten verfügbar.',
+ 'strength.title': 'Top-3 Stärken',
+ 'strength.actual': 'Ist: {score}',
+ 'strength.target': 'Soll: {score}',
+ 'strength.trend': 'Trend: {score}',
+
+ // ── Trainingsplan ────────────────────────────────────────────────────────
+ 'training.title': 'Trainingsplan ({patType})',
+ 'training.hint': '1 aktiver Plan + Historie, Session-Status: offen / erledigt / übersprungen.',
+ 'training.reload': 'Neu laden',
+ 'training.duration': 'Dauer',
+ 'training.2weeks': '2 Wochen',
+ 'training.4weeks': '4 Wochen',
+ 'training.6weeks': '6 Wochen',
+ 'training.sessions_per_week': 'Einheiten/Woche',
+ 'training.generate': 'Plan (neu) generieren',
+ 'training.save_draft': 'Entwurf speichern',
+ 'training.saving': 'Speichert…',
+ 'training.draft_created': 'Neuer Entwurf erstellt. Du kannst jetzt Intensität und Übungen je Session anpassen.',
+ 'training.create_draft_first': 'Erstelle zuerst einen Entwurf.',
+ 'training.save_error': 'Trainingsplan konnte nicht gespeichert werden.',
+ 'training.save_success': 'Trainingsplan gespeichert. Der vorherige aktive Plan wurde archiviert.',
+ 'training.week_session': 'Woche {weekNo} · Einheit {sessionNo}',
+ 'training.main_exercise': 'Hauptübung',
+ 'training.side_exercises': 'Nebenübungen, getrennt mit Komma',
+ 'training.high': 'hoch',
+ 'training.medium': 'mittel',
+ 'training.easy': 'leicht',
+ 'training.draft_label': 'Entwurf (teilweise editierbar)',
+ 'training.sessions_info': 'Sessions: {count} · hoch {high} · mittel {medium} · leicht {easy}',
+ 'training.active_plan': 'Aktiver Plan',
+ 'training.loading': 'Lädt…',
+ 'training.no_active': 'Noch kein aktiver Trainingsplan vorhanden.',
+ 'training.generated': 'Generiert: {date} · {weeks} Wochen · {sessions} Einheiten/Woche',
+ 'training.week_short': 'W{weekNo} · E{sessionNo}',
+ 'training.status_open': 'offen',
+ 'training.status_done': 'erledigt',
+ 'training.status_skipped': 'übersprungen',
+ 'training.note': 'Notiz',
+ 'training.history': 'Plan-Historie',
+ 'training.no_history': 'Keine archivierten Pläne vorhanden.',
+
+ // ── Gap-Chart ────────────────────────────────────────────────────────────
+ 'gapchart.no_data': 'Kein Gap-Chart verfügbar.',
+ 'gapchart.title': 'Gap-Bar-Chart (Priorität)',
+ 'gapchart.aria': 'Gap-Balkendiagramm',
+
+ // ── Live Übersicht ───────────────────────────────────────────────────────
+ 'live.title': 'Live Übersicht',
+ 'live.stats_title': 'Aktuelle Statistik',
+ 'live.basis': 'Basis: alle Bewertungen, geladen beim Seitenaufruf',
+ 'live.loading': 'Lädt…',
+ 'live.timestamp': 'Stand: jetzt',
+ 'live.no_data': 'Noch keine Bewertungen vorhanden. Lege eine neue Bewertung an, um hier Live-Werte zu sehen.',
+ 'live.count': 'Bewertungen',
+ 'live.count_desc': 'In der PAT Datenbank hinterlegt',
+ 'live.avg_points': 'Ø Gesamtpunkte',
+ 'live.avg_desc': 'Durchschnitt aller Assessments',
+ 'live.last': 'Letzte Bewertung',
+ 'live.unnamed': 'Unbenannt',
+ 'live.top': 'Top Ergebnis',
+
+ // ── Geteilte Bewertung ───────────────────────────────────────────────────
+ 'shared.missing_token': 'Freigabelink fehlt.',
+ 'shared.invalid': 'Dieser Freigabelink ist ungültig, deaktiviert oder aktuell nicht öffentlich sichtbar.',
+ 'shared.load_error': 'Geteilter Test konnte nicht geladen werden.',
+ 'shared.loading': 'Geteilter Test wird geladen...',
+ 'shared.label': 'Freigabe',
+ 'shared.not_available': 'Geteilter Test nicht verfügbar',
+ 'shared.title': 'Geteilter Test',
+ 'shared.default_name': 'PAT Test',
+ 'shared.hint': 'Dieser Link zeigt nur diesen einen Test im Nur-Lesen-Modus.',
+ 'shared.date': 'Datum',
+ 'shared.total_points': 'Gesamtpunkte',
+ 'shared.rating': 'Bewertung',
+ 'shared.target_factor': 'Soll {soll} · Faktor {faktor}',
+ 'shared.col_points': 'Points',
+ 'shared.col_values': 'Werte',
+ 'shared.col_avg': 'Durchschnitt',
+ 'shared.col_target': 'Soll',
+ 'shared.col_factor': 'Faktor',
+ 'shared.col_result': 'Ergebnis',
+
+ // ── Anmeldung ────────────────────────────────────────────────────────────
+ 'auth.title': 'PAT Stats',
+ 'auth.subtitle': 'Anmelden oder registrieren',
+ 'auth.description': 'Melde dich mit deinem PAT-Test Konto an, um deine Bewertungen sicher zu verwalten.',
+ 'auth.login_tab': 'Login',
+ 'auth.register_tab': 'Registrieren',
+ 'auth.email': 'E-Mail',
+ 'auth.password': 'Passwort',
+ 'auth.login_success': 'Erfolgreich angemeldet',
+ 'auth.register_success': 'Registrierung abgeschlossen. Prüfe dein Postfach zur Bestätigung.',
+ 'auth.sending': 'Wird gesendet...',
+ 'auth.login_btn': 'Einloggen',
+ 'auth.register_btn': 'Konto erstellen',
+ 'auth.back': 'Zurück',
+ },
+
+ en: {
+ 'nav.logged_in': 'Signed in',
+ 'nav.assessments': 'Assessments',
+ 'nav.analysis': 'Analysis',
+ 'nav.profile': 'Profile',
+ 'nav.sign_out': 'Sign out',
+ 'nav.theme_dark': 'Dark Mode',
+ 'nav.theme_light': 'Light Mode',
+ 'nav.analysis_disabled': 'Analysis is disabled while editing',
+ 'nav.analysis_open': 'Open analysis',
+ 'nav.profile_disabled': 'Profile is disabled while editing',
+ 'nav.profile_open': 'Open profile',
+
+ 'profile.title_label': 'User Profile',
+ 'profile.title': 'Manage Profile',
+ 'profile.description': 'Manage your first name, last name, email address, country and the visibility of your shared tests here.',
+ 'profile.loading': 'Loading profile...',
+ 'profile.first_name': 'First name',
+ 'profile.first_name_placeholder': 'John',
+ 'profile.last_name': 'Last name',
+ 'profile.last_name_placeholder': 'Doe',
+ 'profile.email': 'Email address',
+ 'profile.email_hint': 'If you change your email address, a confirmation will be sent to the new address.',
+ 'profile.country': 'Country',
+ 'profile.country_none': 'No country selected',
+ 'profile.country_hint': 'Your country',
+ 'profile.display': 'Display',
+ 'profile.display_hint': 'Language in which the application is displayed.',
+ 'profile.rank': 'Rank Display',
+ 'profile.rank_placeholder': 'Placeholder',
+ 'profile.rank_hint': 'The user\'s ranking position will be displayed here later.',
+ 'profile.tests_visible_title': 'Tests visible',
+ 'profile.tests_visible_desc': 'When this toggle is active, your share links work. If you deactivate it, existing links are saved but no longer publicly visible.',
+ 'profile.visible': 'Visible',
+ 'profile.not_visible': 'Not visible',
+ 'profile.account_id': 'Account ID',
+ 'profile.delete': 'Delete profile',
+ 'profile.deleting': 'Deleting...',
+ 'profile.save': 'Save profile',
+ 'profile.saving': 'Saving...',
+ 'profile.saved': 'Profile saved.',
+ 'profile.saved_email': 'Profile saved. The new email address may still need to be confirmed via email.',
+ 'profile.error_save': 'Profile could not be saved.',
+ 'profile.error_delete': 'Profile could not be deleted.',
+ 'profile.delete_confirm': 'Really delete profile?\n\nThis will permanently remove all tests for this user and any existing analysis plans.',
+ 'profile.deleted': 'Profile and all tests have been deleted. The account remains and can be set up again.',
+
+ 'patmgr.saved': 'Successfully saved!',
+ 'patmgr.finalized': 'Assessment has been finalized and is now read-only.',
+ 'patmgr.fill_name_date': 'Please fill in name and date before saving',
+ 'patmgr.share_copied': 'Share link for "{label}" copied to clipboard.',
+ 'patmgr.share_localhost_hint': 'Note: The link currently points to localhost and is only accessible if the app is reachable from outside.',
+ 'patmgr.share_link_label': 'Share link for "{label}"',
+ 'patmgr.share_error': 'Share link could not be created: {error}',
+ 'patmgr.unshare_success': 'Share for "{label}" has been revoked.',
+ 'patmgr.unshare_error': 'Share could not be revoked: {error}',
+ 'patmgr.profile_loading': 'Profile is still loading. Please try again in a moment.',
+ 'patmgr.enable_visible': 'Enable "Tests visible" in your profile before sharing tests.',
+ 'patmgr.save_before_share': 'Please save the test first before sharing it.',
+ 'patmgr.save_for_share': 'Please save first so the current state is shared.',
+ 'patmgr.save_first': 'Please save first',
+ 'patmgr.profile_loading_btn': 'Profile loading',
+ 'patmgr.enable_visible_btn': 'Enable "Tests visible" in profile',
+
+ 'patlist.title': 'PAT Test Manager',
+ 'patlist.new_assessment': 'New Assessment',
+ 'patlist.filter_name': 'Filter by name',
+ 'patlist.filter_date': 'Filter by date',
+ 'patlist.col_name': 'Name',
+ 'patlist.col_date': 'Date',
+ 'patlist.col_result': 'Result',
+ 'patlist.date_placeholder': 'dd.mm.yyyy',
+ 'patlist.prev_month': 'Previous month',
+ 'patlist.next_month': 'Next month',
+ 'patlist.today': 'Today',
+ 'patlist.delete': 'Delete',
+ 'patlist.open_assessments': 'Open assessments',
+ 'patlist.no_open': 'No open assessments found with the current filters',
+ 'patlist.closed_assessments': 'Finalized assessments',
+ 'patlist.no_closed': 'No finalized assessments found with the current filters',
+ 'patlist.no_assessments': 'No assessments yet',
+ 'patlist.no_assessments_hint': 'Create a new assessment above and choose PAT Start, PAT 1, PAT 2 or PAT 3',
+ 'patlist.share_active': 'Share active',
+ 'patlist.share_paused': 'Share paused',
+ 'patlist.finalized': 'Finalized',
+ 'patlist.open': 'Open',
+ 'patlist.count': 'Count: {count}',
+ 'patlist.days': 'Mo Tu We Th Fr Sa Su',
+
+ 'detail.back': '← Back to overview',
+ 'detail.unsaved': '● Unsaved changes',
+ 'detail.finalized': 'Finalized',
+ 'detail.share_active': 'Share active',
+ 'detail.share_paused': 'Share paused',
+ 'detail.save': 'Save',
+ 'detail.finalize': 'Finalize',
+ 'detail.pdf': 'PDF',
+ 'detail.share': 'Share',
+ 'detail.unshare': 'Revoke share',
+ 'detail.readonly': 'Read-only mode',
+ 'detail.readonly_hint': 'This assessment has been finalized and can no longer be edited.',
+ 'detail.date_name': 'Date / Name',
+ 'detail.name_placeholder': 'Name',
+ 'detail.col_avg_target': 'Avg. target {soll}',
+ 'detail.col_actual': 'Actual',
+ 'detail.col_factor': 'Factor',
+ 'detail.col_points': 'Points',
+ 'detail.col_result': 'Result',
+
+ 'savedialog.title': 'Unsaved changes',
+ 'savedialog.message': 'You have unsaved changes. Would you like to save them?',
+ 'savedialog.yes': 'Yes, save',
+ 'savedialog.no': 'No, discard',
+ 'savedialog.cancel': 'Cancel',
+
+ 'analysis.title': 'Analysis',
+ 'analysis.priority_title': 'Where should you improve?',
+ 'analysis.priority_desc': 'Prioritized by Gap × Factor × Consistency based on the last 5 assessments.',
+ 'analysis.assessments_count': 'Assessments: {count}',
+ 'analysis.window': 'Analysis window: {count}/5',
+ 'analysis.limited_data': 'Limited data basis',
+ 'analysis.no_data': 'No data for analysis yet',
+ 'analysis.no_data_hint': 'Create an assessment in the "Assessments" section first.',
+ 'analysis.no_type_data': 'No assessments for {patType}',
+ 'analysis.no_type_hint': 'Choose a different PAT type or create a new assessment.',
+
+ 'weakness.no_data': 'No weakness data available.',
+ 'weakness.title': 'Top 3 Weaknesses',
+ 'weakness.gap': 'Gap: {score}',
+ 'weakness.consistency': 'Consistency: {score}',
+ 'weakness.trend': 'Trend: {score}',
+
+ 'strength.no_data': 'No strength data available.',
+ 'strength.title': 'Top 3 Strengths',
+ 'strength.actual': 'Actual: {score}',
+ 'strength.target': 'Target: {score}',
+ 'strength.trend': 'Trend: {score}',
+
+ 'training.title': 'Training Plan ({patType})',
+ 'training.hint': '1 active plan + history, session status: open / done / skipped.',
+ 'training.reload': 'Reload',
+ 'training.duration': 'Duration',
+ 'training.2weeks': '2 weeks',
+ 'training.4weeks': '4 weeks',
+ 'training.6weeks': '6 weeks',
+ 'training.sessions_per_week': 'Sessions/week',
+ 'training.generate': 'Generate plan',
+ 'training.save_draft': 'Save draft',
+ 'training.saving': 'Saving…',
+ 'training.draft_created': 'New draft created. You can now adjust intensity and exercises per session.',
+ 'training.create_draft_first': 'Create a draft first.',
+ 'training.save_error': 'Training plan could not be saved.',
+ 'training.save_success': 'Training plan saved. The previous active plan has been archived.',
+ 'training.week_session': 'Week {weekNo} · Session {sessionNo}',
+ 'training.main_exercise': 'Main exercise',
+ 'training.side_exercises': 'Side exercises, separated by comma',
+ 'training.high': 'high',
+ 'training.medium': 'medium',
+ 'training.easy': 'easy',
+ 'training.draft_label': 'Draft (partially editable)',
+ 'training.sessions_info': 'Sessions: {count} · high {high} · medium {medium} · easy {easy}',
+ 'training.active_plan': 'Active Plan',
+ 'training.loading': 'Loading…',
+ 'training.no_active': 'No active training plan yet.',
+ 'training.generated': 'Generated: {date} · {weeks} weeks · {sessions} sessions/week',
+ 'training.week_short': 'W{weekNo} · S{sessionNo}',
+ 'training.status_open': 'open',
+ 'training.status_done': 'done',
+ 'training.status_skipped': 'skipped',
+ 'training.note': 'Note',
+ 'training.history': 'Plan history',
+ 'training.no_history': 'No archived plans available.',
+
+ 'gapchart.no_data': 'No gap chart available.',
+ 'gapchart.title': 'Gap Bar Chart (Priority)',
+ 'gapchart.aria': 'Gap bar chart',
+
+ 'live.title': 'Live Overview',
+ 'live.stats_title': 'Current Statistics',
+ 'live.basis': 'Based on all assessments, loaded on page open',
+ 'live.loading': 'Loading…',
+ 'live.timestamp': 'As of: now',
+ 'live.no_data': 'No assessments yet. Create a new assessment to see live values here.',
+ 'live.count': 'Assessments',
+ 'live.count_desc': 'Stored in the PAT database',
+ 'live.avg_points': 'Avg. total points',
+ 'live.avg_desc': 'Average of all assessments',
+ 'live.last': 'Latest assessment',
+ 'live.unnamed': 'Unnamed',
+ 'live.top': 'Top result',
+
+ 'shared.missing_token': 'Share link missing.',
+ 'shared.invalid': 'This share link is invalid, disabled or currently not publicly visible.',
+ 'shared.load_error': 'Shared test could not be loaded.',
+ 'shared.loading': 'Loading shared test...',
+ 'shared.label': 'Share',
+ 'shared.not_available': 'Shared test not available',
+ 'shared.title': 'Shared Test',
+ 'shared.default_name': 'PAT Test',
+ 'shared.hint': 'This link shows only this one test in read-only mode.',
+ 'shared.date': 'Date',
+ 'shared.total_points': 'Total points',
+ 'shared.rating': 'Rating',
+ 'shared.target_factor': 'Target {soll} · Factor {faktor}',
+ 'shared.col_points': 'Points',
+ 'shared.col_values': 'Values',
+ 'shared.col_avg': 'Average',
+ 'shared.col_target': 'Target',
+ 'shared.col_factor': 'Factor',
+ 'shared.col_result': 'Result',
+
+ 'auth.title': 'PAT Stats',
+ 'auth.subtitle': 'Sign in or register',
+ 'auth.description': 'Sign in with your PAT Test account to securely manage your assessments.',
+ 'auth.login_tab': 'Login',
+ 'auth.register_tab': 'Register',
+ 'auth.email': 'Email',
+ 'auth.password': 'Password',
+ 'auth.login_success': 'Successfully signed in',
+ 'auth.register_success': 'Registration complete. Check your inbox for confirmation.',
+ 'auth.sending': 'Sending...',
+ 'auth.login_btn': 'Sign in',
+ 'auth.register_btn': 'Create account',
+ 'auth.back': 'Back',
+ },
+
+ it: {
+ 'nav.logged_in': 'Connesso',
+ 'nav.assessments': 'Valutazioni',
+ 'nav.analysis': 'Analisi',
+ 'nav.profile': 'Profilo',
+ 'nav.sign_out': 'Esci',
+ 'nav.theme_dark': 'Modalità scura',
+ 'nav.theme_light': 'Modalità chiara',
+ 'nav.analysis_disabled': 'L\'analisi è disabilitata durante la modifica',
+ 'nav.analysis_open': 'Apri analisi',
+ 'nav.profile_disabled': 'Il profilo è disabilitato durante la modifica',
+ 'nav.profile_open': 'Apri profilo',
+
+ 'profile.title_label': 'Profilo utente',
+ 'profile.title': 'Gestisci profilo',
+ 'profile.description': 'Gestisci qui nome, cognome, indirizzo email, paese e la visibilità delle tue condivisioni di test.',
+ 'profile.loading': 'Caricamento profilo...',
+ 'profile.first_name': 'Nome',
+ 'profile.first_name_placeholder': 'Mario',
+ 'profile.last_name': 'Cognome',
+ 'profile.last_name_placeholder': 'Rossi',
+ 'profile.email': 'Indirizzo email',
+ 'profile.email_hint': 'Se cambi l\'indirizzo email, riceverai una conferma al nuovo indirizzo.',
+ 'profile.country': 'Paese',
+ 'profile.country_none': 'Nessun paese selezionato',
+ 'profile.country_hint': 'Il tuo paese',
+ 'profile.display': 'Visualizzazione',
+ 'profile.display_hint': 'Lingua in cui viene visualizzata l\'applicazione.',
+ 'profile.rank': 'Classifica',
+ 'profile.rank_placeholder': 'Segnaposto',
+ 'profile.rank_hint': 'Qui verrà visualizzata in seguito la posizione in classifica dell\'utente.',
+ 'profile.tests_visible_title': 'Test visibili',
+ 'profile.tests_visible_desc': 'Quando questo interruttore è attivo, i tuoi link di condivisione funzionano. Se lo disattivi, i link esistenti vengono salvati ma non sono più visibili pubblicamente.',
+ 'profile.visible': 'Visibile',
+ 'profile.not_visible': 'Non visibile',
+ 'profile.account_id': 'ID account',
+ 'profile.delete': 'Elimina profilo',
+ 'profile.deleting': 'Eliminazione...',
+ 'profile.save': 'Salva profilo',
+ 'profile.saving': 'Salvataggio...',
+ 'profile.saved': 'Profilo salvato.',
+ 'profile.saved_email': 'Profilo salvato. Il nuovo indirizzo email potrebbe dover essere ancora confermato via email.',
+ 'profile.error_save': 'Impossibile salvare il profilo.',
+ 'profile.error_delete': 'Impossibile eliminare il profilo.',
+ 'profile.delete_confirm': 'Eliminare davvero il profilo?\n\nTutti i test di questo utente e i piani di analisi esistenti verranno rimossi definitivamente.',
+ 'profile.deleted': 'Profilo e tutti i test sono stati eliminati. L\'account rimane e può essere configurato nuovamente.',
+
+ 'patmgr.saved': 'Salvato con successo!',
+ 'patmgr.finalized': 'La valutazione è stata finalizzata ed è ora in sola lettura.',
+ 'patmgr.fill_name_date': 'Compila nome e data prima di salvare',
+ 'patmgr.share_copied': 'Link di condivisione per "{label}" copiato negli appunti.',
+ 'patmgr.share_localhost_hint': 'Nota: Il link punta attualmente a localhost ed è accessibile solo se l\'app è raggiungibile dall\'esterno.',
+ 'patmgr.share_link_label': 'Link di condivisione per "{label}"',
+ 'patmgr.share_error': 'Impossibile creare il link di condivisione: {error}',
+ 'patmgr.unshare_success': 'Condivisione per "{label}" revocata.',
+ 'patmgr.unshare_error': 'Impossibile revocare la condivisione: {error}',
+ 'patmgr.profile_loading': 'Il profilo è ancora in caricamento. Riprova tra poco.',
+ 'patmgr.enable_visible': 'Attiva "Test visibili" nel profilo prima di condividere i test.',
+ 'patmgr.save_before_share': 'Salva prima il test prima di condividerlo.',
+ 'patmgr.save_for_share': 'Salva prima in modo che lo stato attuale venga condiviso.',
+ 'patmgr.save_first': 'Salva prima',
+ 'patmgr.profile_loading_btn': 'Profilo in caricamento',
+ 'patmgr.enable_visible_btn': 'Attiva "Test visibili" nel profilo',
+
+ 'patlist.title': 'PAT Test Manager',
+ 'patlist.new_assessment': 'Nuova valutazione',
+ 'patlist.filter_name': 'Filtra per nome',
+ 'patlist.filter_date': 'Filtra per data',
+ 'patlist.col_name': 'Nome',
+ 'patlist.col_date': 'Data',
+ 'patlist.col_result': 'Risultato',
+ 'patlist.date_placeholder': 'gg.mm.aaaa',
+ 'patlist.prev_month': 'Mese precedente',
+ 'patlist.next_month': 'Mese successivo',
+ 'patlist.today': 'Oggi',
+ 'patlist.delete': 'Elimina',
+ 'patlist.open_assessments': 'Valutazioni aperte',
+ 'patlist.no_open': 'Nessuna valutazione aperta trovata con i filtri attuali',
+ 'patlist.closed_assessments': 'Valutazioni finalizzate',
+ 'patlist.no_closed': 'Nessuna valutazione finalizzata trovata con i filtri attuali',
+ 'patlist.no_assessments': 'Nessuna valutazione ancora',
+ 'patlist.no_assessments_hint': 'Crea una nuova valutazione sopra e scegli PAT Start, PAT 1, PAT 2 o PAT 3',
+ 'patlist.share_active': 'Condivisione attiva',
+ 'patlist.share_paused': 'Condivisione in pausa',
+ 'patlist.finalized': 'Finalizzato',
+ 'patlist.open': 'Apri',
+ 'patlist.count': 'Numero: {count}',
+ 'patlist.days': 'Lu Ma Me Gi Ve Sa Do',
+
+ 'detail.back': '← Torna alla panoramica',
+ 'detail.unsaved': '● Modifiche non salvate',
+ 'detail.finalized': 'Finalizzato',
+ 'detail.share_active': 'Condivisione attiva',
+ 'detail.share_paused': 'Condivisione in pausa',
+ 'detail.save': 'Salva',
+ 'detail.finalize': 'Finalizza',
+ 'detail.pdf': 'PDF',
+ 'detail.share': 'Condividi',
+ 'detail.unshare': 'Revoca condivisione',
+ 'detail.readonly': 'Sola lettura',
+ 'detail.readonly_hint': 'Questa valutazione è stata finalizzata e non può più essere modificata.',
+ 'detail.date_name': 'Data / Nome',
+ 'detail.name_placeholder': 'Nome',
+ 'detail.col_avg_target': 'Media obiettivo {soll}',
+ 'detail.col_actual': 'Effettivo',
+ 'detail.col_factor': 'Fattore',
+ 'detail.col_points': 'Punti',
+ 'detail.col_result': 'Risultato',
+
+ 'savedialog.title': 'Modifiche non salvate',
+ 'savedialog.message': 'Hai modifiche non salvate. Vuoi salvarle?',
+ 'savedialog.yes': 'Sì, salva',
+ 'savedialog.no': 'No, scarta',
+ 'savedialog.cancel': 'Annulla',
+
+ 'analysis.title': 'Analisi',
+ 'analysis.priority_title': 'Dove dovresti migliorare?',
+ 'analysis.priority_desc': 'Priorità secondo Gap × Fattore × Costanza basata sugli ultimi 5 assessment.',
+ 'analysis.assessments_count': 'Valutazioni: {count}',
+ 'analysis.window': 'Finestra analisi: {count}/5',
+ 'analysis.limited_data': 'Base dati limitata',
+ 'analysis.no_data': 'Nessun dato per l\'analisi',
+ 'analysis.no_data_hint': 'Crea prima una valutazione nella sezione "Valutazioni".',
+ 'analysis.no_type_data': 'Nessuna valutazione per {patType}',
+ 'analysis.no_type_hint': 'Scegli un altro tipo PAT o crea una nuova valutazione.',
+
+ 'weakness.no_data': 'Nessun dato sui punti deboli disponibile.',
+ 'weakness.title': 'Top 3 Punti deboli',
+ 'weakness.gap': 'Gap: {score}',
+ 'weakness.consistency': 'Costanza: {score}',
+ 'weakness.trend': 'Tendenza: {score}',
+
+ 'strength.no_data': 'Nessun dato sui punti di forza disponibile.',
+ 'strength.title': 'Top 3 Punti di forza',
+ 'strength.actual': 'Effettivo: {score}',
+ 'strength.target': 'Obiettivo: {score}',
+ 'strength.trend': 'Tendenza: {score}',
+
+ 'training.title': 'Piano di allenamento ({patType})',
+ 'training.hint': '1 piano attivo + cronologia, stato sessione: aperto / completato / saltato.',
+ 'training.reload': 'Ricarica',
+ 'training.duration': 'Durata',
+ 'training.2weeks': '2 settimane',
+ 'training.4weeks': '4 settimane',
+ 'training.6weeks': '6 settimane',
+ 'training.sessions_per_week': 'Sessioni/settimana',
+ 'training.generate': 'Genera piano',
+ 'training.save_draft': 'Salva bozza',
+ 'training.saving': 'Salvataggio…',
+ 'training.draft_created': 'Nuova bozza creata. Puoi ora regolare intensità ed esercizi per sessione.',
+ 'training.create_draft_first': 'Crea prima una bozza.',
+ 'training.save_error': 'Impossibile salvare il piano di allenamento.',
+ 'training.save_success': 'Piano di allenamento salvato. Il piano attivo precedente è stato archiviato.',
+ 'training.week_session': 'Settimana {weekNo} · Sessione {sessionNo}',
+ 'training.main_exercise': 'Esercizio principale',
+ 'training.side_exercises': 'Esercizi secondari, separati da virgola',
+ 'training.high': 'alto',
+ 'training.medium': 'medio',
+ 'training.easy': 'facile',
+ 'training.draft_label': 'Bozza (parzialmente modificabile)',
+ 'training.sessions_info': 'Sessioni: {count} · alto {high} · medio {medium} · facile {easy}',
+ 'training.active_plan': 'Piano attivo',
+ 'training.loading': 'Caricamento…',
+ 'training.no_active': 'Nessun piano di allenamento attivo.',
+ 'training.generated': 'Generato: {date} · {weeks} settimane · {sessions} sessioni/settimana',
+ 'training.week_short': 'S{weekNo} · E{sessionNo}',
+ 'training.status_open': 'aperto',
+ 'training.status_done': 'completato',
+ 'training.status_skipped': 'saltato',
+ 'training.note': 'Nota',
+ 'training.history': 'Cronologia piani',
+ 'training.no_history': 'Nessun piano archiviato disponibile.',
+
+ 'gapchart.no_data': 'Nessun grafico gap disponibile.',
+ 'gapchart.title': 'Grafico Gap (Priorità)',
+ 'gapchart.aria': 'Grafico a barre gap',
+
+ 'live.title': 'Panoramica live',
+ 'live.stats_title': 'Statistiche attuali',
+ 'live.basis': 'Base: tutte le valutazioni, caricate all\'apertura della pagina',
+ 'live.loading': 'Caricamento…',
+ 'live.timestamp': 'Aggiornato: ora',
+ 'live.no_data': 'Nessuna valutazione ancora. Crea una nuova valutazione per vedere i valori live.',
+ 'live.count': 'Valutazioni',
+ 'live.count_desc': 'Salvate nel database PAT',
+ 'live.avg_points': 'Ø Punti totali',
+ 'live.avg_desc': 'Media di tutti gli assessment',
+ 'live.last': 'Ultima valutazione',
+ 'live.unnamed': 'Senza nome',
+ 'live.top': 'Risultato migliore',
+
+ 'shared.missing_token': 'Link di condivisione mancante.',
+ 'shared.invalid': 'Questo link di condivisione non è valido, è disabilitato o non è attualmente visibile pubblicamente.',
+ 'shared.load_error': 'Impossibile caricare il test condiviso.',
+ 'shared.loading': 'Caricamento test condiviso...',
+ 'shared.label': 'Condivisione',
+ 'shared.not_available': 'Test condiviso non disponibile',
+ 'shared.title': 'Test condiviso',
+ 'shared.default_name': 'PAT Test',
+ 'shared.hint': 'Questo link mostra solo questo test in modalità sola lettura.',
+ 'shared.date': 'Data',
+ 'shared.total_points': 'Punti totali',
+ 'shared.rating': 'Valutazione',
+ 'shared.target_factor': 'Obiettivo {soll} · Fattore {faktor}',
+ 'shared.col_points': 'Punti',
+ 'shared.col_values': 'Valori',
+ 'shared.col_avg': 'Media',
+ 'shared.col_target': 'Obiettivo',
+ 'shared.col_factor': 'Fattore',
+ 'shared.col_result': 'Risultato',
+
+ 'auth.title': 'PAT Stats',
+ 'auth.subtitle': 'Accedi o registrati',
+ 'auth.description': 'Accedi con il tuo account PAT Test per gestire le tue valutazioni in modo sicuro.',
+ 'auth.login_tab': 'Login',
+ 'auth.register_tab': 'Registrati',
+ 'auth.email': 'Email',
+ 'auth.password': 'Password',
+ 'auth.login_success': 'Accesso effettuato con successo',
+ 'auth.register_success': 'Registrazione completata. Controlla la tua casella di posta per la conferma.',
+ 'auth.sending': 'Invio in corso...',
+ 'auth.login_btn': 'Accedi',
+ 'auth.register_btn': 'Crea account',
+ 'auth.back': 'Indietro',
+ },
+
+ es: {
+ 'nav.logged_in': 'Conectado',
+ 'nav.assessments': 'Evaluaciones',
+ 'nav.analysis': 'Análisis',
+ 'nav.profile': 'Perfil',
+ 'nav.sign_out': 'Cerrar sesión',
+ 'nav.theme_dark': 'Modo oscuro',
+ 'nav.theme_light': 'Modo claro',
+ 'nav.analysis_disabled': 'El análisis está deshabilitado durante la edición',
+ 'nav.analysis_open': 'Abrir análisis',
+ 'nav.profile_disabled': 'El perfil está deshabilitado durante la edición',
+ 'nav.profile_open': 'Abrir perfil',
+
+ 'profile.title_label': 'Perfil de usuario',
+ 'profile.title': 'Gestionar perfil',
+ 'profile.description': 'Administra aquí tu nombre, apellido, dirección de correo, país y la visibilidad de tus comparticiones de pruebas.',
+ 'profile.loading': 'Cargando perfil...',
+ 'profile.first_name': 'Nombre',
+ 'profile.first_name_placeholder': 'Juan',
+ 'profile.last_name': 'Apellido',
+ 'profile.last_name_placeholder': 'García',
+ 'profile.email': 'Dirección de correo',
+ 'profile.email_hint': 'Si cambias tu dirección de correo, se enviará una confirmación a la nueva dirección.',
+ 'profile.country': 'País',
+ 'profile.country_none': 'Ningún país seleccionado',
+ 'profile.country_hint': 'Tu país',
+ 'profile.display': 'Visualización',
+ 'profile.display_hint': 'Idioma en el que se muestra la aplicación.',
+ 'profile.rank': 'Posición en el ranking',
+ 'profile.rank_placeholder': 'Marcador de posición',
+ 'profile.rank_hint': 'Aquí se mostrará más adelante la posición en el ranking del usuario.',
+ 'profile.tests_visible_title': 'Pruebas visibles',
+ 'profile.tests_visible_desc': 'Cuando este interruptor está activo, tus enlaces de compartición funcionan. Si lo desactivas, los enlaces existentes se guardan pero ya no son visibles públicamente.',
+ 'profile.visible': 'Visible',
+ 'profile.not_visible': 'No visible',
+ 'profile.account_id': 'ID de cuenta',
+ 'profile.delete': 'Eliminar perfil',
+ 'profile.deleting': 'Eliminando...',
+ 'profile.save': 'Guardar perfil',
+ 'profile.saving': 'Guardando...',
+ 'profile.saved': 'Perfil guardado.',
+ 'profile.saved_email': 'Perfil guardado. Es posible que la nueva dirección de correo deba confirmarse por correo electrónico.',
+ 'profile.error_save': 'No se pudo guardar el perfil.',
+ 'profile.error_delete': 'No se pudo eliminar el perfil.',
+ 'profile.delete_confirm': '¿Realmente eliminar el perfil?\n\nEsto eliminará permanentemente todas las pruebas de este usuario y los planes de análisis existentes.',
+ 'profile.deleted': 'El perfil y todas las pruebas han sido eliminados. La cuenta permanece y puede volver a configurarse.',
+
+ 'patmgr.saved': '¡Guardado con éxito!',
+ 'patmgr.finalized': 'La evaluación ha sido finalizada y ahora es de solo lectura.',
+ 'patmgr.fill_name_date': 'Por favor, completa nombre y fecha antes de guardar',
+ 'patmgr.share_copied': 'Enlace de compartición para "{label}" copiado al portapapeles.',
+ 'patmgr.share_localhost_hint': 'Nota: El enlace apunta actualmente a localhost y solo es accesible si la app es alcanzable desde fuera.',
+ 'patmgr.share_link_label': 'Enlace de compartición para "{label}"',
+ 'patmgr.share_error': 'No se pudo crear el enlace de compartición: {error}',
+ 'patmgr.unshare_success': 'Compartición para "{label}" revocada.',
+ 'patmgr.unshare_error': 'No se pudo revocar la compartición: {error}',
+ 'patmgr.profile_loading': 'El perfil aún se está cargando. Por favor, inténtalo de nuevo en un momento.',
+ 'patmgr.enable_visible': 'Activa "Pruebas visibles" en tu perfil antes de compartir pruebas.',
+ 'patmgr.save_before_share': 'Por favor, guarda la prueba primero antes de compartirla.',
+ 'patmgr.save_for_share': 'Por favor, guarda primero para que se comparta el estado actual.',
+ 'patmgr.save_first': 'Por favor, guarda primero',
+ 'patmgr.profile_loading_btn': 'Cargando perfil',
+ 'patmgr.enable_visible_btn': 'Activa "Pruebas visibles" en el perfil',
+
+ 'patlist.title': 'PAT Test Manager',
+ 'patlist.new_assessment': 'Nueva evaluación',
+ 'patlist.filter_name': 'Filtrar por nombre',
+ 'patlist.filter_date': 'Filtrar por fecha',
+ 'patlist.col_name': 'Nombre',
+ 'patlist.col_date': 'Fecha',
+ 'patlist.col_result': 'Resultado',
+ 'patlist.date_placeholder': 'dd.mm.aaaa',
+ 'patlist.prev_month': 'Mes anterior',
+ 'patlist.next_month': 'Mes siguiente',
+ 'patlist.today': 'Hoy',
+ 'patlist.delete': 'Eliminar',
+ 'patlist.open_assessments': 'Evaluaciones abiertas',
+ 'patlist.no_open': 'No se encontraron evaluaciones abiertas con los filtros actuales',
+ 'patlist.closed_assessments': 'Evaluaciones finalizadas',
+ 'patlist.no_closed': 'No se encontraron evaluaciones finalizadas con los filtros actuales',
+ 'patlist.no_assessments': 'Aún no hay evaluaciones',
+ 'patlist.no_assessments_hint': 'Crea una nueva evaluación arriba y elige PAT Start, PAT 1, PAT 2 o PAT 3',
+ 'patlist.share_active': 'Compartición activa',
+ 'patlist.share_paused': 'Compartición pausada',
+ 'patlist.finalized': 'Finalizado',
+ 'patlist.open': 'Abrir',
+ 'patlist.count': 'Cantidad: {count}',
+ 'patlist.days': 'Lu Ma Mi Ju Vi Sá Do',
+
+ 'detail.back': '← Volver a la vista general',
+ 'detail.unsaved': '● Cambios sin guardar',
+ 'detail.finalized': 'Finalizado',
+ 'detail.share_active': 'Compartición activa',
+ 'detail.share_paused': 'Compartición pausada',
+ 'detail.save': 'Guardar',
+ 'detail.finalize': 'Finalizar',
+ 'detail.pdf': 'PDF',
+ 'detail.share': 'Compartir',
+ 'detail.unshare': 'Revocar compartición',
+ 'detail.readonly': 'Solo lectura',
+ 'detail.readonly_hint': 'Esta evaluación ha sido finalizada y ya no puede editarse.',
+ 'detail.date_name': 'Fecha / Nombre',
+ 'detail.name_placeholder': 'Nombre',
+ 'detail.col_avg_target': 'Promedio objetivo {soll}',
+ 'detail.col_actual': 'Real',
+ 'detail.col_factor': 'Factor',
+ 'detail.col_points': 'Puntos',
+ 'detail.col_result': 'Resultado',
+
+ 'savedialog.title': 'Cambios sin guardar',
+ 'savedialog.message': 'Tienes cambios sin guardar. ¿Deseas guardarlos?',
+ 'savedialog.yes': 'Sí, guardar',
+ 'savedialog.no': 'No, descartar',
+ 'savedialog.cancel': 'Cancelar',
+
+ 'analysis.title': 'Análisis',
+ 'analysis.priority_title': '¿Dónde deberías mejorar?',
+ 'analysis.priority_desc': 'Priorizado por Gap × Factor × Consistencia basado en las últimas 5 evaluaciones.',
+ 'analysis.assessments_count': 'Evaluaciones: {count}',
+ 'analysis.window': 'Ventana de análisis: {count}/5',
+ 'analysis.limited_data': 'Base de datos limitada',
+ 'analysis.no_data': 'Aún no hay datos para el análisis',
+ 'analysis.no_data_hint': 'Crea primero una evaluación en la sección "Evaluaciones".',
+ 'analysis.no_type_data': 'No hay evaluaciones para {patType}',
+ 'analysis.no_type_hint': 'Elige otro tipo PAT o crea una nueva evaluación.',
+
+ 'weakness.no_data': 'No hay datos de puntos débiles disponibles.',
+ 'weakness.title': 'Top 3 Puntos débiles',
+ 'weakness.gap': 'Gap: {score}',
+ 'weakness.consistency': 'Consistencia: {score}',
+ 'weakness.trend': 'Tendencia: {score}',
+
+ 'strength.no_data': 'No hay datos de puntos fuertes disponibles.',
+ 'strength.title': 'Top 3 Puntos fuertes',
+ 'strength.actual': 'Real: {score}',
+ 'strength.target': 'Objetivo: {score}',
+ 'strength.trend': 'Tendencia: {score}',
+
+ 'training.title': 'Plan de entrenamiento ({patType})',
+ 'training.hint': '1 plan activo + historial, estado de sesión: abierto / completado / omitido.',
+ 'training.reload': 'Recargar',
+ 'training.duration': 'Duración',
+ 'training.2weeks': '2 semanas',
+ 'training.4weeks': '4 semanas',
+ 'training.6weeks': '6 semanas',
+ 'training.sessions_per_week': 'Sesiones/semana',
+ 'training.generate': 'Generar plan',
+ 'training.save_draft': 'Guardar borrador',
+ 'training.saving': 'Guardando…',
+ 'training.draft_created': 'Nuevo borrador creado. Ahora puedes ajustar la intensidad y los ejercicios por sesión.',
+ 'training.create_draft_first': 'Crea primero un borrador.',
+ 'training.save_error': 'No se pudo guardar el plan de entrenamiento.',
+ 'training.save_success': 'Plan de entrenamiento guardado. El plan activo anterior ha sido archivado.',
+ 'training.week_session': 'Semana {weekNo} · Sesión {sessionNo}',
+ 'training.main_exercise': 'Ejercicio principal',
+ 'training.side_exercises': 'Ejercicios secundarios, separados por coma',
+ 'training.high': 'alto',
+ 'training.medium': 'medio',
+ 'training.easy': 'fácil',
+ 'training.draft_label': 'Borrador (parcialmente editable)',
+ 'training.sessions_info': 'Sesiones: {count} · alto {high} · medio {medium} · fácil {easy}',
+ 'training.active_plan': 'Plan activo',
+ 'training.loading': 'Cargando…',
+ 'training.no_active': 'Aún no hay plan de entrenamiento activo.',
+ 'training.generated': 'Generado: {date} · {weeks} semanas · {sessions} sesiones/semana',
+ 'training.week_short': 'S{weekNo} · E{sessionNo}',
+ 'training.status_open': 'abierto',
+ 'training.status_done': 'completado',
+ 'training.status_skipped': 'omitido',
+ 'training.note': 'Nota',
+ 'training.history': 'Historial de planes',
+ 'training.no_history': 'No hay planes archivados disponibles.',
+
+ 'gapchart.no_data': 'No hay gráfico de gap disponible.',
+ 'gapchart.title': 'Gráfico de Gap (Prioridad)',
+ 'gapchart.aria': 'Gráfico de barras de gap',
+
+ 'live.title': 'Vista en vivo',
+ 'live.stats_title': 'Estadísticas actuales',
+ 'live.basis': 'Base: todas las evaluaciones, cargadas al abrir la página',
+ 'live.loading': 'Cargando…',
+ 'live.timestamp': 'Actualizado: ahora',
+ 'live.no_data': 'Aún no hay evaluaciones. Crea una nueva evaluación para ver valores en vivo aquí.',
+ 'live.count': 'Evaluaciones',
+ 'live.count_desc': 'Almacenadas en la base de datos PAT',
+ 'live.avg_points': 'Ø Puntos totales',
+ 'live.avg_desc': 'Promedio de todas las evaluaciones',
+ 'live.last': 'Última evaluación',
+ 'live.unnamed': 'Sin nombre',
+ 'live.top': 'Mejor resultado',
+
+ 'shared.missing_token': 'Falta el enlace de compartición.',
+ 'shared.invalid': 'Este enlace de compartición no es válido, está desactivado o no es visible públicamente.',
+ 'shared.load_error': 'No se pudo cargar el test compartido.',
+ 'shared.loading': 'Cargando test compartido...',
+ 'shared.label': 'Compartición',
+ 'shared.not_available': 'Test compartido no disponible',
+ 'shared.title': 'Test compartido',
+ 'shared.default_name': 'PAT Test',
+ 'shared.hint': 'Este enlace muestra solo este test en modo de solo lectura.',
+ 'shared.date': 'Fecha',
+ 'shared.total_points': 'Puntos totales',
+ 'shared.rating': 'Evaluación',
+ 'shared.target_factor': 'Objetivo {soll} · Factor {faktor}',
+ 'shared.col_points': 'Puntos',
+ 'shared.col_values': 'Valores',
+ 'shared.col_avg': 'Promedio',
+ 'shared.col_target': 'Objetivo',
+ 'shared.col_factor': 'Factor',
+ 'shared.col_result': 'Resultado',
+
+ 'auth.title': 'PAT Stats',
+ 'auth.subtitle': 'Iniciar sesión o registrarse',
+ 'auth.description': 'Inicia sesión con tu cuenta PAT Test para gestionar tus evaluaciones de forma segura.',
+ 'auth.login_tab': 'Login',
+ 'auth.register_tab': 'Registrarse',
+ 'auth.email': 'Correo electrónico',
+ 'auth.password': 'Contraseña',
+ 'auth.login_success': 'Sesión iniciada con éxito',
+ 'auth.register_success': 'Registro completado. Revisa tu bandeja de entrada para confirmar.',
+ 'auth.sending': 'Enviando...',
+ 'auth.login_btn': 'Iniciar sesión',
+ 'auth.register_btn': 'Crear cuenta',
+ 'auth.back': 'Volver',
+ },
+
+ pt: {
+ 'nav.logged_in': 'Conectado',
+ 'nav.assessments': 'Avaliações',
+ 'nav.analysis': 'Análise',
+ 'nav.profile': 'Perfil',
+ 'nav.sign_out': 'Sair',
+ 'nav.theme_dark': 'Modo escuro',
+ 'nav.theme_light': 'Modo claro',
+ 'nav.analysis_disabled': 'A análise está desativada durante a edição',
+ 'nav.analysis_open': 'Abrir análise',
+ 'nav.profile_disabled': 'O perfil está desativado durante a edição',
+ 'nav.profile_open': 'Abrir perfil',
+
+ 'profile.title_label': 'Perfil do utilizador',
+ 'profile.title': 'Gerir perfil',
+ 'profile.description': 'Gira aqui o seu nome, apelido, endereço de e-mail, país e a visibilidade das suas partilhas de testes.',
+ 'profile.loading': 'A carregar perfil...',
+ 'profile.first_name': 'Nome próprio',
+ 'profile.first_name_placeholder': 'João',
+ 'profile.last_name': 'Apelido',
+ 'profile.last_name_placeholder': 'Silva',
+ 'profile.email': 'Endereço de e-mail',
+ 'profile.email_hint': 'Se alterar o endereço de e-mail, será enviada uma confirmação para o novo endereço.',
+ 'profile.country': 'País',
+ 'profile.country_none': 'Nenhum país selecionado',
+ 'profile.country_hint': 'O seu país',
+ 'profile.display': 'Exibição',
+ 'profile.display_hint': 'Idioma em que a aplicação é exibida.',
+ 'profile.rank': 'Classificação',
+ 'profile.rank_placeholder': 'Marcador',
+ 'profile.rank_hint': 'Aqui será exibida posteriormente a posição do utilizador no ranking.',
+ 'profile.tests_visible_title': 'Testes visíveis',
+ 'profile.tests_visible_desc': 'Quando este interruptor está ativo, os seus links de partilha funcionam. Se o desativar, os links existentes são guardados mas deixam de ser visíveis publicamente.',
+ 'profile.visible': 'Visível',
+ 'profile.not_visible': 'Não visível',
+ 'profile.account_id': 'ID da conta',
+ 'profile.delete': 'Eliminar perfil',
+ 'profile.deleting': 'A eliminar...',
+ 'profile.save': 'Guardar perfil',
+ 'profile.saving': 'A guardar...',
+ 'profile.saved': 'Perfil guardado.',
+ 'profile.saved_email': 'Perfil guardado. O novo endereço de e-mail pode ainda precisar de confirmação por e-mail.',
+ 'profile.error_save': 'Não foi possível guardar o perfil.',
+ 'profile.error_delete': 'Não foi possível eliminar o perfil.',
+ 'profile.delete_confirm': 'Eliminar realmente o perfil?\n\nIsso removerá permanentemente todos os testes deste utilizador e os planos de análise existentes.',
+ 'profile.deleted': 'Perfil e todos os testes foram eliminados. A conta permanece e pode ser configurada novamente.',
+
+ 'patmgr.saved': 'Guardado com sucesso!',
+ 'patmgr.finalized': 'A avaliação foi finalizada e agora é somente leitura.',
+ 'patmgr.fill_name_date': 'Por favor, preencha nome e data antes de guardar',
+ 'patmgr.share_copied': 'Link de partilha para "{label}" copiado para a área de transferência.',
+ 'patmgr.share_localhost_hint': 'Nota: O link aponta atualmente para localhost e só é acessível se a app estiver acessível externamente.',
+ 'patmgr.share_link_label': 'Link de partilha para "{label}"',
+ 'patmgr.share_error': 'Não foi possível criar o link de partilha: {error}',
+ 'patmgr.unshare_success': 'Partilha para "{label}" revogada.',
+ 'patmgr.unshare_error': 'Não foi possível revogar a partilha: {error}',
+ 'patmgr.profile_loading': 'O perfil ainda está a carregar. Por favor, tente novamente em breve.',
+ 'patmgr.enable_visible': 'Ative "Testes visíveis" no perfil antes de partilhar testes.',
+ 'patmgr.save_before_share': 'Por favor, guarde o teste primeiro antes de o partilhar.',
+ 'patmgr.save_for_share': 'Por favor, guarde primeiro para que o estado atual seja partilhado.',
+ 'patmgr.save_first': 'Por favor, guarde primeiro',
+ 'patmgr.profile_loading_btn': 'A carregar perfil',
+ 'patmgr.enable_visible_btn': 'Ative "Testes visíveis" no perfil',
+
+ 'patlist.title': 'PAT Test Manager',
+ 'patlist.new_assessment': 'Nova avaliação',
+ 'patlist.filter_name': 'Filtrar por nome',
+ 'patlist.filter_date': 'Filtrar por data',
+ 'patlist.col_name': 'Nome',
+ 'patlist.col_date': 'Data',
+ 'patlist.col_result': 'Resultado',
+ 'patlist.date_placeholder': 'dd.mm.aaaa',
+ 'patlist.prev_month': 'Mês anterior',
+ 'patlist.next_month': 'Próximo mês',
+ 'patlist.today': 'Hoje',
+ 'patlist.delete': 'Eliminar',
+ 'patlist.open_assessments': 'Avaliações abertas',
+ 'patlist.no_open': 'Nenhuma avaliação aberta encontrada com os filtros atuais',
+ 'patlist.closed_assessments': 'Avaliações finalizadas',
+ 'patlist.no_closed': 'Nenhuma avaliação finalizada encontrada com os filtros atuais',
+ 'patlist.no_assessments': 'Ainda não há avaliações',
+ 'patlist.no_assessments_hint': 'Crie uma nova avaliação acima e escolha PAT Start, PAT 1, PAT 2 ou PAT 3',
+ 'patlist.share_active': 'Partilha ativa',
+ 'patlist.share_paused': 'Partilha pausada',
+ 'patlist.finalized': 'Finalizado',
+ 'patlist.open': 'Abrir',
+ 'patlist.count': 'Quantidade: {count}',
+ 'patlist.days': 'Se Te Qu Qu Se Sá Do',
+
+ 'detail.back': '← Voltar à vista geral',
+ 'detail.unsaved': '● Alterações não guardadas',
+ 'detail.finalized': 'Finalizado',
+ 'detail.share_active': 'Partilha ativa',
+ 'detail.share_paused': 'Partilha pausada',
+ 'detail.save': 'Guardar',
+ 'detail.finalize': 'Finalizar',
+ 'detail.pdf': 'PDF',
+ 'detail.share': 'Partilhar',
+ 'detail.unshare': 'Revogar partilha',
+ 'detail.readonly': 'Somente leitura',
+ 'detail.readonly_hint': 'Esta avaliação foi finalizada e já não pode ser editada.',
+ 'detail.date_name': 'Data / Nome',
+ 'detail.name_placeholder': 'Nome',
+ 'detail.col_avg_target': 'Média objetivo {soll}',
+ 'detail.col_actual': 'Real',
+ 'detail.col_factor': 'Fator',
+ 'detail.col_points': 'Pontos',
+ 'detail.col_result': 'Resultado',
+
+ 'savedialog.title': 'Alterações não guardadas',
+ 'savedialog.message': 'Tem alterações não guardadas. Deseja guardá-las?',
+ 'savedialog.yes': 'Sim, guardar',
+ 'savedialog.no': 'Não, descartar',
+ 'savedialog.cancel': 'Cancelar',
+
+ 'analysis.title': 'Análise',
+ 'analysis.priority_title': 'Onde deve melhorar?',
+ 'analysis.priority_desc': 'Priorizado por Gap × Fator × Consistência com base nas últimas 5 avaliações.',
+ 'analysis.assessments_count': 'Avaliações: {count}',
+ 'analysis.window': 'Janela de análise: {count}/5',
+ 'analysis.limited_data': 'Base de dados limitada',
+ 'analysis.no_data': 'Ainda não há dados para a análise',
+ 'analysis.no_data_hint': 'Crie primeiro uma avaliação na secção "Avaliações".',
+ 'analysis.no_type_data': 'Sem avaliações para {patType}',
+ 'analysis.no_type_hint': 'Escolha outro tipo PAT ou crie uma nova avaliação.',
+
+ 'weakness.no_data': 'Sem dados de pontos fracos disponíveis.',
+ 'weakness.title': 'Top 3 Pontos fracos',
+ 'weakness.gap': 'Gap: {score}',
+ 'weakness.consistency': 'Consistência: {score}',
+ 'weakness.trend': 'Tendência: {score}',
+
+ 'strength.no_data': 'Sem dados de pontos fortes disponíveis.',
+ 'strength.title': 'Top 3 Pontos fortes',
+ 'strength.actual': 'Real: {score}',
+ 'strength.target': 'Objetivo: {score}',
+ 'strength.trend': 'Tendência: {score}',
+
+ 'training.title': 'Plano de treino ({patType})',
+ 'training.hint': '1 plano ativo + histórico, estado da sessão: aberto / concluído / ignorado.',
+ 'training.reload': 'Recarregar',
+ 'training.duration': 'Duração',
+ 'training.2weeks': '2 semanas',
+ 'training.4weeks': '4 semanas',
+ 'training.6weeks': '6 semanas',
+ 'training.sessions_per_week': 'Sessões/semana',
+ 'training.generate': 'Gerar plano',
+ 'training.save_draft': 'Guardar rascunho',
+ 'training.saving': 'A guardar…',
+ 'training.draft_created': 'Novo rascunho criado. Pode agora ajustar intensidade e exercícios por sessão.',
+ 'training.create_draft_first': 'Crie primeiro um rascunho.',
+ 'training.save_error': 'Não foi possível guardar o plano de treino.',
+ 'training.save_success': 'Plano de treino guardado. O plano ativo anterior foi arquivado.',
+ 'training.week_session': 'Semana {weekNo} · Sessão {sessionNo}',
+ 'training.main_exercise': 'Exercício principal',
+ 'training.side_exercises': 'Exercícios secundários, separados por vírgula',
+ 'training.high': 'alto',
+ 'training.medium': 'médio',
+ 'training.easy': 'fácil',
+ 'training.draft_label': 'Rascunho (parcialmente editável)',
+ 'training.sessions_info': 'Sessões: {count} · alto {high} · médio {medium} · fácil {easy}',
+ 'training.active_plan': 'Plano ativo',
+ 'training.loading': 'A carregar…',
+ 'training.no_active': 'Ainda não há plano de treino ativo.',
+ 'training.generated': 'Gerado: {date} · {weeks} semanas · {sessions} sessões/semana',
+ 'training.week_short': 'S{weekNo} · E{sessionNo}',
+ 'training.status_open': 'aberto',
+ 'training.status_done': 'concluído',
+ 'training.status_skipped': 'ignorado',
+ 'training.note': 'Nota',
+ 'training.history': 'Histórico de planos',
+ 'training.no_history': 'Sem planos arquivados disponíveis.',
+
+ 'gapchart.no_data': 'Sem gráfico de gap disponível.',
+ 'gapchart.title': 'Gráfico de Gap (Prioridade)',
+ 'gapchart.aria': 'Gráfico de barras de gap',
+
+ 'live.title': 'Vista em direto',
+ 'live.stats_title': 'Estatísticas atuais',
+ 'live.basis': 'Base: todas as avaliações, carregadas ao abrir a página',
+ 'live.loading': 'A carregar…',
+ 'live.timestamp': 'Atualizado: agora',
+ 'live.no_data': 'Ainda não há avaliações. Crie uma nova avaliação para ver valores em direto aqui.',
+ 'live.count': 'Avaliações',
+ 'live.count_desc': 'Guardadas na base de dados PAT',
+ 'live.avg_points': 'Ø Pontos totais',
+ 'live.avg_desc': 'Média de todas as avaliações',
+ 'live.last': 'Última avaliação',
+ 'live.unnamed': 'Sem nome',
+ 'live.top': 'Melhor resultado',
+
+ 'shared.missing_token': 'Link de partilha em falta.',
+ 'shared.invalid': 'Este link de partilha é inválido, está desativado ou não está atualmente visível publicamente.',
+ 'shared.load_error': 'Não foi possível carregar o teste partilhado.',
+ 'shared.loading': 'A carregar teste partilhado...',
+ 'shared.label': 'Partilha',
+ 'shared.not_available': 'Teste partilhado não disponível',
+ 'shared.title': 'Teste partilhado',
+ 'shared.default_name': 'PAT Test',
+ 'shared.hint': 'Este link mostra apenas este teste em modo de somente leitura.',
+ 'shared.date': 'Data',
+ 'shared.total_points': 'Pontos totais',
+ 'shared.rating': 'Avaliação',
+ 'shared.target_factor': 'Objetivo {soll} · Fator {faktor}',
+ 'shared.col_points': 'Pontos',
+ 'shared.col_values': 'Valores',
+ 'shared.col_avg': 'Média',
+ 'shared.col_target': 'Objetivo',
+ 'shared.col_factor': 'Fator',
+ 'shared.col_result': 'Resultado',
+
+ 'auth.title': 'PAT Stats',
+ 'auth.subtitle': 'Entrar ou registar',
+ 'auth.description': 'Entre com a sua conta PAT Test para gerir as suas avaliações de forma segura.',
+ 'auth.login_tab': 'Login',
+ 'auth.register_tab': 'Registar',
+ 'auth.email': 'E-mail',
+ 'auth.password': 'Palavra-passe',
+ 'auth.login_success': 'Sessão iniciada com sucesso',
+ 'auth.register_success': 'Registo concluído. Verifique a sua caixa de entrada para confirmação.',
+ 'auth.sending': 'A enviar...',
+ 'auth.login_btn': 'Entrar',
+ 'auth.register_btn': 'Criar conta',
+ 'auth.back': 'Voltar',
+ },
+};
+
+const SUPPORTED_LANGUAGES = ['de', 'en', 'it', 'es', 'pt'];
+
+export function getTranslator(language) {
+ const lang = SUPPORTED_LANGUAGES.includes(language) ? language : 'de';
+ const dict = translations[lang];
+ const fallback = translations.de;
+
+ return (key, vars) => {
+ let str = dict[key] ?? fallback[key] ?? key;
+ if (vars) {
+ str = str.replace(/\{(\w+)\}/g, (_, k) => (vars[k] !== undefined ? vars[k] : `{${k}}`));
+ }
+ return str;
+ };
+}
+
+export { SUPPORTED_LANGUAGES };
+export default translations;
diff --git a/supabase/migrations/20260323020000_user_profile_language.sql b/supabase/migrations/20260323020000_user_profile_language.sql
new file mode 100644
index 0000000..20a2880
--- /dev/null
+++ b/supabase/migrations/20260323020000_user_profile_language.sql
@@ -0,0 +1,2 @@
+alter table public.user_profiles
+ add column if not exists language text not null default 'de';