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.
package/dist/index.js CHANGED
@@ -8,87 +8,6 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
- // src/translations/utils/utils.ts
12
- import * as fs from "fs";
13
- import * as path from "path";
14
- function readTranslations(translationsPath, language) {
15
- const langPath = path.join(translationsPath, language);
16
- if (!fs.existsSync(langPath)) {
17
- return {};
18
- }
19
- const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
20
- const translations = {};
21
- for (const file of files) {
22
- const namespace = path.basename(file, ".json");
23
- const filePath = path.join(langPath, file);
24
- const content = fs.readFileSync(filePath, "utf-8");
25
- translations[namespace] = JSON.parse(content);
26
- }
27
- return translations;
28
- }
29
- function writeTranslation(translationsPath, language, namespace, translations) {
30
- const langPath = path.join(translationsPath, language);
31
- if (!fs.existsSync(langPath)) {
32
- fs.mkdirSync(langPath, { recursive: true });
33
- }
34
- const filePath = path.join(langPath, `${namespace}.json`);
35
- fs.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
36
- `, "utf-8");
37
- }
38
- function getAvailableLanguages(translationsPath) {
39
- if (!fs.existsSync(translationsPath)) {
40
- return [];
41
- }
42
- return fs.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
43
- }
44
- function getNamespaces(translationsPath, language) {
45
- const langPath = path.join(translationsPath, language);
46
- if (!fs.existsSync(langPath)) {
47
- return [];
48
- }
49
- return fs.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path.basename(f, ".json"));
50
- }
51
- function hasInterpolation(text) {
52
- return /\{\{[^}]+\}\}/g.test(text);
53
- }
54
- function extractVariables(text) {
55
- const matches = text.match(/\{\{([^}]+)\}\}/g);
56
- if (!matches) return [];
57
- return matches.map((match) => match.replace(/\{\{|\}\}/g, "").trim());
58
- }
59
- function validateVariables(sourceText, translatedText) {
60
- const sourceVars = extractVariables(sourceText).sort();
61
- const translatedVars = extractVariables(translatedText).sort();
62
- if (sourceVars.length !== translatedVars.length) {
63
- return false;
64
- }
65
- return sourceVars.every((v, i) => v === translatedVars[i]);
66
- }
67
- function sortKeys(obj) {
68
- const sorted = {};
69
- const keys = Object.keys(obj).sort();
70
- for (const key of keys) {
71
- sorted[key] = obj[key];
72
- }
73
- return sorted;
74
- }
75
- function ensureTranslationsStructure(translationsPath, languages) {
76
- if (!fs.existsSync(translationsPath)) {
77
- fs.mkdirSync(translationsPath, { recursive: true });
78
- }
79
- for (const lang of languages) {
80
- const langPath = path.join(translationsPath, lang);
81
- if (!fs.existsSync(langPath)) {
82
- fs.mkdirSync(langPath, { recursive: true });
83
- }
84
- }
85
- }
86
- var init_utils = __esm({
87
- "src/translations/utils/utils.ts"() {
88
- "use strict";
89
- }
90
- });
91
-
92
11
  // src/translations/core/schema.ts
93
12
  function isValidLanguage(lang) {
94
13
  return SUPPORTED_LANGUAGES.includes(lang);
@@ -112,14 +31,14 @@ function isValidLanguageForProvider(lang, provider) {
112
31
  function validateLanguages(languages) {
113
32
  const invalid = languages.filter((lang) => !isValidLanguage(lang));
114
33
  return {
115
- valid: invalid.length === 0,
34
+ valid: !invalid.length,
116
35
  invalid
117
36
  };
118
37
  }
119
38
  function validateLanguagesForProvider(languages, provider) {
120
39
  const invalid = languages.filter((lang) => !isValidLanguageForProvider(lang, provider));
121
40
  return {
122
- valid: invalid.length === 0,
41
+ valid: !invalid.length,
123
42
  invalid
124
43
  };
125
44
  }
@@ -137,6 +56,7 @@ var TRANSLATION_PROVIDERS, DEEPL_LANGUAGES, GOOGLE_LANGUAGES, SUPPORTED_LANGUAGE
137
56
  var init_schema = __esm({
138
57
  "src/translations/core/schema.ts"() {
139
58
  "use strict";
59
+ init_language_fallback();
140
60
  TRANSLATION_PROVIDERS = ["deepl", "google"];
141
61
  DEEPL_LANGUAGES = [
142
62
  "ar",
@@ -490,6 +410,11 @@ var init_schema = __esm({
490
410
  description: "Translation provider to use (deepl or google)",
491
411
  enum: TRANSLATION_PROVIDERS,
492
412
  default: "deepl"
413
+ },
414
+ useFallbackLanguages: {
415
+ type: "boolean",
416
+ description: "Enable automatic language fallback for unsupported regional variants (e.g., de_at -> de)",
417
+ default: false
493
418
  }
494
419
  },
495
420
  required: ["translationsPath", "languages", "sourceLanguage"],
@@ -498,6 +423,314 @@ var init_schema = __esm({
498
423
  }
499
424
  });
500
425
 
426
+ // src/translations/utils/language-fallback.ts
427
+ function resolveLanguageWithFallback(language, provider, enableFallback = true) {
428
+ const normalizedLanguage = language.toLowerCase();
429
+ const supportedLanguages = getSupportedLanguagesForProvider(provider);
430
+ if (isLanguageSupported(normalizedLanguage, supportedLanguages)) {
431
+ return {
432
+ resolvedLanguage: normalizedLanguage,
433
+ usedFallback: false,
434
+ originalLanguage: language
435
+ };
436
+ }
437
+ if (!enableFallback) {
438
+ return {
439
+ resolvedLanguage: normalizedLanguage,
440
+ usedFallback: false,
441
+ originalLanguage: language
442
+ };
443
+ }
444
+ const fallbackChain = LANGUAGE_FALLBACK_MAP[normalizedLanguage] || [];
445
+ for (const fallbackLang of fallbackChain) {
446
+ if (isLanguageSupported(fallbackLang, supportedLanguages)) {
447
+ return {
448
+ resolvedLanguage: fallbackLang,
449
+ usedFallback: true,
450
+ originalLanguage: language,
451
+ fallbackChain: [normalizedLanguage, ...fallbackChain]
452
+ };
453
+ }
454
+ }
455
+ const baseLang = normalizedLanguage.split("_")[0];
456
+ if (baseLang !== normalizedLanguage && isLanguageSupported(baseLang, supportedLanguages)) {
457
+ return {
458
+ resolvedLanguage: baseLang,
459
+ usedFallback: true,
460
+ originalLanguage: language,
461
+ fallbackChain: [normalizedLanguage, baseLang]
462
+ };
463
+ }
464
+ return {
465
+ resolvedLanguage: normalizedLanguage,
466
+ usedFallback: false,
467
+ originalLanguage: language
468
+ };
469
+ }
470
+ function getSupportedLanguagesForProvider(provider) {
471
+ switch (provider) {
472
+ case "deepl":
473
+ return DEEPL_LANGUAGES;
474
+ case "google":
475
+ return GOOGLE_LANGUAGES;
476
+ default:
477
+ return [];
478
+ }
479
+ }
480
+ function isLanguageSupported(language, supportedLanguages) {
481
+ return supportedLanguages.includes(language);
482
+ }
483
+ function logLanguageFallback(result, provider) {
484
+ if (result.usedFallback) {
485
+ console.warn(
486
+ `\u26A0\uFE0F Language fallback: '${result.originalLanguage}' is not supported by ${provider}, using '${result.resolvedLanguage}' instead`
487
+ );
488
+ if (result.fallbackChain && result.fallbackChain.length > 2) {
489
+ console.warn(` Fallback chain: ${result.fallbackChain.join(" \u2192 ")}`);
490
+ }
491
+ }
492
+ }
493
+ function getFallbackMappings() {
494
+ return { ...LANGUAGE_FALLBACK_MAP };
495
+ }
496
+ var LANGUAGE_FALLBACK_MAP;
497
+ var init_language_fallback = __esm({
498
+ "src/translations/utils/language-fallback.ts"() {
499
+ "use strict";
500
+ init_schema();
501
+ LANGUAGE_FALLBACK_MAP = {
502
+ // German variants
503
+ de_at: ["de"],
504
+ // Austrian German -> German
505
+ de_ch: ["de"],
506
+ // Swiss German -> German
507
+ de_de: ["de"],
508
+ // Standard German -> German
509
+ // English variants
510
+ en_gb: ["en"],
511
+ // British English -> English
512
+ en_us: ["en"],
513
+ // American English -> English
514
+ en_au: ["en"],
515
+ // Australian English -> English
516
+ en_ca: ["en"],
517
+ // Canadian English -> English
518
+ en_nz: ["en"],
519
+ // New Zealand English -> English
520
+ // Chinese variants (Hong Kong, Taiwan -> Traditional)
521
+ zh_hk: ["zh_hant", "zh"],
522
+ // Hong Kong Chinese -> Traditional Chinese -> Chinese
523
+ zh_tw: ["zh_hant", "zh"],
524
+ // Taiwan Chinese -> Traditional Chinese -> Chinese
525
+ zh_mo: ["zh_hant", "zh"],
526
+ // Macau Chinese -> Traditional Chinese -> Chinese
527
+ // Chinese variants (Mainland, Singapore -> Simplified)
528
+ zh_cn: ["zh_hans", "zh"],
529
+ // Mainland Chinese -> Simplified Chinese -> Chinese
530
+ zh_sg: ["zh_hans", "zh"],
531
+ // Singapore Chinese -> Simplified Chinese -> Chinese
532
+ // Portuguese variants
533
+ pt_pt: ["pt"],
534
+ // European Portuguese -> Portuguese
535
+ pt_ao: ["pt"],
536
+ // Angolan Portuguese -> Portuguese
537
+ pt_mz: ["pt"],
538
+ // Mozambican Portuguese -> Portuguese
539
+ // Spanish variants (Latin America)
540
+ es_mx: ["es_419", "es"],
541
+ // Mexican Spanish -> Latin American Spanish -> Spanish
542
+ es_ar: ["es_419", "es"],
543
+ // Argentine Spanish -> Latin American Spanish -> Spanish
544
+ es_co: ["es_419", "es"],
545
+ // Colombian Spanish -> Latin American Spanish -> Spanish
546
+ es_cl: ["es_419", "es"],
547
+ // Chilean Spanish -> Latin American Spanish -> Spanish
548
+ es_pe: ["es_419", "es"],
549
+ // Peruvian Spanish -> Latin American Spanish -> Spanish
550
+ es_ve: ["es_419", "es"],
551
+ // Venezuelan Spanish -> Latin American Spanish -> Spanish
552
+ es_ec: ["es_419", "es"],
553
+ // Ecuadorian Spanish -> Latin American Spanish -> Spanish
554
+ es_gt: ["es_419", "es"],
555
+ // Guatemalan Spanish -> Latin American Spanish -> Spanish
556
+ es_cu: ["es_419", "es"],
557
+ // Cuban Spanish -> Latin American Spanish -> Spanish
558
+ es_do: ["es_419", "es"],
559
+ // Dominican Spanish -> Latin American Spanish -> Spanish
560
+ es_hn: ["es_419", "es"],
561
+ // Honduran Spanish -> Latin American Spanish -> Spanish
562
+ es_ni: ["es_419", "es"],
563
+ // Nicaraguan Spanish -> Latin American Spanish -> Spanish
564
+ es_sv: ["es_419", "es"],
565
+ // Salvadoran Spanish -> Latin American Spanish -> Spanish
566
+ es_cr: ["es_419", "es"],
567
+ // Costa Rican Spanish -> Latin American Spanish -> Spanish
568
+ es_pa: ["es_419", "es"],
569
+ // Panamanian Spanish -> Latin American Spanish -> Spanish
570
+ es_uy: ["es_419", "es"],
571
+ // Uruguayan Spanish -> Latin American Spanish -> Spanish
572
+ es_py: ["es_419", "es"],
573
+ // Paraguayan Spanish -> Latin American Spanish -> Spanish
574
+ es_bo: ["es_419", "es"],
575
+ // Bolivian Spanish -> Latin American Spanish -> Spanish
576
+ // Spanish (European)
577
+ es_es: ["es"],
578
+ // European Spanish -> Spanish
579
+ // French variants
580
+ fr_ca: ["fr"],
581
+ // Canadian French -> French
582
+ fr_ch: ["fr"],
583
+ // Swiss French -> French
584
+ fr_be: ["fr"],
585
+ // Belgian French -> French
586
+ fr_fr: ["fr"],
587
+ // Standard French -> French
588
+ // Norwegian variants
589
+ no: ["nb"],
590
+ // Norwegian -> Norwegian Bokmål
591
+ nn: ["nb"],
592
+ // Norwegian Nynorsk -> Norwegian Bokmål
593
+ // Other regional variants
594
+ it_ch: ["it"],
595
+ // Swiss Italian -> Italian
596
+ nl_be: ["nl"],
597
+ // Belgian Dutch (Flemish) -> Dutch
598
+ sv_fi: ["sv"],
599
+ // Finland Swedish -> Swedish
600
+ ar_ae: ["ar"],
601
+ // UAE Arabic -> Arabic
602
+ ar_sa: ["ar"],
603
+ // Saudi Arabic -> Arabic
604
+ ar_eg: ["ar"]
605
+ // Egyptian Arabic -> Arabic
606
+ };
607
+ }
608
+ });
609
+
610
+ // src/translations/utils/utils.ts
611
+ import * as fs from "fs";
612
+ import * as path from "path";
613
+ function readTranslations(translationsPath, language) {
614
+ const langPath = path.join(translationsPath, language);
615
+ if (!fs.existsSync(langPath)) {
616
+ return {};
617
+ }
618
+ const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
619
+ const translations = {};
620
+ for (const file of files) {
621
+ const namespace = path.basename(file, ".json");
622
+ const filePath = path.join(langPath, file);
623
+ const content = fs.readFileSync(filePath, "utf-8");
624
+ translations[namespace] = JSON.parse(content);
625
+ }
626
+ return translations;
627
+ }
628
+ function writeTranslation(translationsPath, language, namespace, translations) {
629
+ const langPath = path.join(translationsPath, language);
630
+ if (!fs.existsSync(langPath)) {
631
+ fs.mkdirSync(langPath, { recursive: true });
632
+ }
633
+ const filePath = path.join(langPath, `${namespace}.json`);
634
+ fs.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
635
+ `, "utf-8");
636
+ }
637
+ function getAvailableLanguages(translationsPath) {
638
+ if (!fs.existsSync(translationsPath)) {
639
+ return [];
640
+ }
641
+ return fs.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
642
+ }
643
+ function getNamespaces(translationsPath, language) {
644
+ const langPath = path.join(translationsPath, language);
645
+ if (!fs.existsSync(langPath)) {
646
+ return [];
647
+ }
648
+ return fs.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path.basename(f, ".json"));
649
+ }
650
+ function hasInterpolation(text) {
651
+ return /\{\{[^}]+\}\}/g.test(text);
652
+ }
653
+ function extractVariables(text) {
654
+ const matches = text.match(/\{\{([^}]+)\}\}/g);
655
+ if (!matches) return [];
656
+ return matches.map((match) => match.replace(/\{\{|\}\}/g, "").trim());
657
+ }
658
+ function validateVariables(sourceText, translatedText) {
659
+ const sourceVars = extractVariables(sourceText).sort();
660
+ const translatedVars = extractVariables(translatedText).sort();
661
+ if (sourceVars.length !== translatedVars.length) {
662
+ return false;
663
+ }
664
+ return sourceVars.every((v, i) => v === translatedVars[i]);
665
+ }
666
+ function sortKeys(obj) {
667
+ const sorted = {};
668
+ const keys = Object.keys(obj).sort();
669
+ for (const key of keys) {
670
+ sorted[key] = obj[key];
671
+ }
672
+ return sorted;
673
+ }
674
+ function ensureTranslationsStructure(translationsPath, languages) {
675
+ if (!fs.existsSync(translationsPath)) {
676
+ fs.mkdirSync(translationsPath, { recursive: true });
677
+ }
678
+ for (const lang of languages) {
679
+ const langPath = path.join(translationsPath, lang);
680
+ if (!fs.existsSync(langPath)) {
681
+ fs.mkdirSync(langPath, { recursive: true });
682
+ }
683
+ }
684
+ }
685
+ function createEmptyTranslationStructure(sourceFile) {
686
+ const result = {};
687
+ for (const key of Object.keys(sourceFile)) {
688
+ result[key] = "";
689
+ }
690
+ return result;
691
+ }
692
+ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
693
+ const result = {
694
+ createdFolders: [],
695
+ createdFiles: [],
696
+ skippedFiles: []
697
+ };
698
+ ensureTranslationsStructure(translationsPath, languages);
699
+ const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
700
+ if (!sourceNamespaces.length) {
701
+ return result;
702
+ }
703
+ const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
704
+ const targetLanguages = languages.filter((lang) => lang !== sourceLanguage);
705
+ for (const language of targetLanguages) {
706
+ for (const namespace of sourceNamespaces) {
707
+ const filePath = path.join(translationsPath, language, `${namespace}.json`);
708
+ if (fs.existsSync(filePath)) {
709
+ result.skippedFiles.push({
710
+ language,
711
+ namespace,
712
+ reason: "already exists"
713
+ });
714
+ continue;
715
+ }
716
+ const sourceFile = sourceTranslations[namespace] || {};
717
+ const emptyStructure = createEmptyTranslationStructure(sourceFile);
718
+ writeTranslation(translationsPath, language, namespace, emptyStructure);
719
+ result.createdFiles.push({
720
+ language,
721
+ namespace,
722
+ path: filePath
723
+ });
724
+ }
725
+ }
726
+ return result;
727
+ }
728
+ var init_utils = __esm({
729
+ "src/translations/utils/utils.ts"() {
730
+ "use strict";
731
+ }
732
+ });
733
+
501
734
  // src/translations/core/types.ts
