自动翻译插件中的智能字符串切割方案

🔧 自动翻译插件中的智能字符串切割方案详解

问题背景

在开发 auto-i18n-translation-plugins 时遇到核心问题:如何处理包含HTML标签和文本的混合字符串?

比如Vue3对于静态节点就是不会编译的,当你得到一个带有html的代码块时,如果拿去进行翻译api的翻译就会使得翻译过程非常不可控,甚至导致语法错误渲染异常

javascript 复制代码
// 问题示例:这种字符串如何精准切割?
"欢迎来到<strong>官方网站</strong>!请点击<button>登录</button>继续。"

传统AST分析无法区分哪些需要翻译,哪些是代码结构。

核心思路:三阶段渐进式切割

设计理念:从粗到细的渐进式处理,确保语义完整性和结构保护

scss 复制代码
原始字符串 → 粗分割(最细粒度) → 语义重组 → 结构合并 → AST模板字符串

第一阶段:粗分割(最细粒度分解)

目标

将字符串按照目标语言字符和标点符号拆分成最小的语义单元。

代码逻辑

typescript 复制代码
// 定义标点符号的正则表达式
const punctuationRegex = /[,。?!《》,..:!?""'';'"、0-9\n\r\t\v\f]/
// 创建组合正则:目标语言 OR 标点符号
const splitRegex = new RegExp(
    `(${separatorRegex.source}|${punctuationRegex.source})`,
    separatorRegex.flags
)
// 执行分割
const splitArr = str.split(splitRegex).filter(Boolean)

实际效果演示

输入字符串

css 复制代码
"欢迎来到<strong>官方网站</strong>!请点击<button>登录</button>继续。"

粗分割结果

javascript 复制代码
[
  '欢', '迎', '来', '到', '<strong>', '官', '方', '网', '站', 
  '</strong>', '!', '请', '点', '击', '<button>', '登', '录', 
  '</button>', '继', '续', '。'
]
// 共21个片段,每个汉字、标点、HTML标签都被拆成最小单元

关键特点

  • 每个汉字单独分离:'欢', '迎', '来', '到'
  • 标点符号独立:'!', '。'
  • HTML标签完整保留:'<strong>', '</strong>'

第二阶段:语义重组(智能连接)

目标

将语义相关的片段重新连接,形成有意义的翻译单元。

代码逻辑

typescript 复制代码
// 定义连接规则:目标语言字符 OR 特定标点符号
const connectPunctuationRegex = /[,。?!《》,..:!?;'"、0-9]/
const connectRegex = new RegExp(
    `(${separatorRegex.source}|${connectPunctuationRegex.source})`,
    separatorRegex.flags
)

// 智能连接逻辑
for (const item of splitArr) {
    if (connectRegex.test(item)) {
        // 符合连接条件,加入当前片段
        currentMatch += item
    } else {
        // 不符合连接条件,结束当前片段
        if (currentMatch) {
            result.push(currentMatch)
            currentMatch = ''
        }
        result.push(item)  // HTML标签等独立推入
    }
}

实际效果演示

处理过程详解

javascript 复制代码
// 遍历粗分割结果:
'欢' → 中文字符 → 连接 → currentMatch = '欢'
'迎' → 中文字符 → 连接 → currentMatch = '欢迎'  
'来' → 中文字符 → 连接 → currentMatch = '欢迎来'
'到' → 中文字符 → 连接 → currentMatch = '欢迎来到'
'<strong>' → 非连接条件 → 推出'欢迎来到',独立推入'<strong>'
'官' → 中文字符 → 连接 → currentMatch = '官'
'方' → 中文字符 → 连接 → currentMatch = '官方'
'网' → 中文字符 → 连接 → currentMatch = '官方网'
'站' → 中文字符 → 连接 → currentMatch = '官方网站'
'</strong>' → 非连接条件 → 推出'官方网站',独立推入'</strong>'
'!' → 标点符号 → 连接 → currentMatch = '!'
'请' → 中文字符 → 连接 → currentMatch = '!请'
'点' → 中文字符 → 连接 → currentMatch = '!请点'
'击' → 中文字符 → 连接 → currentMatch = '!请点击'
'<button>' → 非连接条件 → 推出'!请点击',独立推入'<button>'
// ... 继续处理

语义重组结果

javascript 复制代码
[
  '欢迎来到', '<strong>', '官方网站', '</strong>', 
  '!请点击', '<button>', '登录', '</button>', '继续。'
]

关键变化

  • 连续汉字合并:'欢迎来到''官方网站''继续'
  • 标点与文字合并'!请点击''继续。'
  • HTML标签保持独立:'<strong>''</strong>'

第三阶段:结构合并(代码结构保护)

目标

将不包含目标语言的相邻片段合并,保护代码结构的完整性。

代码逻辑

typescript 复制代码
for (let i = 0; i < result.length; i++) {
    const item = result[i]
    if (separatorRegex.test(item)) {
        // 包含目标语言,需要翻译
        if (tempStr) {
            finalResult.push(tempStr)  // 先推入积累的代码结构
            tempStr = ''
        }
        finalResult.push(item)  // 推入翻译文本
    } else {
        // 不包含目标语言,是代码结构
        tempStr += item
        // 如果下一项是翻译内容或已到末尾,结束积累
        if (i === result.length - 1 || separatorRegex.test(result[i + 1])) {
            finalResult.push(tempStr)
            tempStr = ''
        }
    }
}

实际效果演示

处理过程

javascript 复制代码
'欢迎来到' → 包含中文 → 直接推入 → ['欢迎来到']
'<strong>' → 不包含中文 → 积累 → tempStr = '<strong>'
'官方网站' → 包含中文 → 先推入tempStr,再推入当前项 → ['欢迎来到', '<strong>', '官方网站']
'</strong>' → 不包含中文 → 积累 → tempStr = '</strong>'
'!请点击' → 包含中文 → 先推入tempStr,再推入当前项 → ['欢迎来到', '<strong>', '官方网站', '</strong>', '!请点击']
'<button>' → 不包含中文 → 积累 → tempStr = '<button>'
'登录' → 包含中文 → 先推入tempStr,再推入当前项 → [..., '<button>', '登录']
'</button>' → 不包含中文 → 积累 → tempStr = '</button>'
'继续。' → 包含中文 → 先推入tempStr,再推入当前项 → [..., '</button>', '继续。']

最终结构合并结果

javascript 复制代码
[
  '欢迎来到',     // 翻译单元
  '<strong>',     // 代码结构  
  '官方网站',     // 翻译单元
  '</strong>',    // 代码结构
  '!请点击',     // 翻译单元(标点+文字)
  '<button>',     // 代码结构
  '登录',         // 翻译单元
  '</button>',    // 代码结构  
  '继续。'        // 翻译单元(文字+标点)
]

第四阶段:AST模板字符串生成

目标

将切割结果转换为Babel AST模板字符串节点,用于代码生成。

代码逻辑

typescript 复制代码
strArray.forEach((str, index) => {
    if (getOriginRegex().test(str)) {
        // 包含目标语言,生成翻译表达式
        expressions.push(
            baseUtils.createI18nTranslator({
                value: str,
                isExpression: true,
                insertOption: option
            })
        )
    } else {
        // 不包含目标语言,作为静态部分
        quasis.push(types.templateElement({ raw: str, cooked: str }, false))
    }
})

最终生成结果

模板字符串

javascript 复制代码
$deepScan`${$t('欢迎来到')}<strong>${$t('官方网站')}</strong>${$t('!请点击')}<button>${$t('登录')}</button>${$t('继续。')}`

关键特点

  • 翻译单元:${$t('欢迎来到')}${$t('!请点击')}${$t('继续。')}
  • 结构保护:<strong></strong><button></button> 保持原样
  • 语义完整:'!请点击''继续。' 作为完整的翻译单元

核心价值总结

  1. 渐进式处理:从最细粒度到语义单元的渐进式合并
  2. 智能识别:准确区分翻译内容和代码结构
  3. 语义保护:确保标点符号与相关文字保持在同一翻译单元
  4. 结构完整:严格保护HTML标签等代码结构不被破坏
  5. 自动化:全程无需人工干预,自动生成可用的国际化代码

这个方案已成功应用于 auto-i18n-translation-plugins 的 Webpack、Vite、Rsbuild 三个插件中,解决了混合字符串的自动翻译难题。

相关推荐
TechFrank3 小时前
浏览器云端写代码,远程开发 Next.js 应用的简易教程
前端
PaytonD3 小时前
LoopBack 2 如何设置静态资源缓存时间
前端·javascript·node.js
snow@li3 小时前
d3.js:学习积累
开发语言·前端·javascript
vincention3 小时前
JavaScript 中 this 指向完全指南
前端
qyresearch_4 小时前
射频前端MMIC:5G时代的技术引擎与市场机遇
前端·5g
天蓝色的鱼鱼4 小时前
Next.js 渲染模式全解析:如何正确选择客户端与服务端渲染
前端·react.js·next.js
一枚前端小能手4 小时前
🚀 巨型列表渲染卡顿?这几个优化技巧让你的页面丝滑如德芙
前端·javascript
酷柚易汛智推官4 小时前
Electron技术深度解析:跨平台桌面开发的利器与挑战
前端·javascript·electron
llz_1124 小时前
第五周作业(JavaScript)
开发语言·前端·javascript