@el-j/google-sheet-translations 2.2.0-beta.2 → 2.2.0-beta.4
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 +540 -38
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +550 -37
- package/dist/utils/auth.d.ts +44 -4
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/docIngester.d.ts +111 -0
- package/dist/utils/docIngester.d.ts.map +1 -0
- package/dist/utils/docParser.d.ts +77 -0
- package/dist/utils/docParser.d.ts.map +1 -0
- package/dist/utils/driveDocScanner.d.ts +58 -0
- package/dist/utils/driveDocScanner.d.ts.map +1 -0
- package/dist/utils/driveFolderScanner.d.ts.map +1 -1
- package/dist/utils/driveImageSync.d.ts.map +1 -1
- package/dist/utils/driveProjectIndex.d.ts +39 -0
- package/dist/utils/driveProjectIndex.d.ts.map +1 -1
- package/dist/utils/getDriveTranslations.d.ts +46 -0
- package/dist/utils/getDriveTranslations.d.ts.map +1 -1
- 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 +18 -18
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",
|
|
@@ -1148,13 +1188,13 @@ GOOGLE_SPREADSHEET_ID=${id}
|
|
|
1148
1188
|
async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
|
|
1149
1189
|
const config = normalizeConfig(options);
|
|
1150
1190
|
const baseDelayMs = config.waitSeconds * 1e3;
|
|
1151
|
-
const
|
|
1152
|
-
if (
|
|
1191
|
+
const docTitle2 = _docTitle ?? [];
|
|
1192
|
+
if (docTitle2.length === 0) {
|
|
1153
1193
|
console.warn("No sheet titles provided, cannot process spreadsheet data");
|
|
1154
1194
|
return {};
|
|
1155
1195
|
}
|
|
1156
|
-
if (!
|
|
1157
|
-
|
|
1196
|
+
if (!docTitle2.includes("i18n")) {
|
|
1197
|
+
docTitle2.push("i18n");
|
|
1158
1198
|
}
|
|
1159
1199
|
const finalTranslations = {};
|
|
1160
1200
|
const allLocales = /* @__PURE__ */ new Set();
|
|
@@ -1191,9 +1231,9 @@ async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
|
|
|
1191
1231
|
"No spreadsheet ID provided. Set GOOGLE_SPREADSHEET_ID or pass spreadsheetId in options."
|
|
1192
1232
|
);
|
|
1193
1233
|
}
|
|
1194
|
-
console.log(`Processing ${
|
|
1234
|
+
console.log(`Processing ${docTitle2.length} sheets: ${docTitle2.join(", ")}`);
|
|
1195
1235
|
await Promise.all(
|
|
1196
|
-
|
|
1236
|
+
docTitle2.map(async (title) => {
|
|
1197
1237
|
let rows;
|
|
1198
1238
|
try {
|
|
1199
1239
|
rows = await withRetry(
|
|
@@ -1226,7 +1266,7 @@ async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
|
|
|
1226
1266
|
);
|
|
1227
1267
|
}
|
|
1228
1268
|
}
|
|
1229
|
-
console.log(`Processing ${
|
|
1269
|
+
console.log(`Processing ${docTitle2.length} sheets: ${docTitle2.join(", ")}`);
|
|
1230
1270
|
const doc = new GoogleSpreadsheet2(spreadsheetId, serviceAuthClient);
|
|
1231
1271
|
try {
|
|
1232
1272
|
await withRetry(() => doc.loadInfo(true), "loadInfo", baseDelayMs);
|
|
@@ -1234,7 +1274,7 @@ async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
|
|
|
1234
1274
|
throw new Error(`Failed to load spreadsheet "${spreadsheetId}"`, { cause: err });
|
|
1235
1275
|
}
|
|
1236
1276
|
await Promise.all(
|
|
1237
|
-
|
|
1277
|
+
docTitle2.map(async (title) => {
|
|
1238
1278
|
const sheet = doc.sheetsByTitle[title];
|
|
1239
1279
|
if (!sheet) {
|
|
1240
1280
|
console.warn(`Sheet "${title}" not found in the document`);
|
|
@@ -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",
|
|
@@ -1474,19 +1512,19 @@ function normalizeExtension(name) {
|
|
|
1474
1512
|
if (ext === "jpeg") ext = "jpg";
|
|
1475
1513
|
return `${base}.${ext}`;
|
|
1476
1514
|
}
|
|
1515
|
+
var DRIVE_SCOPES2 = ["https://www.googleapis.com/auth/drive.readonly"];
|
|
1477
1516
|
async function getAccessToken2(credentials) {
|
|
1478
1517
|
const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
|
|
1479
1518
|
const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
|
|
1480
|
-
|
|
1519
|
+
let driveCredentials;
|
|
1520
|
+
if (clientEmail && privateKey) {
|
|
1521
|
+
driveCredentials = { client_email: clientEmail, private_key: normalizePrivateKey(privateKey) };
|
|
1522
|
+
} else if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
1481
1523
|
throw new Error(
|
|
1482
|
-
"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."
|
|
1483
1525
|
);
|
|
1484
1526
|
}
|
|
1485
|
-
const
|
|
1486
|
-
const auth = new GoogleAuth2({
|
|
1487
|
-
credentials: { client_email: clientEmail, private_key: normalizedKey },
|
|
1488
|
-
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
1489
|
-
});
|
|
1527
|
+
const auth = buildGoogleAuth(DRIVE_SCOPES2, driveCredentials);
|
|
1490
1528
|
const client = await auth.getClient();
|
|
1491
1529
|
const tokenResponse = await client.getAccessToken();
|
|
1492
1530
|
return tokenResponse.token;
|
|
@@ -1771,6 +1809,7 @@ function buildManifest(options) {
|
|
|
1771
1809
|
locales,
|
|
1772
1810
|
defaultLocale: options.defaultLocale,
|
|
1773
1811
|
spreadsheets: options.spreadsheets,
|
|
1812
|
+
docs: options.docs,
|
|
1774
1813
|
outputDirectory: options.outputDirectory,
|
|
1775
1814
|
flatten: options.flatten,
|
|
1776
1815
|
projectMetadata: options.projectMetadata
|
|
@@ -1784,11 +1823,385 @@ function writeManifest(manifest, manifestPath) {
|
|
|
1784
1823
|
fs6.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
1785
1824
|
console.log(`[driveProjectIndex] Wrote project manifest \u2192 ${manifestPath}`);
|
|
1786
1825
|
}
|
|
1826
|
+
function readManifest(manifestPath) {
|
|
1827
|
+
try {
|
|
1828
|
+
const content = fs6.readFileSync(manifestPath, "utf8");
|
|
1829
|
+
return JSON.parse(content);
|
|
1830
|
+
} catch {
|
|
1831
|
+
return void 0;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// src/utils/driveDocScanner.ts
|
|
1836
|
+
import { GoogleAuth as GoogleAuth2 } from "google-auth-library";
|
|
1837
|
+
var DOC_MIME = "application/vnd.google-apps.document";
|
|
1838
|
+
var FOLDER_MIME3 = "application/vnd.google-apps.folder";
|
|
1839
|
+
var DRIVE_FILES_URL3 = "https://www.googleapis.com/drive/v3/files";
|
|
1840
|
+
async function getAccessToken3(credentials) {
|
|
1841
|
+
const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
|
|
1842
|
+
const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
|
|
1843
|
+
if (!clientEmail || !privateKey) {
|
|
1844
|
+
throw new Error(
|
|
1845
|
+
"Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
const normalizedKey = privateKey.replace(/\\n/g, "\n");
|
|
1849
|
+
const auth = new GoogleAuth2({
|
|
1850
|
+
credentials: { client_email: clientEmail, private_key: normalizedKey },
|
|
1851
|
+
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
1852
|
+
});
|
|
1853
|
+
const client = await auth.getClient();
|
|
1854
|
+
const tokenResponse = await client.getAccessToken();
|
|
1855
|
+
return tokenResponse.token;
|
|
1856
|
+
}
|
|
1857
|
+
async function listFilesInFolder3(folderId, mimeType, token) {
|
|
1858
|
+
const results = [];
|
|
1859
|
+
let pageToken;
|
|
1860
|
+
do {
|
|
1861
|
+
const query = `'${folderId}' in parents and mimeType = '${mimeType}' and trashed = false`;
|
|
1862
|
+
const params = new URLSearchParams({
|
|
1863
|
+
q: query,
|
|
1864
|
+
fields: "nextPageToken,files(id,name,mimeType,modifiedTime)",
|
|
1865
|
+
pageSize: "1000"
|
|
1866
|
+
});
|
|
1867
|
+
if (pageToken) params.set("pageToken", pageToken);
|
|
1868
|
+
const response = await fetch(`${DRIVE_FILES_URL3}?${params.toString()}`, {
|
|
1869
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1870
|
+
});
|
|
1871
|
+
if (!response.ok) {
|
|
1872
|
+
const text = await response.text();
|
|
1873
|
+
throw new Error(`Drive API error ${response.status}: ${text}`);
|
|
1874
|
+
}
|
|
1875
|
+
const data = await response.json();
|
|
1876
|
+
results.push(...data.files);
|
|
1877
|
+
pageToken = data.nextPageToken;
|
|
1878
|
+
} while (pageToken);
|
|
1879
|
+
return results;
|
|
1880
|
+
}
|
|
1881
|
+
function inferLocaleFromDocName(name) {
|
|
1882
|
+
const baseName = name.replace(/\.[^.]+$/, "");
|
|
1883
|
+
const match = baseName.match(/_([a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?)$/);
|
|
1884
|
+
if (!match) return void 0;
|
|
1885
|
+
const candidate = match[1].replace("_", "-");
|
|
1886
|
+
const parts = candidate.split("-");
|
|
1887
|
+
if (parts.length === 2) {
|
|
1888
|
+
return `${parts[0].toLowerCase()}-${parts[1].toUpperCase()}`;
|
|
1889
|
+
}
|
|
1890
|
+
return parts[0].toLowerCase();
|
|
1891
|
+
}
|
|
1892
|
+
async function scanFolder2(folderId, folderPath, token, recursive, nameFilter, seen = /* @__PURE__ */ new Set()) {
|
|
1893
|
+
console.log(
|
|
1894
|
+
`[driveDocScanner] Scanning folder: ${folderId} (path: "${folderPath}")`
|
|
1895
|
+
);
|
|
1896
|
+
const docs = await listFilesInFolder3(folderId, DOC_MIME, token);
|
|
1897
|
+
const results = [];
|
|
1898
|
+
for (const file of docs) {
|
|
1899
|
+
if (seen.has(file.id)) continue;
|
|
1900
|
+
seen.add(file.id);
|
|
1901
|
+
if (nameFilter && !nameFilter.test(file.name)) continue;
|
|
1902
|
+
results.push({
|
|
1903
|
+
id: file.id,
|
|
1904
|
+
name: file.name,
|
|
1905
|
+
folderPath,
|
|
1906
|
+
mimeType: file.mimeType,
|
|
1907
|
+
modifiedTime: file.modifiedTime,
|
|
1908
|
+
sourceLocale: inferLocaleFromDocName(file.name)
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
if (recursive) {
|
|
1912
|
+
const subfolders = await listFilesInFolder3(folderId, FOLDER_MIME3, token);
|
|
1913
|
+
for (const folder of subfolders) {
|
|
1914
|
+
const subPath = folderPath ? `${folderPath}/${folder.name}` : folder.name;
|
|
1915
|
+
const subResults = await scanFolder2(
|
|
1916
|
+
folder.id,
|
|
1917
|
+
subPath,
|
|
1918
|
+
token,
|
|
1919
|
+
recursive,
|
|
1920
|
+
nameFilter,
|
|
1921
|
+
seen
|
|
1922
|
+
);
|
|
1923
|
+
results.push(...subResults);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
return results;
|
|
1927
|
+
}
|
|
1928
|
+
async function scanDriveFolderForDocs(options) {
|
|
1929
|
+
const { folderId, recursive = true, nameFilter, credentials } = options;
|
|
1930
|
+
const token = await getAccessToken3(credentials);
|
|
1931
|
+
return scanFolder2(folderId, "", token, recursive, nameFilter);
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// src/utils/docIngester.ts
|
|
1935
|
+
import { GoogleAuth as GoogleAuth3 } from "google-auth-library";
|
|
1936
|
+
import { GoogleSpreadsheet as GoogleSpreadsheet3 } from "google-spreadsheet";
|
|
1937
|
+
|
|
1938
|
+
// src/utils/docParser.ts
|
|
1939
|
+
function slugifyKey(text) {
|
|
1940
|
+
return text.toLowerCase().replace(/[^\w]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
1941
|
+
}
|
|
1942
|
+
function parseDocContent(content, options = {}) {
|
|
1943
|
+
const { strategy = "heading", defaultSheetName = "content" } = options;
|
|
1944
|
+
if (strategy === "marker") return parseWithMarkers(content, defaultSheetName);
|
|
1945
|
+
if (strategy === "numbered") return parseNumbered(content, defaultSheetName);
|
|
1946
|
+
return parseWithHeadings(content, defaultSheetName);
|
|
1947
|
+
}
|
|
1948
|
+
function parseWithHeadings(content, defaultSheetName) {
|
|
1949
|
+
const lines = content.split("\n");
|
|
1950
|
+
const entries = [];
|
|
1951
|
+
let currentSheet = defaultSheetName;
|
|
1952
|
+
let currentKey = null;
|
|
1953
|
+
const valueLines = [];
|
|
1954
|
+
function flushEntry() {
|
|
1955
|
+
if (currentKey !== null) {
|
|
1956
|
+
const value = valueLines.join("\n").trim();
|
|
1957
|
+
if (value) {
|
|
1958
|
+
entries.push({ sheetName: currentSheet, key: currentKey, value });
|
|
1959
|
+
}
|
|
1960
|
+
currentKey = null;
|
|
1961
|
+
valueLines.length = 0;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
for (const rawLine of lines) {
|
|
1965
|
+
const line = rawLine.trimEnd();
|
|
1966
|
+
if (line.startsWith("# ")) {
|
|
1967
|
+
flushEntry();
|
|
1968
|
+
currentSheet = slugifyKey(line.slice(2).trim()) || defaultSheetName;
|
|
1969
|
+
currentKey = null;
|
|
1970
|
+
valueLines.length = 0;
|
|
1971
|
+
} else if (line.startsWith("## ")) {
|
|
1972
|
+
flushEntry();
|
|
1973
|
+
currentKey = slugifyKey(line.slice(3).trim());
|
|
1974
|
+
valueLines.length = 0;
|
|
1975
|
+
} else if (currentKey !== null) {
|
|
1976
|
+
valueLines.push(line);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
flushEntry();
|
|
1980
|
+
return entries;
|
|
1981
|
+
}
|
|
1982
|
+
function parseWithMarkers(content, defaultSheetName) {
|
|
1983
|
+
const MARKER_RE = /\[\[key:([^\]]{1,200})\]\]/g;
|
|
1984
|
+
const entries = [];
|
|
1985
|
+
const segments = content.split(MARKER_RE);
|
|
1986
|
+
for (let i = 1; i < segments.length; i += 2) {
|
|
1987
|
+
const keyPath = segments[i].trim();
|
|
1988
|
+
const value = (segments[i + 1] ?? "").trim();
|
|
1989
|
+
if (!keyPath || !value) continue;
|
|
1990
|
+
const dotIdx = keyPath.indexOf(".");
|
|
1991
|
+
let sheetName;
|
|
1992
|
+
let key;
|
|
1993
|
+
if (dotIdx !== -1) {
|
|
1994
|
+
sheetName = slugifyKey(keyPath.slice(0, dotIdx));
|
|
1995
|
+
key = slugifyKey(keyPath.slice(dotIdx + 1));
|
|
1996
|
+
} else {
|
|
1997
|
+
sheetName = defaultSheetName;
|
|
1998
|
+
key = slugifyKey(keyPath);
|
|
1999
|
+
}
|
|
2000
|
+
if (sheetName && key) {
|
|
2001
|
+
entries.push({ sheetName, key, value });
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
return entries;
|
|
2005
|
+
}
|
|
2006
|
+
function parseNumbered(content, defaultSheetName) {
|
|
2007
|
+
const entries = [];
|
|
2008
|
+
let counter = 0;
|
|
2009
|
+
const paragraphs = content.split(/\n{2,}/);
|
|
2010
|
+
for (const para of paragraphs) {
|
|
2011
|
+
const value = para.replace(/^[#\s]+/, "").trim();
|
|
2012
|
+
if (value) {
|
|
2013
|
+
counter++;
|
|
2014
|
+
entries.push({
|
|
2015
|
+
sheetName: defaultSheetName,
|
|
2016
|
+
key: `item_${counter}`,
|
|
2017
|
+
value
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return entries;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/utils/docIngester.ts
|
|
2025
|
+
async function getDriveExportToken(credentials) {
|
|
2026
|
+
const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
|
|
2027
|
+
const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
|
|
2028
|
+
if (!clientEmail || !privateKey) {
|
|
2029
|
+
throw new Error(
|
|
2030
|
+
"Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
const normalizedKey = privateKey.replace(/\\n/g, "\n");
|
|
2034
|
+
const auth = new GoogleAuth3({
|
|
2035
|
+
credentials: { client_email: clientEmail, private_key: normalizedKey },
|
|
2036
|
+
scopes: ["https://www.googleapis.com/auth/drive.readonly"]
|
|
2037
|
+
});
|
|
2038
|
+
const client = await auth.getClient();
|
|
2039
|
+
const tokenResponse = await client.getAccessToken();
|
|
2040
|
+
return tokenResponse.token;
|
|
2041
|
+
}
|
|
2042
|
+
async function exportDoc(docId, credentials) {
|
|
2043
|
+
const token = await getDriveExportToken(credentials);
|
|
2044
|
+
const base = `https://www.googleapis.com/drive/v3/files/${docId}/export`;
|
|
2045
|
+
const mdRes = await fetch(
|
|
2046
|
+
`${base}?mimeType=text%2Fmarkdown`,
|
|
2047
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
2048
|
+
);
|
|
2049
|
+
if (mdRes.ok) return mdRes.text();
|
|
2050
|
+
const txtRes = await fetch(
|
|
2051
|
+
`${base}?mimeType=text%2Fplain`,
|
|
2052
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
2053
|
+
);
|
|
2054
|
+
if (txtRes.ok) return txtRes.text();
|
|
2055
|
+
const errText = await txtRes.text();
|
|
2056
|
+
throw new Error(
|
|
2057
|
+
`Failed to export doc ${docId}: HTTP ${txtRes.status} \u2013 ${errText}`
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
function entriesToSeedKeys(entries) {
|
|
2061
|
+
const keys = {};
|
|
2062
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2063
|
+
for (const entry of entries) {
|
|
2064
|
+
const base = `${entry.sheetName}.${entry.key}`;
|
|
2065
|
+
const count = (counts.get(base) ?? 0) + 1;
|
|
2066
|
+
counts.set(base, count);
|
|
2067
|
+
const finalKey = count > 1 ? `${base}_${count}` : base;
|
|
2068
|
+
keys[finalKey] = entry.value;
|
|
2069
|
+
}
|
|
2070
|
+
return keys;
|
|
2071
|
+
}
|
|
2072
|
+
function entriesToTranslationData(entries, locale) {
|
|
2073
|
+
const data = {};
|
|
2074
|
+
data[locale] = {};
|
|
2075
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2076
|
+
for (const entry of entries) {
|
|
2077
|
+
const sheetName = entry.sheetName;
|
|
2078
|
+
const entryKey = entry.key;
|
|
2079
|
+
if (sheetName === "__proto__" || sheetName === "constructor" || sheetName === "prototype" || entryKey === "__proto__" || entryKey === "constructor" || entryKey === "prototype") {
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
if (!data[locale][sheetName]) {
|
|
2083
|
+
data[locale][sheetName] = {};
|
|
2084
|
+
}
|
|
2085
|
+
const base = `${sheetName}::${entryKey}`;
|
|
2086
|
+
const count = (counts.get(base) ?? 0) + 1;
|
|
2087
|
+
counts.set(base, count);
|
|
2088
|
+
const finalKey = count > 1 ? `${entryKey}_${count}` : entryKey;
|
|
2089
|
+
data[locale][sheetName][finalKey] = entry.value;
|
|
2090
|
+
}
|
|
2091
|
+
return data;
|
|
2092
|
+
}
|
|
2093
|
+
function docTitle(name) {
|
|
2094
|
+
return name.replace(/_[a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?$/, "").trim() || name;
|
|
2095
|
+
}
|
|
2096
|
+
async function ingestDoc(docFile, options = {}) {
|
|
2097
|
+
const {
|
|
2098
|
+
targetLocales,
|
|
2099
|
+
keyStrategy = "heading",
|
|
2100
|
+
updateMode = "create-only",
|
|
2101
|
+
credentials,
|
|
2102
|
+
existingEntry,
|
|
2103
|
+
waitSeconds = 1
|
|
2104
|
+
} = options;
|
|
2105
|
+
const sourceLocale = docFile.sourceLocale ?? "en";
|
|
2106
|
+
const entry = existingEntry ? { ...existingEntry, modifiedTime: docFile.modifiedTime } : {
|
|
2107
|
+
id: docFile.id,
|
|
2108
|
+
name: docFile.name,
|
|
2109
|
+
folderPath: docFile.folderPath,
|
|
2110
|
+
generatedFromDoc: true,
|
|
2111
|
+
sourceLocale,
|
|
2112
|
+
modifiedTime: docFile.modifiedTime
|
|
2113
|
+
};
|
|
2114
|
+
const hasLinkedSheet = !!entry.linkedSpreadsheetId;
|
|
2115
|
+
const shouldRefresh = updateMode === "refresh-if-newer" && hasLinkedSheet && !!docFile.modifiedTime && !!existingEntry?.lastIngestedAt && new Date(docFile.modifiedTime) > new Date(existingEntry.lastIngestedAt);
|
|
2116
|
+
if (hasLinkedSheet && !shouldRefresh) {
|
|
2117
|
+
console.log(
|
|
2118
|
+
`[docIngester] Skipping "${docFile.name}" \u2013 linked spreadsheet is already up-to-date.`
|
|
2119
|
+
);
|
|
2120
|
+
return { action: "skipped", entry };
|
|
2121
|
+
}
|
|
2122
|
+
console.log(
|
|
2123
|
+
`[docIngester] Exporting doc "${docFile.name}" (id: ${docFile.id})\u2026`
|
|
2124
|
+
);
|
|
2125
|
+
const content = await exportDoc(docFile.id, credentials);
|
|
2126
|
+
const sheetBaseName = slugifyKey(docTitle(docFile.name)) || "content";
|
|
2127
|
+
const entries = parseDocContent(content, {
|
|
2128
|
+
strategy: keyStrategy,
|
|
2129
|
+
defaultSheetName: sheetBaseName
|
|
2130
|
+
});
|
|
2131
|
+
if (entries.length === 0) {
|
|
2132
|
+
console.warn(
|
|
2133
|
+
`[docIngester] Doc "${docFile.name}" produced no translation entries \u2013 skipping.`
|
|
2134
|
+
);
|
|
2135
|
+
return { action: "skipped", entry };
|
|
2136
|
+
}
|
|
2137
|
+
if (!hasLinkedSheet) {
|
|
2138
|
+
const authClient2 = createAuthClient();
|
|
2139
|
+
const seedKeys = entriesToSeedKeys(entries);
|
|
2140
|
+
const title = docTitle(docFile.name);
|
|
2141
|
+
const { spreadsheetId: spreadsheetId2 } = await createSpreadsheet(authClient2, {
|
|
2142
|
+
title,
|
|
2143
|
+
sourceLocale,
|
|
2144
|
+
targetLocales,
|
|
2145
|
+
seedKeys
|
|
2146
|
+
});
|
|
2147
|
+
entry.linkedSpreadsheetId = spreadsheetId2;
|
|
2148
|
+
entry.lastIngestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2149
|
+
console.log(
|
|
2150
|
+
`[docIngester] Created spreadsheet ${spreadsheetId2} from doc "${docFile.name}".`
|
|
2151
|
+
);
|
|
2152
|
+
return { action: "created", entry };
|
|
2153
|
+
}
|
|
2154
|
+
const authClient = createAuthClient();
|
|
2155
|
+
const spreadsheetId = entry.linkedSpreadsheetId;
|
|
2156
|
+
const doc = new GoogleSpreadsheet3(spreadsheetId, authClient);
|
|
2157
|
+
await doc.loadInfo();
|
|
2158
|
+
const changes = entriesToTranslationData(entries, sourceLocale);
|
|
2159
|
+
await updateSpreadsheetWithLocalChanges(
|
|
2160
|
+
doc,
|
|
2161
|
+
changes,
|
|
2162
|
+
waitSeconds,
|
|
2163
|
+
false,
|
|
2164
|
+
// autoTranslate – formulas already exist in non-base columns
|
|
2165
|
+
{},
|
|
2166
|
+
false
|
|
2167
|
+
);
|
|
2168
|
+
entry.lastIngestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2169
|
+
console.log(
|
|
2170
|
+
`[docIngester] Refreshed spreadsheet ${spreadsheetId} from doc "${docFile.name}".`
|
|
2171
|
+
);
|
|
2172
|
+
return { action: "refreshed", entry };
|
|
2173
|
+
}
|
|
1787
2174
|
|
|
1788
2175
|
// src/utils/getDriveTranslations.ts
|
|
1789
2176
|
function sanitizeFolderName(name) {
|
|
1790
2177
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "sheet";
|
|
1791
2178
|
}
|
|
2179
|
+
async function moveSpreadsheetToFolder(spreadsheetId, folderId) {
|
|
2180
|
+
const clientEmail = process.env.GOOGLE_CLIENT_EMAIL;
|
|
2181
|
+
const rawPrivateKey = process.env.GOOGLE_PRIVATE_KEY;
|
|
2182
|
+
let credentials;
|
|
2183
|
+
if (clientEmail && rawPrivateKey) {
|
|
2184
|
+
credentials = { client_email: clientEmail, private_key: normalizePrivateKey(rawPrivateKey) };
|
|
2185
|
+
}
|
|
2186
|
+
const driveAuth = buildGoogleAuth(
|
|
2187
|
+
["https://www.googleapis.com/auth/drive.file"],
|
|
2188
|
+
credentials
|
|
2189
|
+
);
|
|
2190
|
+
const fileRes = await driveAuth.request({
|
|
2191
|
+
url: `https://www.googleapis.com/drive/v3/files/${spreadsheetId}`,
|
|
2192
|
+
params: { fields: "parents" }
|
|
2193
|
+
});
|
|
2194
|
+
const parentIds = fileRes.data.parents ?? [];
|
|
2195
|
+
await driveAuth.request({
|
|
2196
|
+
url: `https://www.googleapis.com/drive/v3/files/${spreadsheetId}`,
|
|
2197
|
+
method: "PATCH",
|
|
2198
|
+
params: {
|
|
2199
|
+
addParents: folderId,
|
|
2200
|
+
...parentIds.length > 0 ? { removeParents: parentIds.join(",") } : {},
|
|
2201
|
+
fields: "id,parents"
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
1792
2205
|
async function manageDriveTranslations(options) {
|
|
1793
2206
|
const {
|
|
1794
2207
|
driveFolderId,
|
|
@@ -1806,7 +2219,14 @@ async function manageDriveTranslations(options) {
|
|
|
1806
2219
|
projectName,
|
|
1807
2220
|
domain,
|
|
1808
2221
|
defaultLocale,
|
|
1809
|
-
projectMetadata
|
|
2222
|
+
projectMetadata,
|
|
2223
|
+
// Doc ingestion options
|
|
2224
|
+
scanForDocs = false,
|
|
2225
|
+
docNameFilter,
|
|
2226
|
+
docSourceLocale,
|
|
2227
|
+
docKeyStrategy,
|
|
2228
|
+
docUpdateMode,
|
|
2229
|
+
docTargetLocales
|
|
1810
2230
|
} = options;
|
|
1811
2231
|
if (syncImages && !imageOutputPath) {
|
|
1812
2232
|
throw new Error(
|
|
@@ -1837,6 +2257,36 @@ async function manageDriveTranslations(options) {
|
|
|
1837
2257
|
if (!name) return true;
|
|
1838
2258
|
return spreadsheetNameFilter.test(name);
|
|
1839
2259
|
}) : allIds;
|
|
2260
|
+
if (driveFolderId && filteredIds.length === 0 && translationOptions.autoCreate !== false) {
|
|
2261
|
+
console.log(
|
|
2262
|
+
`[manageDriveTranslations] Drive folder "${driveFolderId}" contains no spreadsheets. Bootstrapping a new spreadsheet\u2026`
|
|
2263
|
+
);
|
|
2264
|
+
const authClient = createAuthClient();
|
|
2265
|
+
const bootstrapTitle = translationOptions.spreadsheetTitle ?? "google-sheet-translations";
|
|
2266
|
+
const created = await createSpreadsheet(authClient, {
|
|
2267
|
+
title: bootstrapTitle,
|
|
2268
|
+
sourceLocale: translationOptions.sourceLocale,
|
|
2269
|
+
targetLocales: translationOptions.targetLocales
|
|
2270
|
+
});
|
|
2271
|
+
console.log(`[manageDriveTranslations] \u2705 Spreadsheet created: ${created.url}`);
|
|
2272
|
+
try {
|
|
2273
|
+
await moveSpreadsheetToFolder(created.spreadsheetId, driveFolderId);
|
|
2274
|
+
console.log(
|
|
2275
|
+
`[manageDriveTranslations] \u2705 Spreadsheet moved into Drive folder "${driveFolderId}"`
|
|
2276
|
+
);
|
|
2277
|
+
} catch (moveErr) {
|
|
2278
|
+
console.warn(
|
|
2279
|
+
`[manageDriveTranslations] \u26A0\uFE0F Could not move spreadsheet into Drive folder:`,
|
|
2280
|
+
moveErr.message
|
|
2281
|
+
);
|
|
2282
|
+
console.warn(
|
|
2283
|
+
` Please move spreadsheet "${created.spreadsheetId}" into folder "${driveFolderId}" manually.`
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
filteredIds.push(created.spreadsheetId);
|
|
2287
|
+
discoveredNames.set(created.spreadsheetId, bootstrapTitle);
|
|
2288
|
+
discoveredFolderPaths.set(created.spreadsheetId, "");
|
|
2289
|
+
}
|
|
1840
2290
|
let translations;
|
|
1841
2291
|
const spreadsheetEntries = [];
|
|
1842
2292
|
const baseOutputDir = translationOptions.translationsOutputDir ?? "translations";
|
|
@@ -1890,8 +2340,48 @@ async function manageDriveTranslations(options) {
|
|
|
1890
2340
|
});
|
|
1891
2341
|
}
|
|
1892
2342
|
let manifest;
|
|
2343
|
+
let docIngestResults;
|
|
2344
|
+
const docEntries = [];
|
|
2345
|
+
const resolvedManifestPath = manifestPath ?? path6.join(baseOutputDir, "i18n-manifest.json");
|
|
2346
|
+
if (driveFolderId && scanForDocs) {
|
|
2347
|
+
const previousManifest = readManifest(resolvedManifestPath);
|
|
2348
|
+
const docScanOptions = {
|
|
2349
|
+
folderId: driveFolderId
|
|
2350
|
+
};
|
|
2351
|
+
const discoveredDocs = await scanDriveFolderForDocs(docScanOptions);
|
|
2352
|
+
console.log(
|
|
2353
|
+
`[manageDriveTranslations] Found ${discoveredDocs.length} doc(s) in Drive folder`
|
|
2354
|
+
);
|
|
2355
|
+
docIngestResults = [];
|
|
2356
|
+
for (const docFile of discoveredDocs) {
|
|
2357
|
+
if (docNameFilter && !docNameFilter.test(docFile.name)) continue;
|
|
2358
|
+
if (!docFile.sourceLocale && docSourceLocale) {
|
|
2359
|
+
docFile.sourceLocale = docSourceLocale;
|
|
2360
|
+
}
|
|
2361
|
+
const existingEntry = previousManifest?.docs?.find(
|
|
2362
|
+
(d) => d.id === docFile.id
|
|
2363
|
+
);
|
|
2364
|
+
const ingesterOptions = {
|
|
2365
|
+
targetLocales: docTargetLocales,
|
|
2366
|
+
keyStrategy: docKeyStrategy,
|
|
2367
|
+
updateMode: docUpdateMode,
|
|
2368
|
+
existingEntry,
|
|
2369
|
+
waitSeconds: translationOptions.waitSeconds
|
|
2370
|
+
};
|
|
2371
|
+
try {
|
|
2372
|
+
const result = await ingestDoc(docFile, ingesterOptions);
|
|
2373
|
+
docEntries.push(result.entry);
|
|
2374
|
+
docIngestResults.push({ docName: docFile.name, action: result.action });
|
|
2375
|
+
} catch (err) {
|
|
2376
|
+
console.error(
|
|
2377
|
+
`[manageDriveTranslations] Failed to ingest doc "${docFile.name}":`,
|
|
2378
|
+
err
|
|
2379
|
+
);
|
|
2380
|
+
if (existingEntry) docEntries.push(existingEntry);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
1893
2384
|
if (shouldCreateManifest) {
|
|
1894
|
-
const resolvedManifestPath = manifestPath ?? path6.join(baseOutputDir, "i18n-manifest.json");
|
|
1895
2385
|
manifest = buildManifest({
|
|
1896
2386
|
translations,
|
|
1897
2387
|
spreadsheets: spreadsheetEntries,
|
|
@@ -1900,11 +2390,12 @@ async function manageDriveTranslations(options) {
|
|
|
1900
2390
|
projectName,
|
|
1901
2391
|
domain,
|
|
1902
2392
|
defaultLocale,
|
|
1903
|
-
projectMetadata
|
|
2393
|
+
projectMetadata,
|
|
2394
|
+
docs: docEntries.length > 0 ? docEntries : void 0
|
|
1904
2395
|
});
|
|
1905
2396
|
writeManifest(manifest, resolvedManifestPath);
|
|
1906
2397
|
}
|
|
1907
|
-
return { translations, spreadsheetIds: filteredIds, imageSync, manifest };
|
|
2398
|
+
return { translations, spreadsheetIds: filteredIds, imageSync, manifest, docIngestResults };
|
|
1908
2399
|
}
|
|
1909
2400
|
|
|
1910
2401
|
// src/index.ts
|
|
@@ -1912,6 +2403,7 @@ var index_default = getSpreadSheetData;
|
|
|
1912
2403
|
export {
|
|
1913
2404
|
DEFAULT_IMAGE_EXTENSIONS,
|
|
1914
2405
|
DEFAULT_WAIT_SECONDS,
|
|
2406
|
+
buildGoogleAuth,
|
|
1915
2407
|
buildManifest,
|
|
1916
2408
|
convertFromDataJsonFormat,
|
|
1917
2409
|
convertToDataJsonFormat,
|
|
@@ -1919,6 +2411,9 @@ export {
|
|
|
1919
2411
|
createLocaleMapping,
|
|
1920
2412
|
createSpreadsheet,
|
|
1921
2413
|
index_default as default,
|
|
2414
|
+
entriesToSeedKeys,
|
|
2415
|
+
entriesToTranslationData,
|
|
2416
|
+
exportDoc,
|
|
1922
2417
|
filterValidLocales,
|
|
1923
2418
|
findLocalChanges,
|
|
1924
2419
|
getGoogleTranslateCode,
|
|
@@ -1930,16 +2425,23 @@ export {
|
|
|
1930
2425
|
getSpreadSheetData,
|
|
1931
2426
|
getTranslationSummary,
|
|
1932
2427
|
handleBidirectionalSync,
|
|
2428
|
+
inferLocaleFromDocName,
|
|
2429
|
+
ingestDoc,
|
|
1933
2430
|
isValidLocale,
|
|
1934
2431
|
manageDriveTranslations,
|
|
1935
2432
|
mergeMultipleTranslationData,
|
|
1936
2433
|
mergeSheets,
|
|
1937
2434
|
normalizeExtension,
|
|
1938
2435
|
normalizeLocaleCode,
|
|
2436
|
+
normalizePrivateKey,
|
|
2437
|
+
parseDocContent,
|
|
1939
2438
|
processRawRows,
|
|
2439
|
+
readManifest,
|
|
1940
2440
|
readPublicSheet,
|
|
1941
2441
|
resolveLocaleWithFallback,
|
|
2442
|
+
scanDriveFolderForDocs,
|
|
1942
2443
|
scanDriveFolderForSpreadsheets,
|
|
2444
|
+
slugifyKey,
|
|
1943
2445
|
syncDriveImages,
|
|
1944
2446
|
updateSpreadsheetWithLocalChanges,
|
|
1945
2447
|
validateCredentials,
|