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

一、背景

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

二、核心思路

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

  1. 模板中的中文:插值表达式、文本节点、属性值
  2. 脚本中的中文:字符串字面量、模板字符串
  3. 国际化动态注入:通过插件动态替换原中文片段
  4. 自动生成语言包:输出标准的i18n语言文件

技术栈

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

三、插件实现

1.插件基础结构

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/,],methodPrefix: '$smartT',keyPrefix: '',...options,};// 解析结构this.extractedTexts = new Map();// 文件统计this.fileStats = {totalFiles: 0,processedFiles: 0,extractedCount: 0,injectedFiles: 0,skippedFiles: 0,};}/*** 插件入口文件* @param {*} compiler*/apply(compiler) {// 插件主流程}// 插件核心方法// ......
}

2. AST语法树抽取

  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.injectReplaceCodes();// 输出结果this.outputResults();});}

3.Vue Template AST解析

 /*** 模版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)) {this.addExtractedText(text,filePath,'template-expression',{line: node.start || 0,column: 0,},{oldText: text,newText: `{{${this.options.methodPrefix}('${text}')}}`,});}}// 递归处理子节点if (node.children) {node.children.forEach((child) => {this.traverseTemplateAST(child, filePath);});}}

4. Vue Script AST解析

/*** 脚本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. 代码替换(最小化案例)

  /*** 注入替换代码片段*/injectReplaceCodes() {// 对所有文件分组const injectionMap = new Map();this.extractedTexts.forEach((extractedText) => {const { filePath } = extractedText;if (injectionMap.has(filePath)) {injectionMap.get(filePath).push(extractedText);} else {injectionMap.set(filePath, [extractedText]);}});// 处理每个文件[...injectionMap.keys()].forEach((filePath) => {let content = fs.readFileSync(filePath, 'utf-8');const extractedTextList = injectionMap.get(filePath);for (const extractedText of extractedTextList) {const { text, replaceConfig } = extractedText;// 在遍历AST树时,请自行处理if (replaceConfig) {content = content.replace(replaceConfig.oldText, `${replaceConfig.newText}`);} }fs.writeFileSync(filePath, content, 'utf-8');});}

6. 语言包生成

 /*** 输出结果*/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}`);}

7. 其它辅助方法

 /*** 添加提取的文本*/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配置

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

2. 运行配置

package.json(script)

"extract": "webpack --config webpack.config.extract.js  --mode development "

运行

npm run extract

3. 项目案例

<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. 提取结果

i18n/
└──zh.js                    # 中文语言包// 自动生成的中文语言包
// 生成时间: 2025/9/1 11:38:04
// 共提取 12 个中文片段export default {'欢迎使用Vue2国际化演示': '欢迎使用Vue2国际化演示','这是一个完整的国际化解决方案演示项目': '这是一个完整的国际化解决方案演示项目','首页': '首页','用户管理': '用户管理','商品管理': '商品管理','当前页面:': '当前页面:',',欢迎使用系统': ',欢迎使用系统','今天是': '今天是',',祝您工作愉快': ',祝您工作愉快','版权所有 © 2024 Vue2国际化演示项目': '版权所有 © 2024 Vue2国际化演示项目','张三': '张三','你好,这是一个Vue2国际化改造案例~': '你好,这是一个Vue2国际化改造案例~'
};

五、总结

通过自定义Webpack插件的方式,我们成功实现了Vue2项目中文文本的自动提取,大大提升了国际化改造的效率。这种基于AST分析的方案不仅准确率高,而且可以灵活扩展,是大型项目国际化改造的理想选择,而且不仅针对Vue几乎所有的webpack项目都可以用类似的方案再结合i8n,进行国际化改造~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/95420.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/95420.shtml
英文地址,请注明出处:http://en.pswp.cn/web/95420.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

硬件开发(1)—单片机(1)

1.单片机相关概念1.CPU&#xff1a;中央处理器&#xff0c;数据运算、指令处理&#xff0c;CPU性能越高&#xff0c;完成指令处理和数据运算的速度越快核心&#xff1a;指令解码执行数据运算处理2.MCU&#xff1a;微控制器&#xff0c;集成度比较高&#xff0c;将所有功能集成到…

Elasticsearch面试精讲 Day 4:集群发现与节点角色

【Elasticsearch面试精讲 Day 4】集群发现与节点角色 在“Elasticsearch面试精讲”系列的第四天&#xff0c;我们将深入探讨Elasticsearch分布式架构中的核心机制——集群发现&#xff08;Cluster Discovery&#xff09;与节点角色&#xff08;Node Roles&#xff09;。这是构…

微信小程序长按识别图片二维码

提示&#xff1a;二维码图片才能显示识别菜单1.第一种方法添加属性&#xff1a;show-menu-by-longpress添加属性&#xff1a;show-menu-by-longpress <image src"{{shop.wx_qrcode}}" mode"widthFix" show-menu-by-longpress></image>2.第二种…

智能化数据平台:AI 与大模型驱动的架构升级

在前面的文章中,我们探讨了 存算分离与云原生,以及 流批一体化计算架构 的演进趋势。这些演进解决了“算力与数据效率”的问题。但在今天,企业在数据平台上的需求已经从 存储与计算的统一,逐步走向 智能化与自动化。 尤其是在 AI 与大模型快速发展的背景下,数据平台正在发…

解锁 Vue 动画的终极指南:Vue Bits 实战进阶教程,让你的Vue动画比原生动画还丝滑,以及动画不生效解决方案。

一条 Splash Cursor 的 10 秒 Demo 视频曾创下 200 万 播放量&#xff0c;让 React Bits 风靡全球。如今&#xff0c;Vue 开发者终于迎来了官方移植版 —— Vue Bits。 在现代 Web 开发中&#xff0c;UI 动效已成为提升用户体验的关键因素。Vue Bits 作为 React Bits 的官方 Vu…

《微服务协作实战指南:构建全链路稳健性的防御体系》

在微服务架构从“技术尝鲜”迈向“规模化落地”的进程中&#xff0c;服务间的协作不再是简单的接口调用&#xff0c;而是涉及超时控制、事务一致性、依赖容错、配置同步等多维度的复杂博弈。那些潜藏于协作链路中的隐性Bug&#xff0c;往往不是单一服务的功能缺陷&#xff0c;而…

STM32F103C8T6的智能医疗药品存储柜系统设计与华为云实现

项目开发背景 随着现代医疗技术的快速发展&#xff0c;药品的安全存储与管理成为医疗质量控制中的重要环节。许多药品对存储环境的温湿度具有严格的要求&#xff0c;一旦超出允许范围&#xff0c;药品的理化性质可能发生改变&#xff0c;甚至失效&#xff0c;直接影响患者的用药…

python批量调用大模型API:多线程和异步协程

文章目录 多线程批量调用 基本原理 实现代码 代码解析 使用注意事项 异步协程实现批量调用 异步协程实现方式 异步实现的核心原理 多线程 vs 异步协程 选择建议 多线程批量调用 基本原理 多线程批量调用大模型API的核心思想是通过并发处理提高效率,主要原理包括: 并发请求:…

硬件开发1-51单片机1

一、嵌入式1、概念&#xff1a;以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软硬件可裁剪的专用计算机系统以应用为中心&#xff1a;系统设计的起点是 “具体应用场景”&#xff0c;按照应用需求出发以计算机技术为基础&#xff1a; 硬件技术&#xff1a;嵌…

Redis核心数据类型解析——string篇

Redis的常见数据类型Redis 提供了 5 种数据结构&#xff0c;理解每种数据结构的特点对于 Redis 开发运维⾮常重要&#xff0c;同时掌握每 种数据结构的常⻅命令&#xff0c;会在使⽤ Redis 的时候做到游刃有余。预备在正式介绍 5 种数据结构之前&#xff0c;了解⼀下 Redis 的⼀…

爬虫逆向--Day20Day21--扣JS逆向练习【案例4:深证信服务平台】

一、案例【深证信数据服务平台】案例地址链接&#xff1a;https://webapi.cninfo.com.cn/#/marketDataDate案例爬取链接&#xff1a;https://webapi.cninfo.com.cn/api/sysapi/p_sysapi10071.1、入口定位当进行入口定位时&#xff0c;我们首先需要进行查看响应、载荷、请求头是…

ExcelJS实现导入转换HTML展示(附源码可直接使用)

目录 简介 开始实践 难点 文件示例 效果预览 具体实现 安装 完整代码 总结 简介 在日常工作中&#xff0c;我们可能会遇到需要上传并展示 Excel 文件的需求&#xff0c;实现文件内容的在线预览。 这里给大家接收一个组件库exceljs&#xff0c;这个组件库进过实践发现…

ECDH和数字签名

文章目录一、核心区别&#xff1a;目的完全不同二、协同工作关系&#xff1a;缺一不可的安全组合三、技术结合点&#xff1a;都基于ECC(椭圆曲线密码学)ECDH&#xff08;椭圆曲线迪菲-赫尔曼密钥交换&#xff09;和数字签名&#xff08;如ECDSA&#xff0c;椭圆曲线数字签名算法…

withCredentials(简单说:带不带凭证)

一、withCredentials是什么&#xff1f;withCredentials 是浏览器 XMLHttpRequest 或 Fetch API&#xff08;以及 axios 等基于它们的库&#xff09;中的一个配置项&#xff0c;作用是控制跨域请求时是否携带 Cookie、HTTP 认证信息等凭证。用更通俗的方式解释&#xff1a;二、…

window系统使用命令行来安装OpenSSH服务器或客户端

可以通过 PowerShell 命令行来安装&#xff0c;这种方式更直接可靠&#xff1a;以管理员身份打开 PowerShell&#xff1a; 按下 Win S 搜索 “PowerShell”右键点击 “Windows PowerShell”&#xff0c;选择"以管理员身份运行"安装 OpenSSH 客户端&#xff1a; Add-…

vim中常见操作及命令

在 Vim 中为所有行的行首添加相同字符&#xff0c;可以使用以下方法&#xff1a; 方法1&#xff1a;使用 :%s 替换命令&#xff08;推荐&#xff09; vim :%s/^/要添加的字符/ 例如要在所有行首添加 #&#xff1a;vim :%s/^/#/ 方法2&#xff1a;使用块选择模式&#xff08;可视…

开发使用mybatis是用混合模式还是全注解模式

在使用 MyBatis 开发项目时&#xff0c;Mapper 接口是为数据库操作提供最直观的方法&#xff0c;但在实现方式上&#xff0c;我们有两种选择&#xff1a;全注解模式和混合模式。那么&#xff0c;他们有什么区别&#xff0c;应该如何选择&#xff1f;我们一起来讨论一下。一、全…

WS2812灯带效果设计器上位机

软件使用方法介绍&#xff1a;bilibili地址 【免写单片机代码WS2812灯带效果设计软件-哔哩哔哩】 https://b23.tv/xFhxMGm

Docker 容器(二)

Docker四、Docker容器数据卷1.数据卷的主要特点2.卷的共享与继承&#xff08;1&#xff09;卷的共享&#xff08;Sharing&#xff09;(2) 卷的继承&#xff08;Inheritance&#xff09;3.数据卷运行实例五、Dockerfile1.Dockerfile2. 创建一个名为 myubuntu的自定义镜像第 1 步…

PCB基础细节--工艺篇

pcb基础细节&#xff08;工艺篇&#xff09; 1. 孔与焊盘2. PCB各层之间的作用3. 阻抗匹配 3.1. 什么是传输线&#xff1f;我们只看特性阻抗&#xff0c;时延以后再说。 在画原理图时&#xff0c;我们把电阻&#xff0c;电容&#xff0c;电感是抽象成一个点了。两边加一个电压&…