fix(i18n): honor language toggle in rendered game data
a6f0a51 parent: f7c8008 modified
app/globals.css +3 -2 | @@ -31,8 +31,9 @@ main::before { content:""; position:absolute; inset:0; background-image:linear-g | ||
| 31 | 31 | .level-input { width:76px; background:#07171c; border:1px solid rgba(150,239,240,.2); color:var(--cyan); padding:13px 8px; font-size:16px; text-align:center; outline:none; } |
| 32 | 32 | .level-input:focus { border-color:var(--cyan); box-shadow:0 0 0 2px rgba(150,239,240,.08); } |
| 33 | 33 | .scale { display:flex;justify-content:space-between;color:#4c7476;font-size:13px; } |
| 34 | -.field-row { display:grid;grid-template-columns:1.45fr .55fr;gap:12px;margin-top:30px; } | |
| 35 | -.field-row label>span { display:block;font-size:14px;color:#6f9193;letter-spacing:.14em;margin-bottom:8px;text-transform:uppercase; } | |
| 34 | +.field-row { display:grid;grid-template-columns:1.25fr .75fr;gap:12px;margin-top:30px; } | |
| 35 | +.field-row label { display:flex;flex-direction:column; } | |
| 36 | +.field-row label>span { flex:1;font-size:14px;color:#6f9193;letter-spacing:.14em;margin-bottom:8px;text-transform:uppercase; } | |
| 36 | 37 | select { width:100%;appearance:none;color:#dce9e6;background:#07171c url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%2396eff0'/%3E%3C/svg%3E") no-repeat right 13px center;border:1px solid rgba(150,239,240,.2);padding:13px 34px 13px 13px;font-size:16px;outline:none; } |
| 37 | 38 | select:focus { border-color:var(--cyan);box-shadow:0 0 0 2px rgba(150,239,240,.08); } |
| 38 | 39 | .class-readout { display:grid;grid-template-columns:1fr 1fr;gap:22px;border-top:1px solid var(--line);margin-top:22px;padding-top:18px; } |
| @@ -31,8 +31,9 @@ main::before { content:""; position:absolute; inset:0; background-image:linear-g | |||
| 31 | .level-input { width:76px; background:#07171c; border:1px solid rgba(150,239,240,.2); color:var(--cyan); padding:13px 8px; font-size:16px; text-align:center; outline:none; } | 31 | .level-input { width:76px; background:#07171c; border:1px solid rgba(150,239,240,.2); color:var(--cyan); padding:13px 8px; font-size:16px; text-align:center; outline:none; } |
| 32 | .level-input:focus { border-color:var(--cyan); box-shadow:0 0 0 2px rgba(150,239,240,.08); } | 32 | .level-input:focus { border-color:var(--cyan); box-shadow:0 0 0 2px rgba(150,239,240,.08); } |
| 33 | .scale { display:flex;justify-content:space-between;color:#4c7476;font-size:13px; } | 33 | .scale { display:flex;justify-content:space-between;color:#4c7476;font-size:13px; } |
| 34 | -.field-row { display:grid;grid-template-columns:1.45fr .55fr;gap:12px;margin-top:30px; } | 34 | +.field-row { display:grid;grid-template-columns:1.25fr .75fr;gap:12px;margin-top:30px; } |
| 35 | -.field-row label>span { display:block;font-size:14px;color:#6f9193;letter-spacing:.14em;margin-bottom:8px;text-transform:uppercase; } | 35 | +.field-row label { display:flex;flex-direction:column; } |
| 36 | +.field-row label>span { flex:1;font-size:14px;color:#6f9193;letter-spacing:.14em;margin-bottom:8px;text-transform:uppercase; } | ||
| 36 | select { width:100%;appearance:none;color:#dce9e6;background:#07171c url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%2396eff0'/%3E%3C/svg%3E") no-repeat right 13px center;border:1px solid rgba(150,239,240,.2);padding:13px 34px 13px 13px;font-size:16px;outline:none; } | 37 | select { width:100%;appearance:none;color:#dce9e6;background:#07171c url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%2396eff0'/%3E%3C/svg%3E") no-repeat right 13px center;border:1px solid rgba(150,239,240,.2);padding:13px 34px 13px 13px;font-size:16px;outline:none; } |
| 37 | select:focus { border-color:var(--cyan);box-shadow:0 0 0 2px rgba(150,239,240,.08); } | 38 | select:focus { border-color:var(--cyan);box-shadow:0 0 0 2px rgba(150,239,240,.08); } |
| 38 | .class-readout { display:grid;grid-template-columns:1fr 1fr;gap:22px;border-top:1px solid var(--line);margin-top:22px;padding-top:18px; } | 39 | .class-readout { display:grid;grid-template-columns:1fr 1fr;gap:22px;border-top:1px solid var(--line);margin-top:22px;padding-top:18px; } |
modified
src/components/BuildCard.tsx +5 -5 | @@ -2,7 +2,7 @@ import { armsManufacturers } from "@/src/data/arms"; | ||
| 2 | 2 | import { buildStrategies } from "@/src/data/builds"; |
| 3 | 3 | import type { Copy } from "@/src/data/i18n"; |
| 4 | 4 | import { |
| 5 | - dual, | |
| 5 | + localized, | |
| 6 | 6 | recommendSkellAlternatives, |
| 7 | 7 | recommendSkellFrame, |
| 8 | 8 | recommendSkellLoadout, |
| @@ -49,12 +49,12 @@ export default function BuildCard({ | ||
| 49 | 49 | <header className="build-card-head"> |
| 50 | 50 | <span className="member-number">{String(index + 1).padStart(2, "0")}</span> |
| 51 | 51 | <div><h3>{member.name}</h3><p>{member.className} · LV {member.effectiveLevel} · RANK {member.rank}</p></div> |
| 52 | - <div className="role-tag"><small>{strategy.damage}</small><b>{dual(strategy.role, lang)}</b></div> | |
| 52 | + <div className="role-tag"><small>{strategy.damage}</small><b>{localized(strategy.role, lang)}</b></div> | |
| 53 | 53 | </header> |
| 54 | 54 | <div className="build-columns"> |
| 55 | 55 | <section className="loadout-block arts-block"> |
| 56 | 56 | <h4><span>A</span>{t.artsLoadout} · {member.arts.length}/8</h4> |
| 57 | - <p className="weapon-lock">{member.weapons.ranged} + {member.weapons.melee} · {dual(member.note, lang)}</p> | |
| 57 | + <p className="weapon-lock">{member.weapons.ranged} + {member.weapons.melee} · {localized(member.note, lang)}</p> | |
| 58 | 58 | {member.arts.length > 0 ? <ol> |
| 59 | 59 | {member.arts.slice(0, 8).map((art, artIndex) => ( |
| 60 | 60 | <li key={`${art.source.id}-${art.name}`}> |
| @@ -88,7 +88,7 @@ export default function BuildCard({ | ||
| 88 | 88 | </ol> |
| 89 | 89 | |
| 90 | 90 | <h5>{t.amRecommended}</h5> |
| 91 | - <p className="am-note"><b>{armsManufacturer.name}</b><span>{dual(armsManufacturer.specialty, lang)}</span></p> | |
| 91 | + <p className="am-note"><b>{armsManufacturer.name}</b><span>{localized(armsManufacturer.specialty, lang)}</span></p> | |
| 92 | 92 | |
| 93 | 93 | <h5>{t.planReasons}</h5> |
| 94 | 94 | <ul className="plan-reasons"> |
| @@ -102,7 +102,7 @@ export default function BuildCard({ | ||
| 102 | 102 | <div className="skell-frame"> |
| 103 | 103 | <small>FRAME</small> |
| 104 | 104 | <strong>{skell.frame}</strong> |
| 105 | - <span>{dual(strategy.skellRole, lang)}</span> | |
| 105 | + <span>{localized(strategy.skellRole, lang)}</span> | |
| 106 | 106 | </div> |
| 107 | 107 | <dl className="skell-facts"> |
| 108 | 108 | <div><dt>{t.requiredPilotLevel}</dt><dd>LV {skell.requiredLevel}</dd></div> |
| @@ -2,7 +2,7 @@ import { armsManufacturers } from "@/src/data/arms"; | |||
| 2 | import { buildStrategies } from "@/src/data/builds"; | 2 | import { buildStrategies } from "@/src/data/builds"; |
| 3 | import type { Copy } from "@/src/data/i18n"; | 3 | import type { Copy } from "@/src/data/i18n"; |
| 4 | import { | 4 | import { |
| 5 | - dual, | 5 | + localized, |
| 6 | recommendSkellAlternatives, | 6 | recommendSkellAlternatives, |
| 7 | recommendSkellFrame, | 7 | recommendSkellFrame, |
| 8 | recommendSkellLoadout, | 8 | recommendSkellLoadout, |
| @@ -49,12 +49,12 @@ export default function BuildCard({ | |||
| 49 | <header className="build-card-head"> | 49 | <header className="build-card-head"> |
| 50 | <span className="member-number">{String(index + 1).padStart(2, "0")}</span> | 50 | <span className="member-number">{String(index + 1).padStart(2, "0")}</span> |
| 51 | <div><h3>{member.name}</h3><p>{member.className} · LV {member.effectiveLevel} · RANK {member.rank}</p></div> | 51 | <div><h3>{member.name}</h3><p>{member.className} · LV {member.effectiveLevel} · RANK {member.rank}</p></div> |
| 52 | - <div className="role-tag"><small>{strategy.damage}</small><b>{dual(strategy.role, lang)}</b></div> | 52 | + <div className="role-tag"><small>{strategy.damage}</small><b>{localized(strategy.role, lang)}</b></div> |
| 53 | </header> | 53 | </header> |
| 54 | <div className="build-columns"> | 54 | <div className="build-columns"> |
| 55 | <section className="loadout-block arts-block"> | 55 | <section className="loadout-block arts-block"> |
| 56 | <h4><span>A</span>{t.artsLoadout} · {member.arts.length}/8</h4> | 56 | <h4><span>A</span>{t.artsLoadout} · {member.arts.length}/8</h4> |
| 57 | - <p className="weapon-lock">{member.weapons.ranged} + {member.weapons.melee} · {dual(member.note, lang)}</p> | 57 | + <p className="weapon-lock">{member.weapons.ranged} + {member.weapons.melee} · {localized(member.note, lang)}</p> |
| 58 | {member.arts.length > 0 ? <ol> | 58 | {member.arts.length > 0 ? <ol> |
| 59 | {member.arts.slice(0, 8).map((art, artIndex) => ( | 59 | {member.arts.slice(0, 8).map((art, artIndex) => ( |
| 60 | <li key={`${art.source.id}-${art.name}`}> | 60 | <li key={`${art.source.id}-${art.name}`}> |
| @@ -88,7 +88,7 @@ export default function BuildCard({ | |||
| 88 | </ol> | 88 | </ol> |
| 89 | 89 | ||
| 90 | <h5>{t.amRecommended}</h5> | 90 | <h5>{t.amRecommended}</h5> |
| 91 | - <p className="am-note"><b>{armsManufacturer.name}</b><span>{dual(armsManufacturer.specialty, lang)}</span></p> | 91 | + <p className="am-note"><b>{armsManufacturer.name}</b><span>{localized(armsManufacturer.specialty, lang)}</span></p> |
| 92 | 92 | ||
| 93 | <h5>{t.planReasons}</h5> | 93 | <h5>{t.planReasons}</h5> |
| 94 | <ul className="plan-reasons"> | 94 | <ul className="plan-reasons"> |
| @@ -102,7 +102,7 @@ export default function BuildCard({ | |||
| 102 | <div className="skell-frame"> | 102 | <div className="skell-frame"> |
| 103 | <small>FRAME</small> | 103 | <small>FRAME</small> |
| 104 | <strong>{skell.frame}</strong> | 104 | <strong>{skell.frame}</strong> |
| 105 | - <span>{dual(strategy.skellRole, lang)}</span> | 105 | + <span>{localized(strategy.skellRole, lang)}</span> |
| 106 | </div> | 106 | </div> |
| 107 | <dl className="skell-facts"> | 107 | <dl className="skell-facts"> |
| 108 | <div><dt>{t.requiredPilotLevel}</dt><dd>LV {skell.requiredLevel}</dd></div> | 108 | <div><dt>{t.requiredPilotLevel}</dt><dd>LV {skell.requiredLevel}</dd></div> |
modified
src/lib/engine.ts +7 -2 | @@ -102,9 +102,14 @@ function firstSourceUrl(citation: string): string { | ||
| 102 | 102 | } |
| 103 | 103 | |
| 104 | 104 | |
| 105 | -/** Renders a bilingual pair, primary language first. */ | |
| 105 | +/** Renders game-term names: English only in EN, bilingual (FR / EN) in FR. */ | |
| 106 | 106 | export function dual(pair: LocalizedText, lang: Lang): string { |
| 107 | - return lang === "en" ? `${pair[0]} / ${pair[1]}` : `${pair[1]} / ${pair[0]}`; | |
| 107 | + return lang === "en" ? pair[0] : `${pair[1]} / ${pair[0]}`; | |
| 108 | +} | |
| 109 | + | |
| 110 | +/** Renders a localized pair in the active language only — for descriptions. */ | |
| 111 | +export function localized(pair: LocalizedText, lang: Lang): string { | |
| 112 | + return lang === "en" ? pair[0] : pair[1]; | |
| 108 | 113 | } |
| 109 | 114 | |
| 110 | 115 | /** Walks the class tree from Drifter down to the selected class. */ |
| @@ -102,9 +102,14 @@ function firstSourceUrl(citation: string): string { | |||
| 102 | } | 102 | } |
| 103 | 103 | ||
| 104 | 104 | ||
| 105 | -/** Renders a bilingual pair, primary language first. */ | 105 | +/** Renders game-term names: English only in EN, bilingual (FR / EN) in FR. */ |
| 106 | export function dual(pair: LocalizedText, lang: Lang): string { | 106 | export function dual(pair: LocalizedText, lang: Lang): string { |
| 107 | - return lang === "en" ? `${pair[0]} / ${pair[1]}` : `${pair[1]} / ${pair[0]}`; | 107 | + return lang === "en" ? pair[0] : `${pair[1]} / ${pair[0]}`; |
| 108 | +} | ||
| 109 | + | ||
| 110 | +/** Renders a localized pair in the active language only — for descriptions. */ | ||
| 111 | +export function localized(pair: LocalizedText, lang: Lang): string { | ||
| 112 | + return lang === "en" ? pair[0] : pair[1]; | ||
| 108 | } | 113 | } |
| 109 | 114 | ||
| 110 | /** Walks the class tree from Drifter down to the selected class. */ | 115 | /** Walks the class tree from Drifter down to the selected class. */ |