import ResponseInject from "../../interface/ResponseInject"; import { Button, MiniProfile } from "../../interface/responseType/MiniProfile"; import Sidebar from "../../interface/responseType/Sidebar"; import InventoryItemInfo from "../../interface/responseType/InventoryItemInfo"; import CommonUtils from "../utils/CommonUtils"; import { itemNameDict, itemPageDict } from "../../dictionary/translation"; import Provider from "../provider/Provider"; import ClassName from "../../container/ClassName"; import { Injectable } from "../../container/Injectable"; import LocalConfigWrapper from "../LocalConfigWrapper"; import Logger from "../Logger"; /** * 翻译重构 */ @ClassName('TranslateNew') @Injectable() export default class TranslateNew extends Provider implements ResponseInject { constructor( private readonly localConfigWrapper: LocalConfigWrapper, private readonly logger: Logger, private readonly commonUtils: CommonUtils, ) { super(); } /** * fetch xhr 返回数据的翻译处理 * @param url * @param body * @param opt */ public responseHandler( url: string, body: { json: unknown, text: string, isModified: boolean }, opt: { method: 'GET' | 'POST', requestBody: string } ): void { if (!this.localConfigWrapper.config.transNew) return; // TODO 字典抽取 let map = { iconMap: { 'Online': { title: "在线" }, 'Level 100': { title: "100 级" }, 'Jail': { title: "坐牢", description: { map: { 'Being questioned for suspicious online activity.': '因可疑的网上活动而被盘问。', 'Suspect of a presidential assassination': '刺杀总统的嫌疑人', } } }, 'Federal jail': { title: "联邦监狱(FJ)", description: { map: { 'Account marked for deletion': '账号标记删除', } } }, 'Idle': { title: "暂离" }, 'Offline': { title: "离线" }, 'Enby': { title: "中性" }, 'Male': { title: "男性" }, 'Female': { title: "女性" }, 'Donator': { title: "DP捐助者" }, 'Subscriber': { title: "蓝星订阅者" }, 'Traveling': { title: "旅行中" }, 'Hospital': { title: "已入院", description: { map: { 'Fell from a two story building while on a hitman mission': '在执行刺杀任务时从二楼摔下', 'Overdosed on Xanax': '吃 Xan 后 OD', 'Mauled by a guard dog': '被看门狗咬', }, replace: [/(Hospitalized|Mugged|Attacked|Defeated) by (someone|)(.+Early discharge available.+)?/, '被 $2 $1$3'], attachedMap: [ { 'Hospitalized': '强制住院', 'Mugged': '抢劫', 'Attacked': '攻击', 'Defeated': '击败', }, { 'someone': '某人2' }, { '
Early discharge available': '
提前出院(ED)可用' }, ], } }, 'Bounty': { title: "被悬赏", description: { replace: [/On this person's head for (\$[,0-9]+)( : .+)?/, '$1 悬赏此人$2'] } }, 'Married': { title: "已婚", description: { replace: [/To/, '和'] } }, 'Company': { title: "公司", description: { replace: [/([a-zA-Z ]+)( of )(.+) \(([aA-zZ ]+)\)$/, "【$3】$4的$1"], attachedMap: [ { 'Director': '老板', 'Salesperson': '销售员', }, {}, {}, { 'Private Security Firm': '安保公司 (PSF)', 'Lingerie Store': '内衣店 (LS)', 'Adult Novelties': '成人用品店 (AN)', } ] } }, 'Job': { title: "系统公司", description: { replace: [/([a-zA-Z ]+) (in|at) (.+)$/, "$3的$1"], attachedMap: [ { 'Manager': '经理', }, {}, { 'a Grocery Store': '杂货店', } ] } }, 'Faction': { title: "帮派", description: { replace: [/([aA-zZ ]+)( of )(.+)/, "[$3] 帮派的 [$1]"] } }, 'Bazaar': { title: "摊位", description: { map: { 'This person has items in their bazaar for sale': '此人在摊位上有物品出售' } } }, }, buttonMap: { message: { 'Add $0 to your enemy list': '添加 $0 到敌人列表', 'Add $0 to your friend list': '添加 $0 到好友列表', 'You are currently traveling': '你在天上', 'Initiate a chat with $0': '与 $0 私聊', 'You are not in Torn': '你不在城内', "View $0's personal statistics": '查看 $0 的个人统计数据', "Place a bounty on $0": '对 $0 发起悬赏', "Report $0 to staff": '向工作人员举报 $0', "Send $0 a message": '发邮件给 $0', "View $0's display case": '查看 $0 的展柜', "$0 is currently in hospital": '$0 正在住院', "$0 has not been online in the last 6 hours": '$0 超 6 小时未在线', "Give some money to $0": '给 $0 一些钱', "$0's bazaar is closed": '$0 的摊位已关闭', "View $0's bazaar": '查看 $0 的摊位', "Initiate a trade with $0": '与 $0 交易', "$0 has no items in their bazaar": '$0 的摊位是空的', "$0 is currently in jail": '$0 目前在坐牢', "Pay $0's bail": '支付 $0 的保释金', "Bust $0 out of jail": '把 $0 从监狱里踢出来', "$0 is currently in federal jail": '$0 目前在联邦监狱(FJ)', "NPC's cannot be bountied": 'NPC 不能被悬赏', "$0 is hiding out abroad": '$0 正在海外躲藏', "$0 has no items in their display cabinet": '$0 的展柜是空的', "You do not have enough energy to attack $0": '你没有足够的能量攻击 $0', "You have not met this person recently or they have blocked you": '最近你没有遇见此人,或已被屏蔽', }, }, destinationMap: { 'Mexico': '墨西哥', 'Cayman Islands': '开曼群岛', 'Canada': '加拿大', 'Hawaii': '夏威夷', 'United Kingdom': '英国', 'Argentina': '阿根廷', 'Switzerland': '瑞士', 'Japan': '日本', 'China': '中国', 'UAE': '阿联酋(UAE)', 'South Africa': '南非', }, barMap: { 'Chain': { name: '连击' }, 'Energy': { name: '能量' }, 'Happy': { name: '快乐' }, 'Life': { name: '血量' }, 'Nerve': { name: '犯罪' }, }, areaMap: { calendar: { name: '日历', shortName: '日历' }, traveling: { name: '飞行中', shortName: '飞行' }, casino: { name: '赌场' }, city: { name: '城市' }, crimes: { name: '犯罪' }, education: { name: '教育', shortName: '教育' }, forums: { name: '论坛' }, gym: { name: '健身房' }, hall_of_fame: { name: '名人堂', shortName: '排名' }, home: { name: '主页', shortName: '主页' }, hospital: { name: '医院' }, items: { name: '物品' }, jail: { name: '监狱' }, job: { name: '工作', shortName: '工作' }, missions: { name: '任务' }, my_faction: { name: '帮派', shortName: '帮派' }, newspaper: { name: '报纸', shortName: '报纸' }, properties: { name: '住宅', shortName: '住宅' }, recruit_citizens: { name: '招募玩家', shortName: '招募' }, }, accountMap: { awards: { name: '奖章' }, events: { name: '通知' }, messages: { name: '邮件' } } }; try { // Mini Profile if (url.includes('profiles.php?step=getUserNameContextMenu')) { let jsonObj: MiniProfile = body.json as MiniProfile; this.logger.info('翻译mini profile返回内容'); // 状态图标 jsonObj.icons.forEach(icon => { let iconMap = map.iconMap; let oriTitle = icon.title; if (iconMap[oriTitle]) { icon.title = iconMap[oriTitle].title; let desc = iconMap[oriTitle].description; let oriDesc = icon.description; if (icon.description && desc) { if (desc.map && desc.map[oriDesc]) { icon.description = desc.map[oriDesc]; } else if (desc.replace) { icon.description = oriDesc.replace(new RegExp(desc.replace[0]), desc.replace[1]); if (desc.attachedMap) { desc.attachedMap.forEach((item, index) => { let factor = oriDesc.replace(new RegExp(desc.replace[0]), '$' + (index + 1)); this.logger.info({ factor }); let cn = item[factor]; cn && (icon.description = icon.description.replace(factor, cn)); }); } } } } }); // 离线转钱警告 if (jsonObj.user.sendMoneyWarning) { let daysMatch = jsonObj.user.sendMoneyWarning.match(/[0-9]+/); if (daysMatch.length !== 0) jsonObj.user.sendMoneyWarning = `警告:此人已离线 ${ daysMatch[0] } 天`; } // 按钮 let buttons = jsonObj.profileButtons.buttons; let buttonKeyList = Object.keys(buttons); let username = jsonObj.user.playerName; let msgMap = map.buttonMap.message; buttonKeyList.forEach(buttonKey => { if (buttons[buttonKey].state === 'hidden') return; let button: Button = buttons[buttonKey]; let oriMsg = button.message.replace(username, '$0'); if (msgMap[oriMsg]) { button.message = msgMap[oriMsg].replace('$0', username); } }); // TODO 称号 // 用户状态 let status = jsonObj.userStatus.status.type; switch (status) { case 'traveling-to': { let origin = jsonObj.userStatus.status.to.simpleName; let cn = map.destinationMap[origin] cn && (jsonObj.userStatus.status.to.simpleName = cn); break; } case 'traveling-from': { let origin = jsonObj.userStatus.status.from.simpleName; let cn = map.destinationMap[origin] cn && (jsonObj.userStatus.status.from.simpleName = cn); break; } case 'abroad': { let origin = jsonObj.userStatus.status.in.simpleName; let cn = map.destinationMap[origin] cn && (jsonObj.userStatus.status.in.simpleName = cn); break; } case 'jail': { let origin = jsonObj.userStatus.status.description; let cn = map.iconMap.Jail.description.map[origin]; cn && (jsonObj.userStatus.status.description = cn); break; } } body.isModified = true; this.logger.info({ 'localized': jsonObj }); } // TODO 边栏 else if (url.includes('sidebarAjaxAction.php?q=sync')) { let response = body.json as Sidebar; type target = { [k: string]: { name: string, shortName?: string } }; let nameMapReplace = (target: target, _map: target) => { Object.keys(target).forEach(key => { if (target[key] && _map[key]) { target[key].name = _map[key].name; if (target[key].shortName && _map[key].shortName) { target[key].shortName = _map[key].shortName; } } }); }; nameMapReplace(response.areas, map.areaMap); nameMapReplace(response.account, map.accountMap); body.isModified = true; } // 物品详情 else if ((url.includes('inventory.php?step=info')) || (url.includes('inventory.php') && opt?.method === 'POST' && typeof opt?.requestBody === 'string' && opt?.requestBody.includes('step=info'))) { this.logger.info('responseHandler'); let resp = body.json as InventoryItemInfo; // TODO 维护通用物品数据(对应名称、描述、类型)缓存 let map: { [k: string]: Partial } = { 'Glass of Beer': { itemName: '一杯啤酒', itemInfo: '[译]Only savages drink beer straight out of the bottle. This glass of beer is obtained fresh from the keg, and provides the same level of drunken joy as you\'d get from a regular bottle of suds. Provides a moderate nerve increase when consumed.', itemInfoContent: "
" + "一杯啤酒 是酒类物品" + "
" + "Only savages drink beer straight out of the bottle. This glass of beer is obtained fresh from the keg, and provides the same level of drunken joy as you'd get from a regular bottle of suds. Provides a moderate nerve increase when consumed." + "
效果: 犯罪 + 2,增幅CD + 1h。
", }, }; let idMap = { 816: 'Glass of Beer' }; let itemInfo = this.commonUtils.getItemByIdOrName(resp.itemName, idMap, map); if (itemInfo) { body.isModified = true; resp.itemInfo = itemInfo.itemInfo; resp.itemName = itemInfo.itemName; resp.itemInfoContent = itemInfo.itemInfoContent; } // TODO 老字典 let itemName = itemNameDict[resp.itemName]; if (itemName) { body.isModified = true; resp.itemInfoContent = resp.itemInfoContent .replace('The ', '') .replace(resp.itemName, `${ itemName }(${ resp.itemName })`); } } // 物品列表 else if ((url.includes('item.php') || url.includes('inventory.php')) && opt?.method === 'POST' && typeof opt?.requestBody === 'string' && (opt?.requestBody.includes('step=getCategoryList') || opt?.requestBody.includes('step=getList') || opt?.requestBody.includes('step=getNotAllItemsListWithoutGroups') )) { let resp = body.json as { html: string }; if (resp.html) { let tmp = document.createElement('div'); tmp.innerHTML = resp.html; this.logger.info(tmp); tmp.childNodes.forEach(li => { if (li.nodeType === 1) { let elem = li as Element; // 物品名 let name = elem.querySelector('.name-wrap .name'); let nameZh = itemNameDict[name.innerText.trim()]; if (nameZh) { name.innerText = `${ name.innerText } ${ nameZh }`; } // 操作按钮 let actions = elem.querySelectorAll('.icon-h'); actions.forEach(action => { let attrTitle = action.getAttribute('title'); // TODO let zh = itemPageDict[attrTitle]; if (zh) { action.setAttribute('title', zh); } }); } }); resp.html = tmp.innerHTML; body.isModified = true; } } // TODO 物品列表json版 else if (url.includes('inventory.php') && opt?.method === 'POST' && typeof opt?.requestBody === 'string' && opt?.requestBody.includes('step=getList')) { } } catch (e) { this.logger.error('responseHandler', e.stack || e.message); } } }