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
package/main/i18ntk-validate.js
CHANGED
|
@@ -43,8 +43,9 @@ const configManager = require('../utils/config-manager');
|
|
|
43
43
|
const SecurityUtils = require('../utils/security');
|
|
44
44
|
const AdminCLI = require('../utils/admin-cli');
|
|
45
45
|
const watchLocales = require('../utils/watch-locales');
|
|
46
|
-
const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
|
|
47
|
-
const { getUnifiedConfig, parseCommonArgs, displayHelp, validateSourceDir, displayPaths } = require('../utils/config-helper');
|
|
46
|
+
const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
|
|
47
|
+
const { getUnifiedConfig, parseCommonArgs, displayHelp, validateSourceDir, displayPaths } = require('../utils/config-helper');
|
|
48
|
+
const { isInteractive } = require('../utils/prompt-helper');
|
|
48
49
|
const I18nInitializer = require('./i18ntk-init');
|
|
49
50
|
const JsonOutput = require('../utils/json-output');
|
|
50
51
|
const ExitCodes = require('../utils/exit-codes');
|
|
@@ -105,14 +106,21 @@ class I18nValidator {
|
|
|
105
106
|
);
|
|
106
107
|
|
|
107
108
|
// Use the i18n directory for language files
|
|
108
|
-
this.sourceDir = this.config.i18nDir || this.config.sourceDir;
|
|
109
|
-
this.i18nDir = this.config.i18nDir || this.config.sourceDir;
|
|
109
|
+
this.sourceDir = this.config.localesDir || this.config.i18nDir || this.config.sourceDir;
|
|
110
|
+
this.i18nDir = this.config.localesDir || this.config.i18nDir || this.config.sourceDir;
|
|
110
111
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
111
112
|
|
|
112
|
-
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
try {
|
|
114
|
+
if (!SecurityUtils.safeExistsSync(this.sourceDir, process.cwd())) {
|
|
115
|
+
const err = new Error(`Locale directory not found: ${this.sourceDir}`);
|
|
116
|
+
err.exitCode = 2;
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (!isInteractive(args)) {
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
123
|
+
console.log(t('init.requiredTitle'));
|
|
116
124
|
console.log(t('init.requiredBody'));
|
|
117
125
|
const answer = await this.prompt(t('init.promptRunNow'));
|
|
118
126
|
if (answer.trim().toLowerCase() === 'y') {
|
|
@@ -827,11 +835,13 @@ class I18nValidator {
|
|
|
827
835
|
if (args.uiLanguage) {
|
|
828
836
|
loadTranslations(args.uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));}
|
|
829
837
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
this.
|
|
833
|
-
this.
|
|
834
|
-
|
|
838
|
+
const localeDirArg = args.i18nDir || (!args.i18nDir && args.sourceDir ? args.sourceDir : null);
|
|
839
|
+
if (localeDirArg) {
|
|
840
|
+
this.config.i18nDir = localeDirArg;
|
|
841
|
+
this.sourceDir = path.resolve(localeDirArg);
|
|
842
|
+
this.i18nDir = this.sourceDir;
|
|
843
|
+
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
844
|
+
}
|
|
835
845
|
if (args.strictMode) {
|
|
836
846
|
this.config.strictMode = true;
|
|
837
847
|
}
|
|
@@ -1131,14 +1141,18 @@ class I18nValidator {
|
|
|
1131
1141
|
{ message: 'Starting validation run' }
|
|
1132
1142
|
);
|
|
1133
1143
|
|
|
1134
|
-
const result = await this.validate();
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
'
|
|
1140
|
-
|
|
1141
|
-
|
|
1144
|
+
const result = await this.validate();
|
|
1145
|
+
|
|
1146
|
+
if (result.success) {
|
|
1147
|
+
console.log(t('validate.validationProcessCompletedSuccessfully'));
|
|
1148
|
+
} else {
|
|
1149
|
+
console.log('❌ Validation failed.');
|
|
1150
|
+
}
|
|
1151
|
+
SecurityUtils.logSecurityEvent(
|
|
1152
|
+
result.success ? t('validate.runCompleted') : t('validate.runError'),
|
|
1153
|
+
result.success ? 'info' : 'error',
|
|
1154
|
+
{ message: result.success ? 'Validation run completed successfully' : 'Validation run failed' }
|
|
1155
|
+
);
|
|
1142
1156
|
return result;
|
|
1143
1157
|
};
|
|
1144
1158
|
|
|
@@ -1234,8 +1248,8 @@ if (require.main === module) {
|
|
|
1234
1248
|
timestamp: new Date().toISOString()
|
|
1235
1249
|
});
|
|
1236
1250
|
|
|
1237
|
-
process.exit(result.success ? ExitCodes.SUCCESS : ExitCodes.VALIDATION_FAILED);
|
|
1238
|
-
}
|
|
1251
|
+
process.exit(result.success ? ExitCodes.SUCCESS : ExitCodes.VALIDATION_FAILED);
|
|
1252
|
+
}
|
|
1239
1253
|
} catch (error) {
|
|
1240
1254
|
console.error(t('validate.fatalValidationError', { error: error.message }));
|
|
1241
1255
|
console.error(t('validate.stackTrace', { stack: error.stack }));
|
|
@@ -1245,7 +1259,7 @@ if (require.main === module) {
|
|
|
1245
1259
|
timestamp: new Date().toISOString()
|
|
1246
1260
|
});
|
|
1247
1261
|
|
|
1248
|
-
process.exit(ExitCodes.CONFIG_ERROR);
|
|
1262
|
+
process.exit(error.exitCode || ExitCodes.CONFIG_ERROR);
|
|
1249
1263
|
}
|
|
1250
1264
|
})();
|
|
1251
1265
|
}
|
|
@@ -80,10 +80,17 @@ class AnalyzeCommand {
|
|
|
80
80
|
const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
|
|
81
81
|
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
82
82
|
|
|
83
|
-
this.sourceDir = this.config.sourceDir;
|
|
83
|
+
this.sourceDir = this.config.localesDir || this.config.i18nDir || this.config.sourceDir;
|
|
84
84
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
85
85
|
this.outputDir = this.config.outputDir;
|
|
86
86
|
|
|
87
|
+
const explicitLocaleDir = args.i18nDir || (!args.i18nDir && args.sourceDir ? args.sourceDir : null);
|
|
88
|
+
if (explicitLocaleDir && !SecurityUtils.safeExistsSync(this.sourceDir, process.cwd())) {
|
|
89
|
+
const err = new Error(`Locale directory not found: ${this.sourceDir}`);
|
|
90
|
+
err.exitCode = 2;
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
// Validate source directory exists and is readable/writable
|
|
88
95
|
this.validateSourceDirWithFallback(this.sourceDir);
|
|
89
96
|
|
|
@@ -766,12 +773,34 @@ class AnalyzeCommand {
|
|
|
766
773
|
}
|
|
767
774
|
}
|
|
768
775
|
|
|
769
|
-
// Show help message
|
|
770
|
-
showHelp() {
|
|
771
|
-
console.log(t('analyze.help_message'));
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
|
|
776
|
+
// Show help message
|
|
777
|
+
showHelp() {
|
|
778
|
+
console.log(t('analyze.help_message'));
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
provideSetupGuidance() {
|
|
782
|
+
return [
|
|
783
|
+
'Setup guidance:',
|
|
784
|
+
`- Ensure the locale root exists: ${this.sourceDir}`,
|
|
785
|
+
`- Ensure the source locale exists: ${path.join(this.sourceDir, this.config.sourceLanguage)}`,
|
|
786
|
+
'- Add at least one target locale, for example de/common.json or de.json.',
|
|
787
|
+
'- Prefer --locales-dir for locale files. Use --code-dir for application source files.'
|
|
788
|
+
].join('\n');
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
detectStructureType() {
|
|
792
|
+
const sourceLocaleDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
793
|
+
const sourceLocaleFile = path.join(this.sourceDir, `${this.config.sourceLanguage}.json`);
|
|
794
|
+
if (SecurityUtils.safeExistsSync(sourceLocaleFile, this.sourceDir)) {
|
|
795
|
+
return { type: 'monolith' };
|
|
796
|
+
}
|
|
797
|
+
if (SecurityUtils.safeExistsSync(sourceLocaleDir, this.sourceDir)) {
|
|
798
|
+
return { type: 'directory' };
|
|
799
|
+
}
|
|
800
|
+
return { type: 'unknown' };
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Main analyze method
|
|
775
804
|
async analyze() {
|
|
776
805
|
try {
|
|
777
806
|
const results = [];
|
|
@@ -806,11 +835,11 @@ class AnalyzeCommand {
|
|
|
806
835
|
});
|
|
807
836
|
console.log(JSON.stringify(jsonOutput.data, null, args.indent || 2));
|
|
808
837
|
return;
|
|
809
|
-
}
|
|
810
|
-
console.log(error);
|
|
811
|
-
console.log('\n' + guidance);
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
838
|
+
}
|
|
839
|
+
console.log(error);
|
|
840
|
+
console.log('\n' + guidance);
|
|
841
|
+
return { success: true, warnings: 1, message: error };
|
|
842
|
+
}
|
|
814
843
|
|
|
815
844
|
if (!args.json) {
|
|
816
845
|
console.log(t('analyze.foundLanguages', { count: languages.length, languages: languages.join(', ') }) || `📋 Found ${languages.length} languages to analyze: ${languages.join(', ')}`);
|
|
@@ -829,8 +858,11 @@ class AnalyzeCommand {
|
|
|
829
858
|
const report = this.generateLanguageReport(analysis);
|
|
830
859
|
|
|
831
860
|
// Save report
|
|
832
|
-
const reportPath = await this.saveReport(language, report);
|
|
833
|
-
|
|
861
|
+
const reportPath = await this.saveReport(language, report);
|
|
862
|
+
if (!reportPath) {
|
|
863
|
+
throw new Error(`Failed to save analysis report for ${language}`);
|
|
864
|
+
}
|
|
865
|
+
const processedCount = results.length + 1;
|
|
834
866
|
|
|
835
867
|
if (!args.json) {
|
|
836
868
|
console.log(t('analyze.completed', { language }) || `✅ Analysis completed for ${language}`);
|
|
@@ -898,9 +930,9 @@ class AnalyzeCommand {
|
|
|
898
930
|
console.log(t('analyze.finished') || '\n✅ Analysis completed successfully!');
|
|
899
931
|
|
|
900
932
|
// Only prompt for input if running standalone and not in no-prompt mode
|
|
901
|
-
if (require.main === module && !this.noPrompt) {
|
|
902
|
-
await this.prompt('\nPress Enter to continue...');
|
|
903
|
-
}
|
|
933
|
+
if (require.main === module && !this.noPrompt && process.env.CI !== 'true' && process.stdin.isTTY && process.stdout.isTTY) {
|
|
934
|
+
await this.prompt('\nPress Enter to continue...');
|
|
935
|
+
}
|
|
904
936
|
this.closeReadline();
|
|
905
937
|
|
|
906
938
|
return results;
|
|
@@ -937,7 +969,7 @@ class AnalyzeCommand {
|
|
|
937
969
|
const uiLanguage = this.config.uiLanguage || 'en';
|
|
938
970
|
loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
|
|
939
971
|
|
|
940
|
-
this.sourceDir = this.config.sourceDir;
|
|
972
|
+
this.sourceDir = this.config.localesDir || this.config.i18nDir || this.config.sourceDir;
|
|
941
973
|
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
942
974
|
this.outputDir = this.config.outputDir;
|
|
943
975
|
}
|
|
@@ -975,19 +1007,20 @@ class AnalyzeCommand {
|
|
|
975
1007
|
loadTranslations(args.uiLanguage, path.resolve(__dirname, '../../../ui-locales'));}
|
|
976
1008
|
|
|
977
1009
|
// Update config if source directory is provided
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
this.
|
|
981
|
-
this.
|
|
982
|
-
|
|
1010
|
+
const localeDirArg = args.i18nDir || (!args.i18nDir && args.sourceDir ? args.sourceDir : null);
|
|
1011
|
+
if (localeDirArg) {
|
|
1012
|
+
this.config.i18nDir = localeDirArg;
|
|
1013
|
+
this.sourceDir = path.resolve(PROJECT_ROOT, localeDirArg);
|
|
1014
|
+
this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
|
|
1015
|
+
}
|
|
983
1016
|
|
|
984
1017
|
if (args.outputDir) {
|
|
985
1018
|
this.config.outputDir = args.outputDir;
|
|
986
1019
|
this.outputDir = path.resolve(this.config.outputDir);
|
|
987
1020
|
}
|
|
988
|
-
const execute = async () => {
|
|
989
|
-
await this.analyze();
|
|
990
|
-
};
|
|
1021
|
+
const execute = async () => {
|
|
1022
|
+
return await this.analyze();
|
|
1023
|
+
};
|
|
991
1024
|
|
|
992
1025
|
if (args.watch) {
|
|
993
1026
|
await execute();
|
|
@@ -1003,19 +1036,21 @@ class AnalyzeCommand {
|
|
|
1003
1036
|
});
|
|
1004
1037
|
console.log('��� Watching for translation changes. Press Ctrl+C to exit.');
|
|
1005
1038
|
} else {
|
|
1006
|
-
await execute();
|
|
1007
|
-
if (!fromMenu && require.main === module) {
|
|
1008
|
-
process.exit(0);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1039
|
+
const result = await execute();
|
|
1040
|
+
if (!fromMenu && require.main === module) {
|
|
1041
|
+
process.exit(0);
|
|
1042
|
+
}
|
|
1043
|
+
return result;
|
|
1044
|
+
}
|
|
1011
1045
|
} catch (error) {
|
|
1012
1046
|
console.error(t('analyze.error') || '❌ Analysis failed:', error.message);
|
|
1013
1047
|
this.closeReadline();
|
|
1014
|
-
if (!fromMenu && require.main === module) {
|
|
1015
|
-
process.exit(1);
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1048
|
+
if (!fromMenu && require.main === module) {
|
|
1049
|
+
process.exit(1);
|
|
1050
|
+
}
|
|
1051
|
+
throw error;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1019
1054
|
|
|
1020
1055
|
async runSetupWizard() {
|
|
1021
1056
|
console.log('Translation Analysis Setup Wizard');
|
|
@@ -1132,14 +1167,14 @@ class AnalyzeCommand {
|
|
|
1132
1167
|
/**
|
|
1133
1168
|
* Execute the analyze command
|
|
1134
1169
|
*/
|
|
1135
|
-
async execute(options = {}) {
|
|
1136
|
-
try {
|
|
1137
|
-
await this.initialize();
|
|
1138
|
-
await this.run(options);
|
|
1139
|
-
return { success: true, command: 'analyze' };
|
|
1140
|
-
} catch (error) {
|
|
1141
|
-
console.error(`Analyze command failed: ${error.message}`);
|
|
1142
|
-
throw error;
|
|
1170
|
+
async execute(options = {}) {
|
|
1171
|
+
try {
|
|
1172
|
+
await this.initialize();
|
|
1173
|
+
const result = await this.run(options);
|
|
1174
|
+
return result && result.success === false ? result : { success: true, command: 'analyze', result };
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
console.error(`Analyze command failed: ${error.message}`);
|
|
1177
|
+
throw error;
|
|
1143
1178
|
}
|
|
1144
1179
|
}
|
|
1145
1180
|
|
|
@@ -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');
|
|
@@ -122,9 +123,11 @@ class ScannerCommand {
|
|
|
122
123
|
const value = valueParts.join('=');
|
|
123
124
|
|
|
124
125
|
switch (key) {
|
|
125
|
-
case 'source-dir':
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
case 'source-dir':
|
|
127
|
+
case 'code-dir':
|
|
128
|
+
case 'source-code-dir':
|
|
129
|
+
parsed.sourceDir = value || '';
|
|
130
|
+
break;
|
|
128
131
|
case 'framework':
|
|
129
132
|
parsed.framework = value || '';
|
|
130
133
|
break;
|
|
@@ -146,9 +149,13 @@ class ScannerCommand {
|
|
|
146
149
|
case 'output-report':
|
|
147
150
|
parsed.outputReport = true;
|
|
148
151
|
break;
|
|
149
|
-
case 'include-tests':
|
|
150
|
-
parsed.includeTests = true;
|
|
151
|
-
break;
|
|
152
|
+
case 'include-tests':
|
|
153
|
+
parsed.includeTests = true;
|
|
154
|
+
break;
|
|
155
|
+
case 'source-language':
|
|
156
|
+
case 'source-locale':
|
|
157
|
+
parsed.sourceLanguage = value || '';
|
|
158
|
+
break;
|
|
152
159
|
case 'help':
|
|
153
160
|
case 'h':
|
|
154
161
|
parsed.help = true;
|
|
@@ -201,11 +208,24 @@ class ScannerCommand {
|
|
|
201
208
|
const packageJson = SecurityUtils.safeParseJSON(packageJsonContent);
|
|
202
209
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
203
210
|
|
|
211
|
+
if (deps.next || deps['next-intl'] || deps['next-i18next']) return 'next';
|
|
204
212
|
if (deps.react || deps['react-dom']) return 'react';
|
|
205
213
|
if (deps.vue || deps['vue-router']) return 'vue';
|
|
206
214
|
if (deps['@angular/core'] || deps.angular) return 'angular';
|
|
207
|
-
if (deps.next) return 'next';
|
|
208
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';
|
|
209
229
|
|
|
210
230
|
return 'vanilla';
|
|
211
231
|
} catch (error) {
|
|
@@ -231,57 +251,110 @@ class ScannerCommand {
|
|
|
231
251
|
|
|
232
252
|
const frameworkSpecific = {
|
|
233
253
|
react: [
|
|
234
|
-
// React specific patterns - enhanced for i18next detection
|
|
235
254
|
/children:\s*["']([^"']{2,99})["']/g,
|
|
236
255
|
/dangerouslySetInnerHTML={{\s*__html:\s*["']([^"']{2,99})["']/g,
|
|
237
|
-
// JSX text content without translation
|
|
238
256
|
/>([^<{][^<>{]*[^}>])</g,
|
|
239
|
-
// Button text
|
|
240
257
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
241
|
-
|
|
242
|
-
/<
|
|
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
|
|
243
261
|
],
|
|
244
262
|
vue: [
|
|
245
|
-
// Vue specific patterns - enhanced for vue-i18n detection
|
|
246
263
|
/v-text=["']([^"']{2,99})["']/g,
|
|
247
264
|
/v-html=["']([^"']{2,99})["']/g,
|
|
248
|
-
// Vue template text
|
|
249
265
|
/>([^<{][^<>{]*[^}>])</g,
|
|
250
|
-
// Button text
|
|
251
266
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
252
|
-
|
|
253
|
-
|
|
267
|
+
/<span[^>]*>([^<]{2,99})<\/span>/g,
|
|
268
|
+
/\$t\(["']([^"']{2,99})["']\)/g,
|
|
269
|
+
/v-t=["']([^"']{2,99})["']/g
|
|
254
270
|
],
|
|
255
271
|
angular: [
|
|
256
|
-
// Angular specific patterns - enhanced for ngx-translate detection
|
|
257
272
|
/\[innerHTML\]=["']([^"']{2,99})["']/g,
|
|
258
273
|
/\[textContent\]=["']([^"']{2,99})["']/g,
|
|
259
|
-
// Angular template text
|
|
260
274
|
/>([^<{][^<>{]*[^}>])</g,
|
|
261
|
-
// Button text
|
|
262
275
|
/<button[^>]*>([^<]{2,99})<\/button>/g,
|
|
263
|
-
|
|
264
|
-
|
|
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
|
|
265
325
|
],
|
|
266
326
|
django: [
|
|
267
|
-
// Django template patterns
|
|
268
327
|
/\{\%\s*trans\s+["']([^"']{2,99})["']\s*%\}/g,
|
|
269
328
|
/\{\%\s*blocktrans\s*%\}([^%]{2,99})\{\%\s*endblocktrans\s*%\}/g,
|
|
270
329
|
/{{\s*_["']([^"']{2,99})["']\s*}}/g,
|
|
271
330
|
/{{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g
|
|
272
331
|
],
|
|
273
332
|
flask: [
|
|
274
|
-
// Flask/Jinja2 template patterns
|
|
275
333
|
/\{\{\s*_["']([^"']{2,99})["']\s*}}/g,
|
|
276
334
|
/\{\{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g,
|
|
277
335
|
/\{\{\s*lazy_gettext\(["']([^"']{2,99})["']\)\s*}}/g
|
|
278
336
|
],
|
|
279
337
|
python: [
|
|
280
|
-
// Python source patterns
|
|
281
338
|
/gettext\(["']([^"']{2,99})["']\)/g,
|
|
282
339
|
/_\(["']([^"']{2,99})["']\)/g,
|
|
283
340
|
/gettext_lazy\(["']([^"']{2,99})["']\)/g,
|
|
284
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
|
|
285
358
|
]
|
|
286
359
|
};
|
|
287
360
|
|
|
@@ -374,24 +447,63 @@ class ScannerCommand {
|
|
|
374
447
|
key: `ui.${key}`,
|
|
375
448
|
original: text,
|
|
376
449
|
translationKey: `t('ui.${key}')`,
|
|
377
|
-
frameworkSpecific: this.
|
|
450
|
+
frameworkSpecific: getFrameworkSuggestions(this.framework, text)
|
|
378
451
|
};
|
|
379
452
|
}
|
|
380
453
|
|
|
381
454
|
getFrameworkSpecific(text) {
|
|
455
|
+
const keySnippet = (str) => str.toLowerCase()
|
|
456
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
457
|
+
.replace(/\s+/g, '_')
|
|
458
|
+
.substring(0, 40);
|
|
382
459
|
const frameworks = {
|
|
383
460
|
react: {
|
|
384
461
|
hook: `const { t } = useTranslation();`,
|
|
385
|
-
usage: `{t('ui.${text
|
|
386
|
-
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>`
|
|
387
469
|
},
|
|
388
470
|
vue: {
|
|
389
|
-
directive: `{{ $t('ui.${text
|
|
390
|
-
method: `this.$t('ui.${text
|
|
471
|
+
directive: `{{ $t('ui.${keySnippet(text)}') }}`,
|
|
472
|
+
method: `this.$t('ui.${keySnippet(text)}')`
|
|
391
473
|
},
|
|
392
474
|
angular: {
|
|
393
475
|
pipe: `{{ '${text}' | translate }}`,
|
|
394
|
-
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'`
|
|
395
507
|
},
|
|
396
508
|
django: {
|
|
397
509
|
template: `{% trans '${text}' %}`,
|
|
@@ -407,6 +519,17 @@ class ScannerCommand {
|
|
|
407
519
|
gettext: `import gettext\ngettext.gettext('${text}')`,
|
|
408
520
|
underscore: `from gettext import gettext as _\n_('${text}')`,
|
|
409
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)}')`
|
|
410
533
|
}
|
|
411
534
|
};
|
|
412
535
|
|
|
@@ -427,7 +550,7 @@ class ScannerCommand {
|
|
|
427
550
|
}
|
|
428
551
|
|
|
429
552
|
const allResults = [];
|
|
430
|
-
const extensions = [
|
|
553
|
+
const extensions = [...SCANNER_EXTENSIONS];
|
|
431
554
|
|
|
432
555
|
const scanRecursive = (currentDir) => {
|
|
433
556
|
const items = SecurityUtils.safeReaddirSync(currentDir, path.dirname(currentDir), { withFileTypes: true });
|
|
@@ -580,7 +703,7 @@ class ScannerCommand {
|
|
|
580
703
|
} else if (fwPref && fwPref !== 'auto') {
|
|
581
704
|
this.framework = fwPref;
|
|
582
705
|
} else if (fwDetectEnabled) {
|
|
583
|
-
const detected =
|
|
706
|
+
const detected = detectProjectFramework(process.cwd());
|
|
584
707
|
this.framework = detected || fwFallback;
|
|
585
708
|
} else {
|
|
586
709
|
this.framework = fwFallback;
|
|
@@ -606,7 +729,7 @@ class ScannerCommand {
|
|
|
606
729
|
console.log(this.t('scanner.starting', { framework: this.framework }));
|
|
607
730
|
console.log(this.t('scanner.sourceDirectory', { sourceDir: this.sourceDir }));
|
|
608
731
|
|
|
609
|
-
const patterns =
|
|
732
|
+
const patterns = getFrameworkPatterns(this.framework);
|
|
610
733
|
const exclusions = this.config.exclude || ['node_modules', '.git', 'dist', 'build'];
|
|
611
734
|
const minLength = this.config.minLength || 3;
|
|
612
735
|
const maxLength = this.config.maxLength || 100;
|
|
@@ -669,4 +792,4 @@ class ScannerCommand {
|
|
|
669
792
|
}
|
|
670
793
|
}
|
|
671
794
|
|
|
672
|
-
module.exports = ScannerCommand;
|
|
795
|
+
module.exports = ScannerCommand;
|