+ );
+ }
+
+ return children;
}
export function Providers({ children }: ProvidersProps) {
return (
-
- {children}
+ {children}
);
diff --git a/src/hooks/use-async-effect.ts b/src/hooks/use-async-effect.ts
index a4a5a76..b78c085 100644
--- a/src/hooks/use-async-effect.ts
+++ b/src/hooks/use-async-effect.ts
@@ -14,13 +14,25 @@ export function useAsyncEffect(
useEffect(() => {
let cleanup: void | (() => void);
+ let cancelled = false;
+
queueMicrotask(() => {
+ if (cancelled) {
+ return;
+ }
+
void Promise.resolve(factoryRef.current()).then((result) => {
+ if (cancelled) {
+ result?.();
+ return;
+ }
+
cleanup = result;
});
});
return () => {
+ cancelled = true;
cleanup?.();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index 86c9f31..0f39a29 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -69,7 +69,6 @@ if (!i18n.isInitialized) {
},
});
- syncAdminLanguage(initialLanguage);
i18n.on("languageChanged", (lang) => {
syncAdminLanguage(normalizeAdminLanguage(lang));
});
diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json
index e63a0de..cbf1ed4 100644
--- a/src/i18n/locales/en/common.json
+++ b/src/i18n/locales/en/common.json
@@ -183,6 +183,7 @@
"draws": "Draws",
"rules_plays": "Play rules",
"rules_odds": "Odds & rebate",
+ "bet_providers": "Bet providers",
"rules": "Betting rules",
"risk_cap": "Risk cap rules",
"risk": "Risk center",
@@ -212,7 +213,8 @@
"auth": {
"checking": "Checking sign-in status…",
"checkingShort": "Loading workspace…",
- "sessionExpired": "Your session has expired. Please sign in again."
+ "sessionExpired": "Your session has expired. Please sign in again.",
+ "sessionReplaced": "This account was signed in elsewhere. Please sign in again."
},
"confirm": {
"cancel": "Cancel",
diff --git a/src/i18n/locales/en/config.json b/src/i18n/locales/en/config.json
index 75d0d83..9ced231 100644
--- a/src/i18n/locales/en/config.json
+++ b/src/i18n/locales/en/config.json
@@ -282,6 +282,28 @@
},
"tabs": {
"all": "All"
+ },
+ "help": {
+ "openGuide": "Page guide",
+ "guideTitle": "Odds and base rebate guide",
+ "guideDescription": "This page controls standard payout multipliers and the platform base rebate. It does not control jackpot bonus payouts or whether a play is available for sale.",
+ "aria": "View help for {{field}}",
+ "fields": {
+ "provider": "Selects the provider scope. GLOBAL is the default; a provider-specific value overrides it, while providers without an override inherit GLOBAL.",
+ "category": "Filters the list to 4D, 3D, 2D, and other groups. Changing this filter does not change any configuration.",
+ "playType": "Selects the exact play whose prize-tier odds and base rebate you are viewing or editing.",
+ "multiplier": "The standard winning payout multiplier. It is roughly the valid winning amount multiplied by this value, subject to the system's amount rounding rules.",
+ "scopeMultiplier": "Controls the standard payout multiplier when the {{scope}} tier wins. It is not the jackpot burst payout rate.",
+ "rebateRate": "The platform base rebate rate. Player or agent additions are configured elsewhere, and actual deduction and settlement also follow the account mode."
+ },
+ "guide": {
+ "scopeTitle": "Choose the correct scope",
+ "scopeDescription": "Choose the provider, category, and play first. Category is only a filter; provider and play identify the odds set being viewed or edited.",
+ "settingsTitle": "What odds and rebate control",
+ "settingsDescription": "Prize-tier multipliers control standard winning payouts, while base rebate controls the platform's base betting discount. Neither is the jackpot bonus payout.",
+ "workflowTitle": "When changes take effect",
+ "workflowDescription": "Odds use a versioned workflow: create a draft, edit and save it, then publish. Saving alone does not affect players. New tickets use the published values, while existing tickets keep their saved odds and rebate snapshots."
+ }
}
},
"play": {
@@ -389,6 +411,29 @@
"toggleDisable": "Disable",
"toggleEnable": "Enable",
"toggleInstantFailed": "Failed to apply play switch. Try again later.",
+ "help": {
+ "openGuide": "Page guide",
+ "guideTitle": "Play rules guide",
+ "guideDescription": "This page controls whether a play can be sold, the player-facing name and order, and the amount allowed on each betting line. It does not control odds or jackpot eligibility.",
+ "aria": "View help for {{field}}",
+ "fields": {
+ "status": "After publishing, this controls whether players can buy the play. Disabling it hides or blocks the play and rejects new bets; historical tickets are unchanged.",
+ "displayName": "The player-facing play name. Changes stay in the current draft until it is saved and published.",
+ "order": "Controls the order in player-facing lists. Lower numbers usually appear first, although fixed betting grids may keep their own layout.",
+ "minBet": "The minimum amount allowed for one submitted betting line. New bets below it are rejected.",
+ "maxBet": "The maximum amount allowed for one submitted betting line. Expanded combination bets are recalculated from their actual resulting lines.",
+ "batchSwitches": "Enables or disables a group of plays at once. It only changes the current draft and still requires save and publish.",
+ "batchGroup": "Sets all {{count}} plays in “{{group}}” to the same state in the current draft only."
+ },
+ "guide": {
+ "settingsTitle": "What each setting controls",
+ "settingsDescription": "Status controls availability; display name and order control what players see; minimum and maximum bet define the amount accepted per betting line.",
+ "batchTitle": "Using batch switches",
+ "batchDescription": "Batch switches update many draft rows at once. They do not bypass the save and publish workflow.",
+ "workflowTitle": "When changes take effect",
+ "workflowDescription": "The active version is read-only. Create a draft, edit and save it, then publish. Saving alone does not affect players; publishing affects new bets, while historical tickets keep their saved rule snapshot."
+ }
+ },
"validation": {
"displayNameRequired": "Display name is required",
"minMaxInvalid": "{{playCode}}: min bet cannot exceed max bet"
@@ -582,7 +627,7 @@
"autoApprove": "After cooldown ends and settlement completes, whether batches are automatically marked as approved.",
"autoPayout": "After a batch is approved, whether tick automatically credits winnings to player wallets.",
"autoSettlement": "When disabled, tick will not run settlement automatically and admins must trigger it manually.",
- "cooldownMinutes": "How long to wait after publishing before entering settling. Use 0 to settle immediately.",
+ "cooldownMinutes": "How long to wait after publishing before settlement. Use 0 to settle immediately. Changes apply only to results published afterward; draws already in cooldown are not recalculated.",
"manualReview": "When enabled, RNG draw results enter pending review and must be published manually in admin."
},
"loadFailed": "Failed to load system settings",
diff --git a/src/i18n/locales/en/draws.json b/src/i18n/locales/en/draws.json
index 1891547..bd2d25e 100644
--- a/src/i18n/locales/en/draws.json
+++ b/src/i18n/locales/en/draws.json
@@ -78,6 +78,8 @@
"sequenceNo": "Sequence no.",
"plannedDraw": "Planned draw",
"coolingEndTime": "Cooling ends at",
+ "automaticSettlementAt": "Automatic settlement: {{time}}",
+ "cooldownRemaining": "Remaining {{time}}",
"resultSource": "Result source",
"resultSourceOptions": {
"rng": "RNG auto-generated",
@@ -92,6 +94,7 @@
"published": "Published"
},
"currentResultVersion": "Current result version",
+ "resultVersion": "Result version",
"settleVersion": "Settlement version",
"isReopened": "Reopened",
"yes": "Yes",
@@ -109,8 +112,15 @@
"rngDraw": "RNG draw",
"rngAutoGenerate": "Auto draw",
"reopen": "Reopen",
- "cooldownReopen": "Reopen in cooldown",
+ "cooldownReopen": "Withdraw result and reopen",
+ "reopenUsageHint": "Use this only when published numbers are wrong. It requires generating and publishing a new result and does not speed up settlement.",
"runSettlement": "Run settlement",
+ "settleEarly": "Settle early",
+ "retrySettlement": "Retry settlement",
+ "settleEarlySuccess": "Remaining confirmation window skipped; settlement started",
+ "retrySettlementSuccess": "Settlement retry started",
+ "settlementManagePermissionRequired": "Only payout managers may start or retry settlement.",
+ "settlementUnavailable": "No manual settlement action is needed while the draw is {{status}}.",
"processing": "Processing…",
"actionSuccess": "{{name}} succeeded",
"actionFailed": "{{name}} failed",
@@ -121,7 +131,8 @@
"currentPayout": "Current payout total",
"grossProfit": "Approx. gross profit",
"settlementBatchList": "Settlement records",
- "relatedSettlementBatches": "Settlement batches",
+ "relatedSettlementBatches": "Settlement processing records for this draw",
+ "settlementBatchesHint": "A record is normally created for each provider that has bets in this draw. Select a batch number to view its ticket details.",
"noSettlementBatches": "No settlement batch records.",
"ticketCount": "Tickets",
"winCount": "Wins",
@@ -153,7 +164,7 @@
"clear": "Clear",
"saveDraft": "Save draft",
"saving": "Saving…",
- "pendingBatches": "Pending publish",
+ "pendingBatches": "Results awaiting review (not visible to players)",
"noPendingBatches": "None",
"batchId": "Batch",
"numberCount": "Numbers",
@@ -173,6 +184,8 @@
"checkBeforePublish": "Please review the numbers below before publishing",
"checkBeforePublishDesc": "Confirm the numbers are correct and click Publish.",
"publishedView": "View published result",
+ "officialResultsTitle": "Official results (visible to players)",
+ "officialResultsHint": "Only published, player-visible numbers appear here. Review drafts are kept separate.",
"confirmPublish": "Confirm publish",
"submitting": "Submitting…",
"publishSuccess": "Published · {{drawNo}} · status {{status}}",
@@ -181,13 +194,27 @@
"subnav": {
"status": "Overview",
"results": "Results",
- "finance": "Finance",
+ "tickets": "Bets",
+ "finance": "Finance & settlement",
"review": "Publish",
"riskOccupancy": "Risk occupancy",
"riskLockLogs": "Lock logs",
"riskHot": "Hot numbers",
"riskSoldOut": "Sold-out numbers",
- "riskPools": "Risk pools"
+ "riskPools": "Number limits"
+ },
+ "lifecycle": {
+ "currentStage": "Current stage: {{status}}",
+ "pending": "Betting has not opened for this draw.",
+ "open": "Players may place bets until the close time.",
+ "closing": "Betting is closed and the draw is waiting for its scheduled result time.",
+ "closed": "The draw is closed and waiting for result generation or entry.",
+ "drawing": "The system is generating the result.",
+ "review": "The result is under review and is not yet visible to players.",
+ "cooldown": "The result is published and in its verification window. It will settle automatically or a payout manager may settle early.",
+ "settling": "Settlement records are being processed. This page refreshes every 5 seconds.",
+ "settled": "Settlement and payout processing is complete.",
+ "cancelled": "This draw was cancelled and will not proceed to result or settlement."
},
"statusOptions": {
"all": "All",
@@ -216,10 +243,14 @@
"cancelDrawDescription": "This draw will not be drawn. Ensure there is no outstanding bet risk.",
"rngDrawTitle": "Confirm RNG draw?",
"rngDrawDescription": "The system will generate draw numbers and continue the pipeline.",
- "reopenTitle": "Confirm cooldown reopen?",
- "reopenDescription": "Results may need re-review; displayed numbers may change.",
+ "reopenTitle": "Withdraw the result and reopen?",
+ "reopenDescription": "Use only when published numbers are wrong. The draw returns to closed and a new result must be generated, reviewed, and published. This does not speed up settlement.",
"runSettlementTitle": "Confirm run settlement?",
"runSettlementDescription": "A settlement batch will be created from the published result.",
+ "settleEarlyTitle": "Settle this draw early?",
+ "settleEarlyDescription": "This skips the remaining result verification window, creates settlement records immediately, and continues automatic review and payout. Confirm the official result first.",
+ "retrySettlementTitle": "Retry settlement?",
+ "retrySettlementDescription": "This safely retries settlement without duplicating an existing valid batch.",
"saveManualDraftTitle": "Confirm save manual draft?",
"saveManualDraftDescription": "23 numbers will be saved for review.",
"publishTitle": "Confirm publish results?",
diff --git a/src/i18n/locales/en/jackpot.json b/src/i18n/locales/en/jackpot.json
index 1ff62a0..3b0e0a5 100644
--- a/src/i18n/locales/en/jackpot.json
+++ b/src/i18n/locales/en/jackpot.json
@@ -92,5 +92,35 @@
"play_combo": "Triggered by play combo",
"threshold": "Threshold reached"
},
- "winnerCount": "Winner count"
+ "winnerCount": "Winner count",
+ "help": {
+ "openGuide": "Page guide",
+ "guideTitle": "Jackpot page guide",
+ "guideDescription": "The jackpot is an extra reward pool separate from ordinary odds payouts. This page controls how each currency pool grows, when it is released, and where its records are reviewed.",
+ "aria": "View help for {{field}}",
+ "fields": {
+ "currentAmount": "The available balance currently accumulated for this currency. Save cannot edit it directly; qualifying contributions, jackpot payouts, or audited balance adjustments change it.",
+ "status": "Controls whether this currency pool participates in future activity. When disabled, new tickets do not contribute and automatic or manual bursts cannot run; the existing balance remains.",
+ "contributionRate": "After a successful ticket meets the jackpot minimum, its nominal bet amount is multiplied by this percentage and recorded into the pool. It is internal allocation, not an extra player charge.",
+ "minBetAmount": "Only decides whether a successful ticket contributes to the jackpot. It does not decide whether the play may be purchased; play betting limits are configured on Betting rules.",
+ "triggerThreshold": "Once the pool reaches this amount, a draw with at least one first-prize winning ticket may burst by threshold. Reaching the amount alone does not pay out without a first-prize winner.",
+ "payoutRate": "The percentage of the pre-burst balance released for threshold, combo-play, or manual bursts. A forced-gap burst releases the full pool instead.",
+ "forceTriggerGap": "After this many settled draws since the last burst, the next draw with a first-prize winner may force a burst. Set to 0 to disable this condition.",
+ "comboTriggerPlays": "A first-prize win on any selected play may trigger a burst before the balance reaches the threshold. This controls the trigger, not which first-prize winners share the payout.",
+ "balanceAdjustment": "Manually increases or decreases the current pool balance. A reason is required, every operation is logged, and a decrease cannot make the balance negative.",
+ "adjustmentDirection": "Choose whether this manual operation adds to or subtracts from the pool balance.",
+ "adjustmentAmount": "The amount to add or subtract in major currency units, not the desired balance after adjustment.",
+ "adjustmentReason": "Explains why the balance is being changed. At least 3 characters are required for audit and reconciliation.",
+ "manualBurst": "Super admin only. Select a draw with published results, a settlement batch, and first-prize winners to release the pool using the current payout rate.",
+ "records": "Review read-only jackpot payout and per-ticket contribution records, filterable by draw number and exportable."
+ },
+ "guide": {
+ "settingsTitle": "How the pool grows and triggers",
+ "settingsDescription": "Successful tickets first add to the pool when eligible. During settlement, at least one first-prize winner must exist before threshold, forced-gap, or combo-play conditions are checked.",
+ "actionsTitle": "Manual operations and records",
+ "actionsDescription": "Balance adjustments and manual bursts change real pool funds immediately. Verify currency, amount, draw, and reason first.",
+ "effectiveTitle": "When changes take effect",
+ "effectiveDescription": "Jackpot settings have no draft-and-publish workflow. Save applies them immediately to later contributions and unfinished jackpot settlement. A draw already sold but not yet settled may use the new trigger settings and payout rate."
+ }
+ }
}
diff --git a/src/i18n/locales/en/risk.json b/src/i18n/locales/en/risk.json
index 47ff770..c2673e7 100644
--- a/src/i18n/locales/en/risk.json
+++ b/src/i18n/locales/en/risk.json
@@ -14,7 +14,9 @@
"poolsTitle": "Risk pools",
"hotPageTitle": "Hot numbers monitor",
"soldOutPageTitle": "Sold-out numbers",
- "allPoolsPageTitle": "All risk pools",
+ "allPoolsPageTitle": "Number payout limits (risk pool)",
+ "poolExplanationTitle": "This page shows how much potential payout each number can still accept",
+ "poolExplanation": "Each provider and four-digit number has a maximum potential payout limit. Bets reserve capacity using their worst-case payout. The number is sold out when capacity is insufficient and capacity is released after settlement or refund. These amounts are risk reserves, not actual payouts.",
"sourceReasonOptions": {
"ticket_place": "Bet placement",
"ticket_rollback": "Ticket rollback",
@@ -36,10 +38,13 @@
"sortRemainingAsc": "Remaining ↑",
"sortNumberAsc": "Number ↑",
"loadPoolsFailed": "Failed to load risk pools",
- "capAmount": "Cap",
- "lockedAmount": "Locked",
- "remainingAmount": "Remaining",
- "usageRatio": "Usage",
+ "capAmount": "Maximum payout limit",
+ "lockedAmount": "Reserved by current bets",
+ "remainingAmount": "Remaining payout capacity",
+ "usageRatio": "Usage ratio",
+ "emptyPools": "No number-limit records match this filter. Numbers with no reserved bet exposure are normally omitted.",
+ "emptyHighRiskPools": "No number currently has a usage ratio of 80% or more.",
+ "emptySoldOutPools": "No number is currently sold out because of insufficient capacity or manual closure.",
"poolStatus": "Status",
"soldOut": "Sold out",
"warning": "Warning",
diff --git a/src/i18n/locales/ne/common.json b/src/i18n/locales/ne/common.json
index 68a973b..bfc8ca2 100644
--- a/src/i18n/locales/ne/common.json
+++ b/src/i18n/locales/ne/common.json
@@ -43,7 +43,8 @@
"auth": {
"checking": "लगइन स्थिति जाँच हुँदैछ…",
"checkingShort": "कार्यस्थान खोल्दै…",
- "sessionExpired": "लगइन समाप्त भयो। कृपया पुनः लगइन गर्नुहोस्।"
+ "sessionExpired": "लगइन समाप्त भयो। कृपया पुनः लगइन गर्नुहोस्।",
+ "sessionReplaced": "यो खाता अर्को स्थानमा लगइन भएको छ। कृपया पुनः लगइन गर्नुहोस्।"
},
"cancel": "रद्द गर्नुहोस्",
"confirm": {
@@ -198,6 +199,7 @@
"rules": "खेल नियम",
"rules_odds": "बाधा र रिबेट",
"rules_plays": "खेल नियम",
+ "bet_providers": "बेट प्रदायक",
"settings": "सेटिङ",
"settlement": "सेटलमेन्ट",
"settlement_center": "क्रेडिट सेटलमेन्ट",
diff --git a/src/i18n/locales/ne/config.json b/src/i18n/locales/ne/config.json
index 8a62934..205be6f 100644
--- a/src/i18n/locales/ne/config.json
+++ b/src/i18n/locales/ne/config.json
@@ -282,6 +282,28 @@
},
"tabs": {
"all": "सबै"
+ },
+ "help": {
+ "openGuide": "पृष्ठ निर्देशिका",
+ "guideTitle": "अड्स र आधारभूत रिबेट निर्देशिका",
+ "guideDescription": "यस पृष्ठले सामान्य पुरस्कार गुणक र प्लेटफर्मको आधारभूत रिबेट नियन्त्रण गर्छ। यसले ज्याकपोटको अतिरिक्त भुक्तानी वा खेल उपलब्धता नियन्त्रण गर्दैन।",
+ "aria": "{{field}} को सहायता हेर्नुहोस्",
+ "fields": {
+ "provider": "अड्स लागू हुने प्रदायक दायरा छान्छ। GLOBAL पूर्वनिर्धारित हो; प्रदायक-विशेष मानले यसलाई ओभरराइड गर्छ, नभए GLOBAL नै प्रयोग हुन्छ।",
+ "category": "4D, 3D, 2D आदि समूह फिल्टर गर्छ। फिल्टर बदल्दा कन्फिगरेसन बदलिँदैन।",
+ "playType": "हेर्न वा सम्पादन गर्न चाहेको ठ्याक्कै खेल छान्छ। प्रत्येक खेलको पुरस्कार-स्तर अड्स र आधारभूत रिबेट फरक हुन सक्छ।",
+ "multiplier": "सामान्य विजेता भुक्तानी गुणक। रकम प्रणालीको राउन्डिङ नियमअनुसार गणना हुन्छ।",
+ "scopeMultiplier": "{{scope}} पुरस्कार जित्दा प्रयोग हुने सामान्य भुक्तानी गुणक नियन्त्रण गर्छ। यो ज्याकपोट burst भुक्तानी दर होइन।",
+ "rebateRate": "प्लेटफर्मको आधारभूत रिबेट दर। खेलाडी वा एजेन्टको थप रिबेट अन्यत्र सेट हुन्छ; वास्तविक कटौती र सेटलमेन्ट खाता मोडमा पनि निर्भर हुन्छ।"
+ },
+ "guide": {
+ "scopeTitle": "सही दायरा छान्नुहोस्",
+ "scopeDescription": "पहिले प्रदायक, श्रेणी र खेल छान्नुहोस्। श्रेणी केवल फिल्टर हो; प्रदायक र खेलले कुन अड्स सेट हेरिँदै वा सम्पादन हुँदैछ भन्ने तय गर्छ।",
+ "settingsTitle": "अड्स र रिबेटले के नियन्त्रण गर्छ",
+ "settingsDescription": "पुरस्कार-स्तर गुणकले सामान्य विजेता भुक्तानी र आधारभूत रिबेटले प्लेटफर्मको आधार छुट नियन्त्रण गर्छ। यी ज्याकपोट अतिरिक्त भुक्तानी होइनन्।",
+ "workflowTitle": "परिवर्तन कहिले लागू हुन्छ",
+ "workflowDescription": "ड्राफ्ट बनाउनुहोस्, सम्पादन गरी सेभ गर्नुहोस्, अनि प्रकाशित गर्नुहोस्। सेभ मात्र गर्दा खेलाडीमा असर पर्दैन। नयाँ टिकटले प्रकाशित मान प्रयोग गर्छ; पुराना टिकटले सुरक्षित अड्स र रिबेट snapshot राख्छन्।"
+ }
}
},
"play": {
@@ -389,6 +411,29 @@
"toggleDisable": "निष्क्रिय",
"toggleEnable": "सक्रिय",
"toggleInstantFailed": "खेल स्विच तुरुन्त लागू गर्न असफल। पछि पुनः प्रयास गर्नुहोस्।",
+ "help": {
+ "openGuide": "पृष्ठ निर्देशिका",
+ "guideTitle": "खेल नियम निर्देशिका",
+ "guideDescription": "यस पृष्ठले खेल बेच्न मिल्ने वा नमिल्ने, खेलाडीले देख्ने नाम र क्रम, तथा प्रत्येक बेट लाइनको रकम सीमा नियन्त्रण गर्छ। यसले अड्स वा ज्याकपोट योग्यता नियन्त्रण गर्दैन।",
+ "aria": "{{field}} को सहायता हेर्नुहोस्",
+ "fields": {
+ "status": "प्रकाशनपछि खेल किन्न मिल्ने वा नमिल्ने नियन्त्रण गर्छ। बन्द गर्दा नयाँ बेट अस्वीकार हुन्छ; पुराना टिकट बदलिँदैनन्।",
+ "displayName": "खेलाडीले देख्ने खेल नाम। परिवर्तन सेभ र प्रकाशित नभएसम्म हालको ड्राफ्टमै रहन्छ।",
+ "order": "खेलाडी सूचीमा देखिने क्रम नियन्त्रण गर्छ। सानो संख्या सामान्यतया पहिले देखिन्छ; स्थिर बेटिङ ग्रिडले आफ्नै लेआउट राख्न सक्छ।",
+ "minBet": "एउटा बेट लाइनमा स्वीकार हुने न्यूनतम रकम। यसभन्दा कम नयाँ बेट अस्वीकार हुन्छ।",
+ "maxBet": "एउटा बेट लाइनमा स्वीकार हुने अधिकतम रकम। संयोजन बेट विस्तार भएपछि वास्तविक लाइनअनुसार फेरि गणना हुन्छ।",
+ "batchSwitches": "एउटै समूहका धेरै खेल एकैपटक खोल्छ वा बन्द गर्छ। यसले हालको ड्राफ्ट मात्र बदल्छ र अझै सेभ तथा प्रकाशन चाहिन्छ।",
+ "batchGroup": "“{{group}}” का सबै {{count}} खेललाई हालको ड्राफ्टमा मात्र एउटै अवस्थामा राख्छ।"
+ },
+ "guide": {
+ "settingsTitle": "प्रत्येक सेटिङले के नियन्त्रण गर्छ",
+ "settingsDescription": "स्थितिले उपलब्धता, नाम र क्रमले खेलाडीले देख्ने सामग्री, तथा न्यूनतम र अधिकतम बेटले प्रत्येक लाइनको स्वीकार्य रकम नियन्त्रण गर्छ।",
+ "batchTitle": "समूह स्विचको प्रयोग",
+ "batchDescription": "समूह स्विचले धेरै ड्राफ्ट पङ्क्ति एकैपटक बदल्छ। यसले सेभ र प्रकाशन प्रक्रिया छोड्दैन।",
+ "workflowTitle": "परिवर्तन कहिले लागू हुन्छ",
+ "workflowDescription": "सक्रिय संस्करण पढ्न मात्र हो। ड्राफ्ट बनाएर सम्पादन र सेभ गर्नुहोस्, अनि प्रकाशित गर्नुहोस्। सेभ मात्र गर्दा असर पर्दैन; प्रकाशनले नयाँ बेटमा असर गर्छ, पुराना टिकटले सुरक्षित नियम snapshot राख्छन्।"
+ }
+ },
"validation": {
"displayNameRequired": "प्रदर्शित नाम अनिवार्य छ",
"minMaxInvalid": "{{playCode}}: न्यूनतम बेट अधिकतम बेटभन्दा ठूलो हुन सक्दैन"
@@ -581,7 +626,7 @@
"autoApprove": "कूलडाउन सकिएर सेटलमेन्ट पूरा भएपछि ब्याच स्वतः अनुमोदित हुने हो कि होइन।",
"autoPayout": "ब्याच अनुमोदित भएपछि tick ले जित रकम खेलाडीको वालेटमा स्वतः जम्मा गर्ने हो कि होइन।",
"autoSettlement": "बन्द हुँदा tick ले सेटलमेन्ट स्वतः चलाउँदैन र एडमिनले म्यानुअल रूपमा ट्रिगर गर्नुपर्छ।",
- "cooldownMinutes": "प्रकाशनपछि settling मा जानुअघि कति समय पर्खने। 0 राखे तुरुन्त सेटलमेन्ट सुरु हुन्छ।",
+ "cooldownMinutes": "प्रकाशनपछि सेटलमेन्ट सुरु हुनुअघि कति समय पर्खने। 0 राखे तुरुन्त सुरु हुन्छ। परिवर्तनपछि प्रकाशित हुने ड्रमा मात्र लागू हुन्छ; पहिले नै कूलडाउनमा रहेका ड्र पुनः गणना हुँदैनन्।",
"manualReview": "सक्रिय हुँदा RNG ड्रअ परिणाम pending review मा जान्छ र एडमिनबाट म्यानुअल रूपमा प्रकाशित गर्नुपर्छ।"
},
"loadFailed": "प्रणाली सेटिङ लोड असफल भयो",
diff --git a/src/i18n/locales/ne/draws.json b/src/i18n/locales/ne/draws.json
index 7ed6456..7c33417 100644
--- a/src/i18n/locales/ne/draws.json
+++ b/src/i18n/locales/ne/draws.json
@@ -52,19 +52,26 @@
"manualCloseTitle": "म्यानुअल बन्द पुष्टि?",
"publishDescription": "खेलाडीहरूले नतिजा देख्नेछन्।",
"publishTitle": "नतिजा प्रकाशन पुष्टि?",
- "reopenDescription": "नतिजा पुनः समीक्षा हुन सक्छ।",
- "reopenTitle": "कुलडाउन पुनः खोल्ने पुष्टि?",
+ "reopenDescription": "प्रकाशित नम्बर गलत हुँदा मात्र प्रयोग गर्नुहोस्। ड्र फेरि बन्द अवस्थामा फर्किन्छ र नयाँ नतिजा सिर्जना, समीक्षा र प्रकाशित गर्नुपर्छ। यसले सेटलमेन्ट छिटो गर्दैन।",
+ "reopenTitle": "नतिजा फिर्ता लिएर पुनः खोल्ने?",
"rngDrawDescription": "प्रणालीले नतिजा सिर्जना गर्नेछ।",
"rngDrawTitle": "RNG ड्रअ पुष्टि?",
"runSettlementDescription": "प्रकाशित नतिजाबाट सेटलमेन्ट ब्याच बन्नेछ।",
"runSettlementTitle": "सेटलमेन्ट सुरु पुष्टि?",
+ "settleEarlyDescription": "बाँकी नतिजा जाँच समय छोडेर तुरुन्त सेटलमेन्ट र स्वचालित भुक्तानी प्रक्रिया सुरु हुनेछ। पहिले आधिकारिक नतिजा सही भएको पुष्टि गर्नुहोस्।",
+ "settleEarlyTitle": "अगावै सेटल गर्ने?",
+ "retrySettlementDescription": "अवस्थित वैध ब्याच नदोहोर्याई सेटलमेन्ट सुरक्षित रूपमा पुनः प्रयास हुनेछ।",
+ "retrySettlementTitle": "सेटलमेन्ट पुनः प्रयास गर्ने?",
"saveManualDraftDescription": "२३ नम्बर समीक्षाका लागि सुरक्षित हुनेछ।",
"saveManualDraftTitle": "म्यानुअल ड्राफ्ट सुरक्षित पुष्टि?"
},
"confirmPublish": "प्रकाशन पुष्टि गर्नुहोस्",
"confirmedAt": "पुष्टि समय {{time}}",
- "cooldownReopen": "कुलिङमा पुनःखोल्नुहोस्",
+ "cooldownReopen": "नतिजा फिर्ता लिएर पुनः खोल्नुहोस्",
+ "reopenUsageHint": "प्रकाशित नम्बर गलत हुँदा मात्र प्रयोग गर्नुहोस्। यसले नयाँ नतिजा पुनः प्रकाशित गर्नुपर्छ र सेटलमेन्ट छिटो गर्दैन।",
"coolingEndTime": "कुलिङ समाप्ति",
+ "automaticSettlementAt": "स्वचालित सेटलमेन्ट: {{time}}",
+ "cooldownRemaining": "बाँकी {{time}}",
"createDraw": {
"description": "स्थानीय समयक्षेत्र {{tz}} मा मिति र समय प्रविष्ट गर्नुहोस्।",
"drawNoPlaceholder": "ड्रअ नम्बर प्रविष्ट गर्नुहोस्, जस्तै 20260526-008",
@@ -79,6 +86,7 @@
},
"currentPayout": "हालको कुल भुक्तानी",
"currentResultVersion": "हालको नतिजा संस्करण",
+ "resultVersion": "नतिजा संस्करण",
"currentStatusAndDraft": "हालको स्थिति {{status}} · सेभ गरेपछि pending batch बन्छ, सिधै प्रकाशित हुँदैन",
"currentStatusDraftHint": "सेभ गरेपछि समीक्षा बाँकी ब्याच बन्छ, सिधै प्रकाशित हुँदैन",
"currentStatusLabel": "हालको स्थिति",
@@ -148,7 +156,7 @@
"overviewTitle": "ड्र अवलोकन",
"pageGuide": "ड्र जीवनचक्र व्यवस्थापन: योजना, बन्द, नतिजा समीक्षा र सेटलमेन्ट।",
"payoutTotal": "कुल भुक्तानी",
- "pendingBatches": "बाँकी ब्याच",
+ "pendingBatches": "समीक्षाको प्रतीक्षामा नतिजा (खेलाडीलाई नदेखिने)",
"pendingReview": "समीक्षा बाँकी",
"plannedDraw": "योजनाबद्ध ड्रअ",
"prize": "पुरस्कार",
@@ -160,8 +168,11 @@
"publishTitle": "प्रकाशित",
"published": "प्रकाशित",
"publishedView": "प्रकाशित नतिजा हेर्नुहोस्",
+ "officialResultsTitle": "आधिकारिक नतिजा (खेलाडीलाई देखिने)",
+ "officialResultsHint": "यहाँ प्रकाशित र खेलाडीलाई लागू भएका नम्बर मात्र देखिन्छन्; समीक्षा ड्राफ्ट अलग राखिन्छ।",
"queryDraw": "ड्रअ खोज्नुहोस्",
- "relatedSettlementBatches": "सम्बन्धित सेटलमेन्ट ब्याच",
+ "relatedSettlementBatches": "यस ड्रको सेटलमेन्ट प्रक्रिया अभिलेख",
+ "settlementBatchesHint": "सामान्यतया यस ड्रमा बाजी भएको प्रत्येक प्रदायकका लागि अभिलेख बन्छ। टिकट विवरण हेर्न ब्याच नम्बर छान्नुहोस्।",
"reopen": "पुनःखोल्नुहोस्",
"reset": "रिसेट",
"resultBatchesTitle": "नतिजा ब्याच",
@@ -190,6 +201,12 @@
"rngDraw": "RNG ड्रअ",
"rngSummary": "RNG ह्यास {{hash}}",
"runSettlement": "सेटलमेन्ट चलाउनुहोस्",
+ "settleEarly": "अगावै सेटल गर्नुहोस्",
+ "retrySettlement": "सेटलमेन्ट पुनः चलाउनुहोस्",
+ "settleEarlySuccess": "बाँकी पुष्टि समय छोडेर सेटलमेन्ट सुरु भयो",
+ "retrySettlementSuccess": "सेटलमेन्ट पुनः प्रयास सुरु भयो",
+ "settlementManagePermissionRequired": "भुक्तानी व्यवस्थापकले मात्र सेटलमेन्ट सुरु वा पुनः प्रयास गर्न सक्छ।",
+ "settlementUnavailable": "ड्र {{status}} अवस्थामा हुँदा म्यानुअल सेटलमेन्ट आवश्यक छैन।",
"saveDraft": "ड्राफ्ट सुरक्षित गर्नुहोस्",
"saveFailed": "सेभ असफल भयो",
"saving": "सेभ हुँदैछ…",
@@ -218,16 +235,30 @@
},
"submitting": "पेश हुँदैछ…",
"subnav": {
- "finance": "ड्रअ वित्त",
+ "finance": "वित्त र सेटलमेन्ट",
"results": "परिणाम",
+ "tickets": "बाजी अवस्था",
"review": "समीक्षा र प्रकाशन",
"riskHot": "हट नम्बर",
"riskLockLogs": "लक लग",
"riskOccupancy": "जोखिम अकुपेन्सी",
- "riskPools": "जोखिम पूल",
+ "riskPools": "नम्बर सीमा",
"riskSoldOut": "बिक्री समाप्त नम्बर",
"status": "ड्रअ स्थिति"
},
+ "lifecycle": {
+ "currentStage": "हालको चरण: {{status}}",
+ "pending": "यस ड्रका लागि बाजी खुलिसकेको छैन।",
+ "open": "बन्द समयसम्म खेलाडीले बाजी लगाउन सक्छन्।",
+ "closing": "बाजी बन्द छ र निर्धारित नतिजा समय पर्खिरहेको छ।",
+ "closed": "ड्र बन्द छ र नतिजा सिर्जना वा प्रविष्टि पर्खिरहेको छ।",
+ "drawing": "प्रणालीले नतिजा सिर्जना गर्दैछ।",
+ "review": "नतिजा समीक्षामा छ र खेलाडीलाई अझै लागू भएको छैन।",
+ "cooldown": "नतिजा प्रकाशित छ र जाँच अवधिमा छ। समय पुगेपछि स्वचालित सेटल हुनेछ वा व्यवस्थापकले अगावै सेटल गर्न सक्छ।",
+ "settling": "सेटलमेन्ट अभिलेख प्रशोधन हुँदैछ। पृष्ठ प्रत्येक ५ सेकेन्डमा अद्यावधिक हुन्छ।",
+ "settled": "सेटलमेन्ट र भुक्तानी प्रक्रिया पूरा भयो।",
+ "cancelled": "यो ड्र रद्द भयो र नतिजा वा सेटलमेन्टमा जाने छैन।"
+ },
"tail2": "अन्तिम 2",
"tail3": "अन्तिम 3",
"ticketCount": "टिकट",
diff --git a/src/i18n/locales/ne/jackpot.json b/src/i18n/locales/ne/jackpot.json
index 46a295e..19ca361 100644
--- a/src/i18n/locales/ne/jackpot.json
+++ b/src/i18n/locales/ne/jackpot.json
@@ -92,5 +92,35 @@
"tabConfig": "कन्फिगरेसन",
"tabRecords": "रेकर्ड",
"confirmSavePoolTitle": "ज्याकपट सेटिङ बचत गर्ने?",
- "confirmSavePoolDescription": "यसले संचय दर, थ्रेसहोल्ड, भुक्तानी अनुपात र सम्बन्धित प्यारामिटरहरू अद्यावधिक गर्नेछ (पूल ब्यालेन्स होइन)।"
+ "confirmSavePoolDescription": "यसले संचय दर, थ्रेसहोल्ड, भुक्तानी अनुपात र सम्बन्धित प्यारामिटरहरू अद्यावधिक गर्नेछ (पूल ब्यालेन्स होइन)।",
+ "help": {
+ "openGuide": "पृष्ठ जानकारी",
+ "guideTitle": "ज्याकपट पृष्ठ जानकारी",
+ "guideDescription": "ज्याकपट सामान्य odds भुक्तानीभन्दा अलग अतिरिक्त पुरस्कार पूल हो। यस पृष्ठले मुद्रा अनुसार पूल कसरी बढ्छ, कहिले रिलिज हुन्छ र रेकर्ड कहाँ हेर्ने भन्ने नियन्त्रण गर्छ।",
+ "aria": "{{field}} को जानकारी हेर्नुहोस्",
+ "fields": {
+ "currentAmount": "यस मुद्राको पूलमा अहिले जम्मा भएको उपलब्ध ब्यालेन्स। साधारण Save बाट सिधै बदलिँदैन; योग्य योगदान, ज्याकपट भुक्तानी वा कारणसहितको समायोजनले बदल्छ।",
+ "status": "यस मुद्राको पूल भविष्यका कारोबारमा चल्ने वा नचल्ने नियन्त्रण गर्छ। बन्द हुँदा नयाँ टिकटले योगदान गर्दैन र स्वतः वा म्यानुअल बर्स्ट चल्दैन; पुरानो ब्यालेन्स रहन्छ।",
+ "contributionRate": "सफल टिकटले न्यूनतम रकम पुगेपछि नाममात्र बेट रकमलाई यो प्रतिशतले गुणा गरी पूलमा लेखिन्छ। यो आन्तरिक बाँडफाँट हो, खेलाडीबाट थप शुल्क होइन।",
+ "minBetAmount": "सफल टिकटले ज्याकपटमा योगदान गर्ने वा नगर्ने मात्र निर्णय गर्छ। प्ले किन्न मिल्ने न्यूनतम वा अधिकतम रकम Betting rules मा सेट हुन्छ।",
+ "triggerThreshold": "पूल यो रकममा पुगेपछि र सो ड्रअमा प्रथम पुरस्कार विजेता भए threshold बाट बर्स्ट हुन सक्छ। विजेता नभए रकम पुगेको मात्र कारणले भुक्तानी हुँदैन।",
+ "payoutRate": "Threshold, combo-play वा manual burst हुँदा बर्स्टअघिको ब्यालेन्सबाट रिलिज हुने प्रतिशत। Forced-gap burst ले पूरा पूल रिलिज गर्छ।",
+ "forceTriggerGap": "पछिल्लो बर्स्टपछि यति settled draws पुगेपछि, अर्को प्रथम पुरस्कार विजेता भएको ड्रअमा forced burst हुन सक्छ। 0 राख्दा यो सर्त बन्द हुन्छ।",
+ "comboTriggerPlays": "छानिएको प्लेमा प्रथम पुरस्कार परे threshold नपुगे पनि बर्स्ट हुन सक्छ। यसले ट्रिगर मात्र निर्धारण गर्छ, भुक्तानी पाउने विजेता सीमित गर्दैन।",
+ "balanceAdjustment": "हालको पूल ब्यालेन्स म्यानुअल रूपमा बढाउने वा घटाउने। कारण अनिवार्य हुन्छ, सबै काम रेकर्ड हुन्छ र ब्यालेन्स 0 भन्दा तल जान सक्दैन।",
+ "adjustmentDirection": "यो समायोजनले पूल ब्यालेन्स बढाउने वा घटाउने छान्नुहोस्।",
+ "adjustmentAmount": "मुख्य मुद्रा एकाइमा थप्ने वा घटाउने रकम; समायोजनपछि चाहिएको कुल ब्यालेन्स होइन।",
+ "adjustmentReason": "ब्यालेन्स किन बदलिएको हो भन्ने विवरण। Audit र reconciliation का लागि कम्तीमा 3 अक्षर चाहिन्छ।",
+ "manualBurst": "सुपर एडमिन मात्र। प्रकाशित नतिजा, settlement batch र प्रथम पुरस्कार विजेता भएको ड्रअ छानेर हालको payout rate अनुसार पूल रिलिज गर्छ।",
+ "records": "पढ्न मात्र मिल्ने jackpot payout र प्रत्येक टिकटको contribution रेकर्ड draw number अनुसार फिल्टर र export गर्नुहोस्।"
+ },
+ "guide": {
+ "settingsTitle": "पूल कसरी बढ्छ र ट्रिगर हुन्छ",
+ "settingsDescription": "योग्य सफल टिकटले पहिले पूल बढाउँछ। Settlement मा threshold, forced-gap वा combo-play जाँच्नुअघि कम्तीमा एक प्रथम पुरस्कार विजेता हुनुपर्छ।",
+ "actionsTitle": "म्यानुअल काम र रेकर्ड",
+ "actionsDescription": "Balance adjustment र manual burst ले वास्तविक पूल रकम तुरुन्त बदल्छ। पहिले मुद्रा, रकम, ड्रअ र कारण जाँच गर्नुहोस्।",
+ "effectiveTitle": "परिवर्तन कहिले लागू हुन्छ",
+ "effectiveDescription": "Jackpot सेटिङमा draft र publish प्रक्रिया छैन। Save गरेपछि पछिल्ला contribution र नसकिएको jackpot settlement मा तुरुन्त लागू हुन्छ। बेचिसकिएको तर settle नभएको ड्रअले पनि नयाँ trigger र payout rate प्रयोग गर्न सक्छ।"
+ }
+ }
}
diff --git a/src/i18n/locales/ne/risk.json b/src/i18n/locales/ne/risk.json
index cc04a45..85f85b7 100644
--- a/src/i18n/locales/ne/risk.json
+++ b/src/i18n/locales/ne/risk.json
@@ -4,12 +4,14 @@
"actionFilter": "कार्य",
"actions": "कार्य",
"all": "सबै",
- "allPoolsPageTitle": "सबै जोखिम पूल",
+ "allPoolsPageTitle": "नम्बर भुक्तानी सीमा (जोखिम पूल)",
+ "poolExplanationTitle": "हरेक नम्बरले अझै कति सम्भावित भुक्तानी स्वीकार्न सक्छ भन्ने यहाँ देखिन्छ",
+ "poolExplanation": "हरेक प्रदायक र चार-अङ्क नम्बरको अधिकतम सम्भावित भुक्तानी सीमा हुन्छ। बाजीले सबैभन्दा खराब सम्भावित भुक्तानी अनुसार क्षमता सुरक्षित गर्छ। क्षमता अपुग हुँदा नम्बर बिक्री बन्द हुन्छ र सेटलमेन्ट वा फिर्तापछि क्षमता खुल्छ। यो जोखिम आरक्षण हो, वास्तविक भुक्तानी होइन।",
"amount": "रकम",
"applyFilter": "फिल्टर लागू गर्नुहोस्",
"backToAllPools": "सबै जोखिम पूलमा फर्कनुहोस्",
"backToList": "सूचीमा फर्कनुहोस्",
- "capAmount": "क्याप",
+ "capAmount": "अधिकतम भुक्तानी सीमा",
"center": "जोखिम केन्द्र",
"changeDraw": "ड्रअ परिवर्तन गर्नुहोस्",
"close": "बन्द",
@@ -48,7 +50,7 @@
"lockLogsGroupedHint": "पूर्वनिर्धारित रूपमा टिकट अनुसार समूह। विस्तारित प्लेको संयोजन टिकट विवरणमा हेर्नुहोस्।",
"lockLogsTitle": "जोखिम लक लग",
"lockReleaseSummary": "लक / रिलिज पङ्क्ति",
- "lockedAmount": "लक गरिएको",
+ "lockedAmount": "हालको बाजीले सुरक्षित गरेको",
"lockedWorstCase": "लक गरिएको (अधिकतम भुक्तानी सुरक्षित)",
"manualCloseSuccess": "नम्बर बेटिङ म्यानुअल रूपमा बन्द गरियो",
"no": "होइन",
@@ -66,7 +68,10 @@
"recoverSuccess": "नम्बर बेटिङ पुनर्स्थापित गरियो",
"refresh": "रिफ्रेस",
"release": "रिलिज",
- "remainingAmount": "बाँकी",
+ "remainingAmount": "बाँकी भुक्तानी क्षमता",
+ "emptyPools": "यो फिल्टरमा नम्बर सीमा अभिलेख छैन। बाजी जोखिम सुरक्षित नभएका नम्बर सामान्यतया देखिँदैनन्।",
+ "emptyHighRiskPools": "हाल ८०% वा बढी प्रयोग भएको उच्च जोखिम नम्बर छैन।",
+ "emptySoldOutPools": "क्षमता अपुग वा म्यानुअल बन्दका कारण बिक्री रोकिएको नम्बर छैन।",
"remainingSellable": "बाँकी बिक्रीयोग्य",
"riskFilter": "जोखिम फिल्टर",
"provider": "प्रोभाइडर",
@@ -109,7 +114,7 @@
"time": "समय",
"title": "जोखिम",
"totalCap": "क्याप रकम",
- "usageRatio": "प्रयोग अनुपात",
+ "usageRatio": "अकुपेन्सी अनुपात",
"view": "हेर्नुहोस्",
"viewDetail": "विवरण",
"viewTicket": "टिकट",
diff --git a/src/i18n/locales/zh/common.json b/src/i18n/locales/zh/common.json
index 9eec73a..25cfd7f 100644
--- a/src/i18n/locales/zh/common.json
+++ b/src/i18n/locales/zh/common.json
@@ -183,6 +183,7 @@
"draws": "期号列表",
"rules_plays": "投注规则",
"rules_odds": "赔率与基础回水",
+ "bet_providers": "开注商管理",
"rules": "投注规则",
"risk_cap": "限额版本",
"risk": "风控中心",
@@ -212,7 +213,8 @@
"auth": {
"checking": "正在校验登录状态…",
"checkingShort": "正在进入工作台…",
- "sessionExpired": "登录已失效,请重新登录"
+ "sessionExpired": "登录已失效,请重新登录",
+ "sessionReplaced": "该账号已在其他地方登录,当前会话已退出,请重新登录"
},
"confirm": {
"cancel": "取消",
diff --git a/src/i18n/locales/zh/config.json b/src/i18n/locales/zh/config.json
index 87fbbb2..50053be 100644
--- a/src/i18n/locales/zh/config.json
+++ b/src/i18n/locales/zh/config.json
@@ -304,7 +304,7 @@
},
"hints": {
"manualReview": "开启后,RNG 开奖结果会先进入待审核,必须由后台人工发布。",
- "cooldownMinutes": "结果发布后等待多久再进入 settling。填 0 表示发布后直接进入结算。",
+ "cooldownMinutes": "结果发布后等待多久再进入结算;填 0 表示立即结算。修改仅影响之后发布开奖结果的期号,已经进入冷静期的期号不会自动重算。",
"autoSettlement": "关闭后,tick 不会自动跑结算,只能由后台手工执行。",
"autoApprove": "冷静期结束并跑完结算后,是否自动将批次标记为已审核。",
"autoPayout": "批次已审核后,是否由 tick 自动把中奖金额打入玩家钱包。",
@@ -487,6 +487,29 @@
"title": "规则文案(多语言)",
"description": "玩法 {{playCode}};修改内容只会暂存到草稿,保存并发布后才会生效。",
"apply": "应用到草稿"
+ },
+ "help": {
+ "openGuide": "页面说明",
+ "guideTitle": "投注规则页面说明",
+ "guideDescription": "这个页面决定玩家能不能购买某个玩法、玩家看到的玩法名称和顺序,以及每一条下注允许输入的金额范围。它不控制赔率,也不控制奖池资格。",
+ "aria": "查看“{{field}}”的说明",
+ "fields": {
+ "status": "发布后控制玩家是否可以购买这个玩法。关闭后,前台会隐藏或禁用该玩法,后端也会拒绝新的下注;历史注单不受影响。",
+ "displayName": "玩家端展示的玩法名称。修改只进入当前草稿,保存并发布后才会展示给玩家。",
+ "order": "控制玩法在玩家端列表中的显示先后。数字越小通常越靠前;部分固定式投注表可能仍按自己的版面排列。",
+ "minBet": "玩家一次提交的一条玩法下注,允许输入的最小金额。低于该值时,新下注会被拒绝。",
+ "maxBet": "玩家一次提交的一条玩法下注,允许输入的最大金额。组合下注拆成多条后,系统会按实际展开结果重新计算。",
+ "batchSwitches": "按玩法分组一次开启或关闭多项玩法。操作只修改当前草稿,仍需保存并发布后才会对玩家生效。",
+ "batchGroup": "一次将“{{group}}”下 {{count}} 个玩法切换为同一状态;只修改当前草稿。"
+ },
+ "guide": {
+ "settingsTitle": "每个设置控制什么",
+ "settingsDescription": "状态控制是否可售;显示名称和排序控制玩家看到的内容;最小与最大下注控制每条下注可提交的金额范围。",
+ "batchTitle": "批量开关怎么用",
+ "batchDescription": "批量开关适合一次调整整组玩法。它只是帮你修改草稿里的多行数据,不会绕过保存和发布流程。",
+ "workflowTitle": "什么时候真正生效",
+ "workflowDescription": "线上生效版本只能查看。要修改时先创建草稿,在草稿中编辑并保存,最后发布。仅保存不会影响玩家;发布后只影响新的下注,历史注单继续使用下单时保存的规则快照。"
+ }
}
},
"prizeScopes": {
@@ -574,6 +597,28 @@
"currentActive": "当前生效",
"afterPublish": "发布后"
}
+ },
+ "help": {
+ "openGuide": "页面说明",
+ "guideTitle": "赔率与基础回水页面说明",
+ "guideDescription": "这个页面设置普通中奖赔付倍数和平台基础回水。它不控制奖池额外派彩,也不决定玩法是否开放。",
+ "aria": "查看“{{field}}”的说明",
+ "fields": {
+ "provider": "选择赔率适用的供应商范围。GLOBAL 是通用默认值;供应商专属配置会覆盖通用值,没有专属配置时则继承 GLOBAL。",
+ "category": "只用来筛选 4D、3D、2D 等玩法范围,方便查找;切换分类本身不会修改配置。",
+ "playType": "选择当前要查看或修改的具体玩法。每个玩法可以有不同奖级赔率和基础回水。",
+ "multiplier": "普通中奖的赔付倍数。大致可理解为有效中奖金额乘以该倍数,最终金额仍按系统精度规则取整。",
+ "scopeMultiplier": "控制“{{scope}}”这个奖级中奖时使用的普通赔付倍数。它不是奖池爆池的额外派彩比例。",
+ "rebateRate": "平台基础回水比例。玩家或代理的额外回水会在其他配置中叠加;实际扣款和结算还会遵循当前账户模式。"
+ },
+ "guide": {
+ "scopeTitle": "先选对配置范围",
+ "scopeDescription": "先选择供应商、分类和玩法。分类只是筛选条件;供应商和玩法共同决定你正在查看或修改哪一组赔率。",
+ "settingsTitle": "赔率和回水控制什么",
+ "settingsDescription": "各奖级倍数控制普通中奖赔付,基础回水控制平台给下注的基础优惠。两者都不等于奖池页面中的爆池派彩。",
+ "workflowTitle": "什么时候真正生效",
+ "workflowDescription": "赔率使用版本化流程:先创建草稿、修改并保存,再发布。保存草稿不会影响玩家;发布后新注单使用新配置,已经成功下单的注单继续使用下单时保存的赔率和回水快照。"
+ }
}
},
"rebate": {
diff --git a/src/i18n/locales/zh/draws.json b/src/i18n/locales/zh/draws.json
index 8de05e0..c3cff42 100644
--- a/src/i18n/locales/zh/draws.json
+++ b/src/i18n/locales/zh/draws.json
@@ -78,6 +78,8 @@
"sequenceNo": "流水序号",
"plannedDraw": "计划开奖",
"coolingEndTime": "冷静期结束",
+ "automaticSettlementAt": "自动结算时间:{{time}}",
+ "cooldownRemaining": "剩余 {{time}}",
"resultSource": "结果来源",
"resultSourceOptions": {
"rng": "RNG 自动生成",
@@ -92,6 +94,7 @@
"published": "已发布"
},
"currentResultVersion": "当前结果版本",
+ "resultVersion": "开奖结果版本",
"settleVersion": "结算版本",
"isReopened": "是否重开",
"yes": "是",
@@ -109,8 +112,15 @@
"rngDraw": "RNG开奖",
"rngAutoGenerate": "自动开奖",
"reopen": "重开",
- "cooldownReopen": "冷静期重开",
+ "cooldownReopen": "撤回开奖结果并重开",
+ "reopenUsageHint": "“撤回开奖结果并重开”仅用于已发布号码有误,需要重新生成和审核;它不会加快结算。",
"runSettlement": "触发结算",
+ "settleEarly": "提前结算",
+ "retrySettlement": "重新触发结算",
+ "settleEarlySuccess": "已跳过剩余确认窗口并开始结算",
+ "retrySettlementSuccess": "已重新触发本期结算",
+ "settlementManagePermissionRequired": "仅派彩管理人员可提前或重新触发结算。",
+ "settlementUnavailable": "当前状态为“{{status}}”,无需提供手动结算操作。",
"processing": "处理中…",
"actionSuccess": "{{name}}成功",
"actionFailed": "{{name}}失败",
@@ -121,7 +131,8 @@
"currentPayout": "当期派彩合计",
"grossProfit": "近似毛损益",
"settlementBatchList": "结算记录",
- "relatedSettlementBatches": "结算批次",
+ "relatedSettlementBatches": "本期结算处理记录",
+ "settlementBatchesHint": "通常按本期有下注的开注商生成处理记录;点击批次编号可查看对应注单明细。",
"noSettlementBatches": "暂无结算批次记录。",
"ticketCount": "票数",
"winCount": "中奖数",
@@ -153,7 +164,7 @@
"clear": "清空",
"saveDraft": "保存草稿",
"saving": "保存中…",
- "pendingBatches": "待发布",
+ "pendingBatches": "待审核结果(尚未对玩家生效)",
"noPendingBatches": "暂无",
"batchId": "批次",
"numberCount": "号码条数",
@@ -173,6 +184,8 @@
"checkBeforePublish": "请核对以下号码后再发布",
"checkBeforePublishDesc": "确认无误后点击发布。",
"publishedView": "查看已发布展示",
+ "officialResultsTitle": "正式开奖结果(已对玩家显示)",
+ "officialResultsHint": "这里只展示已经发布并对玩家生效的号码;待审核草稿不会出现在这里。",
"confirmPublish": "确认发布",
"submitting": "提交中…",
"publishSuccess": "已发布 · {{drawNo}} · 状态 {{status}}",
@@ -181,13 +194,27 @@
"subnav": {
"status": "概览",
"results": "开奖结果",
- "finance": "收支",
+ "tickets": "下注情况",
+ "finance": "收支结算",
"review": "开奖发布",
"riskOccupancy": "风控占用",
"riskLockLogs": "占用流水",
"riskHot": "热门号码",
"riskSoldOut": "售罄号码",
- "riskPools": "风险池"
+ "riskPools": "号码限额"
+ },
+ "lifecycle": {
+ "currentStage": "当前阶段:{{status}}",
+ "pending": "本期尚未开放下注。",
+ "open": "玩家可以下注;到封盘时间后系统会停止接单。",
+ "closing": "本期已停止接单,等待计划开奖时间。",
+ "closed": "已封盘待开奖,系统将生成或等待录入开奖结果。",
+ "drawing": "系统正在生成开奖结果。",
+ "review": "开奖结果尚在审核,发布前不会对玩家生效。",
+ "cooldown": "结果已发布并进入核错窗口;到期会自动结算,也可由派彩管理人员提前结算。",
+ "settling": "正在生成或处理本期结算记录,页面每 5 秒自动更新。",
+ "settled": "本期结算与派彩流程已完成。",
+ "cancelled": "本期已取消,不再进入开奖与结算流程。"
},
"statusOptions": {
"all": "不限",
@@ -216,10 +243,14 @@
"cancelDrawDescription": "取消后该期将不再开奖,请确认无未处理注单风险。",
"rngDrawTitle": "确认 RNG 自动生成开奖?",
"rngDrawDescription": "将按系统规则生成本期开奖号码并进入后续流程。",
- "reopenTitle": "确认冷静期重开?",
- "reopenDescription": "重开后需重新审核/发布结果,可能影响已展示的开奖信息。",
+ "reopenTitle": "确认撤回开奖结果并重开?",
+ "reopenDescription": "仅在已发布号码有误时使用。操作后期号回到已封盘待开奖,需要重新生成、审核并发布结果;这不会加快结算。",
"runSettlementTitle": "确认触发结算?",
"runSettlementDescription": "将按已发布开奖结果生成本期结算批次。",
+ "settleEarlyTitle": "确认提前结算?",
+ "settleEarlyDescription": "将跳过本期剩余的开奖结果核错窗口,立即生成结算处理记录,并继续自动审核与派彩。请先确认正式开奖结果无误。",
+ "retrySettlementTitle": "确认重新触发结算?",
+ "retrySettlementDescription": "将幂等重试本期结算处理,不会重复生成已存在的有效批次。",
"saveManualDraftTitle": "确认保存人工开奖草稿?",
"saveManualDraftDescription": "将写入 23 个开奖号码草稿,提交后进入审核流程。",
"publishTitle": "确认发布开奖结果?",
diff --git a/src/i18n/locales/zh/jackpot.json b/src/i18n/locales/zh/jackpot.json
index 56ab751..d121286 100644
--- a/src/i18n/locales/zh/jackpot.json
+++ b/src/i18n/locales/zh/jackpot.json
@@ -93,5 +93,35 @@
"time": "时间",
"ticketNo": "注单",
"player": "玩家",
- "contributionAmount": "蓄水额"
+ "contributionAmount": "蓄水额",
+ "help": {
+ "openGuide": "页面说明",
+ "guideTitle": "奖池页面说明",
+ "guideDescription": "奖池是普通赔率奖金之外的额外奖励资金。本页按币种控制奖池如何累积、何时释放,以及如何查看相关流水。",
+ "aria": "查看“{{field}}”说明",
+ "fields": {
+ "currentAmount": "该币种奖池当前累计的可用余额。它不能通过普通保存直接修改,只能由合格注单蓄水、爆池派彩或带原因的余额调整改变。",
+ "status": "控制该币种奖池是否参与后续业务。关闭后新成功注单不再蓄水,自动爆池与手动爆池也不会执行;现有余额会保留。",
+ "contributionRate": "成功下注且达到奖池最低下注额后,系统按名义下注额乘以该百分比计入奖池。这是内部计提,不会在下注额之外再向玩家多扣一笔。",
+ "minBetAmount": "只决定一笔成功注单是否参与奖池蓄水,不决定该玩法能否下注。真正的玩法下注上下限在“投注规则”页面配置。",
+ "triggerThreshold": "奖池余额达到此金额后,如果当期存在头奖中奖注单,可按“达到阈值”触发爆池。只有余额达到阈值但没有头奖得主时不会派发。",
+ "payoutRate": "达到阈值、组合玩法或手动爆池时,从爆池前余额中释放的百分比。连续未爆强制触发走全池释放,不使用此比例。",
+ "forceTriggerGap": "从上次爆池后累计的已结算期数达到此值后,下一次出现头奖得主时可强制爆池。设为 0 表示不启用此条件。",
+ "comboTriggerPlays": "勾选的玩法只要在某期产生头奖得主,就可在未达到余额阈值时触发爆池。它只决定触发条件,不限定最终哪些头奖得主参与分配。",
+ "balanceAdjustment": "人工增加或减少当前奖池余额。必须填写原因,每次操作都会写入调整流水;减少后余额不能小于 0。",
+ "adjustmentDirection": "选择本次人工调整是增加奖池余额还是减少奖池余额。",
+ "adjustmentAmount": "本次人工增加或减少的主币金额,不是调整后的目标余额。",
+ "adjustmentReason": "记录为什么要人工调整奖池,至少填写 3 个字符,后续可用于审计和对账。",
+ "manualBurst": "仅超级管理员可用。指定已发布结果且已有结算批次、存在头奖得主的期号,按当前派彩比例人工释放奖池。",
+ "records": "查看只读的奖池派彩记录和逐笔蓄水记录,可按期号筛选并导出。"
+ },
+ "guide": {
+ "settingsTitle": "奖池如何累积与触发",
+ "settingsDescription": "成功注单先按蓄水条件增加奖池;开奖结算时必须先有头奖得主,再判断阈值、连续未爆或组合玩法条件。",
+ "actionsTitle": "人工操作与流水",
+ "actionsDescription": "余额调整和手动爆池都会直接改变真实奖池资金,请先确认币种、金额、期号和原因。",
+ "effectiveTitle": "生效方式",
+ "effectiveDescription": "奖池配置没有草稿和发布流程,点击保存后立即用于后续蓄水与尚未完成的奖池结算。已经卖票但尚未结算的期号,也可能受到新触发参数和派彩比例影响。"
+ }
+ }
}
diff --git a/src/i18n/locales/zh/risk.json b/src/i18n/locales/zh/risk.json
index f392143..a8f2ffa 100644
--- a/src/i18n/locales/zh/risk.json
+++ b/src/i18n/locales/zh/risk.json
@@ -14,7 +14,9 @@
"poolsTitle": "风险池",
"hotPageTitle": "热门号码监控",
"soldOutPageTitle": "售罄号码列表",
- "allPoolsPageTitle": "全部风险池",
+ "allPoolsPageTitle": "号码赔付限额(风险池)",
+ "poolExplanationTitle": "这里控制每个号码还能承接多少潜在赔付",
+ "poolExplanation": "每个开注商、每个四位号码都有最高潜在赔付额度。下注会按最坏赔付预留额度;额度不足时该号码停售,结算或退款后释放。这里的金额是风险预留,不是实际派彩金额。",
"sourceReasonOptions": {
"ticket_place": "下注占用",
"ticket_rollback": "注单回滚",
@@ -36,10 +38,13 @@
"sortRemainingAsc": "剩余额 ↑(紧俏)",
"sortNumberAsc": "号码 ↑",
"loadPoolsFailed": "加载风险池失败",
- "capAmount": "封顶",
- "lockedAmount": "已占用",
- "remainingAmount": "剩余",
- "usageRatio": "占用比",
+ "capAmount": "最高赔付额度",
+ "lockedAmount": "当前下注预留",
+ "remainingAmount": "剩余可承接赔付",
+ "usageRatio": "占用比例",
+ "emptyPools": "当前筛选下没有号码额度记录;未产生下注预留的号码通常不会列出。",
+ "emptyHighRiskPools": "当前没有占用比例达到 80% 的高风险号码。",
+ "emptySoldOutPools": "当前没有因额度不足或人工关闭而停售的号码。",
"poolStatus": "状态",
"soldOut": "售罄",
"warning": "预警",
diff --git a/src/lib/admin-auth-reject.ts b/src/lib/admin-auth-reject.ts
index b59ec42..6844151 100644
--- a/src/lib/admin-auth-reject.ts
+++ b/src/lib/admin-auth-reject.ts
@@ -1,7 +1,5 @@
import { isAxiosError } from "axios";
-import { toast } from "sonner";
-import i18n from "@/i18n";
import { getAdminSessionState } from "@/stores/admin-session";
import { readToken } from "@/stores/admin-token";
import { isApiEnvelope } from "@/types/api/envelope";
@@ -13,6 +11,9 @@ export const ADMIN_UNAUTHENTICATED_CODE = 8110;
/** 与后端 {@see ErrorCode::AdminAccountDisabled} 一致 */
export const ADMIN_ACCOUNT_DISABLED_CODE = 8113;
+/** 与后端 ErrorCode::AdminSessionReplaced 一致 */
+export const ADMIN_SESSION_REPLACED_CODE = 8115;
+
/** 登录验证码错误等,HTTP 可能为 401/422,不应踢出会话 */
const LOGIN_FORM_ERROR_CODES = new Set([8111, 8112]);
@@ -31,7 +32,8 @@ export function isAdminAuthRejected(err: unknown): boolean {
if (err instanceof LotteryApiBizError) {
return (
err.code === ADMIN_UNAUTHENTICATED_CODE ||
- err.code === ADMIN_ACCOUNT_DISABLED_CODE
+ err.code === ADMIN_ACCOUNT_DISABLED_CODE ||
+ err.code === ADMIN_SESSION_REPLACED_CODE
);
}
@@ -54,7 +56,8 @@ export function isAdminAuthRejected(err: unknown): boolean {
if (
envelopeCode === ADMIN_UNAUTHENTICATED_CODE ||
- envelopeCode === ADMIN_ACCOUNT_DISABLED_CODE
+ envelopeCode === ADMIN_ACCOUNT_DISABLED_CODE ||
+ envelopeCode === ADMIN_SESSION_REPLACED_CODE
) {
return true;
}
@@ -62,7 +65,8 @@ export function isAdminAuthRejected(err: unknown): boolean {
if (isApiEnvelope(body) && body.code !== 0) {
return (
body.code === ADMIN_UNAUTHENTICATED_CODE ||
- body.code === ADMIN_ACCOUNT_DISABLED_CODE
+ body.code === ADMIN_ACCOUNT_DISABLED_CODE ||
+ body.code === ADMIN_SESSION_REPLACED_CODE
);
}
@@ -78,12 +82,32 @@ export function isAdminAuthRejected(err: unknown): boolean {
return false;
}
-export function redirectToAdminLogin(): void {
+function adminAuthErrorCode(err: unknown): number | null {
+ if (err instanceof LotteryApiBizError) {
+ return err.code;
+ }
+
+ if (!isAxiosError(err)) {
+ return null;
+ }
+
+ const body = err.response?.data;
+ if (!body || typeof body !== "object" || !("code" in body)) {
+ return null;
+ }
+
+ const code = (body as { code?: unknown }).code;
+ return typeof code === "number" ? code : null;
+}
+
+export function redirectToAdminLogin(
+ reason: "expired" | "replaced" = "expired",
+): void {
if (typeof window === "undefined" || isAdminLoginPath()) {
return;
}
- const loginPath = "/admin/login";
+ const loginPath = "/admin/login?session=" + reason;
if (window.location.pathname !== loginPath) {
window.location.replace(loginPath);
}
@@ -92,7 +116,7 @@ export function redirectToAdminLogin(): void {
/**
* 清除本地会话并跳转登录页。可在 axios 拦截器、/auth/me 刷新等任意上下文调用。
*/
-export function handleAdminAuthRejected(): void {
+export function handleAdminAuthRejected(err?: unknown): void {
if (typeof window === "undefined" || isAdminLoginPath()) {
return;
}
@@ -104,21 +128,19 @@ export function handleAdminAuthRejected(): void {
const session = getAdminSessionState();
const hadSession = session.bearerToken != null || readToken() != null;
- void fetch("/api/v1/admin/auth/logout", { method: "POST" }).catch(() => {
+ void fetch("/api/v1/admin/auth/logout", {
+ method: "POST",
+ keepalive: true,
+ }).catch(() => {
// Best-effort HttpOnly cookie cleanup; the local session is cleared below.
});
session.clearSession();
- if (hadSession) {
- toast.error(
- i18n.t("auth.sessionExpired", {
- ns: "common",
- defaultValue: "Sign-in expired. Please log in again.",
- }),
- );
- }
-
- redirectToAdminLogin();
+ const reason =
+ hadSession && adminAuthErrorCode(err) === ADMIN_SESSION_REPLACED_CODE
+ ? "replaced"
+ : "expired";
+ redirectToAdminLogin(reason);
queueMicrotask(() => {
authRejectHandling = false;
diff --git a/src/lib/admin-http.ts b/src/lib/admin-http.ts
index 056bffc..f8eed64 100644
--- a/src/lib/admin-http.ts
+++ b/src/lib/admin-http.ts
@@ -26,7 +26,7 @@ adminHttp.interceptors.response.use(
(response) => response,
(error: unknown) => {
if (isAdminAuthRejected(error)) {
- handleAdminAuthRejected();
+ handleAdminAuthRejected(error);
}
if (isAxiosError(error) && typeof window !== "undefined") {
const status = error.response?.status;
@@ -43,7 +43,7 @@ adminHttp.interceptors.response.use(
function rejectAfterAuthCheck(err: unknown): never {
if (isAdminAuthRejected(err)) {
- handleAdminAuthRejected();
+ handleAdminAuthRejected(err);
}
throw err;
}
diff --git a/src/lib/admin-nav-label.ts b/src/lib/admin-nav-label.ts
index 11e4729..494c163 100644
--- a/src/lib/admin-nav-label.ts
+++ b/src/lib/admin-nav-label.ts
@@ -11,6 +11,7 @@ const NAV_SEGMENT_I18N_KEYS: Record = {
draws: "draws",
rules_plays: "rules_plays",
rules_odds: "rules_odds",
+ bet_providers: "bet_providers",
jackpot: "jackpot",
risk_cap: "risk_cap",
risk: "risk",
diff --git a/src/lib/lottery-api-proxy.ts b/src/lib/lottery-api-proxy.ts
index 4feb652..93f8a59 100644
--- a/src/lib/lottery-api-proxy.ts
+++ b/src/lib/lottery-api-proxy.ts
@@ -94,6 +94,12 @@ export async function proxyLotteryApi(
"",
expiredAdminTokenCookieOptions(isSecureRequest(request)),
);
+ } else if (path.startsWith("v1/admin/") && upstream.status === 401) {
+ response.cookies.set(
+ ADMIN_TOKEN_STORAGE_KEY,
+ "",
+ expiredAdminTokenCookieOptions(isSecureRequest(request)),
+ );
}
return response;
diff --git a/src/modules/audit/audit-logs-console.tsx b/src/modules/audit/audit-logs-console.tsx
index 5d21d2b..e084c91 100644
--- a/src/modules/audit/audit-logs-console.tsx
+++ b/src/modules/audit/audit-logs-console.tsx
@@ -141,7 +141,9 @@ export function AuditLogsConsole(): React.ReactElement {