Skip to content

Node

国际化插入脚本示例 ( React 版本 )

仅供参考,替换能够处理大多数的实际场景(>=95%),基于替换位置场景的不同,可能还需要手动微调。

js
import { readFileSync, writeFileSync } from 'node:fs'
const info = {
  path: 'src/utils/utils.tsx',
  i18nObj: {
    utils0: '视频大小不能超过',
    utils1: '请重新调整后再上传',
    utils2: '升级邀请',
    utils3: '立即升级',
    utils4: '请先登录',
    utils5: '您的账号已重复登录',
    utils6: '当前等级不支持',
    utils7: '选择声音类型语言不匹配,请重新选择',
    utils8:
      '字幕文件解析失败,建议:1.检查字幕是否有正确文本内容;2.字幕文件无法解析请重新上传',
    utils9: '请上传与视频时长相对应时长的字幕文件',
    utils10: '字幕文本内容过多,请上传与视频时长相对应时长的字幕文件',
    utils11: '任务处理超时无响应,请稍后重试',
    utils13: '账号禁用',
    utils14: '您的账号存在异常使用操作,若有疑问请联系客服申诉',
    utils15: '微信扫码添加客服',
    utils16: '语言识别未识别到内容',
    // utils17 未成功替换
    utils17: {
      手机号: {
        0: '更改手机号',
        1: '绑定手机号',
      },
      邮箱: {
        1: '绑定邮箱',
        0: '更改邮箱',
      },
      密码: {
        0: '设置新密码',
        2: '设置密码',
      },
      微信: {
        1: '绑定微信',
        3: '解绑微信',
      },
    },
    utils18: '加载失败',
    utils19: '仅支持拖拽文件或者文件夹',
    utils20: 'headers 中没有 Content Disposition字段',
    utils21: '复制成功',
    utils22: '复制失败',
    // 未成功替换
    utils23: {
      0: '图片翻译任务',
      1: '视频翻译任务',
      2: '视频导出任务',
      3: '视频配音任务',
      4: '配音导出任务',
    },
  },
}

i18nReplace(info)

function i18nReplace(info) {
  let code = readFileSync(info.path, 'utf8')
  const i18nObj = Object.fromEntries(
    Object.entries(info.i18nObj).sort((a, b) => b[1].length - a[1].length),
  )
  console.log(i18nObj)
  // 注意 以下仅针对字符串替换有效
  for (const key in i18nObj) {
    code = code.replaceAll(`'${i18nObj[key]}'`, `t['${key}']`)
  }
  for (const key in i18nObj) {
    code = code.replaceAll(`"${i18nObj[key]}"`, `t['${key}']`)
  }
  for (const key in i18nObj) {
    code = code.replaceAll(i18nObj[key], `{t['${key}']}`)
  }
  // if vue
  // for (const key in i18nObj) {
  //   code = code.replaceAll(`'${i18nObj[key]}'`, `"$t['${key}']"`)
  // }
  // for (const key in i18nObj) {
  //   code = code.replaceAll(`"${i18nObj[key]}"`, `"$t['${key}']"`)
  // }
  // for (const key in i18nObj) {
  //   code = code.replaceAll(i18nObj[key], `{{t['${key}']}}`)
  // }
  writeFileSync(info.path, code, 'utf8')
}
import { readFileSync, writeFileSync } from 'node:fs'
const info = {
  path: 'src/utils/utils.tsx',
  i18nObj: {
    utils0: '视频大小不能超过',
    utils1: '请重新调整后再上传',
    utils2: '升级邀请',
    utils3: '立即升级',
    utils4: '请先登录',
    utils5: '您的账号已重复登录',
    utils6: '当前等级不支持',
    utils7: '选择声音类型语言不匹配,请重新选择',
    utils8:
      '字幕文件解析失败,建议:1.检查字幕是否有正确文本内容;2.字幕文件无法解析请重新上传',
    utils9: '请上传与视频时长相对应时长的字幕文件',
    utils10: '字幕文本内容过多,请上传与视频时长相对应时长的字幕文件',
    utils11: '任务处理超时无响应,请稍后重试',
    utils13: '账号禁用',
    utils14: '您的账号存在异常使用操作,若有疑问请联系客服申诉',
    utils15: '微信扫码添加客服',
    utils16: '语言识别未识别到内容',
    // utils17 未成功替换
    utils17: {
      手机号: {
        0: '更改手机号',
        1: '绑定手机号',
      },
      邮箱: {
        1: '绑定邮箱',
        0: '更改邮箱',
      },
      密码: {
        0: '设置新密码',
        2: '设置密码',
      },
      微信: {
        1: '绑定微信',
        3: '解绑微信',
      },
    },
    utils18: '加载失败',
    utils19: '仅支持拖拽文件或者文件夹',
    utils20: 'headers 中没有 Content Disposition字段',
    utils21: '复制成功',
    utils22: '复制失败',
    // 未成功替换
    utils23: {
      0: '图片翻译任务',
      1: '视频翻译任务',
      2: '视频导出任务',
      3: '视频配音任务',
      4: '配音导出任务',
    },
  },
}

