feat(i18n, agents, players): enhance localization and improve agent and player management interfaces
Some checks failed
lotteryadmin CI / build (push) Has been cancelled

Updated localization files for English and Nepali, adding new entries and refining existing translations for better clarity. Enhanced the agent management interface with improved delegation features and child agent creation prompts. Improved player management functionalities, including confirmation dialogs for player actions and better handling of player creation and editing processes.
This commit is contained in:
2026-06-16 17:08:12 +08:00
parent a020e34a7d
commit b924496a26
25 changed files with 4273 additions and 3239 deletions

View File

@@ -0,0 +1,236 @@
#!/usr/bin/env node
/**
* Merge missing English (en) i18n keys.
* Usage: node scripts/merge-en-translations.mjs
*/
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(__dirname, "..");
const localesDir = path.join(root, "src/i18n/locales");
/** @type {Record<string, Record<string, string>>} */
const translations = {
"adminUsers.json": {
roleListHint:
"You can add custom roles and configure permissions. Built-in roles (super admin, site admin, site finance, site support, agent) cannot be deleted.",
"rolePermissionDialog.packageHint":
"Checking a module row on the left grants View only. Select Manage separately for data entry, close, draw, and other admin actions.",
confirmSaveRolesTitle: "Save admin roles?",
confirmSaveRolesDescription:
"This updates role bindings for admin {{name}}; their console permissions will change accordingly.",
confirmSaveAccountTitle: "Save admin account?",
confirmSaveAccountCreateDescription: "This creates a new admin account and assigns the selected roles.",
confirmSaveAccountEditDescription:
"This updates account details for admin {{name}}, including status and password changes.",
confirmSaveRolePermissionsTitle: "Save role permissions?",
confirmSaveRolePermissionsDescription:
"This updates feature permissions for role “{{name}}”. All admins bound to this role are affected immediately.",
confirmSaveRoleTitle: "Save role details?",
confirmSaveRoleCreateDescription: "This creates a new role {{name}}.",
confirmSaveRoleEditDescription: "This updates the name, description, and status of role {{name}}.",
},
"agents.json": {
"lineUi.profileReadOnlyHint":
"Share, credit, and rebate are configured by your upline. Contact your parent agent or the platform to request changes.",
"lineUi.selfAgentOverviewHint":
"Credit below is allocated by your upline. Share and rebate are maintained upstream; this account cannot view or edit them.",
"lineUi.overviewDownlineHint": "{{count}} direct downline agent(s). Manage them in the Downline tab.",
"lineUi.playersUnavailableHint":
"This agent cannot create players. Enable “Allow create player” in the agent profile first.",
"lineUi.playersNoPermissionHint": "This account does not have player management permission on this node.",
"lineUi.downlineColumns.email": "Email",
"lineUi.downlineColumns.downlineCount": "Downline count",
"lineUi.editCurrent": "Edit this agent",
"lineUi.profileTabHint":
"Maintain share, credit, rebate, and risk tags here. Use Account & status for login name and password.",
"lineUi.nextSteps": "Suggested next steps",
"lineUi.stepProfile": "Configure share, credit, and rebate",
"lineUi.stepDownline": "Manage direct downline ({{count}} now)",
"lineUi.stepPlayers": "Create or maintain direct players",
"lineUi.downlineEmpty": "No direct downline yet. Child agents will appear here after creation.",
"lineUi.downlineEmptyShort": "No direct downline yet.",
"lineUi.sidebarShareRate": "Share {{rate}}%",
"lineUi.sidebarAvailableCredit": "Available to grant {{amount}}",
"lineUi.expand": "Expand",
"lineUi.collapse": "Collapse",
"profile.relativeShareRate": "Share rate (% of parent)",
"profile.relativeShareRateValue": "{{rate}}% of parent",
"profile.capabilityHint":
"Takes effect on this agents primary account after save. If the platform Agent role included player/node management, login permissions are now tightened by these switches.",
"profile.parentCaps": "Parent share {{share}}%, available to grant {{credit}}",
"profile.availableCredit": "Available to grant {{amount}}",
"profile.riskTags": "Risk tags",
"profile.riskTagsPlaceholder": "Comma-separated, e.g. overdue, high_turnover",
"profile.saveSuccess": "Share and credit settings saved",
"settlementBills.platform": "Platform",
"settlementBills.filteredByPeriod": "Showing bills for period #{{id}} only",
"settlementBills.clearPeriodFilter": "Show all periods",
"settlementBills.columns.party": "Party",
"settlementBills.columns.counterparty": "Counterparty",
"settlementBills.columns.grossWinLoss": "Win/Loss",
"settlementBills.detail": "Details",
"settlementBills.confirm": "Confirm bill",
"settlementBills.confirmed": "Confirmed",
"settlementBills.paymentAmount": "Payment amount",
"settlementBills.recordPayment": "Record payment",
"settlementBills.paid": "Payment recorded",
"settlementBills.subtreeSummary": "Subtree summary",
"settlementBills.grossWinLoss": "Win/Loss (gross_win_loss)",
"settlementBills.rebateAmount": "Rebate",
"settlementBills.shareProfit": "Share profit",
"settlementBills.platformRounding": "Platform rounding",
"settlementReports.title": "Period reports (credit share line)",
"settlementReports.description":
"Report set: player win/loss, agent share, rebate, credit, unpaid/overdue bills, and platform P/L.",
"settlementReports.type": "Report type",
"settlementReports.footnote":
"These reports use credit-line period semantics, not legacy wallet commission/rebate reports.",
"settlementReports.noPeriodHint":
"When no specific period is selected, the last 7 days are used. Platform P/L requires a period.",
"settlementReports.types.summary": "Summary",
"settlementReports.types.player_win_loss": "Player win/loss",
"settlementReports.types.agent_share": "Agent share",
"settlementReports.types.rebate": "Rebate",
"settlementReports.types.credit": "Credit",
"settlementReports.types.unpaid_bills": "Unpaid bills",
"settlementReports.types.overdue": "Overdue",
"settlementReports.types.platform_pnl": "Platform P/L",
"settlementReports.types.draw_period": "By draw",
"settlementReports.summary.billCount": "Bill count",
"settlementReports.summary.totalNet": "Total net",
"settlementReports.summary.totalUnpaid": "Total unpaid",
"settlementReports.summary.overdueCount": "Overdue bills",
"settlementReports.summary.platformRounding": "Total platform rounding",
"settlementReports.rebate.accrued": "Accrued",
"settlementReports.rebate.inBill": "In bill",
"settlementReports.rebate.settled": "Settled",
"settlementReports.rebate.allocated": "Allocated",
"settlementReports.credit.agents": "Agent credit",
"settlementReports.credit.players": "Player credit",
"settlementReports.platformPnl.periodRequired": "Select a specific period to view platform P/L.",
"settlementReports.platformPnl.billNet": "Platform bill net",
"settlementReports.platformPnl.rounding": "Rounding adjustment",
"settlementReports.platformPnl.shareProfit": "Share profit (metadata)",
"settlementReports.columns.player": "Player",
"settlementReports.columns.gameType": "Play",
"settlementReports.columns.grossWinLoss": "Win/Loss",
"settlementReports.columns.rebate": "Rebate",
"settlementReports.columns.agentId": "Agent ID",
"settlementReports.columns.count": "Count",
"settlementReports.columns.billId": "Bill",
"settlementReports.columns.billType": "Type",
"settlementReports.columns.unpaid": "Unpaid",
"settlementReports.columns.status": "Status",
"settlementReports.columns.overdueDays": "Overdue days",
"settlementReports.columns.drawNo": "Draw no.",
"settlementReports.columns.code": "Code",
"settlementReports.columns.name": "Name",
"settlementReports.columns.creditLimit": "Credit limit",
"settlementReports.columns.allocated": "Allocated",
"settlementReports.columns.available": "Available",
"settlementReports.columns.used": "Used",
"settlementReports.columns.frozen": "Frozen",
"settlementReports.columns.rebateType": "Rebate type",
"settlementReports.columns.amount": "Amount",
"settlementPeriods.title": "Agent periods",
"settlementPeriods.presetHint": "Quick period presets (recommended)",
"settlementPeriods.openWithPreset": "Apply quick preset",
"settlementPeriods.showAdvanced": "Custom start/end dates",
"settlementPeriods.hideAdvanced": "Hide custom dates",
"settlementPeriods.range": "Period",
"settlementPeriods.empty": "No periods yet. Choose a quick preset and open a period.",
"settlementPeriods.start": "Start",
"settlementPeriods.end": "End",
"settlementPeriods.status": "Status",
"settlementPeriods.close": "Close period and generate bills",
"settlementPeriods.viewBills": "Bills",
"settlementPeriods.opened": "Period opened",
"settlementPeriods.closed": "Period closed; bills generated",
"settlementPeriods.closeFailed": "Failed to close period",
"lineProvision.siteCode": "Integration site",
"lineProvision.siteCodePlaceholder": "Select site",
"lineProvision.siteRequired": "Select an integration site",
"lineProvision.noUnboundSite": "No sites without a level-1 agent",
"lineProvision.openIntegrationSites": "Go to integration sites",
"lineProvision.passwordHint": "At least 8 characters",
"playersPanel.siteCode": "Line site",
"playersPanel.passwordHint": "At least 8 characters",
"playersPanel.passwordMinLength": "Initial password must be at least 8 characters",
"playersPanel.creditLimitInvalid": "Credit limit must be an integer ≥ 0",
"playersPanel.creditLimitExceeded": "Credit limit cannot exceed this agents available grant",
"playersPanel.rebateRateInvalid": "Rebate rate must be between 0 and 100%",
"playersPanel.fundingMode": "Funding mode",
"playersPanel.authSource": "Auth source",
"playersPanel.rebateInherited": "Inherit agent default rebate",
"playersPanel.creditListHint":
"Credit line: below shows player credit limits and available credit, not main-site wallet balance.",
"playersPanel.playerRef": "Player ref",
"playersPanel.usernameNickname": "Username / nickname",
"playersPanel.creditLimitAvailable": "Credit limit / available",
"roles.selectedCount": "{{selected}} / {{total}} selected",
"roles.groupSelectedCount": "{{selected}} / {{total}}",
"roles.selectGroup": "Select all in group",
"roles.noAssignablePermissions": "No permissions available to assign",
},
"config.json": {
"integrationSites.columns.currency": "Currency",
"integrationSites.columns.lineRoot": "Level-1 agent",
"integrationSites.columns.h5Url": "Lottery H5",
"integrationSites.columns.ssoSecret": "SSO secret",
"integrationSites.columns.walletApiKey": "Wallet API key",
"integrationSites.lineRootBound": "Bound",
"integrationSites.lineRootUnbound": "Unbound",
"integrationSites.secretNotConfigured": "Secret not configured",
"integrationSites.secretCopyRequiresManage":
"Integration site manage permission is required to copy secrets",
},
"jackpot.json": {
confirmSavePoolTitle: "Save jackpot pool settings?",
confirmSavePoolDescription:
"This updates fill rate, thresholds, payout ratio, and related parameters (not pool balance). Use Balance adjustment for balance changes.",
},
"players.json": {
rebateRate: "Rebate",
riskTags: "Risk tags",
},
};
function setNested(obj, dotKey, value) {
const parts = dotKey.split(".");
let cur = obj;
for (let i = 0; i < parts.length - 1; i++) {
if (!(parts[i] in cur) || typeof cur[parts[i]] !== "object" || Array.isArray(cur[parts[i]])) {
cur[parts[i]] = {};
}
cur = cur[parts[i]];
}
cur[parts[parts.length - 1]] = value;
}
function sortKeysDeep(value) {
if (Array.isArray(value)) return value;
if (value && typeof value === "object") {
return Object.fromEntries(
Object.keys(value)
.sort()
.map((k) => [k, sortKeysDeep(value[k])]),
);
}
return value;
}
let merged = 0;
for (const [file, keys] of Object.entries(translations)) {
const enPath = path.join(localesDir, "en", file);
const en = JSON.parse(fs.readFileSync(enPath, "utf8"));
for (const [dotKey, enValue] of Object.entries(keys)) {
setNested(en, dotKey, enValue);
merged++;
}
fs.writeFileSync(enPath, `${JSON.stringify(sortKeysDeep(en), null, 2)}\n`, "utf8");
console.log(`Updated ${file} (+${Object.keys(keys).length} keys)`);
}
console.log(`Total merged: ${merged}`);

