feat(i18n): 管理端与玩家端三语支持(中/英/马来语)

- 管理后台 adminT 文案库、结算与代理端页面、表单校验
- 玩家端 vue-i18n 补全首页/公告/串关与 ms 文案
- Element Plus ms 语言包与共享 locale 工具
This commit is contained in:
2026-06-03 15:05:36 +08:00
parent 80adc0e928
commit cbfa18d1d3
63 changed files with 3081 additions and 1038 deletions

View File

@@ -0,0 +1,489 @@
import { adminPagesEn, adminPagesZh } from './admin-pages';
import { adminPagesMs } from './admin-pages-ms';
/** 管理后台:中文 + 英文 + 马来语 */
export type AdminLocale = 'zh-CN' | 'en-US' | 'ms-MY';
export const ADMIN_LOCALES: {
code: AdminLocale;
label: string;
flag: string;
}[] = [
{ code: 'zh-CN', label: '中文', flag: '🇨🇳' },
{ code: 'en-US', label: 'English', flag: '🇺🇸' },
{ code: 'ms-MY', label: 'Bahasa Melayu', flag: '🇲🇾' },
];
const zh: Record<string, string> = {
'login.title': '管理后台',
'login.username': '用户名',
'login.password': '密码',
'login.submit': '登录',
'login.username_ph': '请输入用户名',
'login.password_ph': '请输入密码',
'login.err_captcha': '验证码错误,请重试',
'login.err_failed': '登录失败,请检查账号与密码',
'login.err_quick': '快速登录失败',
'login.quick_label': '快速登录(调试)',
'login.quick_admin': '管理员',
'login.quick_agent': '一级代理',
'login.captcha_ph': '验证码',
'login.captcha_refresh': '点击刷新',
'nav.dashboard': '控制台',
'nav.users': '玩家管理',
'nav.agents': '代理管理',
'nav.matches': '赛事管理',
'nav.bets': '注单管理',
'nav.cashback': '返水管理',
'nav.audit': '操作日志',
'nav.players': '直属玩家',
'nav.subAgents': '下级代理',
'nav.myBets': '注单查询',
'role.admin': '系统管理员',
'role.agent': '代理账号',
'logout': '退出',
'lang': '语言',
'portal.admin': '平台后台',
'portal.agent': '代理后台',
'common.all': '全部',
'common.search': '查询',
'common.reset': '重置',
'common.edit': '编辑',
'common.delete': '删除',
'common.cancel': '取消',
'common.confirm': '确定',
'common.status': '状态',
'common.type': '类型',
'common.keyword': '关键词',
'common.actions': '操作',
'common.loading': '加载中…',
'common.no_data': '暂无数据',
'common.yesterday': '昨日',
'common.vs_yesterday': '较昨日',
'common.people': '人',
'common.bets_unit': '单',
'common.matches_unit': '场赛事',
'common.frozen': '冻结',
'common.used': '已用',
'common.platform_direct': '平台直属',
'common.updated_at': '更新于',
'dash.title': '控制台',
'dash.desc': '平台整体运行概况',
'dash.board_title': '整体概览',
'dash.board_hint': '一屏查看经营趋势与平台分布',
'dash.kpi_bet_count': '今日投注笔数',
'dash.kpi_stake': '今日投注额',
'dash.kpi_payout': '今日派彩',
'dash.kpi_ggr': '今日毛利',
'dash.kpi_users': '玩家 / 代理',
'dash.kpi_new_players': '今日新增 {n} 人',
'dash.kpi_pending': '待结算',
'dash.kpi_pending_sub': '{bets} 单 · {matches} 场赛事',
'dash.kpi_wallet': '玩家余额',
'dash.kpi_credit': '代理授信',
'dash.trend_caption': '近 7 日经营趋势(金额折线 + 注单柱)',
'dash.chart_stake': '投注额',
'dash.chart_payout': '派彩',
'dash.chart_ggr': '毛利',
'dash.chart_bet_count': '注单笔数',
'dash.axis_amount': '金额',
'dash.axis_count': '笔数',
'dash.count_suffix': '笔',
'dash.chart_tooltip': '{b}{c}{d}%',
'dash.pie_empty': '暂无',
'dash.pie_matches': '赛事',
'dash.pie_bets': '今日注单',
'dash.pie_users': '用户',
'dash.match_draft': '草稿',
'dash.match_published': '已发布',
'dash.match_closed': '已封盘',
'dash.match_pending_settle': '待结算',
'dash.match_settled': '已结算',
'dash.user_active': '正常玩家',
'dash.user_suspended': '停用',
'dash.user_direct': '直属',
'dash.user_agents': '代理',
'page.users.title': '玩家管理',
'page.users.desc': '创建玩家、查看余额与投注概况,支持上分与状态管理',
'page.agents.title': '代理管理',
'page.agents.desc': '创建一级代理、调整授信额度、查看直属玩家与额度占用',
'page.matches.title': '赛事管理',
'page.matches.desc': '草稿可编辑、删除;已发布可改开赛时间与热门',
'page.bets.title': '注单管理',
'page.bets.desc': '筛选、分页查看全平台注单,支持详情与投注项',
'page.cashback.title': '返水管理',
'page.cashback.desc': '按周期生成返水并发放',
'page.audit.title': '操作日志',
'page.audit.desc': '记录所有管理员操作行为',
'page.settlement.title': '赛事结算',
'page.agent_dash.title': '代理概览',
'page.agent_dash.desc': '实时数据总览',
'page.agent_players.title': '直属玩家',
'page.agent_players.desc': '管理你名下的直属玩家',
'page.agent_sub.title': '下级代理',
'page.agent_sub.desc': '仅一级代理可见',
'page.agent_bets.title': '注单查询',
'page.agent_bets.desc': '下级玩家的全部投注记录',
'agent.credit_limit': '授信额度',
'agent.credit_total': '总额度',
'agent.credit_used': '已用额度',
'agent.credit_occupied': '已占用',
'agent.direct_players': '直属玩家',
'agent.player_count': '玩家数',
'agent.today_stake': '今日投注',
'agent.currency_cny': '人民币',
'bet.status.PENDING': '待结算',
'bet.status.WON': '已赢',
'bet.status.LOST': '已输',
'bet.status.VOID': '作废',
'bet.status.REFUNDED': '已退款',
'bet.type.SINGLE': '单关',
'bet.type.PARLAY': '串关',
'bet.settlement.PENDING': '待结算',
'bet.settlement.SETTLED': '已结算',
'bet.settlement.VOID': '已作废',
'bet.result.WON': '赢',
'bet.result.LOST': '输',
'bet.result.VOID': '走水',
'bet.result.PUSH': '走盘',
'bet.result.HALF_WON': '半赢',
'bet.result.HALF_LOST': '半输',
'match.status.DRAFT': '草稿',
'match.status.PUBLISHED': '已发布',
'match.status.CLOSED': '已封盘',
'match.status.SETTLED': '已结算',
...adminPagesZh,
};
const en: Record<string, string> = {
'login.title': 'Admin Console',
'login.username': 'Username',
'login.password': 'Password',
'login.submit': 'Sign in',
'login.username_ph': 'Enter username',
'login.password_ph': 'Enter password',
'login.err_captcha': 'Captcha incorrect, try again',
'login.err_failed': 'Sign-in failed. Check username and password',
'login.err_quick': 'Quick sign-in failed',
'login.quick_label': 'Quick sign-in (debug)',
'login.quick_admin': 'Admin',
'login.quick_agent': 'Tier-1 agent',
'login.captcha_ph': 'Captcha',
'login.captcha_refresh': 'Click to refresh',
'nav.dashboard': 'Dashboard',
'nav.users': 'Players',
'nav.agents': 'Agents',
'nav.matches': 'Matches',
'nav.bets': 'Bets',
'nav.cashback': 'Cashback',
'nav.audit': 'Audit Log',
'nav.players': 'My Players',
'nav.subAgents': 'Sub-Agents',
'nav.myBets': 'Bet Search',
'role.admin': 'Administrator',
'role.agent': 'Agent',
'logout': 'Logout',
'lang': 'Language',
'portal.admin': 'Platform Admin',
'portal.agent': 'Agent Portal',
'common.all': 'All',
'common.search': 'Search',
'common.reset': 'Reset',
'common.edit': 'Edit',
'common.delete': 'Delete',
'common.cancel': 'Cancel',
'common.confirm': 'OK',
'common.status': 'Status',
'common.type': 'Type',
'common.keyword': 'Keyword',
'common.actions': 'Actions',
'common.loading': 'Loading…',
'common.no_data': 'No data',
'common.yesterday': 'Yesterday',
'common.vs_yesterday': 'vs yesterday',
'common.people': 'users',
'common.bets_unit': 'bets',
'common.matches_unit': 'matches',
'common.frozen': 'Frozen',
'common.used': 'Used',
'common.platform_direct': 'Platform direct',
'common.updated_at': 'Updated',
'dash.title': 'Dashboard',
'dash.desc': 'Platform overview',
'dash.board_title': 'Overview',
'dash.board_hint': 'Trends and distribution at a glance',
'dash.kpi_bet_count': "Today's bet count",
'dash.kpi_stake': "Today's stake",
'dash.kpi_payout': "Today's payout",
'dash.kpi_ggr': "Today's GGR",
'dash.kpi_users': 'Players / Agents',
'dash.kpi_new_players': '{n} new today',
'dash.kpi_pending': 'Pending settlement',
'dash.kpi_pending_sub': '{bets} bets · {matches} matches',
'dash.kpi_wallet': 'Player balance',
'dash.kpi_credit': 'Agent credit',
'dash.trend_caption': 'Last 7 days (amount lines + bet bars)',
'dash.chart_stake': 'Stake',
'dash.chart_payout': 'Payout',
'dash.chart_ggr': 'GGR',
'dash.chart_bet_count': 'Bet count',
'dash.axis_amount': 'Amount',
'dash.axis_count': 'Count',
'dash.count_suffix': 'bets',
'dash.chart_tooltip': '{b}: {c} ({d}%)',
'dash.pie_empty': 'N/A',
'dash.pie_matches': 'Matches',
'dash.pie_bets': "Today's bets",
'dash.pie_users': 'Users',
'dash.match_draft': 'Draft',
'dash.match_published': 'Published',
'dash.match_closed': 'Closed',
'dash.match_pending_settle': 'Pending settlement',
'dash.match_settled': 'Settled',
'dash.user_active': 'Active players',
'dash.user_suspended': 'Suspended',
'dash.user_direct': 'Direct',
'dash.user_agents': 'Agents',
'page.users.title': 'Players',
'page.users.desc': 'Create players, balances, stakes, top-ups, and status',
'page.agents.title': 'Agents',
'page.agents.desc': 'Tier-1 agents, credit limits, players, and usage',
'page.matches.title': 'Matches',
'page.matches.desc': 'Edit/delete drafts; adjust kickoff and featured when published',
'page.bets.title': 'Bets',
'page.bets.desc': 'Filter and paginate all bets with leg details',
'page.cashback.title': 'Cashback',
'page.cashback.desc': 'Generate and issue cashback by period',
'page.audit.title': 'Audit Log',
'page.audit.desc': 'Administrator action history',
'page.settlement.title': 'Settlement',
'page.agent_dash.title': 'Agent overview',
'page.agent_dash.desc': 'Live summary',
'page.agent_players.title': 'My players',
'page.agent_players.desc': 'Players under your account',
'page.agent_sub.title': 'Sub-agents',
'page.agent_sub.desc': 'Tier-1 agents only',
'page.agent_bets.title': 'Bet search',
'page.agent_bets.desc': 'All bets from downstream players',
'agent.credit_limit': 'Credit limit',
'agent.credit_total': 'Total limit',
'agent.credit_used': 'Used credit',
'agent.credit_occupied': 'In use',
'agent.direct_players': 'Direct players',
'agent.player_count': 'Players',
'agent.today_stake': "Today's stake",
'agent.currency_cny': 'CNY',
'bet.status.PENDING': 'Pending',
'bet.status.WON': 'Won',
'bet.status.LOST': 'Lost',
'bet.status.VOID': 'Void',
'bet.status.REFUNDED': 'Refunded',
'bet.type.SINGLE': 'Single',
'bet.type.PARLAY': 'Parlay',
'bet.settlement.PENDING': 'Pending',
'bet.settlement.SETTLED': 'Settled',
'bet.settlement.VOID': 'Void',
'bet.result.WON': 'Won',
'bet.result.LOST': 'Lost',
'bet.result.VOID': 'Void',
'bet.result.PUSH': 'Push',
'bet.result.HALF_WON': 'Half won',
'bet.result.HALF_LOST': 'Half lost',
'match.status.DRAFT': 'Draft',
'match.status.PUBLISHED': 'Published',
'match.status.CLOSED': 'Closed',
'match.status.SETTLED': 'Settled',
...adminPagesEn,
};
const ms: Record<string, string> = {
'login.title': 'Konsol Admin',
'login.username': 'Nama pengguna',
'login.password': 'Kata laluan',
'login.submit': 'Log masuk',
'login.username_ph': 'Masukkan nama pengguna',
'login.password_ph': 'Masukkan kata laluan',
'login.err_captcha': 'Captcha salah, cuba lagi',
'login.err_failed': 'Log masuk gagal. Semak nama pengguna dan kata laluan',
'login.err_quick': 'Log masuk pantas gagal',
'login.quick_label': 'Log masuk pantas (debug)',
'login.quick_admin': 'Admin',
'login.quick_agent': 'Ejen peringkat 1',
'login.captcha_ph': 'Captcha',
'login.captcha_refresh': 'Klik untuk muat semula',
'nav.dashboard': 'Papan pemuka',
'nav.users': 'Pemain',
'nav.agents': 'Ejen',
'nav.matches': 'Perlawanan',
'nav.bets': 'Pertaruhan',
'nav.cashback': 'Rebat',
'nav.audit': 'Log audit',
'nav.players': 'Pemain saya',
'nav.subAgents': 'Sub-ejen',
'nav.myBets': 'Carian pertaruhan',
'role.admin': 'Pentadbir',
'role.agent': 'Ejen',
'logout': 'Log keluar',
'lang': 'Bahasa',
'portal.admin': 'Admin Platform',
'portal.agent': 'Portal Ejen',
'common.all': 'Semua',
'common.search': 'Cari',
'common.reset': 'Set semula',
'common.edit': 'Edit',
'common.delete': 'Padam',
'common.cancel': 'Batal',
'common.confirm': 'OK',
'common.status': 'Status',
'common.type': 'Jenis',
'common.keyword': 'Kata kunci',
'common.actions': 'Tindakan',
'common.loading': 'Memuatkan…',
'common.no_data': 'Tiada data',
'common.yesterday': 'Semalam',
'common.vs_yesterday': 'berbanding semalam',
'common.people': 'pengguna',
'common.bets_unit': 'pertaruhan',
'common.matches_unit': 'perlawanan',
'common.frozen': 'Dibekukan',
'common.used': 'Digunakan',
'common.platform_direct': 'Terus platform',
'common.updated_at': 'Dikemas kini',
'dash.title': 'Papan pemuka',
'dash.desc': 'Gambaran keseluruhan platform',
'dash.board_title': 'Gambaran',
'dash.board_hint': 'Trend dan taburan sepintas lalu',
'dash.kpi_bet_count': 'Bilangan pertaruhan hari ini',
'dash.kpi_stake': 'Stake hari ini',
'dash.kpi_payout': 'Bayaran hari ini',
'dash.kpi_ggr': 'GGR hari ini',
'dash.kpi_users': 'Pemain / Ejen',
'dash.kpi_new_players': '{n} baharu hari ini',
'dash.kpi_pending': 'Menunggu penyelesaian',
'dash.kpi_pending_sub': '{bets} pertaruhan · {matches} perlawanan',
'dash.kpi_wallet': 'Baki pemain',
'dash.kpi_credit': 'Kredit ejen',
'dash.trend_caption': '7 hari lepas (garis jumlah + palang pertaruhan)',
'dash.chart_stake': 'Stake',
'dash.chart_payout': 'Bayaran',
'dash.chart_ggr': 'GGR',
'dash.chart_bet_count': 'Bilangan pertaruhan',
'dash.axis_amount': 'Jumlah',
'dash.axis_count': 'Kiraan',
'dash.count_suffix': 'pertaruhan',
'dash.chart_tooltip': '{b}: {c} ({d}%)',
'dash.pie_empty': 'Tiada',
'dash.pie_matches': 'Perlawanan',
'dash.pie_bets': 'Pertaruhan hari ini',
'dash.pie_users': 'Pengguna',
'dash.match_draft': 'Draf',
'dash.match_published': 'Diterbitkan',
'dash.match_closed': 'Ditutup',
'dash.match_pending_settle': 'Menunggu penyelesaian',
'dash.match_settled': 'Diselesaikan',
'dash.user_active': 'Pemain aktif',
'dash.user_suspended': 'Digantung',
'dash.user_direct': 'Terus',
'dash.user_agents': 'Ejen',
'page.users.title': 'Pemain',
'page.users.desc': 'Cipta pemain, baki, stake, tambah baki dan status',
'page.agents.title': 'Ejen',
'page.agents.desc': 'Ejen peringkat 1, had kredit, pemain dan penggunaan',
'page.matches.title': 'Perlawanan',
'page.matches.desc': 'Edit/padam draf; laraskan masa mula dan pilihan utama bila diterbitkan',
'page.bets.title': 'Pertaruhan',
'page.bets.desc': 'Tapis dan halaman semua pertaruhan dengan butiran pilihan',
'page.cashback.title': 'Rebat',
'page.cashback.desc': 'Jana dan keluarkan rebat mengikut tempoh',
'page.audit.title': 'Log audit',
'page.audit.desc': 'Sejarah tindakan pentadbir',
'page.settlement.title': 'Penyelesaian',
'page.agent_dash.title': 'Gambaran ejen',
'page.agent_dash.desc': 'Ringkasan langsung',
'page.agent_players.title': 'Pemain saya',
'page.agent_players.desc': 'Pemain di bawah akaun anda',
'page.agent_sub.title': 'Sub-ejen',
'page.agent_sub.desc': 'Ejen peringkat 1 sahaja',
'page.agent_bets.title': 'Carian pertaruhan',
'page.agent_bets.desc': 'Semua pertaruhan pemain hiliran',
'agent.credit_limit': 'Had kredit',
'agent.credit_total': 'Jumlah had',
'agent.credit_used': 'Kredit digunakan',
'agent.credit_occupied': 'Sedang digunakan',
'agent.direct_players': 'Pemain terus',
'agent.player_count': 'Pemain',
'agent.today_stake': 'Stake hari ini',
'agent.currency_cny': 'CNY',
'bet.status.PENDING': 'Menunggu',
'bet.status.WON': 'Menang',
'bet.status.LOST': 'Kalah',
'bet.status.VOID': 'Batal',
'bet.status.REFUNDED': 'Dibayar balik',
'bet.type.SINGLE': 'Tunggal',
'bet.type.PARLAY': 'Berganda',
'bet.settlement.PENDING': 'Menunggu',
'bet.settlement.SETTLED': 'Diselesaikan',
'bet.settlement.VOID': 'Batal',
'bet.result.WON': 'Menang',
'bet.result.LOST': 'Kalah',
'bet.result.VOID': 'Batal',
'bet.result.PUSH': 'Seri',
'bet.result.HALF_WON': 'Separuh menang',
'bet.result.HALF_LOST': 'Separuh kalah',
'match.status.DRAFT': 'Draf',
'match.status.PUBLISHED': 'Diterbitkan',
'match.status.CLOSED': 'Ditutup',
'match.status.SETTLED': 'Diselesaikan',
...adminPagesMs,
};
const messages: Record<AdminLocale, Record<string, string>> = {
'zh-CN': zh,
'en-US': en,
'ms-MY': ms,
};
export function adminT(locale: AdminLocale, key: string, params?: Record<string, string | number>): string {
const chain = [locale, 'en-US', 'zh-CN'];
const seen = new Set<string>();
let raw = key;
for (const loc of chain) {
if (seen.has(loc)) continue;
seen.add(loc);
const v = messages[loc as AdminLocale]?.[key];
if (v) {
raw = v;
break;
}
}
if (!params) return raw;
return Object.entries(params).reduce(
(s, [k, v]) => s.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v)),
raw,
);
}
export function adminLocaleTag(locale: AdminLocale): string {
return locale;
}