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,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_LOCALE_KEY_MAP = void 0;
4
+ /**
5
+ * Built-in mapping from common sheet column header spellings to BCP-47 codes.
6
+ *
7
+ * The sheet's own `_keymap` tab and the caller's `localeKeyMap` option are
8
+ * merged on top of this at runtime, so any entry here can be overridden.
9
+ */
10
+ exports.DEFAULT_LOCALE_KEY_MAP = {
11
+ // Short codes
12
+ EN: "en",
13
+ ENG: "en",
14
+ CHT: "zh-TW",
15
+ CHS: "zh-CN",
16
+ SCH: "zh-CN",
17
+ TCH: "zh-TW",
18
+ CZE: "cs",
19
+ DAN: "da",
20
+ DUT: "nl",
21
+ FIN: "fi",
22
+ FRE: "fr",
23
+ GER: "de",
24
+ GRK: "el",
25
+ HUN: "hu",
26
+ ITA: "it",
27
+ JPN: "ja",
28
+ KOR: "ko",
29
+ NOR: "no",
30
+ POL: "pl",
31
+ POR: "pt",
32
+ ROM: "ro",
33
+ RUS: "ru",
34
+ SPA: "es",
35
+ SWE: "sv",
36
+ THA: "th",
37
+ TUR: "tr",
38
+ // Long codes with parenthetical hints
39
+ "TCH (Tchinese)": "zh-TW",
40
+ "SCH (Schinese)": "zh-CN",
41
+ "CZE (Czech)": "cs",
42
+ "DAN (Danish)": "da",
43
+ "DUT (Dutch)": "nl",
44
+ "FIN (Finnish)": "fi",
45
+ "FRE (French)": "fr",
46
+ "GER (German)": "de",
47
+ "GRK (Greek)": "el",
48
+ "HUN (Hungarian)": "hu",
49
+ "ITA (Italian)": "it",
50
+ "JPN (Japanese)": "ja",
51
+ "KOR (Korean)": "ko",
52
+ "NOR (Norwegian)": "no",
53
+ "POL (Polish)": "pl",
54
+ "POR (Portuguese-Brazil)": "pt",
55
+ "ROM (Romanian)": "ro",
56
+ "RUS (Russian)": "ru",
57
+ "SPA (Spanish-Latin)": "es",
58
+ "SWE (Swedish)": "sv",
59
+ "THA (Thai)": "th",
60
+ "TUR (Turkish)": "tr",
61
+ // Full English names
62
+ English: "en",
63
+ Czech: "cs",
64
+ Danish: "da",
65
+ Dutch: "nl",
66
+ Finnish: "fi",
67
+ French: "fr",
68
+ German: "de",
69
+ Greek: "el",
70
+ Hungarian: "hu",
71
+ Italian: "it",
72
+ Japanese: "ja",
73
+ Korean: "ko",
74
+ Norwegian: "no",
75
+ Polish: "pl",
76
+ Portuguese: "pt",
77
+ Romanian: "ro",
78
+ Russian: "ru",
79
+ "Spanish (Spain)": "es",
80
+ Swedish: "sv",
81
+ Thai: "th",
82
+ Turkish: "tr",
83
+ // Native names
84
+ "繁體中文": "zh-TW",
85
+ "简体中文": "zh-CN",
86
+ Dansk: "da",
87
+ Nederlands: "nl",
88
+ Suomi: "fi",
89
+ Français: "fr",
90
+ Deutsch: "de",
91
+ "Ελληνικά": "el",
92
+ Magyar: "hu",
93
+ Italiano: "it",
94
+ "日本語": "ja",
95
+ "한글": "ko",
96
+ Norsk: "no",
97
+ Polski: "pl",
98
+ "Português": "pt",
99
+ "Română": "ro",
100
+ "Русский": "ru",
101
+ "Español": "es",
102
+ Svenska: "sv",
103
+ "ไทย": "th",
104
+ "Turk dili": "tr",
105
+ };
106
+ //# sourceMappingURL=defaultKeyMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultKeyMap.js","sourceRoot":"","sources":["../../../src/lib/defaultKeyMap.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACU,QAAA,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,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listFolderContents = listFolderContents;
4
+ exports.isSpreadsheet = isSpreadsheet;
5
+ exports.isFolder = isFolder;
6
+ exports.isInternal = isInternal;
7
+ exports.isKeymapSheet = isKeymapSheet;
8
+ const googleapis_1 = require("googleapis");
9
+ const MIME_SHEET = "application/vnd.google-apps.spreadsheet";
10
+ const MIME_FOLDER = "application/vnd.google-apps.folder";
11
+ /**
12
+ * Lists all files and sub-folders directly inside a Drive folder.
13
+ * Handles pagination automatically.
14
+ */
15
+ async function listFolderContents(auth, folderId) {
16
+ const drive = googleapis_1.google.drive({ version: "v3", auth });
17
+ const files = [];
18
+ let pageToken;
19
+ do {
20
+ const response = await drive.files.list({
21
+ q: `'${folderId}' in parents and trashed = false`,
22
+ pageSize: 1000,
23
+ fields: "nextPageToken, files(id, name, mimeType)",
24
+ ...(pageToken ? { pageToken } : {}),
25
+ });
26
+ const page = response.data.files ?? [];
27
+ for (const f of page) {
28
+ if (f.id && f.name && f.mimeType) {
29
+ files.push({ id: f.id, name: f.name, mimeType: f.mimeType });
30
+ }
31
+ }
32
+ pageToken = response.data.nextPageToken ?? undefined;
33
+ } while (pageToken);
34
+ return files;
35
+ }
36
+ function isSpreadsheet(file) {
37
+ return file.mimeType === MIME_SHEET;
38
+ }
39
+ function isFolder(file) {
40
+ return file.mimeType === MIME_FOLDER;
41
+ }
42
+ /** Files/folders whose name starts with `_` are treated as internal/skipped. */
43
+ function isInternal(file) {
44
+ return file.name.startsWith("_");
45
+ }
46
+ /** A keymap sheet is a spreadsheet named `_keymap` (internal, but special). */
47
+ function isKeymapSheet(file) {
48
+ return isSpreadsheet(file) && file.name === "_keymap";
49
+ }
50
+ //# sourceMappingURL=driveClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driveClient.js","sourceRoot":"","sources":["../../../src/lib/driveClient.ts"],"names":[],"mappings":";;AAiBA,gDA2BC;AAED,sCAEC;AAED,4BAEC;AAGD,gCAEC;AAGD,sCAEC;AA9DD,2CAAoC;AAUpC,MAAM,UAAU,GAAG,yCAAyC,CAAC;AAC7D,MAAM,WAAW,GAAG,oCAAoC,CAAC;AAEzD;;;GAGG;AACI,KAAK,UAAU,kBAAkB,CACtC,IAAgB,EAChB,QAAgB;IAEhB,MAAM,KAAK,GAAG,mBAAM,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,SAAgB,aAAa,CAAC,IAAe;IAC3C,OAAO,IAAI,CAAC,QAAQ,KAAK,UAAU,CAAC;AACtC,CAAC;AAED,SAAgB,QAAQ,CAAC,IAAe;IACtC,OAAO,IAAI,CAAC,QAAQ,KAAK,WAAW,CAAC;AACvC,CAAC;AAED,gFAAgF;AAChF,SAAgB,UAAU,CAAC,IAAe;IACxC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,+EAA+E;AAC/E,SAAgB,aAAa,CAAC,IAAe;IAC3C,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC;AACxD,CAAC"}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractFromFolder = extractFromFolder;
4
+ const driveClient_js_1 = require("./driveClient.js");
5
+ const sheetsClient_js_1 = require("./sheetsClient.js");
6
+ const parser_js_1 = require("./parser.js");
7
+ /**
8
+ * Recursively extracts translations from all spreadsheets inside a Drive folder.
9
+ *
10
+ * Naming conventions:
11
+ * - Files/folders starting with `_` are skipped (treated as internal).
12
+ * - A spreadsheet named exactly `_keymap` inside a folder provides locale
13
+ * key mappings that apply to all sheets in that folder.
14
+ * - Each spreadsheet's name is used as a namespace path component.
15
+ * - Sub-folder names become path components (e.g. folder `common` containing
16
+ * sheet `buttons` → namespace `common.buttons`).
17
+ */
18
+ async function extractFromFolder(options) {
19
+ const { auth, folderId, includeEmpty = false, userKeyMap } = options;
20
+ const result = {};
21
+ await scanFolder(auth, folderId, [], result, {
22
+ includeEmpty,
23
+ userKeyMap,
24
+ inheritedKeyMap: (0, parser_js_1.buildLocaleKeyMap)(userKeyMap),
25
+ });
26
+ return result;
27
+ }
28
+ async function scanFolder(auth, folderId, namespacePath, result, options) {
29
+ const { includeEmpty, userKeyMap } = options;
30
+ let { inheritedKeyMap } = options;
31
+ const files = await (0, driveClient_js_1.listFolderContents)(auth, folderId);
32
+ // 1. Check for a `_keymap` spreadsheet in this folder and merge it
33
+ const keymapSheet = files.find(driveClient_js_1.isKeymapSheet);
34
+ if (keymapSheet) {
35
+ const grid = await (0, sheetsClient_js_1.getTabData)(auth, keymapSheet.id);
36
+ const folderKeyMap = (0, parser_js_1.parseKeymapGrid)(grid);
37
+ inheritedKeyMap = (0, parser_js_1.buildLocaleKeyMap)(userKeyMap, {
38
+ ...inheritedKeyMap,
39
+ ...folderKeyMap,
40
+ });
41
+ }
42
+ // 2. Recurse into non-internal sub-folders
43
+ const subFolders = files.filter((f) => (0, driveClient_js_1.isFolder)(f) && !(0, driveClient_js_1.isInternal)(f));
44
+ for (const folder of subFolders) {
45
+ await scanFolder(auth, folder.id, [...namespacePath, folder.name], result, {
46
+ includeEmpty,
47
+ userKeyMap,
48
+ inheritedKeyMap,
49
+ });
50
+ }
51
+ // 3. Process non-internal spreadsheets
52
+ const sheets = files.filter((f) => (0, driveClient_js_1.isSpreadsheet)(f) && !(0, driveClient_js_1.isInternal)(f));
53
+ for (const sheet of sheets) {
54
+ const grid = await (0, sheetsClient_js_1.getTabData)(auth, sheet.id);
55
+ const namespace = [...namespacePath, sheet.name].join(".");
56
+ (0, parser_js_1.parseGrid)(grid, result, {
57
+ keyMap: inheritedKeyMap,
58
+ namespace,
59
+ includeEmpty,
60
+ });
61
+ }
62
+ }
63
+ //# sourceMappingURL=folderExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folderExtractor.js","sourceRoot":"","sources":["../../../src/lib/folderExtractor.ts"],"names":[],"mappings":";;AAmCA,8CAaC;AA/CD,qDAM0B;AAC1B,uDAA+C;AAC/C,2CAIqB;AAWrB;;;;;;;;;;GAUG;AACI,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,IAAA,6BAAiB,EAAC,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,IAAA,mCAAkB,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAEvD,mEAAmE;IACnE,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,8BAAa,CAAC,CAAC;IAC9C,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAU,EAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;QACpD,MAAM,YAAY,GAAG,IAAA,2BAAe,EAAC,IAAI,CAAC,CAAC;QAC3C,eAAe,GAAG,IAAA,6BAAiB,EAAC,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,IAAA,yBAAQ,EAAC,CAAC,CAAC,IAAI,CAAC,IAAA,2BAAU,EAAC,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,IAAA,8BAAa,EAAC,CAAC,CAAC,IAAI,CAAC,IAAA,2BAAU,EAAC,CAAC,CAAC,CAAC,CAAC;IACvE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAU,EAAC,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,IAAA,qBAAS,EAAC,IAAI,EAAE,MAAM,EAAE;YACtB,MAAM,EAAE,eAAe;YACvB,SAAS;YACT,YAAY;SACb,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildLocaleKeyMap = buildLocaleKeyMap;
4
+ exports.parseKeymapGrid = parseKeymapGrid;
5
+ exports.setNestedValue = setNestedValue;
6
+ exports.parseGrid = parseGrid;
7
+ const defaultKeyMap_js_1 = require("./defaultKeyMap.js");
8
+ // ─── Key map resolution ───────────────────────────────────────────────────────
9
+ /**
10
+ * Merges the default key map with any user-supplied overrides and an optional
11
+ * `_keymap` tab extracted from the spreadsheet itself.
12
+ *
13
+ * Priority (highest → lowest):
14
+ * 1. Spreadsheet `_keymap` tab
15
+ * 2. `localeKeyMap` option passed by the caller
16
+ * 3. Built-in `DEFAULT_LOCALE_KEY_MAP`
17
+ */
18
+ function buildLocaleKeyMap(userKeyMap, sheetKeyMap) {
19
+ return {
20
+ ...defaultKeyMap_js_1.DEFAULT_LOCALE_KEY_MAP,
21
+ ...(userKeyMap ?? {}),
22
+ ...(sheetKeyMap ?? {}),
23
+ };
24
+ }
25
+ /**
26
+ * Parses a `_keymap` tab (either from a dedicated sheet or from a tab named
27
+ * `_keymap` inside a spreadsheet).
28
+ *
29
+ * Expected layout:
30
+ * ```
31
+ * | locale code | fr | en | de |
32
+ * | header name | French| English | German |
33
+ * ```
34
+ * Row 0 = locale codes (output keys), Row 1+ = header name variants.
35
+ */
36
+ function parseKeymapGrid(grid) {
37
+ if (grid.length < 2)
38
+ return {};
39
+ const [localeCodes, ...headerRows] = grid;
40
+ const result = {};
41
+ for (const row of headerRows) {
42
+ row.forEach((headerVariant, colIndex) => {
43
+ if (colIndex === 0 || !headerVariant)
44
+ return;
45
+ const locale = localeCodes[colIndex];
46
+ if (locale)
47
+ result[headerVariant] = locale;
48
+ });
49
+ }
50
+ return result;
51
+ }
52
+ // ─── Nested key assignment ────────────────────────────────────────────────────
53
+ /**
54
+ * Sets a value at a dotted path inside a nested object, creating intermediate
55
+ * objects as needed.
56
+ *
57
+ * @example
58
+ * setNestedValue({}, "actions.save", "Save")
59
+ * // → { actions: { save: "Save" } }
60
+ */
61
+ function setNestedValue(obj, dotPath, value) {
62
+ const parts = dotPath.split(".");
63
+ let cursor = obj;
64
+ for (let i = 0; i < parts.length - 1; i++) {
65
+ const part = parts[i];
66
+ if (typeof cursor[part] !== "object" || cursor[part] === null) {
67
+ cursor[part] = {};
68
+ }
69
+ cursor = cursor[part];
70
+ }
71
+ cursor[parts[parts.length - 1]] = value;
72
+ }
73
+ /**
74
+ * Parses a single sheet grid and merges all found translations into `result`.
75
+ *
76
+ * Expected grid layout:
77
+ * ```
78
+ * | key (ignored) | EN | FR | DE |
79
+ * | actions.save | Save | Enreg. | Speich. |
80
+ * | actions.cancel | Cancel | Annuler | Abbruch |
81
+ * ```
82
+ *
83
+ * Column 0 is always the key column.
84
+ * Column 1 is the first language (index 1 in the header row).
85
+ */
86
+ function parseGrid(grid, result, options) {
87
+ const { keyMap, namespace, startColumn = 0, includeEmpty = false } = options;
88
+ if (grid.length < 2)
89
+ return;
90
+ const [headerRow, ...dataRows] = grid;
91
+ const slicedHeader = headerRow.slice(startColumn);
92
+ // Map column index (relative to startColumn) → resolved locale code.
93
+ // Column 0 of the sliced header is the key column — skip it (index starts at 1).
94
+ const columnLocales = slicedHeader.map((cell, i) => {
95
+ if (i === 0)
96
+ return null; // key column
97
+ const resolved = keyMap[cell] ?? cell;
98
+ // Skip columns whose name/locale starts with "_" (internal metadata)
99
+ return resolved.startsWith("_") ? null : resolved;
100
+ });
101
+ for (const row of dataRows) {
102
+ const slicedRow = row.slice(startColumn);
103
+ const translationKey = slicedRow[0]?.trim();
104
+ if (!translationKey)
105
+ continue;
106
+ const fullKey = namespace ? `${namespace}.${translationKey}` : translationKey;
107
+ slicedRow.slice(1).forEach((value, colOffset) => {
108
+ const locale = columnLocales[colOffset + 1];
109
+ if (!locale)
110
+ return;
111
+ const trimmedValue = value?.trim() ?? "";
112
+ if (!trimmedValue && !includeEmpty)
113
+ return;
114
+ if (!result[locale])
115
+ result[locale] = {};
116
+ setNestedValue(result[locale], fullKey, trimmedValue);
117
+ });
118
+ }
119
+ }
120
+ //# 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,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractFromSheet = extractFromSheet;
4
+ const sheetsClient_js_1 = require("./sheetsClient.js");
5
+ const parser_js_1 = require("./parser.js");
6
+ /**
7
+ * Extracts translations from a single Google Spreadsheet.
8
+ *
9
+ * - If `tabId` is given → only that tab is parsed, keys are NOT namespaced.
10
+ * - If `tabId` is omitted → all non-internal tabs are parsed;
11
+ * each tab name becomes a namespace prefix (e.g. tab "actions" → key "actions.save").
12
+ *
13
+ * Tabs whose name starts with `_` are treated as internal and skipped,
14
+ * except for `_keymap` which is parsed to build a custom locale key map.
15
+ */
16
+ async function extractFromSheet(options) {
17
+ const { auth, spreadsheetId, tabId, startColumn = 0, includeEmpty = false, userKeyMap, } = options;
18
+ const result = {};
19
+ // 1. List all tabs
20
+ const tabs = await (0, sheetsClient_js_1.getSpreadsheetTabs)(auth, spreadsheetId);
21
+ // 2. Check for a `_keymap` tab and parse it first
22
+ let sheetKeyMap;
23
+ const keymapTab = tabs.find((t) => t.title === "_keymap");
24
+ if (keymapTab) {
25
+ const grid = await (0, sheetsClient_js_1.getTabData)(auth, spreadsheetId, keymapTab.title);
26
+ sheetKeyMap = (0, parser_js_1.parseKeymapGrid)(grid);
27
+ }
28
+ const keyMap = (0, parser_js_1.buildLocaleKeyMap)(userKeyMap, sheetKeyMap);
29
+ // 3. Determine which tabs to process
30
+ let targetTabs = tabId !== undefined
31
+ ? tabs.filter((t) => t.sheetId === tabId)
32
+ : tabs.filter((t) => !t.title.startsWith("_"));
33
+ if (targetTabs.length === 0) {
34
+ throw new Error(tabId !== undefined
35
+ ? `Tab with ID ${tabId} not found in spreadsheet ${spreadsheetId}.`
36
+ : `No processable tabs found in spreadsheet ${spreadsheetId}.`);
37
+ }
38
+ // 4. Parse each tab
39
+ for (const tab of targetTabs) {
40
+ const grid = await (0, sheetsClient_js_1.getTabData)(auth, spreadsheetId, tab.title);
41
+ (0, parser_js_1.parseGrid)(grid, result, {
42
+ keyMap,
43
+ // When a specific tab is requested, don't namespace the keys.
44
+ // When scanning all tabs, use the tab name as namespace.
45
+ namespace: tabId !== undefined ? undefined : tab.title,
46
+ startColumn,
47
+ includeEmpty,
48
+ });
49
+ }
50
+ return result;
51
+ }
52
+ //# sourceMappingURL=sheetExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheetExtractor.js","sourceRoot":"","sources":["../../../src/lib/sheetExtractor.ts"],"names":[],"mappings":";;AA+BA,4CAuDC;AArFD,uDAAmE;AACnE,2CAIqB;AAerB;;;;;;;;;GASG;AACI,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,IAAA,oCAAkB,EAAC,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,IAAA,4BAAU,EAAC,IAAI,EAAE,aAAa,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QACpE,WAAW,GAAG,IAAA,2BAAe,EAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,6BAAiB,EAAC,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,IAAA,4BAAU,EAAC,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAE9D,IAAA,qBAAS,EAAC,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,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSpreadsheetTabs = getSpreadsheetTabs;
4
+ exports.getTabData = getTabData;
5
+ const googleapis_1 = require("googleapis");
6
+ /**
7
+ * Lists all tabs in a spreadsheet and returns their metadata.
8
+ */
9
+ async function getSpreadsheetTabs(auth, spreadsheetId) {
10
+ const sheets = googleapis_1.google.sheets({ version: "v4", auth });
11
+ const response = await sheets.spreadsheets.get({ spreadsheetId });
12
+ const rawSheets = response.data.sheets ?? [];
13
+ return rawSheets
14
+ .map((s) => ({
15
+ sheetId: s.properties?.sheetId ?? 0,
16
+ title: s.properties?.title ?? "",
17
+ }))
18
+ .filter((s) => s.title !== "");
19
+ }
20
+ /**
21
+ * Fetches all cell values from a single tab of a spreadsheet.
22
+ *
23
+ * @param tabName - The display name of the tab. When omitted, the first tab is used.
24
+ */
25
+ async function getTabData(auth, spreadsheetId, tabName) {
26
+ const sheets = googleapis_1.google.sheets({ version: "v4", auth });
27
+ // Encode the tab name to handle special characters (spaces, apostrophes, etc.)
28
+ const range = tabName
29
+ ? `'${tabName.replace(/'/g, "\\'")}'!A1:ZZ9999`
30
+ : "A1:ZZ9999";
31
+ const response = await sheets.spreadsheets.values.get({
32
+ spreadsheetId,
33
+ range,
34
+ });
35
+ return response.data.values ?? [];
36
+ }
37
+ //# sourceMappingURL=sheetsClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheetsClient.js","sourceRoot":"","sources":["../../../src/lib/sheetsClient.ts"],"names":[],"mappings":";;AAQA,gDAcC;AAOD,gCAkBC;AA/CD,2CAAoC;AAKpC;;GAEG;AACI,KAAK,UAAU,kBAAkB,CACtC,IAAgB,EAChB,aAAqB;IAErB,MAAM,MAAM,GAAG,mBAAM,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;AACI,KAAK,UAAU,UAAU,CAC9B,IAAgB,EAChB,aAAqB,EACrB,OAAgB;IAEhB,MAAM,MAAM,GAAG,mBAAM,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,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeTranslationFiles = writeTranslationFiles;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ /**
7
+ * Writes one JSON file per locale into `outputDir`.
8
+ * Creates the directory (and any parents) if it does not exist.
9
+ *
10
+ * @returns Metadata about each file that was written.
11
+ */
12
+ async function writeTranslationFiles(translations, outputDir, indent = 2) {
13
+ const absoluteDir = (0, node_path_1.resolve)(process.cwd(), outputDir);
14
+ await (0, promises_1.mkdir)(absoluteDir, { recursive: true });
15
+ const outputs = [];
16
+ for (const [locale, translationObject] of Object.entries(translations)) {
17
+ // Skip entirely empty locale objects
18
+ if (Object.keys(translationObject).length === 0)
19
+ continue;
20
+ const filePath = (0, node_path_1.join)(absoluteDir, `${locale}.json`);
21
+ const content = JSON.stringify(translationObject, null, indent);
22
+ await (0, promises_1.writeFile)(filePath, content, "utf-8");
23
+ outputs.push({
24
+ locale,
25
+ path: filePath,
26
+ keyCount: Object.keys(translationObject).length,
27
+ });
28
+ }
29
+ // Sort output list by locale code for deterministic logging
30
+ outputs.sort((a, b) => a.locale.localeCompare(b.locale));
31
+ return outputs;
32
+ }
33
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../../src/lib/writer.ts"],"names":[],"mappings":";;AAUA,sDA8BC;AAxCD,+CAAoD;AACpD,yCAA0C;AAG1C;;;;;GAKG;AACI,KAAK,UAAU,qBAAqB,CACzC,YAA4B,EAC5B,SAAiB,EACjB,SAA0B,CAAC;IAE3B,MAAM,WAAW,GAAG,IAAA,mBAAO,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACtD,MAAM,IAAA,gBAAK,EAAC,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,IAAA,gBAAI,EAAC,WAAW,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAEhE,MAAM,IAAA,oBAAS,EAAC,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,4 @@
1
+ "use strict";
2
+ // ─── Authentication ──────────────────────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ //# 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,123 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { extract } from "../index.js";
4
+ import { readFile } from "node:fs/promises";
5
+ import { resolve } from "node:path";
6
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
7
+ function parseIndent(value) {
8
+ if (value === "tab" || value === "\t")
9
+ return "\t";
10
+ const num = Number(value);
11
+ if (!Number.isNaN(num) && num >= 0 && num <= 8)
12
+ return num;
13
+ throw new Error(`Invalid indent value: "${value}". Use a number 0–8 or "tab".`);
14
+ }
15
+ function fatal(message) {
16
+ console.error(`\n ✖ ${message}\n`);
17
+ process.exit(1);
18
+ }
19
+ // ─── CLI definition ───────────────────────────────────────────────────────────
20
+ const program = new Command();
21
+ program
22
+ .name("sheets-i18n")
23
+ .description("Generate i18n JSON files from a Google Spreadsheet or Drive folder.")
24
+ .version("1.0.0");
25
+ // ── sheet command ─────────────────────────────────────────────────────────────
26
+ program
27
+ .command("sheet")
28
+ .description("Extract from a single Google Spreadsheet")
29
+ .requiredOption("-s, --sheet-id <spreadsheetId>", "Google Spreadsheet ID (from the URL)")
30
+ .requiredOption("-k, --key <path>", "Path to the Service Account JSON key file", "./service-account.json")
31
+ .option("-o, --out <path>", "Output directory for the generated JSON files", "./i18n")
32
+ .option("-t, --tab-id <id>", "Numeric tab ID to extract (default: all tabs)")
33
+ .option("--start-column <n>", "Zero-based column index to start reading from", "0")
34
+ .option("--include-empty", "Include keys with empty values in the output", false)
35
+ .option("--indent <value>", 'JSON indentation: a number of spaces or "tab"', "2")
36
+ .option("--key-map <path>", "Path to a JSON file with custom locale key mappings")
37
+ .action(async (opts) => {
38
+ let localeKeyMap;
39
+ if (opts.keyMap) {
40
+ try {
41
+ const raw = await readFile(resolve(process.cwd(), opts.keyMap), "utf-8");
42
+ localeKeyMap = JSON.parse(raw);
43
+ }
44
+ catch {
45
+ fatal(`Could not read key map file: ${opts.keyMap}`);
46
+ return;
47
+ }
48
+ }
49
+ console.log("\n ⏳ Extracting translations…\n");
50
+ try {
51
+ const result = await extract({
52
+ serviceAccountKey: opts.key,
53
+ source: {
54
+ mode: "sheet",
55
+ spreadsheetId: opts.sheetId,
56
+ tabId: opts.tabId !== undefined ? Number(opts.tabId) : undefined,
57
+ startColumn: Number(opts.startColumn),
58
+ },
59
+ outputDir: opts.out,
60
+ localeKeyMap,
61
+ includeEmpty: opts.includeEmpty,
62
+ indent: parseIndent(opts.indent),
63
+ });
64
+ console.log(` ✔ Done in ${(result.durationMs / 1000).toFixed(2)}s\n`);
65
+ console.log(` 📂 ${resolve(process.cwd(), opts.out)}\n`);
66
+ for (const file of result.files) {
67
+ console.log(` ${file.locale}.json (${file.keyCount} top-level keys)`);
68
+ }
69
+ console.log();
70
+ }
71
+ catch (err) {
72
+ fatal(err instanceof Error ? err.message : String(err));
73
+ }
74
+ });
75
+ // ── folder command ────────────────────────────────────────────────────────────
76
+ program
77
+ .command("folder")
78
+ .description("Extract from all spreadsheets inside a Google Drive folder")
79
+ .requiredOption("-f, --folder-id <folderId>", "Google Drive folder ID (from the URL)")
80
+ .requiredOption("-k, --key <path>", "Path to the Service Account JSON key file", "./service-account.json")
81
+ .option("-o, --out <path>", "Output directory for the generated JSON files", "./i18n")
82
+ .option("--include-empty", "Include keys with empty values in the output", false)
83
+ .option("--indent <value>", 'JSON indentation: a number of spaces or "tab"', "2")
84
+ .option("--key-map <path>", "Path to a JSON file with custom locale key mappings")
85
+ .action(async (opts) => {
86
+ let localeKeyMap;
87
+ if (opts.keyMap) {
88
+ try {
89
+ const raw = await readFile(resolve(process.cwd(), opts.keyMap), "utf-8");
90
+ localeKeyMap = JSON.parse(raw);
91
+ }
92
+ catch {
93
+ fatal(`Could not read key map file: ${opts.keyMap}`);
94
+ return;
95
+ }
96
+ }
97
+ console.log("\n ⏳ Extracting translations…\n");
98
+ try {
99
+ const result = await extract({
100
+ serviceAccountKey: opts.key,
101
+ source: {
102
+ mode: "folder",
103
+ folderId: opts.folderId,
104
+ },
105
+ outputDir: opts.out,
106
+ localeKeyMap,
107
+ includeEmpty: opts.includeEmpty,
108
+ indent: parseIndent(opts.indent),
109
+ });
110
+ console.log(` ✔ Done in ${(result.durationMs / 1000).toFixed(2)}s\n`);
111
+ console.log(` 📂 ${resolve(process.cwd(), opts.out)}\n`);
112
+ for (const file of result.files) {
113
+ console.log(` ${file.locale}.json (${file.keyCount} top-level keys)`);
114
+ }
115
+ console.log();
116
+ }
117
+ catch (err) {
118
+ fatal(err instanceof Error ? err.message : String(err));
119
+ }
120
+ });
121
+ // ─── Parse ────────────────────────────────────────────────────────────────────
122
+ program.parse();
123
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":"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"}