@el-j/google-sheet-translations 2.2.0-beta.3 → 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/index.js CHANGED
@@ -40,6 +40,9 @@ __export(index_exports, {
40
40
  createLocaleMapping: () => createLocaleMapping,
41
41
  createSpreadsheet: () => createSpreadsheet,
42
42
  default: () => index_default,
43
+ entriesToSeedKeys: () => entriesToSeedKeys,
44
+ entriesToTranslationData: () => entriesToTranslationData,
45
+ exportDoc: () => exportDoc,
43
46
  filterValidLocales: () => filterValidLocales,
44
47
  findLocalChanges: () => findLocalChanges,
45
48
  getGoogleTranslateCode: () => getGoogleTranslateCode,
@@ -51,6 +54,8 @@ __export(index_exports, {
51
54
  getSpreadSheetData: () => getSpreadSheetData,
52
55
  getTranslationSummary: () => getTranslationSummary,
53
56
  handleBidirectionalSync: () => handleBidirectionalSync,
57
+ inferLocaleFromDocName: () => inferLocaleFromDocName,
58
+ ingestDoc: () => ingestDoc,
54
59
  isValidLocale: () => isValidLocale,
55
60
  manageDriveTranslations: () => manageDriveTranslations,
56
61
  mergeMultipleTranslationData: () => mergeMultipleTranslationData,
@@ -58,10 +63,14 @@ __export(index_exports, {
58
63
  normalizeExtension: () => normalizeExtension,
59
64
  normalizeLocaleCode: () => normalizeLocaleCode,
60
65
  normalizePrivateKey: () => normalizePrivateKey,
66
+ parseDocContent: () => parseDocContent,
61
67
  processRawRows: () => processRawRows,
68
+ readManifest: () => readManifest,
62
69
  readPublicSheet: () => readPublicSheet,
63
70
  resolveLocaleWithFallback: () => resolveLocaleWithFallback,
71
+ scanDriveFolderForDocs: () => scanDriveFolderForDocs,
64
72
  scanDriveFolderForSpreadsheets: () => scanDriveFolderForSpreadsheets,
73
+ slugifyKey: () => slugifyKey,
65
74
  syncDriveImages: () => syncDriveImages,
66
75
  updateSpreadsheetWithLocalChanges: () => updateSpreadsheetWithLocalChanges,
67
76
  validateCredentials: () => validateCredentials,
@@ -1267,13 +1276,13 @@ GOOGLE_SPREADSHEET_ID=${id}
1267
1276
  async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
1268
1277
  const config = normalizeConfig(options);
1269
1278
  const baseDelayMs = config.waitSeconds * 1e3;
1270
- const docTitle = _docTitle ?? [];
1271
- if (docTitle.length === 0) {
1279
+ const docTitle2 = _docTitle ?? [];
1280
+ if (docTitle2.length === 0) {
1272
1281
  console.warn("No sheet titles provided, cannot process spreadsheet data");
1273
1282
  return {};
1274
1283
  }
1275
- if (!docTitle.includes("i18n")) {
1276
- docTitle.push("i18n");
1284
+ if (!docTitle2.includes("i18n")) {
1285
+ docTitle2.push("i18n");
1277
1286
  }
1278
1287
  const finalTranslations = {};
1279
1288
  const allLocales = /* @__PURE__ */ new Set();
@@ -1310,9 +1319,9 @@ async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
1310
1319
  "No spreadsheet ID provided. Set GOOGLE_SPREADSHEET_ID or pass spreadsheetId in options."
1311
1320
  );
1312
1321
  }
1313
- console.log(`Processing ${docTitle.length} sheets: ${docTitle.join(", ")}`);
1322
+ console.log(`Processing ${docTitle2.length} sheets: ${docTitle2.join(", ")}`);
1314
1323
  await Promise.all(
1315
- docTitle.map(async (title) => {
1324
+ docTitle2.map(async (title) => {
1316
1325
  let rows;
1317
1326
  try {
1318
1327
  rows = await withRetry(
@@ -1345,7 +1354,7 @@ async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
1345
1354
  );
1346
1355
  }
1347
1356
  }
1348
- console.log(`Processing ${docTitle.length} sheets: ${docTitle.join(", ")}`);
1357
+ console.log(`Processing ${docTitle2.length} sheets: ${docTitle2.join(", ")}`);
1349
1358
  const doc = new import_google_spreadsheet2.GoogleSpreadsheet(spreadsheetId, serviceAuthClient);
1350
1359
  try {
1351
1360
  await withRetry(() => doc.loadInfo(true), "loadInfo", baseDelayMs);
@@ -1353,7 +1362,7 @@ async function getSpreadSheetData(_docTitle, options = {}, _refreshDepth = 0) {
1353
1362
  throw new Error(`Failed to load spreadsheet "${spreadsheetId}"`, { cause: err });
1354
1363
  }
1355
1364
  await Promise.all(
1356
- docTitle.map(async (title) => {
1365
+ docTitle2.map(async (title) => {
1357
1366
  const sheet = doc.sheetsByTitle[title];
1358
1367
  if (!sheet) {
1359
1368
  console.warn(`Sheet "${title}" not found in the document`);
@@ -1888,6 +1897,7 @@ function buildManifest(options) {
1888
1897
  locales,
1889
1898
  defaultLocale: options.defaultLocale,
1890
1899
  spreadsheets: options.spreadsheets,
1900
+ docs: options.docs,
1891
1901
  outputDirectory: options.outputDirectory,
1892
1902
  flatten: options.flatten,
1893
1903
  projectMetadata: options.projectMetadata
@@ -1901,11 +1911,385 @@ function writeManifest(manifest, manifestPath) {
1901
1911
  import_node_fs8.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
1902
1912
  console.log(`[driveProjectIndex] Wrote project manifest \u2192 ${manifestPath}`);
1903
1913
  }
1914
+ function readManifest(manifestPath) {
1915
+ try {
1916
+ const content = import_node_fs8.default.readFileSync(manifestPath, "utf8");
1917
+ return JSON.parse(content);
1918
+ } catch {
1919
+ return void 0;
1920
+ }
1921
+ }
1922
+
1923
+ // src/utils/driveDocScanner.ts
1924
+ var import_google_auth_library2 = require("google-auth-library");
1925
+ var DOC_MIME = "application/vnd.google-apps.document";
1926
+ var FOLDER_MIME3 = "application/vnd.google-apps.folder";
1927
+ var DRIVE_FILES_URL3 = "https://www.googleapis.com/drive/v3/files";
1928
+ async function getAccessToken3(credentials) {
1929
+ const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
1930
+ const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
1931
+ if (!clientEmail || !privateKey) {
1932
+ throw new Error(
1933
+ "Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
1934
+ );
1935
+ }
1936
+ const normalizedKey = privateKey.replace(/\\n/g, "\n");
1937
+ const auth = new import_google_auth_library2.GoogleAuth({
1938
+ credentials: { client_email: clientEmail, private_key: normalizedKey },
1939
+ scopes: ["https://www.googleapis.com/auth/drive.readonly"]
1940
+ });
1941
+ const client = await auth.getClient();
1942
+ const tokenResponse = await client.getAccessToken();
1943
+ return tokenResponse.token;
1944
+ }
1945
+ async function listFilesInFolder3(folderId, mimeType, token) {
1946
+ const results = [];
1947
+ let pageToken;
1948
+ do {
1949
+ const query = `'${folderId}' in parents and mimeType = '${mimeType}' and trashed = false`;
1950
+ const params = new URLSearchParams({
1951
+ q: query,
1952
+ fields: "nextPageToken,files(id,name,mimeType,modifiedTime)",
1953
+ pageSize: "1000"
1954
+ });
1955
+ if (pageToken) params.set("pageToken", pageToken);
1956
+ const response = await fetch(`${DRIVE_FILES_URL3}?${params.toString()}`, {
1957
+ headers: { Authorization: `Bearer ${token}` }
1958
+ });
1959
+ if (!response.ok) {
1960
+ const text = await response.text();
1961
+ throw new Error(`Drive API error ${response.status}: ${text}`);
1962
+ }
1963
+ const data = await response.json();
1964
+ results.push(...data.files);
1965
+ pageToken = data.nextPageToken;
1966
+ } while (pageToken);
1967
+ return results;
1968
+ }
1969
+ function inferLocaleFromDocName(name) {
1970
+ const baseName = name.replace(/\.[^.]+$/, "");
1971
+ const match = baseName.match(/_([a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?)$/);
1972
+ if (!match) return void 0;
1973
+ const candidate = match[1].replace("_", "-");
1974
+ const parts = candidate.split("-");
1975
+ if (parts.length === 2) {
1976
+ return `${parts[0].toLowerCase()}-${parts[1].toUpperCase()}`;
1977
+ }
1978
+ return parts[0].toLowerCase();
1979
+ }
1980
+ async function scanFolder2(folderId, folderPath, token, recursive, nameFilter, seen = /* @__PURE__ */ new Set()) {
1981
+ console.log(
1982
+ `[driveDocScanner] Scanning folder: ${folderId} (path: "${folderPath}")`
1983
+ );
1984
+ const docs = await listFilesInFolder3(folderId, DOC_MIME, token);
1985
+ const results = [];
1986
+ for (const file of docs) {
1987
+ if (seen.has(file.id)) continue;
1988
+ seen.add(file.id);
1989
+ if (nameFilter && !nameFilter.test(file.name)) continue;
1990
+ results.push({
1991
+ id: file.id,
1992
+ name: file.name,
1993
+ folderPath,
1994
+ mimeType: file.mimeType,
1995
+ modifiedTime: file.modifiedTime,
1996
+ sourceLocale: inferLocaleFromDocName(file.name)
1997
+ });
1998
+ }
1999
+ if (recursive) {
2000
+ const subfolders = await listFilesInFolder3(folderId, FOLDER_MIME3, token);
2001
+ for (const folder of subfolders) {
2002
+ const subPath = folderPath ? `${folderPath}/${folder.name}` : folder.name;
2003
+ const subResults = await scanFolder2(
2004
+ folder.id,
2005
+ subPath,
2006
+ token,
2007
+ recursive,
2008
+ nameFilter,
2009
+ seen
2010
+ );
2011
+ results.push(...subResults);
2012
+ }
2013
+ }
2014
+ return results;
2015
+ }
2016
+ async function scanDriveFolderForDocs(options) {
2017
+ const { folderId, recursive = true, nameFilter, credentials } = options;
2018
+ const token = await getAccessToken3(credentials);
2019
+ return scanFolder2(folderId, "", token, recursive, nameFilter);
2020
+ }
2021
+
2022
+ // src/utils/docIngester.ts
2023
+ var import_google_auth_library3 = require("google-auth-library");
2024
+ var import_google_spreadsheet3 = require("google-spreadsheet");
2025
+
2026
+ // src/utils/docParser.ts
2027
+ function slugifyKey(text) {
2028
+ return text.toLowerCase().replace(/[^\w]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
2029
+ }
2030
+ function parseDocContent(content, options = {}) {
2031
+ const { strategy = "heading", defaultSheetName = "content" } = options;
2032
+ if (strategy === "marker") return parseWithMarkers(content, defaultSheetName);
2033
+ if (strategy === "numbered") return parseNumbered(content, defaultSheetName);
2034
+ return parseWithHeadings(content, defaultSheetName);
2035
+ }
2036
+ function parseWithHeadings(content, defaultSheetName) {
2037
+ const lines = content.split("\n");
2038
+ const entries = [];
2039
+ let currentSheet = defaultSheetName;
2040
+ let currentKey = null;
2041
+ const valueLines = [];
2042
+ function flushEntry() {
2043
+ if (currentKey !== null) {
2044
+ const value = valueLines.join("\n").trim();
2045
+ if (value) {
2046
+ entries.push({ sheetName: currentSheet, key: currentKey, value });
2047
+ }
2048
+ currentKey = null;
2049
+ valueLines.length = 0;
2050
+ }
2051
+ }
2052
+ for (const rawLine of lines) {
2053
+ const line = rawLine.trimEnd();
2054
+ if (line.startsWith("# ")) {
2055
+ flushEntry();
2056
+ currentSheet = slugifyKey(line.slice(2).trim()) || defaultSheetName;
2057
+ currentKey = null;
2058
+ valueLines.length = 0;
2059
+ } else if (line.startsWith("## ")) {
2060
+ flushEntry();
2061
+ currentKey = slugifyKey(line.slice(3).trim());
2062
+ valueLines.length = 0;
2063
+ } else if (currentKey !== null) {
2064
+ valueLines.push(line);
2065
+ }
2066
+ }
2067
+ flushEntry();
2068
+ return entries;
2069
+ }
2070
+ function parseWithMarkers(content, defaultSheetName) {
2071
+ const MARKER_RE = /\[\[key:([^\]]{1,200})\]\]/g;
2072
+ const entries = [];
2073
+ const segments = content.split(MARKER_RE);
2074
+ for (let i = 1; i < segments.length; i += 2) {
2075
+ const keyPath = segments[i].trim();
2076
+ const value = (segments[i + 1] ?? "").trim();
2077
+ if (!keyPath || !value) continue;
2078
+ const dotIdx = keyPath.indexOf(".");
2079
+ let sheetName;
2080
+ let key;
2081
+ if (dotIdx !== -1) {
2082
+ sheetName = slugifyKey(keyPath.slice(0, dotIdx));
2083
+ key = slugifyKey(keyPath.slice(dotIdx + 1));
2084
+ } else {
2085
+ sheetName = defaultSheetName;
2086
+ key = slugifyKey(keyPath);
2087
+ }
2088
+ if (sheetName && key) {
2089
+ entries.push({ sheetName, key, value });
2090
+ }
2091
+ }
2092
+ return entries;
2093
+ }
2094
+ function parseNumbered(content, defaultSheetName) {
2095
+ const entries = [];
2096
+ let counter = 0;
2097
+ const paragraphs = content.split(/\n{2,}/);
2098
+ for (const para of paragraphs) {
2099
+ const value = para.replace(/^[#\s]+/, "").trim();
2100
+ if (value) {
2101
+ counter++;
2102
+ entries.push({
2103
+ sheetName: defaultSheetName,
2104
+ key: `item_${counter}`,
2105
+ value
2106
+ });
2107
+ }
2108
+ }
2109
+ return entries;
2110
+ }
2111
+
2112
+ // src/utils/docIngester.ts
2113
+ async function getDriveExportToken(credentials) {
2114
+ const clientEmail = credentials?.GOOGLE_CLIENT_EMAIL ?? process.env.GOOGLE_CLIENT_EMAIL;
2115
+ const privateKey = credentials?.GOOGLE_PRIVATE_KEY ?? process.env.GOOGLE_PRIVATE_KEY;
2116
+ if (!clientEmail || !privateKey) {
2117
+ throw new Error(
2118
+ "Google Drive credentials required: GOOGLE_CLIENT_EMAIL and GOOGLE_PRIVATE_KEY"
2119
+ );
2120
+ }
2121
+ const normalizedKey = privateKey.replace(/\\n/g, "\n");
2122
+ const auth = new import_google_auth_library3.GoogleAuth({
2123
+ credentials: { client_email: clientEmail, private_key: normalizedKey },
2124
+ scopes: ["https://www.googleapis.com/auth/drive.readonly"]
2125
+ });
2126
+ const client = await auth.getClient();
2127
+ const tokenResponse = await client.getAccessToken();
2128
+ return tokenResponse.token;
2129
+ }
2130
+ async function exportDoc(docId, credentials) {
2131
+ const token = await getDriveExportToken(credentials);
2132
+ const base = `https://www.googleapis.com/drive/v3/files/${docId}/export`;
2133
+ const mdRes = await fetch(
2134
+ `${base}?mimeType=text%2Fmarkdown`,
2135
+ { headers: { Authorization: `Bearer ${token}` } }
2136
+ );
2137
+ if (mdRes.ok) return mdRes.text();
2138
+ const txtRes = await fetch(
2139
+ `${base}?mimeType=text%2Fplain`,
2140
+ { headers: { Authorization: `Bearer ${token}` } }
2141
+ );
2142
+ if (txtRes.ok) return txtRes.text();
2143
+ const errText = await txtRes.text();
2144
+ throw new Error(
2145
+ `Failed to export doc ${docId}: HTTP ${txtRes.status} \u2013 ${errText}`
2146
+ );
2147
+ }
2148
+ function entriesToSeedKeys(entries) {
2149
+ const keys = {};
2150
+ const counts = /* @__PURE__ */ new Map();
2151
+ for (const entry of entries) {
2152
+ const base = `${entry.sheetName}.${entry.key}`;
2153
+ const count = (counts.get(base) ?? 0) + 1;
2154
+ counts.set(base, count);
2155
+ const finalKey = count > 1 ? `${base}_${count}` : base;
2156
+ keys[finalKey] = entry.value;
2157
+ }
2158
+ return keys;
2159
+ }
2160
+ function entriesToTranslationData(entries, locale) {
2161
+ const data = {};
2162
+ data[locale] = {};
2163
+ const counts = /* @__PURE__ */ new Map();
2164
+ for (const entry of entries) {
2165
+ const sheetName = entry.sheetName;
2166
+ const entryKey = entry.key;
2167
+ if (sheetName === "__proto__" || sheetName === "constructor" || sheetName === "prototype" || entryKey === "__proto__" || entryKey === "constructor" || entryKey === "prototype") {
2168
+ continue;
2169
+ }
2170
+ if (!data[locale][sheetName]) {
2171
+ data[locale][sheetName] = {};
2172
+ }
2173
+ const base = `${sheetName}::${entryKey}`;
2174
+ const count = (counts.get(base) ?? 0) + 1;
2175
+ counts.set(base, count);
2176
+ const finalKey = count > 1 ? `${entryKey}_${count}` : entryKey;
2177
+ data[locale][sheetName][finalKey] = entry.value;
2178
+ }
2179
+ return data;
2180
+ }
2181
+ function docTitle(name) {
2182
+ return name.replace(/_[a-zA-Z]{2,3}(?:[-_][a-zA-Z]{2,4})?$/, "").trim() || name;
2183
+ }
2184
+ async function ingestDoc(docFile, options = {}) {
2185
+ const {
2186
+ targetLocales,
2187
+ keyStrategy = "heading",
2188
+ updateMode = "create-only",
2189
+ credentials,
2190
+ existingEntry,
2191
+ waitSeconds = 1
2192
+ } = options;
2193
+ const sourceLocale = docFile.sourceLocale ?? "en";
2194
+ const entry = existingEntry ? { ...existingEntry, modifiedTime: docFile.modifiedTime } : {
2195
+ id: docFile.id,
2196
+ name: docFile.name,
2197
+ folderPath: docFile.folderPath,
2198
+ generatedFromDoc: true,
2199
+ sourceLocale,
2200
+ modifiedTime: docFile.modifiedTime
2201
+ };
2202
+ const hasLinkedSheet = !!entry.linkedSpreadsheetId;
2203
+ const shouldRefresh = updateMode === "refresh-if-newer" && hasLinkedSheet && !!docFile.modifiedTime && !!existingEntry?.lastIngestedAt && new Date(docFile.modifiedTime) > new Date(existingEntry.lastIngestedAt);
2204
+ if (hasLinkedSheet && !shouldRefresh) {
2205
+ console.log(
2206
+ `[docIngester] Skipping "${docFile.name}" \u2013 linked spreadsheet is already up-to-date.`
2207
+ );
2208
+ return { action: "skipped", entry };
2209
+ }
2210
+ console.log(
2211
+ `[docIngester] Exporting doc "${docFile.name}" (id: ${docFile.id})\u2026`
2212
+ );
2213
+ const content = await exportDoc(docFile.id, credentials);
2214
+ const sheetBaseName = slugifyKey(docTitle(docFile.name)) || "content";
2215
+ const entries = parseDocContent(content, {
2216
+ strategy: keyStrategy,
2217
+ defaultSheetName: sheetBaseName
2218
+ });
2219
+ if (entries.length === 0) {
2220
+ console.warn(
2221
+ `[docIngester] Doc "${docFile.name}" produced no translation entries \u2013 skipping.`
2222
+ );
2223
+ return { action: "skipped", entry };
2224
+ }
2225
+ if (!hasLinkedSheet) {
2226
+ const authClient2 = createAuthClient();
2227
+ const seedKeys = entriesToSeedKeys(entries);
2228
+ const title = docTitle(docFile.name);
2229
+ const { spreadsheetId: spreadsheetId2 } = await createSpreadsheet(authClient2, {
2230
+ title,
2231
+ sourceLocale,
2232
+ targetLocales,
2233
+ seedKeys
2234
+ });
2235
+ entry.linkedSpreadsheetId = spreadsheetId2;
2236
+ entry.lastIngestedAt = (/* @__PURE__ */ new Date()).toISOString();
2237
+ console.log(
2238
+ `[docIngester] Created spreadsheet ${spreadsheetId2} from doc "${docFile.name}".`
2239
+ );
2240
+ return { action: "created", entry };
2241
+ }
2242
+ const authClient = createAuthClient();
2243
+ const spreadsheetId = entry.linkedSpreadsheetId;
2244
+ const doc = new import_google_spreadsheet3.GoogleSpreadsheet(spreadsheetId, authClient);
2245
+ await doc.loadInfo();
2246
+ const changes = entriesToTranslationData(entries, sourceLocale);
2247
+ await updateSpreadsheetWithLocalChanges(
2248
+ doc,
2249
+ changes,
2250
+ waitSeconds,
2251
+ false,
2252
+ // autoTranslate – formulas already exist in non-base columns
2253
+ {},
2254
+ false
2255
+ );
2256
+ entry.lastIngestedAt = (/* @__PURE__ */ new Date()).toISOString();
2257
+ console.log(
2258
+ `[docIngester] Refreshed spreadsheet ${spreadsheetId} from doc "${docFile.name}".`
2259
+ );
2260
+ return { action: "refreshed", entry };
2261
+ }
1904
2262
 
1905
2263
  // src/utils/getDriveTranslations.ts
1906
2264
  function sanitizeFolderName(name) {
1907
2265
  return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "sheet";
1908
2266
  }
2267
+ async function moveSpreadsheetToFolder(spreadsheetId, folderId) {
2268
+ const clientEmail = process.env.GOOGLE_CLIENT_EMAIL;
2269
+ const rawPrivateKey = process.env.GOOGLE_PRIVATE_KEY;
2270
+ let credentials;
2271
+ if (clientEmail && rawPrivateKey) {
2272
+ credentials = { client_email: clientEmail, private_key: normalizePrivateKey(rawPrivateKey) };
2273
+ }
2274
+ const driveAuth = buildGoogleAuth(
2275
+ ["https://www.googleapis.com/auth/drive.file"],
2276
+ credentials
2277
+ );
2278
+ const fileRes = await driveAuth.request({
2279
+ url: `https://www.googleapis.com/drive/v3/files/${spreadsheetId}`,
2280
+ params: { fields: "parents" }
2281
+ });
2282
+ const parentIds = fileRes.data.parents ?? [];
2283
+ await driveAuth.request({
2284
+ url: `https://www.googleapis.com/drive/v3/files/${spreadsheetId}`,
2285
+ method: "PATCH",
2286
+ params: {
2287
+ addParents: folderId,
2288
+ ...parentIds.length > 0 ? { removeParents: parentIds.join(",") } : {},
2289
+ fields: "id,parents"
2290
+ }
2291
+ });
2292
+ }
1909
2293
  async function manageDriveTranslations(options) {
1910
2294
  const {
1911
2295
  driveFolderId,
@@ -1923,7 +2307,14 @@ async function manageDriveTranslations(options) {
1923
2307
  projectName,
1924
2308
  domain,
1925
2309
  defaultLocale,
1926
- projectMetadata
2310
+ projectMetadata,
2311
+ // Doc ingestion options
2312
+ scanForDocs = false,
2313
+ docNameFilter,
2314
+ docSourceLocale,
2315
+ docKeyStrategy,
2316
+ docUpdateMode,
2317
+ docTargetLocales
1927
2318
  } = options;
1928
2319
  if (syncImages && !imageOutputPath) {
1929
2320
  throw new Error(
@@ -1954,6 +2345,36 @@ async function manageDriveTranslations(options) {
1954
2345
  if (!name) return true;
1955
2346
  return spreadsheetNameFilter.test(name);
1956
2347
  }) : allIds;
2348
+ if (driveFolderId && filteredIds.length === 0 && translationOptions.autoCreate !== false) {
2349
+ console.log(
2350
+ `[manageDriveTranslations] Drive folder "${driveFolderId}" contains no spreadsheets. Bootstrapping a new spreadsheet\u2026`
2351
+ );
2352
+ const authClient = createAuthClient();
2353
+ const bootstrapTitle = translationOptions.spreadsheetTitle ?? "google-sheet-translations";
2354
+ const created = await createSpreadsheet(authClient, {
2355
+ title: bootstrapTitle,
2356
+ sourceLocale: translationOptions.sourceLocale,
2357
+ targetLocales: translationOptions.targetLocales
2358
+ });
2359
+ console.log(`[manageDriveTranslations] \u2705 Spreadsheet created: ${created.url}`);
2360
+ try {
2361
+ await moveSpreadsheetToFolder(created.spreadsheetId, driveFolderId);
2362
+ console.log(
2363
+ `[manageDriveTranslations] \u2705 Spreadsheet moved into Drive folder "${driveFolderId}"`
2364
+ );
2365
+ } catch (moveErr) {
2366
+ console.warn(
2367
+ `[manageDriveTranslations] \u26A0\uFE0F Could not move spreadsheet into Drive folder:`,
2368
+ moveErr.message
2369
+ );
2370
+ console.warn(
2371
+ ` Please move spreadsheet "${created.spreadsheetId}" into folder "${driveFolderId}" manually.`
2372
+ );
2373
+ }
2374
+ filteredIds.push(created.spreadsheetId);
2375
+ discoveredNames.set(created.spreadsheetId, bootstrapTitle);
2376
+ discoveredFolderPaths.set(created.spreadsheetId, "");
2377
+ }
1957
2378
  let translations;
1958
2379
  const spreadsheetEntries = [];
1959
2380
  const baseOutputDir = translationOptions.translationsOutputDir ?? "translations";
@@ -2007,8 +2428,48 @@ async function manageDriveTranslations(options) {
2007
2428
  });
2008
2429
  }
2009
2430
  let manifest;
2431
+ let docIngestResults;
2432
+ const docEntries = [];
2433
+ const resolvedManifestPath = manifestPath ?? import_node_path8.default.join(baseOutputDir, "i18n-manifest.json");
2434
+ if (driveFolderId && scanForDocs) {
2435
+ const previousManifest = readManifest(resolvedManifestPath);
2436
+ const docScanOptions = {
2437
+ folderId: driveFolderId
2438
+ };
2439
+ const discoveredDocs = await scanDriveFolderForDocs(docScanOptions);
2440
+ console.log(
2441
+ `[manageDriveTranslations] Found ${discoveredDocs.length} doc(s) in Drive folder`
2442
+ );
2443
+ docIngestResults = [];
2444
+ for (const docFile of discoveredDocs) {
2445
+ if (docNameFilter && !docNameFilter.test(docFile.name)) continue;
2446
+ if (!docFile.sourceLocale && docSourceLocale) {
2447
+ docFile.sourceLocale = docSourceLocale;
2448
+ }
2449
+ const existingEntry = previousManifest?.docs?.find(
2450
+ (d) => d.id === docFile.id
2451
+ );
2452
+ const ingesterOptions = {
2453
+ targetLocales: docTargetLocales,
2454
+ keyStrategy: docKeyStrategy,
2455
+ updateMode: docUpdateMode,
2456
+ existingEntry,
2457
+ waitSeconds: translationOptions.waitSeconds
2458
+ };
2459
+ try {
2460
+ const result = await ingestDoc(docFile, ingesterOptions);
2461
+ docEntries.push(result.entry);
2462
+ docIngestResults.push({ docName: docFile.name, action: result.action });
2463
+ } catch (err) {
2464
+ console.error(
2465
+ `[manageDriveTranslations] Failed to ingest doc "${docFile.name}":`,
2466
+ err
2467
+ );
2468
+ if (existingEntry) docEntries.push(existingEntry);
2469
+ }
2470
+ }
2471
+ }
2010
2472
  if (shouldCreateManifest) {
2011
- const resolvedManifestPath = manifestPath ?? import_node_path8.default.join(baseOutputDir, "i18n-manifest.json");
2012
2473
  manifest = buildManifest({
2013
2474
  translations,
2014
2475
  spreadsheets: spreadsheetEntries,
@@ -2017,11 +2478,12 @@ async function manageDriveTranslations(options) {
2017
2478
  projectName,
2018
2479
  domain,
2019
2480
  defaultLocale,
2020
- projectMetadata
2481
+ projectMetadata,
2482
+ docs: docEntries.length > 0 ? docEntries : void 0
2021
2483
  });
2022
2484
  writeManifest(manifest, resolvedManifestPath);
2023
2485
  }
2024
- return { translations, spreadsheetIds: filteredIds, imageSync, manifest };
2486
+ return { translations, spreadsheetIds: filteredIds, imageSync, manifest, docIngestResults };
2025
2487
  }
2026
2488
 
2027
2489
  // src/index.ts
@@ -2037,6 +2499,9 @@ var index_default = getSpreadSheetData;
2037
2499
  createAuthClient,
2038
2500
  createLocaleMapping,
2039
2501
  createSpreadsheet,
2502
+ entriesToSeedKeys,
2503
+ entriesToTranslationData,
2504
+ exportDoc,
2040
2505
  filterValidLocales,
2041
2506
  findLocalChanges,
2042
2507
  getGoogleTranslateCode,
@@ -2048,6 +2513,8 @@ var index_default = getSpreadSheetData;
2048
2513
  getSpreadSheetData,
2049
2514
  getTranslationSummary,
2050
2515
  handleBidirectionalSync,
2516
+ inferLocaleFromDocName,
2517
+ ingestDoc,
2051
2518
  isValidLocale,
2052
2519
  manageDriveTranslations,
2053
2520
  mergeMultipleTranslationData,
@@ -2055,10 +2522,14 @@ var index_default = getSpreadSheetData;
2055
2522
  normalizeExtension,
2056
2523
  normalizeLocaleCode,
2057
2524
  normalizePrivateKey,
2525
+ parseDocContent,
2058
2526
  processRawRows,
2527
+ readManifest,
2059
2528
  readPublicSheet,
2060
2529
  resolveLocaleWithFallback,
2530
+ scanDriveFolderForDocs,
2061
2531
  scanDriveFolderForSpreadsheets,
2532
+ slugifyKey,
2062
2533
  syncDriveImages,
2063
2534
  updateSpreadsheetWithLocalChanges,
2064
2535
  validateCredentials,