feat(web): translate hunt screen and encounter card copy to English
Translates every static German string in hunt-page.component.html and encounter-card.component.html/ts: headings, buttons, aria-labels, loading/error text, the monster level label, and the encounter action-label logic (Defeated/In Combat/Attack). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACkMEDYiwtcfchqKkiUJNX
This commit is contained in:
@@ -18,7 +18,7 @@
|
|||||||
<img
|
<img
|
||||||
class="encounter-card__defeated-mark"
|
class="encounter-card__defeated-mark"
|
||||||
[src]="defeatedMark"
|
[src]="defeatedMark"
|
||||||
alt="Besiegt"
|
alt="Defeated"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<h3 class="encounter-card__name">{{ encounter.monster.name }}</h3>
|
<h3 class="encounter-card__name">{{ encounter.monster.name }}</h3>
|
||||||
|
|
||||||
<div class="encounter-card__meta">
|
<div class="encounter-card__meta">
|
||||||
<span class="encounter-card__level">Stufe {{ encounter.monster.level }}</span>
|
<span class="encounter-card__level">Level {{ encounter.monster.level }}</span>
|
||||||
<app-danger-badge class="encounter-card__danger" [rating]="encounter.dangerRating" />
|
<app-danger-badge class="encounter-card__danger" [rating]="encounter.dangerRating" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ describe('EncounterCardComponent', () => {
|
|||||||
const artwork = element.querySelector('.encounter-card__artwork');
|
const artwork = element.querySelector('.encounter-card__artwork');
|
||||||
|
|
||||||
expect(element.textContent).toContain('Dämmerwolf');
|
expect(element.textContent).toContain('Dämmerwolf');
|
||||||
expect(element.textContent).toContain('Stufe 3');
|
expect(element.textContent).toContain('Level 3');
|
||||||
expect(element.textContent).toContain('Suitable');
|
expect(element.textContent).toContain('Suitable');
|
||||||
expect(artwork?.getAttribute('src')).toBe('/images/enemies/Dawnwolf.png');
|
expect(artwork?.getAttribute('src')).toBe('/images/enemies/Dawnwolf.png');
|
||||||
expect(artwork?.getAttribute('alt')).toBe('Dämmerwolf');
|
expect(artwork?.getAttribute('alt')).toBe('Dämmerwolf');
|
||||||
@@ -72,7 +72,7 @@ describe('EncounterCardComponent', () => {
|
|||||||
expect(element.querySelector('.encounter-card__crest-icon')).toBeNull();
|
expect(element.querySelector('.encounter-card__crest-icon')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('emits the encounter id (never the monster key) when Angreifen is clicked', () => {
|
it('emits the encounter id (never the monster key) when Attack is clicked', () => {
|
||||||
const fixture = TestBed.createComponent(EncounterCardComponent);
|
const fixture = TestBed.createComponent(EncounterCardComponent);
|
||||||
fixture.componentRef.setInput('encounter', dawnwolfEncounter);
|
fixture.componentRef.setInput('encounter', dawnwolfEncounter);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -82,7 +82,7 @@ describe('EncounterCardComponent', () => {
|
|||||||
|
|
||||||
const button = Array.from(fixture.nativeElement.querySelectorAll('button')).find(
|
const button = Array.from(fixture.nativeElement.querySelectorAll('button')).find(
|
||||||
(candidate): candidate is HTMLButtonElement =>
|
(candidate): candidate is HTMLButtonElement =>
|
||||||
candidate instanceof HTMLButtonElement && candidate.textContent?.trim() === 'Angreifen',
|
candidate instanceof HTMLButtonElement && candidate.textContent?.trim() === 'Attack',
|
||||||
);
|
);
|
||||||
button?.click();
|
button?.click();
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ describe('EncounterCardComponent', () => {
|
|||||||
const mark = element.querySelector('.encounter-card__defeated-mark');
|
const mark = element.querySelector('.encounter-card__defeated-mark');
|
||||||
|
|
||||||
expect(mark).not.toBeNull();
|
expect(mark).not.toBeNull();
|
||||||
expect(mark?.getAttribute('alt')).toBe('Besiegt');
|
expect(mark?.getAttribute('alt')).toBe('Defeated');
|
||||||
expect(
|
expect(
|
||||||
element.querySelector<HTMLButtonElement>('.encounter-card__attack')?.disabled,
|
element.querySelector<HTMLButtonElement>('.encounter-card__attack')?.disabled,
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ export class EncounterCardComponent {
|
|||||||
|
|
||||||
protected get actionLabel(): string {
|
protected get actionLabel(): string {
|
||||||
if (this.defeated) {
|
if (this.defeated) {
|
||||||
return 'Besiegt';
|
return 'Defeated';
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.encounter.status === 'IN_PROGRESS' ? 'Im Kampf' : 'Angreifen';
|
return this.encounter.status === 'IN_PROGRESS' ? 'In Combat' : 'Attack';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onAttack(): void {
|
protected onAttack(): void {
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
<section class="hunt-page" aria-label="Jagd">
|
<section class="hunt-page" aria-label="Hunt">
|
||||||
@if (worldStore.currentLocation(); as location) {
|
@if (worldStore.currentLocation(); as location) {
|
||||||
@if (!location.huntingEnabled) {
|
@if (!location.huntingEnabled) {
|
||||||
<section class="hunt-page__unavailable">
|
<section class="hunt-page__unavailable">
|
||||||
<h2>Keine Jagd verfügbar</h2>
|
<h2>No Hunt Available</h2>
|
||||||
<p>
|
<p>No hunting ground is available here right now.</p>
|
||||||
Am Südtor von Graufurt gibt es keine regulären Jagdgebiete. Reise in ein gefährlicheres
|
<button type="button" data-hunt-to-location (click)="goToLocation()">Back to Location</button>
|
||||||
Gebiet, um nach Gegnern zu suchen.
|
|
||||||
</p>
|
|
||||||
<button type="button" data-hunt-to-location (click)="goToLocation()">Zurück zum Ort</button>
|
|
||||||
</section>
|
</section>
|
||||||
} @else if (huntingStore.currentHunt(); as hunt) {
|
} @else if (huntingStore.currentHunt(); as hunt) {
|
||||||
<section class="hunt-page__results" [attr.aria-label]="'Begegnungen bei ' + location.name">
|
<section class="hunt-page__results" [attr.aria-label]="'Encounters at ' + location.name">
|
||||||
<p class="hunt-page__results-heading">{{ location.name }} — Begegnungen</p>
|
<p class="hunt-page__results-heading">{{ location.name }} — Encounters</p>
|
||||||
|
|
||||||
<div class="hunt-page__encounters">
|
<div class="hunt-page__encounters">
|
||||||
@for (encounter of huntingStore.encounters(); track encounter.id) {
|
@for (encounter of huntingStore.encounters(); track encounter.id) {
|
||||||
@@ -26,9 +23,9 @@
|
|||||||
[disabled]="huntingStore.loading()"
|
[disabled]="huntingStore.loading()"
|
||||||
(click)="refreshHunt()"
|
(click)="refreshHunt()"
|
||||||
>
|
>
|
||||||
Neu suchen
|
Search Again
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-hunt-to-location (click)="goToLocation()">Zurück zum Ort</button>
|
<button type="button" data-hunt-to-location (click)="goToLocation()">Back to Location</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
} @else {
|
} @else {
|
||||||
@@ -36,7 +33,7 @@
|
|||||||
<h2>{{ location.name }}</h2>
|
<h2>{{ location.name }}</h2>
|
||||||
<p>{{ location.description }}</p>
|
<p>{{ location.description }}</p>
|
||||||
<p class="hunt-page__hint">
|
<p class="hunt-page__hint">
|
||||||
Durchsuche die Umgebung nach Spuren und Gegnern, bevor du dich in den Kampf wagst.
|
Search the surroundings for tracks and enemies before venturing into combat.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -44,31 +41,31 @@
|
|||||||
[disabled]="huntingStore.loading()"
|
[disabled]="huntingStore.loading()"
|
||||||
(click)="startHunt()"
|
(click)="startHunt()"
|
||||||
>
|
>
|
||||||
Jagd beginnen
|
Begin Hunt
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (huntingStore.loading()) {
|
@if (huntingStore.loading()) {
|
||||||
<p class="hunt-page__loading" role="status">Du suchst nach Spuren...</p>
|
<p class="hunt-page__loading" role="status">You search for tracks...</p>
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
<section class="hunt-page__empty" aria-live="polite">
|
<section class="hunt-page__empty" aria-live="polite">
|
||||||
<p>Jagdgebiet wird vorbereitet.</p>
|
<p>Preparing hunting ground.</p>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (huntingStore.error(); as error) {
|
@if (huntingStore.error(); as error) {
|
||||||
<section class="hunt-page__error" role="alert">
|
<section class="hunt-page__error" role="alert">
|
||||||
<p>{{ error }}</p>
|
<p>{{ error }}</p>
|
||||||
<button type="button" data-hunt-retry (click)="retry()">Erneut versuchen</button>
|
<button type="button" data-hunt-retry (click)="retry()">Retry</button>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (combatStore.error(); as combatError) {
|
@if (combatStore.error(); as combatError) {
|
||||||
<section class="hunt-page__error" role="alert">
|
<section class="hunt-page__error" role="alert">
|
||||||
<p>{{ combatError }}</p>
|
<p>{{ combatError }}</p>
|
||||||
<button type="button" data-hunt-combat-dismiss (click)="dismissCombatError()">Schließen</button>
|
<button type="button" data-hunt-combat-dismiss (click)="dismissCombatError()">Close</button>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -130,26 +130,26 @@ describe('HuntPageComponent', () => {
|
|||||||
return fixture;
|
return fixture;
|
||||||
}
|
}
|
||||||
|
|
||||||
it('shows the hunting-unavailable state at the Südtor, with no Jagd beginnen button, and a way back to the location', async () => {
|
it('shows the hunting-unavailable state at the Südtor, with no Begin Hunt button, and a way back to the location', async () => {
|
||||||
const fixture = await setup(southGate);
|
const fixture = await setup(southGate);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
expect(element.textContent).toContain('Keine Jagd verfügbar');
|
expect(element.textContent).toContain('No Hunt Available');
|
||||||
expect(element.textContent).toContain('Am Südtor von Graufurt gibt es keine regulären Jagdgebiete.');
|
expect(element.textContent).toContain('No hunting ground is available here right now.');
|
||||||
expect(
|
expect(
|
||||||
Array.from(element.querySelectorAll('button')).some(
|
Array.from(element.querySelectorAll('button')).some(
|
||||||
(button) => button.textContent?.trim() === 'Jagd beginnen',
|
(button) => button.textContent?.trim() === 'Begin Hunt',
|
||||||
),
|
),
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
|
|
||||||
const backButton = element.querySelector<HTMLButtonElement>('[data-hunt-to-location]');
|
const backButton = element.querySelector<HTMLButtonElement>('[data-hunt-to-location]');
|
||||||
expect(backButton?.textContent?.trim()).toBe('Zurück zum Ort');
|
expect(backButton?.textContent?.trim()).toBe('Back to Location');
|
||||||
backButton?.click();
|
backButton?.click();
|
||||||
|
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['/location']);
|
expect(router.navigate).toHaveBeenCalledWith(['/location']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls startHunt when Jagd beginnen is clicked at a hunting-enabled location', async () => {
|
it('calls startHunt when Begin Hunt is clicked at a hunting-enabled location', async () => {
|
||||||
const fixture = await setup(burnedRoad);
|
const fixture = await setup(burnedRoad);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
@@ -175,11 +175,11 @@ describe('HuntPageComponent', () => {
|
|||||||
'.encounter-card__artwork[src="/images/combat/sprites/road-bandit-620.png"]',
|
'.encounter-card__artwork[src="/images/combat/sprites/road-bandit-620.png"]',
|
||||||
).length,
|
).length,
|
||||||
).toBe(1);
|
).toBe(1);
|
||||||
expect(element.textContent).toContain('Stufe 1');
|
expect(element.textContent).toContain('Level 1');
|
||||||
expect(element.textContent).toContain('Stufe 3');
|
expect(element.textContent).toContain('Level 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls refreshHunt when Neu suchen is clicked', async () => {
|
it('calls refreshHunt when Search Again is clicked', async () => {
|
||||||
const fixture = await setup(burnedRoad, threeEncounterHunt);
|
const fixture = await setup(burnedRoad, threeEncounterHunt);
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ describe('HuntPageComponent', () => {
|
|||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
const attackButtons = Array.from(element.querySelectorAll('button')).filter(
|
const attackButtons = Array.from(element.querySelectorAll('button')).filter(
|
||||||
(button) => button.textContent?.trim() === 'Angreifen',
|
(button) => button.textContent?.trim() === 'Attack',
|
||||||
);
|
);
|
||||||
expect(attackButtons.length).toBe(3);
|
expect(attackButtons.length).toBe(3);
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ describe('HuntPageComponent', () => {
|
|||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
const attackButtons = Array.from(element.querySelectorAll('button')).filter(
|
const attackButtons = Array.from(element.querySelectorAll('button')).filter(
|
||||||
(button) => button.textContent?.trim() === 'Angreifen',
|
(button) => button.textContent?.trim() === 'Attack',
|
||||||
);
|
);
|
||||||
attackButtons[0].click();
|
attackButtons[0].click();
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
@@ -234,7 +234,7 @@ describe('HuntPageComponent', () => {
|
|||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
const attackButtons = Array.from(element.querySelectorAll('button')).filter(
|
const attackButtons = Array.from(element.querySelectorAll('button')).filter(
|
||||||
(button) => button.textContent?.trim() === 'Angreifen',
|
(button) => button.textContent?.trim() === 'Attack',
|
||||||
);
|
);
|
||||||
attackButtons[0].click();
|
attackButtons[0].click();
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
@@ -254,7 +254,7 @@ describe('HuntPageComponent', () => {
|
|||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
|
|
||||||
const attackButtons = Array.from(element.querySelectorAll('button')).filter(
|
const attackButtons = Array.from(element.querySelectorAll('button')).filter(
|
||||||
(button) => button.textContent?.trim() === 'Angreifen',
|
(button) => button.textContent?.trim() === 'Attack',
|
||||||
);
|
);
|
||||||
attackButtons[0].click();
|
attackButtons[0].click();
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
@@ -374,7 +374,7 @@ describe('HuntPageComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
expect(element.textContent).toContain('Du suchst nach Spuren...');
|
expect(element.textContent).toContain('You search for tracks...');
|
||||||
expect(element.querySelector<HTMLButtonElement>('[data-hunt-start]')?.disabled).toBe(true);
|
expect(element.querySelector<HTMLButtonElement>('[data-hunt-start]')?.disabled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user