-
Notifications
You must be signed in to change notification settings - Fork 239
Expand file tree
/
Copy pathextension.ts
More file actions
459 lines (439 loc) · 16.6 KB
/
Copy pathextension.ts
File metadata and controls
459 lines (439 loc) · 16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
/**
* @author linhuiw
* @desc 插件主入口
*/
import * as vscode from 'vscode';
import * as _ from 'lodash';
import * as fs from 'fs-extra';
import { UI } from './ui';
import { getSuggestLangObj } from './getLangData';
import { DIR_ADAPTOR, I18N_GLOB, KiwiSearchTypes } from './const';
import { findAllI18N, findI18N } from './findAllI18N';
import { triggerUpdateDecorations } from './chineseCharDecorations';
import { TargetStr, TranslateAPiEnum } from './define';
import { replaceAndUpdate } from './replaceAndUpdate';
import { AutoImportI18NFixer } from './autoImportI18n';
import {
findMatchKey,
getConfiguration,
getConfigFile,
translateText,
getKiwiLinterConfigFile,
getCurrActivePageI18nKey,
getTranslateAPiList,
getSafePath
} from './utils';
/**
* 主入口文件
* @param context
*/
export function activate(context: vscode.ExtensionContext) {
/** 存在配置文件则开启 */
if (!getKiwiLinterConfigFile() && !getConfigFile() && !fs.existsSync(DIR_ADAPTOR)) {
vscode.window.showErrorMessage('请执行 kiwi --init 指令进行初始化!');
return;
}
// 检测配置文件中的翻译源
const translateApiList = getTranslateAPiList();
const ui = new UI();
let translateApi = translateApiList[translateApiList.length - 1].label;
ui.init(translateApi);
vscode.commands.registerCommand('vscode-i18n-linter.switchTranslateApi', () => {
if (translateApiList.length > 1) {
vscode.window.showQuickPick(translateApiList).then(val => {
ui.init(val.label);
translateApi = val.label;
});
} else {
vscode.window.showInformationMessage('无其他翻译源可供切换,请配置!');
}
});
vscode.commands.registerCommand('vscode-i18n-linter.searchI18N', () => {
vscode.window.showQuickPick(KiwiSearchTypes).then(val => {
if (val?.label === '在当前文件中搜索') {
findI18N();
} else {
findAllI18N();
}
});
});
console.log('Congratulations, your extension "kiwi-linter" is now active!');
context.subscriptions.push(vscode.commands.registerCommand('vscode-i18n-linter.findAllI18N', findAllI18N));
let targetStrs: TargetStr[] = [];
let finalLangObj = {};
let suggestion = [];
let autoFixer: AutoImportI18NFixer;
let activeEditor = vscode.window.activeTextEditor;
if (activeEditor) {
triggerUpdateDecorations(newTargetStrs => {
targetStrs = newTargetStrs;
});
suggestion = getCurrActivePageI18nKey();
autoFixer = new AutoImportI18NFixer();
}
context.subscriptions.push(vscode.commands.registerTextEditorCommand('vscode-i18n-linter.findI18N', findI18N));
// 识别到出错时点击小灯泡弹出的操作
const hasLightBulb = getConfiguration('enableReplaceSuggestion');
if (hasLightBulb) {
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
[
{ scheme: 'file', language: 'typescriptreact' },
{ scheme: 'file', language: 'html' },
{ scheme: 'file', language: 'typescript' },
{ scheme: 'file', language: 'javascriptreact' },
{ scheme: 'file', language: 'javascript' },
{ scheme: '*', language: 'vue' }
],
{
provideCodeActions: function(document, range, context, token) {
const targetStr = targetStrs.find(t => range.intersection(t.range) !== undefined);
if (targetStr) {
const sameTextStrs = targetStrs.filter(t => t.text === targetStr.text);
const text = targetStr.text;
const actions = [];
finalLangObj = getSuggestLangObj();
for (const key in finalLangObj) {
if (finalLangObj[key] === text) {
actions.push({
title: `抽取为 \`I18N.${key}\``,
command: 'vscode-i18n-linter.extractI18N',
arguments: [
{
targets: sameTextStrs,
varName: `I18N.${key}`
}
]
});
}
}
return actions.concat([
{
title: `抽取为自定义 I18N 变量(共${sameTextStrs.length}处)`,
command: 'vscode-i18n-linter.extractI18N',
arguments: [
{
targets: sameTextStrs
}
]
},
{
title: `在当前行中禁用I18N提取`,
command: 'vscode-i18n-linter.IngoreI18N'
},
{
title: `在当前文件中禁用I18N提取`,
command: 'vscode-i18n-linter.IngoreFileI18N'
}
]);
}
}
}
)
);
}
// 点击小灯泡后进行替换操作
context.subscriptions.push(
vscode.commands.registerCommand('vscode-i18n-linter.extractI18N', args => {
return new Promise(resolve => {
// 若变量名已确定则直接开始替换
if (args.varName) {
return resolve(args.varName);
}
// 否则要求用户输入变量名
return resolve(
vscode.window.showInputBox({
prompt: '请输入变量名,格式 `I18N.[page].[key]`,按 <回车> 启动替换',
value: `I18N.${suggestion.length ? suggestion.join('.') + '.' : ''}`,
validateInput(input) {
if (!input.match(/^I18N\.\w+\.\w+/)) {
return '变量名格式 `I18N.[page].[key]`,如 `I18N.dim.new`,[key] 中可包含更多 `.`';
}
}
})
);
}).then((val: string) => {
// 没有输入变量名
if (!val) {
return;
}
const finalArgs = Array.isArray(args.targets) ? args.targets : [args.targets];
return finalArgs
.reverse()
.reduce((prev: Promise<any>, curr: TargetStr, index: number) => {
return prev.then(() => {
const isEditCommon = val.startsWith('I18N.common.');
return replaceAndUpdate(curr, val, !isEditCommon && index === 0 ? !args.varName : false);
});
}, Promise.resolve())
.then(
() => {
vscode.window.showInformationMessage(`成功替换 ${finalArgs.length} 处文案`);
if (autoFixer) {
autoFixer.fix(vscode.window.activeTextEditor.document);
}
},
err => {
console.log(err, 'err');
}
);
});
})
);
// 点击小灯泡后忽略当前行提取
context.subscriptions.push(
vscode.commands.registerCommand('vscode-i18n-linter.IngoreI18N', () => {
return new Promise(resolve => {
const activeTextEditor = vscode.window.activeTextEditor;
// 鼠标所属行
const activeLine = activeTextEditor.selection.active.line;
// 当前行的代码
const currentLineText = activeTextEditor.document.lineAt(activeLine).text;
// 匹配当前行的缩进
const indentMatch = currentLineText.match(/^\s*/);
const indentWhitespace = indentMatch ? indentMatch[0] : '';
activeTextEditor.edit(editBuilder => {
// 在当前行之前插入注释
editBuilder.insert(
new vscode.Position(activeLine, 0),
indentWhitespace +
(activeTextEditor.document.fileName.endsWith('.html')
? '<!-- kiwi-disable-next-line --> \n'
: '/* kiwi-disable-next-line */ \n')
);
});
resolve(undefined);
}).then(() => {
return Promise.resolve().then(() => {
vscode.window.showInformationMessage('文案提取已禁用');
});
});
})
);
// 点击小灯泡后忽略当前文件中的I18N文案提取
context.subscriptions.push(
vscode.commands.registerCommand('vscode-i18n-linter.IngoreFileI18N', () => {
return new Promise(resolve => {
const activeTextEditor = vscode.window.activeTextEditor;
activeTextEditor.edit(editBuilder => {
// 在当前文件的第一行插入注释
editBuilder.insert(
new vscode.Position(0, 0),
activeTextEditor.document.fileName.endsWith('.html')
? '<!-- kiwi-disable-file --> \n'
: '/* kiwi-disable-file */ \n'
);
});
resolve(undefined);
}).then(() => {
return Promise.resolve().then(() => {
vscode.window.showInformationMessage('文案提取已禁用');
});
});
})
);
// 使用 cmd + shift + p 执行的公共文案替换
context.subscriptions.push(
vscode.commands.registerCommand('vscode-i18n-linter.replaceCommon', () => {
const commandKeys = Object.keys(finalLangObj).filter(k => k.includes('common.'));
if (targetStrs.length === 0 || commandKeys.length === 0) {
vscode.window.showInformationMessage('没有找到可替换的公共文案');
return;
}
const replaceableStrs = targetStrs.reduce((prev, curr) => {
const key = findMatchKey(finalLangObj, curr.text);
if (key && key.startsWith('common.')) {
return prev.concat({
target: curr,
key
});
}
return prev;
}, []);
if (replaceableStrs.length === 0) {
vscode.window.showInformationMessage('没有找到可替换的公共文案');
return;
}
vscode.window
.showInformationMessage(
`共找到 ${replaceableStrs.length} 处可自动替换的文案,是否替换?`,
{ modal: true },
'Yes'
)
.then(action => {
if (action === 'Yes') {
replaceableStrs
.reverse()
.reduce((prev: Promise<any>, obj) => {
return prev.then(() => {
return replaceAndUpdate(obj.target, `I18N.${obj.key}`, false);
});
}, Promise.resolve())
.then(() => {
vscode.window.showInformationMessage('替换完成');
if (autoFixer) {
autoFixer.fix(vscode.window.activeTextEditor.document);
}
})
.catch(e => {
vscode.window.showErrorMessage(e.message);
});
}
});
})
);
// 一键替换所有中文
context.subscriptions.push(
vscode.commands.registerCommand('vscode-i18n-linter.kiwigo', () => {
// 将嵌套的模板字符串情况移除,只替换最外层的文案,内部中文保留
const newTargetStrs = targetStrs.filter((item, i) => {
if (i > 0) {
const beforeStrs = targetStrs.slice(0, i);
const curRange = item.range;
const [curStartLine, curEndLine] = [curRange.start.line, curRange.end.line];
const [curStart, curEnd] = [curRange.start.character, curRange.end.character];
const include = beforeStrs.some(str => {
const preRange = str.range;
const [preStartLine, preEndLine] = [preRange.start.line, preRange.end.line];
const [preStart, preEnd] = [preRange.start.character, preRange.end.character];
// 当前字符的范围包含在已提取的文案的范围内
return !(
curEndLine < preStartLine ||
curStartLine > preEndLine ||
(curStartLine === preEndLine && curStart > preEnd) ||
(curEndLine === preStartLine && curEnd < preStart)
);
});
return !include;
}
return true;
});
if (newTargetStrs.length === 0) {
vscode.window.showInformationMessage('没有找到可替换的文案');
return;
}
vscode.window
.showInputBox({
prompt: '请调整文案抽取后的位置,格式 `I18N.[page]`,不修改即默认',
value: `I18N.${suggestion.length ? suggestion.join('.') : ''}`,
validateInput(input) {
if (!input.match(/^I18N\.\w+/)) {
return '变量名格式 `I18N.[page]`,如 `I18N.dim`,[page] 中可包含更多 `.`';
}
}
})
.then((path: string) => {
if (!path) {
return;
}
const newPath = getSafePath(
path
.split('.')
.slice(1)
.join('.'),
I18N_GLOB
);
const virtualMemory = {};
finalLangObj = getSuggestLangObj();
// 根据在文件中的位置进行排序,防止后续生成key和文案位置错位
const sortTargetStrs: any = _.sortBy(newTargetStrs, item => {
return item.range.start.line;
});
// 翻译中文文案
const delimiter = translateApi === TranslateAPiEnum.Baidu ? '\n' : '$';
const translateTexts = sortTargetStrs.reduce((prev, curr, i) => {
// 避免翻译的字符里包含数字或者特殊字符等情况,只过滤出汉字和字母
const reg = /[a-zA-Z\u4e00-\u9fa5]+/g;
const findText = curr.text.match(reg) || [];
const transText = findText.join('').slice(0, 5) || '中文符号';
if (i === 0) {
return transText;
}
return `${prev}${delimiter}${transText}`;
}, '');
console.log('key值翻译原文:', translateTexts);
translateText(translateTexts, translateApi)
.then(translateTexts => {
console.log('翻译后:', translateTexts);
const replaceableStrs = sortTargetStrs.reduce((prev, curr, i) => {
const key = findMatchKey(finalLangObj, curr.text);
if (!virtualMemory[curr.text]) {
if (key) {
virtualMemory[curr.text] = key;
return prev.concat({
target: curr,
key: `${key}`
});
}
const transText = translateTexts[i] && _.camelCase(translateTexts[i]);
let transKey = `${newPath + '.'}${transText}`;
let occurTime = 1;
// 防止出现前四位相同但是整体文案不同的情况
while (
finalLangObj[transKey] !== curr.text &&
_.keys(finalLangObj).includes(`${transKey}${occurTime >= 2 ? occurTime : ''}`)
) {
occurTime++;
}
if (occurTime >= 2) {
transKey = `${transKey}${occurTime}`;
}
virtualMemory[curr.text] = transKey;
finalLangObj[transKey] = curr.text;
return prev.concat({
target: curr,
key: transKey
});
} else {
return prev.concat({
target: curr,
key: virtualMemory[curr.text]
});
}
}, []);
replaceableStrs
.reverse()
.reduce((prev: Promise<any>, obj) => {
return prev.then(() => {
return replaceAndUpdate(obj.target, `I18N.${obj.key}`, false);
});
}, Promise.resolve())
.then(() => {
vscode.window.showInformationMessage('替换完成');
if (autoFixer) {
autoFixer.fix(vscode.window.activeTextEditor.document);
}
})
.catch(e => {
vscode.window.showErrorMessage(e.message);
});
})
.catch(err => {
vscode.window.showErrorMessage(err);
});
});
})
);
// 当 切换文档 的时候重新检测当前文档中的中文文案
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor(editor => {
activeEditor = editor;
if (editor) {
triggerUpdateDecorations(newTargetStrs => {
targetStrs = newTargetStrs;
});
suggestion = getCurrActivePageI18nKey();
}
}, null)
);
// 当 文档发生变化时 的时候重新检测当前文档中的中文文案
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument(event => {
if (activeEditor && event.document === activeEditor.document) {
triggerUpdateDecorations(newTargetStrs => {
targetStrs = newTargetStrs;
});
}
}, null)
);
}