misack's picture
Claude Opus 4.7 (1M context)
Round 6: German localization (en + de)
bd956f0
Raw History Blame Contribute Delete
5.61 kB
import { ReactNode } from 'react';
import { useI18n, type Language } from '../i18n';
// Shell components ported from components/Shell.jsx — class names preserved
// so the paper/ink/oxblood Board Console look is identical.
export function TopBar(props: {
game?: { id: string; turn: number; max: number; recon_remaining: number } | null;
route: string;
onNav: (r: string) => void;
}) {
const { game, route, onNav } = props;
const { t, lang, setLang } = useI18n();
return (
<header className="nh-topbar">
<div className="nh-topbar__l">
<button className="nh-brand" onClick={() => onNav('list')}>
Norhard<em>/</em>War<span className="nh-brand__tag">{t('shell.brand.tag')}</span>
</button>
<nav className="nh-nav">
<button className={route === 'list' ? 'on' : ''} onClick={() => onNav('list')}>
{t('shell.nav.cases')}
</button>
<button className={route === 'new' ? 'on' : ''} onClick={() => onNav('new')}>
{t('shell.nav.newCase')}
</button>
</nav>
</div>
<div className="nh-topbar__r" style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{game && (
<div className="nh-status">
<span>{t('shell.status.case')} <b className="mono">{game.id.slice(0, 4)}…{game.id.slice(-4)}</b></span>
<span className="nh-sep"></span>
<span>{t('shell.status.turn')} <b>{game.turn} / {game.max}</b></span>
<span className="nh-sep"></span>
<span>{t('shell.status.recon')} <b>{game.recon_remaining} / 3</b></span>
</div>
)}
<LangToggle lang={lang} setLang={setLang} ariaLabel={t('shell.lang.toggle.aria')} />
</div>
</header>
);
}
function LangToggle({
lang, setLang, ariaLabel,
}: {
lang: Language;
setLang: (l: Language) => void;
ariaLabel: string;
}) {
return (
<div
role="group"
aria-label={ariaLabel}
className="nh-lang"
style={{
display: 'inline-flex',
gap: 2,
fontFamily: 'var(--font-mono)',
fontSize: 11,
textTransform: 'uppercase',
letterSpacing: '0.06em',
}}
>
<button
type="button"
onClick={() => setLang('en')}
aria-pressed={lang === 'en'}
style={{
background: 'transparent',
border: 0,
padding: '4px 6px',
cursor: 'pointer',
color: lang === 'en' ? 'var(--ink)' : 'var(--slate)',
fontWeight: lang === 'en' ? 700 : 400,
textDecoration: lang === 'en' ? 'underline' : 'none',
textUnderlineOffset: 3,
fontFamily: 'inherit',
fontSize: 'inherit',
letterSpacing: 'inherit',
textTransform: 'inherit',
}}
>
EN
</button>
<span style={{ color: 'var(--slate)' }}>|</span>
<button
type="button"
onClick={() => setLang('de')}
aria-pressed={lang === 'de'}
style={{
background: 'transparent',
border: 0,
padding: '4px 6px',
cursor: 'pointer',
color: lang === 'de' ? 'var(--ink)' : 'var(--slate)',
fontWeight: lang === 'de' ? 700 : 400,
textDecoration: lang === 'de' ? 'underline' : 'none',
textUnderlineOffset: 3,
fontFamily: 'inherit',
fontSize: 'inherit',
letterSpacing: 'inherit',
textTransform: 'inherit',
}}
>
DE
</button>
</div>
);
}
const PHASES: { n: string; key: 'briefing' | 'recon' | 'action' | 'deliberation' | 'adjudication' }[] = [
{ n: '01', key: 'briefing' },
{ n: '02', key: 'recon' },
{ n: '03', key: 'action' },
{ n: '04', key: 'deliberation' },
{ n: '05', key: 'adjudication' },
];
export function PhaseRail({ current }: { current: 'briefing' | 'recon' | 'action' | 'deliberation' | 'adjudication' }) {
const { t } = useI18n();
const order = ['briefing', 'recon', 'action', 'deliberation', 'adjudication'];
const idx = order.indexOf(current);
return (
<div className="nh-rail">
{PHASES.map((p, i) => (
<div key={p.n} className={`nh-rail__p ${i < idx ? 'done' : ''} ${i === idx ? 'active' : ''}`}>
<span className="n mono">{p.n}</span>
<span className="t">{t(`phaseRail.${p.key}` as const)}</span>
</div>
))}
</div>
);
}
export function Dossier(props: {
tag?: string; title?: string; children?: ReactNode; footnote?: string[];
className?: string; fog?: boolean; inverse?: boolean;
}) {
const { tag, title, children, footnote, className = '', fog = false, inverse = false } = props;
const { t } = useI18n();
return (
<article className={`nh-dossier ${fog ? 'fog' : ''} ${inverse ? 'inverse' : ''} ${className}`}>
{fog && <span className="nh-dossier__fog">{t('shell.dossier.fog')}</span>}
{tag && <span className="nh-dossier__tag">{tag}</span>}
{title && <h3 className="nh-dossier__h">{title}</h3>}
{children}
{footnote && (
<div className="nh-dossier__fn">
{footnote.map((f, i) => <span key={i}>{f}</span>)}
</div>
)}
</article>
);
}
export function Panel({ tag, title, children }: { tag?: string; title?: string; children: ReactNode }) {
return (
<section className="nh-panel">
{tag && <span className="nh-panel__tag">{tag}</span>}
{title && <h3>{title}</h3>}
{children}
</section>
);
}
export function Spinner() {
return <span className="nh-typing"><i /><i /><i /></span>;
}