Skip to content

Commit 70b9bd5

Browse files
author
Brian Vaughn
committed
Added config validation and fixed language <> locale mappin
1 parent 5f2ef48 commit 70b9bd5

File tree

4 files changed

+62
-34
lines changed

4 files changed

+62
-34
lines changed

crowdin/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
__translations/
2+
translations/

crowdin/config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
22
key: process.env.CROWDIN_API_KEY,
33
url: 'https://api.crowdin.com/api/project/react',
4-
translation_threshold: 50,
4+
threshold: 50,
5+
downloadedRootDirectory: 'test-17',
56
};

crowdin/download.js

+56-30
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,41 @@ const {symlink, lstatSync, readdirSync} = require('fs');
55

66
const SYMLINKED_TRANSLATIONS_PATH = path.resolve(__dirname, 'translations');
77
const DOWNLOADED_TRANSLATIONS_PATH = path.resolve(__dirname, '__translations');
8-
const DOWNLOADED_TRANSLATIONS_DOCS_PATH = path.resolve(
8+
9+
// Path to the "docs" folder within the downloaded Crowdin translations bundle.
10+
const downloadedDocsPath = path.resolve(
911
__dirname,
1012
'__translations',
13+
config.downloadedRootDirectory,
1114
'docs',
1215
);
1316

14-
function main() {
15-
const crowdin = new Crowdin({apiKey: config.key, endpointUrl: config.url});
16-
process.chdir(SYMLINKED_TRANSLATIONS_PATH);
17+
// Sanity check (local) Crowdin config file for expected values.
18+
const validateCrowdinConfig = () => {
19+
const errors = [];
20+
if (!config.key) {
21+
errors.push('key: No process.env.CROWDIN_API_KEY value defined.');
22+
}
23+
if (!Number.isInteger(config.threshold)) {
24+
errors.push(`threshold: Invalid translation threshold defined.`);
25+
}
26+
if (!config.downloadedRootDirectory) {
27+
errors.push('downloadedRootDirectory: No root directory defined for the downloaded translations bundle.');
28+
}
29+
if (!config.url) {
30+
errors.push('url: No Crowdin project URL defined.');
31+
}
32+
if (errors.length > 0) {
33+
console.error('Invalid Crowdin config values for:\n• ' + errors.join('\n• '));
34+
throw Error('Invalid Crowdin config');
35+
}
36+
};
1737

38+
// Download Crowdin translations (into DOWNLOADED_TRANSLATIONS_PATH),
39+
// Filter languages that have been sufficiently translated (based on config.threshold),
40+
// And setup symlinks for them (in SYMLINKED_TRANSLATIONS_PATH) for Gatsby to read.
41+
const downloadAndSymlink = () => {
42+
const crowdin = new Crowdin({apiKey: config.key, endpointUrl: config.url});
1843
crowdin
1944
// .export() // Not sure if this should be called in the script since it could be very slow
2045
// .then(() => crowdin.downloadToPath(DOWNLOADED_TRANSLATIONS_PATH))
@@ -23,56 +48,56 @@ function main() {
2348
.then(locales => {
2449
const usableLocales = locales
2550
.filter(
26-
locale => locale.translated_progress > config.translation_threshold,
51+
locale => locale.translated_progress > config.threshold,
2752
)
2853
.map(local => local.code);
2954

30-
const localeDirectories = getDirectories(
31-
DOWNLOADED_TRANSLATIONS_DOCS_PATH,
32-
);
33-
55+
const localeDirectories = getLanguageDirectories(downloadedDocsPath);
3456
const localeToFolderMap = createLocaleToFolderMap(localeDirectories);
3557

3658
usableLocales.forEach(locale => {
3759
createSymLink(localeToFolderMap.get(locale));
3860
});
3961
});
40-
}
62+
63+
};
4164

4265
// Creates a relative symlink from a downloaded translation in the current working directory
4366
// Note that the current working directory of this node process should be where the symlink is created
4467
// or else the relative paths would be incorrect
45-
function createSymLink(folder) {
46-
symlink(`../__translations/docs/${folder}`, folder, err => {
68+
const createSymLink = (folder) => {
69+
const from = path.resolve(downloadedDocsPath, folder);
70+
const to = path.resolve(SYMLINKED_TRANSLATIONS_PATH, folder);
71+
symlink(from, to, err => {
4772
if (!err) {
48-
console.log(`Created symlink for ${folder}.`);
4973
return;
5074
}
5175

5276
if (err.code === 'EEXIST') {
53-
console.log(
54-
`Skipped creating symlink for ${folder}. A symlink already exists.`,
55-
);
77+
// eslint-disable-next-line no-console
78+
console.info(`Symlink already exists for ${folder}`);
5679
} else {
5780
console.error(err);
5881
process.exit(1);
5982
}
6083
});
61-
}
84+
};
6285

63-
// When we run getTranslationStatus(), it gives us 2-ALPHA locale codes unless necessary
64-
// However, the folder structure of downloaded translations always has 4-ALPHA locale codes
65-
// This function creates a map from a locale code to its corresponding folder name
66-
function createLocaleToFolderMap(directories) {
67-
const twoAlphaLocale = locale => locale.substring(0, 2);
86+
// Crowdin.getTranslationStatus() provides ISO 639-1 (e.g. "fr" for French) or 639-3 (e.g. "fil" for Filipino) language codes,
87+
// But the folder structure of downloaded translations uses locale codes (e.g. "fr-FR" for French, "fil-PH" for the Philippines).
88+
// This function creates a map between language and locale code.
89+
const createLocaleToFolderMap = (directories) => {
90+
const localeToLanguageCode = locale => locale.includes('-') ? locale.substr(0, locale.indexOf('-')) : locale;
6891
const localeToFolders = new Map();
6992
const localeToFolder = new Map();
7093

7194
for (let locale of directories) {
95+
const languageCode = localeToLanguageCode(locale);
96+
7297
localeToFolders.set(
73-
twoAlphaLocale(locale),
74-
localeToFolders.has(twoAlphaLocale(locale))
75-
? localeToFolders.get(twoAlphaLocale(locale)).concat(locale)
98+
languageCode,
99+
localeToFolders.has(languageCode)
100+
? localeToFolders.get(languageCode).concat(locale)
76101
: [locale],
77102
);
78103
}
@@ -88,13 +113,14 @@ function createLocaleToFolderMap(directories) {
88113
});
89114

90115
return localeToFolder;
91-
}
116+
};
92117

93-
function getDirectories(source) {
94-
return readdirSync(source).filter(
118+
// Parse downloaded translation folder to determine which langauges it contains.
119+
const getLanguageDirectories = source =>
120+
readdirSync(source).filter(
95121
name =>
96122
lstatSync(path.join(source, name)).isDirectory() && name !== '_data',
97123
);
98-
}
99124

100-
main();
125+
validateCrowdinConfig(config);
126+
downloadAndSymlink();

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,20 @@
7979
"build": "gatsby build",
8080
"check-all": "npm-run-all prettier --parallel lint flow",
8181
"ci-check": "npm-run-all prettier:diff --parallel lint flow",
82+
"crowdin:download": "node ./crowdin/download",
8283
"dev": "gatsby develop -H 0.0.0.0",
8384
"flow": "flow",
8485
"format:source": "prettier --config .prettierrc --write \"{gatsby-*.js,{flow-typed,plugins,src}/**/*.js}\"",
8586
"format:examples": "prettier --config .prettierrc.examples --write \"examples/**/*.js\"",
8687
"lint": "eslint .",
87-
"netlify": "yarn install && yarn translations && yarn build",
88+
"netlify": "yarn install && yarn crowdin:download && yarn build",
8889
"nit:source": "prettier --config .prettierrc --list-different \"{gatsby-*.js,{flow-typed,plugins,src}/**/*.js}\"",
8990
"nit:examples": "prettier --config .prettierrc.examples --list-different \"examples/**/*.js\"",
9091
"prettier": "yarn format:source && yarn format:examples",
9192
"prettier:diff": "yarn nit:source && yarn nit:examples",
9293
"reset": "yarn reset:cache && yarn reset:translations",
9394
"reset:cache": "rimraf ./.cache",
94-
"reset:translations": "rimraf ./crowdin/__translations && find crowdin/translations -type l -not -name '*en-US' -delete",
95-
"translations": "node ./crowdin/download"
95+
"reset:translations": "rimraf ./crowdin/__translations && find crowdin/translations -type l -not -name '*en-US' -delete"
9696
},
9797
"devDependencies": {
9898
"eslint-config-prettier": "^2.6.0",

0 commit comments

Comments
 (0)