502
735
  var DEFAULT_CONFIG, DEFAULT_LANGUAGES;
503
736
  var init_types = __esm({
@@ -507,24 +740,11 @@ var init_types = __esm({
507
740
  translationsPath: "public/static/locales",
508
741
  languages: ["en"],
509
742
  sourceLanguage: "en",
510
- typesOutputPath: "src/types/i18nTypes.ts"
743
+ typesOutputPath: "src/types/i18nTypes.ts",
744
+ provider: "google",
745
+ useFallbackLanguages: true
511
746
  };
512
- DEFAULT_LANGUAGES = [
513
- "en",
514
- "fr",
515
- "it",
516
- "pl",
517
- "es",
518
- "pt",
519
- "de",
520
- "de_at",
521
- "nl",
522
- "sv",
523
- "hu",
524
- "cs",
525
- "ja",
526
- "zh_hk"
527
- ];
747
+ DEFAULT_LANGUAGES = ["en", "fr", "it", "pl", "es", "pt", "de", "nl", "sv", "hu", "cs", "ja"];
528
748
  }
529
749
  });
530
750
 
@@ -598,12 +818,14 @@ function initTranslations(projectRoot, config = {}) {
598
818
  `, "utf-8");
599
819
  console.log(`Created sample file: ${commonPath}`);
600
820
  }
601
- for (const lang of languages) {
602
- if (lang === sourceLanguage) continue;
603
- const langCommonPath = path2.join(translationsPath, lang, "common.json");
604
- if (!fs2.existsSync(langCommonPath)) {
605
- fs2.writeFileSync(langCommonPath, "{}\n", "utf-8");
606
- console.log(`Created empty file: ${langCommonPath}`);
821
+ console.log("\nSynchronizing translation structure...");
822
+ const syncResult = syncTranslationStructure(translationsPath, languages, sourceLanguage);
823
+ if (syncResult.createdFiles.length > 0) {
824
+ console.log(`Created ${syncResult.createdFiles.length} namespace files in target languages`);
825
+ const languageGroups = new Set(syncResult.createdFiles.map((f) => f.language));
826
+ for (const lang of languageGroups) {
827
+ const langFiles = syncResult.createdFiles.filter((f) => f.language === lang);
828
+ console.log(` ${lang}: ${langFiles.map((f) => f.namespace).join(", ")}`);
607
829
  }
608
830
  }
609
831
  const configPath = path2.join(projectRoot, ".translationsrc.json");
@@ -613,7 +835,8 @@ function initTranslations(projectRoot, config = {}) {
613
835
  translationsPath: finalConfig.translationsPath,
614
836
  languages,
615
837
  sourceLanguage,
616
- typesOutputPath: finalConfig.typesOutputPath
838
+ typesOutputPath: finalConfig.typesOutputPath,
839
+ provider: finalConfig.provider
617
840
  };
618
841
  fs2.writeFileSync(configPath, `${JSON.stringify(configContent, null, 2)}
619
842
  `, "utf-8");
@@ -661,6 +884,7 @@ var init_init = __esm({
661
884
  import * as path3 from "path";
662
885
 
663
886
  // src/translations/utils/google-translate-provider.ts
887
+ init_language_fallback();
664
888
  function preserveVariables(text) {
665
889
  const variableMap = /* @__PURE__ */ new Map();
666
890
  let placeholderIndex = 0;
@@ -681,14 +905,24 @@ function restoreVariables(text, variableMap) {
681
905
  }
682
906
  var GoogleTranslateProvider = class {
683
907
  async translate(options) {
684
- const { text, sourceLang, targetLang, apiKey } = options;
908
+ const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true } = options;
685
909
  if (!apiKey) {
686
910
  throw new Error(
687
911
  "Google Translate API key is required. Set GOOGLE_TRANSLATE_API_KEY environment variable or provide apiKey in options."
688
912
  );
689
913
  }
914
+ const targetLangResult = resolveLanguageWithFallback(targetLang, "google", useFallbackLanguages);
915
+ logLanguageFallback(targetLangResult, "google");
916
+ let resolvedSourceLang;
917
+ if (sourceLang) {
918
+ const sourceLangResult = resolveLanguageWithFallback(sourceLang, "google", useFallbackLanguages);
919
+ logLanguageFallback(sourceLangResult, "google");
920
+ resolvedSourceLang = sourceLangResult.resolvedLanguage;
921
+ }
690
922
  const { textWithPlaceholders, variableMap } = preserveVariables(text);
691
923
  const url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
924
+ const sourceForGoogle = resolvedSourceLang?.includes("_") ? resolvedSourceLang.split("_")[0] : resolvedSourceLang;
925
+ const targetForGoogle = targetLangResult.resolvedLanguage.includes("_") ? targetLangResult.resolvedLanguage.split("_")[0] : targetLangResult.resolvedLanguage;
692
926
  const response = await fetch(url, {
693
927
  method: "POST",
694
928
  headers: {
@@ -696,9 +930,8 @@ var GoogleTranslateProvider = class {
696
930
  },
697
931
  body: JSON.stringify({
698
932
  q: textWithPlaceholders,
699
- source: sourceLang,
700
- target: targetLang.split("_")[0],
701
- // Convert 'pt_BR' to 'pt'
933
+ source: sourceForGoogle,
934
+ target: targetForGoogle,
702
935
  format: "text"
703
936
  })
704
937
  });
@@ -743,13 +976,14 @@ function getTranslationProvider() {
743
976
  function resetTranslationProvider() {
744
977
  customProvider = null;
745
978
  }
746
- async function translateText(text, targetLang, sourceLang = "en", apiKey) {
979
+ async function translateText(text, targetLang, sourceLang = "en", apiKey, useFallbackLanguages = true) {
747
980
  const provider = getTranslationProvider();
748
981
  return provider.translate({
749
982
  text,
750
983
  sourceLang,
751
984
  targetLang,
752
- apiKey
985
+ apiKey,
986
+ useFallbackLanguages
753
987
  });
754
988
  }
755
989
  async function translateBatch(texts, targetLang, sourceLang = "en", apiKey, delayMs = 100) {
@@ -845,6 +1079,101 @@ async function addTranslationKeys(projectRoot, entries, autoTranslate = false, a
845
1079
 
846
1080
  // src/translations/cli/auto-fill.ts
847
1081
  import * as path5 from "path";
1082
+
1083
+ // src/translations/utils/deepl-translate-provider.ts
1084
+ init_language_fallback();
1085
+ function preserveVariables2(text) {
1086
+ const variableMap = /* @__PURE__ */ new Map();
1087
+ let placeholderIndex = 0;
1088
+ const textWithPlaceholders = text.replace(/\{\{([^}]+)\}\}/g, (match) => {
1089
+ const placeholder = `XXX_${placeholderIndex}_XXX`;
1090
+ variableMap.set(placeholder, match);
1091
+ placeholderIndex++;
1092
+ return placeholder;
1093
+ });
1094
+ return { textWithPlaceholders, variableMap };
1095
+ }
1096
+ function restoreVariables2(text, variableMap) {
1097
+ let result = text;
1098
+ for (const [placeholder, original] of variableMap) {
1099
+ result = result.replace(new RegExp(placeholder, "g"), original);
1100
+ }
1101
+ return result;
1102
+ }
1103
+ function normalizeLanguageCode(langCode) {
1104
+ return langCode.replace("_", "-").toUpperCase();
1105
+ }
1106
+ var DeepLTranslateProvider = class {
1107
+ isFreeApi;
1108
+ constructor(isFreeApi = false) {
1109
+ this.isFreeApi = isFreeApi;
1110
+ }
1111
+ getApiEndpoint() {
1112
+ return this.isFreeApi ? "https://api-free.deepl.com/v2/translate" : "https://api.deepl.com/v2/translate";
1113
+ }
1114
+ async translate(options) {
1115
+ const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true } = options;
1116
+ if (!apiKey) {
1117
+ throw new Error(
1118
+ "DeepL API key is required. Set DEEPL_API_KEY environment variable or provide apiKey in options."
1119
+ );
1120
+ }
1121
+ const targetLangResult = resolveLanguageWithFallback(targetLang, "deepl", useFallbackLanguages);
1122
+ logLanguageFallback(targetLangResult, "deepl");
1123
+ let resolvedSourceLang;
1124
+ if (sourceLang) {
1125
+ const sourceLangResult = resolveLanguageWithFallback(sourceLang, "deepl", useFallbackLanguages);
1126
+ logLanguageFallback(sourceLangResult, "deepl");
1127
+ resolvedSourceLang = sourceLangResult.resolvedLanguage;
1128
+ }
1129
+ const { textWithPlaceholders, variableMap } = preserveVariables2(text);
1130
+ const body = {
1131
+ text: [textWithPlaceholders],
1132
+ target_lang: normalizeLanguageCode(targetLangResult.resolvedLanguage),
1133
+ ...resolvedSourceLang && { source_lang: normalizeLanguageCode(resolvedSourceLang) }
1134
+ };
1135
+ const response = await fetch(this.getApiEndpoint(), {
1136
+ method: "POST",
1137
+ headers: {
1138
+ Authorization: `DeepL-Auth-Key ${apiKey}`,
1139
+ "Content-Type": "application/json"
1140
+ },
1141
+ body: JSON.stringify(body)
1142
+ });
1143
+ if (!response.ok) {
1144
+ const errorData = await response.json().catch(() => ({}));
1145
+ throw new Error(`DeepL API error: ${errorData.message || response.statusText} (${response.status})`);
1146
+ }
1147
+ const data = await response.json();
1148
+ if (!data.translations || data.translations.length === 0) {
1149
+ throw new Error("DeepL API returned no translations");
1150
+ }
1151
+ const translatedText = data.translations[0].text;
1152
+ return restoreVariables2(translatedText, variableMap);
1153
+ }
1154
+ async translateBatch(texts, sourceLang, targetLang, apiKey, delayMs = 100) {
1155
+ const results = [];
1156
+ for (const text of texts) {
1157
+ const translated = await this.translate({
1158
+ text,
1159
+ sourceLang,
1160
+ targetLang,
1161
+ apiKey
1162
+ });
1163
+ results.push(translated);
1164
+ if (delayMs > 0) {
1165
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1166
+ }
1167
+ }
1168
+ return results;
1169
+ }
1170
+ async validateConfig() {
1171
+ const apiKey = process.env.DEEPL_API_KEY;
1172
+ return !!apiKey;
1173
+ }
1174
+ };
1175
+
1176
+ // src/translations/cli/auto-fill.ts
848
1177
  init_utils();
849
1178
  init_init();
850
1179
 
@@ -860,7 +1189,11 @@ function validateTranslations(projectRoot = process.cwd()) {
860
1189
  const empty = [];
861
1190
  const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
862
1191
  const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
863
- const languages = getAvailableLanguages(translationsPath).filter((lang) => lang !== sourceLanguage);
1192
+ const languages = config.languages.filter((lang) => lang !== sourceLanguage);
1193
+ const syncResult = syncTranslationStructure(translationsPath, config.languages, sourceLanguage);
1194
+ if (syncResult.createdFiles.length > 0) {
1195
+ console.log(`Created ${syncResult.createdFiles.length} missing namespace files during sync`);
1196
+ }
864
1197
  console.log("=====");
865
1198
  console.log("Validating translations");
866
1199
  console.log("=====");
@@ -893,7 +1226,7 @@ function validateTranslations(projectRoot = process.cwd()) {
893
1226
  }
894
1227
  }
895
1228
  }
896
- const valid = missing.length === 0 && empty.length === 0;
1229
+ const valid = !missing.length && !empty.length;
897
1230
  if (valid) {
898
1231
  console.log("\u2713 All translations are valid!");
899
1232
  } else {
@@ -935,10 +1268,28 @@ async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
935
1268
  const config = loadConfig(projectRoot);
936
1269
  const translationsPath = path5.join(projectRoot, config.translationsPath);
937
1270
  const { apiKey, limit = 1e3, delayMs = 100, dryRun = false } = options;
1271
+ const currentProvider = getTranslationProvider();
1272
+ const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
1273
+ if (isDefaultGoogleProvider) {
1274
+ const provider = config.provider || "deepl";
1275
+ if (provider === "deepl") {
1276
+ setTranslationProvider(new DeepLTranslateProvider());
1277
+ } else {
1278
+ setTranslationProvider(new GoogleTranslateProvider());
1279
+ }
1280
+ }
938
1281
  if (!apiKey) {
939
- throw new Error("Google Translate API key is required. Set GOOGLE_TRANSLATE_API_KEY or pass --api-key");
1282
+ const provider = config.provider || "deepl";
1283
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
1284
+ throw new Error(`Translation API key is required. Set ${envVarName} or pass --api-key`);
940
1285
  }
941
1286
  const languagesToProcess = options.language ? [options.language] : config.languages.filter((lang) => lang !== config.sourceLanguage);
1287
+ console.log("\u{1F504} Synchronizing translation structure...");
1288
+ const syncResult = syncTranslationStructure(translationsPath, config.languages, config.sourceLanguage);
1289
+ if (syncResult.createdFiles.length > 0) {
1290
+ console.log(`Created ${syncResult.createdFiles.length} namespace files
1291
+ `);
1292
+ }
942
1293
  console.log("=====");
943
1294
  console.log("Auto-filling translations");
944
1295
  console.log("=====");
@@ -957,7 +1308,7 @@ Reached limit of ${limit} translations`);
957
1308
  console.log(`
958
1309
  Processing language: ${language}`);
