Skip to content

Commit

Permalink
feat: generate currentlySupportedLangs.jsx dynamically (#389)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
OmarIthawi committed Feb 5, 2024
1 parent fd5375f commit 500ae54
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 129 deletions.
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ transifex_langs = "ar,fr,es_419,zh_CN"
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
transifex_utils = ./node_modules/.bin/edx_reactifex
generate_supported_langs = src/i18n/scripts/generateSupportedLangs.js

# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl
Expand Down Expand Up @@ -102,8 +101,8 @@ else
pull_translations:
rm -rf src/i18n/messages
cd src/i18n/ \
&& atlas pull --filter=$(transifex_langs) translations/studio-frontend/src/i18n/messages:messages
$(generate_supported_langs) $(transifex_langs)
&& atlas pull $(ATLAS_OPTIONS) translations/studio-frontend/src/i18n/messages:messages
node src/utils/i18n/scripts/generateSupportedLangs.js src/i18n/messages
endif

copy-dist:
Expand Down
90 changes: 0 additions & 90 deletions src/i18n/scripts/generateSupportedLangs.test.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ NAME
generateSupportedLangs.js — Script to generate the 'src/i18n/messages/currentlySupportedLangs.jsx' file which contains static import for react-intl data.
SYNOPSIS
generateSupportedLangs.js [comma separated list of languages]
generateSupportedLangs.js [-h | --help] MESSAGES_DIR
DESCRIPTION
Run this script after 'atlas' has pulled the files in the following structure:
$ generateSupportedLangs.js ar,es_419,fr_CA
$ node src/utils/i18n/scripts/generateSupportedLangs.js src/i18n/messages
This script will generate the 'src/i18n/messages/currentlySupportedLangs.jsx' file which contains static import for
react-intl data based on the JSON language files present in the 'src/i18n/messages' directory.
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.
`;
Expand All @@ -24,26 +26,24 @@ const path = require('path');
const loggingPrefix = path.basename(`${__filename}`); // the name of this JS file

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

/**
* Create main `src/i18n/index.js` messages import file.
*
*
* @param languages - List of directories with a boolean flag whether its "index.js" file is written
* The format is "[\{ directory: "frontend-component-example", isWritten: false \}, ...]"
* @param log - Mockable process.stdout.write
* @param writeFileSync - Mockable fs.writeFileSync
* @param i18nDir` - Path to `src/i18n` directory
*/
function generateSupportedLangsFile({
languages,
log,
writeFileSync,
i18nDir,
i18nMessagesDir,
}) {
const importLines = [];
const exportLines = [];
let importLines = [];

languages.forEach(language => {
const [languageFamilyCode] = language.split('_'); // Get `es` from `es-419`
Expand All @@ -58,14 +58,13 @@ function generateSupportedLangsFile({
//
// This pattern should probably be refactored to pull the translations directly within the `edx-platform`.
const jsonFilename = `${language}.json`;
if (fs.existsSync(`${i18nDir}/messages/${jsonFilename}`)) {
importLines.push(`import './${jsonFilename}';`);
log(`${loggingPrefix}: Notice: Not importing 'messages/${jsonFilename}' because the file wasn't found.\n`);
}

importLines.push(`import './${jsonFilename}';`);
exportLines.push(` '${dashLanguageCode}': ${importVariableName},`);
});

importLines = Array.from(new Set(importLines)); // Remove duplicates
importLines.sort(); // Ensure consistent file output

// See the help message above for sample output.
const indexFileContent = [
filesCodeGeneratorNoticeHeader,
Expand All @@ -75,45 +74,57 @@ function generateSupportedLangsFile({
'};\n',
].join('\n');

writeFileSync(`${i18nDir}/messages/currentlySupportedLangs.jsx`, indexFileContent);
writeFileSync(`${i18nMessagesDir}/currentlySupportedLangs.jsx`, indexFileContent);
}

/*
* Main function of the file.
*/
function main({
parameters,
log,
writeFileSync,
pwd,
i18nMessagesDir,
}) {
const i18nDir = `${pwd}/src/i18n`; // The Micro-frontend i18n root directory
const [languagesString] = parameters;
if (!i18nMessagesDir) {
log(scriptHelpDocument);
log(`${loggingPrefix}: Error: The "MESSAGES_DIR" parameter is required.\n`);
return false;
}

if (parameters.includes('--help') || parameters.includes('-h')) {
if (i18nMessagesDir === '-h' || i18nMessagesDir === '--help') {
log(scriptHelpDocument);
} else if (!parameters.length) {
return true;
}

const languageFiles = fs.readdirSync(`${i18nMessagesDir}`).filter(file => file.endsWith('.json'));
const languages = languageFiles.map(file => file.replace('.json', ''));
languages.sort();

if (!languages.length) {
log(scriptHelpDocument);
log(`${loggingPrefix}: Error: A comma separated list of languages is required.\n`);
} else {
generateSupportedLangsFile({
languages: languagesString.split(','),
log,
writeFileSync,
i18nDir,
});
log(`${loggingPrefix}: Finished generating the 'currentlySupportedLangs.jsx' file.`);
log(`${loggingPrefix}: Error: No language files found in the "${i18nMessagesDir}"'.\n`);
return false;
}

generateSupportedLangsFile({
languages,
writeFileSync,
i18nMessagesDir,
});
log(`${loggingPrefix}: Finished generating the 'currentlySupportedLangs.jsx' file.\n`);
return true;
}

// istanbul ignore next
if (require.main === module) {
// Run the main() function if called from the command line.
main({
parameters: process.argv.slice(2),
const success = main({
i18nMessagesDir: process.argv[2],
log: text => process.stdout.write(text),
writeFileSync: fs.writeFileSync,
pwd: process.env.PWD,
});

process.exit(success ? 0 : 1);
}

module.exports.main = main; // Allow tests to use the main function.
130 changes: 130 additions & 0 deletions src/utils/i18n/scripts/generateSupportedLangs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Tests for the generateSupportedLangs.js command line.

import path from 'path';
import { main as realMain } from './generateSupportedLangs';

const sempleAppsDirectory = path.join(__dirname, '../../../../test-apps');

// History for `process.stdout.write` mock calls.
const logHistory = {
log: [],
latest: null,
};

// History for `fs.writeFileSync` mock calls.
const writeFileHistory = {
log: [],
latest: null,
};

// Mock for process.stdout.write
const log = (text) => {
logHistory.latest = text;
logHistory.log.push(text);
};

// Mock for fs.writeFileSync
const writeFileSync = (filename, content) => {
const entry = { filename, content };
writeFileHistory.latest = entry;
writeFileHistory.log.push(entry);
};

// Main with mocked output
const main = (args) => realMain({
log,
writeFileSync,
i18nMessagesDir: `${sempleAppsDirectory}/app-with-translations/src/i18n/messages`,
...args,
});

// Clean up mock histories
beforeEach(() => {
logHistory.log = [];
logHistory.latest = null;
writeFileHistory.log = [];
writeFileHistory.latest = null;
});

describe('help document', () => {
it('should print help for --help', () => {
const success = main({
i18nMessagesDir: '--help',
});
expect(logHistory.latest).toMatch(
"generateSupportedLangs.js — Script to generate the 'src/i18n/messages/currentlySupportedLangs.jsx'"
);
expect(success).toBe(true);
});

it('should print help for -h', () => {
const success = main({
i18nMessagesDir: '--help',
});
expect(logHistory.latest).toMatch(
"generateSupportedLangs.js — Script to generate the 'src/i18n/messages/currentlySupportedLangs.jsx'"
);
expect(success).toBe(true);
});
});

describe('generate with three languages', () => {
it('should generate currentlySupportedLangs.jsx', () => {
const success = main({
i18nMessagesDir: `${sempleAppsDirectory}/app-with-translations/src/i18n/messages`,
});

expect(writeFileHistory.log.length).toBe(1);
expect(writeFileHistory.latest.filename).toBe(`${sempleAppsDirectory}/app-with-translations/src/i18n/messages/currentlySupportedLangs.jsx`);
expect(success).toBe(true); // Languages generated successfully

// It should write the file with the following content:
// - import 'react-intl/locale-data/ar' and ar.json messages
// - import 'react-intl/locale-data/fr' and fr.json messages
// - import fr_CA.json messages without duplicating the `fr` import because it's the same language
// - import 'react-intl/locale-data/zh' and zh_CN.json messages
// - export the imported locale-data
expect(writeFileHistory.latest.content).toEqual(`// This file is generated by the "generateSupportedLangs.js" script.
import './ar.json';
import './fr.json';
import './fr_CA.json';
import './zh_CN.json';
import arData from 'react-intl/locale-data/ar';
import frData from 'react-intl/locale-data/fr';
import zhData from 'react-intl/locale-data/zh';
export default {
'ar': arData,
'fr': frData,
'fr-ca': frData,
'zh-cn': zhData,
};
`);
});
});

describe('generate errors', () => {
it('should fail with no languages', () => {
const success = main({
i18nMessagesDir: `${sempleAppsDirectory}/app-without-translations/src/i18n/messages`,
});

// It should fail with the following error message:
expect(logHistory.latest).toContain('generateSupportedLangs.js: Error: No language files found in the "');

expect(writeFileHistory.log).toEqual([]);
expect(success).toBe(false); // No languages to generate
});

it('should fail with no MESSAGES_DIR parameter', () => {
const success = main({
i18nMessagesDir: '',
});

// It should fail with the following error message:
expect(logHistory.latest).toBe('generateSupportedLangs.js: Error: The "MESSAGES_DIR" parameter is required.\n');

expect(writeFileHistory.log).toEqual([]);
expect(success).toBe(false); // MESSAGES_DIR parameter is required
});
});
3 changes: 0 additions & 3 deletions test-app/src/i18n/README.md

This file was deleted.

3 changes: 3 additions & 0 deletions test-apps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Test apps

These test apps are used by the `src/utils/i18n/scripts/generateSupportedLangs.test.js` file.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 500ae54

Please sign in to comment.