Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: generate currentlySupportedLangs.jsx dynamically | FC-0012 #389

Merged
merged 1 commit into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Loading
Loading