@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.
- package/dist/action-entrypoint.d.ts.map +1 -1
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/esm/index.js +78 -11
- package/dist/getSpreadSheetData.d.ts.map +1 -1
- package/dist/index.js +78 -11
- package/dist/utils/configurationHandler.d.ts +19 -0
- package/dist/utils/configurationHandler.d.ts.map +1 -1
- package/dist/utils/dataConverter/findLocalChanges.d.ts.map +1 -1
- package/dist/utils/spreadsheetUpdater.d.ts +6 -1
- package/dist/utils/spreadsheetUpdater.d.ts.map +1 -1
- package/dist/utils/syncManager.d.ts +3 -1
- package/dist/utils/syncManager.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -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,
|
|
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"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -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
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -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 =
|
|
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 =
|
|
843
|
+
const shouldSyncToSheet = dataJsonExists && (cleanPush || syncLocalChanges && isDataJsonNewer(dataJsonPath, translationsOutputDir));
|
|
784
844
|
if (!shouldSyncToSheet || !localData) {
|
|
785
845
|
return result;
|
|
786
846
|
}
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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 =
|
|
908
|
+
const shouldSyncToSheet = dataJsonExists && (cleanPush || syncLocalChanges && isDataJsonNewer(dataJsonPath, translationsOutputDir));
|
|
849
909
|
if (!shouldSyncToSheet || !localData) {
|
|
850
910
|
return result;
|
|
851
911
|
}
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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
|
|
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;
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|