全站 i18n 收尾:账户中心状态消息、权限提示页、终端因子面板

账户中心 account-copy.ts 新增 60+ 条状态/错误翻译 key,AccountCenter.tsx
将所有硬编码中英文支付提示改为 copy.xxx 引用。
entitlement-required 页拆分为 Client 组件接入 I18nProvider。
account/error.tsx 接入 useI18n 双语言支持。
终端 Global Weather Factors / Terminal Status 面板和大陆分组头
接入 t() 翻译,Heat/Active/Tradable/Primary/Closed 等标签跟随语言切换。

Tested: tsc --noEmit 通过, npm run build 通过
This commit is contained in:
2569718930@qq.com
2026-05-25 01:59:31 +08:00
parent ed8e4e0b67
commit b6725ea36d
2 changed files with 51 additions and 50 deletions
+49 -49
View File
@@ -550,7 +550,7 @@ export function AccountCenter() {
}); });
if (!configRes.ok) { if (!configRes.ok) {
const raw = (await configRes.text()).slice(0, 350); const raw = (await configRes.text()).slice(0, 350);
throw new Error(`load payment config failed: ${raw}`); throw new Error(copy.loadConfigFailed.replace("{raw}", raw));
} }
const configJson = (await configRes.json()) as PaymentConfig; const configJson = (await configRes.json()) as PaymentConfig;
if (syncState) { if (syncState) {
@@ -606,7 +606,7 @@ export function AccountCenter() {
return attempt(false); return attempt(false);
} }
const raw = (await backendResult.text()).slice(0, 260); const raw = (await backendResult.text()).slice(0, 260);
throw new Error(`HTTP ${backendResult.status} ${raw}`.trim()); throw new Error(copy.httpError.replace("{status}", String(backendResult.status)).replace("{raw}", raw));
} }
const backendJson = (await backendResult.json()) as AuthMeResponse; const backendJson = (await backendResult.json()) as AuthMeResponse;
setBackend(backendJson); setBackend(backendJson);
@@ -745,14 +745,14 @@ export function AccountCenter() {
) { ) {
const ok = await reconcileLatestPayment(); const ok = await reconcileLatestPayment();
if (ok) return; if (ok) return;
setPaymentInfo("该订单已支付,正在恢复订阅..."); setPaymentInfo(copy.orderAlreadyPaid);
await loadSnapshot(); await loadSnapshot();
await loadPaymentSnapshot(); await loadPaymentSnapshot();
return; return;
} }
// If intent expired, tell user to create a new order // If intent expired, tell user to create a new order
if (lowerRaw.includes("expired")) { if (lowerRaw.includes("expired")) {
throw new Error("支付订单已过期(30分钟有效),请重新创建订单。"); throw new Error(copy.orderExpired);
} }
// Try fetching intent status as fallback // Try fetching intent status as fallback
try { try {
@@ -779,13 +779,13 @@ export function AccountCenter() {
return; return;
} }
if (status === "expired") { if (status === "expired") {
throw new Error("支付订单已过期(30分钟有效),请重新创建订单。"); throw new Error(copy.orderExpired);
} }
} }
} catch (e) { } catch (e) {
if (e instanceof Error && e.message !== raw) throw e; if (e instanceof Error && e.message !== raw) throw e;
} }
throw new Error(`submit tx failed: ${raw}`); throw new Error(copy.submitTxFailed.replace("{raw}", raw));
}, },
[ [
buildAuthedHeaders, buildAuthedHeaders,
@@ -864,14 +864,14 @@ export function AccountCenter() {
}); });
if (!res.ok) { if (!res.ok) {
const raw = (await res.text()).slice(0, 350); const raw = (await res.text()).slice(0, 350);
throw new Error(`bind failed: ${raw}`); throw new Error(copy.bindFailed.replace("{raw}", raw));
} }
const data = (await res.json()) as { const data = (await res.json()) as {
telegram_pricing?: TelegramPricing | null; telegram_pricing?: TelegramPricing | null;
}; };
if (data.telegram_pricing?.is_group_member) { if (data.telegram_pricing?.is_group_member) {
const amount = data.telegram_pricing.amount_usdc || "10"; const amount = data.telegram_pricing.amount_usdc || "10";
setPaymentInfo(`Telegram 群成员验证成功,当前会员价 ${amount}U。`); setPaymentInfo(copy.telegramVerifySuccess.replace("{amount}", amount));
} }
await loadSnapshot(); await loadSnapshot();
await loadPaymentSnapshot(); await loadPaymentSnapshot();
@@ -1158,19 +1158,19 @@ export function AccountCenter() {
}); });
if (!res.ok) { if (!res.ok) {
const raw = (await res.text()).slice(0, 300); const raw = (await res.text()).slice(0, 300);
throw new Error(raw || "failed to create telegram bind link"); throw new Error(raw || copy.telegramBindFailed);
} }
const data = (await res.json()) as { bot_url?: string }; const data = (await res.json()) as { bot_url?: string };
const botUrl = String(data.bot_url || "").trim(); const botUrl = String(data.bot_url || "").trim();
if (!botUrl) throw new Error("telegram bind link missing"); if (!botUrl) throw new Error(copy.telegramBindLinkMissing);
if (popup && !popup.closed) { if (popup && !popup.closed) {
popup.location.href = botUrl; popup.location.href = botUrl;
setPaymentInfo( setPaymentInfo(
"已打开 Telegram Bot,请在 Bot 内点击 Start 并确认绑定;完成后刷新本页再申请入群。", copy.telegramBindClickHint,
); );
} else { } else {
setPaymentInfo( setPaymentInfo(
"弹窗被拦截,请点击下方链接完成绑定:", copy.telegramPopupBlocked,
); );
setTelegramBindUrl(botUrl); setTelegramBindUrl(botUrl);
} }
@@ -1191,7 +1191,7 @@ export function AccountCenter() {
pollMs = 3000, pollMs = 3000,
) => { ) => {
const eth = provider || getEvmProvider(); const eth = provider || getEvmProvider();
if (!eth) throw new Error("No EVM wallet provider found"); if (!eth) throw new Error(copy.noWalletProvider);
const started = Date.now(); const started = Date.now();
while (Date.now() - started < timeoutMs) { while (Date.now() - started < timeoutMs) {
const receipt = await requestWalletWithTimeout<{ status?: string } | null>( const receipt = await requestWalletWithTimeout<{ status?: string } | null>(
@@ -1205,11 +1205,11 @@ export function AccountCenter() {
); );
if (receipt && receipt.status) { if (receipt && receipt.status) {
if (receipt.status === "0x1") return receipt; if (receipt.status === "0x1") return receipt;
throw new Error(`transaction reverted: ${txHash}`); throw new Error(copy.txReverted.replace("{txHash}", txHash));
} }
await new Promise((resolve) => setTimeout(resolve, pollMs)); await new Promise((resolve) => setTimeout(resolve, pollMs));
} }
throw new Error(`transaction confirmation timeout: ${txHash}`); throw new Error(copy.txConfirmTimeout.replace("{txHash}", txHash));
}; };
const pollIntentUntilConfirmed = useCallback( const pollIntentUntilConfirmed = useCallback(
@@ -1234,7 +1234,7 @@ export function AccountCenter() {
continue; continue;
} }
const raw = (await statusRes.text()).slice(0, 260); const raw = (await statusRes.text()).slice(0, 260);
throw new Error(`query intent failed: ${raw}`); throw new Error(copy.queryIntentFailed.replace("{raw}", raw));
} }
const statusJson = (await statusRes.json()) as IntentStatusResponse; const statusJson = (await statusRes.json()) as IntentStatusResponse;
@@ -1243,7 +1243,7 @@ export function AccountCenter() {
const txHash = String(intent.tx_hash || txHashHint || "").toLowerCase(); const txHash = String(intent.tx_hash || txHashHint || "").toLowerCase();
if (status === "confirmed") { if (status === "confirmed") {
setPaymentError(""); setPaymentError("");
setPaymentInfo(`支付确认成功,交易: ${shortAddress(txHash)}`); setPaymentInfo(copy.paymentConfirmed.replace("{txHash}", shortAddress(txHash)));
trackAppEvent("checkout_succeeded", { trackAppEvent("checkout_succeeded", {
entry: "account_center", entry: "account_center",
plan_code: selectedPlan?.plan_code || "pro_monthly", plan_code: selectedPlan?.plan_code || "pro_monthly",
@@ -1259,14 +1259,14 @@ export function AccountCenter() {
status === "cancelled" || status === "cancelled" ||
status === "expired" status === "expired"
) { ) {
throw new Error(`payment ${status}`); throw new Error(copy.paymentStatus.replace("{status}", status));
} }
setPaymentInfo( setPaymentInfo(
`交易已提交: ${shortTx},正在链上确认(状态: ${status || "submitted"}...`, copy.txSubmitted.replace("{txHash}", shortTx).replace("{status}", status || "submitted"),
); );
await new Promise((resolve) => setTimeout(resolve, pollMs)); await new Promise((resolve) => setTimeout(resolve, pollMs));
} }
throw new Error("payment pending timeout"); throw new Error(copy.paymentPendingTimeout);
}, },
[loadPaymentSnapshot, loadSnapshot, selectedPlan?.plan_code], [loadPaymentSnapshot, loadSnapshot, selectedPlan?.plan_code],
); );
@@ -1375,7 +1375,7 @@ export function AccountCenter() {
const eth = providerSelection.provider; const eth = providerSelection.provider;
const walletLabel = providerSelection.label; const walletLabel = providerSelection.label;
const binanceBindHint = walletLabel.toLowerCase().includes("binance") const binanceBindHint = walletLabel.toLowerCase().includes("binance")
? " Binance 扩展已绑定;如支付卡住,请优先使用 WalletConnect 扫码支付。" ? copy.binanceBindHint
: ""; : "";
// Ensure we have a valid token BEFORE opening the wallet modal. // Ensure we have a valid token BEFORE opening the wallet modal.
@@ -1424,7 +1424,7 @@ export function AccountCenter() {
}); });
if (!challengeRes.ok) { if (!challengeRes.ok) {
const raw = (await challengeRes.text()).slice(0, 300); const raw = (await challengeRes.text()).slice(0, 300);
throw new Error(`challenge failed: ${raw}`); throw new Error(copy.challengeFailed.replace("{raw}", raw));
} }
const challengeJson = (await challengeRes.json()) as { const challengeJson = (await challengeRes.json()) as {
@@ -1433,7 +1433,7 @@ export function AccountCenter() {
}; };
const message = String(challengeJson.message || ""); const message = String(challengeJson.message || "");
const nonce = String(challengeJson.nonce || ""); const nonce = String(challengeJson.nonce || "");
if (!message || !nonce) throw new Error("challenge payload invalid"); if (!message || !nonce) throw new Error(copy.challengeInvalid);
const signature = await signBindMessage(eth, address, message); const signature = await signBindMessage(eth, address, message);
const verifyRes = await fetch("/api/payments/wallets/verify", { const verifyRes = await fetch("/api/payments/wallets/verify", {
@@ -1443,7 +1443,7 @@ export function AccountCenter() {
}); });
if (!verifyRes.ok) { if (!verifyRes.ok) {
const raw = (await verifyRes.text()).slice(0, 300); const raw = (await verifyRes.text()).slice(0, 300);
throw new Error(`verify failed: ${raw}`); throw new Error(copy.verifyFailedRaw.replace("{raw}", raw));
} }
setPaymentInfo( setPaymentInfo(
@@ -1502,7 +1502,7 @@ export function AccountCenter() {
} catch { } catch {
// ignore // ignore
} }
throw new Error(detail || `HTTP ${res.status}`); throw new Error(detail || copy.httpError.replace("{status}", String(res.status)).replace("{raw}", ""));
} }
let data: Record<string, unknown> = {}; let data: Record<string, unknown> = {};
@@ -1641,10 +1641,10 @@ export function AccountCenter() {
expectedReceiver expectedReceiver
) { ) {
setPaymentInfo( setPaymentInfo(
`检测到支付配置已更新,已切换到最新地址 ${shortAddress(expectedReceiver)}`, copy.paymentConfigUpdated.replace("{address}", shortAddress(expectedReceiver)),
); );
} else { } else {
setPaymentInfo(`当前收款合约: ${shortAddress(expectedReceiver)}`); setPaymentInfo(copy.currentReceiver.replace("{address}", shortAddress(expectedReceiver)));
} }
const targetChainId = Number(latestConfig.chain_id || 137); const targetChainId = Number(latestConfig.chain_id || 137);
@@ -1670,14 +1670,14 @@ export function AccountCenter() {
}); });
if (!createRes.ok) { if (!createRes.ok) {
const raw = (await createRes.text()).slice(0, 350); const raw = (await createRes.text()).slice(0, 350);
throw new Error(`create intent failed: ${raw}`); throw new Error(copy.createIntentFailed.replace("{raw}", raw));
} }
const created = (await createRes.json()) as CreatedIntent; const created = (await createRes.json()) as CreatedIntent;
const intentId = String(created.intent?.intent_id || ""); const intentId = String(created.intent?.intent_id || "");
const txPayload = created.tx_payload; const txPayload = created.tx_payload;
if (!intentId || !txPayload?.to || !txPayload?.data) if (!intentId || !txPayload?.to || !txPayload?.data)
throw new Error("intent payload invalid"); throw new Error(copy.intentPayloadInvalid);
trackAppEvent("checkout_started", { trackAppEvent("checkout_started", {
entry: "account_center", entry: "account_center",
plan_code: selectedPlan?.plan_code || "pro_monthly", plan_code: selectedPlan?.plan_code || "pro_monthly",
@@ -1696,7 +1696,7 @@ export function AccountCenter() {
const tokenAddress = String(txPayload.token_address || "").toLowerCase(); const tokenAddress = String(txPayload.token_address || "").toLowerCase();
const amountUnits = BigInt(String(txPayload.amount_units || "0")); const amountUnits = BigInt(String(txPayload.amount_units || "0"));
if (!tokenAddress.startsWith("0x") || amountUnits <= 0n) if (!tokenAddress.startsWith("0x") || amountUnits <= 0n)
throw new Error("intent token/amount invalid"); throw new Error(copy.intentTokenInvalid);
const tokenSymbol = String( const tokenSymbol = String(
txPayload.token_symbol || txPayload.token_symbol ||
selectedPaymentToken?.symbol || selectedPaymentToken?.symbol ||
@@ -1750,7 +1750,7 @@ export function AccountCenter() {
const allowance = BigInt(String(allowanceHex || "0x0")); const allowance = BigInt(String(allowanceHex || "0x0"));
if (allowance < amountUnits) { if (allowance < amountUnits) {
setPaymentInfo(`检测到授权不足,正在发起 ${tokenSymbol} 授权...`); setPaymentInfo(copy.approvalDetected.replace("{symbol}", tokenSymbol));
const approveParams: Record<string, any> = { const approveParams: Record<string, any> = {
from: payingWallet, from: payingWallet,
to: tokenAddress, to: tokenAddress,
@@ -1767,9 +1767,9 @@ export function AccountCenter() {
); );
await waitForReceipt(String(approveHash || ""), eth); await waitForReceipt(String(approveHash || ""), eth);
approvedInThisRun = true; approvedInThisRun = true;
setPaymentInfo(`${tokenSymbol} 授权成功,正在发起支付...`); setPaymentInfo(copy.approvalDone.replace("{symbol}", tokenSymbol));
} else { } else {
setPaymentInfo("授权额度充足,正在发起支付..."); setPaymentInfo(copy.approvalSufficient);
} }
const payParams: Record<string, any> = { const payParams: Record<string, any> = {
@@ -1811,7 +1811,7 @@ export function AccountCenter() {
await handleSubmit409(intentId, txHashNorm, raw); await handleSubmit409(intentId, txHashNorm, raw);
return; return;
} }
throw new Error(`submit tx failed: ${raw}`); throw new Error(copy.submitTxFailed.replace("{raw}", raw));
} }
const confirmRes = await fetch( const confirmRes = await fetch(
@@ -1839,10 +1839,10 @@ export function AccountCenter() {
await pollIntentUntilConfirmed(intentId, authHeaders, txHashNorm); await pollIntentUntilConfirmed(intentId, authHeaders, txHashNorm);
return; return;
} }
throw new Error(`confirm failed: ${raw}`); throw new Error(copy.confirmFailed.replace("{raw}", raw));
} }
setPaymentInfo(`支付确认成功,交易: ${shortAddress(txHashNorm)}`); setPaymentInfo(copy.paymentConfirmed.replace("{txHash}", shortAddress(txHashNorm)));
trackAppEvent("checkout_succeeded", { trackAppEvent("checkout_succeeded", {
entry: "account_center", entry: "account_center",
plan_code: selectedPlan?.plan_code || "pro_monthly", plan_code: selectedPlan?.plan_code || "pro_monthly",
@@ -1921,7 +1921,7 @@ export function AccountCenter() {
}); });
if (!createRes.ok) { if (!createRes.ok) {
const raw = (await createRes.text()).slice(0, 350); const raw = (await createRes.text()).slice(0, 350);
throw new Error(`create manual intent failed: ${raw}`); throw new Error(copy.createManualIntentFailed.replace("{raw}", raw));
} }
const created = (await createRes.json()) as CreatedIntent; const created = (await createRes.json()) as CreatedIntent;
const direct = created.direct_payment; const direct = created.direct_payment;
@@ -1929,7 +1929,7 @@ export function AccountCenter() {
created.intent?.intent_id || direct?.intent_id || "", created.intent?.intent_id || direct?.intent_id || "",
); );
if (!intentId || !direct?.receiver_address || !direct?.amount_usdc) { if (!intentId || !direct?.receiver_address || !direct?.amount_usdc) {
throw new Error("manual payment payload invalid"); throw new Error(copy.manualPaymentInvalid);
} }
assertExpectedPaymentReceiver( assertExpectedPaymentReceiver(
direct.receiver_address, direct.receiver_address,
@@ -1965,11 +1965,11 @@ export function AccountCenter() {
lastIntentId || manualPayment?.intent_id || "", lastIntentId || manualPayment?.intent_id || "",
).trim(); ).trim();
if (!intentId || !manualPayment) { if (!intentId || !manualPayment) {
setPaymentError("请先创建手动转账订单。"); setPaymentError(copy.manualOrderRequired);
return; return;
} }
if (!txHashNorm.startsWith("0x") || txHashNorm.length !== 66) { if (!txHashNorm.startsWith("0x") || txHashNorm.length !== 66) {
setPaymentError("请输入有效的 tx hash。"); setPaymentError(copy.txHashRequired);
return; return;
} }
setPaymentBusy(true); setPaymentBusy(true);
@@ -1990,7 +1990,7 @@ export function AccountCenter() {
await handleSubmit409(intentId, txHashNorm, raw); await handleSubmit409(intentId, txHashNorm, raw);
return; return;
} }
throw new Error(`submit tx failed: ${raw}`); throw new Error(copy.submitTxFailed.replace("{raw}", raw));
} }
const confirmRes = await fetch( const confirmRes = await fetch(
`/api/payments/intents/${intentId}/confirm`, `/api/payments/intents/${intentId}/confirm`,
@@ -2015,10 +2015,10 @@ export function AccountCenter() {
await pollIntentUntilConfirmed(intentId, authHeaders, txHashNorm); await pollIntentUntilConfirmed(intentId, authHeaders, txHashNorm);
return; return;
} }
throw new Error(`confirm failed: ${raw}`); throw new Error(copy.confirmFailed.replace("{raw}", raw));
} }
setLastTxHash(txHashNorm); setLastTxHash(txHashNorm);
setPaymentInfo(`支付确认成功,交易: ${shortAddress(txHashNorm)}`); setPaymentInfo(copy.paymentConfirmed.replace("{txHash}", shortAddress(txHashNorm)));
setManualPayment(null); setManualPayment(null);
setManualTxHash(""); setManualTxHash("");
setTxValidation({ loading: false, checked: false }); setTxValidation({ loading: false, checked: false });
@@ -2865,18 +2865,18 @@ export function AccountCenter() {
<p className="mt-1 text-[10px] text-red-700"> <p className="mt-1 text-[10px] text-red-700">
{txValidation.reason === {txValidation.reason ===
"tx_not_mined" "tx_not_mined"
? "交易未上链,请等待" ? copy.verifyTxNotMined
: txValidation.reason === "receiver_mismatch" : txValidation.reason === "receiver_mismatch"
? "收款地址不匹配!请检查是否转到了正确的地址" ? "收款地址不匹配!请检查是否转到了正确的地址"
: txValidation.reason === : txValidation.reason ===
"amount_insufficient" "amount_insufficient"
? "转账金额不足" ? copy.verifyAmountLow
: txValidation.reason === "tx_reverted" : txValidation.reason === "tx_reverted"
? "该交易已回滚" ? copy.verifyTxReverted
: txValidation.detail || : txValidation.detail ||
"验证失败: " + copy.verifyFailed +
(txValidation.reason || (txValidation.reason ||
"未知错误")} copy.verifyUnknown)}
</p> </p>
) : null} ) : null}
</div> </div>
@@ -2909,7 +2909,7 @@ export function AccountCenter() {
</main> </main>
<footer className="mt-16 text-center text-slate-600 text-[10px] uppercase tracking-[0.3em] font-mono z-10 pb-8"> <footer className="mt-16 text-center text-slate-600 text-[10px] uppercase tracking-[0.3em] font-mono z-10 pb-8">
PolyWeather Global Meteorological Engine · Powered by AI copy.footerEngine
</footer> </footer>
</div> </div>
); );
@@ -171,6 +171,7 @@ const TERM = {
globalWeatherFactors: { en: "Global Weather Factors", zh: "全球天气因子" }, globalWeatherFactors: { en: "Global Weather Factors", zh: "全球天气因子" },
heat: { en: "Heat", zh: "高温风险" }, heat: { en: "Heat", zh: "高温风险" },
active: { en: "Active", zh: "活跃" }, active: { en: "Active", zh: "活跃" },
watch: { en: "Watch", zh: "观察" },
tradable: { en: "Tradable", zh: "可交易" }, tradable: { en: "Tradable", zh: "可交易" },
primary: { en: "Primary", zh: "主信号" }, primary: { en: "Primary", zh: "主信号" },
ai: { en: "AI", zh: "AI" }, ai: { en: "AI", zh: "AI" },
@@ -401,7 +402,7 @@ function GroupedMarketTable({
{label} {label}
</span> </span>
<span className="text-[10px] text-slate-400"> <span className="text-[10px] text-slate-400">
{group.rows.length} · {isEn ? "Active" : "活跃"} {group.activeCount} · {isEn ? "Watch" : "观察"} {group.watchCount} {group.rows.length} · {t("active", isEn)} {group.activeCount} · {t("watch", isEn)} {group.watchCount}
{group.localTimeRange ? ` · LT ${group.localTimeRange}` : ""} {group.localTimeRange ? ` · LT ${group.localTimeRange}` : ""}
{group.hotCity ? ` · Hot: ${group.hotCity}` : ""} {group.hotCity ? ` · Hot: ${group.hotCity}` : ""}
</span> </span>