npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tsx-i18n

v1.0.11

Published

一个在react应用中替换tsx文件内中文文案为i18n函数,实现国际化的脚本。

Downloads

9

Readme

这是一个替换tsx内中文文案的脚本。

使用方法

1、在项目根目录新建js脚本文件

// i18n.js
const TsxI18n = require('tsx-i18n');

// 配置信息
const config = {
  area: 'area', // 业务场景
  moduleName: 'moduleName', // 业务模块
  point: 'point', // 功能点
  entry: '/src/index.tsx', // 入口文件,相对项目根路径地址
  transResult: true, // log 中是否包含翻译结果
  importI18n: `import i18n from '@/config/i18n';`, //
  prettierOption: {}, // 输出文件格式化配置
  exclude: (name) => { // 需要排除的文件
    // console.log(name, '==name==')
    return /api|config|utils/.test(name);
  },
  getWordMap: () => ({}), // 获取已有的文案字典
  getLog: (log) => { /*获取替换日志*/ },
};

const tsxI18n = new TsxI18n(config);
tsxI18n.init();

2、在命令行启动脚本

  node i18n.js

工作原理

分析

1、在tsx代码中,文案类型可大致分为三类:string、templateString、jsxText。

  const str = 'string';
  const templateStr = `template ${str}`;
  <div>jsxText</div>

2、其中string、templateString出现的位置也可大致分为三处:变量声明赋值、对象属性赋值、组件属性赋值。

  // 变量声明赋值
  const str = 'jack';
  const templateStr = `这是模版字符串 ${name}`;

  // 对象属性赋值
  const info = {
    name: 'tom',
    adress: `${str} 在杭州`
  }

  // 组件属性赋值
  <Component name="tom" age={`${age}`} />

本仓库替换脚本基于以上场景分析进行文案替换。

替换流程

替换脚本从入口文件入手,通过编译入口文件获取所有依赖,并对依赖模块进行递归查找,建立依赖图谱。循环所有依赖模块,编译、收集文案。若文案已存在字典中,则不做处理,否则使用百度翻译对文案进行翻译,根据配置项生成取值key,更新字典。之后再次循环编译所有依赖,替换文案,并将替换后的代码覆写入原文件。

大致流程如下:

获取入口文件绝对路径 -> 获取所有本地依赖模块 -> 遍历所有依赖文件收集文案 -> 建立字典 -> 替换文案 -> 覆写文件。

代码演示

一、基础替换

对于一般字符串、模版字符串、jsxText,脚本会直接替换。若模版字符串中包含变量,则会将变量提取,在词条中使用占位符代替变量,并将变量数据作为i18n函数的第二个参数传递。

如下所示:

  // 源代码
  ...
  const tem = `这是一个模版字符串-${str}`
  ...

  // 替换后代码
  ...
  const tem = i18n("area.moduleName.thisIsATemplateString", { str: str });
  ...

  // 字典词条
  {
    ...
    "area.moduleName.thisIsATemplateString": "这是一个模版字符串-${str}",
    ...
  }

demo 演示:

  node index.js demo1

源代码:

  import React, { FC, useRef, useState } from 'react';
  import i18n from '@/config/i18n';
  import PageHeader from '@/components/PageHeader';
  import { getUrlParams } from '@/utils/globalFn';
  import List from './List';
  import Search, { ISearchValues } from './Search';

  interface IEntryListPageProps {}

  const EntryListPage: FC<IEntryListPageProps> = () => {

    const str = '这是一个中文字符串'
    const tem = `这是一个模版字符串-${str}`

    const info = {
      user: {
        age: 18
      },
      name: '这是一个中文属性',
      adress: `这是一个中文模版字符串属性-${str}`
    }
  
    return (
      <div>
        <div>中文文案</div>
        <Component name="tom" age={`年龄是${info.user.age}`} />
      </div>
    );
  };

  export default EntryListPage;

