poly-lexis 0.9.3 → 0.11.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.
@@ -529,9 +529,100 @@ var init_language_fallback = __esm({
529
529
  }
530
530
  });
531
531
 
532
- // src/translations/utils/utils.ts
532
+ // src/translations/utils/metadata.ts
533
+ import { createHash } from "crypto";
533
534
  import * as fs from "fs";
534
535
  import * as path2 from "path";
536
+ function emptyMetadata() {
537
+ return { version: CURRENT_VERSION, languages: {} };
538
+ }
539
+ function hashSourceValue(value) {
540
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
541
+ }
542
+ function getMetadataPath(translationsPath) {
543
+ return path2.join(translationsPath, METADATA_FILE_NAME);
544
+ }
545
+ function metadataExists(translationsPath) {
546
+ return fs.existsSync(getMetadataPath(translationsPath));
547
+ }
548
+ function readMetadata(translationsPath) {
549
+ const metadataPath = getMetadataPath(translationsPath);
550
+ if (!fs.existsSync(metadataPath)) {
551
+ return emptyMetadata();
552
+ }
553
+ try {
554
+ const parsed = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
555
+ return {
556
+ version: parsed.version ?? CURRENT_VERSION,
557
+ languages: parsed.languages ?? {}
558
+ };
559
+ } catch {
560
+ return emptyMetadata();
561
+ }
562
+ }
563
+ function writeMetadata(translationsPath, metadata) {
564
+ if (!fs.existsSync(translationsPath)) {
565
+ fs.mkdirSync(translationsPath, { recursive: true });
566
+ }
567
+ const metadataPath = getMetadataPath(translationsPath);
568
+ fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
569
+ `, "utf-8");
570
+ }
571
+ function setSourceHash(metadata, language, namespace, key, sourceValue) {
572
+ if (!metadata.languages[language]) {
573
+ metadata.languages[language] = {};
574
+ }
575
+ if (!metadata.languages[language][namespace]) {
576
+ metadata.languages[language][namespace] = {};
577
+ }
578
+ metadata.languages[language][namespace][key] = hashSourceValue(sourceValue);
579
+ return metadata;
580
+ }
581
+ function getSourceHash(metadata, language, namespace, key) {
582
+ return metadata.languages[language]?.[namespace]?.[key];
583
+ }
584
+ function pruneMetadata(metadata, sourceKeysByNamespace) {
585
+ let changed = false;
586
+ for (const language of Object.keys(metadata.languages)) {
587
+ const namespaces = metadata.languages[language];
588
+ for (const namespace of Object.keys(namespaces)) {
589
+ const sourceKeys = sourceKeysByNamespace[namespace];
590
+ if (!sourceKeys) {
591
+ delete namespaces[namespace];
592
+ changed = true;
593
+ continue;
594
+ }
595
+ for (const key of Object.keys(namespaces[namespace])) {
596
+ if (!sourceKeys.has(key)) {
597
+ delete namespaces[namespace][key];
598
+ changed = true;
599
+ }
600
+ }
601
+ if (Object.keys(namespaces[namespace]).length === 0) {
602
+ delete namespaces[namespace];
603
+ changed = true;
604
+ }
605
+ }
606
+ if (Object.keys(namespaces).length === 0) {
607
+ delete metadata.languages[language];
608
+ changed = true;
609
+ }
610
+ }
611
+ return changed;
612
+ }
613
+ var METADATA_FILE_NAME, CURRENT_VERSION;
614
+ var init_metadata = __esm({
615
+ "src/translations/utils/metadata.ts"() {
616
+ "use strict";
617
+ init_esm_shims();
618
+ METADATA_FILE_NAME = ".translations-meta.json";
619
+ CURRENT_VERSION = 1;
620
+ }
621
+ });
622
+
623
+ // src/translations/utils/utils.ts
624
+ import * as fs2 from "fs";
625
+ import * as path3 from "path";
535
626
  function flattenObject(obj, prefix = "") {
536
627
  const result = {};
537
628
  for (const [key, value] of Object.entries(obj)) {
@@ -548,16 +639,16 @@ function isNestedObject(obj) {
548
639
  return Object.values(obj).some((value) => typeof value === "object" && value !== null);
549
640
  }
550
641
  function readTranslations(translationsPath, language) {
551
- const langPath = path2.join(translationsPath, language);
552
- if (!fs.existsSync(langPath)) {
642
+ const langPath = path3.join(translationsPath, language);
643
+ if (!fs2.existsSync(langPath)) {
553
644
  return {};
554
645
  }
555
- const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
646
+ const files = fs2.readdirSync(langPath).filter((f) => f.endsWith(".json"));
556
647
  const translations = {};
557
648
  for (const file of files) {
558
- const namespace = path2.basename(file, ".json");
559
- const filePath = path2.join(langPath, file);
560
- const content = fs.readFileSync(filePath, "utf-8");
649
+ const namespace = path3.basename(file, ".json");
650
+ const filePath = path3.join(langPath, file);
651
+ const content = fs2.readFileSync(filePath, "utf-8");
561
652
  const parsed = JSON.parse(content);
562
653
  if (isNestedObject(parsed)) {
563
654
  translations[namespace] = flattenObject(parsed);
@@ -568,26 +659,26 @@ function readTranslations(translationsPath, language) {
568
659
  return translations;
569
660
  }
570
661
  function writeTranslation(translationsPath, language, namespace, translations) {
571
- const langPath = path2.join(translationsPath, language);
572
- if (!fs.existsSync(langPath)) {
573
- fs.mkdirSync(langPath, { recursive: true });
662
+ const langPath = path3.join(translationsPath, language);
663
+ if (!fs2.existsSync(langPath)) {
664
+ fs2.mkdirSync(langPath, { recursive: true });
574
665
  }
575
- const filePath = path2.join(langPath, `${namespace}.json`);
576
- fs.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
666
+ const filePath = path3.join(langPath, `${namespace}.json`);
667
+ fs2.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
577
668
  `, "utf-8");
578
669
  }
579
670
  function getAvailableLanguages(translationsPath) {
580
- if (!fs.existsSync(translationsPath)) {
671
+ if (!fs2.existsSync(translationsPath)) {
581
672
  return [];
582
673
  }
583
- return fs.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
674
+ return fs2.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
584
675
  }
585
676
  function getNamespaces(translationsPath, language) {
586
- const langPath = path2.join(translationsPath, language);
587
- if (!fs.existsSync(langPath)) {
677
+ const langPath = path3.join(translationsPath, language);
678
+ if (!fs2.existsSync(langPath)) {
588
679
  return [];
589
680
  }
590
- return fs.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path2.basename(f, ".json"));
681
+ return fs2.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path3.basename(f, ".json"));
591
682
  }
