poly-lexis 0.3.2 → 0.4.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.
@@ -18,72 +18,6 @@ var init_esm_shims = __esm({
18
18
  }
19
19
  });
20
20
 
21
- // src/translations/utils/utils.ts
22
- import * as fs from "fs";
23
- import * as path2 from "path";
24
- function readTranslations(translationsPath, language) {
25
- const langPath = path2.join(translationsPath, language);
26
- if (!fs.existsSync(langPath)) {
27
- return {};
28
- }
29
- const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
30
- const translations = {};
31
- for (const file of files) {
32
- const namespace = path2.basename(file, ".json");
33
- const filePath = path2.join(langPath, file);
34
- const content = fs.readFileSync(filePath, "utf-8");
35
- translations[namespace] = JSON.parse(content);
36
- }
37
- return translations;
38
- }
39
- function writeTranslation(translationsPath, language, namespace, translations) {
40
- const langPath = path2.join(translationsPath, language);
41
- if (!fs.existsSync(langPath)) {
42
- fs.mkdirSync(langPath, { recursive: true });
43
- }
44
- const filePath = path2.join(langPath, `${namespace}.json`);
45
- fs.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
46
- `, "utf-8");
47
- }
48
- function getAvailableLanguages(translationsPath) {
49
- if (!fs.existsSync(translationsPath)) {
50
- return [];
51
- }
52
- return fs.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
53
- }
54
- function getNamespaces(translationsPath, language) {
55
- const langPath = path2.join(translationsPath, language);
56
- if (!fs.existsSync(langPath)) {
57
- return [];
58
- }
59
- return fs.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path2.basename(f, ".json"));
60
- }
61
- function sortKeys(obj) {
62
- const sorted = {};
63
- const keys = Object.keys(obj).sort();
64
- for (const key of keys) {
65
- sorted[key] = obj[key];
66
- }
67
- return sorted;
68
- }
69
- function ensureTranslationsStructure(translationsPath, languages) {
70
- if (!fs.existsSync(translationsPath)) {
71
- fs.mkdirSync(translationsPath, { recursive: true });
72
- }
73
- for (const lang of languages) {
74
- const langPath = path2.join(translationsPath, lang);
75
- if (!fs.existsSync(langPath)) {
76
- fs.mkdirSync(langPath, { recursive: true });
77
- }
78
- }
79
- }
80
- var init_utils = __esm({
81
- "src/translations/utils/utils.ts"() {
82
- "use strict";
83
- init_esm_shims();
84
- }
85
- });
86
-
87
21
  // src/translations/core/schema.ts
88
22
  function isValidLanguage(lang) {
89
23
  return SUPPORTED_LANGUAGES.includes(lang);
@@ -91,7 +25,7 @@ function isValidLanguage(lang) {
91
25
  function validateLanguages(languages) {
92
26
  const invalid = languages.filter((lang) => !isValidLanguage(lang));
93
27
  return {
94
- valid: invalid.length === 0,
28
+ valid: !invalid.length,
95
29
  invalid
96
30
  };
97
31
  }
@@ -100,6 +34,7 @@ var init_schema = __esm({
100
34
  "src/translations/core/schema.ts"() {
101
35
  "use strict";
102
36
  init_esm_shims();
37
+ init_language_fallback();
103
38
  DEEPL_LANGUAGES = [
104
39
  "ar",
105
40
  // Arabic
@@ -412,6 +347,297 @@ var init_schema = __esm({
412
347
  }
413
348
  });
414
349
 
350
+ // src/translations/utils/language-fallback.ts
351
+ function resolveLanguageWithFallback(language, provider, enableFallback = true) {
352
+ const normalizedLanguage = language.toLowerCase();
353
+ const supportedLanguages = getSupportedLanguagesForProvider(provider);
354
+ if (isLanguageSupported(normalizedLanguage, supportedLanguages)) {
355
+ return {
356
+ resolvedLanguage: normalizedLanguage,
357
+ usedFallback: false,
358
+ originalLanguage: language
359
+ };
360
+ }
361
+ if (!enableFallback) {
362
+ return {
363
+ resolvedLanguage: normalizedLanguage,
364
+ usedFallback: false,
365
+ originalLanguage: language
366
+ };
367
+ }
368
+ const fallbackChain = LANGUAGE_FALLBACK_MAP[normalizedLanguage] || [];
369
+ for (const fallbackLang of fallbackChain) {
370
+ if (isLanguageSupported(fallbackLang, supportedLanguages)) {
371
+ return {
372
+ resolvedLanguage: fallbackLang,
373
+ usedFallback: true,
374
+ originalLanguage: language,
375
+ fallbackChain: [normalizedLanguage, ...fallbackChain]
376
+ };
377
+ }
378
+ }
379
+ const baseLang = normalizedLanguage.split("_")[0];
380
+ if (baseLang !== normalizedLanguage && isLanguageSupported(baseLang, supportedLanguages)) {
381
+ return {
382
+ resolvedLanguage: baseLang,
383
+ usedFallback: true,
384
+ originalLanguage: language,
385
+ fallbackChain: [normalizedLanguage, baseLang]
386
+ };
387
+ }
388
+ return {
389
+ resolvedLanguage: normalizedLanguage,
390
+ usedFallback: false,
391
+ originalLanguage: language
392
+ };
393
+ }
394
+ function getSupportedLanguagesForProvider(provider) {
395
+ switch (provider) {
396
+ case "deepl":
397
+ return DEEPL_LANGUAGES;
398
+ case "google":
399
+ return GOOGLE_LANGUAGES;
400
+ default:
401
+ return [];
402
+ }
403
+ }
404
+ function isLanguageSupported(language, supportedLanguages) {
405
+ return supportedLanguages.includes(language);
406
+ }
407
+ function logLanguageFallback(result, provider) {
408
+ if (result.usedFallback) {
409
+ console.warn(
410
+ `\u26A0\uFE0F Language fallback: '${result.originalLanguage}' is not supported by ${provider}, using '${result.resolvedLanguage}' instead`
411
+ );
412
+ if (result.fallbackChain && result.fallbackChain.length > 2) {
413
+ console.warn(` Fallback chain: ${result.fallbackChain.join(" \u2192 ")}`);
414
+ }
415
+ }
416
+ }
417
+ var LANGUAGE_FALLBACK_MAP;
418
+ var init_language_fallback = __esm({
419
+ "src/translations/utils/language-fallback.ts"() {
420
+ "use strict";
421
+ init_esm_shims();
422
+ init_schema();
423
+ LANGUAGE_FALLBACK_MAP = {
424
+ // German variants
425
+ de_at: ["de"],
426
+ // Austrian German -> German
427
+ de_ch: ["de"],
428
+ // Swiss German -> German
429
+ de_de: ["de"],
430
+ // Standard German -> German
431
+ // English variants
432
+ en_gb: ["en"],
433
+ // British English -> English
434
+ en_us: ["en"],
435
+ // American English -> English
436
+ en_au: ["en"],
437
+ // Australian English -> English
438
+ en_ca: ["en"],
439
+ // Canadian English -> English
440
+ en_nz: ["en"],
441
+ // New Zealand English -> English
442
+ // Chinese variants (Hong Kong, Taiwan -> Traditional)
443
+ zh_hk: ["zh_hant", "zh"],
444
+ // Hong Kong Chinese -> Traditional Chinese -> Chinese
445
+ zh_tw: ["zh_hant", "zh"],
446
+ // Taiwan Chinese -> Traditional Chinese -> Chinese
447
+ zh_mo: ["zh_hant", "zh"],
448
+ // Macau Chinese -> Traditional Chinese -> Chinese
449
+ // Chinese variants (Mainland, Singapore -> Simplified)
450
+ zh_cn: ["zh_hans", "zh"],
451
+ // Mainland Chinese -> Simplified Chinese -> Chinese
452
+ zh_sg: ["zh_hans", "zh"],
453
+ // Singapore Chinese -> Simplified Chinese -> Chinese
454
+ // Portuguese variants
455
+ pt_pt: ["pt"],
456
+ // European Portuguese -> Portuguese
457
+ pt_ao: ["pt"],
458
+ // Angolan Portuguese -> Portuguese
459
+ pt_mz: ["pt"],
460
+ // Mozambican Portuguese -> Portuguese
461
+ // Spanish variants (Latin America)
462
+ es_mx: ["es_419", "es"],
463
+ // Mexican Spanish -> Latin American Spanish -> Spanish
464
+ es_ar: ["es_419", "es"],
465
+ // Argentine Spanish -> Latin American Spanish -> Spanish
466
+ es_co: ["es_419", "es"],
467
+ // Colombian Spanish -> Latin American Spanish -> Spanish
468
+ es_cl: ["es_419", "es"],
469
+ // Chilean Spanish -> Latin American Spanish -> Spanish
470
+ es_pe: ["es_419", "es"],
471
+ // Peruvian Spanish -> Latin American Spanish -> Spanish
472
+ es_ve: ["es_419", "es"],
473
+ // Venezuelan Spanish -> Latin American Spanish -> Spanish
474
+ es_ec: ["es_419", "es"],
475
+ // Ecuadorian Spanish -> Latin American Spanish -> Spanish
476
+ es_gt: ["es_419", "es"],
477
+ // Guatemalan Spanish -> Latin American Spanish -> Spanish
478
+ es_cu: ["es_419", "es"],
479
+ // Cuban Spanish -> Latin American Spanish -> Spanish
480
+ es_do: ["es_419", "es"],
481
+ // Dominican Spanish -> Latin American Spanish -> Spanish
482
+ es_hn: ["es_419", "es"],
483
+ // Honduran Spanish -> Latin American Spanish -> Spanish
484
+ es_ni: ["es_419", "es"],
485
+ // Nicaraguan Spanish -> Latin American Spanish -> Spanish
486
+ es_sv: ["es_419", "es"],
487
+ // Salvadoran Spanish -> Latin American Spanish -> Spanish
488
+ es_cr: ["es_419", "es"],
489
+ // Costa Rican Spanish -> Latin American Spanish -> Spanish
490
+ es_pa: ["es_419", "es"],
491
+ // Panamanian Spanish -> Latin American Spanish -> Spanish
492
+ es_uy: ["es_419", "es"],
493
+ // Uruguayan Spanish -> Latin American Spanish -> Spanish
494
+ es_py: ["es_419", "es"],
495
+ // Paraguayan Spanish -> Latin American Spanish -> Spanish
496
+ es_bo: ["es_419", "es"],
497
+ // Bolivian Spanish -> Latin American Spanish -> Spanish
498
+ // Spanish (European)
499
+ es_es: ["es"],
500
+ // European Spanish -> Spanish
501
+ // French variants
502
+ fr_ca: ["fr"],
503
+ // Canadian French -> French
504
+ fr_ch: ["fr"],
505
+ // Swiss French -> French
506
+ fr_be: ["fr"],
507
+ // Belgian French -> French
508
+ fr_fr: ["fr"],
509
+ // Standard French -> French
510
+ // Norwegian variants
511
+ no: ["nb"],
512
+ // Norwegian -> Norwegian Bokmål
513
+ nn: ["nb"],
514
+ // Norwegian Nynorsk -> Norwegian Bokmål
515
+ // Other regional variants
516
+ it_ch: ["it"],
517
+ // Swiss Italian -> Italian
518
+ nl_be: ["nl"],
519
+ // Belgian Dutch (Flemish) -> Dutch
520
+ sv_fi: ["sv"],
521
+ // Finland Swedish -> Swedish
522
+ ar_ae: ["ar"],
523
+ // UAE Arabic -> Arabic
524
+ ar_sa: ["ar"],
525
+ // Saudi Arabic -> Arabic
526
+ ar_eg: ["ar"]
527
+ // Egyptian Arabic -> Arabic
528
+ };
529
+ }
530
+ });
531
+
532
+ // src/translations/utils/utils.ts
533
+ import * as fs from "fs";
534
+ import * as path2 from "path";
535
+ function readTranslations(translationsPath, language) {
536
+ const langPath = path2.join(translationsPath, language);
537
+ if (!fs.existsSync(langPath)) {
538
+ return {};
539
+ }
540
+ const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
541
+ const translations = {};
542
+ for (const file of files) {
543
+ const namespace = path2.basename(file, ".json");
544
+ const filePath = path2.join(langPath, file);
545
+ const content = fs.readFileSync(filePath, "utf-8");
546
+ translations[namespace] = JSON.parse(content);
547
+ }
548
+ return translations;
549
+ }
550
+ function writeTranslation(translationsPath, language, namespace, translations) {
551
+ const langPath = path2.join(translationsPath, language);
552
+ if (!fs.existsSync(langPath)) {
553
+ fs.mkdirSync(langPath, { recursive: true });
554
+ }
555
+ const filePath = path2.join(langPath, `${namespace}.json`);
556
+ fs.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
557
+ `, "utf-8");
558
+ }
559
+ function getAvailableLanguages(translationsPath) {
560
+ if (!fs.existsSync(translationsPath)) {
561
+ return [];
562
+ }
563
+ return fs.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
564
+ }
565
+ function getNamespaces(translationsPath, language) {
566
+ const langPath = path2.join(translationsPath, language);
567
+ if (!fs.existsSync(langPath)) {
568
+ return [];
569
+ }
570
+ return fs.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path2.basename(f, ".json"));
571
+ }
572
+ function sortKeys(obj) {
573
+ const sorted = {};
574
+ const keys = Object.keys(obj).sort();
575
+ for (const key of keys) {
576
+ sorted[key] = obj[key];
577
+ }
578
+ return sorted;
579
+ }
580
+ function ensureTranslationsStructure(translationsPath, languages) {
581
+ if (!fs.existsSync(translationsPath)) {
582
+ fs.mkdirSync(translationsPath, { recursive: true });
583
+ }
584
+ for (const lang of languages) {
585
+ const langPath = path2.join(translationsPath, lang);
586
+ if (!fs.existsSync(langPath)) {
587
+ fs.mkdirSync(langPath, { recursive: true });
588
+ }
589
+ }
590
+ }
591
+ function createEmptyTranslationStructure(sourceFile) {
592
+ const result = {};
593
+ for (const key of Object.keys(sourceFile)) {
594
+ result[key] = "";
595
+ }
596
+ return result;
597
+ }
598
+ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
599
+ const result = {
600
+ createdFolders: [],
601
+ createdFiles: [],
602
+ skippedFiles: []
603
+ };
604
+ ensureTranslationsStructure(translationsPath, languages);
605
+ const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
606
+ if (!sourceNamespaces.length) {
607
+ return result;
608
+ }
609
+ const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
610
+ const targetLanguages = languages.filter((lang) => lang !== sourceLanguage);
611
+ for (const language of targetLanguages) {
612
+ for (const namespace of sourceNamespaces) {
613
+ const filePath = path2.join(translationsPath, language, `${namespace}.json`);
614
+ if (fs.existsSync(filePath)) {
615
+ result.skippedFiles.push({
616
+ language,
617
+ namespace,
618
+ reason: "already exists"
619
+ });
620
+ continue;
621
+ }
622
+ const sourceFile = sourceTranslations[namespace] || {};
623
+ const emptyStructure = createEmptyTranslationStructure(sourceFile);
624
+ writeTranslation(translationsPath, language, namespace, emptyStructure);
625
+ result.createdFiles.push({
626
+ language,
627
+ namespace,
628
+ path: filePath
629
+ });
630
+ }
631
+ }
632
+ return result;
633
+ }
634
+ var init_utils = __esm({
635
+ "src/translations/utils/utils.ts"() {
636
+ "use strict";
637
+ init_esm_shims();
638
+ }
639
+ });
640
+
415
641
  // src/translations/core/types.ts
