diff options
| author | LalleSX | 2026-10-24 14:58:21 +0200 |
|---|---|---|
| committer | LalleSX | 2026-10-24 14:58:21 +0200 |
| commit | 73b09270c54acab810c10a80bed1d77c1a7bb2ab (patch) | |
| tree | e5bae1180c9a76345cc7779a24988875542d2dd9 /src/i18n.ts | |
| parent | Add tests for math rendering and interactive scenes; update navigation and viewport handling (diff) | |
| download | 3D-Electrophysics-main.tar.gz 3D-Electrophysics-main.zip | |
Diffstat (limited to '')
| -rw-r--r-- | src/i18n.ts | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..2fd3dca --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,308 @@ +export type Language = 'en' | 'da'; + +export const languageOptions: { id: Language; label: string }[] = [ + { id: 'en', label: 'English' }, + { id: 'da', label: 'Dansk' }, +]; + +export interface Chrome { + subtitle: string; + title: string; + documentTitle: string; + metaDescription: string; + home: string; + collection: string; + language: string; + test: string; + experiments: string; + overview: string; + overviewTitle: string; + overviewText: string; + score: string; + subjects: string; + lessons: string; + reset: string; + original: string; + parameters: string; + measurements: string; + camera: string; + pause: string; + resume: string; + hint: string; + notice: string; + connect: string; + physics: string; + learn: string; + try: string; + scales: string; + build: string; + reasoning: string; + originalAnswers: string; + predict: string; + reveal: string; + hide: string; + navigation: string; + previous: string; + next: string; + view: string; + viewport: string; + study: string; + canvas: string; + webglStart: string; + webglLost: string; + experimentKicker: (index: number, total: number) => string; + questionKicker: (section: string, question: number, points: number) => string; +} + +const pad = (value: number) => String(value).padStart(2, '0'); + +const chrome: Record<Language, Chrome> = { + en: { + subtitle: 'Explore the invisible. Understand the physical.', + title: 'Electrotechnique & Electrophysics', + documentTitle: 'Test 4 in 3D · Electrophysics Lab', + metaDescription: 'Learn electric work, potential energy and dipole rotation through six interactive 3D lessons from Test 4.', + home: 'Field Lab home', + collection: 'Learning collection', + language: 'Language', + test: 'Test 4 in 3D', + experiments: 'All experiments', + overview: 'FROM EQUATIONS TO INTUITION', + overviewTitle: 'Electric energy, made visible.', + overviewText: 'Explore the six questions from Test 4. Change the scene, make a prediction, then follow the derivation.', + score: '6 questions · 3D study guide', + subjects: 'Electrophysics subjects', + lessons: 'Lessons', + reset: 'Reset experiment', + original: 'Original test question', + parameters: 'Experiment parameters', + measurements: 'Live measurements', + camera: 'Reset camera', + pause: 'Pause animation', + resume: 'Resume animation', + hint: 'Drag to orbit · Scroll to zoom · Right-drag to pan · Arrow keys to pan when focused', + notice: 'NOTICE WHAT CHANGES', + connect: 'Connect the scene to the answer', + physics: 'THE PHYSICS', + learn: 'Learn the physics', + try: 'TRY THIS', + scales: 'Model & visual scales', + build: 'BUILD YOUR ANSWER', + reasoning: 'Follow the reasoning', + originalAnswers: 'Original answers ↗', + predict: 'PAUSE & PREDICT', + reveal: 'Reveal explanation', + hide: 'Hide explanation', + navigation: 'Question navigation', + previous: '← Previous question', + next: 'Next question →', + view: '3D VIEW', + viewport: '3D workbench', + study: 'Test answers and prediction', + canvas: '3D experiment: drag to orbit, scroll to zoom, right-drag to pan', + webglStart: 'WebGL could not start. Enable browser hardware acceleration and reload to see the 3D scenes. Lessons, controls and measurements still work.', + webglLost: 'WebGL context lost. Reload to restore the 3D view. Numeric experiments still work.', + experimentKicker: (index, total) => `EXPERIMENT ${pad(index)} / ${pad(total)}`, + questionKicker: (section, question, points) => `TEST ${section} · QUESTION ${question} · ${points} POINTS`, + }, + da: { + subtitle: 'Udforsk det usynlige. Forstå fysikken.', + title: 'Elektroteknik og elektrofysik', + documentTitle: 'Test 4 i 3D · Elektrofysiklaboratoriet', + metaDescription: 'Lær elektrisk arbejde, potentiel energi og dipolrotation gennem seks interaktive 3D-lektioner fra Test 4.', + home: 'Feltlaboratoriets forside', + collection: 'Læringssamling', + language: 'Sprog', + test: 'Test 4 i 3D', + experiments: 'Alle eksperimenter', + overview: 'FRA LIGNINGER TIL INTUITION', + overviewTitle: 'Elektrisk energi gjort synlig.', + overviewText: 'Udforsk de seks spørgsmål fra Test 4. Ændr scenen, forudsig resultatet, og følg derefter udledningen.', + score: '6 spørgsmål · 3D-studievejledning', + subjects: 'Elektrofysikkens emner', + lessons: 'Lektioner', + reset: 'Nulstil eksperiment', + original: 'Oprindeligt eksamensspørgsmål', + parameters: 'Eksperimentparametre', + measurements: 'Målinger i realtid', + camera: 'Nulstil kamera', + pause: 'Sæt animation på pause', + resume: 'Genoptag animation', + hint: 'Træk for at dreje · Rul for at zoome · Højreklik og træk for at panorere · Piletaster panorerer, når feltet er valgt', + notice: 'BEMÆRK, HVAD DER ÆNDRER SIG', + connect: 'Forbind scenen med svaret', + physics: 'FYSIKKEN', + learn: 'Lær fysikken', + try: 'PRØV DETTE', + scales: 'Model- og visuelle skalaer', + build: 'BYG DIT SVAR', + reasoning: 'Følg ræsonnementet', + originalAnswers: 'Oprindelige svar ↗', + predict: 'PAUSE OG FORUDSIG', + reveal: 'Vis forklaring', + hide: 'Skjul forklaring', + navigation: 'Spørgsmålsnavigation', + previous: '← Forrige spørgsmål', + next: 'Næste spørgsmål →', + view: '3D-VISNING', + viewport: '3D-arbejdsbord', + study: 'Prøvesvar og forudsigelse', + canvas: '3D-eksperiment: træk for at dreje, rul for at zoome, højreklik og træk for at panorere', + webglStart: 'WebGL kunne ikke starte. Slå browserens hardwareacceleration til, og genindlæs for at se 3D-scenerne. Lektioner, betjening og målinger virker stadig.', + webglLost: 'WebGL-konteksten gik tabt. Genindlæs for at gendanne 3D-visningen. De numeriske eksperimenter virker stadig.', + experimentKicker: (index, total) => `EKSPERIMENT ${pad(index)} / ${pad(total)}`, + questionKicker: (section, question, points) => `TEST ${section} · SPØRGSMÅL ${question} · ${points} POINT`, + }, +}; + +const scenePhrases = { + en: { + origin: 'Origin', + onRightCharge: 'on right charge', + gaussian: 'Gaussian surface', + probe: 'Probe', + isolated: 'isolated', + connected: 'supply connected', + noCurrent: 'No net current', + conventional: 'Conventional current', + drift: String.raw`Electron drift · displayed $\times600$`, + conduction: 'Conduction band', + valence: 'Valence band', + midgap: 'midgap', + gap: 'gap', + energyConstant: 'Energy changes form · total stays constant', + }, + da: { + origin: 'Origo', + onRightCharge: 'på højre ladning', + gaussian: 'Gaussisk flade', + probe: 'Sonde', + isolated: 'isoleret', + connected: 'forsyning tilsluttet', + noCurrent: 'Ingen nettostrøm', + conventional: 'Konventionel strøm', + drift: String.raw`Elektrondrift · vist $\times600$`, + conduction: 'Ledningsbånd', + valence: 'Valensbånd', + midgap: 'båndmidte', + gap: 'gab', + energyConstant: 'Energien skifter form · summen er konstant', + }, +} as const; + +export type SceneKey = keyof typeof scenePhrases.en; + +const words: Record<Language, Record<string, string>> = { + en: {}, + da: { + Repulsive: 'Frastødende', + Attractive: 'Tiltrækkende', + 'On surface': 'På overfladen', + Inside: 'Inden for', + Outside: 'Uden for', + Undefined: 'Udefineret', + pairs: 'par', + }, +}; + +const measurementLabels: Record<Language, Record<string, string>> = { + en: {}, + da: { + '|E| at origin': '|E| i origo', + '|Force on right charge|': '|Kraft på højre ladning|', + Interaction: 'Vekselvirkning', + 'Net electric flux': 'Netto elektrisk flux', + 'Enclosed charge': 'Indesluttet ladning', + 'Source location': 'Kildens placering', + 'Interaction energy': 'Vekselvirkningsenergi', + 'Work by field, from 6 m': 'Feltets arbejde fra 6 m', + '|Force on moving charge|': '|Kraft på den bevægelige ladning|', + 'Potential at probe': 'Potential ved sonden', + 'Ex at probe': 'Ex ved sonden', + '|E| at probe': '|E| ved sonden', + Capacitance: 'Kapacitans', + 'Stored free charge |Q|': 'Lagret fri ladning |Q|', + 'Electric field': 'Elektrisk felt', + 'Stored energy': 'Lagret energi', + 'Plate voltage': 'Pladespænding', + 'Free charge |Q|': 'Fri ladning |Q|', + 'Net field': 'Nettofelt', + 'Polarization |P|': 'Polarisering |P|', + Resistance: 'Resistans', + 'Conventional current': 'Konventionel strøm', + 'Electron drift, x': 'Elektrondrift, x', + 'Dissipated power': 'Afgivet effekt', + 'Electron density n': 'Elektrontæthed n', + 'Hole density p': 'Hulletæthed p', + 'Conductivity σ': 'Ledningsevne σ', + 'Fermi shift EF − Ei': 'Forskydning af Fermi EF − Ei', + 'Field work a → b': 'Feltets arbejde a → b', + 'Work to current position': 'Arbejde til nuværende position', + 'Potential energy Uᵦ': 'Potentiel energi Uᵦ', + 'Potential energy U': 'Potentiel energi U', + 'Kinetic energy K': 'Kinetisk energi K', + 'Total K + U': 'Samlet K + U', + 'Particle speed': 'Partikelhastighed', + 'Potential energy Uₐ': 'Potentiel energi Uₐ', + 'Energy of current assembly': 'Energi af den aktuelle samling', + 'Three-charge energy U₁₂₃': 'Tre ladningers energi U₁₂₃', + 'Additional energy ΔU₀': 'Ekstra energi ΔU₀', + 'Pairs currently assembled': 'Par samlet nu', + 'Projection Δx': 'Projektion Δx', + 'Current angle θ': 'Aktuel vinkel θ', + 'Torque τz': 'Kraftmoment τz', + 'Work to current angle': 'Arbejde til aktuel vinkel', + }, +}; + +function storedLanguage(): Language { + try { + const stored = globalThis.localStorage?.getItem('language'); + if (stored === 'da' || stored === 'en') return stored; + } catch { + // Storage can be unavailable in private or non-browser contexts. + } + return 'en'; +} + +let language: Language = storedLanguage(); +const listeners = new Set<() => void>(); + +export function getLanguage(): Language { + return language; +} + +export function setLanguage(next: Language) { + language = next; + try { + localStorage.setItem('language', next); + } catch { + // Ignore storage failures; the in-memory choice still applies. + } + if (typeof document !== 'undefined') document.documentElement.lang = next; + for (const listener of listeners) listener(); +} + +export function onLanguageChange(listener: () => void) { + listeners.add(listener); + return () => listeners.delete(listener); +} + +export function t<K extends keyof Chrome>(key: K): Chrome[K] { + return chrome[language][key]; +} + +export function scene(key: SceneKey): string { + return scenePhrases[language][key]; +} + +export function word(english: string): string { + return words[language][english] ?? english; +} + +export function measurementLabel(english: string): string { + return measurementLabels[language][english] ?? english; +} + +export const daMeasurementLabels = measurementLabels.da; +export const daWords = words.da; |