i18nReplace(info)

function i18nReplace(info) {
  let code = readFileSync(info.path, 'utf8')
  const i18nObj = Object.fromEntries(
    Object.entries(info.i18nObj).sort((a, b) => b[1].length - a[1].length),
  )
  console.log(i18nObj)
  // 注意 以下仅针对字符串替换有效
  for (const key in i18nObj) {
    code = code.replaceAll(`'${i18nObj[key]}'`, `t['${key}']`)
  }
  for (const key in i18nObj) {
    code = code.replaceAll(`"${i18nObj[key]}"`, `t['${key}']`)
  }
  for (const key in i18nObj) {
    code = code.replaceAll(i18nObj[key], `{t['${key}']}`)
  }
  // if vue
  // for (const key in i18nObj) {
  //   code = code.replaceAll(`'${i18nObj[key]}'`, `"$t['${key}']"`)
  // }
  // for (const key in i18nObj) {
  //   code = code.replaceAll(`"${i18nObj[key]}"`, `"$t['${key}']"`)
  // }
  // for (const key in i18nObj) {
  //   code = code.replaceAll(i18nObj[key], `{{t['${key}']}}`)
  // }
  writeFileSync(info.path, code, 'utf8')
}

读取项目文件(可配置)

js
const fs = require('fs')
const path = require('path')

const projectPath = './' // 项目根目录
const ignoreDirs = ['node_modules', '.nuxt', 'assets', 'api', 'locales'] // 忽略的目录
const ignoreFiles = ['i18n.js'] // 忽略的文件
const fileRegex = /\.(js|jsx|ts|tsx|vue)$/ // 需要搜索的文件类型

function walkDir(dir, doSomething) {
  const files = fs.readdirSync(dir)
  for (const file of files) {
    const fullPath = path.join(dir, file)
    const stat = fs.statSync(fullPath)
    if (stat.isDirectory() && !ignoreDirs.includes(file)) {
      walkDir(fullPath)
    } else if (
      stat.isFile() &&
      !ignoreFiles.includes(file) &&
      fileRegex.test(file)
    ) {
      doSomething && doSomething(fullPath)
    }
  }
}
const fs = require('fs')
const path = require('path')

const projectPath = './' // 项目根目录
const ignoreDirs = ['node_modules', '.nuxt', 'assets', 'api', 'locales'] // 忽略的目录
const ignoreFiles = ['i18n.js'] // 忽略的文件
const fileRegex = /\.(js|jsx|ts|tsx|vue)$/ // 需要搜索的文件类型

function walkDir(dir, doSomething) {
  const files = fs.readdirSync(dir)
  for (const file of files) {
    const fullPath = path.join(dir, file)
    const stat = fs.statSync(fullPath)
    if (stat.isDirectory() && !ignoreDirs.includes(file)) {
      walkDir(fullPath)
    } else if (
      stat.isFile() &&
      !ignoreFiles.includes(file) &&
      fileRegex.test(file)
    ) {
      doSomething && doSomething(fullPath)
    }
  }
}

部分关键概念

koa2