416
642
  var DEFAULT_CONFIG, DEFAULT_LANGUAGES;
417
643
  var init_types = __esm({
@@ -422,24 +648,11 @@ var init_types = __esm({
422
648
  translationsPath: "public/static/locales",
423
649
  languages: ["en"],
424
650
  sourceLanguage: "en",
425
- typesOutputPath: "src/types/i18nTypes.ts"
651
+ typesOutputPath: "src/types/i18nTypes.ts",
652
+ provider: "google",
653
+ useFallbackLanguages: true
426
654
  };
427
- DEFAULT_LANGUAGES = [
428
- "en",
429
- "fr",
430
- "it",
431
- "pl",
432
- "es",
433
- "pt",
434
- "de",
435
- "de_at",
436
- "nl",
437
- "sv",
438
- "hu",
439
- "cs",
440
- "ja",
441
- "zh_hk"
442
- ];
655
+ DEFAULT_LANGUAGES = ["en", "fr", "it", "pl", "es", "pt", "de", "nl", "sv", "hu", "cs", "ja"];
443
656
  }
444
657
  });
445
658
 
@@ -513,12 +726,14 @@ function initTranslations(projectRoot, config = {}) {
513
726
  `, "utf-8");
514
727
  console.log(`Created sample file: ${commonPath}`);
515
728
  }
516
- for (const lang of languages) {
517
- if (lang === sourceLanguage) continue;
518
- const langCommonPath = path3.join(translationsPath, lang, "common.json");
519
- if (!fs2.existsSync(langCommonPath)) {
520
- fs2.writeFileSync(langCommonPath, "{}\n", "utf-8");
521
- console.log(`Created empty file: ${langCommonPath}`);
729
+ console.log("\nSynchronizing translation structure...");
730
+ const syncResult = syncTranslationStructure(translationsPath, languages, sourceLanguage);
731
+ if (syncResult.createdFiles.length > 0) {
732
+ console.log(`Created ${syncResult.createdFiles.length} namespace files in target languages`);
733
+ const languageGroups = new Set(syncResult.createdFiles.map((f) => f.language));
734
+ for (const lang of languageGroups) {
735
+ const langFiles = syncResult.createdFiles.filter((f) => f.language === lang);
736
+ console.log(` ${lang}: ${langFiles.map((f) => f.namespace).join(", ")}`);
522
737
  }
523
738
  }
524
739
  const configPath = path3.join(projectRoot, ".translationsrc.json");
@@ -528,7 +743,8 @@ function initTranslations(projectRoot, config = {}) {
528
743
  translationsPath: finalConfig.translationsPath,
529
744
  languages,
530
745
  sourceLanguage,
531
- typesOutputPath: finalConfig.typesOutputPath
746
+ typesOutputPath: finalConfig.typesOutputPath,
747
+ provider: finalConfig.provider
532
748
  };
533
749
  fs2.writeFileSync(configPath, `${JSON.stringify(configContent, null, 2)}
534
750
  `, "utf-8");
@@ -595,7 +811,7 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
595
811
  throw new Error(`Source language directory not found: ${dirPath}`);
596
812
  }
597
813
  const namespaces = getNamespaces(translationsPath, sourceLanguage);
598
- if (namespaces.length === 0) {
814
+ if (!namespaces.length) {
599
815
  throw new Error(`No translation files found in ${dirPath}`);
600
816
  }
601
817
  const translations = readTranslations(translationsPath, sourceLanguage);
@@ -646,7 +862,7 @@ import "dotenv/config";
646
862
  import * as fs6 from "fs";
647
863
  import * as path10 from "path";
648
864
  import { parseArgs } from "util";
649
- import { confirm as confirm2, input as input2, select } from "@inquirer/prompts";
865
+ import { confirm as confirm2, input as input2, select as select2 } from "@inquirer/prompts";
650
866
 
651
867
  // src/translations/cli/add-key.ts
652
868
  init_esm_shims();
@@ -657,6 +873,7 @@ init_esm_shims();
657
873
 
658
874
  // src/translations/utils/google-translate-provider.ts
659
875
  init_esm_shims();
876
+ init_language_fallback();
660
877
  function preserveVariables(text) {
661
878
  const variableMap = /* @__PURE__ */ new Map();
662
879
  let placeholderIndex = 0;
@@ -677,14 +894,24 @@ function restoreVariables(text, variableMap) {
677
894
  }
678
895
  var GoogleTranslateProvider = class {
679
896
  async translate(options) {
680
- const { text, sourceLang, targetLang, apiKey } = options;
897
+ const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true } = options;
681
898
  if (!apiKey) {
682
899
  throw new Error(
683
900
  "Google Translate API key is required. Set GOOGLE_TRANSLATE_API_KEY environment variable or provide apiKey in options."
684
901
  );
685
902
  }
903
+ const targetLangResult = resolveLanguageWithFallback(targetLang, "google", useFallbackLanguages);
904
+ logLanguageFallback(targetLangResult, "google");
905
+ let resolvedSourceLang;
906
+ if (sourceLang) {
907
+ const sourceLangResult = resolveLanguageWithFallback(sourceLang, "google", useFallbackLanguages);
908
+ logLanguageFallback(sourceLangResult, "google");
909
+ resolvedSourceLang = sourceLangResult.resolvedLanguage;
910
+ }
686
911
  const { textWithPlaceholders, variableMap } = preserveVariables(text);
687
912
  const url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
913
+ const sourceForGoogle = resolvedSourceLang?.includes("_") ? resolvedSourceLang.split("_")[0] : resolvedSourceLang;
914
+ const targetForGoogle = targetLangResult.resolvedLanguage.includes("_") ? targetLangResult.resolvedLanguage.split("_")[0] : targetLangResult.resolvedLanguage;
688
915
  const response = await fetch(url, {
689
916
  method: "POST",
690
917
  headers: {
@@ -692,9 +919,8 @@ var GoogleTranslateProvider = class {
692
919
  },
693
920
  body: JSON.stringify({
694
921
  q: textWithPlaceholders,
695
- source: sourceLang,
696
- target: targetLang.split("_")[0],
697
- // Convert 'pt_BR' to 'pt'
922
+ source: sourceForGoogle,
923
+ target: targetForGoogle,
698
924
  format: "text"
699
925
  })
700
926
  });
@@ -730,16 +956,20 @@ var GoogleTranslateProvider = class {
730
956
  // src/translations/utils/translator.ts
731
957
  var defaultProvider = new GoogleTranslateProvider();
732
958
  var customProvider = null;
959
+ function setTranslationProvider(provider) {
960
+ customProvider = provider;
961
+ }
733
962
  function getTranslationProvider() {
734
963
  return customProvider || defaultProvider;
735
964
  }
736
- async function translateText(text, targetLang, sourceLang = "en", apiKey) {
965
+ async function translateText(text, targetLang, sourceLang = "en", apiKey, useFallbackLanguages = true) {
737
966
  const provider = getTranslationProvider();
738
967
  return provider.translate({
739
968
  text,
740
969
  sourceLang,
741
970
  targetLang,
742
- apiKey
971
+ apiKey,
972
+ useFallbackLanguages
743
973
  });
744
974
  }
745
975
 
@@ -827,7 +1057,7 @@ init_types();
827
1057
  init_init();
828
1058
  import * as fs3 from "fs";
829
1059
  import * as path5 from "path";
830
- import { checkbox, confirm, input } from "@inquirer/prompts";
1060
+ import { checkbox, confirm, input, select } from "@inquirer/prompts";
831
1061
  async function initTranslationsInteractive(projectRoot = process.cwd()) {
832
1062
  console.log("\n\u{1F30D} Translation System Setup\n");
833
1063
  const configPath = path5.join(projectRoot, ".translationsrc.json");
@@ -906,11 +1136,28 @@ async function initTranslationsInteractive(projectRoot = process.cwd()) {
906
1136
  message: "Where should TypeScript types be generated?",
907
1137
  default: DEFAULT_CONFIG.typesOutputPath
908
1138
  });
1139
+ const provider = await select({
1140
+ message: "Which translation provider would you like to use?",
1141
+ choices: [
1142
+ {
1143
+ name: "DeepL (recommended)",
1144
+ value: "deepl",
1145
+ description: "High-quality translations, requires DEEPL_API_KEY"
1146
+ },
1147
+ {
1148
+ name: "Google Translate",
1149
+ value: "google",
1150
+ description: "Google Cloud Translation API, requires GOOGLE_TRANSLATE_API_KEY"
1151
+ }
1152
+ ],
1153
+ default: "deepl"
1154
+ });
909
1155
  console.log("\n\u{1F4CB} Configuration Summary:");
910
1156
  console.log(` Translations: ${translationsPath}`);
911
1157
  console.log(` Languages: ${languages.join(", ")}`);
912
1158
  console.log(` Source: ${sourceLanguage}`);
913
1159
  console.log(` Types: ${typesOutputPath}`);
1160
+ console.log(` Provider: ${provider}`);
914
1161
  const confirmInit = await confirm({
915
1162
  message: "\nProceed with initialization?",
916
1163
  default: true
@@ -923,7 +1170,8 @@ async function initTranslationsInteractive(projectRoot = process.cwd()) {
923
1170
  translationsPath,
924
1171
  languages,
925
1172
  sourceLanguage,
926
- typesOutputPath
1173
+ typesOutputPath,
1174
+ provider
927
1175
  };
928
1176
  console.log();
929
1177
  initTranslations(projectRoot, config);
@@ -981,12 +1229,109 @@ function getLanguageName(code) {
981
1229
 
982
1230
  // src/translations/cli/manage.ts
983
1231
  init_esm_shims();
1232
+ init_utils();
984
1233
  import * as fs5 from "fs";
985
1234
  import * as path9 from "path";
986
1235
 
987
1236
  // src/translations/cli/auto-fill.ts
988
1237
  init_esm_shims();
989
1238
  import * as path7 from "path";
1239
+
1240
+ // src/translations/utils/deepl-translate-provider.ts
1241
+ init_esm_shims();
1242
+ init_language_fallback();
1243
+ function preserveVariables2(text) {
1244
+ const variableMap = /* @__PURE__ */ new Map();
1245
+ let placeholderIndex = 0;
1246
+ const textWithPlaceholders = text.replace(/\{\{([^}]+)\}\}/g, (match) => {
1247
+ const placeholder = `XXX_${placeholderIndex}_XXX`;
1248
+ variableMap.set(placeholder, match);
1249
+ placeholderIndex++;
1250
+ return placeholder;
1251
+ });
1252
+ return { textWithPlaceholders, variableMap };
1253
+ }
1254
+ function restoreVariables2(text, variableMap) {
1255
+ let result = text;
1256
+ for (const [placeholder, original] of variableMap) {
1257
+ result = result.replace(new RegExp(placeholder, "g"), original);
1258
+ }
1259
+ return result;
1260
+ }
1261
+ function normalizeLanguageCode(langCode) {
1262
+ return langCode.replace("_", "-").toUpperCase();
1263
+ }
1264
+ var DeepLTranslateProvider = class {
1265
+ isFreeApi;
1266
+ constructor(isFreeApi = false) {
1267
+ this.isFreeApi = isFreeApi;
1268
+ }
1269
+ getApiEndpoint() {
1270
+ return this.isFreeApi ? "https://api-free.deepl.com/v2/translate" : "https://api.deepl.com/v2/translate";
1271
+ }
1272
+ async translate(options) {
1273
+ const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true } = options;
1274
+ if (!apiKey) {
1275
+ throw new Error(
1276
+ "DeepL API key is required. Set DEEPL_API_KEY environment variable or provide apiKey in options."
1277
+ );
1278
+ }
1279
+ const targetLangResult = resolveLanguageWithFallback(targetLang, "deepl", useFallbackLanguages);
1280
+ logLanguageFallback(targetLangResult, "deepl");
1281
+ let resolvedSourceLang;
1282
+ if (sourceLang) {
1283
+ const sourceLangResult = resolveLanguageWithFallback(sourceLang, "deepl", useFallbackLanguages);
1284
+ logLanguageFallback(sourceLangResult, "deepl");
1285
+ resolvedSourceLang = sourceLangResult.resolvedLanguage;
1286
+ }
1287
+ const { textWithPlaceholders, variableMap } = preserveVariables2(text);
1288
+ const body = {
1289
+ text: [textWithPlaceholders],
1290
+ target_lang: normalizeLanguageCode(targetLangResult.resolvedLanguage),
1291
+ ...resolvedSourceLang && { source_lang: normalizeLanguageCode(resolvedSourceLang) }
1292
+ };
1293
+ const response = await fetch(this.getApiEndpoint(), {
1294
+ method: "POST",
1295
+ headers: {
1296
+ Authorization: `DeepL-Auth-Key ${apiKey}`,
1297
+ "Content-Type": "application/json"
1298
+ },
1299
+ body: JSON.stringify(body)
1300
+ });
1301
+ if (!response.ok) {
1302
+ const errorData = await response.json().catch(() => ({}));
1303
+ throw new Error(`DeepL API error: ${errorData.message || response.statusText} (${response.status})`);
1304
+ }
1305
+ const data = await response.json();
1306
+ if (!data.translations || data.translations.length === 0) {
1307
+ throw new Error("DeepL API returned no translations");
1308
+ }
1309
+ const translatedText = data.translations[0].text;
1310
+ return restoreVariables2(translatedText, variableMap);
1311
+ }
1312
+ async translateBatch(texts, sourceLang, targetLang, apiKey, delayMs = 100) {
1313
+ const results = [];
1314
+ for (const text of texts) {
1315
+ const translated = await this.translate({
1316
+ text,
1317
+ sourceLang,
1318
+ targetLang,
1319
+ apiKey
1320
+ });
1321
+ results.push(translated);
1322
+ if (delayMs > 0) {
1323
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1324
+ }
1325
+ }
1326
+ return results;
1327
+ }
1328
+ async validateConfig() {
1329
+ const apiKey = process.env.DEEPL_API_KEY;
1330
+ return !!apiKey;
1331
+ }
1332
+ };
1333
+
1334
+ // src/translations/cli/auto-fill.ts
990
1335
  init_utils();
991
1336
  init_init();
992
1337
 
@@ -1003,7 +1348,11 @@ function validateTranslations(projectRoot = process.cwd()) {
1003
1348
  const empty = [];
1004
1349
  const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
1005
1350
  const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
1006
- const languages = getAvailableLanguages(translationsPath).filter((lang) => lang !== sourceLanguage);
1351
+ const languages = config.languages.filter((lang) => lang !== sourceLanguage);
1352
+ const syncResult = syncTranslationStructure(translationsPath, config.languages, sourceLanguage);
1353
+ if (syncResult.createdFiles.length > 0) {
1354
+ console.log(`Created ${syncResult.createdFiles.length} missing namespace files during sync`);
1355
+ }
1007
1356
  console.log("=====");
1008
1357
  console.log("Validating translations");
1009
1358
  console.log("=====");
@@ -1036,7 +1385,7 @@ function validateTranslations(projectRoot = process.cwd()) {
1036
1385
  }
1037
1386
  }
1038
1387
  }
1039
- const valid = missing.length === 0 && empty.length === 0;
1388
+ const valid = !missing.length && !empty.length;
1040
1389
  if (valid) {
1041
1390
  console.log("\u2713 All translations are valid!");
1042
1391
  } else {
@@ -1078,10 +1427,28 @@ async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
1078
1427
  const config = loadConfig(projectRoot);
1079
1428
  const translationsPath = path7.join(projectRoot, config.translationsPath);
1080
1429
  const { apiKey, limit = 1e3, delayMs = 100, dryRun = false } = options;
1430
+ const currentProvider = getTranslationProvider();
1431
+ const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
1432
+ if (isDefaultGoogleProvider) {
1433
+ const provider = config.provider || "deepl";
1434
+ if (provider === "deepl") {
1435
+ setTranslationProvider(new DeepLTranslateProvider());
1436
+ } else {
1437
+ setTranslationProvider(new GoogleTranslateProvider());
1438
+ }
1439
+ }
1081
1440
  if (!apiKey) {
1082
- throw new Error("Google Translate API key is required. Set GOOGLE_TRANSLATE_API_KEY or pass --api-key");
1441
+ const provider = config.provider || "deepl";
1442
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
1443
+ throw new Error(`Translation API key is required. Set ${envVarName} or pass --api-key`);
1083
1444
  }
1084
1445
  const languagesToProcess = options.language ? [options.language] : config.languages.filter((lang) => lang !== config.sourceLanguage);
1446
+ console.log("\u{1F504} Synchronizing translation structure...");
1447
+ const syncResult = syncTranslationStructure(translationsPath, config.languages, config.sourceLanguage);
1448
+ if (syncResult.createdFiles.length > 0) {
1449
+ console.log(`Created ${syncResult.createdFiles.length} namespace files
1450
+ `);
1451
+ }
1085
1452
  console.log("=====");
1086
1453
  console.log("Auto-filling translations");
1087
1454
  console.log("=====");
@@ -1100,7 +1467,7 @@ Reached limit of ${limit} translations`);
1100
1467
  console.log(`
1101
1468
  Processing language: ${language}`);
