@el-j/google-sheet-translations 2.1.5-beta.1 → 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/dist/action-entrypoint.d.ts.map +1 -1
- package/dist/esm/index.js +366 -0
- package/dist/getMultipleSpreadSheetsData.d.ts +19 -0
- package/dist/getMultipleSpreadSheetsData.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +371 -0
- package/dist/utils/driveFolderScanner.d.ts +26 -0
- package/dist/utils/driveFolderScanner.d.ts.map +1 -0
- package/dist/utils/driveImageSync.d.ts +44 -0
- package/dist/utils/driveImageSync.d.ts.map +1 -0
- package/dist/utils/getDriveTranslations.d.ts +80 -0
- package/dist/utils/getDriveTranslations.d.ts.map +1 -0
- package/dist/utils/multiSpreadsheetMerger.d.ts +7 -0
- package/dist/utils/multiSpreadsheetMerger.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-entrypoint.d.ts","sourceRoot":"","sources":["../src/action-entrypoint.ts"],"names":[],"mappings":"
|
|
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
|
@@ -1317,6 +1317,367 @@ function mergeSheets(translations, locale, sheetNames) {
|
|
|
1317
1317
|
return merged;
|
|
1318
1318
|
}
|
|
1319
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
|
+
|
|
1320
1681
|
// src/index.ts
|
|
1321
1682
|
var index_default = getSpreadSheetData;
|
|
1322
1683
|
export {
|
|
@@ -1332,17 +1693,22 @@ export {
|
|
|
1332
1693
|
getGoogleTranslateCode,
|
|
1333
1694
|
getLanguagePrefix,
|
|
1334
1695
|
getLocaleDisplayName,
|
|
1696
|
+
getMultipleSpreadSheetsData,
|
|
1335
1697
|
getNormalizedLocaleForHeader,
|
|
1336
1698
|
getOriginalHeaderForLocale,
|
|
1337
1699
|
getSpreadSheetData,
|
|
1338
1700
|
getTranslationSummary,
|
|
1339
1701
|
handleBidirectionalSync,
|
|
1340
1702
|
isValidLocale,
|
|
1703
|
+
manageDriveTranslations,
|
|
1704
|
+
mergeMultipleTranslationData,
|
|
1341
1705
|
mergeSheets,
|
|
1342
1706
|
normalizeLocaleCode,
|
|
1343
1707
|
processRawRows,
|
|
1344
1708
|
readPublicSheet,
|
|
1345
1709
|
resolveLocaleWithFallback,
|
|
1710
|
+
scanDriveFolderForSpreadsheets,
|
|
1711
|
+
syncDriveImages,
|
|
1346
1712
|
updateSpreadsheetWithLocalChanges,
|
|
1347
1713
|
validateCredentials,
|
|
1348
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
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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,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"}
|
package/dist/index.js
CHANGED
|
@@ -42,17 +42,22 @@ __export(index_exports, {
|
|
|
42
42
|
getGoogleTranslateCode: () => getGoogleTranslateCode,
|
|
43
43
|
getLanguagePrefix: () => getLanguagePrefix,
|
|
44
44
|
getLocaleDisplayName: () => getLocaleDisplayName,
|
|
45
|
+
getMultipleSpreadSheetsData: () => getMultipleSpreadSheetsData,
|
|
45
46
|
getNormalizedLocaleForHeader: () => getNormalizedLocaleForHeader,
|
|
46
47
|
getOriginalHeaderForLocale: () => getOriginalHeaderForLocale,
|
|
47
48
|
getSpreadSheetData: () => getSpreadSheetData,
|
|
48
49
|
getTranslationSummary: () => getTranslationSummary,
|
|
49
50
|
handleBidirectionalSync: () => handleBidirectionalSync,
|
|
50
51
|
isValidLocale: () => isValidLocale,
|
|
52
|
+
manageDriveTranslations: () => manageDriveTranslations,
|
|
53
|
+
mergeMultipleTranslationData: () => mergeMultipleTranslationData,
|
|
51
54
|
mergeSheets: () => mergeSheets,
|
|
52
55
|
normalizeLocaleCode: () => normalizeLocaleCode,
|
|
53
56
|
processRawRows: () => processRawRows,
|
|
54
57
|
readPublicSheet: () => readPublicSheet,
|
|
55
58
|
resolveLocaleWithFallback: () => resolveLocaleWithFallback,
|
|
59
|
+
scanDriveFolderForSpreadsheets: () => scanDriveFolderForSpreadsheets,
|
|
60
|
+
syncDriveImages: () => syncDriveImages,
|
|
56
61
|
updateSpreadsheetWithLocalChanges: () => updateSpreadsheetWithLocalChanges,
|
|
57
62
|
validateCredentials: () => validateCredentials,
|
|
58
63
|
validateEnv: () => validateEnv,
|
|
@@ -1383,6 +1388,367 @@ function mergeSheets(translations, locale, sheetNames) {
|
|
|
1383
1388
|
return merged;
|
|
1384
1389
|
}
|
|
1385
1390
|
|
|
1391
|
+
// src/utils/multiSpreadsheetMerger.ts
|
|
1392
|
+
function mergeMultipleTranslationData(results, mergeStrategy = "later-wins") {
|
|
1393
|
+
const merged = {};
|
|
1394
|
+
for (const result of results) {
|
|
1395
|
+
for (const [locale, sheets] of Object.entries(result)) {
|
|
1396
|
+
if (!merged[locale]) {
|
|
1397
|
+
merged[locale] = {};
|
|
1398
|
+
}
|
|
1399
|
+
for (const [sheet, keys] of Object.entries(sheets)) {
|
|
1400
|
+
if (!merged[locale][sheet]) {
|
|
1401
|
+
merged[locale][sheet] = {};
|
|
1402
|
+
}
|
|
1403
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
1404
|
+
if (mergeStrategy === "first-wins" && key in merged[locale][sheet]) {
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
merged[locale][sheet][key] = value;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return merged;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// src/getMultipleSpreadSheetsData.ts
|
|
1416
|
+
async function getMultipleSpreadSheetsData(docTitles, options = {}) {
|
|
1417
|
+
const { spreadsheetIds, mergeStrategy = "later-wins", ...baseOptions } = options;
|
|
1418
|
+
if (!spreadsheetIds || spreadsheetIds.length === 0) {
|
|
1419
|
+
return getSpreadSheetData(docTitles, baseOptions);
|
|
1420
|
+
}
|
|
1421
|
+
console.log(`[getMultipleSpreadSheetsData] Fetching ${spreadsheetIds.length} spreadsheets...`);
|
|
1422
|
+
const results = [];
|
|
1423
|
+
for (let i = 0; i < spreadsheetIds.length; i++) {
|
|
1424
|
+
const id = spreadsheetIds[i];
|
|
1425
|
+
console.log(`[getMultipleSpreadSheetsData] (${i + 1}/${spreadsheetIds.length}) "${id}"...`);
|
|
1426
|
+
const result = await getSpreadSheetData(docTitles, { ...baseOptions, spreadsheetId: id });
|
|
1427
|
+
results.push(result);
|
|
1428
|
+
}
|
|
1429
|
+
return mergeMultipleTranslationData(results, mergeStrategy);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// src/utils/driveFolderScanner.ts
|
|
1433
|
+
var import_google_auth_library2 = require("google-auth-library");
|
|
1434
|
+
var SPREADSHEET_MIME = "application/vnd.google-apps.spreadsheet";
|
|
1435
|
+
var FOLDER_MIME = "application/vnd.google-apps.folder";
|
|
1436
|
+
var DRIVE_FILES_URL = "https://www.googleapis.com/drive/v3/files";
|
|
1437
|
+
async function getAccessToken(credentials) {
|
|
1438
|
+
const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
|
|
1439
|
+
const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
|
|
1440
|
+
if (!clientEmail || !privateKey) {
|
|
1441
|
+
throw new Error(
|
|
1442
|
+
"Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
const normalizedKey = privateKey.replace(/\\n/g, "\n");
|
|
1446
|
+
const auth = new import_google_auth_library2.GoogleAuth({
|
|
1447
|
+
credentials: { client_email: clientEmail, private_key: normalizedKey },
|
|
1448
|
+
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
1449
|
+
});
|
|
1450
|
+
const client = await auth.getClient();
|
|
1451
|
+
const tokenResponse = await client.getAccessToken();
|
|
1452
|
+
return tokenResponse.token;
|
|
1453
|
+
}
|
|
1454
|
+
async function listFilesInFolder(folderId, mimeType, token) {
|
|
1455
|
+
const results = [];
|
|
1456
|
+
let pageToken;
|
|
1457
|
+
do {
|
|
1458
|
+
const query = `'${folderId}' in parents and mimeType = '${mimeType}' and trashed = false`;
|
|
1459
|
+
const params = new URLSearchParams({
|
|
1460
|
+
q: query,
|
|
1461
|
+
fields: "nextPageToken,files(id,name,mimeType,modifiedTime,parents)",
|
|
1462
|
+
pageSize: "1000"
|
|
1463
|
+
});
|
|
1464
|
+
if (pageToken) params.set("pageToken", pageToken);
|
|
1465
|
+
const response = await fetch(`${DRIVE_FILES_URL}?${params.toString()}`, {
|
|
1466
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1467
|
+
});
|
|
1468
|
+
if (!response.ok) {
|
|
1469
|
+
const text = await response.text();
|
|
1470
|
+
throw new Error(
|
|
1471
|
+
`Drive API error ${response.status}: ${text}`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
const data = await response.json();
|
|
1475
|
+
results.push(...data.files);
|
|
1476
|
+
pageToken = data.nextPageToken;
|
|
1477
|
+
} while (pageToken);
|
|
1478
|
+
return results;
|
|
1479
|
+
}
|
|
1480
|
+
async function scanFolder(folderId, folderPath, token, recursive, nameFilter, seen = /* @__PURE__ */ new Set()) {
|
|
1481
|
+
console.log(`[driveFolderScanner] Scanning folder: ${folderId} (path: "${folderPath}")`);
|
|
1482
|
+
const spreadsheets = await listFilesInFolder(folderId, SPREADSHEET_MIME, token);
|
|
1483
|
+
const results = [];
|
|
1484
|
+
for (const file of spreadsheets) {
|
|
1485
|
+
if (seen.has(file.id)) continue;
|
|
1486
|
+
seen.add(file.id);
|
|
1487
|
+
if (nameFilter && !nameFilter.test(file.name)) continue;
|
|
1488
|
+
results.push({
|
|
1489
|
+
id: file.id,
|
|
1490
|
+
name: file.name,
|
|
1491
|
+
folderPath,
|
|
1492
|
+
mimeType: file.mimeType,
|
|
1493
|
+
modifiedTime: file.modifiedTime
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
if (recursive) {
|
|
1497
|
+
const subfolders = await listFilesInFolder(folderId, FOLDER_MIME, token);
|
|
1498
|
+
for (const folder of subfolders) {
|
|
1499
|
+
const subPath = folderPath ? `${folderPath}/${folder.name}` : folder.name;
|
|
1500
|
+
const subResults = await scanFolder(
|
|
1501
|
+
folder.id,
|
|
1502
|
+
subPath,
|
|
1503
|
+
token,
|
|
1504
|
+
recursive,
|
|
1505
|
+
nameFilter,
|
|
1506
|
+
seen
|
|
1507
|
+
);
|
|
1508
|
+
results.push(...subResults);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
return results;
|
|
1512
|
+
}
|
|
1513
|
+
async function scanDriveFolderForSpreadsheets(options) {
|
|
1514
|
+
const { folderId, recursive = true, nameFilter, credentials } = options;
|
|
1515
|
+
const token = await getAccessToken(credentials);
|
|
1516
|
+
return scanFolder(folderId, "", token, recursive, nameFilter);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/utils/driveImageSync.ts
|
|
1520
|
+
var import_node_fs6 = require("node:fs");
|
|
1521
|
+
var import_node_path5 = require("node:path");
|
|
1522
|
+
var import_promises4 = require("node:stream/promises");
|
|
1523
|
+
var import_node_stream = require("node:stream");
|
|
1524
|
+
var import_google_auth_library3 = require("google-auth-library");
|
|
1525
|
+
var DEFAULT_IMAGE_MIME_TYPES = [
|
|
1526
|
+
"image/jpeg",
|
|
1527
|
+
"image/jpg",
|
|
1528
|
+
"image/png",
|
|
1529
|
+
"image/webp",
|
|
1530
|
+
"image/avif",
|
|
1531
|
+
"image/gif",
|
|
1532
|
+
"image/svg+xml",
|
|
1533
|
+
"image/tiff",
|
|
1534
|
+
"image/bmp",
|
|
1535
|
+
"image/ico",
|
|
1536
|
+
"image/x-icon"
|
|
1537
|
+
];
|
|
1538
|
+
var FOLDER_MIME2 = "application/vnd.google-apps.folder";
|
|
1539
|
+
var DRIVE_FILES_URL2 = "https://www.googleapis.com/drive/v3/files";
|
|
1540
|
+
async function getAccessToken2(credentials) {
|
|
1541
|
+
const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
|
|
1542
|
+
const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
|
|
1543
|
+
if (!clientEmail || !privateKey) {
|
|
1544
|
+
throw new Error(
|
|
1545
|
+
"Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
const normalizedKey = privateKey.replace(/\\n/g, "\n");
|
|
1549
|
+
const auth = new import_google_auth_library3.GoogleAuth({
|
|
1550
|
+
credentials: { client_email: clientEmail, private_key: normalizedKey },
|
|
1551
|
+
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
1552
|
+
});
|
|
1553
|
+
const client = await auth.getClient();
|
|
1554
|
+
const tokenResponse = await client.getAccessToken();
|
|
1555
|
+
return tokenResponse.token;
|
|
1556
|
+
}
|
|
1557
|
+
async function listFilesInFolder2(folderId, token, mimeTypeFilter) {
|
|
1558
|
+
const results = [];
|
|
1559
|
+
let pageToken;
|
|
1560
|
+
do {
|
|
1561
|
+
const mimeClause = mimeTypeFilter ? ` and mimeType = '${mimeTypeFilter}'` : "";
|
|
1562
|
+
const query = `'${folderId}' in parents${mimeClause} and trashed = false`;
|
|
1563
|
+
const params = new URLSearchParams({
|
|
1564
|
+
q: query,
|
|
1565
|
+
fields: "nextPageToken,files(id,name,mimeType,parents)",
|
|
1566
|
+
pageSize: "1000"
|
|
1567
|
+
});
|
|
1568
|
+
if (pageToken) params.set("pageToken", pageToken);
|
|
1569
|
+
const response = await fetch(`${DRIVE_FILES_URL2}?${params.toString()}`, {
|
|
1570
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1571
|
+
});
|
|
1572
|
+
if (!response.ok) {
|
|
1573
|
+
const text = await response.text();
|
|
1574
|
+
throw new Error(`Drive API error ${response.status}: ${text}`);
|
|
1575
|
+
}
|
|
1576
|
+
const data = await response.json();
|
|
1577
|
+
results.push(...data.files);
|
|
1578
|
+
pageToken = data.nextPageToken;
|
|
1579
|
+
} while (pageToken);
|
|
1580
|
+
return results;
|
|
1581
|
+
}
|
|
1582
|
+
async function collectFiles(folderId, folderRelPath, outputPath, token, allowedMimeTypes, recursive, folderPattern) {
|
|
1583
|
+
console.log(`[driveImageSync] Scanning folder: ${folderId} (path: "${folderRelPath}")`);
|
|
1584
|
+
const allItems = await listFilesInFolder2(folderId, token);
|
|
1585
|
+
const entries = [];
|
|
1586
|
+
for (const item of allItems) {
|
|
1587
|
+
if (item.mimeType === FOLDER_MIME2) {
|
|
1588
|
+
if (!recursive) continue;
|
|
1589
|
+
const subRelPath = folderRelPath ? `${folderRelPath}/${item.name}` : item.name;
|
|
1590
|
+
if (folderPattern && !folderPattern.test(subRelPath)) continue;
|
|
1591
|
+
const subEntries = await collectFiles(
|
|
1592
|
+
item.id,
|
|
1593
|
+
subRelPath,
|
|
1594
|
+
outputPath,
|
|
1595
|
+
token,
|
|
1596
|
+
allowedMimeTypes,
|
|
1597
|
+
recursive,
|
|
1598
|
+
folderPattern
|
|
1599
|
+
);
|
|
1600
|
+
entries.push(...subEntries);
|
|
1601
|
+
} else if (allowedMimeTypes.includes(item.mimeType)) {
|
|
1602
|
+
const localPath = folderRelPath ? (0, import_node_path5.join)(outputPath, folderRelPath, item.name) : (0, import_node_path5.join)(outputPath, item.name);
|
|
1603
|
+
entries.push({ id: item.id, name: item.name, localPath, mimeType: item.mimeType });
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return entries;
|
|
1607
|
+
}
|
|
1608
|
+
async function downloadFile(fileId, localPath, token) {
|
|
1609
|
+
const url = `${DRIVE_FILES_URL2}/${fileId}?alt=media`;
|
|
1610
|
+
const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
1611
|
+
if (!response.ok) {
|
|
1612
|
+
throw new Error(`Failed to download ${fileId}: ${response.status}`);
|
|
1613
|
+
}
|
|
1614
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path5.dirname)(localPath), { recursive: true });
|
|
1615
|
+
const dest = (0, import_node_fs6.createWriteStream)(localPath);
|
|
1616
|
+
await (0, import_promises4.pipeline)(import_node_stream.Readable.fromWeb(response.body), dest);
|
|
1617
|
+
}
|
|
1618
|
+
function collectLocalFiles(dir, base) {
|
|
1619
|
+
const results = [];
|
|
1620
|
+
if (!(0, import_node_fs6.existsSync)(dir)) return results;
|
|
1621
|
+
for (const entry of (0, import_node_fs6.readdirSync)(dir)) {
|
|
1622
|
+
const fullPath = (0, import_node_path5.join)(dir, entry);
|
|
1623
|
+
const stat = (0, import_node_fs6.statSync)(fullPath);
|
|
1624
|
+
if (stat.isDirectory()) {
|
|
1625
|
+
results.push(...collectLocalFiles(fullPath, base));
|
|
1626
|
+
} else {
|
|
1627
|
+
results.push(fullPath);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return results;
|
|
1631
|
+
}
|
|
1632
|
+
async function runConcurrent(tasks, concurrency) {
|
|
1633
|
+
const results = [];
|
|
1634
|
+
for (let i = 0; i < tasks.length; i += concurrency) {
|
|
1635
|
+
const batch = tasks.slice(i, i + concurrency).map((t) => t());
|
|
1636
|
+
results.push(...await Promise.all(batch));
|
|
1637
|
+
}
|
|
1638
|
+
return results;
|
|
1639
|
+
}
|
|
1640
|
+
async function syncDriveImages(options) {
|
|
1641
|
+
const {
|
|
1642
|
+
folderId,
|
|
1643
|
+
outputPath,
|
|
1644
|
+
mimeTypes = DEFAULT_IMAGE_MIME_TYPES,
|
|
1645
|
+
recursive = true,
|
|
1646
|
+
folderPattern,
|
|
1647
|
+
credentials,
|
|
1648
|
+
cleanSync = false,
|
|
1649
|
+
concurrency = 3
|
|
1650
|
+
} = options;
|
|
1651
|
+
const token = await getAccessToken2(credentials);
|
|
1652
|
+
(0, import_node_fs6.mkdirSync)(outputPath, { recursive: true });
|
|
1653
|
+
const entries = await collectFiles(
|
|
1654
|
+
folderId,
|
|
1655
|
+
"",
|
|
1656
|
+
outputPath,
|
|
1657
|
+
token,
|
|
1658
|
+
mimeTypes,
|
|
1659
|
+
recursive,
|
|
1660
|
+
folderPattern
|
|
1661
|
+
);
|
|
1662
|
+
const downloaded = [];
|
|
1663
|
+
const skipped = [];
|
|
1664
|
+
const errors = [];
|
|
1665
|
+
const tasks = entries.map((entry) => async () => {
|
|
1666
|
+
if ((0, import_node_fs6.existsSync)(entry.localPath)) {
|
|
1667
|
+
console.log(`[driveImageSync] Skipping (exists): ${entry.localPath}`);
|
|
1668
|
+
skipped.push(entry.localPath);
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
console.log(`[driveImageSync] Downloading: ${entry.localPath}`);
|
|
1672
|
+
try {
|
|
1673
|
+
await downloadFile(entry.id, entry.localPath, token);
|
|
1674
|
+
downloaded.push(entry.localPath);
|
|
1675
|
+
} catch (err) {
|
|
1676
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1677
|
+
console.error(`[driveImageSync] Error downloading ${entry.localPath}: ${msg}`);
|
|
1678
|
+
errors.push(entry.localPath);
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
await runConcurrent(tasks, concurrency);
|
|
1682
|
+
const deleted = [];
|
|
1683
|
+
if (cleanSync) {
|
|
1684
|
+
const driveLocalPaths = new Set(entries.map((e) => e.localPath));
|
|
1685
|
+
const localFiles = collectLocalFiles(outputPath, outputPath);
|
|
1686
|
+
for (const localFile of localFiles) {
|
|
1687
|
+
if (!driveLocalPaths.has(localFile)) {
|
|
1688
|
+
console.log(`[driveImageSync] Deleting (not in Drive): ${localFile}`);
|
|
1689
|
+
(0, import_node_fs6.unlinkSync)(localFile);
|
|
1690
|
+
deleted.push(localFile);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
console.log(
|
|
1695
|
+
`[driveImageSync] Synced ${downloaded.length} files, skipped ${skipped.length}, deleted ${deleted.length}, errors ${errors.length}`
|
|
1696
|
+
);
|
|
1697
|
+
return { downloaded, skipped, deleted, errors };
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// src/utils/getDriveTranslations.ts
|
|
1701
|
+
async function manageDriveTranslations(options) {
|
|
1702
|
+
const {
|
|
1703
|
+
driveFolderId,
|
|
1704
|
+
scanForSpreadsheets = true,
|
|
1705
|
+
spreadsheetIds: explicitIds = [],
|
|
1706
|
+
spreadsheetNameFilter,
|
|
1707
|
+
syncImages = false,
|
|
1708
|
+
imageOutputPath,
|
|
1709
|
+
imageSyncOptions,
|
|
1710
|
+
translationOptions = {},
|
|
1711
|
+
docTitles
|
|
1712
|
+
} = options;
|
|
1713
|
+
if (syncImages && !imageOutputPath) {
|
|
1714
|
+
throw new Error(
|
|
1715
|
+
"[manageDriveTranslations] imageOutputPath is required when syncImages is true"
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
const discoveredIds = [];
|
|
1719
|
+
const discoveredNames = /* @__PURE__ */ new Map();
|
|
1720
|
+
if (driveFolderId && scanForSpreadsheets) {
|
|
1721
|
+
const scanOptions = { folderId: driveFolderId };
|
|
1722
|
+
const discovered = await scanDriveFolderForSpreadsheets(scanOptions);
|
|
1723
|
+
console.log(
|
|
1724
|
+
`[manageDriveTranslations] Found ${discovered.length} spreadsheet(s) in Drive folder`
|
|
1725
|
+
);
|
|
1726
|
+
for (const file of discovered) {
|
|
1727
|
+
discoveredIds.push(file.id);
|
|
1728
|
+
discoveredNames.set(file.id, file.name);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
const allIds = [.../* @__PURE__ */ new Set([...discoveredIds, ...explicitIds])];
|
|
1732
|
+
const filteredIds = spreadsheetNameFilter ? allIds.filter((id) => {
|
|
1733
|
+
const name = discoveredNames.get(id);
|
|
1734
|
+
if (!name) return true;
|
|
1735
|
+
return spreadsheetNameFilter.test(name);
|
|
1736
|
+
}) : allIds;
|
|
1737
|
+
const translations = await getMultipleSpreadSheetsData(docTitles, {
|
|
1738
|
+
...translationOptions,
|
|
1739
|
+
spreadsheetIds: filteredIds.length > 0 ? filteredIds : void 0
|
|
1740
|
+
});
|
|
1741
|
+
let imageSync;
|
|
1742
|
+
if (syncImages && driveFolderId && imageOutputPath) {
|
|
1743
|
+
imageSync = await syncDriveImages({
|
|
1744
|
+
...imageSyncOptions,
|
|
1745
|
+
folderId: driveFolderId,
|
|
1746
|
+
outputPath: imageOutputPath
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
return { translations, spreadsheetIds: filteredIds, imageSync };
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1386
1752
|
// src/index.ts
|
|
1387
1753
|
var index_default = getSpreadSheetData;
|
|
1388
1754
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1398,17 +1764,22 @@ var index_default = getSpreadSheetData;
|
|
|
1398
1764
|
getGoogleTranslateCode,
|
|
1399
1765
|
getLanguagePrefix,
|
|
1400
1766
|
getLocaleDisplayName,
|
|
1767
|
+
getMultipleSpreadSheetsData,
|
|
1401
1768
|
getNormalizedLocaleForHeader,
|
|
1402
1769
|
getOriginalHeaderForLocale,
|
|
1403
1770
|
getSpreadSheetData,
|
|
1404
1771
|
getTranslationSummary,
|
|
1405
1772
|
handleBidirectionalSync,
|
|
1406
1773
|
isValidLocale,
|
|
1774
|
+
manageDriveTranslations,
|
|
1775
|
+
mergeMultipleTranslationData,
|
|
1407
1776
|
mergeSheets,
|
|
1408
1777
|
normalizeLocaleCode,
|
|
1409
1778
|
processRawRows,
|
|
1410
1779
|
readPublicSheet,
|
|
1411
1780
|
resolveLocaleWithFallback,
|
|
1781
|
+
scanDriveFolderForSpreadsheets,
|
|
1782
|
+
syncDriveImages,
|
|
1412
1783
|
updateSpreadsheetWithLocalChanges,
|
|
1413
1784
|
validateCredentials,
|
|
1414
1785
|
validateEnv,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { GoogleEnvVars } from '../types';
|
|
2
|
+
export interface DriveSpreadsheetFile {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
/** Relative path within the Drive folder (e.g., "subproject/translations") */
|
|
6
|
+
folderPath: string;
|
|
7
|
+
mimeType: string;
|
|
8
|
+
modifiedTime?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ScanDriveFolderOptions {
|
|
11
|
+
/** Google Drive folder ID to scan */
|
|
12
|
+
folderId: string;
|
|
13
|
+
/** Whether to recursively scan subfolders (default: true) */
|
|
14
|
+
recursive?: boolean;
|
|
15
|
+
/** Only return spreadsheets with names matching this pattern (optional) */
|
|
16
|
+
nameFilter?: RegExp;
|
|
17
|
+
/** Google service account credentials (falls back to env vars) */
|
|
18
|
+
credentials?: GoogleEnvVars;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Scans a Google Drive folder for Google Spreadsheet files.
|
|
22
|
+
* Returns all spreadsheets found (recursively by default).
|
|
23
|
+
* Uses Drive API v3 via authenticated fetch (no googleapis package needed).
|
|
24
|
+
*/
|
|
25
|
+
export declare function scanDriveFolderForSpreadsheets(options: ScanDriveFolderOptions): Promise<DriveSpreadsheetFile[]>;
|
|
26
|
+
//# sourceMappingURL=driveFolderScanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driveFolderScanner.d.ts","sourceRoot":"","sources":["../../src/utils/driveFolderScanner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,WAAW,CAAC,EAAE,aAAa,CAAC;CAC7B;AA8HD;;;;GAIG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAKjC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { GoogleEnvVars } from '../types';
|
|
2
|
+
export interface DriveImageSyncOptions {
|
|
3
|
+
/** Google Drive root folder ID to sync images from */
|
|
4
|
+
folderId: string;
|
|
5
|
+
/** Local directory to download images into (will be created if missing) */
|
|
6
|
+
outputPath: string;
|
|
7
|
+
/** Only download files matching these MIME types (default: all image types) */
|
|
8
|
+
mimeTypes?: string[];
|
|
9
|
+
/** Whether to recursively sync subfolders (default: true) */
|
|
10
|
+
recursive?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Subfolder filter pattern. If provided, only subfolders whose relative path
|
|
13
|
+
* (from the root folderId) matches this pattern will be synced.
|
|
14
|
+
* The pattern is tested against the full relative path, e.g. "projects/icons".
|
|
15
|
+
* Useful for patterns like `/^projects\//` or `/icons$/`.
|
|
16
|
+
*/
|
|
17
|
+
folderPattern?: RegExp;
|
|
18
|
+
/** Google service account credentials (falls back to env vars) */
|
|
19
|
+
credentials?: GoogleEnvVars;
|
|
20
|
+
/** If true, delete local files that no longer exist in Drive (default: false) */
|
|
21
|
+
cleanSync?: boolean;
|
|
22
|
+
/** Max concurrent downloads (default: 3) */
|
|
23
|
+
concurrency?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface DriveImageSyncResult {
|
|
26
|
+
downloaded: string[];
|
|
27
|
+
skipped: string[];
|
|
28
|
+
deleted: string[];
|
|
29
|
+
errors: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Syncs images from a Google Drive folder to a local directory.
|
|
33
|
+
* Preserves subfolder structure. Uses Drive API v3 via fetch.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* await syncDriveImages({
|
|
37
|
+
* folderId: 'your-drive-folder-id',
|
|
38
|
+
* outputPath: './src/assets/remote-images',
|
|
39
|
+
* recursive: true,
|
|
40
|
+
* credentials: { GOOGLE_CLIENT_EMAIL: '...', GOOGLE_PRIVATE_KEY: '...', GOOGLE_SPREADSHEET_ID: '' }
|
|
41
|
+
* });
|
|
42
|
+
*/
|
|
43
|
+
export declare function syncDriveImages(options: DriveImageSyncOptions): Promise<DriveImageSyncResult>;
|
|
44
|
+
//# sourceMappingURL=driveImageSync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driveImageSync.d.ts","sourceRoot":"","sources":["../../src/utils/driveImageSync.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,WAAW,qBAAqB;IACpC,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kEAAkE;IAClE,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAoLD;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAoE/B"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { TranslationData } from '../types';
|
|
2
|
+
import type { MultiSpreadsheetOptions } from '../getMultipleSpreadSheetsData';
|
|
3
|
+
import type { DriveImageSyncOptions, DriveImageSyncResult } from './driveImageSync';
|
|
4
|
+
export interface GoogleDriveManagerOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Google Drive folder ID to scan for spreadsheets and/or images.
|
|
7
|
+
* If provided without explicit spreadsheetIds, the folder is scanned
|
|
8
|
+
* automatically for spreadsheet files.
|
|
9
|
+
*/
|
|
10
|
+
driveFolderId?: string;
|
|
11
|
+
/**
|
|
12
|
+
* When true, scans driveFolderId for all Google Spreadsheet files and
|
|
13
|
+
* fetches translations from each. Requires driveFolderId. (default: true when driveFolderId set)
|
|
14
|
+
*/
|
|
15
|
+
scanForSpreadsheets?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Explicit list of spreadsheet IDs to fetch from.
|
|
18
|
+
* If provided together with driveFolderId + scanForSpreadsheets, the
|
|
19
|
+
* explicit list is merged with the discovered ones (deduped).
|
|
20
|
+
*/
|
|
21
|
+
spreadsheetIds?: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Optional filter: only process spreadsheets whose name matches this pattern.
|
|
24
|
+
* Useful when the Drive folder contains non-translation spreadsheets.
|
|
25
|
+
* @example /^translations-/i
|
|
26
|
+
*/
|
|
27
|
+
spreadsheetNameFilter?: RegExp;
|
|
28
|
+
/**
|
|
29
|
+
* When true, also sync images from driveFolderId to imageOutputPath.
|
|
30
|
+
* Requires driveFolderId. (default: false)
|
|
31
|
+
*/
|
|
32
|
+
syncImages?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Local directory to download Drive images into.
|
|
35
|
+
* Required when syncImages: true.
|
|
36
|
+
* @example './src/assets/remote-images'
|
|
37
|
+
*/
|
|
38
|
+
imageOutputPath?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Image sync options passed to syncDriveImages (mimeTypes, concurrency, etc.)
|
|
41
|
+
*/
|
|
42
|
+
imageSyncOptions?: Partial<DriveImageSyncOptions>;
|
|
43
|
+
/**
|
|
44
|
+
* Options forwarded to getMultipleSpreadSheetsData (rowLimit, waitSeconds,
|
|
45
|
+
* translationsOutputDir, autoTranslate, etc.)
|
|
46
|
+
*/
|
|
47
|
+
translationOptions?: MultiSpreadsheetOptions;
|
|
48
|
+
/** Sheet names to fetch from each discovered spreadsheet */
|
|
49
|
+
docTitles?: string[];
|
|
50
|
+
}
|
|
51
|
+
export interface GoogleDriveManagerResult {
|
|
52
|
+
translations: TranslationData;
|
|
53
|
+
/** List of spreadsheet IDs that were processed */
|
|
54
|
+
spreadsheetIds: string[];
|
|
55
|
+
/** Image sync result (only present if syncImages: true) */
|
|
56
|
+
imageSync?: DriveImageSyncResult;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Top-level "headless CMS bridge" function.
|
|
60
|
+
*
|
|
61
|
+
* Scans a Google Drive folder for spreadsheets, fetches all translations,
|
|
62
|
+
* optionally syncs images, and returns merged results.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* const result = await manageDriveTranslations({
|
|
66
|
+
* driveFolderId: 'your-folder-id',
|
|
67
|
+
* scanForSpreadsheets: true,
|
|
68
|
+
* spreadsheetNameFilter: /^i18n-/,
|
|
69
|
+
* syncImages: true,
|
|
70
|
+
* imageOutputPath: './src/assets/remote-images',
|
|
71
|
+
* translationOptions: {
|
|
72
|
+
* autoTranslate: false,
|
|
73
|
+
* translationsOutputDir: './src/translations'
|
|
74
|
+
* }
|
|
75
|
+
* });
|
|
76
|
+
* console.log(result.translations);
|
|
77
|
+
* console.log(result.imageSync?.downloaded.length + ' images downloaded');
|
|
78
|
+
*/
|
|
79
|
+
export declare function manageDriveTranslations(options: GoogleDriveManagerOptions): Promise<GoogleDriveManagerResult>;
|
|
80
|
+
//# sourceMappingURL=getDriveTranslations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getDriveTranslations.d.ts","sourceRoot":"","sources":["../../src/utils/getDriveTranslations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAI9E,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAGpF,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAElD;;;OAGG;IACH,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAE7C,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACxC,YAAY,EAAE,eAAe,CAAC;IAC9B,kDAAkD;IAClD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uBAAuB,CAC5C,OAAO,EAAE,yBAAyB,GAChC,OAAO,CAAC,wBAAwB,CAAC,CAkEnC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TranslationData } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Merges multiple TranslationData results (from different spreadsheets) into one.
|
|
4
|
+
* Sheets/keys from later spreadsheets override earlier ones if collisions occur.
|
|
5
|
+
*/
|
|
6
|
+
export declare function mergeMultipleTranslationData(results: TranslationData[], mergeStrategy?: 'later-wins' | 'first-wins'): TranslationData;
|
|
7
|
+
//# sourceMappingURL=multiSpreadsheetMerger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multiSpreadsheetMerger.d.ts","sourceRoot":"","sources":["../../src/utils/multiSpreadsheetMerger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD;;;GAGG;AACH,wBAAgB,4BAA4B,CAC3C,OAAO,EAAE,eAAe,EAAE,EAC1B,aAAa,GAAE,YAAY,GAAG,YAA2B,GACvD,eAAe,CAuBjB"}
|
package/package.json
CHANGED