diff --git a/README.md b/README.md index 0c8f22c..563edc0 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ The goal is a single, private place for everything that keeps a household runnin - **PWA** — installable on any device, works offline, dark mode, responsive from phone to desktop - **Privacy First** — SQLCipher AES-256 encrypted database, fully self-hosted, zero telemetry - **Zero Build Step** — pure ES modules, no bundler, no transpiler, no framework -- **Multilingual** — 16 languages with automatic locale detection (de, en, es, fr, it, sv, el, ru, tr, zh, ja, ar, hi, pt, uk, pl) +- **Multilingual** — 17 languages with automatic locale detection (de, en, es, fr, it, sv, da, el, ru, tr, zh, ja, ar, hi, pt, uk, pl) ## Quick Start diff --git a/TRANSLATION_DA_NOTES.md b/TRANSLATION_DA_NOTES.md new file mode 100644 index 0000000..286ac6b --- /dev/null +++ b/TRANSLATION_DA_NOTES.md @@ -0,0 +1,16 @@ +# Danish translation notes + +No blocking translation issues found. + +A few terms involved product-style judgment calls: + +- `notes.title` / `nav.notes` → **"Tavle"** to match the family-planner/board feel better than a literal "Noter". +- `housekeeping.title` → **"Rengøringsområde"** while navigation keeps **"Rengøring"** for shorter UI labels. +- `dashboard.title` / `nav.dashboard` → **"Oversigt"** to stay natural in Danish UI copy. +- Existing non-English source strings inside `en.json` (mainly a few German settings labels) were translated into Danish from their apparent UI meaning. + +Verified: + +- `public/locales/da.json` parses as valid JSON. +- `da.json` matches `en.json` key structure exactly. +- Placeholder tokens such as `{{name}}`, `{{count}}`, `{{date}}`, and `{{title}}` were preserved exactly. diff --git a/docs/SPEC.md b/docs/SPEC.md index fc1ed1b..151ea94 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -791,7 +791,7 @@ All UI strings are managed via `public/i18n.js`. No hardcoded text in JS files o ### Architecture - **Module:** `public/i18n.js` - exports: `initI18n()`, `setLocale()`, `t(key, params?)`, `getLocale()`, `getSupportedLocales()`, `formatDate(date)`, `formatTime(date)` -- **Locale files:** `public/locales/de.json` (reference), `public/locales/en.json`, `public/locales/es.json`, `public/locales/fr.json`, `public/locales/it.json`, `public/locales/sv.json`, `public/locales/el.json`, `public/locales/ru.json`, `public/locales/tr.json`, `public/locales/zh.json`, `public/locales/ja.json`, `public/locales/ar.json`, `public/locales/hi.json`, `public/locales/pt.json`, `public/locales/uk.json`, `public/locales/pl.json` - structure: `{ "module.camelCaseKey": "Value" }` +- **Locale files:** `public/locales/de.json` (reference), `public/locales/en.json`, `public/locales/es.json`, `public/locales/fr.json`, `public/locales/it.json`, `public/locales/sv.json`, `public/locales/da.json`, `public/locales/el.json`, `public/locales/ru.json`, `public/locales/tr.json`, `public/locales/zh.json`, `public/locales/ja.json`, `public/locales/ar.json`, `public/locales/hi.json`, `public/locales/pt.json`, `public/locales/uk.json`, `public/locales/pl.json` - structure: `{ "module.camelCaseKey": "Value" }` - **Variables:** `{{variable}}` syntax in translation strings, e.g. `t('tasks.assignedTo', { name: 'Anna' })` - **Fallback chain:** active locale → German (`de`) → key itself - **Date format:** `Intl.DateTimeFormat` with current locale - use `formatDate()` and `formatTime()` from `i18n.js` diff --git a/public/components/oikos-locale-picker.js b/public/components/oikos-locale-picker.js index 876dcf1..9ec07d9 100644 --- a/public/components/oikos-locale-picker.js +++ b/public/components/oikos-locale-picker.js @@ -14,6 +14,7 @@ const LOCALE_LABELS = { fr: 'Français', it: 'Italiano', sv: 'Svenska', + da: 'Dansk', el: 'Ελληνικά', ru: 'Русский', tr: 'Türkçe', diff --git a/public/i18n.js b/public/i18n.js index c73564f..a911f3e 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', 'es', 'fr', 'it', 'sv', 'el', 'ru', 'tr', 'zh', 'ja', 'ar', 'hi', 'pt', 'uk', 'pl']; +const SUPPORTED_LOCALES = ['de', 'en', 'es', 'fr', 'it', 'sv', 'da', 'el', 'ru', 'tr', 'zh', 'ja', 'ar', 'hi', 'pt', 'uk', 'pl']; const DEFAULT_LOCALE = 'de'; const STORAGE_KEY = 'oikos-locale'; const DATE_FORMAT_KEY = 'oikos-date-format'; diff --git a/public/locales/da.json b/public/locales/da.json new file mode 100644 index 0000000..50902be --- /dev/null +++ b/public/locales/da.json @@ -0,0 +1,1462 @@ +{ + "common": { + "save": "Gem", + "cancel": "Annuller", + "delete": "Slet", + "edit": "Rediger", + "close": "Luk", + "create": "Opret", + "add": "Tilføj", + "back": "Tilbage", + "next": "Næste", + "loading": "Indlæser…", + "saving": "Gemmer…", + "required": "Dette felt er påkrævet.", + "error": "Fejl", + "allFieldsRequired": "Udfyld venligst alle felter.", + "today": "I dag", + "tomorrow": "I morgen", + "skipToContent": "Spring til indhold", + "reload": "Genindlæs", + "errorOccurred": "Noget gik galt.", + "unexpectedError": "Der opstod en uventet fejl.", + "errorGeneric": "Der opstod en fejl.", + "updateAvailable": "Opdatering tilgængelig - genindlæs siden for at få den nyeste version.", + "titleRequired": "Titel er påkrævet", + "nameRequired": "Navn er påkrævet", + "contentRequired": "Indhold er påkrævet", + "all": "Alle", + "unknownError": "Ukendt fejl", + "confirm": "Bekræft", + "undo": "Fortryd", + "reset": "Nulstil til original", + "errorOffline": "Ingen internetforbindelse. Tjek venligst dit netværk.", + "errorForbidden": "Adgang nægtet. Log venligst ind igen.", + "errorNotFound": "Posten blev ikke fundet.", + "errorServer": "Serverfejl. Prøv venligst igen senere.", + "errorTimeout": "Forbindelsen tog for lang tid. Prøv venligst igen." + }, + "nav": { + "dashboard": "Oversigt", + "tasks": "Opgaver", + "calendar": "Kalender", + "meals": "Måltider", + "shopping": "Indkøb", + "notes": "Tavle", + "contacts": "Kontakter", + "birthdays": "Fødselsdage", + "budget": "Budget", + "settings": "Indstillinger", + "main": "Hovednavigation", + "navigation": "Navigation", + "quickActions": "Hurtige handlinger", + "recipes": "Opskrifter", + "more": "Mere", + "documents": "Dokumenter", + "kitchen": "Køkken", + "search": "Søg", + "housekeeping": "Rengøring" + }, + "dashboard": { + "title": "Oversigt", + "greetingMorning": "Godmorgen, {{name}}", + "greetingDay": "God eftermiddag, {{name}}", + "greetingEvening": "Godaften, {{name}}", + "allDone": "Alt er klaret", + "noEvents": "Ingen begivenheder", + "noPinnedNotes": "Ingen fastgjorte noter", + "todayMeals": "Dagens måltider", + "allLink": "Alle", + "weekLink": "Uge", + "urgentTasksChip": "{{count}} opgave forfalder snart", + "urgentTasksChipPlural": "{{count}} opgaver forfalder snart", + "eventsChip": "{{count}} begivenhed i dag", + "eventsChipPlural": "{{count}} begivenheder i dag", + "todayMealChip": "I dag: {{title}}", + "loadError": "Dashboardet kunne ikke indlæses fuldt ud.", + "weatherRefresh": "Opdater vejr", + "weatherRefreshTitle": "Opdater", + "weatherUpdated": "Vejr opdateret", + "weatherFeelsLike": "Føles som {{temp}}° · {{humidity}}% · Vind {{wind}} {{windUnit}}", + "fabTaskLabel": "Tilføj opgave", + "fabCalendarLabel": "Tilføj begivenhed", + "fabShoppingLabel": "Tilføj indkøb", + "fabNoteLabel": "Tilføj note", + "fabTask": "Opgave", + "fabCalendar": "Begivenhed", + "fabShopping": "Indkøb", + "fabNote": "Note", + "overdue": "Forsinket", + "dueSoon": "Forfalder i dag", + "dueToday": "Forfalder i dag", + "dueTomorrow": "Forfalder i morgen", + "allDay": "Hele dagen", + "shoppingMore": "+{{count}} flere", + "weather": "Vejr", + "familyMembers": "Familiemedlemmer", + "participantsAdded": "deltagere tilføjet", + "upcomingBirthdays": "Kommende fødselsdage", + "noBirthdays": "Ingen fødselsdage endnu", + "daysLeft": "{{count}} dage", + "budgetOverview": "Budgetoversigt", + "monthlyIncome": "Indtægter", + "monthlyExpenses": "Udgifter", + "monthlyBalance": "Balance", + "savingsRate": "Opsparingsrate", + "topExpense": "Største udgift", + "budgetEntries": "Poster", + "noBudgetData": "Ingen budgetdata denne måned.", + "customize": "Tilpas", + "customizeTitle": "Tilpas widgets", + "customizeReset": "Nulstil", + "customizeSaved": "Dashboard gemt", + "customizeMoveUp": "Flyt op", + "customizeMoveDown": "Flyt ned", + "overdueTasksChip": "{{count}} forsinket opgave", + "overdueTasksChipPlural": "{{count}} forsinkede opgaver", + "customizeManage": "Widgets", + "customizeExit": "Afslut tilpasning", + "customizeDrag": "Træk widget", + "customizeSize": "Størrelse", + "customizeSizeFor": "Størrelse for {{widget}}", + "customizeHide": "Skjul {{widget}}", + "widgetSizeTiny": "Lille (1×1)", + "widgetSizeNarrow": "Smal (2×1)", + "widgetSizeStandard": "Standard (2×2)", + "widgetSizeLarge": "Stor (3×2)", + "widgetSizeFull": "Fuld (4×2)" + }, + "tasks": { + "title": "Opgaver", + "newTask": "Ny opgave", + "editTask": "Rediger opgave", + "emptyTitle": "Ingen opgaver - alt klaret?", + "emptyDescription": "Opret nye opgaver med + knappen.", + "titleLabel": "Titel *", + "titlePlaceholder": "Hvad skal gøres?", + "descriptionLabel": "Note", + "descriptionPlaceholder": "Valgfri detaljer…", + "priorityLabel": "Prioritet", + "categoryLabel": "Kategori", + "dueDateLabel": "Forfaldsdato", + "dueTimeLabel": "Tid", + "assignedLabel": "Tildelt til", + "assignedNobody": "- Ingen -", + "statusLabel": "Status", + "priorityUrgent": "Akut", + "priorityHigh": "Høj", + "priorityMedium": "Mellem", + "priorityLow": "Lav", + "priorityNone": "Ingen", + "statusOpen": "Åben", + "statusInProgress": "I gang", + "statusDone": "Færdig", + "statusArchived": "Arkiveret", + "categoryHousehold": "Husholdning", + "categorySchool": "Skole", + "categoryShopping": "Indkøb", + "categoryRepair": "Reparation", + "categoryHealth": "Sundhed", + "categoryFinance": "Økonomi", + "categoryLeisure": "Fritid", + "categoryMisc": "Diverse", + "overdue": "Forsinket", + "overdueDay": "{{count}}d forsinket", + "dueToday": "Forfalder i dag", + "dueTomorrow": "Forfalder i morgen", + "groupOverdue": "Forsinket", + "groupToday": "I dag", + "groupThisWeek": "Denne uge", + "groupNextWeek": "Næste uge", + "groupLater": "Senere", + "groupNoDate": "Ingen dato", + "markDone": "Markér {{title}} som færdig", + "markOpen": "Markér {{title}} som åben", + "editButton": "Rediger opgave", + "archiveButton": "Arkivér opgave", + "swipeOpen": "Genåbn", + "swipeDone": "Færdig", + "swipeEdit": "Rediger", + "subtaskAdd": "+ Tilføj underopgave", + "subtaskToggle": "Vis underopgaver", + "subtaskMarkDone": "Markér {{title}} som færdig", + "deleteConfirm": "Slet opgaven og alle underopgaver?", + "savedToast": "Opgave gemt.", + "createdToast": "Opgave oprettet.", + "deletedToast": "Opgave slettet.", + "archivedToast": "Opgave arkiveret.", + "bulkSelect": "Flervalg", + "selectTask": "Vælg opgave", + "bulkSelectedCount": "{{count}} valgt", + "bulkMarkDone": "Markér som færdig", + "bulkMarkOpen": "Markér som åben", + "bulkArchive": "Arkivér", + "bulkDelete": "Slet", + "bulkDeleteConfirm": "Slet {{count}} opgaver permanent?", + "bulkStatusChanged": "Status ændret.", + "bulkArchived": "Opgaver arkiveret.", + "bulkDeleted": "Opgaver slettet.", + "loadError": "Opgaven kunne ikke indlæses.", + "subtaskPrompt": "Underopgave:", + "kanbanOpen": "Åben", + "kanbanInProgress": "I gang", + "kanbanDone": "Færdig", + "kanbanArchived": "Arkiveret", + "kanbanMoveToInProgress": "Sæt til i gang", + "kanbanMoveToDone": "Markér som færdig", + "kanbanMoveToOpen": "Genåbn", + "recurring": "Gentagende", + "listView": "Listevisning", + "kanbanView": "Kanban-visning", + "filterBtn": "Filter", + "filterClearAll": "Ryd alle filtre", + "filterGroupPerson": "Person", + "filterGroupPriority": "Prioritet", + "filterGroupStatus": "Status", + "swipedDoneToast": "Markeret som færdig.", + "swipedOpenToast": "Markeret som åben.", + "reminderNeedsDueDate": "Angiv en forfaldsdato for at aktivere påmindelser for opgaven.", + "emptyAction": "Opret opgave", + "navLabelOverdue": "Opgaver, {{count}} forsinkede" + }, + "shopping": { + "title": "Indkøb", + "noLists": "Ingen lister", + "noListsDescription": "Opret en liste med + knappen.", + "emptyList": "Listen er tom", + "emptyListDescription": "Tilføj varer via feltet ovenfor.", + "newListPrompt": "Navn på den nye liste:", + "newListButton": "Opret ny liste", + "renameListPrompt": "Nyt listenavn:", + "deleteListConfirm": "Slet listen \"{{name}}\" og alle varer?", + "deletedListToast": "Liste slettet.", + "itemDeletedToast": "\"{{name}}\" fjernet.", + "itemsRemovedToast": "{{count}} varer fjernet.", + "clearChecked": "Fjern markerede ({{count}})", + "itemNamePlaceholder": "Tilføj vare…", + "itemQtyPlaceholder": "Mængde", + "itemNameLabel": "Varenavn", + "itemQtyLabel": "Mængde", + "categoryLabel": "Kategori", + "addItemLabel": "Tilføj vare", + "renameListLabel": "Omdøb liste", + "deleteListLabel": "Slet liste", + "swipeBack": "Fortryd", + "swipeCheck": "Sæt kryds", + "swipeDelete": "Slet", + "markDoneLabel": "Sæt kryds ved {{name}}", + "markUndoneLabel": "Fjern kryds ved {{name}}", + "deleteItemLabel": "Slet {{name}}", + "listsLoadError": "Lister kunne ikke indlæses.", + "itemsLoadError": "Varer kunne ikke indlæses.", + "catFruitVeg": "Frugt & grønt", + "catBakery": "Bageri", + "catDairy": "Mejeri", + "catMeatFish": "Kød & fisk", + "catFrozen": "Frost", + "catDrinks": "Drikkevarer", + "catHousehold": "Husholdning", + "catDrugstore": "Apotek", + "catMisc": "Diverse", + "emptyAction": "Tilføj vare" + }, + "meals": { + "title": "Madplan", + "noMealPlanned": "Intet måltid planlagt", + "addMeal": "Tilføj {{type}}", + "editMeal": "Rediger måltid", + "addMealTitle": "Tilføj måltid", + "deleteMeal": "Slet måltid", + "transferToShoppingList": "Tilføj ingredienser til indkøbsliste", + "today": "I dag", + "prevWeek": "Forrige uge", + "nextWeek": "Næste uge", + "loadError": "Madplanen kunne ikke indlæses.", + "typeBreakfast": "Morgenmad", + "typeLunch": "Frokost", + "typeDinner": "Aftensmad", + "typeSnack": "Snack", + "dayMo": "Man", + "dayDi": "Tir", + "dayMi": "Ons", + "dayDo": "Tor", + "dayFr": "Fre", + "daySa": "Lør", + "daySo": "Søn", + "dateLabel": "Dato", + "mealTypeLabel": "Måltid", + "titleLabel": "Titel *", + "titlePlaceholder": "fx Spaghetti Bolognese", + "notesLabel": "Noter", + "notesPlaceholder": "Valgfrit…", + "ingredientsLabel": "Ingredienser", + "addIngredient": "Tilføj ingrediens", + "ingredientNamePlaceholder": "Ingrediens", + "ingredientQtyPlaceholder": "Mængde", + "ingredientCategoryLabel": "Kategori", + "ingredientCategoryDefault": "Diverse", + "removeIngredient": "Fjern ingrediens", + "transferLabel": "Overfør ingredienser til indkøbsliste", + "transferNow": "Overfør nu", + "noShoppingLists": "Ingen indkøbslister tilgængelige", + "transferSuccess": "{{count}} ingrediens overført", + "transferSuccessPlural": "{{count}} ingredienser overført", + "transferAlreadyDone": "Alle ingredienser er allerede overført", + "ingredientCount": "{{count}} ingrediens", + "ingredientCountPlural": "{{count}} ingredienser", + "titleRequired": "Titel er påkrævet", + "loadingIndicator": "Indlæser…", + "recipeUrlLabel": "Opskriftslink (valgfrit)", + "recipeUrlPlaceholder": "https://…", + "openRecipe": "Åbn opskrift", + "savedRecipeLabel": "Gemt opskrift", + "savedRecipePlaceholder": "Vælg opskrift", + "saveAsRecipe": "Gem som opskrift", + "recipeScaleLabel": "Skalér ingredienser", + "deletedToast": "Måltid slettet" + }, + "calendar": { + "title": "Kalender", + "newEvent": "Ny begivenhed", + "editEvent": "Rediger begivenhed", + "addEvent": "Tilføj begivenhed", + "deleteEvent": "Slet begivenhed", + "noEvents": "Ingen begivenheder i den valgte periode.", + "today": "I dag", + "back": "Tilbage", + "forward": "Frem", + "viewMonth": "Måned", + "viewWeek": "Uge", + "viewDay": "Dag", + "viewAgenda": "Agenda", + "allDay": "Hele dagen", + "allDayShort": "hele dagen", + "moreEvents": "+{{count}} flere", + "weekNumberLabel": "U{{week}} · {{month}} {{year}}", + "agendaFrom": "Fra {{date}}", + "titleLabel": "Titel *", + "titlePlaceholder": "fx Tandlæge", + "allDayToggle": "Hele dagen", + "startDateLabel": "Startdato", + "startTimeLabel": "Starttid", + "endDateLabel": "Slutdato", + "endTimeLabel": "Sluttid", + "fromLabel": "Fra", + "toLabel": "Til", + "locationLabel": "Sted", + "locationPlaceholder": "Valgfrit", + "assignedLabel": "Tildelt til", + "assignedNobody": "- Ingen -", + "colorLabel": "Farve {{color}}", + "descriptionLabel": "Beskrivelse", + "descriptionPlaceholder": "Valgfrit…", + "popupEdit": "Rediger", + "deleteConfirm": "Vil du virkelig slette \"{{title}}\"?", + "createdToast": "Begivenhed oprettet", + "savedToast": "Begivenhed gemt", + "deletedToast": "Begivenhed slettet", + "loadError": "Begivenheder kunne ikke indlæses.", + "saveError": "Fejl ved gemning", + "deleteError": "Fejl ved sletning", + "titleRequired": "Titel er påkrævet", + "monthJanuary": "januar", + "monthFebruary": "februar", + "monthMarch": "marts", + "monthApril": "april", + "monthMay": "maj", + "monthJune": "juni", + "monthJuly": "juli", + "monthAugust": "august", + "monthSeptember": "september", + "monthOctober": "oktober", + "monthNovember": "november", + "monthDecember": "december", + "dayShortSunday": "Søn", + "dayShortMonday": "Man", + "dayShortTuesday": "Tir", + "dayShortWednesday": "Ons", + "dayShortThursday": "Tor", + "dayShortFriday": "Fre", + "dayShortSaturday": "Lør", + "dayLongSunday": "søndag", + "dayLongMonday": "mandag", + "dayLongTuesday": "tirsdag", + "dayLongWednesday": "onsdag", + "dayLongThursday": "torsdag", + "dayLongFriday": "fredag", + "dayLongSaturday": "lørdag", + "timeSuffix": "", + "ics": { + "reset": "Nulstil til original", + "resetToast": "Ændringer nulstillet." + }, + "iconLabel": "Ikon", + "iconSearchPlaceholder": "Søg ikon...", + "iconSearchEmpty": "Intet ikon fundet.", + "iconCategoryGeneral": "Generelt", + "iconCategoryHealth": "Sundhed & sport", + "iconCategoryTransport": "Transport & rejser", + "iconCategoryWork": "Arbejde & uddannelse", + "iconCategoryFood": "Mad & drikke", + "iconCategoryShopping": "Indkøb & økonomi", + "iconCategoryLeisure": "Fritid & underholdning", + "iconCategoryFamily": "Familie & kæledyr", + "iconCategoryHome": "Hjem & husholdning", + "iconCategoryNature": "Natur & diverse", + "iconCalendar": "Kalender", + "iconAlarm": "Alarm", + "iconClock": "Ur", + "iconBell": "Påmindelse", + "iconLocation": "Placering", + "iconStar": "Favorit", + "iconFlag": "Flag", + "iconTarget": "Mål", + "iconFlame": "Vigtigt", + "iconTooth": "Tandlæge", + "iconHospital": "Hospital", + "iconDoctor": "Læge", + "iconVaccine": "Vaccination", + "iconMedicine": "Medicin", + "iconBandage": "Forbinding", + "iconHealth": "Sundhed", + "iconActivity": "Aktivitet", + "iconHaircut": "Frisør", + "iconSports": "Sport", + "iconTrophy": "Konkurrence", + "iconCar": "Bil", + "iconBus": "Bus", + "iconTrain": "Tog", + "iconPlane": "Rejse", + "iconFlight": "Flyvning", + "iconFuel": "Tankning", + "iconNavigation": "Navigation", + "iconWork": "Arbejde", + "iconLaptop": "Laptop", + "iconPresentation": "Præsentation", + "iconSchool": "Skole", + "iconEducation": "Uddannelse", + "iconReading": "Læsning", + "iconStudy": "Studie", + "iconCalculator": "Lommeregner", + "iconMeal": "Måltid", + "iconCooking": "Madlavning", + "iconCoffee": "Kaffe", + "iconCake": "Fødselsdag", + "iconPizza": "Pizza", + "iconWine": "Vin", + "iconBeer": "Bar", + "iconShopping": "Indkøb", + "iconGroceries": "Dagligvarer", + "iconGift": "Gave", + "iconCard": "Kort", + "iconWallet": "Pung", + "iconSavings": "Opsparing", + "iconBank": "Bank", + "iconMusic": "Musik", + "iconMovie": "Film", + "iconTicket": "Billet", + "iconGame": "Spil", + "iconPhoto": "Foto", + "iconParty": "Fest", + "iconFamily": "Familie", + "iconBaby": "Baby", + "iconDog": "Hund", + "iconCat": "Kat", + "iconPet": "Kæledyr", + "iconHome": "Hjem", + "iconBuilding": "Bygning", + "iconRepair": "Reparation", + "iconMaintenance": "Vedligeholdelse", + "iconDecoration": "Dekoration", + "iconFurniture": "Møbler", + "iconLaundry": "Vasketøj", + "iconLeaf": "Natur", + "iconTree": "Træ", + "iconFlower": "Blomst", + "iconSun": "Dag", + "iconMoon": "Nat", + "iconWeather": "Vejr", + "invalidDate": "Brug en gyldig dato i det valgte datoformat.", + "caldavTargetLabel": "Synkroniser til CalDAV", + "caldavTargetLocal": "Gem kun lokalt", + "caldavTargetHint": "Vælg en CalDAV-kalender til at synkronisere denne begivenhed.", + "attachmentLabel": "Vedhæftning", + "attachmentHint": "Vedhæft et lokalt billede, PDF eller dokument. Billeder vises i begivenhedens popup.", + "attachmentFallback": "Vedhæftning", + "attachmentReadError": "Vedhæftningen kunne ikke læses.", + "attachmentTooLarge": "Vedhæftningen må højst være 5 MB.", + "colorBlue": "Blå", + "colorCoral": "Koral", + "colorCyan": "Cyan", + "colorGray": "Grå", + "colorGreen": "Grøn", + "colorOrange": "Orange", + "colorPurple": "Lilla", + "colorRed": "Rød", + "colorSkyBlue": "Himmelblå", + "colorYellow": "Gul", + "iconCleaning": "Rengøring", + "attachmentDocumentName": "{{title}} - {{name}}", + "attachmentDocumentDescription": "Vedhæftning uploadet til kalenderbegivenheden \"{{title}}\"." + }, + "notes": { + "title": "Tavle", + "newNote": "Ny note", + "editNote": "Rediger note", + "addNoteLabel": "Ny note", + "searchPlaceholder": "Søg noter…", + "emptyTitle": "Ingen noter endnu", + "emptyDescription": "Opret en ny note med + knappen.", + "noResultsTitle": "Ingen resultater", + "noResultsDescription": "Ingen note indeholder \"{{query}}\".", + "titleLabel": "Titel (valgfrit)", + "titlePlaceholder": "Ingen titel", + "contentLabel": "Indhold", + "contentMarkdownHint": "(Markdown-formatering understøttes)", + "contentPlaceholder": "Skriv note…", + "colorLabel": "Farve", + "pinnedLabel": "Fastgør (vises på dashboard)", + "pinAction": "Fastgør", + "unpinAction": "Frigør", + "deleteLabel": "Slet note", + "deleteConfirm": "Vil du virkelig slette denne note?", + "createdToast": "Note oprettet", + "savedToast": "Note gemt", + "deletedToast": "Note slettet", + "loadError": "Noter kunne ikke indlæses.", + "formatBold": "Fed (Ctrl+B)", + "formatItalic": "Kursiv (Ctrl+I)", + "formatUnderline": "Understreget (Ctrl+U)", + "formatStrikethrough": "Gennemstreget", + "formatHeading": "Overskrift", + "formatList": "Punktliste", + "formatOrderedList": "Nummereret liste", + "formatChecklist": "Tjekliste", + "formatLink": "Link", + "formatCode": "Kode", + "formatQuote": "Citat", + "formatDivider": "Skillelinje", + "emptyAction": "Opret note", + "colorAmber": "Ravgul", + "colorBlue": "Blå", + "colorGreen": "Grøn", + "colorOrange": "Orange", + "colorPurple": "Lilla", + "colorTeal": "Blågrøn", + "colorWhite": "Hvid", + "colorYellow": "Gul" + }, + "contacts": { + "title": "Kontakter", + "newContact": "Ny kontakt", + "editContact": "Rediger kontakt", + "addButton": "Ny", + "newContactLabel": "Ny kontakt", + "searchPlaceholder": "Søg efter navn, telefon eller e-mail…", + "importButton": "Importér", + "importLabel": "Importér kontakt fra vCard", + "importTooltip": "Importér vCard", + "emptyTitle": "Ingen kontakter endnu", + "emptyDescription": "Tilføj nye kontakter med + knappen.", + "filterAll": "Alle", + "nameLabel": "Navn *", + "namePlaceholder": "Fulde navn", + "categoryLabel": "Kategori", + "phoneLabel": "Telefon", + "phonePlaceholder": "+45 …", + "emailLabel": "E-mail", + "emailPlaceholder": "navn@eksempel.com", + "addressLabel": "Adresse", + "addressPlaceholder": "Gade, postnr. by", + "notesLabel": "Noter", + "notesPlaceholder": "Valgfrit…", + "callLabel": "Ring", + "emailActionLabel": "E-mail", + "mapsLabel": "Åbn i Kort", + "exportLabel": "Eksportér som vCard", + "exportTooltip": "Eksportér vCard", + "deleteLabel": "Slet kontakt", + "deleteConfirm": "Vil du virkelig slette denne kontakt?", + "deletePersonConfirm": "Vil du virkelig slette \"{{name}}\"?", + "savedToast": "Kontakt gemt", + "updatedToast": "Kontakt opdateret", + "deletedToast": "Kontakt slettet", + "importedToast": "{{name}} importeret.", + "importError": "Import mislykkedes: {{error}}", + "vcardNoName": "vCard indeholder ikke et navn.", + "catDoctor": "Læge", + "catSchool": "Skole/børnepasning", + "catAuthority": "Myndighed", + "catInsurance": "Forsikring", + "catCraftsman": "Håndværker", + "catEmergency": "Nødtilfælde", + "catMisc": "Diverse", + "categoryDoctor": "Læge", + "categorySchool": "Skole/daginstitution", + "categoryAuthority": "Myndighed", + "categoryInsurance": "Forsikring", + "categoryCraftsman": "Håndværker", + "categoryEmergency": "Nødtilfælde", + "categoryOther": "Andet", + "emptyAction": "Tilføj kontakt" + }, + "budget": { + "title": "Budget", + "newEntry": "Ny post", + "editEntry": "Rediger post", + "addEntryLabel": "Tilføj post", + "newEntryFabLabel": "Ny post", + "currentMonth": "Aktuel", + "prevMonth": "Forrige måned", + "nextMonth": "Næste måned", + "income": "Indtægter", + "expenses": "Udgifter", + "balance": "Balance", + "byCategory": "Efter kategori", + "transactions": "Transaktioner", + "emptyTitle": "Ingen poster denne måned", + "emptyDescription": "Tilføj budgetposter med + knappen.", + "csvExport": "CSV", + "typeExpense": "Udgift", + "typeIncome": "Indtægt", + "titleLabel": "Titel *", + "titlePlaceholder": "fx Supermarked", + "amountLabel": "Beløb *", + "amountPlaceholder": "0.00", + "categoryLabel": "Kategori", + "dateLabel": "Dato *", + "recurringLabel": "Gentagende", + "deleteLabel": "Slet post", + "deleteConfirm": "Vil du virkelig slette denne post?", + "deletePersonConfirm": "Vil du virkelig slette \"{{title}}\"?", + "addedToast": "Post tilføjet", + "savedToast": "Post gemt", + "deletedToast": "Post slettet", + "loadError": "Budgettet kunne ikke indlæses.", + "trendNeutral": "- samme som {{month}}", + "validAmountRequired": "Indtast venligst et gyldigt beløb", + "dateRequired": "Dato er påkrævet", + "catFood": "Mad", + "catRent": "Husleje", + "catInsurance": "Forsikring", + "catMobility": "Transport", + "catLeisure": "Fritid og underholdning", + "catClothing": "Tøj", + "catHealth": "Sundhed", + "catEducation": "Uddannelse", + "catMisc": "Diverse", + "catEarnedIncome": "Lønindkomst", + "catInvestmentIncome": "Investeringsindtægt", + "catTransferGiftIncome": "Overførsler & gaveindtægt", + "catGovernmentBenefits": "Offentlige ydelser & sociale ydelser", + "catOtherIncome": "Anden indtægt", + "loadingIndicator": "Indlæser…", + "subcategoryLabel": "Underkategori", + "catHousing": "Bolig / hjem", + "catTransport": "Transport", + "catPersonalHealth": "Personlig pleje / sundhed", + "catShoppingClothing": "Shopping og tøj", + "catFinancialOther": "Finansielle tjenester og andet", + "subcatRentMortgage": "Husleje / boliglån", + "subcatCondominium": "Ejerforeningsafgift", + "subcatUtilities": "El / vand / gas", + "subcatInternetTvPhone": "Internet / TV / telefon", + "subcatRenovationMaintenance": "Renovering / vedligeholdelse", + "subcatCleaning": "Rengøring", + "subcatGroceries": "Dagligvarer", + "subcatRestaurantsBars": "Restauranter / barer", + "subcatSnacksFastFood": "Snacks / fastfood", + "subcatBakery": "Bageri", + "subcatFuel": "Brændstof", + "subcatParkingTolls": "Parkering / vejafgifter", + "subcatPublicTransport": "Offentlig transport", + "subcatAppsTaxi": "Apps / taxa", + "subcatMaintenanceInsurance": "Vedligeholdelse / forsikring", + "subcatPharmacy": "Apotek", + "subcatHealthInsurance": "Sundhedsforsikring", + "subcatGymSports": "Fitness / sport", + "subcatBeautyCosmetics": "Skønhed / kosmetik", + "subcatTravel": "Rejser", + "subcatStreaming": "Streaming", + "subcatEvents": "Begivenheder", + "subcatHobbies": "Hobbyer", + "subcatClothesShoes": "Tøj / sko", + "subcatElectronics": "Elektronik", + "subcatGifts": "Gaver", + "subcatCoursesCollege": "Kurser / studie", + "subcatSchoolSupplies": "Skoleartikler", + "subcatLanguages": "Sprog", + "subcatLoansInterest": "Lån / renter", + "subcatBankFees": "Bankgebyrer", + "subcatInsuranceOther": "Forsikring", + "subcatInvestments": "Investeringer", + "subcatTaxes": "Skatter", + "metaLoadError": "Budgetkategorier kunne ikke indlæses.", + "addCategory": "+ kategori", + "addSubcategory": "+ underkategori", + "newCategoryPrompt": "Navn på den nye kategori:", + "newSubcategoryPrompt": "Navn på den nye underkategori:", + "categoryAddedToast": "Kategori tilføjet.", + "subcategoryAddedToast": "Underkategori tilføjet.", + "emptyAction": "Tilføj post", + "loansTitle": "Lån", + "loansSummary": "{{count}} aktive · {{amount}} tilbage", + "newLoan": "Nyt lån", + "createLoan": "Opret lån", + "editLoan": "Rediger lån", + "deleteLoan": "Slet lån", + "deleteLoanConfirm": "Slet lånet \"{{title}}\"? Betalinger, der allerede er bogført i budgettet, bliver også fjernet.", + "deleteLoanPaymentConfirm": "Slet denne lånebetaling?", + "loanRemainingAmount": "Tilbage", + "loanRemainingInstallments": "Afdrag tilbage", + "loanPaidAmount": "Betalt", + "loansEmpty": "Ingen aktive lån.", + "loanInstallmentMeta": "{{paid}} af {{total}} afdrag betalt", + "loanRemainingOf": "af {{total}}", + "loanNextDue": "Næste: {{month}}", + "loanPaidStatus": "Betalt", + "markLoanPaid": "Markér betalt", + "loanBorrowerLabel": "Låntager *", + "loanBorrowerPlaceholder": "fx Lais", + "loanTitleLabel": "Lånetitel", + "loanTitlePlaceholder": "fx Privatlån", + "loanAmountLabel": "Samlet beløb *", + "loanInstallmentsLabel": "Afdrag *", + "loanStartMonthLabel": "Første forfaldsmåned *", + "loanNotesLabel": "Noter", + "loanBorrowerRequired": "Låntager er påkrævet", + "loanInstallmentsRequired": "Indtast antal afdrag", + "loanStartMonthRequired": "Indtast første forfaldsmåned", + "loanAddedToast": "Lån tilføjet", + "loanSavedToast": "Lån gemt", + "loanDeletedToast": "Lån slettet", + "loanPaymentAddedToast": "Betaling registreret", + "loanPaymentTitle": "Lånetilbagebetaling: {{borrower}}", + "typeLoan": "Lån", + "tabsLabel": "Budgetsektioner", + "budgetTab": "Budget", + "loansTab": "Lån", + "filteredTransactions": "Filtrerede transaktioner", + "clearLoanFilter": "Ryd filter", + "loanFilterActive": "Lån: {{title}}", + "filterLoanTransactions": "Vis transaktioner for dette lån", + "loansEmptyDescription": "Opret et lån via + knappen og vælg Lån.", + "newCategoryTitle": "Ny kategori", + "newCategoryPlaceholder": "Kategorinavn", + "newSubcategoryTitle": "Ny underkategori", + "newSubcategoryPlaceholder": "Navn på underkategori", + "loanStatusFilterLabel": "Filter for lånestatus", + "loanStatusActive": "Aktiv", + "loanStatusPaid": "Betalt", + "loanStatusAll": "Alle", + "loanTransactions": "Lånetransaktioner", + "loanInstallmentNumber": "Afdrag {{number}} af {{total}}", + "loanReportTitle": "Lånerapport", + "loanNoTransactions": "Ingen betalinger registreret endnu." + }, + "settings": { + "title": "Indstillinger", + "tabGeneral": "Generelt", + "tabMeals": "Måltider", + "tabBudget": "Budget", + "tabShopping": "Indkøb", + "tabCalendar": "Kalender", + "tabFamily": "Familieadministration", + "tabApiTokens": "API-tokens", + "tabAccount": "Konto", + "tabSync": "Synkronisering", + "tabsAriaLabel": "Indstillingssektioner", + "sectionContactSync": "Kontaktsynkronisering", + "sectionDesign": "Udseende", + "sectionAppName": "Applikationsnavn", + "sectionModules": "Moduler", + "modulesTitle": "Aktive moduler", + "modulesHint": "Deaktiverede moduler forsvinder fra navigationen. Data bevares og vises igen, når modulet aktiveres igen.", + "modulesSaved": "Synlighed af moduler gemt.", + "sectionShopping": "Indkøb", + "shoppingCategoriesLabel": "Indkøbskategorier", + "shoppingCategoriesHint": "Tilføj, omdøb, slet eller omarrangér kategorier.", + "shoppingCategoryPlaceholder": "Ny kategori…", + "shoppingCategoryRenameHint": "Klik for at omdøbe", + "shoppingCategoryRenamePrompt": "Nyt kategorinavn:", + "shoppingCategoryMoveUp": "Flyt kategori op", + "shoppingCategoryMoveDown": "Flyt kategori ned", + "shoppingCategoryDelete": "Slet kategori", + "shoppingCategoryDeleteConfirm": "Slet kategorien \"{{name}}\"? Eksisterende varer flyttes til den næste kategori.", + "shoppingCategoryAdded": "Kategori tilføjet.", + "shoppingCategoryRenamed": "Kategori omdøbt.", + "shoppingCategoryDeleted": "Kategori slettet.", + "sectionAccount": "Min konto", + "sectionCalendarSync": "Kalendersynkronisering", + "sectionFamily": "Familiemedlemmer", + "cardAppearance": "Visning", + "appNameTitle": "Applikationsnavn", + "appNameLabel": "Applikationsnavn", + "appNameHint": "Dette navn vises i sidepanelet, browserens titel og login-skærmen.", + "appNamePlaceholder": "Oikos", + "appNameSavedToast": "Applikationsnavn gemt.", + "sectionDate": "Dato", + "dateFormatTitle": "Datoformat", + "dateFormatLabel": "Foretrukket datoformat", + "dateFormatHint": "Vælg, hvordan datoer vises i hele appen.", + "dateFormatSavedToast": "Datoformat gemt.", + "timeFormatLabel": "Tidsformat", + "timeFormatHours": "timer", + "timeFormatSavedToast": "Tidsformat gemt.", + "themeSystem": "System", + "themeSysLabel": "Brug systemindstilling", + "themeLight": "Lys", + "themeLightLabel": "Lys tilstand", + "themeDark": "Mørk", + "themeDarkLabel": "Mørk tilstand", + "changePassword": "Skift adgangskode", + "currentPasswordLabel": "Nuværende adgangskode", + "newPasswordLabel": "Ny adgangskode", + "confirmPasswordLabel": "Bekræft ny adgangskode", + "savePassword": "Gem adgangskode", + "passwordMismatch": "Adgangskoderne matcher ikke.", + "passwordSavedToast": "Adgangskoden blev ændret.", + "googleCalendar": "Google Kalender", + "appleCalendar": "Apple Kalender (iCloud)", + "syncNow": "Synkroniser nu", + "disconnect": "Afbryd", + "connectGoogle": "Forbind med Google", + "connected": "Forbundet", + "connectedLastSync": "Forbundet · Sidst: {{date}}", + "notConnected": "Ikke forbundet", + "notConfigured": "Ikke konfigureret (mangler .env-variabler)", + "configured": "Konfigureret (via .env)", + "configuredLastSync": "Konfigureret (via .env) · Sidst: {{date}}", + "syncSuccess": "{{provider}} synkroniseret.", + "disconnectedToast": "{{provider}} afbrudt.", + "googleOnlyAdmin": "Kun administrator kan forbinde Google Kalender.", + "appleOnlyAdmin": "Kun administrator kan forbinde Apple Kalender.", + "caldavUrlLabel": "CalDAV-server-URL", + "caldavUrlPlaceholder": "https://caldav.icloud.com", + "appleIdLabel": "Apple-id (e-mail)", + "applePasswordLabel": "Appspecific adgangskode", + "applePasswordHint": "Opret adgangskode på appleid.apple.com → Sikkerhed.", + "appleConnectBtn": "Forbind og test", + "appleConnecting": "Forbinder…", + "appleConnectedToast": "Apple Kalender forbundet.", + "syncSuccessGoogle": "Kalendersynkronisering med Google blev oprettet.", + "syncSuccessApple": "Kalendersynkronisering med Apple blev oprettet.", + "syncErrorGoogle": "Forbindelsen til Google mislykkedes. Prøv igen.", + "syncErrorApple": "Forbindelsen til Apple mislykkedes. Prøv igen.", + "addMember": "+ Tilføj medlem", + "newMemberTitle": "Nyt familiemedlem", + "usernameLabel": "Brugernavn", + "displayNameLabel": "Visningsnavn", + "memberPasswordLabel": "Adgangskode", + "colorLabel": "Farve", + "profilePictureTitle": "Profilbillede", + "profilePictureLabel": "Upload billede", + "profilePictureHint": "PNG, JPEG eller WebP. Store billeder ændres i størrelse før upload.", + "profilePictureRemove": "Fjern billede", + "profilePictureTypeError": "Brug et PNG-, JPEG- eller WebP-billede.", + "profilePictureFileTooLarge": "Billedfilen er for stor.", + "profilePictureTooLarge": "Profilbilledet er stadig for stort efter skalering.", + "profilePictureReadError": "Det valgte billede kunne ikke læses.", + "profileSavedToast": "Profil opdateret.", + "editMemberLabel": "Rediger", + "editMemberTitle": "Rediger familiemedlem", + "saveMember": "Gem medlem", + "memberUpdatedToast": "{{name}} opdateret.", + "familyRoleLabel": "Familierolle", + "familyRoleDad": "Far", + "familyRoleMom": "Mor", + "familyRoleParent": "Forælder", + "familyRoleChild": "Barn", + "familyRoleGrandparent": "Bedsteforælder", + "familyRoleRelative": "Slægtning", + "familyRoleOther": "Familiemedlem", + "systemAdminLabel": "Systemadministrator", + "systemAdminHint": "Systemadministratorer kan administrere applikationsindstillinger, integrationer, API-tokens og familiekonti.", + "systemAdminBadge": "Systemadministrator", + "roleLabel": "Rolle", + "roleMember": "Medlem", + "roleAdmin": "Administrator", + "createMember": "Opret", + "cancelAddMember": "Annuller", + "memberAddedToast": "{{name}} tilføjet.", + "deleteMemberConfirm": "Vil du virkelig slette {{name}}?", + "memberDeletedToast": "{{name}} slettet.", + "deleteMemberLabel": "Slet", + "logout": "Log ud", + "synchronizing": "Synkroniserer…", + "googleDisconnectConfirm": "Afbryd Google Kalender?", + "appleDisconnectConfirm": "Afbryd Apple Kalender?", + "localeSystem": "System", + "localeLabel": "Sprog", + "languageTitle": "Sprog", + "sectionMeals": "Madplan", + "mealTypesLabel": "Synlige måltider", + "mealTypesHint": "Kun valgte måltidstyper vises i madplanen.", + "mealTypesSaved": "Madplansindstillinger gemt.", + "mealTypesMinOne": "Mindst én måltidstype skal være aktiv.", + "sectionBudget": "Budget", + "currencyLabel": "Valuta", + "currencyHint": "Angiver valutaen, der bruges i budgetsektionen.", + "currencySaved": "Valuta gemt.", + "apiTokensTitle": "API-tokens", + "apiTokensCardTitle": "Adgangstokens", + "apiTokensHint": "Opret API-tokens til eksterne integrationer. Den fulde token vises kun én gang efter oprettelse.", + "apiTokenNameLabel": "Tokennavn", + "apiTokenExpiresLabel": "Udløbsdato", + "apiTokenExpiresHint": "Lad feltet være tomt for at oprette en token uden udløb.", + "apiTokenCreatedLabel": "Ny API-token", + "apiTokenCreatedHint": "Gem denne token sikkert. Den kan ikke vises igen.", + "apiTokenCreate": "Opret token", + "apiTokenInvalidExpiration": "Indtast venligst en gyldig udløbsdato.", + "apiTokenCreatedToast": "API-token oprettet.", + "apiTokenRevokedToast": "API-token tilbagekaldt.", + "apiTokenRevokeConfirm": "Tilbagekald API-token \"{{name}}\"?", + "apiTokenRevoke": "Tilbagekald token", + "apiTokenRevoked": "Tilbagekaldt", + "apiTokenExpired": "Udløbet", + "apiTokenActive": "Aktiv", + "apiTokenPrefix": "Præfiks", + "apiTokenExpires": "Udløber", + "apiTokenNeverExpires": "Ingen udløbsdato", + "apiTokenLastUsed": "Sidst brugt", + "apiTokenNeverUsed": "Aldrig brugt", + "ics": { + "title": "ICS-abonnementer", + "add": "Tilføj abonnement", + "addedToast": "Abonnement tilføjet.", + "deletedToast": "Abonnement slettet.", + "syncedToast": "Abonnement synkroniseret.", + "confirm_delete": "Vil du virkelig slette dette abonnement? Alle tilknyttede begivenheder bliver også slettet.", + "empty": "Ingen abonnementer endnu.", + "form": { + "name": "Navn", + "url": "ICS-URL", + "color": "Farve", + "shared": "Synlig for alle" + }, + "actions": { + "submit": "Tilføj", + "save": "Gem", + "cancel": "Annuller", + "delete": "Slet", + "edit": "Rediger", + "sync": "Synkroniser nu" + }, + "status": { + "lastSync": "Sidst synkroniseret:", + "never": "Ikke synkroniseret endnu", + "syncing": "Synkroniserer...", + "syncError": "Synkroniseringsfejl" + }, + "badges": { + "private": "Privat", + "shared": "Delt" + }, + "updatedToast": "Abonnement opdateret." + }, + "memberPhoneLabel": "Telefonnummer (valgfrit)", + "memberEmailLabel": "E-mail (valgfrit)", + "memberBirthDateLabel": "Fødselsdato (valgfrit)", + "memberContactBirthdayHint": "Dette medlem synkroniseres automatisk med Kontakter og Fødselsdage.", + "memberBirthDateInvalid": "Brug en gyldig fødselsdato i det valgte datoformat.", + "memberPhoneMeta": "Telefon: {{value}}", + "memberBirthdayMeta": "Fødselsdag: {{date}}", + "tabBackup": "Backuphåndtering", + "sectionBackup": "Backuphåndtering", + "backupDownloadTitle": "Download databasebackup", + "backupDownloadHint": "Opret en konsistent SQLite-backup af alle appdata.", + "backupDownloadButton": "Download backup", + "backupRestoreTitle": "Gendan databasebackup", + "backupRestoreHint": "Gendannelse erstatter den aktuelle database. Download en frisk backup før du fortsætter.", + "backupDropzoneTitle": "Slip en backupfil her eller klik for at vælge", + "backupDropzoneHint": "SQLite-backupfiler: .db, .sqlite eller .sqlite3", + "backupRestoreButton": "Gendan backup", + "backupRestoreConfirm": "Gendannelse af denne backup erstatter den aktuelle database for alle. Fortsæt?", + "backupRestoring": "Gendanner...", + "backupRestoredToast": "Database gendannet. Genindlæser...", + "backupCliTitle": "CLI / Docker Compose-gendannelse", + "backupCliHint": "Til driftsmæssig gendannelse skal appen stoppes, backupen monteres i en midlertidig container, og databasefilen erstattes.", + "backupCliBackupHint": "Du kan også oprette en backup direkte fra Docker Compose:", + "backupSchedulerTitle": "Automatiske backups", + "backupSchedulerHint": "Planlagte backups oprettes automatisk, og gamle backups roteres.", + "backupSchedulerStatus": "Status", + "backupSchedulerEnabled": "Aktiveret", + "backupSchedulerDisabled": "Deaktiveret", + "backupSchedulerSchedule": "Plan", + "backupSchedulerKeep": "Opbevaring", + "backupSchedulerKeepCount": "{{count}} backups", + "backupSchedulerLastBackup": "Seneste backup", + "backupSchedulerLastSuccess": "{{date}} (lykkedes)", + "backupSchedulerLastFail": "{{date}} (mislykkedes)", + "backupSchedulerNever": "Ingen backup oprettet endnu", + "backupSchedulerTrigger": "Opret backup nu", + "backupSchedulerTriggering": "Opretter backup...", + "backupSchedulerTriggeredToast": "Backup oprettet.", + "caldavTitle": "CalDAV-kalendere", + "caldavDescription": "Forbind flere CalDAV-konti (iCloud, Nextcloud, Radicale, Baikal osv.) og vælg, hvilke kalendere der skal synkroniseres.", + "caldavAddAccount": "Tilføj CalDAV-konto", + "caldavEmptyState": "Ingen CalDAV-konti forbundet endnu. Tilføj din første konto for at komme i gang.", + "caldavNameLabel": "Kontonavn", + "caldavNamePlaceholder": "fx Min Radicale, iCloud, Nextcloud", + "caldavUrlHint": "Basis-URL'en til din CalDAV-server", + "caldavUsernameLabel": "Brugernavn", + "caldavPasswordLabel": "Adgangskode", + "caldavPasswordHint": "For iCloud: Brug en appspecifik adgangskode fra appleid.apple.com", + "caldavAccountAdded": "CalDAV-konto tilføjet", + "caldavAccountDeleted": "CalDAV-konto fjernet", + "caldavCalendarsToggle": "Vis/skjul kalendere", + "caldavRefreshCalendars": "Opdater kalendere", + "caldavSyncSuccess": "CalDAV-synkronisering lykkedes", + "caldavSyncFailed": "CalDAV-synkronisering mislykkedes", + "caldavConnectionFailed": "Forbindelse til CalDAV-server mislykkedes", + "calendarEnabled": "Kalender aktiveret", + "calendarDisabled": "Kalender deaktiveret", + "calendarsRefreshed": "Kalendere opdateret", + "deleteAccountConfirm": "Vil du virkelig slette CalDAV-kontoen? Alle synkroniserede kalendere bliver fjernet.", + "lastSync": "Sidst synkroniseret", + "cardavTitle": "CardDAV-kontakter", + "cardavDescription": "Forbind flere CardDAV-konti (iCloud, Nextcloud, Radicale osv.) og synkroniser dine kontakter.", + "cardavAddAccount": "Tilføj CardDAV-konto", + "cardavEmptyState": "Ingen CardDAV-konti forbundet endnu. Tilføj din første konto for at synkronisere kontakter.", + "cardavNameLabel": "Kontonavn", + "cardavNamePlaceholder": "fx iCloud, Nextcloud", + "cardavUrlLabel": "CardDAV-server-URL", + "cardavUrlPlaceholder": "https://contacts.icloud.com", + "cardavUrlHint": "Basis-URL'en til din CardDAV-server", + "cardavUsernameLabel": "Brugernavn", + "cardavPasswordLabel": "Adgangskode", + "cardavPasswordHint": "For iCloud: Brug en appspecifik adgangskode fra appleid.apple.com", + "cardavAccountAdded": "CardDAV-konto tilføjet", + "cardavAccountDeleted": "CardDAV-konto fjernet", + "cardavSyncSuccess": "CardDAV-synkronisering lykkedes", + "cardavSyncFailed": "CardDAV-synkronisering mislykkedes", + "cardavConnectionFailed": "Forbindelse til CardDAV-server mislykkedes", + "cardavAddressbooksToggle": "Vis/skjul adressebøger", + "cardavRefreshAddressbooks": "Opdater adressebøger", + "addressbookEnabled": "Adressebog aktiveret", + "addressbookDisabled": "Adressebog deaktiveret", + "addressbooksRefreshed": "Adressebøger opdateret", + "deleteCardDAVAccountConfirm": "Vil du virkelig slette CardDAV-kontoen? Alle synkroniserede kontakter forbliver, men mister deres CardDAV-link.", + "sectionHousekeeping": "Rengøring", + "housekeepingPaymentsTitle": "Betalingsopgaver", + "housekeepingPaymentTasksLabel": "Opret en betalingsopgave ved hver indtjekning af rengøringshjælp", + "housekeepingPaymentTasksHint": "Når det er aktiveret, opretter hver indtjekning en opgave til betaling af medarbejderen. Når opgaven fuldføres, markeres besøgets betaling som betalt.", + "housekeepingPaymentTasksSaved": "Indstilling for rengøringsbetaling gemt.", + "breadcrumbLabel": "Sti", + "emptyStateAddFirst": "Tilføj din første konto", + "emptyStateNoAccounts": "Ingen konti forbundet endnu", + "helpTooltipCalDAV": "CalDAV gør det muligt at synkronisere kalendere med iCloud, Nextcloud og andre CalDAV-servere.", + "helpTooltipCardDAV": "CardDAV gør det muligt at synkronisere kontakter med iCloud, Nextcloud og andre CardDAV-servere.", + "navigationLabel": "Navigering i indstillinger", + "sectionAdmin": "Administration", + "sectionCloudServices": "Cloudtjenester", + "sectionModulesNav": "Moduler", + "sectionOpenStandards": "CalDAV & CardDAV", + "sectionPersonal": "Personligt", + "sectionSync": "Synkronisering", + "statusError": "Fejl", + "statusNeverSynced": "Aldrig synkroniseret", + "statusSynced": "Synkroniseret", + "statusSyncing": "Synkroniserer…", + "syncedAgo": "for {{time}} siden", + "tabSyncCalendar": "Kalender", + "tabSyncContacts": "Kontakter" + }, + "login": { + "tagline": "Familieplanlægning. Sikkert. Privatlivsvenligt. Open source.", + "usernameLabel": "Brugernavn", + "usernamePlaceholder": "brugernavn", + "passwordLabel": "Adgangskode", + "passwordPlaceholder": "••••••••", + "loginButton": "Log ind", + "loggingIn": "Logger ind…", + "tooManyAttempts": "For mange forsøg. Vent venligst et øjeblik.", + "invalidCredentials": "Ugyldige loginoplysninger.", + "version": "v{{version}}" + }, + "install": { + "title": "Installer Oikos", + "subtitle": "Tilføj til hjemmeskærm", + "iosTip1": "Tryk på ", + "iosTip2": " → \"Føj til hjemmeskærm\"", + "installButton": "Installer", + "dismissLabel": "Luk" + }, + "modal": { + "closeLabel": "Luk", + "overlayLabel": "Baggrund for modal dialog", + "unsavedChanges": "Kassér ændringer?", + "discardChanges": "Kassér" + }, + "rrule": { + "freqNone": "Ingen gentagelse", + "freqDaily": "Dagligt", + "freqWeekly": "Ugentligt", + "freqMonthly": "Månedligt", + "dayMo": "Ma", + "dayTu": "Ti", + "dayWe": "On", + "dayTh": "To", + "dayFr": "Fr", + "daySa": "Lø", + "daySu": "Sø", + "labelRepeat": "Gentagelse", + "labelEvery": "Hver", + "labelOnDays": "På disse dage", + "labelUntil": "Slutter den (valgfrit)", + "unitDay": "dag", + "unitDays": "dage", + "unitWeek": "uge", + "unitWeeks": "uger", + "unitMonth": "måned", + "unitMonths": "måneder" + }, + "reminders": { + "sectionTitle": "Påmindelse", + "enableLabel": "Angiv påmindelse", + "dateLabel": "Dato", + "timeLabel": "Tid", + "offsetLabel": "Mind mig om", + "offsetNone": "Ingen", + "offset15min": "15 minutter før", + "offset1hour": "1 time før", + "offset1day": "1 dag før", + "offsetAtTime": "Ved tidspunktet for begivenheden", + "toastTitle": "Påmindelse", + "dismiss": "Afvis", + "notificationPermission": "Browsernotifikationer", + "notificationEnable": "Aktivér notifikationer", + "notificationEnabled": "Notifikationer aktive", + "notificationDenied": "Notifikationer blokeret", + "notificationHint": "Modtag notifikationer, mens appen er åben.", + "pendingBadgeTitle": "{{count}} påmindelse forfalder", + "pendingBadgeTitlePlural": "{{count}} påmindelser forfalder", + "offset2days": "2 dage før", + "offset1week": "1 uge før", + "offset2weeks": "2 uger før", + "offsetCustom": "Tilpasset...", + "customAmountLabel": "Antal", + "customUnitLabel": "Enhed", + "customMinutes": "Minutter", + "customHours": "Timer", + "customDays": "Dage", + "customWeeks": "Uger" + }, + "birthdays": { + "title": "Fødselsdage", + "addButton": "Tilføj fødselsdag", + "searchPlaceholder": "Søg fødselsdage…", + "upcomingTitle": "Kommende fødselsdage", + "upcomingHint": "De næste personer, der skal fejres, allerede synkroniseret til kalenderen.", + "peopleTitle": "Personer", + "peopleHint": "Søg, gennemgå og redigér alle gemte fødselsdage.", + "emptyTitle": "Ingen fødselsdage endnu", + "emptyDescription": "Tilføj en fødselsdag for at holde den synlig i kalenderen og i påmindelser.", + "newTitle": "Ny fødselsdag", + "editTitle": "Rediger fødselsdag", + "nameLabel": "Navn", + "birthDateLabel": "Fødselsdato", + "photoLabel": "Profilbillede", + "photoOptional": "Valgfrit: du kan gemme uden profilbillede.", + "removePhoto": "Fjern billede", + "notesLabel": "Noter", + "notesPlaceholder": "Gaveidéer, yndlingskage, familienoter…", + "calendarHint": "Hver fødselsdag tilføjes automatisk til kalenderen og påmindelsessystemet.", + "requiredFields": "Navn og fødselsdato er påkrævet.", + "createdToast": "Fødselsdag gemt.", + "updatedToast": "Fødselsdag opdateret.", + "deletedToast": "Fødselsdag slettet.", + "deleteConfirm": "Slet fødselsdagen for \"{{name}}\"?", + "ageNoteToday": "Fylder {{age}} i dag.", + "ageNoteTomorrow": "Fylder {{age}} i morgen.", + "ageNoteDays": "Fylder {{age}} om {{days}} dage." + }, + "recipes": { + "title": "Opskrifter", + "addRecipe": "Tilføj opskrift", + "editRecipe": "Rediger opskrift", + "emptyTitle": "Ingen opskrifter endnu", + "emptyDescription": "Gem dine yndlingsopskrifter og genbrug dem i madplanen.", + "titleLabel": "Titel *", + "titlePlaceholder": "fx Pasta Carbonara", + "notesLabel": "Noter", + "notesPlaceholder": "Valgfrit...", + "urlLabel": "Opskriftslink", + "urlPlaceholder": "https://...", + "ingredientsLabel": "Ingredienser", + "addToMeals": "Tilføj til madplan", + "openLink": "Åbn opskriftslink", + "deleteConfirm": "Slet opskriften \"{{title}}\"?", + "created": "Opskrift gemt.", + "updated": "Opskrift opdateret.", + "deleted": "Opskrift slettet.", + "titleRequired": "Titel er påkrævet", + "duplicate": "Duplikér", + "duplicated": "Opskrift duplikeret.", + "copySuffix": "kopi", + "emptyAction": "Opret opskrift" + }, + "search": { + "title": "Søg", + "open": "Åbn søgning", + "placeholder": "Søg…", + "noResults": "Ingen resultater fundet." + }, + "onboarding": { + "step1Title": "Velkommen til {{name}}", + "step1Body": "Din personlige familieplanlægger. Opgaver, kalender, indkøb og mere – alt samlet ét sted.", + "step2Title": "Navigation & moduler", + "step2Body": "Nederst kan du gå direkte til Dashboard og Kalender. Knappen ··· åbner flere moduler som Køkken, Noter og Kontakter.", + "step3Title": "Kom hurtigt i gang", + "step3Body": "Brug + FAB-knappen til at oprette nye poster overalt. Stryg listeelementer til venstre eller højre for hurtige handlinger.", + "next": "Næste", + "done": "Kom i gang", + "skip": "Spring over" + }, + "offline": { + "banner": "Offline – genopretter forbindelse…" + }, + "shortcuts": { + "search": "Åbn søgning", + "new": "Opret ny post", + "help": "Tastaturgenveje", + "goDash": "Dashboard", + "goTasks": "Opgaver", + "goCal": "Kalender", + "goShop": "Indkøbsliste", + "goNotes": "Noter", + "goKitchen": "Køkken" + }, + "emptyHint": { + "tasks": "Tryk på + for at oprette din første opgave. Stryg et kort til venstre for at slette.", + "calendar": "Forbind Google Kalender under Indstillinger → Integrationer for automatisk synkronisering.", + "shopping": "Tilføj varer og stryg for at sætte kryds eller slette.", + "notes": "Tryk på + for en ny note. Noter er søgbare i fuld tekst.", + "contacts": "Tilføj vigtige kontakter — læge, skole, nødstilfælde — for hurtig adgang.", + "budget": "Opret kategorier og følg indtægter og udgifter.", + "meals": "Planlæg ugens måltider og link opskrifter.", + "birthdays": "Tilføj fødselsdage — du får en påmindelse i god tid.", + "recipes": "Opret opskrifter og link dem til din madplan." + }, + "documents": { + "title": "Dokumenter", + "addButton": "Tilføj dokument", + "searchPlaceholder": "Søg dokumenter...", + "gridView": "Gittervisning", + "listView": "Listevisning", + "viewToggle": "Dokumentvisning", + "allCategories": "Alle kategorier", + "emptyTitle": "Ingen dokumenter endnu", + "emptyDescription": "Upload familiedokumenter og styr, hvem der kan se hver fil.", + "newTitle": "Nyt dokument", + "editTitle": "Dokumentindstillinger", + "nameLabel": "Navn", + "descriptionLabel": "Beskrivelse", + "categoryLabel": "Kategori", + "fileLabel": "Fil", + "fileHint": "PDF, billeder, tekst- og Office-filer op til 5 MB.", + "visibilityLabel": "Synlighed", + "statusLabel": "Status", + "allowedMembersLabel": "Tilladte medlemmer", + "uploadAction": "Upload", + "downloadAction": "Download", + "editAction": "Indstillinger", + "archiveAction": "Arkivér", + "restoreAction": "Gendan", + "savedToast": "Dokument gemt.", + "uploadedToast": "Dokument uploadet.", + "archivedToast": "Dokument arkiveret.", + "restoredToast": "Dokument gendannet.", + "deletedToast": "Dokument slettet.", + "deleteConfirm": "Slet dokumentet \"{{name}}\"?", + "fileRequired": "Vælg en fil at uploade.", + "fileTooLarge": "Filen må højst være 5 MB.", + "fileReadError": "Filen kunne ikke læses.", + "statusActive": "Aktiv", + "statusArchived": "Arkiveret", + "visibility": { + "family": "Hele familien", + "restricted": "Udvalgte medlemmer", + "private": "Kun mig" + }, + "category": { + "medical": "Medicinsk", + "school": "Skole", + "identity": "Identitet", + "insurance": "Forsikring", + "finance": "Økonomi", + "home": "Hjem", + "vehicle": "Køretøj", + "legal": "Juridisk", + "travel": "Rejse", + "pets": "Kæledyr", + "warranty": "Garanti", + "taxes": "Skat", + "work": "Arbejde", + "other": "Andet" + }, + "dropzoneTitle": "Slip filen her eller klik for at vælge", + "dropzoneHint": "Træk en fil ind i dette område, eller brug filvælgeren.", + "selectedFileLabel": "Valgt: {{name}}", + "addFolderButton": "Tilføj mappe", + "allFolders": "Alle mapper", + "folderLabel": "Mappe", + "noFolder": "Ingen mappe", + "newFolderTitle": "Ny mappe", + "folderNameLabel": "Mappenavn", + "createFolderAction": "Opret mappe", + "folderCreatedToast": "Mappe oprettet.", + "housekeepingFolder": "Rengøring", + "calendarItemsFolder": "Kalenderposter", + "folderBrowserTitle": "Gennemse mapper" + }, + "housekeeping": { + "title": "Rengøringsområde", + "bottomNav": "Navigation for rengøring", + "home": "Hjem", + "tasks": "Opgaver", + "report": "Rapport", + "notCheckedIn": "Ikke tjekket ind", + "checkedInAt": "Tjekket ind kl.", + "monthTotal": "Denne måned · {{count}} sessioner", + "dailyRate": "Dagspris", + "extras": "Ekstra", + "checkIn": "Tjek ind", + "checkOut": "Tjek ud", + "quickSupply": "Manglende produkt", + "supplyName": "Produktnavn", + "supplyPlaceholder": "Hvad mangler?", + "checkedInToast": "Indtjekning registreret.", + "checkedOutToast": "Udtjekning registreret.", + "supplyAddedToast": "Tilføjet til indkøbslisten.", + "overdue": "Forsinket", + "dueToday": "Forfalder i dag", + "ok": "OK", + "noTasks": "Ingen rengøringsopgaver endnu.", + "everyDays": "Hver {{days}}. dag", + "completeTask": "Fuldfør {{name}}", + "taskDoneToast": "Opgave fuldført.", + "reportTitle": "Rapportér et problem", + "problemDescription": "Problembeskrivelse", + "problemPlaceholder": "Eksempel: sprunget pære", + "addPhoto": "Tilføj foto", + "sendReport": "Send rapport", + "reportSentToast": "Problem rapporteret.", + "recentReports": "Seneste rapporter", + "addTask": "Tilføj opgave", + "taskName": "Opgave", + "taskNamePlaceholder": "Eksempel: Rengør badeværelser", + "taskArea": "Område", + "taskAreaPlaceholder": "Eksempel: Badeværelse", + "taskFrequency": "Frekvens", + "createTask": "Opret opgave", + "taskCreatedToast": "Rengøringsopgave oprettet.", + "dashboard": "Dashboard", + "reports": "Rapporter", + "visitsThisMonth": "Besøg denne måned", + "lastVisit": "Seneste besøg", + "pendingChores": "Afventende pligter", + "finishedChores": "Færdige pligter", + "payments": "Betalinger", + "pendingPayments": "Afventende betalinger", + "monthlyPayments": "Månedlige betalinger", + "noPaymentData": "Ingen betalingsdata endnu.", + "noVisits": "Ingen besøg endnu", + "noWorkerTitle": "Ingen rengøringsprofil", + "noWorkerHint": "Opret medarbejderprofilen for at definere kontaktoplysninger, takst og betalingsplan.", + "taskTemplates": "Foreslåede pligter", + "addCustomTask": "Tilføj brugerdefineret pligt", + "noReports": "Ingen rapporter endnu.", + "profileTitle": "Profil for rengøringshjælp", + "profilePicture": "Profilbillede for rengøringshjælp", + "workerName": "Navn", + "workerUsername": "Brugernavn", + "workerPhone": "Telefon", + "workerEmail": "E-mail", + "workerBirthDate": "Fødselsdag", + "paymentSchedule": "Betalingsplan", + "scheduleDaily": "Ved hvert besøg", + "scheduleTwiceMonthly": "To gange om måneden", + "scheduleMonthly": "Månedligt", + "profileColor": "Profilfarve", + "workerNotes": "Noter", + "workerSavedToast": "Profil for rengøringshjælp gemt.", + "staff": "Personale", + "staffTitle": "Rengøringspersonale", + "addWorker": "Tilføj rengøringshjælp", + "editWorker": "Rediger rengøringshjælp", + "noWorkers": "Ingen rengøringshjælp registreret endnu.", + "moreWorkers": "+{{count}} flere", + "checkInDisabled": "Tilføj en rengøringshjælp før indtjekning.", + "calendarColor": "Kalenderfarve", + "visitRecordedAt": "Besøg registreret kl.", + "checkedInToday": "Registreret i dag", + "visitReports": "Rapporter fra personalebesøg", + "noVisitReports": "Ingen personalebesøg registreret denne måned.", + "openVisitReport": "Åbn besøgsrapport", + "visitReportDetails": "Besøgsrapport", + "paymentPaid": "Betalt", + "paymentPending": "Afventer", + "totalPayment": "Samlet betaling", + "paymentStatus": "Betalingsstatus", + "paymentTask": "Betalingsopgave", + "calendarEvent": "Kalenderbegivenhed", + "notAvailable": "Ikke tilgængelig", + "calendarVisitTitle": "Rengøring: {{name}}", + "paymentTaskTitle": "Betal {{name}} for rengøring", + "paymentTaskDescription": "Rengøringsbesøg den {{date}}. Skyldigt beløb: {{amount}}.", + "staffLogTitle": "{{name}} besøg", + "staffLogHint": "Rediger besøgsdatoer, beløb og linkede poster.", + "filterMonth": "Måned", + "editVisit": "Rediger besøg", + "deleteVisit": "Slet besøg", + "deleteVisitConfirm": "Slet dette besøg? Den linkede kalenderbegivenhed og betalingsopgave slettes også.", + "visitDeletedToast": "Besøg slettet.", + "visitSavedToast": "Besøg opdateret.", + "visitDate": "Besøgsdato", + "markPaid": "Markér som betalt", + "visitPaidToast": "Betaling markeret som betalt.", + "receiptUploadTitle": "Upload betalingskvittering", + "receiptUploadHint": "Vedhæft en betalingskvittering. Den vises i Dokumenter.", + "receiptDocumentName": "Kvittering - {{name}} - {{date}}", + "receiptDocumentDescription": "Betalingskvittering for {{name}}s rengøringsbesøg den {{date}}.", + "taskTemplateData": { + "cleanBathrooms": { + "name": "Rengør badeværelser", + "area": "Badeværelser" + }, + "mopKitchenFloor": { + "name": "Vask køkkengulv", + "area": "Køkken" + }, + "dustLivingRoom": { + "name": "Støv af i stuen", + "area": "Stue" + }, + "changeBedLinens": { + "name": "Skift sengetøj", + "area": "Soveværelser" + }, + "cleanRefrigerator": { + "name": "Rengør køleskab", + "area": "Køkken" + }, + "cleanWindows": { + "name": "Puds vinduer", + "area": "Hele huset" + }, + "deepCleanOven": { + "name": "Hovedrengør ovnen", + "area": "Køkken" + }, + "washOutdoor": { + "name": "Vask altan/terrasse", + "area": "Udendørs" + } + } + }, + "userMultiSelect": { + "moreUsers": "flere", + "nobody": "- Ingen -" + } +} diff --git a/public/sw.js b/public/sw.js index bf4f8cb..6c8dfac 100644 --- a/public/sw.js +++ b/public/sw.js @@ -15,7 +15,7 @@ const SHELL_CACHE = 'oikos-shell-v72'; const PAGES_CACHE = 'oikos-pages-v67'; -const LOCALES_CACHE = 'oikos-locales-v16'; +const LOCALES_CACHE = 'oikos-locales-v17'; const ASSETS_CACHE = 'oikos-assets-v67'; const BYPASS_CACHE = 'oikos-bypass-flag'; const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, LOCALES_CACHE, ASSETS_CACHE]; @@ -64,6 +64,7 @@ const APP_SHELL = [ const APP_LOCALES = [ '/locales/ar.json', + '/locales/da.json', '/locales/de.json', '/locales/el.json', '/locales/en.json',