当你使用 Koa2 编写后台接口服务,并且有大量用户同时发起请求时,Node.js 和 Koa2 的内部运行逻辑大致如下:

  1. 接收请求:Node.js 的底层网络模块接收到来自客户端的 HTTP 请求,并建立 TCP 连接。

  2. 事件循环:Node.js 使用事件循环来处理异步事件。当一个新的请求到达时,它会被放入事件队列中等待处理。

  3. Koa2 中间件:事件循环将请求分配给 Koa2 应用程序。Koa2 使用中间件机制来处理请求。每个请求都会通过一系列中间件,这些中间件可以是日志记录、身份验证、数据解析、路由处理等。

  4. 异步处理:在中间件中,任何需要等待的操作(如数据库查询、文件读取等)都应该是异步的。Koa2 支持 async/await 语法,使得异步代码的编写更加直观和易于管理。当这些异步操作被触发时,它们会被放入 Node.js 的内部线程池或者其他异步处理机制中,主事件循环会继续处理其他任务。

  5. 回调执行:一旦异步操作完成(例如,数据库返回了查询结果),相应的回调函数或者 Promiseresolve/reject 会被加入到事件循环中等待执行。

  6. 构建响应:当中间件完成处理后,Koa2 会构建 HTTP 响应,并通过 Node.js 的网络模块发送回客户端。

  7. 继续监听:一旦响应被发送,Node.js 会继续监听新的请求,同时事件循环会处理队列中的下一个事件。

在这个过程中,Node.js 的单线程事件循环机制允许它高效地处理大量并发的 I/O 密集型请求。由于 I/O 操作是非阻塞的,主线程可以在等待 I/O 操作完成的同时处理更多的请求。

然而,如果请求处理涉及到大量的 CPU 计算,则可能会阻塞事件循环,导致性能问题。在这种情况下,可以考虑以下策略:

  • 使用 Node.js 的 worker_threads 模块来在后台线程中执行 CPU 密集型任务。
  • 创建子进程来分担计算负载。
  • 使用负载均衡器分发请求到多个 Node.js 实例或服务器。

总之,Koa2 和 Node.js 能够有效地处理大量并发请求,特别是当这些请求主要涉及到 I/O 操作时。对于 CPU 密集型任务,需要采取额外的措施来确保性能不会受到影响。

I/O密集型和CPU密集型

I/O 密集型(Input/Output Intensive)和 CPU 密集型(CPU Intensive)是两种不同类型的计算任务,它们根据所需的主要资源来分类。

I/O 密集型

I/O 密集型任务是指那些需要大量读写操作的任务,无论是磁盘 I/O 还是网络 I/O。这类任务的特点是 CPU 计算不是主要瓶颈,而是等待外部数据的读取或写入。在 I/O 密集型任务中,程序经常处于等待状态,因为它们依赖于文件系统的响应、数据库查询的返回或网络资源的可用性。

例如,一个 Web 服务器通常处理大量的客户端请求,这些请求涉及到网络数据的接收和发送,可能还需要查询数据库或访问文件系统。这些操作通常不会占用太多的 CPU 时间,但会有大量的等待时间,因此这类任务被认为是 I/O 密集型的。

CPU 密集型

CPU 密集型任务是指那些需要大量计算的任务,这些计算会占用大量的 CPU 时间。在 CPU 密集型任务中,CPU 的处理能力是主要的瓶颈。这类任务通常涉及复杂的算法、数据处理、图形渲染或科学计算等。

例如,视频编码、图像处理或机器学习模型的训练通常是 CPU 密集型的任务,因为它们需要大量的数学运算和数据处理。

区别

主要区别在于它们对系统资源的需求不同:

  • I/O 密集型任务:更多地依赖于 I/O 子系统的性能(如硬盘、网络),CPU 大部分时间可能处于空闲状态,等待 I/O 操作完成。
  • CPU 密集型任务:主要依赖于 CPU 的计算能力,CPU 核心会被充分利用,进行大量的计算工作。

在设计和优化系统时,了解任务的类型是非常重要的。对于 I/O 密集型任务,提高 I/O 性能、使用异步编程模型和非阻塞 I/O 可以显著提高效率。而对于 CPU 密集型任务,可能需要考虑使用多线程、多进程或分布式计算来充分利用 CPU 资源,或者使用专门的硬件加速(如 GPU)。

