feat(web): translate topbar, navigation, context panel, footer, and reputation display to English
Translates the remaining German static copy in the layout/shared components per the design doc glossary: top-bar (World Renown, Silver, loading text), side-navigation (nav labels and aria-labels), context-panel (area info, safety/hunt facts, encounters), game-footer (connection status, Ashen Fields), and reputation-display (aria-label, highest-rank text). Also fixes stale German rank-label fixtures in reputation-display.component.spec.ts flagged by Task 2's reviewer, and updates app.spec.ts assertions that exercised the now-translated side-navigation/top-bar output via the app shell.
This commit is contained in:
@@ -35,7 +35,7 @@ describe('App', () => {
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('renders the reusable game shell with Karte and Jagd available', async () => {
|
||||
it('renders the reusable game shell with Map and Hunt available', async () => {
|
||||
const fixture = TestBed.createComponent(AppShellComponent);
|
||||
const router = TestBed.inject(Router);
|
||||
await router.navigateByUrl('/world');
|
||||
@@ -52,20 +52,20 @@ describe('App', () => {
|
||||
expect(mapButton).not.toBeNull();
|
||||
expect(mapButton?.disabled).toBe(false);
|
||||
expect(mapButton?.getAttribute('aria-current')).toBe('page');
|
||||
expect(mapButton?.getAttribute('aria-label')).toBe('Karte');
|
||||
expect(mapButton?.getAttribute('aria-label')).toBe('Map');
|
||||
|
||||
const huntButton = element.querySelector<HTMLButtonElement>('[data-navigation="hunt"]');
|
||||
expect(huntButton).not.toBeNull();
|
||||
expect(huntButton?.disabled).toBe(false);
|
||||
expect(huntButton?.getAttribute('aria-current')).toBeNull();
|
||||
expect(huntButton?.getAttribute('aria-label')).toBe('Jagd');
|
||||
expect(huntButton?.getAttribute('aria-label')).toBe('Hunt');
|
||||
|
||||
const inventoryButton = element.querySelector<HTMLButtonElement>(
|
||||
'[data-navigation="inventory"]',
|
||||
);
|
||||
expect(inventoryButton).not.toBeNull();
|
||||
expect(inventoryButton?.disabled).toBe(false);
|
||||
expect(inventoryButton?.getAttribute('aria-label')).toBe('Inventar');
|
||||
expect(inventoryButton?.getAttribute('aria-label')).toBe('Inventory');
|
||||
|
||||
for (const destination of ['quests', 'character']) {
|
||||
expect(
|
||||
@@ -76,7 +76,7 @@ describe('App', () => {
|
||||
expect(element.textContent).not.toContain('Shop');
|
||||
});
|
||||
|
||||
it('offers Ort as the first navigation entry, marked active on /location', async () => {
|
||||
it('offers Location as the first navigation entry, marked active on /location', async () => {
|
||||
const fixture = TestBed.createComponent(AppShellComponent);
|
||||
const router = TestBed.inject(Router);
|
||||
await router.navigateByUrl('/location');
|
||||
@@ -98,7 +98,7 @@ describe('App', () => {
|
||||
'[data-navigation="location"]',
|
||||
);
|
||||
expect(locationButton?.disabled).toBe(false);
|
||||
expect(locationButton?.getAttribute('aria-label')).toBe('Ort');
|
||||
expect(locationButton?.getAttribute('aria-label')).toBe('Location');
|
||||
expect(locationButton?.getAttribute('aria-current')).toBe('page');
|
||||
expect(
|
||||
element.querySelector('[data-navigation="world"]')?.getAttribute('aria-current'),
|
||||
@@ -135,7 +135,7 @@ describe('App', () => {
|
||||
expect(fixture.nativeElement.querySelector('app-context-panel')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('marks Jagd as the active navigation entry while on /hunt', async () => {
|
||||
it('marks Hunt as the active navigation entry while on /hunt', async () => {
|
||||
const fixture = TestBed.createComponent(AppShellComponent);
|
||||
const router = TestBed.inject(Router);
|
||||
await router.navigateByUrl('/hunt');
|
||||
@@ -183,7 +183,9 @@ describe('App', () => {
|
||||
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain(
|
||||
'Mara Ashfall',
|
||||
);
|
||||
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('Ansehen 7');
|
||||
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain(
|
||||
'World Renown 7',
|
||||
);
|
||||
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('52 / 80');
|
||||
expect(fixture.nativeElement.querySelector('app-top-bar')?.textContent).toContain('150');
|
||||
});
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
<aside class="context-panel" aria-label="Gebietsinformationen">
|
||||
<aside class="context-panel" aria-label="Area Information">
|
||||
@if (worldStore.currentLocation(); as location) {
|
||||
<span class="context-panel__eyebrow">GEBIETSINFO</span>
|
||||
<span class="context-panel__eyebrow">AREA INFO</span>
|
||||
<h2>{{ location.name }}</h2>
|
||||
|
||||
@if (worldStore.selectedConnection(); as selected) {
|
||||
<p class="context-panel__selection">Ausgewähltes Ziel: {{ selected.targetLocation.name }}</p>
|
||||
<p class="context-panel__selection">Selected Destination: {{ selected.targetLocation.name }}</p>
|
||||
}
|
||||
|
||||
<picture class="context-panel__artwork">
|
||||
@if (runtimeArtworkPath(location.artworkPath); as runtimeArtwork) {
|
||||
<source [srcset]="runtimeArtwork" type="image/jpeg" />
|
||||
}
|
||||
<img [src]="location.artworkPath" [alt]="'Ortsansicht: ' + location.name" />
|
||||
<img [src]="location.artworkPath" [alt]="'Location view: ' + location.name" />
|
||||
</picture>
|
||||
<p class="context-panel__description">{{ location.description }}</p>
|
||||
|
||||
<dl class="context-panel__facts">
|
||||
<div>
|
||||
<dt>Empfohlenes Ansehen</dt>
|
||||
<dt>Recommended Renown</dt>
|
||||
<dd>{{ location.minRecommendedLevel }}–{{ location.maxRecommendedLevel }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Sicherheit</dt>
|
||||
<dt>Safety</dt>
|
||||
<dd [class.context-panel__safe]="location.isSafe">
|
||||
{{ location.isSafe ? 'Sicherer Ort' : 'Gefährliches Gebiet' }}
|
||||
{{ location.isSafe ? 'Safe Location' : 'Dangerous Area' }}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Jagd</dt>
|
||||
<dd>{{ location.huntingEnabled ? 'Jagd möglich' : 'Keine Jagd' }}</dd>
|
||||
<dt>Hunt</dt>
|
||||
<dd>{{ location.huntingEnabled ? 'Hunting Available' : 'No Hunting' }}</dd>
|
||||
</div>
|
||||
@if (location.huntingEnabled && location.possibleMonsters.length) {
|
||||
<div>
|
||||
<dt>Mögliche Begegnungen</dt>
|
||||
<dt>Possible Encounters</dt>
|
||||
<dd>{{ location.possibleMonsters.join(', ') }}</dd>
|
||||
</div>
|
||||
}
|
||||
</dl>
|
||||
} @else {
|
||||
<span class="context-panel__eyebrow">GEBIETSINFO</span>
|
||||
<p class="context-panel__hint">Informationen erscheinen mit der Weltansicht.</p>
|
||||
<span class="context-panel__eyebrow">AREA INFO</span>
|
||||
<p class="context-panel__hint">Information appears with the world view.</p>
|
||||
}
|
||||
</aside>
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('ContextPanelComponent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('hides "Mögliche Begegnungen" when the location has no hunting', async () => {
|
||||
it('hides "Possible Encounters" when the location has no hunting', async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ContextPanelComponent],
|
||||
providers: [
|
||||
@@ -75,7 +75,7 @@ describe('ContextPanelComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
expect(element.textContent).not.toContain('Mögliche Begegnungen');
|
||||
expect(element.textContent).not.toContain('Possible Encounters');
|
||||
});
|
||||
|
||||
it('lists possible monsters as plain text when hunting is enabled', async () => {
|
||||
@@ -96,7 +96,7 @@ describe('ContextPanelComponent', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
expect(element.textContent).toContain('Mögliche Begegnungen');
|
||||
expect(element.textContent).toContain('Possible Encounters');
|
||||
expect(element.textContent).toContain('Aschenratte, Straßenräuber');
|
||||
expect(element.textContent).not.toMatch(/\d+\s?%/);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<footer class="game-footer">
|
||||
<span class="game-footer__status"><span aria-hidden="true">●</span> Verbindung bereit</span>
|
||||
<span class="game-footer__status"><span aria-hidden="true">●</span> Connection ready</span>
|
||||
<span class="game-footer__ornament" aria-hidden="true"></span>
|
||||
<span>Aschenfelder</span>
|
||||
<span>Ashen Fields</span>
|
||||
</footer>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<nav class="side-navigation" aria-label="Spielnavigation">
|
||||
<nav class="side-navigation" aria-label="Game navigation">
|
||||
<button
|
||||
class="side-navigation__item"
|
||||
type="button"
|
||||
@@ -7,12 +7,12 @@
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
ariaCurrentWhenActive="page"
|
||||
data-navigation="location"
|
||||
aria-label="Ort"
|
||||
aria-label="Location"
|
||||
>
|
||||
<!-- Drawn rather than loaded: no medallion for "Ort" has been painted yet.
|
||||
<!-- Drawn rather than loaded: no medallion for "Location" has been painted yet.
|
||||
Framed to sit alongside the existing icons until one exists. -->
|
||||
<img src="/images/hud/runtime/LocationIcon-128.png" alt="" />
|
||||
<span>Ort</span>
|
||||
<span>Location</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -23,10 +23,10 @@
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
ariaCurrentWhenActive="page"
|
||||
data-navigation="world"
|
||||
aria-label="Karte"
|
||||
aria-label="Map"
|
||||
>
|
||||
<img src="/images/hud/runtime/MapsIcon-128.png" alt="" />
|
||||
<span>Karte</span>
|
||||
<span>Map</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -37,17 +37,17 @@
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
ariaCurrentWhenActive="page"
|
||||
data-navigation="hunt"
|
||||
aria-label="Jagd"
|
||||
aria-label="Hunt"
|
||||
>
|
||||
<img src="/images/hud/runtime/HuntIcon-128.png" alt="" />
|
||||
<span>Jagd</span>
|
||||
<span>Hunt</span>
|
||||
</button>
|
||||
<button
|
||||
class="side-navigation__item"
|
||||
type="button"
|
||||
data-navigation="quests"
|
||||
disabled
|
||||
aria-label="Quests sind noch nicht verfügbar"
|
||||
aria-label="Quests are not yet available"
|
||||
>
|
||||
<img src="/images/hud/runtime/QuestsIcon-128.png" alt="" />
|
||||
<span>Quests</span>
|
||||
@@ -60,19 +60,19 @@
|
||||
[routerLinkActiveOptions]="{ exact: true }"
|
||||
ariaCurrentWhenActive="page"
|
||||
data-navigation="inventory"
|
||||
aria-label="Inventar"
|
||||
aria-label="Inventory"
|
||||
>
|
||||
<img src="/images/hud/runtime/InventoryIcon-128.png" alt="" />
|
||||
<span>Inventar</span>
|
||||
<span>Inventory</span>
|
||||
</button>
|
||||
<button
|
||||
class="side-navigation__item"
|
||||
type="button"
|
||||
data-navigation="character"
|
||||
disabled
|
||||
aria-label="Charakter ist noch nicht verfügbar"
|
||||
aria-label="Character is not yet available"
|
||||
>
|
||||
<img src="/images/hud/runtime/CharacterIcon-128.png" alt="" />
|
||||
<span>Charakter</span>
|
||||
<span>Character</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
@if (character(); as character) {
|
||||
<div class="top-bar__identity">
|
||||
<span class="top-bar__name">{{ character.name }}</span>
|
||||
<span class="top-bar__renown">Ansehen {{ character.renown }}</span>
|
||||
<span class="top-bar__renown">World Renown {{ character.renown }}</span>
|
||||
</div>
|
||||
<div class="top-bar__health" aria-label="Lebenspunkte">
|
||||
<div class="top-bar__health" aria-label="Health points">
|
||||
<span>{{ character.currentHp }} / {{ character.maxHp }}</span>
|
||||
<span class="top-bar__health-track" aria-hidden="true">
|
||||
<span
|
||||
@@ -17,12 +17,12 @@
|
||||
</div>
|
||||
<dl class="top-bar__resources">
|
||||
<div class="top-bar__resource" data-top-bar-silver>
|
||||
<dt>Silber</dt>
|
||||
<dt>Silver</dt>
|
||||
<dd>{{ character.silver }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
} @else {
|
||||
<span class="top-bar__loading">Charakterdaten werden geladen</span>
|
||||
<span class="top-bar__loading">Loading character data</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -39,16 +39,16 @@ describe('TopBarComponent', () => {
|
||||
expect(element.querySelector('[data-top-bar-silver]')?.textContent).toContain('120');
|
||||
});
|
||||
|
||||
it('shows Ansehen instead of Level, and displays no XP value', async () => {
|
||||
it('shows World Renown instead of Level, and displays no XP value', async () => {
|
||||
const element = await render(characterFixture({ renown: 3 }));
|
||||
|
||||
expect(element.textContent).toContain('Ansehen 3');
|
||||
expect(element.textContent).toContain('World Renown 3');
|
||||
expect(element.querySelector('[data-top-bar-experience]')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows a loading message when no character is available yet', async () => {
|
||||
const element = await render(null);
|
||||
|
||||
expect(element.textContent).toContain('Charakterdaten werden geladen');
|
||||
expect(element.textContent).toContain('Loading character data');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="reputation-display" [attr.aria-label]="entry().factionName + ' Ruf'">
|
||||
<div class="reputation-display" [attr.aria-label]="entry().factionName + ' Reputation'">
|
||||
<div class="reputation-display__header">
|
||||
<span class="reputation-display__faction">{{ entry().factionName }}</span>
|
||||
<span class="reputation-display__rank">{{ entry().rankLabel }}</span>
|
||||
@@ -20,6 +20,6 @@
|
||||
<span class="reputation-display__value">{{ entry().reputation }} / {{ entry().nextThreshold }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<p class="reputation-display__max" data-reputation-max>{{ entry().reputation }} — Höchster Rang erreicht</p>
|
||||
<p class="reputation-display__max" data-reputation-max>{{ entry().reputation }} — Highest Rank Reached</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -17,13 +17,13 @@ describe('ReputationDisplayComponent', () => {
|
||||
factionName: 'Grenzwacht',
|
||||
reputation: 320,
|
||||
rank: 'KNOWN',
|
||||
rankLabel: 'Bekannt',
|
||||
rankLabel: 'Known',
|
||||
nextThreshold: 500,
|
||||
});
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.textContent).toContain('Grenzwacht');
|
||||
expect(element.textContent).toContain('Bekannt');
|
||||
expect(element.textContent).toContain('Known');
|
||||
});
|
||||
|
||||
it('shows current reputation against the next threshold', async () => {
|
||||
@@ -32,7 +32,7 @@ describe('ReputationDisplayComponent', () => {
|
||||
factionName: 'Grenzwacht',
|
||||
reputation: 320,
|
||||
rank: 'KNOWN',
|
||||
rankLabel: 'Bekannt',
|
||||
rankLabel: 'Known',
|
||||
nextThreshold: 500,
|
||||
});
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
@@ -47,7 +47,7 @@ describe('ReputationDisplayComponent', () => {
|
||||
factionName: 'Grenzwacht',
|
||||
reputation: 320,
|
||||
rank: 'KNOWN',
|
||||
rankLabel: 'Bekannt',
|
||||
rankLabel: 'Known',
|
||||
nextThreshold: 500,
|
||||
});
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
@@ -64,12 +64,12 @@ describe('ReputationDisplayComponent', () => {
|
||||
factionName: 'Grenzwacht',
|
||||
reputation: 1500,
|
||||
rank: 'ESTEEMED',
|
||||
rankLabel: 'Geachtet',
|
||||
rankLabel: 'Esteemed',
|
||||
nextThreshold: null,
|
||||
});
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.textContent).toContain('Geachtet');
|
||||
expect(element.textContent).toContain('Esteemed');
|
||||
expect(element.querySelector('[data-reputation-max]')).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -79,13 +79,13 @@ describe('ReputationDisplayComponent', () => {
|
||||
factionName: 'Der Stille Orden',
|
||||
reputation: 0,
|
||||
rank: 'STRANGER',
|
||||
rankLabel: 'Fremder',
|
||||
rankLabel: 'Stranger',
|
||||
nextThreshold: 100,
|
||||
});
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.textContent).toContain('Der Stille Orden');
|
||||
expect(element.textContent).toContain('Fremder');
|
||||
expect(element.textContent).toContain('Stranger');
|
||||
expect(element.textContent).toContain('0');
|
||||
|
||||
const fill = element.querySelector<HTMLElement>('[data-reputation-progress-fill]');
|
||||
|
||||
Reference in New Issue
Block a user