i18ntk 4.5.4 → 4.6.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/CHANGELOG.md +34 -0
- package/README.md +124 -536
- package/main/i18ntk-scanner.js +148 -29
- package/main/manage/commands/ScannerCommand.js +145 -28
- package/main/manage/services/FrameworkDetectionService.js +67 -9
- package/package.json +27 -43
- package/utils/config-helper.js +2 -2
- package/utils/framework-detector.js +317 -6
- package/utils/report-model.js +17 -4
- package/utils/usage-source.js +2 -1
package/main/i18ntk-scanner.js
CHANGED
|
@@ -22,6 +22,7 @@ const { getUnifiedConfig, displayHelp } = require('../utils/config-helper');
|
|
|
22
22
|
const { loadTranslations } = require('../utils/i18n-helper');
|
|
23
23
|
const SecurityUtils = require('../utils/security');
|
|
24
24
|
const SetupEnforcer = require('../utils/setup-enforcer');
|
|
25
|
+
const { detectProjectFramework, getFrameworkPatterns, getFrameworkSuggestions, SCANNER_EXTENSIONS, WRAPPER_SKIP_PATTERNS, getExcludeDirs } = require('../utils/framework-detector');
|
|
25
26
|
|
|
26
27
|
// Ensure setup is complete before running
|
|
27
28
|
(async () => {
|
|
@@ -225,11 +226,26 @@ class I18nTextScanner {
|
|
|
225
226
|
const packageJson = SecurityUtils.safeParseJSON(packageJsonContent);
|
|
226
227
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
227
228
|
|
|
229
|
+
if (deps.next || deps['next-intl'] || deps['next-i18next']) return 'next';
|
|
228
230
|
if (deps.react || deps['react-dom']) return 'react';
|
|
229
231
|
if (deps.vue || deps['vue-router']) return 'vue';
|
|
230
232
|
if (deps['@angular/core'] || deps.angular) return 'angular';
|
|
231
|
-
if (deps.next) return 'next';
|
|
232
233
|
if (deps.svelte) return 'svelte';
|
|
234
|
+
if (deps.astro || deps['astro-i18next'] || deps['@astrojs/i18n']) return 'astro';
|
|
235
|
+
if (deps['@remix-run/react'] || deps['remix-i18next']) return 'remix';
|
|
236
|
+
if (deps.gatsby || deps['gatsby-plugin-react-i18next']) return 'gatsby';
|
|
237
|
+
if (deps['@builder.io/qwik'] || deps['qwik-speak']) return 'qwik';
|
|
238
|
+
if (deps['solid-js'] || deps['@solid-primitives/i18n']) return 'solid';
|
|
239
|
+
if (deps['ember-source'] || deps['ember-intl']) return 'ember';
|
|
240
|
+
if (deps.nuxt || deps['@nuxtjs/i18n']) return 'nuxt';
|
|
241
|
+
|
|
242
|
+
// Check for Rust project
|
|
243
|
+
const cargoTomlPath = path.join(projectRoot, 'Cargo.toml');
|
|
244
|
+
if (SecurityUtils.safeExistsSync(cargoTomlPath, projectRoot)) return 'rust';
|
|
245
|
+
|
|
246
|
+
// Check for Go project
|
|
247
|
+
const goModPath = path.join(projectRoot, 'go.mod');
|
|
248
|
+
if (SecurityUtils.safeExistsSync(goModPath, projectRoot)) return 'go';
|
|
233
249
|
|
|
234
250
|
return 'vanilla';
|
|
235
251
|
} catch (error) {
|
|
@@ -255,57 +271,110 @@ class I18nTextScanner {
|
|
|
255
271
|
|
|
256
272
|
const frameworkSpecific = {
|
|
257
273
|
react: [
|
|
258
|
-
// React specific patterns - enhanced for i18next detection
|
|
259
274
|
/children:\s*["']([^"']{2,99})["']/g,
|
|
260
275
|
/dangerouslySetInnerHTML={{\s*__html:\s*["']([^"']{2,99})["']/g,
|
|
261
|
-
// JSX text content without translation
|
|
262
276
|
/>([^<{][^<>{]*[^}>])</g,
|
|
263
|
-
// Button text
|
|
264
277
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
265
|
-
|
|
266
|
-
/<
|
|
278
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
279
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g,
|
|
280
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*\{[']\s*([^'}]+\.[^'}]+)\s*[']/g
|
|
267
281
|
],
|
|
268
282
|
vue: [
|
|
269
|
-
// Vue specific patterns - enhanced for vue-i18n detection
|
|
270
283
|
/v-text=["']([^"']{2,99})["']/g,
|
|
271
284
|
/v-html=["']([^"']{2,99})["']/g,
|
|
272
|
-
// Vue template text
|
|
273
285
|
/>([^<{][^<>{]*[^}>])</g,
|
|
274
|
-
// Button text
|
|
275
286
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
276
|
-
|
|
277
|
-
|
|
287
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
288
|
+
/\$t\(["']([^"']{2,99})["']\)/g,
|
|
289
|
+
/v-t=["']([^"']{2,99})["']/g
|
|
278
290
|
],
|
|
279
291
|
angular: [
|
|
280
|
-
// Angular specific patterns - enhanced for ngx-translate detection
|
|
281
292
|
/\[innerHTML\]=["']([^"']{2,99})["']/g,
|
|
282
293
|
/\[textContent\]=["']([^"']{2,99})["']/g,
|
|
283
|
-
// Angular template text
|
|
284
294
|
/>([^<{][^<>{]*[^}>])</g,
|
|
285
|
-
// Button text
|
|
286
295
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
297
|
+
/i18n=["']([^"']{2,99})["']/g,
|
|
298
|
+
/\[attr\.title\]=["']([^"']{2,99})["']/g
|
|
299
|
+
],
|
|
300
|
+
next: [
|
|
301
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
302
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
303
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
304
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
305
|
+
],
|
|
306
|
+
svelte: [
|
|
307
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
308
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
309
|
+
/\$_\(["']([^"']{2,99})["']\)/g,
|
|
310
|
+
/t\.set\(["']([^"']{2,99})["']/g,
|
|
311
|
+
/\{#if[^}]*\}([^<]{2,99})\{\/if\}/g
|
|
312
|
+
],
|
|
313
|
+
astro: [
|
|
314
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
315
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
316
|
+
/t\(["']([^"']{2,99})["']\)/g
|
|
317
|
+
],
|
|
318
|
+
remix: [
|
|
319
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
320
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
321
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
322
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
323
|
+
],
|
|
324
|
+
qwik: [
|
|
325
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
326
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
327
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
328
|
+
/useTranslate\(\)/g
|
|
329
|
+
],
|
|
330
|
+
solid: [
|
|
331
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
332
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
333
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
334
|
+
/useI18n\(\)\.t\(["']([^"']{2,99})["']\)/g
|
|
335
|
+
],
|
|
336
|
+
ember: [
|
|
337
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
338
|
+
/\{\{t\s+["']([^"']{2,99})["']\s*\}\}/g,
|
|
339
|
+
/\{\{intl\.t\s+["']([^"']{2,99})["']\s*\}\}/g
|
|
340
|
+
],
|
|
341
|
+
gatsby: [
|
|
342
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
343
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
344
|
+
/<(?:Trans)[\s\S]*?\bi18nKey\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
289
345
|
],
|
|
290
346
|
django: [
|
|
291
|
-
// Django template patterns
|
|
292
347
|
/\{\%\s*trans\s+["']([^"']{2,99})["']\s*%\}/g,
|
|
293
348
|
/\{\%\s*blocktrans\s*%\}([^%]{2,99})\{\%\s*endblocktrans\s*%\}/g,
|
|
294
349
|
/{{\s*_["']([^"']{2,99})["']\s*}}/g,
|
|
295
350
|
/{{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g
|
|
296
351
|
],
|
|
297
352
|
flask: [
|
|
298
|
-
// Flask/Jinja2 template patterns
|
|
299
353
|
/\{\{\s*_["']([^"']{2,99})["']\s*}}/g,
|
|
300
354
|
/\{\{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g,
|
|
301
355
|
/\{\{\s*lazy_gettext\(["']([^"']{2,99})["']\)\s*}}/g
|
|
302
356
|
],
|
|
303
357
|
python: [
|
|
304
|
-
// Python source patterns
|
|
305
358
|
/gettext\(["']([^"']{2,99})["']\)/g,
|
|
306
359
|
/_\(["']([^"']{2,99})["']\)/g,
|
|
307
360
|
/gettext_lazy\(["']([^"']{2,99})["']\)/g,
|
|
308
361
|
/lazy_gettext\(["']([^"']{2,99})["']\)/g
|
|
362
|
+
],
|
|
363
|
+
rust: [
|
|
364
|
+
/bundle\.get_message\(\s*["']([^"']{2,99})["']\)/g,
|
|
365
|
+
/\.get_message\s*\(\s*["']([^"']{2,99})["']\)/g,
|
|
366
|
+
/ts!\s*\(\s*["']([^"']{2,99})["']/g,
|
|
367
|
+
/fluent!\s*\(\s*["']([^"']{2,99})["']/g
|
|
368
|
+
],
|
|
369
|
+
go: [
|
|
370
|
+
/i18n\.Translate\([^,]+,\s*["']([^"']{2,99})["']\)/g,
|
|
371
|
+
/i18n\.NewMessage\([^,]+,\s*["']([^"']{2,99})["']\)/g,
|
|
372
|
+
/t\.Get\([^,]+,\s*["']([^"']{2,99})["']\)/g
|
|
373
|
+
],
|
|
374
|
+
vanilla: [
|
|
375
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
376
|
+
/i18n\.t\(["']([^"']{2,99})["']\)/g,
|
|
377
|
+
/translate\(["']([^"']{2,99})["']\)/g
|
|
309
378
|
]
|
|
310
379
|
};
|
|
311
380
|
|
|
@@ -518,7 +587,7 @@ class I18nTextScanner {
|
|
|
518
587
|
key: `ui.${key}`,
|
|
519
588
|
original: text,
|
|
520
589
|
translationKey: `t('ui.${key}')`,
|
|
521
|
-
frameworkSpecific: this.
|
|
590
|
+
frameworkSpecific: getFrameworkSuggestions(this.framework, text)
|
|
522
591
|
};
|
|
523
592
|
}
|
|
524
593
|
|
|
@@ -526,16 +595,51 @@ class I18nTextScanner {
|
|
|
526
595
|
const frameworks = {
|
|
527
596
|
react: {
|
|
528
597
|
hook: `const { t } = useTranslation();`,
|
|
529
|
-
usage: `{t('ui.${
|
|
530
|
-
component: `<Trans i18nKey="ui.${
|
|
598
|
+
usage: `{t('ui.${this._keySnippet(text)}')}`,
|
|
599
|
+
component: `<Trans i18nKey="ui.${this._keySnippet(text)}">${text}</Trans>`
|
|
600
|
+
},
|
|
601
|
+
next: {
|
|
602
|
+
hook: `const t = useTranslations();`,
|
|
603
|
+
usage: `{t('ui.${this._keySnippet(text)}')}`,
|
|
604
|
+
component: `<Trans i18nKey="ui.${this._keySnippet(text)}">${text}</Trans>`
|
|
531
605
|
},
|
|
532
606
|
vue: {
|
|
533
|
-
directive: `{{ $t('ui.${
|
|
534
|
-
method: `this.$t('ui.${
|
|
607
|
+
directive: `{{ $t('ui.${this._keySnippet(text)}') }}`,
|
|
608
|
+
method: `this.$t('ui.${this._keySnippet(text)}')`
|
|
535
609
|
},
|
|
536
610
|
angular: {
|
|
537
611
|
pipe: `{{ '${text}' | translate }}`,
|
|
538
|
-
service: `this.translateService.instant('ui.${
|
|
612
|
+
service: `this.translateService.instant('ui.${this._keySnippet(text)}')`
|
|
613
|
+
},
|
|
614
|
+
svelte: {
|
|
615
|
+
store: `$_(('ui.${this._keySnippet(text)}'))`,
|
|
616
|
+
method: `t.set('ui.${this._keySnippet(text)}', '${text}')`
|
|
617
|
+
},
|
|
618
|
+
astro: {
|
|
619
|
+
import: `import { t } from 'astro-i18next';`,
|
|
620
|
+
usage: `{t('ui.${this._keySnippet(text)}')}`
|
|
621
|
+
},
|
|
622
|
+
remix: {
|
|
623
|
+
hook: `const { t } = useTranslation();`,
|
|
624
|
+
usage: `{t('ui.${this._keySnippet(text)}')}`,
|
|
625
|
+
server: `export const handle = i18next.handle;`
|
|
626
|
+
},
|
|
627
|
+
qwik: {
|
|
628
|
+
hook: `const t = useTranslate();`,
|
|
629
|
+
usage: `{t('ui.${this._keySnippet(text)}')}`
|
|
630
|
+
},
|
|
631
|
+
solid: {
|
|
632
|
+
hook: `const [t] = useI18n();`,
|
|
633
|
+
usage: `{t('ui.${this._keySnippet(text)}')}`
|
|
634
|
+
},
|
|
635
|
+
ember: {
|
|
636
|
+
template: `{{t 'ui.${this._keySnippet(text)}'}}`,
|
|
637
|
+
helper: `this.intl.t('ui.${this._keySnippet(text)}')`
|
|
638
|
+
},
|
|
639
|
+
gatsby: {
|
|
640
|
+
hook: `const { t } = useTranslation();`,
|
|
641
|
+
usage: `{t('ui.${this._keySnippet(text)}')}`,
|
|
642
|
+
plugin: `'gatsby-plugin-react-i18next'`
|
|
539
643
|
},
|
|
540
644
|
django: {
|
|
541
645
|
template: `{% trans '${text}' %}`,
|
|
@@ -552,14 +656,29 @@ class I18nTextScanner {
|
|
|
552
656
|
underscore: `from gettext import gettext as _\n_('${text}')`,
|
|
553
657
|
lazy: `from gettext import gettext_lazy as _\n_('${text}')`
|
|
554
658
|
},
|
|
659
|
+
rust: {
|
|
660
|
+
fluent: `bundle.get_message("ui_${this._keySnippet(text)}")`,
|
|
661
|
+
gettext: `gettext("ui_${this._keySnippet(text)}")`
|
|
662
|
+
},
|
|
663
|
+
go: {
|
|
664
|
+
translate: `i18n.Translate("ui_${this._keySnippet(text)}")`,
|
|
665
|
+
message: `i18n.NewMessage("ui_${this._keySnippet(text)}")`
|
|
666
|
+
},
|
|
555
667
|
vanilla: {
|
|
556
|
-
generic: `t('ui.${
|
|
668
|
+
generic: `t('ui.${this._keySnippet(text)}')`
|
|
557
669
|
}
|
|
558
670
|
};
|
|
559
671
|
|
|
560
672
|
return frameworks[this.framework] || frameworks.vanilla;
|
|
561
673
|
}
|
|
562
674
|
|
|
675
|
+
_keySnippet(text) {
|
|
676
|
+
return text.toLowerCase()
|
|
677
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
678
|
+
.replace(/\s+/g, '_')
|
|
679
|
+
.substring(0, 40);
|
|
680
|
+
}
|
|
681
|
+
|
|
563
682
|
async scanDirectory(dir, options = {}) {
|
|
564
683
|
const {
|
|
565
684
|
patterns = [],
|
|
@@ -574,7 +693,7 @@ class I18nTextScanner {
|
|
|
574
693
|
}
|
|
575
694
|
|
|
576
695
|
const allResults = [];
|
|
577
|
-
const extensions = [
|
|
696
|
+
const extensions = [...SCANNER_EXTENSIONS];
|
|
578
697
|
|
|
579
698
|
const scanRecursive = (currentDir) => {
|
|
580
699
|
const items = SecurityUtils.safeReaddirSync(currentDir, path.dirname(currentDir), { withFileTypes: true });
|
|
@@ -736,7 +855,7 @@ class I18nTextScanner {
|
|
|
736
855
|
} else if (fwPref && fwPref !== 'auto') {
|
|
737
856
|
this.framework = fwPref;
|
|
738
857
|
} else if (fwDetectEnabled) {
|
|
739
|
-
const detected =
|
|
858
|
+
const detected = detectProjectFramework(process.cwd());
|
|
740
859
|
this.framework = detected || fwFallback;
|
|
741
860
|
} else {
|
|
742
861
|
this.framework = fwFallback;
|
|
@@ -762,7 +881,7 @@ class I18nTextScanner {
|
|
|
762
881
|
console.log(this.t('scanner.starting', { framework: this.framework }));
|
|
763
882
|
console.log(this.t('scanner.sourceDirectory', { sourceDir: this.sourceDir }));
|
|
764
883
|
|
|
765
|
-
const patterns =
|
|
884
|
+
const patterns = getFrameworkPatterns(this.framework);
|
|
766
885
|
const exclusions = this.config.exclude || ['node_modules', '.git', 'dist', 'build'];
|
|
767
886
|
const minLength = this.config.minLength || 3;
|
|
768
887
|
const maxLength = this.config.maxLength || 100;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
+
const { detectProjectFramework, getFrameworkPatterns, getFrameworkSuggestions, SCANNER_EXTENSIONS, WRAPPER_SKIP_PATTERNS } = require('../../../utils/framework-detector');
|
|
12
13
|
const { getUnifiedConfig, displayHelp } = require('../../../utils/config-helper');
|
|
13
14
|
const { loadTranslations } = require('../../../utils/i18n-helper');
|
|
14
15
|
const SecurityUtils = require('../../../utils/security');
|
|
@@ -207,11 +208,24 @@ class ScannerCommand {
|
|
|
207
208
|
const packageJson = SecurityUtils.safeParseJSON(packageJsonContent);
|
|
208
209
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
209
210
|
|
|
211
|
+
if (deps.next || deps['next-intl'] || deps['next-i18next']) return 'next';
|
|
210
212
|
if (deps.react || deps['react-dom']) return 'react';
|
|
211
213
|
if (deps.vue || deps['vue-router']) return 'vue';
|
|
212
214
|
if (deps['@angular/core'] || deps.angular) return 'angular';
|
|
213
|
-
if (deps.next) return 'next';
|
|
214
215
|
if (deps.svelte) return 'svelte';
|
|
216
|
+
if (deps.astro || deps['astro-i18next'] || deps['@astrojs/i18n']) return 'astro';
|
|
217
|
+
if (deps['@remix-run/react'] || deps['remix-i18next']) return 'remix';
|
|
218
|
+
if (deps.gatsby || deps['gatsby-plugin-react-i18next']) return 'gatsby';
|
|
219
|
+
if (deps['@builder.io/qwik'] || deps['qwik-speak']) return 'qwik';
|
|
220
|
+
if (deps['solid-js'] || deps['@solid-primitives/i18n']) return 'solid';
|
|
221
|
+
if (deps['ember-source'] || deps['ember-intl']) return 'ember';
|
|
222
|
+
if (deps.nuxt || deps['@nuxtjs/i18n']) return 'nuxt';
|
|
223
|
+
|
|
224
|
+
const cargoTomlPath = path.join(projectRoot, 'Cargo.toml');
|
|
225
|
+
if (SecurityUtils.safeExistsSync(cargoTomlPath, projectRoot)) return 'rust';
|
|
226
|
+
|
|
227
|
+
const goModPath = path.join(projectRoot, 'go.mod');
|
|
228
|
+
if (SecurityUtils.safeExistsSync(goModPath, projectRoot)) return 'go';
|
|
215
229
|
|
|
216
230
|
return 'vanilla';
|
|
217
231
|
} catch (error) {
|
|
@@ -237,57 +251,110 @@ class ScannerCommand {
|
|
|
237
251
|
|
|
238
252
|
const frameworkSpecific = {
|
|
239
253
|
react: [
|
|
240
|
-
// React specific patterns - enhanced for i18next detection
|
|
241
254
|
/children:\s*["']([^"']{2,99})["']/g,
|
|
242
255
|
/dangerouslySetInnerHTML={{\s*__html:\s*["']([^"']{2,99})["']/g,
|
|
243
|
-
// JSX text content without translation
|
|
244
256
|
/>([^<{][^<>{]*[^}>])</g,
|
|
245
|
-
// Button text
|
|
246
257
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
247
|
-
|
|
248
|
-
/<
|
|
258
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
259
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g,
|
|
260
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*\{[']\s*([^'}]+\.[^'}]+)\s*[']/g
|
|
249
261
|
],
|
|
250
262
|
vue: [
|
|
251
|
-
// Vue specific patterns - enhanced for vue-i18n detection
|
|
252
263
|
/v-text=["']([^"']{2,99})["']/g,
|
|
253
264
|
/v-html=["']([^"']{2,99})["']/g,
|
|
254
|
-
// Vue template text
|
|
255
265
|
/>([^<{][^<>{]*[^}>])</g,
|
|
256
|
-
// Button text
|
|
257
266
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
258
|
-
|
|
259
|
-
|
|
267
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
268
|
+
/\$t\(["']([^"']{2,99})["']\)/g,
|
|
269
|
+
/v-t=["']([^"']{2,99})["']/g
|
|
260
270
|
],
|
|
261
271
|
angular: [
|
|
262
|
-
// Angular specific patterns - enhanced for ngx-translate detection
|
|
263
272
|
/\[innerHTML\]=["']([^"']{2,99})["']/g,
|
|
264
273
|
/\[textContent\]=["']([^"']{2,99})["']/g,
|
|
265
|
-
// Angular template text
|
|
266
274
|
/>([^<{][^<>{]*[^}>])</g,
|
|
267
|
-
// Button text
|
|
268
275
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
277
|
+
/i18n=["']([^"']{2,99})["']/g,
|
|
278
|
+
/\[attr\.title\]=["']([^"']{2,99})["']/g
|
|
279
|
+
],
|
|
280
|
+
next: [
|
|
281
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
282
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
283
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
284
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
285
|
+
],
|
|
286
|
+
svelte: [
|
|
287
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
288
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
289
|
+
/\$_\(["']([^"']{2,99})["']\)/g,
|
|
290
|
+
/t\.set\(["']([^"']{2,99})["']/g,
|
|
291
|
+
/\{#if[^}]*\}([^<]{2,99})\{\/if\}/g
|
|
292
|
+
],
|
|
293
|
+
astro: [
|
|
294
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
295
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
296
|
+
/t\(["']([^"']{2,99})["']\)/g
|
|
297
|
+
],
|
|
298
|
+
remix: [
|
|
299
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
300
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
301
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
302
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
303
|
+
],
|
|
304
|
+
qwik: [
|
|
305
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
306
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
307
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
308
|
+
/useTranslate\(\)/g
|
|
309
|
+
],
|
|
310
|
+
solid: [
|
|
311
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
312
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
313
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
314
|
+
/useI18n\(\)\.t\(["']([^"']{2,99})["']\)/g
|
|
315
|
+
],
|
|
316
|
+
ember: [
|
|
317
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
318
|
+
/\{\{t\s+["']([^"']{2,99})["']\s*\}\}/g,
|
|
319
|
+
/\{\{intl\.t\s+["']([^"']{2,99})["']\s*\}\}/g
|
|
320
|
+
],
|
|
321
|
+
gatsby: [
|
|
322
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
323
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
324
|
+
/<(?:Trans)[\s\S]*?\bi18nKey\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
271
325
|
],
|
|
272
326
|
django: [
|
|
273
|
-
// Django template patterns
|
|
274
327
|
/\{\%\s*trans\s+["']([^"']{2,99})["']\s*%\}/g,
|
|
275
328
|
/\{\%\s*blocktrans\s*%\}([^%]{2,99})\{\%\s*endblocktrans\s*%\}/g,
|
|
276
329
|
/{{\s*_["']([^"']{2,99})["']\s*}}/g,
|
|
277
330
|
/{{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g
|
|
278
331
|
],
|
|
279
332
|
flask: [
|
|
280
|
-
// Flask/Jinja2 template patterns
|
|
281
333
|
/\{\{\s*_["']([^"']{2,99})["']\s*}}/g,
|
|
282
334
|
/\{\{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g,
|
|
283
335
|
/\{\{\s*lazy_gettext\(["']([^"']{2,99})["']\)\s*}}/g
|
|
284
336
|
],
|
|
285
337
|
python: [
|
|
286
|
-
// Python source patterns
|
|
287
338
|
/gettext\(["']([^"']{2,99})["']\)/g,
|
|
288
339
|
/_\(["']([^"']{2,99})["']\)/g,
|
|
289
340
|
/gettext_lazy\(["']([^"']{2,99})["']\)/g,
|
|
290
341
|
/lazy_gettext\(["']([^"']{2,99})["']\)/g
|
|
342
|
+
],
|
|
343
|
+
rust: [
|
|
344
|
+
/bundle\.get_message\(\s*["']([^"']{2,99})["']\)/g,
|
|
345
|
+
/\.get_message\s*\(\s*["']([^"']{2,99})["']\)/g,
|
|
346
|
+
/ts!\s*\(\s*["']([^"']{2,99})["']/g,
|
|
347
|
+
/fluent!\s*\(\s*["']([^"']{2,99})["']/g
|
|
348
|
+
],
|
|
349
|
+
go: [
|
|
350
|
+
/i18n\.Translate\([^,]+,\s*["']([^"']{2,99})["']\)/g,
|
|
351
|
+
/i18n\.NewMessage\([^,]+,\s*["']([^"']{2,99})["']\)/g,
|
|
352
|
+
/t\.Get\([^,]+,\s*["']([^"']{2,99})["']\)/g
|
|
353
|
+
],
|
|
354
|
+
vanilla: [
|
|
355
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
356
|
+
/i18n\.t\(["']([^"']{2,99})["']\)/g,
|
|
357
|
+
/translate\(["']([^"']{2,99})["']\)/g
|
|
291
358
|
]
|
|
292
359
|
};
|
|
293
360
|
|
|
@@ -380,24 +447,63 @@ class ScannerCommand {
|
|
|
380
447
|
key: `ui.${key}`,
|
|
381
448
|
original: text,
|
|
382
449
|
translationKey: `t('ui.${key}')`,
|
|
383
|
-
frameworkSpecific: this.
|
|
450
|
+
frameworkSpecific: getFrameworkSuggestions(this.framework, text)
|
|
384
451
|
};
|
|
385
452
|
}
|
|
386
453
|
|
|
387
454
|
getFrameworkSpecific(text) {
|
|
455
|
+
const keySnippet = (str) => str.toLowerCase()
|
|
456
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
457
|
+
.replace(/\s+/g, '_')
|
|
458
|
+
.substring(0, 40);
|
|
388
459
|
const frameworks = {
|
|
389
460
|
react: {
|
|
390
461
|
hook: `const { t } = useTranslation();`,
|
|
391
|
-
usage: `{t('ui.${text
|
|
392
|
-
component: `<Trans i18nKey="ui.${text
|
|
462
|
+
usage: `{t('ui.${keySnippet(text)}')}`,
|
|
463
|
+
component: `<Trans i18nKey="ui.${keySnippet(text)}">${text}</Trans>`
|
|
464
|
+
},
|
|
465
|
+
next: {
|
|
466
|
+
hook: `const t = useTranslations();`,
|
|
467
|
+
usage: `{t('ui.${keySnippet(text)}')}`,
|
|
468
|
+
component: `<Trans i18nKey="ui.${keySnippet(text)}">${text}</Trans>`
|
|
393
469
|
},
|
|
394
470
|
vue: {
|
|
395
|
-
directive: `{{ $t('ui.${text
|
|
396
|
-
method: `this.$t('ui.${text
|
|
471
|
+
directive: `{{ $t('ui.${keySnippet(text)}') }}`,
|
|
472
|
+
method: `this.$t('ui.${keySnippet(text)}')`
|
|
397
473
|
},
|
|
398
474
|
angular: {
|
|
399
475
|
pipe: `{{ '${text}' | translate }}`,
|
|
400
|
-
service: `this.translateService.instant('ui.${text
|
|
476
|
+
service: `this.translateService.instant('ui.${keySnippet(text)}')`
|
|
477
|
+
},
|
|
478
|
+
svelte: {
|
|
479
|
+
store: `$_(('ui.${keySnippet(text)}'))`,
|
|
480
|
+
method: `t.set('ui.${keySnippet(text)}', '${text}')`
|
|
481
|
+
},
|
|
482
|
+
astro: {
|
|
483
|
+
import: `import { t } from 'astro-i18next';`,
|
|
484
|
+
usage: `{t('ui.${keySnippet(text)}')}`
|
|
485
|
+
},
|
|
486
|
+
remix: {
|
|
487
|
+
hook: `const { t } = useTranslation();`,
|
|
488
|
+
usage: `{t('ui.${keySnippet(text)}')}`,
|
|
489
|
+
server: `export const handle = i18next.handle;`
|
|
490
|
+
},
|
|
491
|
+
qwik: {
|
|
492
|
+
hook: `const t = useTranslate();`,
|
|
493
|
+
usage: `{t('ui.${keySnippet(text)}')}`
|
|
494
|
+
},
|
|
495
|
+
solid: {
|
|
496
|
+
hook: `const [t] = useI18n();`,
|
|
497
|
+
usage: `{t('ui.${keySnippet(text)}')}`
|
|
498
|
+
},
|
|
499
|
+
ember: {
|
|
500
|
+
template: `{{t 'ui.${keySnippet(text)}'}}`,
|
|
501
|
+
helper: `this.intl.t('ui.${keySnippet(text)}')`
|
|
502
|
+
},
|
|
503
|
+
gatsby: {
|
|
504
|
+
hook: `const { t } = useTranslation();`,
|
|
505
|
+
usage: `{t('ui.${keySnippet(text)}')}`,
|
|
506
|
+
plugin: `'gatsby-plugin-react-i18next'`
|
|
401
507
|
},
|
|
402
508
|
django: {
|
|
403
509
|
template: `{% trans '${text}' %}`,
|
|
@@ -413,6 +519,17 @@ class ScannerCommand {
|
|
|
413
519
|
gettext: `import gettext\ngettext.gettext('${text}')`,
|
|
414
520
|
underscore: `from gettext import gettext as _\n_('${text}')`,
|
|
415
521
|
lazy: `from gettext import gettext_lazy as _\n_('${text}')`
|
|
522
|
+
},
|
|
523
|
+
rust: {
|
|
524
|
+
fluent: `bundle.get_message("ui_${keySnippet(text)}")`,
|
|
525
|
+
gettext: `gettext("ui_${keySnippet(text)}")`
|
|
526
|
+
},
|
|
527
|
+
go: {
|
|
528
|
+
translate: `i18n.Translate("ui_${keySnippet(text)}")`,
|
|
529
|
+
message: `i18n.NewMessage("ui_${keySnippet(text)}")`
|
|
530
|
+
},
|
|
531
|
+
vanilla: {
|
|
532
|
+
generic: `t('ui.${keySnippet(text)}')`
|
|
416
533
|
}
|
|
417
534
|
};
|
|
418
535
|
|
|
@@ -433,7 +550,7 @@ class ScannerCommand {
|
|
|
433
550
|
}
|
|
434
551
|
|
|
435
552
|
const allResults = [];
|
|
436
|
-
const extensions = [
|
|
553
|
+
const extensions = [...SCANNER_EXTENSIONS];
|
|
437
554
|
|
|
438
555
|
const scanRecursive = (currentDir) => {
|
|
439
556
|
const items = SecurityUtils.safeReaddirSync(currentDir, path.dirname(currentDir), { withFileTypes: true });
|
|
@@ -586,7 +703,7 @@ class ScannerCommand {
|
|
|
586
703
|
} else if (fwPref && fwPref !== 'auto') {
|
|
587
704
|
this.framework = fwPref;
|
|
588
705
|
} else if (fwDetectEnabled) {
|
|
589
|
-
const detected =
|
|
706
|
+
const detected = detectProjectFramework(process.cwd());
|
|
590
707
|
this.framework = detected || fwFallback;
|
|
591
708
|
} else {
|
|
592
709
|
this.framework = fwFallback;
|
|
@@ -612,7 +729,7 @@ class ScannerCommand {
|
|
|
612
729
|
console.log(this.t('scanner.starting', { framework: this.framework }));
|
|
613
730
|
console.log(this.t('scanner.sourceDirectory', { sourceDir: this.sourceDir }));
|
|
614
731
|
|
|
615
|
-
const patterns =
|
|
732
|
+
const patterns = getFrameworkPatterns(this.framework);
|
|
616
733
|
const exclusions = this.config.exclude || ['node_modules', '.git', 'dist', 'build'];
|
|
617
734
|
const minLength = this.config.minLength || 3;
|
|
618
735
|
const maxLength = this.config.maxLength || 100;
|