anthodev/xeno-team-builderpublic Fork
⑂ a6f0a51
Commits
⬇ Clone ▾
git clone https://git.rickub.com/anthodev/xeno-team-builder.git
git clone ssh://git@rickub.com/anthodev/xeno-team-builder.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

fix(i18n): honor language toggle in rendered game data

Cedric ANTHONY committed 2026-08-04T00:27:42+02:00 Browse files
a6f0a51 parent: f7c8008
modified app/globals.css +3 -2
@@ -31,8 +31,9 @@ main::before { content:""; position:absolute; inset:0; background-image:linear-g
3131 .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; }
3232 .level-input:focus { border-color:var(--cyan); box-shadow:0 0 0 2px rgba(150,239,240,.08); }
3333 .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; }
3637 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; }
3738 select:focus { border-color:var(--cyan);box-shadow:0 0 0 2px rgba(150,239,240,.08); }
3839 .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";
22 import { buildStrategies } from "@/src/data/builds";
33 import type { Copy } from "@/src/data/i18n";
44 import {
5- dual,
5+ localized,
66 recommendSkellAlternatives,
77 recommendSkellFrame,
88 recommendSkellLoadout,
@@ -49,12 +49,12 @@ export default function BuildCard({
4949 <header className="build-card-head">
5050 <span className="member-number">{String(index + 1).padStart(2, "0")}</span>
5151 <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>
5353 </header>
5454 <div className="build-columns">
5555 <section className="loadout-block arts-block">
5656 <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>
5858 {member.arts.length > 0 ? <ol>
5959 {member.arts.slice(0, 8).map((art, artIndex) => (
6060 <li key={`${art.source.id}-${art.name}`}>
@@ -88,7 +88,7 @@ export default function BuildCard({
8888 </ol>
8989
9090 <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>
9292
9393 <h5>{t.planReasons}</h5>
9494 <ul className="plan-reasons">
@@ -102,7 +102,7 @@ export default function BuildCard({
102102 <div className="skell-frame">
103103 <small>FRAME</small>
104104 <strong>{skell.frame}</strong>
105- <span>{dual(strategy.skellRole, lang)}</span>
105+ <span>{localized(strategy.skellRole, lang)}</span>
106106 </div>
107107 <dl className="skell-facts">
108108 <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 {
102102 }
103103
104104
105-/** Renders a bilingual pair, primary language first. */
105+/** Renders game-term names: English only in EN, bilingual (FR / EN) in FR. */
106106 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];
108113 }
109114
110115 /** 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. */