diff --git a/public/i18n/en.json b/public/i18n/en.json index 6fc41f91..35303830 100644 --- a/public/i18n/en.json +++ b/public/i18n/en.json @@ -952,5 +952,63 @@ "下一局生效": "Applies next match", "这份数据来自更新的版本(v{v}),请先更新游戏": "This data comes from a newer version (v{v}). Update the game first.", "这不是自选编队的数据": "This is not Custom Squad data", - "里面没有自选编队的数据": "It holds no Custom Squad data" + "里面没有自选编队的数据": "It holds no Custom Squad data", + "本局禁用{names}盟约,此策略效果可能无法发挥": "{names} disabled this match: this Strategy may not take full effect", + "【{name}】": "[{name}]", + "每位博士有 {n} 秒": "each Doctor has {n} {n|second|seconds}", + "{skip};{clock}": "{skip}; {clock}", + "{skip};{clock},超时将自动选择当前选中的「{autoName}」": "{skip}; {clock}. When time runs out, the highlighted \"{autoName}\" is picked for you", + "{skip};{clock},超时将自动选择「{autoName}」": "{skip}; {clock}. When time runs out, \"{autoName}\" is picked for you", + "已选择「{name}」": "Selected \"{name}\"", + "跳过": "Skip", + "第 {r} 回合 · 最终攻势": "Round {r} · Final Assault", + "道具无法出售。确定要销毁「{name}」吗?": "Items cannot be sold. Destroy \"{name}\"?", + "展开盟约": "Expand Alliances", + "收起盟约": "Collapse Alliances", + "正在查看 {name} 的阵地(只读)": "Viewing {name}'s field (read-only)", + "观战中 · 点击左侧成员头像切换查看": "Spectating · tap a member's avatar on the left to switch", + "未持有(干员持有):由替补干员上场": "Not owned (Operator Roster): its stand-in takes the field", + "未持有": "Not owned", + "{name} 调配": "{name} Loadout", + "干员持有中标记为未持有:此棋子由替补干员 {name} 上场,技能与模组固定(补位干员技能不可更改);这里的调配在改回「持有」后生效": "Marked as not owned in the Operator Roster: this chess takes the field as its stand-in {name}, with a fixed skill and module (a stand-in's skill cannot be changed); the loadout here applies once it is set back to \"Owned\"", + "干员持有中标记为未持有:此棋子由替补干员上场,技能与模组固定(补位干员技能不可更改);这里的调配在改回「持有」后生效": "Marked as not owned in the Operator Roster: this chess takes the field as its stand-in, with a fixed skill and module (a stand-in's skill cannot be changed); the loadout here applies once it is set back to \"Owned\"", + "全部持有": "Own All", + "将 {nNotOwned} 名未持有的干员恢复为持有(由本人上场)?": "Set {nNotOwned} not-owned {nNotOwned|Operator|Operators} back to owned (fielded as themselves)?", + "将 {nChanged} 名干员的技能与模组恢复为默认配置?": "Reset the skills and modules of {nChanged} {nChanged|Operator|Operators} to the default loadout?", + "文件过大,请选择「导出」下载的干员持有文件": "The file is too large. Choose an Operator Roster file downloaded with \"Export\".", + "已导入:{applied} 名干员未持有(另有 {dropped} 项无效,未导入)": "Imported: {applied} {applied|Operator|Operators} not owned ({dropped} invalid {dropped|entry|entries} skipped)", + "已导入:{applied} 名干员未持有": "Imported: {applied} {applied|Operator|Operators} not owned", + "已导入:全部持有": "Imported: all owned", + "已导入 {applied} 名干员(另有 {dropped} 项未导入)": "Imported {applied} {applied|Operator|Operators} ({dropped} {dropped|entry|entries} not imported)", + "已导入 {applied} 名干员的调配": "Imported the loadout of {applied} {applied|Operator|Operators}", + "干员持有是局外设置:本局按开局时的设置进行,修改将在下一局生效": "The Operator Roster is set outside matches: this match keeps the roster it started with; changes apply from the next match", + "局外设置,下一局生效 · 联机时只影响你自己的棋子 · 默认全部持有": "Set outside matches, applies from the next match · in co-op it only affects your own chess · everything is owned by default", + "标记未持有的干员:由官方指定的替补干员上场": "Mark the Operators you do not own: their official stand-ins take the field", + "干员持有": "Operator Roster", + "{nNotOwned} 名未持有": "{nNotOwned} not owned", + "导出干员持有(可复制或下载)": "Export the Operator Roster (copy or download)", + "导入干员持有(粘贴或选择文件)": "Import an Operator Roster (paste or choose a file)", + "导出干员持有": "Export Operator Roster", + "导入干员持有": "Import Operator Roster", + "共 {n} 名干员未持有。复制或下载这份数据,即可在别的设备或浏览器上导入。": "{n} {count|Operator is|Operators are} not owned. Copy or download this data to import it on another device or browser.", + "把导出的干员持有数据粘贴到下方,或点「选择文件」。导入会{overwrite}当前的干员持有设置。": "Paste exported Operator Roster data below, or click \"Choose File\". Importing will {overwrite} the current Operator Roster.", + "共 {n} 名干员已调整。复制或下载这份数据,即可在别的设备或浏览器上导入。": "{n} {count|Operator has|Operators have} a custom loadout. Copy or download this data to import it on another device or browser.", + "导入会{overwrite}当前的 {nChanged} 名干员调配。": "Importing will {overwrite} the current loadout of {nChanged} {nChanged|Operator|Operators}.", + "在此粘贴导出的干员持有内容…": "Paste the exported Operator Roster here…", + "调整干员携带的技能与模组 · 干员持有": "Change the skills and modules your Operators carry · Operator Roster", + "{n} 名干员已调整": "{n} {n|Operator|Operators} customized", + "{off} 名干员未持有,由替补干员上场": "{off} {off|Operator|Operators} not owned, fielded as {off|its|their} stand-in", + "{off} 名干员未持有(由替补干员上场)": "{off} {off|Operator|Operators} not owned (fielded as {off|its|their} stand-in)", + "{name}:持有": "{name}: owned", + "{name}:未持有,由 {standIn} 上场": "{name}: not owned, {standIn} takes the field", + "替补干员": "the stand-in", + "点击标记为未持有:由 {standIn} 上场": "Click to mark as not owned: {standIn} takes the field", + "点击恢复为持有": "Click to set back to owned", + "无替补数据": "No stand-in data", + "持有": "Owned", + "仅看未持有": "Not owned only", + "全部持有:没有由替补干员上场的棋子": "Everything is owned: no chess takes the field as a stand-in", + "没有可下掉的干员": "No Operators can be marked", + "{name}({note})": "{name} ({note})", + "本局耗时 {n} 分钟": "Duration: {n} {mins|minute|minutes}" } diff --git a/public/js/screens/bandDraft.js b/public/js/screens/bandDraft.js index 76a84f2f..0f0b3a03 100644 --- a/public/js/screens/bandDraft.js +++ b/public/js/screens/bandDraft.js @@ -29,18 +29,19 @@ import { useStore } from '../store.js'; import { data } from '../data.js'; import { audio } from '../audio.js'; import { modeOffBonds, bandOffBonds, bandOffLine } from '../ui/gameLogic.js'; +import { t, tParts, tName } from '../../../shared/i18n.js'; const cx = (...p) => p.flat().filter(Boolean).join(' '); /** 本局禁用 on a strategy card whose bonds `names` the mode switches off (bandOffBonds); nothing otherwise. */ export function BandOffTag({ names = [] }) { - return names.length ? html`本局禁用` : null; + return names.length ? html`${t('本局禁用')}` : null; } /** The detail pane's note for such a strategy: "本局禁用【拉特兰】盟约,此策略效果可能无法发挥" (the bond names struck through). */ export function BandOffNote({ names = [] }) { if (!names.length) return null; - return html`
<${Icon} name="info" />本局禁用${names.map((n, i) => html`【${n}】`)}盟约,此策略效果可能无法发挥
<${Icon} name="info" />${tParts('本局禁用{names}盟约,此策略效果可能无法发挥', { names: names.map((n, i) => html`${tParts('【{name}】', { name: html`${n}` })}`) })}
${draftTip({ timed, turnSeconds: turnLen, autoName: myPick ? null : autoName, selected: autoId === sel })}
` : null} -选择一个策略查看详情
`} + ` : html`${t('选择一个策略查看详情')}
`}<${Icon} name="check" />已选择「${gd.band(myPick)?.name || ''}」${!solo && !draft.done ? ',等待其他博士' : ''}
` - : selTaken ? html`<${Icon} name="close" />队友已选,请选择其他策略
` - : !myTurn ? html`<${Icon} name="hourglass" />${turnName ? `${turnName} 正在决策…` : '等待轮到你'}
` : null} + ${myPick ? html`<${Icon} name="check" />${t('已选择「{name}」', { name: gd.band(myPick)?.name || '' })}${!solo && !draft.done ? t(',等待其他博士') : ''}
` + : selTaken ? html`<${Icon} name="close" />${t('队友已选,请选择其他策略')}
` + : !myTurn ? html`<${Icon} name="hourglass" />${turnName ? t('{turnName} 正在决策…', { turnName }) : t('等待轮到你')}
` : null}本局没有特训敌人
`} +${t('本局没有特训敌人')}
`}${pub ? '正在载入模拟数据…' : '正在进入模拟…'}
+${pub ? t('正在载入模拟数据…') : t('正在进入模拟…')}
同盟已返回等待室
- <${Button} variant="primary" size="lg" icon="chevronLeft" onClick=${() => store.set({ match: emptyMatch() })}>返回同盟/> +${t('同盟已返回等待室')}
+ <${Button} variant="primary" size="lg" icon="chevronLeft" onClick=${() => store.set({ match: emptyMatch() })}>${t('返回同盟')}/>作战已暂停,计时与敌人行动均已停止
+${t('作战已暂停,计时与敌人行动均已停止')}
不装备模组:精锐干员以基础属性、特性与天赋作战。
- ${traitBase?.desc ? html`${t('不装备模组:精锐干员以基础属性、特性与天赋作战。')}
+ ${traitBase?.desc ? html`${pv.elite ? '数值含所选模组;' : golden ? '普通干员没有模组,所选模组在「精锐」中生效;' : ''}不含技能发动、装备、盟约等局内加成
+${pv.elite ? t('数值含所选模组;') : golden ? t('普通干员没有模组,所选模组在「精锐」中生效;') : ''}${t('不含技能发动、装备、盟约等局内加成')}
`; } @@ -283,19 +283,19 @@ function Detail({ m, chess, golden, entries, onChange, onReset, locked, notOwned const [statLevel, setStatLevel] = useState('elite'); // 局内数值: the 精锐 shows the chosen module's effect const bodyRef = useRef(null); useEffect(() => { if (bodyRef.current) bodyRef.current.scrollTop = 0; }, [chess?.chessId]); - if (!chess) return html``; + if (!chess) return html``; const opt = chessOptions(chess, golden); const choice = effectiveChoice(entries, chess, golden); const modOpt = opt.moduleOptions.find((x) => x.id === choice.module) || null; const lv = (c) => c?.status?.skillLevel ?? '—'; - return html`<${Icon} name="info" />干员持有中标记为未持有:此棋子由替补干员${standInName(chess) ? ` ${standInName(chess)} ` : ''}上场,技能与模组固定(补位干员技能不可更改);这里的调配在改回「持有」后生效
` : null} - ${locked ? html`<${Icon} name="info" />本局的调配已锁定,修改将在下一局生效
` : null} + ${notOwned ? html`<${Icon} name="info" />${standInName(chess) ? t('干员持有中标记为未持有:此棋子由替补干员 {name} 上场,技能与模组固定(补位干员技能不可更改);这里的调配在改回「持有」后生效', { name: standInName(chess) }) + : t('干员持有中标记为未持有:此棋子由替补干员上场,技能与模组固定(补位干员技能不可更改);这里的调配在改回「持有」后生效')}
` : null} + ${locked ? html`<${Icon} name="info" />${t('本局的调配已锁定,修改将在下一局生效')}
` : null}<${Icon} name="info" />${diyText}
` : tab === 'ownership' ? html`<${Icon} name="info" />${ownText}
` - : html`<${Icon} name="info" />${locked ? '本局的调配已锁定(确认本局信息后无法修改),修改将在下一局生效' : fromText}
`} - ${!ready ? html`<${Icon} name="info" />${locked ? t('本局的调配已锁定(确认本局信息后无法修改),修改将在下一局生效') : fromText}
`} + ${!ready ? html`没有符合条件的干员
`} + entries=${st.entries} selected=${c.chessId === selId} onPick=${pick} notOwned=${(st.notOwned || []).includes(c.chessId)} />`) : html`${t('没有符合条件的干员')}
`}${ioDiy ? (io.mode === 'export' ? t('共 {n} 个自选名额已选择。复制或下载这份数据,即可在别的设备或浏览器上导入。', { n: nDiy }) : t('把导出的自选编队数据粘贴到下方,或点「选择文件」。导入会覆盖当前的自选编队。')) : ioOwn ? (io.mode === 'export' - ? html`共 ${nNotOwned} 名干员未持有。复制或下载这份数据,即可在别的设备或浏览器上导入。` - : html`把导出的干员持有数据粘贴到下方,或点「选择文件」。导入会覆盖当前的干员持有设置。`) + ? html`${tParts('共 {n} 名干员未持有。复制或下载这份数据,即可在别的设备或浏览器上导入。', { n: html`${nNotOwned}`, count: nNotOwned })}` + : html`${tParts('把导出的干员持有数据粘贴到下方,或点「选择文件」。导入会{overwrite}当前的干员持有设置。', { overwrite: html`${t('覆盖')}` })}`) : io.mode === 'export' - ? html`共 ${nChanged} 名干员已调整。复制或下载这份数据,即可在别的设备或浏览器上导入。` - : html`把导出的内容粘贴到下方,或点「选择文件」。${nChanged ? html`导入会覆盖当前的 ${nChanged} 名干员调配。` : null}`}
+ ? html`${tParts('共 {n} 名干员已调整。复制或下载这份数据,即可在别的设备或浏览器上导入。', { n: html`${nChanged}`, count: nChanged })}` + : html`${t('把导出的内容粘贴到下方,或点「选择文件」。')}${nChanged ? html`${tParts('导入会{overwrite}当前的 {nChanged} 名干员调配。', { overwrite: html`${t('覆盖')}`, nChanged })}` : null}`} />` : null} @@ -675,7 +678,7 @@ export function badgeCount(entries, getChess) { * Entry button (lobby / room / briefing). * @param {{ from: 'lobby'|'room'|'briefing', size?: string, variant?: string, class?: string, label?: string }} props */ -export function LoadoutButton({ from, size = 'md', variant = 'secondary', class: cls, label = '干员调配' }) { +export function LoadoutButton({ from, size = 'md', variant = 'secondary', class: cls, label = t('干员调配') }) { useData('local'); // the official preset icon (re-render once the local-art manifest arrives) const entries = useStore((s) => s.entries, Object.is, loadoutStore); const notOwned = useStore((s) => s.notOwned, Object.is, loadoutStore); @@ -686,11 +689,11 @@ export function LoadoutButton({ from, size = 'md', variant = 'secondary', class: // 0.2.0 自选编队: how many DIY slots the player filled const nDiy = diyCount(diy); return html``; } diff --git a/public/js/screens/ownership.js b/public/js/screens/ownership.js index 4a997b95..579b4b2d 100644 --- a/public/js/screens/ownership.js +++ b/public/js/screens/ownership.js @@ -16,7 +16,7 @@ import { chessAvatarUrl } from '../ui/assetUrls.js'; import { data } from '../data.js'; import { PROF_NAME } from '../ui/loadoutModel.js'; import { ownershipRoster, rosterByTier, isOwned, standInSummary } from '../ui/ownershipModel.js'; -import { t } from '../../../shared/i18n.js'; +import { t, tParts } from '../../../shared/i18n.js'; const cx = (...p) => p.flat().filter(Boolean).join(' '); const ROMAN = ['', 'I', 'II', 'III', 'IV', 'V', 'VI']; @@ -31,15 +31,15 @@ export function OwnCard({ m, chess, backups, owned, onToggle }) { const si = standInSummary(chess, backups); const skill = si?.skill; const slot = skill && Number.isInteger(skill.index) ? `S${skill.index + 1}` : ''; - const label = `${chess.name}:${owned ? '持有' : `未持有,由 ${si?.name || '替补干员'} 上场`}`; + const label = owned ? t('{name}:持有', { name: chess.name }) : t('{name}:未持有,由 {standIn} 上场', { name: chess.name, standIn: si?.name || t('替补干员') }); return html``; } @@ -67,15 +67,15 @@ export function OwnershipPanel({ m, roster, notOwned, onToggle }) {${offOnly ? '全部持有:没有由替补干员上场的棋子' : '没有可下掉的干员'}
`} +${offOnly ? t('全部持有:没有由替补干员上场的棋子') : t('没有可下掉的干员')}
`}${r.victory ? '成功卫戍 · 敌方领袖已被击败' : '防线已被突破'}
+${r.victory ? t('成功卫戍 · 敌方领袖已被击败') : t('防线已被突破')}
本局耗时 ${mins} 分钟
` : null} + ${mins ? html`${tParts('本局耗时 {n} 分钟', { n: html`${mins}`, mins })}
` : null}暂无结算数据
`} + : html`${t('暂无结算数据')}
`}