feat(admin): 从已有玩家升级代理、修复 i18n 与过期 .js 冲突
- 新建一级代理改为选择已有玩家;新建用户可选一级代理 - 操作日志/注单等扁平 key 翻译;清理 src 内误生成 .js,Vite 优先解析 .ts Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,11 +7,10 @@ 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: '🇲🇾' },
|
||||
{ code: 'zh-CN', label: '中文' },
|
||||
{ code: 'en-US', label: 'English' },
|
||||
{ code: 'ms-MY', label: 'Bahasa Melayu' },
|
||||
];
|
||||
|
||||
const zh: Record<string, string> = {
|
||||
@@ -458,32 +457,9 @@ const ms: Record<string, string> = {
|
||||
...adminPagesMs,
|
||||
};
|
||||
|
||||
const messages: Record<AdminLocale, Record<string, string>> = {
|
||||
/** vue-i18n 文案表(扁平 key,与原先 adminT 一致) */
|
||||
export const adminMessages: 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;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'user.btn.save_profile': 'Simpan',
|
||||
'user.btn.confirm_deposit': 'Sahkan tambah baki',
|
||||
'user.deposit_remark_default': 'Tambah baki admin',
|
||||
'user.field.account_type': 'Jenis akaun',
|
||||
'user.type.player': 'Pemain',
|
||||
'user.type.tier1_agent': 'Ejen peringkat 1',
|
||||
'user.hint.account_type': 'Ejen guna had kredit; pemain boleh di bawah ejen',
|
||||
|
||||
'agent.create_btn': '+ Ejen peringkat 1 baharu',
|
||||
'agent.filter.username_ph': 'Nama pengguna',
|
||||
@@ -95,6 +99,9 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'agent.col.credit_after': 'Selepas',
|
||||
'agent.col.no_records': 'Tiada rekod',
|
||||
'agent.btn.confirm_adjust': 'Sahkan',
|
||||
'agent.field.select_user': 'Pilih pengguna',
|
||||
'agent.ph.select_user': 'Cari nama pengguna pemain',
|
||||
'agent.hint.select_user': 'Pilih akaun pemain sedia ada untuk naik taraf ke ejen peringkat 1',
|
||||
|
||||
'match.create_btn': '+ Perlawanan baharu',
|
||||
'match.filter.keyword_ph': 'Nama perlawanan / kod pasukan',
|
||||
@@ -123,6 +130,7 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'bet.col.player': 'Pemain',
|
||||
'bet.col.agent': 'Ejen',
|
||||
'bet.col.selection': 'Pilihan',
|
||||
'bet.col.selection_count': 'Bil. pilihan',
|
||||
'bet.col.stake': 'Stake',
|
||||
'bet.col.odds': 'Odds',
|
||||
'bet.col.payout': 'Bayaran',
|
||||
@@ -147,6 +155,12 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'audit.col.module': 'Modul',
|
||||
'audit.col.target_id': 'ID sasaran',
|
||||
'audit.col.time': 'Masa',
|
||||
'audit.action.CREATE_PLAYER': 'Cipta pemain',
|
||||
'audit.action.UPDATE_PLAYER': 'Kemas kini pemain',
|
||||
'audit.action.CREATE_AGENT': 'Cipta ejen',
|
||||
'audit.action.UPDATE_AGENT': 'Kemas kini ejen',
|
||||
'audit.module.USERS': 'Pemain',
|
||||
'audit.module.AGENTS': 'Ejen',
|
||||
|
||||
'cashback.start_date': 'Tarikh mula',
|
||||
'cashback.end_date': 'Tarikh tamat',
|
||||
@@ -184,6 +198,9 @@ export const adminPagesMs: Record<string, string> = {
|
||||
'err.kickoff_required': 'Sila isi masa mula',
|
||||
'err.teams_required': 'Isi nama pasukan tuan rumah dan pelawat (ZH atau EN)',
|
||||
'err.league_required': 'Sila isi nama liga',
|
||||
'err.user_required': 'Sila pilih pengguna',
|
||||
'err.agent_no_parent': 'Ejen peringkat 1 tidak boleh ada pemain induk',
|
||||
'err.agent_no_initial_deposit': 'Jangan isi baki permulaan pemain apabila cipta ejen',
|
||||
|
||||
'settlement.ht_score': 'Skor separuh masa',
|
||||
'settlement.ft_score': 'Skor penuh masa',
|
||||
|
||||
@@ -62,6 +62,10 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'user.btn.save_profile': '保存资料',
|
||||
'user.btn.confirm_deposit': '确认上分',
|
||||
'user.deposit_remark_default': '管理员上分',
|
||||
'user.field.account_type': '账号类型',
|
||||
'user.type.player': '玩家',
|
||||
'user.type.tier1_agent': '一级代理',
|
||||
'user.hint.account_type': '代理使用授信额度;玩家可挂靠代理并上分',
|
||||
|
||||
'agent.create_btn': '+ 新建一级代理',
|
||||
'agent.filter.username_ph': '用户名',
|
||||
@@ -95,6 +99,9 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'agent.col.credit_after': '变动后',
|
||||
'agent.col.no_records': '暂无记录',
|
||||
'agent.btn.confirm_adjust': '确认调整',
|
||||
'agent.field.select_user': '选择用户',
|
||||
'agent.ph.select_user': '搜索玩家用户名',
|
||||
'agent.hint.select_user': '从已有玩家账号中选择,将其设为一级代理(不新建登录账号)',
|
||||
|
||||
'match.create_btn': '+ 新增赛事',
|
||||
'match.filter.keyword_ph': '赛事名 / 球队代码',
|
||||
@@ -123,6 +130,7 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'bet.col.player': '玩家',
|
||||
'bet.col.agent': '所属代理',
|
||||
'bet.col.selection': '选项',
|
||||
'bet.col.selection_count': '投注项数',
|
||||
'bet.col.stake': '投注额',
|
||||
'bet.col.odds': '赔率',
|
||||
'bet.col.payout': '派彩',
|
||||
@@ -147,6 +155,12 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'audit.col.module': '模块',
|
||||
'audit.col.target_id': '目标 ID',
|
||||
'audit.col.time': '时间',
|
||||
'audit.action.CREATE_PLAYER': '新建玩家',
|
||||
'audit.action.UPDATE_PLAYER': '更新玩家',
|
||||
'audit.action.CREATE_AGENT': '新建代理',
|
||||
'audit.action.UPDATE_AGENT': '更新代理',
|
||||
'audit.module.USERS': '玩家',
|
||||
'audit.module.AGENTS': '代理',
|
||||
|
||||
'cashback.start_date': '开始日期',
|
||||
'cashback.end_date': '结束日期',
|
||||
@@ -184,6 +198,9 @@ export const adminPagesZh: Record<string, string> = {
|
||||
'err.kickoff_required': '请填写开赛时间',
|
||||
'err.teams_required': '请填写主客队名称(中文或英文至少一项)',
|
||||
'err.league_required': '请填写联赛名称',
|
||||
'err.user_required': '请选择用户',
|
||||
'err.agent_no_parent': '一级代理不可设置上级玩家',
|
||||
'err.agent_no_initial_deposit': '设为代理时请勿填写玩家初始余额',
|
||||
|
||||
'settlement.ht_score': '半场比分',
|
||||
'settlement.ft_score': '全场比分',
|
||||
@@ -301,6 +318,10 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'user.btn.save_profile': 'Save',
|
||||
'user.btn.confirm_deposit': 'Confirm top-up',
|
||||
'user.deposit_remark_default': 'Admin top-up',
|
||||
'user.field.account_type': 'Account type',
|
||||
'user.type.player': 'Player',
|
||||
'user.type.tier1_agent': 'Tier-1 agent',
|
||||
'user.hint.account_type': 'Agents use credit limits; players can belong to an agent and receive top-ups',
|
||||
|
||||
'agent.create_btn': '+ New tier-1 agent',
|
||||
'agent.filter.username_ph': 'Username',
|
||||
@@ -334,6 +355,9 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'agent.col.credit_after': 'After',
|
||||
'agent.col.no_records': 'No records',
|
||||
'agent.btn.confirm_adjust': 'Confirm',
|
||||
'agent.field.select_user': 'Select user',
|
||||
'agent.ph.select_user': 'Search player username',
|
||||
'agent.hint.select_user': 'Pick an existing player account to promote to tier-1 agent (no new login)',
|
||||
|
||||
'match.create_btn': '+ New match',
|
||||
'match.filter.keyword_ph': 'Match name / team code',
|
||||
@@ -362,6 +386,7 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'bet.col.player': 'Player',
|
||||
'bet.col.agent': 'Agent',
|
||||
'bet.col.selection': 'Pick',
|
||||
'bet.col.selection_count': 'Legs',
|
||||
'bet.col.stake': 'Stake',
|
||||
'bet.col.odds': 'Odds',
|
||||
'bet.col.payout': 'Payout',
|
||||
@@ -386,6 +411,12 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'audit.col.module': 'Module',
|
||||
'audit.col.target_id': 'Target ID',
|
||||
'audit.col.time': 'Time',
|
||||
'audit.action.CREATE_PLAYER': 'Create player',
|
||||
'audit.action.UPDATE_PLAYER': 'Update player',
|
||||
'audit.action.CREATE_AGENT': 'Create agent',
|
||||
'audit.action.UPDATE_AGENT': 'Update agent',
|
||||
'audit.module.USERS': 'Players',
|
||||
'audit.module.AGENTS': 'Agents',
|
||||
|
||||
'cashback.start_date': 'Start date',
|
||||
'cashback.end_date': 'End date',
|
||||
@@ -423,6 +454,9 @@ export const adminPagesEn: Record<string, string> = {
|
||||
'err.kickoff_required': 'Kickoff time is required',
|
||||
'err.teams_required': 'Enter home and away team names (ZH or EN)',
|
||||
'err.league_required': 'League name is required',
|
||||
'err.user_required': 'Please select a user',
|
||||
'err.agent_no_parent': 'Tier-1 agents cannot have a parent player',
|
||||
'err.agent_no_initial_deposit': 'Do not set initial player balance when creating an agent',
|
||||
|
||||
'settlement.ht_score': 'Half-time score',
|
||||
'settlement.ft_score': 'Full-time score',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 表单校验错误(key 对应 admin-messages / admin-pages) */
|
||||
/** 表单校验错误(key 对应 vue-i18n 文案) */
|
||||
export class FormValidationError extends Error {
|
||||
readonly key: string;
|
||||
|
||||
|
||||
19
apps/admin/src/i18n/index.ts
Normal file
19
apps/admin/src/i18n/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import { adminMessages, type AdminLocale } from './admin-messages';
|
||||
|
||||
export const ADMIN_LOCALE_STORAGE_KEY = 'admin_locale';
|
||||
|
||||
const saved = (localStorage.getItem(ADMIN_LOCALE_STORAGE_KEY) as AdminLocale) || 'zh-CN';
|
||||
|
||||
export const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: saved,
|
||||
fallbackLocale: ['en-US', 'zh-CN'],
|
||||
messages: adminMessages,
|
||||
});
|
||||
|
||||
export function getAdminLocale(): AdminLocale {
|
||||
return i18n.global.locale.value as AdminLocale;
|
||||
}
|
||||
|
||||
export default i18n;
|
||||
16
apps/admin/src/i18n/resolve-message.ts
Normal file
16
apps/admin/src/i18n/resolve-message.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { adminMessages, type AdminLocale } from './admin-messages';
|
||||
import { getAdminLocale } from './index';
|
||||
|
||||
/** 扁平 key 翻译(避免 vue-i18n 把 audit.action.X 当成嵌套路径) */
|
||||
export function resolveAdminMessage(key: string, locale?: AdminLocale): string | undefined {
|
||||
const chain: AdminLocale[] = [];
|
||||
const primary = locale ?? getAdminLocale();
|
||||
for (const loc of [primary, 'en-US', 'zh-CN'] as AdminLocale[]) {
|
||||
if (!chain.includes(loc)) chain.push(loc);
|
||||
}
|
||||
for (const loc of chain) {
|
||||
const v = adminMessages[loc]?.[key];
|
||||
if (v) return v;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user