You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js如何读取多TS文件导出内容合并生成i18n统一配置对象

实现方案

这个需求完全可实现,比当前拼接txt的方案稳定性、可维护性高很多,不需要手动拼接代码字符串,也不会出现TS语法错误、内容漏写的问题,根据你的项目技术栈选下面任意一种方案即可。


方案1:独立Node构建脚本(通用,适配所有技术栈)

这个方案灵活性最高,支持自定义键冲突检测、命名空间前缀等自定义逻辑,不管项目用什么构建工具都能用。

  • 安装依赖:只需要装两个轻量包,tsx用来直接运行TS脚本不用提前转译,glob用来批量匹配目录下的i18n文件
    npm i -D tsx glob
    
  • 在项目下新建scripts/merge-i18n.ts脚本,核心逻辑是自动扫描所有模块的i18n文件、按语言分组合并、输出最终的TS文件:
    import { glob } from 'glob'
    import { writeFileSync } from 'fs'
    import path from 'path'
    
    // 匹配所有模块下的i18n TS文件
    const i18nFilePaths = await glob('src/modules/**/i18n/*.ts')
    const messages: Record<string, Record<string, string>> = {}
    
    for (const filePath of i18nFilePaths) {
      // 用文件名作为语言标识,比如en-US.ts对应en-US语言
      const lang = path.basename(filePath, '.ts')
      // 动态导入TS文件,拿到默认导出的多语言对象
      const i18nModule = await import(path.resolve(filePath))
      const langContent = i18nModule.default
    
      if (!messages[lang]) messages[lang] = {}
    
      // 合并内容,加重复键检测避免静默覆盖
      for (const [key, value] of Object.entries(langContent)) {
        if (messages[lang][key]) {
          throw new Error(`[i18n合并错误] 语言「${lang}」下存在重复key:${key},冲突文件:${filePath}`)
        }
        messages[lang][key] = value
      }
    }
    
    // 生成最终的index.ts文件内容,不用手动拼字符串
    const outputContent = `export default {
      messages: ${JSON.stringify(messages, null, 2)}
    }
    `
    writeFileSync(path.resolve('src/i18n/index.ts'), outputContent)
    
  • 在package.json里加执行脚本,绑定到构建、开发启动的前置钩子,每次运行前自动生成最新的合并文件:
    {
      "scripts": {
        "predev": "tsx scripts/merge-i18n.ts",
        "prebuild": "tsx scripts/merge-i18n.ts"
      }
    }
    
  • 可以把生成的src/i18n/index.ts加到.gitignore里,不用提交到代码仓库,避免多人协作时出现生成文件冲突。

方案2:构建工具原生能力(无需额外脚本,适合Vite/Webpack项目)

如果你的项目用Vite、Rspack这类现代构建工具,不需要写独立脚本,直接用构建工具提供的glob导入能力,在代码里动态合并即可,连物理生成的index.ts都不需要。
以Vite为例,直接在i18n入口文件里写:

// src/i18n/index.ts
//  eager: true表示构建时直接静态解析导入,不用运行时异步加载
const i18nModules = import.meta.glob<{ default: Record<string, string> }>(
  '../modules/**/i18n/*.ts',
  { eager: true }
)

const messages: Record<string, Record<string, string>> = {}
for (const [filePath, mod] of Object.entries(i18nModules)) {
  const lang = filePath.split('/').pop()?.replace('.ts', '') as string
  if (!messages[lang]) messages[lang] = {}
  // 这里同样可以加重复key检测逻辑
  Object.assign(messages[lang], mod.default)
}

export default { messages }

Webpack/Rspack可以用require.context实现同样的效果,逻辑完全一致。


优化建议

  • 不要直接用Object.assign做无感知合并,一定要加重复key检测,不同模块写了同名key时直接抛错,避免线上出现文案被覆盖的问题,排查成本极高
  • 如果后续模块多了怕key重,可以在合并时自动给每个模块的key加前缀,比如clients模块下的Key1自动变成clients.Key1,从根源避免冲突
  • 如果需要保留TS类型提示,可以在生成文件时额外导出多语言key的联合类型,不用手动维护类型定义

内容的提问来源于stack exchange,提问作者Trent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 14:51:35