Node.js 由于其非阻塞 I/O 和事件驱动的特性,特别适合处理 I/O 密集型任务。对于 CPU 密集型任务,Node.js 可能不是最佳选择,除非采用了额外的策略来分散计算负载。

线程池后续

当线程池中的委托任务执行完毕后,其结果需要返回给 Node.js 的主线程,以便执行与任务相关的 JavaScript 回调函数。这个过程是通过事件循环来协调的。以下是详细的步骤:

  1. 任务完成:线程池中的线程完成了其分配的任务(例如,文件读取、DNS 查询等)。

  2. 通知事件循环:任务完成后,线程会将一个完成信号(通常是一个事件)发送回事件循环。这个信号告诉事件循环相关的异步操作已经完成,并且结果已经准备好了。

  3. 排队回调:事件循环接收到完成信号后,会将与该异步操作相关的回调函数放入一个队列中。这个队列通常被称为“任务队列”或“回调队列”。

  4. 执行回调:当事件循环进入适当的阶段时(例如,在处理完其他更高优先级的事件后),它会从任务队列中取出回调函数并执行它们。这些回调函数运行在 Node.js 的主线程上,因此它们可以安全地访问 JavaScript 的全局状态,并与其他 JavaScript 代码互操作。

  5. 返回控制权:一旦回调函数执行完毕,控制权返回给事件循环,它将继续处理队列中的其他事件和回调,或者监听新的异步事件。

这个过程确保了即使在多线程环境中,JavaScript 代码的执行仍然是单线程的,从而避免了传统多线程编程中的许多并发问题。同时,它也使得 Node.js 能够高效地处理大量的并发 I/O 操作,因为主线程不会被阻塞在任何单个 I/O 任务上。

为什么适合I/O密集型

如果你将一个非常复杂的任务交给 Node.js 的线程池,这可能会影响性能,尤其是如果这个任务是 CPU 密集型的。这里有几个原因:

  1. 线程池大小限制:Node.js 的线程池默认大小是有限的(通常是 4 个线程,但可以通过设置环境变量 UV_THREADPOOL_SIZE 来调整)。如果你提交了一个非常复杂的任务,它可能会占用线程池中的一个线程很长时间,从而减少了线程池处理其他异步 I/O 任务的能力。

  2. 任务排队:如果线程池中的所有线程都被占用,新的任务将不得不等待,直到一个线程变得可用。这意味着即使是简单的 I/O 任务也可能会遇到延迟,因为它们必须等待复杂任务完成。

  3. 主线程阻塞:虽然线程池可以帮助避免阻塞主线程,但如果线程池中的任务执行时间过长,主线程在等待任务完成以便执行回调时可能会出现性能瓶颈。这是因为回调只能在主线程上执行,而且必须等待线程池中的任务完成。

  4. 资源竞争:CPU 密集型任务可能会导致线程池中的线程消耗大量 CPU 资源,这可能会影响到运行在同一物理机器上的其他进程或应用程序的性能。

为了避免这些问题,你可以采取以下措施:

  • 分解任务:尽可能将复杂任务分解为更小的、可管理的部分,这样可以减少单个任务对线程池的占用时间。

  • 使用 Worker Threads:对于 CPU 密集型任务,可以使用 Node.js 的 worker_threads 模块来创建额外的工作线程,这样可以在不同的线程中并行处理任务,而不会占用 libuv 线程池中的线程。

  • 负载均衡:如果你的应用程序需要处理大量的复杂任务,可以考虑在多个 Node.js 实例之间分配负载,或者使用微服务架构,将不同的任务分散到不同的服务中处理。

  • 外部服务:对于一些特别复杂的任务,可以考虑使用外部服务或专门的后台作业系统来处理,例如使用消息队列和专门的工作进程。

总之,将复杂任务交给 Node.js 的线程池确实可能影响性能,特别是当这些任务是 CPU 密集型的。合理地管理任务和资源是确保 Node.js 应用程序性能的关键。

Released under the MIT License.