替换后:

  import React, { FC, useRef, useState } from "react";
  import i18n from "@/config/i18n";
  import PageHeader from "@/components/PageHeader";
  import { getUrlParams } from "@/utils/globalFn";
  import List from "./List";
  import Search, { ISearchValues } from "./Search";
  interface IEntryListPageProps {}
  const EntryListPage: FC<IEntryListPageProps> = () => {
    const str = i18n("area.moduleName.thisIsAChineseString");
    const tem = i18n("area.moduleName.thisIsATemplateString", { str: str });
    const info = {
      user: {
        age: 18,
      },
      name: i18n("area.moduleName.thisIsAChineseAttribute"),
      adress: i18n("area.moduleName.thisIsAChineseTemplate", { str: str }),
    };
    return (
      <div>
        <div>{i18n("area.moduleName.chineseCopy")}</div>
        <Component
          name="tom"
          age={i18n("area.moduleName.theAgeIsInfoUser", {
            info_user_age: info.user.age,
          })}
        />
      </div>
    );
  };
  export default EntryListPage;

log 记录:

  {
    "importFile": [
      "/Users/mac/git/tsx-i18n/src/demo1.tsx"
    ],
    "replaceFile": [
      "/Users/mac/git/tsx-i18n/src/demo1.tsx"
    ],
    "noReplace": [],
    "excludeMap": {
      "/Users/mac/git/tsx-i18n/src/demo1.tsx": {
        "@/config/i18n": "不需要编译的引用",
        "@/utils/globalFn": "不需要编译的引用"
      }
    },
    "wordList": [
      "这是一个中文字符串",
      "这是一个模版字符串-${str}",
      "这是一个中文属性",
      "这是一个中文模版字符串属性-${str}",
      "中文文案",
      "年龄是${info_user_age}"
    ],
    "findWord": [],
    "newWordMap": {
      "area.moduleName.thisIsAChineseString": "这是一个中文字符串",
      "area.moduleName.thisIsATemplateString": "这是一个模版字符串-${str}",
      "area.moduleName.thisIsAChineseAttribute": "这是一个中文属性",
      "area.moduleName.thisIsAChineseTemplate": "这是一个中文模版字符串属性-${str}",
      "area.moduleName.chineseCopy": "中文文案",
      "area.moduleName.theAgeIsInfoUser": "年龄是${info_user_age}"
    },
    "writeError": [],
    "handler": {},
    "giveUp": {
      "/Users/mac/git/tsx-i18n/src/components/PageHeader": {
        "/Users/mac/git/tsx-i18n/src/components/PageHeader.tsx": "未找到",
        "/Users/mac/git/tsx-i18n/src/components/PageHeader/index.tsx": "未找到"
      },
      "/Users/mac/git/tsx-i18n/src/List": {
        "/Users/mac/git/tsx-i18n/src/List.tsx": "未找到",
        "/Users/mac/git/tsx-i18n/src/List/index.tsx": "未找到"
      },
      "/Users/mac/git/tsx-i18n/src/Search": {
        "/Users/mac/git/tsx-i18n/src/Search.tsx": "未找到",
        "/Users/mac/git/tsx-i18n/src/Search/index.tsx": "未找到"
      }
    }
  }

二、特殊情况处理

1、模版字符串中包含三目运算

对于此类复杂的模版字符串,替换脚本不会进行文案替换。而是通过日志记录,提示用户手动替换。

demo 演示:

  node index.js demo2

log记录:

  {
    "importFile": [
      "/Users/mac/git/tsx-i18n/src/demo2.tsx"
    ],
    "replaceFile": [],
    "noReplace": [
      "/Users/mac/git/tsx-i18n/src/demo2.tsx"
    ],
    "excludeMap": {},
    "wordList": [],
    "findWord": [],
    "newWordMap": {},
    "writeError": [],
    "handler": {
      "/Users/mac/git/tsx-i18n/src/demo2.tsx": {
        "`我的名字是 ${true ? '张三' : 'zhangsan'}`": "包含三目判断、引号时需手动处理"
      }
    },
    "giveUp": {},
    "transResult": {},
    "translateError": {}
  }

