gsheets-i18n 1.0.0

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.
Files changed (68) hide show
  1. package/README.md +414 -0
  2. package/dist/cjs/cli/index.js +125 -0
  3. package/dist/cjs/cli/index.js.map +1 -0
  4. package/dist/cjs/index.js +57 -0
  5. package/dist/cjs/index.js.map +1 -0
  6. package/dist/cjs/lib/auth.js +34 -0
  7. package/dist/cjs/lib/auth.js.map +1 -0
  8. package/dist/cjs/lib/defaultKeyMap.js +106 -0
  9. package/dist/cjs/lib/defaultKeyMap.js.map +1 -0
  10. package/dist/cjs/lib/driveClient.js +50 -0
  11. package/dist/cjs/lib/driveClient.js.map +1 -0
  12. package/dist/cjs/lib/folderExtractor.js +63 -0
  13. package/dist/cjs/lib/folderExtractor.js.map +1 -0
  14. package/dist/cjs/lib/parser.js +120 -0
  15. package/dist/cjs/lib/parser.js.map +1 -0
  16. package/dist/cjs/lib/sheetExtractor.js +52 -0
  17. package/dist/cjs/lib/sheetExtractor.js.map +1 -0
  18. package/dist/cjs/lib/sheetsClient.js +37 -0
  19. package/dist/cjs/lib/sheetsClient.js.map +1 -0
  20. package/dist/cjs/lib/writer.js +33 -0
  21. package/dist/cjs/lib/writer.js.map +1 -0
  22. package/dist/cjs/types/index.js +4 -0
  23. package/dist/cjs/types/index.js.map +1 -0
  24. package/dist/esm/cli/index.js +123 -0
  25. package/dist/esm/cli/index.js.map +1 -0
  26. package/dist/esm/index.js +54 -0
  27. package/dist/esm/index.js.map +1 -0
  28. package/dist/esm/lib/auth.js +31 -0
  29. package/dist/esm/lib/auth.js.map +1 -0
  30. package/dist/esm/lib/defaultKeyMap.js +103 -0
  31. package/dist/esm/lib/defaultKeyMap.js.map +1 -0
  32. package/dist/esm/lib/driveClient.js +43 -0
  33. package/dist/esm/lib/driveClient.js.map +1 -0
  34. package/dist/esm/lib/folderExtractor.js +60 -0
  35. package/dist/esm/lib/folderExtractor.js.map +1 -0
  36. package/dist/esm/lib/parser.js +114 -0
  37. package/dist/esm/lib/parser.js.map +1 -0
  38. package/dist/esm/lib/sheetExtractor.js +49 -0
  39. package/dist/esm/lib/sheetExtractor.js.map +1 -0
  40. package/dist/esm/lib/sheetsClient.js +33 -0
  41. package/dist/esm/lib/sheetsClient.js.map +1 -0
  42. package/dist/esm/lib/writer.js +30 -0
  43. package/dist/esm/lib/writer.js.map +1 -0
  44. package/dist/esm/types/index.js +3 -0
  45. package/dist/esm/types/index.js.map +1 -0
  46. package/dist/types/cli/index.d.ts +3 -0
  47. package/dist/types/cli/index.d.ts.map +1 -0
  48. package/dist/types/index.d.ts +21 -0
  49. package/dist/types/index.d.ts.map +1 -0
  50. package/dist/types/lib/auth.d.ts +9 -0
  51. package/dist/types/lib/auth.d.ts.map +1 -0
  52. package/dist/types/lib/defaultKeyMap.d.ts +9 -0
  53. package/dist/types/lib/defaultKeyMap.d.ts.map +1 -0
  54. package/dist/types/lib/driveClient.d.ts +20 -0
  55. package/dist/types/lib/driveClient.d.ts.map +1 -0
  56. package/dist/types/lib/folderExtractor.d.ts +23 -0
  57. package/dist/types/lib/folderExtractor.d.ts.map +1 -0
  58. package/dist/types/lib/parser.d.ts +60 -0
  59. package/dist/types/lib/parser.d.ts.map +1 -0
  60. package/dist/types/lib/sheetExtractor.d.ts +26 -0
  61. package/dist/types/lib/sheetExtractor.d.ts.map +1 -0
  62. package/dist/types/lib/sheetsClient.d.ts +15 -0
  63. package/dist/types/lib/sheetsClient.d.ts.map +1 -0
  64. package/dist/types/lib/writer.d.ts +9 -0
  65. package/dist/types/lib/writer.d.ts.map +1 -0
  66. package/dist/types/types/index.d.ts +153 -0
  67. package/dist/types/types/index.d.ts.map +1 -0
  68. package/package.json +62 -0
