@el-j/google-sheet-translations 2.0.0 → 2.1.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.
@@ -1 +1 @@
1
- {"version":3,"file":"action-entrypoint.d.ts","sourceRoot":"","sources":["../src/action-entrypoint.ts"],"names":[],"mappings":"AAKA,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA4DzC"}
1
+ {"version":3,"file":"action-entrypoint.d.ts","sourceRoot":"","sources":["../src/action-entrypoint.ts"],"names":[],"mappings":"AAKA,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA+DzC"}
@@ -3,4 +3,9 @@
3
3
  */
4
4
  /** Default wait time between Google Sheets API calls (in seconds) */
5
5
  export declare const DEFAULT_WAIT_SECONDS = 1;
6
+ /**
7
+ * Name of the reserved i18n metadata sheet.
8
+ * This sheet stores locale display names and must never receive translation key pushes.
9
+ */
10
+ export declare const I18N_SHEET_NAME = "i18n";
6
11
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,IAAI,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,eAAe,SAAS,CAAC"}
package/dist/esm/index.js CHANGED
@@ -59,6 +59,7 @@ import path from "node:path";
59
59
 
60
60
  // src/constants.ts
61
61
  var DEFAULT_WAIT_SECONDS = 1;
62
+ var I18N_SHEET_NAME = "i18n";
62
63
 
63
64
  // src/utils/configurationHandler.ts
64
65
  function normalizeConfig(options = {}) {
@@ -79,7 +80,11 @@ function normalizeConfig(options = {}) {
79
80
  // Default to true
80
81
  spreadsheetTitle: options.spreadsheetTitle ?? "google-sheet-translations",
81
82
  sourceLocale: options.sourceLocale ?? "en",
82
- targetLocales: options.targetLocales ?? ["de", "fr", "es", "it", "pt", "ja", "zh"]
83
+ targetLocales: options.targetLocales ?? ["de", "fr", "es", "it", "pt", "ja", "zh"],
84
+ override: options.override === true,
85
+ // Default to false
86
+ cleanPush: options.cleanPush === true
87
+ // Default to false
83
88
  };
84
89
  }
85
90
 
