; o
aria-labelledby="modal-title"
onMouseDown={(event) => event.stopPropagation()}
>
-
+
{MODAL_ICON[type]}
- Guided discovery
+ {dict.modalGuided}
{title}
{type === "quiz" ? (
-
Which statement best describes the {organName.toLowerCase()}?
-
It plays a specialized role in maintaining the body
-
It works completely independently
-
It is active only during sleep
+
{t("quizQuestion", { name })}
+
{dict.quizOpt1}
+
{dict.quizOpt2}
+
{dict.quizOpt3}
) : type === "system" ? (
<>
- {organ.location}. Trace how the {organName.toLowerCase()} connects to the rest of the body.
+ {t("systemIntro", { location: organ.location, name })}
{/* Shown whole rather than cropped into the circular demo — the
point of this view is the figure and its vessels. */}
-
+
-
System {organ.system}
-
Primary role {organ.function}
-
Blood supply {organ.bloodSupply}
+
{dict.modalSystemLabel} {organ.system}
+
{dict.modalPrimaryRole} {organ.function}
+
{dict.modalBlood} {organ.bloodSupply}
- Continue exploring
+ {dict.continueExploring}
>
) : (
<>
- Follow the highlighted structures, rotate the specimen, and connect form with function. This short study moment is designed to build a durable mental model.
-
- Continue exploring
+ {dict.lessonBody}
+
+ {dict.continueExploring}
>
)}
diff --git a/app/components/LanguageSwitcher.tsx b/app/components/LanguageSwitcher.tsx
new file mode 100644
index 0000000..52e09f7
--- /dev/null
+++ b/app/components/LanguageSwitcher.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import { useEffect, useRef, useState } from "react";
+import { Check, ChevronDown, Globe } from "lucide-react";
+import { LOCALES, LOCALE_META } from "../lib/i18n/config";
+import { useI18n } from "../lib/i18n/context";
+
+export function LanguageSwitcher() {
+ const { locale, setLocale, dict } = useI18n();
+ const [open, setOpen] = useState(false);
+ const ref = useRef
(null);
+
+ useEffect(() => {
+ if (!open) return;
+ const onPointerDown = (event: PointerEvent) => {
+ if (ref.current && !ref.current.contains(event.target as Node)) setOpen(false);
+ };
+ const onKey = (event: KeyboardEvent) => {
+ if (event.key === "Escape") setOpen(false);
+ };
+ window.addEventListener("pointerdown", onPointerDown);
+ window.addEventListener("keydown", onKey);
+ return () => {
+ window.removeEventListener("pointerdown", onPointerDown);
+ window.removeEventListener("keydown", onKey);
+ };
+ }, [open]);
+
+ return (
+
+
setOpen((value) => !value)}
+ >
+
+ {LOCALE_META[locale].short}
+
+
+ {open && (
+
+ {LOCALES.map((code) => (
+
+ {
+ setLocale(code);
+ setOpen(false);
+ }}
+ >
+ {LOCALE_META[code].label}
+ {code === locale && }
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/app/components/OrganViewer.tsx b/app/components/OrganViewer.tsx
index 35a02f4..2718e98 100644
--- a/app/components/OrganViewer.tsx
+++ b/app/components/OrganViewer.tsx
@@ -14,6 +14,7 @@ import {
} from "lucide-react";
import type { Hotspot, Organ } from "../lib/anatomy-data";
import type { AnatomyViewer } from "../lib/three/viewer";
+import { useI18n } from "../lib/i18n/context";
type Props = {
organ: Organ;
@@ -24,6 +25,7 @@ type Props = {
};
export function OrganViewer({ organ, autoRotate, onAutoRotate, compare, onCompare }: Props) {
+ const { t, dict } = useI18n();
const mountRef = useRef(null);
const viewerRef = useRef(null);
const organRef = useRef(organ);
@@ -112,21 +114,21 @@ export function OrganViewer({ organ, autoRotate, onAutoRotate, compare, onCompar
};
const tools = [
- { id: "rotate", label: "Rotate", icon: RotateCcw },
- { id: "zoom", label: "Zoom", icon: Search },
- { id: "isolate", label: "Isolate", icon: CircleDashed },
- { id: "section", label: "Cross-section", icon: ScanLine },
- { id: "layers", label: "Layers", icon: Layers3 },
- { id: "compare", label: "Compare", icon: Box },
- { id: "reset", label: "Reset", icon: RotateCcw },
+ { id: "rotate", label: dict.toolRotate, icon: RotateCcw },
+ { id: "zoom", label: dict.toolZoom, icon: Search },
+ { id: "isolate", label: dict.toolIsolate, icon: CircleDashed },
+ { id: "section", label: dict.toolSection, icon: ScanLine },
+ { id: "layers", label: dict.toolLayers, icon: Layers3 },
+ { id: "compare", label: dict.toolCompare, icon: Box },
+ { id: "reset", label: dict.toolReset, icon: RotateCcw },
];
return (
-
+
-
+
{tools.map(({ id, label, icon: Icon }) => (
-
- Tip
- Drag to rotate Scroll to zoom Click a dot to learn more
+
{selected && (
-
viewerRef.current?.clearSelection()} aria-label="Close">
+ viewerRef.current?.clearSelection()} aria-label={dict.calloutClose}>
{selected.label}
@@ -169,18 +171,18 @@ export function OrganViewer({ organ, autoRotate, onAutoRotate, compare, onCompar
{loading && slowLoad && (
-
Preparing the {organ.name.toLowerCase()}
+
{t("loaderPreparing", { name: organ.name })}
{Math.max(8, Math.round(progress * 100))}%
)}
onAutoRotate(!autoRotate)} aria-pressed={autoRotate}>
- Auto rotate
+ {dict.autoRotate}
- 3D specimen · click a dot to explore
+ {dict.viewCaption}
{organ.scientificName}
diff --git a/app/globals.css b/app/globals.css
index e7a866b..71129c1 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -16,6 +16,23 @@
html { background: #e9ddd0; }
+/* Arabic renders right-to-left; the Latin display fonts have no Arabic glyphs,
+ so swap in an Arabic-capable stack for the whole document when dir=rtl. */
+:root[dir="rtl"] {
+ --font-sans: "Noto Sans Arabic", "Segoe UI", "Geeza Pro", Tahoma, sans-serif;
+ --font-serif: "Noto Naskh Arabic", "Amiri", "Segoe UI", Georgia, serif;
+}
+
+/* Chevrons/arrows that point along the reading direction should mirror in RTL. */
+[dir="rtl"] :is(
+ .lesson-button,
+ .view-all,
+ .action-grid button,
+ .learning-cards article > button:not(.organ-card-image)
+) svg {
+ transform: scaleX(-1);
+}
+
body {
margin: 0;
min-width: 320px;
@@ -58,7 +75,7 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
padding: 0;
background: transparent;
cursor: pointer;
- text-align: left;
+ text-align: start;
}
.brand strong {
@@ -67,7 +84,7 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
white-space: nowrap;
}
-.brand sup { color: var(--coral); font: 400 14px var(--font-sans), sans-serif; padding-left: 3px; }
+.brand sup { color: var(--coral); font: 400 14px var(--font-sans), sans-serif; padding-inline-start: 3px; }
.brand em, .info-panel em, .learning-cards em, .learning-modal > em {
color: var(--lavender);
font-family: "Comic Sans MS", "Bradley Hand", cursive;
@@ -134,6 +151,59 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
}
.mobile-library-trigger { display: none; }
+.topbar-end { justify-self: end; display: flex; align-items: center; gap: 12px; }
+
+.lang-switcher { position: relative; }
+.lang-trigger {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ height: 38px;
+ padding: 0 11px;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ background: rgba(255,255,255,.28);
+ color: var(--ink);
+ cursor: pointer;
+ font: 500 12px var(--font-serif), serif;
+}
+.lang-trigger:hover { background: rgba(255,255,255,.5); }
+.lang-trigger span { letter-spacing: .04em; }
+.lang-menu {
+ position: absolute;
+ z-index: 60;
+ top: calc(100% + 8px);
+ inset-inline-end: 0;
+ min-width: 180px;
+ margin: 0;
+ padding: 6px;
+ list-style: none;
+ border: 1px solid var(--line);
+ border-radius: 14px;
+ background: #fffaf2;
+ box-shadow: 0 18px 44px rgba(63,42,28,.18);
+ animation: modal-in .22s cubic-bezier(.2,.85,.3,1);
+}
+.lang-menu li { margin: 0; }
+.lang-menu button {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 9px 11px;
+ border: 0;
+ border-radius: 9px;
+ background: transparent;
+ cursor: pointer;
+ text-align: start;
+ color: var(--ink);
+ font: 500 13px var(--font-serif), serif;
+}
+.lang-menu button:hover { background: rgba(238,124,106,.08); }
+.lang-menu button.active { background: rgba(238,124,106,.1); color: var(--coral); }
+.lang-menu button svg { flex: none; }
+
.workspace {
max-width: 1720px;
height: min(760px, calc(100vh - 128px));
@@ -169,7 +239,7 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
grid-template-columns: 54px 1fr 18px;
align-items: center;
gap: 8px;
- text-align: left;
+ text-align: start;
padding: 8px 10px;
border: 1px solid transparent;
border-radius: 15px;
@@ -229,7 +299,7 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
.viewer-tools {
position: absolute;
- left: 18px;
+ inset-inline-start: 18px;
top: 50%;
z-index: 3;
transform: translateY(-50%);
@@ -250,7 +320,7 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
.tip-note {
position: absolute;
- right: 16px;
+ inset-inline-end: 16px;
top: 18px;
z-index: 2;
width: 155px;
@@ -279,7 +349,8 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
position: absolute;
top: -30px;
width: 168px;
- padding: 10px 26px 11px 12px;
+ padding-block: 10px 11px;
+ padding-inline: 12px 26px;
border: 1px solid rgba(116,89,67,.14);
border-radius: 13px;
background: rgba(255,252,247,.92);
@@ -302,10 +373,10 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
}
.hotspot-callout[data-side="right"] .callout-body:before { right: 100%; }
.hotspot-callout[data-side="left"] .callout-body:before { left: 100%; }
-.callout-body b { display: block; padding-left: 13px; font: 500 13px var(--font-serif), serif; }
-.callout-body b:before { content: ""; position: absolute; left: 12px; top: 15px; width: 7px; height: 7px; border-radius: 50%; background: var(--hotspot-color); }
-.callout-body small { display: block; margin-top: 3px; padding-left: 13px; color: var(--muted); font-size: 10px; line-height: 1.35; }
-.callout-close { position: absolute; top: 6px; right: 6px; width: 20px; height: 20px; display: grid; place-items: center; border: 0; border-radius: 50%; background: transparent; color: var(--muted); cursor: pointer; }
+.callout-body b { display: block; padding-inline-start: 13px; font: 500 13px var(--font-serif), serif; }
+.callout-body b:before { content: ""; position: absolute; inset-inline-start: 12px; top: 15px; width: 7px; height: 7px; border-radius: 50%; background: var(--hotspot-color); }
+.callout-body small { display: block; margin-top: 3px; padding-inline-start: 13px; color: var(--muted); font-size: 10px; line-height: 1.35; }
+.callout-close { position: absolute; top: 6px; inset-inline-end: 6px; width: 20px; height: 20px; display: grid; place-items: center; border: 0; border-radius: 50%; background: transparent; color: var(--muted); cursor: pointer; }
.callout-close:hover { background: rgba(116,89,67,.09); }
@keyframes callout-in { from { opacity: 0; transform: translateX(-6px) scale(.96); } }
@@ -313,7 +384,7 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
.auto-rotate {
position: absolute;
- right: 17px;
+ inset-inline-end: 17px;
bottom: 16px;
z-index: 4;
display: flex;
@@ -321,7 +392,8 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
gap: 8px;
border: 1px solid var(--line);
border-radius: 13px;
- padding: 8px 10px 8px 12px;
+ padding-block: 8px;
+ padding-inline: 12px 10px;
background: rgba(255,252,247,.88);
box-shadow: 0 5px 14px rgba(65,45,32,.08);
cursor: pointer;
@@ -330,8 +402,8 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
.switch { width: 29px; height: 17px; padding: 2px; border-radius: 99px; background: #c8c2bb; }
.switch i { display: block; width: 13px; height: 13px; border-radius: 50%; background: white; box-shadow: 0 1px 4px rgba(0,0,0,.17); }
.switch.on { background: #70a9cb; }
-.switch.on i { margin-left: 12px; }
-.view-caption { position: absolute; left: 98px; bottom: 17px; display: flex; flex-direction: column; gap: 2px; color: var(--muted); font-size: 9px; text-transform: uppercase; letter-spacing: .1em; }
+.switch.on i { margin-inline-start: 12px; }
+.view-caption { position: absolute; inset-inline-start: 98px; bottom: 17px; display: flex; flex-direction: column; gap: 2px; color: var(--muted); font-size: 9px; text-transform: uppercase; letter-spacing: .1em; }
.view-caption strong { color: var(--ink); font: 500 13px var(--font-serif), serif; text-transform: none; letter-spacing: 0; }
.model-loader { position: absolute; inset: 0; z-index: 6; display: grid; place-content: center; justify-items: center; gap: 8px; background: rgba(251,246,238,.45); backdrop-filter: blur(4px); }
@@ -389,7 +461,7 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
.learning-cards em { display: block; text-transform: uppercase; font-size: 10px; }
.learning-cards h3 { margin: 3px 0 0; font: 500 13px/1.2 var(--font-serif), serif; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.learning-cards article > button:not(.organ-card-image) { margin-top: auto; width: 100%; min-height: 30px; }
-.curiosity-card { justify-content: center; padding-left: 28px !important; background: linear-gradient(145deg, rgba(255,247,234,.7), rgba(247,231,210,.86)) !important; }
+.curiosity-card { justify-content: center; padding-inline-start: 28px !important; background: linear-gradient(145deg, rgba(255,247,234,.7), rgba(247,231,210,.86)) !important; }
.curiosity-card span { color: #c8a0b8; font-size: 28px; }
.curiosity-card p { margin: 5px 0 10px; font: 500 16px/1.4 var(--font-serif), serif; }
.curiosity-card em { text-transform: none; font-size: 13px; }
@@ -411,7 +483,7 @@ button:focus-visible, input:focus-visible, canvas:focus-visible {
}
.organ-card-image .art-fallback { font-size: 42px; }
.organ-glyph .art-fallback { font-size: 21px; }
-.compare-organ > .art-fallback { position: absolute; left: 0; top: 50%; width: 38px; height: 38px; border-radius: 50%; transform: translateY(-50%); font-size: 17px; }
+.compare-organ > .art-fallback { position: absolute; inset-inline-start: 0; top: 50%; width: 38px; height: 38px; border-radius: 50%; transform: translateY(-50%); font-size: 17px; }
.microscope-visual .art-fallback { font-size: 40px; }
.modal-demo .art-fallback { font-size: 52px; }
@@ -439,15 +511,15 @@ button.organ-card-image:focus-visible { outline: 2px solid #8b6fc4; outline-offs
.play-badge { position: absolute; width: 40px; height: 40px; border: 1px solid rgba(255,255,255,.8); border-radius: 50%; background: rgba(255,255,255,.7); color: #b4a99f; display: grid; place-items: center; box-shadow: 0 3px 12px rgba(0,0,0,.1); pointer-events: none; }
/* Same top and bottom gap as an artwork card, so the footer buttons line up. */
.learning-cards ul { list-style: none; padding: 0; margin: var(--card-gap-top) 0 var(--card-gap-bottom); flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 9px; font: 500 11px var(--font-serif), serif; }
-.learning-cards li:before { content: "♥"; color: #e9968b; margin-right: 7px; }
+.learning-cards li:before { content: "♥"; color: #e9968b; margin-inline-end: 7px; }
/* Sits directly under the header like every other card, rather than being
pushed down by an auto top margin. */
.system-visual { overflow: hidden; border-radius: 12px; }
.compare-strip { position: fixed; z-index: 20; left: 50%; bottom: 22px; transform: translateX(-50%); width: min(760px, calc(100% - 28px)); padding: 13px 16px; display: grid; grid-template-columns: 1fr auto 1fr 1.5fr auto; align-items: center; gap: 14px; border: 1px solid var(--line); border-radius: 18px; background: rgba(255,251,245,.94); backdrop-filter: blur(18px); box-shadow: 0 22px 60px rgba(64,43,29,.2); }
.compare-strip > div { display: flex; flex-direction: column; }
-.compare-strip .compare-organ { position: relative; padding-left: 45px; min-height: 38px; justify-content: center; }
-.compare-organ > img { position: absolute; left: 0; top: 50%; width: 38px; height: 38px; border-radius: 50%; object-fit: cover; transform: translateY(-50%); mix-blend-mode: multiply; }
+.compare-strip .compare-organ { position: relative; padding-inline-start: 45px; min-height: 38px; justify-content: center; }
+.compare-organ > img { position: absolute; inset-inline-start: 0; top: 50%; width: 38px; height: 38px; border-radius: 50%; object-fit: cover; transform: translateY(-50%); mix-blend-mode: multiply; }
.compare-strip span { color: var(--lavender); font-size: 9px; text-transform: uppercase; letter-spacing: .12em; }
.compare-strip strong { font: 500 20px var(--font-serif), serif; }
.compare-strip small { color: var(--muted); font-size: 9px; }
@@ -461,7 +533,7 @@ button.organ-card-image:focus-visible { outline: 2px solid #8b6fc4; outline-offs
.learning-modal { width: min(460px, 100%); max-height: min(92vh, 900px); overflow: auto; scrollbar-width: none; position: relative; padding: 32px; border: 1px solid var(--line); border-radius: 26px; background: #fffaf2; box-shadow: 0 30px 90px rgba(63,42,28,.24); text-align: center; animation: modal-in .35s cubic-bezier(.2,.85,.3,1); }
.learning-modal.wide { width: min(540px, 100%); }
@keyframes modal-in { from { opacity: 0; transform: translateY(14px) scale(.98); } }
-.modal-close { position: absolute; top: 13px; right: 13px; width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 50%; background: transparent; cursor: pointer; }
+.modal-close { position: absolute; top: 13px; inset-inline-end: 13px; width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 50%; background: transparent; cursor: pointer; }
.modal-icon { width: 56px; height: 56px; display: grid; place-items: center; margin: 0 auto 12px; border-radius: 50%; background: rgba(237,126,105,.1); color: var(--coral); font: 500 26px var(--font-serif), serif; }
.learning-modal h2 { margin: 6px 0 12px; font: 500 34px var(--font-serif), serif; }
.learning-modal > p, .quiz-options p { color: #6e655f; font: 500 15px/1.5 var(--font-serif), serif; }
@@ -474,12 +546,12 @@ button.organ-card-image:focus-visible { outline: 2px solid #8b6fc4; outline-offs
.modal-figure { margin: 18px 0 4px; aspect-ratio: 1; max-height: 46vh; display: grid; place-items: center; overflow: hidden; border-radius: 20px; background: radial-gradient(circle at 50% 42%, #fff, #f8ecdf); }
.modal-figure img { width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; }
.modal-figure .art-fallback { font-size: 64px; }
-.modal-facts { margin: 14px 0 4px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align: left; }
+.modal-facts { margin: 14px 0 4px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align: start; }
.modal-facts dt { color: var(--muted); font-size: 9px; text-transform: uppercase; letter-spacing: .1em; }
.modal-facts dd { margin: 4px 0 0; font: 500 12px/1.3 var(--font-serif), serif; }
.learning-modal.wide .lesson-button { margin-top: 16px; }
-.quiz-options { display: flex; flex-direction: column; gap: 8px; text-align: left; }
+.quiz-options { display: flex; flex-direction: column; gap: 8px; text-align: start; }
.quiz-options button { padding: 12px; border: 1px solid var(--line); border-radius: 10px; background: rgba(255,255,255,.5); cursor: pointer; }
.drawer-backdrop { display: none; }
@@ -526,7 +598,7 @@ button.organ-card-image:focus-visible { outline: 2px solid #8b6fc4; outline-offs
background: #fffaf3;
}
.organ-library.open { transform: translateY(0); }
- .panel-heading .mobile-close { display: grid; margin-left: auto; margin-right: 7px; }
+ .panel-heading .mobile-close { display: grid; margin-inline-start: auto; margin-inline-end: 7px; }
.drawer-backdrop { display: block; position: fixed; inset: 0; z-index: 40; border: 0; background: rgba(52,39,30,.24); backdrop-filter: blur(5px); }
.organ-list { display: grid; grid-template-columns: 1fr 1fr; }
.organ-item { min-height: 76px; }
@@ -538,8 +610,8 @@ button.organ-card-image:focus-visible { outline: 2px solid #8b6fc4; outline-offs
.tool-button { width: 51px; height: 51px; }
.tool-button:nth-last-child(1) { border: 0; border-radius: 12px; }
.tool-button span { display: none; }
- .auto-rotate { bottom: 82px; right: 10px; }
- .view-caption { left: 12px; bottom: 84px; }
+ .auto-rotate { bottom: 82px; inset-inline-end: 10px; }
+ .view-caption { inset-inline-start: 12px; bottom: 84px; }
.tip-note { width: 130px; min-height: 113px; padding: 11px; }
.tip-note p { font-size: 12px; }
.callout-body { width: 148px; top: -26px; }
diff --git a/app/layout.tsx b/app/layout.tsx
index 696d3aa..8f1591f 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -67,13 +67,23 @@ export const viewport: Viewport = {
themeColor: "#f7f0e7",
};
+/**
+ * Applies the persisted language to before first paint, so an
+ * Arabic reader never sees a left-to-right flash on load. The provider takes
+ * over once React hydrates. Kept in sync with LOCALE_STORAGE_KEY / RTL_LOCALES.
+ */
+const LOCALE_BOOTSTRAP = `(function(){try{var l=localStorage.getItem("anatomy-locale");if(l&&["en","ar","de","fr","it"].indexOf(l)>-1){document.documentElement.lang=l;document.documentElement.dir=l==="ar"?"rtl":"ltr";}}catch(e){}})();`;
+
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
-
+
+
+
+
diff --git a/app/lib/i18n/config.ts b/app/lib/i18n/config.ts
new file mode 100644
index 0000000..f1d560b
--- /dev/null
+++ b/app/lib/i18n/config.ts
@@ -0,0 +1,41 @@
+export const LOCALES = ["en", "ar", "de", "fr", "it"] as const;
+
+export type Locale = (typeof LOCALES)[number];
+
+export const DEFAULT_LOCALE: Locale = "en";
+
+/** Locales that render right-to-left. */
+export const RTL_LOCALES: readonly Locale[] = ["ar"];
+
+export type LocaleMeta = {
+ /** Native name, shown in the switcher (e.g. "Deutsch"). */
+ label: string;
+ /** English name, useful for aria/search. */
+ english: string;
+ /** Short badge shown on the trigger. */
+ short: string;
+ dir: "ltr" | "rtl";
+};
+
+export const LOCALE_META: Record = {
+ en: { label: "English", english: "English", short: "EN", dir: "ltr" },
+ ar: { label: "العربية", english: "Arabic", short: "AR", dir: "rtl" },
+ de: { label: "Deutsch", english: "German", short: "DE", dir: "ltr" },
+ fr: { label: "Français", english: "French", short: "FR", dir: "ltr" },
+ it: { label: "Italiano", english: "Italian", short: "IT", dir: "ltr" },
+};
+
+export function isLocale(value: string): value is Locale {
+ return (LOCALES as readonly string[]).includes(value);
+}
+
+export function isRtl(locale: Locale): boolean {
+ return RTL_LOCALES.includes(locale);
+}
+
+export function dirFor(locale: Locale): "ltr" | "rtl" {
+ return isRtl(locale) ? "rtl" : "ltr";
+}
+
+/** localStorage key the chosen language is persisted under. */
+export const LOCALE_STORAGE_KEY = "anatomy-locale";
diff --git a/app/lib/i18n/context.tsx b/app/lib/i18n/context.tsx
new file mode 100644
index 0000000..0e0794d
--- /dev/null
+++ b/app/lib/i18n/context.tsx
@@ -0,0 +1,135 @@
+"use client";
+
+import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
+import {
+ DEFAULT_LOCALE,
+ LOCALE_STORAGE_KEY,
+ dirFor,
+ isLocale,
+ type Locale,
+} from "./config";
+import { dictionaries, type Dictionary } from "./ui";
+import { organTranslations } from "./organs";
+import { organs as baseOrgans, type Organ, type OrganId } from "../anatomy-data";
+
+type Vars = Record;
+
+/** Fills `{name}`, `{system}`, … placeholders in a template string. */
+function format(template: string, vars?: Vars): string {
+ if (!vars) return template;
+ return template.replace(/\{(\w+)\}/g, (match, key: string) =>
+ key in vars ? String(vars[key]) : match,
+ );
+}
+
+/**
+ * Merges the locale-invariant base organs with the active locale's copy.
+ * Structure (model, icon, accent, hotspot positions/colors, scientificName)
+ * comes from the base; display text is overlaid. Falls back to English when a
+ * locale or a specific organ has no translation.
+ */
+function localizeOrgans(locale: Locale): Organ[] {
+ const table = organTranslations[locale];
+ if (!table) return baseOrgans;
+ return baseOrgans.map((organ) => {
+ const tr = table[organ.id];
+ if (!tr) return organ;
+ return {
+ ...organ,
+ name: tr.name,
+ system: tr.system,
+ description: tr.description,
+ poetic: tr.poetic,
+ size: tr.size,
+ weight: tr.weight,
+ location: tr.location,
+ function: tr.function,
+ dailyFact: tr.dailyFact,
+ medical: tr.medical,
+ bloodSupply: tr.bloodSupply,
+ funFact: tr.funFact,
+ tissue: tr.tissue,
+ comparison: tr.comparison,
+ conditions: tr.conditions,
+ hotspots: organ.hotspots.map((hotspot) => {
+ const th = tr.hotspots[hotspot.id];
+ return th ? { ...hotspot, label: th.label, detail: th.detail } : hotspot;
+ }),
+ };
+ });
+}
+
+type I18n = {
+ locale: Locale;
+ setLocale: (locale: Locale) => void;
+ dir: "ltr" | "rtl";
+ /** Translate a UI key, interpolating `{placeholders}`. */
+ t: (key: keyof Dictionary, vars?: Vars) => string;
+ dict: Dictionary;
+ organs: Organ[];
+ organById: Record;
+};
+
+const I18nContext = createContext(null);
+
+export function LocaleProvider({ children }: { children: React.ReactNode }) {
+ // SSR renders the default locale; the persisted choice is applied on mount so
+ // the initial markup matches and hydration stays clean.
+ const [locale, setLocaleState] = useState(DEFAULT_LOCALE);
+
+ useEffect(() => {
+ try {
+ const saved = window.localStorage.getItem(LOCALE_STORAGE_KEY);
+ // Deferred, client-only init: SSR renders DEFAULT_LOCALE so the first
+ // client render matches; the persisted choice is applied right after
+ // mount. A lazy useState initializer can't do this — it runs during SSR
+ // where localStorage is unavailable, which would desync hydration.
+ // eslint-disable-next-line react-hooks/set-state-in-effect
+ if (saved && isLocale(saved)) setLocaleState(saved);
+ } catch {
+ // Private mode / disabled storage — keep the default.
+ }
+ }, []);
+
+ // Mirror the choice onto so the document direction flips and assistive
+ // tech announces the right language.
+ useEffect(() => {
+ const el = document.documentElement;
+ el.lang = locale;
+ el.dir = dirFor(locale);
+ }, [locale]);
+
+ const setLocale = useCallback((next: Locale) => {
+ setLocaleState(next);
+ try {
+ window.localStorage.setItem(LOCALE_STORAGE_KEY, next);
+ } catch {
+ // Ignore persistence failures; the in-memory choice still applies.
+ }
+ }, []);
+
+ const value = useMemo(() => {
+ const dict = dictionaries[locale];
+ const localized = localizeOrgans(locale);
+ return {
+ locale,
+ setLocale,
+ dir: dirFor(locale),
+ dict,
+ t: (key, vars) => format(dict[key], vars),
+ organs: localized,
+ organById: Object.fromEntries(localized.map((organ) => [organ.id, organ])) as Record<
+ OrganId,
+ Organ
+ >,
+ };
+ }, [locale, setLocale]);
+
+ return {children} ;
+}
+
+export function useI18n(): I18n {
+ const ctx = useContext(I18nContext);
+ if (!ctx) throw new Error("useI18n must be used within a LocaleProvider");
+ return ctx;
+}
diff --git a/app/lib/i18n/organs.ts b/app/lib/i18n/organs.ts
new file mode 100644
index 0000000..96d831b
--- /dev/null
+++ b/app/lib/i18n/organs.ts
@@ -0,0 +1,1230 @@
+import type { Locale } from "./config";
+import type { OrganId } from "../anatomy-data";
+
+/**
+ * Translatable fields of an organ. Locale-invariant data (id, model, icon,
+ * accent, hotspot positions/colors, `scientificName` — Latin, universal) stays
+ * in anatomy-data.ts; this only carries the display copy.
+ *
+ * `hotspots` is keyed by the hotspot's `id` so the merge in context.tsx can look
+ * up each dot's translated label/detail without depending on array order.
+ */
+export type OrganTranslation = {
+ name: string;
+ system: string;
+ description: string;
+ poetic: string;
+ size: string;
+ weight: string;
+ location: string;
+ function: string;
+ dailyFact: string;
+ medical: string;
+ bloodSupply: string;
+ funFact: string;
+ tissue: string;
+ comparison: string;
+ conditions: string[];
+ hotspots: Record;
+};
+
+export type OrganTranslations = Record;
+
+const de: OrganTranslations = {
+ heart: {
+ name: "Herz",
+ system: "Herz-Kreislauf-System",
+ description:
+ "Ein muskulöses Organ, das Blut durch den ganzen Körper pumpt und jede Zelle mit Sauerstoff und Nährstoffen versorgt.",
+ poetic: "Die unermüdliche Pumpe",
+ size: "Etwa so groß wie deine Faust",
+ weight: "250–350 g",
+ location: "Hinter dem Brustbein, leicht links",
+ function: "Lässt sauerstoffreiches Blut zirkulieren",
+ dailyFact: "Schlägt etwa 100.000 Mal",
+ medical: "Sein elektrischer Rhythmus koordiniert jeden Herzschlag.",
+ bloodSupply: "Linke und rechte Koronararterie",
+ funFact:
+ "Es schlägt in einem durchschnittlichen Leben rund 2,5 Milliarden Mal – und beginnt schon vor der Geburt.",
+ tissue: "Herzmuskelgewebe",
+ comparison: "Herz vs. Gehirn",
+ conditions: [
+ "Koronare Herzkrankheit",
+ "Herzrhythmusstörung",
+ "Herzklappenerkrankungen",
+ "Herzinsuffizienz",
+ "Kardiomyopathie",
+ "Myokarditis",
+ "Vorhofflimmern",
+ "Angeborene Herzfehler",
+ ],
+ hotspots: {
+ aorta: { label: "Aorta", detail: "Hauptschlagader" },
+ "left-atrium": { label: "Linker Vorhof", detail: "Nimmt sauerstoffreiches Blut auf" },
+ "right-atrium": { label: "Rechter Vorhof", detail: "Nimmt venöses Blut auf" },
+ "left-ventricle": { label: "Linke Herzkammer", detail: "Pumpt in den Körper" },
+ "right-ventricle": { label: "Rechte Herzkammer", detail: "Pumpt in die Lunge" },
+ mitral: { label: "Mitralklappe", detail: "Verhindert Rückfluss" },
+ },
+ },
+ brain: {
+ name: "Gehirn",
+ system: "Nervensystem",
+ description:
+ "Die Kommandozentrale des Körpers, die Wahrnehmung, Gedächtnis, Emotion und präzise Bewegung vereint.",
+ poetic: "Das Universum im Inneren",
+ size: "Etwa zwei geballte Fäuste",
+ weight: "1,3–1,4 kg",
+ location: "Geschützt im Schädel",
+ function: "Verarbeitet und koordiniert Signale",
+ dailyFact: "Verbraucht etwa 20 % der Körperenergie",
+ medical: "Milliarden Neuronen kommunizieren über elektrische und chemische Signale.",
+ bloodSupply: "Innere Halsschlagader und Wirbelarterien",
+ funFact: "Es besitzt keine eigenen Schmerzrezeptoren – Kopfschmerz entsteht im umgebenden Gewebe.",
+ tissue: "Großhirnrinde",
+ comparison: "Gehirn vs. Auge",
+ conditions: [
+ "Migräne",
+ "Schlaganfall",
+ "Neurodegenerative Erkrankung",
+ "Epilepsie",
+ "Schädel-Hirn-Trauma",
+ "Meningitis",
+ "Multiple Sklerose",
+ "Hirnaneurysma",
+ ],
+ hotspots: {
+ frontal: { label: "Stirnlappen", detail: "Planung & Bewegung" },
+ parietal: { label: "Scheitellappen", detail: "Sensorische Integration" },
+ temporal: { label: "Schläfenlappen", detail: "Gedächtnis & Hören" },
+ cerebellum: { label: "Kleinhirn", detail: "Gleichgewicht & Koordination" },
+ },
+ },
+ lungs: {
+ name: "Lunge",
+ system: "Atmungssystem",
+ description:
+ "Paarige Organe, die Luft einsaugen und über eine riesige, zarte Oberfläche Sauerstoff gegen Kohlendioxid tauschen.",
+ poetic: "Der Atem des Lebens",
+ size: "Jeweils etwa 25 cm hoch",
+ weight: "Etwa 1 kg für das Paar",
+ location: "Beidseits des Herzens, im Brustkorb",
+ function: "Tauscht Sauerstoff gegen Kohlendioxid",
+ dailyFact: "Bewegt rund 11.000 L Luft",
+ medical: "Die Alveolen falten eine tennisplatzgroße Austauschfläche in den Brustkorb.",
+ bloodSupply: "Lungen- und Bronchialarterien",
+ funFact: "Die rechte Lunge hat drei Lappen, die linke nur zwei und lässt eine Kerbe für das Herz.",
+ tissue: "Alveolargewebe",
+ comparison: "Lunge vs. Herz",
+ conditions: [
+ "Asthma",
+ "COPD",
+ "Lungenentzündung",
+ "Lungenembolie",
+ "Lungenfibrose",
+ "Bronchitis",
+ "Mukoviszidose",
+ "Lungenkrebs",
+ ],
+ hotspots: {
+ trachea: { label: "Luftröhre", detail: "Führt Luft zur Lunge" },
+ "right-lung": { label: "Rechte Lunge", detail: "Drei Lappen" },
+ "left-lung": { label: "Linke Lunge", detail: "Zwei Lappen, Platz für das Herz" },
+ bronchus: { label: "Bronchus", detail: "Sich verzweigender Atemweg" },
+ base: { label: "Lungenbasis", detail: "Ruht auf dem Zwerchfell" },
+ },
+ },
+ liver: {
+ name: "Leber",
+ system: "Verdauungssystem",
+ description:
+ "Ein bemerkenswertes Stoffwechselorgan, das Blut filtert, Nährstoffe verarbeitet und Galle produziert.",
+ poetic: "Der stille Alchemist",
+ size: "Etwa so groß wie ein Football",
+ weight: "1,4–1,6 kg",
+ location: "Rechter Oberbauch",
+ function: "Stoffwechsel, Entgiftung & Galle",
+ dailyFact: "Erfüllt mehr als 500 Funktionen",
+ medical: "Sie kann einen großen Teil verlorenen Gewebes nachbilden.",
+ bloodSupply: "Leberarterie und Pfortader",
+ funFact: "Sie ist das einzige menschliche Organ, das aus einem Bruchteil wieder zur vollen Größe nachwächst.",
+ tissue: "Leberläppchen",
+ comparison: "Leber vs. Darm",
+ conditions: [
+ "Fettlebererkrankung",
+ "Hepatitis",
+ "Leberzirrhose",
+ "Gallensteine",
+ "Hämochromatose",
+ "Leberkrebs",
+ "Autoimmunhepatitis",
+ "Portale Hypertension",
+ ],
+ hotspots: {
+ "right-lobe": { label: "Rechter Leberlappen", detail: "Größter Leberlappen" },
+ "left-lobe": { label: "Linker Leberlappen", detail: "Reicht über die Mittellinie" },
+ portal: { label: "Pfortader", detail: "Nährstoffreicher Zufluss" },
+ },
+ },
+ kidneys: {
+ name: "Nieren",
+ system: "Harnsystem",
+ description:
+ "Paarige Filterorgane, die Flüssigkeiten, Elektrolyte, Blutdruck und die Ausscheidung von Abfallstoffen im Gleichgewicht halten.",
+ poetic: "Die Meisterfilter",
+ size: "Jede etwa so groß wie eine Computermaus",
+ weight: "Je 120–170 g",
+ location: "Beidseits der Wirbelsäule unterhalb der Rippen",
+ function: "Filtert Blut und bildet Urin",
+ dailyFact: "Filtern rund 180 L Flüssigkeit",
+ medical: "Nephrone stimmen die Chemie des Blutkreislaufs fein ab.",
+ bloodSupply: "Nierenarterien",
+ funFact: "Sie gewinnen fast alles Gefilterte zurück – nur etwa 1–2 L verlassen den Körper als Urin.",
+ tissue: "Nierenrinde",
+ comparison: "Nieren vs. Leber",
+ conditions: [
+ "Nierensteine",
+ "Chronische Nierenerkrankung",
+ "Harnwegsinfektion",
+ "Glomerulonephritis",
+ "Polyzystische Nierenerkrankung",
+ "Renale Hypertonie",
+ "Akutes Nierenversagen",
+ "Nephrotisches Syndrom",
+ ],
+ hotspots: {
+ cortex: { label: "Nierenrinde", detail: "Äußere Filterschicht" },
+ medulla: { label: "Nierenmark", detail: "Konzentriert den Urin" },
+ ureter: { label: "Harnleiter", detail: "Leitet den Urin" },
+ },
+ },
+ eyeball: {
+ name: "Auge",
+ system: "Sinnessystem",
+ description:
+ "Ein präzises Sinnesorgan, das gebündeltes Licht in Nervensignale umwandelt, die als Sehen gedeutet werden.",
+ poetic: "Ein Fenster aus Licht",
+ size: "Etwa 24 mm im Durchmesser",
+ weight: "Etwa 7,5 g",
+ location: "In der knöchernen Augenhöhle",
+ function: "Fängt Licht ein und bündelt es",
+ dailyFact: "Macht tausende winzige Bewegungen",
+ medical: "Die Netzhaut ist eine Verlängerung des zentralen Nervensystems.",
+ bloodSupply: "Augenarterie",
+ funFact: "Die Hornhaut besitzt gar keine Blutgefäße; sie nimmt Sauerstoff direkt aus der Luft auf.",
+ tissue: "Netzhautschichten",
+ comparison: "Auge vs. Gehirn",
+ conditions: [
+ "Kurzsichtigkeit",
+ "Grauer Star",
+ "Grüner Star",
+ "Makuladegeneration",
+ "Netzhautablösung",
+ "Trockenes Auge",
+ "Astigmatismus",
+ "Bindehautentzündung",
+ ],
+ hotspots: {
+ cornea: { label: "Hornhaut", detail: "Klare Brechungsfläche" },
+ iris: { label: "Regenbogenhaut", detail: "Steuert den Lichteinfall" },
+ optic: { label: "Sehnerv", detail: "Leitet Sehsignale" },
+ },
+ },
+ intestine: {
+ name: "Darm",
+ system: "Verdauungssystem",
+ description:
+ "Ein gefalteter Verdauungsgang, in dem Nährstoffe aufgenommen werden und das Mikrobiom die Gesundheit des ganzen Körpers unterstützt.",
+ poetic: "Der innere Garten",
+ size: "Etwa 6–7 m ausgestreckt",
+ weight: "Je nach Inhalt unterschiedlich",
+ location: "Mittlerer und unterer Bauchraum",
+ function: "Verdauung und Nährstoffaufnahme",
+ dailyFact: "Beherbergt Billionen Mikroorganismen",
+ medical: "Seine Oberfläche wird durch Falten, Zotten und Mikrozotten vergrößert.",
+ bloodSupply: "Obere und untere Mesenterialarterie",
+ funFact:
+ "Seine Schleimhaut erneuert sich alle paar Tage – die schnellste Erneuerung aller Gewebe des Körpers.",
+ tissue: "Darmzotten",
+ comparison: "Darm vs. Leber",
+ conditions: [
+ "Reizdarmsyndrom",
+ "Chronisch-entzündliche Darmerkrankung",
+ "Zöliakie",
+ "Divertikulitis",
+ "Darmverschluss",
+ "Kolorektale Polypen",
+ "Morbus Crohn",
+ "Laktoseintoleranz",
+ ],
+ hotspots: {
+ duodenum: { label: "Zwölffingerdarm", detail: "Erster Dünndarmabschnitt" },
+ jejunum: { label: "Leerdarm", detail: "Wichtigste Resorptionsregion" },
+ colon: { label: "Dickdarm", detail: "Gewinnt Wasser zurück" },
+ },
+ },
+ pancreas: {
+ name: "Bauchspeicheldrüse",
+ system: "Endokrines System",
+ description:
+ "Eine Drüse mit doppelter Aufgabe, die Verdauungsenzyme in den Darm und Hormone zur Stabilisierung des Blutzuckers abgibt.",
+ poetic: "Der stille Regler",
+ size: "Etwa 15 cm lang",
+ weight: "70–100 g",
+ location: "Hinter dem Magen, quer über den Oberbauch",
+ function: "Verdauungsenzyme und Insulin",
+ dailyFact: "Bildet etwa 1,5 L enzymreichen Saft",
+ medical: "Die Langerhans-Inseln geben Insulin und Glukagon ab, um den Blutzucker auszugleichen.",
+ bloodSupply: "Milzarterie und pankreatikoduodenale Arterien",
+ funFact: "Kaum 2 % bilden Hormone; der Rest ist den Verdauungsenzymen gewidmet.",
+ tissue: "Pankreasazini",
+ comparison: "Bauchspeicheldrüse vs. Leber",
+ conditions: [
+ "Pankreatitis",
+ "Typ-1-Diabetes",
+ "Bauchspeicheldrüsenkrebs",
+ "Typ-2-Diabetes",
+ "Exokrine Insuffizienz",
+ "Pankreaszysten",
+ "Gallenstein-Pankreatitis",
+ "Insulinom",
+ ],
+ hotspots: {
+ head: { label: "Kopf", detail: "Vom Zwölffingerdarm umschlossen" },
+ body: { label: "Körper", detail: "Kreuzt die Wirbelsäule" },
+ tail: { label: "Schwanz", detail: "Reicht bis zur Milz" },
+ duct: { label: "Pankreasgang", detail: "Leitet Enzyme in den Darm" },
+ },
+ },
+ skin: {
+ name: "Haut",
+ system: "Hautsystem",
+ description:
+ "Das größte Organ des Körpers – eine lebende Barriere, die Berührung wahrnimmt, Wasser hält und die Temperatur reguliert.",
+ poetic: "Die lebende Grenze",
+ size: "Etwa 2 m² flach ausgebreitet",
+ weight: "3,5–5 kg",
+ location: "Bedeckt den ganzen Körper",
+ function: "Schützt, fühlt und kühlt",
+ dailyFact: "Verliert rund 500 Millionen Zellen",
+ medical: "Drei Schichten – Oberhaut, Lederhaut und Unterhaut – jede mit einer eigenen Aufgabe.",
+ bloodSupply: "Gefäßgeflecht der Lederhaut",
+ funFact:
+ "Ein einziger Quadratzentimeter kann hunderte Schweißdrüsen und Meter an Blutgefäßen enthalten.",
+ tissue: "Schichten der Oberhaut",
+ comparison: "Haut vs. Darm",
+ conditions: [
+ "Ekzem",
+ "Schuppenflechte",
+ "Melanom",
+ "Akne vulgaris",
+ "Zellulitis",
+ "Kontaktdermatitis",
+ "Rosazea",
+ "Vitiligo",
+ ],
+ hotspots: {
+ epidermis: { label: "Oberhaut", detail: "Äußere Schutzschicht" },
+ dermis: { label: "Lederhaut", detail: "Nerven, Gefäße & Drüsen" },
+ hypodermis: { label: "Unterhaut", detail: "Fett und Isolierung" },
+ follicle: { label: "Haarfollikel", detail: "Verankert jedes Haar" },
+ },
+ },
+};
+
+const fr: OrganTranslations = {
+ heart: {
+ name: "Cœur",
+ system: "Système cardiovasculaire",
+ description:
+ "Un organe musculaire qui pompe le sang dans tout le corps, apportant oxygène et nutriments à chaque cellule.",
+ poetic: "La pompe infatigable",
+ size: "À peu près la taille de votre poing",
+ weight: "250–350 g",
+ location: "Derrière le sternum, légèrement à gauche",
+ function: "Fait circuler le sang oxygéné",
+ dailyFact: "Bat environ 100 000 fois",
+ medical: "Son rythme électrique coordonne chaque battement.",
+ bloodSupply: "Artères coronaires gauche et droite",
+ funFact: "Il bat environ 2,5 milliards de fois au cours d’une vie moyenne, et commence avant la naissance.",
+ tissue: "Tissu musculaire cardiaque",
+ comparison: "Cœur vs cerveau",
+ conditions: [
+ "Maladie coronarienne",
+ "Arythmie",
+ "Valvulopathies",
+ "Insuffisance cardiaque",
+ "Cardiomyopathie",
+ "Myocardite",
+ "Fibrillation auriculaire",
+ "Malformations cardiaques congénitales",
+ ],
+ hotspots: {
+ aorta: { label: "Aorte", detail: "Artère principale" },
+ "left-atrium": { label: "Oreillette gauche", detail: "Reçoit le sang oxygéné" },
+ "right-atrium": { label: "Oreillette droite", detail: "Reçoit le sang veineux" },
+ "left-ventricle": { label: "Ventricule gauche", detail: "Pompe vers le corps" },
+ "right-ventricle": { label: "Ventricule droit", detail: "Pompe vers les poumons" },
+ mitral: { label: "Valve mitrale", detail: "Empêche le reflux" },
+ },
+ },
+ brain: {
+ name: "Cerveau",
+ system: "Système nerveux",
+ description:
+ "Le centre de commande du corps, qui intègre sensation, mémoire, émotion et mouvement précis.",
+ poetic: "L’univers intérieur",
+ size: "Environ deux poings serrés",
+ weight: "1,3–1,4 kg",
+ location: "Protégé dans le crâne",
+ function: "Traite et coordonne les signaux",
+ dailyFact: "Utilise environ 20 % de l’énergie du corps",
+ medical: "Des milliards de neurones communiquent par signaux électriques et chimiques.",
+ bloodSupply: "Artères carotides internes et vertébrales",
+ funFact: "Il n’a aucun récepteur de la douleur ; le mal de tête est ressenti dans les tissus qui l’entourent.",
+ tissue: "Cortex cérébral",
+ comparison: "Cerveau vs œil",
+ conditions: [
+ "Migraine",
+ "Accident vasculaire cérébral",
+ "Maladie neurodégénérative",
+ "Épilepsie",
+ "Traumatisme crânien",
+ "Méningite",
+ "Sclérose en plaques",
+ "Anévrisme cérébral",
+ ],
+ hotspots: {
+ frontal: { label: "Lobe frontal", detail: "Planification & mouvement" },
+ parietal: { label: "Lobe pariétal", detail: "Intégration sensorielle" },
+ temporal: { label: "Lobe temporal", detail: "Mémoire & audition" },
+ cerebellum: { label: "Cervelet", detail: "Équilibre & coordination" },
+ },
+ },
+ lungs: {
+ name: "Poumons",
+ system: "Système respiratoire",
+ description:
+ "Des organes pairs qui aspirent l’air et échangent l’oxygène contre le dioxyde de carbone sur une vaste surface délicate.",
+ poetic: "Le souffle de la vie",
+ size: "Chacun environ 25 cm de haut",
+ weight: "Environ 1 kg pour la paire",
+ location: "De part et d’autre du cœur, dans la cage thoracique",
+ function: "Échange l’oxygène contre le dioxyde de carbone",
+ dailyFact: "Déplace environ 11 000 L d’air",
+ medical: "Les alvéoles replient dans la poitrine une surface d’échange grande comme un court de tennis.",
+ bloodSupply: "Artères pulmonaires et bronchiques",
+ funFact: "Le poumon droit compte trois lobes et le gauche seulement deux, laissant une place pour le cœur.",
+ tissue: "Tissu alvéolaire",
+ comparison: "Poumons vs cœur",
+ conditions: [
+ "Asthme",
+ "BPCO",
+ "Pneumonie",
+ "Embolie pulmonaire",
+ "Fibrose pulmonaire",
+ "Bronchite",
+ "Mucoviscidose",
+ "Cancer du poumon",
+ ],
+ hotspots: {
+ trachea: { label: "Trachée", detail: "Achemine l’air vers les poumons" },
+ "right-lung": { label: "Poumon droit", detail: "Trois lobes" },
+ "left-lung": { label: "Poumon gauche", detail: "Deux lobes, place pour le cœur" },
+ bronchus: { label: "Bronche", detail: "Voie aérienne ramifiée" },
+ base: { label: "Base du poumon", detail: "Repose sur le diaphragme" },
+ },
+ },
+ liver: {
+ name: "Foie",
+ system: "Système digestif",
+ description:
+ "Un remarquable organe métabolique qui filtre le sang, transforme les nutriments et produit la bile.",
+ poetic: "Le discret alchimiste",
+ size: "À peu près la taille d’un ballon de football",
+ weight: "1,4–1,6 kg",
+ location: "Abdomen supérieur droit",
+ function: "Métabolisme, détoxification & bile",
+ dailyFact: "Assure plus de 500 fonctions",
+ medical: "Il peut régénérer une part importante du tissu perdu.",
+ bloodSupply: "Artère hépatique et veine porte",
+ funFact:
+ "C’est le seul organe humain capable de repousser à sa taille complète à partir d’une fraction de lui-même.",
+ tissue: "Lobules hépatiques",
+ comparison: "Foie vs intestin",
+ conditions: [
+ "Stéatose hépatique",
+ "Hépatite",
+ "Cirrhose",
+ "Calculs biliaires",
+ "Hémochromatose",
+ "Cancer du foie",
+ "Hépatite auto-immune",
+ "Hypertension portale",
+ ],
+ hotspots: {
+ "right-lobe": { label: "Lobe droit", detail: "Plus grand lobe hépatique" },
+ "left-lobe": { label: "Lobe gauche", detail: "Franchit la ligne médiane" },
+ portal: { label: "Veine porte", detail: "Afflux riche en nutriments" },
+ },
+ },
+ kidneys: {
+ name: "Reins",
+ system: "Système urinaire",
+ description:
+ "Des organes de filtration pairs qui équilibrent les liquides, les électrolytes, la pression artérielle et l’élimination des déchets.",
+ poetic: "Les filtres maîtres",
+ size: "Chacun environ une souris d’ordinateur",
+ weight: "120–170 g chacun",
+ location: "De part et d’autre de la colonne, sous les côtes",
+ function: "Filtre le sang et forme l’urine",
+ dailyFact: "Filtrent environ 180 L de liquide",
+ medical: "Les néphrons ajustent finement la chimie du sang.",
+ bloodSupply: "Artères rénales",
+ funFact: "Ils récupèrent presque tout ce qu’ils filtrent — seul 1 à 2 L quitte le corps sous forme d’urine.",
+ tissue: "Cortex rénal",
+ comparison: "Reins vs foie",
+ conditions: [
+ "Calculs rénaux",
+ "Maladie rénale chronique",
+ "Infection urinaire",
+ "Glomérulonéphrite",
+ "Polykystose rénale",
+ "Hypertension rénale",
+ "Insuffisance rénale aiguë",
+ "Syndrome néphrotique",
+ ],
+ hotspots: {
+ cortex: { label: "Cortex rénal", detail: "Couche de filtration externe" },
+ medulla: { label: "Médullaire rénale", detail: "Concentre l’urine" },
+ ureter: { label: "Uretère", detail: "Achemine l’urine" },
+ },
+ },
+ eyeball: {
+ name: "Œil",
+ system: "Système sensoriel",
+ description:
+ "Un organe sensoriel de précision qui convertit la lumière focalisée en signaux nerveux interprétés comme la vision.",
+ poetic: "Une fenêtre faite de lumière",
+ size: "Environ 24 mm de diamètre",
+ weight: "Environ 7,5 g",
+ location: "Dans l’orbite osseuse",
+ function: "Capte et focalise la lumière",
+ dailyFact: "Effectue des milliers de minuscules mouvements",
+ medical: "La rétine est un prolongement du système nerveux central.",
+ bloodSupply: "Artère ophtalmique",
+ funFact: "La cornée ne possède aucun vaisseau sanguin ; elle prend l’oxygène directement dans l’air.",
+ tissue: "Couches rétiniennes",
+ comparison: "Œil vs cerveau",
+ conditions: [
+ "Myopie",
+ "Cataracte",
+ "Glaucome",
+ "Dégénérescence maculaire",
+ "Décollement de la rétine",
+ "Sécheresse oculaire",
+ "Astigmatisme",
+ "Conjonctivite",
+ ],
+ hotspots: {
+ cornea: { label: "Cornée", detail: "Surface de focalisation transparente" },
+ iris: { label: "Iris", detail: "Règle l’entrée de la lumière" },
+ optic: { label: "Nerf optique", detail: "Transmet les signaux visuels" },
+ },
+ },
+ intestine: {
+ name: "Intestin",
+ system: "Système digestif",
+ description:
+ "Un passage digestif replié où les nutriments sont absorbés et où le microbiote soutient la santé de tout le corps.",
+ poetic: "Le jardin intérieur",
+ size: "Environ 6–7 m déployé",
+ weight: "Variable selon le contenu",
+ location: "Abdomen central et inférieur",
+ function: "Digestion et absorption des nutriments",
+ dailyFact: "Abrite des milliers de milliards de micro-organismes",
+ medical: "Sa surface est amplifiée par des plis, des villosités et des microvillosités.",
+ bloodSupply: "Artères mésentériques supérieure et inférieure",
+ funFact:
+ "Sa muqueuse se renouvelle tous les quelques jours — le renouvellement le plus rapide de tous les tissus du corps.",
+ tissue: "Villosités intestinales",
+ comparison: "Intestin vs foie",
+ conditions: [
+ "Syndrome de l’intestin irritable",
+ "Maladie inflammatoire de l’intestin",
+ "Maladie cœliaque",
+ "Diverticulite",
+ "Occlusion intestinale",
+ "Polypes colorectaux",
+ "Maladie de Crohn",
+ "Intolérance au lactose",
+ ],
+ hotspots: {
+ duodenum: { label: "Duodénum", detail: "Premier segment de l’intestin grêle" },
+ jejunum: { label: "Jéjunum", detail: "Principale zone d’absorption" },
+ colon: { label: "Côlon", detail: "Récupère l’eau" },
+ },
+ },
+ pancreas: {
+ name: "Pancréas",
+ system: "Système endocrinien",
+ description:
+ "Une glande à double fonction qui libère des enzymes digestives dans l’intestin et les hormones qui stabilisent la glycémie.",
+ poetic: "Le régulateur discret",
+ size: "Environ 15 cm de long",
+ weight: "70–100 g",
+ location: "Derrière l’estomac, en travers de l’abdomen supérieur",
+ function: "Enzymes digestives et insuline",
+ dailyFact: "Produit environ 1,5 L de suc riche en enzymes",
+ medical: "Les îlots de Langerhans libèrent insuline et glucagon pour équilibrer la glycémie.",
+ bloodSupply: "Artères splénique et pancréatico-duodénales",
+ funFact: "À peine 2 % fabrique des hormones ; le reste est consacré aux enzymes digestives.",
+ tissue: "Acini pancréatiques",
+ comparison: "Pancréas vs foie",
+ conditions: [
+ "Pancréatite",
+ "Diabète de type 1",
+ "Cancer du pancréas",
+ "Diabète de type 2",
+ "Insuffisance exocrine",
+ "Kystes pancréatiques",
+ "Pancréatite biliaire",
+ "Insulinome",
+ ],
+ hotspots: {
+ head: { label: "Tête", detail: "Enserrée par le duodénum" },
+ body: { label: "Corps", detail: "Traverse la colonne" },
+ tail: { label: "Queue", detail: "Atteint la rate" },
+ duct: { label: "Canal pancréatique", detail: "Draine les enzymes vers l’intestin" },
+ },
+ },
+ skin: {
+ name: "Peau",
+ system: "Système tégumentaire",
+ description:
+ "Le plus grand organe du corps — une barrière vivante qui perçoit le toucher, retient l’eau et régule la température.",
+ poetic: "La frontière vivante",
+ size: "Environ 2 m² à plat",
+ weight: "3,5–5 kg",
+ location: "Recouvre tout le corps",
+ function: "Protège, ressent et rafraîchit",
+ dailyFact: "Perd environ 500 millions de cellules",
+ medical: "Trois couches — épiderme, derme et hypoderme — chacune avec un rôle distinct.",
+ bloodSupply: "Plexus vasculaire dermique",
+ funFact:
+ "Un seul centimètre carré peut contenir des centaines de glandes sudoripares et des mètres de vaisseaux sanguins.",
+ tissue: "Couches épidermiques",
+ comparison: "Peau vs intestin",
+ conditions: [
+ "Eczéma",
+ "Psoriasis",
+ "Mélanome",
+ "Acné vulgaire",
+ "Cellulite infectieuse",
+ "Dermatite de contact",
+ "Rosacée",
+ "Vitiligo",
+ ],
+ hotspots: {
+ epidermis: { label: "Épiderme", detail: "Couche protectrice externe" },
+ dermis: { label: "Derme", detail: "Nerfs, vaisseaux & glandes" },
+ hypodermis: { label: "Hypoderme", detail: "Graisse et isolation" },
+ follicle: { label: "Follicule pileux", detail: "Ancre chaque poil" },
+ },
+ },
+};
+
+const it: OrganTranslations = {
+ heart: {
+ name: "Cuore",
+ system: "Sistema cardiovascolare",
+ description:
+ "Un organo muscolare che pompa il sangue in tutto il corpo, portando ossigeno e nutrienti a ogni cellula.",
+ poetic: "La pompa instancabile",
+ size: "Grande più o meno come il tuo pugno",
+ weight: "250–350 g",
+ location: "Dietro lo sterno, leggermente a sinistra",
+ function: "Fa circolare il sangue ossigenato",
+ dailyFact: "Batte circa 100.000 volte",
+ medical: "Il suo ritmo elettrico coordina ogni battito.",
+ bloodSupply: "Arterie coronarie destra e sinistra",
+ funFact: "Batte circa 2,5 miliardi di volte in una vita media e inizia prima della nascita.",
+ tissue: "Tessuto muscolare cardiaco",
+ comparison: "Cuore vs cervello",
+ conditions: [
+ "Malattia coronarica",
+ "Aritmia",
+ "Valvulopatie",
+ "Insufficienza cardiaca",
+ "Cardiomiopatia",
+ "Miocardite",
+ "Fibrillazione atriale",
+ "Cardiopatie congenite",
+ ],
+ hotspots: {
+ aorta: { label: "Aorta", detail: "Arteria principale" },
+ "left-atrium": { label: "Atrio sinistro", detail: "Riceve il sangue ossigenato" },
+ "right-atrium": { label: "Atrio destro", detail: "Riceve il sangue venoso" },
+ "left-ventricle": { label: "Ventricolo sinistro", detail: "Pompa verso il corpo" },
+ "right-ventricle": { label: "Ventricolo destro", detail: "Pompa verso i polmoni" },
+ mitral: { label: "Valvola mitrale", detail: "Impedisce il reflusso" },
+ },
+ },
+ brain: {
+ name: "Cervello",
+ system: "Sistema nervoso",
+ description:
+ "Il centro di comando del corpo, che integra sensazioni, memoria, emozioni e movimenti precisi.",
+ poetic: "L’universo interiore",
+ size: "Circa due pugni chiusi",
+ weight: "1,3–1,4 kg",
+ location: "Protetto nel cranio",
+ function: "Elabora e coordina i segnali",
+ dailyFact: "Usa circa il 20% dell’energia del corpo",
+ medical: "Miliardi di neuroni comunicano con segnali elettrici e chimici.",
+ bloodSupply: "Arterie carotidi interne e vertebrali",
+ funFact: "Non ha recettori del dolore propri: il mal di testa si avverte nei tessuti circostanti.",
+ tissue: "Corteccia cerebrale",
+ comparison: "Cervello vs occhio",
+ conditions: [
+ "Emicrania",
+ "Ictus",
+ "Malattia neurodegenerativa",
+ "Epilessia",
+ "Trauma cranico",
+ "Meningite",
+ "Sclerosi multipla",
+ "Aneurisma cerebrale",
+ ],
+ hotspots: {
+ frontal: { label: "Lobo frontale", detail: "Pianificazione e movimento" },
+ parietal: { label: "Lobo parietale", detail: "Integrazione sensoriale" },
+ temporal: { label: "Lobo temporale", detail: "Memoria e udito" },
+ cerebellum: { label: "Cervelletto", detail: "Equilibrio e coordinazione" },
+ },
+ },
+ lungs: {
+ name: "Polmoni",
+ system: "Sistema respiratorio",
+ description:
+ "Organi pari che aspirano l’aria e scambiano ossigeno con anidride carbonica su un’ampia e delicata superficie.",
+ poetic: "Il respiro della vita",
+ size: "Ciascuno alto circa 25 cm",
+ weight: "Circa 1 kg per la coppia",
+ location: "Ai lati del cuore, nella gabbia toracica",
+ function: "Scambia ossigeno con anidride carbonica",
+ dailyFact: "Muove circa 11.000 L di aria",
+ medical: "Gli alveoli ripiegano nel torace una superficie di scambio grande come un campo da tennis.",
+ bloodSupply: "Arterie polmonari e bronchiali",
+ funFact: "Il polmone destro ha tre lobi e il sinistro solo due, lasciando spazio per il cuore.",
+ tissue: "Tessuto alveolare",
+ comparison: "Polmoni vs cuore",
+ conditions: [
+ "Asma",
+ "BPCO",
+ "Polmonite",
+ "Embolia polmonare",
+ "Fibrosi polmonare",
+ "Bronchite",
+ "Fibrosi cistica",
+ "Tumore del polmone",
+ ],
+ hotspots: {
+ trachea: { label: "Trachea", detail: "Porta l’aria ai polmoni" },
+ "right-lung": { label: "Polmone destro", detail: "Tre lobi" },
+ "left-lung": { label: "Polmone sinistro", detail: "Due lobi, spazio per il cuore" },
+ bronchus: { label: "Bronco", detail: "Via aerea ramificata" },
+ base: { label: "Base del polmone", detail: "Poggia sul diaframma" },
+ },
+ },
+ liver: {
+ name: "Fegato",
+ system: "Sistema digerente",
+ description:
+ "Un notevole organo metabolico che filtra il sangue, elabora i nutrienti e produce la bile.",
+ poetic: "Il silenzioso alchimista",
+ size: "Grande più o meno come un pallone da football",
+ weight: "1,4–1,6 kg",
+ location: "Addome superiore destro",
+ function: "Metabolismo, detossificazione e bile",
+ dailyFact: "Svolge più di 500 funzioni",
+ medical: "Può rigenerare una parte notevole del tessuto perduto.",
+ bloodSupply: "Arteria epatica e vena porta",
+ funFact: "È l’unico organo umano capace di ricrescere alle dimensioni piene da una sua frazione.",
+ tissue: "Lobuli epatici",
+ comparison: "Fegato vs intestino",
+ conditions: [
+ "Steatosi epatica",
+ "Epatite",
+ "Cirrosi",
+ "Calcoli biliari",
+ "Emocromatosi",
+ "Tumore del fegato",
+ "Epatite autoimmune",
+ "Ipertensione portale",
+ ],
+ hotspots: {
+ "right-lobe": { label: "Lobo destro", detail: "Il lobo epatico più grande" },
+ "left-lobe": { label: "Lobo sinistro", detail: "Attraversa la linea mediana" },
+ portal: { label: "Vena porta", detail: "Afflusso ricco di nutrienti" },
+ },
+ },
+ kidneys: {
+ name: "Reni",
+ system: "Sistema urinario",
+ description:
+ "Organi di filtrazione pari che bilanciano liquidi, elettroliti, pressione sanguigna ed eliminazione dei rifiuti.",
+ poetic: "I filtri maestri",
+ size: "Ciascuno grande come un mouse",
+ weight: "120–170 g ciascuno",
+ location: "Ai lati della colonna, sotto le costole",
+ function: "Filtra il sangue e forma l’urina",
+ dailyFact: "Filtrano circa 180 L di liquido",
+ medical: "I nefroni regolano con precisione la chimica del sangue.",
+ bloodSupply: "Arterie renali",
+ funFact: "Recuperano quasi tutto ciò che filtrano: solo 1–2 L lascia il corpo come urina.",
+ tissue: "Corteccia renale",
+ comparison: "Reni vs fegato",
+ conditions: [
+ "Calcoli renali",
+ "Malattia renale cronica",
+ "Infezione urinaria",
+ "Glomerulonefrite",
+ "Malattia renale policistica",
+ "Ipertensione renale",
+ "Insufficienza renale acuta",
+ "Sindrome nefrosica",
+ ],
+ hotspots: {
+ cortex: { label: "Corteccia renale", detail: "Strato filtrante esterno" },
+ medulla: { label: "Midollare renale", detail: "Concentra l’urina" },
+ ureter: { label: "Uretere", detail: "Trasporta l’urina" },
+ },
+ },
+ eyeball: {
+ name: "Occhio",
+ system: "Sistema sensoriale",
+ description:
+ "Un organo sensoriale di precisione che converte la luce focalizzata in segnali nervosi interpretati come vista.",
+ poetic: "Una finestra fatta di luce",
+ size: "Circa 24 mm di diametro",
+ weight: "Circa 7,5 g",
+ location: "Nell’orbita ossea",
+ function: "Cattura e mette a fuoco la luce",
+ dailyFact: "Compie migliaia di minuscoli movimenti",
+ medical: "La retina è un’estensione del sistema nervoso centrale.",
+ bloodSupply: "Arteria oftalmica",
+ funFact: "La cornea non ha alcun vaso sanguigno; prende l’ossigeno direttamente dall’aria.",
+ tissue: "Strati retinici",
+ comparison: "Occhio vs cervello",
+ conditions: [
+ "Miopia",
+ "Cataratta",
+ "Glaucoma",
+ "Degenerazione maculare",
+ "Distacco della retina",
+ "Occhio secco",
+ "Astigmatismo",
+ "Congiuntivite",
+ ],
+ hotspots: {
+ cornea: { label: "Cornea", detail: "Superficie di messa a fuoco trasparente" },
+ iris: { label: "Iride", detail: "Regola l’ingresso della luce" },
+ optic: { label: "Nervo ottico", detail: "Trasmette i segnali visivi" },
+ },
+ },
+ intestine: {
+ name: "Intestino",
+ system: "Sistema digerente",
+ description:
+ "Un condotto digestivo ripiegato dove i nutrienti vengono assorbiti e il microbiota sostiene la salute di tutto il corpo.",
+ poetic: "Il giardino interiore",
+ size: "Circa 6–7 m disteso",
+ weight: "Variabile in base al contenuto",
+ location: "Addome centrale e inferiore",
+ function: "Digestione e assorbimento dei nutrienti",
+ dailyFact: "Ospita migliaia di miliardi di microrganismi",
+ medical: "La sua superficie è amplificata da pieghe, villi e microvilli.",
+ bloodSupply: "Arterie mesenteriche superiore e inferiore",
+ funFact: "La sua mucosa si rinnova ogni pochi giorni: il ricambio più rapido di ogni tessuto del corpo.",
+ tissue: "Villi intestinali",
+ comparison: "Intestino vs fegato",
+ conditions: [
+ "Sindrome dell’intestino irritabile",
+ "Malattia infiammatoria intestinale",
+ "Celiachia",
+ "Diverticolite",
+ "Occlusione intestinale",
+ "Polipi colorettali",
+ "Morbo di Crohn",
+ "Intolleranza al lattosio",
+ ],
+ hotspots: {
+ duodenum: { label: "Duodeno", detail: "Primo tratto dell’intestino tenue" },
+ jejunum: { label: "Digiuno", detail: "Principale zona di assorbimento" },
+ colon: { label: "Colon", detail: "Recupera l’acqua" },
+ },
+ },
+ pancreas: {
+ name: "Pancreas",
+ system: "Sistema endocrino",
+ description:
+ "Una ghiandola a duplice funzione che rilascia enzimi digestivi nell’intestino e gli ormoni che stabilizzano la glicemia.",
+ poetic: "Il regolatore silenzioso",
+ size: "Lungo circa 15 cm",
+ weight: "70–100 g",
+ location: "Dietro lo stomaco, attraverso l’addome superiore",
+ function: "Enzimi digestivi e insulina",
+ dailyFact: "Produce circa 1,5 L di succo ricco di enzimi",
+ medical: "Le isole di Langerhans rilasciano insulina e glucagone per bilanciare la glicemia.",
+ bloodSupply: "Arterie splenica e pancreaticoduodenali",
+ funFact: "Appena il 2% produce ormoni; il resto è dedicato agli enzimi digestivi.",
+ tissue: "Acini pancreatici",
+ comparison: "Pancreas vs fegato",
+ conditions: [
+ "Pancreatite",
+ "Diabete di tipo 1",
+ "Tumore del pancreas",
+ "Diabete di tipo 2",
+ "Insufficienza esocrina",
+ "Cisti pancreatiche",
+ "Pancreatite biliare",
+ "Insulinoma",
+ ],
+ hotspots: {
+ head: { label: "Testa", detail: "Avvolta dal duodeno" },
+ body: { label: "Corpo", detail: "Attraversa la colonna" },
+ tail: { label: "Coda", detail: "Raggiunge la milza" },
+ duct: { label: "Dotto pancreatico", detail: "Convoglia gli enzimi nell’intestino" },
+ },
+ },
+ skin: {
+ name: "Pelle",
+ system: "Sistema tegumentario",
+ description:
+ "L’organo più grande del corpo — una barriera vivente che percepisce il tatto, trattiene l’acqua e regola la temperatura.",
+ poetic: "Il confine vivente",
+ size: "Circa 2 m² distesa",
+ weight: "3,5–5 kg",
+ location: "Ricopre tutto il corpo",
+ function: "Protegge, percepisce e raffredda",
+ dailyFact: "Perde circa 500 milioni di cellule",
+ medical: "Tre strati — epidermide, derma e ipoderma — ciascuno con un compito distinto.",
+ bloodSupply: "Plesso vascolare dermico",
+ funFact: "Un solo centimetro quadrato può contenere centinaia di ghiandole sudoripare e metri di vasi sanguigni.",
+ tissue: "Strati epidermici",
+ comparison: "Pelle vs intestino",
+ conditions: [
+ "Eczema",
+ "Psoriasi",
+ "Melanoma",
+ "Acne volgare",
+ "Cellulite infettiva",
+ "Dermatite da contatto",
+ "Rosacea",
+ "Vitiligine",
+ ],
+ hotspots: {
+ epidermis: { label: "Epidermide", detail: "Strato protettivo esterno" },
+ dermis: { label: "Derma", detail: "Nervi, vasi e ghiandole" },
+ hypodermis: { label: "Ipoderma", detail: "Grasso e isolamento" },
+ follicle: { label: "Follicolo pilifero", detail: "Ancora ogni pelo" },
+ },
+ },
+};
+
+const ar: OrganTranslations = {
+ heart: {
+ name: "القلب",
+ system: "الجهاز القلبي الوعائي",
+ description: "عضو عضلي يضخّ الدم في جميع أنحاء الجسم، فيوصل الأكسجين والمغذّيات إلى كل خلية.",
+ poetic: "المضخّة التي لا تكلّ",
+ size: "بحجم قبضة يدك تقريبًا",
+ weight: "250–350 غ",
+ location: "خلف عظم القص، وأميل قليلًا إلى اليسار",
+ function: "يدير دورة الدم المؤكسج",
+ dailyFact: "ينبض نحو 100,000 مرة",
+ medical: "ينسّق إيقاعه الكهربائي كل نبضة قلب.",
+ bloodSupply: "الشريانان التاجيان الأيسر والأيمن",
+ funFact: "ينبض نحو 2.5 مليار مرة في العمر المتوسط، ويبدأ قبل أن تولد.",
+ tissue: "نسيج عضلة القلب",
+ comparison: "القلب مقابل الدماغ",
+ conditions: [
+ "مرض الشريان التاجي",
+ "اضطراب نظم القلب",
+ "أمراض صمامات القلب",
+ "قصور القلب",
+ "اعتلال عضلة القلب",
+ "التهاب عضلة القلب",
+ "الرجفان الأذيني",
+ "عيوب القلب الخِلقية",
+ ],
+ hotspots: {
+ aorta: { label: "الأبهر", detail: "الشريان الرئيسي" },
+ "left-atrium": { label: "الأذين الأيسر", detail: "يستقبل الدم المؤكسج" },
+ "right-atrium": { label: "الأذين الأيمن", detail: "يستقبل الدم الوريدي" },
+ "left-ventricle": { label: "البطين الأيسر", detail: "يضخّ إلى الجسم" },
+ "right-ventricle": { label: "البطين الأيمن", detail: "يضخّ إلى الرئتين" },
+ mitral: { label: "الصمام التاجي", detail: "يمنع الارتجاع" },
+ },
+ },
+ brain: {
+ name: "الدماغ",
+ system: "الجهاز العصبي",
+ description: "مركز قيادة الجسم، يدمج الإحساس والذاكرة والعاطفة والحركة الدقيقة.",
+ poetic: "الكون الذي في الداخل",
+ size: "نحو قبضتين مطبقتين",
+ weight: "1.3–1.4 كغ",
+ location: "محميّ داخل الجمجمة",
+ function: "يعالج الإشارات وينسّقها",
+ dailyFact: "يستهلك نحو 20% من طاقة الجسم",
+ medical: "تتواصل مليارات العصبونات عبر إشارات كهربائية وكيميائية.",
+ bloodSupply: "الشريانان السباتيان الباطنان والشرايين الفقرية",
+ funFact: "لا يملك مستقبلات ألم خاصة به؛ فالصداع يُحسّ في الأنسجة المحيطة به.",
+ tissue: "القشرة الدماغية",
+ comparison: "الدماغ مقابل العين",
+ conditions: [
+ "الشقيقة",
+ "السكتة الدماغية",
+ "الأمراض التنكّسية العصبية",
+ "الصرع",
+ "إصابة الدماغ الرضّية",
+ "التهاب السحايا",
+ "التصلّب المتعدّد",
+ "تمدّد الأوعية الدماغية",
+ ],
+ hotspots: {
+ frontal: { label: "الفص الجبهي", detail: "التخطيط والحركة" },
+ parietal: { label: "الفص الجداري", detail: "التكامل الحسّي" },
+ temporal: { label: "الفص الصدغي", detail: "الذاكرة والسمع" },
+ cerebellum: { label: "المخيخ", detail: "التوازن والتناسق" },
+ },
+ },
+ lungs: {
+ name: "الرئتان",
+ system: "الجهاز التنفسي",
+ description: "عضوان متناظران يسحبان الهواء ويبادلان الأكسجين بثاني أكسيد الكربون عبر سطح شاسع رقيق.",
+ poetic: "نَفَس الحياة",
+ size: "طول كلٍّ منهما نحو 25 سم",
+ weight: "نحو 1 كغ للزوج",
+ location: "على جانبي القلب داخل القفص الصدري",
+ function: "يبادل الأكسجين بثاني أكسيد الكربون",
+ dailyFact: "تحرّك نحو 11,000 لتر من الهواء",
+ medical: "تطوي الأسناخ سطح تبادل بحجم ملعب تنس داخل الصدر.",
+ bloodSupply: "الشرايين الرئوية والشعبية",
+ funFact: "للرئة اليمنى ثلاثة فصوص وللّيسرى فصّان فقط، تاركةً موضعًا للقلب.",
+ tissue: "النسيج السنخي",
+ comparison: "الرئتان مقابل القلب",
+ conditions: [
+ "الربو",
+ "الداء الرئوي الانسدادي المزمن",
+ "الالتهاب الرئوي",
+ "الانصمام الرئوي",
+ "التليّف الرئوي",
+ "التهاب القصبات",
+ "التليّف الكيسي",
+ "سرطان الرئة",
+ ],
+ hotspots: {
+ trachea: { label: "الرغامى", detail: "تنقل الهواء إلى الرئتين" },
+ "right-lung": { label: "الرئة اليمنى", detail: "ثلاثة فصوص" },
+ "left-lung": { label: "الرئة اليسرى", detail: "فصّان، مع متّسع للقلب" },
+ bronchus: { label: "القصبة", detail: "مجرى هوائي متفرّع" },
+ base: { label: "قاعدة الرئة", detail: "تستقر على الحجاب الحاجز" },
+ },
+ },
+ liver: {
+ name: "الكبد",
+ system: "الجهاز الهضمي",
+ description: "عضو استقلابي رائع يرشّح الدم ويعالج المغذّيات وينتج الصفراء.",
+ poetic: "الكيميائي الهادئ",
+ size: "بحجم كرة قدم تقريبًا",
+ weight: "1.4–1.6 كغ",
+ location: "أعلى يمين البطن",
+ function: "الاستقلاب وإزالة السموم والصفراء",
+ dailyFact: "يؤدّي أكثر من 500 وظيفة",
+ medical: "يستطيع تجديد جزء كبير من النسيج المفقود.",
+ bloodSupply: "الشريان الكبدي والوريد البابي",
+ funFact: "هو العضو البشري الوحيد الذي ينمو ثانيةً إلى حجمه الكامل من جزء منه.",
+ tissue: "الفصيصات الكبدية",
+ comparison: "الكبد مقابل الأمعاء",
+ conditions: [
+ "مرض الكبد الدهني",
+ "التهاب الكبد",
+ "تشمّع الكبد",
+ "حصى المرارة",
+ "داء ترسّب الأصبغة الدموية",
+ "سرطان الكبد",
+ "التهاب الكبد المناعي الذاتي",
+ "ارتفاع ضغط الوريد البابي",
+ ],
+ hotspots: {
+ "right-lobe": { label: "الفص الأيمن", detail: "أكبر فصوص الكبد" },
+ "left-lobe": { label: "الفص الأيسر", detail: "يتجاوز الخط المنصّف" },
+ portal: { label: "الوريد البابي", detail: "وارد غني بالمغذّيات" },
+ },
+ },
+ kidneys: {
+ name: "الكلى",
+ system: "الجهاز البولي",
+ description: "عضوا ترشيح متناظران يوازنان السوائل والكهارل وضغط الدم وطرح الفضلات.",
+ poetic: "المرشّحات البارعة",
+ size: "كلٌّ بحجم فأرة حاسوب تقريبًا",
+ weight: "120–170 غ لكلٍّ منهما",
+ location: "على جانبي العمود الفقري أسفل الأضلاع",
+ function: "يرشّح الدم ويكوّن البول",
+ dailyFact: "تُرشّح نحو 180 لترًا من السوائل",
+ medical: "تضبط النيفرونات كيمياء مجرى الدم بدقّة.",
+ bloodSupply: "الشرايين الكلوية",
+ funFact: "تستعيدان تقريبًا كل ما ترشّحانه؛ ولا يغادر الجسم كبول سوى نحو 1–2 لتر.",
+ tissue: "القشرة الكلوية",
+ comparison: "الكلى مقابل الكبد",
+ conditions: [
+ "حصى الكلى",
+ "مرض الكلى المزمن",
+ "التهاب المسالك البولية",
+ "التهاب كبيبات الكلى",
+ "مرض الكلى متعدّد الكيسات",
+ "ارتفاع ضغط الدم الكلوي",
+ "الأذى الكلوي الحاد",
+ "المتلازمة الكلائية",
+ ],
+ hotspots: {
+ cortex: { label: "القشرة الكلوية", detail: "طبقة الترشيح الخارجية" },
+ medulla: { label: "لُبّ الكلية", detail: "يركّز البول" },
+ ureter: { label: "الحالب", detail: "ينقل البول" },
+ },
+ },
+ eyeball: {
+ name: "العين",
+ system: "الجهاز الحسي",
+ description: "عضو حسّي دقيق يحوّل الضوء المركَّز إلى إشارات عصبية تُفسَّر رؤيةً.",
+ poetic: "نافذة مصنوعة من ضوء",
+ size: "نحو 24 مم عرضًا",
+ weight: "نحو 7.5 غ",
+ location: "داخل المحجر العظمي",
+ function: "يلتقط الضوء ويركّزه",
+ dailyFact: "يقوم بآلاف الحركات الدقيقة",
+ medical: "الشبكية امتداد للجهاز العصبي المركزي.",
+ bloodSupply: "الشريان العيني",
+ funFact: "لا تحمل القرنية أي أوعية دموية على الإطلاق؛ فهي تأخذ الأكسجين من الهواء مباشرة.",
+ tissue: "طبقات الشبكية",
+ comparison: "العين مقابل الدماغ",
+ conditions: [
+ "قصر النظر",
+ "الساد (المياه البيضاء)",
+ "الزَّرَق (المياه الزرقاء)",
+ "التنكّس البقعي",
+ "انفصال الشبكية",
+ "جفاف العين",
+ "اللابؤرية",
+ "التهاب الملتحمة",
+ ],
+ hotspots: {
+ cornea: { label: "القرنية", detail: "سطح تركيز شفّاف" },
+ iris: { label: "القزحية", detail: "تتحكّم في دخول الضوء" },
+ optic: { label: "العصب البصري", detail: "ينقل الإشارات البصرية" },
+ },
+ },
+ intestine: {
+ name: "الأمعاء",
+ system: "الجهاز الهضمي",
+ description: "ممرّ هضمي مطوي تُمتصّ فيه المغذّيات ويدعم فيه الميكروبيوم صحّة الجسم كلّه.",
+ poetic: "الحديقة الداخلية",
+ size: "نحو 6–7 أمتار عند مدّه",
+ weight: "يختلف حسب المحتوى",
+ location: "وسط البطن وأسفله",
+ function: "الهضم وامتصاص المغذّيات",
+ dailyFact: "يأوي تريليونات الكائنات الدقيقة",
+ medical: "يتضخّم سطحه بالطيّات والزُّغابات والزُّغيبات الدقيقة.",
+ bloodSupply: "الشريانان المساريقيان العلوي والسفلي",
+ funFact: "تتجدّد بطانته كل بضعة أيام — أسرع تجدّد لأي نسيج في الجسم.",
+ tissue: "زُغابات الأمعاء",
+ comparison: "الأمعاء مقابل الكبد",
+ conditions: [
+ "متلازمة القولون العصبي",
+ "داء الأمعاء الالتهابي",
+ "الداء البطني (السيلياك)",
+ "التهاب الرتوج",
+ "انسداد الأمعاء",
+ "سلائل القولون والمستقيم",
+ "داء كرون",
+ "عدم تحمّل اللاكتوز",
+ ],
+ hotspots: {
+ duodenum: { label: "الاثنا عشر", detail: "أول أجزاء الأمعاء الدقيقة" },
+ jejunum: { label: "الصائم", detail: "منطقة الامتصاص الرئيسية" },
+ colon: { label: "القولون", detail: "يستعيد الماء" },
+ },
+ },
+ pancreas: {
+ name: "البنكرياس",
+ system: "جهاز الغدد الصماء",
+ description: "غدّة مزدوجة الوظيفة تفرز الإنزيمات الهاضمة في الأمعاء والهرمونات التي تثبّت سكّر الدم.",
+ poetic: "المنظّم الهادئ",
+ size: "نحو 15 سم طولًا",
+ weight: "70–100 غ",
+ location: "خلف المعدة، عبر أعلى البطن",
+ function: "الإنزيمات الهاضمة والإنسولين",
+ dailyFact: "يصنع نحو 1.5 لتر من العصارة الغنية بالإنزيمات",
+ medical: "تفرز جزر لانغرهانس الإنسولين والغلوكاغون لموازنة سكّر الدم.",
+ bloodSupply: "الشريان الطحالي والشرايين البنكرياسية الاثنا عشرية",
+ funFact: "بالكاد 2% منه يصنع الهرمونات؛ والباقي مكرَّس للإنزيمات الهاضمة.",
+ tissue: "عُنيبات البنكرياس",
+ comparison: "البنكرياس مقابل الكبد",
+ conditions: [
+ "التهاب البنكرياس",
+ "السكري من النوع الأول",
+ "سرطان البنكرياس",
+ "السكري من النوع الثاني",
+ "القصور الإفرازي الخارجي",
+ "كيسات البنكرياس",
+ "التهاب البنكرياس الحصوي",
+ "الورم الإنسوليني",
+ ],
+ hotspots: {
+ head: { label: "الرأس", detail: "يحتضنه الاثنا عشر" },
+ body: { label: "الجسم", detail: "يعبر العمود الفقري" },
+ tail: { label: "الذيل", detail: "يمتدّ إلى الطحال" },
+ duct: { label: "القناة البنكرياسية", detail: "تصرّف الإنزيمات إلى الأمعاء" },
+ },
+ },
+ skin: {
+ name: "الجلد",
+ system: "الجهاز الجلدي",
+ description: "أكبر أعضاء الجسم — حاجز حيّ يحسّ اللمس ويحتفظ بالماء وينظّم الحرارة.",
+ poetic: "الحدّ الحيّ",
+ size: "نحو 2 م² عند بسطه",
+ weight: "3.5–5 كغ",
+ location: "يغطّي الجسم بأكمله",
+ function: "يحمي ويحسّ ويبرّد",
+ dailyFact: "يتساقط منه نحو 500 مليون خلية",
+ medical: "ثلاث طبقات — البشرة والأدمة وتحت الجلد — لكلٍّ منها دور مميّز.",
+ bloodSupply: "الضفيرة الوعائية الأدمية",
+ funFact: "قد يحوي سنتيمتر مربّع واحد مئات الغدد العرقية وأمتارًا من الأوعية الدموية.",
+ tissue: "طبقات البشرة",
+ comparison: "الجلد مقابل الأمعاء",
+ conditions: [
+ "الإكزيما",
+ "الصدفية",
+ "الميلانوما (سرطان الجلد)",
+ "حَبّ الشباب",
+ "التهاب النسيج الخلوي",
+ "التهاب الجلد التماسي",
+ "العُدّ الوردي",
+ "البهاق",
+ ],
+ hotspots: {
+ epidermis: { label: "البشرة", detail: "الطبقة الواقية الخارجية" },
+ dermis: { label: "الأدمة", detail: "أعصاب وأوعية وغدد" },
+ hypodermis: { label: "تحت الجلد", detail: "الدهون والعزل" },
+ follicle: { label: "جُريب الشعرة", detail: "يثبّت كل شعرة" },
+ },
+ },
+};
+
+/**
+ * Per-locale translations. English is intentionally absent — the base
+ * anatomy-data.ts is the English source, and the merge helper falls back to it.
+ */
+export const organTranslations: Partial> = { de, fr, it, ar };
diff --git a/app/lib/i18n/ui.ts b/app/lib/i18n/ui.ts
new file mode 100644
index 0000000..28426a9
--- /dev/null
+++ b/app/lib/i18n/ui.ts
@@ -0,0 +1,645 @@
+import type { Locale } from "./config";
+
+/**
+ * UI chrome strings. Templates use `{name}`, `{system}`, `{location}`,
+ * `{comparison}` placeholders, filled by the `t()` helper in context.tsx.
+ * English is the source of truth; every other locale must match its shape.
+ */
+export type Dictionary = {
+ // Brand + top bar
+ tagline: string;
+ brandHome: string;
+ navExplore: string;
+ navSystems: string;
+ navLessons: string;
+ navLibrary: string;
+ navNotes: string;
+ navPrimary: string;
+ searchPlaceholder: string;
+ profileOpen: string;
+ languageLabel: string;
+ languageChange: string;
+ mobileLibraryOpen: string;
+
+ // Library panel
+ libraryTitle: string;
+ libraryClose: string;
+ librarySaved: string;
+ viewAll: string;
+ curiosityQuote: string;
+ curiosityCta: string;
+
+ // Info panel
+ kickerThe: string;
+ keyFacts: string;
+ factSize: string;
+ factWeight: string;
+ factDaily: string;
+ factLocation: string;
+ factBlood: string;
+ factFunction: string;
+ medicalTitle: string;
+ funTitle: string;
+ viewLesson: string;
+ actAnimate: string;
+ actQuiz: string;
+ actCompare: string;
+
+ // Compare strip
+ compareAria: string;
+ compareComparing: string;
+ compareReference: string;
+ compareVs: string;
+ comparePrimaryRole: string;
+ compareScale: string;
+ compareClose: string;
+
+ // Learning cards
+ learningResources: string;
+ cardMicroTitle: string;
+ cardMicroCta: string;
+ cardCompareTitle: string;
+ cardCompareCta: string;
+ cardFuncTitle: string;
+ cardFuncCta: string;
+ cardFuncPlayAria: string;
+ cardClinicalTitle: string;
+ cardClinicalHeading: string;
+ cardClinicalCta: string;
+ cardSystemTitle: string;
+ cardSystemCta: string;
+ cardSystemAria: string;
+
+ // Modal
+ modalGuided: string;
+ modalQuizTitle: string;
+ modalAnimationTitle: string;
+ modalSystemTitle: string;
+ modalLessonTitle: string;
+ modalClose: string;
+ quizQuestion: string;
+ quizOpt1: string;
+ quizOpt2: string;
+ quizOpt3: string;
+ systemIntro: string;
+ modalSystemLabel: string;
+ modalPrimaryRole: string;
+ modalBlood: string;
+ lessonBody: string;
+ continueExploring: string;
+
+ // Viewer
+ viewerAria: string;
+ toolsAria: string;
+ toolRotate: string;
+ toolZoom: string;
+ toolIsolate: string;
+ toolSection: string;
+ toolLayers: string;
+ toolCompare: string;
+ toolReset: string;
+ tipTitle: string;
+ tipLine1: string;
+ tipLine2: string;
+ tipLine3: string;
+ tipAria: string;
+ calloutClose: string;
+ loaderPreparing: string;
+ autoRotate: string;
+ viewCaption: string;
+
+ // Image alt text
+ altThumb: string;
+ altIllustration: string;
+ altMicro: string;
+ altCompare: string;
+ altPlain: string;
+ altInPlace: string;
+};
+
+const en: Dictionary = {
+ tagline: "Learn anatomy like an artist",
+ brandHome: "Anatomy Atelier home",
+ navExplore: "Explore",
+ navSystems: "Systems",
+ navLessons: "Lessons",
+ navLibrary: "Library",
+ navNotes: "Notes",
+ navPrimary: "Primary navigation",
+ searchPlaceholder: "Search organs, topics…",
+ profileOpen: "Open learner profile",
+ languageLabel: "Language",
+ languageChange: "Change language",
+ mobileLibraryOpen: "Open organ library",
+
+ libraryTitle: "Organ library",
+ libraryClose: "Close library",
+ librarySaved: "Saved organs",
+ viewAll: "View all organs",
+ curiosityQuote: "Learning is\nan act of curiosity.",
+ curiosityCta: "Keep exploring!",
+
+ kickerThe: "The {name}",
+ keyFacts: "Key facts",
+ factSize: "Size",
+ factWeight: "Weight",
+ factDaily: "Daily",
+ factLocation: "Location",
+ factBlood: "Blood supply",
+ factFunction: "Function",
+ medicalTitle: "Medical importance",
+ funTitle: "Did you know",
+ viewLesson: "View lesson",
+ actAnimate: "Animate",
+ actQuiz: "Quiz",
+ actCompare: "Compare",
+
+ compareAria: "Organ comparison",
+ compareComparing: "Comparing",
+ compareReference: "Reference",
+ compareVs: "vs.",
+ comparePrimaryRole: "Primary role",
+ compareScale: "Scale",
+ compareClose: "Close comparison",
+
+ learningResources: "{name} learning resources",
+ cardMicroTitle: "Microscopic view",
+ cardMicroCta: "Explore tissue",
+ cardCompareTitle: "Compare organs",
+ cardCompareCta: "Open comparison",
+ cardFuncTitle: "Function animation",
+ cardFuncCta: "Play animation",
+ cardFuncPlayAria: "Play the {name} function animation",
+ cardClinicalTitle: "Clinical notes",
+ cardClinicalHeading: "Common conditions",
+ cardClinicalCta: "See all",
+ cardSystemTitle: "Where it works",
+ cardSystemCta: "See the system",
+ cardSystemAria: "See where the {name} sits in the body",
+
+ modalGuided: "Guided discovery",
+ modalQuizTitle: "{name} quick quiz",
+ modalAnimationTitle: "{name} in motion",
+ modalSystemTitle: "{name} in the body",
+ modalLessonTitle: "Inside the {name}",
+ modalClose: "Close",
+ quizQuestion: "Which statement best describes the {name}?",
+ quizOpt1: "It plays a specialized role in maintaining the body",
+ quizOpt2: "It works completely independently",
+ quizOpt3: "It is active only during sleep",
+ systemIntro: "{location}. Trace how the {name} connects to the rest of the body.",
+ modalSystemLabel: "System",
+ modalPrimaryRole: "Primary role",
+ modalBlood: "Blood supply",
+ lessonBody:
+ "Follow the highlighted structures, rotate the specimen, and connect form with function. This short study moment is designed to build a durable mental model.",
+ continueExploring: "Continue exploring",
+
+ viewerAria: "{name} interactive viewer",
+ toolsAria: "3D viewer tools",
+ toolRotate: "Rotate",
+ toolZoom: "Zoom",
+ toolIsolate: "Isolate",
+ toolSection: "Cross-section",
+ toolLayers: "Layers",
+ toolCompare: "Compare",
+ toolReset: "Reset",
+ tipTitle: "Tip",
+ tipLine1: "Drag to rotate",
+ tipLine2: "Scroll to zoom",
+ tipLine3: "Click a dot to learn more",
+ tipAria: "Viewer instructions",
+ calloutClose: "Close",
+ loaderPreparing: "Preparing the {name}",
+ autoRotate: "Auto rotate",
+ viewCaption: "3D specimen · click a dot to explore",
+
+ altThumb: "{name} thumbnail",
+ altIllustration: "{name} anatomical illustration",
+ altMicro: "{name} microscopic tissue view",
+ altCompare: "{comparison} anatomical comparison",
+ altPlain: "{name} illustration",
+ altInPlace: "{name} shown in place within the {system}",
+};
+
+const ar: Dictionary = {
+ tagline: "تعلّم التشريح كما يفعل الفنان",
+ brandHome: "الصفحة الرئيسية لأتيليه التشريح",
+ navExplore: "استكشاف",
+ navSystems: "الأجهزة",
+ navLessons: "الدروس",
+ navLibrary: "المكتبة",
+ navNotes: "الملاحظات",
+ navPrimary: "التنقل الرئيسي",
+ searchPlaceholder: "ابحث عن الأعضاء والمواضيع…",
+ profileOpen: "فتح ملف المتعلّم",
+ languageLabel: "اللغة",
+ languageChange: "تغيير اللغة",
+ mobileLibraryOpen: "فتح مكتبة الأعضاء",
+
+ libraryTitle: "مكتبة الأعضاء",
+ libraryClose: "إغلاق المكتبة",
+ librarySaved: "الأعضاء المحفوظة",
+ viewAll: "عرض جميع الأعضاء",
+ curiosityQuote: "التعلّم\nفعلٌ من أفعال الفضول.",
+ curiosityCta: "واصل الاستكشاف!",
+
+ kickerThe: "{name}",
+ keyFacts: "حقائق أساسية",
+ factSize: "الحجم",
+ factWeight: "الوزن",
+ factDaily: "يوميًا",
+ factLocation: "الموقع",
+ factBlood: "التروية الدموية",
+ factFunction: "الوظيفة",
+ medicalTitle: "الأهمية الطبية",
+ funTitle: "هل تعلم",
+ viewLesson: "عرض الدرس",
+ actAnimate: "تحريك",
+ actQuiz: "اختبار",
+ actCompare: "مقارنة",
+
+ compareAria: "مقارنة الأعضاء",
+ compareComparing: "تجري مقارنة",
+ compareReference: "المرجع",
+ compareVs: "مقابل",
+ comparePrimaryRole: "الدور الأساسي",
+ compareScale: "المقياس",
+ compareClose: "إغلاق المقارنة",
+
+ learningResources: "مصادر تعلّم {name}",
+ cardMicroTitle: "منظر مجهري",
+ cardMicroCta: "استكشاف النسيج",
+ cardCompareTitle: "مقارنة الأعضاء",
+ cardCompareCta: "فتح المقارنة",
+ cardFuncTitle: "رسم متحرك للوظيفة",
+ cardFuncCta: "تشغيل الرسم المتحرك",
+ cardFuncPlayAria: "تشغيل الرسم المتحرك لوظيفة {name}",
+ cardClinicalTitle: "ملاحظات سريرية",
+ cardClinicalHeading: "الحالات الشائعة",
+ cardClinicalCta: "عرض الكل",
+ cardSystemTitle: "أين يعمل",
+ cardSystemCta: "عرض الجهاز",
+ cardSystemAria: "شاهد موضع {name} داخل الجسم",
+
+ modalGuided: "اكتشاف موجّه",
+ modalQuizTitle: "اختبار سريع عن {name}",
+ modalAnimationTitle: "{name} أثناء الحركة",
+ modalSystemTitle: "{name} داخل الجسم",
+ modalLessonTitle: "داخل {name}",
+ modalClose: "إغلاق",
+ quizQuestion: "أي عبارة تصف {name} على نحوٍ أدق؟",
+ quizOpt1: "يؤدّي دورًا متخصصًا في الحفاظ على الجسم",
+ quizOpt2: "يعمل باستقلالٍ تام",
+ quizOpt3: "ينشط أثناء النوم فقط",
+ systemIntro: "{location}. تتبّع كيف يتصل {name} ببقية أجزاء الجسم.",
+ modalSystemLabel: "الجهاز",
+ modalPrimaryRole: "الدور الأساسي",
+ modalBlood: "التروية الدموية",
+ lessonBody:
+ "تتبّع البنى المميّزة، وأدِر العيّنة، واربط الشكل بالوظيفة. صُمّمت هذه اللحظة الدراسية القصيرة لبناء نموذجٍ ذهنيٍّ راسخ.",
+ continueExploring: "واصل الاستكشاف",
+
+ viewerAria: "عارض تفاعلي لـ {name}",
+ toolsAria: "أدوات العارض ثلاثي الأبعاد",
+ toolRotate: "تدوير",
+ toolZoom: "تكبير",
+ toolIsolate: "عزل",
+ toolSection: "مقطع عرضي",
+ toolLayers: "طبقات",
+ toolCompare: "مقارنة",
+ toolReset: "إعادة ضبط",
+ tipTitle: "تلميح",
+ tipLine1: "اسحب للتدوير",
+ tipLine2: "مرّر للتكبير",
+ tipLine3: "انقر على نقطة لمعرفة المزيد",
+ tipAria: "تعليمات العارض",
+ calloutClose: "إغلاق",
+ loaderPreparing: "جارٍ تحضير {name}",
+ autoRotate: "تدوير تلقائي",
+ viewCaption: "عيّنة ثلاثية الأبعاد · انقر على نقطة للاستكشاف",
+
+ altThumb: "صورة مصغّرة لـ {name}",
+ altIllustration: "رسم تشريحي لـ {name}",
+ altMicro: "منظر مجهري لنسيج {name}",
+ altCompare: "مقارنة تشريحية: {comparison}",
+ altPlain: "رسم توضيحي لـ {name}",
+ altInPlace: "{name} معروض في موضعه ضمن {system}",
+};
+
+const de: Dictionary = {
+ tagline: "Anatomie lernen wie ein Künstler",
+ brandHome: "Anatomy Atelier Startseite",
+ navExplore: "Entdecken",
+ navSystems: "Systeme",
+ navLessons: "Lektionen",
+ navLibrary: "Bibliothek",
+ navNotes: "Notizen",
+ navPrimary: "Hauptnavigation",
+ searchPlaceholder: "Organe, Themen suchen…",
+ profileOpen: "Lernprofil öffnen",
+ languageLabel: "Sprache",
+ languageChange: "Sprache ändern",
+ mobileLibraryOpen: "Organbibliothek öffnen",
+
+ libraryTitle: "Organbibliothek",
+ libraryClose: "Bibliothek schließen",
+ librarySaved: "Gespeicherte Organe",
+ viewAll: "Alle Organe anzeigen",
+ curiosityQuote: "Lernen ist\nein Akt der Neugier.",
+ curiosityCta: "Weiter erkunden!",
+
+ kickerThe: "{name}",
+ keyFacts: "Wichtige Fakten",
+ factSize: "Größe",
+ factWeight: "Gewicht",
+ factDaily: "Täglich",
+ factLocation: "Lage",
+ factBlood: "Blutversorgung",
+ factFunction: "Funktion",
+ medicalTitle: "Medizinische Bedeutung",
+ funTitle: "Wusstest du",
+ viewLesson: "Lektion ansehen",
+ actAnimate: "Animieren",
+ actQuiz: "Quiz",
+ actCompare: "Vergleichen",
+
+ compareAria: "Organvergleich",
+ compareComparing: "Im Vergleich",
+ compareReference: "Referenz",
+ compareVs: "vs.",
+ comparePrimaryRole: "Hauptaufgabe",
+ compareScale: "Maßstab",
+ compareClose: "Vergleich schließen",
+
+ learningResources: "Lernmaterialien zu {name}",
+ cardMicroTitle: "Mikroskopische Ansicht",
+ cardMicroCta: "Gewebe erkunden",
+ cardCompareTitle: "Organe vergleichen",
+ cardCompareCta: "Vergleich öffnen",
+ cardFuncTitle: "Funktionsanimation",
+ cardFuncCta: "Animation abspielen",
+ cardFuncPlayAria: "Funktionsanimation von {name} abspielen",
+ cardClinicalTitle: "Klinische Notizen",
+ cardClinicalHeading: "Häufige Erkrankungen",
+ cardClinicalCta: "Alle ansehen",
+ cardSystemTitle: "Wo es wirkt",
+ cardSystemCta: "System ansehen",
+ cardSystemAria: "Sehen, wo {name} im Körper liegt",
+
+ modalGuided: "Geführte Entdeckung",
+ modalQuizTitle: "Kurzquiz zu {name}",
+ modalAnimationTitle: "{name} in Bewegung",
+ modalSystemTitle: "{name} im Körper",
+ modalLessonTitle: "Im Inneren von {name}",
+ modalClose: "Schließen",
+ quizQuestion: "Welche Aussage beschreibt {name} am besten?",
+ quizOpt1: "Es erfüllt eine spezialisierte Aufgabe zur Erhaltung des Körpers",
+ quizOpt2: "Es arbeitet völlig unabhängig",
+ quizOpt3: "Es ist nur im Schlaf aktiv",
+ systemIntro: "{location}. Verfolge, wie {name} mit dem übrigen Körper verbunden ist.",
+ modalSystemLabel: "System",
+ modalPrimaryRole: "Hauptaufgabe",
+ modalBlood: "Blutversorgung",
+ lessonBody:
+ "Folge den hervorgehobenen Strukturen, drehe das Präparat und verbinde Form mit Funktion. Dieser kurze Lernmoment soll ein dauerhaftes mentales Modell aufbauen.",
+ continueExploring: "Weiter erkunden",
+
+ viewerAria: "Interaktiver Betrachter für {name}",
+ toolsAria: "3D-Betrachter-Werkzeuge",
+ toolRotate: "Drehen",
+ toolZoom: "Zoom",
+ toolIsolate: "Isolieren",
+ toolSection: "Querschnitt",
+ toolLayers: "Schichten",
+ toolCompare: "Vergleichen",
+ toolReset: "Zurücksetzen",
+ tipTitle: "Tipp",
+ tipLine1: "Ziehen zum Drehen",
+ tipLine2: "Scrollen zum Zoomen",
+ tipLine3: "Auf einen Punkt klicken für mehr",
+ tipAria: "Anleitung zum Betrachter",
+ calloutClose: "Schließen",
+ loaderPreparing: "{name} wird vorbereitet",
+ autoRotate: "Auto-Drehung",
+ viewCaption: "3D-Präparat · auf einen Punkt klicken zum Erkunden",
+
+ altThumb: "Miniaturansicht von {name}",
+ altIllustration: "Anatomische Illustration von {name}",
+ altMicro: "Mikroskopische Gewebeansicht von {name}",
+ altCompare: "Anatomischer Vergleich: {comparison}",
+ altPlain: "Illustration von {name}",
+ altInPlace: "{name} im {system} an seinem Platz gezeigt",
+};
+
+const fr: Dictionary = {
+ tagline: "Apprendre l’anatomie comme un artiste",
+ brandHome: "Accueil Anatomy Atelier",
+ navExplore: "Explorer",
+ navSystems: "Systèmes",
+ navLessons: "Leçons",
+ navLibrary: "Bibliothèque",
+ navNotes: "Notes",
+ navPrimary: "Navigation principale",
+ searchPlaceholder: "Rechercher des organes, des sujets…",
+ profileOpen: "Ouvrir le profil de l’apprenant",
+ languageLabel: "Langue",
+ languageChange: "Changer de langue",
+ mobileLibraryOpen: "Ouvrir la bibliothèque d’organes",
+
+ libraryTitle: "Bibliothèque d’organes",
+ libraryClose: "Fermer la bibliothèque",
+ librarySaved: "Organes enregistrés",
+ viewAll: "Voir tous les organes",
+ curiosityQuote: "Apprendre est\nun acte de curiosité.",
+ curiosityCta: "Continuez à explorer !",
+
+ kickerThe: "{name}",
+ keyFacts: "Faits clés",
+ factSize: "Taille",
+ factWeight: "Poids",
+ factDaily: "Au quotidien",
+ factLocation: "Emplacement",
+ factBlood: "Vascularisation",
+ factFunction: "Fonction",
+ medicalTitle: "Importance médicale",
+ funTitle: "Le saviez-vous",
+ viewLesson: "Voir la leçon",
+ actAnimate: "Animer",
+ actQuiz: "Quiz",
+ actCompare: "Comparer",
+
+ compareAria: "Comparaison d’organes",
+ compareComparing: "Comparaison",
+ compareReference: "Référence",
+ compareVs: "vs",
+ comparePrimaryRole: "Rôle principal",
+ compareScale: "Échelle",
+ compareClose: "Fermer la comparaison",
+
+ learningResources: "Ressources d’apprentissage : {name}",
+ cardMicroTitle: "Vue microscopique",
+ cardMicroCta: "Explorer le tissu",
+ cardCompareTitle: "Comparer les organes",
+ cardCompareCta: "Ouvrir la comparaison",
+ cardFuncTitle: "Animation de la fonction",
+ cardFuncCta: "Lancer l’animation",
+ cardFuncPlayAria: "Lancer l’animation de la fonction de {name}",
+ cardClinicalTitle: "Notes cliniques",
+ cardClinicalHeading: "Affections courantes",
+ cardClinicalCta: "Tout voir",
+ cardSystemTitle: "Où il agit",
+ cardSystemCta: "Voir le système",
+ cardSystemAria: "Voir où se situe {name} dans le corps",
+
+ modalGuided: "Découverte guidée",
+ modalQuizTitle: "Quiz rapide : {name}",
+ modalAnimationTitle: "{name} en mouvement",
+ modalSystemTitle: "{name} dans le corps",
+ modalLessonTitle: "À l’intérieur de {name}",
+ modalClose: "Fermer",
+ quizQuestion: "Quelle affirmation décrit le mieux {name} ?",
+ quizOpt1: "Il joue un rôle spécialisé dans le maintien du corps",
+ quizOpt2: "Il fonctionne de façon totalement indépendante",
+ quizOpt3: "Il n’est actif que pendant le sommeil",
+ systemIntro: "{location}. Suivez comment {name} se connecte au reste du corps.",
+ modalSystemLabel: "Système",
+ modalPrimaryRole: "Rôle principal",
+ modalBlood: "Vascularisation",
+ lessonBody:
+ "Suivez les structures mises en évidence, faites pivoter le spécimen et reliez la forme à la fonction. Ce court moment d’étude vise à bâtir un modèle mental durable.",
+ continueExploring: "Continuer l’exploration",
+
+ viewerAria: "Visionneuse interactive de {name}",
+ toolsAria: "Outils de la visionneuse 3D",
+ toolRotate: "Pivoter",
+ toolZoom: "Zoom",
+ toolIsolate: "Isoler",
+ toolSection: "Coupe",
+ toolLayers: "Couches",
+ toolCompare: "Comparer",
+ toolReset: "Réinitialiser",
+ tipTitle: "Astuce",
+ tipLine1: "Glisser pour pivoter",
+ tipLine2: "Défiler pour zoomer",
+ tipLine3: "Cliquer sur un point pour en savoir plus",
+ tipAria: "Instructions de la visionneuse",
+ calloutClose: "Fermer",
+ loaderPreparing: "Préparation de {name}",
+ autoRotate: "Rotation auto",
+ viewCaption: "Spécimen 3D · cliquez sur un point pour explorer",
+
+ altThumb: "Vignette de {name}",
+ altIllustration: "Illustration anatomique de {name}",
+ altMicro: "Vue microscopique du tissu de {name}",
+ altCompare: "Comparaison anatomique : {comparison}",
+ altPlain: "Illustration de {name}",
+ altInPlace: "{name} montré à sa place dans le {system}",
+};
+
+const it: Dictionary = {
+ tagline: "Impara l’anatomia come un artista",
+ brandHome: "Home di Anatomy Atelier",
+ navExplore: "Esplora",
+ navSystems: "Sistemi",
+ navLessons: "Lezioni",
+ navLibrary: "Libreria",
+ navNotes: "Note",
+ navPrimary: "Navigazione principale",
+ searchPlaceholder: "Cerca organi, argomenti…",
+ profileOpen: "Apri il profilo dello studente",
+ languageLabel: "Lingua",
+ languageChange: "Cambia lingua",
+ mobileLibraryOpen: "Apri la libreria degli organi",
+
+ libraryTitle: "Libreria degli organi",
+ libraryClose: "Chiudi la libreria",
+ librarySaved: "Organi salvati",
+ viewAll: "Vedi tutti gli organi",
+ curiosityQuote: "Imparare è\nun atto di curiosità.",
+ curiosityCta: "Continua a esplorare!",
+
+ kickerThe: "{name}",
+ keyFacts: "Fatti chiave",
+ factSize: "Dimensione",
+ factWeight: "Peso",
+ factDaily: "Ogni giorno",
+ factLocation: "Posizione",
+ factBlood: "Irrorazione sanguigna",
+ factFunction: "Funzione",
+ medicalTitle: "Importanza medica",
+ funTitle: "Lo sapevi",
+ viewLesson: "Vedi la lezione",
+ actAnimate: "Anima",
+ actQuiz: "Quiz",
+ actCompare: "Confronta",
+
+ compareAria: "Confronto tra organi",
+ compareComparing: "In confronto",
+ compareReference: "Riferimento",
+ compareVs: "vs",
+ comparePrimaryRole: "Ruolo principale",
+ compareScale: "Scala",
+ compareClose: "Chiudi il confronto",
+
+ learningResources: "Risorse di apprendimento: {name}",
+ cardMicroTitle: "Vista microscopica",
+ cardMicroCta: "Esplora il tessuto",
+ cardCompareTitle: "Confronta gli organi",
+ cardCompareCta: "Apri il confronto",
+ cardFuncTitle: "Animazione della funzione",
+ cardFuncCta: "Avvia l’animazione",
+ cardFuncPlayAria: "Avvia l’animazione della funzione di {name}",
+ cardClinicalTitle: "Note cliniche",
+ cardClinicalHeading: "Patologie comuni",
+ cardClinicalCta: "Vedi tutto",
+ cardSystemTitle: "Dove agisce",
+ cardSystemCta: "Vedi il sistema",
+ cardSystemAria: "Vedi dove si trova {name} nel corpo",
+
+ modalGuided: "Scoperta guidata",
+ modalQuizTitle: "Quiz veloce: {name}",
+ modalAnimationTitle: "{name} in movimento",
+ modalSystemTitle: "{name} nel corpo",
+ modalLessonTitle: "All’interno di {name}",
+ modalClose: "Chiudi",
+ quizQuestion: "Quale affermazione descrive meglio {name}?",
+ quizOpt1: "Svolge un ruolo specializzato nel mantenere il corpo",
+ quizOpt2: "Funziona in modo completamente indipendente",
+ quizOpt3: "È attivo solo durante il sonno",
+ systemIntro: "{location}. Segui come {name} si collega al resto del corpo.",
+ modalSystemLabel: "Sistema",
+ modalPrimaryRole: "Ruolo principale",
+ modalBlood: "Irrorazione sanguigna",
+ lessonBody:
+ "Segui le strutture evidenziate, ruota il campione e collega la forma alla funzione. Questo breve momento di studio è pensato per costruire un modello mentale duraturo.",
+ continueExploring: "Continua a esplorare",
+
+ viewerAria: "Visualizzatore interattivo di {name}",
+ toolsAria: "Strumenti del visualizzatore 3D",
+ toolRotate: "Ruota",
+ toolZoom: "Zoom",
+ toolIsolate: "Isola",
+ toolSection: "Sezione",
+ toolLayers: "Strati",
+ toolCompare: "Confronta",
+ toolReset: "Reimposta",
+ tipTitle: "Suggerimento",
+ tipLine1: "Trascina per ruotare",
+ tipLine2: "Scorri per ingrandire",
+ tipLine3: "Clicca su un punto per saperne di più",
+ tipAria: "Istruzioni del visualizzatore",
+ calloutClose: "Chiudi",
+ loaderPreparing: "Preparazione di {name}",
+ autoRotate: "Rotazione automatica",
+ viewCaption: "Campione 3D · clicca su un punto per esplorare",
+
+ altThumb: "Miniatura di {name}",
+ altIllustration: "Illustrazione anatomica di {name}",
+ altMicro: "Vista microscopica del tessuto di {name}",
+ altCompare: "Confronto anatomico: {comparison}",
+ altPlain: "Illustrazione di {name}",
+ altInPlace: "{name} mostrato al suo posto nel {system}",
+};
+
+export const dictionaries: Record = { en, ar, de, fr, it };
diff --git a/app/page.tsx b/app/page.tsx
index 9c17304..efb62bc 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,5 +1,10 @@
import { AnatomyApp } from "./components/AnatomyApp";
+import { LocaleProvider } from "./lib/i18n/context";
export default function Home() {
- return ;
+ return (
+
+
+
+ );
}