feat(auth): 集成认证授权功能并优化API客户端

- 实现了完整的登录注册认证流程,包括密码验证和用户资料获取
- 集成了JWT令牌管理和自动刷新机制,支持设备ID生成和管理
- 添加了WebSocket连接配置和API基础URL环境变量设置
- 实现了API客户端的请求拦截器,包括令牌验证和错误处理逻辑
- 集成了MD5加密和认证令牌缓存机制,提升安全性
- 添加了多语言国际化支持,包括英语、中文、马来语和印尼语
- 实现了认证状态管理和本地存储持久化功能
- 添加了表单验证schema和错误处理机制,增强用户体验
This commit is contained in:
JiaJun
2026-05-16 09:03:55 +08:00
parent 6aaf90a6ac
commit 5dd4e31db4
81 changed files with 6086 additions and 627 deletions

View File

@@ -42,6 +42,8 @@ export default {
label: 'Language',
zhCN: '中文',
enUS: 'English',
msMY: 'Bahasa Melayu',
idID: 'Bahasa Indonesia',
},
game: {
metaTitle: 'Game Lobby',
@@ -109,6 +111,59 @@ export default {
'This will later connect to the real announcement body, confirmation checkbox, and persistence flow.',
line2: 'For now it validates the shared modal structure.',
},
modals: {
login: {
title: 'Login',
},
register: {
title: 'Register',
},
notice: {
title: 'Event Notice',
content:
'This area will later load the real event announcement body, rich media, and a longer scrollable message. The current version focuses on shared multilingual modal wiring.',
check: 'View',
},
procedures: {
title: 'Top Up / Withdraw',
contentPlaceholder: 'Choose the action you want to continue with',
withdraw: 'Withdraw',
topup: 'Top Up',
},
autoSetting: {
title: 'Auto Spin',
startAutoSpin: 'Start Auto Spin',
rows: {
stopIfBalanceLowerThan: 'Stop if balance is lower than',
stopIfSingleWinExceeds: 'Stop if a single win exceeds',
stopOnAnyJackpot: 'Stop on any jackpot',
},
},
userInfo: {
title: 'User Info',
tabs: {
profile: 'Profile',
message: 'Messages',
},
profile: {
name: 'Name',
tel: 'Phone',
registeredAt: 'Registered at',
signature:
'My signature is as unique as my personality. This area will later display the real profile summary.',
},
message: {
eventBonus:
'[Top-up Bonus Event] From October 1 to October 7, 2026, claim your rebate rewards...',
check: 'View',
deleteRecords: 'Delete records',
},
},
withdrawTopup: {
applyWithdraw: 'Apply for Withdrawal',
applyTopup: 'Apply for Top Up',
},
},
autoSpin: {
eyebrow: 'Auto spin',
title: 'Auto spin running',
@@ -128,4 +183,234 @@ export default {
maxBet: 'Max bet',
},
},
commonUi: {
modal: {
close: 'Close modal',
defaultAriaLabel: 'Modal',
},
toast: {
lobbyInitFailed: 'Failed to load the game lobby',
loginRequired: 'Please log in before entering the game',
loginSuccess: 'Login successful',
registerSuccess: 'Registration successful',
},
},
auth: {
common: {
arrowIconAlt: 'Arrow',
actions: {
submitting: 'Submitting...',
},
},
login: {
actions: {
submit: 'Log In',
},
fields: {
username: {
label: 'Account / Phone:',
placeholder: 'Enter account or mobile number',
},
password: {
label: 'Password:',
placeholder: 'Enter password',
},
},
footer: {
registerAccount: 'Create account',
forgotPassword: 'Forgot password',
},
errors: {
submitFailed: 'Login failed. Please try again later.',
invalidCredentials: 'Incorrect account or password.',
},
},
register: {
actions: {
submit: 'Register',
},
fields: {
username: {
label: 'Account / Phone:',
placeholder: 'Enter account or mobile number',
},
password: {
label: 'Password:',
placeholder: 'Enter password',
},
confirmPassword: {
label: 'Confirm Password:',
placeholder: 'Re-enter password',
},
inviteCode: {
label: 'Invite Code:',
placeholder: 'Enter invite code',
},
},
footer: {
alreadyHaveAccount: 'Already have an account',
needHelp: 'Need help',
},
errors: {
submitFailed: 'Registration failed. Please try again later.',
unauthorized: 'Registration is not authorized. Please try again later.',
},
},
validation: {
username: {
required: 'Please enter your mobile number.',
invalidPhone: 'Please enter a valid mobile number.',
},
password: {
min: 'Password must be at least 6 characters.',
max: 'Password must be at most 32 characters.',
},
inviteCode: {
required: 'Please enter the invite code.',
max: 'Invite code must be at most 32 characters.',
},
confirmPassword: {
mismatch: 'The two passwords do not match.',
},
},
errors: {
requestFailed: 'Request failed. Please try again later.',
authTokenConfigMissing:
'Authentication configuration is missing. Please contact support.',
timeout: 'Request timed out. Please try again later.',
serviceUnavailable:
'Service is temporarily unavailable. Please try again later.',
},
},
gameDesktop: {
header: {
systemTime: 'System Time',
rules: 'Rules',
message: 'Message',
bgm: 'BGM',
id: 'ID',
fullscreen: 'Full Screen',
login: 'Login',
register: 'Register',
},
control: {
trend: 'Trend',
map: 'Map',
selected: 'Selected',
totalBet: 'Total Bet',
confirm: 'Confirm',
actions: {
clear: 'Clear',
repeat: 'Repeat',
'auto-spin': 'Auto Spin',
},
},
status: {
odds: 'Odds',
streak: 'Streak',
limit: 'Limit',
roundId: 'Round',
phase: {
betting: {
label: 'Open',
description: '(Accepting Bets)',
},
locked: {
label: 'Locked',
description: '(Betting Closed)',
},
revealing: {
label: 'Drawing',
description: '(Revealing Result)',
},
settled: {
label: 'Settled',
description: '(Round Complete)',
},
waiting: {
label: 'Waiting',
description: '(Waiting for Next Round)',
},
},
},
title: {
announcement: 'Announcement',
},
animal: {
loading: 'Loading',
tapToEnter: 'Tap To Enter',
getStart: 'Get Start',
},
history: {
title: 'History',
orderNo: 'Order No.',
roundId: 'Round ID',
numbers: 'Bet Numbers',
settledAt: 'Settled At',
totalPoolAmount: 'Bet Amount',
winningResult: 'Winning Result',
payout: 'Win Amount',
empty: 'No history yet',
end: 'No more records',
loading: 'Loading...',
settled: 'Settled',
},
topup: {
placeholder: 'Top-up content is under construction',
},
mobile: {
placeholder: 'Mobile entry is under construction',
},
withdraw: {
availableBalance: 'Available balance: {{amount}}',
currencySelection: 'Currency selection',
selectCurrency: 'Select currency',
exchangeRateNotice:
'Exchange rates and final payout amounts follow the platform real-time settlement.',
wallet: 'Wallet',
bank: 'Bank',
minimumRm10: 'Minimum RM 10',
processingTime: 'Processing time',
fundsArrivalTime: 'Expected within 1-15 minutes',
feeNotice:
'Please confirm the receiving information carefully. It cannot be changed after submission.',
cancel: 'Cancel',
confirm: 'Confirm',
withdrawal: 'Withdrawal',
fields: {
diamondWithdrawalAmount: 'Diamond Withdrawal Amount',
currencyType: 'Currency Type',
paymentChannel: 'Payment Channel',
bankCode: 'Bank Code',
cardHolderName: 'Card Holder Name',
bankAccountNumber: 'Bank Account Number',
receiverEmail: 'Receiver Email',
receiverPhone: 'Receiver Phone',
},
placeholders: {
cardHolderName: 'Enter card holder name',
bankAccountNumber: 'Enter bank account number',
receiverEmail: 'Enter receiver email',
receiverPhone: 'Enter receiver phone number',
},
errors: {
cardHolderNameRequired: 'Please enter the card holder name.',
bankAccountRequired: 'Please enter the bank account number.',
},
preview: {
title: 'Exchange Preview',
diamondAmount: 'Diamond Amount',
rateMyr: 'MYR Rate',
rateMyrValue: '{{diamonds}} diamonds = 1 MYR',
convertibleMyr: 'Convertible MYR',
usdtMyrRate: 'USDT / MYR Rate',
usdtMyrRateValue: '1 USDT = {{rate}} MYR',
rateVnd: 'VND Rate',
rateVndValue: '1 diamond = {{diamonds}} VND',
convertibleVnd: 'Convertible VND',
convertibleUsdt: 'Convertible USDT',
fixedExchangeDiamondAmount: 'Fixed Exchange Diamond Amount',
},
},
},
} as const

