🔧 自动翻译插件中的智能字符串切割方案详解
问题背景
在开发 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>保持原样 - 语义完整:
'!请点击'和'继续。'作为完整的翻译单元 
核心价值总结
- 渐进式处理:从最细粒度到语义单元的渐进式合并
 - 智能识别:准确区分翻译内容和代码结构
 - 语义保护:确保标点符号与相关文字保持在同一翻译单元
 - 结构完整:严格保护HTML标签等代码结构不被破坏
 - 自动化:全程无需人工干预,自动生成可用的国际化代码
 
这个方案已成功应用于 auto-i18n-translation-plugins 的 Webpack、Vite、Rsbuild 三个插件中,解决了混合字符串的自动翻译难题。