i18ntk 1.7.0 → 1.7.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 +18 -55
- package/main/i18ntk-analyze.js +2 -2
- package/main/i18ntk-autorun.js +1 -1
- package/main/i18ntk-complete.js +1 -1
- package/main/i18ntk-init.js +42 -9
- package/main/i18ntk-manage.js +236 -40
- package/main/i18ntk-sizing.js +2 -2
- package/main/i18ntk-summary.js +2 -2
- package/main/i18ntk-ui.js +6 -0
- package/main/i18ntk-usage.js +2 -2
- package/main/i18ntk-validate.js +47 -11
- package/package.json +7 -8
- package/scripts/admin-auth.test.js +21 -0
- package/scripts/prepublish.js +1 -1
- package/settings/.i18n-admin-config.json +2 -2
- package/settings/i18ntk-config.json +0 -1
- package/settings/initialization.json +7 -0
- package/settings/settings-cli.js +1 -1
- package/settings/settings-manager.js +2 -2
- package/ui-locales/de.json +2 -0
- package/ui-locales/en.json +2 -0
- package/ui-locales/es.json +2 -0
- package/ui-locales/fr.json +2 -0
- package/ui-locales/ja.json +2 -0
- package/ui-locales/ru.json +35 -33
- package/ui-locales/zh.json +2 -0
- package/utils/admin-auth.js +88 -19
- package/utils/admin-cli.js +15 -3
- package/utils/admin-pin.js +519 -526
- package/utils/cli-helper.js +84 -11
- package/utils/config-helper.js +60 -6
- package/utils/config-manager.js +2 -1
- package/utils/config.js +41 -0
- package/utils/i18n-helper.js +2 -1
- package/utils/promptPin.js +76 -0
- package/utils/security-check.js +6 -2
- package/utils/security.js +1 -1
package/utils/cli-helper.js
CHANGED
|
@@ -129,17 +129,90 @@ function showFrameworkWarningOnce(ui) {
|
|
|
129
129
|
if (frameworkWarningShown) return;
|
|
130
130
|
frameworkWarningShown = true;
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
132
|
+
// Try to use the proper translation system first
|
|
133
|
+
let t;
|
|
134
|
+
if (ui && typeof ui.t === 'function') {
|
|
135
|
+
t = ui.t.bind(ui);
|
|
136
|
+
} else {
|
|
137
|
+
// Fallback to loading the UI i18n system properly
|
|
138
|
+
try {
|
|
139
|
+
const UIi18n = require('../main/i18ntk-ui');
|
|
140
|
+
const fallbackUI = new UIi18n();
|
|
141
|
+
fallbackUI.loadLanguage('en'); // Load English as fallback
|
|
142
|
+
t = fallbackUI.t.bind(fallbackUI);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
// Last resort: use locale files directly
|
|
145
|
+
try {
|
|
146
|
+
const path = require('path');
|
|
147
|
+
const fs = require('fs');
|
|
148
|
+
const localePath = path.join(__dirname, '..', 'ui-locales', 'en.json');
|
|
149
|
+
if (fs.existsSync(localePath)) {
|
|
150
|
+
const translations = JSON.parse(fs.readFileSync(localePath, 'utf8'));
|
|
151
|
+
t = (key) => {
|
|
152
|
+
const keys = key.split('.');
|
|
153
|
+
let result = translations;
|
|
154
|
+
for (const k of keys) {
|
|
155
|
+
result = result && result[k];
|
|
156
|
+
}
|
|
157
|
+
return result || key;
|
|
158
|
+
};
|
|
159
|
+
} else {
|
|
160
|
+
throw new Error('Locale file not found');
|
|
161
|
+
}
|
|
162
|
+
} catch (fallbackError) {
|
|
163
|
+
// Final fallback: load from current UI locale or English
|
|
164
|
+
try {
|
|
165
|
+
const path = require('path');
|
|
166
|
+
const fs = require('fs');
|
|
167
|
+
|
|
168
|
+
// Try to determine current language from settings
|
|
169
|
+
const settingsManager = require('../settings/settings-manager');
|
|
170
|
+
const settings = settingsManager.loadSettings();
|
|
171
|
+
const currentLang = settings.uiLanguage || 'en';
|
|
172
|
+
|
|
173
|
+
const localePath = path.join(__dirname, '..', 'ui-locales', `${currentLang}.json`);
|
|
174
|
+
if (fs.existsSync(localePath)) {
|
|
175
|
+
const translations = JSON.parse(fs.readFileSync(localePath, 'utf8'));
|
|
176
|
+
t = (key) => {
|
|
177
|
+
const keys = key.split('.');
|
|
178
|
+
let result = translations;
|
|
179
|
+
for (const k of keys) {
|
|
180
|
+
result = result && result[k];
|
|
181
|
+
}
|
|
182
|
+
return result || key;
|
|
183
|
+
};
|
|
184
|
+
} else {
|
|
185
|
+
// Fallback to English
|
|
186
|
+
const enLocalePath = path.join(__dirname, '..', 'ui-locales', 'en.json');
|
|
187
|
+
if (fs.existsSync(enLocalePath)) {
|
|
188
|
+
const translations = JSON.parse(fs.readFileSync(enLocalePath, 'utf8'));
|
|
189
|
+
t = (key) => {
|
|
190
|
+
const keys = key.split('.');
|
|
191
|
+
let result = translations;
|
|
192
|
+
for (const k of keys) {
|
|
193
|
+
result = result && result[k];
|
|
194
|
+
}
|
|
195
|
+
return result || key;
|
|
196
|
+
};
|
|
197
|
+
} else {
|
|
198
|
+
throw new Error('No locale files found');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (finalError) {
|
|
202
|
+
// Absolute last resort: basic hardcoded fallback
|
|
203
|
+
const messages = {
|
|
204
|
+
'init.suggestions.noFramework': 'No i18n framework detected. Consider using one of the following:',
|
|
205
|
+
'init.frameworks.react': ' - React i18next (react-i18next)',
|
|
206
|
+
'init.frameworks.vue': ' - Vue i18n (vue-i18next)',
|
|
207
|
+
'init.frameworks.i18next': ' - i18next (i18next)',
|
|
208
|
+
'init.frameworks.nuxt': ' - Nuxt i18n (@nuxtjs/i18next)',
|
|
209
|
+
'init.frameworks.svelte': ' - Svelte i18n (svelte-i18next)'
|
|
210
|
+
};
|
|
211
|
+
t = (key) => messages[key] || key;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
143
216
|
|
|
144
217
|
console.log(t('init.suggestions.noFramework'));
|
|
145
218
|
console.log(t('init.frameworks.react'));
|
package/utils/config-helper.js
CHANGED
|
@@ -64,6 +64,7 @@ async function getUnifiedConfig(scriptName, cliArgs = {}) {
|
|
|
64
64
|
outputDir: configManager.toRelative(cfg.outputDir),
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
const settingsDir = settingsManager.configDir;
|
|
67
68
|
const config = {
|
|
68
69
|
...cfg,
|
|
69
70
|
sourceLanguage: cliArgs.sourceLanguage || cfg.sourceLanguage || 'en',
|
|
@@ -73,10 +74,10 @@ async function getUnifiedConfig(scriptName, cliArgs = {}) {
|
|
|
73
74
|
excludeFiles: cfg.excludeFiles || cfg.processing?.excludeFiles || ['.DS_Store', 'Thumbs.db'],
|
|
74
75
|
excludeDirs: cfg.excludeDirs || cfg.processing?.excludeDirs || ['node_modules', '.next', '.git', 'dist', 'build'],
|
|
75
76
|
strictMode: cliArgs.strictMode || cfg.strictMode || false,
|
|
76
|
-
backupDir: path.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
backupDir: path.join(settingsDir, 'backups'),
|
|
78
|
+
tempDir: path.join(settingsDir, 'temp'),
|
|
79
|
+
cacheDir: path.join(settingsDir, '.cache'),
|
|
80
|
+
configDir: settingsDir,
|
|
80
81
|
settings: {
|
|
81
82
|
defaultLanguages: cfg.defaultLanguages || ['de', 'es', 'fr', 'ru'],
|
|
82
83
|
processing: { ...cfg.processing },
|
|
@@ -267,13 +268,42 @@ function displayPaths(cfg = {}) {
|
|
|
267
268
|
// Ensure project has been initialized with source language files
|
|
268
269
|
async function ensureInitialized(cfg) {
|
|
269
270
|
try {
|
|
271
|
+
// Check if initialization has been marked as complete
|
|
272
|
+
const configPath = path.join(settingsManager.configDir, 'initialization.json');
|
|
273
|
+
let initStatus = { initialized: false, version: null, timestamp: null };
|
|
274
|
+
|
|
275
|
+
if (fs.existsSync(configPath)) {
|
|
276
|
+
try {
|
|
277
|
+
initStatus = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
278
|
+
// If initialized and version matches current, skip further checks
|
|
279
|
+
if (initStatus.initialized && initStatus.version === '1.7.2') {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
} catch (e) {
|
|
283
|
+
// Invalid initialization file, proceed with normal check
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
270
287
|
const sourceDir = cfg.sourceDir;
|
|
271
288
|
const sourceLanguage = cfg.sourceLanguage || 'en';
|
|
272
289
|
const langDir = path.join(sourceDir, sourceLanguage);
|
|
273
290
|
|
|
274
|
-
const
|
|
291
|
+
const hasLanguageFiles = fs.existsSync(langDir) &&
|
|
275
292
|
fs.readdirSync(langDir).some(f => f.endsWith('.json'));
|
|
276
|
-
|
|
293
|
+
|
|
294
|
+
// If language files exist and we're upgrading, mark as initialized
|
|
295
|
+
if (hasLanguageFiles) {
|
|
296
|
+
const initDir = path.dirname(configPath);
|
|
297
|
+
ensureDirectory(initDir);
|
|
298
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
299
|
+
initialized: true,
|
|
300
|
+
version: '1.7.2',
|
|
301
|
+
timestamp: new Date().toISOString(),
|
|
302
|
+
sourceDir: sourceDir,
|
|
303
|
+
sourceLanguage: sourceLanguage
|
|
304
|
+
}, null, 2));
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
277
307
|
|
|
278
308
|
const nonInteractive = !process.stdin.isTTY;
|
|
279
309
|
const initScript = path.join(__dirname, '..', 'main', 'i18ntk-init.js');
|
|
@@ -281,6 +311,18 @@ async function ensureInitialized(cfg) {
|
|
|
281
311
|
if (nonInteractive) {
|
|
282
312
|
console.warn(`Missing source language files in ${langDir}. Running initialization...`);
|
|
283
313
|
const result = spawnSync(process.execPath, [initScript, '--yes', `--source-dir=${sourceDir}`, `--source-language=${sourceLanguage}`], { stdio: 'inherit', windowsHide: true });
|
|
314
|
+
if (result.status === 0) {
|
|
315
|
+
// Mark initialization as complete
|
|
316
|
+
const initDir = path.dirname(configPath);
|
|
317
|
+
ensureDirectory(initDir);
|
|
318
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
319
|
+
initialized: true,
|
|
320
|
+
version: '1.7.2',
|
|
321
|
+
timestamp: new Date().toISOString(),
|
|
322
|
+
sourceDir: sourceDir,
|
|
323
|
+
sourceLanguage: sourceLanguage
|
|
324
|
+
}, null, 2));
|
|
325
|
+
}
|
|
284
326
|
return result.status === 0;
|
|
285
327
|
}
|
|
286
328
|
|
|
@@ -290,6 +332,18 @@ async function ensureInitialized(cfg) {
|
|
|
290
332
|
|
|
291
333
|
if (answer.trim().toLowerCase().startsWith('y')) {
|
|
292
334
|
const result = spawnSync(process.execPath, [initScript, `--source-dir=${sourceDir}`, `--source-language=${sourceLanguage}`], { stdio: 'inherit', windowsHide: true });
|
|
335
|
+
if (result.status === 0) {
|
|
336
|
+
// Mark initialization as complete
|
|
337
|
+
const initDir = path.dirname(configPath);
|
|
338
|
+
ensureDirectory(initDir);
|
|
339
|
+
fs.writeFileSync(configPath, JSON.stringify({
|
|
340
|
+
initialized: true,
|
|
341
|
+
version: '1.7.2',
|
|
342
|
+
timestamp: new Date().toISOString(),
|
|
343
|
+
sourceDir: sourceDir,
|
|
344
|
+
sourceLanguage: sourceLanguage
|
|
345
|
+
}, null, 2));
|
|
346
|
+
}
|
|
293
347
|
return result.status === 0;
|
|
294
348
|
}
|
|
295
349
|
return false;
|
package/utils/config-manager.js
CHANGED
|
@@ -3,7 +3,8 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
// Project root is where commands are executed
|
|
5
5
|
const projectRoot = process.cwd();
|
|
6
|
-
const
|
|
6
|
+
const settingsManager = require('../settings/settings-manager');
|
|
7
|
+
const CONFIG_DIR = settingsManager.configDir;
|
|
7
8
|
const CONFIG_PATH = path.join(CONFIG_DIR, 'i18ntk-config.json');
|
|
8
9
|
|
|
9
10
|
// Default configuration values - comprehensive configuration
|
package/utils/config.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const settingsManager = require('../settings/settings-manager');
|
|
5
|
+
const CONFIG_FILE = 'i18ntk-config.json';
|
|
6
|
+
|
|
7
|
+
function getConfigPath(cwd = settingsManager.configDir) {
|
|
8
|
+
return path.join(cwd, CONFIG_FILE);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function loadConfig(cwd = settingsManager.configDir) {
|
|
12
|
+
const p = getConfigPath(cwd);
|
|
13
|
+
try {
|
|
14
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
15
|
+
return JSON.parse(raw);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function saveConfig(cfg, cwd = settingsManager.configDir) {
|
|
22
|
+
const dir = cwd;
|
|
23
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
24
|
+
fs.writeFileSync(path.join(dir, CONFIG_FILE), JSON.stringify(cfg, null, 2), 'utf8');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ensureConfigDefaults(cfg = {}) {
|
|
28
|
+
return {
|
|
29
|
+
initialized: cfg.initialized ?? false,
|
|
30
|
+
i18nDir: cfg.i18nDir ?? null,
|
|
31
|
+
sourceDir: cfg.sourceDir ?? null,
|
|
32
|
+
framework: {
|
|
33
|
+
detected: cfg.framework && cfg.framework.detected != null ? cfg.framework.detected : null,
|
|
34
|
+
preference: cfg.framework && cfg.framework.preference != null ? cfg.framework.preference : 'none',
|
|
35
|
+
prompt: cfg.framework && cfg.framework.prompt != null ? cfg.framework.prompt : 'always',
|
|
36
|
+
lastPromptedVersion: cfg.framework && cfg.framework.lastPromptedVersion != null ? cfg.framework.lastPromptedVersion : null
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { getConfigPath, loadConfig, saveConfig, ensureConfigDefaults };
|
package/utils/i18n-helper.js
CHANGED
|
@@ -125,7 +125,8 @@ function loadTranslations(language) {
|
|
|
125
125
|
const settings = cfg?.getConfig?.() || {};
|
|
126
126
|
const configuredLanguage = settings.uiLanguage || settings.language || 'en';
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
// Prioritize settings file language over environment variable
|
|
129
|
+
const requested = (configuredLanguage || language || 'en').toString();
|
|
129
130
|
const short = requested.split('-')[0].toLowerCase();
|
|
130
131
|
const tryOrder = [requested, short, 'en'];
|
|
131
132
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
|
|
3
|
+
async function promptPin({ rl, label = 'Enter 4-digit Admin PIN: ', length = 4, digitsOnly = true } = {}) {
|
|
4
|
+
return rawMaskedPrompt(rl, label, { length, digitsOnly });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async function promptPinConfirm(rl, label1 = 'Enter new 4-digit Admin PIN: ', label2 = 'Confirm PIN: ') {
|
|
8
|
+
const pin1 = await promptPin({ rl, label: label1 });
|
|
9
|
+
const pin2 = await promptPin({ rl, label: label2 });
|
|
10
|
+
if (pin1 !== pin2) throw new Error('PINs do not match');
|
|
11
|
+
return pin1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function rawMaskedPrompt(rl, promptText, { length = 4, digitsOnly = true } = {}) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const input = rl.input;
|
|
17
|
+
const output = rl.output;
|
|
18
|
+
|
|
19
|
+
const isRaw = input.isRaw;
|
|
20
|
+
if (!isRaw) input.setRawMode && input.setRawMode(true);
|
|
21
|
+
|
|
22
|
+
let buf = '';
|
|
23
|
+
|
|
24
|
+
const onData = (chunk) => {
|
|
25
|
+
const s = chunk.toString('utf8');
|
|
26
|
+
|
|
27
|
+
if (s === '\r' || s === '\n') {
|
|
28
|
+
if (buf.length === length) {
|
|
29
|
+
output.write('\n');
|
|
30
|
+
cleanup();
|
|
31
|
+
return resolve(buf);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (s === '\u0003') {
|
|
37
|
+
output.write('\n');
|
|
38
|
+
cleanup();
|
|
39
|
+
process.exit(130);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (s === '\u0008' || s === '\u007f') {
|
|
43
|
+
if (buf.length > 0) buf = buf.slice(0, -1);
|
|
44
|
+
repaint();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ch = s;
|
|
49
|
+
if (digitsOnly) {
|
|
50
|
+
if (/^\d$/.test(ch) && buf.length < length) {
|
|
51
|
+
buf += ch;
|
|
52
|
+
repaint();
|
|
53
|
+
}
|
|
54
|
+
} else if (buf.length < length) {
|
|
55
|
+
buf += ch;
|
|
56
|
+
repaint();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const repaint = () => {
|
|
61
|
+
output.cursorTo && output.cursorTo(0);
|
|
62
|
+
output.clearLine && output.clearLine(1);
|
|
63
|
+
output.write(promptText + '*'.repeat(buf.length));
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const cleanup = () => {
|
|
67
|
+
input.removeListener('data', onData);
|
|
68
|
+
if (!isRaw) input.setRawMode && input.setRawMode(false);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
output.write(promptText);
|
|
72
|
+
input.on('data', onData);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { promptPin, promptPinConfirm };
|
package/utils/security-check.js
CHANGED
|
@@ -20,8 +20,12 @@ class SecurityChecker {
|
|
|
20
20
|
* Check if we should suppress output (e.g., during npm install)
|
|
21
21
|
*/
|
|
22
22
|
shouldBeSilent() {
|
|
23
|
-
//
|
|
24
|
-
return
|
|
23
|
+
// Determine silent mode based on environment variables
|
|
24
|
+
return (
|
|
25
|
+
process.env.npm_config_loglevel === 'silent' ||
|
|
26
|
+
process.env.I18NTK_SILENT === 'true' ||
|
|
27
|
+
process.env.CI === 'true'
|
|
28
|
+
);
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
package/utils/security.js
CHANGED
|
@@ -356,7 +356,7 @@ class SecurityUtils {
|
|
|
356
356
|
static async saveEncryptedPin(pin) {
|
|
357
357
|
try {
|
|
358
358
|
const hash = crypto.createHash('sha256').update(pin).digest('hex');
|
|
359
|
-
const settingsDir =
|
|
359
|
+
const settingsDir = require('../settings/settings-manager').configDir;
|
|
360
360
|
const pinFile = path.join(settingsDir, 'admin-pin.hash');
|
|
361
361
|
await fs.promises.mkdir(settingsDir, { recursive: true });
|
|
362
362
|
await fs.promises.writeFile(pinFile, hash, 'utf8');
|