feat(web): translate slot, rarity, danger, and location-type labels to English

Translates the four static Angular label maps (SLOT_LABELS, RARITY_LABELS,
DANGER_LABELS, LOCATION_TYPE_LABELS) and the inventory detail panel's
hardcoded item-type fallback per the English Game Content Foundation
design doc glossary. Updates every spec assertion across the web app that
checks rendered text sourced from these maps, including specs owned by
later tasks (combat-page, encounter-card, inventory-page) where they
render one of these labels.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-21 20:16:56 +02:00
parent e48409fbf1
commit 5222bfb2ab
11 changed files with 42 additions and 42 deletions

View File

@@ -580,7 +580,7 @@ describe('CombatPageComponent', () => {
'/images/items/bandit-blade.png',
);
expect(element.querySelector('[data-item-name]')?.textContent).toContain('Räuberklinge');
expect(element.querySelector('[data-item-rarity]')?.textContent).toContain('Gewöhnlich');
expect(element.querySelector('[data-item-rarity]')?.textContent).toContain('Common');
expect(element.querySelector('[data-reward-empty]')).toBeNull();
});

View File

@@ -47,7 +47,7 @@ describe('EncounterCardComponent', () => {
expect(element.textContent).toContain('Dämmerwolf');
expect(element.textContent).toContain('Stufe 3');
expect(element.textContent).toContain('Passend');
expect(element.textContent).toContain('Suitable');
expect(artwork?.getAttribute('src')).toBe('/images/enemies/Dawnwolf.png');
expect(artwork?.getAttribute('alt')).toBe('Dämmerwolf');
});

View File

