@chaoswise/intl 1.0.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.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/bin/chaoswise-intl.js +22 -0
- package/bin/scripts/collect.js +67 -0
- package/bin/scripts/conf/default.js +64 -0
- package/bin/scripts/conf/index.js +42 -0
- package/bin/scripts/service/index.js +29 -0
- package/bin/scripts/update.js +32 -0
- package/bin/scripts/util/downloadJson.js +9 -0
- package/bin/scripts/util/file.js +68 -0
- package/bin/scripts/util/folder.js +34 -0
- package/bin/scripts/util/getTargetFiles.js +25 -0
- package/bin/scripts/util/log.js +8 -0
- package/bin/scripts/util/makeVisitorCollect.js +347 -0
- package/bin/scripts/util/makeVisitorUpdate.js +149 -0
- package/bin/scripts/util/transformAst.js +179 -0
- package/lib/index.js +28 -0
- package/lib/useIntl/context.js +36 -0
- package/lib/useIntl/index.js +48 -0
- package/lib/useIntl/use-force-update.js +21 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 DOCP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const runCollect = require('./scripts/collect');
|
|
3
|
+
const runUpdate = require('./scripts/update');
|
|
4
|
+
|
|
5
|
+
// 可执行的指令
|
|
6
|
+
const SCRIPTS = ['collect', 'update'];
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
|
|
10
|
+
const scriptIndex = args.findIndex(arg => {
|
|
11
|
+
const script = SCRIPTS.filter(script => arg === script)[0];
|
|
12
|
+
return arg === script;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
|
|
16
|
+
|
|
17
|
+
if(script === 'collect') {
|
|
18
|
+
runCollect();
|
|
19
|
+
}
|
|
20
|
+
if(script === 'update') {
|
|
21
|
+
runUpdate();
|
|
22
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const { v4: uuidv4 } = require('uuid');
|
|
2
|
+
|
|
3
|
+
const getConf = require('./conf');
|
|
4
|
+
const getTargetFiles = require('./util/getTargetFiles');
|
|
5
|
+
const transformAst = require('./util/transformAst');
|
|
6
|
+
const log = require('./util/log');
|
|
7
|
+
const file = require('./util/file');
|
|
8
|
+
|
|
9
|
+
const service = require('./service');
|
|
10
|
+
|
|
11
|
+
async function init() {
|
|
12
|
+
const conf = getConf();
|
|
13
|
+
const files = getTargetFiles(conf);
|
|
14
|
+
|
|
15
|
+
// 遍历文件,并对代码进行ast处理的方法:transformAst(type, files, conf, replaceWords)
|
|
16
|
+
// 第一次遍历,只收集中文,不传入replaceWords
|
|
17
|
+
const info = transformAst('Collect', files, conf);
|
|
18
|
+
|
|
19
|
+
// 在国际化平台中获取所有已存在中文信息
|
|
20
|
+
const res = await service.searchByZh(info.allWords);
|
|
21
|
+
const wordsMap = res.data;
|
|
22
|
+
|
|
23
|
+
const replaceWords = {}; // { zh: id } 需要在代码中替换的词条id
|
|
24
|
+
const newWords = {}; // { zh1: '待翻译', zh2: '请选择:中文1/中文2' }
|
|
25
|
+
Object.entries(wordsMap).forEach(([zh, value]) => {
|
|
26
|
+
// 如果当前中文词条在平台中存在,且对应英文翻译有且只有一条,那么直接替换代码中的中文为当前id,否则生成临时key,让开发者手动确认
|
|
27
|
+
if (value.length === 1) {
|
|
28
|
+
replaceWords[zh] = value[0].key;
|
|
29
|
+
} else {
|
|
30
|
+
const id = uuidv4();
|
|
31
|
+
replaceWords[zh] = id;
|
|
32
|
+
newWords[id] = {
|
|
33
|
+
zh,
|
|
34
|
+
en:
|
|
35
|
+
value.length === 0
|
|
36
|
+
? '待翻译'
|
|
37
|
+
: `该词条在平台中对应多个英文翻译,请手动确认:${value
|
|
38
|
+
.map((item) => item.en)
|
|
39
|
+
.join('/')}`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 第二次遍历,传入replaceWords,对代码进行国际化通用API转化,把词条替换成数据库的id,或者脚本临时生成的uuid
|
|
45
|
+
transformAst('collect', files, conf, replaceWords);
|
|
46
|
+
|
|
47
|
+
log.success('脚本执行成功');
|
|
48
|
+
log.success('自定义配置文件在项目根目录的.intlconfig.js中');
|
|
49
|
+
// 本次扫描出新的待翻译词条,写入到newWords中
|
|
50
|
+
if (Object.keys(newWords).length) {
|
|
51
|
+
const newWordsJsonPath = 'newWords.json';
|
|
52
|
+
|
|
53
|
+
let oldNewWords = file.readJson(newWordsJsonPath) || {};
|
|
54
|
+
file.write(
|
|
55
|
+
newWordsJsonPath,
|
|
56
|
+
JSON.stringify({ ...oldNewWords, ...newWords }, null, 2)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// 先注释掉,开始做指标时,会用到
|
|
60
|
+
// const package = file.read('package.json');
|
|
61
|
+
// file.write(newWordsJsonPath, JSON.stringify({ ...oldNewWords, ...newWords, projectName: package.name }, null, 2));
|
|
62
|
+
log.success(`需要翻译的文件在项目根目录的newWords.json中`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// init();
|
|
67
|
+
module.exports = init;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// default options
|
|
2
|
+
module.exports = function (excludes = []) {
|
|
3
|
+
var config = {
|
|
4
|
+
// source path, <string array>
|
|
5
|
+
// e.g. ['src']
|
|
6
|
+
entry: ['src'],
|
|
7
|
+
|
|
8
|
+
// exclude pattern, <string array>
|
|
9
|
+
// e.g. ['**/dist/**', '**/*.config.js', '**/*.data.js']
|
|
10
|
+
exclude: [
|
|
11
|
+
'**/router.config.js',
|
|
12
|
+
'**/src/public/**',
|
|
13
|
+
'**/_MOCK_/**'
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
// output path, <string array>
|
|
17
|
+
// e.g. ['dist']
|
|
18
|
+
output: ['src'], // default i18n in place
|
|
19
|
+
|
|
20
|
+
// ignored components, <string array>
|
|
21
|
+
// e.g. ['EventTracker']
|
|
22
|
+
ignoreComponents: [],
|
|
23
|
+
|
|
24
|
+
// ignored methods, <string array>
|
|
25
|
+
// e.g. ['MirrorTrack']
|
|
26
|
+
ignoreMethods: [],
|
|
27
|
+
|
|
28
|
+
ignoreAttributes: ['style', 'className'],
|
|
29
|
+
|
|
30
|
+
primaryRegx: /[\u4e00-\u9fa5]/,
|
|
31
|
+
|
|
32
|
+
// import codes, <string>
|
|
33
|
+
// e.g. "import { intl } from 'di18n-react';"
|
|
34
|
+
importCode: "import { intl } from '@chaoswise/intl';",
|
|
35
|
+
|
|
36
|
+
// i18n object, <string>
|
|
37
|
+
// e.g. 'intl'
|
|
38
|
+
i18nObject: 'intl',
|
|
39
|
+
|
|
40
|
+
// i18n method, <string>
|
|
41
|
+
// e.g. 't'
|
|
42
|
+
i18nMethod: 'get',
|
|
43
|
+
|
|
44
|
+
// babel配置项
|
|
45
|
+
babelPresets: [],
|
|
46
|
+
babelPlugins: [],
|
|
47
|
+
|
|
48
|
+
// prettier conf, <null | object>
|
|
49
|
+
// e.g. {}
|
|
50
|
+
prettier: {
|
|
51
|
+
singleQuote: true,
|
|
52
|
+
jsxSingleQuote: true,
|
|
53
|
+
trailingComma: 'es5',
|
|
54
|
+
endOfLine: 'lf',
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// 国际化平台地址
|
|
58
|
+
baseURL: 'http://10.0.1.133:18000',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
excludes.forEach((key) => delete config[key]);
|
|
62
|
+
|
|
63
|
+
return config;
|
|
64
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
}
|
|
23
|
+
|
|
24
|
+
// 内置一些,不需要被扫描的函数
|
|
25
|
+
const ignoreMethods = [
|
|
26
|
+
];
|
|
27
|
+
Object.keys(console).forEach(key => ignoreMethods.push(`console.${key}`))
|
|
28
|
+
|
|
29
|
+
module.exports = function () {
|
|
30
|
+
// 获取用户自定义配置文件
|
|
31
|
+
const customConfig = file.readJs('.intlconfig.js');
|
|
32
|
+
|
|
33
|
+
if (!customConfig) {
|
|
34
|
+
createCustomConfig();
|
|
35
|
+
}
|
|
36
|
+
// 合并配置文件
|
|
37
|
+
const finalyConf = { ...getDefaultConfig(), ...customConfig };
|
|
38
|
+
|
|
39
|
+
finalyConf.ignoreMethods.push(...ignoreMethods);
|
|
40
|
+
|
|
41
|
+
return finalyConf;
|
|
42
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const getConf = require('../conf');
|
|
3
|
+
|
|
4
|
+
const conf = getConf();
|
|
5
|
+
|
|
6
|
+
axios.defaults.baseURL = (conf.baseURL || '') + '/api/i18n/dictionary';
|
|
7
|
+
|
|
8
|
+
axios.interceptors.response.use(
|
|
9
|
+
function (response) {
|
|
10
|
+
// Any status code that lie within the range of 2xx cause this function to trigger
|
|
11
|
+
// Do something with response data
|
|
12
|
+
return response.data;
|
|
13
|
+
},
|
|
14
|
+
function (error) {
|
|
15
|
+
// Any status codes that falls outside the range of 2xx cause this function to trigger
|
|
16
|
+
// Do something with response error
|
|
17
|
+
return Promise.reject(error);
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// 在国际化平台中获取所有已存在中文信息
|
|
22
|
+
exports.searchByZh = (zhs) => {
|
|
23
|
+
return axios.post('/searchByZh', zhs);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// 根据词条id获取所有数据库词条信息
|
|
27
|
+
exports.getJson = (ids) => {
|
|
28
|
+
return axios.post('/getJson', ids);
|
|
29
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const getConf = require('./conf');
|
|
2
|
+
const getTargetFiles = require('./util/getTargetFiles');
|
|
3
|
+
const transformAst = require('./util/transformAst');
|
|
4
|
+
const downloadJson = require('./util/downloadJson');
|
|
5
|
+
const log = require('./util/log');
|
|
6
|
+
const file = require('./util/file');
|
|
7
|
+
|
|
8
|
+
async function replace() {
|
|
9
|
+
const conf = getConf();
|
|
10
|
+
const files = getTargetFiles(conf);
|
|
11
|
+
|
|
12
|
+
// relationKey { key: id } key是脚本生成的临时id,id是数据库对应词条的id
|
|
13
|
+
const relationKey = file.readJson('relationKey.json') || {};
|
|
14
|
+
|
|
15
|
+
const info = transformAst('update', files, conf, relationKey);
|
|
16
|
+
downloadJson(info.downloadIds);
|
|
17
|
+
|
|
18
|
+
const needDelete = Boolean(Object.keys(relationKey).length);
|
|
19
|
+
if (needDelete) {
|
|
20
|
+
file.delete('newWords.json');
|
|
21
|
+
file.delete('relationKey.json');
|
|
22
|
+
|
|
23
|
+
log.success('newWords.json和relationKey.json 已删除');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
log.success('脚本执行成功2222');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// replace();
|
|
30
|
+
module.exports = replace;
|
|
31
|
+
|
|
32
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const service = require('../service');
|
|
2
|
+
const file = require('./file');
|
|
3
|
+
|
|
4
|
+
module.exports = async function downloadJson(downloadIds) {
|
|
5
|
+
const res = await service.getJson(downloadIds);
|
|
6
|
+
const { zh, en } = res.data;
|
|
7
|
+
file.write('locales/zh-CN.json', JSON.stringify(zh, null, 2));
|
|
8
|
+
file.write('locales/en-US.json', JSON.stringify(en, null, 2));
|
|
9
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const log = require('./log');
|
|
4
|
+
const folder = require('./folder');
|
|
5
|
+
|
|
6
|
+
// 项目根目录
|
|
7
|
+
const rootPath = process.cwd();
|
|
8
|
+
// 获取指定文件全路径
|
|
9
|
+
const getPath = (relativePath) => path.resolve(rootPath, relativePath);
|
|
10
|
+
|
|
11
|
+
const baseRead = (relativePath) => {
|
|
12
|
+
const fullPath = getPath(relativePath);
|
|
13
|
+
let data = null;
|
|
14
|
+
try {
|
|
15
|
+
data = fs.readFileSync(fullPath, { encoding: 'utf-8' });
|
|
16
|
+
} catch (error) {}
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
// 读取指定文件
|
|
22
|
+
read: baseRead,
|
|
23
|
+
// 读取json文件
|
|
24
|
+
readJson(relativePath) {
|
|
25
|
+
let data = baseRead(relativePath);
|
|
26
|
+
try {
|
|
27
|
+
data = JSON.parse(data);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
log.error(error);
|
|
30
|
+
data = null;
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
},
|
|
34
|
+
// 读取js module 文件
|
|
35
|
+
readJs(relativePath) {
|
|
36
|
+
const fullPath = getPath(relativePath);
|
|
37
|
+
try {
|
|
38
|
+
return require(fullPath);
|
|
39
|
+
} catch (error) {}
|
|
40
|
+
},
|
|
41
|
+
// 对指定文件写入信息
|
|
42
|
+
write(relativePath, data) {
|
|
43
|
+
const fullPath = getPath(relativePath);
|
|
44
|
+
|
|
45
|
+
let res = null;
|
|
46
|
+
try {
|
|
47
|
+
const index = fullPath.lastIndexOf('/');
|
|
48
|
+
const folderPath = fullPath.slice(0, index);
|
|
49
|
+
// 构建文件所需目录
|
|
50
|
+
folder.insure(folderPath);
|
|
51
|
+
|
|
52
|
+
res = fs.writeFileSync(fullPath, data, { encoding: 'utf-8' });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
log.error(error);
|
|
55
|
+
}
|
|
56
|
+
return res;
|
|
57
|
+
},
|
|
58
|
+
// 删除文件
|
|
59
|
+
delete(relativePath) {
|
|
60
|
+
const fullPath = getPath(relativePath);
|
|
61
|
+
try {
|
|
62
|
+
fs.unlinkSync(fullPath);
|
|
63
|
+
return true;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const log = require('./log');
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
根据文件夹路径构建文件夹目录,比如:a/b/c/d
|
|
7
|
+
按文件夹层级依次判断该”文件夹名称“是否存在:
|
|
8
|
+
1、如果不存在则创建新的目录
|
|
9
|
+
2、如果存在并且不是”文件夹类型“,则程序退出,给出相应错误提示(同一路径下不能同名)
|
|
10
|
+
*/
|
|
11
|
+
function insure(folderPath) {
|
|
12
|
+
const folderPathArr = folderPath.split(path.sep).filter(Boolean);
|
|
13
|
+
let currentPath = '/';
|
|
14
|
+
|
|
15
|
+
do {
|
|
16
|
+
currentPath = path.resolve(currentPath, folderPathArr.shift());
|
|
17
|
+
|
|
18
|
+
let has = fs.existsSync(currentPath);
|
|
19
|
+
let isFolder = false;
|
|
20
|
+
if (!has) {
|
|
21
|
+
fs.mkdirSync(currentPath);
|
|
22
|
+
isFolder = true;
|
|
23
|
+
} else {
|
|
24
|
+
const stat = fs.statSync(currentPath);
|
|
25
|
+
isFolder = stat.isDirectory();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!isFolder) {
|
|
29
|
+
log.error(currentPath + '只能是文件夹');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
} while (folderPathArr.length);
|
|
33
|
+
}
|
|
34
|
+
module.exports = { insure };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const glob = require('glob');
|
|
3
|
+
|
|
4
|
+
function getSourceFiles({ entry, exclude }) {
|
|
5
|
+
return glob.sync(`${entry}/**/*.{js,ts,tsx,jsx}`, {
|
|
6
|
+
ignore: exclude || [],
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 根据入口entry,获取所有需要扫描的文件
|
|
11
|
+
module.exports = function ({ entry, output, exclude }) {
|
|
12
|
+
const outputs = output ? [].concat(output) : [];
|
|
13
|
+
|
|
14
|
+
const targetFiles = [].concat(entry).reduce((prev, cur, index) => {
|
|
15
|
+
const files = getSourceFiles({ entry: cur, exclude }).map((file) => ({
|
|
16
|
+
filePath: file,
|
|
17
|
+
currentEntry: cur,
|
|
18
|
+
currentOutput: outputs[index],
|
|
19
|
+
ext: path.extname(file),
|
|
20
|
+
}));
|
|
21
|
+
return prev.concat(files);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
return targetFiles;
|
|
25
|
+
};
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
const log = require('./log');
|
|
2
|
+
const t = require('@babel/types');
|
|
3
|
+
|
|
4
|
+
module.exports = function(
|
|
5
|
+
{
|
|
6
|
+
primaryRegx,
|
|
7
|
+
i18nObject,
|
|
8
|
+
i18nMethod,
|
|
9
|
+
importCode,
|
|
10
|
+
ignoreLines,
|
|
11
|
+
ignoreMethods,
|
|
12
|
+
ignoreComponents = [],
|
|
13
|
+
ignoreAttributes,
|
|
14
|
+
},
|
|
15
|
+
returns
|
|
16
|
+
) {
|
|
17
|
+
const { replaceWords, allWords, downloadIds } = returns;
|
|
18
|
+
const hacked = {};
|
|
19
|
+
|
|
20
|
+
// XXX: [TRICKY] 防止中文转码为 unicode
|
|
21
|
+
function hackValue(value, id) {
|
|
22
|
+
if (id) hacked[id] = true;
|
|
23
|
+
|
|
24
|
+
return Object.assign(t.StringLiteral(value), {
|
|
25
|
+
extra: {
|
|
26
|
+
raw: `'${id}'`, // id
|
|
27
|
+
rawValue: value, // zh
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 是否忽略
|
|
33
|
+
function shouldIgnore(node) {
|
|
34
|
+
if (node?.loc) {
|
|
35
|
+
return ignoreLines.includes(node.loc.start.line);
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 是否命中替换的正则
|
|
41
|
+
function isPrimary(str) {
|
|
42
|
+
return primaryRegx.test(str);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
去掉首尾空白字符,中间的连续空白字符替换成一个空格
|
|
47
|
+
例如:
|
|
48
|
+
<div>
|
|
49
|
+
中文
|
|
50
|
+
<div>
|
|
51
|
+
会提取”/n中文/n“作为记录标记,需要用formatWhitespace去掉
|
|
52
|
+
*/
|
|
53
|
+
function formatWhitespace(str) {
|
|
54
|
+
return str.trim().replace(/\s+/g, ' ');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 处理模板字符串中的变量
|
|
58
|
+
function makeObjectExpression(obj) {
|
|
59
|
+
if (Object.prototype.toString.call(obj) !== '[object Object]') return null;
|
|
60
|
+
|
|
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(
|
|
71
|
+
t.Identifier(k),
|
|
72
|
+
obj[k].isAstNode ? obj[k].value : t.Identifier(obj[k])
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
return t.ObjectExpression(ObjectPropertyArr);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 更新allWords
|
|
80
|
+
function updateWordsInfo(zh) {
|
|
81
|
+
if (allWords.includes(zh)) return;
|
|
82
|
+
allWords.push(zh);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 处理函数的argments
|
|
86
|
+
function dealArgs(args) {
|
|
87
|
+
if (!Array.isArray(args)) return args;
|
|
88
|
+
const resArgs = []; // 参数处理完,用resArgs保存
|
|
89
|
+
args.forEach(node => {
|
|
90
|
+
// 字符串变量
|
|
91
|
+
if (node.type === 'StringLiteral' && isPrimary(node.value)) {
|
|
92
|
+
resArgs.push(makeReplace(node.value));
|
|
93
|
+
}
|
|
94
|
+
// 函数或方法调用表达式
|
|
95
|
+
else if (node.type === 'CallExpression') {
|
|
96
|
+
const { needReplace, value, skip } = dealCallExpressionNode(node);
|
|
97
|
+
// 跳过国际化API转化
|
|
98
|
+
if (skip) {
|
|
99
|
+
resArgs.push(node);
|
|
100
|
+
} else {
|
|
101
|
+
// 对当前函数再次进行参数处理,自调用
|
|
102
|
+
node.arguments = dealArgs(node.arguments);
|
|
103
|
+
resArgs.push(node);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// 字符串模版
|
|
107
|
+
else if (node.type === 'TemplateLiteral') {
|
|
108
|
+
const { needReplace, value, variable, } = dealTemplateLiteralNode(node);
|
|
109
|
+
resArgs.push(
|
|
110
|
+
needReplace
|
|
111
|
+
? makeReplace(value, Object.keys(variable).length ? variable : null)
|
|
112
|
+
: node
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
// ObjectExpression和ObjectProperty,是为了处理参数存在对象的情况,比如:{x:'中文'}
|
|
116
|
+
else if (node.type === 'ObjectExpression') { // Object
|
|
117
|
+
node.properties = dealArgs(node.properties);
|
|
118
|
+
resArgs.push(node);
|
|
119
|
+
} else if (node.type === 'ObjectProperty') { // Object的属性
|
|
120
|
+
const res = dealArgs([node.value]);
|
|
121
|
+
node.value = res[0];
|
|
122
|
+
resArgs.push(node);
|
|
123
|
+
} else if (node.type === 'ArrayExpression') { // Array
|
|
124
|
+
node.elements = dealArgs(node.elements);
|
|
125
|
+
resArgs.push(node);
|
|
126
|
+
} else {
|
|
127
|
+
resArgs.push(node);
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
return resArgs;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 替换字符串为我们使用的国际化API
|
|
134
|
+
function makeReplace(value, variables) {
|
|
135
|
+
// 存在文案替换
|
|
136
|
+
returns.hasTouch = true;
|
|
137
|
+
|
|
138
|
+
value = formatWhitespace(value);
|
|
139
|
+
|
|
140
|
+
updateWordsInfo(value);
|
|
141
|
+
// 将中文词条替换为国际化api:init.t(id/key)
|
|
142
|
+
const id = replaceWords[value];
|
|
143
|
+
|
|
144
|
+
// 生成新的ast节点
|
|
145
|
+
const v = hackValue(value, id);
|
|
146
|
+
const objExp = makeObjectExpression(variables);
|
|
147
|
+
const defaultV = hackValue(value, value);
|
|
148
|
+
|
|
149
|
+
return t.callExpression(
|
|
150
|
+
t.memberExpression(
|
|
151
|
+
t.callExpression(
|
|
152
|
+
// MemberExpression 成员表达式。
|
|
153
|
+
// 如果computed为true,则节点对应于一个computed(a[b])成员表达式,并且属性是一个表达式。
|
|
154
|
+
// 如果computed为false,则节点对应于静态(a.b)成员表达式,属性为标识符或PrivateName。
|
|
155
|
+
i18nObject
|
|
156
|
+
? t.memberExpression(t.identifier(i18nObject), t.identifier(i18nMethod))
|
|
157
|
+
: t.identifier(i18nMethod),
|
|
158
|
+
objExp ? [v, objExp] : [v]
|
|
159
|
+
)
|
|
160
|
+
, t.identifier('d')
|
|
161
|
+
),
|
|
162
|
+
[defaultV]
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 字符串模版
|
|
167
|
+
function dealTemplateLiteralNode(node) {
|
|
168
|
+
let value = '';
|
|
169
|
+
let needReplace = false;
|
|
170
|
+
let slotIndex = 0;
|
|
171
|
+
const variable = {}; // 存储模版中的变量
|
|
172
|
+
if (
|
|
173
|
+
!shouldIgnore(node) &&
|
|
174
|
+
node.quasis.some((word) => isPrimary(word.value.cooked))
|
|
175
|
+
) {
|
|
176
|
+
const tempArr = [...node.quasis, ...node.expressions];
|
|
177
|
+
tempArr.sort((a, b) => a.start - b.start);
|
|
178
|
+
|
|
179
|
+
tempArr.forEach(function (nd) {
|
|
180
|
+
if (nd.type === 'TemplateElement') {
|
|
181
|
+
value += nd.value.cooked;
|
|
182
|
+
if (isPrimary(nd.value.cooked)) {
|
|
183
|
+
needReplace = true;
|
|
184
|
+
}
|
|
185
|
+
} else if (nd.type === 'Identifier') {
|
|
186
|
+
value += `{${nd.name}}`;
|
|
187
|
+
variable[nd.name] = nd.name;
|
|
188
|
+
|
|
189
|
+
needReplace = true;
|
|
190
|
+
} else {
|
|
191
|
+
// 例如 CallExpression 等
|
|
192
|
+
const identifier = `slot${slotIndex++}`;
|
|
193
|
+
value += `{${identifier}}`;
|
|
194
|
+
variable[identifier] = { isAstNode: true, value: nd };
|
|
195
|
+
needReplace = true;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
needReplace: needReplace && value.trim() /*处理模板字符串为空的情况,比如:` `*/,
|
|
202
|
+
value,
|
|
203
|
+
variable,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// 字符串变量
|
|
207
|
+
function dealStringLiteralNode(node, path) {
|
|
208
|
+
let needReplace = false;
|
|
209
|
+
let { value } = node;
|
|
210
|
+
if (!shouldIgnore(node) && isPrimary(value)) {
|
|
211
|
+
switch (path?.parent?.type) {
|
|
212
|
+
// 组件属性,比如:<Root prop='中文' /> 中的prop
|
|
213
|
+
case 'JSXAttribute':
|
|
214
|
+
// ignoreComponents 可忽略一些组件
|
|
215
|
+
if (!ignoreComponents.includes(path?.parentPath?.parent?.name?.name)) {
|
|
216
|
+
needReplace = true;
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
|
|
220
|
+
case 'ObjectProperty':
|
|
221
|
+
// 赋值运算符表达式,比如:const c = (a = '中文单词')
|
|
222
|
+
case 'AssignmentExpression':
|
|
223
|
+
case 'CallExpression':
|
|
224
|
+
// 比如:new A('中文词条')
|
|
225
|
+
case 'NewExpression':
|
|
226
|
+
default:
|
|
227
|
+
needReplace = true;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { needReplace, value };
|
|
232
|
+
}
|
|
233
|
+
// 函数表达式
|
|
234
|
+
function dealCallExpressionNode(node) {
|
|
235
|
+
const res = {
|
|
236
|
+
needReplace: false,
|
|
237
|
+
value: null,
|
|
238
|
+
skip: false,
|
|
239
|
+
}
|
|
240
|
+
if (shouldIgnore(node)) {
|
|
241
|
+
res.skip = true;
|
|
242
|
+
return res;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 处理 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
|
+
}
|
|
254
|
+
|
|
255
|
+
const callExpression = `${parentNode}.${node.callee.property.name}`;
|
|
256
|
+
|
|
257
|
+
if (ignoreMethods.includes(callExpression)) {
|
|
258
|
+
res.skip = true;
|
|
259
|
+
return res;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (node.callee.type === 'Identifier') {
|
|
264
|
+
if (ignoreMethods.includes(node.callee.name)) {
|
|
265
|
+
res.skip = true;
|
|
266
|
+
return res;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return res;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
// 导入声明,比如:import foo from "mod";
|
|
275
|
+
ImportDeclaration(path) {
|
|
276
|
+
// 是否已经导入过 intl 的依赖
|
|
277
|
+
const m = importCode.match(/from ["'](.*)["']/);
|
|
278
|
+
const pkgName = m ? m[1] : '';
|
|
279
|
+
if (path.node.source.value === pkgName) {
|
|
280
|
+
returns.hasImport = true;
|
|
281
|
+
}
|
|
282
|
+
path.skip();
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
// 字符串模版,比如:`我的${bar}${fun('哈哈')}中文` intl.t(我的{bar}{slot1}中文,{bar:fdsafdsdf,slot1:fn()})
|
|
286
|
+
TemplateLiteral(path) {
|
|
287
|
+
const { node } = path;
|
|
288
|
+
|
|
289
|
+
const { needReplace, value, variable, } = dealTemplateLiteralNode(node);
|
|
290
|
+
if (needReplace) {
|
|
291
|
+
path.replaceWith(makeReplace(value, Object.keys(variable).length ? variable : null));
|
|
292
|
+
}
|
|
293
|
+
path.skip();
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
// 字符串变量
|
|
297
|
+
StringLiteral(path) {
|
|
298
|
+
const { node } = path;
|
|
299
|
+
let { value } = node;
|
|
300
|
+
|
|
301
|
+
const { needReplace } = dealStringLiteralNode(node, path);
|
|
302
|
+
if (needReplace) {
|
|
303
|
+
let res = makeReplace(value);
|
|
304
|
+
if (path.parent.type === 'JSXAttribute') {
|
|
305
|
+
res = t.JSXExpressionContainer(res);
|
|
306
|
+
}
|
|
307
|
+
path.replaceWith(res);
|
|
308
|
+
path.skip();
|
|
309
|
+
} else if (isPrimary(value)) {
|
|
310
|
+
log.warn('ignore 1!!!!!!', value);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
// 函数或方法调用表达式,比如:a('中午')
|
|
315
|
+
CallExpression(path) {
|
|
316
|
+
const { node } = path;
|
|
317
|
+
|
|
318
|
+
const { needReplace, value, skip } = dealCallExpressionNode(node);
|
|
319
|
+
if (needReplace) {
|
|
320
|
+
path.replaceWith(makeReplace(value));
|
|
321
|
+
}
|
|
322
|
+
if (skip) path.skip();
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
// JSX文本节点
|
|
326
|
+
JSXText(path) {
|
|
327
|
+
const { node } = path;
|
|
328
|
+
|
|
329
|
+
if (!shouldIgnore(node) && isPrimary(node.value)) {
|
|
330
|
+
// 将中文替换为 JSX 表达式
|
|
331
|
+
path.replaceWith(t.JSXExpressionContainer(makeReplace(node.value)));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
path.skip();
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
// JSX 元素属性
|
|
338
|
+
JSXAttribute(path) {
|
|
339
|
+
const { node } = path;
|
|
340
|
+
|
|
341
|
+
// 跳过被忽略的属性,比如 style、className 等
|
|
342
|
+
if (ignoreAttributes.includes(node.name.name)) {
|
|
343
|
+
path.skip();
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const t = require('@babel/types');
|
|
2
|
+
|
|
3
|
+
module.exports = function f(
|
|
4
|
+
{
|
|
5
|
+
i18nObject,
|
|
6
|
+
i18nMethod,
|
|
7
|
+
importCode,
|
|
8
|
+
},
|
|
9
|
+
returns
|
|
10
|
+
) {
|
|
11
|
+
const { replaceWords, downloadIds } = returns;
|
|
12
|
+
|
|
13
|
+
// XXX: [TRICKY] 防止中文转码为 unicode
|
|
14
|
+
function hackValue(value, id) {
|
|
15
|
+
|
|
16
|
+
return Object.assign(t.StringLiteral(value), {
|
|
17
|
+
extra: {
|
|
18
|
+
raw: `'${id}'`, // id
|
|
19
|
+
rawValue: value, // 临时id
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
去掉首尾空白字符,中间的连续空白字符替换成一个空格
|
|
26
|
+
例如:
|
|
27
|
+
<div>
|
|
28
|
+
中文
|
|
29
|
+
<div>
|
|
30
|
+
会提取”/n中文/n“作为记录标记,需要用formatWhitespace去掉
|
|
31
|
+
*/
|
|
32
|
+
function formatWhitespace(str) {
|
|
33
|
+
return str.trim().replace(/\s+/g, ' ');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 处理模板字符串中的变量
|
|
37
|
+
function makeObjectExpression(obj) {
|
|
38
|
+
if (Object.prototype.toString.call(obj) !== '[object Object]') return null;
|
|
39
|
+
|
|
40
|
+
// 存储每一项变量
|
|
41
|
+
const ObjectPropertyArr = [];
|
|
42
|
+
Object.keys(obj).forEach((k) => {
|
|
43
|
+
ObjectPropertyArr.push(
|
|
44
|
+
t.ObjectProperty(
|
|
45
|
+
t.Identifier(k),
|
|
46
|
+
obj[k].isAstNode ? obj[k].value : t.Identifier(obj[k])
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
return t.ObjectExpression(ObjectPropertyArr);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 替换字符串为我们使用的国际化API
|
|
54
|
+
function makeReplace(value, variables) {
|
|
55
|
+
|
|
56
|
+
// 存在临时id替换
|
|
57
|
+
returns.hasTouch = true;
|
|
58
|
+
|
|
59
|
+
value = formatWhitespace(value);
|
|
60
|
+
|
|
61
|
+
// 将临时id替换为数据词条的id
|
|
62
|
+
const id = replaceWords[value];
|
|
63
|
+
|
|
64
|
+
// 生成新的ast节点
|
|
65
|
+
const v = hackValue(value, id);
|
|
66
|
+
const objExp = makeObjectExpression(variables);
|
|
67
|
+
|
|
68
|
+
return t.callExpression(
|
|
69
|
+
// MemberExpression 成员表达式。
|
|
70
|
+
// 如果computed为true,则节点对应于一个computed(a[b])成员表达式,并且属性是一个表达式。
|
|
71
|
+
// 如果computed为false,则节点对应于静态(a.b)成员表达式,属性为标识符或PrivateName。
|
|
72
|
+
i18nObject
|
|
73
|
+
? t.memberExpression(t.identifier(i18nObject), t.identifier(i18nMethod))
|
|
74
|
+
: t.identifier(i18nMethod),
|
|
75
|
+
objExp ? [v, objExp] : [v]
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 函数或方法调用表达式
|
|
80
|
+
function dealCallExpressionNode(node) {
|
|
81
|
+
const res = {
|
|
82
|
+
needReplace: false,
|
|
83
|
+
value: null,
|
|
84
|
+
skip: false,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
(node.callee.type === 'MemberExpression' &&
|
|
89
|
+
node.callee.object.name === i18nObject &&
|
|
90
|
+
node.callee.property.name === i18nMethod) ||
|
|
91
|
+
(node.callee.type === 'Identifier' && node.callee.name === i18nMethod)
|
|
92
|
+
) {
|
|
93
|
+
// 收集现有的 key
|
|
94
|
+
const args = node.arguments;
|
|
95
|
+
if(!args.length) {
|
|
96
|
+
return res;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let key = args[0].value;
|
|
100
|
+
|
|
101
|
+
// intl.t(key)
|
|
102
|
+
// replaceWords { key: id }
|
|
103
|
+
const id = replaceWords[key];
|
|
104
|
+
if (id) {
|
|
105
|
+
// intl.t(key)
|
|
106
|
+
// 需要把脚本临时生成的key换成id,并存下词条的id
|
|
107
|
+
res.needReplace = true;
|
|
108
|
+
res.value = key;
|
|
109
|
+
downloadIds.push(id);
|
|
110
|
+
} else {
|
|
111
|
+
// intl.t(id)
|
|
112
|
+
// 把intl.t(id)中id对应的词条下载就行
|
|
113
|
+
downloadIds.push(key);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return res;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
// 导入声明,比如:import foo from "mod";
|
|
122
|
+
ImportDeclaration(path) {
|
|
123
|
+
// 是否已经导入过 intl 的依赖
|
|
124
|
+
const m = importCode.match(/from ["'](.*)["']/);
|
|
125
|
+
const pkgName = m ? m[1] : '';
|
|
126
|
+
if (path.node.source.value === pkgName) {
|
|
127
|
+
returns.hasImport = true;
|
|
128
|
+
}
|
|
129
|
+
path.skip();
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// 函数或方法调用表达式,比如:a('中午')
|
|
133
|
+
CallExpression(path) {
|
|
134
|
+
const { node } = path;
|
|
135
|
+
|
|
136
|
+
if (!node.ignore) {
|
|
137
|
+
const { needReplace, value, skip } = dealCallExpressionNode(node);
|
|
138
|
+
|
|
139
|
+
if (needReplace) {
|
|
140
|
+
const res = makeReplace(value);
|
|
141
|
+
path.replaceWith(res);
|
|
142
|
+
|
|
143
|
+
path.node.ignore = true;
|
|
144
|
+
}
|
|
145
|
+
if (skip) path.skip();
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const prettier = require('prettier');
|
|
4
|
+
const babel = require('@babel/core');
|
|
5
|
+
const generate = require('@babel/generator').default;
|
|
6
|
+
const traverse = require('@babel/traverse').default;
|
|
7
|
+
const pluginSyntaxJSX = require('@babel/plugin-syntax-jsx');
|
|
8
|
+
const pluginSyntaxProposalOptionalChaining = require('@babel/plugin-proposal-optional-chaining');
|
|
9
|
+
const pluginSyntaxClassProperties = require('@babel/plugin-syntax-class-properties');
|
|
10
|
+
const pluginSyntaxDecorators = require('@babel/plugin-syntax-decorators');
|
|
11
|
+
const pluginSyntaxObjectRestSpread = require('@babel/plugin-syntax-object-rest-spread');
|
|
12
|
+
const pluginSyntaxAsyncGenerators = require('@babel/plugin-syntax-async-generators');
|
|
13
|
+
const pluginSyntaxDoExpressions = require('@babel/plugin-syntax-do-expressions');
|
|
14
|
+
const pluginSyntaxDynamicImport = require('@babel/plugin-syntax-dynamic-import');
|
|
15
|
+
const pluginSyntaxFunctionBind = require('@babel/plugin-syntax-function-bind');
|
|
16
|
+
const presetTypescript = require('@babel/preset-typescript').default;
|
|
17
|
+
|
|
18
|
+
const makeVisitorCollect = require('./makeVisitorCollect');
|
|
19
|
+
const makeVisitorUpdate = require('./makeVisitorUpdate');
|
|
20
|
+
|
|
21
|
+
// 获取文件中需要忽略转化通用国际化API规范的所有行号
|
|
22
|
+
function getIgnoreLines(ast) {
|
|
23
|
+
const ignoreBlocks = [];
|
|
24
|
+
for (const comment of ast.comments) {
|
|
25
|
+
const { type, value, loc } = comment;
|
|
26
|
+
const last = ignoreBlocks.length - 1;
|
|
27
|
+
|
|
28
|
+
// 单行注释,比如 //
|
|
29
|
+
if (type === 'CommentLine' && value.trim() === 'cw-i18n-disable-line') {
|
|
30
|
+
ignoreBlocks.push({
|
|
31
|
+
start: loc.start.line,
|
|
32
|
+
end: loc.start.line,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// 多行注释,比如 /* */
|
|
36
|
+
else if (type === 'CommentBlock' && value.trim() === 'cw-i18n-disable') {
|
|
37
|
+
// 处理 di18n-disable 关闭 di18n-enable又开启的情况
|
|
38
|
+
if (last < 0 || ignoreBlocks[last].end) {
|
|
39
|
+
ignoreBlocks.push({
|
|
40
|
+
start: loc.start.line,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
} else if (type === 'CommentBlock' && value.trim() === 'cw-i18n-enable') {
|
|
44
|
+
// 处理 di18n-disable 关闭 di18n-enable又开启的情况
|
|
45
|
+
if (last >= 0 && !ignoreBlocks[last].end) {
|
|
46
|
+
ignoreBlocks[last].end = loc.start.line;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 如果缺少 disable-enable,直接作用到最后一行
|
|
52
|
+
const len = ignoreBlocks.length;
|
|
53
|
+
if (len > 0 && !ignoreBlocks[len - 1].end) {
|
|
54
|
+
ignoreBlocks[len - 1].end = ast.loc.end.line;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 转换成需要忽略的行号
|
|
58
|
+
const ignoreLines = [];
|
|
59
|
+
for (const block of ignoreBlocks) {
|
|
60
|
+
for (let i = block.start; i <= block.end; i++) {
|
|
61
|
+
ignoreLines.push(i);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return ignoreLines;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = function (type, files = [], conf = {}, replaceWords) {
|
|
69
|
+
const {
|
|
70
|
+
entry,
|
|
71
|
+
output,
|
|
72
|
+
primaryRegx,
|
|
73
|
+
importCode,
|
|
74
|
+
i18nObject,
|
|
75
|
+
i18nMethod,
|
|
76
|
+
babelPresets = [],
|
|
77
|
+
babelPlugins = [],
|
|
78
|
+
ignoreComponents,
|
|
79
|
+
ignoreMethods,
|
|
80
|
+
ignoreAttributes,
|
|
81
|
+
} = conf;
|
|
82
|
+
|
|
83
|
+
const allWords = []; // 收集到的所有词条
|
|
84
|
+
const downloadIds = []; // 需要从平台下载的词条id
|
|
85
|
+
|
|
86
|
+
// babel配置
|
|
87
|
+
const transformOptions = {
|
|
88
|
+
sourceType: 'module',
|
|
89
|
+
ast: true,
|
|
90
|
+
configFile: false,
|
|
91
|
+
presets: [
|
|
92
|
+
...babelPresets,
|
|
93
|
+
[
|
|
94
|
+
presetTypescript,
|
|
95
|
+
{ isTSX: true, allExtensions: true },
|
|
96
|
+
]
|
|
97
|
+
],
|
|
98
|
+
plugins: [
|
|
99
|
+
pluginSyntaxJSX,
|
|
100
|
+
pluginSyntaxProposalOptionalChaining,
|
|
101
|
+
pluginSyntaxClassProperties,
|
|
102
|
+
[pluginSyntaxDecorators, { decoratorsBeforeExport: true }],
|
|
103
|
+
pluginSyntaxObjectRestSpread,
|
|
104
|
+
pluginSyntaxAsyncGenerators,
|
|
105
|
+
pluginSyntaxDoExpressions,
|
|
106
|
+
pluginSyntaxDynamicImport,
|
|
107
|
+
pluginSyntaxFunctionBind,
|
|
108
|
+
...babelPlugins,
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const opts = {
|
|
113
|
+
primaryRegx,
|
|
114
|
+
i18nObject,
|
|
115
|
+
i18nMethod,
|
|
116
|
+
importCode,
|
|
117
|
+
ignoreLines: [],
|
|
118
|
+
ignoreMethods,
|
|
119
|
+
ignoreComponents,
|
|
120
|
+
ignoreAttributes,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// 处理所有文件
|
|
124
|
+
files.forEach((file) => {
|
|
125
|
+
const { filePath } = file;
|
|
126
|
+
const isTSX = ['.ts', '.tsx'].includes(path.extname(filePath));
|
|
127
|
+
|
|
128
|
+
const r = {
|
|
129
|
+
allWords,
|
|
130
|
+
replaceWords: replaceWords || {},
|
|
131
|
+
downloadIds,
|
|
132
|
+
hasImport: false, // 是否已经引入通用国际化API,import {init} from ...;
|
|
133
|
+
hasTouch: false, // 是否存在需要替换的词条
|
|
134
|
+
};
|
|
135
|
+
const sourceCode = fs.readFileSync(filePath, 'utf8');
|
|
136
|
+
const ast = babel.parseSync(sourceCode, transformOptions);
|
|
137
|
+
opts.ignoreLines = getIgnoreLines(ast);
|
|
138
|
+
|
|
139
|
+
let makeVisitor = makeVisitorCollect;
|
|
140
|
+
if (type === 'update') {
|
|
141
|
+
makeVisitor = makeVisitorUpdate
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const visitor = makeVisitor(opts, r);
|
|
145
|
+
traverse(ast, visitor);
|
|
146
|
+
|
|
147
|
+
// 不传入需要替换的文词条则不进行文件修改
|
|
148
|
+
// 在只需要提取中文词条的情况下不传入replaceWords
|
|
149
|
+
if (!replaceWords) return;
|
|
150
|
+
|
|
151
|
+
// https://stackoverflow.com/a/55478641
|
|
152
|
+
let { code } = generate(ast, {
|
|
153
|
+
retainLines: true,
|
|
154
|
+
decoratorsBeforeExport: true,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!r.hasTouch) {
|
|
158
|
+
code = sourceCode;
|
|
159
|
+
} else if (!r.hasImport && importCode) {
|
|
160
|
+
code = `${importCode}\n${code}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 自定义格式化代码
|
|
164
|
+
if (r.hasTouch) {
|
|
165
|
+
if (conf.prettier) {
|
|
166
|
+
const parser = isTSX ? 'typescript' : 'babel';
|
|
167
|
+
code = prettier.format(code, { ...conf.prettier, parser });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const target = output ? filePath.replace(entry, output) : filePath;
|
|
171
|
+
fs.writeFileSync(target, code, { encoding: 'utf-8' });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
allWords,
|
|
177
|
+
downloadIds,
|
|
178
|
+
};
|
|
179
|
+
};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
|
|
5
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
_Object$defineProperty(exports, "context", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
get: function get() {
|
|
11
|
+
return _context["default"];
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
_Object$defineProperty(exports, "intl", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function get() {
|
|
17
|
+
return _reactIntlUniversal["default"];
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
_Object$defineProperty(exports, "useIntl", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function get() {
|
|
23
|
+
return _useIntl["default"];
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
var _useIntl = _interopRequireDefault(require("./useIntl"));
|
|
27
|
+
var _context = _interopRequireDefault(require("./useIntl/context"));
|
|
28
|
+
var _reactIntlUniversal = _interopRequireDefault(require("react-intl-universal"));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
|
|
5
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports["default"] = void 0;
|
|
9
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
|
|
10
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
|
|
11
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/defineProperty"));
|
|
12
|
+
var IntlStore = /*#__PURE__*/function () {
|
|
13
|
+
function IntlStore() {
|
|
14
|
+
(0, _classCallCheck2["default"])(this, IntlStore);
|
|
15
|
+
(0, _defineProperty2["default"])(this, "locales", {});
|
|
16
|
+
(0, _defineProperty2["default"])(this, "currentLocale", 'zh');
|
|
17
|
+
}
|
|
18
|
+
(0, _createClass2["default"])(IntlStore, [{
|
|
19
|
+
key: "registerLocales",
|
|
20
|
+
value:
|
|
21
|
+
// 存储语言
|
|
22
|
+
function registerLocales(locales) {
|
|
23
|
+
this.locales = locales;
|
|
24
|
+
}
|
|
25
|
+
}, {
|
|
26
|
+
key: "setLocale",
|
|
27
|
+
value:
|
|
28
|
+
// 更新当前语言
|
|
29
|
+
function setLocale(locale) {
|
|
30
|
+
this.currentLocale = locale;
|
|
31
|
+
}
|
|
32
|
+
}]);
|
|
33
|
+
return IntlStore;
|
|
34
|
+
}();
|
|
35
|
+
var _default = new IntlStore();
|
|
36
|
+
exports["default"] = _default;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
|
|
5
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports["default"] = void 0;
|
|
9
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/slicedToArray"));
|
|
10
|
+
var _react = require("react");
|
|
11
|
+
var _reactIntlUniversal = _interopRequireDefault(require("react-intl-universal"));
|
|
12
|
+
var _useForceUpdate = _interopRequireDefault(require("./use-force-update"));
|
|
13
|
+
var _context = _interopRequireDefault(require("./context"));
|
|
14
|
+
var useIntl = function useIntl() {
|
|
15
|
+
var _useState = (0, _react.useState)(true),
|
|
16
|
+
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
|
|
17
|
+
loading = _useState2[0],
|
|
18
|
+
setLoading = _useState2[1];
|
|
19
|
+
var _useState3 = (0, _react.useState)(_context["default"].currentLocale),
|
|
20
|
+
_useState4 = (0, _slicedToArray2["default"])(_useState3, 2),
|
|
21
|
+
currentLocale = _useState4[0],
|
|
22
|
+
setCurrentLocale = _useState4[1];
|
|
23
|
+
var forceUpdate = (0, _useForceUpdate["default"])();
|
|
24
|
+
var registerLocales = function registerLocales(locales) {
|
|
25
|
+
_context["default"].registerLocales(locales);
|
|
26
|
+
};
|
|
27
|
+
var setLocale = function setLocale(locale) {
|
|
28
|
+
_context["default"].setLocale(locale);
|
|
29
|
+
setCurrentLocale(locale);
|
|
30
|
+
_reactIntlUniversal["default"].init({
|
|
31
|
+
currentLocale: locale,
|
|
32
|
+
locales: _context["default"].locales
|
|
33
|
+
});
|
|
34
|
+
if (loading) {
|
|
35
|
+
setLoading(false);
|
|
36
|
+
}
|
|
37
|
+
console.log('强制刷新');
|
|
38
|
+
forceUpdate();
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
currentLocale: currentLocale,
|
|
42
|
+
registerLocales: registerLocales,
|
|
43
|
+
setLocale: setLocale,
|
|
44
|
+
loading: loading
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
var _default = useIntl;
|
|
48
|
+
exports["default"] = _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _Object$defineProperty = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
|
|
5
|
+
_Object$defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
exports["default"] = useForceUpdate;
|
|
9
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/slicedToArray"));
|
|
10
|
+
var _react = require("react");
|
|
11
|
+
var createNewObject = function createNewObject() {
|
|
12
|
+
return {};
|
|
13
|
+
};
|
|
14
|
+
function useForceUpdate() {
|
|
15
|
+
var _useState = (0, _react.useState)(createNewObject),
|
|
16
|
+
_useState2 = (0, _slicedToArray2["default"])(_useState, 2),
|
|
17
|
+
setValue = _useState2[1];
|
|
18
|
+
return (0, _react.useCallback)(function () {
|
|
19
|
+
setValue(createNewObject());
|
|
20
|
+
}, []);
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chaoswise/intl",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "cloudwiser",
|
|
5
|
+
"description": "intl",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"chaoswise-intl": "bin/chaoswise-intl.js"
|
|
9
|
+
},
|
|
10
|
+
"directories": {
|
|
11
|
+
"lib": "lib",
|
|
12
|
+
"test": "__tests__"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"lib"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://git.cloudwise.com/DOCP/aops_web_commons.git"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"init": "npm i",
|
|
27
|
+
"build": "gulp build --gulpfile ./scripts/gulpfile.js"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"intl"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@babel/core": "^7.20.5",
|
|
34
|
+
"@babel/generator": "^7.20.5",
|
|
35
|
+
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
|
36
|
+
"@babel/plugin-proposal-decorators": "^7.17.9",
|
|
37
|
+
"@babel/plugin-proposal-export-default-from": "^7.16.7",
|
|
38
|
+
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
|
|
39
|
+
"@babel/plugin-proposal-object-rest-spread": "^7.17.3",
|
|
40
|
+
"@babel/plugin-proposal-optional-chaining": "^7.18.9",
|
|
41
|
+
"@babel/plugin-proposal-private-methods": "^7.16.11",
|
|
42
|
+
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
|
43
|
+
"@babel/plugin-syntax-class-properties": "^7.12.13",
|
|
44
|
+
"@babel/plugin-syntax-decorators": "^7.19.0",
|
|
45
|
+
"@babel/plugin-syntax-do-expressions": "^7.18.6",
|
|
46
|
+
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
47
|
+
"@babel/plugin-syntax-function-bind": "^7.18.6",
|
|
48
|
+
"@babel/plugin-syntax-jsx": "^7.18.6",
|
|
49
|
+
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
|
50
|
+
"@babel/plugin-transform-arrow-functions": "^7.16.7",
|
|
51
|
+
"@babel/plugin-transform-block-scoping": "^7.16.7",
|
|
52
|
+
"@babel/plugin-transform-classes": "^7.16.7",
|
|
53
|
+
"@babel/plugin-transform-destructuring": "^7.17.7",
|
|
54
|
+
"@babel/plugin-transform-for-of": "^7.16.7",
|
|
55
|
+
"@babel/plugin-transform-parameters": "^7.16.7",
|
|
56
|
+
"@babel/plugin-transform-shorthand-properties": "^7.16.7",
|
|
57
|
+
"@babel/plugin-transform-spread": "^7.16.7",
|
|
58
|
+
"@babel/preset-env": "^7.16.11",
|
|
59
|
+
"@babel/preset-typescript": "^7.18.6",
|
|
60
|
+
"@babel/runtime-corejs3": "^7.11.2",
|
|
61
|
+
"@babel/traverse": "^7.20.5",
|
|
62
|
+
"@babel/types": "^7.20.5",
|
|
63
|
+
"axios": "^0.21.1",
|
|
64
|
+
"babel-eslint": "^10.1.0",
|
|
65
|
+
"babel-loader": "^8.2.4",
|
|
66
|
+
"babel-plugin-import": "^1.13.0",
|
|
67
|
+
"babel-plugin-macros": "^3.1.0",
|
|
68
|
+
"babel-plugin-polyfill-corejs3": "^0.5.2",
|
|
69
|
+
"chalk": "^2.4.1",
|
|
70
|
+
"gulp": "^4.0.2",
|
|
71
|
+
"gulp-babel": "^8.0.0",
|
|
72
|
+
"gulp-typescript": "^5.0.1",
|
|
73
|
+
"gulp-connect": "^5.7.0",
|
|
74
|
+
"gulp-jsdoc3": "^3.0.0",
|
|
75
|
+
"prettier": "^2.8.1",
|
|
76
|
+
"glob": "^8.0.3",
|
|
77
|
+
"react-intl-universal": "^2.6.11",
|
|
78
|
+
"uuid": "^9.0.0"
|
|
79
|
+
},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"react": "^16.13.1",
|
|
82
|
+
"react-dom": "^16.13.1"
|
|
83
|
+
},
|
|
84
|
+
"license": "MIT"
|
|
85
|
+
}
|