i18ntk 2.1.0 → 2.3.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.
Files changed (73) hide show
  1. package/README.md +87 -50
  2. package/main/i18ntk-analyze.js +63 -63
  3. package/main/i18ntk-backup-class.js +37 -41
  4. package/main/i18ntk-backup.js +28 -30
  5. package/main/i18ntk-complete.js +75 -74
  6. package/main/i18ntk-doctor.js +7 -6
  7. package/main/i18ntk-fixer.js +3 -3
  8. package/main/i18ntk-init.js +49 -13
  9. package/main/i18ntk-scanner.js +2 -2
  10. package/main/i18ntk-sizing.js +36 -37
  11. package/main/i18ntk-summary.js +4 -4
  12. package/main/i18ntk-ui.js +95 -96
  13. package/main/i18ntk-usage.js +31 -19
  14. package/main/i18ntk-validate.js +78 -27
  15. package/main/manage/commands/AnalyzeCommand.js +71 -73
  16. package/main/manage/commands/CommandRouter.js +15 -12
  17. package/main/manage/commands/FixerCommand.js +94 -38
  18. package/main/manage/commands/ScannerCommand.js +2 -2
  19. package/main/manage/commands/ValidateCommand.js +87 -36
  20. package/main/manage/index.js +165 -152
  21. package/main/manage/managers/DebugMenu.js +6 -6
  22. package/main/manage/managers/InteractiveMenu.js +6 -6
  23. package/main/manage/managers/LanguageMenu.js +12 -6
  24. package/main/manage/managers/SettingsMenu.js +6 -6
  25. package/main/manage/services/AuthenticationService.js +5 -6
  26. package/main/manage/services/ConfigurationService.js +22 -34
  27. package/main/manage/services/FileManagementService.js +6 -6
  28. package/main/manage/services/InitService.js +44 -8
  29. package/main/manage/services/UsageService.js +24 -12
  30. package/package.json +21 -42
  31. package/settings/settings-cli.js +5 -5
  32. package/settings/settings-manager.js +984 -968
  33. package/ui-locales/de.json +12 -11
  34. package/ui-locales/en.json +12 -11
  35. package/ui-locales/es.json +12 -11
  36. package/ui-locales/fr.json +12 -11
  37. package/ui-locales/ja.json +12 -11
  38. package/ui-locales/ru.json +12 -11
  39. package/ui-locales/zh.json +12 -11
  40. package/utils/config-helper.js +27 -16
  41. package/utils/config-manager.js +8 -7
  42. package/utils/i18n-helper.js +161 -166
  43. package/utils/init-helper.js +3 -2
  44. package/utils/json-output.js +11 -10
  45. package/{scripts → utils}/locale-optimizer.js +61 -60
  46. package/utils/logger.js +4 -4
  47. package/utils/safe-json.js +3 -3
  48. package/utils/secure-backup.js +8 -7
  49. package/utils/setup-enforcer.js +63 -98
  50. package/main/i18ntk-go.js +0 -283
  51. package/main/i18ntk-java.js +0 -380
  52. package/main/i18ntk-js.js +0 -512
  53. package/main/i18ntk-manage.js +0 -1694
  54. package/main/i18ntk-php.js +0 -462
  55. package/main/i18ntk-py.js +0 -379
  56. package/main/i18ntk-settings.js +0 -23
  57. package/main/manage/index-fixed.js +0 -1447
  58. package/scripts/build-lite.js +0 -279
  59. package/scripts/deprecate-versions.js +0 -317
  60. package/scripts/export-translations.js +0 -84
  61. package/scripts/fix-all-i18n.js +0 -236
  62. package/scripts/fix-and-purify-i18n.js +0 -233
  63. package/scripts/fix-locale-control-chars.js +0 -110
  64. package/scripts/lint-locales.js +0 -80
  65. package/scripts/prepublish-dev.js +0 -221
  66. package/scripts/prepublish.js +0 -362
  67. package/scripts/security-check.js +0 -117
  68. package/scripts/sync-translations.js +0 -151
  69. package/scripts/sync-ui-locales.js +0 -20
  70. package/scripts/validate-all-translations.js +0 -195
  71. package/scripts/verify-deprecations.js +0 -157
  72. package/scripts/verify-translations.js +0 -63
  73. package/utils/security-fixed.js +0 -609
