feat(i18n): add internationalization support with English and German translations
- Integrated i18next for language detection and translation management. - Added translation files for English and German languages. - Implemented translation keys for notifications, reminders, and common UI elements. - Updated main application entry point to include i18n initialization. - Styled language selection dropdown in settings. - Enhanced package dependencies to include i18next and react-i18next.
This commit is contained in:
Generated
+99
-1
@@ -8,8 +8,11 @@
|
||||
"name": "medassist-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"i18next": "^24.2.2",
|
||||
"i18next-browser-languagedetector": "^8.0.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-router-dom": "^7.11.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
@@ -257,6 +260,15 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
@@ -1457,6 +1469,56 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "24.2.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz",
|
||||
"integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz",
|
||||
"integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -1621,6 +1683,32 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.7.4",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.4.tgz",
|
||||
"integrity": "sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.4.0",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
@@ -1767,8 +1855,9 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -1884,6 +1973,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
"lint": "echo 'add lint config'"
|
||||
},
|
||||
"dependencies": {
|
||||
"i18next": "^24.2.2",
|
||||
"i18next-browser-languagedetector": "^8.0.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-router-dom": "^7.11.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
+256
-215
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,236 @@
|
||||
{
|
||||
"nav": {
|
||||
"dashboard": "Übersicht",
|
||||
"medications": "Medikamente",
|
||||
"planner": "Planer",
|
||||
"settings": "Einstellungen",
|
||||
"schedule": "Zeitplan"
|
||||
},
|
||||
"header": {
|
||||
"eyebrow": {
|
||||
"overview": "MedAssist · Übersicht",
|
||||
"inventory": "MedAssist · Inventar",
|
||||
"planner": "MedAssist · Planer",
|
||||
"settings": "MedAssist · Einstellungen",
|
||||
"schedule": "MedAssist · Zeitplan"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"reorder": {
|
||||
"title": "Nachbestell-Erinnerung",
|
||||
"badge": "Bestandsüberwachung",
|
||||
"noMeds": "Noch keine Medikamente konfiguriert.",
|
||||
"allGood": "Alles in Ordnung, genug Vorrat.",
|
||||
"sendReminder": "🔔 Erinnerung jetzt senden"
|
||||
},
|
||||
"overview": {
|
||||
"title": "Medikamentenübersicht",
|
||||
"badge": "Bestand"
|
||||
},
|
||||
"schedules": {
|
||||
"title": "Kommende Einnahmen",
|
||||
"1month": "1 Monat",
|
||||
"3months": "3 Monate",
|
||||
"6months": "6 Monate"
|
||||
},
|
||||
"reminders": {
|
||||
"active": "Automatische Erinnerungen aktiv",
|
||||
"allStockOk": "Bestand OK",
|
||||
"allOk": "Alles OK",
|
||||
"lastReminder": "Letzte Erinnerung",
|
||||
"nextIn": "Nächste",
|
||||
"inDays": "in {{days}} Tagen",
|
||||
"noRemindersNeeded": "keine Erinnerungen nötig",
|
||||
"needReorder": "{{count}} Medikament nachbestellen",
|
||||
"needReorder_other": "{{count}} Medikamente nachbestellen",
|
||||
"waitingFirstCheck": "warte auf erste Prüfung"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"name": "Name",
|
||||
"pills": "Tabletten",
|
||||
"days": "Tage",
|
||||
"currentPills": "Aktuelle Tabletten",
|
||||
"daysLeft": "Tage übrig",
|
||||
"status": "Bestand",
|
||||
"runsOut": "Aufgebraucht",
|
||||
"autoRemind": "Auto-Erinnerung",
|
||||
"expiry": "Ablaufdatum"
|
||||
},
|
||||
"medications": {
|
||||
"list": {
|
||||
"title": "Medikamentenliste",
|
||||
"entries": "{{count}} Einträge",
|
||||
"entries_one": "{{count}} Eintrag",
|
||||
"entries_other": "{{count}} Einträge"
|
||||
},
|
||||
"details": {
|
||||
"packs": "Packungen",
|
||||
"blisters": "Blister pro Packung",
|
||||
"pillsPerBlister": "Tabletten pro Blister",
|
||||
"loose": "Lose",
|
||||
"total": "Gesamt"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"editEntry": "Eintrag bearbeiten",
|
||||
"newEntry": "Neuer Eintrag",
|
||||
"badge": "Packungen + lose Tabletten",
|
||||
"commercialName": "Handelsname",
|
||||
"genericName": "Wirkstoff",
|
||||
"takenBy": "Eingenommen von",
|
||||
"packs": "Packungen",
|
||||
"blistersPerPack": "Blister pro Packung",
|
||||
"pillsPerBlister": "Tabletten pro Blister",
|
||||
"loosePills": "Lose Tabletten",
|
||||
"pillWeight": "Tablettengewicht (mg)",
|
||||
"total": "Gesamt (Tabletten)",
|
||||
"expiryDate": "Ablaufdatum",
|
||||
"notes": "Notizen",
|
||||
"medicationImage": "Medikamentenbild",
|
||||
"removeImage": "Bild entfernen",
|
||||
"placeholders": {
|
||||
"commercial": "z.B. Ozempic",
|
||||
"generic": "z.B. Semaglutid (optional)",
|
||||
"takenBy": "z.B. Max, Anna (optional)",
|
||||
"weight": "z.B. 240",
|
||||
"notes": "z.B. Mit Essen einnehmen, Alkohol vermeiden... (optional)"
|
||||
},
|
||||
"slices": {
|
||||
"title": "Einnahmeplan",
|
||||
"remind": "Erinnern",
|
||||
"remindTooltip": "Erhalte eine Benachrichtigung 15 Minuten vor jeder geplanten Einnahme",
|
||||
"addIntake": "Einnahme",
|
||||
"usage": "Dosis (Tabletten)",
|
||||
"everyDays": "Alle (Tage)",
|
||||
"every": "alle",
|
||||
"from": "ab",
|
||||
"start": "Start (Datum/Uhrzeit)"
|
||||
}
|
||||
},
|
||||
"planner": {
|
||||
"title": "Bedarfsrechner",
|
||||
"badge": "Vorrat planen",
|
||||
"from": "Von",
|
||||
"until": "Bis",
|
||||
"calculate": "Berechnen",
|
||||
"calculating": "Wird berechnet...",
|
||||
"sendEmail": "📧 Per E-Mail senden",
|
||||
"table": {
|
||||
"medication": "Medikament",
|
||||
"usage": "Verbrauch",
|
||||
"blistersNeeded": "Blister benötigt",
|
||||
"blisters": "Blister",
|
||||
"available": "Verfügbar"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"loading": "Einstellungen werden geladen...",
|
||||
"language": {
|
||||
"title": "Sprache",
|
||||
"select": "Sprache auswählen"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Benachrichtigungen",
|
||||
"channels": "Kanäle",
|
||||
"email": "E-Mail",
|
||||
"push": "Push",
|
||||
"stockReminders": "Bestands-Erinnerungen",
|
||||
"intakeReminders": "Einnahme-Erinnerungen",
|
||||
"enableHint": "Aktivieren Sie mindestens einen Kanal, um Benachrichtigungen zu erhalten."
|
||||
},
|
||||
"email": {
|
||||
"recipient": "Empfänger",
|
||||
"notConfigured": "Nicht konfiguriert"
|
||||
},
|
||||
"push": {
|
||||
"url": "URL",
|
||||
"supports": "Unterstützt ntfy, Discord, Telegram, Slack"
|
||||
},
|
||||
"schedule": {
|
||||
"stockCheck": "Bestandsprüfung",
|
||||
"dailyAt6": "Täglich um 6:00 Uhr",
|
||||
"intakeCheck": "Einnahmeprüfung",
|
||||
"15minBefore": "15 Min. vor geplanter Zeit",
|
||||
"nextCheck": "Nächste Bestandsprüfung",
|
||||
"lastSent": "Zuletzt gesendet"
|
||||
},
|
||||
"stock": {
|
||||
"title": "Bestand",
|
||||
"threshold": "Erinnerungsschwelle",
|
||||
"remindWhen": "Erinnern wenn Vorrat unter",
|
||||
"repeatDaily": "Täglich wiederholen",
|
||||
"repeatTooltip": "Wenn aktiviert, wird täglich eine Erinnerung gesendet solange der Bestand niedrig ist. Andernfalls nur einmal pro Medikament bis zum Auffüllen.",
|
||||
"display": "Anzeige",
|
||||
"lowStockDays": "Niedriger Bestand (Tage)",
|
||||
"lowStockTooltip": "Gelbe Warnung ab diesem Schwellenwert",
|
||||
"highStockDays": "Hoher Bestand (Tage)",
|
||||
"highStockTooltip": "Grün mit Stern ab diesem Schwellenwert"
|
||||
},
|
||||
"saveSettings": "Einstellungen speichern"
|
||||
},
|
||||
"modal": {
|
||||
"for": "für",
|
||||
"at": "um",
|
||||
"stockInfo": "Bestandsinformationen",
|
||||
"totalPills": "Tabletten gesamt",
|
||||
"packs": "Packungen",
|
||||
"blistersPerPack": "Blister/Packung",
|
||||
"pillsPerBlister": "Tabletten/Blister",
|
||||
"loosePills": "Lose Tabletten",
|
||||
"pillWeight": "Tablettengewicht",
|
||||
"expiryDate": "Ablaufdatum",
|
||||
"intakeSchedule": "Einnahmeplan",
|
||||
"coverageStatus": "Reichweite",
|
||||
"daysLeft": "Tage übrig",
|
||||
"runsOut": "Aufgebraucht",
|
||||
"notes": "Notizen",
|
||||
"exportCalendar": "In Kalender exportieren",
|
||||
"exportTooltip": "Zeitplan in Kalender exportieren",
|
||||
"editMedication": "Medikament bearbeiten",
|
||||
"userMedications": "Medikamente von {{name}}",
|
||||
"noMedsForUser": "Keine Medikamente für {{name}} gefunden"
|
||||
},
|
||||
"status": {
|
||||
"outOfStock": "Leer",
|
||||
"lowStock": "Niedrig",
|
||||
"normal": "Normal",
|
||||
"highStock": "Hoch",
|
||||
"noSchedule": "Kein Zeitplan",
|
||||
"enough": "Ausreichend",
|
||||
"noPillsLeft": "⚠ Keine Tabletten mehr",
|
||||
"stockOk": "✓ Bestand OK"
|
||||
},
|
||||
"tooltips": {
|
||||
"intakeReminders": "Einnahme-Erinnerungen aktiviert",
|
||||
"hasNotes": "Hat Notizen",
|
||||
"lightMode": "Zum hellen Modus wechseln",
|
||||
"darkMode": "Zum dunklen Modus wechseln"
|
||||
},
|
||||
"dose": {
|
||||
"takenBy": "eingenommen von",
|
||||
"markAsTaken": "Als eingenommen markieren"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Wird geladen...",
|
||||
"sending": "Wird gesendet...",
|
||||
"saving": "Wird gespeichert...",
|
||||
"saved": "Gespeichert ✓",
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"close": "Schließen",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"remove": "Entfernen",
|
||||
"reset": "Zurücksetzen",
|
||||
"test": "Test",
|
||||
"undo": "Rückgängig",
|
||||
"optional": "optional",
|
||||
"pill": "Tablette",
|
||||
"pills": "Tabletten",
|
||||
"day": "Tag",
|
||||
"days": "Tage",
|
||||
"blisters": "Blister",
|
||||
"total": "gesamt"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
{
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"medications": "Medications",
|
||||
"planner": "Planner",
|
||||
"settings": "Settings",
|
||||
"schedule": "Schedule"
|
||||
},
|
||||
"header": {
|
||||
"eyebrow": {
|
||||
"overview": "MedAssist · Overview",
|
||||
"inventory": "MedAssist · Inventory",
|
||||
"planner": "MedAssist · Planner",
|
||||
"settings": "MedAssist · Configuration",
|
||||
"schedule": "MedAssist · Schedule"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"reorder": {
|
||||
"title": "Reorder Reminder",
|
||||
"badge": "Stock watch",
|
||||
"noMeds": "No medications configured yet.",
|
||||
"allGood": "All good, enough stock.",
|
||||
"sendReminder": "🔔 Send Reminder Now"
|
||||
},
|
||||
"overview": {
|
||||
"title": "Medication Overview",
|
||||
"badge": "Stock"
|
||||
},
|
||||
"schedules": {
|
||||
"title": "Upcoming Schedules",
|
||||
"1month": "1 month",
|
||||
"3months": "3 months",
|
||||
"6months": "6 months"
|
||||
},
|
||||
"reminders": {
|
||||
"active": "Automatic reminders active",
|
||||
"allStockOk": "All stock OK",
|
||||
"allOk": "All OK",
|
||||
"lastReminder": "Last reminder",
|
||||
"nextIn": "Next",
|
||||
"inDays": "in {{days}} days",
|
||||
"noRemindersNeeded": "no reminders needed",
|
||||
"needReorder": "{{count}} med needs reorder",
|
||||
"needReorder_other": "{{count}} meds need reorder",
|
||||
"waitingFirstCheck": "waiting for first check"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"name": "Name",
|
||||
"pills": "Pills",
|
||||
"days": "Days",
|
||||
"currentPills": "Current pills",
|
||||
"daysLeft": "Days left",
|
||||
"status": "Stock",
|
||||
"runsOut": "Runs out",
|
||||
"autoRemind": "Auto-remind",
|
||||
"expiry": "Expiry"
|
||||
},
|
||||
"medications": {
|
||||
"list": {
|
||||
"title": "Medication list",
|
||||
"entries": "{{count}} entries",
|
||||
"entries_one": "{{count}} entry",
|
||||
"entries_other": "{{count}} entries"
|
||||
},
|
||||
"details": {
|
||||
"packs": "Packs",
|
||||
"blisters": "Blisters per pack",
|
||||
"pillsPerBlister": "Pills per blister",
|
||||
"loose": "Loose",
|
||||
"total": "Total"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"editEntry": "Edit entry",
|
||||
"newEntry": "New entry",
|
||||
"badge": "Packs + loose pills",
|
||||
"commercialName": "Commercial Name",
|
||||
"genericName": "Generic Name",
|
||||
"takenBy": "Taken by",
|
||||
"packs": "Packs",
|
||||
"blistersPerPack": "Blisters per pack",
|
||||
"pillsPerBlister": "Pills per blister",
|
||||
"loosePills": "Loose pills",
|
||||
"pillWeight": "Pill weight (mg)",
|
||||
"total": "Total (pills)",
|
||||
"expiryDate": "Expiry Date",
|
||||
"notes": "Notes",
|
||||
"medicationImage": "Medication Image",
|
||||
"removeImage": "Remove Image",
|
||||
"placeholders": {
|
||||
"commercial": "e.g. Ozempic",
|
||||
"generic": "e.g. Semaglutide (optional)",
|
||||
"takenBy": "e.g. John, Sarah (optional)",
|
||||
"weight": "e.g. 240",
|
||||
"notes": "e.g. Take with food, avoid alcohol... (optional)"
|
||||
},
|
||||
"slices": {
|
||||
"title": "Intake schedule",
|
||||
"remind": "Remind",
|
||||
"remindTooltip": "Receive a notification 15 minutes before each scheduled intake",
|
||||
"addIntake": "Intake",
|
||||
"usage": "Usage (pills)",
|
||||
"everyDays": "Every (days)",
|
||||
"every": "every",
|
||||
"from": "from",
|
||||
"start": "Start (date/time)"
|
||||
}
|
||||
},
|
||||
"planner": {
|
||||
"title": "Demand Calculator",
|
||||
"badge": "Plan your supply",
|
||||
"from": "From",
|
||||
"until": "Until",
|
||||
"calculate": "Calculate",
|
||||
"calculating": "Calculating...",
|
||||
"sendEmail": "📧 Send via Email",
|
||||
"table": {
|
||||
"medication": "Medication",
|
||||
"usage": "Usage",
|
||||
"blistersNeeded": "Blisters needed",
|
||||
"blisters": "Blisters",
|
||||
"available": "Available"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"loading": "Loading settings...",
|
||||
"language": {
|
||||
"title": "Language",
|
||||
"select": "Select language"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"channels": "Channels",
|
||||
"email": "Email",
|
||||
"push": "Push",
|
||||
"stockReminders": "Stock Reminders",
|
||||
"intakeReminders": "Intake Reminders",
|
||||
"enableHint": "Enable at least one channel below to receive notifications."
|
||||
},
|
||||
"email": {
|
||||
"recipient": "Recipient",
|
||||
"notConfigured": "Not configured"
|
||||
},
|
||||
"push": {
|
||||
"url": "URL",
|
||||
"supports": "Supports ntfy, Discord, Telegram, Slack"
|
||||
},
|
||||
"schedule": {
|
||||
"stockCheck": "Stock check",
|
||||
"dailyAt6": "Daily at 6:00 AM",
|
||||
"intakeCheck": "Intake check",
|
||||
"15minBefore": "15 min before scheduled time",
|
||||
"nextCheck": "Next stock check",
|
||||
"lastSent": "Last sent"
|
||||
},
|
||||
"stock": {
|
||||
"title": "Stock",
|
||||
"threshold": "Reminder Threshold",
|
||||
"remindWhen": "Remind when supply drops below",
|
||||
"repeatDaily": "Repeat daily",
|
||||
"repeatTooltip": "When enabled, sends reminders every day while stock is low. Otherwise, only notifies once per medication until restocked.",
|
||||
"display": "Display",
|
||||
"lowStockDays": "Low Stock (days)",
|
||||
"lowStockTooltip": "Yellow warning color threshold",
|
||||
"highStockDays": "High Stock (days)",
|
||||
"highStockTooltip": "Green with star threshold"
|
||||
},
|
||||
"saveSettings": "Save Settings"
|
||||
},
|
||||
"modal": {
|
||||
"for": "for",
|
||||
"at": "at",
|
||||
"stockInfo": "Stock Information",
|
||||
"totalPills": "Total Pills",
|
||||
"packs": "Packs",
|
||||
"blistersPerPack": "Blisters/Pack",
|
||||
"pillsPerBlister": "Pills/Blister",
|
||||
"loosePills": "Loose Pills",
|
||||
"pillWeight": "Pill Weight",
|
||||
"expiryDate": "Expiry Date",
|
||||
"intakeSchedule": "Intake Schedule",
|
||||
"coverageStatus": "Coverage Status",
|
||||
"daysLeft": "Days Left",
|
||||
"runsOut": "Runs Out",
|
||||
"notes": "Notes",
|
||||
"exportCalendar": "Export to Calendar",
|
||||
"exportTooltip": "Export schedule to calendar",
|
||||
"editMedication": "Edit Medication",
|
||||
"userMedications": "{{name}}'s Medications",
|
||||
"noMedsForUser": "No medications found for {{name}}"
|
||||
},
|
||||
"status": {
|
||||
"outOfStock": "Empty",
|
||||
"lowStock": "Low",
|
||||
"normal": "Normal",
|
||||
"highStock": "High",
|
||||
"noSchedule": "No Schedule",
|
||||
"enough": "Enough",
|
||||
"noPillsLeft": "⚠ No pills left",
|
||||
"stockOk": "✓ Stock OK"
|
||||
},
|
||||
"tooltips": {
|
||||
"intakeReminders": "Intake reminders enabled",
|
||||
"hasNotes": "Has notes",
|
||||
"lightMode": "Switch to light mode",
|
||||
"darkMode": "Switch to dark mode"
|
||||
},
|
||||
"dose": {
|
||||
"takenBy": "taken by",
|
||||
"markAsTaken": "Mark as taken"
|
||||
},
|
||||
"common": {
|
||||
"loading": "Loading...",
|
||||
"sending": "Sending...",
|
||||
"saving": "Saving...",
|
||||
"saved": "Saved ✓",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"close": "Close",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"remove": "Remove",
|
||||
"reset": "Reset",
|
||||
"test": "Test",
|
||||
"undo": "Undo",
|
||||
"optional": "optional",
|
||||
"pill": "pill",
|
||||
"pills": "pills",
|
||||
"day": "day",
|
||||
"days": "days",
|
||||
"blisters": "blisters",
|
||||
"total": "total"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
import en from './en.json';
|
||||
import de from './de.json';
|
||||
|
||||
const resources = {
|
||||
en: { translation: en },
|
||||
de: { translation: de },
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: 'en',
|
||||
supportedLngs: ['en', 'de'],
|
||||
interpolation: {
|
||||
escapeValue: false, // React already escapes
|
||||
},
|
||||
detection: {
|
||||
order: ['localStorage', 'navigator'],
|
||||
caches: ['localStorage'],
|
||||
lookupLocalStorage: 'medassist-language',
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from "./App";
|
||||
import "./styles.css";
|
||||
import "./i18n";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
|
||||
@@ -825,6 +825,42 @@ textarea {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.setting-row.language-row {
|
||||
justify-content: flex-start;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.language-select {
|
||||
width: auto;
|
||||
min-width: 160px;
|
||||
max-width: 200px;
|
||||
padding: 0.6rem 2rem 0.6rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0.75rem center;
|
||||
}
|
||||
|
||||
.language-select:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.language-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
|
||||
}
|
||||
|
||||
.language-select option {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.setting-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user