Skip to content

Commit 38d5ef4

Browse files
committed
Update script with CSV auto importing
1 parent a51b9f2 commit 38d5ef4

File tree

1 file changed

+121
-29
lines changed

1 file changed

+121
-29
lines changed

src/scripts/crowdin-import.ts

Lines changed: 121 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,63 @@
11
// Library requires
22
const i18Config = require("../../i18n/config.json")
3-
const { copyFileSync, existsSync, mkdirSync, readdirSync } = require("fs")
3+
const {
4+
copyFileSync,
5+
existsSync,
6+
mkdirSync,
7+
readdirSync,
8+
readFileSync,
9+
} = require("fs")
410
const { resolve, join } = require("path")
511
const argv = require("minimist")(process.argv.slice(2))
12+
13+
/******************************
14+
* Console flags *
15+
******************************/
16+
617
/**
7-
* Console flags
818
* -v,--verbose Prints verbose console logs
919
* -f,--full Prints full name of buckets in summary
20+
*/
21+
22+
/******************************
23+
* Instructions for use *
24+
******************************/
25+
26+
/**
27+
* 1. Run `yarn crowdin-clean` to initialize fresh ./.crowdin folder. This can also be used to erase contents when finished.
1028
*
29+
* 2a. Export/import CSV of languages ready for review:
30+
* 1. Open "Website translation board" document in ethereum.org Notion (internal only)
31+
* 2. Switch view of "Translation status by language" table to "All reviewed"
32+
* 3. Click triple-dot (...) menu in TOP right corner of the entire app
33+
* 4. Select "Export" > "Export as CSV"
34+
* Export format: Markdown & CSV
35+
* Include databases: Current view
36+
* Include content: No files or images
37+
* Include subpages: Off
38+
* Click "Export" > Save zip file
39+
* 5. Unzip contents into (or copy into) ./.crowdin folder in the root of this repo
1140
*
12-
* Follow these steps to import translations from Crowdin export:
13-
*
14-
* 1. Copy languages folder from Crowdin export to ./.crowdin
15-
* ie. ./.crowdin/{lang-codes}
16-
* Tip: Run `yarn crowdin-clean` to initialize the `.crowdin` folder. Can
17-
* also be used to erase contents when finished.
18-
*
19-
* 2. Select buckets to import by adding the number of the corresponding
20-
* content bucket to the chosen language array below
21-
* ie. `es: [1, 10],` would import the "Homepage" and "Learn" buckets for Spanish
22-
*
23-
* 3. Save file without committing
41+
* 2b. Alternatively, you can manually add buckets to import to the USER_OVERRIDE object below.
42+
* 1. Add the number of the corresponding content bucket to the chosen language array below
43+
* ie. `es: [1, 10],` would import the "Homepage" and "Learn" buckets for Spanish
44+
* 2. Save file without committing*
2445
*
25-
* 4. Execute script by running `yarn crowdin-import`
46+
* Export/import translated content from Crowdin:
47+
* 1. Export latest translated content from Crowdin and unzip
48+
* 2. Copy languages folder from Crowdin export to ./.crowdin
49+
* ie. ./.crowdin/{lang-codes}
2650
*
27-
* 5. If successful, copy `GATSBY_BUILD_LANGS={langs}` output and paste in
28-
* your `.env`, then build site to test results.
51+
* Execute script:
52+
* 1. Execute script by running `yarn crowdin-import`
53+
* 2. If successful, copy `GATSBY_BUILD_LANGS={langs}` output and paste in
54+
* your `.env`, then build site to test results.
2955
*
30-
* Remember: Revert working changes to this file before committing Crowdin import
56+
* *Remember: Revert any working changes to this file before committing Crowdin import
3157
*/
3258

