diff --git a/.dockerignore b/.dockerignore
index fa629f9..f7c9f5e 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -7,3 +7,5 @@ Dockerfile
node_modules
npm-debug.log
data
+CLAUDE.md
+docs/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89b9e3d..f4c5760 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog
+## Version 0.2.25
+
+### Added
+
+* 🌍 Added Catalan language support (Català)
+
+### Fixed
+
+* Translation files consistency: Added missing UserForm keys to English and Korean translations
+
## Version 0.2.24
### Added
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..f195b8b
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,91 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+HabitTrove is a gamified habit tracking PWA built with Next.js 15, TypeScript, and Jotai state management. Users earn coins for completing habits and can redeem them for rewards. Features multi-user support with admin capabilities and shared ownership of habits/wishlist items.
+
+## Essential Commands
+
+### Development
+- `npm run dev` - Start development server with Turbopack
+- `npm run setup:dev` - Full setup: installs bun, dependencies, runs typecheck and lint
+- `npm install --force` - Install dependencies (force flag required)
+
+### Quality Assurance (Run these before committing)
+- `npm run typecheck` - TypeScript type checking (required)
+- `npm run lint` - ESLint code linting (required)
+- `npm test` - Run tests with Bun
+- `npm run build` - Build production version
+
+### Docker Deployment
+- `npm run docker-build` - Build Docker image locally
+- `docker compose up -d` - Run with docker-compose (recommended)
+- Requires `AUTH_SECRET` environment variable: `openssl rand -base64 32`
+
+## Architecture Overview
+
+### State Management (Jotai)
+- **Central atoms**: `habitsAtom`, `coinsAtom`, `wishlistAtom`, `usersAtom` in `lib/atoms.ts`
+- **Derived atoms**: Computed values like `dailyHabitsAtom`, `coinsBalanceAtom`
+- **Business logic hooks**: `useHabits`, `useCoins`, `useWishlist` in `/hooks`
+
+### Data Models & Ownership
+- **Individual ownership**: `CoinTransaction` has single `userId`
+- **Shared ownership**: `Habit` and `WishlistItemType` have `userIds: string[]` array
+- **Admin features**: Admin users can view/manage any user's data via dropdown selectors
+- **Data persistence**: JSON files in `/data` directory with automatic `/backups`
+
+### Key Components Structure
+- **Feature components**: `HabitList`, `CoinsManager`, `WishlistManager` - main page components
+- **Modal components**: `AddEditHabitModal`, `AddEditWishlistItemModal`, `UserSelectModal`
+- **UI components**: `/components/ui` - shadcn/ui based components
+
+### Authentication & Users
+- NextAuth.js v5 with multi-user support
+- User permissions: regular users vs admin users
+- Admin dropdown patterns: Similar implementation across Habits/Wishlist pages (reference CoinsManager for pattern)
+
+### Internationalization
+- `next-intl` with messages in `/messages/*.json`
+- Supported languages: English, Spanish, German, French, Russian, Chinese, Japanese
+
+## Code Patterns
+
+### Component Structure
+```typescript
+// Standard component pattern:
+export default function ComponentName() {
+ const [data, setData] = useAtom(dataAtom)
+ const { businessLogicFunction } = useCustomHook()
+ // Component logic
+}
+```
+
+### Hook Patterns
+- Custom hooks accept options: `useHabits({ selectedUser?: string })`
+- Return destructured functions and computed values
+- Handle both individual and shared ownership models
+
+### Shared Ownership Pattern
+```typescript
+// Filtering for shared ownership:
+const userItems = allItems.filter(item =>
+ item.userIds && item.userIds.includes(targetUserId)
+)
+```
+
+### Admin Dropdown Pattern
+Reference `CoinsManager.tsx:107-119` for admin user selection implementation. Similar pattern should be applied to Habits and Wishlist pages.
+
+## Data Safety
+- Always backup `/data` before major changes
+- Test with existing data files to prevent data loss
+- Validate user permissions for all data operations
+- Handle migration scripts carefully (see PLAN.md for shared ownership migration)
+
+## Performance Considerations
+- State updates use immutable patterns
+- Large dataset filtering happens at hook level
+- Derived atoms prevent unnecessary re-renders
\ No newline at end of file
diff --git a/README.md b/README.md
index e31a480..dc296a6 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Want to try HabitTrove before installing? Visit the public [demo instance](https
- 💰 Create a wishlist of rewards to redeem with earned coins
- 📊 View your habit completion streaks and statistics
- 📅 Calendar heatmap to visualize your progress (WIP)
-- 🌍 Multi-language support (English, Español, Deutsch, Français, Русский, 简体中文, 한국어, 日本語)
+- 🌍 Multi-language support (English, Español, Català, Deutsch, Français, Русский, 简体中文, 한국어, 日本語)
- 🌙 Dark mode support
- 📲 Progressive Web App (PWA) support
- 💾 Automatic daily backups with rotation
diff --git a/app/settings/page.tsx b/app/settings/page.tsx
index 96892fb..1a407f3 100644
--- a/app/settings/page.tsx
+++ b/app/settings/page.tsx
@@ -229,6 +229,7 @@ export default function SettingsPage() {
{/* Add more languages as needed */}
+
diff --git a/docs/translation-guide.md b/docs/translation-guide.md
new file mode 100644
index 0000000..c4be7f9
--- /dev/null
+++ b/docs/translation-guide.md
@@ -0,0 +1,77 @@
+# Language Guide
+
+## Adding/Updating Translations
+
+### Adding a New Language
+
+To add a new language translation to HabitTrove:
+
+1. **Create translation file**:
+ - Copy `messages/en.json` as a template
+ - Save as `messages/{language-code}.json` (e.g., `ko.json` for Korean)
+ - Translate all values while preserving keys and placeholder variables like `{username}`, `{count}`, etc.
+
+2. **Validate translation structure**:
+ ```bash
+ # Ensure JSON is valid
+ jq empty messages/{language-code}.json
+
+ # Compare structure with English (should show no differences)
+ diff <(jq -S . messages/en.json | jq -r 'keys | sort | .[]') <(jq -S . messages/{language-code}.json | jq -r 'keys | sort | .[]')
+ ```
+
+3. **Add language option to UI**:
+ - Edit `app/settings/page.tsx`
+ - Add new `` in alphabetical order
+
+4. **Update documentation**:
+ - Add language to README.md supported languages list
+ - Create new changelog entry with version bump
+ - Update package.json version
+
+### Example: Adding Korean (한국어)
+
+```bash
+# 1. Copy translation file
+cp /path/to/ko.json messages/ko.json
+
+# 2. Add to settings page
+# Add:
+
+# 3. Update README.md
+# Change: 简体中文, 日본語
+# To: 简체中文, 한국어, 日본語
+
+# 4. Add changelog entry
+# Create new version section with language addition
+
+# 5. Bump package version
+# Update version in package.json
+```
+
+### Translation Quality Guidelines
+
+- Use natural, contextually appropriate expressions
+- Maintain consistent terminology throughout
+- Preserve all placeholder variables exactly: `{username}`, `{count}`, `{target}`, etc.
+- Use appropriate formality level for the target language
+- Ensure JSON structure matches English file exactly (385 total keys)
+
+### Validation Commands
+
+```bash
+# Check JSON validity
+jq empty messages/{lang}.json
+
+# Compare key structure
+node -e "
+const en = require('./messages/en.json');
+const target = require('./messages/{lang}.json');
+// ... deep key comparison script
+"
+
+# Verify placeholder consistency
+grep -o '{[^}]*}' messages/en.json | sort | uniq > en_vars.txt
+grep -o '{[^}]*}' messages/{lang}.json | sort | uniq > {lang}_vars.txt
+diff en_vars.txt {lang}_vars.txt
+```
diff --git a/messages/ca.json b/messages/ca.json
new file mode 100644
index 0000000..2cf48a5
--- /dev/null
+++ b/messages/ca.json
@@ -0,0 +1,433 @@
+{
+ "Dashboard": {
+ "title": "Tauler"
+ },
+ "HabitList": {
+ "myTasks": "Les meves tasques",
+ "myHabits": "Els meus hàbits",
+ "addTaskButton": "Afegeix tasca",
+ "addHabitButton": "Afegeix hàbit",
+ "searchTasksPlaceholder": "Cerca tasques...",
+ "searchHabitsPlaceholder": "Cerca hàbits...",
+ "sortByLabel": "Ordena per:",
+ "sortByName": "Nom",
+ "sortByCoinReward": "Recompensa de monedes",
+ "sortByDueDate": "Data límit",
+ "sortByFrequency": "Freqüència",
+ "toggleSortOrderAriaLabel": "Canvia l'ordre de classificació",
+ "noTasksFoundMessage": "No s'han trobat tasques que coincideixin amb la teva cerca.",
+ "noHabitsFoundMessage": "No s'han trobat hàbits que coincideixin amb la teva cerca.",
+ "emptyStateTasksTitle": "Encara no hi ha tasques",
+ "emptyStateHabitsTitle": "Encara no hi ha hàbits",
+ "emptyStateTasksDescription": "Crea la teva primera tasca per començar a seguir el teu progrés",
+ "emptyStateHabitsDescription": "Crea el teu primer hàbit per començar a seguir el teu progrés",
+ "archivedSectionTitle": "Arxivat",
+ "deleteTaskDialogTitle": "Elimina tasca",
+ "deleteHabitDialogTitle": "Elimina hàbit",
+ "deleteTaskDialogMessage": "Estàs segur que vols eliminar aquesta tasca? Aquesta acció no es pot desfer.",
+ "deleteHabitDialogMessage": "Estàs segur que vols eliminar aquest hàbit? Aquesta acció no es pot desfer.",
+ "deleteButton": "Elimina"
+ },
+ "DailyOverview": {
+ "addTaskButtonLabel": "Afegeix tasca",
+ "addHabitButtonLabel": "Afegeix hàbit",
+ "todaysOverviewTitle": "Resum d'avui",
+ "dailyTasksTitle": "Tasques diàries",
+ "noTasksDueTodayMessage": "No hi ha tasques per avui. Afegeix-ne algunes per començar!",
+ "dailyHabitsTitle": "Hàbits diaris",
+ "noHabitsDueTodayMessage": "No hi ha hàbits per avui. Afegeix-ne alguns per començar!",
+ "wishlistGoalsTitle": "Objectius de la llista de desitjos",
+ "redeemableBadgeLabel": "{count}/{total} bescanviable",
+ "noWishlistItemsMessage": "Encara no hi ha elements a la llista de desitjos. Afegeix algunes metes per treballar-hi!",
+ "readyToRedeemMessage": "Llest per bescanviar!",
+ "coinsToGoMessage": "Falten {amount} monedes",
+ "showLessButton": "Mostra menys",
+ "showAllButton": "Mostra tot",
+ "viewButton": "Visualitza",
+ "deleteTaskDialogTitle": "Elimina tasca",
+ "deleteHabitDialogTitle": "Elimina hàbit",
+ "confirmDeleteDialogMessage": "Estàs segur que vols eliminar \"{name}\"? Aquesta acció no es pot desfer.",
+ "deleteButton": "Elimina",
+ "overdueTooltip": "Vençut"
+ },
+ "HabitContextMenuItems": {
+ "startPomodoro": "Inicia Pomodoro",
+ "moveToToday": "Mou a avui",
+ "moveToTomorrow": "Mou a demà",
+ "unpin": "Desfixa",
+ "pin": "Fixa",
+ "edit": "Edita",
+ "archive": "Arxiva",
+ "unarchive": "Desarxiva",
+ "delete": "Elimina"
+ },
+ "HabitStreak": {
+ "dailyCompletionStreakTitle": "Ratxa de finalització diària",
+ "tooltipHabitsLabel": "hàbits",
+ "tooltipTasksLabel": "tasques",
+ "tooltipCompletedLabel": "Completat"
+ },
+ "CoinBalance": {
+ "coinBalanceTitle": "Saldo de monedes"
+ },
+ "AddEditHabitModal": {
+ "editTaskTitle": "Edita tasca",
+ "editHabitTitle": "Edita hàbit",
+ "addNewTaskTitle": "Afegeix nova tasca",
+ "addNewHabitTitle": "Afegeix nou hàbit",
+ "nameLabel": "Nom *",
+ "descriptionLabel": "Descripció",
+ "whenLabel": "Quan *",
+ "completeLabel": "Completa",
+ "timesSuffix": "vegades",
+ "rewardLabel": "Recompensa",
+ "coinsSuffix": "monedes",
+ "shareLabel": "Comparteix",
+ "saveChangesButton": "Desa els canvis",
+ "addTaskButton": "Afegeix tasca",
+ "addHabitButton": "Afegeix hàbit"
+ },
+ "ConfirmDialog": {
+ "confirmButton": "Confirma",
+ "cancelButton": "Cancel·la"
+ },
+ "AddEditWishlistItemModal": {
+ "editTitle": "Edita recompensa",
+ "addTitle": "Afegeix nova recompensa",
+ "nameLabel": "Nom *",
+ "descriptionLabel": "Descripció",
+ "costLabel": "Cost",
+ "coinsSuffix": "monedes",
+ "redeemableLabel": "Bescanviable",
+ "timesSuffix": "vegades",
+ "errorNameRequired": "El nom és obligatori",
+ "errorCoinCostMin": "El cost en monedes ha de ser com a mínim 1",
+ "errorTargetCompletionsMin": "El nombre de finalitzacions objectiu ha de ser com a mínim 1",
+ "errorInvalidUrl": "Si us plau, introdueix una URL vàlida",
+ "linkLabel": "Enllaç",
+ "shareLabel": "Comparteix",
+ "saveButton": "Desa els canvis",
+ "addButton": "Afegeix recompensa"
+ },
+ "Navigation": {
+ "dashboard": "Tauler",
+ "tasks": "Tasques",
+ "habits": "Hàbits",
+ "calendar": "Calendari",
+ "wishlist": "Llista de desitjos",
+ "coins": "Monedes"
+ },
+ "TodayEarnedCoins": {
+ "todaySuffix": "avui"
+ },
+ "WishlistItem": {
+ "usesLeftSingular": "ús restant",
+ "usesLeftPlural": "usos restants",
+ "coinsSuffix": "monedes",
+ "redeem": "Bescanvia",
+ "redeemedDone": "Fet",
+ "redeemedExclamation": "Bescanviat!",
+ "editButton": "Edita",
+ "archiveButton": "Arxiva",
+ "unarchiveButton": "Desarxiva",
+ "deleteButton": "Elimina"
+ },
+ "WishlistManager": {
+ "title": "La meva llista de desitjos",
+ "addRewardButton": "Afegeix recompensa",
+ "emptyStateTitle": "La teva llista de desitjos està buida",
+ "emptyStateDescription": "Afegeix recompenses que t'agradaria guanyar amb les teves monedes",
+ "archivedSectionTitle": "Arxivat",
+ "popupBlockedTitle": "Finestra emergent bloquejada",
+ "popupBlockedDescription": "Si us plau, permet les finestres emergents per obrir l'enllaç",
+ "deleteDialogTitle": "Elimina recompensa",
+ "deleteDialogMessage": "Estàs segur que vols eliminar aquesta recompensa? Aquesta acció no es pot desfer.",
+ "deleteButton": "Elimina"
+ },
+ "UserSelectModal": {
+ "addUserButton": "Afegeix usuari",
+ "createNewUserTitle": "Crea nou usuari",
+ "selectUserTitle": "Selecciona usuari",
+ "signInSuccessTitle": "Inici de sessió correcte",
+ "signInSuccessDescription": "Benvingut de nou, {username}!",
+ "errorInvalidPassword": "contrasenya no vàlida",
+ "deleteUserConfirmation": "Estàs segur que vols eliminar l'usuari {username}? Aquesta acció no es pot desfer.",
+ "confirmDeleteButtonText": "Elimina",
+ "deletingButtonText": "Eliminant...",
+ "deleteUserSuccessTitle": "Usuari eliminat",
+ "deleteUserSuccessDescription": "L'usuari {username} s'ha eliminat correctament.",
+ "deleteUserErrorTitle": "Error en eliminar",
+ "genericError": "S'ha produït un error inesperat.",
+ "networkError": "S'ha produït un error de xarxa. Si us plau, torna-ho a intentar.",
+ "editUserTooltip": "Edita usuari",
+ "deleteUserTooltip": "Elimina usuari"
+ },
+ "CoinsManager": {
+ "title": "Gestió de monedes",
+ "currentBalanceLabel": "Saldo actual",
+ "coinsSuffix": "monedes",
+ "addCoinsButton": "Afegeix monedes",
+ "removeCoinsButton": "Elimina monedes",
+ "statisticsTitle": "Estadístiques",
+ "totalEarnedLabel": "Total guanyat",
+ "totalSpentLabel": "Total gastat",
+ "totalTransactionsLabel": "Transaccions totals",
+ "todaysEarnedLabel": "Guanyat avui",
+ "todaysSpentLabel": "Gastat avui",
+ "todaysTransactionsLabel": "Transaccions d'avui",
+ "transactionHistoryTitle": "Historial de transaccions",
+ "showLabel": "Mostra:",
+ "entriesSuffix": "entrades",
+ "showingEntries": "Mostrant {from} a {to} de {total} entrades",
+ "noTransactionsTitle": "Encara no hi ha transaccions",
+ "noTransactionsDescription": "El teu historial de transaccions apareixerà aquí un cop comencis a guanyar o gastar monedes",
+ "pageLabel": "Pàgina",
+ "ofLabel": "de",
+ "transactionTypeHabitCompletion": "Finalització d'hàbit",
+ "transactionTypeTaskCompletion": "Finalització de tasca",
+ "transactionTypeHabitUndo": "Desfer hàbit",
+ "transactionTypeTaskUndo": "Desfer tasca",
+ "transactionTypeWishRedemption": "Bescanvi de desig",
+ "transactionTypeManualAdjustment": "Ajust manual",
+ "transactionTypeCoinReset": "Reinici de monedes",
+ "transactionTypeInitialBalance": "Saldo inicial"
+ },
+ "NotificationBell": {
+ "errorUpdateTimestamp": "Error en actualitzar la marca de temps de notificació llegida:"
+ },
+ "PomodoroTimer": {
+ "focusLabel1": "Mantén-te concentrat",
+ "focusLabel2": "Tu pots",
+ "focusLabel3": "Continua endavant",
+ "focusLabel4": "Fes-ho",
+ "focusLabel5": "Fes que passi",
+ "focusLabel6": "Mantén-te fort",
+ "focusLabel7": "Esforça't",
+ "focusLabel8": "Un pas cada vegada",
+ "focusLabel9": "Tu pots fer-ho",
+ "focusLabel10": "Concentra't i conquereix",
+ "breakLabel1": "Fes un descans",
+ "breakLabel2": "Relaxa't i recarrega",
+ "breakLabel3": "Respira profundament",
+ "breakLabel4": "Estira't",
+ "breakLabel5": "Refresca't",
+ "breakLabel6": "T'ho mereixes",
+ "breakLabel7": "Recarrega la teva energia",
+ "breakLabel8": "Allunya't un moment",
+ "breakLabel9": "Neteja la teva ment",
+ "breakLabel10": "Descansa i recupera't",
+ "focusType": "Concentració",
+ "breakType": "Descans",
+ "pauseButton": "Pausa",
+ "startButton": "Inicia",
+ "resetButton": "Reinicia",
+ "skipButton": "Omet",
+ "wakeLockNotSupported": "El navegador no suporta wake lock",
+ "wakeLockInUse": "Wake lock ja està en ús",
+ "wakeLockRequestError": "Error en sol·licitar wake lock:",
+ "wakeLockReleaseError": "Error en alliberar wake lock:"
+ },
+ "HabitCalendar": {
+ "title": "Calendari d'hàbits",
+ "calendarCardTitle": "Calendari",
+ "selectDatePrompt": "Selecciona una data",
+ "tasksSectionTitle": "Tasques",
+ "habitsSectionTitle": "Hàbits",
+ "errorCompletingPastHabit": "Error en completar hàbit passat:"
+ },
+ "NotificationDropdown": {
+ "notLoggedIn": "No has iniciat sessió.",
+ "userCompletedItem": "{username} ha completat {itemName}.",
+ "userRedeemedItem": "{username} ha bescanviat {itemName}.",
+ "activityRelatedToItem": "Activitat relacionada amb {itemName} per {username}.",
+ "defaultUsername": "Algú",
+ "defaultItemName": "un element compartit",
+ "notificationsTitle": "Notificacions",
+ "notificationsTooltip": "Mostra finalitzacions o bescanvis d'altres usuaris per hàbits o llista de desitjos que has compartit amb ells (has de ser administrador)",
+ "noNotificationsYet": "Encara no hi ha notificacions."
+ },
+ "AboutModal": {
+ "dialogArisLabel": "quant a",
+ "changelogButton": "Registre de canvis",
+ "createdByPrefix": "Creat amb ❤️ per",
+ "starOnGitHubButton": "Dona una estrella a GitHub"
+ },
+ "PermissionSelector": {
+ "permissionsTitle": "Permisos",
+ "adminAccessLabel": "Accés d'administrador",
+ "adminAccessDescription": "Els administradors tenen permís complet sobre totes les dades de tots els usuaris",
+ "resourceHabitTask": "Hàbit / Tasca",
+ "resourceWishlist": "Llista de desitjos",
+ "resourceCoins": "Monedes",
+ "permissionWrite": "Escriptura",
+ "permissionInteract": "Interactua"
+ },
+ "UserForm": {
+ "toastUserUpdatedTitle": "Usuari actualitzat",
+ "toastUserUpdatedDescription": "Usuari {username} actualitzat amb èxit",
+ "toastUserCreatedTitle": "Usuari creat",
+ "toastUserCreatedDescription": "Usuari {username} creat amb èxit",
+ "actionUpdate": "actualitzar",
+ "actionCreate": "crear",
+ "errorFailedUserAction": "Error en {action} usuari",
+ "toastDemoDeleteDisabled": "L'eliminació està deshabilitada a la instància de demostració",
+ "toastCannotDeleteSelf": "No pots eliminar el teu propi compte",
+ "confirmDeleteUser": "Estàs segur que vols eliminar l'usuari {username}?",
+ "toastUserDeletedTitle": "Usuari eliminat",
+ "toastUserDeletedDescription": "L'usuari {username} s'ha eliminat correctament",
+ "toastDeleteUserFailed": "Error en eliminar l'usuari: {error}",
+ "errorTitle": "Error",
+ "errorFileSizeLimit": "La mida de l'arxiu ha de ser inferior a 5MB",
+ "toastAvatarUploadedTitle": "Avatar pujat",
+ "toastAvatarUploadedDescription": "Avatar pujat amb èxit",
+ "errorFailedAvatarUpload": "Error en pujar l'avatar",
+ "changeAvatarButton": "Canvia avatar",
+ "uploadAvatarButton": "Puja avatar",
+ "usernameLabel": "Nom d'usuari",
+ "usernamePlaceholder": "Nom d'usuari",
+ "newPasswordLabel": "Nova contrasenya",
+ "passwordLabel": "Contrasenya",
+ "passwordPlaceholderEdit": "Deixa en blanc per mantenir l'actual",
+ "passwordPlaceholderCreate": "Introdueix contrasenya",
+ "demoPasswordDisabledMessage": "La contrasenya està automàticament desactivada a la instància de demostració",
+ "disablePasswordLabel": "Desactiva contrasenya",
+ "cancelButton": "Cancel·la",
+ "saveChangesButton": "Desa els canvis",
+ "createUserButton": "Crea usuari",
+ "deleteAccountButton": "Elimina compte",
+ "deletingButtonText": "Eliminant...",
+ "areYouSure": "Estàs segur?",
+ "deleteUserConfirmation": "Estàs segur que vols eliminar l'usuari {username}?",
+ "cancel": "Cancel·la",
+ "confirmDeleteButtonText": "Elimina"
+ },
+ "ViewToggle": {
+ "habitsLabel": "Hàbits",
+ "tasksLabel": "Tasques"
+ },
+ "HabitItem": {
+ "overdue": "Vençut",
+ "whenLabel": "Quan: {frequency}",
+ "coinsPerCompletion": "{count} monedes per finalització",
+ "completedStatus": "Completat",
+ "completedStatusCount": "Completat ({completed}/{target})",
+ "completedStatusCountMobile": "{completed}/{target}",
+ "completeButton": "Completa",
+ "completeButtonCount": "Completa ({completed}/{target})",
+ "completeButtonCountMobile": "{completed}/{target}",
+ "undoButton": "Desfés",
+ "editButton": "Edita"
+ },
+ "TransactionNoteEditor": {
+ "noteTooLongTitle": "Nota massa llarga",
+ "noteTooLongDescription": "Les notes han de tenir menys de 200 caràcters",
+ "errorSavingNoteTitle": "Error en desar la nota",
+ "errorDeletingNoteTitle": "Error en eliminar la nota",
+ "pleaseTryAgainDescription": "Si us plau, torna-ho a intentar",
+ "addNotePlaceholder": "Afegeix una nota...",
+ "saveNoteTitle": "Desa nota",
+ "cancelButtonTitle": "Cancel·la",
+ "deleteNoteTitle": "Elimina nota",
+ "editNoteAriaLabel": "Edita nota"
+ },
+ "Profile": {
+ "guestUsername": "Convidat",
+ "editProfileButton": "Edita perfil",
+ "signOutSuccessTitle": "Tancament de sessió correcte",
+ "signOutSuccessDescription": "Has tancat sessió del teu compte",
+ "signOutErrorTitle": "Error en tancar sessió",
+ "signOutErrorDescription": "Error en tancar sessió",
+ "switchUserButton": "Canvia usuari",
+ "settingsLink": "Configuració",
+ "aboutButton": "Quant a",
+ "themeLabel": "Tema",
+ "editProfileModalTitle": "Edita perfil"
+ },
+ "PasswordEntryForm": {
+ "notYouButton": "No ets tu?",
+ "passwordLabel": "Contrasenya",
+ "passwordPlaceholder": "Introdueix contrasenya",
+ "loginErrorToastTitle": "Error",
+ "loginFailedErrorToastDescription": "Error en iniciar sessió",
+ "cancelButton": "Cancel·la",
+ "loginButton": "Inicia sessió"
+ },
+ "CompletionCountBadge": {
+ "countCompleted": "{completedCount}/{totalCount} completat"
+ },
+ "SettingsPage": {
+ "title": "Configuració",
+ "uiSettingsTitle": "Configuració d'interfície",
+ "numberFormattingLabel": "Format numèric",
+ "numberFormattingDescription": "Formateja nombres grans (ex: 1K, 1M, 1B)",
+ "numberGroupingLabel": "Agrupació numèrica",
+ "numberGroupingDescription": "Usa separadors de milers (ex: 1.000 vs 1000)",
+ "systemSettingsTitle": "Configuració del sistema",
+ "timezoneLabel": "Zona horària",
+ "timezoneDescription": "Selecciona la teva zona horària per a un seguiment precís de dates",
+ "weekStartDayLabel": "Dia d'inici de setmana",
+ "weekStartDayDescription": "Selecciona el teu dia preferit per iniciar la setmana",
+ "weekdays": {
+ "sunday": "Diumenge",
+ "monday": "Dilluns",
+ "tuesday": "Dimarts",
+ "wednesday": "Dimecres",
+ "thursday": "Dijous",
+ "friday": "Divendres",
+ "saturday": "Dissabte"
+ },
+ "autoBackupLabel": "Còpia de seguretat automàtica",
+ "autoBackupTooltip": "Quan està habilitat, les dades de l'aplicació (hàbits, monedes, configuracions, etc.) es copien automàticament cada dia al voltant de les 2 AM hora del servidor. Les còpies de seguretat s'emmagatzemen com a fitxers ZIP al directori `backups/` a l'arrel del projecte. Només es conserven les últimes 7 còpies de seguretat; les més antigues s'eliminen automàticament.",
+ "autoBackupDescription": "Realitza còpia de seguretat automàtica diària",
+ "languageLabel": "Idioma",
+ "languageDescription": "Tria el teu idioma preferit per mostrar a l'aplicació.",
+ "languageChangedTitle": "Idioma canviat",
+ "languageChangedDescription": "Si us plau, actualitza la pàgina per veure els canvis",
+ "languageDisabledInDemoTooltip": "Canviar l'idioma està deshabilitat a la versió de demostració."
+ },
+ "Common": {
+ "authenticationRequiredTitle": "Autenticació requerida",
+ "authenticationRequiredDescription": "Si us plau, inicia sessió per continuar.",
+ "permissionDeniedTitle": "Permís denegat",
+ "permissionDeniedDescription": "No tens permís de {action} per a {resource}.",
+ "undoButton": "Desfés",
+ "redoButton": "Refés",
+ "errorTitle": "Error"
+ },
+ "useHabits": {
+ "alreadyCompletedTitle": "Ja completat",
+ "alreadyCompletedDescription": "Ja has completat aquest hàbit avui.",
+ "completedTitle": "Completat!",
+ "earnedCoinsDescription": "Has guanyat {coinReward} monedes.",
+ "progressTitle": "Progrés!",
+ "progressDescription": "Has completat {count}/{target} vegades avui.",
+ "completionUndoneTitle": "Finalització desfeta",
+ "completionUndoneDescription": "Tens {count}/{target} finalitzacions avui.",
+ "noCompletionsToUndoTitle": "No hi ha finalitzacions per desfer",
+ "noCompletionsToUndoDescription": "Aquest hàbit no s'ha completat avui.",
+ "alreadyCompletedPastDateTitle": "Ja completat",
+ "alreadyCompletedPastDateDescription": "Aquest hàbit ja va ser completat el {dateKey}.",
+ "earnedCoinsPastDateDescription": "Vas guanyar {coinReward} monedes per {dateKey}.",
+ "progressPastDateDescription": "Has completat {count}/{target} vegades el {dateKey}."
+ },
+ "useWishlist": {
+ "redemptionLimitReachedTitle": "Límit de bescanvis assolit",
+ "redemptionLimitReachedDescription": "Has assolit el màxim de bescanvis per \"{itemName}\".",
+ "rewardRedeemedTitle": "🎉 Recompensa bescanviada!",
+ "rewardRedeemedDescription": "Has bescanviat \"{itemName}\" per {itemCoinCost} monedes.",
+ "notEnoughCoinsTitle": "No hi ha prou monedes",
+ "notEnoughCoinsDescription": "Necessites {coinsNeeded} monedes més per bescanviar aquesta recompensa."
+ },
+ "Warning": {
+ "areYouSure": "Estàs segur?",
+ "cancel": "Cancel·la"
+ },
+ "useCoins": {
+ "addedCoinsDescription": "S'han afegit {amount} monedes",
+ "invalidAmountTitle": "Quantitat no vàlida",
+ "invalidAmountDescription": "Si us plau, introdueix un nombre positiu vàlid",
+ "successTitle": "Èxit",
+ "transactionNotFoundDescription": "Transacció no trobada",
+ "maxAmountExceededDescription": "La quantitat no pot excedir {max}."
+ }
+}
diff --git a/messages/en.json b/messages/en.json
index fc73841..d03a382 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -288,7 +288,18 @@
"cancelButton": "Cancel",
"saveChangesButton": "Save Changes",
"createUserButton": "Create User",
- "deleteAccountButton": "Delete Account"
+ "deleteAccountButton": "Delete Account",
+ "toastDemoDeleteDisabled": "Deletion is disabled in demo instance",
+ "toastCannotDeleteSelf": "You cannot delete your own account",
+ "confirmDeleteUser": "Are you sure you want to delete user {username}?",
+ "toastUserDeletedTitle": "User deleted",
+ "toastUserDeletedDescription": "User {username} has been deleted successfully",
+ "toastDeleteUserFailed": "Failed to delete user: {error}",
+ "deletingButtonText": "Deleting...",
+ "areYouSure": "Are you sure?",
+ "deleteUserConfirmation": "Are you sure you want to delete user {username}?",
+ "cancel": "Cancel",
+ "confirmDeleteButtonText": "Delete"
},
"ViewToggle": {
"habitsLabel": "Habits",
diff --git a/messages/ko.json b/messages/ko.json
index 987ac5b..7b7e6ce 100644
--- a/messages/ko.json
+++ b/messages/ko.json
@@ -288,7 +288,18 @@
"cancelButton": "취소",
"saveChangesButton": "변경 사항 저장",
"createUserButton": "사용자 생성",
- "deleteAccountButton": "계정 삭제"
+ "deleteAccountButton": "계정 삭제",
+ "toastDemoDeleteDisabled": "데모 인스턴스에서는 삭제가 비활성화되어 있습니다",
+ "toastCannotDeleteSelf": "본인의 계정을 삭제할 수 없습니다",
+ "confirmDeleteUser": "사용자 {username}을(를) 삭제하시겠습니까?",
+ "toastUserDeletedTitle": "사용자 삭제됨",
+ "toastUserDeletedDescription": "사용자 {username}이(가) 성공적으로 삭제되었습니다",
+ "toastDeleteUserFailed": "사용자 삭제 실패: {error}",
+ "deletingButtonText": "삭제 중...",
+ "areYouSure": "정말로 삭제하시겠습니까?",
+ "deleteUserConfirmation": "사용자 {username}을(를) 삭제하시겠습니까?",
+ "cancel": "취소",
+ "confirmDeleteButtonText": "삭제"
},
"ViewToggle": {
"habitsLabel": "습관",
diff --git a/package.json b/package.json
index d163877..a285537 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "habittrove",
- "version": "0.2.24",
+ "version": "0.2.25",
"private": true,
"scripts": {
"dev": "next dev --turbopack",