i18ntk 4.5.3 → 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 +63 -3
- package/README.md +127 -530
- package/main/i18ntk-analyze.js +77 -42
- package/main/i18ntk-complete.js +56 -37
- package/main/i18ntk-report.js +22 -9
- package/main/i18ntk-scanner.js +151 -29
- package/main/i18ntk-sizing.js +9 -6
- package/main/i18ntk-summary.js +61 -39
- package/main/i18ntk-usage.js +29 -16
- package/main/i18ntk-validate.js +38 -24
- package/main/manage/commands/AnalyzeCommand.js +79 -44
- package/main/manage/commands/ScannerCommand.js +158 -35
- package/main/manage/commands/ValidateCommand.js +37 -23
- package/main/manage/index.js +13 -9
- package/main/manage/services/FrameworkDetectionService.js +67 -9
- package/package.json +27 -36
- package/utils/config-helper.js +42 -32
- package/utils/exit-codes.js +8 -6
- package/utils/framework-detector.js +317 -6
- package/utils/prompt-helper.js +1 -1
- package/utils/report-model.js +17 -4
- package/utils/usage-source.js +2 -1
|
@@ -15,7 +15,16 @@ const FRAMEWORK_COMPATIBILITY = {
|
|
|
15
15
|
'python': { minVersion: '3.6.0' },
|
|
16
16
|
'ruby': { minVersion: '2.7.0' },
|
|
17
17
|
'ruby-on-rails': { minVersion: '6.0.0' },
|
|
18
|
-
'go': { minVersion: '1.16.0' }
|
|
18
|
+
'go': { minVersion: '1.16.0' },
|
|
19
|
+
'rust': { minVersion: '1.65.0' },
|
|
20
|
+
'fluent': { minVersion: '0.16.0' },
|
|
21
|
+
'remix': { minVersion: '1.0.0' },
|
|
22
|
+
'gatsby': { minVersion: '4.0.0' },
|
|
23
|
+
'astro': { minVersion: '2.0.0' },
|
|
24
|
+
'qwik': { minVersion: '1.0.0' },
|
|
25
|
+
'ember-intl': { minVersion: '5.0.0' },
|
|
26
|
+
'react-native-localize': { minVersion: '2.0.0' },
|
|
27
|
+
'ionic': { minVersion: '6.0.0' }
|
|
19
28
|
};
|
|
20
29
|
|
|
21
30
|
// Define framework detection in order of specificity
|
|
@@ -245,10 +254,10 @@ const FRAMEWORKS = {
|
|
|
245
254
|
'test/tmp/**'
|
|
246
255
|
]
|
|
247
256
|
},
|
|
248
|
-
go: {
|
|
249
|
-
name: 'go',
|
|
250
|
-
deps: ['go-i18n', 'x-text'],
|
|
251
|
-
globs: ['**/*.go'],
|
|
257
|
+
go: {
|
|
258
|
+
name: 'go',
|
|
259
|
+
deps: ['go-i18n', 'x-text'],
|
|
260
|
+
globs: ['**/*.go'],
|
|
252
261
|
patterns: [
|
|
253
262
|
/i18n\.NewMessage\([^,]+,\s*["`]([^"`]+)["`]/g,
|
|
254
263
|
/i18n\.NewLocalizer\([^)]+\)\.MustLocalize\([^,]+,\s*["`]([^"`]+)["`]/g,
|
|
@@ -275,6 +284,27 @@ const FRAMEWORKS = {
|
|
|
275
284
|
]
|
|
276
285
|
},
|
|
277
286
|
|
|
287
|
+
rust: {
|
|
288
|
+
name: 'rust',
|
|
289
|
+
deps: ['fluent', 'fluent-bundle', 'fluent-resmgr', 'fluent-rs', 'intl-memoizer', 'unic-langid'],
|
|
290
|
+
globs: ['**/*.rs'],
|
|
291
|
+
patterns: [
|
|
292
|
+
/\bLocalization::new\(\s*&\[\s*([^\]]+)\s*\],\s*&\[\s*([^\]]+)\s*\]\)/g,
|
|
293
|
+
/bundle\.get_message\(\s*["`]([^"`]+)["`]/g,
|
|
294
|
+
/\.get_message\s*\(\s*["`]([^"`]+)["`]/g,
|
|
295
|
+
/\bformat!\s*\(\s*["`]([^"`]+)["`]/g
|
|
296
|
+
],
|
|
297
|
+
ignore: [
|
|
298
|
+
'target/**',
|
|
299
|
+
'**/*_test.rs',
|
|
300
|
+
'**/test/**',
|
|
301
|
+
'**/tests/**',
|
|
302
|
+
'**/examples/**',
|
|
303
|
+
'**/benches/**',
|
|
304
|
+
'**/migrations/**'
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
|
|
278
308
|
'ngx-translate': {
|
|
279
309
|
name: 'Angular ngx-translate',
|
|
280
310
|
deps: ['@ngx-translate/core', '@ngx-translate/http-loader'],
|
|
@@ -322,9 +352,287 @@ const FRAMEWORKS = {
|
|
|
322
352
|
/\buseI18n\(\s*\)\s*\.\s*t\(\s*['"`]([^'"`]+)['"`]/g,
|
|
323
353
|
/\bt\(\s*['"`]([^'"`]+)['"`]/g
|
|
324
354
|
]
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
remix: {
|
|
358
|
+
name: 'Remix i18n',
|
|
359
|
+
deps: ['remix-i18next', 'i18next-remix', '@remix-run/react'],
|
|
360
|
+
globs: ['app/**/*.{js,jsx,ts,tsx}'],
|
|
361
|
+
patterns: [
|
|
362
|
+
/\buseTranslation\(\s*['"`][^'"`]*['"`]?\s*\)/g,
|
|
363
|
+
/\bt\(\s*['"`]([^'"`]+)['"`]/g,
|
|
364
|
+
/\bgetServerSideTranslations\(/g
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
gatsby: {
|
|
369
|
+
name: 'Gatsby i18n',
|
|
370
|
+
deps: ['gatsby-plugin-react-i18next', '@herob191/gatsby-plugin-react-i18next', 'gatsby-plugin-intl', 'gatsby-theme-i18n'],
|
|
371
|
+
globs: ['src/**/*.{js,jsx,ts,tsx}'],
|
|
372
|
+
patterns: [
|
|
373
|
+
/\bt\(\s*['"`]([^'"`]+)['"`]/g,
|
|
374
|
+
/\buseTranslation\(\s*['"`][^'"`]*['"`]?\s*\)/g,
|
|
375
|
+
/\bgraphql\s*`[^`]*locales[^`]*`/g
|
|
376
|
+
]
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
astro: {
|
|
380
|
+
name: 'Astro i18n',
|
|
381
|
+
deps: ['astro-i18next', '@astrojs/i18n', 'astro-i18n', '@inox-tools/astro-i18n'],
|
|
382
|
+
globs: ['src/**/*.{astro,js,jsx,ts,tsx}'],
|
|
383
|
+
patterns: [
|
|
384
|
+
/\bt\(\s*['"`]([^'"`]+)['"`]/g,
|
|
385
|
+
/\bAstro\.i18n\(\s*['"`]([^'"`]+)['"`]/g,
|
|
386
|
+
/\buseTranslations\(\s*['"`][^'"`]*['"`]?\s*\)/g
|
|
387
|
+
]
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
qwik: {
|
|
391
|
+
name: 'Qwik i18n',
|
|
392
|
+
deps: ['qwik-speak', 'qwik-i18n', '@qwikdev/i18n'],
|
|
393
|
+
globs: ['src/**/*.{tsx,ts,jsx,js}'],
|
|
394
|
+
patterns: [
|
|
395
|
+
/\buseTranslate\(\s*\)/g,
|
|
396
|
+
/\bt\(\s*['"`]([^'"`]+)['"`]/g,
|
|
397
|
+
/\buseSpeak\(\s*\)/g
|
|
398
|
+
]
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
'ember-intl': {
|
|
402
|
+
name: 'Ember Intl',
|
|
403
|
+
deps: ['ember-intl', '@ember-intl/formatjs'],
|
|
404
|
+
globs: ['app/**/*.{js,ts,hbs}'],
|
|
405
|
+
patterns: [
|
|
406
|
+
/\bintl\.t\(\s*['"`]([^'"`]+)['"`]/g,
|
|
407
|
+
/\{\{\s*t\s+['"`]([^'"`]+)['"`]\s*\}\}/g,
|
|
408
|
+
/\{\{t\s+['"`]([^'"`]+)['"`]\s*\}\}/g
|
|
409
|
+
]
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
'react-native-localize': {
|
|
413
|
+
name: 'React Native Localize',
|
|
414
|
+
deps: ['react-native-localize', 'i18n-js', 'expo-localization'],
|
|
415
|
+
globs: ['**/*.{js,jsx,ts,tsx}'],
|
|
416
|
+
patterns: [
|
|
417
|
+
/\bi18n\.t\(\s*['"`]([^'"`]+)['"`]/g,
|
|
418
|
+
/\bt\(\s*['"`]([^'"`]+)['"`]/g,
|
|
419
|
+
/\btranslate\(\s*['"`]([^'"`]+)['"`]/g
|
|
420
|
+
]
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
ionic: {
|
|
424
|
+
name: 'Ionic i18n',
|
|
425
|
+
deps: ['@ionic/angular', 'ionic-react', '@ionic/vue'],
|
|
426
|
+
globs: ['src/**/*.{ts,tsx,html}'],
|
|
427
|
+
patterns: [
|
|
428
|
+
/\btranslateService\.instant\(\s*['"`]([^'"`]+)['"`]/g,
|
|
429
|
+
/\btranslateService\.get\(\s*['"`]([^'"`]+)['"`]/g,
|
|
430
|
+
/\|\s*translate\s*[^}]*\}\}/g
|
|
431
|
+
]
|
|
325
432
|
}
|
|
326
433
|
};
|
|
327
434
|
|
|
435
|
+
// ============================================================
|
|
436
|
+
// CENTRALIZED SHARED CONSTANTS — single source of truth
|
|
437
|
+
// All scanner/validator/extension code imports from here.
|
|
438
|
+
// ============================================================
|
|
439
|
+
|
|
440
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
441
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts', '.cjs', '.cts',
|
|
442
|
+
'.vue', '.svelte', '.astro', '.mdx', '.html', '.rs',
|
|
443
|
+
'.py', '.pyx', '.pyi', '.go', '.rb', '.java', '.php', '.hbs'
|
|
444
|
+
]);
|
|
445
|
+
|
|
446
|
+
const SCANNER_EXTENSIONS = new Set([
|
|
447
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.mts', '.cjs', '.cts',
|
|
448
|
+
'.vue', '.html', '.svelte', '.astro', '.mdx',
|
|
449
|
+
'.py', '.pyx', '.pyi', '.php', '.rb', '.go', '.rs', '.java'
|
|
450
|
+
]);
|
|
451
|
+
|
|
452
|
+
const EXCLUDE_DIRS = new Set([
|
|
453
|
+
'node_modules', '.git', '.hg', '.svn',
|
|
454
|
+
'.next', '.nuxt', '.output', '.astro', '.svelte-kit', '.cache', '__generated__',
|
|
455
|
+
'dist', 'build', 'coverage', 'out', 'target',
|
|
456
|
+
'vendor', 'tmp', 'temp', '.terraform'
|
|
457
|
+
]);
|
|
458
|
+
|
|
459
|
+
const SOURCE_DIRS = [
|
|
460
|
+
'src', 'app', 'lib', 'source', 'pages', 'routes', 'components',
|
|
461
|
+
'layouts', 'api', 'content', 'modules', 'views'
|
|
462
|
+
];
|
|
463
|
+
|
|
464
|
+
const FRAMEWORK_PATTERNS = {
|
|
465
|
+
react: [
|
|
466
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
467
|
+
/dangerouslySetInnerHTML={{\s*__html:\s*["']([^"']{2,99})["']/g,
|
|
468
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
469
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
470
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
471
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
472
|
+
],
|
|
473
|
+
next: [
|
|
474
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
475
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
476
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
477
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
478
|
+
],
|
|
479
|
+
vue: [
|
|
480
|
+
/v-text=["']([^"']{2,99})["']/g,
|
|
481
|
+
/v-html=["']([^"']{2,99})["']/g,
|
|
482
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
483
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
484
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
485
|
+
/\$t\(["']([^"']{2,99})["']\)/g,
|
|
486
|
+
/v-t=["']([^"']{2,99})["']/g
|
|
487
|
+
],
|
|
488
|
+
angular: [
|
|
489
|
+
/\[innerHTML\]=["']([^"']{2,99})["']/g,
|
|
490
|
+
/\[textContent\]=["']([^"']{2,99})["']/g,
|
|
491
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
492
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
493
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
494
|
+
/i18n=["']([^"']{2,99})["']/g,
|
|
495
|
+
/\[attr\.title\]=["']([^"']{2,99})["']/g
|
|
496
|
+
],
|
|
497
|
+
svelte: [
|
|
498
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
499
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
500
|
+
/\$_\(["']([^"']{2,99})["']\)/g,
|
|
501
|
+
/t\.set\(["']([^"']{2,99})["']/g,
|
|
502
|
+
/\{#if[^}]*\}([^<]{2,99})\{\/if\}/g
|
|
503
|
+
],
|
|
504
|
+
astro: [
|
|
505
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
506
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
507
|
+
/t\(["']([^"']{2,99})["']\)/g
|
|
508
|
+
],
|
|
509
|
+
remix: [
|
|
510
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
511
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
512
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
513
|
+
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
514
|
+
],
|
|
515
|
+
qwik: [
|
|
516
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
517
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
518
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
519
|
+
/useTranslate\(\)/g
|
|
520
|
+
],
|
|
521
|
+
solid: [
|
|
522
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
523
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
524
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
525
|
+
/useI18n\(\)\.t\(["']([^"']{2,99})["']\)/g
|
|
526
|
+
],
|
|
527
|
+
ember: [
|
|
528
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
529
|
+
/\{\{t\s+["']([^"']{2,99})["']\s*\}\}/g,
|
|
530
|
+
/\{\{intl\.t\s+["']([^"']{2,99})["']\s*\}\}/g
|
|
531
|
+
],
|
|
532
|
+
gatsby: [
|
|
533
|
+
/children:\s*["']([^"']{2,99})["']/g,
|
|
534
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
535
|
+
/<(?:Trans)[\s\S]*?\bi18nKey\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
536
|
+
],
|
|
537
|
+
django: [
|
|
538
|
+
/\{\%\s*trans\s+["']([^"']{2,99})["']\s*%\}/g,
|
|
539
|
+
/\{\%\s*blocktrans\s*%\}([^%]{2,99})\{\%\s*endblocktrans\s*%\}/g,
|
|
540
|
+
/{{\s*_["']([^"']{2,99})["']\s*}}/g,
|
|
541
|
+
/{{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g
|
|
542
|
+
],
|
|
543
|
+
flask: [
|
|
544
|
+
/\{\{\s*_["']([^"']{2,99})["']\s*}}/g,
|
|
545
|
+
/\{\{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g,
|
|
546
|
+
/\{\{\s*lazy_gettext\(["']([^"']{2,99})["']\)\s*}}/g
|
|
547
|
+
],
|
|
548
|
+
python: [
|
|
549
|
+
/gettext\(["']([^"']{2,99})["']\)/g,
|
|
550
|
+
/_\(["']([^"']{2,99})["']\)/g,
|
|
551
|
+
/gettext_lazy\(["']([^"']{2,99})["']\)/g,
|
|
552
|
+
/lazy_gettext\(["']([^"']{2,99})["']\)/g
|
|
553
|
+
],
|
|
554
|
+
rust: [
|
|
555
|
+
/bundle\.get_message\(\s*["']([^"']{2,99})["']\)/g,
|
|
556
|
+
/\.get_message\s*\(\s*["']([^"']{2,99})["']\)/g,
|
|
557
|
+
/ts!\s*\(\s*["']([^"']{2,99})["']/g,
|
|
558
|
+
/fluent!\s*\(\s*["']([^"']{2,99})["']/g
|
|
559
|
+
],
|
|
560
|
+
go: [
|
|
561
|
+
/i18n\.Translate\([^,]+,\s*["']([^"']{2,99})["']\)/g,
|
|
562
|
+
/i18n\.NewMessage\([^,]+,\s*["']([^"']{2,99})["']\)/g,
|
|
563
|
+
/t\.Get\([^,]+,\s*["']([^"']{2,99})["']\)/g
|
|
564
|
+
],
|
|
565
|
+
vanilla: [
|
|
566
|
+
/t\(["']([^"']{2,99})["']\)/g,
|
|
567
|
+
/i18n\.t\(["']([^"']{2,99})["']\)/g,
|
|
568
|
+
/translate\(["']([^"']{2,99})["']\)/g
|
|
569
|
+
]
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const FRAMEWORK_SUGGESTIONS = {
|
|
573
|
+
react: { hook: 'const { t } = useTranslation();', usage: "{t('ui.KEY')}", component: "<Trans i18nKey=\"ui.KEY\">text</Trans>" },
|
|
574
|
+
next: { hook: 'const t = useTranslations();', usage: "{t('ui.KEY')}", component: "<Trans i18nKey=\"ui.KEY\">text</Trans>" },
|
|
575
|
+
vue: { directive: "{{ $t('ui.KEY') }}", method: "this.$t('ui.KEY')" },
|
|
576
|
+
angular: { pipe: "{{ 'text' | translate }}", service: "this.translateService.instant('ui.KEY')" },
|
|
577
|
+
svelte: { store: "$_(('ui.KEY'))", method: "t.set('ui.KEY', 'text')" },
|
|
578
|
+
astro: { import: "import { t } from 'astro-i18next';", usage: "{t('ui.KEY')}" },
|
|
579
|
+
remix: { hook: 'const { t } = useTranslation();', usage: "{t('ui.KEY')}", server: 'export const handle = i18next.handle;' },
|
|
580
|
+
qwik: { hook: 'const t = useTranslate();', usage: "{t('ui.KEY')}" },
|
|
581
|
+
solid: { hook: 'const [t] = useI18n();', usage: "{t('ui.KEY')}" },
|
|
582
|
+
ember: { template: "{{t 'ui.KEY'}}", helper: "this.intl.t('ui.KEY')" },
|
|
583
|
+
gatsby: { hook: 'const { t } = useTranslation();', usage: "{t('ui.KEY')}", plugin: "'gatsby-plugin-react-i18next'" },
|
|
584
|
+
django: { template: "{% trans 'text' %}", python: "from django.utils.translation import gettext as _\n_('text')", model: "from django.utils.translation import gettext_lazy as _\n_('text')" },
|
|
585
|
+
flask: { template: "{{ _('text') }}", python: "from flask_babel import gettext as _\n_('text')", lazy: "from flask_babel import lazy_gettext as _\n_('text')" },
|
|
586
|
+
python: { gettext: "import gettext\ngettext.gettext('text')", underscore: "from gettext import gettext as _\n_('text')", lazy: "from gettext import gettext_lazy as _\n_('text')" },
|
|
587
|
+
rust: { fluent: 'bundle.get_message("ui_KEY")', gettext: 'gettext("ui_KEY")' },
|
|
588
|
+
go: { translate: 'i18n.Translate("ui_KEY")', message: 'i18n.NewMessage("ui_KEY")' },
|
|
589
|
+
vanilla: { generic: "t('ui.KEY')" }
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const WRAPPER_SKIP_PATTERNS = [
|
|
593
|
+
't(', 'tx(', 'i18n.t(', 'i18n.translate(', 'translate(',
|
|
594
|
+
'useI18n(', 'useTranslation(', '__', '__t', '$_(', '$t(',
|
|
595
|
+
'gettext(', 'gettext_lazy(', 'lazy_gettext(', 'pgettext(', 'ngettext('
|
|
596
|
+
];
|
|
597
|
+
|
|
598
|
+
function _keySnippet(text) {
|
|
599
|
+
return String(text || '').toLowerCase()
|
|
600
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
601
|
+
.replace(/\s+/g, '_')
|
|
602
|
+
.substring(0, 40);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function getFrameworkPatterns(framework) {
|
|
606
|
+
const base = [
|
|
607
|
+
/(?<![\w])["'`]([^"'`]{2,99})["'`]/g,
|
|
608
|
+
/`([^`]{2,99})`/g,
|
|
609
|
+
/>([^<]{2,99})</g,
|
|
610
|
+
/title=["']([^"']{2,99})["']/g,
|
|
611
|
+
/alt=["']([^"']{2,99})["']/g,
|
|
612
|
+
/placeholder=["']([^"']{2,99})["']/g
|
|
613
|
+
];
|
|
614
|
+
return [...base, ...(FRAMEWORK_PATTERNS[framework] || FRAMEWORK_PATTERNS.vanilla || [])];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function getFrameworkSuggestions(framework, text) {
|
|
618
|
+
const template = FRAMEWORK_SUGGESTIONS[framework] || FRAMEWORK_SUGGESTIONS.vanilla || {};
|
|
619
|
+
const result = {};
|
|
620
|
+
for (const [k, v] of Object.entries(template)) {
|
|
621
|
+
result[k] = v.replace(/ui\.KEY/g, `ui.${_keySnippet(text)}`).replace(/\btext\b/g, text);
|
|
622
|
+
}
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function getSourceExtensions() { return [...SOURCE_EXTENSIONS]; }
|
|
627
|
+
|
|
628
|
+
function getExcludeDirs() { return [...EXCLUDE_DIRS]; }
|
|
629
|
+
|
|
630
|
+
function detectProjectFramework(projectRoot) {
|
|
631
|
+
const detected = detectFramework(projectRoot);
|
|
632
|
+
if (detected && detected.id) return detected.id;
|
|
633
|
+
return 'vanilla';
|
|
634
|
+
}
|
|
635
|
+
|
|
328
636
|
/**
|
|
329
637
|
* Detects the i18n framework being used in the project
|
|
330
638
|
* @param {string} projectRoot - Path to the project root
|
|
@@ -414,4 +722,7 @@ function detectFramework(projectRoot) {
|
|
|
414
722
|
}
|
|
415
723
|
}
|
|
416
724
|
|
|
417
|
-
module.exports = { detectFramework, FRAMEWORKS
|
|
725
|
+
module.exports = { detectFramework, FRAMEWORKS, FRAMEWORK_COMPATIBILITY,
|
|
726
|
+
SOURCE_EXTENSIONS, SCANNER_EXTENSIONS, EXCLUDE_DIRS, SOURCE_DIRS,
|
|
727
|
+
FRAMEWORK_PATTERNS, FRAMEWORK_SUGGESTIONS, WRAPPER_SKIP_PATTERNS,
|
|
728
|
+
getFrameworkPatterns, getFrameworkSuggestions, getSourceExtensions, getExcludeDirs, detectProjectFramework };
|
package/utils/prompt-helper.js
CHANGED
|
@@ -7,7 +7,7 @@ function isInteractive(opts={}) {
|
|
|
7
7
|
envManager.get('npm_config_loglevel') === 'silent';
|
|
8
8
|
if (opts.noPrompt) return false;
|
|
9
9
|
if (envSilent) return false;
|
|
10
|
-
return process.stdin.isTTY === true;
|
|
10
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
class NoopPrompt {
|
package/utils/report-model.js
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { getSourceExtensions, getExcludeDirs } = require('./framework-detector');
|
|
5
6
|
const SecurityUtils = require('./security');
|
|
6
7
|
|
|
8
|
+
const EXCLUDE_DIRS = new Set(getExcludeDirs());
|
|
9
|
+
const SOURCE_EXTENSIONS = getSourceExtensions();
|
|
10
|
+
|
|
7
11
|
const ISSUE_TYPES = new Set([
|
|
8
12
|
'missing_key',
|
|
9
13
|
'unused_key',
|
|
@@ -265,7 +269,7 @@ function discoverLocaleFiles(localesDir) {
|
|
|
265
269
|
function walk(dir, extensions, acc = [], basePath = process.cwd()) {
|
|
266
270
|
if (!SecurityUtils.safeExistsSync(dir, basePath)) return acc;
|
|
267
271
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
268
|
-
if (
|
|
272
|
+
if (EXCLUDE_DIRS.has(entry.name)) continue;
|
|
269
273
|
const entryPath = path.join(dir, entry.name);
|
|
270
274
|
if (entry.isDirectory()) walk(entryPath, extensions, acc, basePath);
|
|
271
275
|
else if (entry.isFile() && extensions.includes(path.extname(entry.name))) acc.push(entryPath);
|
|
@@ -290,7 +294,7 @@ function scanSourceFiles(sourceDir, availableKeys) {
|
|
|
290
294
|
const hardcodedTexts = [];
|
|
291
295
|
const translationValueIndex = new Map();
|
|
292
296
|
const basePath = path.resolve(sourceDir);
|
|
293
|
-
const files = walk(sourceDir,
|
|
297
|
+
const files = walk(sourceDir, SOURCE_EXTENSIONS, [], basePath);
|
|
294
298
|
|
|
295
299
|
for (const file of files) {
|
|
296
300
|
const content = SecurityUtils.safeReadFileSync(file, basePath, 'utf8');
|
|
@@ -339,7 +343,13 @@ function findKeyByValue(_text, _availableKeys) {
|
|
|
339
343
|
function extractPlaceholders(value) {
|
|
340
344
|
const placeholders = new Set();
|
|
341
345
|
const text = String(value || '');
|
|
342
|
-
const patterns = [
|
|
346
|
+
const patterns = [
|
|
347
|
+
/\{\{\s*([\w.-]+)\s*\}\}/g,
|
|
348
|
+
/%\{([\w.-]+)\}/g,
|
|
349
|
+
/\{([\w.-]+)\}/g,
|
|
350
|
+
/\$\{([\w.-]+)\}/g,
|
|
351
|
+
/\$([a-zA-Z_][a-zA-Z0-9_-]+)/g
|
|
352
|
+
];
|
|
343
353
|
for (const pattern of patterns) {
|
|
344
354
|
let match;
|
|
345
355
|
while ((match = pattern.exec(text))) placeholders.add(match[1]);
|
|
@@ -356,7 +366,10 @@ function looksLikelyUntranslated(source, target, locale) {
|
|
|
356
366
|
}
|
|
357
367
|
|
|
358
368
|
function normalizeText(value) {
|
|
359
|
-
return String(value || '')
|
|
369
|
+
return String(value || '')
|
|
370
|
+
.replace(/\{\{[^}]+\}\}|\{[^}]+\}|%\{[^}]+\}/g, '')
|
|
371
|
+
.replace(/\$[a-zA-Z_][a-zA-Z0-9_-]*(?:\s*\{\s*[^}]+\})?/g, '')
|
|
372
|
+
.trim().toLowerCase();
|
|
360
373
|
}
|
|
361
374
|
|
|
362
375
|
function getExpansionPercent(source, target) {
|
package/utils/usage-source.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const SecurityUtils = require('./security');
|
|
5
|
+
const { SOURCE_DIRS } = require('./framework-detector');
|
|
5
6
|
|
|
6
|
-
const DEFAULT_SOURCE_DIRS =
|
|
7
|
+
const DEFAULT_SOURCE_DIRS = SOURCE_DIRS;
|
|
7
8
|
|
|
8
9
|
function resolveUsageSourceDir(options = {}) {
|
|
9
10
|
const projectRoot = path.resolve(options.projectRoot || process.cwd());
|