959
1310
  const missing = getMissingForLanguage(projectRoot, language);
960
- if (missing.length === 0) {
1311
+ if (!missing.length) {
961
1312
  console.log(" No missing or empty translations");
962
1313
  continue;
963
1314
  }
@@ -969,7 +1320,13 @@ Processing language: ${language}`);
969
1320
  try {
970
1321
  console.log(` [${totalProcessed}/${limit}] Translating ${item.namespace}.${item.key}`);
971
1322
  console.log(` EN: "${item.sourceValue}"`);
972
- const translated = await translateText(item.sourceValue, language, config.sourceLanguage, apiKey);
1323
+ const translated = await translateText(
1324
+ item.sourceValue,
1325
+ language,
1326
+ config.sourceLanguage,
1327
+ apiKey,
1328
+ config.useFallbackLanguages ?? true
1329
+ );
973
1330
  console.log(` ${language.toUpperCase()}: "${translated}"`);
974
1331
  if (!dryRun) {
975
1332
  const translations = readTranslations(translationsPath, language);
@@ -1003,6 +1360,16 @@ Processing language: ${language}`);
1003
1360
  async function fillNamespace(projectRoot, language, namespace, apiKey) {
1004
1361
  const config = loadConfig(projectRoot);
1005
1362
  const translationsPath = path5.join(projectRoot, config.translationsPath);
1363
+ const currentProvider = getTranslationProvider();
1364
+ const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
1365
+ if (isDefaultGoogleProvider) {
1366
+ const provider = config.provider || "deepl";
1367
+ if (provider === "deepl") {
1368
+ setTranslationProvider(new DeepLTranslateProvider());
1369
+ } else {
1370
+ setTranslationProvider(new GoogleTranslateProvider());
1371
+ }
1372
+ }
1006
1373
  console.log(`Filling translations for ${language}/${namespace}.json`);
1007
1374
  const sourceTranslations = readTranslations(translationsPath, config.sourceLanguage);
1008
1375
  const targetTranslations = readTranslations(translationsPath, language);
@@ -1015,7 +1382,13 @@ async function fillNamespace(projectRoot, language, namespace, apiKey) {
1015
1382
  continue;
1016
1383
  }
1017
1384
  console.log(` Translating ${key}...`);
1018
- const translated = await translateText(sourceValue, language, config.sourceLanguage, apiKey);
1385
+ const translated = await translateText(
1386
+ sourceValue,
1387
+ language,
1388
+ config.sourceLanguage,
1389
+ apiKey,
1390
+ config.useFallbackLanguages ?? true
1391
+ );
1019
1392
  targetKeys[key] = translated;
1020
1393
  count++;
1021
1394
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -1056,7 +1429,7 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
1056
1429
  throw new Error(`Source language directory not found: ${dirPath}`);
1057
1430
  }
