@el-j/google-sheet-translations 2.1.4 → 2.2.0-beta.1

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/README.md CHANGED
@@ -245,18 +245,19 @@ When enabled, the auto-translation feature automatically adds Google Translate f
245
245
 
246
246
  The formula follows this format:
247
247
  ```
248
- =GOOGLETRANSLATE(INDIRECT(sourceColumn&ROW());$sourceColumn$1;targetColumn$1)
248
+ =GOOGLETRANSLATE(INDIRECT(srcCol&ROW()); langCodeFormula($srcCol$1); langCodeFormula(tgtCol$1))
249
249
  ```
250
250
 
251
- For example, if you add a new key with an English translation in column B but no German translation in column C, the system will automatically add:
251
+ The `langCodeFormula` dynamically extracts the GOOGLETRANSLATE-compatible language code from the header cell stripping region qualifiers (e.g. `"tr-TR"` `"tr"`) while preserving Chinese variants (`zh-TW` / `zh-CN`). All formula separators are automatically matched to the spreadsheet's locale.
252
+
253
+ For example, if you add a new key with an English translation in column B but no Turkish translation (header `tr-TR`) in column C, the system will automatically add:
252
254
  ```
253
- =GOOGLETRANSLATE(INDIRECT("B"&ROW());$B$1;C$1)
255
+ =GOOGLETRANSLATE(INDIRECT("B"&ROW());IF(LOWER(LEFT($B$1;3))="zh-";LOWER($B$1);LOWER(IFERROR(LEFT($B$1;FIND("-";$B$1)-1);$B$1)));IF(LOWER(LEFT(C$1;3))="zh-";LOWER(C$1);LOWER(IFERROR(LEFT(C$1;FIND("-";C$1)-1);C$1))))
254
256
  ```
255
257
 
256
258
  Where:
257
259
  - `INDIRECT("B"&ROW())` dynamically references the source text cell in the same row
258
- - `$B$1` references the header cell containing the source language code
259
- - `C$1` references the header cell containing the target language code
260
+ - Each `IF(…)` wrapper extracts the language code from the header cell (`$B$1` / `C$1`), producing `"en"` and `"tr"` respectively at evaluation time
260
261
 
261
262
  [View the complete Auto-Translation Guide](docs/auto-translation-guide.md) for more details and best practices.
262
263
 
