首页 > 其他分享 >中英文自动化 vve-i18n-cli

中英文自动化 vve-i18n-cli

时间:2024-05-09 11:12:05浏览次数:18  
标签:resource zh cli 词条 key i18n const vve

安装

npm install vve-i18n-cli -D

package 里添加脚本命令,简化命令使用

{
  "scripts": {
    "i18n": "vve-i18n-cli",
    "i18n-wrap": "vve-i18n-zh-wrap-cli",
    "i18n-check": "vve-i18n-zh-check-cli"
  }
}

根目录下创建配置文件 vve-i18n-cli.config.js

// vve-i18n-cli.config.js
module.exports = {
  // 工作目录
  cwd: ".",
  // 根目录,国际文本所在的根目录
  rootDir: "src",
  // 默认所有模块,如果有传module参数,就只处理某个模块
  // '**/module-**/**/index.js'
  moduleIndexRules: ["."],
  // 忽略模块
  ignoreModuleIndexRules: [],
  // 匹配含有国际化文本的文件规则
  i18nFileRules: ["**/*.+(vue|js)"],
  // 不匹配含有国际化文本的文件规则
  ignoreI18nFileRules: [],
  // 国际化文本的正则表达式,正则中第一个捕获对象当做国际化文本
  i18nTextRules: [/(?:[\$.])t\([\s\n]*['"](.+?)['"]/g],
  // 模块的国际化的json文件需要被保留下的key,即使这些组件在项目中没有被引用
  // 规则可以是一个字符串,正则,或者是函数
  keepKeyRules: [
    /^G\/+/, // G/开头的会被保留
  ],
  // 忽略国际化KEY的规则
  // 规则可以是一个字符串,正则,或者是函数
  ignoreKeyRules: [],
  // 生成的国际化资源包的输出目录
  outDir: "lang",
  // 生成的国际化的语言
  i18nLanguages: [
    "zh", // 中文
    "en", // 英文
  ],
  // 配置文件的路径,没有配置,默认路径是在${cwd}/vve-i18n-cli.config.js
  config: undefined,
  // 是否取配置文件
  disableConfigFile: false,
  // 是否翻译
  translate: false,
  // 翻译的基础语言,默认是用中文翻译
  translateFromLang: "zh",
  // 是否强制翻译,即已翻译修改的内容,也重新用翻译生成
  forceTranslate: false,
  // 翻译的语言
  translateLanguage: ["zh", "en"],
  // 非中文使用拼音来来翻译
  translateUsePinYin: false,
  // 模块下${outDir}/index.js文件不存在才拷贝index.js
  copyIndex: false,
  // 是否强制拷贝最新index.js
  forceCopyIndex: false,

  // 国际化文本包裹相关
  zhWrap: {
    cwd: ".",
    // 根目录,国际文本所在的根目录
    rootDir: ".",
    ignoreI18nFileRules: [],
    i18nFileRules: ["!(node_modules|config|statsvnTmp)/**/*.+(js|vue)"],
    ignorePreReg: [
      /t\s*\([\s\n]*$/,
      /tl\s*\([\s\n]*$/,
      /console\.(?:log|error|warn|info|debug)\s*\(\s*$/,
      /\/\/\s*$/,
      new RegExp("//.+"),
      new RegExp("i18n-disabled.+"),
    ],
    ignoreText: ["^[\\u4e00-\\u9fa5a-zA-Z0-9“._=,':;*#!”-]+$"],
    // js相关文件需要引入的国际化文件
    i18nImportForJs: "",
    // js相关文件需要使用国际化方法
    jsI18nFuncName: "$i18n.$t",
    // vue相关文件需要使用的国际化方法
    vueI18nFuncName: "$t",
  },
  zhCheck: {
    cwd: ".",
    // 根目录,国际文本所在的根目录
    rootDir: ".",
    ignoreI18nFileRules: [],
    i18nFileRules: ["!(node_modules|config|statsvnTmp)/**/*.+(vue|js)"],
    // 反引号中需要忽略的文本规则,可以是正则或者字符串
    ignoreTextInQuoteRules: [/t\(/],
  },
};

 

  

先检查是否存在不合理的代码实现

npm run i18n-check

再执行自动对词条包裹翻译函数的命令

npm run i18n-wrap

最后把这些被翻译函数包裹的词条提取到 json 文件里

npm run i18n


温馨提示

{"type":"script-pre","text":"这里也可能有中文,还用不了this上下文"}
{"type":"props","text":"这里的中文也用不了this上下文"}
{"type":"zh-key","text":"中文"}

script-pre 场景是说发现有中文存在于 script 标签内,这部分代码运行在 js 模块作用域内,this 指向不是 vue 组件,包裹 this.$t 的话会导致程序异常,所以要先手动处理下,能下沉就下沉,否则就先手动用全局函数包裹,然后忽略这个处理

props 场景是说发现有中文存在于 vue 的 props 字段里,这里也无法访问 this,会报异常,建议这块更改成 computed 用法

zh-key 场景是说发现中文做 key 值,需要用户确认是否能被翻译处理

这个命令可以提前主动的发现代码里的国际化处理问题,避免将问题遗留到测试或线上阶段

标记无需处理的词条

总有些场景,你希望这个中文词条不要被国际化处理,这时候可以类似 es-lint 的忽略配置一样,既可以忽略整个文件,也可以忽略文件中的某个词条

在 vve-i18n-cli.config.js 里增加下忽略配置规则:

// vve-i18n-cli.config.js
module.exports = {
  // 国际化文本包裹相关
  zhWrap: {
    // 需要过滤的文件
    ignoreI18nFileRules: [],
    // 需要处理的文件
    i18nFileRules: ["!(node_modules|config|statsvnTmp)/**/*.+(js|vue)"],
    // 当词条前缀出现以下正则时,该词条过滤不处理
    ignorePreReg: [
      /t\s*\([\s\n]*$/, //  词条在 t( 方法内的不处理,$t() 符合该规则
      /console\.(?:log|error|warn|info|debug)\s*\(\s*$/, //  词条在 console.xxx 方法内的不处理,过滤掉日志内的中文处理
      new RegExp("//.+"), // 注释中的词条不处理
      new RegExp("i18n-disabled.+"), // 词条前面出现 i18n-disabled 关键词的不处理
    ],
  },
};

 

  然后代码中这些场景就不被处理了:

const ZH_KEY = /*i18n-disabled*/ "出现忽略处理的关键词的词条也不会被处理";
// 注释里中文不会被处理
console.error("日志里的中文也不会被处理");

 

扩展

我们团队的翻译不是机翻,而是有专门的翻译团队进行翻译,因此提取完 json 词条后,还需要用 excel 跟翻译团队打交道

所以可以来扩展下几个脚本

提取未翻译词条到 excel 文件中

/**
 * 抽取未翻译的词条到excel文件中
 */
const map = require("map-stream");
const path = require("path");
const vfs = require("vinyl-fs");
const XLSX = require("xlsx");

const ROOT_DIR = path.resolve("./");
const fileRules = [
  "**/*/i18n/en.json",
  // "**/eweb-setting-planningDeployment/i18n/en.json",
];

const writeExcel = (arr, name = "未翻译词条") => {
  const sheet_data = arr.map((v) => {
    return {
      中文: v,
      English: "",
    };
  });
  const new_sheet = XLSX.utils.json_to_sheet(sheet_data);
  // // 创新一个新的excel对象,就是workbook
  const new_workbook = XLSX.utils.book_new();
  // // 将表的内容写入workbook
  XLSX.utils.book_append_sheet(new_workbook, new_sheet, "sheet1");
  XLSX.writeFile(new_workbook, `${name}.xlsx`);
};

function run() {
  const zhList = [];
  console.log("================================>start", ROOT_DIR);
  vfs
    .src(
      fileRules.map((item) => path.resolve(ROOT_DIR, item)),
      {
        ignore: ["node_modules/**/*", "statsvnTmp/**/*"],
      }
    )
    .pipe(
      map((file, cb) => {
        console.log("处理文件 =========================>", file.path);

        let fileContent = file.contents.toString();
        fileContent = JSON.parse(fileContent);
        Object.keys(fileContent).map((zh) => {
          if (zh.match(/[\u4E00-\u9FFF]/)) {
            if (zh === fileContent[zh]) {
              // 未翻译
              zhList.push(zh);
            }
          }
        });
        cb();
      })
    )
    .on("end", () => {
      const uniZh = Array.from(new Set(zhList));
      writeExcel(uniZh);
      console.log("未翻译词条数量:", uniZh.length);
      console.log(
        "================================>end",
        "根目录下生成 excle 文件"
      );
    });
}

run();

将 excel 中的词条回填到 json 文件中

/**
 * 将翻译后的内容替换到en.json文件中
 */
const map = require("map-stream");
const path = require("path");
const vfs = require("vinyl-fs");
const fs = require("fs");
const XLSX = require("xlsx");

const ROOT_DIR = path.resolve("./");
const fileRules = ["**/*/i18n/en.json"];
// 文件名称 默认名称 resource.json
const fileName = "resource";
const fileJsonName = fileName + ".json";
const fileXlsName = fileName + ".xlsx";

const excelReader = (exlcePathArray = []) => {
  if (!Array.isArray(exlcePathArray)) {
    exlcePathArray = [exlcePathArray];
  }
  const obj = {};
  for (const i in exlcePathArray) {
    if (Object.hasOwnProperty.call(exlcePathArray, i)) {
      const excleFilePath = exlcePathArray[i];
      console.log("读取excle " + excleFilePath);
      const workbook = XLSX.readFileSync(excleFilePath, {
        type: "binary",
      });
      for (const sheet in workbook.Sheets) {
        const dataArray = XLSX.utils.sheet_to_json(workbook.Sheets[sheet]);
        obj[sheet] = dataArray;
      }
    }
  }
  return obj;
};

// 如果未找到 resource.json 查找 excel文件
if (!fs.existsSync(path.resolve(ROOT_DIR, fileJsonName))) {
  // 判断resource.excel是否存在
  if (fs.existsSync(path.resolve(ROOT_DIR, fileXlsName))) {
    let fileObj = excelReader(path.resolve(ROOT_DIR, fileXlsName));
    let dataObj = {};
    if (fileObj.sheet1) {
      // 中文列存在 例子: {"中文":"下载中...","English":"down…"}
      for (let i = 0; i < fileObj.sheet1.length; i++) {
        let itemZhKey = fileObj.sheet1[i]["中文"];
        let itemEnKey = fileObj.sheet1[i]["English"];
        if (itemZhKey && itemEnKey) {
          dataObj[itemZhKey] = itemEnKey;
        }
      }
    }
    // 获取sheet1的内容
    const data = JSON.stringify(dataObj);
    try {
      fs.writeFileSync(path.resolve(ROOT_DIR, fileJsonName), data);
    } catch (error) {
      console.log("生成文件 resource.xlsx 异常");
      throw error;
    }
  } else {
    console.log("不存在文件 resource.xlsx");
    throw new Error("不存在文件 resource.xlsx");
  }
}

const originResource = require(path.resolve(ROOT_DIR, fileJsonName));
let resource = Object.assign({}, originResource);
Object.keys(originResource).map((key) => {
  resource[`${key}:`] = `${originResource[key]}:`;
  resource[`${key}:`] = `${originResource[key]}:`;
  resource[`${key})`] = `${originResource[key]})`;
  resource[`${key})`] = `${originResource[key]})`;
  resource[`${key} `] = `${originResource[key]} `;
  resource[` ${key}`] = ` ${originResource[key]}`;
  resource[`(${key}`] = `(${originResource[key]}`;
  resource[`(${key}`] = `(${originResource[key]}`;
  resource[` ${key} `] = ` ${originResource[key]} `;
});

function run() {
  console.log("================================>start", ROOT_DIR);
  let failedCount = 0;
  let successCount = 0;
  let failedZhs = [];
  vfs
    .src(
      fileRules.map((item) => path.resolve(ROOT_DIR, item)),
      {
        ignore: ["node_modules/**/*", "statsvnTmp/**/*"],
      }
    )
    .pipe(
      map((file, cb) => {
        console.log("处理文件 =========================>", file.path);

        let fileContent = file.contents.toString();
        fileContent = JSON.parse(fileContent);
        let hasChange = false;
        Object.keys(fileContent).map((zh) => {
          if (zh.match(/[\u4E00-\u9FFF]/)) {
            if (zh === fileContent[zh]) {
              // 未翻译
              if (resource[zh] && resource[zh] !== zh) {
                hasChange = true;
                fileContent[zh] = resource[zh];
                successCount++;
              } else {
                failedCount++;
                failedZhs.push(zh);
              }
            }
          }
        });
        if (hasChange) {
          fs.writeFileSync(
            file.path,
            JSON.stringify(fileContent, " ", 2) + "\n"
          );
        }
        cb();
      })
    )
    .on("end", () => {
      fs.writeFileSync(
        "unHandle.json",
        JSON.stringify(failedZhs, " ", 2) + "\n"
      );
      console.log("本次翻译成功词条数量:", successCount);
      console.log("还剩余未翻译词条数量:", failedCount);
      console.log("================================>end");
    });
}

run();

 

 

 

标签:resource,zh,cli,词条,key,i18n,const,vve
From: https://www.cnblogs.com/Deer-Mr/p/18181686

相关文章

  • nmcli NetworkManager 的命令行工具 它允许用户管理网络连接和网络设备
    1、列出所有连接nmcliconnectionshow2、启用/禁用网络连接nmcliconnectionup<ConnectionName>nmcliconnectiondown<ConnectionName>3、连接到一个Wi-Fi网络nmclidevicewificonnect<SSID>password<password>4、显示网络设备的状态nmclidevicestatus......
  • 2024CVPR_Low-light Image Enhancement via CLIP-Fourier Guided Wavelet Diffusion(C
    一、Motivation1、单模态监督问题:大多数方法往往只考虑从图像层面监督增强过程,而忽略了图像的详细重建和多模态语义对特征空间的指导作用。这种单模态监督导致不确定区域的次优重建和较差的局部结构,导致视觉结果不理想的出现。------》扩散模型缺乏有效性约束,容易出现多种生成效......
  • 「Java开发指南」如何用MyEclipse搭建GWT 2.1和Spring?(一)
    本教程将指导您如何生成一个可运行的GoogleWebToolkit(GWT)2.1和Spring应用程序,该应用程序为域模型实现了CRUD应用程序模式。在本教程中,您将学习如何:安装GoogleEclipse插件为GWT配置一个项目搭建从数据库表到一个现有的项目GWT编译部署应用程序注意:自定义Spring代码......
  • 原始翎风CLIENT8位 (11) fsata的学习
    本单元提供系统中的所有对话框显示MAINIMAGEFILE='Data\Prguse.wil';MAINIMAGEFILE2='Data\Prguse2.wil';MAINIMAGEFILE3='Data\Prguse3.wil';CHRSELIMAGEFILE='Data\ChrSel.wil';MINMAPIMAGEFILE='Data\mmap.wil......
  • 原始翎风CLIENT8位 (12) playscn的学习
    绘图关键的数据地图客户区,以主角的地图坐标为中心左右各9格,上下9,8格一切以主角为中心进行计算,换算。Map.m_ClientRectLeft:=g_MySelf.m_nRx-9;Top:=g_MySelf.m_nRy-9;Right:=g_MySelf.m_nRx+9;Bottom:=g_MySelf.m_nRy+8; 地图地面绘图m_MapSurface的像素大小......
  • 原始翎风CLIENT8位 (10) tscenc的学习
    IntroScn.pas嗟夫DELPHI输入法相关知识凡是窗口类都有TImeMode这是个集合其中包含有:TImeMode=(imDisable,imClose,imOpen,imDontCare,imSAlpha,imAlpha,imHira,imSKata,imKata,imChinese,imSHanguel,imHanguel);指定imDisable的话IME变得无效。既无法作使......
  • 原始翎风CLIENT8位 (13) actor的学习
    functionGetOffset(appr:integer):integer偏移大于1000退出nrace:=apprdiv10nrace0-90npos:=apprmod10npos0-9这个找的是怪物图片在文件中图片索引偏移量分为很多种,有偏移280,280是一个怪物的一组图片,例如MON1有偏移230,例如MON2有偏移360的,例如MON3appr应该......
  • 原始翎风CLIENT8位 (14) mapunit的学习
    8.MaxInt格式:constMaxInt=High(integer);说明:MaxInt常量代表Integer类型的最大可能值.MaxInt的真正的值会随着Delphi的版本不同而改变,目前它的值是21474836472g?地图好像是分块?40*40个地图坐标分为一个广场块一次读取是当前块的-1X/Y+2合计4*4块合计160*160个......
  • 开发环境搭建之一,Clion的下载和安装
    前言在最近的两年工作经历中,用的微控制器的架构越来越杂,STC的八位机,STM8,STM32G系,仿STM32系,乐鑫无线系列,沁恒的RISV-V系列,也自己摸索了几个型号的cortexM7并尝试使用A7。我越来越觉得单片机无聊和乏力。单片机层次的嵌入式软件工程师大多是电子系出身,专业主干课程是电子物理相关,......
  • 原始翎风CLIENT8位 (8) CLUNIT的学习
    这个里面是绘图,有汇编,那个绘画效果的汇编看明白了,实际是先建立了一个颜色的转换索引表,颜色对应下标,数据是转换后的颜色在绘画效果中用函数功能找到混合表面的的指针,锁定它用汇编语言,将混合表面的每一个像素查找转换索引表,转换过去。因为用了MM0寄存器,这是个MMX的指令里面的,它是64......