Skip to content

Commit 500ae54

Browse files
authored
feat: generate currentlySupportedLangs.jsx dynamically (#389)
instead of hardcoded `transifex_langs = "ar,fr,es_419,zh_CN"` now it generates the file based on the `atlas pull` result Refs: FC-0012 OEP-58
1 parent fd5375f commit 500ae54

File tree

11 files changed

+183
-129
lines changed

11 files changed

+183
-129
lines changed

Makefile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ transifex_langs = "ar,fr,es_419,zh_CN"
44
i18n = ./src/i18n
55
transifex_input = $(i18n)/transifex_input.json
66
transifex_utils = ./node_modules/.bin/edx_reactifex
7-
generate_supported_langs = src/i18n/scripts/generateSupportedLangs.js
87

98
# This directory must match .babelrc .
109
transifex_temp = ./temp/babel-plugin-react-intl
@@ -102,8 +101,8 @@ else
102101
pull_translations:
103102
rm -rf src/i18n/messages
104103
cd src/i18n/ \
105-
&& atlas pull --filter=$(transifex_langs) translations/studio-frontend/src/i18n/messages:messages
106-
$(generate_supported_langs) $(transifex_langs)
104+
&& atlas pull $(ATLAS_OPTIONS) translations/studio-frontend/src/i18n/messages:messages
105+
node src/utils/i18n/scripts/generateSupportedLangs.js src/i18n/messages
107106
endif
108107

109108
copy-dist:

src/i18n/scripts/generateSupportedLangs.test.js

Lines changed: 0 additions & 90 deletions
This file was deleted.

src/i18n/scripts/generateSupportedLangs.js renamed to src/utils/i18n/scripts/generateSupportedLangs.js

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ NAME
55
generateSupportedLangs.js — Script to generate the 'src/i18n/messages/currentlySupportedLangs.jsx' file which contains static import for react-intl data.
66
77
SYNOPSIS
8-
generateSupportedLangs.js [comma separated list of languages]
9-
8+
generateSupportedLangs.js [-h | --help] MESSAGES_DIR
109
1110
1211
DESCRIPTION
1312
1413
Run this script after 'atlas' has pulled the files in the following structure:
15-
16-
$ generateSupportedLangs.js ar,es_419,fr_CA
14+
15+
$ node src/utils/i18n/scripts/generateSupportedLangs.js src/i18n/messages
16+
17+
This script will generate the 'src/i18n/messages/currentlySupportedLangs.jsx' file which contains static import for
18+
react-intl data based on the JSON language files present in the 'src/i18n/messages' directory.
1719
1820
This script is intended as a temporary solution until the studio-frontend can dynamically load the languages from the react-intl data like the other micro-frontends.
1921
`;
@@ -24,26 +26,24 @@ const path = require('path');
2426
const loggingPrefix = path.basename(`${__filename}`); // the name of this JS file
2527

2628
// Header note for generated src/i18n/index.js file
27-
const filesCodeGeneratorNoticeHeader = '// This file is generated by the "i18n/scripts/generateSupportedLangs.js" script.';
29+
const filesCodeGeneratorNoticeHeader = '// This file is generated by the "generateSupportedLangs.js" script.';
2830

2931
/**
3032
* Create main `src/i18n/index.js` messages import file.
3133
*
3234
*
3335
* @param languages - List of directories with a boolean flag whether its "index.js" file is written
3436
* The format is "[\{ directory: "frontend-component-example", isWritten: false \}, ...]"
35-
* @param log - Mockable process.stdout.write
3637
* @param writeFileSync - Mockable fs.writeFileSync
3738
* @param i18nDir` - Path to `src/i18n` directory
3839
*/
3940
function generateSupportedLangsFile({
4041
languages,
41-
log,
4242
writeFileSync,
43-
i18nDir,
43+
i18nMessagesDir,
4444
}) {
45-
const importLines = [];
4645
const exportLines = [];
46+
let importLines = [];
4747

4848
languages.forEach(language => {
4949
const [languageFamilyCode] = language.split('_'); // Get `es` from `es-419`
@@ -58,14 +58,13 @@ function generateSupportedLangsFile({
5858
//
5959
// This pattern should probably be refactored to pull the translations directly within the `edx-platform`.
6060
const jsonFilename = `${language}.json`;
61-
if (fs.existsSync(`${i18nDir}/messages/${jsonFilename}`)) {
62-
importLines.push(`import './${jsonFilename}';`);
63-
log(`${loggingPrefix}: Notice: Not importing 'messages/${jsonFilename}' because the file wasn't found.\n`);
64-
}
65-
61+
importLines.push(`import './${jsonFilename}';`);
6662
exportLines.push(` '${dashLanguageCode}': ${importVariableName},`);
6763
});
6864

65+
importLines = Array.from(new Set(importLines)); // Remove duplicates
66+
importLines.sort(); // Ensure consistent file output
67+
6968
// See the help message above for sample output.
7069
const indexFileContent = [
7170
filesCodeGeneratorNoticeHeader,
@@ -75,45 +74,57 @@ function generateSupportedLangsFile({
7574
'};\n',
7675
].join('\n');
7776

78-
writeFileSync(`${i18nDir}/messages/currentlySupportedLangs.jsx`, indexFileContent);
77+
writeFileSync(`${i18nMessagesDir}/currentlySupportedLangs.jsx`, indexFileContent);
7978
}
8079

8180
/*
8281
* Main function of the file.
8382
*/
8483
function main({
85-
parameters,
8684
log,
8785
writeFileSync,
88-
pwd,
86+
i18nMessagesDir,
8987
}) {
90-
const i18nDir = `${pwd}/src/i18n`; // The Micro-frontend i18n root directory
91-
const [languagesString] = parameters;
88+
if (!i18nMessagesDir) {
89+
log(scriptHelpDocument);
90+
log(`${loggingPrefix}: Error: The "MESSAGES_DIR" parameter is required.\n`);
91+
return false;
92+
}
9293

93-
if (parameters.includes('--help') || parameters.includes('-h')) {
94+
if (i18nMessagesDir === '-h' || i18nMessagesDir === '--help') {
9495
log(scriptHelpDocument);
95-
} else if (!parameters.length) {
96+
return true;
97+
}
98+
99+
const languageFiles = fs.readdirSync(`${i18nMessagesDir}`).filter(file => file.endsWith('.json'));
100+
const languages = languageFiles.map(file => file.replace('.json', ''));
101+
languages.sort();
102+
103+
if (!languages.length) {
96104
log(scriptHelpDocument);
97-
log(`${loggingPrefix}: Error: A comma separated list of languages is required.\n`);
98-
} else {
99-
generateSupportedLangsFile({
100-
languages: languagesString.split(','),
101-
log,
102-
writeFileSync,
103-
i18nDir,
104-
});
105-
log(`${loggingPrefix}: Finished generating the 'currentlySupportedLangs.jsx' file.`);
105+
log(`${loggingPrefix}: Error: No language files found in the "${i18nMessagesDir}"'.\n`);
106+
return false;
106107
}
108+
109+
generateSupportedLangsFile({
110+
languages,
111+
writeFileSync,
112+
i18nMessagesDir,
113+
});
114+
log(`${loggingPrefix}: Finished generating the 'currentlySupportedLangs.jsx' file.\n`);
115+
return true;
107116
}
108117

118+
// istanbul ignore next
109119
if (require.main === module) {
110120
// Run the main() function if called from the command line.
111-
main({
112-
parameters: process.argv.slice(2),
121+
const success = main({
122+
i18nMessagesDir: process.argv[2],
113123
log: text => process.stdout.write(text),
114124
writeFileSync: fs.writeFileSync,
115-
pwd: process.env.PWD,
116125
});
126+
127+
process.exit(success ? 0 : 1);
117128
}
118129

119130
module.exports.main = main; // Allow tests to use the main function.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Tests for the generateSupportedLangs.js command line.
2+
3+
import path from 'path';
4+
import { main as realMain } from './generateSupportedLangs';
5+
6+
const sempleAppsDirectory = path.join(__dirname, '../../../../test-apps');
7+
8+
// History for `process.stdout.write` mock calls.
9+
const logHistory = {
10+
log: [],
11+
latest: null,
12+
};
13+
14+
// History for `fs.writeFileSync` mock calls.
15+
const writeFileHistory = {
16+
log: [],
17+
latest: null,
18+
};
19+
20+
// Mock for process.stdout.write
21+
const log = (text) => {
22+
logHistory.latest = text;
23+
logHistory.log.push(text);
24+
};
25+
26+
// Mock for fs.writeFileSync
27+
const writeFileSync = (filename, content) => {
28+
const entry = { filename, content };
29+
writeFileHistory.latest = entry;
30+
writeFileHistory.log.push(entry);
31+
};
32+
33+
// Main with mocked output
34+
const main = (args) => realMain({
35+
log,
36+
writeFileSync,
37+
i18nMessagesDir: `${sempleAppsDirectory}/app-with-translations/src/i18n/messages`,
38+
...args,
39+
});
40+
41+
// Clean up mock histories
42+
beforeEach(() => {
43+
logHistory.log = [];
44+
logHistory.latest = null;
45+
writeFileHistory.log = [];
46+
writeFileHistory.latest = null;
47+
});
48+
49+
describe('help document', () => {
50+
it('should print help for --help', () => {
51+
const success = main({
52+
i18nMessagesDir: '--help',
53+
});
54+
expect(logHistory.latest).toMatch(
55+
"generateSupportedLangs.js — Script to generate the 'src/i18n/messages/currentlySupportedLangs.jsx'"
56+
);
57+
expect(success).toBe(true);
58+
});
59+
60+
it('should print help for -h', () => {
61+
const success = main({
62+
i18nMessagesDir: '--help',
63+
});
64+
expect(logHistory.latest).toMatch(
65+
"generateSupportedLangs.js — Script to generate the 'src/i18n/messages/currentlySupportedLangs.jsx'"
66+
);
67+
expect(success).toBe(true);
68+
});
69+
});
70+
71+
describe('generate with three languages', () => {
72+
it('should generate currentlySupportedLangs.jsx', () => {
73+
const success = main({
74+
i18nMessagesDir: `${sempleAppsDirectory}/app-with-translations/src/i18n/messages`,
75+
});
76+
77+
expect(writeFileHistory.log.length).toBe(1);
78+
expect(writeFileHistory.latest.filename).toBe(`${sempleAppsDirectory}/app-with-translations/src/i18n/messages/currentlySupportedLangs.jsx`);
79+
expect(success).toBe(true); // Languages generated successfully
80+
81+
// It should write the file with the following content:
82+
// - import 'react-intl/locale-data/ar' and ar.json messages
83+
// - import 'react-intl/locale-data/fr' and fr.json messages
84+
// - import fr_CA.json messages without duplicating the `fr` import because it's the same language
85+
// - import 'react-intl/locale-data/zh' and zh_CN.json messages
86+
// - export the imported locale-data
87+
expect(writeFileHistory.latest.content).toEqual(`// This file is generated by the "generateSupportedLangs.js" script.
88+
import './ar.json';
89+
import './fr.json';
90+
import './fr_CA.json';
91+
import './zh_CN.json';
92+
import arData from 'react-intl/locale-data/ar';
93+
import frData from 'react-intl/locale-data/fr';
94+
import zhData from 'react-intl/locale-data/zh';
95+
96+
export default {
97+
'ar': arData,
98+
'fr': frData,
99+
'fr-ca': frData,
100+
'zh-cn': zhData,
101+
};
102+
`);
103+
});
104+
});
105+
106+
describe('generate errors', () => {
107+
it('should fail with no languages', () => {
108+
const success = main({
109+
i18nMessagesDir: `${sempleAppsDirectory}/app-without-translations/src/i18n/messages`,
110+
});
111+
112+
// It should fail with the following error message:
113+
expect(logHistory.latest).toContain('generateSupportedLangs.js: Error: No language files found in the "');
114+
115+
expect(writeFileHistory.log).toEqual([]);
116+
expect(success).toBe(false); // No languages to generate
117+
});
118+
119+
it('should fail with no MESSAGES_DIR parameter', () => {
120+
const success = main({
121+
i18nMessagesDir: '',
122+
});
123+
124+
// It should fail with the following error message:
125+
expect(logHistory.latest).toBe('generateSupportedLangs.js: Error: The "MESSAGES_DIR" parameter is required.\n');
126+
127+
expect(writeFileHistory.log).toEqual([]);
128+
expect(success).toBe(false); // MESSAGES_DIR parameter is required
129+
});
130+
});

test-app/src/i18n/README.md

Lines changed: 0 additions & 3 deletions
This file was deleted.

test-apps/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Test apps
2+
3+
These test apps are used by the `src/utils/i18n/scripts/generateSupportedLangs.test.js` file.

0 commit comments

Comments
 (0)