2、两条文案产生相同的取值key

文案取值key的生成法则为:默认获取翻译结果的前五个单词进行驼峰拼接,生成字段编码,然后和 areamoduleNamepoint 进行拼接。

  // 取值key生成规则
  const resolveKey = [area, moduleName, point, wordKey].filter(it => it).join('.')

因此,若文案前段相同,极有可能获取相同字段编码。大致可分为以下四类:

1、翻译后生成的取值key已存在旧字典中,但对应的中文文案不同

  // 旧字典
  {
    ...
    "thisIsAChineseCopy": "这里是一条中文文案"
    ...
  }
  // 当前文案
  这是一条中文文案啊 -> This is a Chinese copy(thisIsAChineseCopy /*驼峰拼接后*/)

2、已有文案比当前文案短

  这是一条中文文案 -> This is a Chinese copy (已有文案)
  这是一条中文文案重复 -> This is a Chinese copy repetition (当前文案)

3、已有文案比当前文案长

  这是一条中文文案重复 -> This is a Chinese copy repetition(已有文案)
  这是一条中文文案 -> This is a Chinese copy(当前文案)

4、已有文案与当前文案不相同,但翻译结果相同

  这是一条中文文案 -> This is a Chinese copy(已有文案)
  这是一条中文文案啊 -> This is a Chinese copy(当前文案)

对于以上三种情况,脚本采用以下处理策略:

  • 若新获取的取值key已存在旧字典中,则该条文案不会被替换,并记录log信息,提示用户手动处理。
  • 遍历当前文案翻译单词,查找与已有文案翻译的不同,并在字段编码后拼接不同单词。
  • 遍历已有文案翻译,查找与当前文案翻译的不同,将不同单词拼接,产生新key,用新key替换翻译记录信息中的旧key。旧key让渡给当前文案,并记录信息。
  • 查找不到新老词条翻译的不同,当前文案不会被替换,并记录log信息,提示用户手动处理。

demo 演示:

  node index.js demo3

3、文案以冒号结尾

当文案以冒号结尾时,脚本在匹配取值key时,会匹配包含冒号和不包含冒号两种情况,并进行记录,提示用户检查结果是否缺失冒号。

demo 演示:

  node index.js demo4

发布记录

2023-03-21: v1.0.11

1、log 输出新增 prveKeysError 属性。记录收集 prveKeys 时产生的错误。

如下所示:

  // log 信息
  {
    ...
    "prveKeysError": { ... }
    ...
  }

2、新增 diffKeys 配置。对比已在使用的 key 与现字典的差异。
使用场景:项目中包含 A、B、C 等模块,原有的字典文案包含A、B、C所有的文案词条,现需要将A、B、C模块字典拆分,可用此字段获取A模块所有正在使用的key。
如下所示:

  // 配置信息
  const config = {
    ...
    diffKeys: true,
     getWordMap: () => ({}) // 返回现有A模块字典
    ...
  }

  // log 信息
  {
    "diffKeys": {
      ...
      "area.moduleName.${name}": "i18n(`area.moduleName.${name}`)",
      ...
    }
  }

2023-03-21: 1.0.10

新增 getPrveKeys 属性。若该属性为 true,则脚本只会收集代码中已有的 key,并将收集的 key 写入 log,不会进行文案替换。

如下所示:

  // 配置信息
  const config = {
    ...
    getPrveKeys: true
    ...
  }

  // 源代码
  ...
  Message.error(i18n('area.moduleName.changeList.pleaseFillInAPiece'));
  ...

  // log 信息
  {
    ...
    "prveKeys": {
      ...
      "area.moduleName.changeList.pleaseFillInAPiece": "i18n('area.moduleName.changeList.pleaseFillInAPiece')"
      ...
    }
    ...
  }