Compare commits

..

5 Commits
prod ... ph4

Author SHA1 Message Date
mohamad
f8788ee42d feat: Add Dutch translations and update de, es, fr 2025-06-08 00:03:38 +02:00
whtvrboo
b0ec84b8ca
Merge pull request #16 from whtvrboo/i18n-pages-partial
feat: Add missing i18n translations for page components (partial)
2025-06-07 22:41:18 +02:00
google-labs-jules[bot]
198222c3ff feat: Add missing i18n translations for page components (partial)
This commit introduces internationalization for several page components by identifying hardcoded strings, adding them to translation files, and updating the components to use translation keys.

Processed pages:
- fe/src/pages/AuthCallbackPage.vue: I internationalized an error message.
- fe/src/pages/ChoresPage.vue: I internationalized console error messages and an input placeholder.
- fe/src/pages/ErrorNotFound.vue: I found no missing translations.
- fe/src/pages/GroupDetailPage.vue: I internationalized various UI elements (ARIA labels, button text, fallback user display names) and console/error messages.
- fe/src/pages/GroupsPage.vue: I internationalized error messages and console logs.
- fe/src/pages/IndexPage.vue: I found no missing user-facing translations.
- fe/src/pages/ListDetailPage.vue: My analysis is complete, and I identified a console message and a fallback string for translation (implementation of changes for this page is pending).

For each processed page where changes were needed:
- I added new keys to `fe/src/i18n/en.json`.
- I added corresponding placeholder keys `"[TRANSLATE] Original Text"` to `fe/src/i18n/de.json`, `fe/src/i18n/es.json`, and `fe/src/i18n/fr.json`.
- I updated the Vue component to use the `t()` function with the new keys.

Further pages in `fe/src/pages/` are pending analysis and internationalization as per our original plan.
2025-06-07 20:40:49 +00:00
whtvrboo
7ef225daec
Merge pull request #15 from whtvrboo/fix/offline-logout-on-startup
Fix: Prevent automatic logout when starting app offline
2025-06-07 22:31:23 +02:00
google-labs-jules[bot]
6e56e164df Fix: Prevent automatic logout when starting app offline
Problem:
The application would inadvertently log you out if it was started while offline.
This occurred because the `fetchCurrentUser` action in the `authStore` would attempt to fetch your profile, and if this network request failed (as it does when offline), the catch block would unconditionally call `clearTokens()`. This removed the authentication token, effectively logging you out and preventing access to any cached data or offline functionality.

Solution:
I modified the `fetchCurrentUser` action in `fe/src/stores/auth.ts`:
- The `catch` block now inspects the error.
- `clearTokens()` is only called if the error is a specific HTTP authentication error from the server (401 Unauthorized or 403 Forbidden) when online.
- For network errors (indicating offline status) or other non-auth HTTP errors, tokens are preserved. The user object (`user.value`) might remain null if no cached profile is available, but the authentication token itself is kept.

This change allows the application to remain in a logged-in state when started offline. The service worker can then serve cached API responses, and you can view previously accessed data. Navigation guards rely on `isAuthenticated` (which now remains true offline as long as a token exists), so you are not incorrectly redirected to the login page.
2025-06-07 20:30:52 +00:00
12 changed files with 2074 additions and 1181 deletions

File diff suppressed because it is too large Load Diff

View File

