#!/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>} */ 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 agent’s 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 agent’s 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}`);