@@ -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,CA+DzC"}
1
+ {"version":3,"file":"action-entrypoint.d.ts","sourceRoot":"","sources":["../src/action-entrypoint.ts"],"names":[],"mappings":"AAMA,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CA2FzC"}
package/dist/esm/index.js CHANGED
@@ -169,6 +169,14 @@ function filterValidLocales(headerRow, keyColumn) {
169
169
  function getLanguagePrefix(locale) {
170
170
  return locale.toLowerCase().split(/[-_]/)[0];
171
171
  }
172
+ var GOOGLE_TRANSLATE_CODES_REQUIRING_REGION = /* @__PURE__ */ new Set(["zh-tw", "zh-cn"]);
173
+ function getGoogleTranslateCode(locale) {
174
+ const normalized = locale.toLowerCase().trim().replace("_", "-");
175
+ if (GOOGLE_TRANSLATE_CODES_REQUIRING_REGION.has(normalized)) {
176
+ return normalized;
177
+ }
178
+ return normalized.split(/[-_]/)[0];
179
+ }
172
180
  var LANGUAGE_TO_COUNTRY_MAP = {
173
181
  "en": "en-GB",
174
182
  "de": "de-DE",
@@ -531,12 +539,23 @@ function columnIndexToLetter(index) {
531
539
  } while (i >= 0);
532
540
  return result;
533
541
  }
534
- function langCodeFormula(cellRef) {
535
- return `LOWER(IFERROR(LEFT(${cellRef},FIND("-",${cellRef})-1),${cellRef}))`;
542
+ function getFormulaSeparator(doc) {
543
+ try {
544
+ const locale = doc._rawProperties?.locale || "";
545
+ if (/^(en|ja|ko|zh|th|id|ms)/i.test(locale)) return ",";
546
+ } catch {
547
+ }
548
+ return ";";
549
+ }
550
+ function langCodeFormula(cellRef, sep) {
551
+ const prefix = `LOWER(IFERROR(LEFT(${cellRef}${sep}FIND("-"${sep}${cellRef})-1)${sep}${cellRef}))`;
552
+ const full = `LOWER(${cellRef})`;
553
+ return `IF(LOWER(LEFT(${cellRef}${sep}3))="zh-"${sep}${full}${sep}${prefix})`;
536
554
  }
537
555
  async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, autoTranslate = false, localeMapping = {}, override = false) {
538
556
  console.log("Updating spreadsheet with local changes...");
539
557
  const baseDelayMs = waitSeconds * 1e3;
558
+ const sep = getFormulaSeparator(doc);
540
559
  for (const sheetTitle of new Set(
541
560
  Object.values(changes).flatMap((locale) => Object.keys(locale))
542
561
  )) {
@@ -680,7 +699,7 @@ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, auto
680
699
  const targetColumnLetter = columnIndexToLetter(targetHeaderIndex);
681
700
  row.set(
682
701
  exactTargetHeader,
683
- `=GOOGLETRANSLATE(INDIRECT("${sourceColumnLetter}"&ROW());${langCodeFormula(`$${sourceColumnLetter}$1`)};${langCodeFormula(`${targetColumnLetter}$1`)})`
702
+ `=GOOGLETRANSLATE(INDIRECT("${sourceColumnLetter}"&ROW())${sep}${langCodeFormula(`$${sourceColumnLetter}$1`, sep)}${sep}${langCodeFormula(`${targetColumnLetter}$1`, sep)})`
684
703
  );
685
704
  }
686
705
  }
@@ -729,7 +748,7 @@ async function updateSpreadsheetWithLocalChanges(doc, changes, waitSeconds, auto
729
748
  }
730
749
  const sourceColumnLetter = columnIndexToLetter(sourceHeaderIndex);
731
750
  const targetColumnLetter = columnIndexToLetter(targetHeaderIndex);
732
- rowData[exactHeaderName] = `=GOOGLETRANSLATE(INDIRECT("${sourceColumnLetter}"&ROW());${langCodeFormula(`$${sourceColumnLetter}$1`)};${langCodeFormula(`${targetColumnLetter}$1`)})`;
751
+ rowData[exactHeaderName] = `=GOOGLETRANSLATE(INDIRECT("${sourceColumnLetter}"&ROW())${sep}${langCodeFormula(`$${sourceColumnLetter}$1`, sep)}${sep}${langCodeFormula(`${targetColumnLetter}$1`, sep)})`;
733
752
  }
734
753
  }
735
754
  }
@@ -985,8 +1004,18 @@ function colLetter(index) {
985
1004
  } while (i >= 0);
986
1005
  return result;
987
1006
  }
