i18ntk 2.3.0 → 2.3.2

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # i18ntk v2.3.0
1
+ # i18ntk v2.3.2
2
2
 
3
3
  Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, and translation completion.
4
4
 
@@ -9,12 +9,14 @@ Zero-dependency internationalization toolkit for setup, scanning, analysis, vali
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.3.0)](https://socket.dev/npm/package/i18ntk/overview/2.3.0)
12
+ [![socket](https://socket.dev/api/badge/npm/package/i18ntk/2.3.2)](https://socket.dev/npm/package/i18ntk/overview/2.3.2)
13
13
 
14
14
  ## Upgrade Notice
15
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.
16
+ Versions earlier than `2.3.2` may contain known stability and security issues.
17
+ They are considered unsupported for production use. Upgrade to `2.3.2` or newer.
18
+ The CLI now checks npm registry metadata at startup and warns when your installed version is out of date.
19
+ Set `I18NTK_DISABLE_UPDATE_CHECK=true` to disable this warning in restricted/offline environments.
18
20
 
19
21
  ## What i18ntk Does
20
22
 
@@ -151,7 +153,7 @@ Example `.i18ntk-config`:
151
153
 
152
154
  ```json
153
155
  {
154
- "version": "2.3.0",
156
+ "version": "2.3.2",
155
157
  "sourceDir": "./locales",
156
158
  "i18nDir": "./locales",
157
159
  "outputDir": "./i18ntk-reports",
@@ -174,7 +176,7 @@ See [docs/api/CONFIGURATION.md](docs/api/CONFIGURATION.md) for the full configur
174
176
  - [Runtime API Guide](docs/runtime.md)
175
177
  - [Scanner Guide](docs/scanner-guide.md)
176
178
  - [Environment Variables](docs/environment-variables.md)
177
- - [Migration Guide v2.3.0](docs/migration-guide-v2.3.0.md)
179
+ - [Migration Guide v2.3.2](docs/migration-guide-v2.3.2.md)
178
180
  - [Optimization Prompt](docs/development/package-optimization-prompt.md)
179
181
 
180
182
  ## License
@@ -7,10 +7,11 @@
7
7
  * Contains embedded business logic from I18nAnalyzer.
8
8
  */
9
9
 
10
+ const fs = require('fs');
10
11
  const path = require('path');
11
12
  const cliHelper = require('../../../utils/cli-helper');
12
13
  const { loadTranslations, t } = require('../../../utils/i18n-helper');
13
- const { getUnifiedConfig, parseCommonArgs, displayHelp } = require('../../../utils/config-helper');
14
+ const { getUnifiedConfig, parseCommonArgs, displayHelp, validateSourceDir } = require('../../../utils/config-helper');
14
15
  const SecurityUtils = require('../../../utils/security');
15
16
  const AdminCLI = require('../../../utils/admin-cli');
16
17
  const AdminAuth = require('../../../utils/admin-auth');
@@ -21,7 +22,7 @@ loadTranslations('en', path.resolve(__dirname, '../../../ui-locales'));
21
22
 
22
23
  const PROJECT_ROOT = process.cwd();
23
24
 
24
- class AnalyzeCommand {
25
+ class AnalyzeCommand {
25
26
  constructor(config = {}, ui = null) {
26
27
  this.config = config;
27
28
  this.ui = ui;
@@ -44,11 +45,11 @@ class AnalyzeCommand {
44
45
  this.safeClose = safeClose;
45
46
  }
46
47
 
47
- /**
48
- * Initialize the analyzer with configuration
49
- */
50
- async initialize() {
51
- try {
48
+ /**
49
+ * Initialize the analyzer with configuration
50
+ */
51
+ async initialize() {
52
+ try {
52
53
  const args = this.parseArgs();
53
54
  if (args.help) {
54
55
  displayHelp('i18ntk-analyze', {
@@ -78,17 +79,62 @@ class AnalyzeCommand {
78
79
  loadTranslations(uiLanguage, path.resolve(__dirname, '../../../ui-locales'));
79
80
 
80
81
  this.sourceDir = this.config.sourceDir;
81
- this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
82
- this.outputDir = this.config.outputDir;
83
-
84
- // Validate source directory exists
85
- validateSourceDir(this.sourceDir, 'i18ntk-analyze');
86
-
87
- } catch (error) {
88
- console.error(`Fatal analysis error: ${error.message}`);
89
- throw error;
90
- }
91
- }
82
+ this.sourceLanguageDir = path.join(this.sourceDir, this.config.sourceLanguage);
83
+ this.outputDir = this.config.outputDir;
84
+
85
+ // Validate source directory exists and is readable/writable
86
+ this.validateSourceDirWithFallback(this.sourceDir);
87
+
88
+ } catch (error) {
89
+ SecurityUtils.logSecurityEvent('Analyze command initialization failed', 'error', {
90
+ component: 'AnalyzeCommand',
91
+ error: error.message
92
+ });
93
+ console.error(`Fatal analysis error: ${error.message}`);
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Validate source directory with fallback logic if the shared helper is unavailable.
100
+ * This prevents runtime crashes from missing imports or module regressions.
101
+ * @param {string} sourceDir
102
+ */
103
+ validateSourceDirWithFallback(sourceDir) {
104
+ if (!sourceDir || typeof sourceDir !== 'string') {
105
+ throw new Error('Source directory is missing or invalid');
106
+ }
107
+
108
+ if (typeof validateSourceDir === 'function') {
109
+ validateSourceDir(sourceDir, 'i18ntk-analyze');
110
+ } else {
111
+ // Graceful fallback: create directory if needed.
112
+ const created = SecurityUtils.safeMkdirSync(sourceDir, process.cwd(), { recursive: true });
113
+ if (created) {
114
+ SecurityUtils.logSecurityEvent('Fallback source directory creation executed', 'warn', {
115
+ component: 'AnalyzeCommand',
116
+ sourceDir,
117
+ reason: 'validateSourceDir helper unavailable'
118
+ });
119
+ }
120
+ }
121
+
122
+ const safePath = SecurityUtils.validatePath(sourceDir, process.cwd());
123
+ if (!safePath) {
124
+ throw new Error(`Source directory path is unsafe: ${sourceDir}`);
125
+ }
126
+
127
+ const stat = SecurityUtils.safeStatSync(safePath, process.cwd());
128
+ if (!stat || !stat.isDirectory()) {
129
+ throw new Error(`Source directory does not exist or is not a directory: ${safePath}`);
130
+ }
131
+
132
+ try {
133
+ fs.accessSync(safePath, fs.constants.R_OK | fs.constants.W_OK);
134
+ } catch {
135
+ throw new Error(`Insufficient permissions for source directory: ${safePath}`);
136
+ }
137
+ }
92
138
 
93
139
  // Initialize readline interface (deprecated - use cliHelper directly)
94
140
  initReadline() {
@@ -24,6 +24,7 @@ const { showFrameworkWarningOnce } = require('../../utils/cli-helper');
24
24
  const { createPrompt, isInteractive } = require('../../utils/prompt-helper');
25
25
  const { loadTranslations, t, refreshLanguageFromSettings} = require('../../utils/i18n-helper');
26
26
  const cliHelper = require('../../utils/cli-helper');
27
+ const { printUpgradeWarningIfOutdated } = require('../../utils/npm-version-warning');
27
28
  const { blue } = require('../../utils/colors-new');
28
29
  const { loadConfig, saveConfig, ensureConfigDefaults } = require('../../utils/config');
29
30
  const SettingsCLI = require('../../settings/settings-cli');
@@ -315,10 +316,20 @@ class I18nManager {
315
316
  // Show help immediately without any setup/auth (useful for CI/uninitialized projects)
316
317
  if (args.help) {
317
318
  this.showHelp();
318
- return;
319
- }
320
-
321
- let startupTimeout = setTimeout(() => {
319
+ return;
320
+ }
321
+
322
+ // Warn users when their installed CLI version is behind npm latest.
323
+ try {
324
+ await printUpgradeWarningIfOutdated({
325
+ packageName: pkg.name,
326
+ currentVersion: pkg.version
327
+ });
328
+ } catch {
329
+ // Keep startup resilient if registry access is unavailable.
330
+ }
331
+
332
+ let startupTimeout = setTimeout(() => {
322
333
  console.error('❌ CLI startup timeout - something is hanging');
323
334
  process.exit(1);
324
335
  }, 10000); // 10 second timeout
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18ntk",
3
- "version": "2.3.0",
3
+ "version": "2.3.2",
4
4
  "description": "🚀 The fastest internationalization toolkit with 97% performance boost! Zero-dependency, enterprise-grade internationalization for React, Vue, Angular, Python, Java, PHP & more. Features PIN protection, auto framework detection, 7+ UI languages, and comprehensive translation management. Perfect for startups to enterprises.",
5
5
  "keywords": [
6
6
  "i18n",
@@ -227,17 +227,19 @@
227
227
  },
228
228
  "preferGlobal": true,
229
229
  "versionInfo": {
230
- "version": "2.3.0",
230
+ "version": "2.3.2",
231
231
  "releaseDate": "12/04/2026",
232
232
  "lastUpdated": "12/04/2026",
233
- "maintainer": "Vladimir Noskov",
233
+ "maintainer": "Vlad Noskov",
234
234
  "changelog": "./CHANGELOG.md",
235
235
  "documentation": "./README.md",
236
236
  "apiReference": "./docs/api/API_REFERENCE.md",
237
237
  "majorChanges": [
238
+ "HOTFIX: Removed deprecated package-path fallback that caused production build warnings for non-exported subpaths.",
238
239
  "CRITICAL FIX: Resolved sizing and usage-analysis regressions in v2 command flow.",
239
240
  "PACKAGING: Reduced publish footprint by removing internal development scripts and legacy fixed-file artifacts.",
240
- "SECURITY: Hardened release checks and added explicit support guidance to update from pre-2.3.0 versions.",
241
+ "SECURITY: Hardened release checks and added explicit support guidance to update from pre-2.3.2 versions.",
242
+ "CLI: Added npm registry version check with upgrade warning for out-of-date installs.",
241
243
  "I18N: Completed internal UI locale parity and actionable untranslated-key cleanup across supported languages."
242
244
  ],
243
245
  "breakingChanges": [],
@@ -263,7 +265,7 @@
263
265
  "spring-boot": ">=2.5.0",
264
266
  "laravel": ">=8.0.0"
265
267
  },
266
- "supportPolicy": "Versions earlier than 2.3.0 may be unstable or insecure. Upgrade to 2.3.0 or newer."
268
+ "supportPolicy": "Versions earlier than 2.3.2 may be unstable or insecure. Upgrade to 2.3.2 or newer."
267
269
  },
268
270
  "_comment": "This package is zero-dependency and uses only native Node.js modules"
269
271
  }
@@ -476,7 +476,7 @@
476
476
  "report_saved_to": "Report saved to: {reportPath}",
477
477
  "considerReviewingTranslations": "Consider reviewing ${lang} translations - they are ${data.percentageDifference}% longer than baseline",
478
478
  "csv_report_saved_to": "CSV report saved to: {csvPath}",
479
- "human_report_saved": "Human-readable report saved: {reportPath}",
479
+ "human_report_saved": "Readable report saved: {reportPath}",
480
480
  "folder_summary_title": "Folder Summary",
481
481
  "folder_summary_table_header": "Language Size(KB) Keys Avg Length Total Chars",
482
482
  "folder_summary_row": "{lang} {sizeKB} {totalKeys} {avgLength} {totalChars}",
@@ -100,19 +100,15 @@ function pkgUiLocalesDirViaThisFile() {
100
100
  return path.resolve(__dirname, '..', 'ui-locales');
101
101
  }
102
102
 
103
- function pkgUiLocalesDirViaResolve() {
104
- try {
105
- // Try the new correct path first (ui-locales/en.json)
106
- const enJson = require.resolve('i18ntk/ui-locales/en.json');
107
- return path.dirname(enJson);
108
- } catch {
109
- try {
110
- // Fallback to the old incorrect path for backward compatibility
111
- const enJson = require.resolve('i18ntk/resources/i18n/ui-locales/en.json');
112
- return path.dirname(enJson);
113
- } catch { return null; }
114
- }
115
- }
103
+ function pkgUiLocalesDirViaResolve() {
104
+ try {
105
+ // Resolve using the current exported package path.
106
+ const enJson = require.resolve('i18ntk/ui-locales/en.json');
107
+ return path.dirname(enJson);
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
116
112
 
117
113
  // Removed legacyResourcesUiLocalesDir as resources/i18n/ui-locales is deprecated
118
114
 
@@ -0,0 +1,142 @@
1
+ const https = require('https');
2
+ const { compareVersions } = require('./version-utils');
3
+
4
+ const DEFAULT_TIMEOUT_MS = 1800;
5
+ const NPM_REGISTRY_BASE = 'https://registry.npmjs.org';
6
+
7
+ function isSemverLike(version) {
8
+ return typeof version === 'string' && /^\d+\.\d+\.\d+([-.][0-9A-Za-z.-]+)?$/.test(version.trim());
9
+ }
10
+
11
+ function fetchPackageMetadata(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
12
+ const safePackageName = encodeURIComponent(packageName);
13
+ const requestUrl = `${NPM_REGISTRY_BASE}/${safePackageName}`;
14
+
15
+ return new Promise((resolve) => {
16
+ let settled = false;
17
+
18
+ const finish = (value) => {
19
+ if (!settled) {
20
+ settled = true;
21
+ resolve(value);
22
+ }
23
+ };
24
+
25
+ const req = https.get(
26
+ requestUrl,
27
+ {
28
+ timeout: timeoutMs,
29
+ headers: {
30
+ Accept: 'application/json',
31
+ 'User-Agent': 'i18ntk-version-check'
32
+ }
33
+ },
34
+ (res) => {
35
+ if (res.statusCode !== 200) {
36
+ res.resume();
37
+ finish(null);
38
+ return;
39
+ }
40
+
41
+ let raw = '';
42
+ res.on('data', (chunk) => {
43
+ raw += chunk;
44
+ });
45
+ res.on('end', () => {
46
+ try {
47
+ finish(JSON.parse(raw));
48
+ } catch {
49
+ finish(null);
50
+ }
51
+ });
52
+ }
53
+ );
54
+
55
+ req.on('timeout', () => {
56
+ req.destroy();
57
+ finish(null);
58
+ });
59
+ req.on('error', () => finish(null));
60
+ });
61
+ }
62
+
63
+ function getOutdatedStatus(currentVersion, metadata) {
64
+ if (!isSemverLike(currentVersion) || !metadata || !metadata.versions) {
65
+ return null;
66
+ }
67
+
68
+ const distTags = metadata['dist-tags'] || {};
69
+ const taggedLatest = distTags.latest;
70
+ const allPublishedVersions = Object.keys(metadata.versions).filter(isSemverLike);
71
+
72
+ if (allPublishedVersions.length === 0) {
73
+ return null;
74
+ }
75
+
76
+ const latestVersion = isSemverLike(taggedLatest)
77
+ ? taggedLatest
78
+ : allPublishedVersions.sort(compareVersions).at(-1);
79
+
80
+ if (!latestVersion || !isSemverLike(latestVersion)) {
81
+ return null;
82
+ }
83
+
84
+ const currentMeta = metadata.versions[currentVersion] || null;
85
+ const isCurrentDeprecated = Boolean(currentMeta && currentMeta.deprecated);
86
+ const isOutdated = compareVersions(currentVersion, latestVersion) < 0;
87
+
88
+ const newerStableVersions = allPublishedVersions.filter((version) => (
89
+ compareVersions(version, currentVersion) > 0 &&
90
+ compareVersions(version, latestVersion) <= 0
91
+ ));
92
+
93
+ return {
94
+ latestVersion,
95
+ isOutdated,
96
+ isCurrentDeprecated,
97
+ newerStableCount: newerStableVersions.length
98
+ };
99
+ }
100
+
101
+ async function checkNpmOutdated({ packageName, currentVersion, timeoutMs = DEFAULT_TIMEOUT_MS }) {
102
+ const metadata = await fetchPackageMetadata(packageName, timeoutMs);
103
+ return getOutdatedStatus(currentVersion, metadata);
104
+ }
105
+
106
+ async function printUpgradeWarningIfOutdated({
107
+ packageName,
108
+ currentVersion,
109
+ timeoutMs = DEFAULT_TIMEOUT_MS
110
+ }) {
111
+ if (process.env.I18NTK_DISABLE_UPDATE_CHECK === 'true') {
112
+ return;
113
+ }
114
+
115
+ const status = await checkNpmOutdated({ packageName, currentVersion, timeoutMs });
116
+ if (!status) {
117
+ return;
118
+ }
119
+
120
+ if (status.isCurrentDeprecated) {
121
+ console.warn(
122
+ `\n⚠️ Installed ${packageName}@${currentVersion} is deprecated on npm. ` +
123
+ `Upgrade to ${packageName}@${status.latestVersion}:\n` +
124
+ ` npm install -g ${packageName}@latest`
125
+ );
126
+ return;
127
+ }
128
+
129
+ if (status.isOutdated) {
130
+ const suffix = status.newerStableCount === 1 ? '' : 's';
131
+ console.warn(
132
+ `\n⚠️ Update available for ${packageName}: ${currentVersion} -> ${status.latestVersion} ` +
133
+ `(${status.newerStableCount} newer release${suffix}).\n` +
134
+ ` Run: npm install -g ${packageName}@latest`
135
+ );
136
+ }
137
+ }
138
+
139
+ module.exports = {
140
+ checkNpmOutdated,
141
+ printUpgradeWarningIfOutdated
142
+ };