fix: close out remaining German player-facing text across web and API tests

Repo-wide grep sweep (apps/web/src, apps/api/src) for leftover German
content strings missed by Tasks 1-9, mostly in spec fixtures/assertions
that mirror already-translated seed content (monster/item names, POI
titles and action labels, location names/descriptions) plus a few real
source-file gaps:

- inventory-detail-panel.component.ts: STAT_LABELS (Waffenschaden,
  Angriff, Leben, Rüstung) were never translated by Task 6; now match
  the identical English labels already used in inventory-page.component.html.
- app-shell.component.html: aria-label="Spielinhalt" -> "Game content"
  (this file was outside every prior task's file list).
- location-interaction-panel.component.spec.ts: dead NPC-quote fixture
  translated to match the real wounded-scout POI text.

Code comments referencing German source-spec section titles or
not-yet-seeded faction names, and inline calculation-documentation
comments, are left as-is per the source spec's scope (dev-facing
comments may stay German).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACkMEDYiwtcfchqKkiUJNX
This commit is contained in:
Bastian Wagner
2026-08-21 22:59:34 +02:00
parent ae163f000a
commit 1322285b0f
38 changed files with 243 additions and 241 deletions

View File

@@ -17,8 +17,8 @@ const CHARACTER_ID = '10000000-0000-4000-8000-000000000001';
const BURNED_ROAD_POIS: LocationPointOfInterestContent[] = [
{
key: 'hunt-area',
title: 'Jagdgebiet',
actionLabel: 'Jagd beginnen',
title: 'Hunting Ground',
actionLabel: 'Begin Hunt',
type: 'HUNT',
iconKey: 'hunt',
xPercent: 52,
@@ -27,42 +27,42 @@ const BURNED_ROAD_POIS: LocationPointOfInterestContent[] = [
},
{
key: 'inspect-tracks',
title: 'Verdächtige Spuren',
actionLabel: 'Untersuchen',
title: 'Suspicious Tracks',
actionLabel: 'Investigate',
type: 'INVESTIGATE',
iconKey: 'investigate',
xPercent: 32,
yPercent: 78,
enabled: true,
resultTitle: 'Verdächtige Spuren',
resultTitle: 'Suspicious Tracks',
resultText:
'Zwischen Asche und zerbrochenen Steinen erkennst du mehrere frische Stiefelabdrücke.',
'Between the ash and broken stones you make out several fresh bootprints.',
},
{
key: 'sealed-crypt',
title: 'Versiegelte Krypta',
title: 'Sealed Crypt',
type: 'DUNGEON',
iconKey: 'search',
xPercent: 90,
yPercent: 20,
enabled: false,
resultTitle: 'Versiegelte Krypta',
resultText: 'Noch verschlossen.',
resultTitle: 'Sealed Crypt',
resultText: 'Still sealed.',
},
];
const SOUTH_GATE_POIS: LocationPointOfInterestContent[] = [
{
key: 'gate-watch',
title: 'Torwache',
actionLabel: 'Sprechen',
title: 'Gate Watch',
actionLabel: 'Talk',
type: 'NPC',
iconKey: 'speak',
xPercent: 45,
yPercent: 52,
enabled: true,
resultTitle: 'Torwache',
resultText: 'Nur am Südtor zu hören.',
resultTitle: 'Gate Watch',
resultText: 'Only heard at the South Gate.',
},
];
@@ -109,8 +109,8 @@ describe('WorldService.runLocalInteraction', () => {
service.runLocalInteraction(CHARACTER_ID, 'inspect-tracks'),
).resolves.toEqual({
interactionKey: 'inspect-tracks',
title: 'Verdächtige Spuren',
text: 'Zwischen Asche und zerbrochenen Steinen erkennst du mehrere frische Stiefelabdrücke.',
title: 'Suspicious Tracks',
text: 'Between the ash and broken stones you make out several fresh bootprints.',
});
});

View File

