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.
@@ -129,17 +129,90 @@ function showFrameworkWarningOnce(ui) {
129
129
  if (frameworkWarningShown) return;
130
130
  frameworkWarningShown = true;
131
131
 
132
- const t = ui && typeof ui.t === 'function' ? ui.t.bind(ui) : (key) => {
133
- const messages = {
134
- 'init.suggestions.noFramework': 'No i18n framework detected. Consider using one of the following:',
135
- 'init.frameworks.react': ' - react-i18next',
136
- 'init.frameworks.vue': ' - vue-i18n',
137
- 'init.frameworks.i18next': ' - i18next',
138
- 'init.frameworks.nuxt': ' - @nuxtjs/i18n',
139
- 'init.frameworks.svelte': ' - svelte-i18n'
140
- };
141
- return messages[key] || key;
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'));
@@ -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.resolve(cfg.projectRoot, path.join('settings', 'backups')),
77
- tempDir: path.resolve(cfg.projectRoot, path.join('settings', 'temp')),
78
- cacheDir: path.resolve(cfg.projectRoot, path.join('settings', '.cache')),
79
- configDir: path.resolve(cfg.projectRoot, 'settings'),
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 initialized = fs.existsSync(langDir) &&
291
+ const hasLanguageFiles = fs.existsSync(langDir) &&
275
292
  fs.readdirSync(langDir).some(f => f.endsWith('.json'));
276
- if (initialized) return true;
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;
@@ -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 CONFIG_DIR = path.join(projectRoot, 'settings');
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
@@ -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 };
@@ -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
- const requested = (language || configuredLanguage || 'en').toString();
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 };
@@ -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
- // Check if we're running in silent mode or specific contexts
24
- return this.isSilent;
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 = path.join(process.cwd(), 'settings');
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');