View File

@@ -0,0 +1,316 @@
#!/usr/bin/env node
/**
* Merge missing Nepali (ne) i18n keys from translations map.
* Usage: node scripts/merge-ne-translations.mjs
*/
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(__dirname, "..");
const localesDir = path.join(root, "src/i18n/locales");
/** @type {Record<string, Record<string, string>>} */
const translations = {
"adminUsers.json": {
siteRequired: "साइट छान्नुहोस्",
"table.sites": "बाँधिएका साइटहरू",
"permissionDialog.site": "साइट",
"accountDialog.site": "बाँधिएको साइट",
"accountDialog.sitePlaceholder": "यो खाताले पहुँच पाउने साइट छान्नुहोस्",
},
"agents.json": {
"lineUi.kicker": "क्रेडिट शेयर · एजेन्ट ट्री",
"lineUi.agentCount": "यो समूहमा {{count}} एजेन्ट",
"lineUi.searchPlaceholder": "नाम वा लगइन खोज्नुहोस्",
"lineUi.directChildren": "प्रत्यक्ष अधीनस्थ {{count}}",
"lineUi.selectAgent": "शेयर र क्रेडिट हेर्न एजेन्ट छान्नुहोस्",
"lineUi.selectAgentHint":
"सेटलमेन्ट सीमा एजेन्ट ट्री अनुसार; शेयर, क्रेडिट र रिबेट प्रति नोड कन्फिग गरिन्छ।",
"lineUi.allocatedCredit": "बाँडिएको",
"lineUi.availableCredit": "उपलब्ध",
"lineUi.profileFootnote": "रिबेट सीमा {{rebate}}% · पूर्वनिर्धारित {{defaultRebate}}%",
"lineUi.tabOverview": "अवलोकन",
"lineUi.tabProfile": "शेयर र क्रेडिट",
"lineUi.tabProfileReadOnly": "शेयर र क्रेडिट (पढ्न मात्र)",
"lineUi.currentSite": "साइट",
"lineUi.viewAll": "सबै हेर्नुहोस्",
"lineUi.shareRebateCap": "रिबेट सीमा {{rate}}%",
"lineUi.overviewDownlineCard": "प्रत्यक्ष अधीनस्थ एजेन्ट हेर्नुहोस् र व्यवस्थापन गर्नुहोस्",
"lineUi.overviewDownlineCount": "{{count}}",
"lineUi.overviewPlayersHint": "प्रत्यक्ष खेलाडी र क्रेडिट स्थिति हेर्नुहोस्",
"lineUi.overviewPlayersSummary": "खेलाडी व्यवस्थापन",
"lineUi.downlineEmptyTitle": "अहिले प्रत्यक्ष अधीनस्थ छैन",
"lineUi.editAccount": "खाता र स्थिति",
"lineUi.saveProfile": "शेयर र क्रेडिट बचत",
"lineUi.tabDownline": "अधीनस्थ",
"lineUi.tabPlayers": "खेलाडी",
"lineUi.noDelegatedTabs":
"यो एजेन्टले अधीनस्थ एजेन्ट वा खेलाडी सिर्जना गर्न सक्दैन; शेयर र क्रेडिट मात्र लागू हुन्छ।",
listSearch: "नाम / कोड / लगइन खोज्नुहोस्",
siteSearch: "साइट नाम खोज्नुहोस्",
parentAgent: "माथिल्लो",
childrenCount: "प्रत्यक्ष अधीनस्थ",
"subnav.provisionHint": "एक पटकको अनबोर्डिङ; दैनिक कामका लागि लाइन र एजेन्ट ट्री प्रयोग गर्नुहोस्",
"profile.validation.creditBelowAllocated":
"क्रेडिट सीमा बाँडिएको क्रेडिभन्दा कम हुन सक्दैन (न्यूनतम {{min}})",
"profile.validation.creditExceedsParentWithMax": "क्रेडिट सीमा {{max}} भन्दा बढी हुन सक्दैन",
"settlementBills.periodLabel": "अवधि",
"settlementBills.periodPlaceholder": "अवधि छान्नुहोस्",
"settlementBills.allPeriods": "सबै अवधि",
"settlementBills.filteredByPeriodRange": "{{range}} का बिलहरू",
"settlementBills.emptyNoPeriodsManage":
"अहिले अवधि वा बिल छैन। अवधि व्यवस्थापनमा छिटो प्रिसेट प्रयोग गर्नुहोस्, त्यसपछि अवधि बन्द गर्नुहोस्।",
"settlementBills.emptyNoPeriodsAgent":
"अहिले बिल छैन। तपाईंको माथिल्लो वा प्लेटफर्मले अवधि बन्द गर्छ; मिति प्रविष्ट गर्नु पर्दैन।",
"settlementBills.emptyNoClosed": "बन्द अवधि छैन। बिल बन्द पछि सिर्जना हुन्छ।",
"settlementBills.typePlayer": "खेलाडी बिल",
"settlementBills.typeAgent": "एजेन्ट बिल",
"settlementBills.columns.period": "अवधि",
"settlementPeriods.manageTitle": "अवधि व्यवस्थापन",
"settlementPeriods.manageHint":
"यहाँ अवधि खोल्नुहोस् र बन्द गर्नुहोस्; माथिका बिल स्वतः अद्यावधिक हुन्छ। छिटो प्रिसेट सामान्यतया पर्याप्त।",
"settlementPeriods.presetThisWeek": "यो हप्ता",
"settlementPeriods.presetLastWeek": "गत हप्ता",
"settlementPeriods.presetThisMonth": "यो महिना",
"settlementPeriods.openHint": "स्थानीय मिति ({{tz}})",
"settlementPeriods.startDate": "सुरु मिति",
"settlementPeriods.endDate": "अन्त्य मिति",
"settlementPeriods.open": "अवधि खोल्नुहोस्",
"settlementPeriods.openFailed": "अवधि खोल्न असफल",
"settlementPeriods.datesRequired": "सुरु र अन्त्य मिति प्रविष्ट गर्नुहोस्",
"settlementPeriods.invalidRange": "अन्त्य मिति सुरु मितिभन्दा अगाडि हुन सक्दैन",
"settlementPeriods.statusOpen": "खुला",
"settlementPeriods.statusClosed": "बन्द",
"playersPanel.loginRequired": "लगइन प्रयोगकर्ता नाम र प्रारम्भिक पासवर्ड प्रविष्ट गर्नुहोस्",
"playersPanel.loginUsername": "लगइन प्रयोगकर्ता नाम",
"playersPanel.initialPassword": "प्रारम्भिक पासवर्ड",
"playersPanel.externalIdOptional": "बाह्य ID (वैकल्पिक)",
"playersPanel.externalIdHint": "स्वतः सिर्जनाका लागि खाली राख्नुहोस्",
"playersPanel.creditLimit": "क्रेडिट सीमा",
"playersPanel.rebateRate": "रिबेट दर (%)",
"playersPanel.rebateRateHint": "प्रतिशत लेख्नुहोस्, जस्तै 5 = 5%",
"playersPanel.availableToGrant": "एजेन्टले दिन सक्ने: {{amount}}",
"playersPanel.riskTags": "जोखिम ट्याग",
"playersPanel.riskTagsPlaceholder": "अल्पविरामले छुट्याउनुहोस्",
"playersPanel.createSuccessNative": "खेलाडी {{name}} सिर्जना भयो — लटरी /login प्रयोग गर्नुहोस्",
"users.email": "इमेल",
},
"common.json": {
"export.ticketCombinations.filename": "ticket-combinations",
"export.ticketCombinations.sheetName": "संयोजनहरू",
"integrationSites.emptyPlatformHint":
"अहिले एकीकरण साइट छैन। एजेन्ट, खेलाडी वा सेटलमेन्ट व्यवस्थापन अघि साइट सिर्जना गर्नुहोस्।",
"integrationSites.createSite": "एकीकरण साइट सिर्जना",
},
"config.json": {
"integrationSites.adminAccountCreated": "साइट एडमिन खाता {{username}} सिर्जना भयो",
"integrationSites.delete": "साइट मेटाउनुहोस्",
"integrationSites.deleteSuccess": "साइट {{code}} मेटाइयो",
"integrationSites.deleteFailed": "साइट मेटाउन असफल",
"integrationSites.deleteConfirmTitle": "यो साइट मेटाउने?",
"integrationSites.deleteConfirmDescription":
"यसले साइट {{code}} ({{name}}), यसको एजेन्ट लाइन, सेटलमेन्ट अवधि, खेलाडी र साइट एडमिन खाता स्थायी रूपमा हटाउँछ। पूर्ववत गर्न मिल्दैन।",
"integrationSites.deleteConfirm": "मेटाउनुहोस्",
"integrationSites.deleting": "मेटाउँदै…",
"integrationSites.form.adminUsernameRequired": "साइट एडमिन प्रयोगकर्ता नाम आवश्यक",
"integrationSites.form.adminNicknameRequired": "साइट एडमिन उपनाम आवश्यक",
"integrationSites.form.adminPasswordRequired": "प्रारम्भिक साइट एडमिन पासवर्ड कम्तीमा ८ अक्षर",
"integrationSites.fields.adminUsername": "एडमिन प्रयोगकर्ता नाम",
"integrationSites.fields.adminNickname": "एडमिन उपनाम",
"integrationSites.fields.adminPassword": "प्रारम्भिक पासवर्ड",
"integrationSites.fields.adminEmail": "इमेल (वैकल्पिक)",
"integrationSites.placeholders.adminUsername": "एडमिन प्रयोगकर्ता नाम लेख्नुहोस्",
"integrationSites.placeholders.adminNickname": "खाता उपनाम लेख्नुहोस्",
"integrationSites.placeholders.adminPassword": "कम्तीमा ८ अक्षर",
"integrationSites.placeholders.adminEmail": "इमेल लेख्नुहोस्",
"integrationSites.adminAccountSectionTitle": "साइट एडमिन खाता",
"integrationSites.adminAccountSectionDescription":
"साइट सिर्जना गर्दा त्यस साइटसँग बाँधिएको एउटा एडमिन खाता पनि सिर्जना हुन्छ।",
"system.saveDrawSuccess": "ड्र प्यारामिटर बचत भयो",
"system.saveCurrencyFormatSuccess": "मुद्रा प्रदर्शन ढाँचा बचत भयो",
"system.saveSettlementSuccess": "सेटलमेन्ट स्वचालन बचत भयो",
"system.sections.draw": "ड्र तालिका र समीक्षा",
"system.sections.drawDescription":
"ड्र समय, बन्द सञ्झ्याल, म्यानुअल समीक्षा र कूलडाउन नियन्त्रण। यो ब्लकमा परिवर्तित फिल्ड मात्र पेश हुन्छ।",
"system.sections.currencyFormat": "मुद्रा प्रदर्शन ढाँचा",
"system.sections.currencyFormatDescription":
"साइटभरि रकमका दशमलव र विभाजक (मुद्रा मास्टर डाटाबाट अलग)।",
"system.sections.settlement": "सेटलमेन्ट स्वचालन",
"system.sections.settlementDescription":
"टिक स्वतः सेटलमेन्ट, अनुमोदन र भुक्तानी चलाउने नियन्त्रण। यो ब्लकमा परिवर्तित फिल्ड मात्र पेश हुन्छ।",
"system.confirmSaveDrawTitle": "ड्र प्यारामिटर बचत गर्ने?",
"system.confirmSaveDrawDescription":
"यसले यो ब्लकमा ड्र समीक्षा, तालिका समय र कूलडाउन मात्र अद्यावधिक गर्छ।",
"system.confirmSaveCurrencyFormatTitle": "मुद्रा प्रदर्शन ढाँचा बचत गर्ने?",
"system.confirmSaveCurrencyFormatDescription": "यसले दशमलव स्थान र विभाजक अद्यावधिक गर्छ।",
"system.confirmSaveSettlementTitle": "सेटलमेन्ट स्वचालन बचत गर्ने?",
"system.confirmSaveSettlementDescription":
"यसले स्वतः सेटलमेन्ट, अनुमोदन र भुक्तानी स्विच अद्यावधिक गर्छ।",
"play.filters.sectionTitle": "खेल फिल्टर",
"play.filters.sectionDescription":
"पहिले सूची संकुचित गर्नुहोस्, त्यसपछि ब्याच स्विच वा पङ्क्ति सम्पादन प्रयोग गर्नुहोस्।",
"play.filters.keyword": "खेल खोज्नुहोस्",
"play.filters.keywordPlaceholder": "खेल कोड, प्रदर्शन नाम वा श्रेणीले फिल्टर",
"play.filters.category": "श्रेणी",
"play.filters.status": "स्थिति",
"play.filters.allCategories": "सबै श्रेणी",
"play.filters.allStatuses": "सबै स्थिति",
"play.filters.uncategorized": "अवर्गीकृत",
"play.filters.reset": "फिल्टर हटाउनुहोस्",
"play.filters.empty": "मिल्ने खेल प्रकार छैन",
"play.filters.groupCount": "{{count}} खेल",
},
"dashboard.json": {
"analytics.summaryShareProfit": "आफ्नो शेयर नाफा",
"analytics.shareProfitHint":
"विभाजन पछि तपाईंको शेयर — प्लेटफर्म वा सम्पूर्ण टोलीको कुल नाफा/नोक्सान होइन",
currentDrawFinanceHint: "तलका चार्ट ड्र {{drawNo}} का लागि",
"agent.heroEyebrow": "आजको लाइन ककपिट",
"agent.heroTitle": "{{name}} प्रत्यक्ष सञ्चालन",
"agent.creditAllocated": "बाँडिएको {{amount}}",
"agent.creditUsed": "प्रयोग {{amount}}",
"agent.settlementCycle": "चक्र {{cycle}}",
"agent.betOrdersToday": "आजका बाजी अर्डर",
"agent.todayPayout": "आजको भुक्तानी",
"agent.todayProfit": "आजको नाफा",
"agent.sevenDayPayout": "भुक्तानी {{amount}}",
"agent.sevenDayProfit": "नाफा {{amount}}",
"agent.sevenDayShareProfit": "शेयर नाफा {{amount}}",
"agent.topMomentum": "आजको बाजी केन्द्र",
"agent.topMomentumHint": "नाफा {{profit}}",
"agent.topMomentumPayout": "भुक्तानी {{amount}}",
"agent.managementFocus": "व्यवस्थापन केन्द्र",
"agent.focusBet": "आजको बाजी मात्रा निगरानी",
"agent.focusPlayers": "आजका सक्रिय खेलाडी",
"agent.focusBills": "पछ्याउन बाँकी बिल",
"agent.quickStatsTitle": "लाइन अनुमति झलक",
"agent.canCreateChildAgent": "सन्तान एजेन्ट सिर्जना गर्न सक्छ",
"agent.canCreatePlayer": "खेलाडी सिर्जना गर्न सक्छ",
"agent.lineDepth": "लाइन गहिराइ",
"agent.viewBills": "बिल हेर्नुहोस्",
"agent.viewLine": "एजेन्ट लाइन",
"agent.quickLinks.tickets": "टिकट",
"agent.quickLinks.players": "खेलाडी",
"agent.quickLinks.reports": "रिपोर्ट",
"agent.quickLinks.agents": "अधीनस्थ एजेन्ट",
"agent.quickLinks.bills": "एजेन्ट बिल",
"warnings.apiResourceMissing":
"ड्यासबोर्ड विश्लेषण API दर्ता छैन। चलाउनुहोस्: php artisan lottery:admin-auth-sync (वा पछिल्लो माइग्रेसन), त्यसपछि रिफ्रेस।",
},
"draws.json": {
backToList: "ड्र सूचीमा फर्कनुहोस्",
overviewTitle: "ड्र अवलोकन",
overviewBetTotal: "कुल बाजी",
overviewPayoutTotal: "कुल भुक्तानी",
overviewProfitLoss: "नाफा/नोक्सान",
reviewQueueHint: "नतिजा सिर्जना पछि समीक्षा र प्रकाशनमा जारी राख्नुहोस्।",
},
"players.json": {
ticketTableHint:
"यो तालिकाले खेलाडीका हालैका टिकट देखाउँछ। पूर्ण सन्दर्भ र समस्या समाधानका लागि पङ्क्ति कार्यबाट मुख्य टिकट सूचीमा जानुहोस्।",
tabCreditLedger: "क्रेडिट लेजर",
filterSite: "साइट",
filterAllSites: "सबै साइट",
scopeAllSites: "स्कोप: सबै साइटका सबै खेलाडी (सुपर एडमिन)",
scopeFilteredSite: "स्कोप: साइट {{site}}",
scopeAgentLine: "स्कोप: {{site}} · एजेन्ट लाइन “{{name}}” र अधीनस्थ खेलाडी",
scopeSingleSite: "स्कोप: साइट {{site}}",
scopeMultiSite: "स्कोप: {{count}} बाँधिएका साइट; संकुचित गर्न फिल्टर प्रयोग गर्नुहोस्",
fundingMode: "फन्डिङ मोड",
authSource: "प्रमाणीकरण स्रोत",
creditSection: "क्रेडिट लाइन",
usedCredit: "प्रयोग भएको क्रेडिट",
fundingCredit: "क्रेडिट लाइन",
fundingWallet: "मुख्य साइट वालेट",
authMainSite: "मुख्य साइट SSO",
authNative: "लटरी नेटिभ",
creditLimit: "क्रेडिट सीमा",
availableCredit: "उपलब्ध क्रेडिट",
confirmFreezeTitle: "खेलाडी फ्रिज गर्ने?",
confirmFreezeDescription: "खेलाडी {{name}} ले बाजी लगाउन सक्ने छैन।",
confirmUnfreezeTitle: "खेलाडी अनफ्रिज गर्ने?",
confirmUnfreezeDescription: "खेलाडी {{name}} सामान्य स्थितिमा फर्किनेछ।",
},
"reconcile.json": {
createHint:
"छानिएको अवधिमा ट्रान्सफर अर्डर स्क्यान गर्छ, लटरी वालेट लेजर तुलना गर्छ, र वालेट API कन्फिग भएमा मुख्य साइट idempotent रेकर्ड जाँच गर्छ।",
createSuccessEmpty: "स्क्यान सम्पन्न: कुनै समस्या फेला परेन",
transferNo: "ट्रान्सफर नं.",
walletTxnNo: "लटरी वालेट लेनदेन",
mainSiteRef: "मुख्य साइट सन्दर्भ",
mainSiteCheck: "मुख्य साइट जाँच",
actions: "कार्य",
itemMainSiteRecordMissing: "मुख्य साइटमा छैन",
itemMainSiteFailed: "मुख्य साइटमा असफल",
mainSiteMatched: "मुख्य साइट ठीक",
mainSiteNotFound: "मुख्य साइटमा छैन",
mainSiteFailed: "मुख्य साइट असफल",
mainSiteUnavailable: "मुख्य साइट उपलब्ध छैन",
mainSiteSkipped: "जाँच गरिएको छैन",
},
"reports.json": {
"tasks.currentReportHint": "यहाँ हाल छानिएको रिपोर्टका निर्यात कार्य मात्र देखिन्छ।",
"preview.stats.notQueried": "क्वेरी गरिएको छैन",
"preview.stats.notSet": "सेट गरिएको छैन",
},
"risk.json": {
"confirm.closeTitle": "नम्बर बन्द गर्ने?",
"confirm.closeDescription": "नम्बर {{number}} यो ड्रका लागि ब्लक हुनेछ।",
"confirm.recoverTitle": "नम्बर पुन: खोल्ने?",
"confirm.recoverDescription": "नम्बर {{number}} फेरि बाजीका लागि खुल्नेछ।",
},
"tickets.json": {
filterSite: "साइट",
filterAllSites: "सबै साइट",
viewTicketInList: "यो टिकट हेर्नुहोस्",
statusSelectedCount: "{{count}} छानिएको",
},
"wallet.json": {
scopeHint:
"यो क्षेत्र मुख्य साइट वालेट मोड (वालेट लेनदेन र ट्रान्सफर) का लागि हो। क्रेडिट लाइन अवधि सेटलमेन्टका लागि हेर्नुहोस्",
scopeHintSettlementLink: "सेटलमेन्ट केन्द्र",
scopeHintSettlement: "सेटलमेन्ट केन्द्र",
ledgerChannel: "लेजर",
ledgerCredit: "क्रेडिट लेजर",
ledgerWallet: "वालेट लेनदेन",
},
};
function setNested(obj, dotKey, value) {
const parts = dotKey.split(".");
let cur = obj;
for (let i = 0; i < parts.length - 1; i++) {
if (!(parts[i] in cur) || typeof cur[parts[i]] !== "object" || Array.isArray(cur[parts[i]])) {
cur[parts[i]] = {};
}
cur = cur[parts[i]];
}
cur[parts[parts.length - 1]] = value;
}
function sortKeysDeep(value) {
if (Array.isArray(value)) return value;
if (value && typeof value === "object") {
return Object.fromEntries(
Object.keys(value)
.sort()
.map((k) => [k, sortKeysDeep(value[k])]),
);
}
return value;
}
let merged = 0;
for (const [file, keys] of Object.entries(translations)) {
const nePath = path.join(localesDir, "ne", file);
const ne = JSON.parse(fs.readFileSync(nePath, "utf8"));
for (const [dotKey, neValue] of Object.entries(keys)) {
setNested(ne, dotKey, neValue);
merged++;
}
fs.writeFileSync(nePath, `${JSON.stringify(sortKeysDeep(ne), null, 2)}\n`, "utf8");
console.log(`Updated ${file} (+${Object.keys(keys).length} keys)`);
}
console.log(`Total merged: ${merged}`);