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.
- package/README.md +414 -0
- package/dist/cjs/cli/index.js +125 -0
- package/dist/cjs/cli/index.js.map +1 -0
- package/dist/cjs/index.js +57 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib/auth.js +34 -0
- package/dist/cjs/lib/auth.js.map +1 -0
- package/dist/cjs/lib/defaultKeyMap.js +106 -0
- package/dist/cjs/lib/defaultKeyMap.js.map +1 -0
- package/dist/cjs/lib/driveClient.js +50 -0
- package/dist/cjs/lib/driveClient.js.map +1 -0
- package/dist/cjs/lib/folderExtractor.js +63 -0
- package/dist/cjs/lib/folderExtractor.js.map +1 -0
- package/dist/cjs/lib/parser.js +120 -0
- package/dist/cjs/lib/parser.js.map +1 -0
- package/dist/cjs/lib/sheetExtractor.js +52 -0
- package/dist/cjs/lib/sheetExtractor.js.map +1 -0
- package/dist/cjs/lib/sheetsClient.js +37 -0
- package/dist/cjs/lib/sheetsClient.js.map +1 -0
- package/dist/cjs/lib/writer.js +33 -0
- package/dist/cjs/lib/writer.js.map +1 -0
- package/dist/cjs/types/index.js +4 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/esm/cli/index.js +123 -0
- package/dist/esm/cli/index.js.map +1 -0
- package/dist/esm/index.js +54 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/auth.js +31 -0
- package/dist/esm/lib/auth.js.map +1 -0
- package/dist/esm/lib/defaultKeyMap.js +103 -0
- package/dist/esm/lib/defaultKeyMap.js.map +1 -0
- package/dist/esm/lib/driveClient.js +43 -0
- package/dist/esm/lib/driveClient.js.map +1 -0
- package/dist/esm/lib/folderExtractor.js +60 -0
- package/dist/esm/lib/folderExtractor.js.map +1 -0
- package/dist/esm/lib/parser.js +114 -0
- package/dist/esm/lib/parser.js.map +1 -0
- package/dist/esm/lib/sheetExtractor.js +49 -0
- package/dist/esm/lib/sheetExtractor.js.map +1 -0
- package/dist/esm/lib/sheetsClient.js +33 -0
- package/dist/esm/lib/sheetsClient.js.map +1 -0
- package/dist/esm/lib/writer.js +30 -0
- package/dist/esm/lib/writer.js.map +1 -0
- package/dist/esm/types/index.js +3 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/types/cli/index.d.ts +3 -0
- package/dist/types/cli/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lib/auth.d.ts +9 -0
- package/dist/types/lib/auth.d.ts.map +1 -0
- package/dist/types/lib/defaultKeyMap.d.ts +9 -0
- package/dist/types/lib/defaultKeyMap.d.ts.map +1 -0
- package/dist/types/lib/driveClient.d.ts +20 -0
- package/dist/types/lib/driveClient.d.ts.map +1 -0
- package/dist/types/lib/folderExtractor.d.ts +23 -0
- package/dist/types/lib/folderExtractor.d.ts.map +1 -0
- package/dist/types/lib/parser.d.ts +60 -0
- package/dist/types/lib/parser.d.ts.map +1 -0
- package/dist/types/lib/sheetExtractor.d.ts +26 -0
- package/dist/types/lib/sheetExtractor.d.ts.map +1 -0
- package/dist/types/lib/sheetsClient.d.ts +15 -0
- package/dist/types/lib/sheetsClient.d.ts.map +1 -0
- package/dist/types/lib/writer.d.ts +9 -0
- package/dist/types/lib/writer.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +153 -0
- package/dist/types/types/index.d.ts.map +1 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,gFAAgF"}
|
|
@@ -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"}
|