33-
type UserSelectionObject = { [key: string]: Array<number> }
34-
const USER_SELECTION: UserSelectionObject = {
59+
type BucketsList = { [key: string]: Array<number> }
60+
const USER_OVERRIDE: BucketsList = {
3561
ar: [],
3662
az: [],
3763
bg: [],
@@ -96,10 +122,15 @@ const USER_SELECTION: UserSelectionObject = {
96122
* slight from those used in the repo). These folders must be copied into the
97123
* root `.crowdin` folder of this repo.
98124
*
99-
* Using the USER_SELECTION object above, the script iterates through each
100-
* language chosen, using the dictionary object below to convert the repo lang
101-
* code to the code used by Crowdin (only if needed, defaults to same). `fs`
102-
* is used to find matching language folder.
125+
* A CSV containing the language buckets that have been "Reviewed" can be exported
126+
* from Crowdin to automate the process of importing the needed buckets. See
127+
* "Instructions for use" above.
128+
*
129+
* You can alternative use the USER_OVERRIDE object above to manually select buckets.
130+
*
131+
* The script iterates through each language chosen, using the dictionary object
132+
* below to convert the repo lang code to the code used by Crowdin (only if needed,
133+
* defaults to same). `fs` is used to find matching language folder.
103134
*
104135
* The "buckets" chosen (type number[]) are then iterated over, opening the
105136
* corresponding folder that begins with the same number string (formatted 00).
@@ -131,7 +162,7 @@ if (!existsSync(crowdinRoot)) mkdirSync(crowdinRoot)
131162
* This is used to convert any codes that may differ when performing folder lookup.
132163
*/
133164
const getCrowdinCode = (code: string): string =>
134-
i18Config.filter((lang) => lang.code === code)?.[0].crowdinCode || code
165+
i18Config.filter((lang) => lang.code === code)?.[0]?.crowdinCode || code
135166

136167
/**
137168
* Names for each bucket in order, zero indexed.
@@ -183,6 +214,67 @@ const trackers: TrackerObject = {
183214
const log = (message: any, ...optionalParams: any): void => {
184215
VERBOSE && console.log(message, ...optionalParams)
185216
}
217+
218+
/**
219+
* Fetches CSV exported from Notion "Website translation board" table
220+
* See above for details on how to export CSV and import into repo
221+
* @returns Object containing language codes as keys, and an array of bucket numbers to be imported
222+
*/
223+
const fetchReviewedCsv = (): BucketsList => {
224+
const csvDir: string = readdirSync(crowdinRoot).filter((dir: string) =>
225+
dir.startsWith("Website translation board")
226+
)[0]
227+
if (!csvDir) return {}
228+
const path = join(crowdinRoot, csvDir)
229+
const reviewedCsvPath: Array<string> = readdirSync(path).filter(
230+
(file: string) => {
231+
const fileParts: Array<string> = file.split(".")
232+
return (
233+
fileParts[0].startsWith("https") &&
234+
!fileParts[0].endsWith("all") &&
235+
fileParts[1] === "csv"
236+
)
237+
}
238+
)[0]
239+
const bucketsList: BucketsList = {}
240+
const csvFile = readFileSync(join(path, reviewedCsvPath), "utf8")
241+
if (!csvFile) return {}
242+
const data = csvFile.split("\n").map((row: string) => {
243+
const quotePair = /"([^"]+)"/g
244+
const sanitized = row.replaceAll(quotePair, (match) =>
245+
match.replace(",", " ").replace(/"/g, "")
246+
)
247+
return sanitized.split(",")
248+
})
249+
const headings = data.shift()
250+
const langCodeIndex = headings.indexOf("code")
251+
const firstBucketIndex = headings.findIndex((item: string) =>
252+
item.startsWith("1)")
253+
)
254+
data.forEach((rowItems: Array<string>) => {
255+
const langCode = rowItems[langCodeIndex].split(" ").at(-1) // "es-EM → es" parses to "es"
256+
if (!langCode) return
257+
const bucketsForLang: Array<number> = []
258+
rowItems.forEach((item: string, idx: number) => {
259+
if (item.includes("Reviewed"))
260+
bucketsForLang.push(idx - firstBucketIndex + 1)
261+
})
262+
bucketsList[langCode] = bucketsForLang
263+
})
264+
return bucketsList
265+
}
266+
267+
/**
268+
* If any buckets are selected in USER_OVERRIDE, use those instead of importing from CSV.
269+
*/
270+
const useUserOverRide =
271+
Object.values(USER_OVERRIDE).filter((buckets) => buckets.length > 0).length >
272+
0
273+
274+
const bucketsToImport: BucketsList = useUserOverRide
275+
? USER_OVERRIDE
276+
: fetchReviewedCsv()
277+
186278
/**
187279
* Reads `ls` file contents of `_path`, moving .md and .json files
188280
* to their corresponding destinations in the repo. Function is called
@@ -253,16 +345,16 @@ type SelectionItem = {
253345
crowdinLangCode: string
254346
buckets: Array<number>
255347
}
256-
const importSelection: Array<SelectionItem> = Object.keys(USER_SELECTION)
348+
const importSelection: Array<SelectionItem> = Object.keys(bucketsToImport)
257349
.filter((repoLangCode: string): boolean => {
258-
if (!USER_SELECTION[repoLangCode].length) trackers.emptyBuckets++
259-
return !!USER_SELECTION[repoLangCode].length
350+
if (!bucketsToImport[repoLangCode].length) trackers.emptyBuckets++
351+
return !!bucketsToImport[repoLangCode].length
260352
})
261353
.map(
262354
(repoLangCode: string): SelectionItem => ({
263355
repoLangCode,
264356
crowdinLangCode: getCrowdinCode(repoLangCode),
265-
buckets: USER_SELECTION[repoLangCode],
357+
buckets: bucketsToImport[repoLangCode],
266358
})
267359
)
268360

0 commit comments

Comments
 (0)