@ -73,7 +73,10 @@
"groupNameRequired": "Group name is required",
"createFailed": "Failed to create group. Please try again.",
"inviteCodeRequired": "Invite code is required",
"joinFailed": "Failed to join group. Please check the invite code and try again."
"joinFailed": "Failed to join group. Please check the invite code and try again.",
"invalidDataFromServer": "Invalid data received from server.",
"createFailedConsole": "Error creating group:",
"joinFailedConsole": "Error joining group:"
},
"notifications": {
"groupCreatedSuccess": "Group '{groupName}' created successfully.",
@ -85,7 +88,8 @@
"authCallbackPage": {
"redirecting": "Redirecting...",
"errors": {
"authenticationFailed": "Authentication failed"
"authenticationFailed": "Authentication failed",
"noTokenProvided": "No token provided"
}
},
"choresPage": {
@ -125,7 +129,17 @@
"save": "Save Changes",
"create": "Create",
"editChore": "Edit Chore",
"createChore": "Create Chore"
"createChore": "Create Chore",
"intervalPlaceholder": "e.g., 10"
},
"consoleErrors": {
"loadFailed": "Failed to load all chores:",
"loadGroupsFailed": "Failed to load groups",
"createAssignmentForNewChoreFailed": "Failed to create assignment for new chore:",
"saveFailed": "Failed to save chore:",
"deleteFailed": "Failed to delete chore:",
"createAssignmentFailed": "Failed to create assignment:",
"updateCompletionStatusFailed": "Failed to update chore completion status:"
},
"deleteConfirm": {
"title": "Confirm Deletion",
@ -160,10 +174,14 @@
"title": "Group Members",
"defaultRole": "Member",
"removeButton": "Remove",
"emptyState": "No members found."
"emptyState": "No members found.",
"closeMenuLabel": "Close menu"
},
"invites": {
"title": "Invite Members",
"description": "Invite new members by generating a shareable code.",
"addMemberButtonLabel": "Add member",
"closeInviteLabel": "Close invite",
"regenerateButton": "Regenerate Invite Code",
"generateButton": "Generate Invite Code",
"activeCodeLabel": "Current Active Invite Code:",
@ -174,6 +192,15 @@
"newDataInvalid": "New invite code data is invalid."
}
},
"errors": {
"failedToFetchActiveInvite": "Failed to fetch active invite code.",
"failedToFetchGroupDetails": "Failed to fetch group details.",
"failedToLoadUpcomingChores": "Error loading upcoming chores:",
"failedToLoadRecentExpenses": "Error loading recent expenses:"
},
"console": {
"noActiveInvite": "No active invite code found for this group."
},
"chores": {
"title": "Group Chores",
"manageButton": "Manage Chores",
@ -191,6 +218,8 @@
"settleShareButton": "Settle My Share",
"activityLabel": "Activity:",
"byUser": "by",
"fallbackUserName": "User ID: {userId}",
"activityByUserFallback": "User {userId}",
"splitTypes": {
"equal": "Equal",
"exactAmounts": "Exact Amounts",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,13 @@
import en from './en.json';
import de from './de.json';
import nl from './nl.json';
import fr from './fr.json';
import es from './es.json';
export default {
en,
de,
nl,
fr,
es
};

631
fe/src/i18n/nl.json Normal file
View File

@ -0,0 +1,631 @@
{
"message": {
"hello": "Hallo"
},
"loginPage": {
"emailLabel": "E-mail",
"passwordLabel": "Wachtwoord",
"togglePasswordVisibilityLabel": "Wachtwoord zichtbaarheid wisselen",
"loginButton": "Inloggen",
"signupLink": "Geen account? Aanmelden",
"errors": {
"emailRequired": "E-mail is vereist",
"emailInvalid": "Ongeldig e-mailformaat",
"passwordRequired": "Wachtwoord is vereist",
"loginFailed": "Inloggen mislukt. Controleer uw gegevens."
},
"notifications": {
"loginSuccess": "Succesvol ingelogd"
}
},
"listsPage": {
"retryButton": "Opnieuw proberen",
"emptyState": {
"noListsForGroup": "Geen lijsten gevonden voor deze groep.",
"noListsYet": "U heeft nog geen lijsten.",
"personalGlobalInfo": "Maak een persoonlijke lijst of word lid van een groep om gedeelde lijsten te zien.",
"groupSpecificInfo": "Deze groep heeft nog geen lijsten."
},
"createNewListButton": "Nieuwe lijst maken",
"loadingLists": "Lijsten laden...",
"noDescription": "Geen beschrijving",
"addItemPlaceholder": "Nieuw item toevoegen...",
"createCard": {
"title": "+ Lijst"
},
"pageTitle": {
"forGroup": "Lijsten voor {groupName}",
"forGroupId": "Lijsten voor Groep {groupId}",
"myLists": "Mijn Lijsten"
},
"errors": {
"fetchFailed": "Ophalen van lijsten mislukt."
}
},
"groupsPage": {
"retryButton": "Opnieuw proberen",
"emptyState": {
"title": "Nog geen groepen!",
"description": "U bent nog geen lid van groepen. Maak er een aan of word lid met een uitnodigingscode.",
"createButton": "Nieuwe groep maken"
},
"groupCard": {
"newListButton": "Lijst"
},
"createCard": {
"title": "+ Groep"
},
"joinGroup": {
"title": "Lid worden van een groep met uitnodigingscode",
"inputLabel": "Voer uitnodigingscode in",
"inputPlaceholder": "Voer uitnodigingscode in",
"joinButton": "Deelnemen"
},
"createDialog": {
"title": "Nieuwe groep maken",
"closeButtonLabel": "Sluiten",
"groupNameLabel": "Groepsnaam",
"cancelButton": "Annuleren",
"createButton": "Maken"
},
"errors": {
"fetchFailed": "Laden van groepen mislukt",
"groupNameRequired": "Groepsnaam is vereist",
"createFailed": "Maken van groep mislukt. Probeer het opnieuw.",
"inviteCodeRequired": "Uitnodigingscode is vereist",
"joinFailed": "Deelnemen aan groep mislukt. Controleer de uitnodigingscode en probeer het opnieuw.",
"invalidDataFromServer": "Ongeldige gegevens ontvangen van server.",
"createFailedConsole": "Fout bij het maken van groep:",
"joinFailedConsole": "Fout bij het deelnemen aan groep:"
},
"notifications": {
"groupCreatedSuccess": "Groep '{groupName}' succesvol aangemaakt.",
"joinSuccessNamed": "Succesvol lid geworden van groep '{groupName}'.",
"joinSuccessGeneric": "Succesvol lid geworden van groep.",
"listCreatedSuccess": "Lijst '{listName}' succesvol aangemaakt."
}
},
"authCallbackPage": {
"redirecting": "Bezig met omleiden...",
"errors": {
"authenticationFailed": "Authenticatie mislukt",
"noTokenProvided": "Geen token opgegeven"
}
},
"choresPage": {
"title": "Taken",
"tabs": {
"overdue": "Achterstallig",
"today": "Vandaag",
"upcoming": "Aankomend",
"allPending": "Alle openstaande",
"completed": "Voltooid"
},
"viewToggle": {
"calendarLabel": "Kalenderweergave",
"calendarText": "Kalender",
"listLabel": "Lijstweergave",
"listText": "Lijst"
},
"newChoreButtonLabel": "Nieuwe taak",
"newChoreButtonText": "Nieuwe taak",
"loadingState": {
"loadingChores": "Taken laden..."
},
"calendar": {
"prevMonthLabel": "Vorige maand",
"nextMonthLabel": "Volgende maand",
"weekdays": {
"sun": "Zo",
"mon": "Ma",
"tue": "Di",
"wed": "Wo",
"thu": "Do",
"fri": "Vr",
"sat": "Za"
},
"addChoreToDayLabel": "Taak aan deze dag toevoegen",
"emptyState": "Geen taken om weer te geven voor deze periode."
},
"listView": {
"choreTypePersonal": "Persoonlijk",
"choreTypeGroupFallback": "Groep",
"completedDatePrefix": "Voltooid:",
"actions": {
"doneTitle": "Markeer als voltooid",
"doneText": "Gedaan",
"undoTitle": "Markeer als niet voltooid",
"undoText": "Ongedaan maken",
"editTitle": "Bewerken",
"editLabel": "Taak bewerken",
"editText": "Bewerken",
"deleteTitle": "Verwijderen",
"deleteLabel": "Taak verwijderen",
"deleteText": "Verwijderen"
},
"emptyState": {
"message": "Geen taken in deze weergave. Goed gedaan!",
"viewAllButton": "Alle openstaande bekijken"
}
},
"choreModal": {
"editTitle": "Taak bewerken",
"newTitle": "Nieuwe taak",
"closeButtonLabel": "Modal sluiten",
"nameLabel": "Naam",
"namePlaceholder": "Voer taaknaam in",
"typeLabel": "Type",
"typePersonal": "Persoonlijk",
"typeGroup": "Groep",
"groupLabel": "Groep",
"groupSelectDefault": "Selecteer een groep",
"descriptionLabel": "Beschrijving",
"descriptionPlaceholder": "Voeg een beschrijving toe (optioneel)",
"frequencyLabel": "Frequentie",
"intervalLabel": "Interval (dagen)",
"intervalPlaceholder": "bijv. 10",
"dueDateLabel": "Vervaldatum",
"quickDueDateToday": "Vandaag",
"quickDueDateTomorrow": "Morgen",
"quickDueDateNextWeek": "Volgende week",
"cancelButton": "Annuleren",
"saveButton": "Opslaan"
},
"consoleErrors": {
"loadFailed": "Laden van alle taken mislukt:",
"loadGroupsFailed": "Laden van groepen mislukt",
"createAssignmentForNewChoreFailed": "Toewijzing voor nieuwe taak kon niet worden gemaakt:",
"saveFailed": "Opslaan van taak mislukt:",
"deleteFailed": "Verwijderen van taak mislukt:",
"createAssignmentFailed": "Toewijzing kon niet worden gemaakt:",
"updateCompletionStatusFailed": "Voltooiingsstatus van taak kon niet worden bijgewerkt:"
},
"deleteDialog": {
"title": "Taak verwijderen",
"confirmationText": "Weet u zeker dat u deze taak wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
"deleteButton": "Verwijderen"
},
"shortcutsModal": {
"title": "Sneltoetsen",
"descNewChore": "Nieuwe taak",
"descToggleView": "Weergave wisselen (Lijst/Kalender)",
"descToggleShortcuts": "Sneltoetsen tonen/verbergen",
"descCloseModal": "Open Modal/Dialoog sluiten"
},
"frequencyOptions": {
"oneTime": "Eenmalig",
"daily": "Dagelijks",
"weekly": "Wekelijks",
"monthly": "Maandelijks",
"custom": "Aangepast"
},
"frequency": {
"customInterval": "Elke {n} dag | Elke {n} dagen"
},
"formatters": {
"noDueDate": "Geen vervaldatum",
"dueToday": "Vandaag te doen",
"dueTomorrow": "Morgen te doen",
"overdueFull": "Achterstallig: {date}",
"dueFull": "Vervalt op {date}",
"invalidDate": "Ongeldige datum"
},
"notifications": {
"loadFailed": "Laden van taken mislukt.",
"loadGroupsFailed": "Laden van groepen mislukt.",
"updateSuccess": "Taak '{name}' succesvol bijgewerkt.",
"createSuccess": "Taak '{name}' succesvol aangemaakt.",
"updateFailed": "Bijwerken van taak mislukt.",
"createFailed": "Aanmaken van taak mislukt.",
"deleteSuccess": "Taak '{name}' succesvol verwijderd.",
"deleteFailed": "Verwijderen van taak mislukt.",
"markedDone": "{name} gemarkeerd als voltooid.",
"markedNotDone": "{name} gemarkeerd als niet voltooid.",
"statusUpdateFailed": "Status van taak kon niet worden bijgewerkt.",
"createAssignmentFailed": "Toewijzing voor taak kon niet worden gemaakt."
},
"validation": {
"nameRequired": "Taaknaam is vereist.",
"groupRequired": "Selecteer een groep voor groepstaken.",
"intervalRequired": "Aangepast interval moet minimaal 1 dag zijn.",
"dueDateRequired": "Vervaldatum is vereist.",
"invalidDueDate": "Ongeldig formaat vervaldatum."
},
"unsavedChangesConfirmation": "U heeft niet-opgeslagen wijzigingen in het taakformulier. Weet u zeker dat u wilt vertrekken?"
},
"errorNotFoundPage": {
"errorCode": "404",
"errorMessage": "Oeps. Hier is niets...",
"goHomeButton": "Naar de startpagina"
},
"groupDetailPage": {
"loadingLabel": "Groepsdetails laden...",
"retryButton": "Opnieuw proberen",
"groupNotFound": "Groep niet gevonden of er is een fout opgetreden.",
"members": {
"title": "Groepsleden",
"defaultRole": "Lid",
"removeButton": "Verwijderen",
"emptyState": "Geen leden gevonden.",
"closeMenuLabel": "Menu sluiten"
},
"invites": {
"title": "Leden uitnodigen",
"description": "Nodig nieuwe leden uit door een deelbare code te genereren.",
"addMemberButtonLabel": "Lid toevoegen",
"closeInviteLabel": "Uitnodiging sluiten",
"regenerateButton": "Uitnodigingscode opnieuw genereren",
"generateButton": "Uitnodigingscode genereren",
"activeCodeLabel": "Huidige actieve uitnodigingscode:",
"copyButtonLabel": "Kopieer uitnodigingscode",
"copySuccess": "Uitnodigingscode gekopieerd naar klembord!",
"emptyState": "Geen actieve uitnodigingscode. Klik op de knop hierboven om er een te genereren.",
"errors": {
"newDataInvalid": "Gegevens van nieuwe uitnodigingscode zijn ongeldig."
}
},
"errors": {
"failedToFetchActiveInvite": "Ophalen van actieve uitnodigingscode mislukt.",
"failedToFetchGroupDetails": "Ophalen van groepsdetails mislukt.",
"failedToLoadUpcomingChores": "Fout bij het laden van aankomende taken:",
"failedToLoadRecentExpenses": "Fout bij het laden van recente uitgaven:"
},
"console": {
"noActiveInvite": "Geen actieve uitnodigingscode gevonden voor deze groep."
},
"chores": {
"title": "Groepstaken",
"manageButton": "Taken beheren",
"duePrefix": "Vervalt:",
"emptyState": "Geen taken gepland. Klik op \"Taken beheren\" om er enkele aan te maken!"
},
"expenses": {
"title": "Groepsuitgaven",
"manageButton": "Uitgaven beheren",
"emptyState": "Geen uitgaven geregistreerd. Klik op \"Uitgaven beheren\" om er enkele toe te voegen!",
"paidBy": "Betaald door:",
"owes": "is verschuldigd",
"paidAmount": "Betaald:",
"onDate": "op",
"settleShareButton": "Mijn deel vereffenen",
"activityLabel": "Activiteit:",
"byUser": "door",
"fallbackUserName": "Gebruikers-ID: {userId}",
"activityByUserFallback": "Gebruiker {userId}",
"splitTypes": {
"equal": "Gelijk",
"exactAmounts": "Exacte bedragen",
"percentage": "Percentage",
"shares": "Aandelen",
"itemBased": "Op item gebaseerd"
}
},
"notifications": {
"fetchDetailsFailed": "Ophalen van groepsdetails mislukt.",
"fetchInviteFailed": "Ophalen van actieve uitnodigingscode mislukt.",
"generateInviteSuccess": "Nieuwe uitnodigingscode succesvol gegenereerd!",
"generateInviteError": "Genereren van uitnodigingscode mislukt.",
"clipboardNotSupported": "Klembord niet ondersteund of geen code om te kopiëren.",
"copyInviteFailed": "Kopiëren van uitnodigingscode mislukt.",
"removeMemberSuccess": "Lid succesvol verwijderd",
"removeMemberFailed": "Verwijderen van lid mislukt",
"loadExpensesFailed": "Laden van recente uitgaven mislukt.",
"cannotSettleOthersShares": "U kunt alleen uw eigen aandelen vereffenen.",
"settlementDataMissing": "Kan vereffening niet verwerken: gegevens ontbreken.",
"settleShareSuccess": "Aandeel succesvol vereffend!",
"settleShareFailed": "Vereffenen van aandeel mislukt."
},
"loading": {
"settlement": "Bezig met vereffenen..."
},
"settleShareModal": {
"title": "Aandeel vereffenen",
"settleAmountFor": "Bedrag vereffenen voor {userName}:",
"amountLabel": "Bedrag",
"cancelButton": "Annuleren",
"confirmButton": "Bevestigen",
"errors": {
"enterAmount": "Voer een bedrag in.",
"positiveAmount": "Voer een positief bedrag in.",
"exceedsRemaining": "Bedrag mag resterend bedrag niet overschrijden: {amount}.",
"noSplitSelected": "Fout: Geen verdeling geselecteerd."
}
},
"status": {
"settled": "Vereffend",
"partiallySettled": "Gedeeltelijk vereffend",
"unsettled": "Openstaand",
"paid": "Betaald",
"partiallyPaid": "Gedeeltelijk betaald",
"unpaid": "Onbetaald",
"unknown": "Onbekende status"
}
},
"accountPage": {
"title": "Accountinstellingen",
"loadingProfile": "Profiel laden...",
"retryButton": "Opnieuw proberen",
"profileSection": {
"header": "Profielinformatie",
"nameLabel": "Naam",
"emailLabel": "E-mail",
"saveButton": "Wijzigingen opslaan"
},
"passwordSection": {
"header": "Wachtwoord wijzigen",
"currentPasswordLabel": "Huidig wachtwoord",
"newPasswordLabel": "Nieuw wachtwoord",
"changeButton": "Wachtwoord wijzigen"
},
"notificationsSection": {
"header": "Notificatievoorkeuren",
"emailNotificationsLabel": "E-mailnotificaties",
"emailNotificationsDescription": "Ontvang e-mailnotificaties voor belangrijke updates",
"listUpdatesLabel": "Lijstupdates",
"listUpdatesDescription": "Ontvang een melding wanneer lijsten worden bijgewerkt",
"groupActivitiesLabel": "Groepsactiviteiten",
"groupActivitiesDescription": "Ontvang meldingen voor groepsactiviteiten"
},
"notifications": {
"profileLoadFailed": "Laden van profiel mislukt",
"profileUpdateSuccess": "Profiel succesvol bijgewerkt",
"profileUpdateFailed": "Bijwerken van profiel mislukt",
"passwordFieldsRequired": "Vul zowel het huidige als het nieuwe wachtwoordveld in.",
"passwordTooShort": "Nieuw wachtwoord moet minimaal 8 tekens lang zijn.",
"passwordChangeSuccess": "Wachtwoord succesvol gewijzigd",
"passwordChangeFailed": "Wijzigen van wachtwoord mislukt",
"preferencesUpdateSuccess": "Voorkeuren succesvol bijgewerkt",
"preferencesUpdateFailed": "Bijwerken van voorkeuren mislukt"
},
"saving": "Opslaan..."
},
"signupPage": {
"header": "Aanmelden",
"fullNameLabel": "Volledige naam",
"emailLabel": "E-mail",
"passwordLabel": "Wachtwoord",
"confirmPasswordLabel": "Bevestig wachtwoord",
"togglePasswordVisibility": "Wachtwoord zichtbaarheid wisselen",
"submitButton": "Aanmelden",
"loginLink": "Heeft u al een account? Inloggen",
"validation": {
"nameRequired": "Naam is vereist",
"emailRequired": "E-mail is vereist",
"emailInvalid": "Ongeldig e-mailformaat",
"passwordRequired": "Wachtwoord is vereist",
"passwordLength": "Wachtwoord moet minimaal 8 tekens lang zijn",
"confirmPasswordRequired": "Bevestig uw wachtwoord",
"passwordsNoMatch": "Wachtwoorden komen niet overeen"
},
"notifications": {
"signupFailed": "Aanmelden mislukt. Probeer het opnieuw.",
"signupSuccess": "Account succesvol aangemaakt. Log in alstublieft."
}
},
"listDetailPage": {
"loading": {
"list": "Lijst laden...",
"items": "Items laden...",
"ocrProcessing": "Afbeelding verwerken...",
"addingOcrItems": "OCR-items toevoegen...",
"costSummary": "Samenvatting laden...",
"expenses": "Uitgaven laden...",
"settlement": "Bezig met vereffenen..."
},
"errors": {
"fetchFailed": "Laden van lijstdetails mislukt.",
"genericLoadFailure": "Groep niet gevonden of er is een fout opgetreden.",
"ocrNoItems": "Geen items uit de afbeelding gehaald.",
"ocrFailed": "Verwerken van afbeelding mislukt.",
"addItemFailed": "Toevoegen van item mislukt.",
"updateItemFailed": "Bijwerken van item mislukt.",
"updateItemPriceFailed": "Bijwerken van itemprijs mislukt.",
"deleteItemFailed": "Verwijderen van item mislukt.",
"addOcrItemsFailed": "Toevoegen van OCR-items mislukt.",
"fetchItemsFailed": "Laden van items mislukt: {errorMessage}",
"loadCostSummaryFailed": "Laden van kostensamenvatting mislukt."
},
"retryButton": "Opnieuw proberen",
"buttons": {
"addViaOcr": "Toevoegen via OCR",
"addItem": "Toevoegen",
"addItems": "Items toevoegen",
"cancel": "Annuleren",
"confirm": "Bevestigen",
"saveChanges": "Wijzigingen opslaan",
"close": "Sluiten",
"costSummary": "Kostensamenvatting"
},
"badges": {
"groupList": "Groepslijst",
"personalList": "Persoonlijke lijst"
},
"items": {
"emptyState": {
"title": "Nog geen items!",
"message": "Voeg items toe via het onderstaande formulier."
},
"addItemForm": {
"placeholder": "Nieuw item toevoegen",
"quantityPlaceholder": "Aantal",
"itemNameSrLabel": "Naam nieuw item",
"quantitySrLabel": "Hoeveelheid"
},
"pricePlaceholder": "Prijs",
"editItemAriaLabel": "Item bewerken",
"deleteItemAriaLabel": "Item verwijderen"
},
"modals": {
"ocr": {
"title": "Items toevoegen via OCR",
"uploadLabel": "Afbeelding uploaden"
},
"confirmation": {
"title": "Bevestiging"
},
"editItem": {
"title": "Item bewerken",
"nameLabel": "Itemnaam",
"quantityLabel": "Hoeveelheid"
},
"costSummary": {
"title": "Kostensamenvatting lijst",
"totalCostLabel": "Totale kosten lijst:",
"equalShareLabel": "Gelijk deel per gebruiker:",
"participantsLabel": "Deelnemende gebruikers:",
"userBalancesHeader": "Gebruikerssaldi",
"tableHeaders": {
"user": "Gebruiker",
"itemsAddedValue": "Waarde toegevoegde items",
"amountDue": "Verschuldigd bedrag",
"balance": "Saldo"
},
"emptyState": "Geen kostensamenvatting beschikbaar."
},
"settleShare": {
"title": "Aandeel vereffenen",
"settleAmountFor": "Bedrag vereffenen voor {userName}:",
"amountLabel": "Bedrag",
"errors": {
"enterAmount": "Voer een bedrag in.",
"positiveAmount": "Voer een positief bedrag in.",
"exceedsRemaining": "Bedrag mag resterend bedrag niet overschrijden: {amount}.",
"noSplitSelected": "Fout: Geen verdeling geselecteerd."
}
}
},
"confirmations": {
"updateMessage": "'{itemName}' markeren als {status}?",
"statusComplete": "voltooid",
"statusIncomplete": "onvolledig",
"deleteMessage": "'{itemName}' verwijderen? Dit kan niet ongedaan worden gemaakt."
},
"notifications": {
"itemAddedSuccess": "Item succesvol toegevoegd.",
"itemsAddedSuccessOcr": "{count} item(s) succesvol toegevoegd via OCR.",
"itemUpdatedSuccess": "Item succesvol bijgewerkt.",
"itemDeleteSuccess": "Item succesvol verwijderd.",
"enterItemName": "Voer een itemnaam in.",
"costSummaryLoadFailed": "Laden van kostensamenvatting mislukt.",
"cannotSettleOthersShares": "U kunt alleen uw eigen aandelen vereffenen.",
"settlementDataMissing": "Kan vereffening niet verwerken: gegevens ontbreken.",
"settleShareSuccess": "Aandeel succesvol vereffend!",
"settleShareFailed": "Vereffenen van aandeel mislukt."
},
"expensesSection": {
"title": "Uitgaven",
"addExpenseButton": "Uitgave toevoegen",
"loading": "Uitgaven laden...",
"emptyState": "Nog geen uitgaven geregistreerd voor deze lijst.",
"paidBy": "Betaald door:",
"onDate": "op",
"owes": "is verschuldigd",
"paidAmount": "Betaald:",
"activityLabel": "Activiteit:",
"byUser": "door",
"settleShareButton": "Mijn deel vereffenen",
"retryButton": "Opnieuw proberen"
},
"status": {
"settled": "Vereffend",
"partiallySettled": "Gedeeltelijk vereffend",
"unsettled": "Openstaand",
"paid": "Betaald",
"partiallyPaid": "Gedeeltelijk betaald",
"unpaid": "Onbetaald",
"unknown": "Onbekende status"
}
},
"myChoresPage": {
"title": "Mijn toegewezen taken",
"showCompletedToggle": "Voltooide tonen",
"timelineHeaders": {
"overdue": "Achterstallig",
"today": "Vandaag te doen",
"thisWeek": "Deze week",
"later": "Later",
"completed": "Voltooid"
},
"choreCard": {
"personal": "Persoonlijk",
"group": "Groep",
"duePrefix": "Vervalt",
"completedPrefix": "Voltooid",
"dueToday": "Vandaag te doen",
"markCompleteButton": "Markeer als voltooid"
},
"frequencies": {
"one_time": "Eenmalig",
"daily": "Dagelijks",
"weekly": "Wekelijks",
"monthly": "Maandelijks",
"custom": "Aangepast",
"unknown": "Onbekende frequentie"
},
"dates": {
"invalidDate": "Ongeldige datum",
"unknownDate": "Onbekende datum"
},
"emptyState": {
"title": "Nog geen toewijzingen!",
"noAssignmentsPending": "U heeft geen openstaande taaktoewijzingen.",
"noAssignmentsAll": "U heeft geen taaktoewijzingen (voltooid of openstaand).",
"viewAllChoresButton": "Alle taken bekijken"
},
"notifications": {
"loadFailed": "Laden van toewijzingen mislukt",
"markedComplete": "\"{choreName}\" gemarkeerd als voltooid!",
"markCompleteFailed": "Markeren van toewijzing als voltooid mislukt"
}
},
"personalChoresPage": {
"title": "Persoonlijke taken",
"newChoreButton": "Nieuwe taak",
"editButton": "Bewerken",
"deleteButton": "Verwijderen",
"cancelButton": "Annuleren",
"saveButton": "Opslaan",
"modals": {
"editChoreTitle": "Taak bewerken",
"newChoreTitle": "Nieuwe taak",
"deleteChoreTitle": "Taak verwijderen"
},
"form": {
"nameLabel": "Naam",
"descriptionLabel": "Beschrijving",
"frequencyLabel": "Frequentie",
"intervalLabel": "Interval (dagen)",
"dueDateLabel": "Volgende vervaldatum"
},
"deleteDialog": {
"confirmationText": "Weet u zeker dat u deze taak wilt verwijderen?"
},
"frequencies": {
"one_time": "Eenmalig",
"daily": "Dagelijks",
"weekly": "Wekelijks",
"monthly": "Maandelijks",
"custom": "Aangepast",
"unknown": "Onbekende frequentie"
},
"dates": {
"invalidDate": "Ongeldige datum",
"duePrefix": "Vervalt"
},
"notifications": {
"loadFailed": "Laden van persoonlijke taken mislukt",
"updateSuccess": "Persoonlijke taak succesvol bijgewerkt",
"createSuccess": "Persoonlijke taak succesvol aangemaakt",
"saveFailed": "Opslaan van persoonlijke taak mislukt",
"deleteSuccess": "Persoonlijke taak succesvol verwijderd",
"deleteFailed": "Verwijderen van persoonlijke taak mislukt"
}
},
"indexPage": {
"welcomeMessage": "Welkom bij de Valerie UI App",
"mainPageInfo": "Dit is de hoofdindexpagina.",
"sampleTodosHeader": "Voorbeeldtaken (uit IndexPage-gegevens)",
"totalCountLabel": "Totaal aantal uit meta:",
"noTodos": "Geen taken om weer te geven."
}
}

View File

@ -9,6 +9,7 @@ import enMessages from './i18n/en.json' // Import en.json directly
import deMessages from './i18n/de.json'
import frMessages from './i18n/fr.json'
import esMessages from './i18n/es.json'
import nlMessages from './i18n/nl.json'
// Global styles
import './assets/main.scss'

View File

@ -38,7 +38,7 @@ onMounted(async () => {
const tokenToUse = accessToken || legacyToken;
if (!tokenToUse) {
throw new Error('No token provided');
throw new Error(t('authCallbackPage.errors.noTokenProvided'));
}
await authStore.setTokens({ access_token: tokenToUse, refresh_token: refreshToken });

View File

@ -80,7 +80,7 @@ const loadChores = async () => {
cachedChores.value = mappedChores;
cachedTimestamp.value = Date.now()
} catch (error) {
console.error('Failed to load all chores:', error)
console.error(t('choresPage.consoleErrors.loadFailed'), error)
notificationStore.addNotification({ message: t('choresPage.notifications.loadFailed', 'Failed to load chores.'), type: 'error' })
} finally {
isLoading.value = false
@ -91,7 +91,7 @@ const loadGroups = async () => {
try {
groups.value = await groupService.getUserGroups();
} catch (error) {
console.error("Failed to load groups", error);
console.error(t('choresPage.consoleErrors.loadGroupsFailed'), error);
notificationStore.addNotification({ message: t('choresPage.notifications.loadGroupsFailed', 'Failed to load groups.'), type: 'error' });
}
}
@ -227,7 +227,7 @@ const handleFormSubmit = async () => {
due_date: createdChore.next_due_date
});
} catch (assignmentError) {
console.error('Failed to create assignment for new chore:', assignmentError);
console.error(t('choresPage.consoleErrors.createAssignmentForNewChoreFailed'), assignmentError);
// Continue anyway since the chore was created
}
}
@ -237,7 +237,7 @@ const handleFormSubmit = async () => {
showChoreModal.value = false;
await loadChores();
} catch (error) {
console.error('Failed to save chore:', error);
console.error(t('choresPage.consoleErrors.saveFailed'), error);
notificationStore.addNotification({ message: t('choresPage.notifications.saveFailed', 'Failed to save the chore.'), type: 'error' });
}
}
@ -255,7 +255,7 @@ const deleteChore = async () => {
showDeleteDialog.value = false
await loadChores()
} catch (error) {
console.error('Failed to delete chore:', error)
console.error(t('choresPage.consoleErrors.deleteFailed'), error)
notificationStore.addNotification({ message: t('choresPage.notifications.deleteFailed', 'Failed to delete chore.'), type: 'error' })
}
}
@ -271,7 +271,7 @@ const toggleCompletion = async (chore: ChoreWithCompletion) => {
});
chore.current_assignment_id = assignment.id;
} catch (error) {
console.error('Failed to create assignment:', error);
console.error(t('choresPage.consoleErrors.createAssignmentFailed'), error);
notificationStore.addNotification({
message: t('choresPage.notifications.createAssignmentFailed', 'Failed to create assignment for chore.'),
type: 'error'
@ -299,7 +299,7 @@ const toggleCompletion = async (chore: ChoreWithCompletion) => {
});
await loadChores();
} catch (error) {
console.error('Failed to update chore completion status:', error);
console.error(t('choresPage.consoleErrors.updateCompletionStatusFailed'), error);
notificationStore.addNotification({ message: t('choresPage.notifications.updateFailed', 'Failed to update chore status.'), type: 'error' });
chore.is_completed = originalCompleted;
} finally {
@ -403,7 +403,7 @@ const toggleCompletion = async (chore: ChoreWithCompletion) => {
<label class="form-label" for="chore-interval">{{ t('choresPage.form.interval', 'Interval (days)')
}}</label>
<input id="chore-interval" type="number" v-model.number="choreForm.custom_interval_days"
class="form-input" placeholder="e.g., 10" min="1">
class="form-input" :placeholder="t('choresPage.form.intervalPlaceholder')" min="1">
</div>
<div class="form-group">
<label class="form-label">{{ t('choresPage.form.type', 'Type') }}</label>

View File

@ -21,7 +21,7 @@
<div class="popup-header">
<span class="font-semibold truncate">{{ member.email }}</span>
<VButton variant="neutral" size="sm" :icon-only="true" iconLeft="x" @click="activeMemberMenu = null"
aria-label="Close menu" />
:aria-label="t('groupDetailPage.members.closeMenuLabel')" />
</div>
<div class="member-menu-content">
<VBadge :text="member.role || t('groupDetailPage.members.defaultRole')"
@ -37,8 +37,7 @@
</div>
<button ref="addMemberButtonRef" @click="toggleInviteUI" class="add-member-btn"
:aria-label="t('groupDetailPage.invites.title')">
<!-- <VIcon name="plus" size="md" /> -->
+
{{ t('groupDetailPage.invites.addMemberButtonLabel') }}
</button>
<!-- Invite Members Popup -->
@ -47,9 +46,9 @@
<VHeading :level="3" class="!m-0 !p-0 !border-none">{{ t('groupDetailPage.invites.title') }}
</VHeading>
<VButton variant="neutral" size="sm" :icon-only="true" iconLeft="x" @click="showInviteUI = false"
aria-label="Close invite" />
:aria-label="t('groupDetailPage.invites.closeInviteLabel')" />
</div>
<p class="text-sm text-gray-500 my-2">Invite new members by generating a shareable code.</p>
<p class="text-sm text-gray-500 my-2">{{ t('groupDetailPage.invites.description') }}</p>
<VButton variant="primary" class="w-full" @click="generateInviteCode" :disabled="generatingInvite">
<VSpinner v-if="generatingInvite" size="sm" /> {{ inviteCode ?
t('groupDetailPage.invites.regenerateButton') :
@ -146,7 +145,7 @@
<div class="neo-splits-list">
<div v-for="split in expense.splits" :key="split.id" class="neo-split-item">
<div class="split-col split-user">
<strong>{{ split.user?.name || split.user?.email || `User ID: ${split.user_id}` }}</strong>
<strong>{{ split.user?.name || split.user?.email || t('groupDetailPage.expenses.fallbackUserName', { userId: split.user_id }) }}</strong>
</div>
<div class="split-col split-owes">
{{ t('groupDetailPage.expenses.owes') }} <strong>{{
@ -178,8 +177,7 @@
{{ t('groupDetailPage.expenses.activityLabel') }} {{
formatCurrency(activity.amount_paid) }}
{{
t('groupDetailPage.expenses.byUser') }} {{ activity.payer?.name || `User
${activity.paid_by_user_id}` }} {{ t('groupDetailPage.expenses.onDate') }} {{ new
t('groupDetailPage.expenses.byUser') }} {{ activity.payer?.name || t('groupDetailPage.expenses.activityByUserFallback', { userId: activity.paid_by_user_id }) }} {{ t('groupDetailPage.expenses.onDate') }} {{ new
Date(activity.paid_at).toLocaleDateString() }}
</li>
</ul>
@ -209,7 +207,7 @@
<div v-else>
<p>{{ t('groupDetailPage.settleShareModal.settleAmountFor', {
userName: selectedSplitForSettlement?.user?.name
|| selectedSplitForSettlement?.user?.email || `User ID: ${selectedSplitForSettlement?.user_id}`
|| selectedSplitForSettlement?.user?.email || t('groupDetailPage.expenses.fallbackUserName', { userId: selectedSplitForSettlement?.user_id })
}) }}</p>
<VFormField :label="t('groupDetailPage.settleShareModal.amountLabel')"
:error-message="settleAmountError || undefined">
@ -383,9 +381,9 @@ const fetchActiveInviteCode = async () => {
inviteCode.value = null; // Explicitly set to null on 404
inviteExpiresAt.value = null;
// Optional: notify user or set a flag to show "generate one" message more prominently
console.info('No active invite code found for this group.');
console.info(t('groupDetailPage.console.noActiveInvite'));
} else {
const message = err instanceof Error ? err.message : 'Failed to fetch active invite code.';
const message = err instanceof Error ? err.message : t('groupDetailPage.errors.failedToFetchActiveInvite');
// error.value = message; // This would display a large error banner, might be too much
console.error('Error fetching active invite code:', err);
notificationStore.addNotification({ message, type: 'error' });
@ -418,7 +416,7 @@ const fetchGroupDetails = async () => {
timestamp: Date.now(),
};
} catch (err: unknown) {
const message = err instanceof Error ? err.message : 'Failed to fetch group details.';
const message = err instanceof Error ? err.message : t('groupDetailPage.errors.failedToFetchGroupDetails');
// Only show the main error banner if we have no data at all to show
if (!group.value) {
error.value = message;
@ -524,7 +522,7 @@ const loadUpcomingChores = async () => {
timestamp: Date.now()
};
} catch (error) {
console.error('Error loading upcoming chores:', error)
console.error(t('groupDetailPage.errors.failedToLoadUpcomingChores'), error)
}
}
@ -563,7 +561,7 @@ const loadRecentExpenses = async () => {
)
recentExpenses.value = response.data
} catch (error) {
console.error('Error loading recent expenses:', error)
console.error(t('groupDetailPage.errors.failedToLoadRecentExpenses'), error)
notificationStore.addNotification({ message: t('groupDetailPage.notifications.loadExpensesFailed'), type: 'error' });
}
}

View File

@ -281,12 +281,12 @@ const handleCreateGroup = async () => {
cachedGroups.value = groups.value;
cachedTimestamp.value = Date.now();
} else {
throw new Error('Invalid data received from server.');
throw new Error(t('groupsPage.errors.invalidDataFromServer'));
}
} catch (error: any) {
const message = error.response?.data?.detail || (error instanceof Error ? error.message : t('groupsPage.errors.createFailed'));
createGroupFormError.value = message;
console.error('Error creating group:', error);
console.error(t('groupsPage.errors.createFailedConsole'), error);
notificationStore.addNotification({ message, type: 'error' });
} finally {
creatingGroup.value = false;
@ -327,7 +327,7 @@ const handleJoinGroup = async () => {
} catch (error: any) {
const message = error.response?.data?.detail || (error instanceof Error ? error.message : t('groupsPage.errors.joinFailed'));
joinGroupFormError.value = message;
console.error('Error joining group:', error);
console.error(t('groupsPage.errors.joinFailedConsole'), error);
notificationStore.addNotification({ message, type: 'error' });
} finally {
joiningGroup.value = false;

View File

@ -57,6 +57,7 @@ export const useAuthStore = defineStore('auth', () => {
const fetchCurrentUser = async () => {
if (!accessToken.value) {
// No token, so definitely clear any residual state and return.
clearTokens()
return null
}
@ -65,7 +66,28 @@ export const useAuthStore = defineStore('auth', () => {
setUser(response.data)
return response.data
} catch (error: any) {
// Check if the error is from an Axios request and has a response status
if (error.isAxiosError && error.response) {
const status = error.response.status
if (status === 401 || status === 403) {
// Authentication error from the server, clear tokens.
console.error('Authentication error fetching user, clearing tokens:', error)
clearTokens()
} else {
// Other HTTP error, log it but don't clear tokens.
// The user might be null, but the token remains for other cached calls.
console.error('HTTP error fetching user, token preserved:', error)
}
} else {
// Network error (offline) or other non-HTTP error.
// Log the error but preserve tokens.
// This allows the app to function with cached data if available.
console.error('Network or other error fetching user, token preserved:', error)
}
// In all error cases where tokens are not cleared, return null for the user object.
// The existing user object (if any) will remain until explicitly cleared or overwritten.
// If the intention is to clear the user object on any fetch error, uncomment the next line:
// setUser(null);
return null
}
}