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

@@ -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>
)}