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
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # i18ntk v2.1.0
1
+ # i18ntk v2.3.0
2
2
 
3
- Zero-dependency i18n toolkit for initialization, scanning, analysis, validation, usage tracking, and translation completion.
3
+ Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, and translation completion.
4
4
 
5
5
  ![i18ntk Logo](docs/screenshots/i18ntk-logo-public.PNG)
6
6
 
@@ -9,52 +9,76 @@ Zero-dependency i18n toolkit for initialization, scanning, analysis, validation,
9
9
  [![node](https://img.shields.io/badge/node-%3E%3D16-339933)](https://nodejs.org)
10
10
  [![dependencies](https://img.shields.io/badge/dependencies-0-success)](https://www.npmjs.com/package/i18ntk)
11
11
  [![license](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)
12
- [![socket](https://socket.dev/api/badge/npm/package/i18ntk/2.1.0)](https://socket.dev/npm/package/i18ntk/overview/2.1.0)
12
+ [![socket](https://socket.dev/api/badge/npm/package/i18ntk/2.3.0)](https://socket.dev/npm/package/i18ntk/overview/2.3.0)
13
13
 
14
- ## Why i18ntk
14
+ ## Upgrade Notice
15
+
16
+ Versions earlier than `2.3.0` may contain known stability and security issues.
17
+ They are considered unsupported for production use. Upgrade to `2.3.0` or newer.
18
+
19
+ ## What i18ntk Does
15
20
 
16
21
  - Zero runtime dependencies
17
- - Works across JS/TS, React, Vue, Angular, and generic projects
18
- - Supports non-interactive CI runs (`--no-prompt`)
19
- - Includes usage/coverage validation and missing-key completion
20
- - Ships with runtime translation helpers via `i18ntk/runtime`
22
+ - Interactive and non-interactive project setup
23
+ - Translation completeness analysis and usage tracking
24
+ - Validation, sizing, and summary reporting
25
+ - Missing-key completion and fixer workflows
26
+ - Runtime translation helpers for application code
27
+ - Support for JS/TS, React, Vue, Angular, and generic projects
28
+
29
+ ## Getting Started
30
+
31
+ 1. Install the package.
32
+ 2. Run `i18ntk` or `i18ntk --command=init` to initialize the project.
33
+ 3. Confirm the source language and locale directories.
34
+ 4. Run `i18ntk --command=analyze` or `i18ntk --command=validate` to inspect translation coverage.
35
+ 5. Use `i18ntk --command=complete` to fill missing keys when needed.
36
+
37
+ The full onboarding flow is documented in [docs/getting-started.md](docs/getting-started.md).
21
38
 
22
39
  ## Install
23
40
 
24
41
  ```bash
25
- # global (recommended for CLI use)
42
+ # global CLI use
26
43
  npm install -g i18ntk
27
44
 
28
- # local
45
+ # local project use
29
46
  npm install --save-dev i18ntk
30
47
 
31
- # one-off
48
+ # one-off execution
32
49
  npx i18ntk --help
33
50
  ```
34
51
 
35
- ## Quick Start
52
+ ## Setup
53
+
54
+ The toolkit stores project configuration in `.i18ntk-config` at the project root.
55
+
56
+ Recommended setup flow:
36
57
 
37
58
  ```bash
38
- # initialize locales/project settings
59
+ i18ntk
60
+ # or
39
61
  i18ntk --command=init
62
+ ```
40
63
 
41
- # analyze translation completeness
42
- i18ntk --command=analyze
64
+ During setup, you can define:
43
65
 
44
- # validate translation structure/content
45
- i18ntk --command=validate
66
+ - source directory
67
+ - source language
68
+ - UI language
69
+ - framework preference
70
+ - output directory
71
+ - backup behavior
46
72
 
47
- # complete missing keys
48
- i18ntk --command=complete
49
- ```
73
+ If you run in CI or a non-interactive shell, use:
50
74
 
51
- ## Command Model (v2)
75
+ ```bash
76
+ i18ntk --command=init --no-prompt
77
+ ```
52
78
 
53
- Primary CLI:
79
+ ## Daily Use
54
80
 
55
81
  ```bash
56
- i18ntk
57
- i18ntk --command=init
58
82
  i18ntk --command=analyze
59
83
  i18ntk --command=validate
60
84
  i18ntk --command=usage
@@ -62,10 +86,9 @@ i18ntk --command=scanner
62
86
  i18ntk --command=sizing
63
87
  i18ntk --command=complete
64
88
  i18ntk --command=summary
65
- i18ntk --command=debug
66
89
  ```
67
90
 
68
- Standalone executables:
91
+ Standalone commands are also available:
69
92
 
70
93
  ```bash
71
94
  i18ntk-init
@@ -92,15 +115,43 @@ i18ntk-backup
92
115
  - `--dry-run`
93
116
  - `--help`
94
117
 
95
- ## Configuration
118
+ Example:
119
+
120
+ ```bash
121
+ i18ntk --command=analyze --source-dir=./src --i18n-dir=./locales --output-dir=./i18ntk-reports
122
+ ```
96
123
 
97
- i18ntk reads project settings from `.i18ntk-config` in your project root.
124
+ ## Runtime API
98
125
 
99
- Example:
126
+ Use `i18ntk/runtime` when your application needs to read locale JSON files at runtime.
127
+
128
+ ```js
129
+ const runtime = require('i18ntk/runtime');
130
+
131
+ runtime.initRuntime({
132
+ baseDir: './locales',
133
+ language: 'en',
134
+ fallbackLanguage: 'en',
135
+ keySeparator: '.',
136
+ preload: true
137
+ });
138
+
139
+ console.log(runtime.t('common.hello'));
140
+ runtime.setLanguage('fr');
141
+ console.log(runtime.getLanguage());
142
+ console.log(runtime.getAvailableLanguages());
143
+ runtime.refresh('fr');
144
+ ```
145
+
146
+ For a deeper walkthrough, see [docs/runtime.md](docs/runtime.md).
147
+
148
+ ## Configuration
149
+
150
+ Example `.i18ntk-config`:
100
151
 
101
152
  ```json
102
153
  {
103
- "version": "2.1.0",
154
+ "version": "2.3.0",
104
155
  "sourceDir": "./locales",
105
156
  "i18nDir": "./locales",
106
157
  "outputDir": "./i18ntk-reports",
@@ -112,34 +163,20 @@ Example:
112
163
  }
113
164
  ```
114
165
 
115
- ## Runtime API
116
-
117
- ```ts
118
- import { initRuntime, t, setLanguage, getLanguage } from 'i18ntk/runtime';
119
-
120
- initRuntime({
121
- baseDir: './locales',
122
- language: 'en',
123
- fallbackLanguage: 'en',
124
- preload: true
125
- });
126
-
127
- console.log(t('common.hello'));
128
- setLanguage('fr');
129
- console.log(getLanguage());
130
- ```
166
+ See [docs/api/CONFIGURATION.md](docs/api/CONFIGURATION.md) for the full configuration model.
131
167
 
132
- ## Documentation
168
+ ## Docs
133
169
 
134
170
  - [Documentation Index](docs/README.md)
171
+ - [Getting Started](docs/getting-started.md)
135
172
  - [API Reference](docs/api/API_REFERENCE.md)
136
173
  - [Configuration Guide](docs/api/CONFIGURATION.md)
137
174
  - [Runtime API Guide](docs/runtime.md)
138
175
  - [Scanner Guide](docs/scanner-guide.md)
139
176
  - [Environment Variables](docs/environment-variables.md)
140
- - [Migration Guide v2.1.0](docs/migration-guide-v2.1.0.md)
141
- - [Release Runbook](DEVUPDATE.md)
177
+ - [Migration Guide v2.3.0](docs/migration-guide-v2.3.0.md)
178
+ - [Optimization Prompt](docs/development/package-optimization-prompt.md)
142
179
 
143
180
  ## License
144
181
 
145
- MIT
182
+ MIT
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * I18NTK TRANSLATION ANALYSIS SCRIPT
4
4
  *
@@ -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
  const PROJECT_ROOT = process.cwd();
33
33
 
@@ -67,7 +67,7 @@ class I18nAnalyzer {
67
67
  this.config = { ...baseConfig, ...(this.config || {}) };
68
68
 
69
69
  const uiLanguage = (this.config && this.config.uiLanguage) || 'en';
70
- loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));
70
+ loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
71
71
 
72
72
  this.sourceDir = this.config.sourceDir;
73
73
  this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
@@ -142,44 +142,44 @@ class I18nAnalyzer {
142
142
  }
143
143
  }
144
144
 
145
- // Get all available languages
146
- isValidLanguageCode(code) {
147
- if (!code || typeof code !== 'string') return false;
148
- return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(code.trim());
149
- }
150
-
151
- isExcludedLanguageDirectory(name) {
152
- if (!name || typeof name !== 'string') return true;
153
- const lowered = name.toLowerCase();
154
- return lowered.startsWith('backup-') || lowered === 'backup' || lowered === 'reports' || lowered === 'i18ntk-reports';
155
- }
156
-
157
- getAvailableLanguages() {
158
- try {
159
- const items = SecurityUtils.safeReaddirSync(this.sourceDir, process.cwd(), { withFileTypes: true });
160
- if (!items) {
161
- console.error('Error reading source directory: Unable to access directory');
145
+ // Get all available languages
146
+ isValidLanguageCode(code) {
147
+ if (!code || typeof code !== 'string') return false;
148
+ return /^[a-z]{2}(?:-[A-Za-z0-9]{2,8})*$/i.test(code.trim());
149
+ }
150
+
151
+ isExcludedLanguageDirectory(name) {
152
+ if (!name || typeof name !== 'string') return true;
153
+ const lowered = name.toLowerCase();
154
+ return lowered.startsWith('backup-') || lowered === 'backup' || lowered === 'reports' || lowered === 'i18ntk-reports';
155
+ }
156
+
157
+ getAvailableLanguages() {
158
+ try {
159
+ const items = SecurityUtils.safeReaddirSync(this.sourceDir, process.cwd(), { withFileTypes: true });
160
+ if (!items) {
161
+ console.error('Error reading source directory: Unable to access directory');
162
162
  return [];
163
163
  }
164
164
 
165
165
  const languages = [];
166
166
 
167
167
  // Check for directory-based structure
168
- const directories = items
169
- .filter(item => item.isDirectory())
170
- .map(item => item.name)
171
- .filter(name =>
172
- name !== 'node_modules' &&
173
- !name.startsWith('.') &&
174
- name !== this.config.sourceLanguage &&
175
- !this.isExcludedLanguageDirectory(name) &&
176
- this.isValidLanguageCode(name)
177
- )
178
- .filter(name => {
179
- const dirPath = path.join(this.sourceDir, name);
180
- const dirItems = SecurityUtils.safeReaddirSync(dirPath, process.cwd(), { withFileTypes: true }) || [];
181
- return dirItems.some(item => item.isFile() && item.name.endsWith('.json'));
182
- });
168
+ const directories = items
169
+ .filter(item => item.isDirectory())
170
+ .map(item => item.name)
171
+ .filter(name =>
172
+ name !== 'node_modules' &&
173
+ !name.startsWith('.') &&
174
+ name !== this.config.sourceLanguage &&
175
+ !this.isExcludedLanguageDirectory(name) &&
176
+ this.isValidLanguageCode(name)
177
+ )
178
+ .filter(name => {
179
+ const dirPath = path.join(this.sourceDir, name);
180
+ const dirItems = SecurityUtils.safeReaddirSync(dirPath, process.cwd(), { withFileTypes: true }) || [];
181
+ return dirItems.some(item => item.isFile() && item.name.endsWith('.json'));
182
+ });
183
183
 
184
184
  // Check for monolith files (language.json files)
185
185
  const files = items
@@ -190,17 +190,17 @@ class I18nAnalyzer {
190
190
  languages.push(...directories);
191
191
 
192
192
  // Add monolith files as languages (without .json extension)
193
- const monolithLanguages = files
194
- .map(file => file.replace('.json', ''))
195
- .filter(lang =>
196
- !languages.includes(lang) &&
197
- lang !== this.config.sourceLanguage &&
198
- !this.isExcludedLanguageDirectory(lang) &&
199
- this.isValidLanguageCode(lang)
200
- );
201
- languages.push(...monolithLanguages);
202
-
203
- return [...new Set(languages)].sort();
193
+ const monolithLanguages = files
194
+ .map(file => file.replace('.json', ''))
195
+ .filter(lang =>
196
+ !languages.includes(lang) &&
197
+ lang !== this.config.sourceLanguage &&
198
+ !this.isExcludedLanguageDirectory(lang) &&
199
+ this.isValidLanguageCode(lang)
200
+ );
201
+ languages.push(...monolithLanguages);
202
+
203
+ return [...new Set(languages)].sort();
204
204
  } catch (error) {
205
205
  console.error('Error reading source directory:', error.message);
206
206
  return [];
@@ -712,7 +712,7 @@ try {
712
712
  throw new Error(t('analyze.failedToWriteReportFile') || 'Failed to write report file securely');
713
713
  }
714
714
 
715
- return reportPath;
715
+ return reportPath;
716
716
 
717
717
  } catch (error) {
718
718
  console.error(`Failed to save report for ${language}:`, error.message);
@@ -779,21 +779,21 @@ try {
779
779
  console.log(t('analyze.analyzing', { language }) || `\n🔄 Analyzing ${language}...`);
780
780
  }
781
781
 
782
- const analysis = this.analyzeLanguage(language);
783
- const report = this.generateLanguageReport(analysis);
784
-
785
- // Save report
786
- const reportPath = await this.saveReport(language, report);
787
- const processedCount = results.length + 1;
788
-
789
- if (!args.json) {
790
- console.log(t('analyze.completed', { language }) || `✅ Analysis completed for ${language}`);
791
- console.log(t('analyze.progress', {
792
- translated: processedCount,
793
- total: languages.length
794
- }) || ` Progress: ${processedCount}/${languages.length} languages processed`);
795
- console.log(t('analyze.reportSaved', { reportPath }) || ` Report saved: ${reportPath}`);
796
- }
782
+ const analysis = this.analyzeLanguage(language);
783
+ const report = this.generateLanguageReport(analysis);
784
+
785
+ // Save report
786
+ const reportPath = await this.saveReport(language, report);
787
+ const processedCount = results.length + 1;
788
+
789
+ if (!args.json) {
790
+ console.log(t('analyze.completed', { language }) || `✅ Analysis completed for ${language}`);
791
+ console.log(t('analyze.progress', {
792
+ translated: processedCount,
793
+ total: languages.length
794
+ }) || ` Progress: ${processedCount}/${languages.length} languages processed`);
795
+ console.log(t('analyze.reportSaved', { reportPath }) || ` Report saved: ${reportPath}`);
796
+ }
797
797
 
798
798
  results.push({
799
799
  language,
@@ -889,7 +889,7 @@ try {
889
889
  this.config = { ...baseConfig, ...this.config };
890
890
 
891
891
  const uiLanguage = this.config.uiLanguage || 'en';
892
- loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'resources', 'i18n', 'ui-locales'));
892
+ loadTranslations(uiLanguage, path.resolve(__dirname, '..', 'ui-locales'));
893
893
 
894
894
  this.sourceDir = this.config.sourceDir;
895
895
  this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
@@ -2,12 +2,12 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const fs = require('fs/promises');
6
- const path = require('path');
7
- const { existsSync } = require('fs');
8
- const configManager = require('../utils/config-manager');
9
- const { logger } = require('../utils/logger');
10
- const { colors } = require('../utils/logger');
5
+ const fs = require('fs');
6
+ const fsp = fs.promises;
7
+ const path = require('path');
8
+ const configManager = require('../utils/config-manager');
9
+ const { logger } = require('../utils/logger');
10
+ const { colors } = require('../utils/logger');
11
11
  const SecurityUtils = require('../utils/security');
12
12
 
13
13
  /**
@@ -16,10 +16,10 @@ const SecurityUtils = require('../utils/security');
16
16
  * Class-based implementation of backup functionality for use with CommandRouter
17
17
  */
18
18
  class I18nBackup {
19
- constructor(config = {}) {
20
- this.config = config;
21
- this.backupDir = path.join(process.cwd(), 'i18n-backups');
22
- this.maxBackups = config.backup?.maxBackups || 10;
19
+ constructor(config = {}) {
20
+ this.config = config;
21
+ this.backupDir = path.join(process.cwd(), 'i18ntk-backups');
22
+ this.maxBackups = Math.min(Math.max(parseInt(config.backup?.maxBackups, 10) || 1, 1), 3);
23
23
  }
24
24
 
25
25
  /**
@@ -93,7 +93,7 @@ Options:
93
93
 
94
94
  // Create backup directory if it doesn't exist
95
95
  try {
96
- await fs.mkdir(outputDir, { recursive: true });
96
+ await fsp.mkdir(outputDir, { recursive: true });
97
97
  logger.debug(`Created backup directory: ${outputDir}`);
98
98
  } catch (err) {
99
99
  if (err.code !== 'EEXIST') {
@@ -106,7 +106,7 @@ Options:
106
106
  // Validate directory
107
107
  const sourceDir = path.resolve(dir);
108
108
  try {
109
- const stats = await fs.stat(sourceDir);
109
+ const stats = await fsp.stat(sourceDir);
110
110
  if (!stats.isDirectory()) {
111
111
  throw new Error(`Path exists but is not a directory: ${sourceDir}`);
112
112
  }
@@ -121,7 +121,7 @@ Options:
121
121
  logger.info('\nCreating backup...');
122
122
 
123
123
  // Read all files in the directory
124
- const files = (await fs.readdir(sourceDir, { withFileTypes: true }))
124
+ const files = (await fsp.readdir(sourceDir, { withFileTypes: true }))
125
125
  .filter(dirent => dirent.isFile() && dirent.name.endsWith('.json'))
126
126
  .map(dirent => dirent.name);
127
127
 
@@ -135,7 +135,7 @@ Options:
135
135
  for (const file of files) {
136
136
  const filePath = path.join(sourceDir, file);
137
137
  try {
138
- const content = JSON.parse(await fs.readFile(filePath, 'utf8'));
138
+ const content = JSON.parse(await fsp.readFile(filePath, 'utf8'));
139
139
  translations[file] = content;
140
140
  } catch (error) {
141
141
  logger.error(`Could not read file ${file}: ${error.message}`);
@@ -143,8 +143,8 @@ Options:
143
143
  }
144
144
 
145
145
  // Create the backup
146
- await fs.writeFile(backupPath, JSON.stringify(translations, null, 2));
147
- const stats = await fs.stat(backupPath);
146
+ await fsp.writeFile(backupPath, JSON.stringify(translations, null, 2));
147
+ const stats = await fsp.stat(backupPath);
148
148
 
149
149
  logger.success('Backup created successfully');
150
150
  logger.info(` Location: ${backupPath}`);
@@ -218,7 +218,7 @@ Options:
218
218
  try {
219
219
  // Ensure backup directory exists
220
220
  try {
221
- await fs.access(this.backupDir);
221
+ await fsp.access(this.backupDir);
222
222
  } catch (err) {
223
223
  if (err.code === 'ENOENT') {
224
224
  logger.warn('No backups found. The backup directory does not exist yet.');
@@ -228,14 +228,14 @@ Options:
228
228
  return { success: true, backups: [] };
229
229
  }
230
230
 
231
- const files = await fs.readdir(this.backupDir);
231
+ const files = await fsp.readdir(this.backupDir);
232
232
  const backups = [];
233
233
 
234
234
  for (const file of files) {
235
235
  if (file.startsWith('backup-') && file.endsWith('.json')) {
236
236
  try {
237
237
  const filePath = path.join(this.backupDir, file);
238
- const stats = await fs.stat(filePath);
238
+ const stats = await fsp.stat(filePath);
239
239
  backups.push({
240
240
  name: file,
241
241
  path: filePath,
@@ -298,14 +298,14 @@ Options:
298
298
  logger.info('\nVerifying backup...');
299
299
 
300
300
  try {
301
- const data = await fs.readFile(backupPath, 'utf8');
301
+ const data = await fsp.readFile(backupPath, 'utf8');
302
302
  const content = JSON.parse(data);
303
303
 
304
304
  if (typeof content === 'object' && content !== null) {
305
305
  const fileCount = Object.keys(content).length;
306
306
  logger.success('Backup is valid');
307
307
  logger.info(` Contains ${fileCount} translation files`);
308
- logger.info(` Last modified: ${(await fs.stat(backupPath)).mtime.toLocaleString()}`);
308
+ logger.info(` Last modified: ${(await fsp.stat(backupPath)).mtime.toLocaleString()}`);
309
309
 
310
310
  return {
311
311
  success: true,
@@ -334,14 +334,14 @@ Options:
334
334
  logger.info('\nCleaning up old backups...');
335
335
 
336
336
  try {
337
- const files = await fs.readdir(this.backupDir);
337
+ const files = await fsp.readdir(this.backupDir);
338
338
  const backupFiles = files
339
339
  .filter(file => file.startsWith('backup-') && file.endsWith('.json'))
340
340
  .map(file => ({
341
341
  name: file,
342
- path: path.join(this.backupDir, file),
343
- time: fs.statSync(path.join(this.backupDir, file)).mtime.getTime()
344
- }))
342
+ path: path.join(this.backupDir, file),
343
+ time: fs.statSync(path.join(this.backupDir, file)).mtime.getTime()
344
+ }))
345
345
  .sort((a, b) => b.time - a.time);
346
346
 
347
347
  // Keep only the most recent 'keep' files
@@ -355,7 +355,7 @@ Options:
355
355
  // Delete old backups
356
356
  for (const file of toDelete) {
357
357
  try {
358
- await fs.unlink(file.path);
358
+ await fsp.unlink(file.path);
359
359
  logger.info(` - Deleted: ${file.name}`);
360
360
  } catch (err) {
361
361
  logger.error(` - Failed to delete ${file.name}: ${err.message}`);
@@ -374,12 +374,10 @@ Options:
374
374
  } catch (error) {
375
375
  logger.error('Error cleaning up backups:');
376
376
  logger.error(` ${error.message}`);
377
- if (process.env.DEBUG) {
378
- console.error(error);
379
- }
380
- throw error;
381
- }
382
- }
377
+ logger.debug(error.stack || error.message);
378
+ throw error;
379
+ }
380
+ }
383
381
 
384
382
  async cleanupOldBackups(outputDir) {
385
383
  try {
@@ -408,13 +406,11 @@ Options:
408
406
  }
409
407
  }
410
408
 
411
- handleError(error) {
412
- logger.error('Backup operation failed:');
413
- logger.error(` ${error.message}`);
414
- if (process.env.DEBUG) {
415
- console.error(error);
416
- }
417
- }
418
- }
409
+ handleError(error) {
410
+ logger.error('Backup operation failed:');
411
+ logger.error(` ${error.message}`);
412
+ logger.debug(error.stack || error.message);
413
+ }
414
+ }
419
415
 
420
- module.exports = I18nBackup;
416
+ module.exports = I18nBackup;