diff --git a/BACKLOG.md b/BACKLOG.md
index a69b7c6..46c3f37 100644
--- a/BACKLOG.md
+++ b/BACKLOG.md
@@ -51,3 +51,5 @@ New suggestion? → [Open an issue](https://github.com/ulsklyc/oikos/issues/new?
| - | Budget: CNY (Chinese Yuan) added to currency list (#42) | v0.16.2 |
| - | i18n: French (fr), Turkish (tr), Russian (ru), Greek (el), Chinese Simplified (zh) locales | v0.16.3 |
| - | Budget: TRY (Turkish Lira) and RUB (Russian Ruble) added to currency list | v0.16.3 |
+| - | i18n: Japanese (ja), Arabic (ar), Hindi (hi), Portuguese (pt) locales (567 keys each) | v0.19.0 |
+| - | Budget: AED (UAE Dirham), BRL (Brazilian Real), INR (Indian Rupee), SAR (Saudi Riyal) added to currency list | v0.19.0 |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0813b62..1a02432 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.19.0] - 2026-04-14
+
+### Added
+- i18n: Japanese (ja) locale - full translation with 567 keys; Hiragana/Katakana/Kanji script
+- i18n: Arabic (ar) locale - full translation with 567 keys; RTL-ready text
+- i18n: Hindi (hi) locale - full translation with 567 keys; Devanagari script
+- i18n: Portuguese (pt) locale - full translation with 567 keys; Brazilian Portuguese
+- Budget: AED (UAE Dirham), BRL (Brazilian Real), INR (Indian Rupee), SAR (Saudi Riyal) added to currency list
+- Service Worker: new locale files pre-cached in APP_SHELL for offline support (sw v31)
+
## [0.18.2] - 2026-04-14
### Fixed
diff --git a/README.md b/README.md
index 7a59a8f..73d39da 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@
**PWA Native Feel:** Installable on any device, works offline, dark mode, responsive from phone to desktop
-**Multilingual:** German, English, Spanish, French, Italian, Swedish, Greek, Russian, Turkish, and Chinese UI with automatic locale detection
+**Multilingual:** German, English, Spanish, French, Italian, Swedish, Greek, Russian, Turkish, Chinese, Japanese, Arabic, Hindi, and Portuguese UI with automatic locale detection
## Quick Start
diff --git a/docs/SPEC.md b/docs/SPEC.md
index 3484959..acdd100 100644
--- a/docs/SPEC.md
+++ b/docs/SPEC.md
@@ -368,7 +368,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` - 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/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` - 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`
@@ -393,6 +393,10 @@ All UI strings are managed via `public/i18n.js`. No hardcoded text in JS files o
| `ru` | Russian | Full translation (added v0.16.3) |
| `tr` | Turkish | Full translation (added v0.16.3) |
| `zh` | Chinese (Simplified) | Full translation (added v0.16.3) |
+| `ja` | Japanese | Full translation (added v0.19.0) |
+| `ar` | Arabic | Full translation (added v0.19.0) |
+| `hi` | Hindi | Full translation (added v0.19.0) |
+| `pt` | Portuguese | Full translation (added v0.19.0) |
### Adding a New Language
diff --git a/package.json b/package.json
index a8eeb2b..d2ca364 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "oikos",
- "version": "0.18.2",
+ "version": "0.19.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/components/oikos-locale-picker.js b/public/components/oikos-locale-picker.js
index e1a6de0..1a6ac20 100644
--- a/public/components/oikos-locale-picker.js
+++ b/public/components/oikos-locale-picker.js
@@ -18,6 +18,10 @@ const LOCALE_LABELS = {
ru: 'Русский',
tr: 'Türkçe',
zh: '中文',
+ ja: '日本語',
+ ar: 'العربية',
+ hi: 'हिन्दी',
+ pt: 'Português',
};
class OikosLocalePicker extends HTMLElement {
diff --git a/public/i18n.js b/public/i18n.js
index 218cc67..0f173a5 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'];
+const SUPPORTED_LOCALES = ['de', 'en', 'es', 'fr', 'it', 'sv', 'el', 'ru', 'tr', 'zh', 'ja', 'ar', 'hi', 'pt'];
const DEFAULT_LOCALE = 'de';
const STORAGE_KEY = 'oikos-locale';
diff --git a/public/locales/ar.json b/public/locales/ar.json
new file mode 100644
index 0000000..854ffa2
--- /dev/null
+++ b/public/locales/ar.json
@@ -0,0 +1,599 @@
+{
+ "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": "الكمية",
+ "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": "البحث بالاسم أو الهاتف أو البريد الإلكتروني…",
+ "importButton": "استيراد",
+ "importLabel": "استيراد جهة اتصال من vCard",
+ "importTooltip": "استيراد vCard",
+ "emptyTitle": "لا توجد جهات اتصال بعد",
+ "emptyDescription": "أضف جهات اتصال جديدة عبر زر +.",
+ "filterAll": "الكل",
+ "nameLabel": "الاسم *",
+ "namePlaceholder": "الاسم الكامل",
+ "categoryLabel": "الفئة",
+ "phoneLabel": "الهاتف",
+ "phonePlaceholder": "+966 …",
+ "emailLabel": "البريد الإلكتروني",
+ "emailPlaceholder": "name@example.com",
+ "addressLabel": "العنوان",
+ "addressPlaceholder": "الشارع، المدينة",
+ "notesLabel": "ملاحظات",
+ "notesPlaceholder": "اختياري…",
+ "callLabel": "اتصال",
+ "emailActionLabel": "بريد إلكتروني",
+ "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 (البريد الإلكتروني)",
+ "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": "أشهر"
+ }
+}
diff --git a/public/locales/hi.json b/public/locales/hi.json
new file mode 100644
index 0000000..860e6d3
--- /dev/null
+++ b/public/locales/hi.json
@@ -0,0 +1,599 @@
+{
+ "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}} km/h",
+ "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": "मात्रा",
+ "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": "नाम, फ़ोन या ईमेल से खोजें…",
+ "importButton": "आयात",
+ "importLabel": "vCard से संपर्क आयात करें",
+ "importTooltip": "vCard आयात करें",
+ "emptyTitle": "अभी तक कोई संपर्क नहीं",
+ "emptyDescription": "+ बटन से नए संपर्क जोड़ें।",
+ "filterAll": "सभी",
+ "nameLabel": "नाम *",
+ "namePlaceholder": "पूरा नाम",
+ "categoryLabel": "श्रेणी",
+ "phoneLabel": "फ़ोन",
+ "phonePlaceholder": "+91 …",
+ "emailLabel": "ईमेल",
+ "emailPlaceholder": "name@example.com",
+ "addressLabel": "पता",
+ "addressPlaceholder": "सड़क, शहर",
+ "notesLabel": "नोट्स",
+ "notesPlaceholder": "वैकल्पिक…",
+ "callLabel": "कॉल करें",
+ "emailActionLabel": "ईमेल करें",
+ "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": "CalDAV सर्वर URL",
+ "caldavUrlPlaceholder": "https://caldav.icloud.com",
+ "appleIdLabel": "Apple ID (ईमेल)",
+ "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": "माह"
+ }
+}
diff --git a/public/locales/ja.json b/public/locales/ja.json
new file mode 100644
index 0000000..f18defb
--- /dev/null
+++ b/public/locales/ja.json
@@ -0,0 +1,599 @@
+{
+ "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}} km/h",
+ "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": "量",
+ "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": "1月",
+ "monthFebruary": "2月",
+ "monthMarch": "3月",
+ "monthApril": "4月",
+ "monthMay": "5月",
+ "monthJune": "6月",
+ "monthJuly": "7月",
+ "monthAugust": "8月",
+ "monthSeptember": "9月",
+ "monthOctober": "10月",
+ "monthNovember": "11月",
+ "monthDecember": "12月",
+ "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": "名前、電話番号またはメールで検索…",
+ "importButton": "インポート",
+ "importLabel": "vCard から連絡先をインポート",
+ "importTooltip": "vCard をインポート",
+ "emptyTitle": "連絡先なし",
+ "emptyDescription": "+ ボタンで新しい連絡先を追加できます。",
+ "filterAll": "すべて",
+ "nameLabel": "名前 *",
+ "namePlaceholder": "フルネーム",
+ "categoryLabel": "カテゴリー",
+ "phoneLabel": "電話番号",
+ "phonePlaceholder": "+81 …",
+ "emailLabel": "メール",
+ "emailPlaceholder": "name@example.com",
+ "addressLabel": "住所",
+ "addressPlaceholder": "町名、都市",
+ "notesLabel": "メモ",
+ "notesPlaceholder": "任意…",
+ "callLabel": "電話をかける",
+ "emailActionLabel": "メールを送る",
+ "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",
+ "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": "CalDAV サーバー URL",
+ "caldavUrlPlaceholder": "https://caldav.icloud.com",
+ "appleIdLabel": "Apple ID(メール)",
+ "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": "少なくとも1つの食事の種類を有効にしてください。",
+ "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": "ヶ月"
+ }
+}
diff --git a/public/locales/pt.json b/public/locales/pt.json
new file mode 100644
index 0000000..449c5df
--- /dev/null
+++ b/public/locales/pt.json
@@ -0,0 +1,599 @@
+{
+ "common": {
+ "save": "Salvar",
+ "cancel": "Cancelar",
+ "delete": "Excluir",
+ "edit": "Editar",
+ "close": "Fechar",
+ "create": "Criar",
+ "add": "Adicionar",
+ "back": "Voltar",
+ "next": "Próximo",
+ "loading": "Carregando…",
+ "saving": "Salvando…",
+ "required": "Este campo é obrigatório.",
+ "error": "Erro",
+ "allFieldsRequired": "Por favor, preencha todos os campos.",
+ "today": "Hoje",
+ "tomorrow": "Amanhã",
+ "skipToContent": "Pular para o conteúdo",
+ "reload": "Recarregar",
+ "errorOccurred": "Algo deu errado.",
+ "unexpectedError": "Ocorreu um erro inesperado.",
+ "errorGeneric": "Ocorreu um erro.",
+ "updateAvailable": "Atualização disponível - recarregue a página para obter a versão mais recente.",
+ "titleRequired": "Título é obrigatório",
+ "nameRequired": "Nome é obrigatório",
+ "contentRequired": "Conteúdo é obrigatório",
+ "all": "Todos",
+ "unknownError": "Erro desconhecido",
+ "confirm": "Confirmar",
+ "undo": "Desfazer"
+ },
+ "nav": {
+ "dashboard": "Painel",
+ "tasks": "Tarefas",
+ "calendar": "Calendário",
+ "meals": "Refeições",
+ "shopping": "Compras",
+ "notes": "Notas",
+ "contacts": "Contatos",
+ "budget": "Orçamento",
+ "settings": "Configurações",
+ "main": "Navegação principal",
+ "navigation": "Navegação",
+ "quickActions": "Ações rápidas"
+ },
+ "dashboard": {
+ "title": "Painel",
+ "greetingMorning": "Bom dia, {{name}}",
+ "greetingDay": "Boa tarde, {{name}}",
+ "greetingEvening": "Boa noite, {{name}}",
+ "allDone": "Tudo concluído",
+ "noEvents": "Nenhum evento",
+ "noPinnedNotes": "Nenhuma nota fixada",
+ "todayMeals": "Refeições de hoje",
+ "allLink": "Todos",
+ "weekLink": "Semana",
+ "urgentTasksChip": "{{count}} tarefa urgente",
+ "urgentTasksChipPlural": "{{count}} tarefas urgentes",
+ "eventsChip": "{{count}} evento hoje",
+ "eventsChipPlural": "{{count}} eventos hoje",
+ "todayMealChip": "Hoje: {{title}}",
+ "loadError": "Falha ao carregar o painel.",
+ "weatherRefresh": "Atualizar clima",
+ "weatherRefreshTitle": "Atualizar",
+ "weatherUpdated": "Clima atualizado",
+ "weatherFeelsLike": "Sensação {{temp}}° · {{humidity}}% · Vento {{wind}} km/h",
+ "fabTaskLabel": "Adicionar tarefa",
+ "fabCalendarLabel": "Adicionar evento",
+ "fabShoppingLabel": "Adicionar compra",
+ "fabNoteLabel": "Adicionar nota",
+ "fabTask": "Tarefa",
+ "fabCalendar": "Evento",
+ "fabShopping": "Compras",
+ "fabNote": "Nota",
+ "overdue": "Atrasado",
+ "dueSoon": "Vence hoje",
+ "dueTomorrow": "Vence amanhã",
+ "allDay": "Dia inteiro",
+ "shoppingMore": "+{{count}} mais",
+ "weather": "Clima",
+ "customize": "Personalizar",
+ "customizeTitle": "Personalizar widgets",
+ "customizeReset": "Padrão",
+ "customizeSaved": "Painel salvo",
+ "customizeMoveUp": "Mover para cima",
+ "customizeMoveDown": "Mover para baixo"
+ },
+ "tasks": {
+ "title": "Tarefas",
+ "newTask": "Nova tarefa",
+ "editTask": "Editar tarefa",
+ "emptyTitle": "Nenhuma tarefa - tudo concluído?",
+ "emptyDescription": "Crie novas tarefas com o botão +.",
+ "titleLabel": "Título *",
+ "titlePlaceholder": "O que precisa ser feito?",
+ "descriptionLabel": "Nota",
+ "descriptionPlaceholder": "Detalhes opcionais…",
+ "priorityLabel": "Prioridade",
+ "categoryLabel": "Categoria",
+ "dueDateLabel": "Vencimento",
+ "dueTimeLabel": "Horário",
+ "assignedLabel": "Atribuído a",
+ "assignedNobody": "- Ninguém -",
+ "statusLabel": "Status",
+ "priorityUrgent": "Urgente",
+ "priorityHigh": "Alta",
+ "priorityMedium": "Média",
+ "priorityLow": "Baixa",
+ "priorityNone": "Nenhuma",
+ "statusOpen": "Aberto",
+ "statusInProgress": "Em andamento",
+ "statusDone": "Concluído",
+ "categoryHousehold": "Casa",
+ "categorySchool": "Escola",
+ "categoryShopping": "Compras",
+ "categoryRepair": "Reparo",
+ "categoryHealth": "Saúde",
+ "categoryFinance": "Finanças",
+ "categoryLeisure": "Lazer",
+ "categoryMisc": "Outros",
+ "overdue": "Atrasado",
+ "overdueDay": "{{count}}d atrasado",
+ "dueToday": "Vence hoje",
+ "dueTomorrow": "Vence amanhã",
+ "groupOverdue": "Atrasado",
+ "groupToday": "Hoje",
+ "groupThisWeek": "Esta semana",
+ "groupNextWeek": "Próxima semana",
+ "groupLater": "Depois",
+ "groupNoDate": "Sem data",
+ "markDone": "Marcar {{title}} como concluído",
+ "editButton": "Editar tarefa",
+ "swipeOpen": "Abrir",
+ "swipeDone": "Concluído",
+ "swipeEdit": "Editar",
+ "subtaskAdd": "+ Adicionar subtarefa",
+ "subtaskToggle": "Mostrar subtarefas",
+ "subtaskMarkDone": "Marcar {{title}} como concluído",
+ "deleteConfirm": "Excluir tarefa e todas as subtarefas?",
+ "savedToast": "Tarefa salva.",
+ "createdToast": "Tarefa criada.",
+ "deletedToast": "Tarefa excluída.",
+ "loadError": "Falha ao carregar a tarefa.",
+ "subtaskPrompt": "Subtarefa:",
+ "kanbanOpen": "Aberto",
+ "kanbanInProgress": "Em andamento",
+ "kanbanDone": "Concluído",
+ "kanbanMoveToInProgress": "Mover para em andamento",
+ "kanbanMoveToDone": "Marcar como concluído",
+ "kanbanMoveToOpen": "Reabrir",
+ "recurring": "Recorrente",
+ "listView": "Visualização em lista",
+ "kanbanView": "Visualização Kanban"
+ },
+ "shopping": {
+ "title": "Compras",
+ "noLists": "Nenhuma lista",
+ "noListsDescription": "Crie uma lista com o botão +.",
+ "emptyList": "A lista está vazia",
+ "emptyListDescription": "Adicione itens pelo campo de entrada acima.",
+ "newListPrompt": "Nome da nova lista:",
+ "newListButton": "Criar nova lista",
+ "renameListPrompt": "Novo nome da lista:",
+ "deleteListConfirm": "Excluir a lista \"{{name}}\" e todos os itens?",
+ "deletedListToast": "Lista excluída.",
+ "itemDeletedToast": "\"{{name}}\" removido.",
+ "itemsRemovedToast": "{{count}} itens removidos.",
+ "clearChecked": "Excluir marcados ({{count}})",
+ "itemNamePlaceholder": "Adicionar item…",
+ "itemQtyPlaceholder": "Qtd",
+ "itemNameLabel": "Nome do item",
+ "itemQtyLabel": "Quantidade",
+ "categoryLabel": "Categoria",
+ "addItemLabel": "Adicionar item",
+ "renameListLabel": "Renomear lista",
+ "deleteListLabel": "Excluir lista",
+ "swipeBack": "Voltar",
+ "swipeCheck": "Marcar",
+ "swipeDelete": "Excluir",
+ "markDoneLabel": "Marcar {{name}}",
+ "markUndoneLabel": "Desmarcar {{name}}",
+ "deleteItemLabel": "Excluir {{name}}",
+ "listsLoadError": "Falha ao carregar listas.",
+ "itemsLoadError": "Falha ao carregar itens.",
+ "catFruitVeg": "Frutas e Legumes",
+ "catBakery": "Padaria",
+ "catDairy": "Laticínios",
+ "catMeatFish": "Carnes e Peixes",
+ "catFrozen": "Congelados",
+ "catDrinks": "Bebidas",
+ "catHousehold": "Casa",
+ "catDrugstore": "Farmácia",
+ "catMisc": "Outros"
+ },
+ "meals": {
+ "title": "Plano de refeições",
+ "noMealPlanned": "Nenhuma refeição planejada",
+ "addMeal": "Adicionar {{type}}",
+ "editMeal": "Editar refeição",
+ "addMealTitle": "Adicionar refeição",
+ "deleteMeal": "Excluir refeição",
+ "transferToShoppingList": "Ingredientes para lista de compras",
+ "today": "Hoje",
+ "prevWeek": "Semana anterior",
+ "nextWeek": "Próxima semana",
+ "loadError": "Falha ao carregar o plano de refeições.",
+ "typeBreakfast": "Café da manhã",
+ "typeLunch": "Almoço",
+ "typeDinner": "Jantar",
+ "typeSnack": "Lanche",
+ "dayMo": "Seg",
+ "dayDi": "Ter",
+ "dayMi": "Qua",
+ "dayDo": "Qui",
+ "dayFr": "Sex",
+ "daySa": "Sáb",
+ "daySo": "Dom",
+ "dateLabel": "Data",
+ "mealTypeLabel": "Tipo de refeição",
+ "titleLabel": "Título *",
+ "titlePlaceholder": "Ex.: Feijoada",
+ "notesLabel": "Notas",
+ "notesPlaceholder": "Opcional…",
+ "ingredientsLabel": "Ingredientes",
+ "addIngredient": "Adicionar ingrediente",
+ "ingredientNamePlaceholder": "Ingrediente",
+ "ingredientQtyPlaceholder": "Qtd",
+ "removeIngredient": "Remover ingrediente",
+ "transferLabel": "Transferir ingredientes para lista de compras",
+ "transferNow": "Transferir agora",
+ "noShoppingLists": "Nenhuma lista de compras disponível",
+ "transferSuccess": "{{count}} ingrediente transferido",
+ "transferSuccessPlural": "{{count}} ingredientes transferidos",
+ "transferAlreadyDone": "Todos os ingredientes já foram transferidos",
+ "ingredientCount": "{{count}} ingrediente",
+ "ingredientCountPlural": "{{count}} ingredientes",
+ "titleRequired": "Título é obrigatório",
+ "loadingIndicator": "Carregando…",
+ "recipeUrlLabel": "Link da receita (opcional)",
+ "recipeUrlPlaceholder": "https://…",
+ "openRecipe": "Abrir receita"
+ },
+ "calendar": {
+ "title": "Calendário",
+ "newEvent": "Novo evento",
+ "editEvent": "Editar evento",
+ "addEvent": "Adicionar evento",
+ "deleteEvent": "Excluir evento",
+ "noEvents": "Nenhum evento no período selecionado.",
+ "today": "Hoje",
+ "back": "Voltar",
+ "forward": "Avançar",
+ "viewMonth": "Mês",
+ "viewWeek": "Semana",
+ "viewDay": "Dia",
+ "viewAgenda": "Agenda",
+ "allDay": "Dia inteiro",
+ "allDayShort": "dia int.",
+ "moreEvents": "+{{count}} mais",
+ "weekNumberLabel": "Sem {{week}} · {{month}} {{year}}",
+ "agendaFrom": "A partir de {{date}}",
+ "titleLabel": "Título *",
+ "titlePlaceholder": "Ex.: Dentista",
+ "allDayToggle": "Dia inteiro",
+ "startDateLabel": "Data de início",
+ "startTimeLabel": "Horário de início",
+ "endDateLabel": "Data de término",
+ "endTimeLabel": "Horário de término",
+ "fromLabel": "De",
+ "toLabel": "Até",
+ "locationLabel": "Local",
+ "locationPlaceholder": "Opcional",
+ "assignedLabel": "Atribuído a",
+ "assignedNobody": "- Ninguém -",
+ "colorLabel": "Cor {{color}}",
+ "descriptionLabel": "Descrição",
+ "descriptionPlaceholder": "Opcional…",
+ "popupEdit": "Editar",
+ "deleteConfirm": "Excluir \"{{title}}\"?",
+ "createdToast": "Evento criado",
+ "savedToast": "Evento salvo",
+ "deletedToast": "Evento excluído",
+ "loadError": "Falha ao carregar eventos.",
+ "saveError": "Erro ao salvar",
+ "deleteError": "Erro ao excluir",
+ "titleRequired": "Título é obrigatório",
+ "monthJanuary": "Janeiro",
+ "monthFebruary": "Fevereiro",
+ "monthMarch": "Março",
+ "monthApril": "Abril",
+ "monthMay": "Maio",
+ "monthJune": "Junho",
+ "monthJuly": "Julho",
+ "monthAugust": "Agosto",
+ "monthSeptember": "Setembro",
+ "monthOctober": "Outubro",
+ "monthNovember": "Novembro",
+ "monthDecember": "Dezembro",
+ "dayShortSunday": "Dom",
+ "dayShortMonday": "Seg",
+ "dayShortTuesday": "Ter",
+ "dayShortWednesday": "Qua",
+ "dayShortThursday": "Qui",
+ "dayShortFriday": "Sex",
+ "dayShortSaturday": "Sáb",
+ "dayLongSunday": "Domingo",
+ "dayLongMonday": "Segunda-feira",
+ "dayLongTuesday": "Terça-feira",
+ "dayLongWednesday": "Quarta-feira",
+ "dayLongThursday": "Quinta-feira",
+ "dayLongFriday": "Sexta-feira",
+ "dayLongSaturday": "Sábado",
+ "timeSuffix": ""
+ },
+ "notes": {
+ "title": "Quadro de notas",
+ "newNote": "Nova nota",
+ "editNote": "Editar nota",
+ "addNoteLabel": "Nova nota",
+ "searchPlaceholder": "Pesquisar notas…",
+ "emptyTitle": "Nenhuma nota ainda",
+ "emptyDescription": "Crie uma nova nota com o botão +.",
+ "noResultsTitle": "Nenhum resultado",
+ "noResultsDescription": "Nenhuma nota contém \"{{query}}\".",
+ "titleLabel": "Título (opcional)",
+ "titlePlaceholder": "Sem título",
+ "contentLabel": "Conteúdo",
+ "contentMarkdownHint": "(Formatação Markdown disponível)",
+ "contentPlaceholder": "Escreva uma nota…",
+ "colorLabel": "Cor",
+ "pinnedLabel": "Fixar (aparece no painel)",
+ "pinAction": "Fixar",
+ "unpinAction": "Desafixar",
+ "deleteLabel": "Excluir nota",
+ "deleteConfirm": "Excluir nota?",
+ "createdToast": "Nota criada",
+ "savedToast": "Nota salva",
+ "deletedToast": "Nota excluída",
+ "loadError": "Falha ao carregar notas.",
+ "formatBold": "Negrito (Ctrl+B)",
+ "formatItalic": "Itálico (Ctrl+I)",
+ "formatUnderline": "Sublinhado (Ctrl+U)",
+ "formatStrikethrough": "Tachado",
+ "formatHeading": "Título",
+ "formatList": "Lista",
+ "formatOrderedList": "Lista numerada",
+ "formatChecklist": "Lista de verificação",
+ "formatLink": "Link",
+ "formatCode": "Código",
+ "formatQuote": "Citação",
+ "formatDivider": "Divisor"
+ },
+ "contacts": {
+ "title": "Contatos",
+ "newContact": "Novo contato",
+ "editContact": "Editar contato",
+ "addButton": "Novo",
+ "newContactLabel": "Novo contato",
+ "searchPlaceholder": "Pesquisar por nome, telefone ou e-mail…",
+ "importButton": "Importar",
+ "importLabel": "Importar contato de vCard",
+ "importTooltip": "Importar vCard",
+ "emptyTitle": "Nenhum contato ainda",
+ "emptyDescription": "Adicione novos contatos com o botão +.",
+ "filterAll": "Todos",
+ "nameLabel": "Nome *",
+ "namePlaceholder": "Nome completo",
+ "categoryLabel": "Categoria",
+ "phoneLabel": "Telefone",
+ "phonePlaceholder": "+55 …",
+ "emailLabel": "E-mail",
+ "emailPlaceholder": "nome@exemplo.com",
+ "addressLabel": "Endereço",
+ "addressPlaceholder": "Rua, Cidade",
+ "notesLabel": "Notas",
+ "notesPlaceholder": "Opcional…",
+ "callLabel": "Ligar",
+ "emailActionLabel": "E-mail",
+ "mapsLabel": "Abrir no Maps",
+ "exportLabel": "Exportar como vCard",
+ "exportTooltip": "Exportar vCard",
+ "deleteLabel": "Excluir contato",
+ "deleteConfirm": "Excluir contato?",
+ "deletePersonConfirm": "Excluir \"{{name}}\"?",
+ "savedToast": "Contato salvo",
+ "updatedToast": "Contato atualizado",
+ "deletedToast": "Contato excluído",
+ "importedToast": "{{name}} importado.",
+ "importError": "Falha na importação: {{error}}",
+ "vcardNoName": "vCard não contém nome.",
+ "catDoctor": "Médico",
+ "catSchool": "Escola/Creche",
+ "catAuthority": "Órgão público",
+ "catInsurance": "Seguro",
+ "catCraftsman": "Artesão",
+ "catEmergency": "Emergência",
+ "catMisc": "Outros",
+ "categoryDoctor": "Médico",
+ "categorySchool": "Escola/Creche",
+ "categoryAuthority": "Órgão público",
+ "categoryInsurance": "Seguro",
+ "categoryCraftsman": "Artesão",
+ "categoryEmergency": "Emergência",
+ "categoryOther": "Outros"
+ },
+ "budget": {
+ "title": "Orçamento",
+ "newEntry": "Nova entrada",
+ "editEntry": "Editar entrada",
+ "addEntryLabel": "Adicionar entrada",
+ "newEntryFabLabel": "Nova entrada",
+ "currentMonth": "Atual",
+ "prevMonth": "Mês anterior",
+ "nextMonth": "Próximo mês",
+ "income": "Receita",
+ "expenses": "Despesas",
+ "balance": "Saldo",
+ "byCategory": "Por categoria",
+ "transactions": "Transações",
+ "emptyTitle": "Nenhuma entrada este mês",
+ "emptyDescription": "Adicione entradas de orçamento com o botão +.",
+ "csvExport": "CSV",
+ "typeExpense": "Despesa",
+ "typeIncome": "Receita",
+ "titleLabel": "Título *",
+ "titlePlaceholder": "Ex.: Supermercado",
+ "amountLabel": "Valor *",
+ "amountPlaceholder": "0,00",
+ "categoryLabel": "Categoria",
+ "dateLabel": "Data *",
+ "recurringLabel": "Recorrente",
+ "deleteLabel": "Excluir entrada",
+ "deleteConfirm": "Excluir entrada?",
+ "deletePersonConfirm": "Excluir \"{{title}}\"?",
+ "addedToast": "Entrada adicionada",
+ "savedToast": "Entrada salva",
+ "deletedToast": "Entrada excluída",
+ "loadError": "Falha ao carregar orçamento.",
+ "trendNeutral": "- igual a {{month}}",
+ "validAmountRequired": "Insira um valor válido",
+ "dateRequired": "Data é obrigatória",
+ "catFood": "Alimentação",
+ "catRent": "Aluguel",
+ "catInsurance": "Seguro",
+ "catMobility": "Transporte",
+ "catLeisure": "Lazer",
+ "catClothing": "Roupas",
+ "catHealth": "Saúde",
+ "catEducation": "Educação",
+ "catMisc": "Outros",
+ "loadingIndicator": "Carregando…"
+ },
+ "settings": {
+ "title": "Configurações",
+ "tabGeneral": "Geral",
+ "tabMeals": "Refeições",
+ "tabBudget": "Orçamento",
+ "tabShopping": "Compras",
+ "tabCalendar": "Calendário",
+ "tabAccount": "Conta",
+ "tabsAriaLabel": "Seções de configurações",
+ "sectionDesign": "Design",
+ "sectionShopping": "Compras",
+ "shoppingCategoriesLabel": "Categorias de compras",
+ "shoppingCategoriesHint": "Adicione, renomeie, exclua ou ordene categorias.",
+ "shoppingCategoryPlaceholder": "Nova categoria…",
+ "shoppingCategoryRenameHint": "Clique para renomear",
+ "shoppingCategoryRenamePrompt": "Novo nome da categoria:",
+ "shoppingCategoryMoveUp": "Mover categoria para cima",
+ "shoppingCategoryMoveDown": "Mover categoria para baixo",
+ "shoppingCategoryDelete": "Excluir categoria",
+ "shoppingCategoryDeleteConfirm": "Excluir categoria \"{{name}}\"? Os itens existentes serão atribuídos à próxima categoria.",
+ "shoppingCategoryAdded": "Categoria adicionada.",
+ "shoppingCategoryRenamed": "Categoria renomeada.",
+ "shoppingCategoryDeleted": "Categoria excluída.",
+ "sectionAccount": "Minha conta",
+ "sectionCalendarSync": "Sincronização de calendário",
+ "sectionFamily": "Membros da família",
+ "cardAppearance": "Aparência",
+ "themeSystem": "Sistema",
+ "themeSysLabel": "Usar configuração do sistema",
+ "themeLight": "Claro",
+ "themeLightLabel": "Tema claro",
+ "themeDark": "Escuro",
+ "themeDarkLabel": "Tema escuro",
+ "changePassword": "Alterar senha",
+ "currentPasswordLabel": "Senha atual",
+ "newPasswordLabel": "Nova senha",
+ "confirmPasswordLabel": "Confirmar nova senha",
+ "savePassword": "Salvar senha",
+ "passwordMismatch": "As senhas não coincidem.",
+ "passwordSavedToast": "Senha alterada com sucesso.",
+ "googleCalendar": "Google Agenda",
+ "appleCalendar": "Apple Calendar (iCloud)",
+ "syncNow": "Sincronizar agora",
+ "disconnect": "Desconectar",
+ "connectGoogle": "Conectar ao Google",
+ "connected": "Conectado",
+ "connectedLastSync": "Conectado · Última: {{date}}",
+ "notConnected": "Não conectado",
+ "notConfigured": "Não configurado (variáveis .env ausentes)",
+ "configured": "Configurado (via .env)",
+ "configuredLastSync": "Configurado (via .env) · Última: {{date}}",
+ "syncSuccess": "{{provider}} sincronizado.",
+ "disconnectedToast": "{{provider}} desconectado.",
+ "googleOnlyAdmin": "Apenas o administrador pode conectar o Google Agenda.",
+ "appleOnlyAdmin": "Apenas o administrador pode conectar o Apple Calendar.",
+ "caldavUrlLabel": "URL do servidor CalDAV",
+ "caldavUrlPlaceholder": "https://caldav.icloud.com",
+ "appleIdLabel": "Apple ID (e-mail)",
+ "applePasswordLabel": "Senha específica do app",
+ "applePasswordHint": "Crie a senha em appleid.apple.com → Segurança.",
+ "appleConnectBtn": "Conectar e testar",
+ "appleConnecting": "Conectando…",
+ "appleConnectedToast": "Apple Calendar conectado.",
+ "syncSuccessGoogle": "Sincronização com Google Agenda conectada com sucesso.",
+ "syncSuccessApple": "Sincronização com Apple Calendar conectada com sucesso.",
+ "syncErrorGoogle": "Falha ao conectar ao Google. Tente novamente.",
+ "syncErrorApple": "Falha ao conectar ao Apple. Tente novamente.",
+ "addMember": "+ Adicionar membro",
+ "newMemberTitle": "Novo membro da família",
+ "usernameLabel": "Nome de usuário",
+ "displayNameLabel": "Nome de exibição",
+ "memberPasswordLabel": "Senha",
+ "colorLabel": "Cor",
+ "roleLabel": "Função",
+ "roleMember": "Membro",
+ "roleAdmin": "Admin",
+ "createMember": "Criar",
+ "cancelAddMember": "Cancelar",
+ "memberAddedToast": "{{name}} adicionado.",
+ "deleteMemberConfirm": "Excluir {{name}}?",
+ "memberDeletedToast": "{{name}} excluído.",
+ "deleteMemberLabel": "Excluir",
+ "logout": "Sair",
+ "synchronizing": "Sincronizando…",
+ "googleDisconnectConfirm": "Desconectar Google Agenda?",
+ "appleDisconnectConfirm": "Desconectar Apple Calendar?",
+ "localeSystem": "Sistema",
+ "localeLabel": "Idioma",
+ "languageTitle": "Idioma",
+ "sectionMeals": "Plano de refeições",
+ "mealTypesLabel": "Tipos de refeições visíveis",
+ "mealTypesHint": "Apenas os tipos de refeições selecionados serão exibidos no plano.",
+ "mealTypesSaved": "Configurações do plano de refeições salvas.",
+ "mealTypesMinOne": "Pelo menos um tipo de refeição deve estar ativo.",
+ "sectionBudget": "Orçamento",
+ "currencyLabel": "Moeda",
+ "currencyHint": "Define a moeda usada em toda a área de orçamento.",
+ "currencySaved": "Moeda salva."
+ },
+ "login": {
+ "tagline": "Planejamento familiar. Seguro. Privado. Código aberto.",
+ "usernameLabel": "Nome de usuário",
+ "usernamePlaceholder": "nome de usuário",
+ "passwordLabel": "Senha",
+ "passwordPlaceholder": "••••••••",
+ "loginButton": "Entrar",
+ "loggingIn": "Entrando…",
+ "tooManyAttempts": "Muitas tentativas. Por favor, aguarde.",
+ "invalidCredentials": "Credenciais inválidas."
+ },
+ "install": {
+ "title": "Instalar Oikos",
+ "subtitle": "Adicionar ao app",
+ "iosTip1": "Toque em ",
+ "iosTip2": " → \"Adicionar à Tela de Início\"",
+ "installButton": "Instalar",
+ "dismissLabel": "Fechar"
+ },
+ "modal": {
+ "closeLabel": "Fechar",
+ "overlayLabel": "Fundo do diálogo modal"
+ },
+ "rrule": {
+ "freqNone": "Sem repetição",
+ "freqDaily": "Diariamente",
+ "freqWeekly": "Semanalmente",
+ "freqMonthly": "Mensalmente",
+ "dayMo": "Seg",
+ "dayTu": "Ter",
+ "dayWe": "Qua",
+ "dayTh": "Qui",
+ "dayFr": "Sex",
+ "daySa": "Sáb",
+ "daySu": "Dom",
+ "labelRepeat": "Repetição",
+ "labelEvery": "A cada",
+ "labelOnDays": "Nestes dias",
+ "labelUntil": "Termina em (opcional)",
+ "unitDay": "dia",
+ "unitDays": "dias",
+ "unitWeek": "semana",
+ "unitWeeks": "semanas",
+ "unitMonth": "mês",
+ "unitMonths": "meses"
+ }
+}
diff --git a/public/pages/settings.js b/public/pages/settings.js
index 1fc65e5..7647621 100644
--- a/public/pages/settings.js
+++ b/public/pages/settings.js
@@ -10,7 +10,7 @@ import { t, formatDate, formatTime } from '/i18n.js';
import { esc } from '/utils/html.js';
import '/components/oikos-locale-picker.js';
-const SUPPORTED_CURRENCIES = ['AUD', 'CAD', 'CHF', 'CNY', 'CZK', 'DKK', 'EUR', 'GBP', 'HUF', 'JPY', 'NOK', 'PLN', 'RUB', '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', 'USD'];
const SETTINGS_TAB_KEY = 'oikos:settings:tab';
function buildCurrencyOptions(selected) {
diff --git a/public/sw.js b/public/sw.js
index 0bec896..648375b 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -12,7 +12,7 @@
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
*/
-const SHELL_CACHE = 'oikos-shell-v30';
+const SHELL_CACHE = 'oikos-shell-v31';
const PAGES_CACHE = 'oikos-pages-v28';
const ASSETS_CACHE = 'oikos-assets-v27';
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
@@ -27,6 +27,10 @@ const APP_SHELL = [
'/rrule-ui.js',
'/locales/de.json',
'/locales/en.json',
+ '/locales/ja.json',
+ '/locales/ar.json',
+ '/locales/hi.json',
+ '/locales/pt.json',
'/sw-register.js',
'/lucide.min.js',
'/styles/tokens.css',