988
- function langCodeFormula2(cellRef) {
989
- return `LOWER(IFERROR(LEFT(${cellRef},FIND("-",${cellRef})-1),${cellRef}))`;
1007
+ function getFormulaSeparator2(doc) {
1008
+ try {
1009
+ const locale = doc._rawProperties?.locale || "";
1010
+ if (/^(en|ja|ko|zh|th|id|ms)/i.test(locale)) return ",";
1011
+ } catch {
1012
+ }
1013
+ return ";";
1014
+ }
1015
+ function langCodeFormula2(cellRef, sep) {
1016
+ const prefix = `LOWER(IFERROR(LEFT(${cellRef}${sep}FIND("-"${sep}${cellRef})-1)${sep}${cellRef}))`;
1017
+ const full = `LOWER(${cellRef})`;
1018
+ return `IF(LOWER(LEFT(${cellRef}${sep}3))="zh-"${sep}${full}${sep}${prefix})`;
990
1019
  }
991
1020
  var DEFAULT_TARGET_LOCALES = ["de", "fr", "es", "it", "pt", "ja", "zh"];
992
1021
  var STARTER_KEYS = {
@@ -1068,11 +1097,12 @@ async function createSpreadsheet(authClient, options = {}) {
1068
1097
  "setHeaderRow"
1069
1098
  );
1070
1099
  const sourceColLetter = colLetter(1);
1100
+ const sep = getFormulaSeparator2(doc);
1071
1101
  const rows = Object.entries(seedKeys).map(([key, sourceValue]) => {
1072
1102
  const row = { key, [sourceLocale]: sourceValue };
1073
1103
  targetLocales.forEach((locale, idx) => {
1074
1104
  const targetColLetter = colLetter(2 + idx);
1075
- row[locale] = `=GOOGLETRANSLATE(INDIRECT("${sourceColLetter}"&ROW());${langCodeFormula2(`$${sourceColLetter}$1`)};${langCodeFormula2(`${targetColLetter}$1`)})`;
1105
+ row[locale] = `=GOOGLETRANSLATE(INDIRECT("${sourceColLetter}"&ROW())${sep}${langCodeFormula2(`$${sourceColLetter}$1`, sep)}${sep}${langCodeFormula2(`${targetColLetter}$1`, sep)})`;
1076
1106
  });
1077
1107
  return row;
1078
1108
  });
@@ -1287,6 +1317,367 @@ function mergeSheets(translations, locale, sheetNames) {
1287
1317
  return merged;
1288
1318
  }
1289
1319
 
1320
+ // src/utils/multiSpreadsheetMerger.ts
1321
+ function mergeMultipleTranslationData(results, mergeStrategy = "later-wins") {
1322
+ const merged = {};
1323
+ for (const result of results) {
1324
+ for (const [locale, sheets] of Object.entries(result)) {
1325
+ if (!merged[locale]) {
1326
+ merged[locale] = {};
1327
+ }
1328
+ for (const [sheet, keys] of Object.entries(sheets)) {
1329
+ if (!merged[locale][sheet]) {
1330
+ merged[locale][sheet] = {};
1331
+ }
1332
+ for (const [key, value] of Object.entries(keys)) {
1333
+ if (mergeStrategy === "first-wins" && key in merged[locale][sheet]) {
1334
+ continue;
1335
+ }
1336
+ merged[locale][sheet][key] = value;
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ return merged;
1342
+ }
1343
+
1344
+ // src/getMultipleSpreadSheetsData.ts
1345
+ async function getMultipleSpreadSheetsData(docTitles, options = {}) {
1346
+ const { spreadsheetIds, mergeStrategy = "later-wins", ...baseOptions } = options;
1347
+ if (!spreadsheetIds || spreadsheetIds.length === 0) {
1348
+ return getSpreadSheetData(docTitles, baseOptions);
1349
+ }
1350
+ console.log(`[getMultipleSpreadSheetsData] Fetching ${spreadsheetIds.length} spreadsheets...`);
1351
+ const results = [];
1352
+ for (let i = 0; i < spreadsheetIds.length; i++) {
1353
+ const id = spreadsheetIds[i];
1354
+ console.log(`[getMultipleSpreadSheetsData] (${i + 1}/${spreadsheetIds.length}) "${id}"...`);
1355
+ const result = await getSpreadSheetData(docTitles, { ...baseOptions, spreadsheetId: id });
1356
+ results.push(result);
1357
+ }
1358
+ return mergeMultipleTranslationData(results, mergeStrategy);
1359
+ }
1360
+
1361
+ // src/utils/driveFolderScanner.ts
1362
+ import { GoogleAuth } from "google-auth-library";
1363
+ var SPREADSHEET_MIME = "application/vnd.google-apps.spreadsheet";
1364
+ var FOLDER_MIME = "application/vnd.google-apps.folder";
1365
+ var DRIVE_FILES_URL = "https://www.googleapis.com/drive/v3/files";
1366
+ async function getAccessToken(credentials) {
1367
+ const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
1368
+ const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
1369
+ if (!clientEmail || !privateKey) {
1370
+ throw new Error(
1371
+ "Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
1372
+ );
1373
+ }
1374
+ const normalizedKey = privateKey.replace(/\\n/g, "\n");
1375
+ const auth = new GoogleAuth({
1376
+ credentials: { client_email: clientEmail, private_key: normalizedKey },
1377
+ scopes: ["https://www.googleapis.com/auth/drive.readonly"]
1378
+ });
1379
+ const client = await auth.getClient();
1380
+ const tokenResponse = await client.getAccessToken();
1381
+ return tokenResponse.token;
1382
+ }
1383
+ async function listFilesInFolder(folderId, mimeType, token) {
1384
+ const results = [];
1385
+ let pageToken;
1386
+ do {
1387
+ const query = `'${folderId}' in parents and mimeType = '${mimeType}' and trashed = false`;
1388
+ const params = new URLSearchParams({
1389
+ q: query,
1390
+ fields: "nextPageToken,files(id,name,mimeType,modifiedTime,parents)",
1391
+ pageSize: "1000"
1392
+ });
1393
+ if (pageToken) params.set("pageToken", pageToken);
1394
+ const response = await fetch(`${DRIVE_FILES_URL}?${params.toString()}`, {
1395
+ headers: { Authorization: `Bearer ${token}` }
1396
+ });
1397
+ if (!response.ok) {
1398
+ const text = await response.text();
1399
+ throw new Error(
1400
+ `Drive API error ${response.status}: ${text}`
1401
+ );
1402
+ }
1403
+ const data = await response.json();
1404
+ results.push(...data.files);
1405
+ pageToken = data.nextPageToken;
1406
+ } while (pageToken);
1407
+ return results;
1408
+ }
1409
+ async function scanFolder(folderId, folderPath, token, recursive, nameFilter, seen = /* @__PURE__ */ new Set()) {
1410
+ console.log(`[driveFolderScanner] Scanning folder: ${folderId} (path: "${folderPath}")`);
1411
+ const spreadsheets = await listFilesInFolder(folderId, SPREADSHEET_MIME, token);
1412
+ const results = [];
1413
+ for (const file of spreadsheets) {
1414
+ if (seen.has(file.id)) continue;
1415
+ seen.add(file.id);
1416
+ if (nameFilter && !nameFilter.test(file.name)) continue;
1417
+ results.push({
1418
+ id: file.id,
1419
+ name: file.name,
1420
+ folderPath,
1421
+ mimeType: file.mimeType,
1422
+ modifiedTime: file.modifiedTime
1423
+ });
1424
+ }
1425
+ if (recursive) {
1426
+ const subfolders = await listFilesInFolder(folderId, FOLDER_MIME, token);
1427
+ for (const folder of subfolders) {
1428
+ const subPath = folderPath ? `${folderPath}/${folder.name}` : folder.name;
1429
+ const subResults = await scanFolder(
1430
+ folder.id,
1431
+ subPath,
1432
+ token,
1433
+ recursive,
1434
+ nameFilter,
1435
+ seen
1436
+ );
1437
+ results.push(...subResults);
1438
+ }
1439
+ }
1440
+ return results;
1441
+ }
1442
+ async function scanDriveFolderForSpreadsheets(options) {
1443
+ const { folderId, recursive = true, nameFilter, credentials } = options;
1444
+ const token = await getAccessToken(credentials);
1445
+ return scanFolder(folderId, "", token, recursive, nameFilter);
1446
+ }
1447
+
1448
+ // src/utils/driveImageSync.ts
1449
+ import { createWriteStream, mkdirSync, existsSync, readdirSync, unlinkSync, statSync } from "node:fs";
1450
+ import { join, dirname } from "node:path";
1451
+ import { pipeline } from "node:stream/promises";
1452
+ import { Readable } from "node:stream";
1453
+ import { GoogleAuth as GoogleAuth2 } from "google-auth-library";
1454
+ var DEFAULT_IMAGE_MIME_TYPES = [
1455
+ "image/jpeg",
1456
+ "image/jpg",
1457
+ "image/png",
1458
+ "image/webp",
1459
+ "image/avif",
1460
+ "image/gif",
1461
+ "image/svg+xml",
1462
+ "image/tiff",
1463
+ "image/bmp",
1464
+ "image/ico",
1465
+ "image/x-icon"
1466
+ ];
1467
+ var FOLDER_MIME2 = "application/vnd.google-apps.folder";
1468
+ var DRIVE_FILES_URL2 = "https://www.googleapis.com/drive/v3/files";
1469
+ async function getAccessToken2(credentials) {
1470
+ const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
1471
+ const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
1472
+ if (!clientEmail || !privateKey) {
1473
+ throw new Error(
1474
+ "Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
1475
+ );
1476
+ }
1477
+ const normalizedKey = privateKey.replace(/\\n/g, "\n");
1478
+ const auth = new GoogleAuth2({
1479
+ credentials: { client_email: clientEmail, private_key: normalizedKey },
1480
+ scopes: ["https://www.googleapis.com/auth/drive.readonly"]
1481
+ });
1482
+ const client = await auth.getClient();
1483
+ const tokenResponse = await client.getAccessToken();
1484
+ return tokenResponse.token;
1485
+ }
1486
+ async function listFilesInFolder2(folderId, token, mimeTypeFilter) {
1487
+ const results = [];
1488
+ let pageToken;
1489
+ do {
1490
+ const mimeClause = mimeTypeFilter ? ` and mimeType = '${mimeTypeFilter}'` : "";
1491
+ const query = `'${folderId}' in parents${mimeClause} and trashed = false`;
1492
+ const params = new URLSearchParams({
1493
+ q: query,
1494
+ fields: "nextPageToken,files(id,name,mimeType,parents)",
1495
+ pageSize: "1000"
1496
+ });
1497
+ if (pageToken) params.set("pageToken", pageToken);
1498
+ const response = await fetch(`${DRIVE_FILES_URL2}?${params.toString()}`, {
1499
+ headers: { Authorization: `Bearer ${token}` }
1500
+ });
1501
+ if (!response.ok) {
1502
+ const text = await response.text();
1503
+ throw new Error(`Drive API error ${response.status}: ${text}`);
1504
+ }
1505
+ const data = await response.json();
1506
+ results.push(...data.files);
1507
+ pageToken = data.nextPageToken;
1508
+ } while (pageToken);
1509
+ return results;
1510
+ }
1511
+ async function collectFiles(folderId, folderRelPath, outputPath, token, allowedMimeTypes, recursive, folderPattern) {
1512
+ console.log(`[driveImageSync] Scanning folder: ${folderId} (path: "${folderRelPath}")`);
1513
+ const allItems = await listFilesInFolder2(folderId, token);
1514
+ const entries = [];
1515
+ for (const item of allItems) {
1516
+ if (item.mimeType === FOLDER_MIME2) {
1517
+ if (!recursive) continue;
1518
+ const subRelPath = folderRelPath ? `${folderRelPath}/${item.name}` : item.name;
1519
+ if (folderPattern && !folderPattern.test(subRelPath)) continue;
1520
+ const subEntries = await collectFiles(
1521
+ item.id,
1522
+ subRelPath,
1523
+ outputPath,
1524
+ token,
1525
+ allowedMimeTypes,
1526
+ recursive,
1527
+ folderPattern
1528
+ );
1529
+ entries.push(...subEntries);
1530
+ } else if (allowedMimeTypes.includes(item.mimeType)) {
1531
+ const localPath = folderRelPath ? join(outputPath, folderRelPath, item.name) : join(outputPath, item.name);
1532
+ entries.push({ id: item.id, name: item.name, localPath, mimeType: item.mimeType });
1533
+ }
1534
+ }
1535
+ return entries;
1536
+ }
1537
+ async function downloadFile(fileId, localPath, token) {
1538
+ const url = `${DRIVE_FILES_URL2}/${fileId}?alt=media`;
1539
+ const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
1540
+ if (!response.ok) {
1541
+ throw new Error(`Failed to download ${fileId}: ${response.status}`);
1542
+ }
1543
+ mkdirSync(dirname(localPath), { recursive: true });
1544
+ const dest = createWriteStream(localPath);
1545
+ await pipeline(Readable.fromWeb(response.body), dest);
1546
+ }
1547
+ function collectLocalFiles(dir, base) {
1548
+ const results = [];
1549
+ if (!existsSync(dir)) return results;
1550
+ for (const entry of readdirSync(dir)) {
1551
+ const fullPath = join(dir, entry);
1552
+ const stat = statSync(fullPath);
1553
+ if (stat.isDirectory()) {
1554
+ results.push(...collectLocalFiles(fullPath, base));
1555
+ } else {
1556
+ results.push(fullPath);
1557
+ }
1558
+ }
1559
+ return results;
1560
+ }
1561
+ async function runConcurrent(tasks, concurrency) {
1562
+ const results = [];
1563
+ for (let i = 0; i < tasks.length; i += concurrency) {
1564
+ const batch = tasks.slice(i, i + concurrency).map((t) => t());
1565
+ results.push(...await Promise.all(batch));
1566
+ }
1567
+ return results;
1568
+ }
1569
+ async function syncDriveImages(options) {
1570
+ const {
1571
+ folderId,
1572
+ outputPath,
1573
+ mimeTypes = DEFAULT_IMAGE_MIME_TYPES,
1574
+ recursive = true,
1575
+ folderPattern,
1576
+ credentials,
1577
+ cleanSync = false,
1578
+ concurrency = 3
1579
+ } = options;
1580
+ const token = await getAccessToken2(credentials);
1581
+ mkdirSync(outputPath, { recursive: true });
1582
+ const entries = await collectFiles(
1583
+ folderId,
1584
+ "",
1585
+ outputPath,
1586
+ token,
1587
+ mimeTypes,
1588
+ recursive,
1589
+ folderPattern
1590
+ );
1591
+ const downloaded = [];
1592
+ const skipped = [];
1593
+ const errors = [];
1594
+ const tasks = entries.map((entry) => async () => {
1595
+ if (existsSync(entry.localPath)) {
1596
+ console.log(`[driveImageSync] Skipping (exists): ${entry.localPath}`);
1597
+ skipped.push(entry.localPath);
1598
+ return;
1599
+ }
1600
+ console.log(`[driveImageSync] Downloading: ${entry.localPath}`);
1601
+ try {
1602
+ await downloadFile(entry.id, entry.localPath, token);
1603
+ downloaded.push(entry.localPath);
1604
+ } catch (err) {
1605
+ const msg = err instanceof Error ? err.message : String(err);
1606
+ console.error(`[driveImageSync] Error downloading ${entry.localPath}: ${msg}`);
1607
+ errors.push(entry.localPath);
1608
+ }
1609
+ });
1610
+ await runConcurrent(tasks, concurrency);
1611
+ const deleted = [];
1612
+ if (cleanSync) {
1613
+ const driveLocalPaths = new Set(entries.map((e) => e.localPath));
1614
+ const localFiles = collectLocalFiles(outputPath, outputPath);
1615
+ for (const localFile of localFiles) {
1616
+ if (!driveLocalPaths.has(localFile)) {
1617
+ console.log(`[driveImageSync] Deleting (not in Drive): ${localFile}`);
1618
+ unlinkSync(localFile);
1619
+ deleted.push(localFile);
1620
+ }
1621
+ }
1622
+ }
1623
+ console.log(
1624
+ `[driveImageSync] Synced ${downloaded.length} files, skipped ${skipped.length}, deleted ${deleted.length}, errors ${errors.length}`
1625
+ );
1626
+ return { downloaded, skipped, deleted, errors };
1627
+ }
1628
+
1629
+ // src/utils/getDriveTranslations.ts
1630
+ async function manageDriveTranslations(options) {
1631
+ const {
1632
+ driveFolderId,
1633
+ scanForSpreadsheets = true,
1634
+ spreadsheetIds: explicitIds = [],
1635
+ spreadsheetNameFilter,
1636
+ syncImages = false,
1637
+ imageOutputPath,
1638
+ imageSyncOptions,
1639
+ translationOptions = {},
1640
+ docTitles
1641
+ } = options;
1642
+ if (syncImages && !imageOutputPath) {
1643
+ throw new Error(
1644
+ "[manageDriveTranslations] imageOutputPath is required when syncImages is true"
1645
+ );
1646
+ }
1647
+ const discoveredIds = [];
1648
+ const discoveredNames = /* @__PURE__ */ new Map();
1649
+ if (driveFolderId && scanForSpreadsheets) {
1650
+ const scanOptions = { folderId: driveFolderId };
1651
+ const discovered = await scanDriveFolderForSpreadsheets(scanOptions);
1652
+ console.log(
1653
+ `[manageDriveTranslations] Found ${discovered.length} spreadsheet(s) in Drive folder`
1654
+ );
1655
+ for (const file of discovered) {
1656
+ discoveredIds.push(file.id);
1657
+ discoveredNames.set(file.id, file.name);
1658
+ }
1659
+ }
1660
+ const allIds = [.../* @__PURE__ */ new Set([...discoveredIds, ...explicitIds])];
1661
+ const filteredIds = spreadsheetNameFilter ? allIds.filter((id) => {
1662
+ const name = discoveredNames.get(id);
1663
+ if (!name) return true;
1664
+ return spreadsheetNameFilter.test(name);
1665
+ }) : allIds;
1666
+ const translations = await getMultipleSpreadSheetsData(docTitles, {
1667
+ ...translationOptions,
1668
+ spreadsheetIds: filteredIds.length > 0 ? filteredIds : void 0
1669
+ });
1670
+ let imageSync;
1671
+ if (syncImages && driveFolderId && imageOutputPath) {
1672
+ imageSync = await syncDriveImages({
1673
+ ...imageSyncOptions,
1674
+ folderId: driveFolderId,
1675
+ outputPath: imageOutputPath
1676
+ });
1677
+ }
1678
+ return { translations, spreadsheetIds: filteredIds, imageSync };
1679
+ }
1680
+
1290
1681
  // src/index.ts
1291
1682
  var index_default = getSpreadSheetData;
1292
1683
  export {
@@ -1299,19 +1690,25 @@ export {
1299
1690
  index_default as default,
1300
1691
  filterValidLocales,
1301
1692
  findLocalChanges,
1693
+ getGoogleTranslateCode,
1302
1694
  getLanguagePrefix,
1303
1695
  getLocaleDisplayName,
1696
+ getMultipleSpreadSheetsData,
1304
1697
  getNormalizedLocaleForHeader,
1305
1698
  getOriginalHeaderForLocale,
1306
1699
  getSpreadSheetData,
1307
1700
  getTranslationSummary,
1308
1701
  handleBidirectionalSync,
1309
1702
  isValidLocale,
1703
+ manageDriveTranslations,
1704
+ mergeMultipleTranslationData,
1310
1705
  mergeSheets,
1311
1706
  normalizeLocaleCode,
1312
1707
  processRawRows,
1313
1708
  readPublicSheet,
1314
1709
  resolveLocaleWithFallback,
1710
+ scanDriveFolderForSpreadsheets,
1711
+ syncDriveImages,
1315
1712
  updateSpreadsheetWithLocalChanges,
1316
1713
  validateCredentials,
1317
1714
  validateEnv,
@@ -0,0 +1,19 @@
1
+ import type { TranslationData } from './types';
2
+ import type { SpreadsheetOptions } from './utils/configurationHandler';
3
+ export interface MultiSpreadsheetOptions extends SpreadsheetOptions {
4
+ /** Array of spreadsheet IDs to fetch from. Overrides spreadsheetId if provided. */
5
+ spreadsheetIds?: string[];
6
+ /**
7
+ * How to merge same-locale same-sheet keys from different spreadsheets.
8
+ * 'later-wins': keys from later spreadsheets override earlier (default)
9
+ * 'first-wins': keep first occurrence of each key
10
+ */
11
+ mergeStrategy?: 'later-wins' | 'first-wins';
12
+ }
13
+ /**
14
+ * Fetches translations from multiple Google Spreadsheets and merges them.
15
+ * When spreadsheetIds is not provided, falls back to options.spreadsheetId
16
+ * or GOOGLE_SPREADSHEET_ID env var (same as getSpreadSheetData).
17
+ */
18
+ export declare function getMultipleSpreadSheetsData(docTitles?: string[], options?: MultiSpreadsheetOptions): Promise<TranslationData>;
19
+ //# sourceMappingURL=getMultipleSpreadSheetsData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getMultipleSpreadSheetsData.d.ts","sourceRoot":"","sources":["../src/getMultipleSpreadSheetsData.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAIvE,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IAClE,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;OAIG;IACH,aAAa,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;CAC5C;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAChD,SAAS,CAAC,EAAE,MAAM,EAAE,EACpB,OAAO,GAAE,uBAA4B,GACnC,OAAO,CAAC,eAAe,CAAC,CAmB1B"}
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export { readPublicSheet } from './utils/publicSheetReader';
16
16
  export { createSpreadsheet } from './utils/spreadsheetCreator';
17
17
  export { validateCredentials } from './utils/validateEnv';
18
18
  export { isValidLocale, filterValidLocales } from './utils/localeFilter';
19
- export { getLanguagePrefix, normalizeLocaleCode, createLocaleMapping, getOriginalHeaderForLocale, getNormalizedLocaleForHeader, resolveLocaleWithFallback, } from './utils/localeNormalizer';
19
+ export { getLanguagePrefix, getGoogleTranslateCode, normalizeLocaleCode, createLocaleMapping, getOriginalHeaderForLocale, getNormalizedLocaleForHeader, resolveLocaleWithFallback, } from './utils/localeNormalizer';
20
20
  export { processRawRows } from './utils/sheetProcessor';
21
21
  export type { SheetProcessingResult } from './utils/sheetProcessor';
22
22
  export { getTranslationSummary, getLocaleDisplayName, mergeSheets } from './utils/translationHelpers';
@@ -25,6 +25,15 @@ export { writeTranslationFiles, writeLocalesFile, writeLanguageDataFile } from '
25
25
  export { handleBidirectionalSync } from './utils/syncManager';
26
26
  export type { SyncResult } from './utils/syncManager';
27
27
  export type { TranslationData, TranslationValue, SheetRow, GoogleEnvVars, } from './types';
28
+ export { getMultipleSpreadSheetsData } from './getMultipleSpreadSheetsData';
29
+ export type { MultiSpreadsheetOptions } from './getMultipleSpreadSheetsData';
30
+ export { mergeMultipleTranslationData } from './utils/multiSpreadsheetMerger';
31
+ export { scanDriveFolderForSpreadsheets } from './utils/driveFolderScanner';
32
+ export type { DriveSpreadsheetFile, ScanDriveFolderOptions } from './utils/driveFolderScanner';
33
+ export { syncDriveImages } from './utils/driveImageSync';
34
+ export type { DriveImageSyncOptions, DriveImageSyncResult } from './utils/driveImageSync';
35
+ export { manageDriveTranslations } from './utils/getDriveTranslations';
36
+ export type { GoogleDriveManagerOptions, GoogleDriveManagerResult } from './utils/getDriveTranslations';
28
37
  import { getSpreadSheetData } from './getSpreadSheetData';
29
38
  export default getSpreadSheetData;
30
39
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAGhF,YAAY,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGzE,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACtG,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAGnF,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAGpG,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAGhF,YAAY,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AACxF,OAAO,EAAE,yBAAyB,EAAE,MAAM,iDAAiD,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGzE,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACtG,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAGnF,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAGpG,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,YAAY,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAG9E,OAAO,EAAE,8BAA8B,EAAE,MAAM,4BAA4B,CAAC;AAC5E,YAAY,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAG/F,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG1F,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,YAAY,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAGxG,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,eAAe,kBAAkB,CAAC"}