1058
1431
  const namespaces = getNamespaces(translationsPath, sourceLanguage);
1059
- if (namespaces.length === 0) {
1432
+ if (!namespaces.length) {
1060
1433
  throw new Error(`No translation files found in ${dirPath}`);
1061
1434
  }
1062
1435
  const translations = readTranslations(translationsPath, sourceLanguage);
@@ -1094,7 +1467,7 @@ init_types();
1094
1467
  init_init();
1095
1468
  import * as fs4 from "fs";
1096
1469
  import * as path7 from "path";
1097
- import { checkbox, confirm, input } from "@inquirer/prompts";
1470
+ import { checkbox, confirm, input, select } from "@inquirer/prompts";
1098
1471
  async function initTranslationsInteractive(projectRoot = process.cwd()) {
1099
1472
  console.log("\n\u{1F30D} Translation System Setup\n");
1100
1473
  const configPath = path7.join(projectRoot, ".translationsrc.json");
@@ -1173,11 +1546,28 @@ async function initTranslationsInteractive(projectRoot = process.cwd()) {
1173
1546
  message: "Where should TypeScript types be generated?",
1174
1547
  default: DEFAULT_CONFIG.typesOutputPath
1175
1548
  });
1549
+ const provider = await select({
1550
+ message: "Which translation provider would you like to use?",
1551
+ choices: [
1552
+ {
1553
+ name: "DeepL (recommended)",
1554
+ value: "deepl",
1555
+ description: "High-quality translations, requires DEEPL_API_KEY"
1556
+ },
1557
+ {
1558
+ name: "Google Translate",
1559
+ value: "google",
1560
+ description: "Google Cloud Translation API, requires GOOGLE_TRANSLATE_API_KEY"
1561
+ }
1562
+ ],
1563
+ default: "deepl"
1564
+ });
1176
1565
  console.log("\n\u{1F4CB} Configuration Summary:");
1177
1566
  console.log(` Translations: ${translationsPath}`);
1178
1567
  console.log(` Languages: ${languages.join(", ")}`);
1179
1568
  console.log(` Source: ${sourceLanguage}`);
1180
1569
  console.log(` Types: ${typesOutputPath}`);
1570
+ console.log(` Provider: ${provider}`);
1181
1571
  const confirmInit = await confirm({
1182
1572
  message: "\nProceed with initialization?",
1183
1573
  default: true
@@ -1190,7 +1580,8 @@ async function initTranslationsInteractive(projectRoot = process.cwd()) {
1190
1580
  translationsPath,
1191
1581
  languages,
1192
1582
  sourceLanguage,
1193
- typesOutputPath
1583
+ typesOutputPath,
1584
+ provider
1194
1585
  };
1195
1586
  console.log();
1196
1587
  initTranslations(projectRoot, config);
@@ -1247,6 +1638,7 @@ function getLanguageName(code) {
1247
1638
  }
1248
1639
 
1249
1640
  // src/translations/cli/manage.ts
1641
+ init_utils();
1250
1642
  import * as fs5 from "fs";
1251
1643
  import * as path8 from "path";
1252
1644
  init_init();
@@ -1272,6 +1664,14 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
1272
1664
  console.log("Please add translation files to the source language directory.\n");
1273
1665
  return false;
1274
1666
  }
1667
+ console.log("\u{1F504} Synchronizing translation structure...\n");
1668
+ const syncResult = syncTranslationStructure(translationsPath, config.languages, config.sourceLanguage);
1669
+ if (syncResult.createdFiles.length > 0) {
1670
+ console.log(`\u2713 Created ${syncResult.createdFiles.length} namespace files
1671
+ `);
1672
+ } else {
1673
+ console.log("\u2713 Translation structure is already synchronized\n");
1674
+ }
1275
1675
  console.log("\u{1F50D} Validating translations...\n");
1276
1676
  const validationResult = validateTranslations(projectRoot);
1277
1677
  if (validationResult.valid) {
@@ -1280,8 +1680,11 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
1280
1680
  const totalMissing = validationResult.missing.length + validationResult.empty.length;
1281
1681
  if (autoFill) {
1282
1682
  if (!apiKey) {
1683
+ const provider = config.provider || "deepl";
1684
+ const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
1283
1685
  console.log("\n\u26A0\uFE0F Auto-fill requested but no API key provided.");
1284
- console.log("Set GOOGLE_TRANSLATE_API_KEY or pass --api-key to enable auto-fill.\n");
1686
+ console.log(`Set ${envVarName} or pass --api-key to enable auto-fill.
1687
+ `);
1285
1688
  } else {
1286
1689
  console.log(`
1287
1690
  \u{1F916} Auto-filling ${totalMissing} missing translations...
@@ -1357,12 +1760,14 @@ export {
1357
1760
  addTranslationKey,
1358
1761
  addTranslationKeys,
1359
1762
  autoFillTranslations,
1763
+ createEmptyTranslationStructure,
1360
1764
  detectExistingTranslations,
1361
1765
  ensureTranslationsStructure,
1362
1766
  extractVariables,
1363
1767
  fillNamespace,
1364
1768
  generateTranslationTypes,
1365
1769
  getAvailableLanguages,
1770
+ getFallbackMappings,
1366
1771
  getMissingForLanguage,
1367
1772
  getNamespaces,
1368
1773
  getSupportedLanguages,
@@ -1375,11 +1780,14 @@ export {
1375
1780
  isValidLanguage,
1376
1781
  isValidLanguageForProvider,
1377
1782
  loadConfig,
1783
+ logLanguageFallback,
1378
1784
  manageTranslations,
1379
1785
  readTranslations,
1380
1786
  resetTranslationProvider,
1787
+ resolveLanguageWithFallback,
1381
1788
  setTranslationProvider,
1382
1789
  sortKeys,
1790
+ syncTranslationStructure,
1383
1791
  translateBatch,
1384
1792
  translateText,
1385
1793
  validateLanguages,