592
683
  function sortKeys(obj) {
593
684
  const sorted = {};
@@ -598,13 +689,13 @@ function sortKeys(obj) {
598
689
  return sorted;
599
690
  }
600
691
  function ensureTranslationsStructure(translationsPath, languages) {
601
- if (!fs.existsSync(translationsPath)) {
602
- fs.mkdirSync(translationsPath, { recursive: true });
692
+ if (!fs2.existsSync(translationsPath)) {
693
+ fs2.mkdirSync(translationsPath, { recursive: true });
603
694
  }
604
695
  for (const lang of languages) {
605
- const langPath = path2.join(translationsPath, lang);
606
- if (!fs.existsSync(langPath)) {
607
- fs.mkdirSync(langPath, { recursive: true });
696
+ const langPath = path3.join(translationsPath, lang);
697
+ if (!fs2.existsSync(langPath)) {
698
+ fs2.mkdirSync(langPath, { recursive: true });
608
699
  }
609
700
  }
610
701
  }
@@ -635,8 +726,8 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
635
726
  const targetNamespaces = getNamespaces(translationsPath, language);
636
727
  for (const namespace of targetNamespaces) {
637
728
  if (!sourceNamespaces.includes(namespace)) {
638
- const filePath = path2.join(translationsPath, language, `${namespace}.json`);
639
- fs.unlinkSync(filePath);
729
+ const filePath = path3.join(translationsPath, language, `${namespace}.json`);
730
+ fs2.unlinkSync(filePath);
640
731
  result.removedNamespaces.push({
641
732
  language,
642
733
  namespace,
@@ -645,9 +736,9 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
645
736
  }
646
737
  }
647
738
  for (const namespace of sourceNamespaces) {
648
- const filePath = path2.join(translationsPath, language, `${namespace}.json`);
739
+ const filePath = path3.join(translationsPath, language, `${namespace}.json`);
649
740
  const sourceFile = sourceTranslations[namespace] || {};
650
- if (fs.existsSync(filePath)) {
741
+ if (fs2.existsSync(filePath)) {
651
742
  const targetFile = targetTranslations[namespace] || {};
652
743
  let hasOrphanedKeys = false;
653
744
  const cleanedFile = {};
@@ -687,12 +778,23 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
687
778
  });
688
779
  }
689
780
  }
781
+ if (metadataExists(translationsPath)) {
782
+ const sourceKeysByNamespace = {};
783
+ for (const namespace of sourceNamespaces) {
784
+ sourceKeysByNamespace[namespace] = new Set(Object.keys(sourceTranslations[namespace] || {}));
785
+ }
786
+ const metadata = readMetadata(translationsPath);
787
+ if (pruneMetadata(metadata, sourceKeysByNamespace)) {
788
+ writeMetadata(translationsPath, metadata);
789
+ }
790
+ }
690
791
  return result;
691
792
  }
692
793
  var init_utils = __esm({
693
794
  "src/translations/utils/utils.ts"() {
694
795
  "use strict";
695
796
  init_esm_shims();
797
+ init_metadata();
696
798
  }
697
799
  });
698
800
 
@@ -724,13 +826,13 @@ __export(init_exports, {
724
826
  initTranslations: () => initTranslations,
725
827
  loadConfig: () => loadConfig
726
828
  });
727
- import * as fs2 from "fs";
728
- import * as path3 from "path";
829
+ import * as fs3 from "fs";
830
+ import * as path4 from "path";
729
831
  function detectExistingTranslations(projectRoot) {
730
832
  const possiblePaths = ["public/static/locales", "public/locales", "src/locales", "locales", "i18n", "translations"];
731
833
  for (const possiblePath of possiblePaths) {
732
- const fullPath = path3.join(projectRoot, possiblePath);
733
- if (fs2.existsSync(fullPath)) {
834
+ const fullPath = path4.join(projectRoot, possiblePath);
835
+ if (fs3.existsSync(fullPath)) {
734
836
  const languages = getAvailableLanguages(fullPath);
735
837
  if (languages.length > 0) {
736
838
  return { path: possiblePath, languages };
@@ -760,7 +862,7 @@ function initTranslations(projectRoot, config = {}) {
760
862
  languages: validLanguages.length > 0 ? validLanguages : finalConfig.languages
761
863
  };
762
864
  }
763
- const translationsPath = path3.join(projectRoot, finalConfig.translationsPath);
865
+ const translationsPath = path4.join(projectRoot, finalConfig.translationsPath);
764
866
  const languages = finalConfig.languages.length > 0 ? finalConfig.languages : [...DEFAULT_LANGUAGES];
765
867
  console.log(`Project root: ${projectRoot}`);
766
868
  console.log(`Translations path: ${translationsPath}`);
@@ -772,9 +874,9 @@ function initTranslations(projectRoot, config = {}) {
772
874
  }
773
875
  ensureTranslationsStructure(translationsPath, languages);
774
876
  const sourceLanguage = finalConfig.sourceLanguage;
775
- const sourcePath = path3.join(translationsPath, sourceLanguage);
776
- const commonPath = path3.join(sourcePath, "common.json");
777
- if (!fs2.existsSync(commonPath)) {
877
+ const sourcePath = path4.join(translationsPath, sourceLanguage);
878
+ const commonPath = path4.join(sourcePath, "common.json");
879
+ if (!fs3.existsSync(commonPath)) {
778
880
  const sampleTranslations = {
779
881
  LOADING: "Loading",
780
882
  SAVE: "Save",
@@ -783,7 +885,7 @@ function initTranslations(projectRoot, config = {}) {
783
885
  ERROR: "Error",
784
886
  SUCCESS: "Success"
785
887
  };
786
- fs2.writeFileSync(commonPath, `${JSON.stringify(sampleTranslations, null, 2)}
888
+ fs3.writeFileSync(commonPath, `${JSON.stringify(sampleTranslations, null, 2)}
787
889
  `, "utf-8");
788
890
  console.log(`Created sample file: ${commonPath}`);
789
891
  }
@@ -797,8 +899,8 @@ function initTranslations(projectRoot, config = {}) {
797
899
  console.log(` ${lang}: ${langFiles.map((f) => f.namespace).join(", ")}`);
798
900
  }
799
901
  }
800
- const configPath = path3.join(projectRoot, ".translationsrc.json");
801
- if (!fs2.existsSync(configPath)) {
902
+ const configPath = path4.join(projectRoot, ".translationsrc.json");
903
+ if (!fs3.existsSync(configPath)) {
802
904
  const configContent = {
803
905
  $schema: "./node_modules/poly-lexis/dist/translations/core/translations-config.schema.json",
804
906
  translationsPath: finalConfig.translationsPath,
@@ -807,7 +909,7 @@ function initTranslations(projectRoot, config = {}) {
807
909
  typesOutputPath: finalConfig.typesOutputPath,
808
910
  provider: finalConfig.provider
809
911
  };
810
- fs2.writeFileSync(configPath, `${JSON.stringify(configContent, null, 2)}
912
+ fs3.writeFileSync(configPath, `${JSON.stringify(configContent, null, 2)}
811
913
  `, "utf-8");
812
914
  console.log(`Created config file: ${configPath}`);
813
915
  }
@@ -816,8 +918,8 @@ function initTranslations(projectRoot, config = {}) {
816
918
  console.log("=====");
817
919
  }
818
920
  function loadConfig(projectRoot) {
819
- const configPath = path3.join(projectRoot, ".translationsrc.json");
820
- if (!fs2.existsSync(configPath)) {
921
+ const configPath = path4.join(projectRoot, ".translationsrc.json");
922
+ if (!fs3.existsSync(configPath)) {
821
923
  const existing = detectExistingTranslations(projectRoot);
822
924
  if (existing.path && existing.languages.length > 0) {
823
925
  console.log(`\u2139\uFE0F No config found, but detected translations at ${existing.path}`);
@@ -829,7 +931,7 @@ function loadConfig(projectRoot) {
829
931
  }
830
932
  return DEFAULT_CONFIG;
831
933
  }
832
- const configContent = fs2.readFileSync(configPath, "utf-8");
934
+ const configContent = fs3.readFileSync(configPath, "utf-8");
833
935
  const config = JSON.parse(configContent);
834
936
  if (config.languages) {
835
937
  const validation = validateLanguages(config.languages);
@@ -857,8 +959,8 @@ __export(generate_types_exports, {
857
959
  generateTranslationTypes: () => generateTranslationTypes
858
960
  });
859
961
  import { execSync } from "child_process";
860
- import * as fs3 from "fs";
861
- import * as path4 from "path";
962
+ import * as fs4 from "fs";
963
+ import * as path5 from "path";
862
964
  function extractPluralBaseKeys(keys) {
863
965
  const keySet = new Set(keys);
864
966
  const baseKeys = /* @__PURE__ */ new Set();
@@ -881,11 +983,11 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
881
983
  console.log("Generating i18n types");
882
984
  console.log("=====");
883
985
  const config = loadConfig(projectRoot);
884
- const translationsPath = path4.join(projectRoot, config.translationsPath);
986
+ const translationsPath = path5.join(projectRoot, config.translationsPath);
885
987
  const sourceLanguage = config.sourceLanguage;
886
- const outputFilePath = path4.join(projectRoot, config.typesOutputPath);
887
- const dirPath = path4.join(translationsPath, sourceLanguage);
888
- if (!fs3.existsSync(dirPath)) {
988
+ const outputFilePath = path5.join(projectRoot, config.typesOutputPath);
989
+ const dirPath = path5.join(translationsPath, sourceLanguage);
990
+ if (!fs4.existsSync(dirPath)) {
889
991
  throw new Error(`Source language directory not found: ${dirPath}`);
890
992
  }
891
993
  const namespaces = getNamespaces(translationsPath, sourceLanguage);
@@ -900,12 +1002,12 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
900
1002
  }
901
1003
  const pluralBaseKeys = extractPluralBaseKeys(allKeys);
902
1004
  allKeys = allKeys.concat(pluralBaseKeys);
903
- const outputDir = path4.dirname(outputFilePath);
904
- if (!fs3.existsSync(outputDir)) {
905
- fs3.mkdirSync(outputDir, { recursive: true });
1005
+ const outputDir = path5.dirname(outputFilePath);
1006
+ if (!fs4.existsSync(outputDir)) {
1007
+ fs4.mkdirSync(outputDir, { recursive: true });
906
1008
  }
907
1009
  const typeString = typeTemplate(allKeys, namespaces, config.languages);
908
- fs3.writeFileSync(outputFilePath, typeString, "utf8");
1010
+ fs4.writeFileSync(outputFilePath, typeString, "utf8");
909
1011
  console.log(`Generated types with ${allKeys.length} keys and ${namespaces.length} namespaces`);
910
1012
  console.log(`Output: ${outputFilePath}`);
911
1013
  try {
@@ -946,8 +1048,8 @@ __export(find_unused_exports, {
946
1048
  printUnusedKeysResult: () => printUnusedKeysResult
947
1049
  });
948
1050
  import { execSync as execSync2, spawnSync } from "child_process";
949
- import * as fs6 from "fs";
950
- import * as path10 from "path";
1051
+ import * as fs7 from "fs";
1052
+ import * as path11 from "path";
951
1053
  function isRipgrepAvailable() {
952
1054
  try {
953
1055
  execSync2("rg --version", { stdio: "ignore" });
@@ -1008,15 +1110,15 @@ function findFiles(rootDir, searchPaths, extensions) {
1008
1110
  ]);
1009
1111
  function scanDirectory(dir) {
1010
1112
  try {
1011
- const entries = fs6.readdirSync(dir, { withFileTypes: true });
1113
+ const entries = fs7.readdirSync(dir, { withFileTypes: true });
1012
1114
  for (const entry of entries) {
1013
- const fullPath = path10.join(dir, entry.name);
1115
+ const fullPath = path11.join(dir, entry.name);
1014
1116
  if (entry.isDirectory()) {
1015
1117
  if (!excludeDirs.has(entry.name)) {
1016
1118
  scanDirectory(fullPath);
1017
1119
  }
1018
1120
  } else if (entry.isFile()) {
1019
- const ext = path10.extname(entry.name);
1121
+ const ext = path11.extname(entry.name);
1020
1122
  if (extensionSet.has(ext)) {
1021
1123
  files.push(fullPath);
1022
1124
  }
@@ -1026,8 +1128,8 @@ function findFiles(rootDir, searchPaths, extensions) {
1026
1128
  }
1027
1129
  }
1028
1130
  for (const searchPath of searchPaths) {
1029
- const fullSearchPath = path10.join(rootDir, searchPath);
1030
- if (fs6.existsSync(fullSearchPath)) {
1131
+ const fullSearchPath = path11.join(rootDir, searchPath);
1132
+ if (fs7.existsSync(fullSearchPath)) {
1031
1133
  scanDirectory(fullSearchPath);
1032
1134
  }
1033
1135
  }
@@ -1035,7 +1137,7 @@ function findFiles(rootDir, searchPaths, extensions) {
1035
1137
  }
1036
1138
  function checkKeyInFile(key, filePath) {
1037
1139
  try {
1038
- const content = fs6.readFileSync(filePath, "utf-8");
1140
+ const content = fs7.readFileSync(filePath, "utf-8");
1039
1141
  const exactPatterns = [new RegExp(`['"\`]${key}['"\`]`, "g"), new RegExp(`\\b${key}\\b`, "g")];
1040
1142
  for (const pattern of exactPatterns) {
1041
1143
  if (pattern.test(content)) {
@@ -1058,7 +1160,7 @@ function checkPartialMatches(key, files) {
1058
1160
  const foundParts = /* @__PURE__ */ new Set();
1059
1161
  for (const file of filesToCheck) {
1060
1162
  try {
1061
- const content = fs6.readFileSync(file, "utf-8");
1163
+ const content = fs7.readFileSync(file, "utf-8");
1062
1164
  for (const part of significantParts) {
1063
1165
  if (foundParts.has(part)) continue;
1064
1166
  const partPattern = new RegExp(`\\b${part}\\b`, "gi");
@@ -1078,7 +1180,7 @@ function checkPartialMatches(key, files) {
1078
1180
  }
1079
1181
  function findUnusedKeys(projectRoot = process.cwd()) {
1080
1182
  const config = loadConfig(projectRoot);
1081
- const translationsPath = path10.join(projectRoot, config.translationsPath);
1183
+ const translationsPath = path11.join(projectRoot, config.translationsPath);
1082
1184
  const sourceLanguage = config.sourceLanguage;
1083
1185
  const searchPaths = config.searchPaths || ["src", "app", "pages", "components"];
1084
1186
  const searchExtensions = config.searchExtensions || [".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"];
@@ -1227,10 +1329,10 @@ __export(find_duplicates_exports, {
1227
1329
  findDuplicates: () => findDuplicates,
1228
1330
  printDuplicateKeysResult: () => printDuplicateKeysResult
1229
1331
  });
1230
- import * as path11 from "path";
1332
+ import * as path12 from "path";
1231
1333
  function findDuplicates(projectRoot = process.cwd()) {
1232
1334
  const config = loadConfig(projectRoot);
1233
- const translationsPath = path11.join(projectRoot, config.translationsPath);
1335
+ const translationsPath = path12.join(projectRoot, config.translationsPath);
1234
1336
  const sourceLanguage = config.sourceLanguage;
1235
1337
  const namespaces = getNamespaces(translationsPath, sourceLanguage);
1236
1338
  const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
@@ -1310,14 +1412,14 @@ var init_find_duplicates = __esm({
1310
1412
  // src/cli/translations.ts
1311
1413
  init_esm_shims();
1312
1414
  import "dotenv/config";
1313
- import * as fs7 from "fs";
1314
- import * as path12 from "path";
1415
+ import * as fs8 from "fs";
1416
+ import * as path13 from "path";
1315
1417
  import { parseArgs } from "util";
1316
1418
  import { confirm as confirm2, input as input2, select as select2 } from "@inquirer/prompts";
1317
1419
 
1318
1420
  // src/translations/cli/add-key.ts
1319
1421
  init_esm_shims();
1320
- import * as path5 from "path";
1422
+ import * as path6 from "path";
1321
1423
 
1322
1424
  // src/translations/utils/deepl-translate-provider.ts
1323
1425
  init_esm_shims();
@@ -1525,6 +1627,9 @@ var GoogleTranslateProvider = class {
1525
1627
  }
1526
1628
  };
1527
1629
 
1630
+ // src/translations/cli/add-key.ts
1631
+ init_metadata();
1632
+
1528
1633
  // src/translations/utils/translator.ts
1529
1634
  init_esm_shims();
1530
1635
  var defaultProvider = new GoogleTranslateProvider();
@@ -1553,7 +1658,7 @@ init_generate_types();
1553
1658
  init_init();
1554
1659
  async function addTranslationKey(projectRoot, options) {
1555
1660
  const config = loadConfig(projectRoot);
1556
- const translationsPath = path5.join(projectRoot, config.translationsPath);
1661
+ const translationsPath = path6.join(projectRoot, config.translationsPath);
1557
1662
  const { namespace, key, value, autoTranslate = false, apiKey } = options;
1558
1663
  const currentProvider = getTranslationProvider();
1559
1664
  const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
@@ -1587,6 +1692,7 @@ async function addTranslationKey(projectRoot, options) {
1587
1692
  const otherLanguages = config.languages.filter((lang) => lang !== sourceLang);
1588
1693
  if (autoTranslate && apiKey) {
1589
1694
  console.log("\nAuto-translating to other languages...");
1695
+ const metadata = readMetadata(translationsPath);
1590
1696
  for (const lang of otherLanguages) {
1591
1697
  try {
1592
1698
  const targetTranslations = readTranslations(translationsPath, lang);
@@ -1605,6 +1711,7 @@ async function addTranslationKey(projectRoot, options) {
1605
1711
  targetTranslations[namespace][key] = translated;
1606
1712
  const sorted = sortKeys(targetTranslations[namespace]);
1607
1713
  writeTranslation(translationsPath, lang, namespace, sorted);
1714
+ setSourceHash(metadata, lang, namespace, key, value);
1608
1715
  console.log(` \u2713 ${lang}: "${translated}"`);
1609
1716
  await new Promise((resolve) => setTimeout(resolve, 100));
1610
1717
  } else {
@@ -1614,6 +1721,7 @@ async function addTranslationKey(projectRoot, options) {
1614
1721
  console.error(` \u2717 ${lang}: Translation failed - ${error instanceof Error ? error.message : "Unknown error"}`);
1615
1722
  }
1616
1723
  }
1724
+ writeMetadata(translationsPath, metadata);
1617
1725
  } else {
1618
1726
  console.log("\nAdding empty values to other languages...");
1619
1727
  for (const lang of otherLanguages) {
@@ -1653,13 +1761,13 @@ init_esm_shims();
1653
1761
  init_schema();
1654
1762
  init_types();
1655
1763
  init_init();
1656
- import * as fs4 from "fs";
1657
- import * as path6 from "path";
1764
+ import * as fs5 from "fs";
1765
+ import * as path7 from "path";
1658
1766
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
1659
1767
  async function initTranslationsInteractive(projectRoot = process.cwd()) {
1660
1768
  console.log("\n\u{1F30D} Translation System Setup\n");
1661
- const configPath = path6.join(projectRoot, ".translationsrc.json");
1662
- const alreadyExists = fs4.existsSync(configPath);
1769
+ const configPath = path7.join(projectRoot, ".translationsrc.json");
1770
+ const alreadyExists = fs5.existsSync(configPath);
1663
1771
  if (alreadyExists) {
1664
1772
  console.log("\u26A0\uFE0F Configuration file already exists at .translationsrc.json\n");
1665
1773
  const shouldOverwrite = await confirm({
@@ -1828,29 +1936,33 @@ function getLanguageName(code) {
1828
1936
  // src/translations/cli/manage.ts
1829
1937
  init_esm_shims();
1830
1938
  init_utils();
1831
- import * as fs5 from "fs";
1832
- import * as path9 from "path";
1939
+ import * as fs6 from "fs";
1940
+ import * as path10 from "path";
1833
1941
 
1834
1942
  // src/translations/cli/auto-fill.ts
1835
1943
  init_esm_shims();
1836
- import * as path8 from "path";
1944
+ import * as path9 from "path";
1945
+ init_metadata();
1837
1946
  init_utils();
1838
1947
  init_init();
1839
1948
 
1840
1949
  // src/translations/cli/validate.ts
1841
1950
  init_esm_shims();
1951
+ init_metadata();
1842
1952
  init_utils();
1843
1953
  init_init();
1844
- import * as path7 from "path";
1954
+ import * as path8 from "path";
1845
1955
  function validateTranslations(projectRoot = process.cwd()) {
1846
1956
  const config = loadConfig(projectRoot);
1847
- const translationsPath = path7.join(projectRoot, config.translationsPath);
1957
+ const translationsPath = path8.join(projectRoot, config.translationsPath);
1848
1958
  const sourceLanguage = config.sourceLanguage;
1849
1959
  const missing = [];
1850
1960
  const empty = [];
1851
1961
  const orphaned = [];
1962
+ const stale = [];
1852
1963
  const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
1853
1964
  const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
1965
+ const metadata = readMetadata(translationsPath);
1854
1966
  const languages = config.languages.filter((lang) => lang !== sourceLanguage);
1855
1967
  const syncResult = syncTranslationStructure(translationsPath, config.languages, sourceLanguage);
1856
1968
  if (syncResult.createdFiles.length > 0) {
@@ -1887,6 +1999,17 @@ function validateTranslations(projectRoot = process.cwd()) {
1887
1999
  language,
1888
2000
  sourceValue
1889
2001
  });
2002
+ } else if (typeof targetValue === "string") {
2003
+ const recordedHash = getSourceHash(metadata, language, namespace, key);
2004
+ if (recordedHash !== void 0 && recordedHash !== hashSourceValue(sourceValue)) {
2005
+ stale.push({
2006
+ namespace,
2007
+ key,
2008
+ language,
2009
+ sourceValue,
2010
+ currentValue: targetValue
2011
+ });
2012
+ }
1890
2013
  }
1891
2014
  }
1892
2015
  for (const [key, targetValue] of Object.entries(targetKeys)) {
@@ -1902,7 +2025,7 @@ function validateTranslations(projectRoot = process.cwd()) {
1902
2025
  }
1903
2026
  }
1904
2027
  const valid = !missing.length && !empty.length && !orphaned.length;
1905
- if (valid) {
2028
+ if (valid && !stale.length) {
1906
2029
  console.log("\u2713 All translations are valid!");
1907
2030
  } else {
1908
2031
  if (missing.length > 0) {
@@ -1935,18 +2058,73 @@ function validateTranslations(projectRoot = process.cwd()) {
1935
2058
  console.log(` ... and ${orphaned.length - 10} more`);
1936
2059
  }
1937
2060
  }
2061
+ if (stale.length > 0) {
2062
+ console.log(
2063
+ `
2064
+ \u26A0 Found ${stale.length} potentially outdated translations (source value changed since translation):`
2065
+ );
2066
+ for (const item of stale.slice(0, 10)) {
2067
+ console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);
2068
+ }
2069
+ if (stale.length > 10) {
2070
+ console.log(` ... and ${stale.length - 10} more`);
2071
+ }
2072
+ console.log(" Run auto-fill with --retranslate-changed to update them.");
2073
+ }
1938
2074
  }
1939
2075
  console.log("=====");
1940
- return { valid, missing, empty, orphaned };
2076
+ return { valid, missing, empty, orphaned, stale };
1941
2077
  }
1942
- function getMissingForLanguage(projectRoot, language) {
2078
+ function getMissingForLanguage(projectRoot, language, options = {}) {
1943
2079
  const result = validateTranslations(projectRoot);
1944
2080
  const items = [
1945
2081
  ...result.missing.filter((m) => m.language === language).map((m) => ({ ...m, type: "missing" })),
1946
2082
  ...result.empty.filter((e) => e.language === language).map((e) => ({ ...e, type: "empty" }))
1947
2083
  ];
2084
+ if (options.includeStale) {
2085
+ items.push(
2086
+ ...result.stale.filter((s) => s.language === language).map((s) => ({
2087
+ namespace: s.namespace,
2088
+ key: s.key,
2089
+ language: s.language,
2090
+ sourceValue: s.sourceValue,
2091
+ type: "stale"
2092
+ }))
2093
+ );
2094
+ }
1948
2095
  return items;
1949
2096
  }
2097
+ function ensureBaselineMetadata(projectRoot = process.cwd()) {
2098
+ const config = loadConfig(projectRoot);
2099
+ const translationsPath = path8.join(projectRoot, config.translationsPath);
2100
+ const sourceLanguage = config.sourceLanguage;
2101
+ const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
2102
+ const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
2103
+ const languages = config.languages.filter((lang) => lang !== sourceLanguage);
2104
+ const existedBefore = metadataExists(translationsPath);
2105
+ const metadata = readMetadata(translationsPath);
2106
+ let recorded = 0;
2107
+ for (const language of languages) {
2108
+ const targetTranslations = readTranslations(translationsPath, language);
2109
+ for (const namespace of sourceNamespaces) {
2110
+ const sourceKeys = sourceTranslations[namespace] || {};
2111
+ const targetKeys = targetTranslations[namespace] || {};
2112
+ for (const [key, sourceValue] of Object.entries(sourceKeys)) {
2113
+ const targetValue = targetKeys[key];
2114
+ const alreadyTranslated = typeof targetValue === "string" && targetValue.trim() !== "";
2115
+ const alreadyTracked = getSourceHash(metadata, language, namespace, key) !== void 0;
2116
+ if (alreadyTranslated && !alreadyTracked) {
2117
+ setSourceHash(metadata, language, namespace, key, sourceValue);
2118
+ recorded++;
2119
+ }
2120
+ }
2121
+ }
2122
+ }
2123
+ if (recorded > 0) {
2124
+ writeMetadata(translationsPath, metadata);
2125
+ }
2126
+ return { recorded, created: !existedBefore && recorded > 0 };
2127
+ }
1950
2128
 
1951
2129
  // src/translations/cli/auto-fill.ts
1952
2130
  async function processConcurrently(items, concurrency, processor) {
@@ -1969,10 +2147,31 @@ async function processConcurrently(items, concurrency, processor) {
1969
2147
  await Promise.all(executing);
1970
2148
  return results;
1971
2149
  }
2150
+ function getFillItemsForLanguage(projectRoot, translationsPath, sourceLanguage, language, options) {
2151
+ if (options.force) {
2152
+ const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
2153
+ const items = [];
2154
+ for (const [namespace, keys] of Object.entries(sourceTranslations)) {
2155
+ for (const [key, sourceValue] of Object.entries(keys)) {
2156
+ items.push({ namespace, key, language, sourceValue, type: "forced" });
2157
+ }
2158
+ }
2159
+ return items;
2160
+ }
2161
+ return getMissingForLanguage(projectRoot, language, { includeStale: options.retranslateChanged });
2162
+ }
1972
2163
  async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
1973
2164
  const config = loadConfig(projectRoot);
1974
- const translationsPath = path8.join(projectRoot, config.translationsPath);
1975
- const { apiKey, limit = Infinity, delayMs = 50, dryRun = false, concurrency = 5 } = options;
2165
+ const translationsPath = path9.join(projectRoot, config.translationsPath);
2166
+ const {
2167
+ apiKey,
2168
+ limit = Infinity,
2169
+ delayMs = 50,
2170
+ dryRun = false,
2171
+ concurrency = 5,
2172
+ retranslateChanged = false,
2173
+ force = false
2174
+ } = options;
1976
2175
  const currentProvider = getTranslationProvider();
1977
2176
  const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
1978
2177
  if (isDefaultGoogleProvider) {
@@ -1995,14 +2194,17 @@ async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
1995
2194
  console.log(`Created ${syncResult.createdFiles.length} namespace files
1996
2195
  `);
1997
2196
  }
2197
+ const mode = force ? "all keys (--force)" : retranslateChanged ? "missing, empty & changed" : "missing & empty";
1998
2198
  console.log("=====");
1999
2199
  console.log("Auto-filling translations");
2000
2200
  console.log("=====");
2001
2201
  console.log(`Languages: ${languagesToProcess.join(", ")}`);
2202
+ console.log(`Mode: ${mode}`);
2002
2203
  console.log(`Limit: ${limit === Infinity ? "unlimited" : limit}`);
2003
2204
  console.log(`Concurrency: ${concurrency}`);
2004
2205
  console.log(`Dry run: ${dryRun}`);
2005
2206
  console.log("=====");
2207
+ const metadata = readMetadata(translationsPath);
2006
2208
  let totalProcessed = 0;
2007
2209
  let totalTranslated = 0;
2008
2210
  for (const language of languagesToProcess) {
@@ -2013,9 +2215,12 @@ Reached limit of ${limit} translations`);
2013
2215
  }
2014
2216
  console.log(`
2015
2217
  Processing language: ${language}`);
2016
- const missing = getMissingForLanguage(projectRoot, language);
2218
+ const missing = getFillItemsForLanguage(projectRoot, translationsPath, config.sourceLanguage, language, {
2219
+ retranslateChanged,
2220
+ force
2221
+ });
2017
2222
  if (!missing.length) {
2018
- console.log(" No missing or empty translations");
2223
+ console.log(" Nothing to translate");
2019
2224
  continue;
2020
2225
  }
2021
2226
  console.log(` Found ${missing.length} translations to fill`);
@@ -2044,6 +2249,7 @@ Processing language: ${language}`);
2044
2249
  translations[item.namespace][item.key] = translated;
2045
2250
  const sorted = sortKeys(translations[item.namespace]);
2046
2251
  writeTranslation(translationsPath, language, item.namespace, sorted);
2252
+ setSourceHash(metadata, language, item.namespace, item.key, item.sourceValue);
2047
2253
  console.log(" \u2713 Saved");
2048
2254
  } else {
2049
2255
  console.log(" \u2713 Dry run - not saved");
@@ -2059,6 +2265,9 @@ Processing language: ${language}`);
2059
2265
  });
2060
2266
  totalProcessed += itemsToProcess.length;
2061
2267
  totalTranslated += results.filter((r) => r.success).length;
2268
+ if (!dryRun) {
2269
+ writeMetadata(translationsPath, metadata);
2270
+ }
2062
2271
  }
2063
2272
  console.log("\n=====");
2064
2273
  console.log(`Total processed: ${totalProcessed}`);
@@ -2073,12 +2282,22 @@ Processing language: ${language}`);
2073
2282
  init_generate_types();
2074
2283
  init_init();
2075
2284
  async function manageTranslations(projectRoot = process.cwd(), options = {}) {
2076
- const { autoFill = false, apiKey, limit, concurrency = 5, language, skipTypes = false, dryRun = false } = options;
2285
+ const {
2286
+ autoFill = false,
2287
+ apiKey,
2288
+ limit,
2289
+ concurrency = 5,
2290
+ language,
2291
+ skipTypes = false,
2292
+ dryRun = false,
2293
+ retranslateChanged = false,
2294
+ force = false
2295
+ } = options;
2077
2296
  console.log("=====");
2078
2297
  console.log("Translation Management");
2079
2298
  console.log("=====");
2080
- const configPath = path9.join(projectRoot, ".translationsrc.json");
2081
- const isInitialized = fs5.existsSync(configPath);
2299
+ const configPath = path10.join(projectRoot, ".translationsrc.json");
2300
+ const isInitialized = fs6.existsSync(configPath);
2082
2301
  if (!isInitialized) {
2083
2302
  console.log("\u{1F4C1} No translation configuration found. Initializing...\n");
2084
2303
  initTranslations(projectRoot);
@@ -2087,9 +2306,9 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
2087
2306
  console.log("\u2713 Translation structure initialized\n");
2088
2307
  }
2089
2308
  const config = loadConfig(projectRoot);
2090
- const translationsPath = path9.join(projectRoot, config.translationsPath);
2091
- const sourceLangPath = path9.join(translationsPath, config.sourceLanguage);
2092
- if (!fs5.existsSync(sourceLangPath)) {
2309
+ const translationsPath = path10.join(projectRoot, config.translationsPath);
2310
+ const sourceLangPath = path10.join(translationsPath, config.sourceLanguage);
2311
+ if (!fs6.existsSync(sourceLangPath)) {
2093
2312
  console.log(`\u26A0\uFE0F Source language directory not found: ${sourceLangPath}`);
2094
2313
  console.log("Please add translation files to the source language directory.\n");
2095
2314
  return false;
@@ -2111,11 +2330,25 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
2111
2330
  if (syncResult.createdFiles.length === 0 && syncResult.cleanedKeys.length === 0 && syncResult.removedNamespaces.length === 0) {
2112
2331
  console.log("\u2713 Translation structure is already synchronized\n");
2113
2332
  }
2333
+ if (!dryRun) {
2334
+ const baseline = ensureBaselineMetadata(projectRoot);
2335
+ if (baseline.recorded > 0) {
2336
+ const verb = baseline.created ? "Created change-tracking baseline for" : "Added change tracking for";
2337
+ console.log(`\u{1F4CC} ${verb} ${baseline.recorded} existing translations
2338
+ `);
2339
+ }
2340
+ }
2114
2341
  console.log("\u{1F50D} Validating translations...\n");
2115
2342
  const validationResult = validateTranslations(projectRoot);
2116
- if (validationResult.valid) {
2343
+ const hasWorkToFill = !validationResult.valid || force || retranslateChanged && validationResult.stale.length > 0;
2344
+ if (validationResult.valid && !validationResult.stale.length) {
2117
2345
  console.log("\n\u2705 All translations are complete!\n");
2118
- } else {
2346
+ } else if (validationResult.valid) {
2347
+ console.log(`
2348
+ \u26A0\uFE0F ${validationResult.stale.length} translations may be outdated.
2349
+ `);
2350
+ }
2351
+ if (hasWorkToFill) {
2119
2352
  const totalMissing = validationResult.missing.length + validationResult.empty.length + validationResult.orphaned.length;
2120
2353
  if (autoFill) {
2121
2354
  if (!apiKey) {
@@ -2125,29 +2358,38 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
2125
2358
  console.log(`Set ${envVarName} or pass --api-key to enable auto-fill.
2126
2359
  `);
2127
2360
  } else {
2128
- console.log(`
2129
- \u{1F916} Auto-filling ${totalMissing} missing translations...
2361
+ if (force) {
2362
+ console.log("\n\u{1F916} Force re-translating all keys...\n");
2363
+ } else {
2364
+ const staleCount = retranslateChanged ? validationResult.stale.length : 0;
2365
+ console.log(`
2366
+ \u{1F916} Auto-filling ${totalMissing} missing and ${staleCount} changed translations...
2130
2367
  `);
2368
+ }
2131
2369
  await autoFillTranslations(projectRoot, {
2132
2370
  apiKey,
2133
2371
  limit,
2134
2372
  concurrency,
2135
2373
  language,
2136
2374
  dryRun,
2137
- delayMs: 50
2375
+ delayMs: 50,
2376
+ retranslateChanged,
2377
+ force
2138
2378
  });
2139
2379
  if (!dryRun) {
2140
2380
  console.log("\n\u{1F50D} Re-validating after auto-fill...\n");
2141
2381
  const revalidation = validateTranslations(projectRoot);
2142
- if (revalidation.valid) {
2382
+ if (revalidation.valid && !revalidation.stale.length) {
2143
2383
  console.log("\n\u2705 All translations are now complete!\n");
2144
2384
  }
2145
2385
  }
2146
2386
  }
2147
- } else {
2387
+ } else if (!validationResult.valid) {
2148
2388
  console.log(`
2149
2389
  \u{1F4A1} Tip: Run with --auto-fill to automatically translate missing keys.
2150
2390
  `);
2391
+ } else if (validationResult.stale.length > 0) {
2392
+ console.log("\n\u{1F4A1} Tip: Run with --auto-fill --retranslate-changed to update outdated translations.\n");
2151
2393
  }
2152
2394
  }
2153
2395
  if (!skipTypes && !dryRun) {
@@ -2173,9 +2415,17 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
2173
2415
  if (validationResult.orphaned.length > 0) {
2174
2416
  console.log(`\u26A0\uFE0F ${validationResult.orphaned.length} orphaned translations (will be auto-removed on next sync)`);
2175
2417
  }
2418
+ if (validationResult.stale.length > 0) {
2419
+ console.log(`\u26A0\uFE0F ${validationResult.stale.length} potentially outdated translations`);
2420
+ }
2176
2421
  console.log("\nNext steps:");
2177
2422
  console.log(" 1. Add missing translations manually, or");
2178
2423
  console.log(" 2. Run with --auto-fill to translate automatically");
2424
+ } else if (validationResult.valid && validationResult.stale.length > 0 && !autoFill) {
2425
+ console.log(`
2426
+ \u26A0\uFE0F ${validationResult.stale.length} potentially outdated translations`);
2427
+ console.log("\nNext steps:");
2428
+ console.log(" Run with --auto-fill --retranslate-changed to update them");
2179
2429
  } else if (validationResult.valid) {
2180
2430
  console.log("\n\u2705 All systems ready!");
2181
2431
  }
@@ -2219,6 +2469,14 @@ var { values, positionals } = parseArgs({
2219
2469
  short: "d",
2220
2470
  default: false
2221
2471
  },
2472
+ "retranslate-changed": {
2473
+ type: "boolean",
2474
+ default: false
2475
+ },
2476
+ force: {
2477
+ type: "boolean",
2478
+ default: false
2479
+ },
2222
2480
  namespace: {
2223
2481
  type: "string",
2224
2482
  short: "n"
@@ -2257,6 +2515,8 @@ Options (Smart Mode):
2257
2515
  -l, --language <lang> Process only this language
2258
2516
  --limit <number> Max translations to process (default: unlimited)
2259
2517
  --concurrency <number> Number of concurrent translation requests (default: 5)
2518
+ --retranslate-changed Re-translate keys whose source value has changed since last run
2519
+ --force Re-translate every key, even ones already translated
2260
2520
  --skip-types Skip TypeScript type generation
2261
2521
  -d, --dry-run Preview changes without saving
2262
2522
  -h, --help Show this help
@@ -2287,6 +2547,12 @@ Examples:
2287
2547
  # Preview what would be translated (dry-run)
2288
2548
  translations --auto-fill --dry-run
2289
2549
 
2550
+ # Re-translate keys whose source string changed since last run
2551
+ translations --auto-fill --retranslate-changed
2552
+
2553
+ # Re-translate everything from scratch
2554
+ translations --auto-fill --force
2555
+
2290
2556
  # Add a new translation key (interactive mode)
2291
2557
  translations add
2292
2558
 
@@ -2341,8 +2607,8 @@ if (command === "find-unused") {
2341
2607
  (async () => {
2342
2608
  try {
2343
2609
  console.log("\n\u2728 Add a new translation key\n");
2344
- const configPath = path12.join(process.cwd(), ".translationsrc.json");
2345
- const isInitialized = fs7.existsSync(configPath);
2610
+ const configPath = path13.join(process.cwd(), ".translationsrc.json");
2611
+ const isInitialized = fs8.existsSync(configPath);
2346
2612
  if (!isInitialized) {
2347
2613
  console.log("\u26A0\uFE0F Translation structure not initialized.");
2348
2614
  const shouldInit = await confirm2({
@@ -2358,7 +2624,7 @@ if (command === "find-unused") {
2358
2624
  }
2359
2625
  }
2360
2626
  const config = loadConfig(process.cwd());
2361
- const translationsPath = path12.join(process.cwd(), config.translationsPath);
2627
+ const translationsPath = path13.join(process.cwd(), config.translationsPath);
2362
2628
  const existingNamespaces = getNamespaces(translationsPath, config.sourceLanguage);
2363
2629
  let namespace;
2364
2630
  if (existingNamespaces.length > 0) {
@@ -2479,23 +2745,26 @@ if (command === "find-unused") {
2479
2745
  console.log("Use --help for usage information");
2480
2746
  process.exit(1);
2481
2747
  } else {
2482
- const hasFlags = values["auto-fill"] || values.language || values["skip-types"] || values["dry-run"];
2748
+ const hasFlags = values["auto-fill"] || values.language || values["skip-types"] || values["dry-run"] || values["retranslate-changed"] || values.force;
2483
2749
  if (hasFlags) {
2484
- const configPath = path12.join(process.cwd(), ".translationsrc.json");
2485
- const config = fs7.existsSync(configPath) ? loadConfig(process.cwd()) : { provider: "deepl" };
2750
+ const configPath = path13.join(process.cwd(), ".translationsrc.json");
2751
+ const config = fs8.existsSync(configPath) ? loadConfig(process.cwd()) : { provider: "deepl" };
2486
2752
  const provider = config.provider || "deepl";
2487
2753
  const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
2488
2754
  const apiKey = values["api-key"] || process.env[envVarName];
2489
2755
  const limit = values.limit ? Number.parseInt(values.limit, 10) : void 0;
2490
2756
  const concurrency = Number.parseInt(values.concurrency || "5", 10);
2757
+ const autoFill = values["auto-fill"] || values["retranslate-changed"] || values.force;
2491
2758
  manageTranslations(process.cwd(), {
2492
- autoFill: values["auto-fill"],
2759
+ autoFill,
2493
2760
  apiKey,
2494
2761
  limit,
2495
2762
  concurrency,
2496
2763
  language: values.language,
2497
2764
  skipTypes: values["skip-types"],
2498
- dryRun: values["dry-run"]
2765
+ dryRun: values["dry-run"],
2766
+ retranslateChanged: values["retranslate-changed"],
2767
+ force: values.force
2499
2768
  }).then((isValid) => {
2500
2769
  if (!isValid) {
2501
2770
  process.exit(1);
@@ -2507,8 +2776,8 @@ if (command === "find-unused") {
2507
2776
  } else {
2508
2777
  (async () => {
2509
2778
  try {
2510
- const configPath = path12.join(process.cwd(), ".translationsrc.json");
2511
- const isInitialized = fs7.existsSync(configPath);
2779
+ const configPath = path13.join(process.cwd(), ".translationsrc.json");
2780
+ const isInitialized = fs8.existsSync(configPath);
2512
2781
  console.log("\n\u{1F30D} Translation Management\n");
2513
2782
  const action = await select2({
2514
2783
  message: "What would you like to do?",
@@ -2572,7 +2841,7 @@ if (command === "find-unused") {
2572
2841
  }
2573
2842
  }
2574
2843
  const config = loadConfig(process.cwd());
2575
- const translationsPath = path12.join(process.cwd(), config.translationsPath);
2844
+ const translationsPath = path13.join(process.cwd(), config.translationsPath);
2576
2845
  const existingNamespaces = getNamespaces(translationsPath, config.sourceLanguage);
2577
2846
  let namespace;
2578
2847
  if (existingNamespaces.length > 0) {
@@ -2673,9 +2942,14 @@ if (command === "find-unused") {
2673
2942
  default: true
2674
2943
  });
2675
2944
  if (shouldContinue) {
2945
+ const retranslateChanged = await confirm2({
2946
+ message: "Also re-translate keys whose source value has changed?",
2947
+ default: false
2948
+ });
2676
2949
  await manageTranslations(process.cwd(), {
2677
2950
  autoFill: true,
2678
- apiKey
2951
+ apiKey,
2952
+ retranslateChanged
2679
2953
  });
2680
2954
  }
2681
2955
  } else if (action === "find-unused") {