const DEFAULT_LOCALE = 'zh-CN'; const SUPPORTED_LOCALES = ['zh-CN', 'ms-MY', 'en-US']; /** 后端错误码 → 三语文案({param} 占位) */ export const API_ERROR_MESSAGES = { INTERNAL_SERVER_ERROR: { 'zh-CN': '服务器内部错误', 'en-US': 'Internal server error', 'ms-MY': 'Ralat pelayan dalaman', }, INVALID_CREDENTIALS: { 'zh-CN': '用户名或密码错误', 'en-US': 'Invalid username or password', 'ms-MY': 'Nama pengguna atau kata laluan tidak sah', }, ACCOUNT_DISABLED: { 'zh-CN': '账号已停用', 'en-US': 'Account disabled', 'ms-MY': 'Akaun telah dinyahaktifkan', }, ACCOUNT_SUSPENDED: { 'zh-CN': '账号已冻结', 'en-US': 'Account suspended', 'ms-MY': 'Akaun digantung', }, AGENT_ACCOUNT_SUSPENDED: { 'zh-CN': '代理账号已停用', 'en-US': 'Agent account suspended', 'ms-MY': 'Akaun ejen digantung', }, PARENT_AGENT_SUSPENDED: { 'zh-CN': '上级代理已停用,暂无法登录', 'en-US': 'Parent agent is suspended; login unavailable', 'ms-MY': 'Ejen induk digantung; log masuk tidak tersedia', }, ACCOUNT_LOCKED: { 'zh-CN': '账号已锁定,请稍后再试', 'en-US': 'Account locked, try again later', 'ms-MY': 'Akaun dikunci, cuba lagi nanti', }, USER_NOT_FOUND: { 'zh-CN': '用户不存在', 'en-US': 'User not found', 'ms-MY': 'Pengguna tidak dijumpai', }, PASSWORD_CHANGE_DISABLED: { 'zh-CN': '当前平台未开放玩家自行修改密码', 'en-US': 'Password change is disabled for players', 'ms-MY': 'Pertukaran kata laluan pemain tidak dibenarkan', }, INVALID_OLD_PASSWORD: { 'zh-CN': '当前密码不正确', 'en-US': 'Current password is incorrect', 'ms-MY': 'Kata laluan semasa tidak betul', }, ADMIN_ACCESS_REQUIRED: { 'zh-CN': '需要管理员权限', 'en-US': 'Admin access required', 'ms-MY': 'Akses pentadbir diperlukan', }, INSUFFICIENT_PERMISSIONS: { 'zh-CN': '权限不足', 'en-US': 'Insufficient permissions', 'ms-MY': 'Kebenaran tidak mencukupi', }, ACCESS_DENIED_PORTAL: { 'zh-CN': '无权访问该门户', 'en-US': 'Access denied for this portal', 'ms-MY': 'Akses portal ditolak', }, PLAYER_ACCESS_ONLY: { 'zh-CN': '仅玩家可访问', 'en-US': 'Player access only', 'ms-MY': 'Akses pemain sahaja', }, ADMIN_ACCESS_ONLY: { 'zh-CN': '仅管理员可访问', 'en-US': 'Admin access only', 'ms-MY': 'Akses pentadbir sahaja', }, AGENT_ACCESS_ONLY: { 'zh-CN': '仅代理可访问', 'en-US': 'Agent access only', 'ms-MY': 'Akses ejen sahaja', }, LEAGUE_NAME_REQUIRED: { 'zh-CN': '请填写联赛名称(中文、英文或马来文至少一项)', 'en-US': 'League name required (Chinese, English or Malay)', 'ms-MY': 'Nama liga diperlukan (Cina, Inggeris atau Melayu)', }, LEAGUE_NOT_FOUND: { 'zh-CN': '联赛不存在', 'en-US': 'League not found', 'ms-MY': 'Liga tidak dijumpai', }, LEAGUE_UNPUBLISH_SETTLED: { 'zh-CN': '联赛冠军盘已结算,不可下架', 'en-US': 'Cannot unpublish league after outright market is settled', 'ms-MY': 'Liga tidak boleh ditarik selepas pasaran juara diselesaikan', }, MATCH_UNPUBLISH_FORBIDDEN: { 'zh-CN': '当前状态不可下架', 'en-US': 'Match cannot be unpublished in current status', 'ms-MY': 'Perlawanan tidak boleh ditarik dalam status semasa', }, TEAM_CODE_REQUIRED: { 'zh-CN': '请填写球队代码', 'en-US': 'Team code is required', 'ms-MY': 'Kod pasukan diperlukan', }, TEAMS_NAME_REQUIRED: { 'zh-CN': '请填写主客队名称(中文、英文或马来文至少一项)', 'en-US': 'Home and away team names required', 'ms-MY': 'Nama pasukan home/away diperlukan', }, TEAMS_SAME: { 'zh-CN': '主客队不能相同,请填写不同的队名', 'en-US': 'Home and away teams must be different', 'ms-MY': 'Pasukan home dan away mesti berbeza', }, MATCH_NOT_FOUND: { 'zh-CN': '赛事不存在', 'en-US': 'Match not found', 'ms-MY': 'Perlawanan tidak dijumpai', }, OUTRIGHT_EDIT_VIA_MARKETS: { 'zh-CN': '冠军盘请通过盘口管理维护', 'en-US': 'Edit outright markets in the markets page', 'ms-MY': 'Urus outright melalui halaman pasaran', }, MATCH_NOT_EDITABLE: { 'zh-CN': '当前状态不可编辑', 'en-US': 'Match cannot be edited in current status', 'ms-MY': 'Perlawanan tidak boleh diedit dalam status semasa', }, MATCH_NOT_REOPENABLE: { 'zh-CN': '当前状态不可解除封盘', 'en-US': 'Match cannot be reopened in current status', 'ms-MY': 'Perlawanan tidak boleh dibuka semula dalam status semasa', }, MATCH_NOT_SETTLEABLE: { 'zh-CN': '当前状态不可确认结算', 'en-US': 'Match cannot be settled in current status', 'ms-MY': 'Perlawanan tidak boleh diselesaikan dalam status semasa', }, MATCH_MUST_CLOSE_FOR_SETTLEMENT: { 'zh-CN': '请先封盘后再结算', 'en-US': 'Close betting before settlement', 'ms-MY': 'Tutup pertaruhan sebelum penyelesaian', }, MATCH_REOPEN_KICKOFF_REQUIRED: { 'zh-CN': '开赛时间已过,请设置新的未来开赛时间', 'en-US': 'Kickoff has passed; set a new future start time', 'ms-MY': 'Masa mula telah berlalu; tetapkan masa mula baharu pada masa hadapan', }, MATCH_START_TIME_INVALID: { 'zh-CN': '开赛时间格式无效', 'en-US': 'Invalid kickoff time', 'ms-MY': 'Format masa mula tidak sah', }, OUTRIGHT_DELETE_FORBIDDEN: { 'zh-CN': '冠军盘不可删除', 'en-US': 'Outright events cannot be deleted', 'ms-MY': 'Acara outright tidak boleh dipadam', }, MATCH_DELETE_DRAFT_ONLY: { 'zh-CN': '仅草稿状态可删除', 'en-US': 'Only draft matches can be deleted', 'ms-MY': 'Hanya draf boleh dipadam', }, MATCH_HAS_BETS: { 'zh-CN': '该赛事已有注单关联,无法删除', 'en-US': 'Match has bets and cannot be deleted', 'ms-MY': 'Perlawanan mempunyai pertaruhan dan tidak boleh dipadam', }, ARCHIVE_BLOCKED: { 'zh-CN': '存在未结注单或未结算状态,需确认强制删除', 'en-US': 'Unsettled bets or match state require forced archive', 'ms-MY': 'Pertaruhan belum selesai atau status perlawanan memerlukan arkib paksa', }, LEAGUE_ARCHIVE_NOT_READY: { 'zh-CN': '联赛下仍有未结算赛事或未结注单,无法删除', 'en-US': 'League still has unsettled fixtures or pending bets', 'ms-MY': 'Liga masih mempunyai perlawanan belum selesai atau pertaruhan tertunda', }, ALREADY_ARCHIVED: { 'zh-CN': '已删除或已隐藏', 'en-US': 'Already archived', 'ms-MY': 'Sudah diarkibkan', }, MATCHES_ARRAY_REQUIRED: { 'zh-CN': '请提供 matches 数组', 'en-US': 'matches array is required', 'ms-MY': 'Tatasusunan matches diperlukan', }, SELECTION_NOT_FOUND: { 'zh-CN': '投注选项不存在', 'en-US': 'Selection not found', 'ms-MY': 'Pilihan tidak dijumpai', }, SELECTION_CLOSED: { 'zh-CN': '投注选项已关闭', 'en-US': 'Selection closed', 'ms-MY': 'Pilihan ditutup', }, MARKET_CLOSED: { 'zh-CN': '盘口已关闭', 'en-US': 'Market closed', 'ms-MY': 'Pasaran ditutup', }, MATCH_NOT_BETTING: { 'zh-CN': '该赛事暂不可投注', 'en-US': 'Match not available for betting', 'ms-MY': 'Perlawanan tidak tersedia untuk pertaruhan', }, FOOTBALL_ONLY: { 'zh-CN': '仅支持足球投注', 'en-US': 'Only football betting is supported', 'ms-MY': 'Hanya pertaruhan bola sepak disokong', }, PRE_MATCH_ONLY: { 'zh-CN': '仅支持赛前投注,比赛已开始', 'en-US': 'Pre-match betting only; match has started', 'ms-MY': 'Pertaruhan pra-perlawanan sahaja; perlawanan telah bermula', }, ODDS_CHANGED: { 'zh-CN': '赔率已变更,请重新确认', 'en-US': 'Odds changed, please confirm again', 'ms-MY': 'Odds berubah, sila sahkan semula', }, INVALID_STAKE: { 'zh-CN': '投注金额无效', 'en-US': 'Invalid stake', 'ms-MY': 'Stake tidak sah', }, PARLAY_LEG_COUNT_INVALID: { 'zh-CN': '串关场次须在 {min}–{max} 场之间', 'en-US': 'Parlay must have {min}–{max} legs', 'ms-MY': 'Parlay mesti {min}–{max} pilihan', }, PARLAY_OUTRIGHT_FORBIDDEN: { 'zh-CN': '冠军盘不可加入串关', 'en-US': 'Outright cannot be in parlay', 'ms-MY': 'Outright tidak boleh dalam parlay', }, PARLAY_QUARTER_LINE_FORBIDDEN: { 'zh-CN': '四分之一盘口不可加入串关', 'en-US': 'Quarter line markets cannot be in parlay', 'ms-MY': 'Pasaran suku baris tidak boleh dalam parlay', }, PARLAY_MARKET_NOT_ALLOWED: { 'zh-CN': '该盘口不可加入串关', 'en-US': 'Market not allowed in parlay', 'ms-MY': 'Pasaran tidak dibenarkan dalam parlay', }, SINGLE_MARKET_NOT_ALLOWED: { 'zh-CN': '该盘口不可单关投注', 'en-US': 'Market not allowed for single bets', 'ms-MY': 'Pasaran ini tidak dibenarkan untuk taruhan tunggal', }, PARLAY_SAME_MATCH_FORBIDDEN: { 'zh-CN': '同一场比赛不能串关', 'en-US': 'Cannot parlay selections from the same match', 'ms-MY': 'Perlawanan sama tidak boleh berganda', }, WALLET_FROZEN_INSUFFICIENT: { 'zh-CN': '注单冻结金额不足,无法结算', 'en-US': 'Frozen bet funds are insufficient for settlement', 'ms-MY': 'Dana beku pertaruhan tidak mencukupi untuk penyelesaian', }, BET_NOT_FOUND: { 'zh-CN': '注单不存在', 'en-US': 'Bet not found', 'ms-MY': 'Pertaruhan tidak dijumpai', }, MIN_STAKE: { 'zh-CN': '最低投注额为 {minStake}', 'en-US': 'Minimum stake is {minStake}', 'ms-MY': 'Stake minimum ialah {minStake}', }, MAX_STAKE: { 'zh-CN': '最高投注额为 {maxStake}', 'en-US': 'Maximum stake is {maxStake}', 'ms-MY': 'Stake maksimum ialah {maxStake}', }, MAX_PAYOUT: { 'zh-CN': '潜在派彩超过限额 {maxPayout}', 'en-US': 'Potential return exceeds limit of {maxPayout}', 'ms-MY': 'Bayaran melebihi had {maxPayout}', }, DAILY_STAKE_LIMIT: { 'zh-CN': '已超过日投注限额 {limit}', 'en-US': 'Daily stake limit of {limit} exceeded', 'ms-MY': 'Had stake harian {limit} telah melebihi', }, WALLET_NOT_FOUND: { 'zh-CN': '钱包不存在', 'en-US': 'Wallet not found', 'ms-MY': 'Dompet tidak dijumpai', }, AMOUNT_MUST_BE_POSITIVE: { 'zh-CN': '金额须大于 0', 'en-US': 'Amount must be positive', 'ms-MY': 'Jumlah mesti positif', }, INSUFFICIENT_BALANCE: { 'zh-CN': '余额不足', 'en-US': 'Insufficient balance', 'ms-MY': 'Baki tidak mencukupi', }, AGENT_PROFILE_NOT_FOUND: { 'zh-CN': '代理资料不存在', 'en-US': 'Agent profile not found', 'ms-MY': 'Profil ejen tidak dijumpai', }, AGENT_NOT_FOUND: { 'zh-CN': '代理不存在', 'en-US': 'Agent not found', 'ms-MY': 'Ejen tidak dijumpai', }, CREDIT_LIMIT_NEGATIVE: { 'zh-CN': '授信额度不能为负', 'en-US': 'Credit limit cannot be negative', 'ms-MY': 'Had kredit tidak boleh negatif', }, PLAYER_NOT_FOUND: { 'zh-CN': '玩家不存在', 'en-US': 'Player not found', 'ms-MY': 'Pemain tidak dijumpai', }, NOT_PLAYER: { 'zh-CN': '该用户不是玩家', 'en-US': 'User is not a player', 'ms-MY': 'Pengguna bukan pemain', }, PLAYER_HAS_PENDING_BETS: { 'zh-CN': '玩家仍有未结算注单,无法删除', 'en-US': 'Player has pending bets and cannot be deleted', 'ms-MY': 'Pemain masih ada pertaruhan belum selesai dan tidak boleh dipadam', }, PLAYER_HAS_BALANCE: { 'zh-CN': '玩家钱包仍有余额,无法删除', 'en-US': 'Player wallet still has balance and cannot be deleted', 'ms-MY': 'Dompet pemain masih ada baki dan tidak boleh dipadam', }, MANAGE_DIRECT_PLAYERS_ONLY: { 'zh-CN': '仅可管理直属玩家', 'en-US': 'Can only manage direct players', 'ms-MY': 'Hanya pemain terus boleh diurus', }, PARENT_AGENT_NOT_FOUND: { 'zh-CN': '上级代理不存在', 'en-US': 'Parent agent not found', 'ms-MY': 'Ejen induk tidak dijumpai', }, CREDIT_EXCEEDS_PARENT: { 'zh-CN': '下级代理授信不能超过上级授信额度', 'en-US': 'Sub-agent credit cannot exceed parent limit', 'ms-MY': 'Kredit sub-ejen tidak boleh melebihi induk', }, CASHBACK_RATE_NEGATIVE: { 'zh-CN': '回水比例不能为负', 'en-US': 'Cashback rate cannot be negative', 'ms-MY': 'Kadar rebat tidak boleh negatif', }, CASHBACK_RATE_EXCEEDS_PARENT: { 'zh-CN': '下级代理回水比例不能超过上级', 'en-US': 'Sub-agent cashback rate cannot exceed parent', 'ms-MY': 'Kadar rebat sub-ejen tidak boleh melebihi induk', }, BET_LIMIT_EXCEEDS_PARENT: { 'zh-CN': '下级代理单笔限额不能超过上级', 'en-US': 'Sub-agent bet limit cannot exceed parent', 'ms-MY': 'Had pertaruhan sub-ejen tidak boleh melebihi induk', }, BET_LIMIT_NEGATIVE: { 'zh-CN': '单笔限额不能为负', 'en-US': 'Bet limit cannot be negative', 'ms-MY': 'Had pertaruhan tidak boleh negatif', }, DAILY_LIMIT_EXCEEDS_PARENT: { 'zh-CN': '下级代理日限额不能超过上级', 'en-US': 'Sub-agent daily limit cannot exceed parent', 'ms-MY': 'Had harian sub-ejen tidak boleh melebihi induk', }, DAILY_LIMIT_NEGATIVE: { 'zh-CN': '日限额不能为负', 'en-US': 'Daily limit cannot be negative', 'ms-MY': 'Had harian tidak boleh negatif', }, CREDIT_TOPUP_EXCEEDED: { 'zh-CN': '超过玩家上级代理可用授信,无法上分', 'en-US': 'Exceeds parent agent available credit', 'ms-MY': 'Melebihi kredit ejen induk yang tersedia', }, AGENT_SINGLE_TOPUP_LIMIT: { 'zh-CN': '超过代理单笔上分限额', 'en-US': 'Exceeds agent single top-up limit', 'ms-MY': 'Melebihi had top-up tunggal ejen', }, AGENT_DAILY_TOPUP_LIMIT: { 'zh-CN': '超过代理日上分限额', 'en-US': 'Exceeds agent daily top-up limit', 'ms-MY': 'Melebihi had top-up harian ejen', }, INSUFFICIENT_AGENT_CREDIT: { 'zh-CN': '代理可用授信不足', 'en-US': 'Insufficient agent credit', 'ms-MY': 'Kredit ejen tidak mencukupi', }, INVALID_STATUS: { 'zh-CN': '无效状态', 'en-US': 'Invalid status', 'ms-MY': 'Status tidak sah', }, USERNAME_REQUIRED: { 'zh-CN': '账号名称不能为空', 'en-US': 'Username is required', 'ms-MY': 'Nama pengguna diperlukan', }, USERNAME_TAKEN: { 'zh-CN': '账号名称已被占用', 'en-US': 'Username already taken', 'ms-MY': 'Nama pengguna sudah digunakan', }, INVITE_CODE_REQUIRED: { 'zh-CN': '请填写邀请码', 'en-US': 'Invitation code is required', 'ms-MY': 'Kod jemputan diperlukan', }, INVITE_CODE_INVALID: { 'zh-CN': '邀请码无效或已失效', 'en-US': 'Invalid or inactive invitation code', 'ms-MY': 'Kod jemputan tidak sah atau tidak aktif', }, INVITE_CODE_NOT_AVAILABLE: { 'zh-CN': '该邀请码暂不可用于注册', 'en-US': 'This invitation code is not available for registration', 'ms-MY': 'Kod jemputan ini tidak tersedia untuk pendaftaran', }, INVITE_NOT_FOUND: { 'zh-CN': '邀请码记录不存在', 'en-US': 'Invitation record not found', 'ms-MY': 'Rekod jemputan tidak dijumpai', }, INVITE_MUST_REVOKE_FIRST: { 'zh-CN': '请先作废该邀请码后再删除', 'en-US': 'Revoke the invitation code before deleting', 'ms-MY': 'Batalkan kod jemputan dahulu sebelum padam', }, INVITE_CODE_ALREADY_USED: { 'zh-CN': '该邀请码已被使用,每个邀请码仅可注册一名玩家', 'en-US': 'This invitation code has already been used; each code allows one registration only', 'ms-MY': 'Kod jemputan ini telah digunakan; setiap kod hanya untuk satu pendaftaran', }, INVITE_CANNOT_DELETE_USED: { 'zh-CN': '已使用的邀请码不可删除', 'en-US': 'Used invitation codes cannot be deleted', 'ms-MY': 'Kod jemputan yang telah digunakan tidak boleh dipadam', }, INVITE_CASHBACK_RATE_INVALID: { 'zh-CN': '返水比例无效,请输入非负数', 'en-US': 'Invalid cashback rate; must be a non-negative number', 'ms-MY': 'Kadar rebat tidak sah; mesti nombor bukan negatif', }, USERNAME_FORMAT_INVALID: { 'zh-CN': '玩家用户名仅可使用英文字母和数字(7–32 位),不可含中文或特殊符号', 'en-US': 'Username must be 7–32 letters or digits only', 'ms-MY': 'Nama pengguna mesti 7–32 huruf atau digit sahaja', }, PASSWORD_MIN_LENGTH: { 'zh-CN': '密码至少 8 位', 'en-US': 'Password must be at least 8 characters', 'ms-MY': 'Kata laluan sekurang-kurangnya 8 aksara', }, AUTH_INFO_MISSING: { 'zh-CN': '账号认证信息缺失', 'en-US': 'Account auth info missing', 'ms-MY': 'Maklumat auth akaun tiada', }, USERNAME_CHANGE_DISABLED: { 'zh-CN': '当前平台未开放玩家自行修改账号名称', 'en-US': 'Username change is disabled for players', 'ms-MY': 'Pertukaran nama pengguna pemain tidak dibenarkan', }, INVALID_AVATAR: { 'zh-CN': '无效头像', 'en-US': 'Invalid avatar', 'ms-MY': 'Avatar tidak sah', }, UNSUPPORTED_LOCALE: { 'zh-CN': '不支持的语言', 'en-US': 'Unsupported locale', 'ms-MY': 'Locale tidak disokong', }, NOT_SUB_AGENT: { 'zh-CN': '非您的下级代理', 'en-US': 'Not your sub-agent', 'ms-MY': 'Bukan sub-ejen anda', }, PROMOTE_PLAYER_ONLY: { 'zh-CN': '仅玩家账号可设为代理', 'en-US': 'Only player accounts can be promoted to agent', 'ms-MY': 'Hanya pemain boleh dinaikkan ke ejen', }, ALREADY_AGENT: { 'zh-CN': '该用户已是代理', 'en-US': 'User is already an agent', 'ms-MY': 'Pengguna sudah menjadi ejen', }, AGENT_LEVEL_INVALID: { 'zh-CN': '代理级别无效', 'en-US': 'Invalid agent level', 'ms-MY': 'Tahap ejen tidak sah', }, AGENT_MAX_LEVEL_REACHED: { 'zh-CN': '已达到最大代理层级,无法继续创建下级', 'en-US': 'Maximum agent level reached; cannot create sub-agents', 'ms-MY': 'Tahap ejen maksimum dicapai; tidak boleh cipta sub-ejen', }, AGENT_PARENT_LEVEL_MISMATCH: { 'zh-CN': '上级代理层级与目标层级不匹配', 'en-US': 'Parent agent level does not match target level', 'ms-MY': 'Tahap ejen induk tidak sepadan dengan tahap sasaran', }, AGENT_LEVEL_ROOT_INVALID: { 'zh-CN': '一级代理不可指定上级', 'en-US': 'Root-level agents cannot have a parent', 'ms-MY': 'Ejen peringkat akar tidak boleh ada induk', }, LEVEL2_REQUIRES_PARENT: { 'zh-CN': '二级代理必须指定上级代理', 'en-US': 'Level 2 agent requires parent', 'ms-MY': 'Ejen peringkat 2 memerlukan induk', }, TIER1_NO_PARENT_PLAYER: { 'zh-CN': '一级代理不可设置上级玩家', 'en-US': 'Tier-1 agents cannot have a parent player', 'ms-MY': 'Ejen peringkat 1 tidak boleh ada pemain induk', }, PROMOTE_USE_CREDIT_NOT_BALANCE: { 'zh-CN': '设为代理时请使用授信额度,勿填玩家初始余额', 'en-US': 'Use credit limit when promoting to agent, not initial balance', 'ms-MY': 'Guna had kredit apabila naik taraf ke ejen, bukan baki awal', }, INITIAL_DEPOSIT_REMARK_REQUIRED: { 'zh-CN': '有初始余额时必须选择上分流水说明', 'en-US': 'Ledger note is required when initial balance > 0', 'ms-MY': 'Nota ledger diperlukan apabila baki awal > 0', }, INITIAL_DEPOSIT_REMARK_CUSTOM_INVALID: { 'zh-CN': '自定义流水说明至少 2 个字符', 'en-US': 'Custom ledger note must be at least 2 characters', 'ms-MY': 'Nota ledger tersuai mesti sekurang-kurangnya 2 aksara', }, TIER2_REQUIRES_PARENT_AGENT: { 'zh-CN': '二级代理必须指定上级代理', 'en-US': 'Tier-2 agent must specify parent agent', 'ms-MY': 'Ejen peringkat 2 mesti nyatakan ejen induk', }, PARENT_MUST_BE_AGENT: { 'zh-CN': '上级必须为代理账号', 'en-US': 'Parent must be an agent account', 'ms-MY': 'Induk mesti akaun ejen', }, CREATE_DIRECT_PLAYERS_ONLY: { 'zh-CN': '仅可创建直属玩家', 'en-US': 'Can only create direct players', 'ms-MY': 'Hanya pemain terus boleh dicipta', }, DB_RESET_FORBIDDEN: { 'zh-CN': '生产环境禁止重置数据库(需设置 ALLOW_DB_RESET=true)', 'en-US': 'Database reset forbidden in production (set ALLOW_DB_RESET=true)', 'ms-MY': 'Reset DB dilarang di produksi (set ALLOW_DB_RESET=true)', }, SMOKE_TESTS_FORBIDDEN: { 'zh-CN': '生产环境已禁用自动化测试', 'en-US': 'Smoke tests are disabled in production', 'ms-MY': 'Ujian asap dilumpuhkan di produksi', }, CASHBACK_DATE_RANGE_INVALID: { 'zh-CN': '开始日期不能晚于结束日期', 'en-US': 'Start date cannot be after end date', 'ms-MY': 'Tarikh mula tidak boleh selepas tarikh tamat', }, CASHBACK_ALREADY_ISSUED: { 'zh-CN': '该统计周期已发放返水,不可重复生成预览', 'en-US': 'Cashback already issued for this period', 'ms-MY': 'Rebat telah dikeluarkan untuk tempoh ini', }, CASHBACK_NO_ELIGIBLE_BETS: { 'zh-CN': '该周期内无符合条件的返水,无法生成预览', 'en-US': 'No eligible bets in period for cashback preview', 'ms-MY': 'Tiada pertaruhan layak untuk pratonton rebat', }, CASHBACK_BETS_IN_OTHER_BATCH: { 'zh-CN': '该周期内的有效注单均已计入其他返水批次,无法生成预览', 'en-US': 'Eligible bets already in another cashback batch', 'ms-MY': 'Pertaruhan layak sudah dalam batch rebat lain', }, CASHBACK_BATCH_NOT_FOUND: { 'zh-CN': '返水批次不存在', 'en-US': 'Cashback batch not found', 'ms-MY': 'Batch rebat tidak dijumpai', }, CASHBACK_BATCH_NOT_ISSUABLE: { 'zh-CN': '该批次不可发放', 'en-US': 'Batch cannot be issued', 'ms-MY': 'Batch tidak boleh dikeluarkan', }, CASHBACK_NO_AMOUNT: { 'zh-CN': '批次无有效返水金额', 'en-US': 'Batch has no valid cashback amount', 'ms-MY': 'Batch tiada jumlah rebat sah', }, CASHBACK_PERIOD_ALREADY_ISSUED: { 'zh-CN': '该统计周期已发放返水', 'en-US': 'Cashback already issued for this period', 'ms-MY': 'Rebat tempoh ini telah dikeluarkan', }, CASHBACK_BETS_ALREADY_PAID: { 'zh-CN': '部分注单已在其他批次发放返水,请作废本预览后重新生成', 'en-US': 'Some bets paid in another batch; void preview and regenerate', 'ms-MY': 'Sebahagian pertaruhan dibayar dalam batch lain; batalkan pratonton', }, CASHBACK_PREVIEW_ONLY_VOID: { 'zh-CN': '只能作废待发放批次', 'en-US': 'Only preview batches can be voided', 'ms-MY': 'Hanya batch pratonton boleh dibatalkan', }, UNKNOWN_MARKET_TYPE: { 'zh-CN': '未知盘口类型:{marketType}', 'en-US': 'Unknown market type: {marketType}', 'ms-MY': 'Jenis pasaran tidak diketahui: {marketType}', }, ODDS_MIN: { 'zh-CN': '赔率须大于 1.00', 'en-US': 'Odds must be greater than 1.00', 'ms-MY': 'Odds mesti lebih daripada 1.00', }, MARKET_NOT_FOUND: { 'zh-CN': '盘口不存在', 'en-US': 'Market not found', 'ms-MY': 'Pasaran tidak dijumpai', }, MARKET_TEMPLATE_NOT_FOUND: { 'zh-CN': '盘口模板不存在', 'en-US': 'Market template not found', 'ms-MY': 'Templat pasaran tidak dijumpai', }, MARKET_LINE_REQUIRED: { 'zh-CN': '该盘口类型必须设置盘口线', 'en-US': 'This market type requires a line value', 'ms-MY': 'Jenis pasaran ini memerlukan nilai garisan', }, MARKET_LINE_NOT_ALLOWED: { 'zh-CN': '该盘口类型不允许设置盘口线', 'en-US': 'This market type does not allow a line value', 'ms-MY': 'Jenis pasaran ini tidak membenarkan nilai garisan', }, OPERATOR_REQUIRED: { 'zh-CN': '修改赔率须指定操作员', 'en-US': 'Operator required for odds update', 'ms-MY': 'Operator diperlukan untuk kemas kini odds', }, OUTRIGHT_SELECTION_EXISTS: { 'zh-CN': '该球队代码已存在选项', 'en-US': 'Selection already exists for this team code', 'ms-MY': 'Pilihan untuk kod pasukan ini sudah wujud', }, OUTRIGHT_TEAMS_REQUIRED: { 'zh-CN': '至少添加一支球队', 'en-US': 'At least one team required', 'ms-MY': 'Sekurang-kurangnya satu pasukan diperlukan', }, OUTRIGHT_SELECTION_INVALID: { 'zh-CN': '无效的夺冠选项', 'en-US': 'Invalid selection for this outright event', 'ms-MY': 'Pilihan tidak sah untuk outright ini', }, OUTRIGHT_LEAGUE_FIXTURES_UNSETTLED: { 'zh-CN': '该联赛仍有未结算的单场赛事,请先完成单场结算后再结算冠军盘', 'en-US': 'This league still has unsettled fixture matches. Settle them before settling the outright market.', 'ms-MY': 'Liga ini masih ada perlawanan belum diselesaikan. Selesaikan dahulu sebelum juara.', }, OUTRIGHT_EVENT_NOT_FOUND: { 'zh-CN': '冠军盘赛事不存在', 'en-US': 'Outright event not found', 'ms-MY': 'Acara outright tidak dijumpai', }, SETTLEMENT_WINNER_REQUIRED: { 'zh-CN': '冠军盘结算需指定获胜球队', 'en-US': 'Outright settlement requires winner team', 'ms-MY': 'Penyelesaian outright memerlukan pasukan pemenang', }, SETTLEMENT_WINNER_NOT_FOUND: { 'zh-CN': '获胜球队不存在', 'en-US': 'Winner team not found', 'ms-MY': 'Pasukan pemenang tidak dijumpai', }, SETTLEMENT_WINNER_NOT_IN_MARKET: { 'zh-CN': '该球队不在本冠军盘选项中', 'en-US': 'Team is not in this outright market', 'ms-MY': 'Pasukan tiada dalam pasaran outright ini', }, SCORE_NOT_RECORDED: { 'zh-CN': '尚未录入比分', 'en-US': 'Score not recorded', 'ms-MY': 'Skor belum direkod', }, SETTLEMENT_FACTS_REQUIRED: { 'zh-CN': '缺少比赛统计事实:{fields}', 'en-US': 'Missing match settlement facts: {fields}', 'ms-MY': 'Fakta penyelesaian perlawanan belum lengkap: {fields}', }, SETTLEMENT_BATCH_NOT_FOUND: { 'zh-CN': '结算批次不存在', 'en-US': 'Settlement batch not found', 'ms-MY': 'Batch penyelesaian tidak dijumpai', }, SETTLEMENT_BATCH_NOT_PREVIEW: { 'zh-CN': '结算批次不在预览状态', 'en-US': 'Batch is not in preview status', 'ms-MY': 'Batch bukan dalam status pratonton', }, SETTLEMENT_BATCH_ALREADY_CONFIRMED: { 'zh-CN': '结算批次已确认', 'en-US': 'Batch already confirmed', 'ms-MY': 'Batch sudah disahkan', }, SCORE_NOT_FOUND: { 'zh-CN': '比分不存在', 'en-US': 'Score not found', 'ms-MY': 'Skor tidak dijumpai', }, PARLAY_UNSETTLED_LEGS: { 'zh-CN': '串关注单 {betId} 仍有未结算场次', 'en-US': 'Parlay bet {betId} has unsettled legs', 'ms-MY': 'Parlay {betId} masih ada pilihan belum selesai', }, RESETTLE_SETTLED_ONLY: { 'zh-CN': '仅已结算赛事可重结算', 'en-US': 'Only settled matches can be resettled', 'ms-MY': 'Hanya perlawanan selesai boleh diselesaikan semula', }, RESETTLE_BATCH_ONLY: { 'zh-CN': '非重结算批次', 'en-US': 'Not a resettle batch', 'ms-MY': 'Bukan batch penyelesaian semula', }, UPLOAD_CATEGORY_UNSUPPORTED: { 'zh-CN': '不支持的上传分类', 'en-US': 'Unsupported upload category', 'ms-MY': 'Kategori muat naik tidak disokong', }, UPLOAD_IMAGE_REQUIRED: { 'zh-CN': '请选择图片文件', 'en-US': 'Image file is required', 'ms-MY': 'Fail imej diperlukan', }, UPLOAD_IMAGE_TYPE_INVALID: { 'zh-CN': '仅支持 PNG、JPG、WEBP、GIF 或 SVG 图片', 'en-US': 'Only PNG, JPG, WEBP, GIF or SVG images are allowed', 'ms-MY': 'Hanya PNG, JPG, WEBP, GIF atau SVG dibenarkan', }, UPLOAD_SVG_UNSAFE: { 'zh-CN': 'SVG 内容不安全,已拒绝', 'en-US': 'Unsafe SVG content is not allowed', 'ms-MY': 'Kandungan SVG tidak selamat', }, DB_RESET_PHRASE_INVALID: { 'zh-CN': '确认短语不正确,请输入 RESET', 'en-US': 'Invalid confirmation phrase; type RESET', 'ms-MY': 'Frasa pengesahan salah; taip RESET', }, IMPORT_MATCHES_REQUIRED: { 'zh-CN': '导入数据无效:需要 matches[]', 'en-US': 'Invalid import payload: matches[] required', 'ms-MY': 'Payload import tidak sah: matches[] diperlukan', }, WC_OUTRIGHT_NOT_FOUND: { 'zh-CN': '未找到 WC2026 冠军盘,请先导入', 'en-US': 'WC2026 outright not found — run import', 'ms-MY': 'Outright WC2026 tidak dijumpai — jalankan import', }, FILE_NOT_FOUND: { 'zh-CN': '文件不存在', 'en-US': 'File not found', 'ms-MY': 'Fail tidak dijumpai', }, URL_REQUIRED: { 'zh-CN': '请提供 url 参数', 'en-US': 'url is required', 'ms-MY': 'url diperlukan', }, CONTENT_TYPE_INVALID: { 'zh-CN': '无效内容类型:{type}', 'en-US': 'Invalid contentType: {type}', 'ms-MY': 'Jenis kandungan tidak sah: {type}', }, CONTENT_STATUS_INVALID: { 'zh-CN': '无效状态:{status}', 'en-US': 'Invalid status: {status}', 'ms-MY': 'Status tidak sah: {status}', }, CONTENT_END_BEFORE_START: { 'zh-CN': '结束时间须晚于开始时间', 'en-US': 'endTime must be after startTime', 'ms-MY': 'endTime mesti selepas startTime', }, CONTENT_TRANSLATION_REQUIRED: { 'zh-CN': '至少填写一条翻译', 'en-US': 'At least one translation required', 'ms-MY': 'Sekurang-kurangnya satu terjemahan diperlukan', }, CONTENT_LOCALE_REQUIRED: { 'zh-CN': '翻译语言不能为空', 'en-US': 'Translation locale required', 'ms-MY': 'Locale terjemahan diperlukan', }, CONTENT_LOCALE_DUPLICATE: { 'zh-CN': '重复的语言:{locale}', 'en-US': 'Duplicate locale: {locale}', 'ms-MY': 'Locale pendua: {locale}', }, CONTENT_LINK_TYPE_INVALID: { 'zh-CN': '无效链接类型:{linkType}', 'en-US': 'Invalid linkType: {linkType}', 'ms-MY': 'Jenis pautan tidak sah: {linkType}', }, CONTENT_LINK_TARGET_REQUIRED: { 'zh-CN': '设置链接类型时须填写 linkTarget', 'en-US': 'linkTarget required when linkType is set', 'ms-MY': 'linkTarget diperlukan apabila linkType ditetapkan', }, CONTENT_NOT_FOUND: { 'zh-CN': '内容不存在', 'en-US': 'Content not found', 'ms-MY': 'Kandungan tidak dijumpai', }, CONTENT_ACTIVE_BANNER_INCOMPLETE: { 'zh-CN': '启用 Banner 须至少一种语言配置图片地址', 'en-US': 'ACTIVE banner requires imageUrl in at least one locale', 'ms-MY': 'Banner aktif memerlukan imageUrl sekurang-kurangnya satu locale', }, CONTENT_ACTIVE_NOTICE_INCOMPLETE: { 'zh-CN': '启用公告须至少一种语言填写标题或正文', 'en-US': 'ACTIVE notice requires title or body in at least one locale', 'ms-MY': 'Notis aktif memerlukan tajuk atau kandungan', }, CONTENT_ACTIVE_TICKER_INCOMPLETE: { 'zh-CN': '启用滚动公告须至少一种语言填写正文', 'en-US': 'ACTIVE ticker requires body in at least one locale', 'ms-MY': 'Ticker aktif memerlukan kandungan', }, INVALID_METHOD_TYPE: { 'zh-CN': '无效的收款方式类型', 'en-US': 'Invalid payment method type', 'ms-MY': 'Jenis kaedah pembayaran tidak sah', }, PAYMENT_METHOD_NOT_FOUND: { 'zh-CN': '收款方式不存在或已停用', 'en-US': 'Payment method not found or inactive', 'ms-MY': 'Kaedah pembayaran tidak dijumpai atau tidak aktif', }, ORDER_NOT_FOUND: { 'zh-CN': '充值订单不存在', 'en-US': 'Deposit order not found', 'ms-MY': 'Pesanan deposit tidak dijumpai', }, ORDER_NOT_PENDING: { 'zh-CN': '订单已被审核或不是待审核状态', 'en-US': 'Order is not in pending status', 'ms-MY': 'Pesanan bukan dalam status menunggu', }, ORDER_ALREADY_PENDING: { 'zh-CN': '订单已是待审核状态', 'en-US': 'Order is already pending review', 'ms-MY': 'Pesanan sudah menunggu semakan', }, ORDER_NOT_APPROVED: { 'zh-CN': '仅已通过的充值订单可撤销', 'en-US': 'Only approved deposit orders can be revoked', 'ms-MY': 'Hanya pesanan deposit yang diluluskan boleh dibatalkan', }, DEPOSIT_REVOKE_WINDOW_EXPIRED: { 'zh-CN': '批准已超过 5 分钟,无法撤回', 'en-US': 'Approval was more than 5 minutes ago; revoke is no longer allowed', 'ms-MY': 'Kelulusan melebihi 5 minit; pembatalan tidak dibenarkan', }, DEPOSIT_REVOKE_SETTLED_BETS: { 'zh-CN': '批准后已有投注,无法直接撤回;请走冲正流程', 'en-US': 'Bets were placed after approval; use an adjustment flow instead', 'ms-MY': 'Terdapat pertaruhan selepas kelulusan; sila gunakan aliran pelarasan', }, DEPOSIT_ORDER_FUNDED_DELETE_FORBIDDEN: { 'zh-CN': '已入账充值订单不能删除,请走撤回或冲正流程', 'en-US': 'Funded deposit orders cannot be deleted; use revoke or adjustment flow', 'ms-MY': 'Pesanan deposit yang telah dikreditkan tidak boleh dipadam; gunakan pembatalan atau pelarasan', }, REASON_REQUIRED: { 'zh-CN': '请填写拒绝原因', 'en-US': 'Rejection reason is required', 'ms-MY': 'Sebab penolakan diperlukan', }, SCREENSHOT_REQUIRED: { 'zh-CN': '请上传转账截图', 'en-US': 'Screenshot is required', 'ms-MY': 'Screenshot diperlukan', }, FILE_MUST_BE_IMAGE: { 'zh-CN': '请上传图片文件', 'en-US': 'File must be an image', 'ms-MY': 'Fail mesti imej', }, INVALID_AMOUNT: { 'zh-CN': '金额无效', 'en-US': 'Invalid amount', 'ms-MY': 'Jumlah tidak sah', }, PAYMENT_METHOD_REQUIRED: { 'zh-CN': '请选择收款方式', 'en-US': 'Payment method is required', 'ms-MY': 'Kaedah pembayaran diperlukan', }, PHONE_REQUIRED: { 'zh-CN': '请填写手机号', 'en-US': 'Phone number is required', 'ms-MY': 'Nombor telefon diperlukan', }, PHONE_INVALID: { 'zh-CN': '手机号格式无效', 'en-US': 'Invalid phone number', 'ms-MY': 'Nombor telefon tidak sah', }, PHONE_TAKEN: { 'zh-CN': '该手机号已注册', 'en-US': 'This phone number is already registered', 'ms-MY': 'Nombor telefon ini sudah didaftarkan', }, PHONE_NOT_REGISTERED: { 'zh-CN': '该手机号未注册', 'en-US': 'This phone number is not registered', 'ms-MY': 'Nombor telefon ini belum didaftarkan', }, SMS_CODE_REQUIRED: { 'zh-CN': '请填写短信验证码', 'en-US': 'SMS verification code is required', 'ms-MY': 'Kod pengesahan SMS diperlukan', }, SMS_CODE_INVALID: { 'zh-CN': '验证码错误', 'en-US': 'Incorrect verification code', 'ms-MY': 'Kod pengesahan salah', }, SMS_CODE_EXPIRED: { 'zh-CN': '验证码已过期,请重新获取', 'en-US': 'Verification code expired, please request a new one', 'ms-MY': 'Kod pengesahan tamat tempoh, sila minta yang baharu', }, SMS_RATE_LIMIT: { 'zh-CN': '发送太频繁,请60秒后再试', 'en-US': 'Too many requests, please try again in 60 seconds', 'ms-MY': 'Terlalu kerap, sila cuba lagi dalam 60 saat', }, SMS_SEND_FAILED: { 'zh-CN': '短信发送失败,请稍后重试', 'en-US': 'Failed to send SMS, please try again later', 'ms-MY': 'Gagal menghantar SMS, sila cuba lagi', }, PHONE_COUNTRY_UNSUPPORTED: { 'zh-CN': '暂不支持该国家/地区', 'en-US': 'This country or region is not supported', 'ms-MY': 'Negara atau wilayah ini tidak disokong', }, }; export function normalizeLocale(input) { const raw = String(input ?? '').trim(); if (!raw) return DEFAULT_LOCALE; const lower = raw.toLowerCase(); if (lower.startsWith('zh')) return 'zh-CN'; if (lower.startsWith('ms') || lower.startsWith('my')) return 'ms-MY'; if (lower.startsWith('en')) return 'en-US'; if (SUPPORTED_LOCALES.includes(raw)) return raw; return DEFAULT_LOCALE; } export function formatApiErrorMessage(code, localeInput, params) { const locale = normalizeLocale(localeInput); const template = API_ERROR_MESSAGES[code]?.[locale] ?? API_ERROR_MESSAGES[code]?.[DEFAULT_LOCALE] ?? API_ERROR_MESSAGES.INTERNAL_SERVER_ERROR[locale]; if (!params) return template; return template.replace(/\{(\w+)\}/g, (_match, key) => String(params[key] ?? `{${key}}`)); } export function isApiErrorCode(value) { return typeof value === 'string' && value in API_ERROR_MESSAGES; }