i18ntk 4.6.1 → 4.7.1
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 +44 -0
- package/README.md +17 -11
- package/main/i18ntk-init.js +9 -7
- package/main/i18ntk-ui.js +1 -1
- package/main/i18ntk-usage.js +2 -2
- package/main/i18ntk-validate.js +1 -1
- package/main/manage/commands/ValidateCommand.js +1 -1
- package/main/manage/index.js +16 -20
- package/main/manage/services/ConfigurationService.js +1 -1
- package/main/manage/services/FrameworkDetectionService.js +1 -1
- package/main/manage/services/UsageService.js +2 -2
- package/package.json +12 -9
- package/settings/settings-manager.js +19 -3
- package/ui-locales/ar.json +2636 -0
- package/ui-locales/cs.json +2636 -0
- package/ui-locales/de.json +17 -1
- package/ui-locales/el.json +2636 -0
- package/ui-locales/en.json +17 -1
- package/ui-locales/es.json +17 -1
- package/ui-locales/fr.json +17 -1
- package/ui-locales/he.json +2636 -0
- package/ui-locales/hi.json +2636 -0
- package/ui-locales/hu.json +2636 -0
- package/ui-locales/it.json +2636 -0
- package/ui-locales/ja.json +17 -1
- package/ui-locales/ko.json +2636 -0
- package/ui-locales/nl.json +2636 -0
- package/ui-locales/pl.json +2636 -0
- package/ui-locales/pt.json +2636 -0
- package/ui-locales/ru.json +17 -1
- package/ui-locales/sv.json +2636 -0
- package/ui-locales/th.json +2636 -0
- package/ui-locales/tr.json +2636 -0
- package/ui-locales/uk.json +2636 -0
- package/ui-locales/vi.json +2636 -0
- package/ui-locales/zh.json +17 -1
- package/utils/config-helper.js +2 -2
- package/utils/config-manager.js +3 -4
- package/utils/env-manager.js +1 -1
- package/utils/framework-detector.js +252 -65
- package/utils/locale-optimizer.js +1 -1
- package/utils/report-model.js +16 -5
package/ui-locales/zh.json
CHANGED
|
@@ -2057,7 +2057,23 @@
|
|
|
2057
2057
|
"fr": "法语",
|
|
2058
2058
|
"ru": "俄语",
|
|
2059
2059
|
"ja": "日本人",
|
|
2060
|
-
"zh": "中国人"
|
|
2060
|
+
"zh": "中国人",
|
|
2061
|
+
"it": "意大利语",
|
|
2062
|
+
"pt": "葡萄牙语",
|
|
2063
|
+
"nl": "荷兰语",
|
|
2064
|
+
"pl": "波兰语",
|
|
2065
|
+
"sv": "瑞典语",
|
|
2066
|
+
"uk": "乌克兰语",
|
|
2067
|
+
"cs": "捷克语",
|
|
2068
|
+
"tr": "土耳其语",
|
|
2069
|
+
"ko": "韩语",
|
|
2070
|
+
"ar": "阿拉伯语",
|
|
2071
|
+
"hi": "印地语",
|
|
2072
|
+
"th": "泰语",
|
|
2073
|
+
"vi": "越南语",
|
|
2074
|
+
"he": "希伯来语",
|
|
2075
|
+
"el": "希腊语",
|
|
2076
|
+
"hu": "匈牙利语"
|
|
2061
2077
|
},
|
|
2062
2078
|
"viewAll": {
|
|
2063
2079
|
"title": "查看所有设置"
|
package/utils/config-helper.js
CHANGED
|
@@ -534,7 +534,7 @@ async function initializeSourceFiles(sourceDir, sourceLang) {
|
|
|
534
534
|
SecurityUtils.safeWriteFileSync(sourceFile, JSON.stringify(defaultContent, null, 2), sourceDir, 'utf8');
|
|
535
535
|
|
|
536
536
|
// Create directories for supported languages
|
|
537
|
-
const supportedLanguages = ['es', 'fr', 'de', 'ja', 'ru', 'zh', 'pt'];
|
|
537
|
+
const supportedLanguages = ['es', 'fr', 'de', 'ja', 'ru', 'zh', 'pt', 'it', 'nl', 'pl', 'sv', 'uk', 'cs', 'tr', 'ko', 'ar', 'hi', 'th', 'vi', 'he', 'el', 'hu'];
|
|
538
538
|
|
|
539
539
|
supportedLanguages.forEach(lang => {
|
|
540
540
|
const langFile = path.join(sourceDir, `${lang}.json`);
|
|
@@ -564,7 +564,7 @@ async function initializeSourceFiles(sourceDir, sourceLang) {
|
|
|
564
564
|
sourceDir: sourceDir,
|
|
565
565
|
outputDir: "./i18ntk-reports",
|
|
566
566
|
defaultLanguage: sourceLang,
|
|
567
|
-
supportedLanguages: [sourceLang, 'es', 'fr', 'de', 'ja', 'ru', 'zh', 'pt'],
|
|
567
|
+
supportedLanguages: [sourceLang, 'es', 'fr', 'de', 'ja', 'ru', 'zh', 'pt', 'it', 'nl', 'pl', 'sv', 'uk', 'cs', 'tr', 'ko', 'ar', 'hi', 'th', 'vi', 'he', 'el', 'hu'],
|
|
568
568
|
setup: {
|
|
569
569
|
completed: true,
|
|
570
570
|
completedAt: new Date().toISOString(),
|
package/utils/config-manager.js
CHANGED
|
@@ -106,10 +106,9 @@ const DEFAULT_CONFIG = {
|
|
|
106
106
|
"setupId": null
|
|
107
107
|
},
|
|
108
108
|
"framework": {
|
|
109
|
-
"preference": "auto",
|
|
110
|
-
"fallback": "vanilla",
|
|
111
|
-
"detect": true
|
|
112
|
-
"supported": ["react", "vue", "angular", "svelte", "i18next", "nuxt", "next", "vanilla"]
|
|
109
|
+
"preference": "auto",
|
|
110
|
+
"fallback": "vanilla",
|
|
111
|
+
"detect": true
|
|
113
112
|
},
|
|
114
113
|
"scriptDirectories": {
|
|
115
114
|
"init": null,
|
package/utils/env-manager.js
CHANGED
|
@@ -71,7 +71,7 @@ const ALLOWED_ENV_VARS = {
|
|
|
71
71
|
// UI and interaction
|
|
72
72
|
'I18NTK_UI_LANGUAGE': {
|
|
73
73
|
default: 'en',
|
|
74
|
-
validate: (value) => ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(value.toLowerCase()),
|
|
74
|
+
validate: (value) => ['en', 'de', 'es', 'fr', 'it', 'pt', 'nl', 'pl', 'sv', 'uk', 'cs', 'tr', 'ru', 'ja', 'ko', 'zh', 'ar', 'hi', 'th', 'vi', 'he', 'el', 'hu'].includes(value.toLowerCase()),
|
|
75
75
|
transform: (value) => value.toLowerCase()
|
|
76
76
|
},
|
|
77
77
|
|
|
@@ -22,9 +22,17 @@ const FRAMEWORK_COMPATIBILITY = {
|
|
|
22
22
|
'gatsby': { minVersion: '4.0.0' },
|
|
23
23
|
'astro': { minVersion: '2.0.0' },
|
|
24
24
|
'qwik': { minVersion: '1.0.0' },
|
|
25
|
+
'nuxt': { minVersion: '7.0.0' },
|
|
26
|
+
'nuxt-i18n': { minVersion: '7.0.0' },
|
|
27
|
+
'next-intl': { minVersion: '2.0.0' },
|
|
28
|
+
'ngx-translate': { minVersion: '13.0.0' },
|
|
29
|
+
'svelte-i18n': { minVersion: '3.0.0' },
|
|
30
|
+
'solid-i18n': { minVersion: '1.0.0' },
|
|
25
31
|
'ember-intl': { minVersion: '5.0.0' },
|
|
26
32
|
'react-native-localize': { minVersion: '2.0.0' },
|
|
27
|
-
'ionic': { minVersion: '6.0.0' }
|
|
33
|
+
'ionic': { minVersion: '6.0.0' },
|
|
34
|
+
'expo': { minVersion: '48.0.0' },
|
|
35
|
+
'vanilla': { minVersion: '1.0.0' }
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
// Define framework detection in order of specificity
|
|
@@ -462,6 +470,13 @@ const SOURCE_DIRS = [
|
|
|
462
470
|
];
|
|
463
471
|
|
|
464
472
|
const FRAMEWORK_PATTERNS = {
|
|
473
|
+
'i18ntk-runtime': [
|
|
474
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
475
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
476
|
+
/i18n\.t\(["']([^"']{2,99})["']\)/g,
|
|
477
|
+
/useI18n\(\)\.t\(["']([^"']{2,99})["']\)/g,
|
|
478
|
+
/<t\s[\s\S]*?\bmessage\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
479
|
+
],
|
|
465
480
|
react: [
|
|
466
481
|
/children:\s*["']([^"']{2,99})["']/g,
|
|
467
482
|
/dangerouslySetInnerHTML={{\s*__html:\s*["']([^"']{2,99})["']/g,
|
|
@@ -476,6 +491,14 @@ const FRAMEWORK_PATTERNS = {
|
|
|
476
491
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
477
492
|
/<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
478
493
|
],
|
|
494
|
+
nuxt: [
|
|
495
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
496
|
+
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
497
|
+
/\$t\(["']([^"']{2,99})["']\)/g,
|
|
498
|
+
/\$tc\(["']([^"']{2,99})["']\)/g,
|
|
499
|
+
/v-t=["']([^"']{2,99})["']/g,
|
|
500
|
+
/\blocalePath\(\s*\)/g
|
|
501
|
+
],
|
|
479
502
|
vue: [
|
|
480
503
|
/v-text=["']([^"']{2,99})["']/g,
|
|
481
504
|
/v-html=["']([^"']{2,99})["']/g,
|
|
@@ -562,6 +585,68 @@ const FRAMEWORK_PATTERNS = {
|
|
|
562
585
|
/i18n\.NewMessage\([^,]+,\s*["']([^"']{2,99})["']\)/g,
|
|
563
586
|
/t\.Get\([^,]+,\s*["']([^"']{2,99})["']\)/g
|
|
564
587
|
],
|
|
588
|
+
lingui: [
|
|
589
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
590
|
+
/(?<![\w$.])t\s*\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
591
|
+
/_\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
592
|
+
/<Translate\s[\s\S]*?\bid\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
593
|
+
],
|
|
594
|
+
formatjs: [
|
|
595
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
596
|
+
/formatMessage\(\s*\{\s*id:\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
597
|
+
/<FormattedMessage\s[\s\S]*?\bid\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
598
|
+
],
|
|
599
|
+
'ngx-translate': [
|
|
600
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
601
|
+
/\|\s*translate\s*[^}]*\}\}/g,
|
|
602
|
+
/translateService\.instant\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
603
|
+
/translateService\.get\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
604
|
+
/translate\s*=\s*["'`]([^"'`]{2,99})["'`]/g,
|
|
605
|
+
/\[innerHTML\]=["']([^"']{2,99})["']/g
|
|
606
|
+
],
|
|
607
|
+
'next-intl': [
|
|
608
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
609
|
+
/t\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
610
|
+
/\buseTranslations\(\s*()\)/g
|
|
611
|
+
],
|
|
612
|
+
'svelte-i18n': [
|
|
613
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
614
|
+
/\$_\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
615
|
+
/\$_\w*\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
616
|
+
/\bt\.get\(\s*['"`]([^'"`]{2,99})['"`]/g
|
|
617
|
+
],
|
|
618
|
+
'solid-i18n': [
|
|
619
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
620
|
+
/\buseI18n\(\s*\)\s*\.\s*t\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
621
|
+
/t\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
622
|
+
/<Translate\s[\s\S]*?\bid\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
|
|
623
|
+
],
|
|
624
|
+
fastapi: [
|
|
625
|
+
/_\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
626
|
+
/i18n\.t\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
627
|
+
/\{\{\s*_['"]([^'"]{2,99})['"]\s*\}\}/g,
|
|
628
|
+
/\{\{\s*gettext\(['"`]([^'"`]{2,99})['"`]\)\s*\}\}/g
|
|
629
|
+
],
|
|
630
|
+
'ruby-on-rails': [
|
|
631
|
+
/I18n\.t\(\s*['"]([^'"]{2,99})['"]/g,
|
|
632
|
+
/I18n\.translate\(\s*['"]([^'"]{2,99})['"]/g,
|
|
633
|
+
/I18n\.l\(\s*['"]([^'"]{2,99})['"]/g,
|
|
634
|
+
/<%= t\(['"]([^'"]{2,99})['"]/g,
|
|
635
|
+
/<%= I18n\.t\(['"]([^'"]{2,99})['"]/g,
|
|
636
|
+
/(?<![\w$.])t\s*\(\s*['"]([^'"]{2,99})['"]/g
|
|
637
|
+
],
|
|
638
|
+
'react-native-localize': [
|
|
639
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
640
|
+
/\bi18n\.t\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
641
|
+
/\bt\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
642
|
+
/\btranslate\(\s*['"`]([^'"`]{2,99})['"`]/g
|
|
643
|
+
],
|
|
644
|
+
ionic: [
|
|
645
|
+
/>([^<{][^<>{]*[^}>])</g,
|
|
646
|
+
/\|\s*translate\s*[^}]*\}\}/g,
|
|
647
|
+
/translateService\.instant\(\s*['"`]([^'"`]{2,99})['"`]/g,
|
|
648
|
+
/translateService\.get\(\s*['"`]([^'"`]{2,99})['"`]/g
|
|
649
|
+
],
|
|
565
650
|
vanilla: [
|
|
566
651
|
/t\(["']([^"']{2,99})["']\)/g,
|
|
567
652
|
/i18n\.t\(["']([^"']{2,99})["']\)/g,
|
|
@@ -581,6 +666,18 @@ const FRAMEWORK_SUGGESTIONS = {
|
|
|
581
666
|
solid: { hook: 'const [t] = useI18n();', usage: "{t('ui.KEY')}" },
|
|
582
667
|
ember: { template: "{{t 'ui.KEY'}}", helper: "this.intl.t('ui.KEY')" },
|
|
583
668
|
gatsby: { hook: 'const { t } = useTranslation();', usage: "{t('ui.KEY')}", plugin: "'gatsby-plugin-react-i18next'" },
|
|
669
|
+
nuxt: { directive: "{{ $t('ui.KEY') }}", method: "this.$t('ui.KEY')", component: "<NuxtLink :to=\"localePath('ui.KEY')\">" },
|
|
670
|
+
'i18ntk-runtime': { hook: 'const { t } = useI18n();', usage: "{t('ui.KEY')}", component: "<t message=\"ui.KEY\">text</t>" },
|
|
671
|
+
lingui: { hook: 'import { t } from \"@lingui/macro\";', usage: "{t('ui.KEY')}", component: "<Trans id=\"ui.KEY\">text</Trans>" },
|
|
672
|
+
formatjs: { hook: 'import { FormattedMessage, useIntl } from \"react-intl\";', usage: "intl.formatMessage({ id: 'ui.KEY' })", component: "<FormattedMessage id=\"ui.KEY\" />" },
|
|
673
|
+
'ngx-translate': { pipe: "{{ 'ui.KEY' | translate }}", service: "this.translateService.instant('ui.KEY')" },
|
|
674
|
+
'next-intl': { hook: 'const t = useTranslations();', usage: "{t('ui.KEY')}" },
|
|
675
|
+
'svelte-i18n': { store: "$_('ui.KEY')", method: "t.get('ui.KEY')" },
|
|
676
|
+
'solid-i18n': { hook: 'const [t] = useI18n();', usage: "{t('ui.KEY')}", component: "<Translate id=\"ui.KEY\" />" },
|
|
677
|
+
fastapi: { python: "from fastapi_i18n import _\n_('text')", template: "{{ _('text') }}" },
|
|
678
|
+
'ruby-on-rails': { helper: "t('ui.KEY')", method: "I18n.t('ui.KEY')", template: "<%= t('ui.KEY') %>" },
|
|
679
|
+
'react-native-localize': { hook: 'import { t } from \"i18n-js\";', usage: "{t('ui.KEY')}" },
|
|
680
|
+
ionic: { pipe: "{{ 'ui.KEY' | translate }}", service: "this.translateService.instant('ui.KEY')" },
|
|
584
681
|
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
682
|
flask: { template: "{{ _('text') }}", python: "from flask_babel import gettext as _\n_('text')", lazy: "from flask_babel import lazy_gettext as _\n_('text')" },
|
|
586
683
|
python: { gettext: "import gettext\ngettext.gettext('text')", underscore: "from gettext import gettext as _\n_('text')", lazy: "from gettext import gettext_lazy as _\n_('text')" },
|
|
@@ -591,8 +688,13 @@ const FRAMEWORK_SUGGESTIONS = {
|
|
|
591
688
|
|
|
592
689
|
const WRAPPER_SKIP_PATTERNS = [
|
|
593
690
|
't(', 'tx(', 'i18n.t(', 'i18n.translate(', 'translate(',
|
|
594
|
-
'useI18n(', 'useTranslation(', '__', '__t', '$_(', '$t(',
|
|
595
|
-
'gettext(', 'gettext_lazy(', 'lazy_gettext(', 'pgettext(', 'ngettext('
|
|
691
|
+
'useI18n(', 'useTranslation(', '__', '__t', '$_(', '$t(', '_(',
|
|
692
|
+
'$tc(', 'gettext(', 'gettext_lazy(', 'lazy_gettext(', 'pgettext(', 'ngettext(',
|
|
693
|
+
'I18n.t(', 'I18n.translate(', 'I18n.l(', 'I18n.localize(',
|
|
694
|
+
't.get(', 'useTranslate(', 'useSpeak(', 'withTranslation(',
|
|
695
|
+
'formatMessage(', 'bundle.get_message(', 'i18n.NewMessage(',
|
|
696
|
+
'i18n.Translate(', '_l(', '_n(', 't.set(', 'fluent!', 'ts!',
|
|
697
|
+
'translateService.instant(', 'translateService.get('
|
|
596
698
|
];
|
|
597
699
|
|
|
598
700
|
function _keySnippet(text) {
|
|
@@ -643,83 +745,168 @@ function detectFramework(projectRoot) {
|
|
|
643
745
|
throw new Error('Invalid project root path');
|
|
644
746
|
}
|
|
645
747
|
|
|
646
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
647
748
|
const detectedFrameworks = [];
|
|
648
749
|
|
|
649
|
-
//
|
|
650
|
-
|
|
651
|
-
|
|
750
|
+
// Phase 1: Check package.json for Node.js-based i18n frameworks
|
|
751
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
752
|
+
if (SecurityUtils.safeExistsSync(packageJsonPath, projectRoot)) {
|
|
753
|
+
try {
|
|
754
|
+
const packageJsonContent = SecurityUtils.safeReadFileSync(packageJsonPath, projectRoot, 'utf8');
|
|
755
|
+
if (packageJsonContent) {
|
|
756
|
+
const packageJson = SecurityUtils.safeParseJSON(packageJsonContent);
|
|
757
|
+
if (packageJson) {
|
|
758
|
+
const deps = {
|
|
759
|
+
...(packageJson.dependencies || {}),
|
|
760
|
+
...(packageJson.devDependencies || {}),
|
|
761
|
+
...(packageJson.peerDependencies || {})
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
const sortedFrameworks = Object.entries(FRAMEWORKS).sort((a, b) =>
|
|
765
|
+
(b[1].priority || 0) - (a[1].priority || 0)
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
for (const [id, framework] of sortedFrameworks) {
|
|
769
|
+
try {
|
|
770
|
+
const frameworkDeps = framework.deps || framework.dependencies || [];
|
|
771
|
+
const hasAnyDep = frameworkDeps.some(dep => dep in deps);
|
|
772
|
+
if (hasAnyDep) {
|
|
773
|
+
const mainDep = frameworkDeps[0];
|
|
774
|
+
const frameworkInfo = {
|
|
775
|
+
id, name: framework.name || id, description: framework.description,
|
|
776
|
+
confidence: id === 'i18ntk-runtime' ? 0.95 : 0.9,
|
|
777
|
+
version: deps[mainDep] || '', priority: framework.priority || 0
|
|
778
|
+
};
|
|
779
|
+
detectedFrameworks.push(frameworkInfo);
|
|
780
|
+
}
|
|
781
|
+
} catch (_) { /* skip */ }
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
} catch (_) { /* skip */ }
|
|
652
786
|
}
|
|
653
787
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
788
|
+
// Phase 2: Check Python project files (no package.json or no Node matches)
|
|
789
|
+
if (detectedFrameworks.length === 0) {
|
|
790
|
+
const pyProjectFiles = ['requirements.txt', 'setup.py', 'setup.cfg', 'pyproject.toml', 'Pipfile', 'Pipfile.lock'];
|
|
791
|
+
const hasPyProject = pyProjectFiles.some(f => SecurityUtils.safeExistsSync(path.join(projectRoot, f), projectRoot));
|
|
792
|
+
if (hasPyProject) {
|
|
793
|
+
const pyDeps = readPyProjectDeps(projectRoot);
|
|
794
|
+
const pyFrameworks = ['django', 'flask', 'fastapi', 'python'];
|
|
795
|
+
for (const id of pyFrameworks) {
|
|
796
|
+
const framework = FRAMEWORKS[id];
|
|
797
|
+
if (!framework) continue;
|
|
798
|
+
const frameworkDeps = framework.deps || [];
|
|
799
|
+
const hasAnyDep = frameworkDeps.some(dep => pyDeps.includes(dep.toLowerCase().replace(/[_-]/g, '')));
|
|
800
|
+
if (hasAnyDep) {
|
|
801
|
+
detectedFrameworks.push({
|
|
802
|
+
id, name: framework.name || id, description: framework.description,
|
|
803
|
+
confidence: 0.8, version: '', priority: framework.priority || 0
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (detectedFrameworks.length === 0 && hasPyProject) {
|
|
808
|
+
detectedFrameworks.push({
|
|
809
|
+
id: 'python', name: 'Python', description: 'Python project with gettext/i18n',
|
|
810
|
+
confidence: 0.5, version: '', priority: 0
|
|
811
|
+
});
|
|
812
|
+
}
|
|
663
813
|
}
|
|
814
|
+
}
|
|
664
815
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
};
|
|
670
|
-
|
|
671
|
-
// Sort frameworks by priority (highest first)
|
|
672
|
-
const sortedFrameworks = Object.entries(FRAMEWORKS).sort((a, b) =>
|
|
673
|
-
(b[1].priority || 0) - (a[1].priority || 0)
|
|
674
|
-
);
|
|
675
|
-
|
|
676
|
-
// Check each framework's dependencies
|
|
677
|
-
for (const [id, framework] of sortedFrameworks) {
|
|
816
|
+
// Phase 3: Check Rust project (Cargo.toml)
|
|
817
|
+
if (detectedFrameworks.length === 0) {
|
|
818
|
+
const cargoPath = path.join(projectRoot, 'Cargo.toml');
|
|
819
|
+
if (SecurityUtils.safeExistsSync(cargoPath, projectRoot)) {
|
|
678
820
|
try {
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
description: framework.description,
|
|
688
|
-
confidence: 0.9, // Base confidence when dependencies are found
|
|
689
|
-
version: deps[mainDep] || '',
|
|
690
|
-
priority: framework.priority || 0
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
// Boost confidence for i18ntk-runtime
|
|
694
|
-
if (id === 'i18ntk-runtime') {
|
|
695
|
-
frameworkInfo.confidence = 0.95;
|
|
821
|
+
const cargoContent = SecurityUtils.safeReadFileSync(cargoPath, projectRoot, 'utf8');
|
|
822
|
+
if (cargoContent) {
|
|
823
|
+
const hasRustI18n = /fluent|rust-i18n|gettext/.test(cargoContent);
|
|
824
|
+
if (hasRustI18n || /^\[package\]/m.test(cargoContent)) {
|
|
825
|
+
detectedFrameworks.push({
|
|
826
|
+
id: 'rust', name: 'Rust', description: 'Rust project with i18n',
|
|
827
|
+
confidence: hasRustI18n ? 0.85 : 0.4, version: '', priority: 0
|
|
828
|
+
});
|
|
696
829
|
}
|
|
697
|
-
|
|
698
|
-
detectedFrameworks.push(frameworkInfo);
|
|
699
830
|
}
|
|
700
|
-
} catch (
|
|
701
|
-
console.warn(`Error checking framework ${id}:`, error.message);
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
831
|
+
} catch (_) { /* skip */ }
|
|
704
832
|
}
|
|
833
|
+
}
|
|
705
834
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
835
|
+
// Phase 4: Check Go project (go.mod)
|
|
836
|
+
if (detectedFrameworks.length === 0) {
|
|
837
|
+
const goModPath = path.join(projectRoot, 'go.mod');
|
|
838
|
+
if (SecurityUtils.safeExistsSync(goModPath, projectRoot)) {
|
|
839
|
+
try {
|
|
840
|
+
const goModContent = SecurityUtils.safeReadFileSync(goModPath, projectRoot, 'utf8');
|
|
841
|
+
if (goModContent) {
|
|
842
|
+
const hasGoI18n = /go-i18n|x-text|i18n/.test(goModContent);
|
|
843
|
+
detectedFrameworks.push({
|
|
844
|
+
id: 'go', name: 'Go', description: 'Go project with i18n',
|
|
845
|
+
confidence: hasGoI18n ? 0.85 : 0.4, version: '', priority: 0
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
} catch (_) { /* skip */ }
|
|
849
|
+
}
|
|
850
|
+
}
|
|
712
851
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
852
|
+
// Phase 5: Check Ruby project (Gemfile)
|
|
853
|
+
if (detectedFrameworks.length === 0) {
|
|
854
|
+
const gemfilePaths = ['Gemfile', 'gems.rb'];
|
|
855
|
+
for (const gf of gemfilePaths) {
|
|
856
|
+
const gemfilePath = path.join(projectRoot, gf);
|
|
857
|
+
if (SecurityUtils.safeExistsSync(gemfilePath, projectRoot)) {
|
|
858
|
+
try {
|
|
859
|
+
const gemfileContent = SecurityUtils.safeReadFileSync(gemfilePath, projectRoot, 'utf8');
|
|
860
|
+
if (gemfileContent) {
|
|
861
|
+
const hasRails = /rails/.test(gemfileContent);
|
|
862
|
+
const hasI18n = /i18n/.test(gemfileContent);
|
|
863
|
+
if (hasRails) {
|
|
864
|
+
detectedFrameworks.push({
|
|
865
|
+
id: 'ruby-on-rails', name: 'Ruby on Rails', description: 'Rails project with i18n',
|
|
866
|
+
confidence: hasI18n ? 0.9 : 0.7, version: '', priority: 0
|
|
867
|
+
});
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
if (hasI18n) {
|
|
871
|
+
detectedFrameworks.push({
|
|
872
|
+
id: 'ruby-on-rails', name: 'Ruby on Rails', description: 'Ruby project with i18n',
|
|
873
|
+
confidence: 0.6, version: '', priority: 0
|
|
874
|
+
});
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
} catch (_) { /* skip */ }
|
|
879
|
+
}
|
|
716
880
|
}
|
|
881
|
+
}
|
|
717
882
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
883
|
+
// Return the framework with highest confidence, if any
|
|
884
|
+
if (detectedFrameworks.length > 0) {
|
|
885
|
+
return detectedFrameworks.sort((a, b) => {
|
|
886
|
+
const confidenceDiff = b.confidence - a.confidence;
|
|
887
|
+
if (confidenceDiff !== 0) return confidenceDiff;
|
|
888
|
+
return (b.priority || 0) - (a.priority || 0);
|
|
889
|
+
})[0];
|
|
722
890
|
}
|
|
891
|
+
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function readPyProjectDeps(projectRoot) {
|
|
896
|
+
const deps = [];
|
|
897
|
+
try {
|
|
898
|
+
const reqPath = path.join(projectRoot, 'requirements.txt');
|
|
899
|
+
if (SecurityUtils.safeExistsSync(reqPath, projectRoot)) {
|
|
900
|
+
const content = SecurityUtils.safeReadFileSync(reqPath, projectRoot, 'utf8');
|
|
901
|
+
if (content) {
|
|
902
|
+
for (const line of content.split('\n')) {
|
|
903
|
+
const stripped = line.replace(/[<>=!~].*$/, '').trim();
|
|
904
|
+
if (stripped && !stripped.startsWith('#')) deps.push(stripped.toLowerCase().replace(/[_-]/g, ''));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
} catch (_) { /* skip */ }
|
|
909
|
+
return deps;
|
|
723
910
|
}
|
|
724
911
|
|
|
725
912
|
module.exports = { detectFramework, FRAMEWORKS, FRAMEWORK_COMPATIBILITY,
|
|
@@ -23,7 +23,7 @@ const { getGlobalReadline } = require('./cli');
|
|
|
23
23
|
class LocaleOptimizer {
|
|
24
24
|
constructor() {
|
|
25
25
|
this.uiLocalesDir = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales');
|
|
26
|
-
this.allLocales = ['en', 'de', 'es', 'fr', '
|
|
26
|
+
this.allLocales = ['en', 'de', 'es', 'fr', 'it', 'pt', 'nl', 'pl', 'sv', 'uk', 'cs', 'tr', 'ru', 'ja', 'ko', 'zh', 'ar', 'hi', 'th', 'vi', 'he', 'el', 'hu'];
|
|
27
27
|
this.backupDir = path.join(__dirname, '..', 'backups', 'locales');
|
|
28
28
|
this.rl = getGlobalReadline();
|
|
29
29
|
|
package/utils/report-model.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { getSourceExtensions, getExcludeDirs } = require('./framework-detector');
|
|
5
|
+
const { getSourceExtensions, getExcludeDirs, getFrameworkPatterns } = require('./framework-detector');
|
|
6
6
|
const SecurityUtils = require('./security');
|
|
7
7
|
|
|
8
8
|
const EXCLUDE_DIRS = new Set(getExcludeDirs());
|
|
@@ -83,7 +83,8 @@ function generateI18ntkReport(options = {}) {
|
|
|
83
83
|
Object.keys(values).forEach(key => allKeys.add(key));
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const
|
|
86
|
+
const framework = options.framework || 'vanilla';
|
|
87
|
+
const sourceScan = scanSourceFiles(sourceDir, new Set(Object.keys(sourceValues)), framework);
|
|
87
88
|
const usedKeys = sourceScan.usedKeys;
|
|
88
89
|
const issues = [];
|
|
89
90
|
const addIssue = issue => issues.push(normalizeIssue(issue, issues.length + 1, projectRoot));
|
|
@@ -288,7 +289,7 @@ function flattenObject(value, prefix = '', out = {}) {
|
|
|
288
289
|
return out;
|
|
289
290
|
}
|
|
290
291
|
|
|
291
|
-
function scanSourceFiles(sourceDir, availableKeys) {
|
|
292
|
+
function scanSourceFiles(sourceDir, availableKeys, framework) {
|
|
292
293
|
const usedKeys = new Set();
|
|
293
294
|
const usageLocations = new Map();
|
|
294
295
|
const hardcodedTexts = [];
|
|
@@ -299,15 +300,25 @@ function scanSourceFiles(sourceDir, availableKeys) {
|
|
|
299
300
|
for (const file of files) {
|
|
300
301
|
const content = SecurityUtils.safeReadFileSync(file, basePath, 'utf8');
|
|
301
302
|
const lines = content.split(/\r?\n/);
|
|
303
|
+
const frameworkPatterns = framework ? getFrameworkPatterns(framework) : [];
|
|
302
304
|
const keyPatterns = [
|
|
303
305
|
/\b(?:t|tx|__|_t)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
304
306
|
/\bi18n\.t\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
305
307
|
/\bi18nKey\s*=\s*['"`]([^'"`]+)['"`]/g,
|
|
308
|
+
/\$t\(\s*['"`]([^'"`]+)['"`]/g,
|
|
309
|
+
/\|\s*translate/g,
|
|
310
|
+
/\{\{\s*t\s+['"`]([^'"`]+)['"`]/g,
|
|
311
|
+
/\b(I18n|i18n)\.(t|translate|localize|l)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
312
|
+
/\{\%\s*trans\s+['"`]([^'"`]+)['"`]\s*%\}/g,
|
|
313
|
+
/gettext\(['"`]([^'"`]+)['"`]\)/g,
|
|
314
|
+
/_\(['"`]([^'"`]+)['"`]\)/g,
|
|
315
|
+
/\$_\(\s*['"`]([^'"`]+)['"`]/g
|
|
306
316
|
];
|
|
307
|
-
for (const pattern of keyPatterns) {
|
|
317
|
+
for (const pattern of [...keyPatterns, ...frameworkPatterns]) {
|
|
308
318
|
let match;
|
|
309
319
|
while ((match = pattern.exec(content))) {
|
|
310
|
-
const key = match[1];
|
|
320
|
+
const key = match[1] || match[2];
|
|
321
|
+
if (!key) continue;
|
|
311
322
|
usedKeys.add(key);
|
|
312
323
|
const location = offsetToLocation(content, match.index);
|
|
313
324
|
const list = usageLocations.get(key) || [];
|