mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-01-20 22:24:28 +01:00
Add project documentation and translation guide (#174)
This commit is contained in:
@@ -7,3 +7,5 @@ Dockerfile
|
||||
node_modules
|
||||
npm-debug.log
|
||||
data
|
||||
CLAUDE.md
|
||||
docs/
|
||||
|
||||
10
CHANGELOG.md
10
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
|
||||
|
||||
91
CLAUDE.md
Normal file
91
CLAUDE.md
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
77
docs/translation-guide.md
Normal 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
433
messages/ca.json
Normal 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}."
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "습관",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habittrove",
|
||||
"version": "0.2.24",
|
||||
"version": "0.2.25",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
|
||||
Reference in New Issue
Block a user