Full-repo survey of every player-facing German string (seed content, API-computed reputation rank labels, five client-side error-message translation maps, six static Angular label maps, all combat-log generation, ~30 component templates), plus rulings on everything the source spec leaves open: monster keys stay unchanged (only name changes), no migration is needed (content lives in seed-file values, not schema), reputation rank labels move on the API side, combat log templates are freely composed rather than translated 1:1, and the Renown/Reputation German-only "Ansehen"/"Ruf" split carries over as "World Renown"/"Reputation" without the German-specific collision problem that motivated it. A complete glossary gives every implementer the exact English value to use verbatim -- no translation judgment calls left to task execution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
28 KiB
English Game Content Foundation — Design
Source spec: docs/playable-slices/0.6.6-English-Game-Content-Foundation.md
Depends on: Slice 0.6.5 (Renown & Reputation Foundation), merged to master at cedd87f.
Classification: Architectural (touches ~40 files across both apps; binds a large SDD plan), though the change itself is conceptually simple: mechanical content/string replacement plus a small number of terminology decisions the source spec left open. No new subsystem, no new abstraction, no new data flow.
1. Purpose
Convert every player-facing German string in the currently playable loop to English, per the source spec. Development docs and internal key/code identifiers stay as they are. No localization framework, no language switch, no translation table — this is a one-time content and copy conversion.
2. What the source spec already decided (binding, not re-litigated here)
- Player-facing content is English from this slice on; dev docs may stay German.
- Technical
keyand errorcodevalues are stable and do not change because of this slice. - No large localization framework. Centralized constants/text maps are fine for static UI text.
- Persisted content keeps
key/name/descriptionshape; only the values change. - Domain error
codestays stable; only the player-facingmessageis English (already true everywhere — see R2). - Terminology tables for locations, monsters, core combat actions, navigation, common UI, and danger labels (source spec §5) are used verbatim where they cover a string.
- Recommended Tier-1 item display names (source spec §6) are used verbatim; do not rename item
keys just to prettify them.
3. Survey — current state
A full-repo survey (not reproduced here) found German player-facing text in: seed/content files (items, locations, local-location POI/action content, the reputation faction), a client-side reputation-rank label list on the API (dynamic, not a seed row), five web-side error-message translation maps that re-translate already-English API error codes back to German for display, six static Angular label maps (SLOT_LABELS, RARITY_LABELS, DANGER_LABELS, LOCATION_TYPE_LABELS, and two ad-hoc binary danger labels), all combat-log generation (entirely client-side, template strings keyed off structured event types), and roughly three dozen component templates. It also confirmed several things are already in scope-compliant English and need no change: every domain error message and code, every content key/slug, the ItemRarity/DangerRating/EquipmentSlot enum values themselves (only their display maps are German), and the API's combat engine (pure structured events, no strings at all — already matches source spec §7's stated preference).
Rulings
R1 — Monster keys do not change; only name changes.
The source spec's suggested keys (feral-road-hound, charred-raider) don't match the existing seeded keys (wild-road-dog, charred-looter). Source spec §4 and §6 say not to rename stable keys merely to prettify them, and while that's stated about items, the same reasoning applies with more force to monsters: LocationMonster, HuntEncounter, and CombatEvent rows reference monster identity, and a key rename multiplies blast radius for zero player-visible benefit — only name is ever rendered. Existing keys stay; only MonsterDefinition.name changes to the source spec's English label.
Cost if wrong: a follow-up key rename touching a few more FK-adjacent rows; cheap, since nothing has executed against a real database yet (see R7).
R2 — Domain error message strings need no change.
Surveyed all nine *.errors.ts files on the API: every message is already English, every code is already a stable English identifier. This part of the source spec's scope (§9) is already satisfied. What is German is a set of client-side maps that re-translate the already-English code back into German for display (world.store.ts, hunting.store.ts, combat.store.ts, inventory.store.ts, local-location.store.ts) — those five maps are this slice's actual error-message work, and it is a pure frontend edit.
Cost if wrong: none; this is a factual finding, not a judgment call.
R3 — Reputation rank labels move on the API side, not the frontend.
apps/api/src/reputation/reputation-rank.ts computes rankLabel server-side and returns it as part of GET /api/reputation and turn-in results. Unlike the other label maps in scope (which are static Angular constants), this one is API-computed and reaches the client dynamically. English labels are the rank enum keys, humanized — see the Reputation section of the glossary below.
Cost if wrong: touches one file either way; low.
R4 — This is a content edit, not a migration.
None of the German strings live in a column definition or a CREATE TABLE; they live in row values written by upsert(..., ['key']) calls in seed files. No ALTER TABLE is needed anywhere in this slice — only seed-file literal changes. Per this project's standing practice, no migration runs against any database this session (ownership of the local Postgres container remains unconfirmed); the seed's upsert will apply these values the next time someone runs the seed against a real database. This mirrors the source spec §10's "update existing rows by stable key" guidance exactly — an upsert keyed on key is that update, once run.
Cost if wrong: nothing to undo; this is a description of the existing mechanism, not a new decision.
R5 — Combat log templates are freely composed English, not translated 1:1.
The source spec (§7) gives example output ("You use Shield Bash.", "Road Bandit takes 12 damage.") in a phrasing that doesn't match the current German templates 1:1 (current German is ${attacker} trifft ${defender} für X Schaden — third-person "attacker hits defender"; the spec's example is second-person "you use X" / "X takes N damage"). Ruling: write new English templates matching the voice the source spec's examples establish (second-person for the player's own actions, third-person "takes N damage" for outcomes), not a literal translation of the German construction. Exact wording is in the glossary below.
Cost if wrong: copy-only change, no logic; cheap to revise.
R6 — Terminology not covered by the source spec's tables gets translated here, following existing enum-name conventions where one exists.
Several label maps (SLOT_LABELS, RARITY_LABELS, LOCATION_TYPE_LABELS) mirror an existing English enum 1:1 (EquipmentSlot, ItemRarity, LocationType) — for those, the English label is simply the enum value humanized (e.g. HUNTING_GROUND → "Hunting Ground"), which is both the lowest-risk choice and keeps the display string legible from the code without a lookup. Where no enum exists (item/location descriptions, POI narrative content, the reputation faction), original English wording is drafted here, matching the tone of the German source. All of it is listed in the glossary below — implementers use these values verbatim, they do not invent their own.
Cost if wrong: copy-only; cheap to revise per string.
R7 — "Renown" is the primary display term; "World Renown" is used once, where the source spec's Common-UI table requires the literal phrase.
Slice 0.6.5 used "Ansehen" for Renown in German specifically to avoid colliding with "Ruf" (Reputation) — a German-only disambiguation problem that disappears once both are English. The source spec's Common UI list (§5) includes the exact term "World Renown". Ruling: the Topbar (the most prominent, always-visible renown display) uses "World Renown {{n}}", matching the spec's literal term where the spec asks for it. The location panel's recommended-renown line uses the shorter "Recommended Renown" (parallel to "Empfohlenes Ansehen"'s prior German phrasing, and avoiding "World Renown" twice on one screen). Reputation continues to read "Reputation"/"Rep." in context, never "Renown" — the two systems must stay visually distinct in English exactly as they had to in German (0.6.5 ruling R17's spirit carries over, just without the German-specific term collision).
Cost if wrong: copy-only; both terms already exist in the codebase's vocabulary (the field is literally named renown), so a later rename is a find-and-replace, not a redesign.
R8 — The gate-notice POI's over-promise is translated as-is, not fixed. The South Gate's "Aushangtafel" POI text promises "Silber für jeden erlegten Räuber" (silver for every bandit killed) even though slice 0.6.5 zeroed direct monster silver drops — flagged as a known, deliberate non-fix in that slice's final review (N3 in its ledger), on the grounds that it's in-world NPC flavor text and the turn-in mechanism makes the promise eventually true. This slice is a language conversion, not a content-accuracy pass; the English translation preserves the same (already-flagged, already-accepted) inaccuracy rather than silently also fixing it. If the user wants it fixed, that is a separate, explicit content decision — not bundled into a translation task. Cost if wrong: none; this explicitly defers to a standing decision already made and accepted.
4. Glossary — exact values, used verbatim
Locations (source spec §5, verbatim)
| Key (unchanged) | German (current) | English |
|---|---|---|
graufurt |
Graufurt | Graufurt (proper name, unchanged) |
south-gate |
Südtor von Graufurt | Graufurt South Gate |
burned-road |
Verbrannte Straße | Burned Road |
| — | Verlassener Wachtposten | Abandoned Watchpost (not yet seeded) |
| — | Aschengrube | Ash Pit (not yet seeded) |
ashen-fields (regionKey) |
Aschenfelder | Ashen Fields |
Location description (from vertical-slice.seed.ts):
south-gate: "Am schwarzen Südtor endet der Schutz Graufurts. Hinter den Wachtfeuern beginnt die stille Weite der Aschenfelder." → "At the black South Gate, Graufurt's protection ends. Beyond the watch-fires begins the silent expanse of the Ashen Fields."burned-road: "Die alte Handelsstraße führt durch verkohlte Felder. Zwischen Asche und zerbrochenen Wagen warten die ersten Gefahren." → "The old trade road cuts through charred fields. Among the ash and broken wagons, the first dangers wait."
regionTierLabel: "Gebiet 1" → "Tier 1" (both locations; matches the source spec's own "Tier-1 items" vocabulary in §6).
Monsters (source spec §5, verbatim; keys unchanged per R1)
| Key (unchanged) | German (current name) |
English name |
|---|---|---|
ash-rat |
Aschenratte | Ash Rat |
wild-road-dog |
Verwilderter Straßenhund | Feral Road Hound |
road-bandit |
Straßenräuber | Road Bandit |
charred-looter |
Verkohlter Plünderer | Charred Raider |
Loot table name (internal, not currently player-visible, translated anyway for consistency):
ash-rat-loot: "Aschenratte Beute" → "Ash Rat Loot"road-bandit-loot: "Straßenräuber Beute" → "Road Bandit Loot"
Items (source spec §6 gives names for the 10 listed; descriptions and the 3 unlisted names are new here)
| Key (unchanged) | English name |
English description |
|---|---|---|
worn-short-sword |
Worn Shortsword | A recruit's blade, sharpened more often than it's been swung. |
bandit-blade |
Bandit Blade | A roughly serrated blade, forged for quick raids. |
ash-blade |
Ashen Blade | Tempered in the embers of the Ashen Fields; the edge still glows faintly. |
bandit-hood |
Bandit Hood | Scarred leather that hides both the wearer's face and their intent. |
reinforced-leather-jacket |
Reinforced Leather Jacket | Leather sewn with iron plates, heavy and dependable. |
raider-gloves |
Plunderer Gloves | Studded gloves, worn smooth from handling other people's belongings. |
guardsman-legs |
Watchman's Leggings | Leg armor of the Border Watch, patched at the knees. |
ash-boots |
Ashen Boots | Boots that walked through smoldering fields and never quite left them behind. |
borderwatch-sigil |
Mark of the Border Watch | The crest of a watchtower that no longer stands. |
burned-captain-pendant |
Charred Captain's Pendant | A skull cast in slag, its embers never quite gone out. |
small-healing-potion |
Small Healing Potion | A bitter draught that makes wounds forgettable, if only for a breath. |
ash-pelt |
Ash Pelt | Singed pelt, tough as leather and grey with drifting ash. |
bandit-insignia |
Bandit Insignia | A roughly stamped token marking a road bandit as one of their band. |
Reputation (border-guard faction, R3, R8)
- Faction
name: "Grenzwacht" → "Border Watch" (matchesborderwatch-sigil's English item name above — same in-world institution). - Faction
description: "Die letzten organisierten Wächter der Aschenfelder, die verbliebene Handelsrouten und Außenposten schützen." → "The last organized watch of the Ashen Fields, guarding what remains of the trade routes and outposts." - Rank labels (API-computed,
reputation-rank.ts, R3) — English label is the enum key humanized:
| Key | German (current) | English |
|---|---|---|
ESTEEMED |
Geachtet | Esteemed |
TRUSTED |
Vertraut | Trusted |
RECOGNIZED |
Anerkannt | Recognized |
KNOWN |
Bekannt | Known |
TOLERATED |
Geduldet | Tolerated |
STRANGER |
Fremder | Stranger |
Local-location content (local-location.content.ts)
Burned Road:
localDescription: "Ein alter Handelsweg, der durch Feuer und Krieg in Asche gelegt wurde. Verbrannte Karren, zerbrochene Waffen und verstummte Schreie säumen den Pfad in die Aschenfelder." → "An old trade road, burned to ash by fire and war. Charred carts, broken weapons, and silenced cries line the path into the Ashen Fields."- POI
hunt-area: title "Jagdgebiet" → "Hunting Ground"; actionLabel "Jagd beginnen" → "Begin Hunt". - POI
inspect-tracks: title/resultTitle "Verdächtige Spuren" → "Suspicious Tracks"; actionLabel "Untersuchen" → "Investigate"; resultText "Zwischen Asche und zerbrochenen Steinen erkennst du mehrere frische Stiefelabdrücke. Sie führen nach Osten, in Richtung des verlassenen Wachtpostens." → "Among the ash and broken stones you make out several fresh bootprints. They lead east, toward the abandoned watchpost." - POI
search-abandoned-wagon: title/resultTitle "Verlassener Wagen" → "Abandoned Wagon"; actionLabel "Durchsuchen" → "Search"; resultText "Der Wagen wurde gründlich geplündert. Zwischen verbrannten Brettern findest du nur leere Kisten und Spuren eines hastigen Aufbruchs." → "The wagon has been thoroughly looted. Among the charred planks you find only empty crates and signs of a hasty departure." - POI
wounded-scout: title/resultTitle "Verwundeter Kundschafter" → "Wounded Scout"; actionLabel "Sprechen" → "Talk"; resultText „Die Straße ist nicht mehr sicher. Die Plünderer kommen aus Richtung des alten Wachtpostens. Wenn du weitergehst, halte die Augen offen." → "The road isn't safe anymore. The raiders are coming from the direction of the old watchpost. If you keep going, keep your eyes open." (keep the quotation marks around the line; it's spoken dialogue) localPrimaryActions:start-huntlabel "Jagd beginnen" → "Begin Hunt", description "Im Gebiet jagen" → "Hunt in this area".investigate-trackslabel "Spuren untersuchen" → "Investigate tracks", description "Hinweise finden" → "Find clues".search-surroundingslabel "Umgebung durchsuchen" → "Search surroundings", description "Beute finden" → "Find loot".open-maplabel "Zur Karte" → "To Map", description "Gebiet wechseln" → "Change area".localRewardPreview: "Ausrüstung" → "Equipment"; "Material" → "Material" (already English; no change).
South Gate:
localDescription: "Am schwarzen Südtor endet der Schutz Graufurts. Hinter den Wachtfeuern beginnt die stille Weite der Aschenfelder." → same English as theLocationDefinition.descriptionabove ("At the black South Gate, Graufurt's protection ends. Beyond the watch-fires begins the silent expanse of the Ashen Fields.") — the German source repeats the sentence verbatim in both fields; the English does too, for consistency.- POI
gate-notice: title/resultTitle "Aushangtafel" → "Notice Board"; actionLabel "Lesen" → "Read"; resultText "Verwitterte Anschläge flattern im Wind. Ein frischer Zettel warnt vor Plünderern auf der Verbrannten Straße und verspricht Silber für jeden erlegten Räuber." → "Weathered notices flutter in the wind. A fresh one warns of raiders on the Burned Road and promises silver for every bandit killed." (R8: translated as-is, over-promise preserved deliberately) - POI
gate-watch: title/resultTitle "Torwache" → "Gate Watch"; actionLabel "Sprechen" → "Talk"; resultText „Hinter dem Tor endet Graufurts Schutz. Wer nach Süden geht, geht auf eigene Gefahr — und kommt selten so zurück, wie er gegangen ist." → "Beyond the gate, Graufurt's protection ends. Whoever heads south does so at their own risk — and rarely comes back the way they left." - POI
south-road: title "Straße nach Süden" → "Road South"; actionLabel "Zur Karte" → "To Map". localPrimaryActions:talk-to-watchlabel "Wache ansprechen" → "Talk to the watch", description "Lage erfragen" → "Ask about the situation".read-noticelabel "Aushang lesen" → "Read the notice", description "Hinweise finden" → "Find clues".open-maplabel "Zur Karte" → "To Map", description "Gebiet wechseln" → "Change area".
Combat actions & log (source spec §5 core actions, verbatim; §7 style, R5)
- Buttons: Angriff → Attack; Schwerer Hieb → Heavy Strike; Schildstoß → Shield Bash; Verteidigen → Defend; Trank → Potion; (Flee is listed in the spec but not yet implemented in this codebase — no change needed, nothing to translate).
- Round header: "Runde {{n}}" → "Round {{n}}".
- Outcome headings: "Sieg" → "Victory"; "Niederlage" → "Defeat".
- Rewards panel: "Belohnungen" → "Rewards"; "Beute" → "Loot"; "Keine besondere Beute gefunden." → "No notable loot found."; "Silber" → "Silver".
- Post-combat buttons: "Weiter jagen" → "Keep Hunting"; "Zum Ort" → "To Location"; "Inventar öffnen" → "Open Inventory".
- Log panel: "Kampfprotokoll"/"Kampflog" → "Combat Log".
- Loading/error: "Kampf wird geladen…" → "Loading combat…"; "Erneut versuchen" → "Retry".
- Default participant names (fallbacks when a name is unavailable): 'Du' → 'You'; 'Der Gegner' → 'The enemy'.
- Combat log templates (
formatEvent, per event type) and the standalone intent banner (monsterIntentLabel):
| Event | New English template |
|---|---|
| DAMAGE | ${attacker} hits ${defender} for ${amount} damage. |
| HEAL | ${playerName} drinks a potion and heals ${amount} HP. |
| DEFEND | ${playerName} braces to defend. |
| TELEGRAPH / intent banner | ${monsterName} is winding up a Heavy Strike. |
| INTERRUPT | ${playerName} interrupts ${monsterName}'s attack. |
| COMBAT_WON | ${monsterName} has been defeated. |
| default (COMBAT_LOST) | ${playerName} has been defeated. |
Navigation (source spec §5, verbatim) & layout
- Karte → Map; Jagd → Hunt; Quests → Quests (unchanged, already English); Inventar → Inventory; Charakter → Character; Shop → Shop (not yet built; no change needed). The sidebar's "Ort" item (not in the spec's table) → Location.
- Topbar: "Ansehen {{n}}" → "World Renown {{n}}" (R7); "Silber" → Silver; "Charakterdaten werden geladen" → "Loading character data".
- Footer: "Verbindung bereit" → "Connection ready"; "Aschenfelder" → Ashen Fields.
Danger labels (source spec §5, verbatim)
| Key | German (current) | English |
|---|---|---|
WEAK |
Schwach | Weak |
MATCH |
Passend | Suitable |
STRONG |
Stark | Strong |
VERY_DANGEROUS |
Sehr gefährlich | Very Dangerous |
DEADLY |
Tödlich | Deadly |
Separate binary travel-danger label (travel-panel.component.ts, not the 5-tier scale above): Niedrig/Hoch → Low/High.
Equipment slots, rarity, location type (R6 — enum names humanized)
SLOT_LABELS key |
German | English |
|---|---|---|
| WEAPON | Waffe | Weapon |
| HEAD | Kopf | Head |
| CHEST | Brust | Chest |
| HANDS | Handschuhe | Hands |
| LEGS | Beine | Legs |
| FEET | Stiefel | Feet |
| AMULET | Amulett | Amulet |
RARITY_LABELS key |
German | English |
|---|---|---|
| COMMON | Gewöhnlich | Common |
| RARE | Selten | Rare |
| EPIC | Episch | Epic |
LOCATION_TYPE_LABELS key |
German | English |
|---|---|---|
| SAFE_HUB | Zuflucht | Safe Haven |
| TRANSITION | Übergang | Transition |
| HUNTING_GROUND | Jagdgebiet | Hunting Ground |
| QUEST_LOCATION | Questort | Quest Location |
| OUTPOST | Außenposten | Outpost |
| ELITE_ZONE | Elitegebiet | Elite Zone |
| BOSS_LOCATION | Bossort | Boss Location |
| DUNGEON_ENTRANCE | Verlieseingang | Dungeon Entrance |
typeLabel fallback (inventory detail panel, hardcoded 'Gegenstand'): → 'Item'.
Error-message translation maps (R2 — frontend only; code unchanged in all cases)
| Store | code |
German (current) | English |
|---|---|---|---|
world.store.ts |
(generic) | Weltzustand konnte nicht geladen werden. | Could not load world state. |
TRAVEL_ALREADY_ACTIVE |
Du befindest dich bereits auf Reisen. | You're already travelling. | |
INVALID_TRAVEL_TARGET |
Dieses Ziel ist von hier aus nicht erreichbar. | This destination isn't reachable from here. | |
CHARACTER_NOT_FOUND |
Dein Charakter konnte nicht gefunden werden. | Your character could not be found. | |
TRAVEL_STATE_INVALID |
Der Reisezustand ist ungültig. Bitte lade die Seite neu. | Travel state is invalid. Please reload the page. | |
hunting.store.ts |
(generic) | (same as above) | Could not load world state. |
CHARACTER_NOT_FOUND |
(same as above) | Your character could not be found. | |
CHARACTER_TRAVELLING |
Du kannst nicht jagen, während du unterwegs bist. | You can't hunt while travelling. | |
HUNTING_NOT_AVAILABLE |
An diesem Ort gibt es keine Jagdgebiete. | There's no hunting ground at this location. | |
NO_HUNT_ENCOUNTERS_AVAILABLE |
Aktuell sind hier keine Gegner zu finden. | No enemies can currently be found here. | |
combat.store.ts |
(generic) | Der Kampf konnte nicht geladen werden. | Could not load the combat. |
HUNT_ENCOUNTER_NOT_FOUND |
Diese Begegnung wurde nicht gefunden. | This encounter could not be found. | |
HUNT_ENCOUNTER_ALREADY_CONSUMED |
Diese Begegnung wurde bereits genutzt. | This encounter has already been used. | |
INVALID_HUNT_ENCOUNTER |
Diese Begegnung ist nicht mehr gültig. | This encounter is no longer valid. | |
CHARACTER_TRAVELLING |
Du kannst nicht kämpfen, während du unterwegs bist. | You can't fight while travelling. | |
CHARACTER_TOO_WOUNDED |
Du bist zu schwer verwundet, um zu kämpfen. Warte, bis du dich erholt hast. | You're too badly wounded to fight. Wait until you've recovered. | |
COMBAT_ALREADY_ACTIVE |
Du befindest dich bereits in einem Kampf. | You're already in a combat. | |
COMBAT_NOT_FOUND |
Dieser Kampf wurde nicht gefunden. | This combat could not be found. | |
COMBAT_ALREADY_FINISHED |
Dieser Kampf ist bereits beendet. | This combat has already ended. | |
COMBAT_NO_POTIONS_REMAINING |
Du hast keine Tränke mehr. | You have no potions left. | |
inventory.store.ts |
(generic) | Inventar konnte nicht geladen werden. | Could not load the inventory. |
CHARACTER_ITEM_NOT_FOUND |
Dieser Gegenstand konnte nicht gefunden werden. | This item could not be found. | |
ITEM_NOT_OWNED |
Dieser Gegenstand gehört dir nicht. | This item doesn't belong to you. | |
ITEM_NOT_EQUIPPABLE |
Dieser Gegenstand kann nicht ausgerüstet werden. | This item can't be equipped. | |
INVALID_EQUIPMENT_SLOT |
Dieser Ausrüstungsplatz ist ungültig. | This equipment slot is invalid. | |
CHARACTER_IN_COMBAT |
Ausrüstung kann während eines Kampfes nicht geändert werden. | Equipment can't be changed during combat. | |
local-location.store.ts |
(generic) | Diese Handlung ist gerade nicht möglich. | This action isn't possible right now. |
LOCATION_INTERACTION_UNAVAILABLE |
Hier gibt es dazu nichts zu entdecken. | There's nothing to discover here. | |
CHARACTER_NOT_FOUND |
Dein Charakter konnte nicht gefunden werden. | Your character could not be found. |
Every row above was read from the actual source files and is pinned exactly; there are no remaining (verify) cells.
Remaining component copy (not itemized above — implementer translates in-file, same voice, using the terms already fixed elsewhere in this glossary for anything that overlaps — e.g. "Jagd" is always "Hunt", "Ort" is always "Location")
Hunt screen, inventory screen, world/travel screens, and their aria-labels follow the same direct, plain register as the rest of the UI. No new terms are introduced beyond what's already fixed above; where a screen's copy uses a word already translated elsewhere in this glossary (Jagd, Ort, Silber, Ausrüstung, etc.), use that exact translation for consistency.
5. Architecture / file scope
No new files, no new abstractions. Every change is either a seed-file literal, a component template/TS string, or a small constant map. Grouped by SDD task (task boundaries chosen so each is same-shape, same-layer work — batched per subagent-driven-development's guidance rather than one task per file):
- API seed content —
item-content.ts,vertical-slice.seed.ts,local-location.content.ts,reputation-content.tsand their specs. - API reputation rank labels —
reputation-rank.tsand its spec. - Web static label maps —
inventory.labels.ts(SLOT_LABELS),item-card.component.ts(RARITY_LABELS),danger-badge.component.ts(DANGER_LABELS),location-sidebar.component.ts(LOCATION_TYPE_LABELS), and their specs. - Web error-message maps — the five store files in R2's table, and their specs.
- Combat feature —
combat-page.component.ts(log templates, intent label) +.html(static labels) +.spec.ts. - Inventory feature —
inventory-page.component.html,inventory-detail-panel.component.html/.ts(typeLabel), and specs. - World/travel/location feature —
location-page,location-sidebar,travel-panel,location-interaction-panel,location-node,world-page(templates +travel-panel.component.ts's binary danger label) and specs. - Hunting feature —
hunt-page.component.html,encounter-card.component.html/.ts, and specs. - Layout/shared —
top-bar,side-navigation,context-panel,game-footer,reputation-displaystatic text, and specs. - Fixture and final sweep —
current-location.fixture.ts(the one fixture shared across multiple spec files — updated last, after every task that reads from it has landed, to avoid conflicting edits), plus a full-suite grep for any surviving German string (umlaut/ß scan acrossapps/) as the closing gate.
6. Testing strategy
No new test infrastructure. Every task updates its own component/service specs and fixtures to expect English, in the same commit as the production change (TDD: update the assertion, watch it fail against the still-German code, change the code, watch it pass). The final task (10) runs a repo-wide grep for German-only characters (ä/ö/ü/ß) and remaining known German words as a closing gate — not a substitute for the per-task updates, matching source spec §11's own caveat that a grep "is not a substitute for actual runtime verification."
7. Self-review
- Placeholder scan: no TBD/TODO in this document; every glossary row has a concrete value except the explicitly-marked
(verify)cells, which are a deliberate instruction (read the real string, translate in-voice) rather than an unfilled placeholder. - Internal consistency: "Hunt" is used consistently for Jagd across navigation, buttons, and location type; "Location" is used consistently for Ort; "Silver" for Silber; "Equipment" for Ausrüstung. Checked for collisions — none found.
- Scope check: matches the source spec's §3 scope list exactly (navigation, locations, monsters, items, combat actions, combat log, hunt/travel/loot screens, inventory, character screen, system messages, danger labels, player-visible errors); nothing extra proposed (no localization framework, per source spec §13).
- Ambiguity check: every place the source spec left a term undecided (item descriptions, location descriptions, POI content, faction content, rank labels, log template wording, the Renown/World Renown split) has an explicit ruling above.