@chaoswise/intl 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  const runCollect = require('./scripts/collect');
3
3
  const runUpdate = require('./scripts/update');
4
+ const runInitConfig = require('./scripts/initConfig');
4
5
 
5
6
  // 可执行的指令
6
- const SCRIPTS = ['collect', 'update'];
7
+ const SCRIPTS = ['intl', 'collect', 'update'];
7
8
 
8
9
  const args = process.argv.slice(2);
9
10
 
@@ -14,6 +15,9 @@ const scriptIndex = args.findIndex(arg => {
14
15
 
15
16
  const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
16
17
 
18
+ if(script === 'init') {
19
+ runInitConfig();
20
+ }
17
21
  if(script === 'collect') {
18
22
  runCollect();
19
23
  }
@@ -5,11 +5,24 @@ const getTargetFiles = require('./util/getTargetFiles');
5
5
  const transformAst = require('./util/transformAst');
6
6
  const log = require('./util/log');
7
7
  const file = require('./util/file');
8
+ const getGroupName = require('./util/getGroupName');
9
+ const { readWordJson } = require('./util/getWord');
8
10
 
9
11
  const service = require('./service');
10
12
 
11
- async function init() {
13
+ async function collect() {
14
+ log.info('词条扫描中...')
12
15
  const conf = getConf();
16
+
17
+ // 读取wordJson
18
+ if (conf.localWordPath && !readWordJson(conf.localWordPath)) {
19
+ log.error('localWordPath配置有误');
20
+ process.exit(1);
21
+ }
22
+ // 读取groupName
23
+ const groupName = getGroupName();
24
+
25
+ // 获取需要遍历的文件
13
26
  const files = getTargetFiles(conf);
14
27
 
15
28
  // 遍历文件,并对代码进行ast处理的方法:transformAst(type, files, conf, replaceWords)
@@ -18,19 +31,25 @@ async function init() {
18
31
 
19
32
  // 在国际化平台中获取所有已存在中文信息
20
33
  const res = await service.searchByZh(info.allWords);
34
+
35
+ if (res.code !== 10000) {
36
+ log.error('请求数据出错:' + res.msg);
37
+ process.exit(1);
38
+ }
39
+
21
40
  const wordsMap = res.data;
22
41
 
23
42
  const replaceWords = {}; // { zh: id } 需要在代码中替换的词条id
24
43
  const newWords = {}; // { zh1: '待翻译', zh2: '请选择:中文1/中文2' }
25
- Object.entries(wordsMap).forEach(([zh, value]) => {
44
+ Object.entries(wordsMap).forEach(([word, value]) => {
26
45
  // 如果当前中文词条在平台中存在,且对应英文翻译有且只有一条,那么直接替换代码中的中文为当前id,否则生成临时key,让开发者手动确认
27
46
  if (value.length === 1) {
28
- replaceWords[zh] = value[0].key;
47
+ replaceWords[word] = value[0].key;
29
48
  } else {
30
49
  const id = uuidv4();
31
- replaceWords[zh] = id;
50
+ replaceWords[word] = id;
32
51
  newWords[id] = {
33
- zh,
52
+ zh: word,
34
53
  en:
35
54
  value.length === 0
36
55
  ? '待翻译'
@@ -50,18 +69,15 @@ async function init() {
50
69
  if (Object.keys(newWords).length) {
51
70
  const newWordsJsonPath = 'newWords.json';
52
71
 
53
- let oldNewWords = file.readJson(newWordsJsonPath) || {};
72
+ const oldNewWords = file.readJson(newWordsJsonPath) || {};
54
73
  file.write(
55
74
  newWordsJsonPath,
56
- JSON.stringify({ ...oldNewWords, ...newWords }, null, 2)
75
+ JSON.stringify({ ...oldNewWords, ...newWords, groupName }, null, 2)
57
76
  );
58
77
 
59
- // 先注释掉,开始做指标时,会用到
60
- // const package = file.read('package.json');
61
- // file.write(newWordsJsonPath, JSON.stringify({ ...oldNewWords, ...newWords, projectName: package.name }, null, 2));
62
78
  log.success(`需要翻译的文件在项目根目录的newWords.json中`);
63
79
  }
64
80
  }
65
81
 
66
- // init();
67
- module.exports = init;
82
+ // collect();
83
+ module.exports = collect;
@@ -8,7 +8,6 @@ module.exports = function (excludes = []) {
8
8
  // exclude pattern, <string array>
9
9
  // e.g. ['**/dist/**', '**/*.config.js', '**/*.data.js']
10
10
  exclude: [
11
- '**/router.config.js',
12
11
  '**/src/public/**',
13
12
  '**/_MOCK_/**'
14
13
  ],
@@ -19,7 +18,7 @@ module.exports = function (excludes = []) {
19
18
 
20
19
  // ignored components, <string array>
21
20
  // e.g. ['EventTracker']
22
- ignoreComponents: [],
21
+ ignoreComponents: ['style', 'svg'],
23
22
 
24
23
  // ignored methods, <string array>
25
24
  // e.g. ['MirrorTrack']
@@ -41,6 +40,8 @@ module.exports = function (excludes = []) {
41
40
  // e.g. 't'
42
41
  i18nMethod: 'get',
43
42
 
43
+ i18nDefaultFunctionKey: 'd',
44
+
44
45
  // babel配置项
45
46
  babelPresets: [],
46
47
  babelPlugins: [],
@@ -54,6 +55,40 @@ module.exports = function (excludes = []) {
54
55
  endOfLine: 'lf',
55
56
  },
56
57
 
58
+ // <Tag id={{ key: 'id' }} defaultMessage='中文' />
59
+ // intlTag 是 'Tag'
60
+ // intlTagIdPath 是 'id.key'
61
+ // intlTagDefaultWordPath 是 'defaultMessage',并且如果不配置,默认是'defaultMessage'
62
+ intlTag: '',
63
+ intlTagIdPath: '',
64
+ intlTagDefaultWordPath: '',
65
+
66
+ // intl.formatMessage({
67
+ // key: 'id',
68
+ // defaultMessage: '默认值',
69
+ // })
70
+ // intlFunctionName 是 intl.formatMessage
71
+ // intlFunctionIdPath 是 '0.key'。 如果 配置成 'key' 也行,intlFunctionIdPath如果不以数字开头,会在前面加上'0.' 'key' 就会变成 '0.key'
72
+ // intlFunctionDefaultWordPath 是 '0.defaultMessage',其他的说明同intlFunctionIdPath
73
+ intlFunctionName: '',
74
+ intlFunctionIdPath: '',
75
+ intlFunctionDefaultWordPath: '',
76
+
77
+ // 做过国际化的项目,中文config的路径。例如:'src/local/zh.json'
78
+ localWordPath: '',
79
+
80
+ // 配置 需要被脚本删除的依赖包的名字
81
+ removePkgName: [],
82
+
83
+ // 需要删除的函数和装饰器 的名称
84
+ removeFunctionName: ['injectIntl'],
85
+
86
+ // package.json中项目名称的key
87
+ groupNameKey: 'packageName',
88
+
89
+ // 特殊文件处理的正则 这里默认匹配router.config.js
90
+ specialFileReg: [/src\/config\/router\.config\.js/],
91
+
57
92
  // 国际化平台地址
58
93
  baseURL: 'http://10.0.1.133:18000',
59
94
  };
@@ -0,0 +1,8 @@
1
+ const file = require('../util/file');
2
+
3
+ module.exports = function () {
4
+ // 获取用户自定义配置文件
5
+ const customConfig = file.readJs('.intlconfig.js');
6
+
7
+ return customConfig;
8
+ };
@@ -1,25 +1,5 @@
1
1
  const getDefaultConfig = require('./default');
2
- const file = require('../util/file');
3
- const prettier = require('prettier');
4
-
5
- function createCustomConfig() {
6
- // 默认生成的配置文件,不需要给出全量可配置属性
7
- const ignoreAttributes = [
8
- 'primaryRegx',
9
- 'importCode',
10
- 'i18nObject',
11
- 'i18nMethod',
12
- 'baseURL',
13
- ];
14
- const defaultConfig = getDefaultConfig(ignoreAttributes);
15
-
16
- const code = `module.exports = ${JSON.stringify(defaultConfig, null, 2)};`;
17
-
18
- file.write(
19
- '.intlconfig.js',
20
- prettier.format(code, { ...defaultConfig.prettier, parser: 'babel' })
21
- );
22
- }
2
+ const getCustomConfig = require('./getCustomConfig');
23
3
 
24
4
  // 内置一些,不需要被扫描的函数
25
5
  const ignoreMethods = [
@@ -28,11 +8,8 @@ Object.keys(console).forEach(key => ignoreMethods.push(`console.${key}`))
28
8
 
29
9
  module.exports = function () {
30
10
  // 获取用户自定义配置文件
31
- const customConfig = file.readJs('.intlconfig.js');
11
+ const customConfig = getCustomConfig() || {};
32
12
 
33
- if (!customConfig) {
34
- createCustomConfig();
35
- }
36
13
  // 合并配置文件
37
14
  const finalyConf = { ...getDefaultConfig(), ...customConfig };
38
15
 
@@ -0,0 +1,37 @@
1
+ const getCustomConfig = require('./getCustomConfig');
2
+ const getDefaultConfig = require('./default');
3
+ const file = require('../util/file');
4
+ const prettier = require('prettier');
5
+ const log = require('../util/log');
6
+
7
+ module.exports = function initConfig() {
8
+ const customConfigName = '.intlconfig.js'
9
+
10
+ const customConfig = getCustomConfig();
11
+
12
+ if (customConfig) {
13
+ console.log(`${customConfigName}已存在,请手动删除本地已经有的配置文件再重新执行 npm run intl-init`);
14
+ return;
15
+ }
16
+
17
+ // 默认生成的配置文件,不需要给出全量可配置属性
18
+ const ignoreAttributes = [
19
+ 'primaryRegx',
20
+ 'importCode',
21
+ 'i18nObject',
22
+ 'i18nMethod',
23
+ 'i18nDefaultFunctionKey',
24
+ 'specialFileReg',
25
+ 'baseURL',
26
+ ];
27
+ const defaultConfig = getDefaultConfig(ignoreAttributes);
28
+
29
+ const code = `module.exports = ${JSON.stringify(defaultConfig, null, 2)};`;
30
+
31
+ file.write(
32
+ customConfigName,
33
+ prettier.format(code, { ...defaultConfig.prettier, parser: 'babel' })
34
+ );
35
+
36
+ log.success(`初始化配置文件成功:.intlconfig.js`);
37
+ }
@@ -0,0 +1,3 @@
1
+ const initConfig = require('./conf/initConfig');
2
+
3
+ module.exports = initConfig;
@@ -24,6 +24,6 @@ exports.searchByZh = (zhs) => {
24
24
  };
25
25
 
26
26
  // 根据词条id获取所有数据库词条信息
27
- exports.getJson = (ids) => {
28
- return axios.post('/getJson', ids);
27
+ exports.getJson = ({ groupName, downloadIds }) => {
28
+ return axios.post('/getJson', { groupName, downloadIds });
29
29
  };
@@ -5,7 +5,7 @@ const downloadJson = require('./util/downloadJson');
5
5
  const log = require('./util/log');
6
6
  const file = require('./util/file');
7
7
 
8
- async function replace() {
8
+ async function update() {
9
9
  const conf = getConf();
10
10
  const files = getTargetFiles(conf);
11
11
 
@@ -13,7 +13,7 @@ async function replace() {
13
13
  const relationKey = file.readJson('relationKey.json') || {};
14
14
 
15
15
  const info = transformAst('update', files, conf, relationKey);
16
- downloadJson(info.downloadIds);
16
+ await downloadJson(info.downloadIds);
17
17
 
18
18
  const needDelete = Boolean(Object.keys(relationKey).length);
19
19
  if (needDelete) {
@@ -23,10 +23,10 @@ async function replace() {
23
23
  log.success('newWords.json和relationKey.json 已删除');
24
24
  }
25
25
 
26
- log.success('脚本执行成功2222');
26
+ log.success('脚本执行成功');
27
27
  }
28
28
 
29
- // replace();
30
- module.exports = replace;
29
+ // update();
30
+ module.exports = update;
31
31
 
32
32
 
@@ -1,8 +1,17 @@
1
1
  const service = require('../service');
2
2
  const file = require('./file');
3
+ const log = require('./log');
4
+ const getGroupName = require('./getGroupName');
3
5
 
4
6
  module.exports = async function downloadJson(downloadIds) {
5
- const res = await service.getJson(downloadIds);
7
+ // 读取groupName
8
+ const groupName = getGroupName();
9
+
10
+ const res = await service.getJson({ groupName, downloadIds });
11
+ if (res.code !== 10000) {
12
+ log.error(res.msg);
13
+ process.exit(1);
14
+ }
6
15
  const { zh, en } = res.data;
7
16
  file.write('locales/zh-CN.json', JSON.stringify(zh, null, 2));
8
17
  file.write('locales/en-US.json', JSON.stringify(en, null, 2));
@@ -0,0 +1,15 @@
1
+ const file = require('./file');
2
+ const log = require('./log');
3
+ const getConf = require('../conf');
4
+
5
+ module.exports = function getGroupName() {
6
+ const conf = getConf();
7
+ // 读取packageName
8
+ const package = file.readJson('package.json');
9
+ const groupName = package?.[conf.groupNameKey];
10
+ if (!groupName) {
11
+ log.error(`package.json中,groupName不能为空`);
12
+ process.exit(1);
13
+ }
14
+ return groupName;
15
+ };
@@ -0,0 +1,13 @@
1
+ const file = require('./file');
2
+
3
+ let wordJson = null;
4
+ // 根据中文配置文件和id,来获取实际的中文
5
+ exports.readWordJson = function (localWordPath) {
6
+ wordJson = file.readJson(localWordPath);
7
+ return Boolean(wordJson);
8
+ }
9
+ // 根据中文配置文件和id,来获取实际的中文
10
+ exports.getWord = function (id) {
11
+ const word = (wordJson || {})[id];
12
+ return word;
13
+ }
@@ -1,30 +1,160 @@
1
1
  const log = require('./log');
2
2
  const t = require('@babel/types');
3
+ const generate = require('@babel/generator').default;
4
+ const { getWord } = require('./getWord');
5
+ const specialMatch = require('./specialMatch');
6
+ const { FormPath } = require('@formily/antd');
3
7
 
4
- module.exports = function(
8
+ module.exports = function (
5
9
  {
6
10
  primaryRegx,
7
11
  i18nObject,
8
12
  i18nMethod,
13
+ i18nDefaultFunctionKey,
9
14
  importCode,
10
15
  ignoreLines,
11
16
  ignoreMethods,
12
17
  ignoreComponents = [],
13
18
  ignoreAttributes,
19
+ intlTag,
20
+ intlTagIdPath,
21
+ intlTagDefaultWordPath,
22
+ intlFunctionName,
23
+ intlFunctionIdPath,
24
+ intlFunctionDefaultWordPath,
25
+ removePkgName,
26
+ localWordPath,
27
+ removeFunctionName,
14
28
  },
15
- returns
29
+ returns,
30
+ { filePath, special },
16
31
  ) {
32
+
33
+ // jsx默认 defaultMessage
34
+ intlTagDefaultWordPath = intlTagDefaultWordPath || 'defaultMessage';
35
+
17
36
  const { replaceWords, allWords, downloadIds } = returns;
18
37
  const hacked = {};
19
38
 
39
+ // 在没有replaceWords的情况下,不用log信息,这里统一处理
40
+ const properLog = {};
41
+ Object.keys(log).forEach(fnName => {
42
+ properLog[fnName] = (...args) => {
43
+ if (!Object.keys(replaceWords).length) return;
44
+ log[fnName](...args);
45
+ }
46
+ })
47
+
48
+ // 获取完整路劲。例如:path='key',返回'0.key'
49
+ function getFullPath (path) {
50
+ if (!path) return path;
51
+ if (typeof path !== 'string') return path;
52
+ if (/^[0-9]/.test(path)) {
53
+ return path;
54
+ }
55
+
56
+ let preFixPath = '0';
57
+ if (!/^\./.test(path)) {
58
+ preFixPath = '0.';
59
+ }
60
+ return preFixPath + path;
61
+ }
62
+ // 插入注释
63
+ function insertFailCommon(node) {
64
+ const commentStr = ' @chaoswise/intl脚本替换失败 ';
65
+ const comment = node?.leadingComments?.value
66
+ if (comment && comment.trim() === commentStr) return;
67
+ t.addComment(node, 'leading', commentStr);
68
+ }
69
+
70
+ // 获取国际化jsx标签中的id或者defaultMessage
71
+ function getJsxTagChaoswiseIntlValue(openingElement, intlTagValuePath) {
72
+ // <Tag id={{ x: ['id'] }} defaultMessage={{ x: ['中文'] }} />
73
+ // valueAst是{ x: ['id'] }的ast
74
+ let valueAst;
75
+ // intlTagValuePath 是 id.x.0
76
+ // valueKey 是 id
77
+ // valuePath 是 x.0
78
+ let valueKey = intlTagValuePath;
79
+ let valuePath = '';
80
+
81
+ const index = (intlTagValuePath || '').indexOf('.');
82
+ if (index !== -1) {
83
+ valueKey = intlTagValuePath.slice(0, index);
84
+ valuePath = intlTagValuePath.slice(index + 1);
85
+ }
86
+
87
+ // <Tag {...props} />不能找到对应的值
88
+ valueAst = openingElement.attributes.find((item) => {
89
+ return item?.name?.name === valueKey;
90
+ })?.value;
91
+ // intlTagValuePath配置不正确就会找不到value
92
+ // 比如<Tag id2='id' /> 把intlTagValuePath配置为id就不行,因为这里应该是id2
93
+ if (!valueAst) return false;
94
+
95
+ // 如果是jsx容器,就 去掉jsx容器,拿里面的值
96
+ // 从{{ x: ['id'] }} 变为 { x: ['id'] }
97
+ if (valueAst.type === 'JSXExpressionContainer') {
98
+ valueAst = valueAst.expression;
99
+ }
100
+
101
+ // 把ast转为js, 再用FormPath.getIn来取最终的值
102
+ const { code: valueCode } = generate(valueAst);
103
+ var chaoswiseIntlValue;
104
+ try {
105
+ eval('var chaoswiseIntlValue =' + valueCode);
106
+ chaoswiseIntlValue = FormPath.getIn(chaoswiseIntlValue, valuePath);
107
+ } catch (e) {
108
+ // var a = 'id'; <Tag id={a} 这样会出错
109
+ }
110
+
111
+ if (typeof chaoswiseIntlValue !== 'string') {
112
+ return false;
113
+ }
114
+
115
+ return chaoswiseIntlValue;
116
+ }
117
+
118
+ // 获取函数名称 fn/object.fn/object.a.b.c/intl.get().d
119
+ function getFnName(node) {
120
+ let fnName = '';
121
+ while (['CallExpression', 'MemberExpression', 'Identifier'].includes(node?.type)) {
122
+ if (node.type === 'CallExpression') {
123
+ let name = getFnName(node.callee);
124
+ fnName = `${name}().` + fnName;
125
+ break;
126
+ } else if (node.type === 'MemberExpression') {
127
+ fnName = `${node.property.name}.` + fnName;
128
+ if (node.object.name) {
129
+ fnName = `${node.object.name}.` + fnName;
130
+ }
131
+ node = node.object;
132
+ } else if (node.type === 'Identifier') {
133
+ // 这里获取修饰器的函数名称
134
+ fnName = fnName || (node.name + '.')
135
+ break;
136
+ }
137
+ }
138
+
139
+ // 把最后的 点. 取消
140
+ fnName = fnName.slice(0, -1);
141
+ return fnName;
142
+ }
143
+
20
144
  // XXX: [TRICKY] 防止中文转码为 unicode
21
145
  function hackValue(value, id) {
22
146
  if (id) hacked[id] = true;
23
147
 
148
+ // 字符串默认是单引号,如果模板字符串中出现单引号,需要转义,把 ' 换成 \'
149
+ // 就算用户自定配置字符串为双引号,也没事,默认会转义双引号,但是默认不会处理单引号,所以自己手动处理
150
+ if (/\'/.test(id)) {
151
+ id = id.replace(/\'/g, '\\\'');
152
+ }
153
+
24
154
  return Object.assign(t.StringLiteral(value), {
25
155
  extra: {
26
156
  raw: `'${id}'`, // id
27
- rawValue: value, // zh
157
+ rawValue: value, // word
28
158
  },
29
159
  });
30
160
  }
@@ -37,8 +167,12 @@ module.exports = function(
37
167
  return false;
38
168
  }
39
169
 
40
- // 是否命中替换的正则
170
+ // 是否命中替换条件
41
171
  function isPrimary(str) {
172
+ if (special) {
173
+ // 如果匹配到了替换过的,就返回false。为了防止多次执行collect,多次嵌套替换
174
+ if (specialMatch(str)) return false;
175
+ }
42
176
  return primaryRegx.test(str);
43
177
  }
44
178
 
@@ -58,28 +192,37 @@ module.exports = function(
58
192
  function makeObjectExpression(obj) {
59
193
  if (Object.prototype.toString.call(obj) !== '[object Object]') return null;
60
194
 
61
- // 存储每一项变量
62
- const ObjectPropertyArr = [];
63
- Object.keys(obj).forEach((k) => {
64
- const node = obj[k].value;
65
- // 模板字符串中,变量存在函数调用的情况下:如果当前变量为函数,那么需要处理函数的中文参数,比如:`hello ${getText('中文')}`
66
- if (!shouldIgnore(node) && node?.type === 'CallExpression') {
67
- node.arguments = dealArgs(node.arguments);
68
- }
69
- ObjectPropertyArr.push(
70
- t.ObjectProperty(
195
+ // 模板字符串中,转换后,如果第二项参数还有中文,或者更深层级有中文,这里处理好
196
+ // 模板字符串转换前`${f(`aa${f('中文')}中文`)}中文`
197
+ /* 转换后
198
+ intl
199
+ .get('id1', {
200
+ slot0: f(
201
+ intl
202
+ .get('id2', { slot0: f(intl.get('id3').d('中文')) })
203
+ .d('aa{slot0}中文')
204
+ ),
205
+ })
206
+ .d('{slot0}中文');
207
+ */
208
+ const keys = Object.keys(obj);
209
+ const datas = dealArgs(Object.values(obj).map(item => item.value || item));
210
+
211
+ return t.ObjectExpression(
212
+ keys.map((k, i) => {
213
+ const node = datas[i];
214
+ return t.ObjectProperty(
71
215
  t.Identifier(k),
72
- obj[k].isAstNode ? obj[k].value : t.Identifier(obj[k])
216
+ obj[k].isAstNode ? node : t.Identifier(obj[k])
73
217
  )
74
- );
75
- });
76
- return t.ObjectExpression(ObjectPropertyArr);
218
+ })
219
+ );
77
220
  }
78
221
 
79
222
  // 更新allWords
80
- function updateWordsInfo(zh) {
81
- if (allWords.includes(zh)) return;
82
- allWords.push(zh);
223
+ function updateWordsInfo(word) {
224
+ if (allWords.includes(word)) return;
225
+ allWords.push(word);
83
226
  }
84
227
 
85
228
  // 处理函数的argments
@@ -87,7 +230,7 @@ module.exports = function(
87
230
  if (!Array.isArray(args)) return args;
88
231
  const resArgs = []; // 参数处理完,用resArgs保存
89
232
  args.forEach(node => {
90
- // 字符串变量
233
+ // 字符串变量
91
234
  if (node.type === 'StringLiteral' && isPrimary(node.value)) {
92
235
  resArgs.push(makeReplace(node.value));
93
236
  }
@@ -141,6 +284,11 @@ module.exports = function(
141
284
  // 将中文词条替换为国际化api:init.t(id/key)
142
285
  const id = replaceWords[value];
143
286
 
287
+ // 特殊文件处理
288
+ if (special) {
289
+ const str = `${i18nObject ? `${i18nObject}.` : ''}${i18nMethod}("${id}").${i18nDefaultFunctionKey}("${value}")`;
290
+ return hackValue(str, str,);
291
+ }
144
292
  // 生成新的ast节点
145
293
  const v = hackValue(value, id);
146
294
  const objExp = makeObjectExpression(variables);
@@ -157,7 +305,7 @@ module.exports = function(
157
305
  : t.identifier(i18nMethod),
158
306
  objExp ? [v, objExp] : [v]
159
307
  )
160
- , t.identifier('d')
308
+ , t.identifier(i18nDefaultFunctionKey)
161
309
  ),
162
310
  [defaultV]
163
311
  );
@@ -230,67 +378,119 @@ module.exports = function(
230
378
  }
231
379
  return { needReplace, value };
232
380
  }
381
+
233
382
  // 函数表达式
234
383
  function dealCallExpressionNode(node) {
235
384
  const res = {
236
385
  needReplace: false,
237
386
  value: null,
238
387
  skip: false,
388
+ remove: false,
239
389
  }
240
390
  if (shouldIgnore(node)) {
241
391
  res.skip = true;
242
392
  return res;
243
393
  }
244
394
 
395
+ const fnName = getFnName(node.callee);
396
+
245
397
  // 处理 ignoreMethods
246
- if (node.callee.type === 'MemberExpression') {
247
- let parentNode = '';
248
- if (node.callee.object.name) {
249
- parentNode = node.callee.object.name;
250
- } else if (node.callee.object.property) {
251
- // 处理忽略类似 a.b.c 方法情况
252
- parentNode = node.callee.object.property.name;
253
- }
398
+ if ([
399
+ ...(ignoreMethods || []),
400
+ `${i18nObject}.${i18nMethod}`, // intl.get
401
+ `${i18nObject}.${i18nMethod}().${i18nDefaultFunctionKey}`, // intl.get().d
402
+ ].includes(fnName)) {
403
+ res.skip = true;
404
+ return res;
405
+ }
254
406
 
255
- const callExpression = `${parentNode}.${node.callee.property.name}`;
407
+ // 需要删除的函数
408
+ if (removeFunctionName.includes(fnName)) {
409
+ // 文件需要修改
410
+ returns.hasTouch = true;
411
+ res.remove = true;
412
+ return res;
413
+ }
414
+
415
+ // 已经做过国际化的项目,替换国际化函数
416
+ if (fnName === intlFunctionName) {
417
+ res.skip = true;
256
418
 
257
- if (ignoreMethods.includes(callExpression)) {
258
- res.skip = true;
259
- return res;
419
+ const argsAst = node.arguments;
420
+ // 把ast转为js, 再用FormPath.getIn来取最终的值
421
+ const { code: argsCode } = generate(t.arrayExpression(argsAst));
422
+ let chaoswiseIntlId
423
+ var chaoswiseIntlIdData
424
+ try {
425
+ eval('var chaoswiseIntlIdData =' + argsCode);
426
+ chaoswiseIntlId = FormPath.getIn(chaoswiseIntlIdData, getFullPath(intlFunctionIdPath));
427
+ } catch (e) {
428
+ // var a = 'id'; f(a) 这样会出错
429
+ // TODO
260
430
  }
261
- }
262
431
 
263
- if (node.callee.type === 'Identifier') {
264
- if (ignoreMethods.includes(node.callee.name)) {
265
- res.skip = true;
266
- return res;
432
+ let value = getWord(chaoswiseIntlId);
433
+ if (!value) {
434
+ let defautValue = FormPath.getIn(chaoswiseIntlIdData, getFullPath(intlFunctionDefaultWordPath));
435
+ if (typeof defautValue === 'string') {
436
+ value = defautValue;
437
+ } else {
438
+ if (intlFunctionIdPath && !chaoswiseIntlId) {
439
+ properLog.warn(`intlFunctionIdPath配置有误,在文件${filePath}中函数为${intlFunctionName},按照intlFunctionIdPath(${intlFunctionIdPath})未找到对应的数据`)
440
+ }
441
+ if (localWordPath && chaoswiseIntlId && !value) {
442
+ properLog.warn(`在${localWordPath}中未找到id为${chaoswiseIntlId}的数据`)
443
+ }
444
+ if (intlFunctionDefaultWordPath) {
445
+ properLog.warn(`intlFunctionDefaultWordPath配置有误,在文件${filePath}中函数为${intlFunctionName},按照intlFunctionDefaultWordPath(${intlFunctionDefaultWordPath})未找到对应的数据`)
446
+ }
447
+ insertFailCommon(node);
448
+ }
449
+ }
450
+
451
+ if (value && isPrimary(value)) {
452
+ res.needReplace = true;
453
+ res.value = value;
267
454
  }
268
455
  }
269
456
 
270
457
  return res;
271
458
  }
272
459
 
460
+ // TODO
461
+ /*
462
+ this.props.intl = lang === 'zh'? '中文': 'en'
463
+ intl.get(id).d('中文')
464
+ this.props.intl.lang = 'zh' ==> intl.lang = 'zh'
465
+ replaceName: { ['this.props.intl.lang']: () => 'intl.lang' }
466
+ 或者replaceName: { ['this.props.intl.lang']: 'intl.lang' }
467
+ */
273
468
  return {
274
469
  // 导入声明,比如:import foo from "mod";
275
470
  ImportDeclaration(path) {
276
471
  // 是否已经导入过 intl 的依赖
277
472
  const m = importCode.match(/from ["'](.*)["']/);
278
473
  const pkgName = m ? m[1] : '';
279
- if (path.node.source.value === pkgName) {
474
+ const name = path.node.source.value;
475
+ if (name === pkgName) {
280
476
  returns.hasImport = true;
281
477
  }
478
+ if (removePkgName.includes(name)) {
479
+ path.remove();
480
+ returns.hasTouch = true;
481
+ }
282
482
  path.skip();
283
483
  },
284
484
 
285
- // 字符串模版,比如:`我的${bar}${fun('哈哈')}中文` intl.t(我的{bar}{slot1}中文,{bar:fdsafdsdf,slot1:fn()})
485
+ // 字符串模版,比如:`我的${bar}${fun('哈哈')}中文` intl.get(我的{bar}{slot1}中文,{bar:fdsafdsdf,slot1:fn()})
286
486
  TemplateLiteral(path) {
287
487
  const { node } = path;
288
488
 
289
489
  const { needReplace, value, variable, } = dealTemplateLiteralNode(node);
290
490
  if (needReplace) {
291
491
  path.replaceWith(makeReplace(value, Object.keys(variable).length ? variable : null));
492
+ path.skip();
292
493
  }
293
- path.skip();
294
494
  },
295
495
 
296
496
  // 字符串变量
@@ -307,7 +507,18 @@ module.exports = function(
307
507
  path.replaceWith(res);
308
508
  path.skip();
309
509
  } else if (isPrimary(value)) {
310
- log.warn('ignore 1!!!!!!', value);
510
+ properLog.warn('ignore 1!!!!!!', value);
511
+ }
512
+ },
513
+
514
+ // 修饰器
515
+ Decorator(path) {
516
+ const { node } = path;
517
+ const fnName = getFnName(node.expression);
518
+ if (removeFunctionName.includes(fnName)) {
519
+ returns.hasTouch = true;
520
+ path.remove();
521
+ path.skip();
311
522
  }
312
523
  },
313
524
 
@@ -315,13 +526,83 @@ module.exports = function(
315
526
  CallExpression(path) {
316
527
  const { node } = path;
317
528
 
318
- const { needReplace, value, skip } = dealCallExpressionNode(node);
529
+ const { needReplace, value, skip, remove } = dealCallExpressionNode(node);
530
+
531
+ if (remove) {
532
+ if (node.arguments.length === 1) {
533
+ // 去掉外侧的包裹组件
534
+ path.replaceWith(node.arguments[0]);
535
+ } else {
536
+ // f( 1, 2)
537
+ // 1, 2
538
+ // 移除函数有问题,这里不处理
539
+ }
540
+
541
+ return;
542
+ }
543
+
319
544
  if (needReplace) {
320
545
  path.replaceWith(makeReplace(value));
321
546
  }
322
547
  if (skip) path.skip();
323
548
  },
324
549
 
550
+ // jsx标签
551
+ JSXElement(path) {
552
+ const { node } = path;
553
+ const { openingElement } = node;
554
+ const jsxName = openingElement.name.name;
555
+
556
+ // 不解析jsx中的style标签
557
+ if (ignoreComponents.includes(jsxName)) {
558
+ path.skip();
559
+ return;
560
+ }
561
+
562
+ // 如果是配置的国际化jsx标签
563
+ if (!shouldIgnore(node) && intlTag && jsxName === intlTag) {
564
+
565
+ let id = getJsxTagChaoswiseIntlValue(openingElement, intlTagIdPath || 'id');
566
+
567
+ let word = getWord(id);
568
+
569
+ if (!word) {
570
+ // 根据id找不到中文,就找jsx标签的默认中文
571
+ let defaultValue = getJsxTagChaoswiseIntlValue(openingElement, intlTagDefaultWordPath);
572
+ if (typeof defaultValue === 'string') {
573
+ word = defaultValue;
574
+ } else {
575
+ if (!intlTagIdPath) {
576
+ properLog.warn(`intlTagIdPath未配置`);
577
+ } else {
578
+ if (!id) {
579
+ properLog.warn(`intlTagIdPath配置有误,文件${filePath}`);
580
+ } else {
581
+ // word找不到
582
+ localWordPath && properLog.warn(`在${localWordPath}中未找到id为${id}的中文`);
583
+ }
584
+ }
585
+ if (defaultValue === false) {
586
+ properLog.warn(`intlTagDefaultWordPath配置有误,文件${filePath}`);
587
+ }
588
+ insertFailCommon(node);
589
+ }
590
+ }
591
+ if (word && isPrimary(word)) {
592
+ // 替换国际化标签,<FormatMessage id='zhongwenid' defaultMessage="中文" /> 换成 {intl.get(id).d('中文')}
593
+ const parentNodeType = path.parentPath?.node?.type;
594
+ let res = makeReplace(word);
595
+
596
+ // 如果节点的父节点是JSXFragment或者JSXElement,那么就包一层JSXExpressionContainer
597
+ if (['JSXFragment', 'JSXElement'].includes(parentNodeType)) {
598
+ res = t.JSXExpressionContainer(res);
599
+ }
600
+ path.replaceWith(res);
601
+ }
602
+ path.skip();
603
+ }
604
+ },
605
+
325
606
  // JSX文本节点
326
607
  JSXText(path) {
327
608
  const { node } = path;
@@ -1,12 +1,16 @@
1
1
  const t = require('@babel/types');
2
+ const specialMatch = require('./specialMatch');
2
3
 
3
4
  module.exports = function f(
4
5
  {
5
6
  i18nObject,
6
7
  i18nMethod,
7
8
  importCode,
9
+ ignoreLines,
10
+ i18nDefaultFunctionKey,
8
11
  },
9
- returns
12
+ returns,
13
+ { special },
10
14
  ) {
11
15
  const { replaceWords, downloadIds } = returns;
12
16
 
@@ -33,6 +37,14 @@ module.exports = function f(
33
37
  return str.trim().replace(/\s+/g, ' ');
34
38
  }
35
39
 
40
+ // 是否忽略
41
+ function shouldIgnore(node) {
42
+ if (node?.loc) {
43
+ return ignoreLines.includes(node.loc.start.line);
44
+ }
45
+ return false;
46
+ }
47
+
36
48
  // 处理模板字符串中的变量
37
49
  function makeObjectExpression(obj) {
38
50
  if (Object.prototype.toString.call(obj) !== '[object Object]') return null;
@@ -118,6 +130,31 @@ module.exports = function f(
118
130
  }
119
131
 
120
132
  return {
133
+ // 字符串变量 src/config/router.config.js中的id进行替换
134
+ StringLiteral(path) {
135
+ const { node } = path;
136
+ let { value } = node;
137
+
138
+ if (!shouldIgnore(node) && special) {
139
+ // intl.get('id').d(中文) 获取id和中文
140
+ const { id, defaultValue } = specialMatch(value) || {};
141
+ if (!id) return;
142
+
143
+ const finalyId = replaceWords[id];
144
+ if (finalyId) {
145
+ returns.hasTouch = true;
146
+ downloadIds.push(finalyId);
147
+
148
+ const str = `${i18nObject ? `${i18nObject}.` : ''}${i18nMethod}("${finalyId}").${i18nDefaultFunctionKey}("${defaultValue}")`;
149
+ const newStr = hackValue(str, str,);
150
+ path.replaceWith(newStr);
151
+ path.skip();
152
+ } else {
153
+ downloadIds.push(id);
154
+ }
155
+ }
156
+ },
157
+
121
158
  // 导入声明,比如:import foo from "mod";
122
159
  ImportDeclaration(path) {
123
160
  // 是否已经导入过 intl 的依赖
@@ -0,0 +1,14 @@
1
+ const getConf = require('../conf');
2
+
3
+ let reg;
4
+
5
+ module.exports = function (str) {
6
+ if (!reg) {
7
+ const conf = getConf();
8
+ const { i18nObject, i18nMethod, i18nDefaultFunctionKey } = conf;
9
+ reg = new RegExp(`${i18nObject? `${i18nObject}\\.`: ''}${i18nMethod}\\([\\'\\"]([^\\)]*)[\\'\\"]\\)\\.${i18nDefaultFunctionKey}\\([\\'\\"]([^\\)]*)[\\'\\"]\\)`);
10
+ }
11
+ const res = str.match(reg);
12
+ if (!res) return;
13
+ return { id: res[1], defaultValue: res[2] };
14
+ }
@@ -13,10 +13,12 @@ const pluginSyntaxAsyncGenerators = require('@babel/plugin-syntax-async-generato
13
13
  const pluginSyntaxDoExpressions = require('@babel/plugin-syntax-do-expressions');
14
14
  const pluginSyntaxDynamicImport = require('@babel/plugin-syntax-dynamic-import');
15
15
  const pluginSyntaxFunctionBind = require('@babel/plugin-syntax-function-bind');
16
+ const pluginExportDefaultFrom = require('@babel/plugin-proposal-export-default-from');
16
17
  const presetTypescript = require('@babel/preset-typescript').default;
17
18
 
18
19
  const makeVisitorCollect = require('./makeVisitorCollect');
19
20
  const makeVisitorUpdate = require('./makeVisitorUpdate');
21
+ const log = require('./log');
20
22
 
21
23
  // 获取文件中需要忽略转化通用国际化API规范的所有行号
22
24
  function getIgnoreLines(ast) {
@@ -69,16 +71,13 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
69
71
  const {
70
72
  entry,
71
73
  output,
72
- primaryRegx,
73
- importCode,
74
- i18nObject,
75
- i18nMethod,
76
74
  babelPresets = [],
77
75
  babelPlugins = [],
78
- ignoreComponents,
79
- ignoreMethods,
80
- ignoreAttributes,
76
+ specialFileReg,
77
+
78
+ ...opts
81
79
  } = conf;
80
+ const { importCode } = opts;
82
81
 
83
82
  const allWords = []; // 收集到的所有词条
84
83
  const downloadIds = []; // 需要从平台下载的词条id
@@ -105,24 +104,18 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
105
104
  pluginSyntaxDoExpressions,
106
105
  pluginSyntaxDynamicImport,
107
106
  pluginSyntaxFunctionBind,
107
+ pluginExportDefaultFrom,
108
108
  ...babelPlugins,
109
109
  ],
110
110
  };
111
111
 
112
- const opts = {
113
- primaryRegx,
114
- i18nObject,
115
- i18nMethod,
116
- importCode,
117
- ignoreLines: [],
118
- ignoreMethods,
119
- ignoreComponents,
120
- ignoreAttributes,
121
- };
122
-
123
112
  // 处理所有文件
124
113
  files.forEach((file) => {
125
114
  const { filePath } = file;
115
+
116
+ if (specialFileReg.some(reg => reg.test(filePath))) {
117
+ file.special = true;
118
+ }
126
119
  const isTSX = ['.ts', '.tsx'].includes(path.extname(filePath));
127
120
 
128
121
  const r = {
@@ -133,7 +126,16 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
133
126
  hasTouch: false, // 是否存在需要替换的词条
134
127
  };
135
128
  const sourceCode = fs.readFileSync(filePath, 'utf8');
136
- const ast = babel.parseSync(sourceCode, transformOptions);
129
+
130
+ let ast;
131
+ try {
132
+ ast = babel.parseSync(sourceCode, transformOptions);
133
+ } catch (error) {
134
+ replaceWords && log.error(`文件解析出错:${file.filePath}
135
+ ${e}`)
136
+ return;
137
+ }
138
+
137
139
  opts.ignoreLines = getIgnoreLines(ast);
138
140
 
139
141
  let makeVisitor = makeVisitorCollect;
@@ -141,8 +143,14 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
141
143
  makeVisitor = makeVisitorUpdate
142
144
  }
143
145
 
144
- const visitor = makeVisitor(opts, r);
145
- traverse(ast, visitor);
146
+ try {
147
+ const visitor = makeVisitor(opts, r, file);
148
+ traverse(ast, visitor);
149
+ } catch (e) {
150
+ replaceWords && log.error(`文件解析出错:${file.filePath}
151
+ ${e}`)
152
+ return;
153
+ }
146
154
 
147
155
  // 不传入需要替换的文词条则不进行文件修改
148
156
  // 在只需要提取中文词条的情况下不传入replaceWords
@@ -156,7 +164,7 @@ module.exports = function (type, files = [], conf = {}, replaceWords) {
156
164
 
157
165
  if (!r.hasTouch) {
158
166
  code = sourceCode;
159
- } else if (!r.hasImport && importCode) {
167
+ } else if (!r.hasImport && !file.special && importCode && Object.keys(replaceWords).length) {
160
168
  code = `${importCode}\n${code}`;
161
169
  }
162
170
 
package/lib/index.js CHANGED
@@ -25,4 +25,5 @@ _Object$defineProperty(exports, "useIntl", {
25
25
  });
26
26
  var _useIntl = _interopRequireDefault(require("./useIntl"));
27
27
  var _context = _interopRequireDefault(require("./useIntl/context"));
28
- var _reactIntlUniversal = _interopRequireDefault(require("react-intl-universal"));
28
+ var _reactIntlUniversal = _interopRequireDefault(require("react-intl-universal"));
29
+ _reactIntlUniversal["default"].options.escapeHtml = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaoswise/intl",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "author": "cloudwiser",
5
5
  "description": "intl",
6
6
  "main": "lib/index.js",
@@ -67,13 +67,13 @@
67
67
  "babel-plugin-macros": "^3.1.0",
68
68
  "babel-plugin-polyfill-corejs3": "^0.5.2",
69
69
  "chalk": "^2.4.1",
70
+ "glob": "^8.0.3",
70
71
  "gulp": "^4.0.2",
71
72
  "gulp-babel": "^8.0.0",
72
- "gulp-typescript": "^5.0.1",
73
73
  "gulp-connect": "^5.7.0",
74
74
  "gulp-jsdoc3": "^3.0.0",
75
+ "gulp-typescript": "^5.0.1",
75
76
  "prettier": "^2.8.1",
76
- "glob": "^8.0.3",
77
77
  "react-intl-universal": "^2.6.11",
78
78
  "uuid": "^9.0.0"
79
79
  },
@@ -81,5 +81,6 @@
81
81
  "react": "^16.13.1",
82
82
  "react-dom": "^16.13.1"
83
83
  },
84
- "license": "MIT"
84
+ "license": "MIT",
85
+ "gitHead": "6ed1475f01a4130625bbed5cf0a4daadde68d43e"
85
86
  }