@@ -40,7 +40,7 @@ export class InventoryDetailPanelComponent {
});
/** Stand-in for items with no slot: the API does not expose an item type. */
protected readonly typeLabel = computed(() => 'Gegenstand');
protected readonly typeLabel = computed(() => 'Item');
protected readonly statRows = computed<StatRow[]>(() => {
const item = this.item();

View File

@@ -149,9 +149,9 @@ describe('InventoryPageComponent', () => {
const { fixture } = await setup();
const text = (fixture.nativeElement as HTMLElement).querySelector('.inventory-page__equipment-list')?.textContent ?? '';
expect(text).toContain('Waffe');
expect(text).toContain('Weapon');
expect(text).toContain('Abgenutztes Kurzschwert');
expect(text).toContain('Kopf');
expect(text).toContain('Head');
expect(text).toContain('Leer');
});

View File

@@ -1,12 +1,12 @@
import type { EquipmentSlot } from '../../core/api/game-api.models';
/** German slot names, shared by the paper doll and the item detail panel. */
/** Slot names, shared by the paper doll and the item detail panel. */
export const SLOT_LABELS: Readonly<Record<EquipmentSlot, string>> = {
WEAPON: 'Waffe',
HEAD: 'Kopf',
CHEST: 'Brust',
HANDS: 'Handschuhe',
LEGS: 'Beine',
FEET: 'Stiefel',
AMULET: 'Amulett',
WEAPON: 'Weapon',
HEAD: 'Head',
CHEST: 'Chest',
HANDS: 'Hands',
LEGS: 'Legs',
FEET: 'Feet',
AMULET: 'Amulet',
};

View File

@@ -22,14 +22,14 @@ describe('LocationSidebarComponent', () => {
expect(text).toContain('Aschenfelder');
expect(text).toContain('Verbrannte Straße');
expect(text).toContain('Jagdgebiet');
expect(text).toContain('Hunting Ground');
expect(text).toContain('12');
});
it('shows the danger rating as the shared badge', async () => {
const element = await render(burnedRoadFixture());
expect(element.querySelector('app-danger-badge')?.textContent).toContain('Passend');
expect(element.querySelector('app-danger-badge')?.textContent).toContain('Suitable');
});
it('calls a location with no hostile pool safe instead of inventing a rating', async () => {

View File

@@ -4,14 +4,14 @@ import { DangerBadgeComponent } from '../../../shared/danger-badge/danger-badge.
import { LocationIconComponent } from '../location-icon/location-icon.component';
const LOCATION_TYPE_LABELS: Readonly<Record<string, string>> = {
SAFE_HUB: 'Zuflucht',
TRANSITION: 'Übergang',
HUNTING_GROUND: 'Jagdgebiet',
QUEST_LOCATION: 'Questort',
OUTPOST: 'Außenposten',
ELITE_ZONE: 'Elitegebiet',
BOSS_LOCATION: 'Bossort',
DUNGEON_ENTRANCE: 'Verlieseingang',
SAFE_HUB: 'Safe Haven',
TRANSITION: 'Transition',
HUNTING_GROUND: 'Hunting Ground',
QUEST_LOCATION: 'Quest Location',
OUTPOST: 'Outpost',
ELITE_ZONE: 'Elite Zone',
BOSS_LOCATION: 'Boss Location',
DUNGEON_ENTRANCE: 'Dungeon Entrance',
};
interface InteractionSummary {

View File

@@ -3,20 +3,20 @@ import type { DangerRating } from '../../core/api/game-api.models';
import { DangerBadgeComponent } from './danger-badge.component';
const expectations: Array<{ rating: DangerRating; label: string; modifierClass: string }> = [
{ rating: 'WEAK', label: 'Schwach', modifierClass: 'danger-badge--weak' },
{ rating: 'MATCH', label: 'Passend', modifierClass: 'danger-badge--match' },
{ rating: 'STRONG', label: 'Stark', modifierClass: 'danger-badge--strong' },
{ rating: 'WEAK', label: 'Weak', modifierClass: 'danger-badge--weak' },
{ rating: 'MATCH', label: 'Suitable', modifierClass: 'danger-badge--match' },
{ rating: 'STRONG', label: 'Strong', modifierClass: 'danger-badge--strong' },
{
rating: 'VERY_DANGEROUS',
label: 'Sehr gefährlich',
label: 'Very Dangerous',
modifierClass: 'danger-badge--very-dangerous',
},
{ rating: 'DEADLY', label: 'Tödlich', modifierClass: 'danger-badge--deadly' },
{ rating: 'DEADLY', label: 'Deadly', modifierClass: 'danger-badge--deadly' },
];
describe('DangerBadgeComponent', () => {
for (const { rating, label, modifierClass } of expectations) {
it(`renders the German label "${label}" and modifier class for ${rating}`, () => {
it(`renders the label "${label}" and modifier class for ${rating}`, () => {
const fixture = TestBed.createComponent(DangerBadgeComponent);
fixture.componentRef.setInput('rating', rating);
fixture.detectChanges();

View File

@@ -2,11 +2,11 @@ import { Component, Input } from '@angular/core';
import { DangerRating } from '../../core/api/game-api.models';
const DANGER_LABELS: Record<DangerRating, string> = {
WEAK: 'Schwach',
MATCH: 'Passend',
STRONG: 'Stark',
VERY_DANGEROUS: 'Sehr gefährlich',
DEADLY: 'Tödlich',
WEAK: 'Weak',
MATCH: 'Suitable',
STRONG: 'Strong',
VERY_DANGEROUS: 'Very Dangerous',
DEADLY: 'Deadly',
};
@Component({

View File

@@ -21,7 +21,7 @@ async function setup(item: RewardItemSummary, quantity = 1) {
}
describe('ItemCardComponent', () => {
it('renders the icon, name, and German rarity label', async () => {
it('renders the icon, name, and rarity label', async () => {
const fixture = await setup(banditBlade);
const element = fixture.nativeElement as HTMLElement;
@@ -29,19 +29,19 @@ describe('ItemCardComponent', () => {
expect(icon?.getAttribute('src')).toBe('/images/items/bandit-blade.png');
expect(icon?.getAttribute('alt')).toBe('Räuberklinge');
expect(element.querySelector('[data-item-name]')?.textContent).toContain('Räuberklinge');
expect(element.querySelector('[data-item-rarity]')?.textContent).toContain('Gewöhnlich');
expect(element.querySelector('[data-item-rarity]')?.textContent).toContain('Common');
});
it('labels the other rarities in German too', async () => {
it('labels the other rarities too', async () => {
const rare = await setup({ ...banditBlade, rarity: 'RARE' });
expect(
(rare.nativeElement as HTMLElement).querySelector('[data-item-rarity]')?.textContent,
).toContain('Selten');
).toContain('Rare');
const epic = await setup({ ...banditBlade, rarity: 'EPIC' });
expect(
(epic.nativeElement as HTMLElement).querySelector('[data-item-rarity]')?.textContent,
).toContain('Episch');
).toContain('Epic');
});
it('hides the quantity for a single item and shows it for a stack', async () => {

View File

@@ -2,9 +2,9 @@ import { Component, computed, input } from '@angular/core';
import type { ItemRarity, RewardItemSummary } from '../../core/api/game-api.models';
export const RARITY_LABELS: Readonly<Record<ItemRarity, string>> = {
COMMON: 'Gewöhnlich',
RARE: 'Selten',
EPIC: 'Episch',
COMMON: 'Common',
RARE: 'Rare',
EPIC: 'Epic',
};
/**