415
src/locales/id-ID/common.ts Normal file
View File

@@ -0,0 +1,415 @@
export default {
nav: {
home: 'Beranda',
game: 'Game',
},
shell: {
eyebrow: '36 Character Flower',
subtitle: 'Frontend game undian real-time untuk mobile dan desktop',
},
notFound: {
eyebrow: '404',
title: 'Halaman yang kamu minta tidak ditemukan.',
description: 'Rute ini tidak ada. Kembali ke halaman utama scaffold.',
home: 'Kembali ke beranda',
},
home: {
eyebrow: 'Shell game sedang dibangun',
title: 'Framework game dual-device 36-character-flower sedang dibangun.',
description:
'Proyek ini sudah melewati tahap scaffold umum. Sekarang strukturnya dibangun dengan rute game bersama, state bersama, serta tampilan mobile dan desktop terpisah untuk pengalaman betting real-time.',
cards: {
routingMode: 'Routing',
dataLayer: 'Model state',
transport: 'Real-time',
auth: 'Produk',
metadata: 'Fokus saat ini',
},
values: {
routingMode: 'URL bersama + tampilan device terpisah',
dataLayer: 'Round / Bet / User / UI / Connection',
transport: 'HTTP + WebSocket',
auth: 'Gameplay live draw 36-grid',
metadata: 'Bangun struktur dulu sebelum polishing state machine',
},
footnote:
'Berikutnya: rute utama game, model bisnis bersama, dan shell halaman mobile serta desktop.',
primaryAction: 'Masuk lobby game',
secondaryAction: 'Lihat struktur proyek',
},
language: {
label: 'Bahasa',
zhCN: '中文',
enUS: 'English',
msMY: 'Bahasa Melayu',
idID: 'Bahasa Indonesia',
},
game: {
metaTitle: 'Lobby Game',
metaDescription: 'Lobby game live 36-character-flower.',
lobbyTitle: 'Lobby 36 Character Flower',
lobbySubtitle:
'Dalam satu rute bisnis bersama, mobile dan desktop memasang tampilan berbeda di atas data dan state game yang sama.',
status: {
roundState: 'Status ronde',
currentRound: 'Ronde saat ini {{id}}',
tablePool: 'Pool meja',
onlineCount: '{{count}} online',
activeChip: 'Chip aktif',
announcementsRead: '{{read}}/{{total}} pengumuman dibaca',
connection: 'Koneksi',
connectionHealthy: 'Sinkronisasi stabil',
connectionRecovering: 'Menunggu pemulihan',
synced: 'Tersinkron',
degraded: 'Menurun',
},
board: {
historyTitle: 'Riwayat ronde',
historySubtitle: 'Jejak undian dan payout terbaru',
trendTitle: 'Radar tren',
trendSubtitle: 'Ringkasan momentum dan miss streak',
stageTitle: 'Panggung undian',
stageSubtitle:
'Panggung ini menampung papan utama dan struktur kontrol sebelum integrasi penuh state machine dan animasi.',
currentPhase: 'Fase saat ini',
selectedBet: 'Bet {{amount}}',
hitCount: '{{count}} hit',
hitBadge: '{{count}}x',
badgeWin: 'Menang',
badgeBet: 'Bet',
cellLabel: 'Sel {{id}}',
winningCell: 'Sel pemenang {{id}}',
missedRounds: 'Miss {{count}} ronde',
rising: 'Naik',
falling: 'Turun',
steady: 'Stabil',
hitTotal: '{{count}} hit',
},
phases: {
betting: 'Betting',
locked: 'Terkunci',
revealing: 'Mengungkap',
settled: 'Selesai',
},
actions: {
unifiedBetHint: 'Bet seragam',
totalBet: 'Total bet',
canBet: 'Bisa bet',
yes: 'Ya',
no: 'Tidak',
quickBet: 'Quick bet 08',
clearPending: 'Hapus pending',
autoModeDemo: 'Demo mode auto',
stopAuto: 'Stop auto',
},
modal: {
eyebrow: 'Pengumuman',
acknowledge: 'Saya paham',
later: 'Nanti',
line1:
'Ini nantinya akan terhubung ke konten pengumuman nyata, checkbox konfirmasi, dan alur penyimpanan status.',
line2: 'Untuk sekarang ini memvalidasi struktur modal bersama.',
},
modals: {
login: {
title: 'Masuk',
},
register: {
title: 'Daftar',
},
notice: {
title: 'Pengumuman Acara',
content:
'Bagian ini nantinya akan memuat konten pengumuman acara yang sebenarnya, materi visual, dan pesan panjang yang dapat digulir. Versi saat ini fokus pada sambungan modal multibahasa.',
check: 'Lihat',
},
procedures: {
title: 'Isi Ulang / Tarik Dana',
contentPlaceholder: 'Pilih tindakan yang ingin kamu lanjutkan',
withdraw: 'Tarik Dana',
topup: 'Isi Ulang',
},
autoSetting: {
title: 'Auto Spin',
startAutoSpin: 'Mulai Auto Spin',
rows: {
stopIfBalanceLowerThan: 'Berhenti jika saldo lebih rendah dari',
stopIfSingleWinExceeds: 'Berhenti jika kemenangan tunggal melebihi',
stopOnAnyJackpot: 'Berhenti pada jackpot apa pun',
},
},
userInfo: {
title: 'Info Pengguna',
tabs: {
profile: 'Profil',
message: 'Pesan',
},
profile: {
name: 'Nama',
tel: 'Telepon',
registeredAt: 'Tanggal daftar',
signature:
'Tanda tanganku seunik diriku. Bagian ini nantinya akan menampilkan ringkasan profil yang sebenarnya.',
},
message: {
eventBonus:
'[Event Bonus Isi Ulang] Dari 1 Oktober hingga 7 Oktober 2026, klaim hadiah rebate kamu...',
check: 'Lihat',
deleteRecords: 'Hapus riwayat',
},
},
withdrawTopup: {
applyWithdraw: 'Ajukan Penarikan',
applyTopup: 'Ajukan Isi Ulang',
},
},
autoSpin: {
eyebrow: 'Auto spin',
title: 'Auto spin berjalan',
description:
'Mode auto akan menutupi board sambil mempertahankan fokus sel target dan progres.',
trailingLabel: 'Input manual terkunci',
},
footer: {
implementationTitle: 'Implementasi saat ini',
implementationSubtitle:
'Iterasi ini memprioritaskan shell dual-device, model bersama, dan wiring bisnis.',
implementationBody:
'Langkah berikutnya adalah API nyata, WebSocket, UI store penuh, dan state machine siklus ronde.',
limitsTitle: 'Batas meja',
limitsSubtitle: 'Berasal dari data mock dashboard',
minBet: 'Bet minimum',
maxBet: 'Bet maksimum',
},
},
commonUi: {
modal: {
close: 'Tutup modal',
defaultAriaLabel: 'Modal',
},
toast: {
lobbyInitFailed: 'Gagal memuat lobby game',
loginRequired: 'Silakan masuk sebelum memasuki game',
loginSuccess: 'Berhasil masuk',
registerSuccess: 'Pendaftaran berhasil',
},
},
auth: {
common: {
arrowIconAlt: 'Panah',
actions: {
submitting: 'Mengirim...',
},
},
login: {
actions: {
submit: 'Masuk',
},
fields: {
username: {
label: 'Akun / Telepon:',
placeholder: 'Masukkan akun atau nomor ponsel',
},
password: {
label: 'Kata Sandi:',
placeholder: 'Masukkan kata sandi',
},
},
footer: {
registerAccount: 'Daftar akun',
forgotPassword: 'Lupa kata sandi',
},
errors: {
submitFailed: 'Login gagal. Silakan coba lagi nanti.',
invalidCredentials: 'Akun atau kata sandi salah.',
},
},
register: {
actions: {
submit: 'Daftar',
},
fields: {
username: {
label: 'Akun / Telepon:',
placeholder: 'Masukkan akun atau nomor ponsel',
},
password: {
label: 'Kata Sandi:',
placeholder: 'Masukkan kata sandi',
},
confirmPassword: {
label: 'Konfirmasi Kata Sandi:',
placeholder: 'Masukkan ulang kata sandi',
},
inviteCode: {
label: 'Kode Undangan:',
placeholder: 'Masukkan kode undangan',
},
},
footer: {
alreadyHaveAccount: 'Sudah punya akun',
needHelp: 'Butuh bantuan',
},
errors: {
submitFailed: 'Pendaftaran gagal. Silakan coba lagi nanti.',
unauthorized: 'Pendaftaran tidak diizinkan. Silakan coba lagi nanti.',
},
},
validation: {
username: {
required: 'Silakan masukkan nomor ponsel.',
invalidPhone: 'Silakan masukkan nomor ponsel yang valid.',
},
password: {
min: 'Kata sandi minimal 6 karakter.',
max: 'Kata sandi maksimal 32 karakter.',
},
inviteCode: {
required: 'Silakan masukkan kode undangan.',
max: 'Kode undangan maksimal 32 karakter.',
},
confirmPassword: {
mismatch: 'Kedua kata sandi tidak sama.',
},
},
errors: {
requestFailed: 'Permintaan gagal. Silakan coba lagi nanti.',
authTokenConfigMissing:
'Konfigurasi autentikasi tidak ada. Silakan hubungi dukungan.',
timeout: 'Permintaan habis waktu. Silakan coba lagi nanti.',
serviceUnavailable:
'Layanan sedang tidak tersedia. Silakan coba lagi nanti.',
},
},
gameDesktop: {
header: {
systemTime: 'Waktu Sistem',
rules: 'Aturan',
message: 'Pesan',
bgm: 'BGM',
id: 'ID',
fullscreen: 'Layar Penuh',
login: 'Masuk',
register: 'Daftar',
},
control: {
trend: 'Tren',
map: 'Peta',
selected: 'Dipilih',
totalBet: 'Total Bet',
confirm: 'Konfirmasi',
actions: {
clear: 'Hapus',
repeat: 'Ulang',
'auto-spin': 'Auto Spin',
},
},
status: {
odds: 'Odds',
streak: 'Streak',
limit: 'Batas',
roundId: 'Ronde',
phase: {
betting: {
label: 'Buka',
description: '(Menerima Bet)',
},
locked: {
label: 'Terkunci',
description: '(Bet Ditutup)',
},
revealing: {
label: 'Drawing',
description: '(Mengungkap Hasil)',
},
settled: {
label: 'Selesai',
description: '(Ronde Selesai)',
},
waiting: {
label: 'Menunggu',
description: '(Menunggu Ronde Berikutnya)',
},
},
},
title: {
announcement: 'Pengumuman',
},
animal: {
loading: 'Memuat',
tapToEnter: 'Ketuk Untuk Masuk',
getStart: 'Mulai',
},
history: {
title: 'Riwayat',
orderNo: 'No. Order',
roundId: 'ID Ronde',
numbers: 'Nomor Taruhan',
settledAt: 'Waktu Selesai',
totalPoolAmount: 'Jumlah Taruhan',
winningResult: 'Hasil Menang',
payout: 'Jumlah Menang',
empty: 'Belum ada riwayat',
end: 'Tidak ada catatan lagi',
loading: 'Memuat...',
settled: 'Selesai',
},
topup: {
placeholder: 'Konten isi ulang sedang dibangun',
},
mobile: {
placeholder: 'Halaman mobile sedang dibangun',
},
withdraw: {
availableBalance: 'Saldo tersedia: {{amount}}',
currencySelection: 'Pilihan mata uang',
selectCurrency: 'Pilih mata uang',
exchangeRateNotice:
'Kurs dan jumlah akhir mengikuti penyelesaian real-time platform.',
wallet: 'Dompet',
bank: 'Bank',
minimumRm10: 'Minimum RM 10',
processingTime: 'Waktu proses',
fundsArrivalTime: 'Diperkirakan masuk dalam 1-15 menit',
feeNotice:
'Pastikan informasi penerima benar. Data tidak dapat diubah setelah dikirim.',
cancel: 'Batal',
confirm: 'Konfirmasi',
withdrawal: 'Penarikan',
fields: {
diamondWithdrawalAmount: 'Jumlah Berlian Ditarik',
currencyType: 'Jenis Mata Uang',
paymentChannel: 'Saluran Pembayaran',
bankCode: 'Kode Bank',
cardHolderName: 'Nama Pemilik Rekening',
bankAccountNumber: 'Nomor Rekening Bank',
receiverEmail: 'Email Penerima',
receiverPhone: 'Telepon Penerima',
},
placeholders: {
cardHolderName: 'Masukkan nama pemilik rekening',
bankAccountNumber: 'Masukkan nomor rekening bank',
receiverEmail: 'Masukkan email penerima',
receiverPhone: 'Masukkan nomor telepon penerima',
},
errors: {
cardHolderNameRequired: 'Silakan masukkan nama pemilik rekening.',
bankAccountRequired: 'Silakan masukkan nomor rekening bank.',
},
preview: {
title: 'Pratinjau Penukaran',
diamondAmount: 'Jumlah Berlian',
rateMyr: 'Kurs MYR',
rateMyrValue: '{{diamonds}} berlian = 1 MYR',
convertibleMyr: 'Bisa Ditukar ke MYR',
usdtMyrRate: 'Kurs USDT / MYR',
usdtMyrRateValue: '1 USDT = {{rate}} MYR',
rateVnd: 'Kurs VND',
rateVndValue: '1 berlian = {{diamonds}} VND',
convertibleVnd: 'Bisa Ditukar ke VND',
convertibleUsdt: 'Bisa Ditukar ke USDT',
fixedExchangeDiamondAmount: 'Jumlah Berlian Tukar Tetap',
},
},
},
} as const

