const fs = require('fs'); const path = require('path'); const javaRoot = path.join(__dirname, '..', 'fs-service', 'src', 'main', 'java'); const adminRoots = [ path.join(__dirname, '..', 'fs-admin', 'src', 'main', 'java'), path.join(__dirname, '..', 'fs-saas-admin', 'src', 'main', 'java'), ]; function u(...codes) { return String.fromCharCode(...codes); } function w(rel, content, root = javaRoot) { const full = path.join(root, rel); fs.mkdirSync(path.dirname(full), { recursive: true }); fs.writeFileSync(full, content, 'utf8'); console.log('Wrote', path.relative(path.join(__dirname, '..'), full)); } function patch(rel, fn, root = javaRoot) { const full = path.join(root, rel); if (!fs.existsSync(full)) return; w(rel, fn(fs.readFileSync(full, 'utf8')), root); } function replaceClassComment(content, commentLine) { return content.replace(/\/\*\*[\s\S]*?\*\//, `/**\n * ${commentLine}\n */`); } const T = { behaviorSvcComment: u(0x6309,0x6e20,0x9053,0x805a,0x5408,0x884c,0x4e3a,0x8f68,0x8ff9,0xff1a,0x4f01,0x5fae,0x770b,0x8bfe,0x2f,0x76f4,0x64ad,0xff0c,0x4e2a,0x5fae,0x5916,0x547c,0x7b49,0x3002), courseLiveTab: u(0x770b,0x8bfe,0x2f,0x76f4,0x64ad), mediaSvcComment: u(0x4f01,0x5fae,0x6e20,0x9053,0xff1a,0x770b,0x8bfe,0x20,0x2b,0x20,0x76f4,0x64ad,0x8f68,0x8ff9,0xff08,0x8fd1,0x20,0x4e,0x20,0x5929,0x6309,0x65e5,0x805a,0x5408,0xff09,0x3002), callSvcComment: u(0x4e2a,0x5fae,0x7b49,0x6e20,0x9053,0xff1a,0x5916,0x547c,0x2f,0x901a,0x8bdd,0x8f68,0x8ff9,0xff08,0x63,0x6f,0x6d,0x70,0x61,0x6e,0x79,0x5f,0x76,0x6f,0x69,0x63,0x65,0x5f,0x6c,0x6f,0x67,0x73,0xff09,0x3002), outboundTab: u(0x5916,0x547c,0x8bb0,0x5f55), unknown: u(0x672a,0x77e5), connected: u(0x5df2,0x63a5,0x901a), notConnected: u(0x672a,0x63a5,0x901a), statusPrefix: u(0x72b6,0x6001), watching: u(0x770b,0x8bfe,0x4e2d), finishedCourse: u(0x5b8c,0x8bfe), pendingCourse: u(0x5f85,0x770b,0x8bfe), interrupted: u(0x770b,0x8bfe,0x4e2d,0x65ad), finishedDay: u(0x5df2,0x5b8c,0x8bfe), noRecord: u(0x65e0,0x8bb0,0x5f55), live: u(0x76f4,0x64ad), replay: u(0x56de,0x653e), listHub: u(0x5217,0x8868,0x20,0x48,0x75,0x62,0x20,0x805a,0x5408,0x6587,0x6848,0xff1a,0x7a33,0x5b9a,0x753b,0x50cf,0x20,0x2b,0x20,0x6700,0x65b0,0x6458,0x8981,0x20,0x2b,0x20,0x914d,0x7f6e,0x5b57,0x6bb5,0x20,0x2192,0x20,0x4f9b,0x641c,0x7d22,0x2f,0x5217,0x8868,0x5c55,0x793a,0x3002), statsSnapshot: u(0x753b,0x50cf,0x7edf,0x8ba1,0x5feb,0x7167,0xff08,0x5bf9,0x5e94,0x20,0x6c,0x6f,0x62,0x73,0x74,0x65,0x72,0x5f,0x70,0x72,0x6f,0x66,0x69,0x6c,0x65,0x5f,0x73,0x74,0x61,0x74,0x73,0xff09,0x3002), statsDelta: u(0x753b,0x50cf,0x7edf,0x8ba1,0x589e,0x91cf,0xff08,0x524d,0x540e,0x5feb,0x7167,0x5dee,0xff09,0x3002), summaryView: u(0x5c06,0x4f1a,0x8bdd,0x6458,0x8981,0x5b9e,0x4f53,0x8f6c,0x4e3a,0x9762,0x677f,0x2f,0x8fd0,0x884c,0x65f6,0x53ef,0x7528,0x7684,0x7ed3,0x6784,0x5316,0x89c6,0x56fe,0xff08,0x542b,0x20,0x31,0x31,0x20,0x7ef4,0x20,0x65,0x78,0x74,0x65,0x6e,0x64,0x65,0x64,0x53,0x75,0x6d,0x6d,0x61,0x72,0x79,0xff09,0x3002), factLoader: u(0x4ece) + ' customer_fact / customer_habit ' + u(0x52a0,0x8f7d,0x7a33,0x5b9a,0x753b,0x50cf,0xff0c,0x5408,0x5e76,0x8fdb,0x5165) + ' profile ' + u(0x89c6,0x56fe,0x3002), projectionSync: u(0x4f1a,0x8bdd,0x6458,0x8981,0x5199,0x5165,0x753b,0x50cf,0x8868,0x65f6,0xff0c,0x6295,0x5f71,0x5b57,0x6bb5,0x540c,0x6b65,0x5de5,0x5177,0x3002), statsRecorder: u(0x753b,0x50cf,0x5199,0x5165,0x540e,0x89e6,0x53d1,0x7edf,0x8ba1,0x8868,0x589e,0x91cf,0x66f4,0x65b0,0x3002), statsService: u(0x5ba2,0x6237,0x753b,0x50cf,0x7edf,0x8ba1,0x4e2d,0x95f4,0x5c42,0xff1a,0x589e,0x91cf,0x66f4,0x65b0,0x20,0x2b,0x20,0x6309,0x9700,0x91cd,0x7b97,0xff08,0x975e,0x5b9a,0x65f6,0x5168,0x91cf,0x626b,0x63cf,0xff09,0x3002), timeIntent: u(0x4ece,0x5ba2,0x6237,0x6d88,0x606f,0x89e3,0x6790,0x7ea6,0x5b9a,0x65f6,0x95f4,0x610f,0x56fe,0xff0c,0x7528,0x4e8e,0x20,0x77,0x61,0x69,0x74,0x55,0x6e,0x74,0x69,0x6c,0x20,0x8282,0x70b9,0x3002), canvasSync: u(0x5c06,0x20,0x62,0x72,0x61,0x6e,0x63,0x68,0x65,0x73,0x20,0x4e,0x20,0x6c,0x65,0x67,0x61,0x63,0x79,0x20,0x6E,0x65,0x78,0x74,0x4E,0x6F,0x64,0x65,0x20,0x5F52,0x4E00,0x4E3A,0x20,0x6E,0x65,0x78,0x74,0x4E,0x6F,0x64,0x65,0x43,0x6F,0x64,0x65), bindingStats: u(0x7ED1,0x5B9A,0x6267,0x884C,0x7EDF,0x8BA1,0xFF08,0x6C47,0x603B,0x20,0x2B,0x20,0x5237,0x65B0,0xFF09,0x3002), bindingProvision: u(0x7ED1,0x5B9A,0x5F00,0x901A,0xFF1A,0x6309,0x20,0x72,0x65,0x6C,0x20,0x6279,0x91CF,0x751F,0x6210,0x89E6,0x8FBE,0x4EFB,0x52A1,0xFF08,0x5F02,0x6B65,0x5206,0x6279,0xFF09,0x3002), stageBracket: u(0x3010,0x9636,0x6bb5,0x3011), pipe: u(0xff5c), ellipsis: u(0x2026), touchTask: u(0x3010) + '${channel}' + u(0x3011,0x89e6,0x8fbe,0x4efb,0x52a1), nodeBracket: u(0x8282,0x70b9,0x5b), highQuality: u(0x9ad8,0x8d28,0x91cf,0x56de,0x590d), customerLabel: u(0x5ba2,0x6237), replyLabel: u(0x56de,0x590d), noInstances: u(0x672a,0x9009,0x62e9,0x4efb,0x4f55,0x5b9e,0x4f8b), instancePrefix: u(0x5b9e,0x4f8b), noAccess: u(0x65e0,0x6743,0x9650,0x6216,0x4e0d,0x5b58,0x5728), opFailed: u(0x64cd,0x4f5c,0x5931,0x8d25), splitChars: u(0xff0c,0xff1b), terminated: u(0x5df2,0x7ec8,0x6b62), switchedHuman: u(0x5df2,0x5207,0x6362,0x4eba,0x5de5), instUnit: u(0x4e2a,0x5b9e,0x4f8b), skipPart: u(0xff0c,0x8df3,0x8fc7), unit: u(0x4e2a), allTenants: u(0x5168,0x90e8,0x79df,0x6237), }; // fix broken canvasSync and bindingStats - use proper unicode T.canvasSync = u(0x5c06,0x20,0x62,0x72,0x61,0x6e,0x63,0x68,0x65,0x73,0x20,0x4e,0x20,0x6c,0x65,0x67,0x61,0x63,0x79,0x20,0x6e,0x65,0x78,0x74,0x4e,0x6f,0x64,0x65,0x20,0x5f52,0x4e00,0x4e3A,0x20,0x6e,0x65,0x78,0x74,0x4e,0x6f,0x64,0x65,0x43,0x6f,0x64,0x65); T.bindingStats = u(0x7ed1,0x5b9a,0x6267,0x884c,0x7edf,0x8ba1,0xff08,0x6c47,0x603b,0x20,0x2b,0x20,0x5237,0x65b0,0xff09,0x3002); T.bindingProvision = u(0x7ed1,0x5b9a,0x5f00,0x901a,0xff1a,0x6309,0x20,0x72,0x65,0x6c,0x20,0x6279,0x91cf,0x751f,0x6210,0x89e6,0x8fbe,0x4efb,0x52a1,0xff08,0x5f02,0x6b65,0x5206,0x6279,0xff09,0x3002); w('com/fs/company/service/workflow/profile/panel/CustomerProfileBehaviorTrajectoryService.java', `package com.fs.company.service.workflow.profile.panel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.LinkedHashMap; import java.util.Map; /** * ${T.behaviorSvcComment} */ @Service public class CustomerProfileBehaviorTrajectoryService { @Autowired private CustomerProfileMediaTrajectoryService mediaTrajectoryService; @Autowired private CustomerProfileCallTrajectoryService callTrajectoryService; public Map loadBehaviorTrajectory(Long companyId, String channelType, String profileKey, Long customerId, Long contactId, Integer days) { String channel = normalizeChannel(channelType); Map result = new LinkedHashMap<>(); result.put("channelType", channel); if ("WX".equals(channel)) { Map call = callTrajectoryService.loadCallTrajectory(companyId, customerId, days); result.put("behaviorType", call.get("behaviorType")); result.put("tabLabel", call.get("tabLabel")); result.put("days", call.get("days")); result.put("stats", call.get("stats")); result.put("callEvents", call.get("callEvents")); result.put("courseDays", java.util.Collections.emptyList()); result.put("liveEvents", java.util.Collections.emptyList()); return result; } Map media = mediaTrajectoryService.loadMediaTrajectory( companyId, channel, profileKey, customerId, contactId, days); result.put("behaviorType", "course_live"); result.put("tabLabel", "${T.courseLiveTab}"); result.put("days", media.get("days")); result.put("stats", media.get("stats")); result.put("courseDays", media.get("courseDays")); result.put("liveEvents", media.get("liveEvents")); result.put("callEvents", java.util.Collections.emptyList()); result.put("externalContactId", media.get("externalContactId")); return result; } private String normalizeChannel(String channelType) { if (!StringUtils.hasText(channelType)) { return "QW"; } return channelType.trim().toUpperCase(); } } `); patch('com/fs/company/service/workflow/profile/panel/CustomerProfileMediaTrajectoryService.java', (c) => { let s = c; s = replaceClassComment(s, T.mediaSvcComment); s = s.replace(/labels\.put\(1, "[^"]+"\);[\s\S]*?labels\.put\(4, "[^"]+"\);/, `labels.put(1, "${T.watching}");\n labels.put(2, "${T.finishedCourse}");\n labels.put(3, "${T.pendingCourse}");\n labels.put(4, "${T.interrupted}");`); s = s.replace(/item\.put\("statusLabel", "[^"]+"\);/g, `item.put("statusLabel", "${T.noRecord}");`); s = s.replace(/LOG_TYPE_LABELS\.getOrDefault\(logType, "[^"]+"\)/g, `LOG_TYPE_LABELS.getOrDefault(logType, "${T.unknown}")`); s = s.replace(/if \("finished"\.equals\(status\)\) \{\s*return "[^"]+";/, `if ("finished".equals(status)) {\n return "${T.finishedDay}";`); s = s.replace(/if \("watching"\.equals\(status\)\) \{\s*return "[^"]+";/, `if ("watching".equals(status)) {\n return "${T.watching}";`); s = s.replace(/if \("interrupted"\.equals\(status\)\) \{\s*return "[^"]+";/, `if ("interrupted".equals(status)) {\n return "${T.interrupted}";`); s = s.replace(/if \("pending"\.equals\(status\)\) \{\s*return "[^"]+";/, `if ("pending".equals(status)) {\n return "${T.pendingCourse}";`); s = s.replace(/return "[^"]+";\s*\}\s*\n\s*private String watchTypeLabel/, `return "${T.noRecord}";\n }\n\n private String watchTypeLabel`); s = s.replace(/if \(watchType == 1\) \{\s*return "[^"]+";/, `if (watchType == 1) {\n return "${T.live}";`); s = s.replace(/if \(watchType == 2\) \{\s*return "[^"]+";/, `if (watchType == 2) {\n return "${T.replay}";`); return s; }); patch('com/fs/company/service/workflow/profile/panel/CustomerProfileCallTrajectoryService.java', (c) => { let s = c; s = replaceClassComment(s, T.callSvcComment); s = s.replace(/result\.put\("tabLabel", "[^"]+"\);/, `result.put("tabLabel", "${T.outboundTab}");`); s = s.replace(/private String callStatusLabel[\s\S]*?return "[^"]*" \+ s;/, `private String callStatusLabel(Object status) { Integer s = toInteger(status); if (s == null) { return "${T.unknown}"; } if (s == 1) { return "${T.connected}"; } if (s == 0) { return "${T.notConnected}"; } return "${T.statusPrefix}" + s;`); return s; }); const commentMap = { 'com/fs/company/service/workflow/profile/panel/ProfileListAggregateHelper.java': T.listHub, 'com/fs/company/service/workflow/profile/panel/ProfileStatsSnapshot.java': T.statsSnapshot, 'com/fs/company/service/workflow/profile/panel/ProfileStatsDelta.java': T.statsDelta, 'com/fs/company/service/workflow/profile/panel/ConversationSummaryViewHelper.java': T.summaryView, 'com/fs/company/service/workflow/profile/panel/CustomerProfileFactLoader.java': T.factLoader, 'com/fs/company/service/workflow/profile/panel/ProfileSummaryProjectionSync.java': T.projectionSync, 'com/fs/company/service/workflow/profile/LobsterProfileStatsRecorder.java': T.statsRecorder, 'com/fs/company/service/workflow/profile/LobsterProfileStatsService.java': T.statsService, 'com/fs/company/service/workflow/trigger/TimeIntentDetector.java': T.timeIntent, 'com/fs/company/service/workflow/ILobsterBindingExecStatsService.java': T.bindingStats, 'com/fs/company/service/workflow/execution/LobsterBindingProvisionServiceImpl.java': T.bindingProvision, }; for (const [rel, text] of Object.entries(commentMap)) { patch(rel, (c) => replaceClassComment(c, text)); } patch('com/fs/company/service/workflow/canvas/WorkflowCanvasSyncUtil.java', (c) => c.replace(/\/\*\*[^*]*\*\//, `/** ${T.canvasSync} */`)); patch('com/fs/company/service/workflow/profile/panel/ProfileSummaryProjectionSync.java', (c) => { let s = c; s = s.replace(/clipPreview\("[^"]*" \+ stage\.trim\(\) \+ " [^"]* " \+ snippet\)/, `clipPreview("${T.stageBracket}" + stage.trim() + "${T.pipe}" + snippet)`); s = s.replace(/clipPreview\("[^"]*" \+ stage\.trim\(\)\)/, `clipPreview("${T.stageBracket}" + stage.trim())`); s = s.replace(/PREVIEW_MAX\) \+ "[^"]+"/, `PREVIEW_MAX) + "${T.ellipsis}"`); return s; }); patch('com/fs/company/service/workflow/trigger/TimeIntentDetector.java', (c) => c.replace(/\[\u007cu70b9:\?\?\]/g, '[\\u70b9:\\uFF1A]').replace(/\[:\\?\?\]/g, '[:\\uFF1A]')); patch('com/fs/company/service/workflow/execution/LobsterBindingProvisionServiceImpl.java', (c) => c.replace(/setTaskName\("[^"]*" \+ channelType \+ "[^"]*"\)/, `setTaskName("${u(0x3010)}" + channelType + "${u(0x3011,0x89e6,0x8fbe,0x4efb,0x52a1)}")`)); patch('com/fs/company/service/workflow/learning/impl/LobsterSkillManageServiceImpl.java', (c) => { let s = c; s = s.replace(/sb\.append\("[^"]*\["\)/, `sb.append("${T.nodeBracket}")`); s = s.replace(/append\("\] [^"]* \("/, `append("] ${T.highQuality} ("`); let n = 0; s = s.replace(/sb\.append\("[^"]*: "\)/g, () => { n += 1; return n === 1 ? `sb.append("${T.customerLabel}: ")` : `sb.append("${T.replyLabel}: ")`; }); return s; }); patch('com/fs/company/service/workflow/impl/LobsterInstanceOpsService.java', (c) => { let s = c; s = s.replace(/result\.put\("message", "[^"]+"\)/, `result.put("message", "${T.noInstances}")`); s = s.replace(/errors\.add\("[^"]* " \+ instanceId \+ " [^"]+"\)/, `errors.add("${T.instancePrefix} " + instanceId + " ${T.noAccess}")`); s = s.replace(/ajax\.get\("msg"\) : "[^"]+"\)/, `ajax.get("msg") : "${T.opFailed}")`); s = s.replace(/errors\.add\("[^"]* " \+ instanceId \+ ": "/g, `errors.add("${T.instancePrefix} " + instanceId + ": "`); s = s.replace(/split\("\[,;[^"]+"\)/, `split("[,;${T.splitChars}]")`); s = s.replace(/\? \("[^"]* " \+ success \+ " [^"]*"\)/, `? ("${T.terminated} " + success + " ${T.instUnit}" + (skipped > 0 ? "${T.skipPart} " + skipped + " ${T.unit}" : "") )`); s = s.replace(/:\("[^"]* " \+ success \+ " [^"]*"\)/, `: ("${T.switchedHuman} " + success + " ${T.instUnit}" + (skipped > 0 ? "${T.skipPart} " + skipped + " ${T.unit}" : "") )`); return s; }); for (const root of adminRoots) { patch('com/fs/lobster/controller/LobsterOptimizationController.java', (c) => c.replace(/scopeInfo\.put\("label", "[^"]+"\)/, `scopeInfo.put("label", "${T.allTenants}")`), root); } function scanGarbled(dir) { const bad = []; (function walk(d) { for (const ent of fs.readdirSync(d, { withFileTypes: true })) { const p = path.join(d, ent.name); if (ent.isDirectory()) walk(p); else if (ent.name.endsWith('.java')) { const c = fs.readFileSync(p, 'utf8'); if (/\?\?\?/.test(c)) bad.push(path.relative(path.join(__dirname, '..'), p)); } } })(dir); return bad; } console.log('Remaining ??? in fs-service:', scanGarbled(javaRoot)); console.log('Done');