前言
最近迷上了我的世界,不过curseforge平台上面下载的整合包都没有汉化,而对于我这样的英语不咋地的玩家就非常不友好,等着其他大佬汉化完不知道要什么时候,而且整合包更新的有些比较快,于是就想着简单的机翻一些,能看懂个大概意思,并保留原内容。
项目使用nodejs完成,使用的百度翻译进行翻译。代码就不解释了,直接放上完整代码吧。(仅支持1.16,其他未测试,估计不能用)
完整代码
const path = require('path')
const fse = require('fs-extra')
const axios = require('axios')
// 游戏版本根目录
const baseUrl = 'D:/app/pcl2/.minecraft/versions/Ragnamod VI'
// ftb任务目录
const ftbQuests = path.join(baseUrl, 'config/ftbquests/quests/chapters')
// 自定义方块目录
const displayName = path.join(baseUrl, 'kubejs/startup_scripts')
startTranslate()
// 开始翻译
async function startTranslate() {
await startFtbQuests()
await startDisplayName()
}
// 开始汉化ftb任务
async function startFtbQuests() {
// 获取任务文件
const snbtFiles = fse.readdirSync(ftbQuests)
if (snbtFiles.length) {
for (let i = 0; i < snbtFiles.length; i++) {
const fileName = snbtFiles[i]
// 备份文件或者有备份文件的任务文件不再翻译
if (
fileName.endsWith('.bak') ||
fse.existsSync(path.join(ftbQuests, fileName + '.bak'))
) {
continue
}
// 读取任务文件内容
const filePath = path.join(ftbQuests, fileName)
const fileContent = fse.readFileSync(filePath, { encoding: 'utf-8' })
// 获取每一行内容
const lines = fileContent.split(/\r?\n/)
// 存储当前行所属的字段
let currentType = ''
// 存储要翻译的信息(行号:文字)
const translateObject = {}
// 遍历行
for (let j = 0; j < lines.length; j++) {
const line = lines[j]
let text = ''
// 如果当前行含有冒号(:),则读取要翻译的文字,并存储当前行所属字段
if (line.includes(':')) {
const lineTrim = line.trim()
// 存储当前行所属字段
currentType = lineTrim.split(':')[0]
// 只需对下面三个字段进行翻译,所以仅存储这些
if (lineTrim.indexOf('title') === 0) {
text = lineTrim.match(/"([^"]+)/)?.[1]
} else if (lineTrim.indexOf('subtitle') === 0) {
text = lineTrim.match(/"([^"]+)/)?.[1]
} else if (lineTrim.indexOf('description') === 0) {
text = lineTrim.match(/"([^"]+)/)?.[1]
}
// 否则如果当前行不包括特殊符号([]{}),则认为有可能是subtitle或者description的数据,判断后进行存储
} else if (!/\}|\]|\{|\[/g.test(line)) {
if (currentType === 'subtitle' || currentType === 'description') {
text = line.match(/"([^"]+)/)?.[1]
}
}
// 如果当前行有要翻译的文字,则进行存储
if (text) {
translateObject[j] = text
}
}
// 处理要翻译的数据
const translateObjectKeys = Object.keys(translateObject)
const translateObjectValues = Object.values(translateObject)
// 有则翻译
if (translateObjectValues.length) {
try {
// 调用翻译方法,使用同步,防止接口请求频繁导致失败。
const result = await translate(translateObjectValues)
const res = result.flat()
console.log(`文件 ${fileName} 获取翻译成功。`)
const newContent = lines
.map((line, index) => {
if (translateObject[index]) {
const lineIndex = translateObjectKeys.indexOf(index.toString())
return line.replace(
/"[^"]+"/g,
`"${res[lineIndex].dst}(${res[lineIndex].src})"`
)
}
return line
})
.join('\n')
backupAndWrite(ftbQuests, fileName, newContent)
} catch (err) {
console.log('-------------ERROR START------------')
console.log(`文件 ${fileName} 获取翻译失败。`)
console.log(err)
console.log('-------------ERROR END------------')
}
}
}
}
}
// 翻译自定义物品
async function startDisplayName() {
// 获取自定义物品文件
const files = fse.readdirSync(displayName)
if (files.length) {
for (let i = 0; i < files.length; i++) {
const fileName = files[i]
// 备份文件或者有备份文件的文件不再翻译
if (
fileName.endsWith('.bak') ||
fse.existsSync(path.join(displayName, fileName + '.bak'))
) {
continue
}
// 读取任务文件内容
const filePath = path.join(displayName, fileName)
const fileContent = fse.readFileSync(filePath, { encoding: 'utf-8' })
// 获取每一行内容
const lines = fileContent.split(/\r?\n/)
// 存储要翻译的信息(行号:文字)
const translateObject = {}
// 遍历行
for (let j = 0; j < lines.length; j++) {
const line = lines[j]
// 如果当前行含有.displayName(,则读取要翻译的文字
if (line.includes('.displayName(')) {
const text = line.match(/\.displayName\(['"]([^\)]+)['"]\)/)?.[1]
translateObject[j] = text
}
}
// 处理要翻译的数据
const translateObjectKeys = Object.keys(translateObject)
const translateObjectValues = Object.values(translateObject)
// 有则翻译
if (translateObjectValues.length) {
try {
// 调用翻译方法,使用同步,防止接口请求频繁导致失败。
const result = await translate(translateObjectValues)
const res = result.flat()
console.log(`文件 ${fileName} 获取翻译成功。`)
const newContent = lines
.map((line, index) => {
if (translateObject[index]) {
const lineIndex = translateObjectKeys.indexOf(index.toString())
return line.replace(
/\.displayName\(['"][^'"]+['"]\)/g,
`.displayName('${res[lineIndex].dst}(${res[lineIndex].src})')`
)
}
return line
})
.join('\n')
backupAndWrite(displayName, fileName, newContent)
} catch (err) {
console.log('-------------ERROR START------------')
console.log(`文件 ${fileName} 获取翻译失败。`)
console.log(err)
console.log('-------------ERROR END------------')
}
}
}
}
}
// 翻译(因百度接口限制一次最多5000字,先处理,后调用接口)
function translate(texts) {
if (!texts.length) {
return ''
}
let textIndex = 0
const textArr = ['']
for (let i = 0; i < texts.length; i++) {
if ((textArr[textIndex] + texts[i] + '\n').length < 5000) {
textArr[textIndex] = textArr[textIndex] + texts[i] + '\n'
} else {
textIndex++
textArr[textIndex] = texts[i] + '\n'
}
}
return Promise.all(textArr.map((text) => translateBase(text)))
}
// 翻译接口
function translateBase(text) {
return axios({
method: 'post',
url: 'https://fanyi.baidu.com/transapi',
headers: {
Accept: '*/*',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Host: 'fanyi.baidu.com',
Origin: 'https://fanyi.baidu.com',
Referer: 'https://fanyi.baidu.com/'
},
data: {
from: 'en',
to: 'zh',
source: 'txt',
query: text
}
}).then((res) => {
return res.data.data
})
}
// 备份文件并写入新数据
function backupAndWrite(fileDir, fileName, text) {
const filePath = path.join(fileDir, fileName)
const bakPath = path.join(fileDir, fileName + '.bak')
// 备份文件不存在时备份
if (!fse.existsSync(bakPath)) {
fse.renameSync(filePath, bakPath)
}
// 写入新数据
fse.writeFile(filePath, text, { encoding: 'utf-8' }, () => {
console.log(`文件 ${fileName} 翻译替换完成。`)
})
}
项目依赖
"axios": "^1.2.0",
"fs-extra": "^10.1.0"
使用说明
1、创建一个目录,并新建文件main.js,将完整代码粘贴进去。
2、安装依赖包,修改整合包目录:baseUrl
3、运行脚本:node main.js
结语:适合尝鲜整合包,却看不懂英文的,可以有大致了解。