feedback5 WI: UI strings through t() with an English switch on the title screen and in 设置; pilot screens converted
public/i18n/en.json: Chinese msgid -> English, seeded from PR #70 (@YuriRestia, credited) for every Chinese literal of the current code (≈830 entries; 966 of the 1 036 literals left for phase 2 already translated), completed by hand in the official EN terms. public/js/ui/lang.js: boot language (?lang=en|zh, then localStorage sp.pref.lang, else Chinese; the browser language is not consulted), switchLang, useLang, LangToggle; main.js renders after initLang and re-renders in place on a switch, and translates server texts (m.toast / m.ticker frames, room.closed reasons, error codes via ui/toasts.js describeError). Converted: title screen (English title, wordmark hidden), lobby, room, settings (语言 row, On/Off, quality, hotkeys), the shell (crash screen, toasts) and DifficultyTag. tools/i18n.mjs: extract (msgids + untranslated literals per file), check (msgids missing from en.json), codemod (html`` text / attributes / strings / template literals -> t(), nested strings, N_ for module level, i18n-ignore lines), seed (fill en.json from a msgid map).
This commit is contained in:
1 parent
e596478bef
commit
a12a539e4d
13 files changed
+2102
-171
No files matched your search
+23
-16
@@ -1,4 +1,5 @@
|
||||
// Title screen: season-style backdrop, big title 卫戍协议:盟约, remembered nickname, 开始.
|
||||
// Title screen: season-style backdrop, big title 卫戍协议:盟约, remembered nickname, 开始, the language switch
|
||||
// (中文 | English, ui/lang.js; in English the big title is the English one and the small wordmark above it hides).
|
||||
//
|
||||
// Pressing 开始 validates the nickname (1..NAME_MAX_LEN chars, no control characters), stores it,
|
||||
// marks this tab as "entered" (so reloads skip the title) and hands the name to net.js, which
|
||||
@@ -17,6 +18,8 @@ import { net, identity } from '../net.js';
|
||||
import { store, useStore, shallowEqual } from '../store.js';
|
||||
import { data, useData } from '../data.js';
|
||||
import { FullscreenButton, detectFeatures } from '../ui/device.js';
|
||||
import { LangToggle, useLang } from '../ui/lang.js';
|
||||
import { t, N_ } from '../../../shared/i18n.js';
|
||||
|
||||
// Same character classes as server/net.js sanitizeName (control, zero-width, bidi, BOM), so a name
|
||||
// the client accepts is never rejected by the server's hello validation.
|
||||
@@ -174,14 +177,15 @@ function Ridges() {
|
||||
}
|
||||
|
||||
const STATUS_TEXT = {
|
||||
idle: '准备连接', connecting: '正在连接服务器', connected: '已连接服务器', handshaking: '正在验证身份',
|
||||
online: '已连接服务器', reconnecting: '连接中断,正在重连', closed: '连接已关闭',
|
||||
idle: N_('准备连接'), connecting: N_('正在连接服务器'), connected: N_('已连接服务器'), handshaking: N_('正在验证身份'),
|
||||
online: N_('已连接服务器'), reconnecting: N_('连接中断,正在重连'), closed: N_('连接已关闭'),
|
||||
};
|
||||
|
||||
/** Title screen component. */
|
||||
export function TitleScreen() {
|
||||
const conn = useStore((s) => s.connection, shallowEqual);
|
||||
const pendingJoin = useStore((s) => s.ui.pendingJoin);
|
||||
const lang = useLang();
|
||||
const [name, setName] = useState(() => store.get().me.name || identity.loadName() || '');
|
||||
const assetsSettled = useData('assets');
|
||||
const assets = data.get('assets');
|
||||
@@ -199,7 +203,7 @@ export function TitleScreen() {
|
||||
|
||||
const valid = isValidName(name);
|
||||
const start = () => {
|
||||
if (!valid) { toast('请输入博士代号', 'warn'); return; }
|
||||
if (!valid) { toast(t('请输入博士代号'), 'warn'); return; }
|
||||
enterSession(name);
|
||||
};
|
||||
|
||||
@@ -232,39 +236,42 @@ export function TitleScreen() {
|
||||
<div><${MicroLabel} tone="mint">RHODES ISLAND // SIMULATION SERVICE<//><br /><${MicroLabel}>TACTICAL CO-OP NODE · 02<//></div>
|
||||
</div>
|
||||
<div class="title-corner title-corner--tr">
|
||||
<${MicroLabel} tone="hi">TARGET POINT<//><br /><${MicroLabel}>STRONGHOLD PROTOCOL<//>
|
||||
<div>
|
||||
<${LangToggle} class="title-lang" />
|
||||
<${MicroLabel} tone="hi">TARGET POINT<//><br /><${MicroLabel}>STRONGHOLD PROTOCOL<//>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="title-main">
|
||||
<${Emblem} />
|
||||
<div class="title-en">
|
||||
${lang === 'en' ? null : html`<div class="title-en">
|
||||
<span class="title-en__a">STRONGHOLD PROTOCOL</span>
|
||||
<span class="title-en__b">ALLIANCE</span>
|
||||
</div>
|
||||
<h1 class="title-cn">卫戍协议<span class="title-cn__colon">:</span><em>盟约</em></h1>
|
||||
<p class="title-tag">调配资金与干员,与同伴协同布防,抵御多波次进攻,直至击败敌方领袖。</p>
|
||||
</div>`}
|
||||
<h1 class=${`title-cn${lang === 'en' ? ' title-cn--latin' : ''}`}>${t('卫戍协议')}<span class="title-cn__colon">${lang === 'en' ? ': ' : ':'}</span><em>${t('盟约')}</em></h1>
|
||||
<p class="title-tag">${t('调配资金与干员,与同伴协同布防,抵御多波次进攻,直至击败敌方领袖。')}</p>
|
||||
|
||||
<div class="title-login">
|
||||
${pendingJoin ? html`<div class="title-invite">
|
||||
<${Icon} name="key" />
|
||||
<span>收到同盟邀请</span><b class="num">${pendingJoin}</b><span class="t-lo">· 输入代号后将自动加入</span>
|
||||
<span>${t('收到同盟邀请')}</span><b class="num">${pendingJoin}</b><span class="t-lo">${t('· 输入代号后将自动加入')}</span>
|
||||
</div>` : null}
|
||||
<${TextField} label="博士代号" micro="CALLSIGN" size="lg" icon="user" value=${name} maxLength=${NAME_MAX_LEN}
|
||||
placeholder="输入你的代号(最多 ${NAME_MAX_LEN} 字)" autoFocus=${!touchUi}
|
||||
<${TextField} label=${t('博士代号')} micro="CALLSIGN" size="lg" icon="user" value=${name} maxLength=${NAME_MAX_LEN}
|
||||
placeholder=${t('输入你的代号(最多 {NAME_MAX_LEN} 字)', { NAME_MAX_LEN })} autoFocus=${!touchUi}
|
||||
onInput=${setName} onEnter=${start} />
|
||||
<${Button} variant="primary" size="xl" block=${true} iconRight="chevrons" disabled=${!valid} onClick=${start}>开始<//>
|
||||
<${Button} variant="primary" size="xl" block=${true} iconRight="chevrons" disabled=${!valid} onClick=${start}>${t('开始')}<//>
|
||||
<div class="title-conn">
|
||||
<span class=${`status-dot ${dotClass}`}></span>
|
||||
<span>${STATUS_TEXT[conn.status] || conn.status}</span>
|
||||
<span>${STATUS_TEXT[conn.status] ? t(STATUS_TEXT[conn.status]) : conn.status}</span>
|
||||
${conn.status === 'online' ? html`<${PingPill} ms=${conn.ping} />` : null}
|
||||
<${GuideButton} class="title-guide" />
|
||||
<${GuideButton} class="title-guide" label=${t('玩法说明')} />
|
||||
<${FullscreenButton} class="title-fs" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="title-foot">
|
||||
<span>非官方同人复刻 · 游戏素材版权归 上海鹰角网络 / Yostar 所有</span>
|
||||
<span>${t('非官方同人复刻 · 游戏素材版权归 上海鹰角网络 / Yostar 所有')}</span>
|
||||
<${MicroLabel}>v${APP_VERSION} · WEB SIMULATION<//>
|
||||
</footer>
|
||||
</div>`;
|
||||
|
||||
Reference in new issue
Block a user