Add localization support and docs

This commit is contained in:
2025-10-18 21:44:18 +02:00
parent 0fb168b050
commit 4166cf0460
15 changed files with 424 additions and 110 deletions

View File

@@ -6,36 +6,40 @@ import DeviationList from './components/DeviationList.js';
import SuggestionPanel from './components/SuggestionPanel.js';
import SimulationChart from './components/SimulationChart.js';
import Settings from './components/Settings.js';
import LanguageSelector from './components/LanguageSelector.js';
// Custom Hooks
import { useAppState } from './hooks/useAppState.js';
import { useSimulation } from './hooks/useSimulation.js';
import { useLanguage } from './hooks/useLanguage.js';
// --- Main Component ---
const MedPlanAssistant = () => {
const {
appState,
updateState,
updateNestedState,
updateUiSetting,
handleReset
const { currentLanguage, t, changeLanguage } = useLanguage();
const {
appState,
updateState,
updateNestedState,
updateUiSetting,
handleReset
} = useAppState();
const {
pkParams,
doses,
therapeuticRange,
doseIncrement,
uiSettings
const {
pkParams,
doses,
therapeuticRange,
doseIncrement,
uiSettings
} = appState;
const {
showDayTimeXAxis,
chartView,
yAxisMin,
yAxisMax,
simulationDays,
displayedDays
const {
showDayTimeXAxis,
chartView,
yAxisMin,
yAxisMax,
simulationDays,
displayedDays
} = uiSettings;
const {
@@ -54,57 +58,65 @@ const MedPlanAssistant = () => {
<div className="bg-gray-100 font-sans p-4 sm:p-6 lg:p-8">
<div className="max-w-7xl mx-auto">
<header className="mb-8">
<h1 className="text-3xl md:text-4xl font-bold text-gray-800">Medikationsplan-Assistent</h1>
<p className="text-gray-600 mt-1">Simulation für Lisdexamfetamin (LDX) und d-Amphetamin (d-amph)</p>
<div className="flex justify-between items-start">
<div>
<h1 className="text-3xl md:text-4xl font-bold text-gray-800">{t.appTitle}</h1>
<p className="text-gray-600 mt-1">{t.appSubtitle}</p>
</div>
<LanguageSelector currentLanguage={currentLanguage} onLanguageChange={changeLanguage} t={t} />
</div>
</header>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Column - Controls */}
<div className="lg:col-span-1 space-y-6 lg:order-1">
<DoseSchedule
<DoseSchedule
doses={doses}
doseIncrement={doseIncrement}
onUpdateDoses={(newDoses) => updateState('doses', newDoses)}
t={t}
/>
<DeviationList
<DeviationList
deviations={deviations}
doseIncrement={doseIncrement}
simulationDays={simulationDays}
onAddDeviation={addDeviation}
onRemoveDeviation={removeDeviation}
onDeviationChange={handleDeviationChange}
t={t}
/>
<SuggestionPanel
<SuggestionPanel
suggestion={suggestion}
onApplySuggestion={applySuggestion}
t={t}
/>
</div>
{/* Center Column - Chart */}
<div className="lg:col-span-2 bg-white p-5 rounded-lg shadow-sm border min-h-[600px] flex flex-col lg:order-2">
<div className="flex justify-center space-x-2 mb-4">
<button
onClick={() => updateUiSetting('chartView', 'damph')}
<button
onClick={() => updateUiSetting('chartView', 'damph')}
className={`px-4 py-2 text-sm font-medium rounded-md ${chartView === 'damph' ? 'bg-sky-600 text-white' : 'bg-gray-200 text-gray-700'}`}
>
d-Amphetamin
{t.dAmphetamine}
</button>
<button
onClick={() => updateUiSetting('chartView', 'ldx')}
<button
onClick={() => updateUiSetting('chartView', 'ldx')}
className={`px-4 py-2 text-sm font-medium rounded-md ${chartView === 'ldx' ? 'bg-sky-600 text-white' : 'bg-gray-200 text-gray-700'}`}
>
Lisdexamfetamin
{t.lisdexamfetamine}
</button>
<button
onClick={() => updateUiSetting('chartView', 'both')}
<button
onClick={() => updateUiSetting('chartView', 'both')}
className={`px-4 py-2 text-sm font-medium rounded-md ${chartView === 'both' ? 'bg-sky-600 text-white' : 'bg-gray-200 text-gray-700'}`}
>
Beide
{t.both}
</button>
</div>
<SimulationChart
idealProfile={idealProfile}
deviatedProfile={deviatedProfile}
@@ -116,12 +128,13 @@ const MedPlanAssistant = () => {
displayedDays={displayedDays}
yAxisMin={yAxisMin}
yAxisMax={yAxisMax}
t={t}
/>
</div>
{/* Right Column - Settings */}
<div className="lg:col-span-1 space-y-6 lg:order-3">
<Settings
<Settings
pkParams={pkParams}
therapeuticRange={therapeuticRange}
uiSettings={uiSettings}
@@ -129,17 +142,18 @@ const MedPlanAssistant = () => {
onUpdateTherapeuticRange={(key, value) => updateNestedState('therapeuticRange', key, { [key]: value })}
onUpdateUiSetting={updateUiSetting}
onReset={handleReset}
t={t}
/>
</div>
</div>
<footer className="mt-8 p-4 bg-gray-100 rounded-lg text-sm text-gray-700 border">
<h3 className="font-semibold mb-2">Wichtiger Hinweis</h3>
<p>Dieses Tool dient ausschließlich zu Illustrations- und Informationszwecken. Es ist kein medizinisches Gerät und ersetzt nicht die Beratung durch einen Arzt oder Apotheker. Alle Berechnungen sind Simulationen, die auf allgemeinen pharmakokinetischen Modellen basieren und von individuellen Faktoren erheblich abweichen können. Bitte konsultiere deinen behandelnden Arzt, bevor du Anpassungen an deiner Medikation vornimmst.</p>
<h3 className="font-semibold mb-2">{t.importantNote}</h3>
<p>{t.disclaimer}</p>
</footer>
</div>
</div>
);
};
export default MedPlanAssistant;
export default MedPlanAssistant;

View File

@@ -8,11 +8,12 @@ const DeviationList = ({
simulationDays,
onAddDeviation,
onRemoveDeviation,
onDeviationChange
onDeviationChange,
t
}) => {
return (
<div className="bg-amber-50 p-5 rounded-lg shadow-sm border border-amber-200">
<h2 className="text-xl font-semibold mb-4 text-gray-700">Abweichungen vom Plan</h2>
<h2 className="text-xl font-semibold mb-4 text-gray-700">{t.deviationsFromPlan}</h2>
{deviations.map((dev, index) => (
<div key={index} className="flex items-center space-x-2 mb-2 p-2 bg-white rounded flex-wrap">
<select
@@ -21,7 +22,7 @@ const DeviationList = ({
className="p-2 border rounded-md text-sm"
>
{[...Array(parseInt(simulationDays, 10) || 1).keys()].map(day => (
<option key={day} value={day}>Tag {day + 1}</option>
<option key={day} value={day}>{t.day} {day + 1}</option>
))}
</select>
<TimeInput
@@ -34,7 +35,7 @@ const DeviationList = ({
onChange={newDose => onDeviationChange(index, 'dose', newDose)}
increment={doseIncrement}
min={0}
unit="mg"
unit={t.mg}
/>
</div>
<button
@@ -43,7 +44,7 @@ const DeviationList = ({
>
&times;
</button>
<div className="flex items-center mt-1" title="Mark this if it was an extra dose instead of a replacement for a planned one.">
<div className="flex items-center mt-1" title={t.additionalTooltip}>
<input
type="checkbox"
id={`add_dose_${index}`}
@@ -52,7 +53,7 @@ const DeviationList = ({
className="h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-500"
/>
<label htmlFor={`add_dose_${index}`} className="ml-2 text-xs text-gray-600">
Zusätzlich
{t.additional}
</label>
</div>
</div>
@@ -61,10 +62,8 @@ const DeviationList = ({
onClick={onAddDeviation}
className="mt-2 w-full bg-amber-500 text-white py-2 rounded-md hover:bg-amber-600 text-sm"
>
Abweichung hinzufügen
{t.addDeviation}
</button>
</div>
);
};
export default DeviationList;
};export default DeviationList;

View File

@@ -2,10 +2,10 @@ import React from 'react';
import TimeInput from './TimeInput.js';
import NumericInput from './NumericInput.js';
const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses }) => {
const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses, t }) => {
return (
<div className="bg-white p-5 rounded-lg shadow-sm border">
<h2 className="text-xl font-semibold mb-4 text-gray-700">Mein Plan</h2>
<h2 className="text-xl font-semibold mb-4 text-gray-700">{t.myPlan}</h2>
{doses.map((dose, index) => (
<div key={index} className="flex items-center space-x-3 mb-3">
<TimeInput
@@ -18,10 +18,10 @@ const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses }) => {
onChange={newDose => onUpdateDoses(doses.map((d, i) => i === index ? {...d, dose: newDose} : d))}
increment={doseIncrement}
min={0}
unit="mg"
unit={t.mg}
/>
</div>
<span className="text-gray-600 text-sm flex-1">{dose.label}</span>
<span className="text-gray-600 text-sm flex-1">{t[dose.label] || dose.label}</span>
</div>
))}
</div>

View File

@@ -0,0 +1,19 @@
import React from 'react';
const LanguageSelector = ({ currentLanguage, onLanguageChange, t }) => {
return (
<div className="flex items-center space-x-2">
<label className="text-sm font-medium text-gray-600">{t.language}:</label>
<select
value={currentLanguage}
onChange={(e) => onLanguageChange(e.target.value)}
className="p-1 border rounded text-sm bg-white"
>
<option value="en">{t.english}</option>
<option value="de">{t.german}</option>
</select>
</div>
);
};
export default LanguageSelector;

View File

@@ -8,13 +8,14 @@ const Settings = ({
onUpdatePkParams,
onUpdateTherapeuticRange,
onUpdateUiSetting,
onReset
onReset,
t
}) => {
const { showDayTimeXAxis, yAxisMin, yAxisMax, simulationDays, displayedDays } = uiSettings;
return (
<div className="bg-white p-5 rounded-lg shadow-sm border">
<h2 className="text-xl font-semibold mb-4 text-gray-700">Erweiterte Einstellungen</h2>
<h2 className="text-xl font-semibold mb-4 text-gray-700">{t.advancedSettings}</h2>
<div className="space-y-4 text-sm">
<div className="flex items-center">
<input
@@ -25,11 +26,11 @@ const Settings = ({
className="h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-500"
/>
<label htmlFor="showDayTimeXAxis" className="ml-3 block font-medium text-gray-600">
24h-Zeitachse anzeigen
{t.show24hTimeAxis}
</label>
</div>
<label className="block font-medium text-gray-600 pt-2">Simulationsdauer</label>
<label className="block font-medium text-gray-600 pt-2">{t.simulationDuration}</label>
<div className="w-40">
<NumericInput
value={simulationDays}
@@ -37,11 +38,11 @@ const Settings = ({
increment={'1'}
min={2}
max={7}
unit="Tage"
unit={t.days}
/>
</div>
<label className="block font-medium text-gray-600">Angezeigte Tage</label>
<label className="block font-medium text-gray-600">{t.displayedDays}</label>
<div className="w-40">
<NumericInput
value={displayedDays}
@@ -49,11 +50,11 @@ const Settings = ({
increment={'1'}
min={1}
max={parseInt(simulationDays, 10) || 1}
unit="Tage"
unit={t.days}
/>
</div>
<label className="block font-medium text-gray-600 pt-2">Y-Achsen-Bereich</label>
<label className="block font-medium text-gray-600 pt-2">{t.yAxisRange}</label>
<div className="flex items-center space-x-2 mt-1">
<div className="w-32">
<NumericInput
@@ -61,7 +62,7 @@ const Settings = ({
onChange={val => onUpdateUiSetting('yAxisMin', val)}
increment={'5'}
min={0}
placeholder="Auto"
placeholder={t.auto}
unit="ng/ml"
/>
</div>
@@ -72,13 +73,13 @@ const Settings = ({
onChange={val => onUpdateUiSetting('yAxisMax', val)}
increment={'5'}
min={0}
placeholder="Auto"
placeholder={t.auto}
unit="ng/ml"
/>
</div>
</div>
<label className="block font-medium text-gray-600">Therapeutischer Bereich</label>
<label className="block font-medium text-gray-600">{t.therapeuticRange}</label>
<div className="flex items-center space-x-2 mt-1">
<div className="w-32">
<NumericInput
@@ -86,7 +87,7 @@ const Settings = ({
onChange={val => onUpdateTherapeuticRange('min', val)}
increment={'0.5'}
min={0}
placeholder="Min"
placeholder={t.min}
unit="ng/ml"
/>
</div>
@@ -97,15 +98,15 @@ const Settings = ({
onChange={val => onUpdateTherapeuticRange('max', val)}
increment={'0.5'}
min={0}
placeholder="Max"
placeholder={t.max}
unit="ng/ml"
/>
</div>
</div>
<h3 className="text-lg font-semibold mt-4 pt-4 border-t">d-Amphetamin Parameter</h3>
<h3 className="text-lg font-semibold mt-4 pt-4 border-t">{t.dAmphetamineParameters}</h3>
<div className="w-40">
<label className="block font-medium text-gray-600">Halbwertszeit</label>
<label className="block font-medium text-gray-600">{t.halfLife}</label>
<NumericInput
value={pkParams.damph.halfLife}
onChange={val => onUpdatePkParams('damph', { halfLife: val })}
@@ -115,9 +116,9 @@ const Settings = ({
/>
</div>
<h3 className="text-lg font-semibold mt-4 pt-4 border-t">Lisdexamfetamin Parameter</h3>
<h3 className="text-lg font-semibold mt-4 pt-4 border-t">{t.lisdexamfetamineParameters}</h3>
<div className="w-40">
<label className="block font-medium text-gray-600">Umwandlungs-Halbwertszeit</label>
<label className="block font-medium text-gray-600">{t.conversionHalfLife}</label>
<NumericInput
value={pkParams.ldx.halfLife}
onChange={val => onUpdatePkParams('ldx', { halfLife: val })}
@@ -127,13 +128,13 @@ const Settings = ({
/>
</div>
<div className="w-40">
<label className="block font-medium text-gray-600">Absorptionsrate</label>
<label className="block font-medium text-gray-600">{t.absorptionRate}</label>
<NumericInput
value={pkParams.ldx.absorptionRate}
onChange={val => onUpdatePkParams('ldx', { absorptionRate: val })}
increment={'0.1'}
min={0.1}
unit="(schneller >)"
unit={t.faster}
/>
</div>
@@ -142,7 +143,7 @@ const Settings = ({
onClick={onReset}
className="w-full bg-red-600 text-white py-2 rounded-md hover:bg-red-700 text-sm"
>
Alle Einstellungen zurücksetzen
{t.resetAllSettings}
</button>
</div>
</div>

View File

@@ -11,7 +11,8 @@ const SimulationChart = ({
simulationDays,
displayedDays,
yAxisMin,
yAxisMax
yAxisMax,
t
}) => {
const totalHours = (parseInt(simulationDays, 10) || 3) * 24;
const chartTicks = Array.from({length: Math.floor(totalHours / 6) + 1}, (_, i) => i * 6);
@@ -36,7 +37,7 @@ const SimulationChart = ({
type="number"
domain={[0, totalHours]}
ticks={chartTicks}
tickFormatter={(h) => `${h}h`}
tickFormatter={(h) => `${h}${t.hour}`}
xAxisId="continuous"
/>
{showDayTimeXAxis && (
@@ -45,26 +46,26 @@ const SimulationChart = ({
type="number"
domain={[0, totalHours]}
ticks={chartTicks}
tickFormatter={(h) => `${h % 24}h`}
tickFormatter={(h) => `${h % 24}${t.hour}`}
xAxisId="daytime"
orientation="top"
/>
)}
<YAxis
label={{ value: 'Konzentration (ng/ml)', angle: -90, position: 'insideLeft', offset: -10 }}
label={{ value: t.concentration, angle: -90, position: 'insideLeft', offset: -10 }}
domain={chartDomain}
allowDecimals={false}
/>
<Tooltip
formatter={(value, name) => [`${value.toFixed(1)} ng/ml`, name]}
labelFormatter={(label) => `Stunde: ${label}h`}
formatter={(value, name) => [`${value.toFixed(1)} ${t.ngml}`, name]}
labelFormatter={(label) => `${t.hour.replace('h', 'Hour')}: ${label}${t.hour}`}
/>
<Legend verticalAlign="top" height={36} />
{(chartView === 'damph' || chartView === 'both') && (
<ReferenceLine
y={parseFloat(therapeuticRange.min) || 0}
label={{ value: 'Min', position: 'insideTopLeft' }}
label={{ value: t.min, position: 'insideTopLeft' }}
stroke="green"
strokeDasharray="3 3"
xAxisId="continuous"
@@ -73,7 +74,7 @@ const SimulationChart = ({
{(chartView === 'damph' || chartView === 'both') && (
<ReferenceLine
y={parseFloat(therapeuticRange.max) || 0}
label={{ value: 'Max', position: 'insideTopLeft' }}
label={{ value: t.max, position: 'insideTopLeft' }}
stroke="red"
strokeDasharray="3 3"
xAxisId="continuous"
@@ -97,7 +98,7 @@ const SimulationChart = ({
type="monotone"
data={idealProfile}
dataKey="damph"
name="d-Amphetamin (Ideal)"
name={`${t.dAmphetamine} (Ideal)`}
stroke="#3b82f6"
strokeWidth={2.5}
dot={false}
@@ -109,7 +110,7 @@ const SimulationChart = ({
type="monotone"
data={idealProfile}
dataKey="ldx"
name="Lisdexamfetamin (Ideal)"
name={`${t.lisdexamfetamine} (Ideal)`}
stroke="#8b5cf6"
strokeWidth={2}
dot={false}
@@ -123,7 +124,7 @@ const SimulationChart = ({
type="monotone"
data={deviatedProfile}
dataKey="damph"
name="d-Amphetamin (Abweichung)"
name={`${t.dAmphetamine} (Deviation)`}
stroke="#f59e0b"
strokeWidth={2}
strokeDasharray="5 5"
@@ -136,7 +137,7 @@ const SimulationChart = ({
type="monotone"
data={deviatedProfile}
dataKey="ldx"
name="Lisdexamfetamin (Abweichung)"
name={`${t.lisdexamfetamine} (Deviation)`}
stroke="#f97316"
strokeWidth={1.5}
strokeDasharray="5 5"
@@ -150,7 +151,7 @@ const SimulationChart = ({
type="monotone"
data={correctedProfile}
dataKey="damph"
name="d-Amphetamin (Korrektur)"
name={`${t.dAmphetamine} (Correction)`}
stroke="#10b981"
strokeWidth={2.5}
strokeDasharray="3 7"
@@ -163,7 +164,7 @@ const SimulationChart = ({
type="monotone"
data={correctedProfile}
dataKey="ldx"
name="Lisdexamfetamin (Korrektur)"
name={`${t.lisdexamfetamine} (Correction)`}
stroke="#059669"
strokeWidth={2}
strokeDasharray="3 7"
@@ -176,6 +177,4 @@ const SimulationChart = ({
</div>
</div>
);
};
export default SimulationChart;
};export default SimulationChart;

View File

@@ -1,21 +1,21 @@
import React from 'react';
const SuggestionPanel = ({ suggestion, onApplySuggestion }) => {
const SuggestionPanel = ({ suggestion, onApplySuggestion, t }) => {
if (!suggestion) return null;
return (
<div className="bg-sky-100 border-l-4 border-sky-500 p-4 rounded-r-lg shadow-md">
<h3 className="font-bold text-lg mb-2">Was wäre wenn?</h3>
<h3 className="font-bold text-lg mb-2">{t.whatIf}</h3>
{suggestion.dose ? (
<>
<p className="text-sm text-sky-800 mb-3">
Vorschlag: <span className="font-bold">{suggestion.dose}mg</span> (statt {suggestion.originalDose}mg) um <span className="font-bold">{suggestion.time}</span>.
{t.suggestion}: <span className="font-bold">{suggestion.dose}{t.mg}</span> ({t.instead} {suggestion.originalDose}{t.mg}) {t.at} <span className="font-bold">{suggestion.time}</span>.
</p>
<button
onClick={onApplySuggestion}
className="w-full bg-sky-600 text-white py-2 rounded-md hover:bg-sky-700 text-sm"
>
Vorschlag als Abweichung übernehmen
{t.applySuggestion}
</button>
</>
) : (
@@ -23,6 +23,4 @@ const SuggestionPanel = ({ suggestion, onApplySuggestion }) => {
)}
</div>
);
};
export default SuggestionPanel;
};export default SuggestionPanel;

View File

@@ -67,7 +67,7 @@ const TimeInput = ({ value, onChange }) => {
<div className="absolute top-full mt-2 z-10 bg-white p-4 rounded-lg shadow-xl border w-64">
<div className="text-center text-lg font-bold mb-3">{value}</div>
<div>
<div className="mb-2"><span className="font-semibold">Stunde:</span></div>
<div className="mb-2"><span className="font-semibold">Hour:</span></div>
<div className="grid grid-cols-6 gap-1">
{[...Array(24).keys()].map(h => (
<button
@@ -98,7 +98,7 @@ const TimeInput = ({ value, onChange }) => {
onClick={() => setIsPickerOpen(false)}
className="mt-4 w-full bg-gray-600 text-white py-1 rounded-md text-sm"
>
Schließen
Close
</button>
</div>
)}

View File

@@ -1,19 +1,19 @@
// --- Constants ---
// Application constants
export const LOCAL_STORAGE_KEY = 'medPlanAssistantState_v5';
export const LDX_TO_DAMPH_CONVERSION_FACTOR = 0.2948;
// --- Default State ---
// Default application state
export const getDefaultState = () => ({
pkParams: {
damph: { halfLife: '11' },
ldx: { halfLife: '0.8', absorptionRate: '1.5' },
},
doses: [
{ time: '06:30', dose: '25', label: 'Morgens' },
{ time: '12:30', dose: '10', label: 'Mittags' },
{ time: '17:00', dose: '10', label: 'Nachmittags' },
{ time: '21:00', dose: '10', label: 'Abends' },
{ time: '01:00', dose: '0', label: 'Nachts' },
{ time: '06:30', dose: '25', label: 'morning' },
{ time: '12:30', dose: '10', label: 'midday' },
{ time: '17:00', dose: '10', label: 'afternoon' },
{ time: '21:00', dose: '10', label: 'evening' },
{ time: '01:00', dose: '0', label: 'night' },
],
steadyStateConfig: { daysOnMedication: '7' },
therapeuticRange: { min: '11.5', max: '14' },

24
src/hooks/useLanguage.js Normal file
View File

@@ -0,0 +1,24 @@
import React from 'react';
import { translations, getInitialLanguage } from '../locales/index.js';
export const useLanguage = () => {
const [currentLanguage, setCurrentLanguage] = React.useState(getInitialLanguage);
// Get current translations
const t = translations[currentLanguage] || translations.en;
// Change language and save to localStorage
const changeLanguage = (lang) => {
if (translations[lang]) {
setCurrentLanguage(lang);
localStorage.setItem('medPlanAssistant_language', lang);
}
};
return {
currentLanguage,
changeLanguage,
t,
availableLanguages: Object.keys(translations)
};
};

77
src/locales/de.js Normal file
View File

@@ -0,0 +1,77 @@
// German translations
export const de = {
// Header
appTitle: "Medikationsplan-Assistent",
appSubtitle: "Simulation für Lisdexamfetamin (LDX) und d-Amphetamin (d-amph)",
// Chart view buttons
dAmphetamine: "d-Amphetamin",
lisdexamfetamine: "Lisdexamfetamin",
both: "Beide",
// Language selector
language: "Sprache",
english: "English",
german: "Deutsch",
// Dose Schedule
myPlan: "Mein Plan",
morning: "Morgens",
midday: "Mittags",
afternoon: "Nachmittags",
evening: "Abends",
night: "Nachts",
// Deviations
deviationsFromPlan: "Abweichungen vom Plan",
addDeviation: "Abweichung hinzufügen",
day: "Tag",
additional: "Zusätzlich",
additionalTooltip: "Markiere dies, wenn es eine zusätzliche Dosis war anstatt eines Ersatzes für eine geplante.",
// Suggestions
whatIf: "Was wäre wenn?",
suggestion: "Vorschlag",
instead: "statt",
at: "um",
applySuggestion: "Vorschlag als Abweichung übernehmen",
noSignificantCorrection: "Keine signifikante Korrektur notwendig.",
noSuitableNextDose: "Keine passende nächste Dosis für Korrektur gefunden.",
// Chart
concentration: "Konzentration (ng/ml)",
hour: "h",
min: "Min",
max: "Max",
// Settings
advancedSettings: "Erweiterte Einstellungen",
show24hTimeAxis: "24h-Zeitachse anzeigen",
simulationDuration: "Simulationsdauer",
days: "Tage",
displayedDays: "Angezeigte Tage",
yAxisRange: "Y-Achsen-Bereich",
auto: "Auto",
therapeuticRange: "Therapeutischer Bereich",
dAmphetamineParameters: "d-Amphetamin Parameter",
halfLife: "Halbwertszeit",
hours: "h",
lisdexamfetamineParameters: "Lisdexamfetamin Parameter",
conversionHalfLife: "Umwandlungs-Halbwertszeit",
absorptionRate: "Absorptionsrate",
faster: "(schneller >)",
resetAllSettings: "Alle Einstellungen zurücksetzen",
// Units
mg: "mg",
ngml: "ng/ml",
// Reset confirmation
resetConfirmation: "Bist du sicher, dass du alle Einstellungen auf die Standardwerte zurücksetzen möchtest? Dies kann nicht rückgängig gemacht werden.",
// Footer disclaimer
importantNote: "Wichtiger Hinweis",
disclaimer: "Dieses Tool dient ausschließlich zu Illustrations- und Informationszwecken. Es ist kein medizinisches Gerät und ersetzt nicht die Beratung durch einen Arzt oder Apotheker. Alle Berechnungen sind Simulationen, die auf allgemeinen pharmakokinetischen Modellen basieren und von individuellen Faktoren erheblich abweichen können. Bitte konsultiere deinen behandelnden Arzt, bevor du Anpassungen an deiner Medikation vornimmst."
};
export default de;

77
src/locales/en.js Normal file
View File

@@ -0,0 +1,77 @@
// English translations
export const en = {
// App header and navigation
appTitle: "Medication Plan Assistant",
appSubtitle: "Simulation for Lisdexamfetamine (LDX) and d-Amphetamine (d-amph)",
// Chart view buttons
dAmphetamine: "d-Amphetamine",
lisdexamfetamine: "Lisdexamfetamine",
both: "Both",
// Language selector
language: "Language",
english: "English",
german: "Deutsch",
// Dose Schedule
myPlan: "My Plan",
morning: "Morning",
midday: "Midday",
afternoon: "Afternoon",
evening: "Evening",
night: "Night",
// Deviations
deviationsFromPlan: "Deviations from Plan",
addDeviation: "Add Deviation",
day: "Day",
additional: "Additional",
additionalTooltip: "Mark this if it was an extra dose instead of a replacement for a planned one.",
// Suggestions
whatIf: "What if?",
suggestion: "Suggestion",
instead: "instead",
at: "at",
applySuggestion: "Apply suggestion as deviation",
noSignificantCorrection: "No significant correction necessary.",
noSuitableNextDose: "No suitable next dose found for correction.",
// Chart
concentration: "Concentration (ng/ml)",
hour: "h",
min: "Min",
max: "Max",
// Settings
advancedSettings: "Advanced Settings",
show24hTimeAxis: "Show 24h time axis",
simulationDuration: "Simulation Duration",
days: "Days",
displayedDays: "Displayed Days",
yAxisRange: "Y-Axis Range",
auto: "Auto",
therapeuticRange: "Therapeutic Range",
dAmphetamineParameters: "d-Amphetamine Parameters",
halfLife: "Half-life",
hours: "h",
lisdexamfetamineParameters: "Lisdexamfetamine Parameters",
conversionHalfLife: "Conversion Half-life",
absorptionRate: "Absorption Rate",
faster: "(faster >)",
resetAllSettings: "Reset All Settings",
// Units
mg: "mg",
ngml: "ng/ml",
// Reset confirmation
resetConfirmation: "Are you sure you want to reset all settings to default values? This cannot be undone.",
// Footer disclaimer
importantNote: "Important Notice",
disclaimer: "This tool is for illustration and information purposes only. It is not a medical device and does not replace consultation with a doctor or pharmacist. All calculations are simulations based on general pharmacokinetic models and may differ significantly from individual factors. Please consult your treating physician before making adjustments to your medication."
};
export default en;

24
src/locales/index.js Normal file
View File

@@ -0,0 +1,24 @@
import en from './en.js';
import de from './de.js';
export const translations = {
en,
de
};
// Get browser language preference
export const getBrowserLanguage = () => {
const browserLang = navigator.language || navigator.userLanguage;
return browserLang.startsWith('de') ? 'de' : 'en';
};
// Get stored language or fall back to browser preference or English
export const getInitialLanguage = () => {
const stored = localStorage.getItem('medPlanAssistant_language');
if (stored && translations[stored]) {
return stored;
}
return getBrowserLanguage();
};
export default translations;