@@ -2,7 +2,8 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const fs = require('fs/promises');
5
+ const fs = require('fs');
6
+ const fsp = fs.promises;
6
7
  const path = require('path');
7
8
 
8
9
  // Simple CLI argument parser
@@ -36,16 +37,15 @@ function parseArgs(args) {
36
37
 
37
38
  return result;
38
39
  }
39
- const { existsSync } = require('fs');
40
- const configManager = require('../utils/config-manager');
41
- const { logger } = require('../utils/logger');
42
- const { colors } = require('../utils/logger');
40
+ const configManager = require('../utils/config-manager');
41
+ const { logger } = require('../utils/logger');
42
+ const { colors } = require('../utils/logger');
43
43
  const prompt = require('../utils/prompt');
44
44
 
45
45
  // Backup configuration
46
46
  const config = configManager.getConfig();
47
- const backupDir = path.join(process.cwd(), 'i18n-backups');
48
- const maxBackups = config.backup?.maxBackups || 10;
47
+ const backupDir = path.join(process.cwd(), 'i18ntk-backups');
48
+ const maxBackups = Math.min(Math.max(parseInt(config.backup?.maxBackups, 10) || 1, 1), 3);
49
49
 
50
50
  // Main function to handle commands
51
51
  async function main() {
@@ -121,7 +121,7 @@ async function handleCreate(args) {
121
121
 
122
122
  // Create backup directory if it doesn't exist
123
123
  try {
124
- await fs.mkdir(outputDir, { recursive: true });
124
+ await fsp.mkdir(outputDir, { recursive: true });
125
125
  logger.debug(`Created backup directory: ${outputDir}`);
126
126
  } catch (err) {
127
127
  if (err.code !== 'EEXIST') {
@@ -134,7 +134,7 @@ async function handleCreate(args) {
134
134
  // Validate directory
135
135
  const sourceDir = path.resolve(dir);
136
136
  try {
137
- const stats = await fs.stat(sourceDir);
137
+ const stats = await fsp.stat(sourceDir);
138
138
  if (!stats.isDirectory()) {
139
139
  throw new Error(`Path exists but is not a directory: ${sourceDir}`);
140
140
  }
@@ -149,7 +149,7 @@ async function handleCreate(args) {
149
149
  logger.info('\nCreating backup...');
150
150
 
151
151
  // Read all files in the directory
152
- const files = (await fs.readdir(sourceDir, { withFileTypes: true }))
152
+ const files = (await fsp.readdir(sourceDir, { withFileTypes: true }))
153
153
  .filter(dirent => dirent.isFile() && dirent.name.endsWith('.json'))
154
154
  .map(dirent => dirent.name);
155
155
 
@@ -163,7 +163,7 @@ async function handleCreate(args) {
163
163
  for (const file of files) {
164
164
  const filePath = path.join(sourceDir, file);
165
165
  try {
166
- const content = JSON.parse(await fs.readFile(filePath, 'utf8'));
166
+ const content = JSON.parse(await fsp.readFile(filePath, 'utf8'));
167
167
  translations[file] = content;
168
168
  } catch (error) {
169
169
  logger.error(`Could not read file ${file}: ${error.message}`);
@@ -171,8 +171,8 @@ async function handleCreate(args) {
171
171
  }
172
172
 
173
173
  // Create the backup
174
- await fs.writeFile(backupPath, JSON.stringify(translations, null, 2));
175
- const stats = await fs.stat(backupPath);
174
+ await fsp.writeFile(backupPath, JSON.stringify(translations, null, 2));
175
+ const stats = await fsp.stat(backupPath);
176
176
 
177
177
  logger.success('Backup created successfully');
178
178
  logger.info(` Location: ${backupPath}`);
@@ -203,12 +203,12 @@ async function handleRestore(args) {
203
203
 
204
204
  try {
205
205
  // Read the backup file
206
- const backupData = await fs.readFile(backupPath, 'utf8');
206
+ const backupData = await fsp.readFile(backupPath, 'utf8');
207
207
  const translations = JSON.parse(backupData);
208
208
 
209
209
  // Create output directory if it doesn't exist
210
210
  try {
211
- await fs.mkdir(outputDir, { recursive: true });
211
+ await fsp.mkdir(outputDir, { recursive: true });
212
212
  } catch (err) {
213
213
  if (err.code !== 'EEXIST') throw err;
214
214
  }
@@ -216,7 +216,7 @@ async function handleRestore(args) {
216
216
  // Write the restored files
217
217
  for (const [file, content] of Object.entries(translations)) {
218
218
  const filePath = path.join(outputDir, file);
219
- await fs.writeFile(filePath, JSON.stringify(content, null, 2));
219
+ await fsp.writeFile(filePath, JSON.stringify(content, null, 2));
220
220
  }
221
221
 
222
222
  logger.success('Backup restored successfully');
@@ -230,7 +230,7 @@ async function handleList() {
230
230
  try {
231
231
  // Ensure backup directory exists
232
232
  try {
233
- await fs.access(backupDir);
233
+ await fsp.access(backupDir);
234
234
  } catch (err) {
235
235
  if (err.code === 'ENOENT') {
236
236
  logger.warn('No backups found. The backup directory does not exist yet.');
@@ -240,14 +240,14 @@ async function handleList() {
240
240
  return;
241
241
  }
242
242
 
243
- const files = await fs.readdir(backupDir);
243
+ const files = await fsp.readdir(backupDir);
244
244
  const backups = [];
245
245
 
246
246
  for (const file of files) {
247
247
  if (file.startsWith('backup-') && file.endsWith('.json')) {
248
248
  try {
249
249
  const filePath = path.join(backupDir, file);
250
- const stats = await fs.stat(filePath);
250
+ const stats = await fsp.stat(filePath);
251
251
  backups.push({
252
252
  name: file,
253
253
  path: filePath,
@@ -307,14 +307,14 @@ async function handleVerify(args) {
307
307
  logger.info('\nVerifying backup...');
308
308
 
309
309
  try {
310
- const data = await fs.readFile(backupPath, 'utf8');
310
+ const data = await fsp.readFile(backupPath, 'utf8');
311
311
  const content = JSON.parse(data);
312
312
 
313
313
  if (typeof content === 'object' && content !== null) {
314
314
  const fileCount = Object.keys(content).length;
315
315
  logger.success('Backup is valid');
316
316
  logger.info(` Contains ${fileCount} translation files`);
317
- logger.info(` Last modified: ${(await fs.stat(backupPath)).mtime.toLocaleString()}`);
317
+ logger.info(` Last modified: ${(await fsp.stat(backupPath)).mtime.toLocaleString()}`);
318
318
  } else {
319
319
  throw new Error('Invalid backup format');
320
320
  }
@@ -331,13 +331,13 @@ async function handleCleanup(args) {
331
331
  logger.info('\nCleaning up old backups...');
332
332
 
333
333
  try {
334
- const files = await fs.readdir(backupDir);
334
+ const files = await fsp.readdir(backupDir);
335
335
  const backupFiles = files
336
336
  .filter(file => file.startsWith('backup-') && file.endsWith('.json'))
337
337
  .map(file => ({
338
338
  name: file,
339
339
  path: path.join(backupDir, file),
340
- time: fs.statSync(path.join(backupDir, file)).mtime.getTime()
340
+ time: fs.statSync(path.join(backupDir, file)).mtime.getTime()
341
341
  }))
342
342
  .sort((a, b) => b.time - a.time);
343
343
 
@@ -352,7 +352,7 @@ async function handleCleanup(args) {
352
352
  // Delete old backups
353
353
  for (const file of toDelete) {
354
354
  try {
355
- await fs.unlink(file.path);
355
+ await fsp.unlink(file.path);
356
356
  logger.info(` - Deleted: ${file.name}`);
357
357
  } catch (err) {
358
358
  logger.error(` - Failed to delete ${file.name}: ${err.message}`);
@@ -365,12 +365,10 @@ async function handleCleanup(args) {
365
365
  } catch (error) {
366
366
  logger.error('Error cleaning up backups:');
367
367
  logger.error(` ${error.message}`);
368
- if (process.env.DEBUG) {
369
- console.error(error);
370
- }
371
- process.exit(1);
372
- }
373
- }
368
+ logger.debug(error.stack || error.message);
369
+ process.exit(1);
370
+ }
371
+ }
374
372
 
375
373
  // Start the application
376
374
  // Handle unhandled promise rejections
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * I18NTK TRANSLATION COMPLETION SCRIPT
4
4
  *
@@ -19,15 +19,15 @@ const { loadTranslations, t } = require('../utils/i18n-helper');
19
19
  const { getGlobalReadline, closeGlobalReadline } = require('../utils/cli');
20
20
  const SetupEnforcer = require('../utils/setup-enforcer');
21
21
 
22
- // Ensure setup is complete before running, except for help output.
23
- (async () => {
24
- const isHelpRequest = process.argv.slice(2).some(arg => arg === '--help' || arg === '-h');
25
- if (isHelpRequest) {
26
- return;
27
- }
28
- try {
29
- await SetupEnforcer.checkSetupCompleteAsync();
30
- } catch (error) {
22
+ // Ensure setup is complete before running, except for help output.
23
+ (async () => {
24
+ const isHelpRequest = process.argv.slice(2).some(arg => arg === '--help' || arg === '-h');
25
+ if (isHelpRequest) {
26
+ return;
27
+ }
28
+ try {
29
+ await SetupEnforcer.checkSetupCompleteAsync();
30
+ } catch (error) {
31
31
  console.error('Setup check failed:', error.message);
32
32
  process.exit(1);
33
33
  }
@@ -37,7 +37,7 @@ loadTranslations();
37
37
 
38
38
 
39
39
 
40
- class I18nCompletionTool {
40
+ class I18nCompletionTool {
41
41
  constructor(config = {}) {
42
42
  this.config = config;
43
43
  this.sourceDir = null;
@@ -109,57 +109,57 @@ class I18nCompletionTool {
109
109
  parsed.autoTranslate = true;
110
110
  } else if (key === 'dry-run') {
111
111
  parsed.dryRun = true;
112
- } else if (key === 'no-prompt') {
113
- parsed.noPrompt = true;
114
- } else if (key === 'help' || key === 'h') {
115
- parsed.help = true;
116
- }
117
- }
118
- });
112
+ } else if (key === 'no-prompt') {
113
+ parsed.noPrompt = true;
114
+ } else if (key === 'help' || key === 'h') {
115
+ parsed.help = true;
116
+ }
117
+ }
118
+ });
119
119
 
120
120
  return parsed;
121
121
  }
122
122
 
123
- // Get all available languages
124
- getAvailableLanguages() {
123
+ // Get all available languages
124
+ getAvailableLanguages() {
125
125
  if (!SecurityUtils.safeExistsSync(this.sourceDir, this.config.projectRoot)) {
126
126
  throw new Error(`Source directory not found: ${this.sourceDir}`);
127
127
  }
128
128
 
129
129
  // Check for monolith JSON files (en.json, es.json, etc.)
130
- const files = SecurityUtils.safeReaddirSync(this.sourceDir, this.config.projectRoot);
131
- const languagesFromFiles = files
132
- .filter(file => file.endsWith('.json'))
133
- .map(file => path.basename(file, '.json'))
134
- .filter(code => this.isValidLanguageCode(code));
135
-
136
- // Also check for directory-based structure for backward compatibility
137
- const directories = SecurityUtils.safeReaddirSync(this.sourceDir, this.config.projectRoot)
138
- .filter(item => {
139
- if (this.isExcludedLanguageDirectory(item)) return false;
140
- if (!this.isValidLanguageCode(item)) return false;
141
- const itemPath = path.join(this.sourceDir, item);
142
- const stat = SecurityUtils.safeStatSync(itemPath, this.config.projectRoot);
143
- if (!stat || !stat.isDirectory()) return false;
144
-
145
- const localeFiles = SecurityUtils.safeReaddirSync(itemPath, this.config.projectRoot)
146
- .filter(name => name.endsWith('.json'));
147
- return localeFiles.length > 0;
148
- });
149
-
150
- return [...new Set([...languagesFromFiles, ...directories])];
151
- }
152
-
153
- isValidLanguageCode(code) {
154
- if (!code || typeof code !== 'string') return false;
155
- return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(code.trim());
156
- }
157
-
158
- isExcludedLanguageDirectory(name) {
159
- if (!name || typeof name !== 'string') return true;
160
- const lowered = name.toLowerCase();
161
- return lowered.startsWith('backup-') || lowered === 'backup' || lowered === 'reports' || lowered === 'i18ntk-reports';
162
- }
130
+ const files = SecurityUtils.safeReaddirSync(this.sourceDir, this.config.projectRoot);
131
+ const languagesFromFiles = files
132
+ .filter(file => file.endsWith('.json'))
133
+ .map(file => path.basename(file, '.json'))
134
+ .filter(code => this.isValidLanguageCode(code));
135
+
136
+ // Also check for directory-based structure for backward compatibility
137
+ const directories = SecurityUtils.safeReaddirSync(this.sourceDir, this.config.projectRoot)
138
+ .filter(item => {
139
+ if (this.isExcludedLanguageDirectory(item)) return false;
140
+ if (!this.isValidLanguageCode(item)) return false;
141
+ const itemPath = path.join(this.sourceDir, item);
142
+ const stat = SecurityUtils.safeStatSync(itemPath, this.config.projectRoot);
143
+ if (!stat || !stat.isDirectory()) return false;
144
+
145
+ const localeFiles = SecurityUtils.safeReaddirSync(itemPath, this.config.projectRoot)
146
+ .filter(name => name.endsWith('.json'));
147
+ return localeFiles.length > 0;
148
+ });
149
+
150
+ return [...new Set([...languagesFromFiles, ...directories])];
151
+ }
152
+
153
+ isValidLanguageCode(code) {
154
+ if (!code || typeof code !== 'string') return false;
155
+ return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(code.trim());
156
+ }
157
+
158
+ isExcludedLanguageDirectory(name) {
159
+ if (!name || typeof name !== 'string') return true;
160
+ const lowered = name.toLowerCase();
161
+ return lowered.startsWith('backup-') || lowered === 'backup' || lowered === 'reports' || lowered === 'i18ntk-reports';
162
+ }
163
163
 
164
164
  // Get all JSON files from a language directory
165
165
  getLanguageFiles(language) {
@@ -481,7 +481,8 @@ class I18nCompletionTool {
481
481
  this.config = { ...baseConfig, ...(this.config || {}) };
482
482
 
483
483
  const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
484
- loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));this.sourceDir = this.config.sourceDir;
484
+ loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
485
+ this.sourceDir = this.config.sourceDir;
485
486
  this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
486
487
  } else {
487
488
  await this.initialize();
@@ -552,28 +553,28 @@ class I18nCompletionTool {
552
553
  console.log(t("complete.languagesProcessed", { languagesProcessed: languages.length }));
553
554
  console.log(t("complete.missingKeysAdded", { missingKeysAdded: missingKeys.length }));
554
555
 
555
- if (!args.dryRun && allChanges.length > 0 && !args.noPrompt) {
556
- const rl = this.rl || this.initReadline();
557
- const answer = await this.prompt('\n' + t('complete.generateReportPrompt') + ' (Y/N): ');
558
-
559
- if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
560
- await this.generateReport(allChanges, languages);
556
+ if (!args.dryRun && allChanges.length > 0 && !args.noPrompt) {
557
+ const rl = this.rl || this.initReadline();
558
+ const answer = await this.prompt('\n' + t('complete.generateReportPrompt') + ' (Y/N): ');
559
+
560
+ if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
561
+ await this.generateReport(allChanges, languages);
561
562
  }
562
563
  }
563
564
 
564
- if (!args.dryRun) {
565
- console.log('\n' + t("complete.nextStepsTitle"));
566
- console.log(t("complete.separator"));
567
- console.log('1. Run usage analysis:');
568
- console.log(' i18ntk --command=usage');
569
- console.log('2. Validate translations:');
570
- console.log(' i18ntk --command=validate');
571
- console.log('3. Analyze translation status:');
572
- console.log(' i18ntk --command=analyze');
573
- console.log('\n' + t("complete.allKeysAvailable"));
574
- } else {
575
- console.log('\n' + t("complete.runWithoutDryRun"));
576
- }
565
+ if (!args.dryRun) {
566
+ console.log('\n' + t("complete.nextStepsTitle"));
567
+ console.log(t("complete.separator"));
568
+ console.log('1. Run usage analysis:');
569
+ console.log(' i18ntk --command=usage');
570
+ console.log('2. Validate translations:');
571
+ console.log(' i18ntk --command=validate');
572
+ console.log('3. Analyze translation status:');
573
+ console.log(' i18ntk --command=analyze');
574
+ console.log('\n' + t("complete.allKeysAvailable"));
575
+ } else {
576
+ console.log('\n' + t("complete.runWithoutDryRun"));
577
+ }
577
578
 
578
579
  // Only prompt when run from the menu (i.e., when a callback or menu context is present)
579
580
  if (typeof this.prompt === "function" && args.fromMenu) {
@@ -605,4 +606,4 @@ if (require.main === module) {
605
606
  });
606
607
  }
607
608
 
608
- module.exports = I18nCompletionTool;
609
+ module.exports = I18nCompletionTool;
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- const SecurityUtils = require('../utils/security');
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../utils/config-helper');
2
+ const SecurityUtils = require('../utils/security');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const packageJson = require('../package.json');
6
+ const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../utils/config-helper');
6
7
  const SetupEnforcer = require('../utils/setup-enforcer');
7
8
 
8
9
  // Ensure setup is complete before running (only for standalone execution)
@@ -97,7 +98,7 @@ class I18nDoctor {
97
98
  }
98
99
  }
99
100
 
100
- const pkgVersion = require('../package.json').version;
101
+ const pkgVersion = packageJson.version;
101
102
  if (config.version && config.version !== pkgVersion) {
102
103
  issues.push(`Config version mismatch: ${config.version} != ${pkgVersion}`);
103
104
  exitCode = Math.max(exitCode, ExitCodes.CONFIG_ERROR);
@@ -181,4 +182,4 @@ if (require.main === module) {
181
182
  doctor.run();
182
183
  }
183
184
 
184
- module.exports = I18nDoctor;
185
+ module.exports = I18nDoctor;
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * I18NTK TRANSLATION FIXER SCRIPT
@@ -27,7 +27,7 @@ const SetupEnforcer = require('../utils/setup-enforcer');
27
27
  }
28
28
  })();
29
29
 
30
- loadTranslations('en', path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));
30
+ loadTranslations('en', path.resolve(__dirname, '..', 'ui-locales'));
31
31
 
32
32
  class I18nFixer {
33
33
  constructor(config = {}) {
@@ -51,7 +51,7 @@ class I18nFixer {
51
51
  this.config = { ...baseConfig, ...(this.config || {}) };
52
52
 
53
53
  const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
54
- loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));
54
+ loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
55
55
 
56
56
  this.sourceDir = this.config.sourceDir;
57
57
  this.outputDir = this.config.outputDir;
@@ -754,7 +754,7 @@ class I18nInitializer {
754
754
  }
755
755
 
756
756
  // Interactive admin PIN setup
757
- async promptAdminPinSetup() {
757
+ async promptAdminPinSetup() {
758
758
  const { ask, askHidden, flushStdout } = require('../utils/cli');
759
759
 
760
760
  console.log('\n' + t('init.adminPinSetupOptional'));
@@ -806,8 +806,31 @@ class I18nInitializer {
806
806
  }
807
807
  } else {
808
808
  console.log(t('init.skippingAdminPinSetup'));
809
- }
810
- }
809
+ }
810
+ }
811
+
812
+ async promptBackupConfiguration(skipPrompt = false) {
813
+ const defaultBackupConfig = { enabled: false, maxBackups: 1, location: './i18ntk-backups' };
814
+ if (skipPrompt || !isInteractive()) {
815
+ return null;
816
+ }
817
+
818
+ const { ask } = require('../utils/cli');
819
+ console.log('\nBackup Settings');
820
+ console.log('Backups are disabled by default to avoid backup recursion and repo pollution.');
821
+ const enableAnswer = await ask('Enable automatic backups? (y/N): ');
822
+ const enabled = ['y', 'yes'].includes(String(enableAnswer || '').trim().toLowerCase());
823
+
824
+ if (!enabled) {
825
+ return defaultBackupConfig;
826
+ }
827
+
828
+ const keepAnswer = await ask('How many backups should be kept automatically (1-3, default 1): ');
829
+ const parsedKeep = parseInt(String(keepAnswer || '').trim(), 10);
830
+ const maxBackups = Number.isInteger(parsedKeep) ? Math.min(Math.max(parsedKeep, 1), 3) : 1;
831
+
832
+ return { enabled: true, maxBackups, location: './i18ntk-backups' };
833
+ }
811
834
 
812
835
  // Interactive language selection
813
836
  async selectLanguages(skipPrompt = false) {
@@ -921,11 +944,24 @@ class I18nInitializer {
921
944
  // Prompt for admin PIN setup if not already configured
922
945
  const securitySettings = configManager.getConfig().security || {};
923
946
 
924
- if (!securitySettings.adminPinEnabled && securitySettings.adminPinPromptOnInit !== false && !args.noPrompt) {
925
- const { flushStdout } = require('../utils/cli');
926
- await flushStdout();
927
- await this.promptAdminPinSetup();
928
- }
947
+ if (!securitySettings.adminPinEnabled && securitySettings.adminPinPromptOnInit !== false && !args.noPrompt) {
948
+ const { flushStdout } = require('../utils/cli');
949
+ await flushStdout();
950
+ await this.promptAdminPinSetup();
951
+ }
952
+
953
+ const backupSettings = await this.promptBackupConfiguration(args.noPrompt);
954
+ if (backupSettings) {
955
+ await configManager.updateConfig({
956
+ backup: {
957
+ ...(this.config.backup || {}),
958
+ ...backupSettings
959
+ }
960
+ });
961
+ this.config.backup = { ...(this.config.backup || {}), ...backupSettings };
962
+ } else if (!this.config.backup) {
963
+ this.config.backup = { enabled: false, maxBackups: 1, location: './i18ntk-backups' };
964
+ }
929
965
 
930
966
  // Get target languages - use args.languages if provided
931
967
  let targetLanguages = args.languages || await this.selectLanguages(args.noPrompt);
@@ -1122,7 +1158,7 @@ class I18nInitializer {
1122
1158
 
1123
1159
  try {
1124
1160
  // Import locale optimizer directly
1125
- const LocaleOptimizer = require('../scripts/locale-optimizer');
1161
+ const LocaleOptimizer = require('../utils/locale-optimizer');
1126
1162
 
1127
1163
  // First run dry run to show current state
1128
1164
  console.log('\n🔍 Running locale optimization preview...');
@@ -1139,12 +1175,12 @@ class I18nInitializer {
1139
1175
  console.log('\n✅ Package optimization completed!');
1140
1176
  } else {
1141
1177
  console.log('\n💡 You can run locale optimization later with:');
1142
- console.log(' node scripts/locale-optimizer.js --interactive');
1178
+ console.log(' node utils/locale-optimizer.js --interactive');
1143
1179
  }
1144
1180
  } catch (error) {
1145
1181
  console.log('\n⚠️ Could not offer locale optimization:', error.message);
1146
1182
  console.log('\n💡 You can run locale optimization later with:');
1147
- console.log(' node scripts/locale-optimizer.js --interactive');
1183
+ console.log(' node utils/locale-optimizer.js --interactive');
1148
1184
  }
1149
1185
  } catch (error) {
1150
1186
  console.log('\n⚠️ Could not offer locale optimization:', error.message);
@@ -1178,7 +1214,7 @@ class I18nInitializer {
1178
1214
  // Load translations for UI messages
1179
1215
  const uiLanguage = this.config.uiLanguage || 'en';
1180
1216
  const { loadTranslations } = require('../utils/i18n-helper');
1181
- loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));
1217
+ loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
1182
1218
  }
1183
1219
 
1184
1220
  // Setup is now handled centrally by config manager
@@ -1261,7 +1297,7 @@ class I18nInitializer {
1261
1297
  // Load translations for UI messages
1262
1298
  const uiLanguage = this.config.uiLanguage || 'en';
1263
1299
  const { loadTranslations } = require('../utils/i18n-helper');
1264
- loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));
1300
+ loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
1265
1301
 
1266
1302
  // Skip i18n framework check in non-interactive mode
1267
1303
  console.log('Running initialization in non-interactive mode...');
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * I18NTK TEXT SCANNER
@@ -47,7 +47,7 @@ class I18nTextScanner {
47
47
  }
48
48
 
49
49
  loadLocale() {
50
- const uiLocalesDir = path.join(__dirname, '..', 'resources', 'i18n', 'ui-locales');
50
+ const uiLocalesDir = path.join(__dirname, '..', 'ui-locales');
51
51
  const localeFile = path.join(uiLocalesDir, 'en.json');
52
52
 
53
53
  try {