-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
update.mts
executable file
·228 lines (187 loc) · 6.66 KB
/
update.mts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#!/usr/bin/env -S deno run --allow-net --allow-read --allow-write --allow-run=bash,unzip --allow-env
// This script fetches the latest docs from Notion and translations from Crowdin,
// then copies the files to the appropriate directories.
// One current limitation is that when the docs from Notion are ahead of the translations, it can result in broken links between the two.
// A possible remediation would be to copy the English files to the other locales first, then overwrite those with the translated files.
// This doesn't fully solve the problem though, because metadata in the translated files may be different from the English files.
import { copy, readerFromStreamReader } from "jsr:@std/io";
const projectId = Deno.env.get("CROWDIN_PROJECT_ID");
const apiKey = Deno.env.get("CROWDIN_API_KEY");
const projectRoot = Deno.cwd();
function ensureSuccess(response: Response) {
if (!response.ok)
throw new Error(
`Request for ${response.url} failed with status code ${response.status}`
);
}
async function saveLatestBuild() {
// Create a new build
const response = await fetch(
`https://api.crowdin.com/api/v2/projects/${projectId}/translations/builds`,
{
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-type": "application/json",
},
body: JSON.stringify({
skipUntranslatedStrings: false,
skipUntranslatedFiles: false,
exportApprovedOnly: false,
}),
}
);
ensureSuccess(response);
const buildResponseBody = await response.json();
const buildId = buildResponseBody.data.id;
// Poll the build status
let finished = false;
while (finished === false) {
console.log("Checking build status...");
const buildStatusResponse = await fetch(
`https://api.crowdin.com/api/v2/projects/${projectId}/translations/builds/${buildId}`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
ensureSuccess(buildStatusResponse);
const buildStatusBody = await buildStatusResponse.json();
if (buildStatusBody.data.status === "finished") {
finished = true;
} else if (buildStatusBody.data.status === "inProgress") {
console.log(
`Build status: ${buildStatusBody.data.status}. Waiting for 5 seconds...`
);
await new Promise((resolve) => setTimeout(resolve, 5000));
} else {
throw new Error(
`Unexpected build status: ${buildStatusBody.data.status}`
);
}
}
console.log("Build finished!");
const buildDownloadResponse = await fetch(
`https://api.crowdin.com/api/v2/projects/${projectId}/translations/builds/${buildId}/download`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
ensureSuccess(buildDownloadResponse);
const buildDownloadBody = await buildDownloadResponse.json();
const buildUrl = buildDownloadBody.data.url;
// Download and save the file
console.log("Downloading the build:");
console.log(buildUrl);
const downloadResponse = await fetch(buildUrl);
ensureSuccess(downloadResponse);
const buffer = await downloadResponse.bytes();
const zipFilePath = `${projectRoot}/translations.zip`;
await Deno.writeFile(zipFilePath, buffer);
console.log("File downloaded and saved to", zipFilePath);
// Extract the zip file
const command = new Deno.Command("unzip", {
args: ["-o", zipFilePath, "-d", `${projectRoot}/translations`],
stdout: "piped",
stderr: "piped",
});
const { code, stderr } = await command.output();
if (code === 0) {
console.log("Extraction completed successfully.");
} else {
const errorString = new TextDecoder().decode(stderr);
console.error(errorString);
throw new Error("ZIP extraction failed");
}
}
const helpLocales = [
{
docusaurus: "de",
crowdin: "de",
},
{
docusaurus: "es",
crowdin: "es-ES",
},
{
docusaurus: "fr",
crowdin: "fr",
},
{
docusaurus: "pt-BR",
crowdin: "pt-BR",
},
] as const;
async function copyFiles() {
for (const locale of helpLocales) {
const source = `${projectRoot}/translations/${locale.crowdin}/Guides`;
const dest = `${projectRoot}/i18n/${locale.docusaurus}/docusaurus-plugin-content-docs/current/`;
console.log(`Copying files from ${source} to ${dest}`);
await Deno.mkdir(dest, { recursive: true });
for await (const dirEntry of Deno.readDir(source)) {
if (dirEntry.isFile) {
const oldFile = `${source}/${dirEntry.name}`;
const newFile = `${dest}/${dirEntry.name}`;
await Deno.copyFile(oldFile, newFile);
}
}
for await (const dirEntry of Deno.readDir(`${projectRoot}/docs`)) {
if (dirEntry.isFile && dirEntry.name.endsWith(".png")) {
const oldFile = `${projectRoot}/docs/${dirEntry.name}`;
const newFile = `${dest}/${dirEntry.name}`;
await Deno.copyFile(oldFile, newFile);
}
}
}
}
async function cleanup() {
await Deno.remove(`${projectRoot}/translations.zip`);
await Deno.remove(`${projectRoot}/translations`, { recursive: true });
}
async function deleteExistingFiles() {
const docsDir = `${projectRoot}/docs`;
console.log(`Deleting most files in ${docsDir}`);
// delete all files except docs/getting-started.json and docs/getting-started.mdx
const filesToKeep = ["getting-started.json", "getting-started.mdx"];
for await (const dirEntry of Deno.readDir(docsDir)) {
if (dirEntry.isFile && !filesToKeep.includes(dirEntry.name)) {
await Deno.remove(`${docsDir}/${dirEntry.name}`);
}
}
const i18nDir = `${projectRoot}/i18n`;
console.log(`Deleting ${i18nDir}`);
await Deno.remove(i18nDir, { recursive: true });
}
async function fetchNotionDocs() {
const child = new Deno.Command("bash", {
args: ["pull_docs.sh"],
stdout: "piped",
stderr: "piped",
}).spawn();
copy(readerFromStreamReader(child.stdout.getReader()), Deno.stdout);
copy(readerFromStreamReader(child.stderr.getReader()), Deno.stderr);
const status = await child.status;
if (!status.success) {
const code = status.code;
throw new Error(
`Failed to fetch Notion docs. pull_docs.sh exited with code ${code}`
);
}
}
try {
console.log("--- Deleting existing files ---");
await deleteExistingFiles();
console.log();
console.log("--- Fetching latest docs from Notion ---");
await fetchNotionDocs();
console.log();
console.log("--- Fetching latest translations from Crowdin ---");
await saveLatestBuild();
console.log();
console.log("--- Copying files to i18n directory ---");
await copyFiles();
console.log();
console.log("--- Completed successfully ---");
} catch (e) {
console.error(e);
} finally {
console.log("--- Cleaning up ---");
await cleanup();
console.log();
console.log("--- Done ---");
}