Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e020a89
Call low-level translation function on relevant module header fields.
felixarntz Dec 21, 2021
e1d6eee
Implement command to extract relevant translation strings from module…
felixarntz Dec 21, 2021
c4a9784
Fix getting translations from module files.
felixarntz Dec 21, 2021
1d8a5ef
Implement logic to output module header translation strings in a gene…
felixarntz Dec 21, 2021
7f14a41
Complete generation of module header translation PHP file.
felixarntz Dec 21, 2021
c050a7b
Rename textdomain to textDomain.
felixarntz Dec 22, 2021
7440486
Use template strings instead of concatenation.
felixarntz Dec 22, 2021
beecdb8
Merge branch 'fix/59-module-headers-translatable' of github.com:WordP…
felixarntz Dec 22, 2021
9b78e0f
Use JS-native Array.flat instead of lodash.flatten.
felixarntz Dec 22, 2021
ba841e8
Fix broken FILE_FOOTER.
felixarntz Dec 22, 2021
b53c00c
Use template string instead of concatenation.
felixarntz Dec 22, 2021
68702fe
Fix JS formatting.
felixarntz Dec 22, 2021
ce10d75
Do not redefine module.exports.
felixarntz Dec 22, 2021
86321e8
Move JS constants towards top of the file.
felixarntz Dec 22, 2021
48ea1f3
Use EOL constant.
felixarntz Dec 22, 2021
430e98f
Simplify module file header parsing to use a single regex for all hea…
felixarntz Dec 22, 2021
053e2e3
Include context in all generated translation entries, using _x.
felixarntz Dec 22, 2021
f6f77e6
Return early if there is an error.
felixarntz Dec 22, 2021
848f0d2
Update production code to also use context so that it works with the …
felixarntz Dec 22, 2021
7aa49c7
Remove unused import.
felixarntz Dec 22, 2021
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ nbproject/
############

build
module-i18n.php

############
## Vendor
Expand Down
12 changes: 12 additions & 0 deletions admin/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -291,5 +292,16 @@ function perflab_get_module_data( $module_file ) {
$module_data['experimental'] = false;
}

// 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;
}
11 changes: 11 additions & 0 deletions bin/plugin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
33 changes: 14 additions & 19 deletions bin/plugin/commands/changelog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand All @@ -33,7 +45,7 @@ const config = require( '../config' );
* @property {string=} token Optional personal access token.
*/

const options = [
exports.options = [
{
argname: '-m, --milestone <milestone>',
description: 'Milestone',
Expand All @@ -49,32 +61,15 @@ 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';
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.
Expand Down
166 changes: 166 additions & 0 deletions bin/plugin/commands/translations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* External dependencies
*/
const path = require( 'path' );
const glob = require( 'fast-glob' );
const fs = require( 'fs' );
const { EOL } = require( 'os' );

/**
* Internal dependencies
*/
const { log, formats } = require( '../lib/logger' );
const config = require( '../config' );

const TAB = '\t';
const NEWLINE = EOL;
const FILE_HEADER = `<?php
/* THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. */
$generated_i18n_strings = array(
`;
const FILE_FOOTER = `
);
/* THIS IS THE END OF THE GENERATED FILE */
`;

/**
* @typedef WPTranslationsCommandOptions
*
* @property {string=} directory Optional directory, default is the root `/modules` directory.
* @property {string=} output Optional output PHP file, default is the root `/module-i18n.php` file.
*/

/**
* @typedef WPTranslationsSettings
*
* @property {string} textDomain Plugin text domain.
* @property {string} directory Modules directory.
* @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 <directory>',
description: 'Modules directory',
},
{
argname: '-d, --output <output>',
description: 'Output file',
},
];

/**
* Command that generates a PHP file from module header translation strings.
*
* @param {WPTranslationsCommandOptions} opt
*/
exports.handler = async ( opt ) => {
await createTranslations( {
textDomain: config.textDomain,
directory: opt.directory || 'modules',
output: opt.output || 'module-i18n.php',
} );
};

/**
* Parses module header translation strings.
*
* @param {WPTranslationsSettings} settings Translations settings.
*
* @return {[]WPTranslationEntry} List of translation entries.
*/
async function getTranslations( settings ) {
const moduleFilePattern = path.join( settings.directory, '*/load.php' );
const moduleFiles = await glob( path.resolve( '.', moduleFilePattern ) );

const moduleTranslations = moduleFiles
.map( ( moduleFile ) => {
// 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]*<?php)?[ \t/*#@]*(${ Object.keys( headers ).join(
'|'
) }):(.*)$`,
'gmi'
);
let match = regex.exec( fileContent );
while ( match ) {
const text = match[ 2 ].trim();
const context = headers[ match[ 1 ] ];
if ( text && context ) {
translationEntries.push( {
text,
context,
} );
}
match = regex.exec( fileContent );
}

return translationEntries;
} )
.filter( ( translations ) => !! translations.length );

return moduleTranslations.flat();
}

/**
* Parses module header 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 }_x( '${ translation.text.replace( /'/g, "\\'" ) }', '${
translation.context
}', '${ 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 ) {
log(
formats.title(
`\n💃Preparing module translations for "${ settings.directory }" in "${ settings.output }"\n\n`
)
);

try {
const translations = await getTranslations( settings );
createTranslationsPHPFile( translations, settings );
} catch ( error ) {
if ( error instanceof Error ) {
log( formats.error( error.stack ) );
return;
}
}

log(
formats.success(
`\n💃Module translations successfully set in "${ settings.output }"\n\n`
)
);
}
1 change: 1 addition & 0 deletions bin/plugin/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
const config = {
githubRepositoryOwner: 'WordPress',
githubRepositoryName: 'performance',
textDomain: 'performance-lab',
};

module.exports = config;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
"@wordpress/scripts": "^19.0",
"chalk": "4.1.1",
"commander": "4.1.0",
"fast-glob": "^3.2.7",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixarntz, we also need to update the package-lock.json to make sure everybody uses the same version of this dependency.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may have been doing something wrong, but when I tried it, the package-lock.json was unchanged. It looks like the same version of this dependency was already previously required via another dependency.

"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",
Expand Down