418
src/locales/ms-MY/common.ts Normal file
View File

@@ -0,0 +1,418 @@
export default {
nav: {
home: 'Laman Utama',
game: 'Permainan',
},
shell: {
eyebrow: '36 Character Flower',
subtitle:
'Antara muka permainan cabutan masa nyata untuk mudah alih dan desktop',
},
notFound: {
eyebrow: '404',
title: 'Halaman yang anda minta tidak ditemui.',
description:
'Laluan ini tidak wujud. Kembali ke halaman utama rangka kerja.',
home: 'Kembali ke utama',
},
home: {
eyebrow: 'Rangka permainan sedang dibina',
title:
'Rangka permainan dwi-peranti 36-character-flower sedang dibangunkan.',
description:
'Projek ini telah melepasi peringkat rangka asas. Kini ia disusun dengan laluan permainan dikongsi, keadaan dikongsi, serta paparan berasingan untuk mudah alih dan desktop bagi pengalaman pertaruhan masa nyata.',
cards: {
routingMode: 'Laluan',
dataLayer: 'Model keadaan',
transport: 'Masa nyata',
auth: 'Produk',
metadata: 'Fokus semasa',
},
values: {
routingMode: 'URL dikongsi + paparan peranti berasingan',
dataLayer: 'Round / Bet / User / UI / Connection',
transport: 'HTTP + WebSocket',
auth: 'Permainan cabutan langsung grid 36',
metadata: 'Bina struktur dahulu sebelum kemasan state machine',
},
footnote:
'Seterusnya: laluan utama permainan, model perniagaan dikongsi, dan rangka halaman mudah alih serta desktop.',
primaryAction: 'Masuk lobi permainan',
secondaryAction: 'Lihat struktur projek',
},
language: {
label: 'Bahasa',
zhCN: '中文',
enUS: 'English',
msMY: 'Bahasa Melayu',
idID: 'Bahasa Indonesia',
},
game: {
metaTitle: 'Lobi Permainan',
metaDescription: 'Lobi permainan langsung 36-character-flower.',
lobbyTitle: 'Lobi 36 Character Flower',
lobbySubtitle:
'Di bawah satu laluan perniagaan yang dikongsi, mudah alih dan desktop memaparkan antara muka berbeza di atas data dan keadaan permainan yang sama.',
status: {
roundState: 'Keadaan pusingan',
currentRound: 'Pusingan semasa {{id}}',
tablePool: 'Dana meja',
onlineCount: '{{count}} dalam talian',
activeChip: 'Cip aktif',
announcementsRead: '{{read}}/{{total}} pengumuman dibaca',
connection: 'Sambungan',
connectionHealthy: 'Penyegerakan stabil',
connectionRecovering: 'Menunggu pemulihan',
synced: 'Disegerakkan',
degraded: 'Terganggu',
},
board: {
historyTitle: 'Sejarah pusingan',
historySubtitle: 'Rekod cabutan dan pembayaran terkini',
trendTitle: 'Radar trend',
trendSubtitle: 'Ringkasan momentum dan kekerapan miss',
stageTitle: 'Pentas cabutan',
stageSubtitle:
'Pentas ini memuatkan papan utama dan struktur kawalan sebelum integrasi penuh state machine serta animasi.',
currentPhase: 'Fasa semasa',
selectedBet: 'Pertaruhan {{amount}}',
hitCount: '{{count}} kena',
hitBadge: '{{count}}x',
badgeWin: 'Menang',
badgeBet: 'Taruhan',
cellLabel: 'Sel {{id}}',
winningCell: 'Sel menang {{id}}',
missedRounds: 'Terlepas {{count}} pusingan',
rising: 'Meningkat',
falling: 'Menurun',
steady: 'Stabil',
hitTotal: '{{count}} kena',
},
phases: {
betting: 'Taruhan',
locked: 'Dikunci',
revealing: 'Cabutan',
settled: 'Selesai',
},
actions: {
unifiedBetHint: 'Taruhan seragam',
totalBet: 'Jumlah taruhan',
canBet: 'Boleh taruhan',
yes: 'Ya',
no: 'Tidak',
quickBet: 'Taruhan cepat 08',
clearPending: 'Kosongkan belum sah',
autoModeDemo: 'Demo mod auto',
stopAuto: 'Henti auto',
},
modal: {
eyebrow: 'Pengumuman',
acknowledge: 'Faham',
later: 'Nanti',
line1:
'Ini akan disambungkan kepada kandungan pengumuman sebenar, kotak pengesahan, dan aliran penyimpanan status.',
line2: 'Buat masa ini, ia mengesahkan struktur modal yang dikongsi.',
},
modals: {
login: {
title: 'Log Masuk',
},
register: {
title: 'Daftar',
},
notice: {
title: 'Notis Acara',
content:
'Bahagian ini akan memuatkan kandungan notis acara sebenar, bahan visual, dan mesej boleh skrol yang lebih panjang. Versi semasa memfokuskan sambungan modal pelbagai bahasa.',
check: 'Semak',
},
procedures: {
title: 'Tambah Nilai / Pengeluaran',
contentPlaceholder: 'Pilih tindakan yang ingin anda teruskan',
withdraw: 'Keluarkan',
topup: 'Tambah Nilai',
},
autoSetting: {
title: 'Putaran Auto',
startAutoSpin: 'Mula Putaran Auto',
rows: {
stopIfBalanceLowerThan: 'Henti jika baki lebih rendah daripada',
stopIfSingleWinExceeds: 'Henti jika kemenangan tunggal melebihi',
stopOnAnyJackpot: 'Henti pada sebarang jackpot',
},
},
userInfo: {
title: 'Maklumat Pengguna',
tabs: {
profile: 'Profil',
message: 'Mesej',
},
profile: {
name: 'Nama',
tel: 'Telefon',
registeredAt: 'Tarikh daftar',
signature:
'Tandatangan saya unik seperti personaliti saya. Bahagian ini akan memaparkan ringkasan profil sebenar kemudian.',
},
message: {
eventBonus:
'[Acara Bonus Tambah Nilai] Dari 1 Oktober hingga 7 Oktober 2026, tuntut ganjaran rebat anda...',
check: 'Semak',
deleteRecords: 'Padam rekod',
},
},
withdrawTopup: {
applyWithdraw: 'Mohon Pengeluaran',
applyTopup: 'Mohon Tambah Nilai',
},
},
autoSpin: {
eyebrow: 'Putaran auto',
title: 'Putaran auto sedang berjalan',
description:
'Mod auto akan menutup papan sambil mengekalkan fokus sel sasaran dan kemajuan.',
trailingLabel: 'Input manual dikunci',
},
footer: {
implementationTitle: 'Pelaksanaan semasa',
implementationSubtitle:
'Iterasi ini mengutamakan shell dwi-peranti, model dikongsi, dan sambungan logik perniagaan.',
implementationBody:
'Langkah seterusnya ialah API sebenar, WebSocket, UI store penuh, dan state machine kitaran pusingan.',
limitsTitle: 'Had meja',
limitsSubtitle: 'Diambil daripada data mock dashboard',
minBet: 'Taruhan minimum',
maxBet: 'Taruhan maksimum',
},
},
commonUi: {
modal: {
close: 'Tutup modal',
defaultAriaLabel: 'Modal',
},
toast: {
lobbyInitFailed: 'Gagal memuatkan lobi permainan',
loginRequired: 'Sila log masuk sebelum memasuki permainan',
loginSuccess: 'Log masuk berjaya',
registerSuccess: 'Pendaftaran berjaya',
},
},
auth: {
common: {
arrowIconAlt: 'Anak panah',
actions: {
submitting: 'Menghantar...',
},
},
login: {
actions: {
submit: 'Log Masuk',
},
fields: {
username: {
label: 'Akaun / Telefon:',
placeholder: 'Masukkan akaun atau nombor telefon',
},
password: {
label: 'Kata Laluan:',
placeholder: 'Masukkan kata laluan',
},
},
footer: {
registerAccount: 'Daftar akaun',
forgotPassword: 'Lupa kata laluan',
},
errors: {
submitFailed: 'Log masuk gagal. Sila cuba lagi kemudian.',
invalidCredentials: 'Akaun atau kata laluan tidak betul.',
},
},
register: {
actions: {
submit: 'Daftar',
},
fields: {
username: {
label: 'Akaun / Telefon:',
placeholder: 'Masukkan akaun atau nombor telefon',
},
password: {
label: 'Kata Laluan:',
placeholder: 'Masukkan kata laluan',
},
confirmPassword: {
label: 'Sahkan Kata Laluan:',
placeholder: 'Masukkan semula kata laluan',
},
inviteCode: {
label: 'Kod Jemputan:',
placeholder: 'Masukkan kod jemputan',
},
},
footer: {
alreadyHaveAccount: 'Sudah ada akaun',
needHelp: 'Perlukan bantuan',
},
errors: {
submitFailed: 'Pendaftaran gagal. Sila cuba lagi kemudian.',
unauthorized: 'Pendaftaran tidak dibenarkan. Sila cuba lagi kemudian.',
},
},
validation: {
username: {
required: 'Sila masukkan nombor telefon anda.',
invalidPhone: 'Sila masukkan nombor telefon yang sah.',
},
password: {
min: 'Kata laluan mesti sekurang-kurangnya 6 aksara.',
max: 'Kata laluan mesti maksimum 32 aksara.',
},
inviteCode: {
required: 'Sila masukkan kod jemputan.',
max: 'Kod jemputan mesti maksimum 32 aksara.',
},
confirmPassword: {
mismatch: 'Kedua-dua kata laluan tidak sepadan.',
},
},
errors: {
requestFailed: 'Permintaan gagal. Sila cuba lagi kemudian.',
authTokenConfigMissing:
'Konfigurasi pengesahan tiada. Sila hubungi sokongan.',
timeout: 'Permintaan tamat masa. Sila cuba lagi kemudian.',
serviceUnavailable:
'Perkhidmatan tidak tersedia buat sementara waktu. Sila cuba lagi kemudian.',
},
},
gameDesktop: {
header: {
systemTime: 'Masa Sistem',
rules: 'Peraturan',
message: 'Mesej',
bgm: 'BGM',
id: 'ID',
fullscreen: 'Skrin Penuh',
login: 'Log Masuk',
register: 'Daftar',
},
control: {
trend: 'Trend',
map: 'Peta',
selected: 'Dipilih',
totalBet: 'Jumlah Taruhan',
confirm: 'Sahkan',
actions: {
clear: 'Kosongkan',
repeat: 'Ulang',
'auto-spin': 'Putaran Auto',
},
},
status: {
odds: 'Peluang',
streak: 'Streak',
limit: 'Had',
roundId: 'Pusingan',
phase: {
betting: {
label: 'Buka',
description: '(Menerima Taruhan)',
},
locked: {
label: 'Dikunci',
description: '(Taruhan Ditutup)',
},
revealing: {
label: 'Cabutan',
description: '(Mendedahkan Hasil)',
},
settled: {
label: 'Selesai',
description: '(Pusingan Tamat)',
},
waiting: {
label: 'Menunggu',
description: '(Menunggu Pusingan Seterusnya)',
},
},
},
title: {
announcement: 'Pengumuman',
},
animal: {
loading: 'Memuatkan',
tapToEnter: 'Ketik Untuk Masuk',
getStart: 'Mula',
},
history: {
title: 'Sejarah',
orderNo: 'No. Pesanan',
roundId: 'ID Pusingan',
numbers: 'Nombor Pertaruhan',
settledAt: 'Masa Selesai',
totalPoolAmount: 'Jumlah Pertaruhan',
winningResult: 'Keputusan Menang',
payout: 'Jumlah Menang',
empty: 'Belum ada sejarah',
end: 'Tiada lagi rekod',
loading: 'Memuatkan...',
settled: 'Selesai',
},
topup: {
placeholder: 'Kandungan tambah nilai sedang dibina',
},
mobile: {
placeholder: 'Halaman mudah alih sedang dibina',
},
withdraw: {
availableBalance: 'Baki tersedia: {{amount}}',
currencySelection: 'Pilihan mata wang',
selectCurrency: 'Pilih mata wang',
exchangeRateNotice:
'Kadar pertukaran dan jumlah akhir tertakluk kepada penyelesaian masa nyata platform.',
wallet: 'Dompet',
bank: 'Bank',
minimumRm10: 'Minimum RM 10',
processingTime: 'Masa pemprosesan',
fundsArrivalTime: 'Dijangka tiba dalam 1-15 minit',
feeNotice:
'Sila pastikan maklumat penerima adalah tepat. Ia tidak boleh diubah selepas dihantar.',
cancel: 'Batal',
confirm: 'Sahkan',
withdrawal: 'Pengeluaran',
fields: {
diamondWithdrawalAmount: 'Jumlah Berlian Dikeluarkan',
currencyType: 'Jenis Mata Wang',
paymentChannel: 'Saluran Pembayaran',
bankCode: 'Kod Bank',
cardHolderName: 'Nama Pemegang Kad',
bankAccountNumber: 'Nombor Akaun Bank',
receiverEmail: 'E-mel Penerima',
receiverPhone: 'Telefon Penerima',
},
placeholders: {
cardHolderName: 'Masukkan nama pemegang kad',
bankAccountNumber: 'Masukkan nombor akaun bank',
receiverEmail: 'Masukkan e-mel penerima',
receiverPhone: 'Masukkan nombor telefon penerima',
},
errors: {
cardHolderNameRequired: 'Sila masukkan nama pemegang kad.',
bankAccountRequired: 'Sila masukkan nombor akaun bank.',
},
preview: {
title: 'Pratonton Pertukaran',
diamondAmount: 'Jumlah Berlian',
rateMyr: 'Kadar MYR',
rateMyrValue: '{{diamonds}} berlian = 1 MYR',
convertibleMyr: 'Boleh Tukar MYR',
usdtMyrRate: 'Kadar USDT / MYR',
usdtMyrRateValue: '1 USDT = {{rate}} MYR',
rateVnd: 'Kadar VND',
rateVndValue: '1 berlian = {{diamonds}} VND',
convertibleVnd: 'Boleh Tukar VND',
convertibleUsdt: 'Boleh Tukar USDT',
fixedExchangeDiamondAmount: 'Jumlah Berlian Tukaran Tetap',
},
},
},
} as const

