i18ntk 4.5.4 → 4.6.1

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.
@@ -9,6 +9,7 @@
9
9
 
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
+ const { detectProjectFramework, getFrameworkPatterns, getFrameworkSuggestions, SCANNER_EXTENSIONS, WRAPPER_SKIP_PATTERNS } = require('../../../utils/framework-detector');
12
13
  const { getUnifiedConfig, displayHelp } = require('../../../utils/config-helper');
13
14
  const { loadTranslations } = require('../../../utils/i18n-helper');
14
15
  const SecurityUtils = require('../../../utils/security');
@@ -207,11 +208,24 @@ class ScannerCommand {
207
208
  const packageJson = SecurityUtils.safeParseJSON(packageJsonContent);
208
209
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
209
210
 
211
+ if (deps.next || deps['next-intl'] || deps['next-i18next']) return 'next';
210
212
  if (deps.react || deps['react-dom']) return 'react';
211
213
  if (deps.vue || deps['vue-router']) return 'vue';
212
214
  if (deps['@angular/core'] || deps.angular) return 'angular';
213
- if (deps.next) return 'next';
214
215
  if (deps.svelte) return 'svelte';
216
+ if (deps.astro || deps['astro-i18next'] || deps['@astrojs/i18n']) return 'astro';
217
+ if (deps['@remix-run/react'] || deps['remix-i18next']) return 'remix';
218
+ if (deps.gatsby || deps['gatsby-plugin-react-i18next']) return 'gatsby';
219
+ if (deps['@builder.io/qwik'] || deps['qwik-speak']) return 'qwik';
220
+ if (deps['solid-js'] || deps['@solid-primitives/i18n']) return 'solid';
221
+ if (deps['ember-source'] || deps['ember-intl']) return 'ember';
222
+ if (deps.nuxt || deps['@nuxtjs/i18n']) return 'nuxt';
223
+
224
+ const cargoTomlPath = path.join(projectRoot, 'Cargo.toml');
225
+ if (SecurityUtils.safeExistsSync(cargoTomlPath, projectRoot)) return 'rust';
226
+
227
+ const goModPath = path.join(projectRoot, 'go.mod');
228
+ if (SecurityUtils.safeExistsSync(goModPath, projectRoot)) return 'go';
215
229
 
216
230
  return 'vanilla';
217
231
  } catch (error) {
@@ -237,57 +251,110 @@ class ScannerCommand {
237
251
 
238
252
  const frameworkSpecific = {
239
253
  react: [
240
- // React specific patterns - enhanced for i18next detection
241
254
  /children:\s*["']([^"']{2,99})["']/g,
242
255
  /dangerouslySetInnerHTML={{\s*__html:\s*["']([^"']{2,99})["']/g,
243
- // JSX text content without translation
244
256
  />([^<{][^<>{]*[^}>])</g,
245
- // Button text
246
257
  /<button[^>]*>([^<]{2,99})<\/button>/g,
247
- // Span text
248
- /<span[^>]*>([^<]{2,99})<\/span>/g
258
+ /<span[^>]*>([^<]{2,99})<\/span>/g,
259
+ /<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g,
260
+ /<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*\{[']\s*([^'}]+\.[^'}]+)\s*[']/g
249
261
  ],
250
262
  vue: [
251
- // Vue specific patterns - enhanced for vue-i18n detection
252
263
  /v-text=["']([^"']{2,99})["']/g,
253
264
  /v-html=["']([^"']{2,99})["']/g,
254
- // Vue template text
255
265
  />([^<{][^<>{]*[^}>])</g,
256
- // Button text
257
266
  /<button[^>]*>([^<]{2,99})<\/button>/g,
258
- // Span text
259
- /<span[^>]*>([^<]{2,99})<\/span>/g
267
+ /<span[^>]*>([^<]{2,99})<\/span>/g,
268
+ /\$t\(["']([^"']{2,99})["']\)/g,
269
+ /v-t=["']([^"']{2,99})["']/g
260
270
  ],
261
271
  angular: [
262
- // Angular specific patterns - enhanced for ngx-translate detection
263
272
  /\[innerHTML\]=["']([^"']{2,99})["']/g,
264
273
  /\[textContent\]=["']([^"']{2,99})["']/g,
265
- // Angular template text
266
274
  />([^<{][^<>{]*[^}>])</g,
267
- // Button text
268
275
  /<button[^>]*>([^<]{2,99})<\/button>/g,
269
- // Span text
270
- /<span[^>]*>([^<]{2,99})<\/span>/g
276
+ /<span[^>]*>([^<]{2,99})<\/span>/g,
277
+ /i18n=["']([^"']{2,99})["']/g,
278
+ /\[attr\.title\]=["']([^"']{2,99})["']/g
279
+ ],
280
+ next: [
281
+ /children:\s*["']([^"']{2,99})["']/g,
282
+ />([^<{][^<>{]*[^}>])</g,
283
+ /<button[^>]*>([^<]{2,99})<\/button>/g,
284
+ /<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
285
+ ],
286
+ svelte: [
287
+ />([^<{][^<>{]*[^}>])</g,
288
+ /<button[^>]*>([^<]{2,99})<\/button>/g,
289
+ /\$_\(["']([^"']{2,99})["']\)/g,
290
+ /t\.set\(["']([^"']{2,99})["']/g,
291
+ /\{#if[^}]*\}([^<]{2,99})\{\/if\}/g
292
+ ],
293
+ astro: [
294
+ />([^<{][^<>{]*[^}>])</g,
295
+ /<button[^>]*>([^<]{2,99})<\/button>/g,
296
+ /t\(["']([^"']{2,99})["']\)/g
297
+ ],
298
+ remix: [
299
+ /children:\s*["']([^"']{2,99})["']/g,
300
+ />([^<{][^<>{]*[^}>])</g,
301
+ /<button[^>]*>([^<]{2,99})<\/button>/g,
302
+ /<(?:FormattedMessage|Trans)[\s\S]*?\b(?:id|defaultMessage|i18nKey)\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
303
+ ],
304
+ qwik: [
305
+ />([^<{][^<>{]*[^}>])</g,
306
+ /<button[^>]*>([^<]{2,99})<\/button>/g,
307
+ /t\(["']([^"']{2,99})["']\)/g,
308
+ /useTranslate\(\)/g
309
+ ],
310
+ solid: [
311
+ />([^<{][^<>{]*[^}>])</g,
312
+ /<button[^>]*>([^<]{2,99})<\/button>/g,
313
+ /t\(["']([^"']{2,99})["']\)/g,
314
+ /useI18n\(\)\.t\(["']([^"']{2,99})["']\)/g
315
+ ],
316
+ ember: [
317
+ />([^<{][^<>{]*[^}>])</g,
318
+ /\{\{t\s+["']([^"']{2,99})["']\s*\}\}/g,
319
+ /\{\{intl\.t\s+["']([^"']{2,99})["']\s*\}\}/g
320
+ ],
321
+ gatsby: [
322
+ /children:\s*["']([^"']{2,99})["']/g,
323
+ />([^<{][^<>{]*[^}>])</g,
324
+ /<(?:Trans)[\s\S]*?\bi18nKey\s*=\s*(?:\{|)(['"`])([^'"`}]+)\1[^>]*>/g
271
325
  ],
272
326
  django: [
273
- // Django template patterns
274
327
  /\{\%\s*trans\s+["']([^"']{2,99})["']\s*%\}/g,
275
328
  /\{\%\s*blocktrans\s*%\}([^%]{2,99})\{\%\s*endblocktrans\s*%\}/g,
276
329
  /{{\s*_["']([^"']{2,99})["']\s*}}/g,
277
330
  /{{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g
278
331
  ],
279
332
  flask: [
280
- // Flask/Jinja2 template patterns
281
333
  /\{\{\s*_["']([^"']{2,99})["']\s*}}/g,
282
334
  /\{\{\s*gettext\(["']([^"']{2,99})["']\)\s*}}/g,
283
335
  /\{\{\s*lazy_gettext\(["']([^"']{2,99})["']\)\s*}}/g
284
336
  ],
285
337
  python: [
286
- // Python source patterns
287
338
  /gettext\(["']([^"']{2,99})["']\)/g,
288
339
  /_\(["']([^"']{2,99})["']\)/g,
289
340
  /gettext_lazy\(["']([^"']{2,99})["']\)/g,
290
341
  /lazy_gettext\(["']([^"']{2,99})["']\)/g
342
+ ],
343
+ rust: [
344
+ /bundle\.get_message\(\s*["']([^"']{2,99})["']\)/g,
345
+ /\.get_message\s*\(\s*["']([^"']{2,99})["']\)/g,
346
+ /ts!\s*\(\s*["']([^"']{2,99})["']/g,
347
+ /fluent!\s*\(\s*["']([^"']{2,99})["']/g
348
+ ],
349
+ go: [
350
+ /i18n\.Translate\([^,]+,\s*["']([^"']{2,99})["']\)/g,
351
+ /i18n\.NewMessage\([^,]+,\s*["']([^"']{2,99})["']\)/g,
352
+ /t\.Get\([^,]+,\s*["']([^"']{2,99})["']\)/g
353
+ ],
354
+ vanilla: [
355
+ /t\(["']([^"']{2,99})["']\)/g,
356
+ /i18n\.t\(["']([^"']{2,99})["']\)/g,
357
+ /translate\(["']([^"']{2,99})["']\)/g
291
358
  ]
292
359
  };
293
360
 
@@ -380,24 +447,63 @@ class ScannerCommand {
380
447
  key: `ui.${key}`,
381
448
  original: text,
382
449
  translationKey: `t('ui.${key}')`,
383
- frameworkSpecific: this.getFrameworkSpecific(text)
450
+ frameworkSpecific: getFrameworkSuggestions(this.framework, text)
384
451
  };
385
452
  }
386
453
 
387
454
  getFrameworkSpecific(text) {
455
+ const keySnippet = (str) => str.toLowerCase()
456
+ .replace(/[^a-z0-9\s]/g, '')
457
+ .replace(/\s+/g, '_')
458
+ .substring(0, 40);
388
459
  const frameworks = {
389
460
  react: {
390
461
  hook: `const { t } = useTranslation();`,
391
- usage: `{t('ui.${text.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '_')}')}`,
392
- component: `<Trans i18nKey="ui.${text.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '_')}">${text}</Trans>`
462
+ usage: `{t('ui.${keySnippet(text)}')}`,
463
+ component: `<Trans i18nKey="ui.${keySnippet(text)}">${text}</Trans>`
464
+ },
465
+ next: {
466
+ hook: `const t = useTranslations();`,
467
+ usage: `{t('ui.${keySnippet(text)}')}`,
468
+ component: `<Trans i18nKey="ui.${keySnippet(text)}">${text}</Trans>`
393
469
  },
394
470
  vue: {
395
- directive: `{{ $t('ui.${text.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '_')}') }}`,
396
- method: `this.$t('ui.${text.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '_')}')`
471
+ directive: `{{ $t('ui.${keySnippet(text)}') }}`,
472
+ method: `this.$t('ui.${keySnippet(text)}')`
397
473
  },
398
474
  angular: {
399
475
  pipe: `{{ '${text}' | translate }}`,
400
- service: `this.translateService.instant('ui.${text.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, '_')}')`
476
+ service: `this.translateService.instant('ui.${keySnippet(text)}')`
477
+ },
478
+ svelte: {
479
+ store: `$_(('ui.${keySnippet(text)}'))`,
480
+ method: `t.set('ui.${keySnippet(text)}', '${text}')`
481
+ },
482
+ astro: {
483
+ import: `import { t } from 'astro-i18next';`,
484
+ usage: `{t('ui.${keySnippet(text)}')}`
485
+ },
486
+ remix: {
487
+ hook: `const { t } = useTranslation();`,
488
+ usage: `{t('ui.${keySnippet(text)}')}`,
489
+ server: `export const handle = i18next.handle;`
490
+ },
491
+ qwik: {
492
+ hook: `const t = useTranslate();`,
493
+ usage: `{t('ui.${keySnippet(text)}')}`
494
+ },
495
+ solid: {
496
+ hook: `const [t] = useI18n();`,
497
+ usage: `{t('ui.${keySnippet(text)}')}`
498
+ },
499
+ ember: {
500
+ template: `{{t 'ui.${keySnippet(text)}'}}`,
501
+ helper: `this.intl.t('ui.${keySnippet(text)}')`
502
+ },
503
+ gatsby: {
504
+ hook: `const { t } = useTranslation();`,
505
+ usage: `{t('ui.${keySnippet(text)}')}`,
506
+ plugin: `'gatsby-plugin-react-i18next'`
401
507
  },
402
508
  django: {
403
509
  template: `{% trans '${text}' %}`,
@@ -413,6 +519,17 @@ class ScannerCommand {
413
519
  gettext: `import gettext\ngettext.gettext('${text}')`,
414
520
  underscore: `from gettext import gettext as _\n_('${text}')`,
415
521
  lazy: `from gettext import gettext_lazy as _\n_('${text}')`
522
+ },
523
+ rust: {
524
+ fluent: `bundle.get_message("ui_${keySnippet(text)}")`,
525
+ gettext: `gettext("ui_${keySnippet(text)}")`
526
+ },
527
+ go: {
528
+ translate: `i18n.Translate("ui_${keySnippet(text)}")`,
529
+ message: `i18n.NewMessage("ui_${keySnippet(text)}")`
530
+ },
531
+ vanilla: {
532
+ generic: `t('ui.${keySnippet(text)}')`
416
533
  }
417
534
  };
418
535
 
@@ -433,7 +550,7 @@ class ScannerCommand {
433
550
  }
434
551
 
435
552
  const allResults = [];
436
- const extensions = ['.js', '.jsx', '.ts', '.tsx', '.vue', '.html', '.svelte', '.py', '.pyx', '.pyi'];
553
+ const extensions = [...SCANNER_EXTENSIONS];
437
554
 
438
555
  const scanRecursive = (currentDir) => {
439
556
  const items = SecurityUtils.safeReaddirSync(currentDir, path.dirname(currentDir), { withFileTypes: true });
@@ -586,7 +703,7 @@ class ScannerCommand {
586
703
  } else if (fwPref && fwPref !== 'auto') {
587
704
  this.framework = fwPref;
588
705
  } else if (fwDetectEnabled) {
589
- const detected = this.detectFramework(process.cwd());
706
+ const detected = detectProjectFramework(process.cwd());
590
707
  this.framework = detected || fwFallback;
591
708
  } else {
592
709
  this.framework = fwFallback;
@@ -612,7 +729,7 @@ class ScannerCommand {
612
729
  console.log(this.t('scanner.starting', { framework: this.framework }));
613
730
  console.log(this.t('scanner.sourceDirectory', { sourceDir: this.sourceDir }));
614
731
 
615
- const patterns = this.getFrameworkPatterns(this.framework);
732
+ const patterns = getFrameworkPatterns(this.framework);
616
733
  const exclusions = this.config.exclude || ['node_modules', '.git', 'dist', 'build'];
617
734
  const minLength = this.config.minLength || 3;
618
735
  const maxLength = this.config.maxLength || 100;
@@ -40,6 +40,7 @@ module.exports = class FrameworkDetectionService {
40
40
  const goModPath = path.join(process.cwd(), 'go.mod');
41
41
  const pomPath = path.join(process.cwd(), 'pom.xml');
42
42
  const composerPath = path.join(process.cwd(), 'composer.json');
43
+ const cargoTomlPath = path.join(process.cwd(), 'Cargo.toml');
43
44
 
44
45
  let detectedLanguage = 'generic';
45
46
  let detectedFramework = 'generic';
@@ -89,12 +90,21 @@ module.exports = class FrameworkDetectionService {
89
90
 
90
91
  // Only check other frameworks if i18ntk-runtime wasn't detected
91
92
  if (detectedFramework !== 'i18ntk-runtime') {
92
- if (deps.react || deps['react-dom']) detectedFramework = 'react';
93
- else if (deps.vue || deps['vue-router']) detectedFramework = 'vue';
93
+ if (deps.next || deps['next-intl'] || deps['next-i18next']) detectedFramework = 'nextjs';
94
+ else if (deps.react || deps['react-dom']) detectedFramework = 'react';
94
95
  else if (deps['@angular/core']) detectedFramework = 'angular';
95
- else if (deps.next) detectedFramework = 'nextjs';
96
- else if (deps.nuxt) detectedFramework = 'nuxt';
96
+ else if (deps.vue || deps['vue-router']) detectedFramework = 'vue';
97
+ else if (deps.nuxt || deps['@nuxtjs/i18n']) detectedFramework = 'nuxt';
97
98
  else if (deps.svelte) detectedFramework = 'svelte';
99
+ else if (deps.astro || deps['astro-i18next'] || deps['@astrojs/i18n']) detectedFramework = 'astro';
100
+ else if (deps['@builder.io/qwik'] || deps['qwik-speak'] || deps['qwik-i18n']) detectedFramework = 'qwik';
101
+ else if (deps.gatsby || deps['gatsby-plugin-react-i18next'] || deps['gatsby-plugin-intl']) detectedFramework = 'gatsby';
102
+ else if (deps['@remix-run/react'] || deps['remix-i18next'] || deps['i18next-remix']) detectedFramework = 'remix';
103
+ else if (deps['solid-js'] || deps['@solid-primitives/i18n']) detectedFramework = 'solid';
104
+ else if (deps['ember-source'] || deps['ember-intl']) detectedFramework = 'ember';
105
+ else if (deps['react-native'] || deps['react-native-localize']) detectedFramework = 'react-native';
106
+ else if (deps['expo-localization'] || deps.expo) detectedFramework = 'expo';
107
+ else if (deps['@ionic/angular'] || deps['ionic-react'] || deps['@ionic/vue']) detectedFramework = 'ionic';
98
108
  else detectedFramework = 'generic';
99
109
  }
100
110
  } catch (error) {
@@ -140,7 +150,17 @@ module.exports = class FrameworkDetectionService {
140
150
  } catch (error) {
141
151
  detectedFramework = 'generic';
142
152
  }
143
- }
153
+ } else if (SecurityUtils.safeExistsSync(cargoTomlPath)) {
154
+ detectedLanguage = 'rust';
155
+ try {
156
+ const cargoContent = SecurityUtils.safeReadFileSync(cargoTomlPath, path.dirname(cargoTomlPath), 'utf8');
157
+ if (cargoContent.includes('fluent') || cargoContent.includes('fluent-rs')) detectedFramework = 'fluent';
158
+ else if (cargoContent.includes('gettext')) detectedFramework = 'gettext-rs';
159
+ else detectedFramework = 'generic';
160
+ } catch (error) {
161
+ detectedFramework = 'generic';
162
+ }
163
+ }
144
164
 
145
165
  return { detectedLanguage, detectedFramework };
146
166
  }
@@ -155,13 +175,27 @@ module.exports = class FrameworkDetectionService {
155
175
  javascript: [
156
176
  { name: 'i18next', description: 'Feature-rich i18n framework for JavaScript' },
157
177
  { name: 'react-i18next', description: 'React integration for i18next' },
178
+ { name: 'next-intl', description: 'Next.js i18n integration' },
179
+ { name: 'remix-i18next', description: 'Remix i18n integration' },
180
+ { name: 'gatsby-plugin-react-i18next', description: 'Gatsby i18n integration' },
158
181
  { name: 'vue-i18n', description: 'Vue.js i18n plugin' },
159
- { name: 'Angular i18n', description: 'Built-in Angular i18n' }
182
+ { name: 'Angular i18n', description: 'Built-in Angular i18n' },
183
+ { name: 'svelte-i18n', description: 'Svelte i18n library' },
184
+ { name: 'astro-i18next', description: 'Astro i18n integration' },
185
+ { name: 'qwik-speak', description: 'Qwik i18n library' },
186
+ { name: 'solid-i18n', description: 'SolidJS i18n library' },
187
+ { name: 'ember-intl', description: 'Ember i18n library' },
188
+ { name: 'react-native-localize', description: 'React Native localization' },
189
+ { name: 'ionic-angular', description: 'Ionic i18n support' }
160
190
  ],
161
191
  typescript: [
162
192
  { name: 'i18next', description: 'TypeScript-first i18n framework' },
163
193
  { name: 'react-i18next', description: 'React + TypeScript integration' },
164
- { name: 'vue-i18n', description: 'Vue.js i18n with TypeScript support' }
194
+ { name: 'next-intl', description: 'Next.js + TypeScript i18n' },
195
+ { name: 'vue-i18n', description: 'Vue.js i18n with TypeScript support' },
196
+ { name: 'angular i18n', description: 'Angular i18n with TypeScript' },
197
+ { name: 'astro-i18next', description: 'Astro i18n with TypeScript' },
198
+ { name: 'qwik-speak', description: 'Qwik i18n with TypeScript' }
165
199
  ],
166
200
  python: [
167
201
  { name: 'Django i18n', description: 'Built-in Django internationalization' },
@@ -177,6 +211,11 @@ module.exports = class FrameworkDetectionService {
177
211
  { name: 'go-i18n', description: 'Go i18n library with pluralization' },
178
212
  { name: 'nicksnyder/go-i18n', description: 'Feature-rich Go i18n' }
179
213
  ],
214
+ rust: [
215
+ { name: 'fluent', description: 'Project Fluent localization for Rust' },
216
+ { name: 'fluent-rs', description: 'Rust implementation of Project Fluent' },
217
+ { name: 'gettext-rs', description: 'GNU gettext bindings for Rust' }
218
+ ],
180
219
  php: [
181
220
  { name: 'Laravel i18n', description: 'Built-in Laravel localization' },
182
221
  { name: 'Symfony Translation', description: 'Symfony translation component' },
@@ -335,13 +374,32 @@ module.exports = class FrameworkDetectionService {
335
374
 
336
375
  const i18nFrameworks = [
337
376
  'react-i18next',
377
+ 'next-intl',
378
+ 'next-i18next',
379
+ 'remix-i18next',
380
+ 'i18next-remix',
381
+ 'gatsby-plugin-react-i18next',
382
+ 'gatsby-plugin-intl',
338
383
  'vue-i18n',
339
384
  'angular-i18n',
340
385
  'i18next',
341
- 'next-i18next',
342
386
  'svelte-i18n',
387
+ 'sveltekit-i18n',
388
+ 'astro-i18next',
389
+ '@astrojs/i18n',
390
+ 'qwik-speak',
391
+ 'qwik-i18n',
392
+ '@solid-primitives/i18n',
393
+ 'ember-intl',
394
+ 'react-native-localize',
395
+ 'expo-localization',
396
+ '@ngx-translate/core',
397
+ '@ionic/angular',
343
398
  '@nuxtjs/i18n',
344
- 'i18ntk-runtime'
399
+ 'formatjs',
400
+ '@lingui/core',
401
+ 'i18ntk-runtime',
402
+ 'i18ntk/runtime'
345
403
  ];
346
404
 
347
405
  const installedFrameworks = i18nFrameworks.filter(framework => dependencies[framework]);
@@ -18,29 +18,56 @@ const AdminAuth = require('../../../utils/admin-auth');
18
18
  const { parseConfirmation } = require('../../../utils/localized-confirm');
19
19
  const { normalizeReportFormat, writeReportFile } = require('../../../utils/report-writer');
20
20
 
21
- // Language configurations with native names
22
- const LANGUAGE_CONFIG = {
23
- 'en': { name: 'English', nativeName: 'English' },
24
- 'de': { name: 'German', nativeName: 'Deutsch' },
25
- 'es': { name: 'Spanish', nativeName: 'Espa\u00f1ol' },
26
- 'fr': { name: 'French', nativeName: 'Fran\u00e7ais' },
27
- 'ru': { name: 'Russian', nativeName: '\u0420\u0443\u0441\u0441\u043a\u0438\u0439' },
28
- 'it': { name: 'Italian', nativeName: 'Italiano' },
29
- 'ja': { name: 'Japanese', nativeName: '\u65e5\u672c\u8a9e' },
30
- 'ko': { name: 'Korean', nativeName: '\ud55c\uad6d\uc5b4' },
31
- 'zh': { name: 'Chinese', nativeName: '\u4e2d\u6587' },
32
- 'ar': { name: 'Arabic', nativeName: '\u0627\u0644\u0639\u0631\u0628\u064a\u0629' },
33
- 'hi': { name: 'Hindi', nativeName: '\u0939\u093f\u0928\u094d\u0926\u0940' },
34
- 'nl': { name: 'Dutch', nativeName: 'Nederlands' },
35
- 'sv': { name: 'Swedish', nativeName: 'Svenska' },
36
- 'da': { name: 'Danish', nativeName: 'Dansk' },
37
- 'no': { name: 'Norwegian', nativeName: 'Norsk' },
38
- 'fi': { name: 'Finnish', nativeName: 'Suomi' },
39
- 'pl': { name: 'Polish', nativeName: 'Polski' },
40
- 'cs': { name: 'Czech', nativeName: '\u010ce\u0161tina' },
41
- 'hu': { name: 'Hungarian', nativeName: 'Magyar' },
42
- 'tr': { name: 'Turkish', nativeName: 'T\u00fcrk\u00e7e' }
43
- };
21
+ // Language configurations with native names — comprehensive ISO 639-1 coverage
22
+ const LANGUAGE_CONFIG = {
23
+ 'af': { name: 'Afrikaans', nativeName: 'Afrikaans' }, 'sq': { name: 'Albanian', nativeName: 'Shqip' },
24
+ 'ar': { name: 'Arabic', nativeName: '\u0627\u0644\u0639\u0631\u0628\u064a\u0629' }, 'hy': { name: 'Armenian', nativeName: '\u0540\u0561\u0575\u0565\u0580\u0565\u0576' },
25
+ 'az': { name: 'Azerbaijani', nativeName: 'Az\u0259rbaycan' }, 'eu': { name: 'Basque', nativeName: 'Euskara' },
26
+ 'be': { name: 'Belarusian', nativeName: '\u0411\u0435\u043b\u0430\u0440\u0443\u0441\u043a\u0430\u044f' },
27
+ 'bn': { name: 'Bengali', nativeName: '\u09ac\u09be\u0982\u09b2\u09be' },
28
+ 'bs': { name: 'Bosnian', nativeName: 'Bosanski' }, 'bg': { name: 'Bulgarian', nativeName: '\u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438' },
29
+ 'ca': { name: 'Catalan', nativeName: 'Catal\u00e0' }, 'zh': { name: 'Chinese', nativeName: '\u4e2d\u6587' },
30
+ 'hr': { name: 'Croatian', nativeName: 'Hrvatski' }, 'cs': { name: 'Czech', nativeName: '\u010ce\u0161tina' },
31
+ 'da': { name: 'Danish', nativeName: 'Dansk' }, 'nl': { name: 'Dutch', nativeName: 'Nederlands' },
32
+ 'en': { name: 'English', nativeName: 'English' }, 'et': { name: 'Estonian', nativeName: 'Eesti' },
33
+ 'fi': { name: 'Finnish', nativeName: 'Suomi' }, 'fr': { name: 'French', nativeName: 'Fran\u00e7ais' },
34
+ 'ka': { name: 'Georgian', nativeName: '\u10e5\u10d0\u10e0\u10d7\u10e3\u10da\u10d8' },
35
+ 'de': { name: 'German', nativeName: 'Deutsch' }, 'el': { name: 'Greek', nativeName: '\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac' },
36
+ 'gu': { name: 'Gujarati', nativeName: '\u0a97\u0ac1\u0a9c\u0ab0\u0abe\u0aa4\u0ac0' },
37
+ 'he': { name: 'Hebrew', nativeName: '\u05e2\u05d1\u05e8\u05d9\u05ea' },
38
+ 'hi': { name: 'Hindi', nativeName: '\u0939\u093f\u0928\u094d\u0926\u0940' },
39
+ 'hu': { name: 'Hungarian', nativeName: 'Magyar' }, 'is': { name: 'Icelandic', nativeName: '\u00cdslenska' },
40
+ 'id': { name: 'Indonesian', nativeName: 'Bahasa Indonesia' },
41
+ 'ga': { name: 'Irish', nativeName: 'Gaeilge' }, 'it': { name: 'Italian', nativeName: 'Italiano' },
42
+ 'ja': { name: 'Japanese', nativeName: '\u65e5\u672c\u8a9e' },
43
+ 'kk': { name: 'Kazakh', nativeName: '\u049a\u0430\u0437\u0430\u049b' },
44
+ 'ko': { name: 'Korean', nativeName: '\ud55c\uad6d\uc5b4' },
45
+ 'lv': { name: 'Latvian', nativeName: 'Latvie\u0161u' }, 'lt': { name: 'Lithuanian', nativeName: 'Lietuvi\u0173' },
46
+ 'mk': { name: 'Macedonian', nativeName: '\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438' },
47
+ 'ms': { name: 'Malay', nativeName: 'Bahasa Melayu' },
48
+ 'ml': { name: 'Malayalam', nativeName: '\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02' },
49
+ 'mr': { name: 'Marathi', nativeName: '\u092e\u0930\u093e\u0920\u0940' },
50
+ 'mn': { name: 'Mongolian', nativeName: '\u041c\u043e\u043d\u0433\u043e\u043b' },
51
+ 'no': { name: 'Norwegian', nativeName: 'Norsk' },
52
+ 'fa': { name: 'Persian', nativeName: '\u0641\u0627\u0631\u0633\u06cc' },
53
+ 'pl': { name: 'Polish', nativeName: 'Polski' }, 'pt': { name: 'Portuguese', nativeName: 'Portugu\u00eas' },
54
+ 'pa': { name: 'Punjabi', nativeName: '\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40' },
55
+ 'ro': { name: 'Romanian', nativeName: 'Rom\u00e2n\u0103' },
56
+ 'ru': { name: 'Russian', nativeName: '\u0420\u0443\u0441\u0441\u043a\u0438\u0439' },
57
+ 'sr': { name: 'Serbian', nativeName: '\u0421\u0440\u043f\u0441\u043a\u0438' },
58
+ 'sk': { name: 'Slovak', nativeName: 'Sloven\u010dina' },
59
+ 'sl': { name: 'Slovenian', nativeName: 'Sloven\u0161\u010dina' },
60
+ 'es': { name: 'Spanish', nativeName: 'Espa\u00f1ol' },
61
+ 'sw': { name: 'Swahili', nativeName: 'Kiswahili' }, 'sv': { name: 'Swedish', nativeName: 'Svenska' },
62
+ 'tl': { name: 'Filipino', nativeName: 'Filipino' },
63
+ 'ta': { name: 'Tamil', nativeName: '\u0ba4\u0bae\u0bbf\u0bb4\u0bcd' },
64
+ 'te': { name: 'Telugu', nativeName: '\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41' },
65
+ 'th': { name: 'Thai', nativeName: '\u0e44\u0e17\u0e22' },
66
+ 'tr': { name: 'Turkish', nativeName: 'T\u00fcrk\u00e7e' },
67
+ 'uk': { name: 'Ukrainian', nativeName: '\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430' },
68
+ 'ur': { name: 'Urdu', nativeName: '\u0627\u0631\u062f\u0648' },
69
+ 'vi': { name: 'Vietnamese', nativeName: 'Ti\u1ebfng Vi\u1ec7t' },
70
+ };
44
71
 
45
72
  class InitService {
46
73
  constructor(config = {}) {
@@ -56,7 +83,9 @@ class InitService {
56
83
  };
57
84
 
58
85
  this.format = getFormatAdapter(this.config.format);
59
- this.config.supportedExtensions = [this.format.extension];
86
+ if (!this.config.supportedExtensions || !this.config.supportedExtensions.length) {
87
+ this.config.supportedExtensions = [this.format.extension];
88
+ }
60
89
  this.detectedFramework = detectFramework(process.cwd());
61
90
  if (this.detectedFramework && !this.config.translationPatterns) {
62
91
  this.config.translationPatterns = this.detectedFramework.patterns;
@@ -68,7 +97,9 @@ class InitService {
68
97
  : path.join(this.sourceDir, this.config.sourceLanguage);
69
98
 
70
99
  // Ensure defaultLanguages is properly initialized from config
71
- this.config.defaultLanguages = this.config.defaultLanguages || ['en', 'de', 'es', 'fr', 'ru'];
100
+ if (!this.config.defaultLanguages || !Array.isArray(this.config.defaultLanguages) || this.config.defaultLanguages.length === 0) {
101
+ this.config.defaultLanguages = ['en', 'de', 'es', 'fr', 'ru'];
102
+ }
72
103
  }
73
104
 
74
105
  // Check i18n dependencies