Vue2存量项目国际化改造踩坑

Vue2存量项目国际化改造踩坑

一、背景

在各类业务场景中,国际化作为非常重要的一部分已经有非常多成熟的方案,但对于一些存量项目则存在非常的改造成本,本文将分享一个的Vue2项目国际化改造方案,通过自定义Webpack插件自动提取中文文本,大大提升改造效率。

二、核心思路(提取阶段)

通过开放自定义Webpack插件,利用AST(抽象语法树)分析技术,自动扫描Vue组件中的中文文本

  1. 模板中的中文:插值表达式、文本节点、属性值
  2. 脚本中的中文:字符串字面量、模板字符串
  3. 自动生成语言包:输出标准的i18n语言文件

技术栈

  • vue-template-compiler:解析Vue单文件组件
  • @babel/parser:解析JavaScript代码为AST
  • @babel/traverse:遍历AST节点
  • Webpack Plugin API:集成到构建流程

三、插件实现

1.插件基础结构

javascript 复制代码
class MatureChineseExtractorPlugin {
  constructor(options = {}) {
    this.options = {
      outputPath: './i18n',
      includePatterns: [/src\/.*\.(vue|js|jsx|ts|tsx)$/, '/src/App.vue'],
      excludePatterns: [
        /node_modules/,
        /dist/,
        /i18n/,
        /plugins/,
        /public/,
        /webpack\.config\.js/,
        /package\.json/,
      ],
      keyPrefix: '',
      ...options,
    };
	// 解析结构
    this.extractedTexts = new Map();
    // 文件统计
    this.fileStats = {
      totalFiles: 0,
      processedFiles: 0,
      extractedCount: 0,
      injectedFiles: 0,
      skippedFiles: 0,
    };
  }

  /**
   * 插件入口文件
   * @param {*} compiler
   */
  apply(compiler) {
    // 插件主流程
  }
  
  // 插件核心方法
  // ......
}

2. AST语法树抽取

javascript 复制代码
  apply(compiler) {
    compiler.hooks.done.tap('MatureChineseExtractorPlugin', (stats) => {
      const projectRoot = compiler.context;
      const filePath = path.resolve(projectRoot, './src/App.vue');
      // 解析Vue组件
      const content = fs.readFileSync(filePath, 'utf-8');
      const component = parseComponent(content);

      // 解析模版AST语法树
      const templateAst = compile(component.template.content, {
        preserveWhitespace: false,
        whitespace: 'condense',
      });
      this.traverseTemplateAST(templateAst.ast, filePath);

      // 解析Script语法树
      const scriptAst = parse(component.script.content, {
        sourceType: 'module',
        plugins: ['jsx', 'typescript', 'decorators-legacy', 'classProperties'],
      });
      this.traverseScriptAst(scriptAst, filePath);

      // 输出结果
      this.outputResults();
    });
  }

3.Vue Template AST解析

