@el-j/google-sheet-translations 3.0.0-beta.13 → 3.0.0-beta.14

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.
@@ -1 +1 @@
1
- {"version":3,"file":"rowTransformer.d.ts","sourceRoot":"","sources":["../../src/core/rowTransformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,eAAe,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACzE,mBAAmB,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,KAAK,mBAAmB,CAAC;IAC3F,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC,CAAC;CAClD;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EAAE,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,0BAA0B,GAC/B,qBAAqB,CA8EvB"}
1
+ {"version":3,"file":"rowTransformer.d.ts","sourceRoot":"","sources":["../../src/core/rowTransformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,eAAe,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IACzE,mBAAmB,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,KAAK,mBAAmB,CAAC;IAC3F,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC,CAAC;CAClD;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EAAE,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,0BAA0B,GAC/B,qBAAqB,CAqFvB"}
package/dist/esm/index.js CHANGED
@@ -12,6 +12,7 @@ import fs$1 from "node:fs/promises";
12
12
  import { parse } from "csv-parse/sync";
13
13
  import crypto from "node:crypto";
14
14
  import nacl from "tweetnacl";
15
+ import { WebSocket } from "ws";
15
16
  //#region \0rolldown/runtime.js
16
17
  var __create = Object.create;
17
18
  var __defProp = Object.defineProperty;
@@ -224,9 +225,9 @@ function transformRowsToSheetData(rows, sheetTitle, deps) {
224
225
  if (!originalHeader) continue;
225
226
  const nonEmptyLanguageCells = rows.map((row) => {
226
227
  const keyField = Object.keys(row).find((k) => k.toLowerCase() === keyColumn);
227
- if (!keyField || !row[keyField] || !row[originalHeader]) return {};
228
+ if (!keyField || !row[keyField]) return {};
228
229
  const rowLocal = {};
229
- rowLocal[row[keyField].toString().toLowerCase()] = row[originalHeader];
230
+ rowLocal[row[keyField].toString().toLowerCase()] = row[originalHeader] ?? "";
230
231
  return rowLocal;
231
232
  }).filter((cell) => Object.keys(cell).length > 0);
232
233
  const prepareObj = {};
@@ -737,7 +738,7 @@ function findLocalChanges(localData, spreadsheetData) {
737
738
  for (const sheet of Object.keys(localData[locale])) {
738
739
  if (!localData[locale][sheet]) continue;
739
740
  if (sheet === "i18n") continue;
740
- for (const key of Object.keys(localData[locale][sheet])) if (!resolvedLocale || !spreadsheetData[resolvedLocale]?.[sheet] || !spreadsheetData[resolvedLocale][sheet][key]) {
741
+ for (const key of Object.keys(localData[locale][sheet])) if (!resolvedLocale || !spreadsheetData[resolvedLocale]?.[sheet] || spreadsheetData[resolvedLocale][sheet][key] === void 0) {
741
742
  if (!changes[locale]) changes[locale] = {};
742
743
  if (!changes[locale][sheet]) changes[locale][sheet] = {};
743
744
  changes[locale][sheet][key] = localData[locale][sheet][key];
@@ -3428,6 +3429,49 @@ function encryptCryptPadPayload(plaintext, cryptKey, signKey) {
3428
3429
  }
3429
3430
  //#endregion
3430
3431
  //#region src/providers/cryptpad/netflux.ts
3432
+ function getWebSocketImpl() {
3433
+ if (typeof window !== "undefined" && window.WebSocket) return window.WebSocket;
3434
+ if (typeof process !== "undefined" && process.env.VITEST && typeof globalThis !== "undefined" && globalThis.WebSocket) return globalThis.WebSocket;
3435
+ return WebSocket;
3436
+ }
3437
+ function formatUnknownError(err) {
3438
+ const errObj = err;
3439
+ const details = [];
3440
+ if (errObj?.message) details.push(`message=${errObj.message}`);
3441
+ if (errObj?.error) {
3442
+ if (typeof errObj.error === "string") details.push(`inner=${errObj.error}`);
3443
+ else {
3444
+ const inner = errObj.error;
3445
+ if (inner.name) details.push(`innerName=${inner.name}`);
3446
+ if (inner.message) details.push(`innerMessage=${inner.message}`);
3447
+ if (inner.cause !== void 0) details.push(`innerCause=${String(inner.cause)}`);
3448
+ if (inner.stack) details.push(`innerStack=${inner.stack}`);
3449
+ }
3450
+ }
3451
+ if (err && typeof err === "object") {
3452
+ const e = err;
3453
+ if (e.type) details.push(`eventType=${e.type}`);
3454
+ const keys = Object.keys(e);
3455
+ if (keys.length > 0) details.push(`eventKeys=${keys.join(",")}`);
3456
+ }
3457
+ if (details.length > 0) return details.join(" | ");
3458
+ return String(err);
3459
+ }
3460
+ function shouldRetryBroadcastError(err) {
3461
+ const message = formatUnknownError(err).toLowerCase();
3462
+ return message.includes("unexpected server response: 429") || message.includes("server response: 429") || message.includes("too many requests");
3463
+ }
3464
+ function wait$1(ms) {
3465
+ return new Promise((resolve) => setTimeout(resolve, ms));
3466
+ }
3467
+ function withAttemptContext(err, attempt, maxAttempts) {
3468
+ if (err instanceof Error) {
3469
+ if (maxAttempts <= 1) return err;
3470
+ if (err.message.includes("Timeout") || err.message.includes("aborted") || err.message.includes("Operation aborted")) return err;
3471
+ }
3472
+ const detail = formatUnknownError(err);
3473
+ return /* @__PURE__ */ new Error(`Attempt ${attempt}/${maxAttempts} failed: ${detail}`);
3474
+ }
3431
3475
  /**
3432
3476
  * Discovers the real WebSocket URL for a CryptPad instance by querying its `/api/config` endpoint.
3433
3477
  */
@@ -3437,7 +3481,17 @@ async function resolveCryptPadWebsocketUrl(origin, signal) {
3437
3481
  const res = await fetch(configUrl, { signal });
3438
3482
  if (res.ok) {
3439
3483
  const match = (await res.text()).match(/"websocketPath":\s*"([^"]+)"/);
3440
- if (match && match[1]) return match[1];
3484
+ if (match && match[1]) {
3485
+ const candidate = match[1].trim();
3486
+ if (!candidate) throw new Error("Empty websocketPath");
3487
+ if (/^ws(s)?:\/\//i.test(candidate)) return candidate;
3488
+ if (candidate.startsWith("/") || !/^[a-z]+:\/\//i.test(candidate)) try {
3489
+ const urlObj = new URL(origin);
3490
+ const baseWsScheme = urlObj.protocol === "https:" ? "wss:" : "ws:";
3491
+ return candidate.startsWith("/") ? `${baseWsScheme}//${urlObj.host}${candidate}` : `${baseWsScheme}//${urlObj.host}/${candidate.replace(/^\//, "")}`;
3492
+ } catch {}
3493
+ return candidate;
3494
+ }
3441
3495
  }
3442
3496
  } catch {}
3443
3497
  const urlObj = new URL(origin);
@@ -3450,9 +3504,9 @@ async function resolveCryptPadWebsocketUrl(origin, signal) {
3450
3504
  */
3451
3505
  function fetchChannelHistory(wsUrl, channelHex, cryptKey, options = {}) {
3452
3506
  const { timeoutMs = 8e3, quietPeriodMs = 1200, signal } = options;
3453
- return new Promise((resolve, reject) => {
3507
+ const fetchOnce = () => new Promise((resolve, reject) => {
3454
3508
  if (signal?.aborted) return reject(/* @__PURE__ */ new Error("Operation aborted"));
3455
- const ws = new WebSocket(wsUrl);
3509
+ const ws = new (getWebSocketImpl())(wsUrl);
3456
3510
  let seq = 1;
3457
3511
  const decryptedMessages = [];
3458
3512
  let quietTimer = null;
@@ -3545,11 +3599,24 @@ function fetchChannelHistory(wsUrl, channelHex, cryptKey, options = {}) {
3545
3599
  };
3546
3600
  ws.onerror = (err) => {
3547
3601
  cleanup();
3548
- const errObj = err;
3549
- const detail = errObj?.message || errObj?.error?.message || (errObj?.error ? String(errObj.error) : String(err));
3550
- reject(/* @__PURE__ */ new Error(`CryptPad WebSocket error: ${detail}`));
3602
+ reject(/* @__PURE__ */ new Error(`CryptPad WebSocket error: ${formatUnknownError(err)}`));
3551
3603
  };
3552
3604
  });
3605
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 3);
3606
+ const retryDelayMs = Math.max(100, options.retryDelayMs ?? 1500);
3607
+ return (async () => {
3608
+ let lastError;
3609
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
3610
+ return await fetchOnce();
3611
+ } catch (error) {
3612
+ lastError = withAttemptContext(error, attempt, maxAttempts);
3613
+ if (signal?.aborted) throw new Error("Operation aborted");
3614
+ if (attempt >= maxAttempts || !shouldRetryBroadcastError(error)) throw lastError;
3615
+ const jitter = Math.floor(Math.random() * 250);
3616
+ await wait$1(retryDelayMs * attempt + jitter);
3617
+ }
3618
+ throw lastError ?? /* @__PURE__ */ new Error("CryptPad WebSocket history fetch failed.");
3619
+ })();
3553
3620
  }
3554
3621
  /**
3555
3622
  * Connects to the CryptPad Netflux WebSocket server, joins a channel,
@@ -3558,9 +3625,9 @@ function fetchChannelHistory(wsUrl, channelHex, cryptKey, options = {}) {
3558
3625
  */
3559
3626
  function broadcastChannelMessage(wsUrl, channelHex, cryptKey, message, options = {}) {
3560
3627
  const { timeoutMs = 8e3, signal } = options;
3561
- return new Promise((resolve, reject) => {
3628
+ const broadcastOnce = () => new Promise((resolve, reject) => {
3562
3629
  if (signal?.aborted) return reject(/* @__PURE__ */ new Error("Operation aborted"));
3563
- const ws = new WebSocket(wsUrl);
3630
+ const ws = new (getWebSocketImpl())(wsUrl);
3564
3631
  let seq = 1;
3565
3632
  let timeout = null;
3566
3633
  let sent = false;
@@ -3596,19 +3663,28 @@ function broadcastChannelMessage(wsUrl, channelHex, cryptKey, message, options =
3596
3663
  signal.addEventListener("abort", abortListener);
3597
3664
  }
3598
3665
  ws.onopen = () => {
3599
- ws.send(JSON.stringify([
3600
- seq++,
3601
- "JOIN",
3602
- channelHex
3603
- ]));
3666
+ try {
3667
+ ws.send(JSON.stringify([
3668
+ seq++,
3669
+ "JOIN",
3670
+ channelHex
3671
+ ]));
3672
+ } catch (error) {
3673
+ rejectOnce(/* @__PURE__ */ new Error(`CryptPad WebSocket JOIN send failed: ${formatUnknownError(error)}`));
3674
+ }
3604
3675
  };
3605
3676
  ws.onmessage = (event) => {
3677
+ const raw = typeof event.data === "string" ? event.data : event.data.toString();
3678
+ let msg;
3606
3679
  try {
3607
- const raw = typeof event.data === "string" ? event.data : event.data.toString();
3608
- const msg = JSON.parse(raw);
3609
- if (!Array.isArray(msg)) return;
3610
- const [, peerId, cmd] = msg;
3611
- if (cmd === "JOIN" && typeof peerId === "string" && !sent) {
3680
+ msg = JSON.parse(raw);
3681
+ } catch {
3682
+ return;
3683
+ }
3684
+ if (!Array.isArray(msg)) return;
3685
+ const [, peerId, cmd] = msg;
3686
+ if (cmd === "JOIN" && typeof peerId === "string" && !sent) {
3687
+ try {
3612
3688
  sent = true;
3613
3689
  const encrypted = encryptCryptPadPayload(message, cryptKey, options.signKey);
3614
3690
  ws.send(JSON.stringify([
@@ -3617,18 +3693,39 @@ function broadcastChannelMessage(wsUrl, channelHex, cryptKey, message, options =
3617
3693
  channelHex,
3618
3694
  encrypted
3619
3695
  ]));
3620
- setTimeout(() => {
3621
- resolveOnce();
3622
- }, 350);
3696
+ } catch (error) {
3697
+ rejectOnce(/* @__PURE__ */ new Error(`CryptPad WebSocket MSG send failed: ${formatUnknownError(error)}`));
3698
+ return;
3623
3699
  }
3624
- } catch {}
3700
+ setTimeout(() => {
3701
+ resolveOnce();
3702
+ }, 350);
3703
+ }
3704
+ };
3705
+ ws.onclose = (event) => {
3706
+ if (settled) return;
3707
+ if (!sent) rejectOnce(/* @__PURE__ */ new Error(`CryptPad WebSocket closed before broadcast (code=${event.code}, reason=${event.reason || "none"}, clean=${event.wasClean}).`));
3625
3708
  };
3626
3709
  ws.onerror = (err) => {
3627
- const errObj = err;
3628
- const detail = errObj?.message || errObj?.error?.message || (errObj?.error ? String(errObj.error) : String(err));
3629
- rejectOnce(/* @__PURE__ */ new Error(`CryptPad WebSocket broadcast error: ${detail}`));
3710
+ rejectOnce(/* @__PURE__ */ new Error(`CryptPad WebSocket broadcast error: ${formatUnknownError(err)}`));
3630
3711
  };
3631
3712
  });
3713
+ const maxAttempts = Math.max(1, options.maxAttempts ?? 3);
3714
+ const retryDelayMs = Math.max(100, options.retryDelayMs ?? 1500);
3715
+ return (async () => {
3716
+ let lastError;
3717
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
3718
+ await broadcastOnce();
3719
+ return;
3720
+ } catch (error) {
3721
+ lastError = withAttemptContext(error, attempt, maxAttempts);
3722
+ if (signal?.aborted) throw new Error("Operation aborted");
3723
+ if (attempt >= maxAttempts || !shouldRetryBroadcastError(error)) throw lastError;
3724
+ const jitter = Math.floor(Math.random() * 250);
3725
+ await wait$1(retryDelayMs * attempt + jitter);
3726
+ }
3727
+ throw lastError ?? /* @__PURE__ */ new Error("CryptPad WebSocket broadcast failed.");
3728
+ })();
3632
3729
  }
3633
3730
  /*!
3634
3731
  * Copyright 2015-2017 Thomas Rosenau
@@ -7352,6 +7449,8 @@ function extractOnlyOfficeChannelId(metadataMessages) {
7352
7449
  function parseOnlyOfficeChanges(rtMessages) {
7353
7450
  const cells = {};
7354
7451
  const sheetNames = /* @__PURE__ */ new Map();
7452
+ const preMapped = extractOnlyOfficeSheetIdMap(rtMessages);
7453
+ for (const [id, name] of Object.entries(preMapped.idToName)) sheetNames.set(id, name);
7355
7454
  let defaultSheetId = "6";
7356
7455
  for (const raw of rtMessages) try {
7357
7456
  const data = JSON.parse(raw);
@@ -7375,7 +7474,14 @@ function parseOnlyOfficeChanges(rtMessages) {
7375
7474
  const len = buf.readUInt32LE(j + 1);
7376
7475
  if (len > 0 && j + 5 + len <= buf.length) strings.push(buf.subarray(j + 5, j + 5 + len).toString("utf16le"));
7377
7476
  }
7378
- if (strings.length >= 2) sheetNames.set(defaultSheetId, strings[1]);
7477
+ if (strings.length >= 2) {
7478
+ let sid = defaultSheetId;
7479
+ if (buf[7] === 1) {
7480
+ const sLen = buf.readUInt32LE(8);
7481
+ if (sLen > 0 && 12 + sLen <= buf.length) sid = buf.subarray(12, 12 + sLen).toString("utf16le");
7482
+ }
7483
+ sheetNames.set(sid, strings[1]);
7484
+ }
7379
7485
  }
7380
7486
  if (magic === 19595520) {
7381
7487
  const strings = [];
@@ -7389,6 +7495,25 @@ function parseOnlyOfficeChanges(rtMessages) {
7389
7495
  sheetNames.set(id, name);
7390
7496
  }
7391
7497
  }
7498
+ if (magic === 19595776) {
7499
+ const strings = [];
7500
+ for (let j = 0; j < buf.length - 4; j++) if (buf[j] === 8) {
7501
+ const len = buf.readUInt32LE(j + 1);
7502
+ if (len > 0 && j + 5 + len <= buf.length) strings.push(buf.subarray(j + 5, j + 5 + len).toString("utf16le"));
7503
+ }
7504
+ if (strings.length >= 1) {
7505
+ const deletedId = strings[0];
7506
+ const deletedName = sheetNames.get(deletedId);
7507
+ sheetNames.delete(deletedId);
7508
+ if (deletedName) {
7509
+ const prefix = `${deletedName}!`;
7510
+ for (const key of Object.keys(cells)) if (key.startsWith(prefix)) delete cells[key];
7511
+ }
7512
+ if (deletedId === "6") {
7513
+ for (const key of Object.keys(cells)) if (!key.includes("!") || key.startsWith("Sheet1!")) delete cells[key];
7514
+ }
7515
+ }
7516
+ }
7392
7517
  if (magic === 19468289) {
7393
7518
  const sheetIdLen = buf.readUInt32LE(8);
7394
7519
  if (sheetIdLen > 0 && sheetIdLen < 200 && 12 + sheetIdLen + 17 <= buf.length) {
@@ -7402,7 +7527,7 @@ function parseOnlyOfficeChanges(rtMessages) {
7402
7527
  if (strLen >= 0 && strLen < 1e5 && k + 5 + strLen <= buf.length) {
7403
7528
  const val = buf.subarray(k + 5, k + 5 + strLen).toString("utf16le");
7404
7529
  const colLetter = colIndexToLetter(c1);
7405
- const tabName = sheetNames.get(sheetIdStr) || (sheetIdStr === "6" ? sheetNames.get("6") || "" : "");
7530
+ const tabName = sheetNames.get(sheetIdStr) && sheetNames.get(sheetIdStr) !== "Sheet1" ? sheetNames.get(sheetIdStr) : "";
7406
7531
  const cellRef = `${tabName ? `${tabName}!` : ""}${colLetter}${r1 + 1}`;
7407
7532
  cells[cellRef] = val;
7408
7533
  }
@@ -7469,9 +7594,15 @@ function extractOnlyOfficeSheetIdMap(rtMessages) {
7469
7594
  if (len > 0 && j + 5 + len <= buf.length) strings.push(buf.subarray(j + 5, j + 5 + len).toString("utf16le"));
7470
7595
  }
7471
7596
  if (strings.length >= 2) {
7472
- delete nameToId[idToName[defaultSheetId]];
7473
- idToName[defaultSheetId] = strings[1];
7474
- nameToId[strings[1]] = defaultSheetId;
7597
+ let sid = defaultSheetId;
7598
+ if (buf[7] === 1) {
7599
+ const sLen = buf.readUInt32LE(8);
7600
+ if (sLen > 0 && 12 + sLen <= buf.length) sid = buf.subarray(12, 12 + sLen).toString("utf16le");
7601
+ }
7602
+ const oldName = idToName[sid] || strings[0];
7603
+ delete nameToId[oldName];
7604
+ idToName[sid] = strings[1];
7605
+ nameToId[strings[1]] = sid;
7475
7606
  }
7476
7607
  }
7477
7608
  if (magic === 19595520) {
@@ -7487,6 +7618,20 @@ function extractOnlyOfficeSheetIdMap(rtMessages) {
7487
7618
  nameToId[name] = id;
7488
7619
  }
7489
7620
  }
7621
+ if (magic === 19595776) {
7622
+ const strings = [];
7623
+ for (let j = 0; j < buf.length - 4; j++) if (buf[j] === 8) {
7624
+ const len = buf.readUInt32LE(j + 1);
7625
+ if (len > 0 && j + 5 + len <= buf.length) strings.push(buf.subarray(j + 5, j + 5 + len).toString("utf16le"));
7626
+ }
7627
+ if (strings.length >= 1) {
7628
+ const id = strings[0];
7629
+ const name = idToName[id];
7630
+ delete idToName[id];
7631
+ if (name) delete nameToId[name];
7632
+ if (id === defaultSheetId) delete nameToId["Sheet1"];
7633
+ }
7634
+ }
7490
7635
  }
7491
7636
  } catch {}
7492
7637
  return {
@@ -7765,8 +7910,7 @@ function buildOnlyOfficeChangePayload(updates, defaultSheetId) {
7765
7910
  time: now
7766
7911
  });
7767
7912
  for (const u of updates) {
7768
- const colStr = typeof u.col === "number" ? colIndexToLetter(u.col) : u.col.toUpperCase();
7769
- const ref = `${u.sheet && u.sheet.trim().length > 0 ? `${u.sheet.trim()}!` : ""}${colStr}${u.row}`;
7913
+ const ref = `${typeof u.col === "number" ? colIndexToLetter(u.col) : u.col.toUpperCase()}${u.row}`;
7770
7914
  const sid = u.sheetId ?? defaultSheetId ?? 6;
7771
7915
  const rec = u.formula !== void 0 ? encodeOnlyOfficeFormulaCellRecord(ref, u.formula, sid) : encodeOnlyOfficeCellRecord(ref, u.value ?? "", sid);
7772
7916
  changeItems.push({
@@ -7879,6 +8023,142 @@ function buildOnlyOfficeSheetAddPayload(name, sheetId, insertBeforeIndex) {
7879
8023
  isExcel: true
7880
8024
  });
7881
8025
  }
8026
+ /**
8027
+ * Encodes a single "delete worksheet tab" record into the native OnlyOffice binary format:
8028
+ * AscCH.historyitem_Workbook_SheetDelete (magic 0x012b0200), UndoRedoDataTypes.SheetDelete (26, 0x1a).
8029
+ *
8030
+ * @param sheetId - Sheet identifier to delete.
8031
+ */
8032
+ function encodeOnlyOfficeSheetDeleteRecord(sheetId) {
8033
+ const sheetIdStr = String(sheetId);
8034
+ const sheetIdBuf = Buffer.from(sheetIdStr, "utf16le");
8035
+ const idLen = sheetIdBuf.length;
8036
+ const propertyBlock = Buffer.concat([
8037
+ Buffer.from([
8038
+ 0,
8039
+ 2,
8040
+ 1
8041
+ ]),
8042
+ Buffer.from([1, 8]),
8043
+ Buffer.alloc(4),
8044
+ sheetIdBuf,
8045
+ Buffer.from([
8046
+ 2,
8047
+ 9,
8048
+ 255,
8049
+ 0,
8050
+ 0
8051
+ ])
8052
+ ]);
8053
+ propertyBlock.writeUInt32LE(idLen, 5);
8054
+ const remainingLen = Buffer.alloc(4);
8055
+ remainingLen.writeUInt32LE(propertyBlock.length + 2, 0);
8056
+ const magic = Buffer.alloc(4);
8057
+ magic.writeUInt32BE(19595776, 0);
8058
+ const body = Buffer.concat([
8059
+ magic,
8060
+ Buffer.from([0, 26]),
8061
+ remainingLen,
8062
+ propertyBlock
8063
+ ]);
8064
+ const header = Buffer.alloc(4);
8065
+ header.writeUInt32LE(body.length + 2, 0);
8066
+ return Buffer.concat([header, body]);
8067
+ }
8068
+ /**
8069
+ * Formats a single "delete worksheet tab" operation into an OnlyOffice `saveChanges`
8070
+ * message, structured the same way as {@link buildOnlyOfficeChangePayload}.
8071
+ */
8072
+ function buildOnlyOfficeSheetDeletePayload(sheetId) {
8073
+ const txOpen = Buffer.from("0a0000000129000000ff00000000", "hex");
8074
+ const now = Date.now();
8075
+ const rec = encodeOnlyOfficeSheetDeleteRecord(sheetId);
8076
+ return JSON.stringify({
8077
+ type: "saveChanges",
8078
+ changes: [{
8079
+ change: JSON.stringify(`${txOpen.length};${txOpen.toString("base64")}`),
8080
+ time: now
8081
+ }, {
8082
+ change: JSON.stringify(`${rec.length};${rec.toString("base64")}`),
8083
+ time: now
8084
+ }],
8085
+ startSaveChanges: true,
8086
+ endSaveChanges: true,
8087
+ isExcel: true
8088
+ });
8089
+ }
8090
+ /**
8091
+ * Encodes a single "rename worksheet tab" record into the native OnlyOffice binary format:
8092
+ * AscCH.historyitem_Worksheet_Rename (magic 0x012a1201), UndoRedoDataTypes.FromTo (5),
8093
+ * matching `UndoRedoData_FromTo` (`from` = 0, `to` = 1, `copyRange` = 2, `sheetIdTo` = 3).
8094
+ *
8095
+ * @param sheetId - Internal sheet identifier to rename (e.g. '6').
8096
+ * @param oldName - Current name of the sheet tab (e.g. 'Sheet1').
8097
+ * @param newName - Desired new name for the tab (e.g. 'i18n').
8098
+ */
8099
+ function encodeOnlyOfficeSheetRenameRecord(sheetId, oldName, newName) {
8100
+ const sheetIdBuf = Buffer.from(String(sheetId), "utf16le");
8101
+ const sheetIdLen = Buffer.alloc(4);
8102
+ sheetIdLen.writeUInt32LE(sheetIdBuf.length, 0);
8103
+ const oldNameBuf = Buffer.from(oldName, "utf16le");
8104
+ const oldNameLen = Buffer.alloc(4);
8105
+ oldNameLen.writeUInt32LE(oldNameBuf.length, 0);
8106
+ const newNameBuf = Buffer.from(newName, "utf16le");
8107
+ const newNameLen = Buffer.alloc(4);
8108
+ newNameLen.writeUInt32LE(newNameBuf.length, 0);
8109
+ const propertyBlock = Buffer.concat([
8110
+ Buffer.from([0, 8]),
8111
+ oldNameLen,
8112
+ oldNameBuf,
8113
+ Buffer.from([1, 8]),
8114
+ newNameLen,
8115
+ newNameBuf,
8116
+ Buffer.from([2, 0]),
8117
+ Buffer.from([3, 1])
8118
+ ]);
8119
+ const propertyLen = Buffer.alloc(4);
8120
+ propertyLen.writeUInt32LE(propertyBlock.length, 0);
8121
+ const magic = Buffer.from([
8122
+ 1,
8123
+ 42,
8124
+ 18,
8125
+ 1
8126
+ ]);
8127
+ const body = Buffer.concat([
8128
+ magic,
8129
+ sheetIdLen,
8130
+ sheetIdBuf,
8131
+ Buffer.from([0]),
8132
+ Buffer.from([5]),
8133
+ propertyLen,
8134
+ propertyBlock
8135
+ ]);
8136
+ const header = Buffer.alloc(4);
8137
+ header.writeUInt32LE(body.length, 0);
8138
+ return Buffer.concat([header, body]);
8139
+ }
8140
+ /**
8141
+ * Formats a single "rename worksheet tab" operation into an OnlyOffice `saveChanges`
8142
+ * message, structured the same way as {@link buildOnlyOfficeChangePayload}.
8143
+ */
8144
+ function buildOnlyOfficeSheetRenamePayload(sheetId, oldName, newName) {
8145
+ const txOpen = Buffer.from("0a0000000129000000ff00000000", "hex");
8146
+ const now = Date.now();
8147
+ const rec = encodeOnlyOfficeSheetRenameRecord(sheetId, oldName, newName);
8148
+ return JSON.stringify({
8149
+ type: "saveChanges",
8150
+ changes: [{
8151
+ change: JSON.stringify(`${txOpen.length};${txOpen.toString("base64")}`),
8152
+ time: now
8153
+ }, {
8154
+ change: JSON.stringify(`${rec.length};${rec.toString("base64")}`),
8155
+ time: now
8156
+ }],
8157
+ startSaveChanges: true,
8158
+ endSaveChanges: true,
8159
+ isExcel: true
8160
+ });
8161
+ }
7882
8162
  //#endregion
7883
8163
  //#region src/providers/cryptpad/client.ts
7884
8164
  /**
@@ -7998,7 +8278,7 @@ var CryptPadClient = class {
7998
8278
  await new Promise((r) => setTimeout(r, 800));
7999
8279
  }
8000
8280
  const sheetId = generateOnlyOfficeSheetId();
8001
- const insertAt = insertBeforeIndex ?? data.sheetNames.length;
8281
+ const insertAt = Math.min(insertBeforeIndex ?? data.sheetNames.length, data.sheetNames.length);
8002
8282
  await this.broadcastSheetAdd(name, sheetId, insertAt, rtChannel, signal);
8003
8283
  return sheetId;
8004
8284
  }
@@ -8020,6 +8300,61 @@ var CryptPadClient = class {
8020
8300
  });
8021
8301
  }
8022
8302
  /**
8303
+ * Deletes a worksheet tab by name from the CryptPad workbook.
8304
+ *
8305
+ * @param sheetName - Name of the sheet tab to delete (e.g. 'Sheet1').
8306
+ * @param signal - Optional AbortSignal.
8307
+ */
8308
+ async deleteSheet(sheetName, signal) {
8309
+ const data = await this.fetchSheetData(signal);
8310
+ const targetSheetId = data.sheetIds?.[sheetName];
8311
+ if (!targetSheetId) return;
8312
+ if (data.sheetNames.length <= 1) throw new Error(`Cannot delete sheet "${sheetName}": workbook must contain at least one worksheet.`);
8313
+ const rtChannel = data.metadata.rtChannelId;
8314
+ if (!rtChannel) throw new Error("Cannot delete sheet: pad has no active real-time collaboration channel.");
8315
+ await this.broadcastSheetDelete(targetSheetId, rtChannel, signal);
8316
+ }
8317
+ /**
8318
+ * Broadcasts a `Workbook_SheetDelete` record to an already-known RT channel.
8319
+ */
8320
+ async broadcastSheetDelete(sheetId, rtChannel, signal) {
8321
+ const wsUrl = await this.getWebsocketUrl(signal);
8322
+ const { cryptKey, signKey } = this.getKeys();
8323
+ await broadcastChannelMessage(wsUrl, rtChannel, cryptKey, buildOnlyOfficeSheetDeletePayload(sheetId), {
8324
+ timeoutMs: this.timeoutMs,
8325
+ signal,
8326
+ signKey
8327
+ });
8328
+ }
8329
+ /**
8330
+ * Broadcasts a `Worksheet_Rename` record to an already-known RT channel.
8331
+ */
8332
+ async broadcastSheetRename(sheetId, oldName, newName, rtChannel, signal) {
8333
+ const wsUrl = await this.getWebsocketUrl(signal);
8334
+ const { cryptKey, signKey } = this.getKeys();
8335
+ await broadcastChannelMessage(wsUrl, rtChannel, cryptKey, buildOnlyOfficeSheetRenamePayload(sheetId, oldName, newName), {
8336
+ timeoutMs: this.timeoutMs,
8337
+ signal,
8338
+ signKey
8339
+ });
8340
+ }
8341
+ /**
8342
+ * Renames an existing worksheet tab by name in the CryptPad workbook.
8343
+ *
8344
+ * @param oldSheetName - Current name of the sheet tab (e.g. 'Sheet1').
8345
+ * @param newSheetName - Desired new name for the tab (e.g. 'i18n').
8346
+ * @param signal - Optional AbortSignal.
8347
+ */
8348
+ async renameSheet(oldSheetName, newSheetName, signal) {
8349
+ if (oldSheetName === newSheetName) return;
8350
+ const data = await this.fetchSheetData(signal);
8351
+ const targetSheetId = data.sheetIds?.[oldSheetName];
8352
+ if (!targetSheetId) throw new Error(`Cannot rename sheet "${oldSheetName}": sheet not found in the workbook.`);
8353
+ const rtChannel = data.metadata.rtChannelId;
8354
+ if (!rtChannel) throw new Error("Cannot rename sheet: pad has no active real-time collaboration channel.");
8355
+ await this.broadcastSheetRename(targetSheetId, oldSheetName, newSheetName, rtChannel, signal);
8356
+ }
8357
+ /**
8023
8358
  * Writes a literal-text header row (row 1) into a sheet — the canonical, non-linked
8024
8359
  * form used for the `i18n` sheet itself (there is nothing to link an `i18n` header to)
8025
8360
  * and as the safe fallback whenever a new sheet's columns can't be aligned 1:1 with an
@@ -8138,7 +8473,12 @@ var CryptPadClient = class {
8138
8473
  sheetIdMap = extractOnlyOfficeSheetIdMap(rtMessages);
8139
8474
  }
8140
8475
  const multiSheets = convertCellsToMultiSheetRows(cells);
8141
- const sheetNames = Object.keys(multiSheets);
8476
+ const discoveredSheetNames = /* @__PURE__ */ new Set();
8477
+ for (const name of Object.keys(sheetIdMap.nameToId)) if (name && name.trim().length > 0) discoveredSheetNames.add(name);
8478
+ if (discoveredSheetNames.size === 0) {
8479
+ for (const name of Object.keys(multiSheets)) if (name && name.trim().length > 0) discoveredSheetNames.add(name);
8480
+ }
8481
+ const sheetNames = Array.from(discoveredSheetNames);
8142
8482
  const defaultSheet = multiSheets["Sheet1"] ? "Sheet1" : sheetNames[0];
8143
8483
  const defaultRows = defaultSheet && multiSheets[defaultSheet] ? multiSheets[defaultSheet] : convertCellsToSheetRows(cells);
8144
8484
  const groupedCells = groupCellsBySheet(cells);
@@ -8215,16 +8555,26 @@ var CryptPadClient = class {
8215
8555
  async writeSheetRows(sheetName, rows, options = {}) {
8216
8556
  if (rows.length === 0) return 0;
8217
8557
  const data = await this.fetchSheetData(options.signal);
8558
+ let rtChannel = data.metadata.rtChannelId;
8218
8559
  let existingRows = data.sheets[sheetName]?.rows ?? [];
8219
8560
  let targetSheetId = data.sheetIds?.[sheetName];
8220
8561
  let justCreated = false;
8221
8562
  if (!targetSheetId) {
8222
- this.onProgress?.(`Sheet "${sheetName}" not found — creating it.`);
8223
- targetSheetId = await this.createSheet(sheetName, data.sheetNames.length, options.signal);
8224
- existingRows = [];
8225
- justCreated = true;
8563
+ if (data.sheetNames.includes("Sheet1") && (data.sheets["Sheet1"]?.rows.length ?? 0) === 0 && Object.keys(data.sheets["Sheet1"]?.cells ?? {}).length === 0 && sheetName !== "Sheet1" && (data.sheetNames.length === 1 || sheetName === "i18n")) {
8564
+ const sheet1Id = data.sheetIds?.["Sheet1"] ?? "6";
8565
+ this.onProgress?.(`Renaming fresh empty "Sheet1" tab to "${sheetName}".`);
8566
+ if (rtChannel) await this.broadcastSheetRename(sheet1Id, "Sheet1", sheetName, rtChannel, options.signal);
8567
+ else await this.renameSheet("Sheet1", sheetName, options.signal);
8568
+ targetSheetId = sheet1Id;
8569
+ existingRows = [];
8570
+ justCreated = true;
8571
+ } else {
8572
+ this.onProgress?.(`Sheet "${sheetName}" not found — creating it.`);
8573
+ targetSheetId = await this.createSheet(sheetName, data.sheetNames.length, options.signal);
8574
+ existingRows = [];
8575
+ justCreated = true;
8576
+ }
8226
8577
  }
8227
- let rtChannel = data.metadata.rtChannelId;
8228
8578
  let keyColName = "key";
8229
8579
  const firstExisting = existingRows[0];
8230
8580
  const firstIncoming = rows[0];
@@ -8338,7 +8688,7 @@ function createCryptPadSheetInputProvider(options) {
8338
8688
  }).fetchSheetData(request.signal);
8339
8689
  const sourceTables = [];
8340
8690
  if (Array.isArray(sheetData.sheetNames) && sheetData.sheetNames.length > 1) {
8341
- for (const tabName of sheetData.sheetNames) if (requested.size === 0 || requested.has(tabName) || requested.has(source.tableName)) sourceTables.push({
8691
+ for (const tabName of sheetData.sheetNames) if (requested.size === 0 || requested.has(tabName)) sourceTables.push({
8342
8692
  tableId: `${source.tableId ?? url}#${tabName}`,
8343
8693
  tableName: tabName,
8344
8694
  rows: sheetData.sheets[tabName]?.rows ?? [],
@@ -8351,7 +8701,7 @@ function createCryptPadSheetInputProvider(options) {
8351
8701
  }
8352
8702
  });
8353
8703
  }
8354
- if (sourceTables.length === 0) sourceTables.push({
8704
+ if (sourceTables.length === 0 && (!Array.isArray(sheetData.sheetNames) || sheetData.sheetNames.length <= 1)) sourceTables.push({
8355
8705
  tableId: source.tableId ?? url,
8356
8706
  tableName: source.tableName,
8357
8707
  rows: sheetData.rows,
@@ -8380,12 +8730,12 @@ const CRYPTPAD_SHEET_OUTPUT_CAPABILITIES = createCapabilitySet({ writeTables: tr
8380
8730
  * Converts nested TranslationData `[locale][sheet][key] = value` into
8381
8731
  * tabular rows `Record<sheetName, SheetRow[]>`.
8382
8732
  */
8383
- function convertTranslationsToSheetRows(translations, localeMapping = {}, keyColumnName = "key") {
8733
+ function convertTranslationsToSheetRows(translations, localeMapping = {}, keyColumnName = "key", includeI18nSheet = false) {
8384
8734
  const sheetRowsMap = {};
8385
8735
  for (const [locale, sheets] of Object.entries(translations)) {
8386
8736
  const colHeader = localeMapping[locale] ?? locale;
8387
8737
  for (const [sheetName, keys] of Object.entries(sheets)) {
8388
- if (sheetName === "i18n") continue;
8738
+ if (!includeI18nSheet && sheetName === "i18n") continue;
8389
8739
  if (!sheetRowsMap[sheetName]) sheetRowsMap[sheetName] = /* @__PURE__ */ new Map();
8390
8740
  for (const [key, value] of Object.entries(keys)) {
8391
8741
  if (!sheetRowsMap[sheetName].has(key)) sheetRowsMap[sheetName].set(key, { [keyColumnName]: key });
@@ -8418,17 +8768,22 @@ function createCryptPadSheetOutputProvider(options = {}) {
8418
8768
  timeoutMs: options.timeoutMs
8419
8769
  });
8420
8770
  const effectiveMapping = options.localeMapping ?? payload.localeMapping ?? {};
8421
- const sheetRowsMap = convertTranslationsToSheetRows(payload.translations, effectiveMapping, options.keyColumnName ?? "key");
8771
+ const sheetRowsMap = convertTranslationsToSheetRows(payload.translations, effectiveMapping, options.keyColumnName ?? "key", options.includeI18nSheet ?? false);
8422
8772
  const updatedSheets = [];
8423
8773
  let totalUpdatedCells = 0;
8424
- for (const [sheetName, rows] of Object.entries(sheetRowsMap)) {
8774
+ const sheetEntries = Object.entries(sheetRowsMap).sort(([a], [b]) => {
8775
+ if (a === "i18n") return -1;
8776
+ if (b === "i18n") return 1;
8777
+ return 0;
8778
+ });
8779
+ for (const [sheetName, rows] of sheetEntries) {
8425
8780
  const count = await client.writeSheetRows(sheetName, rows, {
8426
8781
  override: options.override ?? false,
8427
8782
  linkHeadersToI18nSheet: options.linkHeadersToI18nSheet ?? false
8428
8783
  });
8429
8784
  updatedSheets.push(sheetName);
8430
8785
  totalUpdatedCells += count;
8431
- await new Promise((r) => setTimeout(r, 400));
8786
+ await new Promise((r) => setTimeout(r, 1200));
8432
8787
  }
8433
8788
  return {
8434
8789
  wroteFiles: [url],
@@ -9738,4 +10093,4 @@ function migrateProjectToV3(options = {}) {
9738
10093
  //#region src/index.ts
9739
10094
  var src_default = getSpreadSheetData;
9740
10095
  //#endregion
9741
- export { CRYPTPAD_ASSET_SYNC_CAPABILITIES, CRYPTPAD_CSV_INPUT_CAPABILITIES, CRYPTPAD_SHEET_INPUT_CAPABILITIES, CRYPTPAD_WORKSPACE_OUTPUT_CAPABILITIES, CRYPTPAD_WORKSPACE_SYNC_CAPABILITIES, CryptPadClient, DEFAULT_IMAGE_EXTENSIONS, DEFAULT_WAIT_SECONDS, EMPTY_PROVIDER_CAPABILITIES, GOOGLE_SHEETS_PROVIDER_CAPABILITIES, GcpApiError, OPERATION_CAPABILITY_REQUIREMENTS, PROVIDER_CAPABILITIES, assertOperationCapabilities, assertRequiredCapabilities, assertValidProviderRuntimeConfig, broadcastChannelMessage, buildGoogleAuth, buildManifest, buildSyncPlan, columnIndexToLetter, convertCellsToSheetRows, convertFromDataJsonFormat, convertToDataJsonFormat, createAuthClient, createCapabilitySet, createCryptPadAssetSyncProvider, createCryptPadCsvInputProvider, createCryptPadSheetInputProvider, createCryptPadWorkspaceOutputProvider, createCryptPadWorkspaceSyncProvider, createDefaultProviderCatalog, createGoogleSheetsInputProvider, createGoogleSheetsOutputProvider, createGoogleSheetsSyncProvider, createInMemoryProviderCatalogProvider, createLocaleMapping, createProvidersFromRuntimeConfig, createSpreadsheet, decryptCryptPadPayload, src_default as default, deriveCryptPadKeys, encodeOnlyOfficeCellRecord, encryptCryptPadPayload, entriesToSeedKeys, entriesToTranslationData, exportDoc, extractOnlyOfficeChannelId, extractOnlyOfficeMetadata, extractOnlyOfficeSheetIdMap, fetchChannelHistory, filterValidLocales, findLocalChanges, getGoogleTranslateCode, getLanguagePrefix, getLocaleDisplayName, getMultipleSpreadSheetsData, getNormalizedLocaleForHeader, getOriginalHeaderForLocale, getSpreadSheetData, getTranslationSummary, grantDrivePermissions, handleBidirectionalSync, hasRequiredCapabilities, inferLocaleFromDocName, ingestDoc, isValidLocale, manageDriveTranslations, mapLegacyGoogleOptionsToProviderConfig, mergeMultipleTranslationData, mergeSheets, migrateProjectToV3, missingCapabilities, moveSpreadsheetToFolder, normalizeExtension, normalizeLocaleCode, normalizePrivateKey, parseDocContent, parseOnlyOfficeChanges, parsePadUrl, processRawRows, readManifest, readPublicSheet, requiresGoogleAuthForRuntimeConfig, resolveCryptPadWebsocketUrl, resolveLocaleWithFallback, resolveSyncPlan, runProviderPipeline, sanitizeFolderName, scanDriveFolderForDocs, scanDriveFolderForSpreadsheets, setupWIF, slugifyKey, syncDriveImages, updateSpreadsheetWithLocalChanges, validateCredentials, validateEnv, validateImageDirectory, validateProviderRuntimeConfig, wait, walkDirectory, withRetry, writeLanguageDataFile, writeLocalesFile, writeManifest, writeTranslationFiles };
10096
+ export { CRYPTPAD_ASSET_SYNC_CAPABILITIES, CRYPTPAD_CSV_INPUT_CAPABILITIES, CRYPTPAD_SHEET_INPUT_CAPABILITIES, CRYPTPAD_WORKSPACE_OUTPUT_CAPABILITIES, CRYPTPAD_WORKSPACE_SYNC_CAPABILITIES, CryptPadClient, DEFAULT_IMAGE_EXTENSIONS, DEFAULT_WAIT_SECONDS, EMPTY_PROVIDER_CAPABILITIES, GOOGLE_SHEETS_PROVIDER_CAPABILITIES, GcpApiError, OPERATION_CAPABILITY_REQUIREMENTS, PROVIDER_CAPABILITIES, assertOperationCapabilities, assertRequiredCapabilities, assertValidProviderRuntimeConfig, broadcastChannelMessage, buildGoogleAuth, buildManifest, buildOnlyOfficeSheetDeletePayload, buildOnlyOfficeSheetRenamePayload, buildSyncPlan, columnIndexToLetter, convertCellsToSheetRows, convertFromDataJsonFormat, convertToDataJsonFormat, createAuthClient, createCapabilitySet, createCryptPadAssetSyncProvider, createCryptPadCsvInputProvider, createCryptPadSheetInputProvider, createCryptPadWorkspaceOutputProvider, createCryptPadWorkspaceSyncProvider, createDefaultProviderCatalog, createGoogleSheetsInputProvider, createGoogleSheetsOutputProvider, createGoogleSheetsSyncProvider, createInMemoryProviderCatalogProvider, createLocaleMapping, createProvidersFromRuntimeConfig, createSpreadsheet, decryptCryptPadPayload, src_default as default, deriveCryptPadKeys, encodeOnlyOfficeCellRecord, encodeOnlyOfficeSheetDeleteRecord, encodeOnlyOfficeSheetRenameRecord, encryptCryptPadPayload, entriesToSeedKeys, entriesToTranslationData, exportDoc, extractOnlyOfficeChannelId, extractOnlyOfficeMetadata, extractOnlyOfficeSheetIdMap, fetchChannelHistory, filterValidLocales, findLocalChanges, getGoogleTranslateCode, getLanguagePrefix, getLocaleDisplayName, getMultipleSpreadSheetsData, getNormalizedLocaleForHeader, getOriginalHeaderForLocale, getSpreadSheetData, getTranslationSummary, grantDrivePermissions, handleBidirectionalSync, hasRequiredCapabilities, inferLocaleFromDocName, ingestDoc, isValidLocale, manageDriveTranslations, mapLegacyGoogleOptionsToProviderConfig, mergeMultipleTranslationData, mergeSheets, migrateProjectToV3, missingCapabilities, moveSpreadsheetToFolder, normalizeExtension, normalizeLocaleCode, normalizePrivateKey, parseDocContent, parseOnlyOfficeChanges, parsePadUrl, processRawRows, readManifest, readPublicSheet, requiresGoogleAuthForRuntimeConfig, resolveCryptPadWebsocketUrl, resolveLocaleWithFallback, resolveSyncPlan, runProviderPipeline, sanitizeFolderName, scanDriveFolderForDocs, scanDriveFolderForSpreadsheets, setupWIF, slugifyKey, syncDriveImages, updateSpreadsheetWithLocalChanges, validateCredentials, validateEnv, validateImageDirectory, validateProviderRuntimeConfig, wait, walkDirectory, withRetry, writeLanguageDataFile, writeLocalesFile, writeManifest, writeTranslationFiles };