View File

@@ -41,6 +41,8 @@ export default {
label: '语言',
zhCN: '中文',
enUS: 'English',
msMY: 'Bahasa Melayu',
idID: 'Bahasa Indonesia',
},
game: {
metaTitle: '游戏大厅',
@@ -106,6 +108,57 @@ export default {
line1: '这里后续会接真实公告图文、勾选确认和已读状态。',
line2: '当前先用共享弹窗骨架验证结构。',
},
modals: {
login: {
title: '登录',
},
register: {
title: '注册',
},
notice: {
title: '活动公告',
content:
'这里后续将接入真实的活动公告内容、图文素材和滚动阅读区域。当前版本先用多语言结构完成桌面端公告弹窗能力。',
check: '查看',
},
procedures: {
title: '充值 / 提现',
contentPlaceholder: '请选择你要进行的操作',
withdraw: '提现',
topup: '充值',
},
autoSetting: {
title: '自动托管',
startAutoSpin: '开始自动托管',
rows: {
stopIfBalanceLowerThan: '余额低于时停止',
stopIfSingleWinExceeds: '单次盈利超过时停止',
stopOnAnyJackpot: '出现任意 Jackpot 时停止',
},
},
userInfo: {
title: '用户信息',
tabs: {
profile: '个人信息',
message: '站内消息',
},
profile: {
name: '姓名',
tel: '电话',
registeredAt: '注册时间',
signature: '我的个性签名和灵魂一样独特,后续这里会接真实资料字段。',
},
message: {
eventBonus: '[充值活动] 10 月 1 日至 10 月 7 日期间可获得返利奖励……',
check: '查看',
deleteRecords: '删除记录',
},
},
withdrawTopup: {
applyWithdraw: '申请提现',
applyTopup: '申请充值',
},
},
autoSpin: {
eyebrow: '自动托管',
title: '自动托管运行中',
@@ -124,4 +177,230 @@ export default {
maxBet: '最高下注',
},
},
commonUi: {
modal: {
close: '关闭弹窗',
defaultAriaLabel: '弹窗',
},
toast: {
lobbyInitFailed: '游戏大厅加载失败',
loginRequired: '请先登录后进入游戏',
loginSuccess: '登录成功',
registerSuccess: '注册成功',
},
},
auth: {
common: {
arrowIconAlt: '箭头',
actions: {
submitting: '提交中...',
},
},
login: {
actions: {
submit: '登录',
},
fields: {
username: {
label: '账号/电话:',
placeholder: '请输入账号或手机号',
},
password: {
label: '密码:',
placeholder: '请输入密码',
},
},
footer: {
registerAccount: '注册账号',
forgotPassword: '忘记密码',
},
errors: {
submitFailed: '登录失败,请稍后重试',
invalidCredentials: '账号或密码错误',
},
},
register: {
actions: {
submit: '注册',
},
fields: {
username: {
label: '账号/电话:',
placeholder: '请输入账号或手机号',
},
password: {
label: '密码:',
placeholder: '请输入密码',
},
confirmPassword: {
label: '确认密码:',
placeholder: '请再次输入密码',
},
inviteCode: {
label: '邀请码:',
placeholder: '请输入邀请码',
},
},
footer: {
alreadyHaveAccount: '已有账号',
needHelp: '需要帮助',
},
errors: {
submitFailed: '注册失败,请稍后重试',
unauthorized: '注册未授权,请稍后重试',
},
},
validation: {
username: {
required: '请输入手机号',
invalidPhone: '请输入正确的手机号',
},
password: {
min: '密码至少 6 位',
max: '密码最多 32 位',
},
inviteCode: {
required: '请输入邀请码',
max: '邀请码最多 32 位',
},
confirmPassword: {
mismatch: '两次输入的密码不一致',
},
},
errors: {
requestFailed: '请求失败,请稍后重试',
authTokenConfigMissing: '认证配置缺失,请联系管理员',
timeout: '请求超时,请稍后重试',
serviceUnavailable: '服务暂不可用,请稍后重试',
},
},
gameDesktop: {
header: {
systemTime: '系统时间',
rules: '规则',
message: '消息',
bgm: '音乐',
id: '编号',
fullscreen: '全屏',
login: '登录',
register: '注册',
},
control: {
trend: '走势',
map: '地图',
selected: '已选',
totalBet: '总下注',
confirm: '确认',
actions: {
clear: '清空',
repeat: '重复',
'auto-spin': '自动托管',
},
},
status: {
odds: '赔率',
streak: '连中',
limit: '限额',
roundId: '期号',
phase: {
betting: {
label: '下注中',
description: '(接受下注)',
},
locked: {
label: '已封盘',
description: '(停止下注)',
},
revealing: {
label: '开奖中',
description: '(正在开奖)',
},
settled: {
label: '已结算',
description: '(本轮结束)',
},
waiting: {
label: '等待中',
description: '(等待下一轮)',
},
},
},
title: {
announcement: '公告栏',
},
animal: {
loading: '加载中',
tapToEnter: '点击进入',
getStart: '开始游戏',
},
history: {
title: '历史记录',
orderNo: '订单号',
roundId: '期号',
numbers: '下注号码',
settledAt: '结算时间',
totalPoolAmount: '下注金额',
winningResult: '中奖字花',
payout: '中奖金额',
empty: '暂无历史记录',
end: '没有更多记录了',
loading: '加载中...',
settled: '已结算',
},
topup: {
placeholder: '充值内容建设中',
},
mobile: {
placeholder: '移动端页面建设中',
},
withdraw: {
availableBalance: '可用余额:{{amount}}',
currencySelection: '币种选择',
selectCurrency: '请选择币种',
exchangeRateNotice: '汇率与到账金额以平台实时结算为准。',
wallet: '钱包',
bank: '银行卡',
minimumRm10: '最低 RM 10',
processingTime: '处理时间',
fundsArrivalTime: '预计 1-15 分钟到账',
feeNotice: '请确认收款信息准确无误,提交后不可修改。',
cancel: '取消',
confirm: '确认',
withdrawal: '提现',
fields: {
diamondWithdrawalAmount: '提取钻石数量',
currencyType: '币种类型',
paymentChannel: '付款渠道',
bankCode: '银行代码',
cardHolderName: '持卡人姓名',
bankAccountNumber: '银行账号',
receiverEmail: '收款邮箱',
receiverPhone: '收款手机',
},
placeholders: {
cardHolderName: '请输入持卡人姓名',
bankAccountNumber: '请输入银行账号',
receiverEmail: '请输入收款邮箱',
receiverPhone: '请输入收款手机号',
},
errors: {
cardHolderNameRequired: '请输入持卡人姓名',
bankAccountRequired: '请输入银行账号',
},
preview: {
title: '兑换预览',
diamondAmount: '钻石数量',
rateMyr: '马币汇率',
rateMyrValue: '{{diamonds}} 钻石 = 1 MYR',
convertibleMyr: '可兑换 MYR',
usdtMyrRate: 'USDT / MYR 汇率',
usdtMyrRateValue: '1 USDT = {{rate}} MYR',
rateVnd: '越南盾汇率',
rateVndValue: '1 钻石 = {{diamonds}} VND',
convertibleVnd: '可兑换 VND',
convertibleUsdt: '可兑换 USDT',
fixedExchangeDiamondAmount: '固定兑换钻石金额',
},
},
},
} as const