feat: 增强国际化支持与安全头配置
- 在 .env.example 中新增 i18next 相关配置项以支持多语言功能 - 在 next.config.ts 中添加安全头配置以支持 iframe 嵌入 - 更新 Providers 组件以引入 i18n 配置 - 在 PlayerAppShell 中集成 LanguageSwitcher 组件以实现语言切换功能 - 优化 HallWalletStrip 组件的网络状态管理逻辑 - 更新多个组件以支持国际化文本
This commit is contained in:
85
src/i18n/index.ts
Normal file
85
src/i18n/index.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import i18n from "i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
|
||||
import enCommon from "./locales/en/common.json";
|
||||
import enEntry from "./locales/en/entry.json";
|
||||
import enLayout from "./locales/en/layout.json";
|
||||
import neCommon from "./locales/ne/common.json";
|
||||
import neEntry from "./locales/ne/entry.json";
|
||||
import neLayout from "./locales/ne/layout.json";
|
||||
import zhCommon from "./locales/zh/common.json";
|
||||
import zhEntry from "./locales/zh/entry.json";
|
||||
import zhLayout from "./locales/zh/layout.json";
|
||||
|
||||
/** 对齐后端与产品:尼泊尔语 / 英语 / 中文(简体) */
|
||||
export const SUPPORTED_LANGUAGES = [
|
||||
{ code: "en" as const, flag: "🇺🇸" },
|
||||
{ code: "ne" as const, flag: "🇳🇵" },
|
||||
{ code: "zh" as const, flag: "🇨🇳" },
|
||||
];
|
||||
|
||||
export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number]["code"];
|
||||
|
||||
export const DEFAULT_LANGUAGE: AppLanguage = "en";
|
||||
|
||||
const namespaces = ["common", "entry", "layout"] as const;
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
common: enCommon,
|
||||
entry: enEntry,
|
||||
layout: enLayout,
|
||||
},
|
||||
ne: {
|
||||
common: neCommon,
|
||||
entry: neEntry,
|
||||
layout: neLayout,
|
||||
},
|
||||
zh: {
|
||||
common: zhCommon,
|
||||
entry: zhEntry,
|
||||
layout: zhLayout,
|
||||
},
|
||||
} satisfies Record<
|
||||
AppLanguage,
|
||||
Record<(typeof namespaces)[number], Record<string, unknown>>
|
||||
>;
|
||||
|
||||
export function normalizeLanguage(lang: string | undefined): AppLanguage {
|
||||
const base = lang?.split("-")[0]?.toLowerCase();
|
||||
if (base === "ne") return "ne";
|
||||
if (base === "zh") return "zh";
|
||||
return "en";
|
||||
}
|
||||
|
||||
if (!i18n.isInitialized) {
|
||||
void i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: DEFAULT_LANGUAGE,
|
||||
supportedLngs: ["en", "ne", "zh"],
|
||||
defaultNS: "common",
|
||||
ns: [...namespaces],
|
||||
/** zh-CN → zh,ne-NP → ne,未匹配时用 fallbackLng */
|
||||
load: "languageOnly",
|
||||
|
||||
detection: {
|
||||
order: ["localStorage", "navigator"],
|
||||
caches: ["localStorage"],
|
||||
lookupLocalStorage: "i18nextLng",
|
||||
},
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default i18n;
|
||||
21
src/i18n/locales/en/common.json
Normal file
21
src/i18n/locales/en/common.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"language": {
|
||||
"en": "English",
|
||||
"ne": "नेपाली",
|
||||
"zh": "中文"
|
||||
},
|
||||
"languageShort": {
|
||||
"en": "EN",
|
||||
"ne": "NE",
|
||||
"zh": "ZH"
|
||||
},
|
||||
"status": {
|
||||
"done": "Done",
|
||||
"inProgress": "In progress",
|
||||
"pending": "Pending",
|
||||
"failed": "Failed"
|
||||
},
|
||||
"errors": {
|
||||
"general": "General"
|
||||
}
|
||||
}
|
||||
65
src/i18n/locales/en/entry.json
Normal file
65
src/i18n/locales/en/entry.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"header": {
|
||||
"backgroundAlt": "Header background"
|
||||
},
|
||||
"loading": {
|
||||
"title": "Loading lottery hall",
|
||||
"progress": "Progress"
|
||||
},
|
||||
"steps": {
|
||||
"token": {
|
||||
"title": "Checking token",
|
||||
"description": "Verifying your session securely."
|
||||
},
|
||||
"account": {
|
||||
"title": "Creating account",
|
||||
"description": "Setting up your account."
|
||||
},
|
||||
"hall": {
|
||||
"title": "Loading lottery hall",
|
||||
"description": "Preparing the lottery hall."
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"initializing": "Initializing…",
|
||||
"retrying": "Retrying…",
|
||||
"verifying": "Verifying session…",
|
||||
"connectingHall": "Connecting to lottery hall…",
|
||||
"ready": "Ready",
|
||||
"retryProgress": "Retrying ({{current}}/{{total}})…"
|
||||
},
|
||||
"failure": {
|
||||
"title": "Authorization failed",
|
||||
"subtitle": "We couldn’t authorize your session. Please try again.",
|
||||
"detailsTitle": "Failure details",
|
||||
"table": {
|
||||
"no": "No.",
|
||||
"check": "Check",
|
||||
"reason": "Reason"
|
||||
},
|
||||
"reenter": "Re-enter"
|
||||
},
|
||||
"success": {
|
||||
"title": "Authorization successful!",
|
||||
"subtitle": "Your session has been verified successfully.",
|
||||
"doneLabel": "Done",
|
||||
"continue": "Continue to lottery hall"
|
||||
},
|
||||
"footer": {
|
||||
"secure": "Secure. Trusted. Authorized access."
|
||||
},
|
||||
"errors": {
|
||||
"noToken": "No authorization token found",
|
||||
"noTokenDetail": "Please return to the main site and try again.",
|
||||
"authFailed": "Authorization failed",
|
||||
"unknown": "Unknown error",
|
||||
"network": "Network error occurred",
|
||||
"networkDetail": "Please check your internet connection and try again.",
|
||||
"maxRetries": "Unable to connect after multiple attempts",
|
||||
"maxRetriesDetail": "The server may be temporarily unavailable.",
|
||||
"tryLater": "Please try again later",
|
||||
"http401": "Token is invalid or expired",
|
||||
"http403": "Account has been blocked",
|
||||
"http404": "Account not found in the system"
|
||||
}
|
||||
}
|
||||
5
src/i18n/locales/en/layout.json
Normal file
5
src/i18n/locales/en/layout.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"brand": {
|
||||
"title": "Lottery"
|
||||
}
|
||||
}
|
||||
21
src/i18n/locales/ne/common.json
Normal file
21
src/i18n/locales/ne/common.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"language": {
|
||||
"en": "English",
|
||||
"ne": "नेपाली",
|
||||
"zh": "中文"
|
||||
},
|
||||
"languageShort": {
|
||||
"en": "EN",
|
||||
"ne": "NE",
|
||||
"zh": "ZH"
|
||||
},
|
||||
"status": {
|
||||
"done": "पूरा",
|
||||
"inProgress": "जारी",
|
||||
"pending": "बाँकी",
|
||||
"failed": "असफल"
|
||||
},
|
||||
"errors": {
|
||||
"general": "सामान्य"
|
||||
}
|
||||
}
|
||||
65
src/i18n/locales/ne/entry.json
Normal file
65
src/i18n/locales/ne/entry.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"header": {
|
||||
"backgroundAlt": "हेडर पृष्ठभूमि"
|
||||
},
|
||||
"loading": {
|
||||
"title": "लटरी हल लोड हुँदैछ",
|
||||
"progress": "प्रगति"
|
||||
},
|
||||
"steps": {
|
||||
"token": {
|
||||
"title": "टोकन जाँच गर्दै",
|
||||
"description": "तपाईंको सत्र सुरक्षित रूपमा प्रमाणित गर्दै।"
|
||||
},
|
||||
"account": {
|
||||
"title": "खाता सिर्जना गर्दै",
|
||||
"description": "तपाईंको खाता सेट अप गर्दै।"
|
||||
},
|
||||
"hall": {
|
||||
"title": "लटरी हल लोड गर्दै",
|
||||
"description": "लटरी हल तयार गर्दै।"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"initializing": "सुरु गर्दै…",
|
||||
"retrying": "पुन: प्रयास गर्दै…",
|
||||
"verifying": "सत्र प्रमाणित गर्दै…",
|
||||
"connectingHall": "लटरी हलमा जोडिँदै…",
|
||||
"ready": "तयार",
|
||||
"retryProgress": "पुन: प्रयास ({{current}}/{{total}})…"
|
||||
},
|
||||
"failure": {
|
||||
"title": "प्राधिकरण असफल",
|
||||
"subtitle": "हामीले तपाईंको सत्र प्राधिकृत गर्न सकेनौं। कृपया फेरि प्रयास गर्नुहोस्।",
|
||||
"detailsTitle": "विफलताको विवरण",
|
||||
"table": {
|
||||
"no": "क्र.सं.",
|
||||
"check": "जाँच",
|
||||
"reason": "कारण"
|
||||
},
|
||||
"reenter": "पुन: प्रवेश"
|
||||
},
|
||||
"success": {
|
||||
"title": "प्राधिकरण सफल!",
|
||||
"subtitle": "तपाईंको सत्र सफलतापूर्वक प्रमाणित भयो।",
|
||||
"doneLabel": "पूरा",
|
||||
"continue": "लटरी हलमा जानुहोस्"
|
||||
},
|
||||
"footer": {
|
||||
"secure": "सुरक्षित। विश्वसनीय। अधिकृत पहुँच।"
|
||||
},
|
||||
"errors": {
|
||||
"noToken": "कुनै प्राधिकरण टोकन फेला परेन",
|
||||
"noTokenDetail": "कृपया मुख्य साइटमा फर्कनुहोस् र फेरि प्रयास गर्नुहोस्।",
|
||||
"authFailed": "प्राधिकरण असफल",
|
||||
"unknown": "अज्ञात त्रुटि",
|
||||
"network": "नेटवर्क त्रुटि भयो",
|
||||
"networkDetail": "कृपया आफ्नो इन्टरनेट जाँच गर्नुहोस् र फेरि प्रयास गर्नुहोस्।",
|
||||
"maxRetries": "धेरै पटक पछि पनि जडान हुन सकेन",
|
||||
"maxRetriesDetail": "सर्भर अस्थायी रूपमा अनुपलब्ध हुन सक्छ।",
|
||||
"tryLater": "कृपया पछि प्रयास गर्नुहोस्",
|
||||
"http401": "टोकन अमान्य वा म्याद सकियो",
|
||||
"http403": "खाता रोकिएको छ",
|
||||
"http404": "प्रणालीमा खाता फेला परेन"
|
||||
}
|
||||
}
|
||||
5
src/i18n/locales/ne/layout.json
Normal file
5
src/i18n/locales/ne/layout.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"brand": {
|
||||
"title": "लटरी"
|
||||
}
|
||||
}
|
||||
21
src/i18n/locales/zh/common.json
Normal file
21
src/i18n/locales/zh/common.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"language": {
|
||||
"en": "English",
|
||||
"ne": "नेपाली",
|
||||
"zh": "中文"
|
||||
},
|
||||
"languageShort": {
|
||||
"en": "EN",
|
||||
"ne": "NE",
|
||||
"zh": "ZH"
|
||||
},
|
||||
"status": {
|
||||
"done": "完成",
|
||||
"inProgress": "进行中",
|
||||
"pending": "待处理",
|
||||
"failed": "失败"
|
||||
},
|
||||
"errors": {
|
||||
"general": "通用"
|
||||
}
|
||||
}
|
||||
65
src/i18n/locales/zh/entry.json
Normal file
65
src/i18n/locales/zh/entry.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"header": {
|
||||
"backgroundAlt": "页头背景"
|
||||
},
|
||||
"loading": {
|
||||
"title": "正在进入彩票大厅",
|
||||
"progress": "进度"
|
||||
},
|
||||
"steps": {
|
||||
"token": {
|
||||
"title": "校验登录凭证",
|
||||
"description": "正在安全验证您的会话。"
|
||||
},
|
||||
"account": {
|
||||
"title": "创建/同步账号",
|
||||
"description": "正在为您设置账号。"
|
||||
},
|
||||
"hall": {
|
||||
"title": "加载彩票大厅",
|
||||
"description": "正在准备彩票大厅。"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"initializing": "正在初始化…",
|
||||
"retrying": "正在重试…",
|
||||
"verifying": "正在校验会话…",
|
||||
"connectingHall": "正在连接彩票大厅…",
|
||||
"ready": "就绪",
|
||||
"retryProgress": "正在重试({{current}}/{{total}})…"
|
||||
},
|
||||
"failure": {
|
||||
"title": "授权失败",
|
||||
"subtitle": "无法完成授权,请重试。",
|
||||
"detailsTitle": "失败详情",
|
||||
"table": {
|
||||
"no": "序号",
|
||||
"check": "检查项",
|
||||
"reason": "原因"
|
||||
},
|
||||
"reenter": "重新进入"
|
||||
},
|
||||
"success": {
|
||||
"title": "授权成功!",
|
||||
"subtitle": "您的会话已通过验证。",
|
||||
"doneLabel": "完成",
|
||||
"continue": "进入彩票大厅"
|
||||
},
|
||||
"footer": {
|
||||
"secure": "安全 · 可信 · 授权访问"
|
||||
},
|
||||
"errors": {
|
||||
"noToken": "未发现授权令牌",
|
||||
"noTokenDetail": "请返回主站后重试。",
|
||||
"authFailed": "授权失败",
|
||||
"unknown": "未知错误",
|
||||
"network": "网络异常",
|
||||
"networkDetail": "请检查网络连接后重试。",
|
||||
"maxRetries": "多次重试仍无法连接",
|
||||
"maxRetriesDetail": "服务器可能暂时不可用。",
|
||||
"tryLater": "请稍后再试",
|
||||
"http401": "令牌无效或已过期",
|
||||
"http403": "账号已被封禁",
|
||||
"http404": "系统中未找到该账号"
|
||||
}
|
||||
}
|
||||
5
src/i18n/locales/zh/layout.json
Normal file
5
src/i18n/locales/zh/layout.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"brand": {
|
||||
"title": "彩票"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user