抽取ts代码中的中文字符串(包括模板字符串)

功能

  • 给定其实ID和类名后缀;
  • 找出字面量字符串、模板字符串(替换为{x});
  • 生成.ts枚举ID类、可直接复制到表的文本。

流程

    1.动态部分要改:ID起点、类名
    2.脚本执行后——>复制.ts到游戏 —— 复制.txt到表
    3.修改代码
    4.提交:表、代码

源码(nodejs环境运行)

const fs = require('fs');
const path = require('path');
const { parse } = require('@typescript-eslint/typescript-estree');
const readline = require('readline');

const POSTFIX_TS = ".ts"
// =============================动态部分=============================
const START_ID = 2000;
const FILENAME_POSTFIX = "Equip";
// =============================动态部分=============================
const scriptDir = __dirname;
const CharConstClsName = "CharConst";
const IO_ENCODING = "utf8";
const COMBINE_NAME = `${ CharConstClsName }${ FILENAME_POSTFIX }`;
const CharConstTSFile = `${COMBINE_NAME}${POSTFIX_TS}`;
const input_dir = `${scriptDir}\\input\\`;
const output_dir = `${scriptDir}\\output\\`;
const output = `${COMBINE_NAME}.txt`;
const output_simple = `${COMBINE_NAME}_Simple.txt`;
const SHOW_FILE_LINE = true;
const IS_GEN_SIMPLE_STR = true;
let strings = new Set();
let strings_simple = new Set();

function traverse(filePath) {
    const content = fs.readFileSync(filePath, IO_ENCODING);
    try {
        // 生成 AST
        const ast = parse(content, {
            ecmaVersion: 2020,     // 指定 ECMAScript 版本
            sourceType: 'module',   // 模块化语法
            loc: true,             // 保留行号信息
            range: true            // 保留字符位置信息
        });
        traverseAST(ast, filePath);
    } catch (e) {
        console.warn(`Parse error in ${filePath}`);
    }
}
let tempDiffIdx = 1;
const __Mark = "___";

function traverseAST(node, file) {
    if (node.type === 'Literal' &&
        typeof node.value === 'string' &&
        /[\u4e00-\u9fa5]/.test(node.value)) { // Unicode 中文字符范围
        handleStringLiteral(node, file);
    }
    if (node.type === 'TemplateLiteral') {
        let processedStr = '';
        let argIndex = 0; // 参数索引计数器

        // 遍历所有静态部分和动态表达式
        for (let i = 0; i < node.quasis.length; i++) {
            const quasi = node.quasis[i];
            processedStr += quasi.value.raw;
            if (i < node.quasis.length - 1) {
                processedStr += `{${argIndex++}}`;
            }
        }

        if (/[\u4e00-\u9fa5]/.test(processedStr)) {
            handleStringLiteral({
                value: processedStr,
                loc: node.loc
            }, file);
        }
    }

    for (let key in node) {
        if (node.hasOwnProperty(key) &&
            typeof node[key] === 'object' &&
            node[key] !== null) {
            traverseAST(node[key], file);
        }
    }
}

function handleStringLiteral(node, file) {
    if (SHOW_FILE_LINE) {
        const line = node.loc ? node.loc.start.line : '未知行';
        strings.add(`${file}:${line}\n${node.value}`);
    }

    if (IS_GEN_SIMPLE_STR) {
        strings_simple.add(`${node.value}${__Mark}${tempDiffIdx++}`);
    }
}

function walkDir(currentPath) {
    if (!fs.existsSync(currentPath)) {
        console.error(`路径不存在: ${currentPath}`);
        return;
    }

    const files = fs.readdirSync(currentPath);

    for (const file of files) {
        const fullPath = path.join(currentPath, file);
        const stat = fs.statSync(fullPath);
        if (stat.isDirectory()) {
            walkDir(fullPath);
        } else if (file.endsWith(POSTFIX_TS)) {
            traverse(fullPath);
        } else {
            
        }
    }
}


// ============== 新增代码:生成 CharConst.ts ==============
function generateEnumFile() {
    const entries = Array.from(strings).map(entry => {
        const [fileLine, value] = entry.split('\n');
        const lastColonIndex = fileLine.lastIndexOf(':');
        return {
            file: fileLine.slice(0, lastColonIndex),
            line: fileLine.slice(lastColonIndex + 1),
            value: value
        };
    });

    const enumLines = entries.map(({ file, line, value }, index) => {
        const id = index + START_ID;
        const key = `ID_${id}`;
        const fileName = path.basename(file);
        // 使用 JSON.stringify 自动转义特殊字符(如 "、\n 等)
        return `\t// ${fileName}:${line}\n\t${key} = ${id}, //${JSON.stringify(value)}`;
    });
    const enumContent = `/** Attention! The annotations will not change along with the actual table. */\nexport enum ${COMBINE_NAME} {\n${enumLines.join('\n')}\n}`;
    fs.writeFileSync(output_dir+CharConstTSFile, enumContent, IO_ENCODING);
}
const enableDelOutput = true;
const delOutputTipsShow = true;

async function clearOutputDir() {
    const dirPath = output_dir;
    if (!enableDelOutput) return;
    console.log(`enableDelOutput:${output_dir}`)

    if (!fs.existsSync(dirPath)) {
        console.log(`目录不存在: ${dirPath}`);
        return;
    }

    // 创建 readline 接口
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout
    });

    try {
        // 等待用户确认
        let rst = "y";
        if (delOutputTipsShow){
            rst = await new Promise(resolve => {
                rl.question(`即将清空目录 ${dirPath},确认继续吗?(y/n) `, resolve);
            });
        }
        if (rst.trim().toLowerCase() === 'y') {
            fs.rmSync(dirPath, { recursive: true, force: true });
            fs.mkdirSync(dirPath);
            console.log('√ 目录已清空');
        } else {
            console.log('× 操作已取消');
        }
    } finally {
        rl.close();
    }
}

async function main() {
    try {
        await clearOutputDir();
        walkDir(input_dir);
        fs.writeFileSync(
            path.join(output_dir, output),
            Array.from(strings).join('\n'),
            IO_ENCODING
        );
        fs.writeFileSync(path.join(output_dir, output_simple), Array.from(strings_simple).map(str=>str.split(__Mark)[0]).join('\n'), IO_ENCODING);
        generateEnumFile();
        console.log(`
        √ 提取完成:
           - 找到 ${strings.size} 条中文字符串(已写入 ${output}${output_simple})
           - 生成枚举文件 ${CharConstTSFile}
        `);
    } catch (err) {
        console.error('× 运行出错:', err);
        process.exit(1);
    }
}
main();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值