Skip to content

Commit

Permalink
feat: use atlas in make pull_translations
Browse files Browse the repository at this point in the history
Changes
-------
 - Add `generateSupportedLangs.js` script to generate `currentlySupportedLangs.jsx`
 - Add `atlas` into `make pull_translations` when `OPENEDX_ATLAS_PULL` environment variable is set.
 - Add step to generate `currentlySupportedLangs.jsx` to use any list of languages in Makefile

Refs: [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) implementing Translation Infrastructure OEP-58.
  • Loading branch information
OmarIthawi committed Jun 2, 2023
1 parent 4b63ee6 commit 2116cdb
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 3 deletions.
18 changes: 15 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
UNAME := $(shell uname)

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 @@ -90,9 +92,19 @@ push_translations:
$$(npm bin)/edx_reactifex $(transifex_temp) --comments --v3-scripts-path
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh

pull_translations: ## must be exactly this name for edx tooling support, see ecommerce-scripts/transifex/pull.py
# explicit list of languages defined here and in currentlySupportedLangs.jsx
tx pull -t -f --mode reviewed --languages="ar,fr,es_419,zh_CN"

ifeq ($(OPENEDX_ATLAS_PULL),)
# Pulls translations from Transifex.
pull_translations:
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
else
# Experimental: OEP-58 Pulls translations using atlas
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)
endif

copy-dist:
for f in dist/*; do docker cp $$f edx.devstack.studio:/edx/app/edxapp/edx-platform/node_modules/@edx/studio-frontend/dist/; done
119 changes: 119 additions & 0 deletions src/i18n/scripts/generateSupportedLangs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env node

const scriptHelpDocument = `
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]
DESCRIPTION
Run this script after 'atlas' has pulled the files in the following structure:
$ generateSupportedLangs.js ar,es_419,fr_CA
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.
`;

const fs = require('fs');
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.';

/**
* 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,
}) {
const importLines = [];
const exportLines = [];

languages.forEach(language => {
const [languageFamilyCode] = language.split('_'); // Get `es` from `es-419`

const importVariableName = `${languageFamilyCode.toLowerCase()}Data`;
const dashLanguageCode = language.toLowerCase().replace(/_/g, '-');
importLines.push(`import ${importVariableName} from 'react-intl/locale-data/${languageFamilyCode}';`);

// Note: These imports are not directly consumed by the studio-frontend React app. They're imported to ensure that
// the messages/*.json files exists and they can be loaded via the load_sfe_i18n_messages() function in
// the `edx-platform`.
//
// 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`);
}

exportLines.push(` '${dashLanguageCode}': ${importVariableName},`);
});

// See the help message above for sample output.
const indexFileContent = [
filesCodeGeneratorNoticeHeader,
importLines.join('\n'),
'\nexport default {',
exportLines.join('\n'),
'};\n',
].join('\n');

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

/*
* Main function of the file.
*/
function main({
parameters,
log,
writeFileSync,
pwd,
}) {
const i18nDir = `${pwd}/src/i18n`; // The Micro-frontend i18n root directory
const [languagesString] = parameters;

if (parameters.includes('--help') || parameters.includes('-h')) {
log(scriptHelpDocument);
} else if (!parameters.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.`);
}
}

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

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

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

const sempleAppDirectory = path.join(__dirname, '../../../test-app');

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

// 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 = (...parameters) => realMain({
parameters,
log,
writeFileSync,
pwd: sempleAppDirectory,
});

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

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

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

describe('generate with three languages', () => {
main('ar,de,fr_CA'); // German doesn't have a messages file in the `test-app`

expect(writeFileHistory.log.length).toBe(1);
expect(writeFileHistory.latest.filename).toBe(`${sempleAppDirectory}/src/i18n/messages/currentlySupportedLangs.jsx`);

// It should write the file with the following content:
// - import 'react-intl/locale-data/ar' and ar.json messages
// - import 'react-intl/locale-data/de' without de.json because it doesn't exist in the
// test-app/src/i18n/messages directory
// - import 'react-intl/locale-data/fr' and fr_CA.json messages
// - export the imported locale-data
expect(writeFileHistory.latest.content).toMatch(`// This file is generated by the "i18n/scripts/generateSupportedLangs.js" script.
import arData from 'react-intl/locale-data/ar';
import './ar.json';
import deData from 'react-intl/locale-data/de';
import frData from 'react-intl/locale-data/fr';
import './fr_CA.json';
export default {
'ar': arData,
'de': deData,
'fr-ca': frData,
};
`);
});
3 changes: 3 additions & 0 deletions test-app/src/i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Test i18n directory

These test files are used by the `src/i18n/scripts/generateSupportedLangs.test.js` file.
3 changes: 3 additions & 0 deletions test-app/src/i18n/messages/ar.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"a11yBodyPolicyLink": "سياسة إمكانية الوصول"
}
3 changes: 3 additions & 0 deletions test-app/src/i18n/messages/fr_CA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"a11yBodyPolicyLink": "Politique d'accessibilité"
}
3 changes: 3 additions & 0 deletions test-app/src/i18n/messages/zh_CN.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"a11yBodyPolicyLink": "网站可访问策略"
}

0 comments on commit 2116cdb

Please sign in to comment.