From 7940ea4ded9cd3101efec8b7859065f51703eef9 Mon Sep 17 00:00:00 2001 From: Ulas Kalayci Date: Wed, 29 Apr 2026 07:20:53 +0200 Subject: [PATCH] =?UTF-8?q?release:=20v0.31.0=20=E2=80=94=20family-contact?= =?UTF-8?q?s=20integration=20&=20settings=20restructure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Family member records now store phone, email, and birthday, auto-synced to Contacts and Birthdays modules (PR #99 by @rafaelfoster) - Settings gets dedicated Family and API Tokens tabs (admin-only) - Avatar editor redesigned with icon buttons; tab bar fits standard width - Dentist calendar icon normalised to 'tooth'; theme reset bug fixed - i18n: missing family field translations added to 12 locales Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 19 +++++++++++++++++++ package.json | 2 +- public/locales/ar.json | 14 +++++++------- public/locales/el.json | 14 +++++++------- public/locales/es.json | 14 +++++++------- public/locales/fr.json | 14 +++++++------- public/locales/hi.json | 14 +++++++------- public/locales/it.json | 14 +++++++------- public/locales/ja.json | 14 +++++++------- public/locales/ru.json | 14 +++++++------- public/locales/sv.json | 14 +++++++------- public/locales/tr.json | 14 +++++++------- public/locales/uk.json | 14 +++++++------- public/locales/zh.json | 14 +++++++------- 14 files changed, 104 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c40db..19399c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.31.0] - 2026-04-29 + +### Added +- Family: phone, email, and birthday fields on family member records, automatically synced to Contacts and Birthdays +- Settings: dedicated "Family Management" tab (admin-only) for managing family members including contact details +- Settings: dedicated "API Tokens" tab (admin-only) for token management +- Calendar: local tooth SVG icon for dentist events replaces the drill icon (migration 24 restores tooth icon for existing events) +- i18n: `reset`, `tabFamily`, `tabApiTokens`, and family member field keys translated in all 15 locales + +### Changed +- Settings: avatar editor uses icon buttons instead of a file input label for a cleaner UX +- Settings: tab bar constrained to standard app width so all tabs fit in one row on desktop +- Family members page moved from Account tab to its own Family tab (admin only); Account tab stays focused on personal profile and password + +### Fixed +- Theme toggle: `data-theme` attribute removed when reset to system default (previously left stale) +- Calendar: dentist icon normalised — `tooth` is now the canonical stored value (`drill` accepted as alias for backwards compatibility) +- i18n: missing translations for family member fields added to ar, el, es, fr, hi, it, ja, ru, sv, tr, uk, zh + ## [0.30.3] - 2026-04-28 ### Changed diff --git a/package.json b/package.json index b1618cf..2d87493 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.30.3", + "version": "0.31.0", "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/locales/ar.json b/public/locales/ar.json index bdb1594..7c5ebd3 100644 --- a/public/locales/ar.json +++ b/public/locales/ar.json @@ -734,13 +734,13 @@ "shared": "مشترك" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "رقم الهاتف (اختياري)", + "memberEmailLabel": "البريد الإلكتروني (اختياري)", + "memberBirthDateLabel": "تاريخ الميلاد (اختياري)", + "memberContactBirthdayHint": "تتم مزامنة هذا العضو تلقائيًا مع جهات الاتصال وأعياد الميلاد.", + "memberBirthDateInvalid": "استخدم تاريخ ميلاد صحيحًا بالتنسيق المحدد.", + "memberPhoneMeta": "الهاتف: {{value}}", + "memberBirthdayMeta": "عيد الميلاد: {{date}}" }, "login": { "tagline": "تخطيط عائلي. آمن. يحترم الخصوصية. مفتوح المصدر.", diff --git a/public/locales/el.json b/public/locales/el.json index 604f400..2fc71ba 100644 --- a/public/locales/el.json +++ b/public/locales/el.json @@ -734,13 +734,13 @@ "shared": "Κοινόχρηστο" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "Αριθμός τηλεφώνου (προαιρετικό)", + "memberEmailLabel": "Email (προαιρετικό)", + "memberBirthDateLabel": "Ημερομηνία γέννησης (προαιρετικό)", + "memberContactBirthdayHint": "Αυτό το μέλος συγχρονίζεται αυτόματα με τις Επαφές και τα Γενέθλια.", + "memberBirthDateInvalid": "Χρησιμοποιήστε μια έγκυρη ημερομηνία γέννησης στην επιλεγμένη μορφή.", + "memberPhoneMeta": "Τηλέφωνο: {{value}}", + "memberBirthdayMeta": "Γενέθλια: {{date}}" }, "login": { "tagline": "Οικογενειακός προγραμματισμός. Ασφαλής. Φιλικός προς την ιδιωτικότητα. Ανοιχτός κώδικας.", diff --git a/public/locales/es.json b/public/locales/es.json index 7fba826..b2b70f8 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -734,13 +734,13 @@ "shared": "Compartido" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "Número de teléfono (opcional)", + "memberEmailLabel": "Correo electrónico (opcional)", + "memberBirthDateLabel": "Fecha de nacimiento (opcional)", + "memberContactBirthdayHint": "Este miembro se sincroniza automáticamente con Contactos y Cumpleaños.", + "memberBirthDateInvalid": "Usa una fecha de nacimiento válida en el formato seleccionado.", + "memberPhoneMeta": "Teléfono: {{value}}", + "memberBirthdayMeta": "Cumpleaños: {{date}}" }, "login": { "tagline": "Planificación familiar. Segura. Privada. Código abierto.", diff --git a/public/locales/fr.json b/public/locales/fr.json index b4d32aa..adb1b26 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -734,13 +734,13 @@ "shared": "Partagé" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "Numéro de téléphone (facultatif)", + "memberEmailLabel": "E-mail (facultatif)", + "memberBirthDateLabel": "Date de naissance (facultatif)", + "memberContactBirthdayHint": "Ce membre est automatiquement synchronisé avec les Contacts et les Anniversaires.", + "memberBirthDateInvalid": "Utilisez une date de naissance valide dans le format sélectionné.", + "memberPhoneMeta": "Téléphone : {{value}}", + "memberBirthdayMeta": "Anniversaire : {{date}}" }, "login": { "tagline": "Planification familiale. Sécurisée. Respectueuse de la vie privée. Open source.", diff --git a/public/locales/hi.json b/public/locales/hi.json index b83e25b..19c96d4 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -734,13 +734,13 @@ "shared": "साझा" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "फ़ोन नंबर (वैकल्पिक)", + "memberEmailLabel": "ईमेल (वैकल्पिक)", + "memberBirthDateLabel": "जन्म तिथि (वैकल्पिक)", + "memberContactBirthdayHint": "यह सदस्य स्वचालित रूप से संपर्क और जन्मदिन के साथ समन्वयित होता है।", + "memberBirthDateInvalid": "चयनित दिनांक प्रारूप में एक मान्य जन्म तिथि का उपयोग करें।", + "memberPhoneMeta": "फ़ोन: {{value}}", + "memberBirthdayMeta": "जन्मदिन: {{date}}" }, "login": { "tagline": "पारिवारिक योजना। सुरक्षित। गोपनीयता-अनुकूल। ओपन सोर्स।", diff --git a/public/locales/it.json b/public/locales/it.json index 0b3f4e1..20bf470 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -734,13 +734,13 @@ "shared": "Condiviso" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "Numero di telefono (opzionale)", + "memberEmailLabel": "E-mail (opzionale)", + "memberBirthDateLabel": "Data di nascita (opzionale)", + "memberContactBirthdayHint": "Questo membro viene sincronizzato automaticamente con i Contatti e i Compleanni.", + "memberBirthDateInvalid": "Usa una data di nascita valida nel formato selezionato.", + "memberPhoneMeta": "Telefono: {{value}}", + "memberBirthdayMeta": "Compleanno: {{date}}" }, "login": { "tagline": "Pianificazione familiare. Sicura. Rispettosa della privacy. Open source.", diff --git a/public/locales/ja.json b/public/locales/ja.json index 0487682..2588864 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -734,13 +734,13 @@ "shared": "共有" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "電話番号(任意)", + "memberEmailLabel": "メールアドレス(任意)", + "memberBirthDateLabel": "生年月日(任意)", + "memberContactBirthdayHint": "このメンバーは連絡先と誕生日と自動的に同期されます。", + "memberBirthDateInvalid": "選択した日付形式で有効な生年月日を入力してください。", + "memberPhoneMeta": "電話: {{value}}", + "memberBirthdayMeta": "誕生日: {{date}}" }, "login": { "tagline": "家族計画。安全。プライバシー重視。オープンソース。", diff --git a/public/locales/ru.json b/public/locales/ru.json index 46bc0ac..053d761 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -734,13 +734,13 @@ "shared": "Общее" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "Номер телефона (необязательно)", + "memberEmailLabel": "Электронная почта (необязательно)", + "memberBirthDateLabel": "Дата рождения (необязательно)", + "memberContactBirthdayHint": "Этот участник автоматически синхронизируется с контактами и днями рождения.", + "memberBirthDateInvalid": "Используйте действительную дату рождения в выбранном формате.", + "memberPhoneMeta": "Телефон: {{value}}", + "memberBirthdayMeta": "День рождения: {{date}}" }, "login": { "tagline": "Семейное планирование. Безопасно. С уважением к приватности. Открытый исходный код.", diff --git a/public/locales/sv.json b/public/locales/sv.json index 0e2eaf7..c1a06a7 100644 --- a/public/locales/sv.json +++ b/public/locales/sv.json @@ -734,13 +734,13 @@ "shared": "Delad" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "Telefonnummer (valfritt)", + "memberEmailLabel": "E-post (valfritt)", + "memberBirthDateLabel": "Födelsedatum (valfritt)", + "memberContactBirthdayHint": "Den här medlemmen synkroniseras automatiskt med Kontakter och Födelsedagar.", + "memberBirthDateInvalid": "Använd ett giltigt födelsedatum i det valda datumformatet.", + "memberPhoneMeta": "Telefon: {{value}}", + "memberBirthdayMeta": "Födelsedag: {{date}}" }, "login": { "tagline": "Familjeplanering. Säker. Sekretessvänlig. Öppen källkod.", diff --git a/public/locales/tr.json b/public/locales/tr.json index 45a7a95..9c705c4 100644 --- a/public/locales/tr.json +++ b/public/locales/tr.json @@ -734,13 +734,13 @@ "shared": "Paylaşımlı" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "Telefon numarası (isteğe bağlı)", + "memberEmailLabel": "E-posta (isteğe bağlı)", + "memberBirthDateLabel": "Doğum tarihi (isteğe bağlı)", + "memberContactBirthdayHint": "Bu üye otomatik olarak Kişiler ve Doğum Günleri ile senkronize edilir.", + "memberBirthDateInvalid": "Seçilen tarih formatında geçerli bir doğum tarihi kullanın.", + "memberPhoneMeta": "Telefon: {{value}}", + "memberBirthdayMeta": "Doğum günü: {{date}}" }, "login": { "tagline": "Aile planlaması. Güvenli. Gizlilik dostu. Açık kaynak.", diff --git a/public/locales/uk.json b/public/locales/uk.json index e8b2822..5ab2109 100644 --- a/public/locales/uk.json +++ b/public/locales/uk.json @@ -734,13 +734,13 @@ "shared": "Спільне" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "Номер телефону (необов'язково)", + "memberEmailLabel": "Електронна пошта (необов'язково)", + "memberBirthDateLabel": "Дата народження (необов'язково)", + "memberContactBirthdayHint": "Цей учасник автоматично синхронізується з контактами та днями народження.", + "memberBirthDateInvalid": "Використовуйте дійсну дату народження у вибраному форматі.", + "memberPhoneMeta": "Телефон: {{value}}", + "memberBirthdayMeta": "День народження: {{date}}" }, "login": { "tagline": "Планування для родини. Безпечно. Конфіденційно. Відкритий код.", diff --git a/public/locales/zh.json b/public/locales/zh.json index cb803b6..d425200 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -734,13 +734,13 @@ "shared": "共享" } }, - "memberPhoneLabel": "Phone number (optional)", - "memberEmailLabel": "Email (optional)", - "memberBirthDateLabel": "Birthday date (optional)", - "memberContactBirthdayHint": "This member is automatically synchronized with Contacts and Birthdays.", - "memberBirthDateInvalid": "Use a valid birthday date in the selected date format.", - "memberPhoneMeta": "Phone: {{value}}", - "memberBirthdayMeta": "Birthday: {{date}}" + "memberPhoneLabel": "电话号码(可选)", + "memberEmailLabel": "电子邮件(可选)", + "memberBirthDateLabel": "出生日期(可选)", + "memberContactBirthdayHint": "此成员自动与联系人和生日同步。", + "memberBirthDateInvalid": "请使用所选日期格式的有效出生日期。", + "memberPhoneMeta": "电话:{{value}}", + "memberBirthdayMeta": "生日:{{date}}" }, "login": { "tagline": "家庭规划。安全。注重隐私。开源。",