@@ -19,8 +19,8 @@ describe('WorldController', () => {
it('forwards only the interaction key, never a caller-supplied location', () => {
const runLocalInteraction = jest.fn().mockResolvedValue({
interactionKey: 'inspect-tracks',
title: 'Verdächtige Spuren',
text: 'Frische Stiefelabdrücke.',
title: 'Suspicious Tracks',
text: 'Fresh bootprints.',
});
const controller = new WorldController({
runLocalInteraction,

View File

@@ -20,23 +20,23 @@ const CHARACTER_ID = '10000000-0000-4000-8000-000000000001';
const SOUTH_GATE_POIS: LocationPointOfInterestContent[] = [
{
key: 'gate-watch',
title: 'Torwache',
actionLabel: 'Sprechen',
title: 'Gate Watch',
actionLabel: 'Talk',
type: 'NPC',
iconKey: 'speak',
xPercent: 45,
yPercent: 52,
enabled: true,
resultTitle: 'Torwache',
resultText: 'Geheimer Servertext.',
resultTitle: 'Gate Watch',
resultText: 'Secret server text.',
},
];
const BURNED_ROAD_POIS: LocationPointOfInterestContent[] = [
{
key: 'hunt-area',
title: 'Jagdgebiet',
actionLabel: 'Jagd beginnen',
title: 'Hunting Ground',
actionLabel: 'Begin Hunt',
type: 'HUNT',
iconKey: 'hunt',
xPercent: 52,
@@ -45,42 +45,42 @@ const BURNED_ROAD_POIS: LocationPointOfInterestContent[] = [
},
{
key: 'inspect-tracks',
title: 'Verdächtige Spuren',
actionLabel: 'Untersuchen',
title: 'Suspicious Tracks',
actionLabel: 'Investigate',
type: 'INVESTIGATE',
iconKey: 'investigate',
xPercent: 32,
yPercent: 78,
enabled: true,
resultTitle: 'Verdächtige Spuren',
resultText: 'Frische Stiefelabdrücke führen nach Osten.',
resultTitle: 'Suspicious Tracks',
resultText: 'Fresh bootprints lead east.',
},
{
key: 'sealed-crypt',
title: 'Versiegelte Krypta',
actionLabel: 'Öffnen',
title: 'Sealed Crypt',
actionLabel: 'Open',
type: 'DUNGEON',
iconKey: 'search',
xPercent: 90,
yPercent: 20,
enabled: false,
resultTitle: 'Versiegelte Krypta',
resultText: 'Noch verschlossen.',
resultTitle: 'Sealed Crypt',
resultText: 'Still sealed.',
},
];
const BURNED_ROAD_ACTIONS: LocationPrimaryActionContent[] = [
{
key: 'start-hunt',
label: 'Jagd beginnen',
description: 'Im Gebiet jagen',
label: 'Begin Hunt',
description: 'Hunt in this area',
type: 'HUNT',
iconKey: 'hunt',
enabled: true,
},
{
key: 'investigate-tracks',
label: 'Spuren untersuchen',
label: 'Investigate tracks',
type: 'INVESTIGATE',
iconKey: 'investigate',
enabled: true,
@@ -121,17 +121,17 @@ function poolEntry(
// worst entry this pool reads STRONG; judged by what a traveller actually
// meets it reads MATCH.
const BURNED_ROAD_POOL = [
poolEntry('Aschenratte', 70, { level: 1, attack: 5, armor: 0, maxHp: 45 }),
poolEntry('Straßenräuber', 30, { level: 2, attack: 9, armor: 5, maxHp: 75 }),
poolEntry('Ash Rat', 70, { level: 1, attack: 5, armor: 0, maxHp: 45 }),
poolEntry('Road Bandit', 30, { level: 2, attack: 9, armor: 5, maxHp: 75 }),
];
function currentLocation(): LocationDefinition {
return {
id: SOUTH_GATE_ID,
key: 'south-gate',
name: 'S\u00fcdtor von Graufurt',
name: 'Graufurt South Gate',
description:
'Das S\u00fcdtor ist der sichere Ausgangspunkt nach S\u00fcden.',
'The South Gate is the safe starting point heading south.',
regionKey: 'ashen-fields',
minRecommendedLevel: 1,
maxRecommendedLevel: 1,
@@ -139,10 +139,10 @@ function currentLocation(): LocationDefinition {
isSafe: true,
huntingEnabled: false,
artworkPath: '/assets/locations/south-gate.webp',
regionName: 'Aschenfelder',
regionTierLabel: 'Gebiet 1',
regionName: 'Ashen Fields',
regionTierLabel: 'Tier 1',
locationType: 'TRANSITION',
localDescription: 'Hinter den Wachtfeuern beginnen die Aschenfelder.',
localDescription: 'Beyond the watch-fires begin the Ashen Fields.',
localArtworkPath: '/images/backgrounds/Suedtor.png',
localPointsOfInterest: SOUTH_GATE_POIS,
localPrimaryActions: [],
@@ -159,8 +159,8 @@ function burnedRoad(): LocationDefinition {
return {
id: BURNED_ROAD_ID,
key: 'burned-road',
name: 'Verbrannte Stra\u00dfe',
description: 'Eine verbrannte Handelsroute durch die Aschenfelder.',
name: 'Burned Road',
description: 'A burned trade route through the Ashen Fields.',
regionKey: 'ashen-fields',
minRecommendedLevel: 1,
maxRecommendedLevel: 2,
@@ -168,14 +168,14 @@ function burnedRoad(): LocationDefinition {
isSafe: false,
huntingEnabled: true,
artworkPath: '/assets/locations/burned-road.webp',
regionName: 'Aschenfelder',
regionTierLabel: 'Gebiet 1',
regionName: 'Ashen Fields',
regionTierLabel: 'Tier 1',
locationType: 'HUNTING_GROUND',
localDescription: 'Ein alter Handelsweg, in Asche gelegt.',
localDescription: 'An old trade road, burned to ash.',
localArtworkPath: '/images/backgrounds/Aschestrasse.png',
localPointsOfInterest: BURNED_ROAD_POIS,
localPrimaryActions: BURNED_ROAD_ACTIONS,
localRewardPreview: [{ key: 'silver', label: 'Silber', iconKey: 'silver' }],
localRewardPreview: [{ key: 'silver', label: 'Silver', iconKey: 'silver' }],
createdAt: new Date('2026-08-18T09:00:00.000Z'),
updatedAt: new Date('2026-08-18T09:00:00.000Z'),
characters: [],
@@ -244,9 +244,9 @@ describe('WorldService', () => {
expect(result).toEqual({
id: SOUTH_GATE_ID,
key: 'south-gate',
name: 'S\u00fcdtor von Graufurt',
name: 'Graufurt South Gate',
description:
'Das S\u00fcdtor ist der sichere Ausgangspunkt nach S\u00fcden.',
'The South Gate is the safe starting point heading south.',
regionKey: 'ashen-fields',
minRecommendedLevel: 1,
maxRecommendedLevel: 1,
@@ -254,18 +254,18 @@ describe('WorldService', () => {
isSafe: true,
huntingEnabled: false,
artworkPath: '/assets/locations/south-gate.webp',
regionName: 'Aschenfelder',
regionTierLabel: 'Gebiet 1',
regionName: 'Ashen Fields',
regionTierLabel: 'Tier 1',
locationType: 'TRANSITION',
localDescription: 'Hinter den Wachtfeuern beginnen die Aschenfelder.',
localDescription: 'Beyond the watch-fires begin the Ashen Fields.',
localArtworkPath: '/images/backgrounds/Suedtor.png',
dangerRating: null,
recommendationLabel: '1',
pointsOfInterest: [
{
key: 'gate-watch',
title: 'Torwache',
actionLabel: 'Sprechen',
title: 'Gate Watch',
actionLabel: 'Talk',
type: 'NPC',
iconKey: 'speak',
xPercent: 45,
@@ -281,7 +281,7 @@ describe('WorldService', () => {
targetLocation: {
id: BURNED_ROAD_ID,
key: 'burned-road',
name: 'Verbrannte Stra\u00dfe',
name: 'Burned Road',
},
travelDurationSeconds: 10,
danger: 'LOW',
@@ -325,8 +325,8 @@ describe('WorldService', () => {
const result = await service.getCurrentLocation(CHARACTER_ID);
expect(result.possibleMonsters).toEqual([
'Aschenratte',
'Stra\u00dfenr\u00e4uber',
'Ash Rat',
'Road Bandit',
]);
expect(findLocationMonsters).toHaveBeenCalledWith({
where: { locationId: BURNED_ROAD_ID, enabled: true },
@@ -373,13 +373,13 @@ describe('WorldService', () => {
expect(result).toEqual(
expect.objectContaining({
regionName: 'Aschenfelder',
regionTierLabel: 'Gebiet 1',
regionName: 'Ashen Fields',
regionTierLabel: 'Tier 1',
locationType: 'HUNTING_GROUND',
localDescription: 'Ein alter Handelsweg, in Asche gelegt.',
localDescription: 'An old trade road, burned to ash.',
localArtworkPath: '/images/backgrounds/Aschestrasse.png',
recommendationLabel: '12',
rewardPreview: [{ key: 'silver', label: 'Silber', iconKey: 'silver' }],
rewardPreview: [{ key: 'silver', label: 'Silver', iconKey: 'silver' }],
}),
);
expect(result.primaryActions).toEqual(BURNED_ROAD_ACTIONS);
@@ -397,8 +397,8 @@ describe('WorldService', () => {
expect(result.pointsOfInterest).toEqual([
{
key: 'hunt-area',
title: 'Jagdgebiet',
actionLabel: 'Jagd beginnen',
title: 'Hunting Ground',
actionLabel: 'Begin Hunt',
type: 'HUNT',
iconKey: 'hunt',
xPercent: 52,
@@ -407,8 +407,8 @@ describe('WorldService', () => {
},
{
key: 'inspect-tracks',
title: 'Verdächtige Spuren',
actionLabel: 'Untersuchen',
title: 'Suspicious Tracks',
actionLabel: 'Investigate',
type: 'INVESTIGATE',
iconKey: 'investigate',
xPercent: 32,
@@ -417,8 +417,8 @@ describe('WorldService', () => {
},
{
key: 'sealed-crypt',
title: 'Versiegelte Krypta',
actionLabel: 'Öffnen',
title: 'Sealed Crypt',
actionLabel: 'Open',
type: 'DUNGEON',
iconKey: 'search',
xPercent: 90,
@@ -426,8 +426,8 @@ describe('WorldService', () => {
enabled: false,
},
]);
expect(JSON.stringify(result)).not.toContain('Frische Stiefelabdrücke');
expect(JSON.stringify(result)).not.toContain('Noch verschlossen');
expect(JSON.stringify(result)).not.toContain('Fresh bootprints');
expect(JSON.stringify(result)).not.toContain('Still sealed');
});
it('derives the encounter preview from the location monster pool', async () => {
@@ -435,16 +435,16 @@ describe('WorldService', () => {
expect(result.encounterPreview).toEqual([
{
key: 'aschenratte',
name: 'Aschenratte',
key: 'ash rat',
name: 'Ash Rat',
level: 1,
iconPath: '/images/monsters/icons/aschenratte-128.png',
iconPath: '/images/monsters/icons/ash rat-128.png',
},
{
key: 'straßenräuber',
name: 'Straßenräuber',
key: 'road bandit',
name: 'Road Bandit',
level: 2,
iconPath: '/images/monsters/icons/straßenräuber-128.png',
iconPath: '/images/monsters/icons/road bandit-128.png',
},
]);
});