javascript 复制代码
 /**
   * 模版AST语法树处理
   * @param {AST节点} node
   */
  traverseTemplateAST(node, filePath) {
    if (!node) return;

    // 处理元素节点的属性
    if (node.type === 1) {
      // 处理静态属性
      if (node.attrsList) {
        node.attrsList.forEach((attr) => {
          if (attr.value && this.containsChinese(attr.value)) {
            this.addExtractedText(attr.value, filePath, `template-attr-${attr.name}`, {
              line: node.start || 0,
              column: 0,
            });
          }
        });
      }

      // 处理动态属性
      if (node.attrs) {
        node.attrs.forEach((attr) => {
          if (attr.value && this.containsChinese(attr.value)) {
            this.addExtractedText(attr.value, filePath, `template-dynamic-attr-${attr.name}`, {
              line: 0,
              column: 0,
            });
          }
        });
      }
    }

    // 处理{{}}表达式节点
    if (node.type === 2 && node.expression) {
      // 检查表达式中是否包含中文字符串
      const chineseMatches =
        node.expression.match(/'([^']*[\u4e00-\u9fa5][^']*)'/g) ||
        node.expression.match(/"([^"]*[\u4e00-\u9fa5][^"]*)"/g) ||
        node.expression.match(/`([^`]*[\u4e00-\u9fa5][^`]*)`/g);

      if (chineseMatches) {
        chineseMatches.forEach((match) => {
          // 去掉引号
          const text = match.slice(1, -1);
          if (this.containsChinese(text)) {
            this.addExtractedText(text, filePath, 'template-expression', {
              line: node.start || 0,
              column: 0,
            });
          }
        });
      }

      // 处理模板字符串中的中文
      if (node.expression.includes('`') && this.containsChinese(node.expression)) {
        // 简单提取模板字符串中的中文部分
        const templateStringMatch = node.expression.match(/`([^`]*)`/);
        if (templateStringMatch) {
          const templateContent = templateStringMatch[1];
          // 提取非变量部分的中文
          const chineseParts = templateContent
            .split('${')
            .map((part) => {
              return part.split('}')[part.includes('}') ? 1 : 0];
            })
            .filter((part) => part && this.containsChinese(part));
          chineseParts.forEach((part) => {
            this.addExtractedText(part, filePath, 'template-string', {
              line: node.start || 0,
              column: 0,
            });
          });
        }
      }
    }

    // 处理文本节点
    if (node.type === 3 && node.text) {
      const text = node.text.trim();
      if (this.containsChinese(text)) {
      }
    }

    // 递归处理子节点
    if (node.children) {
      node.children.forEach((child) => {
        this.traverseTemplateAST(child, filePath);
      });
    }
  }

4. Vue Script AST解析

javascript 复制代码
/**
   * 脚本AST语法树处理
   * @param {*} astTree
   * @param {*} filePath
   */
  traverseScriptAst(astTree, filePath) {
    traverse(astTree, {
      // 捕获所有字符串字面量中的中文
      StringLiteral: (path) => {
        const value = path.node.value;
        if (this.containsChinese(value)) {
          this.addExtractedText(value, filePath, 'script-string', {
            line: path.node.loc ? path.node.loc.start.line : 0,
            column: path.node.loc ? path.node.loc.start.column : 0,
          });
        }
      },
      // 捕获模板字符串中的中文
      TemplateLiteral: (path) => {
        path.node.quasis.forEach((quasi) => {
          if (quasi.value.raw && this.containsChinese(quasi.value.raw)) {
            this.addExtractedText(quasi.value.raw, filePath, 'script-template', {
              line: quasi.loc ? quasi.loc.start.line : 0,
              column: quasi.loc ? quasi.loc.start.column : 0,
            });
          }
        });
      },
    });
  }

5. 语言包生成

javascript 复制代码
 /**
   * 输出结果
   */
  outputResults() {
    const results = Array.from(this.extractedTexts.values());
    console.log(results);
    // 生成中文映射文件
    const chineseMap = {};
    results.forEach((item) => {
      chineseMap[item.text] = item.text;
    });
    const entries = Object.entries(chineseMap);

    // 写入JSON文件
    const outputFile = path.join(this.options.outputPath, 'extracted-chinese.json');
    fs.writeFileSync(outputFile, JSON.stringify(chineseMap, null, 2), 'utf-8');

    // 生成对象属性字符串
    const properties = entries
      .map(([key, value]) => {
        // 转义特殊字符
        const escapedValue = value
          .replace(/\\/g, '\\\\')
          .replace(/'/g, "\\'")
          .replace(/\n/g, '\\n')
          .replace(/\r/g, '\\r');
        return `  '${key}': '${escapedValue}'`;
      })
      .join(',\n');

    // 生成zh.js文件
    const zhJsPath = path.join(this.options.outputPath, 'zh.js');
    fs.writeFileSync(
      zhJsPath,
      `// 自动生成的中文语言包
// 生成时间: ${new Date().toLocaleString()}
// 共提取 ${entries.length} 个中文片段

export default {
${properties}
};`,
      'utf-8'
    );

    console.log(`提取完成,共提取 ${results.length} 个中文片段`);
    console.log(`结果已保存到: ${outputFile}`);
  }

6. 其它辅助方法

typescript 复制代码
 /**
   * 添加提取的文本
   */
  addExtractedText(text, filePath, type, location) {
    this.extractedTexts.set(text, {
      text,
      filePath,
      type,
      location,
    });
    this.fileStats.extractedCount++;
  }

  /**
   * 检查是否包含中文
   */
  containsChinese(text) {
    return /[\u4e00-\u9fa5]/.test(text);
  }

四、使用方式

1. Webpack配置

java 复制代码
// webpack.config.extract.js
const MatureChineseExtractorPlugin = require('./MatureChineseExtractorPlugin');

module.exports = {
  // ... 其他配置
  plugins: [
    new MatureChineseExtractorPlugin({
      outputPath: './i18n',
      verbose: true
    })
  ]
};

2. 运行配置

package.json(script)

json 复制代码
"extract": "webpack --config webpack.config.extract.js  --mode development "

运行

arduino 复制代码
npm run extract

3. 项目案例

xml 复制代码
<template>
  <div id="app">
    <header class="header">
      <h1>{{ '欢迎使用Vue2国际化演示' }}</h1>
      <p>{{ '这是一个完整的国际化解决方案演示项目' }}</p>
    </header>

    <nav class="nav">
      <button @click="currentView = 'home'" :class="{ active: currentView === 'home' }">
        {{ '首页' }}
      </button>
      <button @click="currentView = 'user'" :class="{ active: currentView === 'user' }">
        {{ '用户管理' }}
      </button>
      <button @click="currentView = 'product'" :class="{ active: currentView === 'product' }">
        {{ '商品管理' }}
      </button>
    </nav>

    <main class="main">
      <div class="status-bar">
        <span>当前页面:{{ currentComponent }}</span>
        <span>{{ userInfo.name }},欢迎使用系统</span>
        <span>今天是{{ currentDate }},祝您工作愉快</span>
      </div>
      <component :is="currentComponent"></component>
    </main>

    <footer class="footer">
      <p>{{ '版权所有 © 2024 Vue2国际化演示项目' }}</p>
    </footer>
  </div>
</template>

<script>
import HomePage from './components/HomePage.vue';
import UserManagement from './components/UserManagement.vue';
import ProductManagement from './components/ProductManagement.vue';

export default {
  name: 'App',
  components: {
    HomePage,
    UserManagement,
    ProductManagement,
  },
  data() {
    return {
      currentView: 'home',
      userInfo: {
        name: '张三',
      },
      currentDate: new Date().toLocaleDateString(),
    };
  },
  methods: {
    sayHello() {
      console.log('你好,这是一个Vue2国际化改造案例~');
    },
  },
  computed: {
    currentComponent() {
      const components = {
        home: '首页',
        user: '用户管理',
        product: '商品管理',
      };
      return components[this.currentView];
    },
  },
};
</script>

4. 提取结果

arduino 复制代码
i18n/
└──zh.js                    # 中文语言包

// 自动生成的中文语言包
// 生成时间: 2025/9/1 11:38:04
// 共提取 12 个中文片段

export default {
  '欢迎使用Vue2国际化演示': '欢迎使用Vue2国际化演示',
  '这是一个完整的国际化解决方案演示项目': '这是一个完整的国际化解决方案演示项目',
  '首页': '首页',
  '用户管理': '用户管理',
  '商品管理': '商品管理',
  '当前页面:': '当前页面:',
  ',欢迎使用系统': ',欢迎使用系统',
  '今天是': '今天是',
  ',祝您工作愉快': ',祝您工作愉快',
  '版权所有 © 2024 Vue2国际化演示项目': '版权所有 © 2024 Vue2国际化演示项目',
  '张三': '张三',
  '你好,这是一个Vue2国际化改造案例~': '你好,这是一个Vue2国际化改造案例~'
};

五、总结

通过自定义Webpack插件的方式,我们成功实现了Vue2项目中文文本的自动提取,大大提升了国际化改造的效率。这种基于AST分析的方案不仅准确率高,而且可以灵活扩展,是大型项目国际化改造的理想选择,后面会对自动化注入流程进行拆解~

相关推荐
EnCi Zheng8 分钟前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen12 分钟前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技13 分钟前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人24 分钟前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实24 分钟前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha35 分钟前
三目运算符
linux·服务器·前端
晓晨的博客42 分钟前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
GISer_Jing1 小时前
AI全栈转型_TS后端学习路线
前端·人工智能·后端·学习