refactor: 完成全站国际化改造,统一多语言支持

此提交完成了全项目的国际化适配:
1. 新增多语言翻译文件与基础配置
2. 替换所有硬编码文本为i18n调用
3. 优化语言切换与文档语言同步逻辑
4. 重构部分业务逻辑以支持动态翻译
5. 移除过时代码与硬编码配置
This commit is contained in:
2026-05-15 10:41:14 +08:00
parent ac612cb32c
commit f2c7f5e4f1
53 changed files with 2179 additions and 767 deletions

View File

@@ -0,0 +1,397 @@
{
"brand": {
"name": "N lotto"
},
"actions": {
"retry": "Retry",
"refresh": "Refresh",
"apply": "Apply",
"clear": "Clear",
"cancel": "Cancel",
"confirm": "Confirm",
"loading": "Loading...",
"loadMore": "Load More",
"processing": "Processing...",
"deleteRow": "Delete row {{row}}"
},
"nav": {
"aria": "Main navigation",
"home": "Home",
"results": "Results",
"orders": "My Bets",
"wallet": "Wallet"
},
"panel": {
"home": "Home"
},
"network": {
"offline": "Network disconnected. Please check your connection.",
"degraded": "Network is unstable. Switched to fallback mode (polling...).",
"retryReconnect": "Try reconnecting",
"retry": "Retry",
"recoverWebsocket": "Try restoring WebSocket connection",
"recover": "Recover",
"reconnect": "Reconnect"
},
"token": {
"critical": "Login will expire soon",
"warning": "Login expiring soon",
"remaining": "Remaining {{time}}",
"autoRenewing": "Auto-renewing...",
"renewNow": "Renew now"
},
"serverError": {
"title": "Server error",
"appTitle": "Application error",
"unavailable": "Server temporarily unavailable",
"serverMessage": "Server temporarily unavailable. Please try again later.",
"appMessage": "Sorry, the application encountered a problem. Try refreshing the page.",
"details": "Error details:",
"errorId": "Error ID: {{id}}",
"refreshPage": "Refresh page",
"hall": "Betting hall",
"wallet": "My wallet",
"code": "Error code: 500 Internal Server Error",
"home": "Back home"
},
"notFound": {
"title": "Page not found",
"description": "Sorry, the page you visited may have been deleted, moved, or never existed.",
"home": "Back home",
"hall": "Betting hall",
"results": "Results",
"wallet": "My wallet"
},
"player": {
"fallback": "Player #{{id}}"
},
"draw": {
"currentIssue": "Current issue",
"currentTime": "Current Time",
"closesIn": "Closes In",
"drawsIn": "Draws In",
"coolDown": "Cool Down",
"loadFailedRefresh": "Failed to load. Pull down to refresh.",
"issueNo": "Issue No.",
"hall": "Betting Hall",
"noIssue": "No available issue. Please try again later.",
"sealedNotice": "Closed. Please wait for the next issue.",
"status": {
"pending": "Not started",
"open": "Open",
"closing": "Closed",
"closed": "Awaiting draw",
"drawing": "Drawing",
"review": "Under review",
"cooldown": "Cool down",
"settling": "Settling",
"settled": "Settled",
"cancelled": "Cancelled"
}
},
"hall": {
"aria": "Betting table",
"loadingError": "Failed to load play rules. Please try again later.",
"noDraw": "No current issue. Cannot submit.",
"notBettable": "This issue is closed or cannot accept bets.",
"catalogNotReady": "Play configuration is still loading.",
"emptyLines": "Please enter at least one valid number and stake amount.",
"previewFailed": "Preview failed",
"closedSubmit": "Closed. Cannot submit.",
"changedBeforeSubmit": "Your draft changed before submission. Close the preview and try again.",
"placeFailed": "Submission failed",
"placeSuccess": "Bet submitted. Order {{orderNo}}, deducted {{amount}}.",
"closed": {
"title": "Closed",
"subtitle": "This issue is now closed.",
"description": "The betting window has closed. Please wait for the next issue to place your bets."
},
"boxMode": {
"iboxTitle": "iBox",
"iboxDesc": "Divide all by amount",
"boxTitle": "Box",
"boxDesc": "Multiply all by amount"
},
"table": {
"no": "No.",
"number": "Number",
"stake": "Stake Amount",
"rebate": "Commission / Rebate",
"actual": "Actual Deduction",
"delete": "Delete",
"addRow": "Add Row",
"draftTotal": "Draft Total",
"sealedHint": "Closed: this table is locked. Please wait for the next issue.",
"previewing": "Previewing...",
"submitBet": "Submit Bet"
},
"preview": {
"title": "Confirm bet",
"description": "Check the number, play type, and actual deduction. After confirmation, the lottery wallet will be debited and the ticket cannot be cancelled.",
"sealedTitle": "Closed",
"sealedDescription": "This issue has stopped accepting tickets. Please choose the next issue.",
"empty": "No preview data",
"draw": "Issue",
"status": "Status",
"totalBet": "Total stake",
"rebateDeduct": "Rebate deduction",
"actualDeduct": "Actual deduction",
"estimatedPayout": "Estimated max payout",
"lines": "Ticket lines",
"normalizedNumber": "Normalized number",
"combinationCount": "Combinations",
"actual": "Actual",
"estimatedMax": "Estimated max",
"backEdit": "Back to edit",
"submitting": "Submitting...",
"confirmSubmit": "Confirm submit",
"warningsTitle": "Payout pool warning",
"warningsDescription": "The following numbers have high payout pool usage for this issue. Betting is still allowed, but the order may be rejected as sold out if capacity is insufficient."
},
"amountInput": {
"limit": "Limit {{min}} - {{max}}",
"placeholder": "e.g. 100.00"
},
"numberInput": {
"rollPlaceholder": "e.g. 12R4",
"digitPlaceholder": "0-9"
},
"playSwitcher": {
"empty": "No open play types are available for this currency.",
"label": "Play"
},
"playCatalog": {
"notReady": "Play configuration has not been initialized. Run the seed that includes OperationalConfigV1Seeder in Laravel.",
"loadFailed": "Failed to load play configuration. Please try again later.",
"loading": "Loading plays and odds...",
"meta": "Currency {{currency}} · Config versions play#{{playVersion}} / odds#{{oddsVersion}} · Limits use the smallest currency unit, same as wallet.",
"play": "Play",
"status": "Status",
"limit": "Bet limit",
"odds": "Odds x",
"description": "Description",
"open": "Open",
"closed": "Closed",
"riskTitle": "Risk cap (sample numbers)",
"number": "Number",
"capAmount": "Cap amount",
"type": "Type",
"title": "Plays and Odds",
"descriptionText": "Data comes from GET /api/v1/play/effective. After admin changes are published, it refreshes automatically within about {{seconds}}s, or manually.",
"refresh": "Refresh config"
},
"ticketError": {
"4001": "This number has insufficient payout pool capacity for this issue and is sold out. Change the number, amount, or play type and try again.",
"1001": "Insufficient balance. Transfer in before betting.",
"2001": "This issue is closed. Betting is no longer available.",
"2002": "This play type is closed. Choose another play type.",
"2004": "The number format or length does not match this play type.",
"2005": "Play parameters are incomplete, such as digit slot or dimension.",
"2006": "This issue cannot accept bets.",
"2007": "This play type is unsupported or missing odds configuration.",
"2008": "Odds or play configuration has changed. Close the preview and try again.",
"1003": "Stake amount is outside the allowed range for this play type.",
"fallback": "Bet failed. Please try again later."
},
"amountHint": {
"iboxRoll": "This play uses a per-bet amount. The system calculates total stake and deduction after expanding combinations.",
"mbox": "This play uses a total input amount. It is split across permutations, rounded down to the smallest currency unit.",
"default": "Amount is the stake for this ticket line, using the smallest currency unit just like wallet."
}
},
"wallet": {
"title": "Wallet",
"subtitle": "Balance and transfer records",
"balance": "Wallet Balance",
"available": "Available {{amount}}",
"transferIn": "Transfer In",
"transferOut": "Transfer Out",
"inPage": "In Page",
"outPage": "Out Page",
"logs": "Logs",
"logsTitle": "Wallet Logs",
"logsSubtitle": "Transfer and betting records",
"typeFilter": "Type Filter",
"transferInTitle": "Transfer In",
"transferOutTitle": "Transfer Out",
"transferInSubtitle": "Add funds to lottery wallet ({{currency}})",
"transferOutSubtitle": "Move available balance back ({{currency}})",
"transferInDescription": "Move funds from the main-site wallet into the lottery wallet. Per-transaction limits are validated by the server.",
"transferOutDescription": "Move funds back to the main-site wallet. Per-transaction limits are validated by the server.",
"dialogInDescription": "Move funds from the main-site wallet into the lottery wallet. The minimum amount is validated by the server, usually about 1.00 {{currency}}.",
"dialogOutDescription": "Move funds back to the main-site wallet. Per-transaction limits are validated by the server.",
"mainBalance": "Main-site wallet balance:",
"mainPending": "— (main site integration pending)",
"lotteryBalance": "Lottery wallet balance:",
"lotteryAvailable": "Lottery wallet available:",
"inAmount": "Transfer-in amount",
"outAmount": "Transfer-out amount",
"exampleIn": "e.g. 1000.00",
"exampleOut": "e.g. 500.00",
"afterInPreview": "Lottery balance after transfer-in (preview): {{amount}}",
"afterOutPreview": "Lottery balance after transfer-out (preview): {{amount}}",
"allOut": "Transfer all {{amount}}",
"confirmIn": "Confirm transfer in",
"confirmOut": "Confirm transfer out",
"successIn": "Transfer in succeeded. Lottery wallet balance updated.",
"successOut": "Transfer out succeeded. Funds will return to the main-site wallet.",
"pendingShort": "Transfer is processing. Refresh later.",
"pendingToast": "Processing...",
"invalidAmount": "Enter a valid amount.",
"outExceeds": "Transfer-out amount cannot exceed available balance.",
"pendingTitle": "Pending reconciliation",
"pendingDescription": "The following transfers have not been finally confirmed by the main site. Contact support if they do not arrive after a long time.",
"pendingStatus": "Processing",
"flowsTitle": "Wallet logs",
"totalRecords": "{{total}} records",
"emptyLogs": "No wallet logs",
"flow": {
"all": "All",
"transfer_in": "Transfer in",
"transfer_out": "Transfer out",
"bet": "Bet deduction",
"prize": "Payout",
"refund": "Refund",
"reversal": "Reversal"
},
"txnStatus": {
"posted": "Success",
"pending_reconcile": "Pending reconciliation",
"reversed": "Reversed",
"manually_processed": "Manually processed"
},
"error": {
"401": "Login expired. Please return and enter again.",
"409": "Transfer is processing. Refresh later. Contact support if it does not arrive after a long time.",
"network": "Network error. Check your connection and try again.",
"timeout": "Request timed out. Please try again later.",
"fallback": "Request failed. Please try again later.",
"1001": "Lottery wallet balance is insufficient. Transfer in or reduce the transfer-out amount.",
"1002": "Transfer is processing. Refresh the balance here later. Contact support if it does not arrive after a long time.",
"1003": "Amount exceeds the per-transaction or daily limit. Adjust the amount.",
"1004": "Transfer in is temporarily paused. Try again later or contact support.",
"1005": "Currency is invalid or unavailable.",
"1006": "Transfer out is temporarily paused. Try again later or contact support.",
"1007": "Lottery wallet is frozen and cannot transfer funds.",
"1008": "Amount format is invalid.",
"1009": "The main site could not complete this transfer. Please try again later.",
"1010": "Do not use the same idempotency key for transfers with different amounts."
}
},
"orders": {
"title": "My Bets",
"subtitle": "Recent ticket records",
"betDetail": "Bet Detail",
"filteredIssue": "Filtered Issue",
"totalRecords": "Total Records",
"betNow": "Bet Now",
"empty": "No bet records yet.",
"submitBet": "Submit Bet",
"stake": "Stake",
"deduction": "Deduction",
"win": "Win {{amount}}",
"detailTitle": "Ticket detail",
"ticketNo": "Ticket {{ticketNo}}",
"orderNo": "Order {{orderNo}}",
"drawNo": "Issue",
"placedAt": "Placed at",
"number": "Number",
"play": "Play",
"amount": "Stake amount",
"rebateRate": "Rebate rate",
"actualDeduct": "Actual deduction",
"oddsSnapshot": "Odds snapshot",
"drawNumbers": "Draw numbers (this issue)",
"firstPrize": "First prize",
"hit": "Hit",
"notPublished": "Draw numbers are not published or cannot be displayed yet.",
"matchWin": "Match result: hit {{tier}}",
"winAmount": "Win amount {{amount}}",
"jackpotAmount": "Jackpot {{amount}}",
"payoutTotal": "Payout total {{amount}}",
"matchLose": "Match result: not won",
"settledAt": "Settled at {{time}}",
"viewDraw": "View this draw",
"backToOrders": "Back to My Bets",
"notFound": "Ticket does not exist or cannot be viewed.",
"noData": "No data",
"loadFailed": "Failed to load"
},
"results": {
"title": "Results",
"subtitle": "Latest draw history",
"detailTitle": "Result Detail",
"businessDate": "Business Date",
"empty": "No results yet.",
"detail": "Detail",
"loadFailed": "Failed to load",
"unavailable": "This result is unavailable or does not exist.",
"noData": "No data",
"previous": " Previous",
"next": "Next ",
"drawTime": "Draw time: {{time}}",
"myPayout": "My payout for this issue",
"regular": "Regular: {{amount}}",
"jackpot": "Jackpot: {{amount}}",
"hitPending": "Your ticket has hit a result cell in this issue. The amount summary will show after payout is completed.",
"hitHint": "If you win, numbers matched by your tickets are highlighted in gold (login required).",
"viewMyWinning": "View my winning status",
"jackpotLabel": "Jackpot",
"tier": {
"first": "First prize",
"second": "Second prize",
"third": "Third prize",
"starter": "Starter",
"consolation": "Consolation"
},
"grid": {
"first": "First prize",
"second": "Second prize",
"third": "Third prize",
"starter": "Starter",
"consolation": "Consolation"
}
},
"ticketStatus": {
"success": "Awaiting draw",
"settled_win": "Paid",
"settled_lose": "Not won",
"unknown": "{{status}}"
},
"prizeTier": {
"first": "First prize",
"second": "Second prize",
"third": "Third prize",
"starter": "Starter",
"consolation": "Consolation"
},
"playLabels": {
"big": "Big",
"small": "Small",
"pos_4a": "4A",
"pos_4b": "4B",
"pos_4c": "4C",
"pos_4d": "4D",
"pos_4e": "4E",
"pos_3a": "3A",
"pos_3b": "3B",
"pos_3c": "3C",
"pos_3abc": "3ABC",
"pos_2a": "2A",
"pos_2b": "2B",
"pos_2c": "2C",
"pos_2abc": "2ABC",
"straight": "Straight",
"box": "Box",
"ibox": "iBox",
"mbox": "mBox",
"roll": "Roll",
"half_box": "Half Box",
"head": "Head",
"tail": "Tail",
"odd": "Odd",
"even": "Even",
"digit_big": "Big Digit",
"digit_small": "Small Digit"
}
}