@el-j/google-sheet-translations 2.2.0-beta.1 → 2.2.0-beta.3
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 +6 -0
- package/dist/esm/index.js +318 -43
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +325 -42
- package/dist/utils/auth.d.ts +44 -4
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/driveFolderScanner.d.ts.map +1 -1
- package/dist/utils/driveImageSync.d.ts +24 -0
- package/dist/utils/driveImageSync.d.ts.map +1 -1
- package/dist/utils/driveProjectIndex.d.ts +74 -0
- package/dist/utils/driveProjectIndex.d.ts.map +1 -0
- package/dist/utils/getDriveTranslations.d.ts +31 -0
- package/dist/utils/getDriveTranslations.d.ts.map +1 -1
- package/dist/utils/localImageUtils.d.ts +105 -0
- package/dist/utils/localImageUtils.d.ts.map +1 -0
- package/dist/utils/spreadsheetCreator.d.ts +2 -2
- package/dist/utils/spreadsheetCreator.d.ts.map +1 -1
- package/dist/utils/validateEnv.d.ts +7 -3
- package/dist/utils/validateEnv.d.ts.map +1 -1
- package/package.json +20 -19
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Google Sheet Translations
|
|
2
2
|
|
|
3
|
+
[](https://github.com/el-j/google-sheet-translations/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/el-j/google-sheet-translations/actions/workflows/release.yml)
|
|
5
|
+
[](https://github.com/el-j/google-sheet-translations/actions/workflows/docs.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/@el-j/google-sheet-translations)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
3
9
|
A Node.js package for managing translations stored in Google Sheets.
|
|
4
10
|
|
|
5
11
|
## Features
|
package/dist/esm/index.js
CHANGED
|
@@ -4,17 +4,24 @@ import path4 from "node:path";
|
|
|
4
4
|
import { GoogleSpreadsheet as GoogleSpreadsheet2 } from "google-spreadsheet";
|
|
5
5
|
|
|
6
6
|
// src/utils/auth.ts
|
|
7
|
-
import {
|
|
7
|
+
import { GoogleAuth } from "google-auth-library";
|
|
8
8
|
|
|
9
9
|
// src/utils/validateEnv.ts
|
|
10
10
|
function validateCredentials() {
|
|
11
|
+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
12
|
+
return {
|
|
13
|
+
GOOGLE_CLIENT_EMAIL: process.env.GOOGLE_CLIENT_EMAIL ?? "",
|
|
14
|
+
GOOGLE_PRIVATE_KEY: process.env.GOOGLE_PRIVATE_KEY ?? ""
|
|
15
|
+
};
|
|
16
|
+
}
|
|
11
17
|
const requiredVars = ["GOOGLE_CLIENT_EMAIL", "GOOGLE_PRIVATE_KEY"];
|
|
12
18
|
const missing = requiredVars.filter((v) => !process.env[v]);
|
|
13
19
|
if (missing.length > 0) {
|
|
14
20
|
throw new Error(
|
|
15
21
|
`Missing required environment variables: ${missing.join(", ")}
|
|
16
22
|
|
|
17
|
-
Make sure these are set in your .env file or environment
|
|
23
|
+
Make sure these are set in your .env file or environment.
|
|
24
|
+
Alternatively, set GOOGLE_APPLICATION_CREDENTIALS for Workload Identity Federation.`
|
|
18
25
|
);
|
|
19
26
|
}
|
|
20
27
|
return {
|
|
@@ -23,6 +30,21 @@ Make sure these are set in your .env file or environment.`
|
|
|
23
30
|
};
|
|
24
31
|
}
|
|
25
32
|
function validateEnv() {
|
|
33
|
+
const spreadsheetId = process.env.GOOGLE_SPREADSHEET_ID;
|
|
34
|
+
if (!spreadsheetId) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Missing required environment variable: GOOGLE_SPREADSHEET_ID
|
|
37
|
+
|
|
38
|
+
Make sure this is set in your .env file or environment.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
42
|
+
return {
|
|
43
|
+
GOOGLE_CLIENT_EMAIL: process.env.GOOGLE_CLIENT_EMAIL ?? "",
|
|
44
|
+
GOOGLE_PRIVATE_KEY: process.env.GOOGLE_PRIVATE_KEY ?? "",
|
|
45
|
+
GOOGLE_SPREADSHEET_ID: spreadsheetId
|
|
46
|
+
};
|
|
47
|
+
}
|
|
26
48
|
const requiredVars = [
|
|
27
49
|
"GOOGLE_CLIENT_EMAIL",
|
|
28
50
|
"GOOGLE_PRIVATE_KEY",
|
|
@@ -33,24 +55,43 @@ function validateEnv() {
|
|
|
33
55
|
throw new Error(
|
|
34
56
|
`Missing required environment variables: ${missingVars.join(", ")}
|
|
35
57
|
|
|
36
|
-
Make sure these are set in your .env file or environment
|
|
58
|
+
Make sure these are set in your .env file or environment.
|
|
59
|
+
Alternatively, set GOOGLE_APPLICATION_CREDENTIALS for Workload Identity Federation.`
|
|
37
60
|
);
|
|
38
61
|
}
|
|
39
62
|
return {
|
|
40
63
|
GOOGLE_CLIENT_EMAIL: process.env.GOOGLE_CLIENT_EMAIL,
|
|
41
64
|
GOOGLE_PRIVATE_KEY: process.env.GOOGLE_PRIVATE_KEY,
|
|
42
|
-
GOOGLE_SPREADSHEET_ID:
|
|
65
|
+
GOOGLE_SPREADSHEET_ID: spreadsheetId
|
|
43
66
|
};
|
|
44
67
|
}
|
|
45
68
|
|
|
46
69
|
// src/utils/auth.ts
|
|
70
|
+
function normalizePrivateKey(key) {
|
|
71
|
+
let normalized = key;
|
|
72
|
+
const outer = key.trim();
|
|
73
|
+
if (outer.startsWith('"') && outer.endsWith('"') || outer.startsWith("'") && outer.endsWith("'")) {
|
|
74
|
+
normalized = outer.slice(1, -1);
|
|
75
|
+
}
|
|
76
|
+
normalized = normalized.replace(/\\n/g, "\n");
|
|
77
|
+
normalized = normalized.replace(/\r\n/g, "\n");
|
|
78
|
+
return normalized;
|
|
79
|
+
}
|
|
80
|
+
function buildGoogleAuth(scopes, credentials) {
|
|
81
|
+
if (credentials) {
|
|
82
|
+
return new GoogleAuth({ credentials, scopes });
|
|
83
|
+
}
|
|
84
|
+
return new GoogleAuth({ scopes });
|
|
85
|
+
}
|
|
47
86
|
function createAuthClient() {
|
|
87
|
+
if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
88
|
+
return buildGoogleAuth(["https://www.googleapis.com/auth/spreadsheets"]);
|
|
89
|
+
}
|
|
48
90
|
const { GOOGLE_CLIENT_EMAIL, GOOGLE_PRIVATE_KEY } = validateCredentials();
|
|
49
|
-
const normalizedKey = GOOGLE_PRIVATE_KEY
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
scopes: ["https://www.googleapis.com/auth/spreadsheets"]
|
|
91
|
+
const normalizedKey = normalizePrivateKey(GOOGLE_PRIVATE_KEY);
|
|
92
|
+
return buildGoogleAuth(["https://www.googleapis.com/auth/spreadsheets"], {
|
|
93
|
+
client_email: GOOGLE_CLIENT_EMAIL,
|
|
94
|
+
private_key: normalizedKey
|
|
54
95
|
});
|
|
55
96
|
}
|
|
56
97
|
|
|
@@ -1037,7 +1078,6 @@ async function createSpreadsheet(authClient, options = {}) {
|
|
|
1037
1078
|
targetLocales = DEFAULT_TARGET_LOCALES,
|
|
1038
1079
|
seedKeys = STARTER_KEYS
|
|
1039
1080
|
} = options;
|
|
1040
|
-
await authClient.authorize();
|
|
1041
1081
|
const createRes = await withRetry(
|
|
1042
1082
|
() => authClient.request({
|
|
1043
1083
|
url: "https://sheets.googleapis.com/v4/spreadsheets",
|
|
@@ -1359,23 +1399,22 @@ async function getMultipleSpreadSheetsData(docTitles, options = {}) {
|
|
|
1359
1399
|
}
|
|
1360
1400
|
|
|
1361
1401
|
// src/utils/driveFolderScanner.ts
|
|
1362
|
-
import { GoogleAuth } from "google-auth-library";
|
|
1363
1402
|
var SPREADSHEET_MIME = "application/vnd.google-apps.spreadsheet";
|
|
1364
1403
|
var FOLDER_MIME = "application/vnd.google-apps.folder";
|
|
1365
1404
|
var DRIVE_FILES_URL = "https://www.googleapis.com/drive/v3/files";
|
|
1405
|
+
var DRIVE_SCOPES = ["https://www.googleapis.com/auth/drive.readonly"];
|
|
1366
1406
|
async function getAccessToken(credentials) {
|
|
1367
1407
|
const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
|
|
1368
1408
|
const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
|
|
1369
|
-
|
|
1409
|
+
let driveCredentials;
|
|
1410
|
+
if (clientEmail && privateKey) {
|
|
1411
|
+
driveCredentials = { client_email: clientEmail, private_key: normalizePrivateKey(privateKey) };
|
|
1412
|
+
} else if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
1370
1413
|
throw new Error(
|
|
1371
|
-
"Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
|
|
1414
|
+
"Google Drive credentials required: set GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY, or set GOOGLE_APPLICATION_CREDENTIALS for Workload Identity Federation."
|
|
1372
1415
|
);
|
|
1373
1416
|
}
|
|
1374
|
-
const
|
|
1375
|
-
const auth = new GoogleAuth({
|
|
1376
|
-
credentials: { client_email: clientEmail, private_key: normalizedKey },
|
|
1377
|
-
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
1378
|
-
});
|
|
1417
|
+
const auth = buildGoogleAuth(DRIVE_SCOPES, driveCredentials);
|
|
1379
1418
|
const client = await auth.getClient();
|
|
1380
1419
|
const tokenResponse = await client.getAccessToken();
|
|
1381
1420
|
return tokenResponse.token;
|
|
@@ -1450,7 +1489,6 @@ import { createWriteStream, mkdirSync, existsSync, readdirSync, unlinkSync, stat
|
|
|
1450
1489
|
import { join, dirname } from "node:path";
|
|
1451
1490
|
import { pipeline } from "node:stream/promises";
|
|
1452
1491
|
import { Readable } from "node:stream";
|
|
1453
|
-
import { GoogleAuth as GoogleAuth2 } from "google-auth-library";
|
|
1454
1492
|
var DEFAULT_IMAGE_MIME_TYPES = [
|
|
1455
1493
|
"image/jpeg",
|
|
1456
1494
|
"image/jpg",
|
|
@@ -1466,19 +1504,27 @@ var DEFAULT_IMAGE_MIME_TYPES = [
|
|
|
1466
1504
|
];
|
|
1467
1505
|
var FOLDER_MIME2 = "application/vnd.google-apps.folder";
|
|
1468
1506
|
var DRIVE_FILES_URL2 = "https://www.googleapis.com/drive/v3/files";
|
|
1507
|
+
function normalizeExtension(name) {
|
|
1508
|
+
const dot = name.lastIndexOf(".");
|
|
1509
|
+
if (dot === -1) return name;
|
|
1510
|
+
const base = name.slice(0, dot);
|
|
1511
|
+
let ext = name.slice(dot + 1).toLowerCase();
|
|
1512
|
+
if (ext === "jpeg") ext = "jpg";
|
|
1513
|
+
return `${base}.${ext}`;
|
|
1514
|
+
}
|
|
1515
|
+
var DRIVE_SCOPES2 = ["https://www.googleapis.com/auth/drive.readonly"];
|
|
1469
1516
|
async function getAccessToken2(credentials) {
|
|
1470
1517
|
const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
|
|
1471
1518
|
const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
|
|
1472
|
-
|
|
1519
|
+
let driveCredentials;
|
|
1520
|
+
if (clientEmail && privateKey) {
|
|
1521
|
+
driveCredentials = { client_email: clientEmail, private_key: normalizePrivateKey(privateKey) };
|
|
1522
|
+
} else if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
1473
1523
|
throw new Error(
|
|
1474
|
-
"Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
|
|
1524
|
+
"Google Drive credentials required: set GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY, or set GOOGLE_APPLICATION_CREDENTIALS for Workload Identity Federation."
|
|
1475
1525
|
);
|
|
1476
1526
|
}
|
|
1477
|
-
const
|
|
1478
|
-
const auth = new GoogleAuth2({
|
|
1479
|
-
credentials: { client_email: clientEmail, private_key: normalizedKey },
|
|
1480
|
-
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
1481
|
-
});
|
|
1527
|
+
const auth = buildGoogleAuth(DRIVE_SCOPES2, driveCredentials);
|
|
1482
1528
|
const client = await auth.getClient();
|
|
1483
1529
|
const tokenResponse = await client.getAccessToken();
|
|
1484
1530
|
return tokenResponse.token;
|
|
@@ -1491,7 +1537,7 @@ async function listFilesInFolder2(folderId, token, mimeTypeFilter) {
|
|
|
1491
1537
|
const query = `'${folderId}' in parents${mimeClause} and trashed = false`;
|
|
1492
1538
|
const params = new URLSearchParams({
|
|
1493
1539
|
q: query,
|
|
1494
|
-
fields: "nextPageToken,files(id,name,mimeType,parents)",
|
|
1540
|
+
fields: "nextPageToken,files(id,name,mimeType,modifiedTime,parents)",
|
|
1495
1541
|
pageSize: "1000"
|
|
1496
1542
|
});
|
|
1497
1543
|
if (pageToken) params.set("pageToken", pageToken);
|
|
@@ -1508,7 +1554,7 @@ async function listFilesInFolder2(folderId, token, mimeTypeFilter) {
|
|
|
1508
1554
|
} while (pageToken);
|
|
1509
1555
|
return results;
|
|
1510
1556
|
}
|
|
1511
|
-
async function collectFiles(folderId, folderRelPath, outputPath, token, allowedMimeTypes, recursive, folderPattern) {
|
|
1557
|
+
async function collectFiles(folderId, folderRelPath, outputPath, token, allowedMimeTypes, recursive, folderPattern, normalizeExts = true) {
|
|
1512
1558
|
console.log(`[driveImageSync] Scanning folder: ${folderId} (path: "${folderRelPath}")`);
|
|
1513
1559
|
const allItems = await listFilesInFolder2(folderId, token);
|
|
1514
1560
|
const entries = [];
|
|
@@ -1524,12 +1570,20 @@ async function collectFiles(folderId, folderRelPath, outputPath, token, allowedM
|
|
|
1524
1570
|
token,
|
|
1525
1571
|
allowedMimeTypes,
|
|
1526
1572
|
recursive,
|
|
1527
|
-
folderPattern
|
|
1573
|
+
folderPattern,
|
|
1574
|
+
normalizeExts
|
|
1528
1575
|
);
|
|
1529
1576
|
entries.push(...subEntries);
|
|
1530
1577
|
} else if (allowedMimeTypes.includes(item.mimeType)) {
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1578
|
+
const localName = normalizeExts ? normalizeExtension(item.name) : item.name;
|
|
1579
|
+
const localPath = folderRelPath ? join(outputPath, folderRelPath, localName) : join(outputPath, localName);
|
|
1580
|
+
entries.push({
|
|
1581
|
+
id: item.id,
|
|
1582
|
+
name: item.name,
|
|
1583
|
+
localPath,
|
|
1584
|
+
mimeType: item.mimeType,
|
|
1585
|
+
driveModifiedTime: item.modifiedTime
|
|
1586
|
+
});
|
|
1533
1587
|
}
|
|
1534
1588
|
}
|
|
1535
1589
|
return entries;
|
|
@@ -1575,7 +1629,9 @@ async function syncDriveImages(options) {
|
|
|
1575
1629
|
folderPattern,
|
|
1576
1630
|
credentials,
|
|
1577
1631
|
cleanSync = false,
|
|
1578
|
-
concurrency = 3
|
|
1632
|
+
concurrency = 3,
|
|
1633
|
+
incrementalSync = true,
|
|
1634
|
+
normalizeExtensions = true
|
|
1579
1635
|
} = options;
|
|
1580
1636
|
const token = await getAccessToken2(credentials);
|
|
1581
1637
|
mkdirSync(outputPath, { recursive: true });
|
|
@@ -1586,16 +1642,33 @@ async function syncDriveImages(options) {
|
|
|
1586
1642
|
token,
|
|
1587
1643
|
mimeTypes,
|
|
1588
1644
|
recursive,
|
|
1589
|
-
folderPattern
|
|
1645
|
+
folderPattern,
|
|
1646
|
+
normalizeExtensions
|
|
1590
1647
|
);
|
|
1591
1648
|
const downloaded = [];
|
|
1592
1649
|
const skipped = [];
|
|
1593
1650
|
const errors = [];
|
|
1594
1651
|
const tasks = entries.map((entry) => async () => {
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1652
|
+
const localExists = existsSync(entry.localPath);
|
|
1653
|
+
if (localExists) {
|
|
1654
|
+
if (incrementalSync && entry.driveModifiedTime) {
|
|
1655
|
+
try {
|
|
1656
|
+
const localMtimeMs = statSync(entry.localPath).mtimeMs;
|
|
1657
|
+
const driveMtimeMs = new Date(entry.driveModifiedTime).getTime();
|
|
1658
|
+
if (driveMtimeMs <= localMtimeMs) {
|
|
1659
|
+
console.log(`[driveImageSync] Skipping (up to date): ${entry.localPath}`);
|
|
1660
|
+
skipped.push(entry.localPath);
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
console.log(`[driveImageSync] Re-downloading (changed in Drive): ${entry.localPath}`);
|
|
1664
|
+
} catch {
|
|
1665
|
+
console.log(`[driveImageSync] Downloading (could not stat local): ${entry.localPath}`);
|
|
1666
|
+
}
|
|
1667
|
+
} else {
|
|
1668
|
+
console.log(`[driveImageSync] Skipping (exists): ${entry.localPath}`);
|
|
1669
|
+
skipped.push(entry.localPath);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1599
1672
|
}
|
|
1600
1673
|
console.log(`[driveImageSync] Downloading: ${entry.localPath}`);
|
|
1601
1674
|
try {
|
|
@@ -1626,7 +1699,134 @@ async function syncDriveImages(options) {
|
|
|
1626
1699
|
return { downloaded, skipped, deleted, errors };
|
|
1627
1700
|
}
|
|
1628
1701
|
|
|
1702
|
+
// src/utils/localImageUtils.ts
|
|
1703
|
+
import { promises as fsp } from "node:fs";
|
|
1704
|
+
import { join as join2, extname } from "node:path";
|
|
1705
|
+
async function walkDirectory(dir, options) {
|
|
1706
|
+
const { extensions } = options ?? {};
|
|
1707
|
+
const lowerExts = extensions?.map((e) => e.toLowerCase());
|
|
1708
|
+
async function go(current) {
|
|
1709
|
+
const entries = await fsp.readdir(current, { withFileTypes: true, encoding: "utf8" });
|
|
1710
|
+
const results = [];
|
|
1711
|
+
for (const entry of entries) {
|
|
1712
|
+
const full = join2(current, entry.name);
|
|
1713
|
+
if (entry.isDirectory()) {
|
|
1714
|
+
results.push(...await go(full));
|
|
1715
|
+
} else if (entry.isFile()) {
|
|
1716
|
+
if (!lowerExts || lowerExts.includes(extname(entry.name).toLowerCase())) {
|
|
1717
|
+
results.push(full);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
return results;
|
|
1722
|
+
}
|
|
1723
|
+
return go(dir);
|
|
1724
|
+
}
|
|
1725
|
+
var DEFAULT_IMAGE_EXTENSIONS = [
|
|
1726
|
+
".jpg",
|
|
1727
|
+
".jpeg",
|
|
1728
|
+
".png",
|
|
1729
|
+
".webp",
|
|
1730
|
+
".avif",
|
|
1731
|
+
".gif",
|
|
1732
|
+
".svg",
|
|
1733
|
+
".tiff",
|
|
1734
|
+
".bmp",
|
|
1735
|
+
".ico"
|
|
1736
|
+
];
|
|
1737
|
+
async function validateImageDirectory(options) {
|
|
1738
|
+
const {
|
|
1739
|
+
rootDir,
|
|
1740
|
+
imageExtensions = DEFAULT_IMAGE_EXTENSIONS,
|
|
1741
|
+
allowRootFiles = false,
|
|
1742
|
+
expectedSubfolders = []
|
|
1743
|
+
} = options;
|
|
1744
|
+
const errors = [];
|
|
1745
|
+
const warnings = [];
|
|
1746
|
+
const rootFiles = [];
|
|
1747
|
+
const subfolders = [];
|
|
1748
|
+
const lowerExts = imageExtensions.map((e) => e.toLowerCase());
|
|
1749
|
+
let entries;
|
|
1750
|
+
try {
|
|
1751
|
+
entries = await fsp.readdir(rootDir, { withFileTypes: true, encoding: "utf8" });
|
|
1752
|
+
} catch (err) {
|
|
1753
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1754
|
+
return {
|
|
1755
|
+
valid: false,
|
|
1756
|
+
errors: [`Could not read directory "${rootDir}": ${msg}`],
|
|
1757
|
+
warnings,
|
|
1758
|
+
rootFiles,
|
|
1759
|
+
subfolders
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
for (const entry of entries) {
|
|
1763
|
+
if (entry.isDirectory()) {
|
|
1764
|
+
subfolders.push(entry.name);
|
|
1765
|
+
} else if (entry.isFile()) {
|
|
1766
|
+
if (lowerExts.includes(extname(entry.name).toLowerCase())) {
|
|
1767
|
+
rootFiles.push(entry.name);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (!allowRootFiles && rootFiles.length > 0) {
|
|
1772
|
+
errors.push(
|
|
1773
|
+
`Image files found directly in "${rootDir}" \u2014 the folder structure may have been flattened during sync. Files: ${rootFiles.slice(0, 5).join(", ")}${rootFiles.length > 5 ? ` \u2026 (+${rootFiles.length - 5} more)` : ""}`
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
if (!allowRootFiles && subfolders.length === 0) {
|
|
1777
|
+
errors.push(
|
|
1778
|
+
`No sub-directories found in "${rootDir}". Expected a nested folder structure (e.g. projects/, performances/).`
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
const missing = expectedSubfolders.filter((name) => !subfolders.includes(name));
|
|
1782
|
+
if (missing.length > 0) {
|
|
1783
|
+
warnings.push(
|
|
1784
|
+
`Some expected sub-folders are absent from "${rootDir}": ${missing.join(", ")}`
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
return {
|
|
1788
|
+
valid: errors.length === 0,
|
|
1789
|
+
errors,
|
|
1790
|
+
warnings,
|
|
1791
|
+
rootFiles,
|
|
1792
|
+
subfolders
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1629
1796
|
// src/utils/getDriveTranslations.ts
|
|
1797
|
+
import path6 from "node:path";
|
|
1798
|
+
|
|
1799
|
+
// src/utils/driveProjectIndex.ts
|
|
1800
|
+
import fs6 from "node:fs";
|
|
1801
|
+
import path5 from "node:path";
|
|
1802
|
+
function buildManifest(options) {
|
|
1803
|
+
const locales = Object.keys(options.translations).sort();
|
|
1804
|
+
return {
|
|
1805
|
+
version: "1",
|
|
1806
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1807
|
+
projectName: options.projectName,
|
|
1808
|
+
domain: options.domain,
|
|
1809
|
+
locales,
|
|
1810
|
+
defaultLocale: options.defaultLocale,
|
|
1811
|
+
spreadsheets: options.spreadsheets,
|
|
1812
|
+
outputDirectory: options.outputDirectory,
|
|
1813
|
+
flatten: options.flatten,
|
|
1814
|
+
projectMetadata: options.projectMetadata
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
function writeManifest(manifest, manifestPath) {
|
|
1818
|
+
const dir = path5.dirname(manifestPath);
|
|
1819
|
+
if (!fs6.existsSync(dir)) {
|
|
1820
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
1821
|
+
}
|
|
1822
|
+
fs6.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
1823
|
+
console.log(`[driveProjectIndex] Wrote project manifest \u2192 ${manifestPath}`);
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// src/utils/getDriveTranslations.ts
|
|
1827
|
+
function sanitizeFolderName(name) {
|
|
1828
|
+
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "sheet";
|
|
1829
|
+
}
|
|
1630
1830
|
async function manageDriveTranslations(options) {
|
|
1631
1831
|
const {
|
|
1632
1832
|
driveFolderId,
|
|
@@ -1637,15 +1837,25 @@ async function manageDriveTranslations(options) {
|
|
|
1637
1837
|
imageOutputPath,
|
|
1638
1838
|
imageSyncOptions,
|
|
1639
1839
|
translationOptions = {},
|
|
1640
|
-
docTitles
|
|
1840
|
+
docTitles,
|
|
1841
|
+
flatten = true,
|
|
1842
|
+
createManifest,
|
|
1843
|
+
manifestPath,
|
|
1844
|
+
projectName,
|
|
1845
|
+
domain,
|
|
1846
|
+
defaultLocale,
|
|
1847
|
+
projectMetadata
|
|
1641
1848
|
} = options;
|
|
1642
1849
|
if (syncImages && !imageOutputPath) {
|
|
1643
1850
|
throw new Error(
|
|
1644
1851
|
"[manageDriveTranslations] imageOutputPath is required when syncImages is true"
|
|
1645
1852
|
);
|
|
1646
1853
|
}
|
|
1854
|
+
const shouldCreateManifest = createManifest ?? driveFolderId !== void 0;
|
|
1647
1855
|
const discoveredIds = [];
|
|
1648
1856
|
const discoveredNames = /* @__PURE__ */ new Map();
|
|
1857
|
+
const discoveredFolderPaths = /* @__PURE__ */ new Map();
|
|
1858
|
+
const discoveredModifiedTimes = /* @__PURE__ */ new Map();
|
|
1649
1859
|
if (driveFolderId && scanForSpreadsheets) {
|
|
1650
1860
|
const scanOptions = { folderId: driveFolderId };
|
|
1651
1861
|
const discovered = await scanDriveFolderForSpreadsheets(scanOptions);
|
|
@@ -1655,6 +1865,8 @@ async function manageDriveTranslations(options) {
|
|
|
1655
1865
|
for (const file of discovered) {
|
|
1656
1866
|
discoveredIds.push(file.id);
|
|
1657
1867
|
discoveredNames.set(file.id, file.name);
|
|
1868
|
+
discoveredFolderPaths.set(file.id, file.folderPath);
|
|
1869
|
+
if (file.modifiedTime) discoveredModifiedTimes.set(file.id, file.modifiedTime);
|
|
1658
1870
|
}
|
|
1659
1871
|
}
|
|
1660
1872
|
const allIds = [.../* @__PURE__ */ new Set([...discoveredIds, ...explicitIds])];
|
|
@@ -1663,10 +1875,50 @@ async function manageDriveTranslations(options) {
|
|
|
1663
1875
|
if (!name) return true;
|
|
1664
1876
|
return spreadsheetNameFilter.test(name);
|
|
1665
1877
|
}) : allIds;
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1878
|
+
let translations;
|
|
1879
|
+
const spreadsheetEntries = [];
|
|
1880
|
+
const baseOutputDir = translationOptions.translationsOutputDir ?? "translations";
|
|
1881
|
+
if (!flatten) {
|
|
1882
|
+
const { mergeStrategy = "later-wins", ...baseOptions } = translationOptions;
|
|
1883
|
+
const perResults = [];
|
|
1884
|
+
for (const id of filteredIds) {
|
|
1885
|
+
const name = discoveredNames.get(id) ?? id;
|
|
1886
|
+
const subDir = sanitizeFolderName(name);
|
|
1887
|
+
const subOutputDir = path6.join(baseOutputDir, subDir);
|
|
1888
|
+
console.log(
|
|
1889
|
+
`[manageDriveTranslations] (flatten: false) Fetching "${name}" \u2192 ${subOutputDir}`
|
|
1890
|
+
);
|
|
1891
|
+
const result = await getSpreadSheetData(docTitles, {
|
|
1892
|
+
...baseOptions,
|
|
1893
|
+
spreadsheetId: id,
|
|
1894
|
+
translationsOutputDir: subOutputDir
|
|
1895
|
+
});
|
|
1896
|
+
perResults.push(result);
|
|
1897
|
+
spreadsheetEntries.push({
|
|
1898
|
+
id,
|
|
1899
|
+
name,
|
|
1900
|
+
folderPath: discoveredFolderPaths.get(id) ?? "",
|
|
1901
|
+
sheets: docTitles ?? [],
|
|
1902
|
+
modifiedTime: discoveredModifiedTimes.get(id),
|
|
1903
|
+
outputSubDirectory: subDir
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
translations = mergeMultipleTranslationData(perResults, mergeStrategy);
|
|
1907
|
+
} else {
|
|
1908
|
+
translations = await getMultipleSpreadSheetsData(docTitles, {
|
|
1909
|
+
...translationOptions,
|
|
1910
|
+
spreadsheetIds: filteredIds.length > 0 ? filteredIds : void 0
|
|
1911
|
+
});
|
|
1912
|
+
for (const id of filteredIds) {
|
|
1913
|
+
spreadsheetEntries.push({
|
|
1914
|
+
id,
|
|
1915
|
+
name: discoveredNames.get(id) ?? id,
|
|
1916
|
+
folderPath: discoveredFolderPaths.get(id) ?? "",
|
|
1917
|
+
sheets: docTitles ?? [],
|
|
1918
|
+
modifiedTime: discoveredModifiedTimes.get(id)
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1670
1922
|
let imageSync;
|
|
1671
1923
|
if (syncImages && driveFolderId && imageOutputPath) {
|
|
1672
1924
|
imageSync = await syncDriveImages({
|
|
@@ -1675,13 +1927,31 @@ async function manageDriveTranslations(options) {
|
|
|
1675
1927
|
outputPath: imageOutputPath
|
|
1676
1928
|
});
|
|
1677
1929
|
}
|
|
1678
|
-
|
|
1930
|
+
let manifest;
|
|
1931
|
+
if (shouldCreateManifest) {
|
|
1932
|
+
const resolvedManifestPath = manifestPath ?? path6.join(baseOutputDir, "i18n-manifest.json");
|
|
1933
|
+
manifest = buildManifest({
|
|
1934
|
+
translations,
|
|
1935
|
+
spreadsheets: spreadsheetEntries,
|
|
1936
|
+
outputDirectory: baseOutputDir,
|
|
1937
|
+
flatten,
|
|
1938
|
+
projectName,
|
|
1939
|
+
domain,
|
|
1940
|
+
defaultLocale,
|
|
1941
|
+
projectMetadata
|
|
1942
|
+
});
|
|
1943
|
+
writeManifest(manifest, resolvedManifestPath);
|
|
1944
|
+
}
|
|
1945
|
+
return { translations, spreadsheetIds: filteredIds, imageSync, manifest };
|
|
1679
1946
|
}
|
|
1680
1947
|
|
|
1681
1948
|
// src/index.ts
|
|
1682
1949
|
var index_default = getSpreadSheetData;
|
|
1683
1950
|
export {
|
|
1951
|
+
DEFAULT_IMAGE_EXTENSIONS,
|
|
1684
1952
|
DEFAULT_WAIT_SECONDS,
|
|
1953
|
+
buildGoogleAuth,
|
|
1954
|
+
buildManifest,
|
|
1685
1955
|
convertFromDataJsonFormat,
|
|
1686
1956
|
convertToDataJsonFormat,
|
|
1687
1957
|
createAuthClient,
|
|
@@ -1703,7 +1973,9 @@ export {
|
|
|
1703
1973
|
manageDriveTranslations,
|
|
1704
1974
|
mergeMultipleTranslationData,
|
|
1705
1975
|
mergeSheets,
|
|
1976
|
+
normalizeExtension,
|
|
1706
1977
|
normalizeLocaleCode,
|
|
1978
|
+
normalizePrivateKey,
|
|
1707
1979
|
processRawRows,
|
|
1708
1980
|
readPublicSheet,
|
|
1709
1981
|
resolveLocaleWithFallback,
|
|
@@ -1712,9 +1984,12 @@ export {
|
|
|
1712
1984
|
updateSpreadsheetWithLocalChanges,
|
|
1713
1985
|
validateCredentials,
|
|
1714
1986
|
validateEnv,
|
|
1987
|
+
validateImageDirectory,
|
|
1715
1988
|
wait,
|
|
1989
|
+
walkDirectory,
|
|
1716
1990
|
withRetry,
|
|
1717
1991
|
writeLanguageDataFile,
|
|
1718
1992
|
writeLocalesFile,
|
|
1993
|
+
writeManifest,
|
|
1719
1994
|
writeTranslationFiles
|
|
1720
1995
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export type { SpreadsheetOptions } from './utils/configurationHandler';
|
|
|
7
7
|
export { wait } from './utils/wait';
|
|
8
8
|
export { withRetry } from './utils/rateLimiter';
|
|
9
9
|
export { validateEnv } from './utils/validateEnv';
|
|
10
|
-
export { createAuthClient } from './utils/auth';
|
|
10
|
+
export { createAuthClient, buildGoogleAuth, normalizePrivateKey } from './utils/auth';
|
|
11
11
|
export { convertToDataJsonFormat } from './utils/dataConverter/convertToDataJsonFormat';
|
|
12
12
|
export { convertFromDataJsonFormat } from './utils/dataConverter/convertFromDataJsonFormat';
|
|
13
13
|
export { findLocalChanges } from './utils/dataConverter/findLocalChanges';
|
|
@@ -30,10 +30,14 @@ export type { MultiSpreadsheetOptions } from './getMultipleSpreadSheetsData';
|
|
|
30
30
|
export { mergeMultipleTranslationData } from './utils/multiSpreadsheetMerger';
|
|
31
31
|
export { scanDriveFolderForSpreadsheets } from './utils/driveFolderScanner';
|
|
32
32
|
export type { DriveSpreadsheetFile, ScanDriveFolderOptions } from './utils/driveFolderScanner';
|
|
33
|
-
export { syncDriveImages } from './utils/driveImageSync';
|
|
33
|
+
export { syncDriveImages, normalizeExtension } from './utils/driveImageSync';
|
|
34
34
|
export type { DriveImageSyncOptions, DriveImageSyncResult } from './utils/driveImageSync';
|
|
35
|
+
export { walkDirectory, validateImageDirectory, DEFAULT_IMAGE_EXTENSIONS } from './utils/localImageUtils';
|
|
36
|
+
export type { WalkDirectoryOptions, ImageDirectoryValidationOptions, ImageDirectoryValidationResult, } from './utils/localImageUtils';
|
|
35
37
|
export { manageDriveTranslations } from './utils/getDriveTranslations';
|
|
36
38
|
export type { GoogleDriveManagerOptions, GoogleDriveManagerResult } from './utils/getDriveTranslations';
|
|
39
|
+
export { buildManifest, writeManifest } from './utils/driveProjectIndex';
|
|
40
|
+
export type { DriveProjectManifest, SpreadsheetManifestEntry, BuildManifestOptions } from './utils/driveProjectIndex';
|
|
37
41
|
import { getSpreadSheetData } from './getSpreadSheetData';
|
|
38
42
|
export default getSpreadSheetData;
|
|
39
43
|
//# 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;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|