diff --git a/CHANGELOG.md b/CHANGELOG.md index d468d1f..8736db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.20.9] - 2026-04-18 + +### Added +- Ukrainian (uk) translation (closes #52) +- Ukrainian Hryvnia (UAH) currency option in budget settings +- Shopping list category names are now translated in the settings panel; rename and delete dialogs also use the translated name + +### Fixed +- Server-side `VALID_CURRENCIES` now matches the frontend list — `AED`, `BRL`, `INR`, and `SAR` were accepted by the UI but rejected by the API + ## [0.20.8] - 2026-04-18 ### Changed diff --git a/package.json b/package.json index 6f22b28..d007acc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.20.8", + "version": "0.20.9", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/public/components/oikos-locale-picker.js b/public/components/oikos-locale-picker.js index 1a6ac20..ddf7ae9 100644 --- a/public/components/oikos-locale-picker.js +++ b/public/components/oikos-locale-picker.js @@ -22,6 +22,7 @@ const LOCALE_LABELS = { ar: 'العربية', hi: 'हिन्दी', pt: 'Português', + uk: 'Українська', }; class OikosLocalePicker extends HTMLElement { diff --git a/public/i18n.js b/public/i18n.js index 0f173a5..c14da31 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']; +const SUPPORTED_LOCALES = ['de', 'en', 'es', 'fr', 'it', 'sv', 'el', 'ru', 'tr', 'zh', 'ja', 'ar', 'hi', 'pt', 'uk']; const DEFAULT_LOCALE = 'de'; const STORAGE_KEY = 'oikos-locale'; diff --git a/public/locales/uk.json b/public/locales/uk.json new file mode 100644 index 0000000..ece443b --- /dev/null +++ b/public/locales/uk.json @@ -0,0 +1,623 @@ +{ + "common": { + "save": "Зберегти", + "cancel": "Скасувати", + "delete": "Видалити", + "edit": "Редагувати", + "close": "Закрити", + "create": "Створити", + "add": "Додати", + "back": "Назад", + "next": "Далі", + "loading": "Завантаження…", + "saving": "Збереження…", + "required": "Це поле є обов'язковим.", + "error": "Помилка", + "allFieldsRequired": "Будь ласка, заповніть усі поля.", + "today": "Сьогодні", + "tomorrow": "Завтра", + "skipToContent": "Перейти до вмісту", + "reload": "Оновити", + "errorOccurred": "Щось пішло не так.", + "unexpectedError": "Сталася непередбачена помилка.", + "errorGeneric": "Сталася помилка.", + "updateAvailable": "Доступне оновлення — перезавантажте сторінку, щоб отримати останню версію.", + "titleRequired": "Заголовок є обов'язковим", + "nameRequired": "Ім'я є обов'язковим", + "contentRequired": "Вміст є обов'язковим", + "all": "Усі", + "unknownError": "Невідома помилка", + "confirm": "Підтвердити", + "undo": "Скасувати" + }, + "nav": { + "dashboard": "Огляд", + "tasks": "Завдання", + "calendar": "Календар", + "meals": "Харчування", + "shopping": "Покупки", + "notes": "Нотатки", + "contacts": "Контакти", + "budget": "Бюджет", + "settings": "Налаштування", + "main": "Головна навігація", + "navigation": "Навігація", + "quickActions": "Швидкі дії" + }, + "dashboard": { + "title": "Огляд", + "greetingMorning": "Доброго ранку, {{name}}", + "greetingDay": "Доброго дня, {{name}}", + "greetingEvening": "Доброго вечора, {{name}}", + "allDone": "Усе зроблено", + "noEvents": "Немає подій", + "noPinnedNotes": "Немає закріплених нотаток", + "todayMeals": "Страви на сьогодні", + "allLink": "Усі", + "weekLink": "Тиждень", + "urgentTasksChip": "{{count}} термінове завдання", + "urgentTasksChipPlural": "{{count}} термінових завдань", + "eventsChip": "{{count}} подія сьогодні", + "eventsChipPlural": "{{count}} подій сьогодні", + "todayMealChip": "Сьогодні: {{title}}", + "loadError": "Огляд не вдалося повністю завантажити.", + "weatherRefresh": "Оновити погоду", + "weatherRefreshTitle": "Оновити", + "weatherUpdated": "Погоду оновлено", + "weatherFeelsLike": "Відчувається як {{temp}}° · {{humidity}}% · Вітер {{wind}} км/год", + "fabTaskLabel": "Додати завдання", + "fabCalendarLabel": "Додати подію", + "fabShoppingLabel": "Додати покупку", + "fabNoteLabel": "Додати нотатку", + "fabTask": "Завдання", + "fabCalendar": "Подія", + "fabShopping": "Покупка", + "fabNote": "Нотатка", + "overdue": "Прострочено", + "dueSoon": "Сьогодні", + "dueTomorrow": "Завтра", + "allDay": "Весь день", + "shoppingMore": "+{{count}} ще", + "weather": "Погода", + "customize": "Налаштувати", + "customizeTitle": "Налаштувати віджети", + "customizeReset": "Скинути", + "customizeSaved": "Огляд збережено", + "customizeMoveUp": "Перемістити вгору", + "customizeMoveDown": "Перемістити вниз" + }, + "tasks": { + "title": "Завдання", + "newTask": "Нове завдання", + "editTask": "Редагувати завдання", + "emptyTitle": "Завдань немає — все зроблено?", + "emptyDescription": "Створіть нові завдання кнопкою +.", + "titleLabel": "Заголовок *", + "titlePlaceholder": "Що потрібно зробити?", + "descriptionLabel": "Нотатка", + "descriptionPlaceholder": "Необов'язкові деталі…", + "priorityLabel": "Пріоритет", + "categoryLabel": "Категорія", + "dueDateLabel": "Термін виконання", + "dueTimeLabel": "Час", + "assignedLabel": "Призначено", + "assignedNobody": "- Нікому -", + "statusLabel": "Статус", + "priorityUrgent": "Терміново", + "priorityHigh": "Високий", + "priorityMedium": "Середній", + "priorityLow": "Низький", + "priorityNone": "Без пріоритету", + "statusOpen": "Відкрито", + "statusInProgress": "В процесі", + "statusDone": "Виконано", + "categoryHousehold": "Побут", + "categorySchool": "Навчання", + "categoryShopping": "Покупки", + "categoryRepair": "Ремонт", + "categoryHealth": "Здоров'я", + "categoryFinance": "Фінанси", + "categoryLeisure": "Дозвілля", + "categoryMisc": "Різне", + "overdue": "Прострочено", + "overdueDay": "Прострочено на {{count}} д.", + "dueToday": "Сьогодні", + "dueTomorrow": "Завтра", + "groupOverdue": "Прострочено", + "groupToday": "Сьогодні", + "groupThisWeek": "Цього тижня", + "groupNextWeek": "Наступного тижня", + "groupLater": "Пізніше", + "groupNoDate": "Без дати", + "markDone": "Позначити {{title}} як виконане", + "editButton": "Редагувати завдання", + "swipeOpen": "Відкрити знову", + "swipeDone": "Виконано", + "swipeEdit": "Редагувати", + "subtaskAdd": "+ Додати підзавдання", + "subtaskToggle": "Показати підзавдання", + "subtaskMarkDone": "Позначити {{title}} як виконане", + "deleteConfirm": "Видалити завдання та всі підзавдання?", + "savedToast": "Завдання збережено.", + "createdToast": "Завдання створено.", + "deletedToast": "Завдання видалено.", + "loadError": "Не вдалося завантажити завдання.", + "subtaskPrompt": "Підзавдання:", + "kanbanOpen": "Відкрито", + "kanbanInProgress": "В процесі", + "kanbanDone": "Виконано", + "kanbanMoveToInProgress": "Позначити як «в процесі»", + "kanbanMoveToDone": "Позначити як виконане", + "kanbanMoveToOpen": "Відкрити знову", + "recurring": "Повторюване", + "listView": "Список", + "kanbanView": "Канбан" + }, + "shopping": { + "title": "Покупки", + "noLists": "Немає списків", + "noListsDescription": "Створіть список кнопкою +.", + "emptyList": "Список порожній", + "emptyListDescription": "Додайте товари через поле вводу вище.", + "newListPrompt": "Назва нового списку:", + "newListButton": "Створити новий список", + "renameListPrompt": "Нова назва списку:", + "deleteListConfirm": "Видалити список «{{name}}» та всі товари?", + "deletedListToast": "Список видалено.", + "itemDeletedToast": "«{{name}}» видалено.", + "itemsRemovedToast": "{{count}} товарів видалено.", + "clearChecked": "Видалити відмічені ({{count}})", + "itemNamePlaceholder": "Додати товар…", + "itemQtyPlaceholder": "Кількість", + "itemNameLabel": "Назва товару", + "itemQtyLabel": "Кількість", + "categoryLabel": "Категорія", + "addItemLabel": "Додати товар", + "renameListLabel": "Перейменувати список", + "deleteListLabel": "Видалити список", + "swipeBack": "Скасувати", + "swipeCheck": "Відмітити", + "swipeDelete": "Видалити", + "markDoneLabel": "Відмітити {{name}}", + "markUndoneLabel": "Зняти відмітку з {{name}}", + "deleteItemLabel": "Видалити {{name}}", + "listsLoadError": "Не вдалося завантажити списки.", + "itemsLoadError": "Не вдалося завантажити товари.", + "catFruitVeg": "Фрукти та овочі", + "catBakery": "Випічка", + "catDairy": "Молочні продукти", + "catMeatFish": "М'ясо та риба", + "catFrozen": "Заморожені продукти", + "catDrinks": "Напої", + "catHousehold": "Господарські товари", + "catDrugstore": "Аптека", + "catMisc": "Різне" + }, + "meals": { + "title": "План харчування", + "noMealPlanned": "Страву не заплановано", + "addMeal": "Додати {{type}}", + "editMeal": "Редагувати страву", + "addMealTitle": "Додати страву", + "deleteMeal": "Видалити страву", + "transferToShoppingList": "Додати інгредієнти до списку покупок", + "today": "Сьогодні", + "prevWeek": "Попередній тиждень", + "nextWeek": "Наступний тиждень", + "loadError": "Не вдалося завантажити план харчування.", + "typeBreakfast": "Сніданок", + "typeLunch": "Обід", + "typeDinner": "Вечеря", + "typeSnack": "Перекус", + "dayMo": "Пн", + "dayDi": "Вт", + "dayMi": "Ср", + "dayDo": "Чт", + "dayFr": "Пт", + "daySa": "Сб", + "daySo": "Нд", + "dateLabel": "Дата", + "mealTypeLabel": "Прийом їжі", + "titleLabel": "Заголовок *", + "titlePlaceholder": "напр. Спагеті болоньєзе", + "notesLabel": "Нотатки", + "notesPlaceholder": "Необов'язково…", + "ingredientsLabel": "Інгредієнти", + "addIngredient": "Додати інгредієнт", + "ingredientNamePlaceholder": "Інгредієнт", + "ingredientQtyPlaceholder": "Кількість", + "ingredientCategoryLabel": "Категорія", + "ingredientCategoryDefault": "Різне", + "removeIngredient": "Видалити інгредієнт", + "transferLabel": "Перенести інгредієнти до списку покупок", + "transferNow": "Перенести зараз", + "noShoppingLists": "Немає доступних списків покупок", + "transferSuccess": "{{count}} інгредієнт перенесено", + "transferSuccessPlural": "{{count}} інгредієнтів перенесено", + "transferAlreadyDone": "Усі інгредієнти вже перенесено", + "ingredientCount": "{{count}} інгредієнт", + "ingredientCountPlural": "{{count}} інгредієнтів", + "titleRequired": "Заголовок є обов'язковим", + "loadingIndicator": "Завантаження…", + "recipeUrlLabel": "Посилання на рецепт (необов'язково)", + "recipeUrlPlaceholder": "https://…", + "openRecipe": "Відкрити рецепт" + }, + "calendar": { + "title": "Календар", + "newEvent": "Нова подія", + "editEvent": "Редагувати подію", + "addEvent": "Додати подію", + "deleteEvent": "Видалити подію", + "noEvents": "Немає подій у вибраному періоді.", + "today": "Сьогодні", + "back": "Назад", + "forward": "Вперед", + "viewMonth": "Місяць", + "viewWeek": "Тиждень", + "viewDay": "День", + "viewAgenda": "Порядок денний", + "allDay": "Весь день", + "allDayShort": "весь день", + "moreEvents": "+{{count}} ще", + "weekNumberLabel": "Т{{week}} · {{month}} {{year}}", + "agendaFrom": "З {{date}}", + "titleLabel": "Заголовок *", + "titlePlaceholder": "напр. Стоматолог", + "allDayToggle": "Весь день", + "startDateLabel": "Дата початку", + "startTimeLabel": "Час початку", + "endDateLabel": "Дата завершення", + "endTimeLabel": "Час завершення", + "fromLabel": "Від", + "toLabel": "До", + "locationLabel": "Місце", + "locationPlaceholder": "Необов'язково", + "assignedLabel": "Призначено", + "assignedNobody": "- Нікому -", + "colorLabel": "Колір {{color}}", + "descriptionLabel": "Опис", + "descriptionPlaceholder": "Необов'язково…", + "popupEdit": "Редагувати", + "deleteConfirm": "Справді видалити «{{title}}»?", + "createdToast": "Подію створено", + "savedToast": "Подію збережено", + "deletedToast": "Подію видалено", + "loadError": "Не вдалося завантажити події.", + "saveError": "Помилка збереження", + "deleteError": "Помилка видалення", + "titleRequired": "Заголовок є обов'язковим", + "monthJanuary": "Січень", + "monthFebruary": "Лютий", + "monthMarch": "Березень", + "monthApril": "Квітень", + "monthMay": "Травень", + "monthJune": "Червень", + "monthJuly": "Липень", + "monthAugust": "Серпень", + "monthSeptember": "Вересень", + "monthOctober": "Жовтень", + "monthNovember": "Листопад", + "monthDecember": "Грудень", + "dayShortSunday": "Нд", + "dayShortMonday": "Пн", + "dayShortTuesday": "Вт", + "dayShortWednesday": "Ср", + "dayShortThursday": "Чт", + "dayShortFriday": "Пт", + "dayShortSaturday": "Сб", + "dayLongSunday": "Неділя", + "dayLongMonday": "Понеділок", + "dayLongTuesday": "Вівторок", + "dayLongWednesday": "Середа", + "dayLongThursday": "Четвер", + "dayLongFriday": "П'ятниця", + "dayLongSaturday": "Субота", + "timeSuffix": "" + }, + "notes": { + "title": "Нотатки", + "newNote": "Нова нотатка", + "editNote": "Редагувати нотатку", + "addNoteLabel": "Нова нотатка", + "searchPlaceholder": "Пошук нотаток…", + "emptyTitle": "Нотаток поки немає", + "emptyDescription": "Створіть нову нотатку кнопкою +.", + "noResultsTitle": "Результатів немає", + "noResultsDescription": "Жодна нотатка не містить «{{query}}».", + "titleLabel": "Заголовок (необов'язково)", + "titlePlaceholder": "Без заголовку", + "contentLabel": "Вміст", + "contentMarkdownHint": "(підтримується форматування Markdown)", + "contentPlaceholder": "Введіть нотатку…", + "colorLabel": "Колір", + "pinnedLabel": "Закріпити (відображатиметься на огляді)", + "pinAction": "Закріпити", + "unpinAction": "Відкріпити", + "deleteLabel": "Видалити нотатку", + "deleteConfirm": "Справді видалити цю нотатку?", + "createdToast": "Нотатку створено", + "savedToast": "Нотатку збережено", + "deletedToast": "Нотатку видалено", + "loadError": "Не вдалося завантажити нотатки.", + "formatBold": "Жирний (Ctrl+B)", + "formatItalic": "Курсив (Ctrl+I)", + "formatUnderline": "Підкреслений (Ctrl+U)", + "formatStrikethrough": "Закреслений", + "formatHeading": "Заголовок", + "formatList": "Маркований список", + "formatOrderedList": "Нумерований список", + "formatChecklist": "Список завдань", + "formatLink": "Посилання", + "formatCode": "Код", + "formatQuote": "Цитата", + "formatDivider": "Розділювач" + }, + "contacts": { + "title": "Контакти", + "newContact": "Новий контакт", + "editContact": "Редагувати контакт", + "addButton": "Новий", + "newContactLabel": "Новий контакт", + "searchPlaceholder": "Пошук за ім'ям, телефоном або email…", + "importButton": "Імпорт", + "importLabel": "Імпортувати контакт з vCard", + "importTooltip": "Імпортувати vCard", + "emptyTitle": "Контактів поки немає", + "emptyDescription": "Додайте нові контакти кнопкою +.", + "filterAll": "Усі", + "nameLabel": "Ім'я *", + "namePlaceholder": "Повне ім'я", + "categoryLabel": "Категорія", + "phoneLabel": "Телефон", + "phonePlaceholder": "+380 …", + "emailLabel": "Email", + "emailPlaceholder": "ім'я@приклад.com", + "addressLabel": "Адреса", + "addressPlaceholder": "Вулиця, індекс місто", + "notesLabel": "Нотатки", + "notesPlaceholder": "Необов'язково…", + "callLabel": "Зателефонувати", + "emailActionLabel": "Написати email", + "mapsLabel": "Відкрити на картах", + "exportLabel": "Експортувати як vCard", + "exportTooltip": "Експортувати vCard", + "deleteLabel": "Видалити контакт", + "deleteConfirm": "Справді видалити цей контакт?", + "deletePersonConfirm": "Справді видалити «{{name}}»?", + "savedToast": "Контакт збережено", + "updatedToast": "Контакт оновлено", + "deletedToast": "Контакт видалено", + "importedToast": "{{name}} імпортовано.", + "importError": "Помилка імпорту: {{error}}", + "vcardNoName": "vCard не містить імені.", + "catDoctor": "Лікар", + "catSchool": "Школа/Дитсадок", + "catAuthority": "Державний орган", + "catInsurance": "Страхування", + "catCraftsman": "Майстер", + "catEmergency": "Екстрена служба", + "catMisc": "Різне", + "categoryDoctor": "Лікар", + "categorySchool": "Школа/Дитсадок", + "categoryAuthority": "Державний орган", + "categoryInsurance": "Страхування", + "categoryCraftsman": "Майстер", + "categoryEmergency": "Екстрена служба", + "categoryOther": "Інше" + }, + "budget": { + "title": "Бюджет", + "newEntry": "Новий запис", + "editEntry": "Редагувати запис", + "addEntryLabel": "Додати запис", + "newEntryFabLabel": "Новий запис", + "currentMonth": "Поточний", + "prevMonth": "Попередній місяць", + "nextMonth": "Наступний місяць", + "income": "Доходи", + "expenses": "Витрати", + "balance": "Баланс", + "byCategory": "За категоріями", + "transactions": "Транзакції", + "emptyTitle": "Записів у цьому місяці немає", + "emptyDescription": "Додайте записи бюджету кнопкою +.", + "csvExport": "CSV", + "typeExpense": "Витрата", + "typeIncome": "Дохід", + "titleLabel": "Заголовок *", + "titlePlaceholder": "напр. Супермаркет", + "amountLabel": "Сума *", + "amountPlaceholder": "0.00", + "categoryLabel": "Категорія", + "dateLabel": "Дата *", + "recurringLabel": "Повторюване", + "deleteLabel": "Видалити запис", + "deleteConfirm": "Справді видалити цей запис?", + "deletePersonConfirm": "Справді видалити «{{title}}»?", + "addedToast": "Запис додано", + "savedToast": "Запис збережено", + "deletedToast": "Запис видалено", + "loadError": "Не вдалося завантажити бюджет.", + "trendNeutral": "— так само, як у {{month}}", + "validAmountRequired": "Будь ласка, введіть коректну суму", + "dateRequired": "Дата є обов'язковою", + "catFood": "Продукти", + "catRent": "Оренда", + "catInsurance": "Страхування", + "catMobility": "Транспорт", + "catLeisure": "Дозвілля", + "catClothing": "Одяг", + "catHealth": "Здоров'я", + "catEducation": "Освіта", + "catMisc": "Різне", + "loadingIndicator": "Завантаження…" + }, + "settings": { + "title": "Налаштування", + "tabGeneral": "Загальні", + "tabMeals": "Харчування", + "tabBudget": "Бюджет", + "tabShopping": "Покупки", + "tabCalendar": "Календар", + "tabAccount": "Обліковий запис", + "tabsAriaLabel": "Розділи налаштувань", + "sectionDesign": "Зовнішній вигляд", + "sectionShopping": "Покупки", + "shoppingCategoriesLabel": "Категорії покупок", + "shoppingCategoriesHint": "Додавайте, перейменовуйте, видаляйте або змінюйте порядок категорій.", + "shoppingCategoryPlaceholder": "Нова категорія…", + "shoppingCategoryRenameHint": "Натисніть, щоб перейменувати", + "shoppingCategoryRenamePrompt": "Нова назва категорії:", + "shoppingCategoryMoveUp": "Перемістити категорію вгору", + "shoppingCategoryMoveDown": "Перемістити категорію вниз", + "shoppingCategoryDelete": "Видалити категорію", + "shoppingCategoryDeleteConfirm": "Видалити категорію «{{name}}»? Наявні товари буде переміщено до наступної категорії.", + "shoppingCategoryAdded": "Категорію додано.", + "shoppingCategoryRenamed": "Категорію перейменовано.", + "shoppingCategoryDeleted": "Категорію видалено.", + "sectionAccount": "Мій обліковий запис", + "sectionCalendarSync": "Синхронізація календаря", + "sectionFamily": "Члени родини", + "cardAppearance": "Відображення", + "themeSystem": "Системна", + "themeSysLabel": "Використовувати системні налаштування", + "themeLight": "Світла", + "themeLightLabel": "Світла тема", + "themeDark": "Темна", + "themeDarkLabel": "Темна тема", + "changePassword": "Змінити пароль", + "currentPasswordLabel": "Поточний пароль", + "newPasswordLabel": "Новий пароль", + "confirmPasswordLabel": "Підтвердити новий пароль", + "savePassword": "Зберегти пароль", + "passwordMismatch": "Паролі не збігаються.", + "passwordSavedToast": "Пароль успішно змінено.", + "googleCalendar": "Google Календар", + "appleCalendar": "Apple Календар (iCloud)", + "syncNow": "Синхронізувати зараз", + "disconnect": "Від'єднати", + "connectGoogle": "Підключити через Google", + "connected": "Підключено", + "connectedLastSync": "Підключено · Остання: {{date}}", + "notConnected": "Не підключено", + "notConfigured": "Не налаштовано (відсутні змінні .env)", + "configured": "Налаштовано (через .env)", + "configuredLastSync": "Налаштовано (через .env) · Остання: {{date}}", + "syncSuccess": "{{provider}} синхронізовано.", + "disconnectedToast": "{{provider}} від'єднано.", + "googleOnlyAdmin": "Лише адміністратор може підключити Google Календар.", + "appleOnlyAdmin": "Лише адміністратор може підключити Apple Календар.", + "caldavUrlLabel": "URL CalDAV-сервера", + "caldavUrlPlaceholder": "https://caldav.icloud.com", + "appleIdLabel": "Apple ID (email)", + "applePasswordLabel": "Пароль програми", + "applePasswordHint": "Створіть пароль на appleid.apple.com → Безпека.", + "appleConnectBtn": "Підключити та перевірити", + "appleConnecting": "Підключення…", + "appleConnectedToast": "Apple Календар підключено.", + "syncSuccessGoogle": "Синхронізацію з Google Календарем успішно підключено.", + "syncSuccessApple": "Синхронізацію з Apple Календарем успішно підключено.", + "syncErrorGoogle": "Не вдалося підключитися до Google. Спробуйте ще раз.", + "syncErrorApple": "Не вдалося підключитися до Apple. Спробуйте ще раз.", + "addMember": "+ Додати члена", + "newMemberTitle": "Новий член родини", + "usernameLabel": "Ім'я користувача", + "displayNameLabel": "Відображуване ім'я", + "memberPasswordLabel": "Пароль", + "colorLabel": "Колір", + "roleLabel": "Роль", + "roleMember": "Учасник", + "roleAdmin": "Адміністратор", + "createMember": "Створити", + "cancelAddMember": "Скасувати", + "memberAddedToast": "{{name}} додано.", + "deleteMemberConfirm": "Справді видалити {{name}}?", + "memberDeletedToast": "{{name}} видалено.", + "deleteMemberLabel": "Видалити", + "logout": "Вийти", + "synchronizing": "Синхронізація…", + "googleDisconnectConfirm": "Від'єднати Google Календар?", + "appleDisconnectConfirm": "Від'єднати Apple Календар?", + "localeSystem": "Системна", + "localeLabel": "Мова", + "languageTitle": "Мова", + "sectionMeals": "План харчування", + "mealTypesLabel": "Видимі прийоми їжі", + "mealTypesHint": "У планувальнику харчування відображатимуться лише вибрані типи прийомів їжі.", + "mealTypesSaved": "Налаштування харчування збережено.", + "mealTypesMinOne": "Має бути активним принаймні один тип прийому їжі.", + "sectionBudget": "Бюджет", + "currencyLabel": "Валюта", + "currencyHint": "Встановлює валюту, що використовується в розділі бюджету.", + "currencySaved": "Валюту збережено." + }, + "login": { + "tagline": "Планування для родини. Безпечно. Конфіденційно. Відкритий код.", + "usernameLabel": "Ім'я користувача", + "usernamePlaceholder": "ім'я користувача", + "passwordLabel": "Пароль", + "passwordPlaceholder": "••••••••", + "loginButton": "Увійти", + "loggingIn": "Вхід…", + "tooManyAttempts": "Забагато спроб. Будь ласка, зачекайте.", + "invalidCredentials": "Невірні облікові дані." + }, + "install": { + "title": "Встановити Oikos", + "subtitle": "Додати на головний екран", + "iosTip1": "Натисніть ", + "iosTip2": " → «Додати на головний екран»", + "installButton": "Встановити", + "dismissLabel": "Закрити" + }, + "modal": { + "closeLabel": "Закрити", + "overlayLabel": "Фон модального вікна" + }, + "rrule": { + "freqNone": "Без повторення", + "freqDaily": "Щодня", + "freqWeekly": "Щотижня", + "freqMonthly": "Щомісяця", + "dayMo": "Пн", + "dayTu": "Вт", + "dayWe": "Ср", + "dayTh": "Чт", + "dayFr": "Пт", + "daySa": "Сб", + "daySu": "Нд", + "labelRepeat": "Повторення", + "labelEvery": "Кожні", + "labelOnDays": "У ці дні", + "labelUntil": "Закінчується (необов'язково)", + "unitDay": "день", + "unitDays": "днів", + "unitWeek": "тиждень", + "unitWeeks": "тижнів", + "unitMonth": "місяць", + "unitMonths": "місяців" + }, + "reminders": { + "sectionTitle": "Нагадування", + "enableLabel": "Встановити нагадування", + "dateLabel": "Дата", + "timeLabel": "Час", + "offsetLabel": "Нагадати мені", + "offsetNone": "Без нагадування", + "offset15min": "За 15 хвилин", + "offset1hour": "За 1 годину", + "offset1day": "За 1 день", + "offsetAtTime": "У час події", + "toastTitle": "Нагадування", + "dismiss": "Закрити", + "notificationPermission": "Сповіщення браузера", + "notificationEnable": "Увімкнути сповіщення", + "notificationEnabled": "Сповіщення активні", + "notificationDenied": "Сповіщення заблоковано", + "notificationHint": "Отримуйте сповіщення, поки додаток відкрито.", + "pendingBadgeTitle": "{{count}} нагадування", + "pendingBadgeTitlePlural": "{{count}} нагадувань" + } +} + diff --git a/public/pages/settings.js b/public/pages/settings.js index 7647621..5d840e3 100644 --- a/public/pages/settings.js +++ b/public/pages/settings.js @@ -10,9 +10,25 @@ import { t, formatDate, formatTime } from '/i18n.js'; import { esc } from '/utils/html.js'; import '/components/oikos-locale-picker.js'; -const SUPPORTED_CURRENCIES = ['AED', 'AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'CZK', 'DKK', 'EUR', 'GBP', 'HUF', 'INR', 'JPY', 'NOK', 'PLN', 'RUB', 'SAR', 'SEK', 'TRY', 'USD']; +const SUPPORTED_CURRENCIES = ['AED', 'AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'CZK', 'DKK', 'EUR', 'GBP', 'HUF', 'INR', 'JPY', 'NOK', 'PLN', 'RUB', 'SAR', 'SEK', 'TRY', 'UAH', 'USD']; const SETTINGS_TAB_KEY = 'oikos:settings:tab'; +const CATEGORY_I18N = { + 'Obst & Gemüse': 'shopping.catFruitVeg', + 'Backwaren': 'shopping.catBakery', + 'Milchprodukte': 'shopping.catDairy', + 'Fleisch & Fisch': 'shopping.catMeatFish', + 'Tiefkühl': 'shopping.catFrozen', + 'Getränke': 'shopping.catDrinks', + 'Haushalt': 'shopping.catHousehold', + 'Drogerie': 'shopping.catDrugstore', + 'Sonstiges': 'shopping.catMisc', +}; +function catLabel(name) { + const key = CATEGORY_I18N[name]; + return key ? t(key) : name; +} + function buildCurrencyOptions(selected) { const display = typeof Intl.DisplayNames !== 'undefined' ? new Intl.DisplayNames([document.documentElement.lang || 'en'], { type: 'currency' }) @@ -671,7 +687,7 @@ function categoryRowHtml(cat, isFirst, isLast) { return `