From e020a897d1cb01b4c38c6f1a2d36e180dbf61b4b Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 21 Dec 2021 13:32:36 -0800 Subject: [PATCH 01/19] Call low-level translation function on relevant module header fields. --- admin/load.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/admin/load.php b/admin/load.php index 9863027bc3..427334c076 100644 --- a/admin/load.php +++ b/admin/load.php @@ -262,6 +262,7 @@ function( $a, $b ) { * Parses the module main file to get the module's metadata. * * This is similar to how plugin data is parsed in the WordPress core function `get_plugin_data()`. + * The user-facing strings will be translated. * * @since 1.0.0 * @@ -291,5 +292,13 @@ function perflab_get_module_data( $module_file ) { $module_data['experimental'] = false; } + // Translate fields using low-level function since they come from PHP comments. + // See the generated `/module-i18n.php` file. + $translatable_fields = array( 'name', 'description' ); + foreach ( $translatable_fields as $field ) { + // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText + $module_data[ $field ] = translate( $module_data[ $field ], 'performance-lab' ); + } + return $module_data; } From e1d6eee5f9f3e1238b76ba2c809c6cc3ce6d2e4c Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 21 Dec 2021 13:33:22 -0800 Subject: [PATCH 02/19] Implement command to extract relevant translation strings from module headers. --- bin/plugin/cli.js | 11 +++ bin/plugin/commands/translations.js | 136 ++++++++++++++++++++++++++++ package.json | 2 + 3 files changed, 149 insertions(+) create mode 100644 bin/plugin/commands/translations.js diff --git a/bin/plugin/cli.js b/bin/plugin/cli.js index f92873de0d..11fbccdefd 100755 --- a/bin/plugin/cli.js +++ b/bin/plugin/cli.js @@ -30,10 +30,21 @@ const { handler: changelogHandler, options: changelogOptions, } = require( './commands/changelog' ); +const { + handler: translationsHandler, + options: translationsOptions, +} = require( './commands/translations' ); withOptions( program.command( 'release-plugin-changelog' ), changelogOptions ) .alias( 'changelog' ) .description( 'Generates a changelog from merged pull requests' ) .action( catchException( changelogHandler ) ); +withOptions( program.command( 'module-translations' ), translationsOptions ) + .alias( 'translations' ) + .description( + 'Generates a PHP file from module header translation strings' + ) + .action( catchException( translationsHandler ) ); + program.parse( process.argv ); diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js new file mode 100644 index 0000000000..5b9fc11dfc --- /dev/null +++ b/bin/plugin/commands/translations.js @@ -0,0 +1,136 @@ +/** + * External dependencies + */ +const { flatten } = require( 'lodash' ); +const path = require( 'path' ); +const glob = require( 'fast-glob' ); +const fs = require( 'fs' ); +const readline = require( 'readline' ); + +/** + * Internal dependencies + */ +const { log, formats } = require( '../lib/logger' ); + +/** + * @typedef WPTranslationsCommandOptions + * + * @property {string=} directory Optional directory, default is the root `/modules` directory. + * @property {string=} output Optional output file, default is the root `/module-i18n.php` file. + */ + +/** + * @typedef WPTranslationsSettings + * + * @property {string=} directory Optional directory, default is the root `/modules` directory. + * @property {string=} output Optional output file, default is the root `/module-i18n.php` file. + */ + +const options = [ + { + argname: '-d, --directory ', + description: 'Modules directory', + }, + { + argname: '-d, --output ', + description: 'Output file', + }, +]; + +/** + * Command that generates a PHP file from module header translation strings. + * + * @param {WPTranslationsCommandOptions} opt + */ +async function handler( opt ) { + await createTranslations( { + directory: opt.directory, + } ); +} + +module.exports = { + options, + handler, +}; + +/** + * Parses module header translation strings. + * + * @param {WPTranslationsSettings} settings Translations settings. + * + * @return {[]string} List of translation strings. + */ +async function getTranslations( settings ) { + const moduleFilePattern = path.join( settings.directory, '*/load.php' ); + const moduleFiles = await glob( + path.resolve( '../../..', moduleFilePattern ) + ); + + const moduleTranslations = await Promise.all( + moduleFiles.map( async ( moduleFile ) => { + const fileStream = fs.createReadStream( moduleFile ); + + const rl = readline.createInterface( { + input: fileStream, + } ); + + const headers = [ 'Module Name', 'Description' ]; + const translationStrings = []; + + for await ( const line of rl ) { + headers.forEach( ( header, index ) => { + const regex = new RegExp( + '^(?:[ \t]*).*/, '' ) + .trim(); + if ( headerValue ) { + headers.splice( index, 1 ); + translationStrings.push( headerValue ); + } + } + } ); + } + + return translationStrings; + } ) + ); + + return flatten( moduleTranslations ); +} + +/** + * Parses module header translation strings and generates a PHP file with them. + * + * @param {WPTranslationsSettings} settings Translations settings. + */ +async function createTranslations( settings ) { + const directory = settings.directory || 'modules'; + const output = settings.output || 'module-i18n.php'; + + log( + formats.title( + `\nšŸ’ƒPreparing module translations for "${ directory }" in "${ output }"\n\n` + ) + ); + + try { + const translations = await getTranslations( settings ); + log( translations ); + } catch ( error ) { + if ( error instanceof Error ) { + log( formats.error( error.stack ) ); + } + } + + log( + formats.success( + `\nšŸ’ƒModule translations successfully set in "${ output }"\n\n` + ) + ); +} diff --git a/package.json b/package.json index e3b75dd174..25fb0fce22 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,12 @@ "@wordpress/scripts": "^19.0", "chalk": "4.1.1", "commander": "4.1.0", + "fast-glob": "^3.2.7", "lodash": "4.17.21" }, "scripts": { "changelog": "./bin/plugin/cli.js changelog", + "translations": "./bin/plugin/cli.js translations", "format-js": "wp-scripts format ./bin", "lint-js": "wp-scripts lint-js ./bin", "format-php": "wp-env run composer run-script format", From c4a97849f4d54973db19322d075f62d049a8b4f2 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 21 Dec 2021 13:39:21 -0800 Subject: [PATCH 03/19] Fix getting translations from module files. --- bin/plugin/commands/translations.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 5b9fc11dfc..04f3d6306d 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -62,9 +62,7 @@ module.exports = { */ async function getTranslations( settings ) { const moduleFilePattern = path.join( settings.directory, '*/load.php' ); - const moduleFiles = await glob( - path.resolve( '../../..', moduleFilePattern ) - ); + const moduleFiles = await glob( path.resolve( '.', moduleFilePattern ) ); const moduleTranslations = await Promise.all( moduleFiles.map( async ( moduleFile ) => { @@ -110,17 +108,20 @@ async function getTranslations( settings ) { * @param {WPTranslationsSettings} settings Translations settings. */ async function createTranslations( settings ) { - const directory = settings.directory || 'modules'; - const output = settings.output || 'module-i18n.php'; + const fullSettings = { + ...settings, + directory: settings.directory || 'modules', + output: settings.output || 'module-i18n.php', + }; log( formats.title( - `\nšŸ’ƒPreparing module translations for "${ directory }" in "${ output }"\n\n` + `\nšŸ’ƒPreparing module translations for "${ fullSettings.directory }" in "${ fullSettings.output }"\n\n` ) ); try { - const translations = await getTranslations( settings ); + const translations = await getTranslations( fullSettings ); log( translations ); } catch ( error ) { if ( error instanceof Error ) { @@ -130,7 +131,7 @@ async function createTranslations( settings ) { log( formats.success( - `\nšŸ’ƒModule translations successfully set in "${ output }"\n\n` + `\nšŸ’ƒModule translations successfully set in "${ fullSettings.output }"\n\n` ) ); } From 1d8a5eff67ae8cd96cb8d2a0f94183023218b97f Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 21 Dec 2021 13:59:31 -0800 Subject: [PATCH 04/19] Implement logic to output module header translation strings in a generated PHP file. --- bin/plugin/commands/translations.js | 60 ++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 04f3d6306d..6ff5b1a433 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -11,19 +11,21 @@ const readline = require( 'readline' ); * Internal dependencies */ const { log, formats } = require( '../lib/logger' ); +const config = require( '../config' ); /** * @typedef WPTranslationsCommandOptions * * @property {string=} directory Optional directory, default is the root `/modules` directory. - * @property {string=} output Optional output file, default is the root `/module-i18n.php` file. + * @property {string=} output Optional output PHP file, default is the root `/module-i18n.php` file. */ /** * @typedef WPTranslationsSettings * - * @property {string=} directory Optional directory, default is the root `/modules` directory. - * @property {string=} output Optional output file, default is the root `/module-i18n.php` file. + * @property {string} textdomain Plugin textdomain. + * @property {string} directory Modules directory. + * @property {string} output Output PHP file. */ const options = [ @@ -44,7 +46,9 @@ const options = [ */ async function handler( opt ) { await createTranslations( { - directory: opt.directory, + textdomain: config.textdomain, + directory: opt.directory || 'modules', + output: opt.output || 'module-i18n.php', } ); } @@ -53,6 +57,19 @@ module.exports = { handler, }; +const TAB = '\t'; +const NEWLINE = '\n'; +const FILE_HEADER = + [ + ' { + // Escape single quotes. + return ( + TAB + + `__( '${ translation.replace( /'/g, "\\'" ) }', '${ + settings.textdomain + }' ),` + ); + } ); + + const fileOutput = FILE_HEADER + output.join( NEWLINE ) + FILE_FOOTER; + fs.writeFileSync( path.join( '.', settings.output ), fileOutput ); +} + /** * Parses module header translation strings and generates a PHP file with them. * * @param {WPTranslationsSettings} settings Translations settings. */ async function createTranslations( settings ) { - const fullSettings = { - ...settings, - directory: settings.directory || 'modules', - output: settings.output || 'module-i18n.php', - }; - log( formats.title( - `\nšŸ’ƒPreparing module translations for "${ fullSettings.directory }" in "${ fullSettings.output }"\n\n` + `\nšŸ’ƒPreparing module translations for "${ settings.directory }" in "${ settings.output }"\n\n` ) ); try { - const translations = await getTranslations( fullSettings ); - log( translations ); + const translations = await getTranslations( settings ); + createTranslationsPHPFile( translations, settings ); } catch ( error ) { if ( error instanceof Error ) { log( formats.error( error.stack ) ); @@ -131,7 +163,7 @@ async function createTranslations( settings ) { log( formats.success( - `\nšŸ’ƒModule translations successfully set in "${ fullSettings.output }"\n\n` + `\nšŸ’ƒModule translations successfully set in "${ settings.output }"\n\n` ) ); } From 7f14a415996fb2cb4b0d78ae2ead6f1ae84c0d32 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 21 Dec 2021 14:03:36 -0800 Subject: [PATCH 05/19] Complete generation of module header translation PHP file. --- .gitignore | 1 + bin/plugin/config.js | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index a54ea55a46..b5f606531a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ nbproject/ ############ build +module-i18n.php ############ ## Vendor diff --git a/bin/plugin/config.js b/bin/plugin/config.js index 00074e6c05..889353223d 100644 --- a/bin/plugin/config.js +++ b/bin/plugin/config.js @@ -11,6 +11,7 @@ const config = { githubRepositoryOwner: 'WordPress', githubRepositoryName: 'performance', + textdomain: 'performance-lab', }; module.exports = config; From c050a7bb9b41d45f07ff955a67e435d2f4d778e1 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 10:59:46 -0800 Subject: [PATCH 06/19] Rename textdomain to textDomain. --- bin/plugin/commands/translations.js | 6 +++--- bin/plugin/config.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 6ff5b1a433..aac8a039fe 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -23,7 +23,7 @@ const config = require( '../config' ); /** * @typedef WPTranslationsSettings * - * @property {string} textdomain Plugin textdomain. + * @property {string} textDomain Plugin text domain. * @property {string} directory Modules directory. * @property {string} output Output PHP file. */ @@ -46,7 +46,7 @@ const options = [ */ async function handler( opt ) { await createTranslations( { - textdomain: config.textdomain, + textDomain: config.textDomain, directory: opt.directory || 'modules', output: opt.output || 'module-i18n.php', } ); @@ -131,7 +131,7 @@ function createTranslationsPHPFile( translations, settings ) { return ( TAB + `__( '${ translation.replace( /'/g, "\\'" ) }', '${ - settings.textdomain + settings.textDomain }' ),` ); } ); diff --git a/bin/plugin/config.js b/bin/plugin/config.js index 889353223d..076169af89 100644 --- a/bin/plugin/config.js +++ b/bin/plugin/config.js @@ -11,7 +11,7 @@ const config = { githubRepositoryOwner: 'WordPress', githubRepositoryName: 'performance', - textdomain: 'performance-lab', + textDomain: 'performance-lab', }; module.exports = config; From 74404864cc114000d1337c941d7dabcaafbb09c7 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 11:00:59 -0800 Subject: [PATCH 07/19] Use template strings instead of concatenation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Crisoforo Gaspar HernĆ”ndez --- bin/plugin/commands/translations.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 6ff5b1a433..f7671ff49e 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -59,16 +59,13 @@ module.exports = { const TAB = '\t'; const NEWLINE = '\n'; -const FILE_HEADER = - [ - ' Date: Wed, 22 Dec 2021 11:02:43 -0800 Subject: [PATCH 08/19] Use JS-native Array.flat instead of lodash.flatten. --- bin/plugin/commands/translations.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 05aab9cba4..6c8258fbb5 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -1,7 +1,6 @@ /** * External dependencies */ -const { flatten } = require( 'lodash' ); const path = require( 'path' ); const glob = require( 'fast-glob' ); const fs = require( 'fs' ); @@ -113,7 +112,7 @@ async function getTranslations( settings ) { } ) ); - return flatten( moduleTranslations ); + return moduleTranslations.flat(); } /** From ba841e820fd2df20622120bde040aefda33d94a6 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 11:03:51 -0800 Subject: [PATCH 09/19] Fix broken FILE_FOOTER. --- bin/plugin/commands/translations.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 6c8258fbb5..9ce2387720 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -63,7 +63,8 @@ const FILE_HEADER = ` Date: Wed, 22 Dec 2021 11:09:40 -0800 Subject: [PATCH 10/19] Use template string instead of concatenation. --- bin/plugin/commands/translations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 9ce2387720..0c6711db07 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -133,7 +133,7 @@ function createTranslationsPHPFile( translations, settings ) { ); } ); - const fileOutput = FILE_HEADER + output.join( NEWLINE ) + FILE_FOOTER; + const fileOutput = `${ FILE_HEADER }${ output.join( NEWLINE ) }${ FILE_FOOTER }`; fs.writeFileSync( path.join( '.', settings.output ), fileOutput ); } From 68702fe635bb4ffb75adcf12b437a302d8efeff5 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 11:10:18 -0800 Subject: [PATCH 11/19] Fix JS formatting. --- bin/plugin/commands/translations.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 0c6711db07..fc3044b10d 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -133,7 +133,9 @@ function createTranslationsPHPFile( translations, settings ) { ); } ); - const fileOutput = `${ FILE_HEADER }${ output.join( NEWLINE ) }${ FILE_FOOTER }`; + const fileOutput = `${ FILE_HEADER }${ output.join( + NEWLINE + ) }${ FILE_FOOTER }`; fs.writeFileSync( path.join( '.', settings.output ), fileOutput ); } From ce10d75d42a28e5ba820c30e748b882e166317d3 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 13:29:02 -0800 Subject: [PATCH 12/19] Do not redefine module.exports. --- bin/plugin/commands/changelog.js | 9 ++------- bin/plugin/commands/translations.js | 9 ++------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index 237fb43331..63d93081b0 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -33,7 +33,7 @@ const config = require( '../config' ); * @property {string=} token Optional personal access token. */ -const options = [ +exports.options = [ { argname: '-m, --milestone ', description: 'Milestone', @@ -49,18 +49,13 @@ const options = [ * * @param {WPChangelogCommandOptions} opt */ -async function handler( opt ) { +exports.handler = async ( opt ) => { await createChangelog( { owner: config.githubRepositoryOwner, repo: config.githubRepositoryName, milestone: opt.milestone, token: opt.token, } ); -} - -module.exports = { - options, - handler, }; const MISSING_TYPE = 'MISSING_TYPE'; diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index fc3044b10d..0fab24e706 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -27,7 +27,7 @@ const config = require( '../config' ); * @property {string} output Output PHP file. */ -const options = [ +exports.options = [ { argname: '-d, --directory ', description: 'Modules directory', @@ -43,17 +43,12 @@ const options = [ * * @param {WPTranslationsCommandOptions} opt */ -async function handler( opt ) { +exports.handler = async ( opt ) => { await createTranslations( { textDomain: config.textDomain, directory: opt.directory || 'modules', output: opt.output || 'module-i18n.php', } ); -} - -module.exports = { - options, - handler, }; const TAB = '\t'; From 86321e8116df8a09387446a8902d17953386bfe6 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 13:30:43 -0800 Subject: [PATCH 13/19] Move JS constants towards top of the file. --- bin/plugin/commands/changelog.js | 24 ++++++++++++------------ bin/plugin/commands/translations.js | 22 +++++++++++----------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/bin/plugin/commands/changelog.js b/bin/plugin/commands/changelog.js index 63d93081b0..316381d392 100644 --- a/bin/plugin/commands/changelog.js +++ b/bin/plugin/commands/changelog.js @@ -14,6 +14,18 @@ const { } = require( '../lib/milestone' ); const config = require( '../config' ); +const MISSING_TYPE = 'MISSING_TYPE'; +const MISSING_FOCUS = 'MISSING_FOCUS'; +const TYPE_PREFIX = '[Type] '; +const FOCUS_PREFIX = '[Focus] '; +const INFRASTRUCTURE_LABEL = 'Infrastructure'; +const PRIMARY_TYPE_LABELS = { + '[Type] Feature': 'Features', + '[Type] Enhancement': 'Enhancements', + '[Type] Bug': 'Bug Fixes', +}; +const PRIMARY_TYPE_ORDER = Object.values( PRIMARY_TYPE_LABELS ); + /** @typedef {import('@octokit/rest')} GitHub */ /** @typedef {import('@octokit/rest').IssuesListForRepoResponseItem} IssuesListForRepoResponseItem */ @@ -58,18 +70,6 @@ exports.handler = async ( opt ) => { } ); }; -const MISSING_TYPE = 'MISSING_TYPE'; -const MISSING_FOCUS = 'MISSING_FOCUS'; -const TYPE_PREFIX = '[Type] '; -const FOCUS_PREFIX = '[Focus] '; -const INFRASTRUCTURE_LABEL = 'Infrastructure'; -const PRIMARY_TYPE_LABELS = { - '[Type] Feature': 'Features', - '[Type] Enhancement': 'Enhancements', - '[Type] Bug': 'Bug Fixes', -}; -const PRIMARY_TYPE_ORDER = Object.values( PRIMARY_TYPE_LABELS ); - /** * Returns a promise resolving to an array of pull requests associated with the * changelog settings object. diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 0fab24e706..cfd8dbb806 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -12,6 +12,17 @@ const readline = require( 'readline' ); const { log, formats } = require( '../lib/logger' ); const config = require( '../config' ); +const TAB = '\t'; +const NEWLINE = '\n'; +const FILE_HEADER = ` { } ); }; -const TAB = '\t'; -const NEWLINE = '\n'; -const FILE_HEADER = ` Date: Wed, 22 Dec 2021 13:32:12 -0800 Subject: [PATCH 14/19] Use EOL constant. --- bin/plugin/commands/translations.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index cfd8dbb806..bb426a9d10 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -5,6 +5,7 @@ const path = require( 'path' ); const glob = require( 'fast-glob' ); const fs = require( 'fs' ); const readline = require( 'readline' ); +const { EOL } = require( 'os' ); /** * Internal dependencies @@ -13,7 +14,7 @@ const { log, formats } = require( '../lib/logger' ); const config = require( '../config' ); const TAB = '\t'; -const NEWLINE = '\n'; +const NEWLINE = EOL; const FILE_HEADER = ` Date: Wed, 22 Dec 2021 14:10:40 -0800 Subject: [PATCH 15/19] Simplify module file header parsing to use a single regex for all headers and not use promises. --- bin/plugin/commands/translations.js | 42 +++++++++++------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index bb426a9d10..f245a2a687 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -74,40 +74,28 @@ async function getTranslations( settings ) { const moduleFilePattern = path.join( settings.directory, '*/load.php' ); const moduleFiles = await glob( path.resolve( '.', moduleFilePattern ) ); - const moduleTranslations = await Promise.all( - moduleFiles.map( async ( moduleFile ) => { - const fileStream = fs.createReadStream( moduleFile ); - - const rl = readline.createInterface( { - input: fileStream, - } ); - + const moduleTranslations = moduleFiles + .map( ( moduleFile ) => { const headers = [ 'Module Name', 'Description' ]; const translationStrings = []; - for await ( const line of rl ) { - headers.forEach( ( header, index ) => { - const regex = new RegExp( - '^(?:[ \t]*).*/, '' ) - .trim(); - if ( headerValue ) { - headers.splice( index, 1 ); - translationStrings.push( headerValue ); - } - } - } ); + const fileContent = fs.readFileSync( moduleFile, 'utf8' ); + const regex = new RegExp( + `^(?:[ \t]* !! translations.length ); return moduleTranslations.flat(); } From 053e2e3ea2143bbf531a5ca9de155f5942a5e0be Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 14:22:26 -0800 Subject: [PATCH 16/19] Include context in all generated translation entries, using _x. --- bin/plugin/commands/translations.js | 44 +++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index f245a2a687..20b530a5bf 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -39,6 +39,13 @@ const FILE_FOOTER = ` * @property {string} output Output PHP file. */ +/** + * @typedef WPTranslationEntry + * + * @property {string} text String to translate. + * @property {string} context Context for translators. + */ + exports.options = [ { argname: '-d, --directory ', @@ -68,7 +75,7 @@ exports.handler = async ( opt ) => { * * @param {WPTranslationsSettings} settings Translations settings. * - * @return {[]string} List of translation strings. + * @return {[]WPTranslationEntry} List of translation entries. */ async function getTranslations( settings ) { const moduleFilePattern = path.join( settings.directory, '*/load.php' ); @@ -76,24 +83,34 @@ async function getTranslations( settings ) { const moduleTranslations = moduleFiles .map( ( moduleFile ) => { - const headers = [ 'Module Name', 'Description' ]; - const translationStrings = []; + // Map of module header => translator context. + const headers = { + 'Module Name': 'module name', + Description: 'module description', + }; + const translationEntries = []; const fileContent = fs.readFileSync( moduleFile, 'utf8' ); const regex = new RegExp( - `^(?:[ \t]* !! translations.length ); @@ -103,18 +120,15 @@ async function getTranslations( settings ) { /** * Parses module header translation strings. * - * @param {[]string} translations List of translation strings. + * @param {[]WPTranslationEntry} translations List of translation entries. * @param {WPTranslationsSettings} settings Translations settings. */ function createTranslationsPHPFile( translations, settings ) { const output = translations.map( ( translation ) => { // Escape single quotes. - return ( - TAB + - `__( '${ translation.replace( /'/g, "\\'" ) }', '${ - settings.textDomain - }' ),` - ); + return `${ TAB }_x( '${ translation.text.replace( /'/g, "\\'" ) }', '${ + translation.context + }', '${ settings.textDomain }' ),`; } ); const fileOutput = `${ FILE_HEADER }${ output.join( From f6f77e6c47ceaa1a907c6bd1b5436ab2f0aa76c5 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 14:23:52 -0800 Subject: [PATCH 17/19] Return early if there is an error. --- bin/plugin/commands/translations.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 20b530a5bf..71a5bbf5ce 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -155,6 +155,7 @@ async function createTranslations( settings ) { } catch ( error ) { if ( error instanceof Error ) { log( formats.error( error.stack ) ); + return; } } From 848f0d2d103f897f308e2f68087eff660f171d8e Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 14:30:29 -0800 Subject: [PATCH 18/19] Update production code to also use context so that it works with the _x calls. --- admin/load.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/admin/load.php b/admin/load.php index 427334c076..2789bd53d9 100644 --- a/admin/load.php +++ b/admin/load.php @@ -292,12 +292,15 @@ function perflab_get_module_data( $module_file ) { $module_data['experimental'] = false; } - // Translate fields using low-level function since they come from PHP comments. - // See the generated `/module-i18n.php` file. - $translatable_fields = array( 'name', 'description' ); - foreach ( $translatable_fields as $field ) { - // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText - $module_data[ $field ] = translate( $module_data[ $field ], 'performance-lab' ); + // Translate fields using low-level function since they come from PHP comments, including the necessary context for + // `_x()`. This must match how these are translated in the generated `/module-i18n.php` file. + $translatable_fields = array( + 'name' => 'module name', + 'description' => 'module description', + ); + foreach ( $translatable_fields as $field => $context ) { + // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralText + $module_data[ $field ] = translate_with_gettext_context( $module_data[ $field ], $context, 'performance-lab' ); } return $module_data; From 7aa49c756a6cede26c8e8b4e8afa353be6410812 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Wed, 22 Dec 2021 14:37:54 -0800 Subject: [PATCH 19/19] Remove unused import. --- bin/plugin/commands/translations.js | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/plugin/commands/translations.js b/bin/plugin/commands/translations.js index 71a5bbf5ce..f9afa1bb6d 100644 --- a/bin/plugin/commands/translations.js +++ b/bin/plugin/commands/translations.js @@ -4,7 +4,6 @@ const path = require( 'path' ); const glob = require( 'fast-glob' ); const fs = require( 'fs' ); -const readline = require( 'readline' ); const { EOL } = require( 'os' ); /**