@@ -504,6 +509,7 @@ function findLocalChanges(localData, spreadsheetData) {
504
509
  const resolvedLocale = resolveLocaleWithFallback(locale, Object.keys(spreadsheetData));
505
510
  for (const sheet of Object.keys(localData[locale])) {
506
511
  if (!localData[locale][sheet]) continue;
512
+ if (sheet === I18N_SHEET_NAME) continue;
507
513
  for (const key of Object.keys(localData[locale][sheet])) {
508
514
  const isNewKey = !resolvedLocale || !spreadsheetData[resolvedLocale]?.[sheet] || !spreadsheetData[resolvedLocale][sheet][key];
509
515
  if (isNewKey) {
@@ -527,12 +533,16 @@ function columnIndexToLetter(index) {
527
533
  } while (i >= 0);
528
534
  return result;
529
535
  }
530
- async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate = false, localeMapping = {}) {
536
+ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate = false, localeMapping = {}, override = false) {
531
537
  console.log("Updating spreadsheet with local changes...");
532
538
  const baseDelayMs = waitSeconds * 1e3;
533
539
  for (const sheetTitle of new Set(
534
540
  Object.values(changes).flatMap((locale) => Object.keys(locale))
535
541
  )) {
542
+ if (sheetTitle === I18N_SHEET_NAME) {
543
+ console.log(`Skipping reserved metadata sheet "${sheetTitle}" \u2013 its content is managed separately.`);
544
+ continue;
545
+ }
536
546
  console.log(`Processing sheet: ${sheetTitle}`);
537
547
  let sheet = doc.sheetsByTitle[sheetTitle];
538
548
  if (!sheet) {
@@ -583,6 +593,27 @@ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, auto
583
593
  });
584
594
  const newKeys = /* @__PURE__ */ new Map();
585
595
  const keyLocalesMap = /* @__PURE__ */ new Map();
596
+ const pushedLocaleHeadersPerKey = /* @__PURE__ */ new Map();
597
+ if (autoTranslate) {
598
+ for (const pushedLocale of Object.keys(changes)) {
599
+ if (!changes[pushedLocale]?.[sheetTitle]) continue;
600
+ for (const pushedKey of Object.keys(changes[pushedLocale][sheetTitle])) {
601
+ const pushedKeyLower = pushedKey.toLowerCase();
602
+ if (!existingKeys.has(pushedKeyLower)) continue;
603
+ let pushedHeader = getOriginalHeaderForLocale(pushedLocale, localeMapping);
604
+ if (!pushedHeader) {
605
+ const prefix = getLanguagePrefix(pushedLocale);
606
+ pushedHeader = originalHeaders.find((h) => getLanguagePrefix(h) === prefix);
607
+ }
608
+ if (pushedHeader) {
609
+ if (!pushedLocaleHeadersPerKey.has(pushedKeyLower)) {
610
+ pushedLocaleHeadersPerKey.set(pushedKeyLower, /* @__PURE__ */ new Set());
611
+ }
612
+ pushedLocaleHeadersPerKey.get(pushedKeyLower).add(pushedHeader.toLowerCase());
613
+ }
614
+ }
615
+ }
616
+ }
586
617
  for (const locale of Object.keys(changes)) {
587
618
  if (!changes[locale]?.[sheetTitle]) continue;
588
619
  const localeData = changes[locale][sheetTitle];
@@ -625,6 +656,35 @@ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, auto
625
656
  }
626
657
  if (localeHeader) {
627
658
  row.set(localeHeader, String(localeData[key]));
659
+ if (autoTranslate) {
660
+ const sourceHeaderLower = localeHeader.toLowerCase();
661
+ const sourceHeaderIndex = headerRow.indexOf(sourceHeaderLower);
662
+ if (sourceHeaderIndex >= 0) {
663
+ const sourceColumnLetter = columnIndexToLetter(sourceHeaderIndex);
664
+ const rowObj = row.toObject();
665
+ const pushedHeaders = pushedLocaleHeadersPerKey.get(keyLower) ?? /* @__PURE__ */ new Set();
666
+ for (const targetLocaleHeader of locales) {
667
+ const targetLower = targetLocaleHeader.toLowerCase();
668
+ if (targetLower === sourceHeaderLower) continue;
669
+ if (pushedHeaders.has(targetLower)) continue;
670
+ const targetHeaderIndex = headerRow.indexOf(targetLower);
671
+ if (targetHeaderIndex < 0) continue;
672
+ const exactTargetHeader = originalHeaders.find(
673
+ (h) => h.toLowerCase() === targetLower
674
+ );
675
+ if (!exactTargetHeader) continue;
676
+ const existingValue = rowObj[exactTargetHeader];
677
+ const isEmpty = !existingValue || existingValue.toString().trim() === "";
678
+ if (isEmpty || override) {
679
+ const targetColumnLetter = columnIndexToLetter(targetHeaderIndex);
680
+ row.set(
681
+ exactTargetHeader,
682
+ `=GOOGLETRANSLATE(INDIRECT("${sourceColumnLetter}"&ROW());$${sourceColumnLetter}$1;${targetColumnLetter}$1)`
683
+ );
684
+ }
685
+ }
686
+ }
687
+ }
628
688
  try {
629
689
  await withRetry(
630
690
  () => row.save(),
@@ -654,12 +714,12 @@ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, auto
654
714
  if (localesWithValues.has(localeLower) || rowDataKey && rowData[rowDataKey]) {
655
715
  continue;
656
716
  }
657
- const exactHeaderName = headerRow.find(
717
+ const exactHeaderName = originalHeaders.find(
658
718
  (h) => h.toLowerCase() === localeLower
659
719
  );
660
720
  if (exactHeaderName) {
661
721
  const sourceHeaderIndex = headerRow.indexOf(sourceHeader.toLowerCase());
662
- const targetHeaderIndex = headerRow.indexOf(exactHeaderName);
722
+ const targetHeaderIndex = headerRow.indexOf(exactHeaderName.toLowerCase());
663
723
  if (sourceHeaderIndex < 0 || targetHeaderIndex < 0) {
664
724
  continue;
665
725
  }
@@ -773,19 +833,23 @@ function readDataJson(dataJsonPath) {
773
833
  }
774
834
 
775
835
  // src/utils/syncManager.ts
776
- async function handleBidirectionalSync(doc, dataJsonPath, translationsOutputDir, syncLocalChanges, autoTranslate, spreadsheetData, waitSeconds, localeMapping = {}) {
836
+ async function handleBidirectionalSync(doc, dataJsonPath, translationsOutputDir, syncLocalChanges, autoTranslate, spreadsheetData, waitSeconds, localeMapping = {}, override = false, cleanPush = false) {
777
837
  const result = {
778
838
  shouldRefresh: false,
779
839
  hasChanges: false
780
840
  };
781
841
  const localData = readDataJson(dataJsonPath);
782
842
  const dataJsonExists = localData !== null;
783
- const shouldSyncToSheet = syncLocalChanges && dataJsonExists && isDataJsonNewer(dataJsonPath, translationsOutputDir);
843
+ const shouldSyncToSheet = dataJsonExists && (cleanPush || syncLocalChanges && isDataJsonNewer(dataJsonPath, translationsOutputDir));
784
844
  if (!shouldSyncToSheet || !localData) {
785
845
  return result;
786
846
  }
787
- console.log("Local languageData.json is newer than translation files. Checking for changes...");
788
- const changes = findLocalChanges(localData, spreadsheetData);
847
+ if (cleanPush) {
848
+ console.log("Clean push enabled \u2013 pushing ALL keys from languageData.json to the spreadsheet...");
849
+ } else {
850
+ console.log("Local languageData.json is newer than translation files. Checking for changes...");
851
+ }
852
+ const changes = cleanPush ? localData : findLocalChanges(localData, spreadsheetData);
789
853
  const hasChanges = Object.keys(changes).length > 0 && Object.keys(changes).some(
790
854
  (locale) => Object.keys(changes[locale]).length > 0
791
855
  );
@@ -795,9 +859,10 @@ async function handleBidirectionalSync(doc, dataJsonPath, translationsOutputDir,
795
859
  }
796
860
  const localesCount = Object.keys(changes).length;
797
861
  const keysCount = Object.values(changes).flatMap((l) => Object.values(l)).flatMap((s) => Object.keys(s)).length;
798
- console.log(`Found local changes: ${localesCount} locale(s), ~${keysCount} key(s) to sync to the spreadsheet.`);
862
+ const pushMode = cleanPush ? "clean push" : "incremental sync";
863
+ console.log(`Found local changes (${pushMode}): ${localesCount} locale(s), ~${keysCount} key(s) to sync to the spreadsheet.`);
799
864
  try {
800
- await updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate, localeMapping);
865
+ await updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate, localeMapping, override);
801
866
  result.shouldRefresh = true;
802
867
  result.hasChanges = true;
803
868
  } catch (err) {
@@ -1126,7 +1191,9 @@ async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
1126
1191
  config.autoTranslate,
1127
1192
  finalTranslations,
1128
1193
  config.waitSeconds,
1129
- globalLocaleMapping
1194
+ globalLocaleMapping,
1195
+ config.override,
1196
+ config.cleanPush
1130
1197
  );
1131
1198
  if (syncResult.shouldRefresh && _refreshDepth < MAX_SYNC_REFRESH_DEPTH) {
1132
1199
  return getSpreadSheetData(
@@ -1 +1 @@
1
- {"version":3,"file":"getSpreadSheetData.d.ts","sourceRoot":"","sources":["../src/getSpreadSheetData.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAOxF,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAsChC,wBAAsB,kBAAkB,CACvC,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,OAAO,GAAE,kBAAuB,EAChC,aAAa,SAAI,GACf,OAAO,CAAC,eAAe,CAAC,CAyJ1B;AAED,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"getSpreadSheetData.d.ts","sourceRoot":"","sources":["../src/getSpreadSheetData.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAOxF,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,CAAC;AAsChC,wBAAsB,kBAAkB,CACvC,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,OAAO,GAAE,kBAAuB,EAChC,aAAa,SAAI,GACf,OAAO,CAAC,eAAe,CAAC,CA2J1B;AAED,eAAe,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -124,6 +124,7 @@ var import_node_path = __toESM(require("node:path"));
124
124
 
125
125
  // src/constants.ts
126
126
  var DEFAULT_WAIT_SECONDS = 1;
127
+ var I18N_SHEET_NAME = "i18n";
127
128
 
128
129
  // src/utils/configurationHandler.ts
129
130
  function normalizeConfig(options = {}) {
@@ -144,7 +145,11 @@ function normalizeConfig(options = {}) {
144
145
  // Default to true
145
146
  spreadsheetTitle: options.spreadsheetTitle ?? "google-sheet-translations",
146
147
  sourceLocale: options.sourceLocale ?? "en",
147
- targetLocales: options.targetLocales ?? ["de", "fr", "es", "it", "pt", "ja", "zh"]
148
+ targetLocales: options.targetLocales ?? ["de", "fr", "es", "it", "pt", "ja", "zh"],
149
+ override: options.override === true,
150
+ // Default to false
151
+ cleanPush: options.cleanPush === true
152
+ // Default to false
148
153
  };
149
154
  }
150
155
 
@@ -569,6 +574,7 @@ function findLocalChanges(localData, spreadsheetData) {
569
574
  const resolvedLocale = resolveLocaleWithFallback(locale, Object.keys(spreadsheetData));
570
575
  for (const sheet of Object.keys(localData[locale])) {
571
576
  if (!localData[locale][sheet]) continue;
577
+ if (sheet === I18N_SHEET_NAME) continue;
572
578
  for (const key of Object.keys(localData[locale][sheet])) {
573
579
  const isNewKey = !resolvedLocale || !spreadsheetData[resolvedLocale]?.[sheet] || !spreadsheetData[resolvedLocale][sheet][key];
574
580
  if (isNewKey) {
@@ -592,12 +598,16 @@ function columnIndexToLetter(index) {
592
598
  } while (i >= 0);
593
599
  return result;
594
600
  }
595
- async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate = false, localeMapping = {}) {
601
+ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate = false, localeMapping = {}, override = false) {
596
602
  console.log("Updating spreadsheet with local changes...");
597
603
  const baseDelayMs = waitSeconds * 1e3;
598
604
  for (const sheetTitle of new Set(
599
605
  Object.values(changes).flatMap((locale) => Object.keys(locale))
600
606
  )) {
607
+ if (sheetTitle === I18N_SHEET_NAME) {
608
+ console.log(`Skipping reserved metadata sheet "${sheetTitle}" \u2013 its content is managed separately.`);
609
+ continue;
610
+ }
601
611
  console.log(`Processing sheet: ${sheetTitle}`);
602
612
  let sheet = doc.sheetsByTitle[sheetTitle];
603
613
  if (!sheet) {
@@ -648,6 +658,27 @@ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, auto
648
658
  });
649
659
  const newKeys = /* @__PURE__ */ new Map();
650
660
  const keyLocalesMap = /* @__PURE__ */ new Map();
661
+ const pushedLocaleHeadersPerKey = /* @__PURE__ */ new Map();
662
+ if (autoTranslate) {
663
+ for (const pushedLocale of Object.keys(changes)) {
664
+ if (!changes[pushedLocale]?.[sheetTitle]) continue;
665
+ for (const pushedKey of Object.keys(changes[pushedLocale][sheetTitle])) {
666
+ const pushedKeyLower = pushedKey.toLowerCase();
667
+ if (!existingKeys.has(pushedKeyLower)) continue;
668
+ let pushedHeader = getOriginalHeaderForLocale(pushedLocale, localeMapping);
669
+ if (!pushedHeader) {
670
+ const prefix = getLanguagePrefix(pushedLocale);
671
+ pushedHeader = originalHeaders.find((h) => getLanguagePrefix(h) === prefix);
672
+ }
673
+ if (pushedHeader) {
674
+ if (!pushedLocaleHeadersPerKey.has(pushedKeyLower)) {
675
+ pushedLocaleHeadersPerKey.set(pushedKeyLower, /* @__PURE__ */ new Set());
676
+ }
677
+ pushedLocaleHeadersPerKey.get(pushedKeyLower).add(pushedHeader.toLowerCase());
678
+ }
679
+ }
680
+ }
681
+ }
651
682
  for (const locale of Object.keys(changes)) {
652
683
  if (!changes[locale]?.[sheetTitle]) continue;
653
684
  const localeData = changes[locale][sheetTitle];
@@ -690,6 +721,35 @@ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, auto
690
721
  }
691
722
  if (localeHeader) {
692
723
  row.set(localeHeader, String(localeData[key]));
724
+ if (autoTranslate) {
725
+ const sourceHeaderLower = localeHeader.toLowerCase();
726
+ const sourceHeaderIndex = headerRow.indexOf(sourceHeaderLower);
727
+ if (sourceHeaderIndex >= 0) {
728
+ const sourceColumnLetter = columnIndexToLetter(sourceHeaderIndex);
729
+ const rowObj = row.toObject();
730
+ const pushedHeaders = pushedLocaleHeadersPerKey.get(keyLower) ?? /* @__PURE__ */ new Set();
731
+ for (const targetLocaleHeader of locales) {
732
+ const targetLower = targetLocaleHeader.toLowerCase();
733
+ if (targetLower === sourceHeaderLower) continue;
734
+ if (pushedHeaders.has(targetLower)) continue;
735
+ const targetHeaderIndex = headerRow.indexOf(targetLower);
736
+ if (targetHeaderIndex < 0) continue;
737
+ const exactTargetHeader = originalHeaders.find(
738
+ (h) => h.toLowerCase() === targetLower
739
+ );
740
+ if (!exactTargetHeader) continue;
741
+ const existingValue = rowObj[exactTargetHeader];
742
+ const isEmpty = !existingValue || existingValue.toString().trim() === "";
743
+ if (isEmpty || override) {
744
+ const targetColumnLetter = columnIndexToLetter(targetHeaderIndex);
745
+ row.set(
746
+ exactTargetHeader,
747
+ `=GOOGLETRANSLATE(INDIRECT("${sourceColumnLetter}"&ROW());$${sourceColumnLetter}$1;${targetColumnLetter}$1)`
748
+ );
749
+ }
750
+ }
751
+ }
752
+ }
693
753
  try {
694
754
  await withRetry(
695
755
  () => row.save(),
@@ -719,12 +779,12 @@ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, auto
719
779
  if (localesWithValues.has(localeLower) || rowDataKey && rowData[rowDataKey]) {
720
780
  continue;
721
781
  }
722
- const exactHeaderName = headerRow.find(
782
+ const exactHeaderName = originalHeaders.find(
723
783
  (h) => h.toLowerCase() === localeLower
724
784
  );
725
785
  if (exactHeaderName) {
726
786
  const sourceHeaderIndex = headerRow.indexOf(sourceHeader.toLowerCase());
727
- const targetHeaderIndex = headerRow.indexOf(exactHeaderName);
787
+ const targetHeaderIndex = headerRow.indexOf(exactHeaderName.toLowerCase());
728
788
  if (sourceHeaderIndex < 0 || targetHeaderIndex < 0) {
729
789
  continue;
730
790
  }
@@ -838,19 +898,23 @@ function readDataJson(dataJsonPath) {
838
898
  }
839
899
 
840
900
  // src/utils/syncManager.ts
841
- async function handleBidirectionalSync(doc, dataJsonPath, translationsOutputDir, syncLocalChanges, autoTranslate, spreadsheetData, waitSeconds, localeMapping = {}) {
901
+ async function handleBidirectionalSync(doc, dataJsonPath, translationsOutputDir, syncLocalChanges, autoTranslate, spreadsheetData, waitSeconds, localeMapping = {}, override = false, cleanPush = false) {
842
902
  const result = {
843
903
  shouldRefresh: false,
844
904
  hasChanges: false
845
905
  };
846
906
  const localData = readDataJson(dataJsonPath);
847
907
  const dataJsonExists = localData !== null;
848
- const shouldSyncToSheet = syncLocalChanges && dataJsonExists && isDataJsonNewer(dataJsonPath, translationsOutputDir);
908
+ const shouldSyncToSheet = dataJsonExists && (cleanPush || syncLocalChanges && isDataJsonNewer(dataJsonPath, translationsOutputDir));
849
909
  if (!shouldSyncToSheet || !localData) {
850
910
  return result;
851
911
  }
852
- console.log("Local languageData.json is newer than translation files. Checking for changes...");
853
- const changes = findLocalChanges(localData, spreadsheetData);
912
+ if (cleanPush) {
913
+ console.log("Clean push enabled \u2013 pushing ALL keys from languageData.json to the spreadsheet...");
914
+ } else {
915
+ console.log("Local languageData.json is newer than translation files. Checking for changes...");
916
+ }
917
+ const changes = cleanPush ? localData : findLocalChanges(localData, spreadsheetData);
854
918
  const hasChanges = Object.keys(changes).length > 0 && Object.keys(changes).some(
855
919
  (locale) => Object.keys(changes[locale]).length > 0
856
920
  );
@@ -860,9 +924,10 @@ async function handleBidirectionalSync(doc, dataJsonPath, translationsOutputDir,
860
924
  }
861
925
  const localesCount = Object.keys(changes).length;
862
926
  const keysCount = Object.values(changes).flatMap((l) => Object.values(l)).flatMap((s) => Object.keys(s)).length;
863
- console.log(`Found local changes: ${localesCount} locale(s), ~${keysCount} key(s) to sync to the spreadsheet.`);
927
+ const pushMode = cleanPush ? "clean push" : "incremental sync";
928
+ console.log(`Found local changes (${pushMode}): ${localesCount} locale(s), ~${keysCount} key(s) to sync to the spreadsheet.`);
864
929
  try {
865
- await updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate, localeMapping);
930
+ await updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate, localeMapping, override);
866
931
  result.shouldRefresh = true;
867
932
  result.hasChanges = true;
868
933
  } catch (err) {
@@ -1191,7 +1256,9 @@ async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
1191
1256
  config.autoTranslate,
1192
1257
  finalTranslations,
1193
1258
  config.waitSeconds,
1194
- globalLocaleMapping
1259
+ globalLocaleMapping,
1260
+ config.override,
1261
+ config.cleanPush
1195
1262
  );
1196
1263
  if (syncResult.shouldRefresh && _refreshDepth < MAX_SYNC_REFRESH_DEPTH) {
1197
1264
  return getSpreadSheetData(
@@ -47,6 +47,23 @@ export interface SpreadsheetOptions {
47
47
  * Default: ['de', 'fr', 'es', 'it', 'pt', 'ja', 'zh'].
48
48
  */
49
49
  targetLocales?: string[];
50
+ /**
51
+ * When `true`, existing translations in other language columns are overwritten
52
+ * with GOOGLETRANSLATE formulas when `autoTranslate` is also enabled and keys
53
+ * are pushed. When `false` (the default), only **empty** cells receive a
54
+ * formula – cells that already contain a translation are left untouched.
55
+ */
56
+ override?: boolean;
57
+ /**
58
+ * When `true`, **all** keys from the local `languageData.json` are pushed to
59
+ * the spreadsheet – including keys that already exist there. This is useful
60
+ * when the file has been copied from another project and the spreadsheet needs
61
+ * a complete refresh. The file-timestamp guard (`isDataJsonNewer`) is
62
+ * bypassed. Whether existing cell values are overwritten depends on the usual
63
+ * `override` + `autoTranslate` flags. Implies `syncLocalChanges`.
64
+ * Default: `false`.
65
+ */
66
+ cleanPush?: boolean;
50
67
  }
51
68
  /**
52
69
  * Normalized configuration with all defaults applied
@@ -65,6 +82,8 @@ export interface NormalizedConfig {
65
82
  spreadsheetTitle: string;
66
83
  sourceLocale: string;
67
84
  targetLocales: string[];
85
+ override: boolean;
86
+ cleanPush: boolean;
68
87
  }
69
88
  /**
70
89
  * Normalizes configuration options by applying defaults
@@ -1 +1 @@
1
- {"version":3,"file":"configurationHandler.d.ts","sourceRoot":"","sources":["../../src/utils/configurationHandler.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,kBAAuB,GAAG,gBAAgB,CAgBlF"}
1
+ {"version":3,"file":"configurationHandler.d.ts","sourceRoot":"","sources":["../../src/utils/configurationHandler.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,kBAAuB,GAAG,gBAAgB,CAkBlF"}
@@ -1 +1 @@
1
- {"version":3,"file":"findLocalChanges.d.ts","sourceRoot":"","sources":["../../../src/utils/dataConverter/findLocalChanges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC/B,SAAS,EAAE,eAAe,EAC1B,eAAe,EAAE,eAAe,GAC9B,eAAe,CAgCjB"}
1
+ {"version":3,"file":"findLocalChanges.d.ts","sourceRoot":"","sources":["../../../src/utils/dataConverter/findLocalChanges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC/B,SAAS,EAAE,eAAe,EAC1B,eAAe,EAAE,eAAe,GAC9B,eAAe,CAoCjB"}
@@ -8,6 +8,8 @@ import type { TranslationData } from "../types";
8
8
  * - For languages missing translations, it automatically adds Google Translate formulas
9
9
  * - The formula format is: =GOOGLETRANSLATE(INDIRECT(sourceColumn&ROW());$sourceColumn$1;targetColumn$1)
10
10
  * - This dynamic formula uses cell references for language codes and automatically adapts to the correct row
11
+ * - For **existing** keys the same logic applies: empty cells in other language columns receive a
12
+ * formula; cells that already contain a translation are only overwritten when `override` is true.
11
13
  *
12
14
  * If a sheet named `sheetTitle` does not yet exist in the document and `localeMapping` is
13
15
  * non-empty, the sheet is **created automatically** with "key" as the first column followed by
@@ -24,7 +26,10 @@ import type { TranslationData } from "../types";
24
26
  * @param waitSeconds - Base back-off delay in seconds for retrying rate-limited API calls
25
27
  * @param autoTranslate - Whether to add Google Translate formulas for missing translations (default: false)
26
28
  * @param localeMapping - Mapping from normalized locale codes to original spreadsheet headers
29
+ * @param override - When true AND autoTranslate is true, existing translations in other language
30
+ * columns are overwritten with GOOGLETRANSLATE formulas. When false (default) only empty cells
31
+ * receive a formula.
27
32
  * @returns Promise that resolves when the update is complete
28
33
  */
29
- export declare function updateSpreadsheetWithLocalChanges(doc: GoogleSpreadsheet, changes: TranslationData, waitSeconds: number, autoTranslate?: boolean, localeMapping?: Record<string, string>): Promise<void>;
34
+ export declare function updateSpreadsheetWithLocalChanges(doc: GoogleSpreadsheet, changes: TranslationData, waitSeconds: number, autoTranslate?: boolean, localeMapping?: Record<string, string>, override?: boolean): Promise<void>;
30
35
  //# sourceMappingURL=spreadsheetUpdater.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spreadsheetUpdater.d.ts","sourceRoot":"","sources":["../../src/utils/spreadsheetUpdater.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAA8B,MAAM,oBAAoB,CAAC;AACxF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAehD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,iCAAiC,CACnD,GAAG,EAAE,iBAAiB,EACtB,OAAO,EAAE,eAAe,EACxB,WAAW,EAAE,MAAM,EACnB,aAAa,UAAQ,EACrB,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAC3C,OAAO,CAAC,IAAI,CAAC,CAqPf"}
1
+ {"version":3,"file":"spreadsheetUpdater.d.ts","sourceRoot":"","sources":["../../src/utils/spreadsheetUpdater.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAA8B,MAAM,oBAAoB,CAAC;AACxF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAgBhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,iCAAiC,CACnD,GAAG,EAAE,iBAAiB,EACtB,OAAO,EAAE,eAAe,EACxB,WAAW,EAAE,MAAM,EACnB,aAAa,UAAQ,EACrB,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC1C,QAAQ,UAAQ,GACjB,OAAO,CAAC,IAAI,CAAC,CAsUf"}
@@ -17,7 +17,9 @@ export interface SyncResult {
17
17
  * @param spreadsheetData Current data from the spreadsheet
18
18
  * @param waitSeconds Time to wait between API calls
19
19
  * @param localeMapping Mapping from normalized locale codes to original spreadsheet headers
20
+ * @param override When true AND autoTranslate is true, overwrite existing translations with formulas
21
+ * @param cleanPush When true, push ALL keys from localData (bypasses timestamp guard and diff)
20
22
  * @returns Sync operation result
21
23
  */
22
- export declare function handleBidirectionalSync(doc: GoogleSpreadsheet, dataJsonPath: string, translationsOutputDir: string, syncLocalChanges: boolean, autoTranslate: boolean, spreadsheetData: TranslationData, waitSeconds: number, localeMapping?: Record<string, string>): Promise<SyncResult>;
24
+ export declare function handleBidirectionalSync(doc: GoogleSpreadsheet, dataJsonPath: string, translationsOutputDir: string, syncLocalChanges: boolean, autoTranslate: boolean, spreadsheetData: TranslationData, waitSeconds: number, localeMapping?: Record<string, string>, override?: boolean, cleanPush?: boolean): Promise<SyncResult>;
23
25
  //# sourceMappingURL=syncManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"syncManager.d.ts","sourceRoot":"","sources":["../../src/utils/syncManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAMhD;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,uBAAuB,CAC5C,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,MAAM,EACpB,qBAAqB,EAAE,MAAM,EAC7B,gBAAgB,EAAE,OAAO,EACzB,aAAa,EAAE,OAAO,EACtB,eAAe,EAAE,eAAe,EAChC,WAAW,EAAE,MAAM,EACnB,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACxC,OAAO,CAAC,UAAU,CAAC,CAoDrB"}
1
+ {"version":3,"file":"syncManager.d.ts","sourceRoot":"","sources":["../../src/utils/syncManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAMhD;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,uBAAuB,CAC5C,GAAG,EAAE,iBAAiB,EACtB,YAAY,EAAE,MAAM,EACpB,qBAAqB,EAAE,MAAM,EAC7B,gBAAgB,EAAE,OAAO,EACzB,aAAa,EAAE,OAAO,EACtB,eAAe,EAAE,eAAe,EAChC,WAAW,EAAE,MAAM,EACnB,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EAC1C,QAAQ,UAAQ,EAChB,SAAS,UAAQ,GACf,OAAO,CAAC,UAAU,CAAC,CA4DrB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@el-j/google-sheet-translations",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "A package to manage translations stored in Google Spreadsheets",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -75,10 +75,10 @@
75
75
  "@semantic-release/npm": "^13.1.5",
76
76
  "@semantic-release/release-notes-generator": "^14.1.0",
77
77
  "@types/jest": "^30.0.0",
78
- "@types/node": "^25.4.0",
78
+ "@types/node": "^25.5.0",
79
79
  "@typescript-eslint/eslint-plugin": "^8.57.0",
80
80
  "@typescript-eslint/parser": "^8.57.0",
81
- "esbuild": "^0.26.0",
81
+ "esbuild": "^0.27.4",
82
82
  "conventional-changelog-conventionalcommits": "^9.3.0",
83
83
  "dotenv": "17.3.1",
84
84
  "eslint": "^10.0.3",