diff --git a/CHANGELOG.md b/CHANGELOG.md index f34b4f3..3adfb53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.5.8] - 2026-04-03 + +### Added +- Add Italian (Italiano) localization — full translation of all 497 i18n keys (thanks @albanobattistella, PR #7) +- Add Italian as selectable language in Settings locale picker + ## [0.5.7] - 2026-04-03 ### Fixed diff --git a/package.json b/package.json index 5c23267..3fd19f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.5.7", + "version": "0.5.8", "description": "Selbstgehosteter Familienplaner — Kalender, Aufgaben, Einkauf, Essensplan, Budget und mehr. Privat, offen, ohne Abo.", "main": "server/index.js", "engines": { diff --git a/public/components/oikos-locale-picker.js b/public/components/oikos-locale-picker.js index 9078245..883d1c2 100644 --- a/public/components/oikos-locale-picker.js +++ b/public/components/oikos-locale-picker.js @@ -10,6 +10,7 @@ import { t, setLocale, getLocale, getSupportedLocales } from '/i18n.js'; const LOCALE_LABELS = { de: 'Deutsch', en: 'English', + it: 'Italiano', }; class OikosLocalePicker extends HTMLElement { diff --git a/public/i18n.js b/public/i18n.js index 49981e0..d078068 100644 --- a/public/i18n.js +++ b/public/i18n.js @@ -5,7 +5,7 @@ * Dependencies: none (vanilla JS, Fetch API, Intl API) */ -const SUPPORTED_LOCALES = ['de', 'en']; +const SUPPORTED_LOCALES = ['de', 'en', 'it']; const DEFAULT_LOCALE = 'de'; const STORAGE_KEY = 'oikos-locale'; diff --git a/public/locales/it.json b/public/locales/it.json new file mode 100644 index 0000000..3078ab0 --- /dev/null +++ b/public/locales/it.json @@ -0,0 +1,541 @@ +{ + "common": { + "save": "Salva", + "cancel": "Annulla", + "delete": "Elimina", + "edit": "Modifica", + "close": "Chiudi", + "create": "Crea", + "add": "Aggiungi", + "back": "Indietro", + "next": "Avanti", + "loading": "Caricamento…", + "saving": "Salvataggio…", + "required": "Questo campo è obbligatorio.", + "error": "Errore", + "allFieldsRequired": "Compila tutti i campi.", + "today": "Oggi", + "tomorrow": "Domani", + "skipToContent": "Salta al contenuto", + "reload": "Ricarica", + "errorOccurred": "Si è verificato un errore.", + "unexpectedError": "Si è verificato un errore imprevisto.", + "errorGeneric": "Si è verificato un errore.", + "updateAvailable": "Aggiornamento disponibile — ricarica la pagina per ottenere l'ultima versione.", + "titleRequired": "Il titolo è obbligatorio", + "nameRequired": "Il nome è obbligatorio", + "contentRequired": "Il contenuto è obbligatorio", + "all": "Tutto", + "unknownError": "Errore sconosciuto" + }, + + "nav": { + "dashboard": "Panoramica", + "tasks": "Compiti", + "calendar": "Calendario", + "meals": "Pasti", + "shopping": "Spesa", + "notes": "Bacheca", + "contacts": "Contatti", + "budget": "Bilancio", + "settings": "Impostazioni", + "main": "Navigazione principale", + "navigation": "Navigazione", + "quickActions": "Azioni rapide" + }, + + "dashboard": { + "title": "Panoramica", + "greetingMorning": "Buongiorno, {{name}}", + "greetingDay": "Buon pomeriggio, {{name}}", + "greetingEvening": "Buonasera, {{name}}", + "allDone": "Tutto fatto", + "noEvents": "Nessun evento", + "noPinnedNotes": "Nessuna nota fissata", + "todayMeals": "Pasti di oggi", + "allLink": "Tutto", + "weekLink": "Settimana", + "urgentTasksChip": "{{count}} compito urgente", + "urgentTasksChipPlural": "{{count}} compiti urgenti", + "eventsChip": "{{count}} evento oggi", + "eventsChipPlural": "{{count}} eventi oggi", + "todayMealChip": "Oggi: {{title}}", + "loadError": "Impossibile caricare completamente la dashboard.", + "weatherRefresh": "Aggiorna meteo", + "weatherRefreshTitle": "Aggiorna", + "weatherFeelsLike": "Percepiti {{temp}}° · {{humidity}}% · Vento {{wind}} km/h", + "fabTaskLabel": "Aggiungi compito", + "fabCalendarLabel": "Aggiungi evento", + "fabShoppingLabel": "Aggiungi spesa", + "fabNoteLabel": "Aggiungi nota", + "fabTask": "Compito", + "fabCalendar": "Evento", + "fabShopping": "Spesa", + "fabNote": "Nota", + "overdue": "Scaduto", + "dueSoon": "Scade oggi", + "dueTomorrow": "Scade domani", + "allDay": "Tutto il giorno" + }, + + "tasks": { + "title": "Compiti", + "newTask": "Nuovo compito", + "editTask": "Modifica compito", + "emptyTitle": "Nessun compito — tutto fatto?", + "emptyDescription": "Crea nuovi compiti con il pulsante +.", + "titleLabel": "Titolo *", + "titlePlaceholder": "Cosa bisogna fare?", + "descriptionLabel": "Nota", + "descriptionPlaceholder": "Dettagli opzionali…", + "priorityLabel": "Priorità", + "categoryLabel": "Categoria", + "dueDateLabel": "Data di scadenza", + "dueTimeLabel": "Ora", + "assignedLabel": "Assegnato a", + "assignedNobody": "— Nessuno —", + "statusLabel": "Stato", + "priorityUrgent": "Urgente", + "priorityHigh": "Alta", + "priorityMedium": "Media", + "priorityLow": "Bassa", + "statusOpen": "Aperto", + "statusInProgress": "In corso", + "statusDone": "Completato", + "categoryHousehold": "Casa", + "categorySchool": "Scuola", + "categoryShopping": "Spesa", + "categoryRepair": "Riparazioni", + "categoryHealth": "Salute", + "categoryFinance": "Finanze", + "categoryLeisure": "Tempo libero", + "categoryMisc": "Varie", + "overdue": "Scaduto", + "overdueDay": "Scaduto da {{count}}g", + "dueToday": "Scade oggi", + "dueTomorrow": "Scade domani", + "groupOverdue": "Scaduti", + "groupToday": "Oggi", + "groupThisWeek": "Questa settimana", + "groupNextWeek": "Prossima settimana", + "groupLater": "Più avanti", + "groupNoDate": "Senza data", + "markDone": "Segna {{title}} come completato", + "editButton": "Modifica compito", + "swipeOpen": "Riapri", + "swipeDone": "Completato", + "swipeEdit": "Modifica", + "subtaskAdd": "+ Aggiungi sotto-compito", + "subtaskToggle": "Mostra sotto-compiti", + "subtaskMarkDone": "Segna {{title}} come completato", + "deleteConfirm": "Eliminare il compito e tutti i sotto-compiti?", + "savedToast": "Compito salvato.", + "createdToast": "Compito creato.", + "deletedToast": "Compito eliminato.", + "loadError": "Impossibile caricare il compito.", + "subtaskPrompt": "Sotto-compito:", + "kanbanOpen": "Da fare", + "kanbanInProgress": "In corso", + "kanbanDone": "Completato", + "recurring": "Ricorrente", + "listView": "Vista elenco", + "kanbanView": "Vista Kanban" + }, + + "shopping": { + "title": "Spesa", + "noLists": "Nessuna lista", + "noListsDescription": "Crea una lista con il pulsante +.", + "emptyList": "La lista è vuota", + "emptyListDescription": "Aggiungi articoli usando il campo sopra.", + "newListPrompt": "Nome per la nuova lista:", + "newListButton": "Crea nuova lista", + "renameListPrompt": "Nuovo nome lista:", + "deleteListConfirm": "Eliminare la lista \"{{name}}\" e tutti gli articoli?", + "deletedListToast": "Lista eliminata.", + "itemsRemovedToast": "{{count}} articoli rimossi.", + "clearChecked": "Rimuovi selezionati ({{count}})", + "itemNamePlaceholder": "Aggiungi articolo…", + "itemQtyPlaceholder": "Quantità", + "itemNameLabel": "Nome articolo", + "itemQtyLabel": "Quantità", + "categoryLabel": "Categoria", + "addItemLabel": "Aggiungi articolo", + "renameListLabel": "Rinomina lista", + "deleteListLabel": "Elimina lista", + "swipeBack": "Annulla", + "swipeCheck": "Spunta", + "swipeDelete": "Elimina", + "markDoneLabel": "Spunta {{name}}", + "markUndoneLabel": "Togli spunta a {{name}}", + "deleteItemLabel": "Elimina {{name}}", + "listsLoadError": "Impossibile caricare le liste.", + "itemsLoadError": "Impossibile caricare gli articoli.", + "catFruitVeg": "Frutta e Verdura", + "catBakery": "Panetteria", + "catDairy": "Latticini", + "catMeatFish": "Carne e Pesce", + "catFrozen": "Surgelati", + "catDrinks": "Bevande", + "catHousehold": "Casa", + "catDrugstore": "Drogheria", + "catMisc": "Varie" + }, + + "meals": { + "title": "Piano pasti", + "noMealPlanned": "Nessun pasto pianificato", + "addMeal": "Aggiungi {{type}}", + "editMeal": "Modifica pasto", + "addMealTitle": "Aggiungi pasto", + "deleteMeal": "Elimina pasto", + "transferToShoppingList": "Aggiungi ingredienti alla lista della spesa", + "today": "Oggi", + "prevWeek": "Settimana precedente", + "nextWeek": "Settimana successiva", + "loadError": "Impossibile caricare il piano pasti.", + "typeBreakfast": "Colazione", + "typeLunch": "Pranzo", + "typeDinner": "Cena", + "typeSnack": "Spuntino", + "dayMo": "Lun", + "dayDi": "Mar", + "dayMi": "Mer", + "dayDo": "Gio", + "dayFr": "Ven", + "daySa": "Sab", + "daySo": "Dom", + "dateLabel": "Data", + "mealTypeLabel": "Pasto", + "titleLabel": "Titolo *", + "titlePlaceholder": "es. Spaghetti Bolognese", + "notesLabel": "Note", + "notesPlaceholder": "Opzionale…", + "ingredientsLabel": "Ingredienti", + "addIngredient": "Aggiungi ingrediente", + "ingredientNamePlaceholder": "Ingrediente", + "ingredientQtyPlaceholder": "Quantità", + "removeIngredient": "Rimuovi ingrediente", + "transferLabel": "Trasferisci ingredienti alla lista della spesa", + "transferNow": "Trasferisci ora", + "noShoppingLists": "Nessuna lista della spesa disponibile", + "transferSuccess": "{{count}} ingrediente trasferito", + "transferSuccessPlural": "{{count}} ingredienti trasferiti", + "transferAlreadyDone": "Tutti gli ingredienti già trasferiti", + "ingredientCount": "{{count}} ingrediente", + "ingredientCountPlural": "{{count}} ingredienti", + "titleRequired": "Il titolo è obbligatorio", + "loadingIndicator": "Caricamento…" + }, + + "calendar": { + "title": "Calendario", + "newEvent": "Nuovo evento", + "editEvent": "Modifica evento", + "addEvent": "Aggiungi evento", + "deleteEvent": "Elimina evento", + "noEvents": "Nessun evento nel periodo selezionato.", + "today": "Oggi", + "back": "Indietro", + "forward": "Avanti", + "viewMonth": "Mese", + "viewWeek": "Settimana", + "viewDay": "Giorno", + "viewAgenda": "Agenda", + "allDay": "Tutto il giorno", + "allDayShort": "tutto il giorno", + "moreEvents": "+{{count}} altri", + "weekNumberLabel": "S{{week}} · {{month}} {{year}}", + "agendaFrom": "Dal {{date}}", + "titleLabel": "Titolo *", + "titlePlaceholder": "es. Dentista", + "allDayToggle": "Tutto il giorno", + "startDateLabel": "Data inizio", + "startTimeLabel": "Ora inizio", + "endDateLabel": "Data fine", + "endTimeLabel": "Ora fine", + "fromLabel": "Dal", + "toLabel": "Al", + "locationLabel": "Luogo", + "locationPlaceholder": "Opzionale", + "assignedLabel": "Assegnato a", + "assignedNobody": "— Nessuno —", + "colorLabel": "Colore", + "descriptionLabel": "Descrizione", + "descriptionPlaceholder": "Opzionale…", + "popupEdit": "Modifica", + "deleteConfirm": "Eliminare davvero \"{{title}}\"?", + "createdToast": "Evento creato", + "savedToast": "Evento salvato", + "deletedToast": "Evento eliminato", + "loadError": "Impossibile caricare gli eventi.", + "saveError": "Errore durante il salvataggio", + "deleteError": "Errore durante l'eliminazione", + "titleRequired": "Il titolo è obbligatorio", + "monthJanuary": "Gennaio", + "monthFebruary": "Febbraio", + "monthMarch": "Marzo", + "monthApril": "Aprile", + "monthMay": "Maggio", + "monthJune": "Giugno", + "monthJuly": "Luglio", + "monthAugust": "Agosto", + "monthSeptember": "Settembre", + "monthOctober": "Ottobre", + "monthNovember": "Novembre", + "monthDecember": "Dicembre", + "dayShortSunday": "Dom", + "dayShortMonday": "Lun", + "dayShortTuesday": "Mar", + "dayShortWednesday": "Mer", + "dayShortThursday": "Gio", + "dayShortFriday": "Ven", + "dayShortSaturday": "Sab", + "dayLongSunday": "Domenica", + "dayLongMonday": "Lunedì", + "dayLongTuesday": "Martedì", + "dayLongWednesday": "Mercoledì", + "dayLongThursday": "Giovedì", + "dayLongFriday": "Venerdì", + "dayLongSaturday": "Sabato", + "timeSuffix": "", + "colorLabel": "Colore {{color}}" + }, + + "notes": { + "title": "Bacheca", + "newNote": "Nuova nota", + "editNote": "Modifica nota", + "addNoteLabel": "Nuova nota", + "searchPlaceholder": "Cerca note…", + "emptyTitle": "Ancora nessuna nota", + "emptyDescription": "Crea una nuova nota con il pulsante +.", + "noResultsTitle": "Nessun risultato", + "noResultsDescription": "Nessuna nota contiene \"{{query}}\".", + "titleLabel": "Titolo (opzionale)", + "titlePlaceholder": "Senza titolo", + "contentLabel": "Contenuto", + "contentMarkdownHint": "(Formattazione Markdown supportata)", + "contentPlaceholder": "Inserisci nota…", + "colorLabel": "Colore", + "pinnedLabel": "Fissa (appare sulla dashboard)", + "pinAction": "Fissa", + "unpinAction": "Sfissa", + "deleteLabel": "Elimina nota", + "deleteConfirm": "Eliminare davvero questa nota?", + "createdToast": "Nota creata", + "savedToast": "Nota salvata", + "deletedToast": "Nota eliminata", + "loadError": "Impossibile caricare le note.", + "formatBold": "Grassetto (Ctrl+B)", + "formatItalic": "Corsivo (Ctrl+I)", + "formatUnderline": "Sottolineato (Ctrl+U)", + "formatStrikethrough": "Barrato", + "formatHeading": "Titolo", + "formatList": "Elenco puntato", + "formatOrderedList": "Elenco numerato", + "formatChecklist": "Lista di controllo", + "formatLink": "Link", + "formatCode": "Codice", + "formatQuote": "Citazione", + "formatDivider": "Divisore" + }, + + "contacts": { + "title": "Contatti", + "newContact": "Nuovo contatto", + "editContact": "Modifica contatto", + "addButton": "Nuovo", + "newContactLabel": "Nuovo contatto", + "searchPlaceholder": "Cerca per nome, telefono o email…", + "importButton": "Importa", + "importLabel": "Importa contatto da vCard", + "importTooltip": "Importa vCard", + "emptyTitle": "Ancora nessun contatto", + "emptyDescription": "Aggiungi nuovi contatti con il pulsante +.", + "filterAll": "Tutti", + "nameLabel": "Nome *", + "namePlaceholder": "Nome completo", + "categoryLabel": "Categoria", + "phoneLabel": "Telefono", + "phonePlaceholder": "+39 …", + "emailLabel": "Email", + "emailPlaceholder": "nome@example.com", + "addressLabel": "Indirizzo", + "addressPlaceholder": "Via, CAP Città", + "notesLabel": "Note", + "notesPlaceholder": "Opzionale…", + "callLabel": "Chiama", + "emailActionLabel": "Email", + "mapsLabel": "Apri in Maps", + "exportLabel": "Esporta come vCard", + "exportTooltip": "Esporta vCard", + "deleteLabel": "Elimina contatto", + "deleteConfirm": "Eliminare davvero questo contatto?", + "deletePersonConfirm": "Eliminare davvero \"{{name}}\"?", + "savedToast": "Contatto salvato", + "updatedToast": "Contatto aggiornato", + "deletedToast": "Contatto eliminato", + "importedToast": "{{name}} importato.", + "importError": "Importazione fallita: {{error}}", + "vcardNoName": "La vCard non contiene un nome.", + "catDoctor": "Medico", + "catSchool": "Scuola/Asilo", + "catAuthority": "Pubblica amministrazione", + "catInsurance": "Assicurazione", + "catCraftsman": "Artigiano", + "catEmergency": "Emergenza", + "catMisc": "Varie", + "categoryDoctor": "Medico", + "categorySchool": "Scuola/Asilo", + "categoryAuthority": "Pubblica amministrazione", + "categoryInsurance": "Assicurazione", + "categoryCraftsman": "Artigiano", + "categoryEmergency": "Emergenza", + "categoryOther": "Altro" + }, + + "budget": { + "title": "Bilancio", + "newEntry": "Nuova voce", + "editEntry": "Modifica voce", + "addEntryLabel": "Aggiungi voce", + "newEntryFabLabel": "Nuova voce", + "currentMonth": "Corrente", + "prevMonth": "Mese precedente", + "nextMonth": "Mese successivo", + "income": "Entrate", + "expenses": "Uscite", + "balance": "Saldo", + "byCategory": "Per categoria", + "transactions": "Transazioni", + "emptyTitle": "Nessuna voce questo mese", + "emptyDescription": "Aggiungi voci di bilancio con il pulsante +.", + "csvExport": "CSV", + "typeExpense": "Uscita", + "typeIncome": "Entrata", + "titleLabel": "Titolo *", + "titlePlaceholder": "es. Supermercato", + "amountLabel": "Importo (€) *", + "amountPlaceholder": "0,00", + "categoryLabel": "Categoria", + "dateLabel": "Data *", + "recurringLabel": "Ricorrente", + "deleteLabel": "Elimina voce", + "deleteConfirm": "Eliminare davvero questa voce?", + "deletePersonConfirm": "Eliminare davvero \"{{title}}\"?", + "addedToast": "Voce aggiunta", + "savedToast": "Voce salvata", + "deletedToast": "Voce eliminata", + "loadError": "Impossibile caricare il bilancio.", + "trendNeutral": "— come {{month}}", + "validAmountRequired": "Inserisci un importo valido", + "dateRequired": "La data è obbligatoria", + "catFood": "Spesa alimentare", + "catRent": "Affitto", + "catInsurance": "Assicurazione", + "catMobility": "Trasporti", + "catLeisure": "Tempo libero", + "catClothing": "Abbigliamento", + "catHealth": "Salute", + "catEducation": "Istruzione", + "catMisc": "Varie", + "loadingIndicator": "Caricamento…" + }, + + "settings": { + "title": "Impostazioni", + "sectionDesign": "Aspetto", + "sectionAccount": "Il mio account", + "sectionCalendarSync": "Sincronizzazione calendario", + "sectionFamily": "Membri della famiglia", + "cardAppearance": "Visualizzazione", + "themeSystem": "Sistema", + "themeSysLabel": "Usa impostazione di sistema", + "themeLight": "Chiaro", + "themeLightLabel": "Modalità chiara", + "themeDark": "Scuro", + "themeDarkLabel": "Modalità scura", + "changePassword": "Cambia password", + "currentPasswordLabel": "Password attuale", + "newPasswordLabel": "Nuova password", + "confirmPasswordLabel": "Conferma nuova password", + "savePassword": "Salva password", + "passwordMismatch": "Le password non corrispondono.", + "passwordSavedToast": "Password modificata con successo.", + "googleCalendar": "Google Calendar", + "appleCalendar": "Apple Calendar (iCloud)", + "syncNow": "Sincronizza ora", + "disconnect": "Disconnetti", + "connectGoogle": "Connetti con Google", + "connected": "Connesso", + "connectedLastSync": "Connesso · Ultima: {{date}}", + "notConnected": "Non connesso", + "notConfigured": "Non configurato (variabili .env mancanti)", + "configured": "Configurato (tramite .env)", + "configuredLastSync": "Configurato (tramite .env) · Ultima: {{date}}", + "syncSuccess": "{{provider}} sincronizzato.", + "disconnectedToast": "{{provider}} disconnesso.", + "googleOnlyAdmin": "Solo l'admin può connettere Google Calendar.", + "appleOnlyAdmin": "Solo l'admin può connettere Apple Calendar.", + "caldavUrlLabel": "URL server CalDAV", + "caldavUrlPlaceholder": "https://caldav.icloud.com", + "appleIdLabel": "Apple ID (email)", + "applePasswordLabel": "Password app-specifica", + "applePasswordHint": "Crea la password su appleid.apple.com → Sicurezza.", + "appleConnectBtn": "Connetti e testa", + "appleConnecting": "Connessione…", + "appleConnectedToast": "Apple Calendar connesso.", + "syncSuccessGoogle": "Sincronizzazione calendario con Google connessa con successo.", + "syncSuccessApple": "Sincronizzazione calendario con Apple connessa con successo.", + "syncErrorGoogle": "Connessione a Google fallita. Riprova.", + "syncErrorApple": "Connessione ad Apple fallita. Riprova.", + "addMember": "+ Aggiungi membro", + "newMemberTitle": "Nuovo membro familiare", + "usernameLabel": "Nome utente", + "displayNameLabel": "Nome visualizzato", + "memberPasswordLabel": "Password", + "colorLabel": "Colore", + "roleLabel": "Ruolo", + "roleMember": "Membro", + "roleAdmin": "Admin", + "createMember": "Crea", + "cancelAddMember": "Annulla", + "memberAddedToast": "{{name}} aggiunto.", + "deleteMemberConfirm": "Eliminare davvero {{name}}?", + "memberDeletedToast": "{{name}} eliminato.", + "deleteMemberLabel": "Elimina", + "logout": "Esci", + "synchronizing": "Sincronizzazione…", + "googleDisconnectConfirm": "Disconnettere Google Calendar?", + "appleDisconnectConfirm": "Disconnettere Apple Calendar?", + "localeSystem": "Sistema", + "localeLabel": "Lingua", + "languageTitle": "Lingua" + }, + + "login": { + "tagline": "Pianificazione familiare. Sicura. Rispettosa della privacy. Open source.", + "usernameLabel": "Nome utente", + "usernamePlaceholder": "nomeutente", + "passwordLabel": "Password", + "passwordPlaceholder": "••••••••", + "loginButton": "Accedi", + "loggingIn": "Accesso in corso…", + "tooManyAttempts": "Troppi tentativi. Attendi un momento.", + "invalidCredentials": "Credenziali non valide." + }, + + "install": { + "title": "Installa Oikos", + "subtitle": "Aggiungi alla schermata home", + "iosTip1": "Tocca ", + "iosTip2": " → \"Aggiungi a Home\"", + "installButton": "Installa", + "dismissLabel": "Chiudi" + }, + + "modal": { + "closeLabel": "Chiudi" + } +}