feat(i18n): add Swedish (sv) translation and Italian as explicit language option (#19)

Swedish translation contributed by @olsson82 (PR #19), integrated with
minor corrections (dayShortSunday: Sun → Sön, amountLabel aligned with
v0.11.2, new v0.11.2 currency keys added).

Italian was already supported server-side but is now explicitly listed
in the locale picker alongside German, English, and Swedish.
This commit is contained in:
Ulas
2026-04-05 12:01:16 +02:00
parent 446b9b1388
commit 3a6ae4a64a
5 changed files with 561 additions and 2 deletions
+6
View File
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.11.3] - 2026-04-05
### Added
- Swedish (Svenska) translation - contributed by @olsson82 (#19)
- Italian (Italiano) is now explicitly listed as a language option in Settings
## [0.11.2] - 2026-04-05
### Added
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "oikos",
"version": "0.11.2",
"version": "0.11.3",
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
"main": "server/index.js",
"type": "module",
+1
View File
@@ -11,6 +11,7 @@ const LOCALE_LABELS = {
de: 'Deutsch',
en: 'English',
it: 'Italiano',
sv: 'Svenska',
};
class OikosLocalePicker extends HTMLElement {
+1 -1
View File
@@ -5,7 +5,7 @@
* Dependencies: none (vanilla JS, Fetch API, Intl API)
*/
const SUPPORTED_LOCALES = ['de', 'en', 'it'];
const SUPPORTED_LOCALES = ['de', 'en', 'it', 'sv'];
const DEFAULT_LOCALE = 'de';
const STORAGE_KEY = 'oikos-locale';
+552
View File
@@ -0,0 +1,552 @@
{
"common": {
"save": "Spara",
"cancel": "Avbryt",
"delete": "Radera",
"edit": "Redigera",
"close": "Stäng",
"create": "Skapa",
"add": "Lägg till",
"back": "Tillbaka",
"next": "Nästa",
"loading": "Laddar…",
"saving": "Sparar…",
"required": "Detta fält är obligatoriskt.",
"error": "Fel",
"allFieldsRequired": "Vänligen fyll i alla fält.",
"today": "I dag",
"tomorrow": "I morgon",
"skipToContent": "Hoppa till innehållet",
"reload": "Ladda om",
"errorOccurred": "Något gick fel.",
"unexpectedError": "Ett oväntat fel inträffade.",
"errorGeneric": "Ett fel uppstod.",
"updateAvailable": "Uppdatering tillgänglig - ladda om sidan för att få den senaste versionen.",
"titleRequired": "Titel krävs",
"nameRequired": "Namn krävs",
"contentRequired": "Innehåll krävs",
"all": "Alla",
"unknownError": "Okänt fel"
},
"nav": {
"dashboard": "Översikt",
"tasks": "Uppgifter",
"calendar": "Kalender",
"meals": "Måltider",
"shopping": "Shopping",
"notes": "Anteckningar",
"contacts": "Kontakter",
"budget": "Budget",
"settings": "Inställningar",
"main": "Huvudnavigering",
"navigation": "Navigering",
"quickActions": "Snabba åtgärder"
},
"dashboard": {
"title": "Översikt",
"greetingMorning": "God morgon, {{name}}",
"greetingDay": "God eftermiddag, {{name}}",
"greetingEvening": "God kväll, {{name}}",
"allDone": "Allt klart",
"noEvents": "Inga händelser",
"noPinnedNotes": "Inga fästa anteckningar",
"todayMeals": "Dagens måltider",
"allLink": "Alla",
"weekLink": "Vecka",
"urgentTasksChip": "{{count}} brådskande uppgift",
"urgentTasksChipPlural": "{{count}} brådskande uppgifter",
"eventsChip": "{{count}} händelse idag",
"eventsChipPlural": "{{count}} evenemang idag",
"todayMealChip": "Idag: {{title}}",
"loadError": "Instrumentpanelen kunde inte laddas helt.",
"weatherRefresh": "Uppdatera vädret",
"weatherRefreshTitle": "Uppdatera",
"weatherFeelsLike": "Känns som {{temp}}° · {{humidity}}% · Vind {{wind}} km/h",
"fabTaskLabel": "Lägg till uppgift",
"fabCalendarLabel": "Lägg till händelse",
"fabShoppingLabel": "Lägg till shopping",
"fabNoteLabel": "Lägg till anteckning",
"fabTask": "Uppgift",
"fabCalendar": "Händelse",
"fabShopping": "Shopping",
"fabNote": "Anteckning",
"overdue": "Försenad",
"dueSoon": "Förfaller idag",
"dueTomorrow": "Förfaller imorgon",
"allDay": "Hela dagen",
"shoppingMore": "+{{count}} till"
},
"tasks": {
"title": "Uppgifter",
"newTask": "Ny uppgift",
"editTask": "Redigera uppgift",
"emptyTitle": "Inga uppgifter - allt klart?",
"emptyDescription": "Skapa nya uppgifter med + knappen.",
"titleLabel": "Titel *",
"titlePlaceholder": "Vad behöver göras?",
"descriptionLabel": "Anteckning",
"descriptionPlaceholder": "Valfria detaljer...",
"priorityLabel": "Prioritet",
"categoryLabel": "Kategori",
"dueDateLabel": "Förfallo datum",
"dueTimeLabel": "Tid",
"assignedLabel": "Tilldelad till",
"assignedNobody": "- Ingen -",
"statusLabel": "Status",
"priorityUrgent": "Brådskande",
"priorityHigh": "Hög",
"priorityMedium": "Medium",
"priorityLow": "Låg",
"priorityNone": "Ingen",
"statusOpen": "Öppna",
"statusInProgress": "Pågår",
"statusDone": "Slutfört",
"categoryHousehold": "Hushåll",
"categorySchool": "Skola",
"categoryShopping": "Shopping",
"categoryRepair": "Reparera",
"categoryHealth": "Hälsa",
"categoryFinance": "Finansiera",
"categoryLeisure": "Fritid",
"categoryMisc": "Diverse",
"overdue": "Försenad",
"overdueDay": "{{count}}d försenad",
"dueToday": "Förfaller idag",
"dueTomorrow": "Förfaller imorgon",
"groupOverdue": "Försenad",
"groupToday": "I dag",
"groupThisWeek": "Denna vecka",
"groupNextWeek": "Nästa vecka",
"groupLater": "Senare",
"groupNoDate": "Inget datum",
"markDone": "Markera {{title}} som klar",
"editButton": "Redigera uppgift",
"swipeOpen": "Öppna igen",
"swipeDone": "Klart",
"swipeEdit": "Redigera",
"subtaskAdd": "+ Lägg till deluppgift",
"subtaskToggle": "Visa deluppgifter",
"subtaskMarkDone": "Markera {{title}} som klar",
"deleteConfirm": "Ta bort uppgift och alla deluppgifter?",
"savedToast": "Uppgiften sparad.",
"createdToast": "Uppgift skapad.",
"deletedToast": "Uppgiften raderad.",
"loadError": "Uppgiften kunde inte laddas.",
"subtaskPrompt": "Deluppgift:",
"kanbanOpen": "Öppna",
"kanbanInProgress": "Pågår",
"kanbanDone": "Slutfört",
"recurring": "Återkommande",
"listView": "Listvy",
"kanbanView": "Kanban-vy"
},
"shopping": {
"title": "Shopping",
"noLists": "Inga listor",
"noListsDescription": "Skapa en lista med + knappen.",
"emptyList": "Listan är tom",
"emptyListDescription": "Lägg till objekt med inmatningsfältet ovan.",
"newListPrompt": "Namn på den nya listan:",
"newListButton": "Skapa ny lista",
"renameListPrompt": "Nytt listnamn:",
"deleteListConfirm": "Ta bort listan \"{{name}}\" och alla objekt?",
"deletedListToast": "Lista raderad.",
"itemsRemovedToast": "{{count}} objekt har tagits bort.",
"clearChecked": "Ta bort markerad ({{count}})",
"itemNamePlaceholder": "Lägg till objekt...",
"itemQtyPlaceholder": "Kvantitet",
"itemNameLabel": "Objektets namn",
"itemQtyLabel": "Kvantitet",
"categoryLabel": "Kategori",
"addItemLabel": "Lägg till objekt",
"renameListLabel": "Byt namn på listan",
"deleteListLabel": "Ta bort lista",
"swipeBack": "Ångra",
"swipeCheck": "Bocka av",
"swipeDelete": "Radera",
"markDoneLabel": "Bocka av {{name}}",
"markUndoneLabel": "Avmarkera {{name}}",
"deleteItemLabel": "Ta bort {{name}}",
"listsLoadError": "Det gick inte att ladda listor.",
"itemsLoadError": "Objekt kunde inte laddas.",
"catFruitVeg": "Frukt & Grönt",
"catBakery": "Bageri",
"catDairy": "Mejeri",
"catMeatFish": "Kött & Fisk",
"catFrozen": "Frysvaror",
"catDrinks": "Drycker",
"catHousehold": "Hushåll",
"catDrugstore": "Apotek",
"catMisc": "Diverse"
},
"meals": {
"title": "Måltidsplan",
"noMealPlanned": "Ingen måltid planerad",
"addMeal": "Lägg till {{type}}",
"editMeal": "Redigera måltid",
"addMealTitle": "Lägg till måltid",
"deleteMeal": "Ta bort måltid",
"transferToShoppingList": "Lägg till ingredienser till inköpslistan",
"today": "I dag",
"prevWeek": "Föregående vecka",
"nextWeek": "Nästa vecka",
"loadError": "Det gick inte att läsa in måltidsplanen.",
"typeBreakfast": "Frukost",
"typeLunch": "Lunch",
"typeDinner": "Middag",
"typeSnack": "Mellanmål",
"dayMo": "Mån",
"dayDi": "Tis",
"dayMi": "Ons",
"dayDo": "Tor",
"dayFr": "Fre",
"daySa": "Lö",
"daySo": "Sön",
"dateLabel": "Datum",
"mealTypeLabel": "Måltid",
"titleLabel": "Titel *",
"titlePlaceholder": "till exempel Spaghetti Bolognese",
"notesLabel": "Anteckningar",
"notesPlaceholder": "Frivillig…",
"ingredientsLabel": "Ingredienser",
"addIngredient": "Tillsätt ingrediens",
"ingredientNamePlaceholder": "Ingrediens",
"ingredientQtyPlaceholder": "Kvantitet",
"removeIngredient": "Ta bort ingrediensen",
"transferLabel": "Överför ingredienserna till inköpslistan",
"transferNow": "Överför nu",
"noShoppingLists": "Inga inköpslistor tillgängliga",
"transferSuccess": "{{count}} ingrediens har överförts",
"transferSuccessPlural": "{{count}} ingredienser har överförts",
"transferAlreadyDone": "Alla ingredienser är redan överförda",
"ingredientCount": "{{count}} ingrediens",
"ingredientCountPlural": "{{count}} ingredienser",
"titleRequired": "Titel krävs",
"loadingIndicator": "Laddar…"
},
"calendar": {
"title": "Kalender",
"newEvent": "Ny händelse",
"editEvent": "Redigera händelse",
"addEvent": "Lägg till händelse",
"deleteEvent": "Ta bort händelse",
"noEvents": "Inga händelser under den valda perioden.",
"today": "I dag",
"back": "Tillbaka",
"forward": "Fram",
"viewMonth": "Månad",
"viewWeek": "Vecka",
"viewDay": "Dag",
"viewAgenda": "Agenda",
"allDay": "Hela dagen",
"allDayShort": "hela dagen",
"moreEvents": "+{{count}} fler",
"weekNumberLabel": "V{{week}} · {{month}} {{year}}",
"agendaFrom": "Från {{date}}",
"titleLabel": "Titel *",
"titlePlaceholder": "till exempel Tandläkare",
"allDayToggle": "Hela dagen",
"startDateLabel": "Startdatum",
"startTimeLabel": "Starttid",
"endDateLabel": "Slutdatum",
"endTimeLabel": "Sluttid",
"fromLabel": "Från",
"toLabel": "Till",
"locationLabel": "Plats",
"locationPlaceholder": "Frivillig",
"assignedLabel": "Tilldelad till",
"assignedNobody": "- Ingen -",
"colorLabel": "Färg",
"descriptionLabel": "Beskrivning",
"descriptionPlaceholder": "Frivillig…",
"popupEdit": "Redigera",
"deleteConfirm": "Vill du verkligen ta bort \"{{title}}\"?",
"createdToast": "Händelse skapad",
"savedToast": "Händelsen har sparats",
"deletedToast": "Händelse raderad",
"loadError": "Händelser kunde inte laddas.",
"saveError": "Det gick inte att spara",
"deleteError": "Fel vid borttagning",
"titleRequired": "Titel krävs",
"monthJanuary": "Januari",
"monthFebruary": "Februari",
"monthMarch": "Mars",
"monthApril": "April",
"monthMay": "Maj",
"monthJune": "Juni",
"monthJuly": "Juli",
"monthAugust": "Augusti",
"monthSeptember": "September",
"monthOctober": "Oktober",
"monthNovember": "November",
"monthDecember": "December",
"dayShortSunday": "Sön",
"dayShortMonday": "Mån",
"dayShortTuesday": "Tis",
"dayShortWednesday": "Ons",
"dayShortThursday": "Tor",
"dayShortFriday": "Fre",
"dayShortSaturday": "Lör",
"dayLongSunday": "Söndag",
"dayLongMonday": "Måndag",
"dayLongTuesday": "Tisdag",
"dayLongWednesday": "Onsdag",
"dayLongThursday": "Torsdag",
"dayLongFriday": "Fredag",
"dayLongSaturday": "Lördag",
"timeSuffix": "",
"colorLabel": "Färg {{color}}"
},
"notes": {
"title": "Anteckningar",
"newNote": "Ny anteckning",
"editNote": "Redigera anteckning",
"addNoteLabel": "Ny anteckning",
"searchPlaceholder": "Sök anteckningar...",
"emptyTitle": "Inga anteckningar ännu",
"emptyDescription": "Skapa en ny anteckning med + knappen.",
"noResultsTitle": "Inga resultat",
"noResultsDescription": "Ingen anteckning innehåller \"{{query}}\".",
"titleLabel": "Titel (valfritt)",
"titlePlaceholder": "Ingen titel",
"contentLabel": "Innehåll",
"contentMarkdownHint": "(Markdown-formatering stöds)",
"contentPlaceholder": "Ange anteckning...",
"colorLabel": "Färg",
"pinnedLabel": "Fäst (visas på instrumentpanelen)",
"pinAction": "Fäst",
"unpinAction": "Lossa",
"deleteLabel": "Ta bort anteckning",
"deleteConfirm": "Vill du verkligen ta bort den här anteckningen?",
"createdToast": "Anteckning skapad",
"savedToast": "Anteckningen sparad",
"deletedToast": "Anteckningen raderad",
"loadError": "Det gick inte att ladda anteckningar.",
"formatBold": "Fet (Ctrl+B)",
"formatItalic": "Kursiv (Ctrl+I)",
"formatUnderline": "Understrykning (Ctrl+U)",
"formatStrikethrough": "Genomstruken",
"formatHeading": "Rubrik",
"formatList": "Kullista",
"formatOrderedList": "Numrerad lista",
"formatChecklist": "Checklista",
"formatLink": "Länk",
"formatCode": "Kod",
"formatQuote": "Citationstecken",
"formatDivider": "Delare"
},
"contacts": {
"title": "Kontakter",
"newContact": "Ny kontakt",
"editContact": "Redigera kontakt",
"addButton": "Ny",
"newContactLabel": "Ny kontakt",
"searchPlaceholder": "Sök på namn, telefon eller e-post...",
"importButton": "Importera",
"importLabel": "Importera kontakt från vCard",
"importTooltip": "Importera vCard",
"emptyTitle": "Inga kontakter än",
"emptyDescription": "Lägg till nya kontakter med + knappen.",
"filterAll": "Alla",
"nameLabel": "Namn *",
"namePlaceholder": "Fullständigt namn",
"categoryLabel": "Kategori",
"phoneLabel": "Telefon",
"phonePlaceholder": "+46 …",
"emailLabel": "E-post",
"emailPlaceholder": "namn@exempel.se",
"addressLabel": "Adress",
"addressPlaceholder": "Gatunamn, Postnummer Stad",
"notesLabel": "Anteckningar",
"notesPlaceholder": "Frivillig…",
"callLabel": "Samtal",
"emailActionLabel": "E-post",
"mapsLabel": "Öppna i Kartor",
"exportLabel": "Exportera som vCard",
"exportTooltip": "Exportera vCard",
"deleteLabel": "Ta bort kontakt",
"deleteConfirm": "Vill du verkligen ta bort den här kontakten?",
"deletePersonConfirm": "Vill du verkligen ta bort \"{{name}}\"?",
"savedToast": "Kontakten har sparats",
"updatedToast": "Kontakten uppdaterad",
"deletedToast": "Kontakt raderad",
"importedToast": "{{name}} har importerats.",
"importError": "Import misslyckades: {{error}}",
"vcardNoName": "vCard innehåller inget namn.",
"catDoctor": "Läkare",
"catSchool": "Skola/Barnomsorg",
"catAuthority": "Myndighet",
"catInsurance": "Försäkring",
"catCraftsman": "Handlare",
"catEmergency": "Nödsituation",
"catMisc": "Diverse",
"categoryDoctor": "Läkare",
"categorySchool": "Skola/daghem",
"categoryAuthority": "Myndighet",
"categoryInsurance": "Försäkring",
"categoryCraftsman": "Handlare",
"categoryEmergency": "Nödsituation",
"categoryOther": "Andra"
},
"budget": {
"title": "Budget",
"newEntry": "Nytt inlägg",
"editEntry": "Redigera inlägg",
"addEntryLabel": "Lägg till inlägg",
"newEntryFabLabel": "Nytt inlägg",
"currentMonth": "Nuvarande",
"prevMonth": "Föregående månad",
"nextMonth": "Nästa månad",
"income": "Inkomst",
"expenses": "Utgifter",
"balance": "Balans",
"byCategory": "Efter kategori",
"transactions": "Transaktioner",
"emptyTitle": "Inga inlägg denna månad",
"emptyDescription": "Lägg till budgetposter med + knappen.",
"csvExport": "CSV",
"typeExpense": "Utgift",
"typeIncome": "Inkomst",
"titleLabel": "Titel *",
"titlePlaceholder": "till exempel Stormarknad",
"amountLabel": "Belopp *",
"amountPlaceholder": "0,00",
"categoryLabel": "Kategori",
"dateLabel": "Datum *",
"recurringLabel": "Återkommande",
"deleteLabel": "Ta bort post",
"deleteConfirm": "Vill du verkligen ta bort den här posten?",
"deletePersonConfirm": "Vill du verkligen ta bort \"{{title}}\"?",
"addedToast": "Post tillagd",
"savedToast": "Posten sparad",
"deletedToast": "Posten raderad",
"loadError": "Budget kunde inte laddas.",
"trendNeutral": "- samma som {{month}}",
"validAmountRequired": "Ange ett giltigt belopp",
"dateRequired": "Datum krävs",
"catFood": "Specerier",
"catRent": "Hyra",
"catInsurance": "Försäkring",
"catMobility": "Transport",
"catLeisure": "Fritid",
"catClothing": "Kläder",
"catHealth": "Hälsa",
"catEducation": "Utbildning",
"catMisc": "Diverse",
"loadingIndicator": "Laddar…"
},
"settings": {
"title": "Inställningar",
"sectionDesign": "Utseende",
"sectionAccount": "Mitt konto",
"sectionCalendarSync": "Kalendersynkronisering",
"sectionFamily": "Familjemedlemmar",
"cardAppearance": "Visa",
"themeSystem": "System",
"themeSysLabel": "Använd systeminställning",
"themeLight": "Ljus",
"themeLightLabel": "Ljusläge",
"themeDark": "Mörk",
"themeDarkLabel": "Mörkt läge",
"changePassword": "Byt lösenord",
"currentPasswordLabel": "Aktuellt lösenord",
"newPasswordLabel": "Nytt lösenord",
"confirmPasswordLabel": "Bekräfta nytt lösenord",
"savePassword": "Spara lösenord",
"passwordMismatch": "Lösenord stämmer inte överens.",
"passwordSavedToast": "Lösenordet har ändrats.",
"googleCalendar": "Google Kalender",
"appleCalendar": "Apple Calendar (iCloud)",
"syncNow": "Synkronisera nu",
"disconnect": "Koppla från",
"connectGoogle": "Anslut till Google",
"connected": "Ansluten",
"connectedLastSync": "Ansluten · Senast: {{date}}",
"notConnected": "Ej ansluten",
"notConfigured": "Ej konfigurerad (saknar .env-variabler)",
"configured": "Konfigurerad (via .env)",
"configuredLastSync": "Konfigurerad (via .env) · Senast: {{date}}",
"syncSuccess": "{{provider}} synkroniserad.",
"disconnectedToast": "{{provider}} frånkopplad.",
"googleOnlyAdmin": "Endast administratörer kan ansluta Google Kalender.",
"appleOnlyAdmin": "Endast admin kan ansluta Apple Calendar.",
"caldavUrlLabel": "CalDAV Server URL",
"caldavUrlPlaceholder": "https://caldav.icloud.com",
"appleIdLabel": "Apple ID (e-post)",
"applePasswordLabel": "Appspecifikt lösenord",
"applePasswordHint": "Skapa lösenord på <strong>appleid.apple.com → Säkerhet</strong>.",
"appleConnectBtn": "Anslut & testa",
"appleConnecting": "Ansluter...",
"appleConnectedToast": "Apple Calendar ansluten.",
"syncSuccessGoogle": "Kalendersynkronisering med Google ansluten.",
"syncSuccessApple": "Kalendersynkronisering med Apple ansluten.",
"syncErrorGoogle": "Anslutningen till Google misslyckades. Försök igen.",
"syncErrorApple": "Anslutningen till Apple misslyckades. Försök igen.",
"addMember": "+ Lägg till medlem",
"newMemberTitle": "Ny familjemedlem",
"usernameLabel": "Användarnamn",
"displayNameLabel": "Visningsnamn",
"memberPasswordLabel": "Lösenord",
"colorLabel": "Färg",
"roleLabel": "Roll",
"roleMember": "Medlem",
"roleAdmin": "Admin",
"createMember": "Skapa",
"cancelAddMember": "Avbryt",
"memberAddedToast": "{{name}} har lagts till.",
"deleteMemberConfirm": "Vill du verkligen ta bort {{name}}?",
"memberDeletedToast": "{{name}} raderade.",
"deleteMemberLabel": "Radera",
"logout": "Logga ut",
"synchronizing": "Synkroniseras...",
"googleDisconnectConfirm": "Koppla bort Google Kalender?",
"appleDisconnectConfirm": "Koppla bort Apple Calendar?",
"localeSystem": "System",
"localeLabel": "Språk",
"languageTitle": "Språk",
"sectionMeals": "Måltidsplan",
"mealTypesLabel": "Synliga måltider",
"mealTypesHint": "Endast utvalda måltidstyper visas i måltidsplaneraren.",
"mealTypesSaved": "Inställningar för måltidsplan har sparats.",
"mealTypesMinOne": "Minst en måltidstyp måste vara aktiv.",
"sectionBudget": "Budget",
"currencyLabel": "Valuta",
"currencyHint": "Ställer in valutan som används i hela budgetavsnittet.",
"currencySaved": "Valuta sparad."
},
"login": {
"tagline": "Familjeplanering. Säker. Sekretessvänlig. Öppen källkod.",
"usernameLabel": "Användarnamn",
"usernamePlaceholder": "användarnamn",
"passwordLabel": "Lösenord",
"passwordPlaceholder": "••••••••",
"loginButton": "Logga in",
"loggingIn": "Loggar in...",
"tooManyAttempts": "För många försök. Vänta ett ögonblick.",
"invalidCredentials": "Ogiltiga användaruppgifter."
},
"install": {
"title": "Installera Oikos",
"subtitle": "Lägg till på startskärmen",
"iosTip1": "Tryck",
"iosTip2": "→ \"Lägg till på startskärmen\"",
"installButton": "Installera",
"dismissLabel": "Stäng"
},
"modal": {
"closeLabel": "Stäng"
}
}