From b319ef7c2870c6e19fddec8e2556c6b1bea955e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 04:57:17 +0000 Subject: [PATCH] Hoist all hardcoded strings to StringManager i18n and colors to SolarSystemModelsColors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strings: - Add `zodiac` section (12 sign names) to all 3 locale JSON files (EN/FR/ES) - Add `configurations.au`, `configurations.pausedFor` to all locales - Expose `getZodiacStrings()` on StringManager - PtolemaicTimeReadout: use `yearsStringProperty`/`daysStringProperty` via Multilink - ConfigurationsTimeReadout: replace imperative string building with DerivedProperty composed from model properties + string properties; import DAYS_PER_YEAR from constants - ConfigurationsScreenView: include `auStringProperty` in orbit-label Multilink - PtolemaicScreenView, PtolemaicZodiacStrip, ConfigurationsZodiacStrip: replace hardcoded ZODIAC_SIGNS/SIGN_NAMES arrays with per-sign string properties Colors (14 new ProfileColorProperty entries in SolarSystemModelsColors): - `orbitAreaBackgroundColorProperty` — dark sky behind both orbit diagrams - `zodiacTickColorProperty` — tick marks at sign boundaries - `zodiacLabelColorProperty` — zodiac sign labels (ring + strip) - `zodiacBorderColorProperty` — zodiac strip border (ConfigurationsZodiacStrip) - `zodiacDividerColorProperty` — divider lines between sign segments - `constellationLineColorProperty` — stick-figure lines in both ZodiacConstellationNode and ZodiacStrip - `constellationStarColorProperty` — star dot fills in ZodiacConstellationNode - `sunOrbitReferenceColorProperty` — faint reference circle in Ptolemaic view - `timelineBackgroundColorProperty` — ConfigurationsTimeline bg - `timelineBorderColorProperty` — ConfigurationsTimeline border - `timelineEventColorProperty` — event mark lines - `timelineSelectedColorProperty` — selected event highlight - `timelineCursorColorProperty` — time cursor line - `timelineLabelColorProperty` — event label text Co-Authored-By: Claude Sonnet 4.6 Claude-Session: https://claude.ai/code/session_016d5GzzgzLDv3mCVaG7C4BX --- src/SolarSystemModelsColors.ts | 71 +++++++++++++++++++ src/common/ZodiacConstellationNode.ts | 5 +- .../view/ConfigurationsScreenView.ts | 13 ++-- .../view/ConfigurationsTimeReadout.ts | 68 ++++++++++-------- .../view/ConfigurationsTimeline.ts | 25 +++++-- .../view/ConfigurationsZodiacStrip.ts | 45 ++++++------ src/i18n/StringManager.ts | 4 ++ src/i18n/strings_en.json | 16 +++++ src/i18n/strings_es.json | 38 +++++++--- src/i18n/strings_fr.json | 36 +++++++--- src/ptolemaic/view/PtolemaicScreenView.ts | 44 ++++++------ src/ptolemaic/view/PtolemaicTimeReadout.ts | 19 +++-- src/ptolemaic/view/PtolemaicZodiacStrip.ts | 44 ++++++------ 13 files changed, 299 insertions(+), 129 deletions(-) diff --git a/src/SolarSystemModelsColors.ts b/src/SolarSystemModelsColors.ts index 86d1457..b77cfc8 100644 --- a/src/SolarSystemModelsColors.ts +++ b/src/SolarSystemModelsColors.ts @@ -77,6 +77,77 @@ const SolarSystemModelsColors = { default: "#1e293b", projector: "#e8eaf6", }), + + // ── Sky / background ────────────────────────────────────────────────────── + + orbitAreaBackgroundColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "orbitAreaBackground", { + default: "#0a0a18", + projector: "#edf0f5", + }), + + // ── Zodiac ring decorations ──────────────────────────────────────────────── + + zodiacTickColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "zodiacTick", { + default: "#888899", + projector: "#555566", + }), + zodiacLabelColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "zodiacLabel", { + default: "#aabbcc", + projector: "#334455", + }), + zodiacBorderColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "zodiacBorder", { + default: "#555577", + projector: "#9999bb", + }), + zodiacDividerColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "zodiacDivider", { + default: "#444455", + projector: "#9999bb", + }), + + // ── Constellation decorations ────────────────────────────────────────────── + + constellationLineColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "constellationLine", { + default: "#4d6080", + projector: "#99aacc", + }), + constellationStarColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "constellationStar", { + default: "#aabbdd", + projector: "#556677", + }), + + // ── Ptolemaic reference geometry ────────────────────────────────────────── + + sunOrbitReferenceColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "sunOrbitReference", { + default: "#333355", + projector: "#99aabb", + }), + + // ── Configurations timeline ──────────────────────────────────────────────── + + timelineBackgroundColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "timelineBackground", { + default: "#0d1117", + projector: "#f0f4f8", + }), + timelineBorderColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "timelineBorder", { + default: "#334466", + projector: "#aabbcc", + }), + timelineEventColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "timelineEvent", { + default: "#446688", + projector: "#334466", + }), + timelineSelectedColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "timelineSelected", { + default: "#223355", + projector: "#ccd8e8", + }), + timelineCursorColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "timelineCursor", { + default: "#aabbcc", + projector: "#334466", + }), + timelineLabelColorProperty: new ProfileColorProperty(SolarSystemModelsNamespace, "timelineLabel", { + default: "#9999bb", + projector: "#445566", + }), }; export default SolarSystemModelsColors; diff --git a/src/common/ZodiacConstellationNode.ts b/src/common/ZodiacConstellationNode.ts index 9c696a5..c1a423e 100644 --- a/src/common/ZodiacConstellationNode.ts +++ b/src/common/ZodiacConstellationNode.ts @@ -1,5 +1,6 @@ import { Shape } from "scenerystack/kite"; import { Node, Path } from "scenerystack/scenery"; +import SolarSystemModelsColors from "../SolarSystemModelsColors.js"; import { ECLIPTIC_CONSTELLATIONS } from "./ZodiacConstellationsData.js"; // Radius at which constellation stars are drawn (in pixels) — inside the @@ -44,7 +45,7 @@ export class ZodiacConstellationNode extends Node { } } const linesPath = new Path(linesShape, { - stroke: "#556688", + stroke: SolarSystemModelsColors.constellationLineColorProperty, lineWidth: 0.5, opacity: 0.6, }); @@ -61,7 +62,7 @@ export class ZodiacConstellationNode extends Node { } } const starsPath = new Path(starsShape, { - fill: "#aabbdd", + fill: SolarSystemModelsColors.constellationStarColorProperty, stroke: null, opacity: 0.7, }); diff --git a/src/configurations/view/ConfigurationsScreenView.ts b/src/configurations/view/ConfigurationsScreenView.ts index 1ca9b0c..65814bd 100644 --- a/src/configurations/view/ConfigurationsScreenView.ts +++ b/src/configurations/view/ConfigurationsScreenView.ts @@ -54,6 +54,7 @@ export class ConfigurationsScreenView extends ScreenView { this.mvt = buildMvt(model.semimajorAxis1Property.value, model.semimajorAxis2Property.value); const a11y = StringManager.getInstance().getConfigurationsA11yStrings(); + const s = StringManager.getInstance().getConfigurationsStrings(); // ── Background ────────────────────────────────────────────────────────── const background = new Rectangle(0, 0, this.layoutBounds.width, this.layoutBounds.height, { @@ -63,7 +64,7 @@ export class ConfigurationsScreenView extends ScreenView { // ── Orbit area background ─────────────────────────────────────────────── const orbitAreaBg = new Rectangle(0, 0, ORBIT_AREA_SIZE, this.layoutBounds.height, { - fill: "#060d1a", + fill: SolarSystemModelsColors.orbitAreaBackgroundColorProperty, }); this.addChild(orbitAreaBg); @@ -176,11 +177,12 @@ export class ConfigurationsScreenView extends ScreenView { orbit2Circle.shape = Shape.circle(center.x, center.y, r2); // Labels at top of orbit circles - orbitLabel1.string = `${a1.toFixed(2)} AU`; + const au = s.auStringProperty.value; + orbitLabel1.string = `${a1.toFixed(2)} ${au}`; orbitLabel1.centerX = center.x; orbitLabel1.bottom = center.y - r1 - 4; - orbitLabel2.string = `${a2.toFixed(2)} AU`; + orbitLabel2.string = `${a2.toFixed(2)} ${au}`; orbitLabel2.centerX = center.x; orbitLabel2.bottom = center.y - r2 - 4; @@ -188,7 +190,10 @@ export class ConfigurationsScreenView extends ScreenView { updateSunPos(); }; - Multilink.multilink([model.semimajorAxis1Property, model.semimajorAxis2Property] as const, updateOrbits); + Multilink.multilink( + [model.semimajorAxis1Property, model.semimajorAxis2Property, s.auStringProperty] as const, + () => updateOrbits(), + ); // ── Zodiac strip at bottom ────────────────────────────────────────────── const zodiacStrip = new ConfigurationsZodiacStrip(model); diff --git a/src/configurations/view/ConfigurationsTimeReadout.ts b/src/configurations/view/ConfigurationsTimeReadout.ts index 8fa4d4a..21d7447 100644 --- a/src/configurations/view/ConfigurationsTimeReadout.ts +++ b/src/configurations/view/ConfigurationsTimeReadout.ts @@ -1,10 +1,10 @@ -import { Multilink } from "scenerystack/axon"; +import { DerivedProperty } from "scenerystack/axon"; import { Text, VBox } from "scenerystack/scenery"; import { PhetFont } from "scenerystack/scenery-phet"; import { SolarSystemModelsPanel } from "../../common/SolarSystemModelsPanel.js"; import { StringManager } from "../../i18n/StringManager.js"; import SolarSystemModelsColors from "../../SolarSystemModelsColors.js"; -import { PANEL_WIDTH } from "../../SolarSystemModelsConstants.js"; +import { DAYS_PER_YEAR, PANEL_WIDTH } from "../../SolarSystemModelsConstants.js"; import type { ConfigurationsModel } from "../model/ConfigurationsModel.js"; const READOUT_FONT = new PhetFont(13); @@ -14,39 +14,49 @@ const FONT_OPTS = { maxWidth: PANEL_WIDTH - 24, } as const; -const DAYS_PER_YEAR = 365.24; - export class ConfigurationsTimeReadout extends SolarSystemModelsPanel { public constructor(model: ConfigurationsModel) { - StringManager.getInstance().getConfigurationsStrings(); + const s = StringManager.getInstance().getConfigurationsStrings(); - const timeText = new Text("", FONT_OPTS); - const synodicText = new Text("", FONT_OPTS); - const configText = new Text("", FONT_OPTS); - const countdownText = new Text("", FONT_OPTS); - - Multilink.multilink([model.timeProperty, model.synodicPeriodProperty], (time, synodic) => { - const absTime = Math.abs(time); - const totalDays = absTime * DAYS_PER_YEAR; - const yrs = Math.floor(absTime); - const days = totalDays - yrs * DAYS_PER_YEAR; - const sign = time < 0 ? "-" : ""; - timeText.string = `${sign}${absTime.toFixed(3)} yr (${sign}${yrs} yr, ${sign}${days.toFixed(1)} d)`; - synodicText.string = `Synodic: ${synodic.toFixed(3)} yr`; - }); + const timeStringProperty = new DerivedProperty( + [model.timeProperty, s.yearsStringProperty, s.daysStringProperty] as const, + (time, yr, d) => { + const absTime = Math.abs(time); + const totalDays = absTime * DAYS_PER_YEAR; + const yrs = Math.floor(absTime); + const days = totalDays - yrs * DAYS_PER_YEAR; + const sign = time < 0 ? "-" : ""; + return `${sign}${absTime.toFixed(3)} ${yr} (${sign}${yrs} ${yr}, ${sign}${days.toFixed(1)} ${d})`; + }, + ); - model.currentConfigurationProperty.link((cfg) => { - configText.string = cfg.length > 0 ? cfg : ""; - }); + const synodicStringProperty = new DerivedProperty( + [model.synodicPeriodProperty, s.synodicPeriodStringProperty, s.yearsStringProperty] as const, + (synodic, label, yr) => `${label} ${synodic.toFixed(3)} ${yr}`, + ); - model.countdownRemainingProperty.link((remaining) => { - if (remaining > 0) { + const countdownStringProperty = new DerivedProperty( + [ + model.countdownRemainingProperty, + s.pausedForStringProperty, + s.secondStringProperty, + s.secondsStringProperty, + ] as const, + (remaining, pausedFor, second, seconds) => { + if (remaining <= 0) return ""; const secs = Math.ceil(remaining); - const unit = secs === 1 ? "second" : "seconds"; - countdownText.string = `Paused for ${secs} more ${unit}`; - } else { - countdownText.string = ""; - } + const unit = secs === 1 ? second : seconds; + return pausedFor.replace("{0}", String(secs)).replace("{1}", unit); + }, + ); + + const timeText = new Text(timeStringProperty, FONT_OPTS); + const synodicText = new Text(synodicStringProperty, FONT_OPTS); + const configText = new Text("", FONT_OPTS); + const countdownText = new Text(countdownStringProperty, FONT_OPTS); + + model.currentConfigurationProperty.link((cfg) => { + configText.string = cfg; }); const content = new VBox({ diff --git a/src/configurations/view/ConfigurationsTimeline.ts b/src/configurations/view/ConfigurationsTimeline.ts index 54a63ac..59d262d 100644 --- a/src/configurations/view/ConfigurationsTimeline.ts +++ b/src/configurations/view/ConfigurationsTimeline.ts @@ -3,6 +3,7 @@ import { Shape } from "scenerystack/kite"; import { DragListener, Node, Path, Rectangle, Text } from "scenerystack/scenery"; import { PhetFont } from "scenerystack/scenery-phet"; import { Tandem } from "scenerystack/tandem"; +import SolarSystemModelsColors from "../../SolarSystemModelsColors.js"; import { CONFIGURATIONS_TIMELINE_HEIGHT, CONFIGURATIONS_TIMELINE_WIDTH } from "../../SolarSystemModelsConstants.js"; import type { ConfigurationsModel } from "../model/ConfigurationsModel.js"; @@ -46,24 +47,38 @@ export class ConfigurationsTimeline extends Node { public constructor(model: ConfigurationsModel) { super(); - const bg = new Rectangle(0, 0, W, H, { fill: "#0d1117", stroke: "#334466", lineWidth: 1 }); + const bg = new Rectangle(0, 0, W, H, { + fill: SolarSystemModelsColors.timelineBackgroundColorProperty, + stroke: SolarSystemModelsColors.timelineBorderColorProperty, + lineWidth: 1, + }); this.addChild(bg); - const eventLayer = new Path(null, { stroke: "#446688", lineWidth: 1 }); + const eventLayer = new Path(null, { + stroke: SolarSystemModelsColors.timelineEventColorProperty, + lineWidth: 1, + }); this.addChild(eventLayer); - const selectedEventBg = new Rectangle(0, 0, W, 18, { fill: "#223355", visible: false }); + const selectedEventBg = new Rectangle(0, 0, W, 18, { + fill: SolarSystemModelsColors.timelineSelectedColorProperty, + visible: false, + }); this.addChild(selectedEventBg); const cursorLine = new Path(new Shape().moveTo(0, H / 2).lineTo(W, H / 2), { - stroke: "#aabbcc", + stroke: SolarSystemModelsColors.timelineCursorColorProperty, lineWidth: 1.5, }); this.addChild(cursorLine); const labelPool: Text[] = []; for (let i = 0; i < 20; i++) { - const t = new Text("", { font: new PhetFont(9), fill: "#9999bb", maxWidth: W - 8 }); + const t = new Text("", { + font: new PhetFont(9), + fill: SolarSystemModelsColors.timelineLabelColorProperty, + maxWidth: W - 8, + }); t.visible = false; labelPool.push(t); this.addChild(t); diff --git a/src/configurations/view/ConfigurationsZodiacStrip.ts b/src/configurations/view/ConfigurationsZodiacStrip.ts index b9d4b8a..6f28669 100644 --- a/src/configurations/view/ConfigurationsZodiacStrip.ts +++ b/src/configurations/view/ConfigurationsZodiacStrip.ts @@ -1,6 +1,7 @@ import { Multilink } from "scenerystack/axon"; import { Circle, Node, Rectangle, Text } from "scenerystack/scenery"; import { PhetFont } from "scenerystack/scenery-phet"; +import { StringManager } from "../../i18n/StringManager.js"; import SolarSystemModelsColors from "../../SolarSystemModelsColors.js"; import type { ConfigurationsModel } from "../model/ConfigurationsModel.js"; @@ -8,21 +9,6 @@ const STRIP_WIDTH = 600; const STRIP_HEIGHT = 60; const TWO_PI = 2 * Math.PI; -const ZODIAC_SIGNS = [ - "Aries", - "Taurus", - "Gemini", - "Cancer", - "Leo", - "Virgo", - "Libra", - "Scorpius", - "Sagittarius", - "Capricorn", - "Aquarius", - "Pisces", -]; - function mod(x: number, m: number): number { return ((x % m) + m) % m; } @@ -31,10 +17,26 @@ export class ConfigurationsZodiacStrip extends Node { public constructor(model: ConfigurationsModel) { super(); + const z = StringManager.getInstance().getZodiacStrings(); + const ZODIAC_SIGN_PROPS = [ + z.ariesStringProperty, + z.taurusStringProperty, + z.geminiStringProperty, + z.cancerStringProperty, + z.leoStringProperty, + z.virgoStringProperty, + z.libraStringProperty, + z.scorpiusStringProperty, + z.sagittariusStringProperty, + z.capricornStringProperty, + z.aquariusStringProperty, + z.piscesStringProperty, + ]; + // Band background const band = new Rectangle(0, 0, STRIP_WIDTH, STRIP_HEIGHT, { fill: SolarSystemModelsColors.zodiacBandColorProperty, - stroke: "#555577", + stroke: SolarSystemModelsColors.zodiacBorderColorProperty, lineWidth: 1, }); this.addChild(band); @@ -42,9 +44,9 @@ export class ConfigurationsZodiacStrip extends Node { // 12 sign labels const segW = STRIP_WIDTH / 12; for (let i = 0; i < 12; i++) { - const label = new Text(ZODIAC_SIGNS[i] ?? "", { + const label = new Text(ZODIAC_SIGN_PROPS[i]!, { font: new PhetFont(9), - fill: "#9999bb", + fill: SolarSystemModelsColors.zodiacLabelColorProperty, maxWidth: segW - 4, }); label.centerX = (i + 0.5) * segW; @@ -53,7 +55,9 @@ export class ConfigurationsZodiacStrip extends Node { // Divider if (i > 0) { - const divider = new Rectangle(i * segW, 0, 1, STRIP_HEIGHT, { fill: "#445" }); + const divider = new Rectangle(i * segW, 0, 1, STRIP_HEIGHT, { + fill: SolarSystemModelsColors.zodiacDividerColorProperty, + }); this.addChild(divider); } } @@ -89,8 +93,7 @@ export class ConfigurationsZodiacStrip extends Node { sunMarker.centerX = mod((sunLong * STRIP_WIDTH) / TWO_PI, STRIP_WIDTH); planetMarker.centerX = mod((planetLong * STRIP_WIDTH) / TWO_PI, STRIP_WIDTH); - const labelStr = `${Math.abs(elongDeg).toFixed(1)}° ${elongLabel}`; - elongText.string = labelStr; + elongText.string = `${Math.abs(elongDeg).toFixed(1)}° ${elongLabel}`; }, ); } diff --git a/src/i18n/StringManager.ts b/src/i18n/StringManager.ts index 3255f34..b7dc9b0 100644 --- a/src/i18n/StringManager.ts +++ b/src/i18n/StringManager.ts @@ -57,6 +57,10 @@ export class StringManager { return stringProperties.a11y.configurations; } + public getZodiacStrings() { + return stringProperties.zodiac; + } + public getPreferences() { return stringProperties.preferences; } diff --git a/src/i18n/strings_en.json b/src/i18n/strings_en.json index fa98416..3f0f1bd 100644 --- a/src/i18n/strings_en.json +++ b/src/i18n/strings_en.json @@ -48,6 +48,7 @@ "animationRate": "Animation Rate", "years": "yr", "days": "d", + "au": "AU", "synodicPeriod": "Synodic period:", "eventAction": "When Event is Reached", "run": "Run", @@ -56,6 +57,7 @@ "pauseTime": "Pause Time (s)", "second": "second", "seconds": "seconds", + "pausedFor": "Paused: {0} {1}", "opposition": "opposition", "quadratureEastern": "quadrature (eastern)", "conjunction": "conjunction", @@ -123,6 +125,20 @@ } } }, + "zodiac": { + "aries": "Aries", + "taurus": "Taurus", + "gemini": "Gemini", + "cancer": "Cancer", + "leo": "Leo", + "virgo": "Virgo", + "libra": "Libra", + "scorpius": "Scorpius", + "sagittarius": "Sagittarius", + "capricorn": "Capricorn", + "aquarius": "Aquarius", + "pisces": "Pisces" + }, "preferences": { "title": "Simulation", "exampleToggle": "Example Toggle" diff --git a/src/i18n/strings_es.json b/src/i18n/strings_es.json index b8421e6..e9e9d7d 100644 --- a/src/i18n/strings_es.json +++ b/src/i18n/strings_es.json @@ -48,22 +48,24 @@ "animationRate": "Animation Rate", "years": "yr", "days": "d", - "synodicPeriod": "Synodic period:", + "au": "UA", + "synodicPeriod": "Período sinódico:", "eventAction": "When Event is Reached", "run": "Run", "pause": "Pause", "lock": "Lock", "pauseTime": "Pause Time (s)", - "second": "second", - "seconds": "seconds", - "opposition": "opposition", - "quadratureEastern": "quadrature (eastern)", - "conjunction": "conjunction", - "quadratureWestern": "quadrature (western)", - "inferiorConjunction": "inferior conjunction", - "greatestElongationWestern": "greatest elongation (western)", - "superiorConjunction": "superior conjunction", - "greatestElongationEastern": "greatest elongation (eastern)" + "second": "segundo", + "seconds": "segundos", + "pausedFor": "En pausa: {0} {1}", + "opposition": "oposición", + "quadratureEastern": "cuadratura (este)", + "conjunction": "conjunción", + "quadratureWestern": "cuadratura (oeste)", + "inferiorConjunction": "conjunción inferior", + "greatestElongationWestern": "máxima elongación (oeste)", + "superiorConjunction": "conjunción superior", + "greatestElongationEastern": "máxima elongación (este)" }, "a11y": { "ptolemaic": { @@ -123,6 +125,20 @@ } } }, + "zodiac": { + "aries": "Aries", + "taurus": "Tauro", + "gemini": "Géminis", + "cancer": "Cáncer", + "leo": "Leo", + "virgo": "Virgo", + "libra": "Libra", + "scorpius": "Escorpio", + "sagittarius": "Sagitario", + "capricorn": "Capricornio", + "aquarius": "Acuario", + "pisces": "Piscis" + }, "preferences": { "title": "Simulación", "exampleToggle": "Ejemplo de alternancia" diff --git a/src/i18n/strings_fr.json b/src/i18n/strings_fr.json index abedbc6..c841af7 100644 --- a/src/i18n/strings_fr.json +++ b/src/i18n/strings_fr.json @@ -48,22 +48,24 @@ "animationRate": "Animation Rate", "years": "yr", "days": "d", - "synodicPeriod": "Synodic period:", + "au": "UA", + "synodicPeriod": "Période synodique :", "eventAction": "When Event is Reached", "run": "Run", "pause": "Pause", "lock": "Lock", "pauseTime": "Pause Time (s)", - "second": "second", - "seconds": "seconds", + "second": "seconde", + "seconds": "secondes", + "pausedFor": "En pause : {0} {1}", "opposition": "opposition", - "quadratureEastern": "quadrature (eastern)", - "conjunction": "conjunction", - "quadratureWestern": "quadrature (western)", - "inferiorConjunction": "inferior conjunction", - "greatestElongationWestern": "greatest elongation (western)", - "superiorConjunction": "superior conjunction", - "greatestElongationEastern": "greatest elongation (eastern)" + "quadratureEastern": "quadrature (est)", + "conjunction": "conjonction", + "quadratureWestern": "quadrature (ouest)", + "inferiorConjunction": "conjonction inférieure", + "greatestElongationWestern": "plus grande élongation (ouest)", + "superiorConjunction": "conjonction supérieure", + "greatestElongationEastern": "plus grande élongation (est)" }, "a11y": { "ptolemaic": { @@ -123,6 +125,20 @@ } } }, + "zodiac": { + "aries": "Bélier", + "taurus": "Taureau", + "gemini": "Gémeaux", + "cancer": "Cancer", + "leo": "Lion", + "virgo": "Vierge", + "libra": "Balance", + "scorpius": "Scorpion", + "sagittarius": "Sagittaire", + "capricorn": "Capricorne", + "aquarius": "Verseau", + "pisces": "Poissons" + }, "preferences": { "title": "Simulation", "exampleToggle": "Exemple de bascule" diff --git a/src/ptolemaic/view/PtolemaicScreenView.ts b/src/ptolemaic/view/PtolemaicScreenView.ts index ce87718..b2067cd 100644 --- a/src/ptolemaic/view/PtolemaicScreenView.ts +++ b/src/ptolemaic/view/PtolemaicScreenView.ts @@ -29,20 +29,6 @@ import { PtolemaicTimeControls } from "./PtolemaicTimeControls.js"; import { PtolemaicTimeReadout } from "./PtolemaicTimeReadout.js"; import { PtolemaicZodiacStrip } from "./PtolemaicZodiacStrip.js"; -const ZODIAC_SIGNS = [ - "Aries", - "Taurus", - "Gemini", - "Cancer", - "Leo", - "Virgo", - "Libra", - "Scorpius", - "Sagittarius", - "Capricorn", - "Aquarius", - "Pisces", -]; export class PtolemaicScreenView extends ScreenView { private readonly pathTrail: PtolemaicPathTrail; @@ -57,6 +43,22 @@ export class PtolemaicScreenView extends ScreenView { this.model = model; + const zodiacStrings = StringManager.getInstance().getZodiacStrings(); + const ZODIAC_SIGN_PROPS = [ + zodiacStrings.ariesStringProperty, + zodiacStrings.taurusStringProperty, + zodiacStrings.geminiStringProperty, + zodiacStrings.cancerStringProperty, + zodiacStrings.leoStringProperty, + zodiacStrings.virgoStringProperty, + zodiacStrings.libraStringProperty, + zodiacStrings.scorpiusStringProperty, + zodiacStrings.sagittariusStringProperty, + zodiacStrings.capricornStringProperty, + zodiacStrings.aquariusStringProperty, + zodiacStrings.piscesStringProperty, + ]; + // ── Model–view transform ─────────────────────────────────────────────── // Earth at model origin → view center-left; y inverted (Flash screen-y down) this.mvt = ModelViewTransform2.createSinglePointScaleInvertedYMapping( @@ -75,7 +77,7 @@ export class PtolemaicScreenView extends ScreenView { // ── Orbital area background ──────────────────────────────────────────── const orbitAreaBg = new Rectangle(0, 0, ORBIT_VIEW_CENTER_X * 2 + 20, this.layoutBounds.height, { - fill: "#0a0a18", + fill: SolarSystemModelsColors.orbitAreaBackgroundColorProperty, }); this.addChild(orbitAreaBg); @@ -89,7 +91,7 @@ export class PtolemaicScreenView extends ScreenView { for (let i = 0; i < 12; i++) { const angle = (i * Math.PI) / 6; // 0°, 30°, 60°, ... (sign boundaries) const tick = new Path(null, { - stroke: "#888899", + stroke: SolarSystemModelsColors.zodiacTickColorProperty, lineWidth: 1, }); const x1 = ORBIT_VIEW_CENTER_X + Math.cos(angle) * tickInnerR; @@ -106,13 +108,13 @@ export class PtolemaicScreenView extends ScreenView { const vx = ORBIT_VIEW_CENTER_X + Math.cos(angle) * ZODIAC_LABEL_RADIUS; // Inverted Y: positive y in model = up in view, so negate for angle const vy = ORBIT_VIEW_CENTER_Y - Math.sin(angle) * ZODIAC_LABEL_RADIUS; - const label = new Text(ZODIAC_SIGNS[i] ?? "", { + const label = new Text(ZODIAC_SIGN_PROPS[i]!, { font: new PhetFont(10), - fill: "#aabbcc", - centerX: vx, - centerY: vy, + fill: SolarSystemModelsColors.zodiacLabelColorProperty, maxWidth: 55, }); + label.centerX = vx; + label.centerY = vy; this.addChild(label); } @@ -152,7 +154,7 @@ export class PtolemaicScreenView extends ScreenView { const sunOrbitVr = ORBIT_VIEW_SCALE * PTOLEMAIC_SUN_ORBIT_RADIUS; const sunOrbitViewCenter = mvt.modelToViewPosition(Vector2.ZERO); const sunOrbitCircle = new Path(Shape.circle(sunOrbitViewCenter.x, sunOrbitViewCenter.y, sunOrbitVr), { - stroke: "#333355", + stroke: SolarSystemModelsColors.sunOrbitReferenceColorProperty, lineWidth: 1, }); this.addChild(sunOrbitCircle); diff --git a/src/ptolemaic/view/PtolemaicTimeReadout.ts b/src/ptolemaic/view/PtolemaicTimeReadout.ts index 19225f2..68a8753 100644 --- a/src/ptolemaic/view/PtolemaicTimeReadout.ts +++ b/src/ptolemaic/view/PtolemaicTimeReadout.ts @@ -1,5 +1,7 @@ +import { Multilink } from "scenerystack/axon"; import { Node, Text } from "scenerystack/scenery"; import { PhetFont } from "scenerystack/scenery-phet"; +import { StringManager } from "../../i18n/StringManager.js"; import SolarSystemModelsColors from "../../SolarSystemModelsColors.js"; import { DAYS_PER_YEAR } from "../../SolarSystemModelsConstants.js"; import type { PtolemaicModel } from "../model/PtolemaicModel.js"; @@ -8,16 +10,21 @@ export class PtolemaicTimeReadout extends Node { public constructor(model: PtolemaicModel) { super(); - const readout = new Text("0 yr 0 d", { + const strings = StringManager.getInstance().getPtolemaicStrings(); + + const readout = new Text("", { font: new PhetFont(14), fill: SolarSystemModelsColors.textColorProperty, }); this.addChild(readout); - model.ptolemaicTimeProperty.link((days) => { - const yr = Math.floor(days / DAYS_PER_YEAR); - const d = Math.round(days % DAYS_PER_YEAR); - readout.string = `${yr} yr ${d} d`; - }); + Multilink.multilink( + [model.ptolemaicTimeProperty, strings.yearsStringProperty, strings.daysStringProperty] as const, + (days, yr, d) => { + const years = Math.floor(days / DAYS_PER_YEAR); + const daysRem = Math.round(days % DAYS_PER_YEAR); + readout.string = `${years} ${yr} ${daysRem} ${d}`; + }, + ); } } diff --git a/src/ptolemaic/view/PtolemaicZodiacStrip.ts b/src/ptolemaic/view/PtolemaicZodiacStrip.ts index da447d4..35ceebd 100644 --- a/src/ptolemaic/view/PtolemaicZodiacStrip.ts +++ b/src/ptolemaic/view/PtolemaicZodiacStrip.ts @@ -2,6 +2,7 @@ import { Shape } from "scenerystack/kite"; import { Circle, Node, Path, Rectangle, Text } from "scenerystack/scenery"; import { PhetFont } from "scenerystack/scenery-phet"; import { ECLIPTIC_CONSTELLATIONS } from "../../common/ZodiacConstellationsData.js"; +import { StringManager } from "../../i18n/StringManager.js"; import SolarSystemModelsColors from "../../SolarSystemModelsColors.js"; import { ZODIAC_STRIP_HEIGHT, ZODIAC_STRIP_WIDTH } from "../../SolarSystemModelsConstants.js"; import type { PtolemaicModel } from "../model/PtolemaicModel.js"; @@ -9,21 +10,6 @@ import type { PtolemaicModel } from "../model/PtolemaicModel.js"; const TWO_PI = 2 * Math.PI; const LONGITUDE_TO_X = ZODIAC_STRIP_WIDTH / TWO_PI; -const SIGN_NAMES = [ - "Pisces", - "Aquarius", - "Capricorn", - "Sagittarius", - "Scorpius", - "Libra", - "Virgo", - "Leo", - "Cancer", - "Gemini", - "Taurus", - "Aries", -]; - /** Map ecliptic longitude to strip x — AS convention: x = (−λ · width/2π) mod width. */ function lonToX(lon: number): number { return (((-lon * LONGITUDE_TO_X) % ZODIAC_STRIP_WIDTH) + ZODIAC_STRIP_WIDTH) % ZODIAC_STRIP_WIDTH; @@ -81,6 +67,24 @@ export class PtolemaicZodiacStrip extends Node { public constructor(model: PtolemaicModel) { super(); + const z = StringManager.getInstance().getZodiacStrings(); + // Reversed order: ecliptic strip maps increasing longitude rightward, but + // the AS convention maps longitude leftward (negative x), so Pisces appears first. + const SIGN_NAME_PROPS = [ + z.piscesStringProperty, + z.aquariusStringProperty, + z.capricornStringProperty, + z.sagittariusStringProperty, + z.scorpiusStringProperty, + z.libraStringProperty, + z.virgoStringProperty, + z.leoStringProperty, + z.cancerStringProperty, + z.geminiStringProperty, + z.taurusStringProperty, + z.ariesStringProperty, + ]; + // Background band const band = new Rectangle(0, 0, ZODIAC_STRIP_WIDTH, ZODIAC_STRIP_HEIGHT, { fill: SolarSystemModelsColors.zodiacBandColorProperty, @@ -95,7 +99,7 @@ export class PtolemaicZodiacStrip extends Node { const clipRect = new Rectangle(0, 0, ZODIAC_STRIP_WIDTH, ZODIAC_STRIP_HEIGHT); const constelPath = new Path(constelShape, { fill: null, - stroke: "#446688", + stroke: SolarSystemModelsColors.constellationLineColorProperty, lineWidth: 0.5, opacity: 0.5, clipArea: clipRect.shape, @@ -111,13 +115,13 @@ export class PtolemaicZodiacStrip extends Node { }); this.addChild(divider); - const label = new Text(SIGN_NAMES[i] ?? "", { + const label = new Text(SIGN_NAME_PROPS[i]!, { font: new PhetFont(9), - fill: SolarSystemModelsColors.textColorProperty, - centerX: (i + 0.5) * segW, - centerY: ZODIAC_STRIP_HEIGHT * 0.25, + fill: SolarSystemModelsColors.zodiacLabelColorProperty, maxWidth: segW - 4, }); + label.centerX = (i + 0.5) * segW; + label.centerY = ZODIAC_STRIP_HEIGHT * 0.25; this.addChild(label); }