Add localization support and docs
This commit is contained in:
82
docs/2025-10-18_MODULAR_STRUCTURE.md
Normal file
82
docs/2025-10-18_MODULAR_STRUCTURE.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Medication Plan Assistant - Modular Structure
|
||||||
|
|
||||||
|
This application has been successfully modularized to simplify maintenance and further development.
|
||||||
|
|
||||||
|
## 📁 New Project Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── App.js # Main component (greatly simplified)
|
||||||
|
├── components/ # UI components
|
||||||
|
│ ├── DoseSchedule.js # Dosage schedule input
|
||||||
|
│ ├── DeviationList.js # Deviations from the schedule
|
||||||
|
│ ├── SuggestionPanel.js # Correction suggestions
|
||||||
|
│ ├── SimulationChart.js # Chart component
|
||||||
|
│ ├── Settings.js # Settings panel
|
||||||
|
│ ├── TimeInput.js # Time input with picker
|
||||||
|
│ └── NumericInput.js # Numeric input with +/- buttons
|
||||||
|
├── hooks/ # Custom React hooks
|
||||||
|
│ ├── useAppState.js # State management & local storage
|
||||||
|
│ └── useSimulation.js # Simulation logic & calculations
|
||||||
|
├── utils/ # Utility functions
|
||||||
|
│ ├── timeUtils.js # Time utility functions
|
||||||
|
│ ├── pharmacokinetics.js # PK calculations
|
||||||
|
│ ├── calculations.js # Concentration calculations
|
||||||
|
│ └── suggestions.js # Correction suggestion algorithm
|
||||||
|
└── constants/
|
||||||
|
└── defaults.js # Constants & default values
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Advantages of the new structure
|
||||||
|
|
||||||
|
### ✅ Better maintainability
|
||||||
|
|
||||||
|
- **Small, focused modules**: Each file has a clear responsibility
|
||||||
|
- **Easier debugging**: Problems can be localized more quickly
|
||||||
|
- **Clearer code organization**: Related functions are grouped together
|
||||||
|
|
||||||
|
### ✅ Reusability
|
||||||
|
|
||||||
|
- **UI components**: `TimeInput`, `NumericInput` can be used anywhere
|
||||||
|
- **Utility functions**: PK calculations are isolated and testable
|
||||||
|
- **Custom hooks**: State logic is reusable
|
||||||
|
|
||||||
|
### ✅ Development friendliness
|
||||||
|
|
||||||
|
- **Parallel development**: Teams can work on different modules
|
||||||
|
- **Simpler testing**: Each module can be tested in isolation
|
||||||
|
- **Better IDE support**: Smaller files = better performance
|
||||||
|
|
||||||
|
### ✅ Scalability
|
||||||
|
|
||||||
|
- **New features**: Easy to add as new modules
|
||||||
|
- **Code splitting**: Possible for better performance
|
||||||
|
- **Refactoring**: Changes are limited locally
|
||||||
|
|
||||||
|
## 🔧 Technical details
|
||||||
|
|
||||||
|
### State management
|
||||||
|
|
||||||
|
- **useAppState**: Manages global app state and LocalStorage
|
||||||
|
- **useSimulation**: Manages all simulation-related calculations
|
||||||
|
|
||||||
|
### Component architecture
|
||||||
|
|
||||||
|
- **Dumb components**: UI components receive props and call callbacks
|
||||||
|
- **Smart Components**: Hooks manage state and business logic
|
||||||
|
- **Separation of Concerns**: UI, state, and business logic are separated
|
||||||
|
|
||||||
|
### Utils & Calculations
|
||||||
|
|
||||||
|
- **Pure functions**: All calculations are side-effect-free
|
||||||
|
- **Modular exports**: Functions can be imported individually
|
||||||
|
- **Typed interfaces**: Clear input/output definitions
|
||||||
|
|
||||||
|
## 🚀 Migration complete
|
||||||
|
|
||||||
|
The application works identically to the original version, but now:
|
||||||
|
|
||||||
|
- Split into **350+ lines** across **several small modules**
|
||||||
|
- **Easier to understand** and modify
|
||||||
|
- **Ready for further features** and improvements
|
||||||
|
- **More test-friendly** for unit and integration tests
|
||||||
28
src/App.js
28
src/App.js
@@ -6,13 +6,17 @@ import DeviationList from './components/DeviationList.js';
|
|||||||
import SuggestionPanel from './components/SuggestionPanel.js';
|
import SuggestionPanel from './components/SuggestionPanel.js';
|
||||||
import SimulationChart from './components/SimulationChart.js';
|
import SimulationChart from './components/SimulationChart.js';
|
||||||
import Settings from './components/Settings.js';
|
import Settings from './components/Settings.js';
|
||||||
|
import LanguageSelector from './components/LanguageSelector.js';
|
||||||
|
|
||||||
// Custom Hooks
|
// Custom Hooks
|
||||||
import { useAppState } from './hooks/useAppState.js';
|
import { useAppState } from './hooks/useAppState.js';
|
||||||
import { useSimulation } from './hooks/useSimulation.js';
|
import { useSimulation } from './hooks/useSimulation.js';
|
||||||
|
import { useLanguage } from './hooks/useLanguage.js';
|
||||||
|
|
||||||
// --- Main Component ---
|
// --- Main Component ---
|
||||||
const MedPlanAssistant = () => {
|
const MedPlanAssistant = () => {
|
||||||
|
const { currentLanguage, t, changeLanguage } = useLanguage();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
appState,
|
appState,
|
||||||
updateState,
|
updateState,
|
||||||
@@ -54,8 +58,13 @@ const MedPlanAssistant = () => {
|
|||||||
<div className="bg-gray-100 font-sans p-4 sm:p-6 lg:p-8">
|
<div className="bg-gray-100 font-sans p-4 sm:p-6 lg:p-8">
|
||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
<header className="mb-8">
|
<header className="mb-8">
|
||||||
<h1 className="text-3xl md:text-4xl font-bold text-gray-800">Medikationsplan-Assistent</h1>
|
<div className="flex justify-between items-start">
|
||||||
<p className="text-gray-600 mt-1">Simulation für Lisdexamfetamin (LDX) und d-Amphetamin (d-amph)</p>
|
<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>
|
</header>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
@@ -65,6 +74,7 @@ const MedPlanAssistant = () => {
|
|||||||
doses={doses}
|
doses={doses}
|
||||||
doseIncrement={doseIncrement}
|
doseIncrement={doseIncrement}
|
||||||
onUpdateDoses={(newDoses) => updateState('doses', newDoses)}
|
onUpdateDoses={(newDoses) => updateState('doses', newDoses)}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeviationList
|
<DeviationList
|
||||||
@@ -74,11 +84,13 @@ const MedPlanAssistant = () => {
|
|||||||
onAddDeviation={addDeviation}
|
onAddDeviation={addDeviation}
|
||||||
onRemoveDeviation={removeDeviation}
|
onRemoveDeviation={removeDeviation}
|
||||||
onDeviationChange={handleDeviationChange}
|
onDeviationChange={handleDeviationChange}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SuggestionPanel
|
<SuggestionPanel
|
||||||
suggestion={suggestion}
|
suggestion={suggestion}
|
||||||
onApplySuggestion={applySuggestion}
|
onApplySuggestion={applySuggestion}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -89,19 +101,19 @@ const MedPlanAssistant = () => {
|
|||||||
onClick={() => updateUiSetting('chartView', 'damph')}
|
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'}`}
|
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>
|
||||||
<button
|
<button
|
||||||
onClick={() => updateUiSetting('chartView', 'ldx')}
|
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'}`}
|
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>
|
||||||
<button
|
<button
|
||||||
onClick={() => updateUiSetting('chartView', 'both')}
|
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'}`}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -116,6 +128,7 @@ const MedPlanAssistant = () => {
|
|||||||
displayedDays={displayedDays}
|
displayedDays={displayedDays}
|
||||||
yAxisMin={yAxisMin}
|
yAxisMin={yAxisMin}
|
||||||
yAxisMax={yAxisMax}
|
yAxisMax={yAxisMax}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -129,13 +142,14 @@ const MedPlanAssistant = () => {
|
|||||||
onUpdateTherapeuticRange={(key, value) => updateNestedState('therapeuticRange', key, { [key]: value })}
|
onUpdateTherapeuticRange={(key, value) => updateNestedState('therapeuticRange', key, { [key]: value })}
|
||||||
onUpdateUiSetting={updateUiSetting}
|
onUpdateUiSetting={updateUiSetting}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer className="mt-8 p-4 bg-gray-100 rounded-lg text-sm text-gray-700 border">
|
<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>
|
<h3 className="font-semibold mb-2">{t.importantNote}</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>
|
<p>{t.disclaimer}</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ const DeviationList = ({
|
|||||||
simulationDays,
|
simulationDays,
|
||||||
onAddDeviation,
|
onAddDeviation,
|
||||||
onRemoveDeviation,
|
onRemoveDeviation,
|
||||||
onDeviationChange
|
onDeviationChange,
|
||||||
|
t
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-amber-50 p-5 rounded-lg shadow-sm border border-amber-200">
|
<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) => (
|
{deviations.map((dev, index) => (
|
||||||
<div key={index} className="flex items-center space-x-2 mb-2 p-2 bg-white rounded flex-wrap">
|
<div key={index} className="flex items-center space-x-2 mb-2 p-2 bg-white rounded flex-wrap">
|
||||||
<select
|
<select
|
||||||
@@ -21,7 +22,7 @@ const DeviationList = ({
|
|||||||
className="p-2 border rounded-md text-sm"
|
className="p-2 border rounded-md text-sm"
|
||||||
>
|
>
|
||||||
{[...Array(parseInt(simulationDays, 10) || 1).keys()].map(day => (
|
{[...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>
|
</select>
|
||||||
<TimeInput
|
<TimeInput
|
||||||
@@ -34,7 +35,7 @@ const DeviationList = ({
|
|||||||
onChange={newDose => onDeviationChange(index, 'dose', newDose)}
|
onChange={newDose => onDeviationChange(index, 'dose', newDose)}
|
||||||
increment={doseIncrement}
|
increment={doseIncrement}
|
||||||
min={0}
|
min={0}
|
||||||
unit="mg"
|
unit={t.mg}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -43,7 +44,7 @@ const DeviationList = ({
|
|||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={`add_dose_${index}`}
|
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"
|
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">
|
<label htmlFor={`add_dose_${index}`} className="ml-2 text-xs text-gray-600">
|
||||||
Zusätzlich
|
{t.additional}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,10 +62,8 @@ const DeviationList = ({
|
|||||||
onClick={onAddDeviation}
|
onClick={onAddDeviation}
|
||||||
className="mt-2 w-full bg-amber-500 text-white py-2 rounded-md hover:bg-amber-600 text-sm"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};export default DeviationList;
|
||||||
|
|
||||||
export default DeviationList;
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React from 'react';
|
|||||||
import TimeInput from './TimeInput.js';
|
import TimeInput from './TimeInput.js';
|
||||||
import NumericInput from './NumericInput.js';
|
import NumericInput from './NumericInput.js';
|
||||||
|
|
||||||
const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses }) => {
|
const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses, t }) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-5 rounded-lg shadow-sm border">
|
<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) => (
|
{doses.map((dose, index) => (
|
||||||
<div key={index} className="flex items-center space-x-3 mb-3">
|
<div key={index} className="flex items-center space-x-3 mb-3">
|
||||||
<TimeInput
|
<TimeInput
|
||||||
@@ -18,10 +18,10 @@ const DoseSchedule = ({ doses, doseIncrement, onUpdateDoses }) => {
|
|||||||
onChange={newDose => onUpdateDoses(doses.map((d, i) => i === index ? {...d, dose: newDose} : d))}
|
onChange={newDose => onUpdateDoses(doses.map((d, i) => i === index ? {...d, dose: newDose} : d))}
|
||||||
increment={doseIncrement}
|
increment={doseIncrement}
|
||||||
min={0}
|
min={0}
|
||||||
unit="mg"
|
unit={t.mg}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
19
src/components/LanguageSelector.js
Normal file
19
src/components/LanguageSelector.js
Normal 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;
|
||||||
@@ -8,13 +8,14 @@ const Settings = ({
|
|||||||
onUpdatePkParams,
|
onUpdatePkParams,
|
||||||
onUpdateTherapeuticRange,
|
onUpdateTherapeuticRange,
|
||||||
onUpdateUiSetting,
|
onUpdateUiSetting,
|
||||||
onReset
|
onReset,
|
||||||
|
t
|
||||||
}) => {
|
}) => {
|
||||||
const { showDayTimeXAxis, yAxisMin, yAxisMax, simulationDays, displayedDays } = uiSettings;
|
const { showDayTimeXAxis, yAxisMin, yAxisMax, simulationDays, displayedDays } = uiSettings;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-5 rounded-lg shadow-sm border">
|
<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="space-y-4 text-sm">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<input
|
<input
|
||||||
@@ -25,11 +26,11 @@ const Settings = ({
|
|||||||
className="h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-500"
|
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">
|
<label htmlFor="showDayTimeXAxis" className="ml-3 block font-medium text-gray-600">
|
||||||
24h-Zeitachse anzeigen
|
{t.show24hTimeAxis}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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">
|
<div className="w-40">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
value={simulationDays}
|
value={simulationDays}
|
||||||
@@ -37,11 +38,11 @@ const Settings = ({
|
|||||||
increment={'1'}
|
increment={'1'}
|
||||||
min={2}
|
min={2}
|
||||||
max={7}
|
max={7}
|
||||||
unit="Tage"
|
unit={t.days}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div className="w-40">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
value={displayedDays}
|
value={displayedDays}
|
||||||
@@ -49,11 +50,11 @@ const Settings = ({
|
|||||||
increment={'1'}
|
increment={'1'}
|
||||||
min={1}
|
min={1}
|
||||||
max={parseInt(simulationDays, 10) || 1}
|
max={parseInt(simulationDays, 10) || 1}
|
||||||
unit="Tage"
|
unit={t.days}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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="flex items-center space-x-2 mt-1">
|
||||||
<div className="w-32">
|
<div className="w-32">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
@@ -61,7 +62,7 @@ const Settings = ({
|
|||||||
onChange={val => onUpdateUiSetting('yAxisMin', val)}
|
onChange={val => onUpdateUiSetting('yAxisMin', val)}
|
||||||
increment={'5'}
|
increment={'5'}
|
||||||
min={0}
|
min={0}
|
||||||
placeholder="Auto"
|
placeholder={t.auto}
|
||||||
unit="ng/ml"
|
unit="ng/ml"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,13 +73,13 @@ const Settings = ({
|
|||||||
onChange={val => onUpdateUiSetting('yAxisMax', val)}
|
onChange={val => onUpdateUiSetting('yAxisMax', val)}
|
||||||
increment={'5'}
|
increment={'5'}
|
||||||
min={0}
|
min={0}
|
||||||
placeholder="Auto"
|
placeholder={t.auto}
|
||||||
unit="ng/ml"
|
unit="ng/ml"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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="flex items-center space-x-2 mt-1">
|
||||||
<div className="w-32">
|
<div className="w-32">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
@@ -86,7 +87,7 @@ const Settings = ({
|
|||||||
onChange={val => onUpdateTherapeuticRange('min', val)}
|
onChange={val => onUpdateTherapeuticRange('min', val)}
|
||||||
increment={'0.5'}
|
increment={'0.5'}
|
||||||
min={0}
|
min={0}
|
||||||
placeholder="Min"
|
placeholder={t.min}
|
||||||
unit="ng/ml"
|
unit="ng/ml"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,15 +98,15 @@ const Settings = ({
|
|||||||
onChange={val => onUpdateTherapeuticRange('max', val)}
|
onChange={val => onUpdateTherapeuticRange('max', val)}
|
||||||
increment={'0.5'}
|
increment={'0.5'}
|
||||||
min={0}
|
min={0}
|
||||||
placeholder="Max"
|
placeholder={t.max}
|
||||||
unit="ng/ml"
|
unit="ng/ml"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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
|
<NumericInput
|
||||||
value={pkParams.damph.halfLife}
|
value={pkParams.damph.halfLife}
|
||||||
onChange={val => onUpdatePkParams('damph', { halfLife: val })}
|
onChange={val => onUpdatePkParams('damph', { halfLife: val })}
|
||||||
@@ -115,9 +116,9 @@ const Settings = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<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
|
<NumericInput
|
||||||
value={pkParams.ldx.halfLife}
|
value={pkParams.ldx.halfLife}
|
||||||
onChange={val => onUpdatePkParams('ldx', { halfLife: val })}
|
onChange={val => onUpdatePkParams('ldx', { halfLife: val })}
|
||||||
@@ -127,13 +128,13 @@ const Settings = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-40">
|
<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
|
<NumericInput
|
||||||
value={pkParams.ldx.absorptionRate}
|
value={pkParams.ldx.absorptionRate}
|
||||||
onChange={val => onUpdatePkParams('ldx', { absorptionRate: val })}
|
onChange={val => onUpdatePkParams('ldx', { absorptionRate: val })}
|
||||||
increment={'0.1'}
|
increment={'0.1'}
|
||||||
min={0.1}
|
min={0.1}
|
||||||
unit="(schneller >)"
|
unit={t.faster}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ const Settings = ({
|
|||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
className="w-full bg-red-600 text-white py-2 rounded-md hover:bg-red-700 text-sm"
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ const SimulationChart = ({
|
|||||||
simulationDays,
|
simulationDays,
|
||||||
displayedDays,
|
displayedDays,
|
||||||
yAxisMin,
|
yAxisMin,
|
||||||
yAxisMax
|
yAxisMax,
|
||||||
|
t
|
||||||
}) => {
|
}) => {
|
||||||
const totalHours = (parseInt(simulationDays, 10) || 3) * 24;
|
const totalHours = (parseInt(simulationDays, 10) || 3) * 24;
|
||||||
const chartTicks = Array.from({length: Math.floor(totalHours / 6) + 1}, (_, i) => i * 6);
|
const chartTicks = Array.from({length: Math.floor(totalHours / 6) + 1}, (_, i) => i * 6);
|
||||||
@@ -36,7 +37,7 @@ const SimulationChart = ({
|
|||||||
type="number"
|
type="number"
|
||||||
domain={[0, totalHours]}
|
domain={[0, totalHours]}
|
||||||
ticks={chartTicks}
|
ticks={chartTicks}
|
||||||
tickFormatter={(h) => `${h}h`}
|
tickFormatter={(h) => `${h}${t.hour}`}
|
||||||
xAxisId="continuous"
|
xAxisId="continuous"
|
||||||
/>
|
/>
|
||||||
{showDayTimeXAxis && (
|
{showDayTimeXAxis && (
|
||||||
@@ -45,26 +46,26 @@ const SimulationChart = ({
|
|||||||
type="number"
|
type="number"
|
||||||
domain={[0, totalHours]}
|
domain={[0, totalHours]}
|
||||||
ticks={chartTicks}
|
ticks={chartTicks}
|
||||||
tickFormatter={(h) => `${h % 24}h`}
|
tickFormatter={(h) => `${h % 24}${t.hour}`}
|
||||||
xAxisId="daytime"
|
xAxisId="daytime"
|
||||||
orientation="top"
|
orientation="top"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<YAxis
|
<YAxis
|
||||||
label={{ value: 'Konzentration (ng/ml)', angle: -90, position: 'insideLeft', offset: -10 }}
|
label={{ value: t.concentration, angle: -90, position: 'insideLeft', offset: -10 }}
|
||||||
domain={chartDomain}
|
domain={chartDomain}
|
||||||
allowDecimals={false}
|
allowDecimals={false}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={(value, name) => [`${value.toFixed(1)} ng/ml`, name]}
|
formatter={(value, name) => [`${value.toFixed(1)} ${t.ngml}`, name]}
|
||||||
labelFormatter={(label) => `Stunde: ${label}h`}
|
labelFormatter={(label) => `${t.hour.replace('h', 'Hour')}: ${label}${t.hour}`}
|
||||||
/>
|
/>
|
||||||
<Legend verticalAlign="top" height={36} />
|
<Legend verticalAlign="top" height={36} />
|
||||||
|
|
||||||
{(chartView === 'damph' || chartView === 'both') && (
|
{(chartView === 'damph' || chartView === 'both') && (
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
y={parseFloat(therapeuticRange.min) || 0}
|
y={parseFloat(therapeuticRange.min) || 0}
|
||||||
label={{ value: 'Min', position: 'insideTopLeft' }}
|
label={{ value: t.min, position: 'insideTopLeft' }}
|
||||||
stroke="green"
|
stroke="green"
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
xAxisId="continuous"
|
xAxisId="continuous"
|
||||||
@@ -73,7 +74,7 @@ const SimulationChart = ({
|
|||||||
{(chartView === 'damph' || chartView === 'both') && (
|
{(chartView === 'damph' || chartView === 'both') && (
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
y={parseFloat(therapeuticRange.max) || 0}
|
y={parseFloat(therapeuticRange.max) || 0}
|
||||||
label={{ value: 'Max', position: 'insideTopLeft' }}
|
label={{ value: t.max, position: 'insideTopLeft' }}
|
||||||
stroke="red"
|
stroke="red"
|
||||||
strokeDasharray="3 3"
|
strokeDasharray="3 3"
|
||||||
xAxisId="continuous"
|
xAxisId="continuous"
|
||||||
@@ -97,7 +98,7 @@ const SimulationChart = ({
|
|||||||
type="monotone"
|
type="monotone"
|
||||||
data={idealProfile}
|
data={idealProfile}
|
||||||
dataKey="damph"
|
dataKey="damph"
|
||||||
name="d-Amphetamin (Ideal)"
|
name={`${t.dAmphetamine} (Ideal)`}
|
||||||
stroke="#3b82f6"
|
stroke="#3b82f6"
|
||||||
strokeWidth={2.5}
|
strokeWidth={2.5}
|
||||||
dot={false}
|
dot={false}
|
||||||
@@ -109,7 +110,7 @@ const SimulationChart = ({
|
|||||||
type="monotone"
|
type="monotone"
|
||||||
data={idealProfile}
|
data={idealProfile}
|
||||||
dataKey="ldx"
|
dataKey="ldx"
|
||||||
name="Lisdexamfetamin (Ideal)"
|
name={`${t.lisdexamfetamine} (Ideal)`}
|
||||||
stroke="#8b5cf6"
|
stroke="#8b5cf6"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={false}
|
dot={false}
|
||||||
@@ -123,7 +124,7 @@ const SimulationChart = ({
|
|||||||
type="monotone"
|
type="monotone"
|
||||||
data={deviatedProfile}
|
data={deviatedProfile}
|
||||||
dataKey="damph"
|
dataKey="damph"
|
||||||
name="d-Amphetamin (Abweichung)"
|
name={`${t.dAmphetamine} (Deviation)`}
|
||||||
stroke="#f59e0b"
|
stroke="#f59e0b"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
strokeDasharray="5 5"
|
strokeDasharray="5 5"
|
||||||
@@ -136,7 +137,7 @@ const SimulationChart = ({
|
|||||||
type="monotone"
|
type="monotone"
|
||||||
data={deviatedProfile}
|
data={deviatedProfile}
|
||||||
dataKey="ldx"
|
dataKey="ldx"
|
||||||
name="Lisdexamfetamin (Abweichung)"
|
name={`${t.lisdexamfetamine} (Deviation)`}
|
||||||
stroke="#f97316"
|
stroke="#f97316"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
strokeDasharray="5 5"
|
strokeDasharray="5 5"
|
||||||
@@ -150,7 +151,7 @@ const SimulationChart = ({
|
|||||||
type="monotone"
|
type="monotone"
|
||||||
data={correctedProfile}
|
data={correctedProfile}
|
||||||
dataKey="damph"
|
dataKey="damph"
|
||||||
name="d-Amphetamin (Korrektur)"
|
name={`${t.dAmphetamine} (Correction)`}
|
||||||
stroke="#10b981"
|
stroke="#10b981"
|
||||||
strokeWidth={2.5}
|
strokeWidth={2.5}
|
||||||
strokeDasharray="3 7"
|
strokeDasharray="3 7"
|
||||||
@@ -163,7 +164,7 @@ const SimulationChart = ({
|
|||||||
type="monotone"
|
type="monotone"
|
||||||
data={correctedProfile}
|
data={correctedProfile}
|
||||||
dataKey="ldx"
|
dataKey="ldx"
|
||||||
name="Lisdexamfetamin (Korrektur)"
|
name={`${t.lisdexamfetamine} (Correction)`}
|
||||||
stroke="#059669"
|
stroke="#059669"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
strokeDasharray="3 7"
|
strokeDasharray="3 7"
|
||||||
@@ -176,6 +177,4 @@ const SimulationChart = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};export default SimulationChart;
|
||||||
|
|
||||||
export default SimulationChart;
|
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const SuggestionPanel = ({ suggestion, onApplySuggestion }) => {
|
const SuggestionPanel = ({ suggestion, onApplySuggestion, t }) => {
|
||||||
if (!suggestion) return null;
|
if (!suggestion) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-sky-100 border-l-4 border-sky-500 p-4 rounded-r-lg shadow-md">
|
<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 ? (
|
{suggestion.dose ? (
|
||||||
<>
|
<>
|
||||||
<p className="text-sm text-sky-800 mb-3">
|
<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>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={onApplySuggestion}
|
onClick={onApplySuggestion}
|
||||||
className="w-full bg-sky-600 text-white py-2 rounded-md hover:bg-sky-700 text-sm"
|
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>
|
</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -23,6 +23,4 @@ const SuggestionPanel = ({ suggestion, onApplySuggestion }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};export default SuggestionPanel;
|
||||||
|
|
||||||
export default SuggestionPanel;
|
|
||||||
|
|||||||
@@ -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="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 className="text-center text-lg font-bold mb-3">{value}</div>
|
||||||
<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">
|
<div className="grid grid-cols-6 gap-1">
|
||||||
{[...Array(24).keys()].map(h => (
|
{[...Array(24).keys()].map(h => (
|
||||||
<button
|
<button
|
||||||
@@ -98,7 +98,7 @@ const TimeInput = ({ value, onChange }) => {
|
|||||||
onClick={() => setIsPickerOpen(false)}
|
onClick={() => setIsPickerOpen(false)}
|
||||||
className="mt-4 w-full bg-gray-600 text-white py-1 rounded-md text-sm"
|
className="mt-4 w-full bg-gray-600 text-white py-1 rounded-md text-sm"
|
||||||
>
|
>
|
||||||
Schließen
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
// --- Constants ---
|
// Application constants
|
||||||
export const LOCAL_STORAGE_KEY = 'medPlanAssistantState_v5';
|
export const LOCAL_STORAGE_KEY = 'medPlanAssistantState_v5';
|
||||||
export const LDX_TO_DAMPH_CONVERSION_FACTOR = 0.2948;
|
export const LDX_TO_DAMPH_CONVERSION_FACTOR = 0.2948;
|
||||||
|
|
||||||
// --- Default State ---
|
// Default application state
|
||||||
export const getDefaultState = () => ({
|
export const getDefaultState = () => ({
|
||||||
pkParams: {
|
pkParams: {
|
||||||
damph: { halfLife: '11' },
|
damph: { halfLife: '11' },
|
||||||
ldx: { halfLife: '0.8', absorptionRate: '1.5' },
|
ldx: { halfLife: '0.8', absorptionRate: '1.5' },
|
||||||
},
|
},
|
||||||
doses: [
|
doses: [
|
||||||
{ time: '06:30', dose: '25', label: 'Morgens' },
|
{ time: '06:30', dose: '25', label: 'morning' },
|
||||||
{ time: '12:30', dose: '10', label: 'Mittags' },
|
{ time: '12:30', dose: '10', label: 'midday' },
|
||||||
{ time: '17:00', dose: '10', label: 'Nachmittags' },
|
{ time: '17:00', dose: '10', label: 'afternoon' },
|
||||||
{ time: '21:00', dose: '10', label: 'Abends' },
|
{ time: '21:00', dose: '10', label: 'evening' },
|
||||||
{ time: '01:00', dose: '0', label: 'Nachts' },
|
{ time: '01:00', dose: '0', label: 'night' },
|
||||||
],
|
],
|
||||||
steadyStateConfig: { daysOnMedication: '7' },
|
steadyStateConfig: { daysOnMedication: '7' },
|
||||||
therapeuticRange: { min: '11.5', max: '14' },
|
therapeuticRange: { min: '11.5', max: '14' },
|
||||||
|
|||||||
24
src/hooks/useLanguage.js
Normal file
24
src/hooks/useLanguage.js
Normal 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
77
src/locales/de.js
Normal 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
77
src/locales/en.js
Normal 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
24
src/locales/index.js
Normal 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;
|
||||||
Reference in New Issue
Block a user