-
Notifications
You must be signed in to change notification settings - Fork 140
Ensure that module header fields can be translated #60
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
Merged
Merged
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 e1d6eee
Implement command to extract relevant translation strings from module…
felixarntz c4a9784
Fix getting translations from module files.
felixarntz 1d8a5ef
Implement logic to output module header translation strings in a gene…
felixarntz 7f14a41
Complete generation of module header translation PHP file.
felixarntz c050a7b
Rename textdomain to textDomain.
felixarntz 7440486
Use template strings instead of concatenation.
felixarntz beecdb8
Merge branch 'fix/59-module-headers-translatable' of github.com:WordP…
felixarntz 9b78e0f
Use JS-native Array.flat instead of lodash.flatten.
felixarntz ba841e8
Fix broken FILE_FOOTER.
felixarntz b53c00c
Use template string instead of concatenation.
felixarntz 68702fe
Fix JS formatting.
felixarntz ce10d75
Do not redefine module.exports.
felixarntz 86321e8
Move JS constants towards top of the file.
felixarntz 48ea1f3
Use EOL constant.
felixarntz 430e98f
Simplify module file header parsing to use a single regex for all hea…
felixarntz 053e2e3
Include context in all generated translation entries, using _x.
felixarntz f6f77e6
Return early if there is an error.
felixarntz 848f0d2
Update production code to also use context so that it works with the …
felixarntz 7aa49c7
Remove unused import.
felixarntz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ nbproject/ | |
| ############ | ||
|
|
||
| build | ||
| module-i18n.php | ||
|
|
||
| ############ | ||
| ## Vendor | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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` | ||
| ) | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.jsonto make sure everybody uses the same version of this dependency.There was a problem hiding this comment.
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.jsonwas unchanged. It looks like the same version of this dependency was already previously required via another dependency.