@@ -0,0 +1,54 @@
1
+ import { createAuthClient } from "./lib/auth.js";
2
+ import { extractFromSheet } from "./lib/sheetExtractor.js";
3
+ import { extractFromFolder } from "./lib/folderExtractor.js";
4
+ import { writeTranslationFiles } from "./lib/writer.js";
5
+ /**
6
+ * Extracts translations from Google Sheets and writes one JSON file per locale.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { extract } from "sheets-i18n";
11
+ *
12
+ * await extract({
13
+ * serviceAccountKey: "./service-account.json",
14
+ * source: {
15
+ * mode: "sheet",
16
+ * spreadsheetId: "4BxiM********upms",
17
+ * },
18
+ * outputDir: "./src/locales",
19
+ * });
20
+ * ```
21
+ */
22
+ export async function extract(options) {
23
+ const startTime = Date.now();
24
+ const { serviceAccountKey, source, outputDir = "./i18n", localeKeyMap, includeEmpty = false, indent = 2, } = options;
25
+ // 1. Authenticate
26
+ const auth = await createAuthClient(serviceAccountKey);
27
+ // 2. Extract translations
28
+ let translations;
29
+ if (source.mode === "sheet") {
30
+ translations = await extractFromSheet({
31
+ auth,
32
+ spreadsheetId: source.spreadsheetId,
33
+ tabId: source.tabId,
34
+ startColumn: source.startColumn,
35
+ includeEmpty,
36
+ userKeyMap: localeKeyMap,
37
+ });
38
+ }
39
+ else {
40
+ translations = await extractFromFolder({
41
+ auth,
42
+ folderId: source.folderId,
43
+ includeEmpty,
44
+ userKeyMap: localeKeyMap,
45
+ });
46
+ }
47
+ // 3. Write files
48
+ const files = await writeTranslationFiles(translations, outputDir, indent);
49
+ return {
50
+ files,
51
+ durationMs: Date.now() - startTime,
52
+ };
53
+ }
54
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAgBxD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAuB;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,MAAM,EACJ,iBAAiB,EACjB,MAAM,EACN,SAAS,GAAG,QAAQ,EACpB,YAAY,EACZ,YAAY,GAAG,KAAK,EACpB,MAAM,GAAG,CAAC,GACX,GAAG,OAAO,CAAC;IAEZ,kBAAkB;IAClB,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,iBAAiB,CAAC,CAAC;IAEvD,0BAA0B;IAC1B,IAAI,YAAY,CAAC;IAEjB,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,YAAY,GAAG,MAAM,gBAAgB,CAAC;YACpC,IAAI;YACJ,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,YAAY;YACZ,UAAU,EAAE,YAAY;SACzB,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,YAAY,GAAG,MAAM,iBAAiB,CAAC;YACrC,IAAI;YACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,YAAY;YACZ,UAAU,EAAE,YAAY;SACzB,CAAC,CAAC;IACL,CAAC;IAED,iBAAiB;IACjB,MAAM,KAAK,GAAG,MAAM,qBAAqB,CAAC,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAE3E,OAAO;QACL,KAAK;QACL,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;KACnC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { google } from "googleapis";
2
+ import { readFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ const SCOPES = [
5
+ "https://www.googleapis.com/auth/spreadsheets.readonly",
6
+ "https://www.googleapis.com/auth/drive.readonly",
7
+ ];
8
+ /**
9
+ * Builds an authenticated Google JWT client from a Service Account key.
10
+ *
11
+ * @param keySource - Either a file-system path to the key JSON, or the parsed object.
12
+ */
13
+ export async function createAuthClient(keySource) {
14
+ let key;
15
+ if (typeof keySource === "string") {
16
+ const absolutePath = resolve(process.cwd(), keySource);
17
+ const raw = await readFile(absolutePath, "utf-8");
18
+ key = JSON.parse(raw);
19
+ }
20
+ else {
21
+ key = keySource;
22
+ }
23
+ const client = new google.auth.JWT({
24
+ email: key.client_email,
25
+ key: key.private_key,
26
+ scopes: SCOPES,
27
+ });
28
+ await client.authorize();
29
+ return client;
30
+ }
31
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,MAAM,MAAM,GAAG;IACb,uDAAuD;IACvD,gDAAgD;CACjD,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,SAAqC;IAErC,IAAI,GAAsB,CAAC;IAE3B,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAClD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,SAAS,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;QACjC,KAAK,EAAE,GAAG,CAAC,YAAY;QACvB,GAAG,EAAE,GAAG,CAAC,WAAW;QACpB,MAAM,EAAE,MAAM;KACf,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;IACzB,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Built-in mapping from common sheet column header spellings to BCP-47 codes.
3
+ *
4
+ * The sheet's own `_keymap` tab and the caller's `localeKeyMap` option are
5
+ * merged on top of this at runtime, so any entry here can be overridden.
6
+ */
7
+ export const DEFAULT_LOCALE_KEY_MAP = {
8
+ // Short codes
9
+ EN: "en",
10
+ ENG: "en",
11
+ CHT: "zh-TW",
12
+ CHS: "zh-CN",
13
+ SCH: "zh-CN",
14
+ TCH: "zh-TW",
15
+ CZE: "cs",
16
+ DAN: "da",
17
+ DUT: "nl",
18
+ FIN: "fi",
19
+ FRE: "fr",
20
+ GER: "de",
21
+ GRK: "el",
22
+ HUN: "hu",
23
+ ITA: "it",
24
+ JPN: "ja",
25
+ KOR: "ko",
26
+ NOR: "no",
27
+ POL: "pl",
28
+ POR: "pt",
29
+ ROM: "ro",
30
+ RUS: "ru",
31
+ SPA: "es",
32
+ SWE: "sv",
33
+ THA: "th",
34
+ TUR: "tr",
35
+ // Long codes with parenthetical hints
36
+ "TCH (Tchinese)": "zh-TW",
37
+ "SCH (Schinese)": "zh-CN",
38
+ "CZE (Czech)": "cs",
39
+ "DAN (Danish)": "da",
40
+ "DUT (Dutch)": "nl",
41
+ "FIN (Finnish)": "fi",
42
+ "FRE (French)": "fr",
43
+ "GER (German)": "de",
44
+ "GRK (Greek)": "el",
45
+ "HUN (Hungarian)": "hu",
46
+ "ITA (Italian)": "it",
47
+ "JPN (Japanese)": "ja",
48
+ "KOR (Korean)": "ko",
49
+ "NOR (Norwegian)": "no",
50
+ "POL (Polish)": "pl",
51
+ "POR (Portuguese-Brazil)": "pt",
52
+ "ROM (Romanian)": "ro",
53
+ "RUS (Russian)": "ru",
54
+ "SPA (Spanish-Latin)": "es",
55
+ "SWE (Swedish)": "sv",
56
+ "THA (Thai)": "th",
57
+ "TUR (Turkish)": "tr",
58
+ // Full English names
59
+ English: "en",
60
+ Czech: "cs",
61
+ Danish: "da",
62
+ Dutch: "nl",
63
+ Finnish: "fi",
64
+ French: "fr",
65
+ German: "de",
66
+ Greek: "el",
67
+ Hungarian: "hu",
68
+ Italian: "it",
69
+ Japanese: "ja",
70
+ Korean: "ko",
71
+ Norwegian: "no",
72
+ Polish: "pl",
73
+ Portuguese: "pt",
74
+ Romanian: "ro",
75
+ Russian: "ru",
76
+ "Spanish (Spain)": "es",
77
+ Swedish: "sv",
78
+ Thai: "th",
79
+ Turkish: "tr",
80
+ // Native names
81
+ "繁體中文": "zh-TW",
82
+ "简体中文": "zh-CN",
83
+ Dansk: "da",
84
+ Nederlands: "nl",
85
+ Suomi: "fi",
86
+ Français: "fr",
87
+ Deutsch: "de",
88
+ "Ελληνικά": "el",
89
+ Magyar: "hu",
90
+ Italiano: "it",
91
+ "日本語": "ja",
92
+ "한글": "ko",
93
+ Norsk: "no",
94
+ Polski: "pl",
95
+ "Português": "pt",
96
+ "Română": "ro",
97
+ "Русский": "ru",
98
+ "Español": "es",
99
+ Svenska: "sv",
100
+ "ไทย": "th",
101
+ "Turk dili": "tr",
102
+ };
103
+ //# sourceMappingURL=defaultKeyMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultKeyMap.js","sourceRoot":"","sources":["../../../src/lib/defaultKeyMap.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAiB;IAClD,cAAc;IACd,EAAE,EAAE,IAAI;IACR,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IAET,sCAAsC;IACtC,gBAAgB,EAAE,OAAO;IACzB,gBAAgB,EAAE,OAAO;IACzB,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,IAAI;IACnB,eAAe,EAAE,IAAI;IACrB,cAAc,EAAE,IAAI;IACpB,cAAc,EAAE,IAAI;IACpB,aAAa,EAAE,IAAI;IACnB,iBAAiB,EAAE,IAAI;IACvB,eAAe,EAAE,IAAI;IACrB,gBAAgB,EAAE,IAAI;IACtB,cAAc,EAAE,IAAI;IACpB,iBAAiB,EAAE,IAAI;IACvB,cAAc,EAAE,IAAI;IACpB,yBAAyB,EAAE,IAAI;IAC/B,gBAAgB,EAAE,IAAI;IACtB,eAAe,EAAE,IAAI;IACrB,qBAAqB,EAAE,IAAI;IAC3B,eAAe,EAAE,IAAI;IACrB,YAAY,EAAE,IAAI;IAClB,eAAe,EAAE,IAAI;IAErB,qBAAqB;IACrB,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,IAAI;IACb,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,IAAI;IACd,MAAM,EAAE,IAAI;IACZ,SAAS,EAAE,IAAI;IACf,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,IAAI;IAChB,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,IAAI;IACb,iBAAiB,EAAE,IAAI;IACvB,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,IAAI;IAEb,eAAe;IACf,MAAM,EAAE,OAAO;IACf,MAAM,EAAE,OAAO;IACf,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,IAAI;IAChB,KAAK,EAAE,IAAI;IACX,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,IAAI;IAChB,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,IAAI;IACd,KAAK,EAAE,IAAI;IACX,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,WAAW,EAAE,IAAI;IACjB,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,OAAO,EAAE,IAAI;IACb,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,IAAI;CAClB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { google } from "googleapis";
2
+ const MIME_SHEET = "application/vnd.google-apps.spreadsheet";
3
+ const MIME_FOLDER = "application/vnd.google-apps.folder";
4
+ /**
5
+ * Lists all files and sub-folders directly inside a Drive folder.
6
+ * Handles pagination automatically.
7
+ */
8
+ export async function listFolderContents(auth, folderId) {
9
+ const drive = google.drive({ version: "v3", auth });
10
+ const files = [];
11
+ let pageToken;
12
+ do {
13
+ const response = await drive.files.list({
14
+ q: `'${folderId}' in parents and trashed = false`,
15
+ pageSize: 1000,
16
+ fields: "nextPageToken, files(id, name, mimeType)",
17
+ ...(pageToken ? { pageToken } : {}),
18
+ });
19
+ const page = response.data.files ?? [];
20
+ for (const f of page) {
21
+ if (f.id && f.name && f.mimeType) {
22
+ files.push({ id: f.id, name: f.name, mimeType: f.mimeType });
23
+ }
24
+ }
25
+ pageToken = response.data.nextPageToken ?? undefined;
26
+ } while (pageToken);
27
+ return files;
28
+ }
29
+ export function isSpreadsheet(file) {
30
+ return file.mimeType === MIME_SHEET;
31
+ }
32
+ export function isFolder(file) {
33
+ return file.mimeType === MIME_FOLDER;
34
+ }
35
+ /** Files/folders whose name starts with `_` are treated as internal/skipped. */
36
+ export function isInternal(file) {
37
+ return file.name.startsWith("_");
38
+ }
39
+ /** A keymap sheet is a spreadsheet named `_keymap` (internal, but special). */
40
+ export function isKeymapSheet(file) {
41
+ return isSpreadsheet(file) && file.name === "_keymap";
42
+ }
43
+ //# sourceMappingURL=driveClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driveClient.js","sourceRoot":"","sources":["../../../src/lib/driveClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAUpC,MAAM,UAAU,GAAG,yCAAyC,CAAC;AAC7D,MAAM,WAAW,GAAG,oCAAoC,CAAC;AAEzD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAgB,EAChB,QAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,IAAI,SAA6B,CAAC;IAElC,GAAG,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;YACtC,CAAC,EAAE,IAAI,QAAQ,kCAAkC;YACjD,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,0CAA0C;YAClD,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;gBACjC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;QAED,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,IAAI,SAAS,CAAC;IACvD,CAAC,QAAQ,SAAS,EAAE;IAEpB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,OAAO,IAAI,CAAC,QAAQ,KAAK,UAAU,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAe;IACtC,OAAO,IAAI,CAAC,QAAQ,KAAK,WAAW,CAAC;AACvC,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,IAAe;IACxC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,IAAe;IAC3C,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;AACxD,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { listFolderContents, isSpreadsheet, isFolder, isInternal, isKeymapSheet, } from "./driveClient.js";
2
+ import { getTabData } from "./sheetsClient.js";
3
+ import { buildLocaleKeyMap, parseGrid, parseKeymapGrid, } from "./parser.js";
4
+ /**
5
+ * Recursively extracts translations from all spreadsheets inside a Drive folder.
6
+ *
7
+ * Naming conventions:
8
+ * - Files/folders starting with `_` are skipped (treated as internal).
9
+ * - A spreadsheet named exactly `_keymap` inside a folder provides locale
10
+ * key mappings that apply to all sheets in that folder.
11
+ * - Each spreadsheet's name is used as a namespace path component.
12
+ * - Sub-folder names become path components (e.g. folder `common` containing
13
+ * sheet `buttons` → namespace `common.buttons`).
14
+ */
15
+ export async function extractFromFolder(options) {
16
+ const { auth, folderId, includeEmpty = false, userKeyMap } = options;
17
+ const result = {};
18
+ await scanFolder(auth, folderId, [], result, {
19
+ includeEmpty,
20
+ userKeyMap,
21
+ inheritedKeyMap: buildLocaleKeyMap(userKeyMap),
22
+ });
23
+ return result;
24
+ }
25
+ async function scanFolder(auth, folderId, namespacePath, result, options) {
26
+ const { includeEmpty, userKeyMap } = options;
27
+ let { inheritedKeyMap } = options;
28
+ const files = await listFolderContents(auth, folderId);
29
+ // 1. Check for a `_keymap` spreadsheet in this folder and merge it
30
+ const keymapSheet = files.find(isKeymapSheet);
31
+ if (keymapSheet) {
32
+ const grid = await getTabData(auth, keymapSheet.id);
33
+ const folderKeyMap = parseKeymapGrid(grid);
34
+ inheritedKeyMap = buildLocaleKeyMap(userKeyMap, {
35
+ ...inheritedKeyMap,
36
+ ...folderKeyMap,
37
+ });
38
+ }
39
+ // 2. Recurse into non-internal sub-folders
40
+ const subFolders = files.filter((f) => isFolder(f) && !isInternal(f));
41
+ for (const folder of subFolders) {
42
+ await scanFolder(auth, folder.id, [...namespacePath, folder.name], result, {
43
+ includeEmpty,
44
+ userKeyMap,
45
+ inheritedKeyMap,
46
+ });
47
+ }
48
+ // 3. Process non-internal spreadsheets
49
+ const sheets = files.filter((f) => isSpreadsheet(f) && !isInternal(f));
50
+ for (const sheet of sheets) {
51
+ const grid = await getTabData(auth, sheet.id);
52
+ const namespace = [...namespacePath, sheet.name].join(".");
53
+ parseGrid(grid, result, {
54
+ keyMap: inheritedKeyMap,
55
+ namespace,
56
+ includeEmpty,
57
+ });
58
+ }
59
+ }
60
+ //# sourceMappingURL=folderExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folderExtractor.js","sourceRoot":"","sources":["../../../src/lib/folderExtractor.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,QAAQ,EACR,UAAU,EACV,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,eAAe,GAChB,MAAM,aAAa,CAAC;AAWrB;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAA+B;IAE/B,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,GAAG,KAAK,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACrE,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,MAAM,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE;QAC3C,YAAY;QACZ,UAAU;QACV,eAAe,EAAE,iBAAiB,CAAC,UAAU,CAAC;KAC/C,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAQD,KAAK,UAAU,UAAU,CACvB,IAAgB,EAChB,QAAgB,EAChB,aAAuB,EACvB,MAAsB,EACtB,OAAoB;IAEpB,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAC7C,IAAI,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;IAElC,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAEvD,mEAAmE;IACnE,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC9C,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QAC3C,eAAe,GAAG,iBAAiB,CAAC,UAAU,EAAE;YAC9C,GAAG,eAAe;YAClB,GAAG,YAAY;SAChB,CAAC,CAAC;IACL,CAAC;IAED,2CAA2C;IAC3C,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,GAAG,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE;YACzE,YAAY;YACZ,UAAU;YACV,eAAe;SAChB,CAAC,CAAC;IACL,CAAC;IAED,uCAAuC;IACvC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,CAAC,GAAG,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAE3D,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE;YACtB,MAAM,EAAE,eAAe;YACvB,SAAS;YACT,YAAY;SACb,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,114 @@
1
+ import { DEFAULT_LOCALE_KEY_MAP } from "./defaultKeyMap.js";
2
+ // ─── Key map resolution ───────────────────────────────────────────────────────
3
+ /**
4
+ * Merges the default key map with any user-supplied overrides and an optional
5
+ * `_keymap` tab extracted from the spreadsheet itself.
6
+ *
7
+ * Priority (highest → lowest):
8
+ * 1. Spreadsheet `_keymap` tab
9
+ * 2. `localeKeyMap` option passed by the caller
10
+ * 3. Built-in `DEFAULT_LOCALE_KEY_MAP`
11
+ */
12
+ export function buildLocaleKeyMap(userKeyMap, sheetKeyMap) {
13
+ return {
14
+ ...DEFAULT_LOCALE_KEY_MAP,
15
+ ...(userKeyMap ?? {}),
16
+ ...(sheetKeyMap ?? {}),
17
+ };
18
+ }
19
+ /**
20
+ * Parses a `_keymap` tab (either from a dedicated sheet or from a tab named
21
+ * `_keymap` inside a spreadsheet).
22
+ *
23
+ * Expected layout:
24
+ * ```
25
+ * | locale code | fr | en | de |
26
+ * | header name | French| English | German |
27
+ * ```
28
+ * Row 0 = locale codes (output keys), Row 1+ = header name variants.
29
+ */
30
+ export function parseKeymapGrid(grid) {
31
+ if (grid.length < 2)
32
+ return {};
33
+ const [localeCodes, ...headerRows] = grid;
34
+ const result = {};
35
+ for (const row of headerRows) {
36
+ row.forEach((headerVariant, colIndex) => {
37
+ if (colIndex === 0 || !headerVariant)
38
+ return;
39
+ const locale = localeCodes[colIndex];
40
+ if (locale)
41
+ result[headerVariant] = locale;
42
+ });
43
+ }
44
+ return result;
45
+ }
46
+ // ─── Nested key assignment ────────────────────────────────────────────────────
47
+ /**
48
+ * Sets a value at a dotted path inside a nested object, creating intermediate
49
+ * objects as needed.
50
+ *
51
+ * @example
52
+ * setNestedValue({}, "actions.save", "Save")
53
+ * // → { actions: { save: "Save" } }
54
+ */
55
+ export function setNestedValue(obj, dotPath, value) {
56
+ const parts = dotPath.split(".");
57
+ let cursor = obj;
58
+ for (let i = 0; i < parts.length - 1; i++) {
59
+ const part = parts[i];
60
+ if (typeof cursor[part] !== "object" || cursor[part] === null) {
61
+ cursor[part] = {};
62
+ }
63
+ cursor = cursor[part];
64
+ }
65
+ cursor[parts[parts.length - 1]] = value;
66
+ }
67
+ /**
68
+ * Parses a single sheet grid and merges all found translations into `result`.
69
+ *
70
+ * Expected grid layout:
71
+ * ```
72
+ * | key (ignored) | EN | FR | DE |
73
+ * | actions.save | Save | Enreg. | Speich. |
74
+ * | actions.cancel | Cancel | Annuler | Abbruch |
75
+ * ```
76
+ *
77
+ * Column 0 is always the key column.
78
+ * Column 1 is the first language (index 1 in the header row).
79
+ */
80
+ export function parseGrid(grid, result, options) {
81
+ const { keyMap, namespace, startColumn = 0, includeEmpty = false } = options;
82
+ if (grid.length < 2)
83
+ return;
84
+ const [headerRow, ...dataRows] = grid;
85
+ const slicedHeader = headerRow.slice(startColumn);
86
+ // Map column index (relative to startColumn) → resolved locale code.
87
+ // Column 0 of the sliced header is the key column — skip it (index starts at 1).
88
+ const columnLocales = slicedHeader.map((cell, i) => {
89
+ if (i === 0)
90
+ return null; // key column
91
+ const resolved = keyMap[cell] ?? cell;
92
+ // Skip columns whose name/locale starts with "_" (internal metadata)
93
+ return resolved.startsWith("_") ? null : resolved;
94
+ });
95
+ for (const row of dataRows) {
96
+ const slicedRow = row.slice(startColumn);
97
+ const translationKey = slicedRow[0]?.trim();
98
+ if (!translationKey)
99
+ continue;
100
+ const fullKey = namespace ? `${namespace}.${translationKey}` : translationKey;
101
+ slicedRow.slice(1).forEach((value, colOffset) => {
102
+ const locale = columnLocales[colOffset + 1];
103
+ if (!locale)
104
+ return;
105
+ const trimmedValue = value?.trim() ?? "";
106
+ if (!trimmedValue && !includeEmpty)
107
+ return;
108
+ if (!result[locale])
109
+ result[locale] = {};
110
+ setNestedValue(result[locale], fullKey, trimmedValue);
111
+ });
112
+ }
113
+ }
114
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../../src/lib/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { getSpreadsheetTabs, getTabData } from "./sheetsClient.js";
2
+ import { buildLocaleKeyMap, parseGrid, parseKeymapGrid, } from "./parser.js";
3
+ /**
4
+ * Extracts translations from a single Google Spreadsheet.
5
+ *
6
+ * - If `tabId` is given → only that tab is parsed, keys are NOT namespaced.
7
+ * - If `tabId` is omitted → all non-internal tabs are parsed;
8
+ * each tab name becomes a namespace prefix (e.g. tab "actions" → key "actions.save").
9
+ *
10
+ * Tabs whose name starts with `_` are treated as internal and skipped,
11
+ * except for `_keymap` which is parsed to build a custom locale key map.
12
+ */
13
+ export async function extractFromSheet(options) {
14
+ const { auth, spreadsheetId, tabId, startColumn = 0, includeEmpty = false, userKeyMap, } = options;
15
+ const result = {};
16
+ // 1. List all tabs
17
+ const tabs = await getSpreadsheetTabs(auth, spreadsheetId);
18
+ // 2. Check for a `_keymap` tab and parse it first
19
+ let sheetKeyMap;
20
+ const keymapTab = tabs.find((t) => t.title === "_keymap");
21
+ if (keymapTab) {
22
+ const grid = await getTabData(auth, spreadsheetId, keymapTab.title);
23
+ sheetKeyMap = parseKeymapGrid(grid);
24
+ }
25
+ const keyMap = buildLocaleKeyMap(userKeyMap, sheetKeyMap);
26
+ // 3. Determine which tabs to process
27
+ let targetTabs = tabId !== undefined
28
+ ? tabs.filter((t) => t.sheetId === tabId)
29
+ : tabs.filter((t) => !t.title.startsWith("_"));
30
+ if (targetTabs.length === 0) {
31
+ throw new Error(tabId !== undefined
32
+ ? `Tab with ID ${tabId} not found in spreadsheet ${spreadsheetId}.`
33
+ : `No processable tabs found in spreadsheet ${spreadsheetId}.`);
34
+ }
35
+ // 4. Parse each tab
36
+ for (const tab of targetTabs) {
37
+ const grid = await getTabData(auth, spreadsheetId, tab.title);
38
+ parseGrid(grid, result, {
39
+ keyMap,
40
+ // When a specific tab is requested, don't namespace the keys.
41
+ // When scanning all tabs, use the tab name as namespace.
42
+ namespace: tabId !== undefined ? undefined : tab.title,
43
+ startColumn,
44
+ includeEmpty,
45
+ });
46
+ }
47
+ return result;
48
+ }
49
+ //# sourceMappingURL=sheetExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheetExtractor.js","sourceRoot":"","sources":["../../../src/lib/sheetExtractor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,eAAe,GAChB,MAAM,aAAa,CAAC;AAerB;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAA8B;IAE9B,MAAM,EACJ,IAAI,EACJ,aAAa,EACb,KAAK,EACL,WAAW,GAAG,CAAC,EACf,YAAY,GAAG,KAAK,EACpB,UAAU,GACX,GAAG,OAAO,CAAC;IAEZ,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,mBAAmB;IACnB,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IAE3D,kDAAkD;IAClD,IAAI,WAAqC,CAAC;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IAC1D,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QACpE,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAE1D,qCAAqC;IACrC,IAAI,UAAU,GAAG,KAAK,KAAK,SAAS;QAClC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC;QACzC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAEjD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACb,KAAK,KAAK,SAAS;YACjB,CAAC,CAAC,eAAe,KAAK,6BAA6B,aAAa,GAAG;YACnE,CAAC,CAAC,4CAA4C,aAAa,GAAG,CACjE,CAAC;IACJ,CAAC;IAED,oBAAoB;IACpB,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAE9D,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE;YACtB,MAAM;YACN,8DAA8D;YAC9D,yDAAyD;YACzD,SAAS,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK;YACtD,WAAW;YACX,YAAY;SACb,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { google } from "googleapis";
2
+ /**
3
+ * Lists all tabs in a spreadsheet and returns their metadata.
4
+ */
5
+ export async function getSpreadsheetTabs(auth, spreadsheetId) {
6
+ const sheets = google.sheets({ version: "v4", auth });
7
+ const response = await sheets.spreadsheets.get({ spreadsheetId });
8
+ const rawSheets = response.data.sheets ?? [];
9
+ return rawSheets
10
+ .map((s) => ({
11
+ sheetId: s.properties?.sheetId ?? 0,
12
+ title: s.properties?.title ?? "",
13
+ }))
14
+ .filter((s) => s.title !== "");
15
+ }
16
+ /**
17
+ * Fetches all cell values from a single tab of a spreadsheet.
18
+ *
19
+ * @param tabName - The display name of the tab. When omitted, the first tab is used.
20
+ */
21
+ export async function getTabData(auth, spreadsheetId, tabName) {
22
+ const sheets = google.sheets({ version: "v4", auth });
23
+ // Encode the tab name to handle special characters (spaces, apostrophes, etc.)
24
+ const range = tabName
25
+ ? `'${tabName.replace(/'/g, "\\'")}'!A1:ZZ9999`
26
+ : "A1:ZZ9999";
27
+ const response = await sheets.spreadsheets.values.get({
28
+ spreadsheetId,
29
+ range,
30
+ });
31
+ return response.data.values ?? [];
32
+ }
33
+ //# sourceMappingURL=sheetsClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheetsClient.js","sourceRoot":"","sources":["../../../src/lib/sheetsClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKpC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAgB,EAChB,aAAqB;IAErB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC;IAElE,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;IAC7C,OAAO,SAAS;SACb,GAAG,CAAC,CAAC,CAAsE,EAAE,EAAE,CAAC,CAAC;QAChF,OAAO,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,IAAI,CAAC;QACnC,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,KAAK,IAAI,EAAE;KACjC,CAAC,CAAC;SACF,MAAM,CAAC,CAAC,CAAoB,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAgB,EAChB,aAAqB,EACrB,OAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAEtD,+EAA+E;IAC/E,MAAM,KAAK,GAAG,OAAO;QACnB,CAAC,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,aAAa;QAC/C,CAAC,CAAC,WAAW,CAAC;IAEhB,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC;QACpD,aAAa;QACb,KAAK;KACN,CAAC,CAAC;IAEH,OAAQ,QAAQ,CAAC,IAAI,CAAC,MAA2B,IAAI,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { resolve, join } from "node:path";
3
+ /**
4
+ * Writes one JSON file per locale into `outputDir`.
5
+ * Creates the directory (and any parents) if it does not exist.
6
+ *
7
+ * @returns Metadata about each file that was written.
8
+ */
9
+ export async function writeTranslationFiles(translations, outputDir, indent = 2) {
10
+ const absoluteDir = resolve(process.cwd(), outputDir);
11
+ await mkdir(absoluteDir, { recursive: true });
12
+ const outputs = [];
13
+ for (const [locale, translationObject] of Object.entries(translations)) {
14
+ // Skip entirely empty locale objects
15
+ if (Object.keys(translationObject).length === 0)
16
+ continue;
17
+ const filePath = join(absoluteDir, `${locale}.json`);
18
+ const content = JSON.stringify(translationObject, null, indent);
19
+ await writeFile(filePath, content, "utf-8");
20
+ outputs.push({
21
+ locale,
22
+ path: filePath,
23
+ keyCount: Object.keys(translationObject).length,
24
+ });
25
+ }
26
+ // Sort output list by locale code for deterministic logging
27
+ outputs.sort((a, b) => a.locale.localeCompare(b.locale));
28
+ return outputs;
29
+ }
30
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../../src/lib/writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG1C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,YAA4B,EAC5B,SAAiB,EACjB,SAA0B,CAAC;IAE3B,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACtD,MAAM,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,KAAK,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACvE,qCAAqC;QACrC,IAAI,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAE1D,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAEhE,MAAM,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAE5C,OAAO,CAAC,IAAI,CAAC;YACX,MAAM;YACN,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM;SAChD,CAAC,CAAC;IACL,CAAC;IAED,4DAA4D;IAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAEzD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ // ─── Authentication ──────────────────────────────────────────────────────────
2
+ export {};
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,gFAAgF"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import type { ExtractOptions, ExtractResult } from "./types/index.js";
2
+ export type { ExtractOptions, ExtractResult, ExtractionMode, SheetModeOptions, FolderModeOptions, OutputFile, TranslationMap, TranslationObject, LocaleKeyMap, ServiceAccountKey, } from "./types/index.js";
3
+ /**
4
+ * Extracts translations from Google Sheets and writes one JSON file per locale.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { extract } from "sheets-i18n";
9
+ *
10
+ * await extract({
11
+ * serviceAccountKey: "./service-account.json",
12
+ * source: {
13
+ * mode: "sheet",
14
+ * spreadsheetId: "4BxiM********upms",
15
+ * },
16
+ * outputDir: "./src/locales",
17
+ * });
18
+ * ```
19
+ */
20
+ export declare function extract(options: ExtractOptions): Promise<ExtractResult>;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtE,YAAY,EACV,cAAc,EACd,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA2C7E"}
@@ -0,0 +1,9 @@
1
+ import { google } from "googleapis";
2
+ import type { ServiceAccountKey } from "../types/index.js";
3
+ /**
4
+ * Builds an authenticated Google JWT client from a Service Account key.
5
+ *
6
+ * @param keySource - Either a file-system path to the key JSON, or the parsed object.
7
+ */
8
+ export declare function createAuthClient(keySource: string | ServiceAccountKey): Promise<InstanceType<typeof google.auth.JWT>>;
9
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAO3D;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,GAAG,iBAAiB,GACpC,OAAO,CAAC,YAAY,CAAC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAmB/C"}
@@ -0,0 +1,9 @@
1
+ import type { LocaleKeyMap } from "../types/index.js";
2
+ /**
3
+ * Built-in mapping from common sheet column header spellings to BCP-47 codes.
4
+ *
5
+ * The sheet's own `_keymap` tab and the caller's `localeKeyMap` option are
6
+ * merged on top of this at runtime, so any entry here can be overridden.
7
+ */
8
+ export declare const DEFAULT_LOCALE_KEY_MAP: LocaleKeyMap;
9
+ //# sourceMappingURL=defaultKeyMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultKeyMap.d.ts","sourceRoot":"","sources":["../../../src/lib/defaultKeyMap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAkGpC,CAAC"}