Add project documentation and translation guide (#174)

This commit is contained in:
Doh
2025-08-20 09:26:29 -04:00
committed by GitHub
parent a6f5bf1baa
commit 8fb7cd1810
10 changed files with 640 additions and 4 deletions

View File

@@ -7,3 +7,5 @@ Dockerfile
node_modules
npm-debug.log
data
CLAUDE.md
docs/

View File

@@ -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

91
CLAUDE.md Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -229,6 +229,7 @@ export default function SettingsPage() {
{/* Add more languages as needed */}
<option value="en">English</option>
<option value="es">Español</option>
<option value="ca">Català</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
<option value="ru">Русский</option>

77
docs/translation-guide.md Normal file
View File

@@ -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 `<option value="{language-code}">{Language Name}</option>` 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: <option value="ko">한국어</option>
# 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
```

433
messages/ca.json Normal file
View File

@@ -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}."
}
}

View File

@@ -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",

View File

@@ -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": "습관",

View File

@@ -1,6 +1,6 @@
{
"name": "habittrove",
"version": "0.2.24",
"version": "0.2.25",
"private": true,
"scripts": {
"dev": "next dev --turbopack",