1102
1469
  const missing = getMissingForLanguage(projectRoot, language);
1103
- if (missing.length === 0) {
1470
+ if (!missing.length) {
1104
1471
  console.log(" No missing or empty translations");
1105
1472
  continue;
1106
1473
  }
@@ -1112,7 +1479,13 @@ Processing language: ${language}`);
1112
1479
  try {
1113
1480
  console.log(` [${totalProcessed}/${limit}] Translating ${item.namespace}.${item.key}`);
1114
1481
  console.log(` EN: "${item.sourceValue}"`);
1115
- const translated = await translateText(item.sourceValue, language, config.sourceLanguage, apiKey);
1482
+ const translated = await translateText(
1483
+ item.sourceValue,
1484
+ language,
1485
+ config.sourceLanguage,
1486
+ apiKey,
1487
+ config.useFallbackLanguages ?? true
1488
+ );
1116
1489
  console.log(` ${language.toUpperCase()}: "${translated}"`);
1117
1490
  if (!dryRun) {
1118
1491
  const translations = readTranslations(translationsPath, language);
@@ -1169,6 +1542,14 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
1169
1542
  console.log("Please add translation files to the source language directory.\n");
1170
1543
  return false;
1171
1544
  }
1545
+ console.log("\u{1F504} Synchronizing translation structure...\n");
1546
+ const syncResult = syncTranslationStructure(translationsPath, config.languages, config.sourceLanguage);
1547
+ if (syncResult.createdFiles.length > 0) {
1548
+ console.log(`\u2713 Created ${syncResult.createdFiles.length} namespace files
1549
+ `);
1550
+ } else {
1551
+ console.log("\u2713 Translation structure is already synchronized\n");
1552
+ }
1172
1553
  console.log("\u{1F50D} Validating translations...\n");
1173
1554
  const validationResult = validateTranslations(projectRoot);
1174
1555
  if (validationResult.valid) {
@@ -1177,8 +1558,11 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
1177
1558
  const totalMissing = validationResult.missing.length + validationResult.empty.length;
1178
1559
  if (autoFill) {
1179
1560
  if (!apiKey) {
1561
+ const provider = config.provider || "deepl";
1562
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
1180
1563
  console.log("\n\u26A0\uFE0F Auto-fill requested but no API key provided.");
1181
- console.log("Set GOOGLE_TRANSLATE_API_KEY or pass --api-key to enable auto-fill.\n");
1564
+ console.log(`Set ${envVarName} or pass --api-key to enable auto-fill.
1565
+ `);
1182
1566
  } else {
1183
1567
  console.log(`
1184
1568
  \u{1F916} Auto-filling ${totalMissing} missing translations...
@@ -1298,8 +1682,8 @@ Commands:
1298
1682
  add Add a new translation key
1299
1683
 
1300
1684
  Options (Smart Mode):
1301
- -a, --auto-fill Auto-fill missing translations with Google Translate
1302
- --api-key <key> Google Translate API key (or set GOOGLE_TRANSLATE_API_KEY)
1685
+ -a, --auto-fill Auto-fill missing translations with DeepL or Google Translate
1686
+ --api-key <key> Translation API key (or set DEEPL_API_KEY/GOOGLE_TRANSLATE_API_KEY)
1303
1687
  -l, --language <lang> Process only this language
1304
1688
  --limit <number> Max translations to process (default: 1000)
1305
1689
  --skip-types Skip TypeScript type generation
@@ -1318,7 +1702,11 @@ Examples:
1318
1702
  # Smart mode - check and validate translations
1319
1703
  translations
1320
1704
 
1321
- # Smart mode - validate and auto-fill missing translations
1705
+ # Smart mode - validate and auto-fill missing translations (DeepL)
1706
+ export DEEPL_API_KEY=your_key
1707
+ translations --auto-fill
1708
+
1709
+ # Smart mode - validate and auto-fill missing translations (Google)
1322
1710
  export GOOGLE_TRANSLATE_API_KEY=your_key
1323
1711
  translations --auto-fill
1324
1712
 
@@ -1373,7 +1761,7 @@ if (command === "add") {
1373
1761
  const existingNamespaces = getNamespaces(translationsPath, config.sourceLanguage);
1374
1762
  let namespace;
1375
1763
  if (existingNamespaces.length > 0) {
1376
- const namespaceChoice = await select({
1764
+ const namespaceChoice = await select2({
1377
1765
  message: "Select namespace:",
1378
1766
  choices: [
1379
1767
  ...existingNamespaces.map((ns) => ({ name: ns, value: ns })),
@@ -1431,9 +1819,12 @@ if (command === "add") {
1431
1819
  });
1432
1820
  let apiKey;
1433
1821
  if (autoTranslate) {
1434
- apiKey = values["api-key"] || process.env.GOOGLE_TRANSLATE_API_KEY;
1822
+ const provider = config.provider || "deepl";
1823
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
1824
+ apiKey = values["api-key"] || process.env[envVarName];
1435
1825
  if (!apiKey) {
1436
- console.log("\n\u26A0\uFE0F GOOGLE_TRANSLATE_API_KEY environment variable not found.");
1826
+ console.log(`
1827
+ \u26A0\uFE0F ${envVarName} environment variable not found.`);
1437
1828
  console.log("Skipping auto-translation. Set this variable to enable auto-translation.\n");
1438
1829
  }
1439
1830
  }
@@ -1461,11 +1852,12 @@ if (command === "add") {
1461
1852
  console.log("Use --help for usage information");
1462
1853
  process.exit(1);
1463
1854
  }
1464
- const apiKey = values["api-key"] || process.env.GOOGLE_TRANSLATE_API_KEY;
1855
+ const config = loadConfig(process.cwd());
1856
+ const provider = config.provider || "deepl";
1857
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
1858
+ const apiKey = values["api-key"] || process.env[envVarName];
1465
1859
  if (values["auto-fill"] && !apiKey) {
1466
- console.error(
1467
- "Error: --api-key or GOOGLE_TRANSLATE_API_KEY environment variable is required for auto-translation"
1468
- );
1860
+ console.error(`Error: --api-key or ${envVarName} environment variable is required for auto-translation`);
1469
1861
  process.exit(1);
1470
1862
  }
1471
1863
  addTranslationKey(process.cwd(), {
@@ -1488,7 +1880,11 @@ if (command === "add") {
1488
1880
  } else {
1489
1881
  const hasFlags = values["auto-fill"] || values.language || values["skip-types"] || values["dry-run"];
1490
1882
  if (hasFlags) {
1491
- const apiKey = values["api-key"] || process.env.GOOGLE_TRANSLATE_API_KEY;
1883
+ const configPath = path10.join(process.cwd(), ".translationsrc.json");
1884
+ const config = fs6.existsSync(configPath) ? loadConfig(process.cwd()) : { provider: "deepl" };
1885
+ const provider = config.provider || "deepl";
1886
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
1887
+ const apiKey = values["api-key"] || process.env[envVarName];
1492
1888
  const limit = Number.parseInt(values.limit || "1000", 10);
1493
1889
  manageTranslations(process.cwd(), {
1494
1890
  autoFill: values["auto-fill"],
@@ -1511,7 +1907,7 @@ if (command === "add") {
1511
1907
  const configPath = path10.join(process.cwd(), ".translationsrc.json");
1512
1908
  const isInitialized = fs6.existsSync(configPath);
1513
1909
  console.log("\n\u{1F30D} Translation Management\n");
1514
- const action = await select({
1910
+ const action = await select2({
1515
1911
  message: "What would you like to do?",
1516
1912
  choices: [
1517
1913
  {
@@ -1527,7 +1923,7 @@ if (command === "add") {
1527
1923
  {
1528
1924
  name: "\u{1F916} Auto-fill missing translations",
1529
1925
  value: "autofill",
1530
- description: "Automatically translate missing keys with Google Translate"
1926
+ description: "Automatically translate missing keys with DeepL or Google Translate"
1531
1927
  },
1532
1928
  {
1533
1929
  name: "\u{1F4DD} Generate TypeScript types",
@@ -1567,7 +1963,7 @@ if (command === "add") {
1567
1963
  const existingNamespaces = getNamespaces(translationsPath, config.sourceLanguage);
1568
1964
  let namespace;
1569
1965
  if (existingNamespaces.length > 0) {
1570
- const namespaceChoice = await select({
1966
+ const namespaceChoice = await select2({
1571
1967
  message: "Select namespace:",
1572
1968
  choices: [
1573
1969
  ...existingNamespaces.map((ns) => ({ name: ns, value: ns })),
@@ -1625,9 +2021,12 @@ if (command === "add") {
1625
2021
  });
1626
2022
  let apiKey;
1627
2023
  if (autoTranslate) {
1628
- apiKey = process.env.GOOGLE_TRANSLATE_API_KEY;
2024
+ const provider = config.provider || "deepl";
2025
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
2026
+ apiKey = process.env[envVarName];
1629
2027
  if (!apiKey) {
1630
- console.log("\n\u26A0\uFE0F GOOGLE_TRANSLATE_API_KEY environment variable not found.");
2028
+ console.log(`
2029
+ \u26A0\uFE0F ${envVarName} environment variable not found.`);
1631
2030
  console.log("Skipping auto-translation. Set this variable to enable auto-translation.\n");
1632
2031
  }
1633
2032
  }
@@ -1647,9 +2046,12 @@ if (command === "add") {
1647
2046
  skipTypes: true
1648
2047
  });
1649
2048
  } else if (action === "autofill") {
1650
- const apiKey = process.env.GOOGLE_TRANSLATE_API_KEY;
2049
+ const config = loadConfig(process.cwd());
2050
+ const provider = config.provider || "deepl";
2051
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
2052
+ const apiKey = process.env[envVarName];
1651
2053
  if (!apiKey) {
1652
- console.log("\u26A0\uFE0F GOOGLE_TRANSLATE_API_KEY environment variable not found.");
2054
+ console.log(`\u26A0\uFE0F ${envVarName} environment variable not found.`);
1653
2055
  console.log("Please set it to enable auto-translation.\n");
1654
2056
  process.exit(1);
1655
2057
  }