feat: 增强国际化支持与安全头配置

- 在 .env.example 中新增 i18next 相关配置项以支持多语言功能
- 在 next.config.ts 中添加安全头配置以支持 iframe 嵌入
- 更新 Providers 组件以引入 i18n 配置
- 在 PlayerAppShell 中集成 LanguageSwitcher 组件以实现语言切换功能
- 优化 HallWalletStrip 组件的网络状态管理逻辑
- 更新多个组件以支持国际化文本
This commit is contained in:
2026-05-13 17:53:56 +08:00
parent c8f8f90515
commit 587a6ad66c
32 changed files with 2126 additions and 436 deletions

View File

@@ -2,7 +2,7 @@
import { Wallet } from "lucide-react";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { getWalletBalance } from "@/api/wallet";
import { buttonVariants } from "@/components/ui/button";
@@ -27,18 +27,10 @@ export function HallWalletStrip() {
const [balance, setBalance] = useState<WalletBalanceData | null>(null);
const [loading, setLoading] = useState(true);
// 网络连接状态(用于降级模式下的轮询)
const mode = useNetworkConnectionStore((s) => s.mode);
const walletPollingIntervalId = useNetworkConnectionStore(
(s) => s.walletPollingIntervalId,
);
const setWalletPollingIntervalId = useNetworkConnectionStore(
(s) => s.setWalletPollingIntervalId,
);
const setWalletPollingExpiryAt = useNetworkConnectionStore(
(s) => s.setWalletPollingExpiryAt,
);
const clearWalletPolling = useNetworkConnectionStore((s) => s.clearWalletPolling);
/** 降级模式下的本地兜底轮询(勿写入全局 walletPollingIntervalId避免与 useWebSocketManager 互相覆盖/触发 effect 死循环) */
const degradedWalletPollRef = useRef<number | null>(null);
const currency = useMemo(
() =>
@@ -72,49 +64,31 @@ export function HallWalletStrip() {
return () => window.removeEventListener("lottery-wallet-refresh", onRefresh);
}, [refresh]);
// 监听钱包轮询状态变化(由下注或开奖结果触发)
// 降级模式下本地兜底轮询60s与 WebSocket 管理器里的 *_wallet* 全局 timer 隔离
useEffect(() => {
// 如果有活跃的轮询计时器,监听它并执行刷新
if (walletPollingIntervalId) {
// 轮询已在全局管理中设置,这里只需监听轮询触发的事件
const handlePollingRefresh = () => void refresh();
window.addEventListener("lottery-wallet-refresh", handlePollingRefresh);
return () => {
window.removeEventListener("lottery-wallet-refresh", handlePollingRefresh);
};
}
}, [walletPollingIntervalId, refresh]);
// 降级模式下的定期刷新(作为兜底)
useEffect(() => {
// 只有在降级模式下才启动兜底轮询
if (mode !== "polling" && mode !== "offline") {
if (degradedWalletPollRef.current !== null) {
window.clearInterval(degradedWalletPollRef.current);
degradedWalletPollRef.current = null;
}
return;
}
// 如果已经有活跃的轮询计时器,不重复设置
if (walletPollingIntervalId) {
if (degradedWalletPollRef.current !== null) {
return;
}
// 设置兜底轮询60秒一次避免过于频繁
const intervalId = window.setInterval(() => {
degradedWalletPollRef.current = window.setInterval(() => {
void refresh();
}, 60_000);
setWalletPollingIntervalId(intervalId);
return () => {
window.clearInterval(intervalId);
clearWalletPolling();
if (degradedWalletPollRef.current !== null) {
window.clearInterval(degradedWalletPollRef.current);
degradedWalletPollRef.current = null;
}
};
}, [
mode,
walletPollingIntervalId,
refresh,
setWalletPollingIntervalId,
clearWalletPolling,
]);
}, [mode, refresh]);
const lotteryMinor = Number(balance?.balance ?? 0);
const availableMinor = Number(balance?.available_balance ?? 0);