Skip to content

Commit 73edf39

Browse files
B2JK-Industryclaude
andcommitted
feat(i18n): PR-P G-35 — AI envelope localized title/tagline/description (schema + consumer)
User feedback (screenshot 8:51): in CS session on /games/ai/<id> the page chrome translates correctly but the envelope strings (title, tagline, description) stay PL. Pipeline currently translates only the spec questions via translateSpec; envelope metadata is derived from the static PL seed and never goes through Haiku. Schema-first delivery so future envelopes can carry per-locale strings without breaking existing ones: - `AiGameSchema` gains optional `titleLocalized`, `taglineLocalized`, `descriptionLocalized`: Record<Lang, string> partial maps. Optional → pre-G-35 envelopes still validate; the consumer falls back to top-level PL fields with `lang="pl"` so screen readers + browser auto-translate know the chunk is Polish. - `app/games/ai/[id]/page.tsx` consumer reads `game.titleLocalized?.[lang] ?? game.title` etc., per field. The `lang="pl"` attribute is conditionally set (only on PL fallback, not on real translations) so accessibility tools don't lie. Generator wiring (publish.ts envelope build → translate envelope via Haiku alongside the existing translateSpec flow) is the next slice — Sprint H. Until then non-pl players see the PL fallback exactly as they do today; this commit just ensures new envelopes can carry localized fields the day Sprint H lands. Validation: - pnpm typecheck → 0 errors Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2dfdfae commit 73edf39

2 files changed

Lines changed: 47 additions & 16 deletions

File tree

app/games/ai/[id]/page.tsx

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,45 @@ export default async function AiGamePage({
9393
</span>
9494
)}
9595
</div>
96-
{/* E-03 — AI games are generated PL-side then translated by
97-
Haiku into uk/cs/en. The header copy renders the locale
98-
already mapped on the server (see lib/ai-pipeline). When
99-
the active locale falls back to the PL master (translation
100-
still pending), the `lang="pl"` attribute hints screen
101-
readers + Chrome auto-translate that the chunk is Polish
102-
— no more mid-page lang mix without a marker. */}
103-
<h1 className="text-3xl font-semibold" lang="pl">
104-
{game.title}
105-
</h1>
106-
<p className="text-[var(--ink-muted)]" lang="pl">
107-
{game.tagline}
108-
</p>
109-
<p className="text-sm text-[var(--ink-muted)]" lang="pl">
110-
{game.description}
111-
</p>
96+
{/* E-03 + G-35 — AI envelopes are generated PL-side. Newer
97+
envelopes carry per-locale title/tagline/description in
98+
`*Localized` maps; older envelopes have only the PL
99+
canonical fields. Prefer the localized variant for the
100+
active lang; fall back to PL with `lang="pl"` so screen
101+
readers + Chrome auto-translate know the chunk is Polish.
102+
G-35 generator wires the localized fields next sprint;
103+
until then non-pl players see the PL fallback. */}
104+
{(() => {
105+
const titleText = game.titleLocalized?.[lang] ?? game.title;
106+
const taglineText = game.taglineLocalized?.[lang] ?? game.tagline;
107+
const descriptionText =
108+
game.descriptionLocalized?.[lang] ?? game.description;
109+
const titleIsPl = !game.titleLocalized?.[lang];
110+
const taglineIsPl = !game.taglineLocalized?.[lang];
111+
const descIsPl = !game.descriptionLocalized?.[lang];
112+
return (
113+
<>
114+
<h1
115+
className="text-3xl font-semibold"
116+
{...(titleIsPl ? { lang: "pl" } : {})}
117+
>
118+
{titleText}
119+
</h1>
120+
<p
121+
className="text-[var(--ink-muted)]"
122+
{...(taglineIsPl ? { lang: "pl" } : {})}
123+
>
124+
{taglineText}
125+
</p>
126+
<p
127+
className="text-sm text-[var(--ink-muted)]"
128+
{...(descIsPl ? { lang: "pl" } : {})}
129+
>
130+
{descriptionText}
131+
</p>
132+
</>
133+
);
134+
})()}
112135
</header>
113136

114137
{spec.kind === "quiz" && (

lib/ai-pipeline/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,14 @@ export const AiGameSchema = z.object({
336336
title: z.string().min(3).max(60),
337337
tagline: z.string().max(140),
338338
description: z.string().max(600),
339+
// G-35 — per-locale envelope strings. Optional so pre-G-35
340+
// envelopes keep validating; the consumer falls back to the
341+
// top-level PL fields when missing. Generator wires these in
342+
// alongside the per-locale spec translation; old envelopes
343+
// continue to render in PL with `lang="pl"` until they expire.
344+
titleLocalized: z.record(z.enum(LANGS), z.string().min(1).max(60)).optional(),
345+
taglineLocalized: z.record(z.enum(LANGS), z.string().max(140)).optional(),
346+
descriptionLocalized: z.record(z.enum(LANGS), z.string().max(600)).optional(),
339347
theme: z.string().max(80),
340348
source: z.string().max(200).optional(),
341349
buildingName: z.string().max(60),

0 commit comments

Comments
 (0)