Comments (24)
I don't think Twig or Timber is popular enough to warrant bundling functionality for them with standard WP-CLI, especially if it needs major changes to the way the command runs.
This is perfectly fine as a third-party command, though, and we can make sure that it is straight-forward enough to extend the bundled command to enable said third-party command.
Note: You can extend a bundled command with a third-party command and have that override the bundled command when installed through the package manager.
This could be something like this (untested):
class TwigAwareMakePotCommand extends MakePotCommand {
public function __invoke( $args, $assoc_args ) {
$twig = Utils\get_flag_value( $assoc_args, 'twig', false );
// For Twig handling, defer execution to after WP was loaded.
if ( $twig ) {
WP_CLI::add_hook( 'after_wp_load', [ $this, 'process_twig' ] );
}
// Continue with bundled command execution.
parent::__invoke( $args, $assoc_args );
}
}
from i18n-command.
Hello there,
This feature has been requested since quite a long time. I'm happy to announce that we finally manage to handle WordPress translations extraction from Twig file using WP-CLI:
https://github.com/timber/wp-i18n-twig
Technical details (WP-CLI)
In order to provide a consistent DX, the command overrides the default make-pot
command. That required a couple a hacky things:
- Grabbing the original longdesc and adding the
--skip-twig
arg - Adding the longdesc to the command
- Dummy overriding the
__invoke
method so the modified longdesc is preferred over the original one
Just mentioning those details in order to improve extending WP-CLI commands in the future, if that's something you might consider.
Technical details (Twig)
Although it's released by the Timber team, the package is Timber agnostic, should be working with any Twig implementation (see limitations) and outside the project if needed (CI, etc.).
The key here is to avoid compiling Twig files (which would lead to many problems), the logic has been borrowed from Symfony Translation extractor and consists in only parsing Twig templates and adding a "visitor" that collects all translations found in nodes.
The collected translation function calls are shaped as an array just like what the one expected by the gettext PhpFunctionsScanner
class.
Sadly, Twig comments are completely skipped by the lexer and can't be found in the parsed tokens. Translator comments extraction was made possible at the cost of really bad code practice 🙈 but I felt it was still better than writing/overriding/maintaining a whole Lexer class just to grab comments that were already available and "easy" to be picked up here.
The benefit is that the package covers 100% of wp i18n
tests 🥳
Twig and WP-CLI
Since wp i18n
is also supporting Blade templates, would you be willing to add Twig support? I have of course seen the previous discussions/issues about this. I know that a third package is preferred. I'm just wondering if, considering this new package covers all tests, you might reconsider (I could add Twig 2.0 support if needed, which requires "only" PHP ^7.0).
I would obviously understand if you're not, this would ring another scope to maintain, etc. I'm fine keeping it as a third party package 😄
from i18n-command.
from i18n-command.
Thank you!
In the meantime, here is my working code for Twig extraction:
<?php
use Gettext\Translations;
use Gettext\Extractors\Twig;
use WP_CLI\I18n;
final class TwigExtractor extends Twig {
use WP_CLI\I18n\IterableCodeExtractor;
public static $options = [
'twig' => NULL,
'extractComments' => [ 'translators', 'Translators' ],
'constants' => [],
'functions' => [
'__' => 'text_domain',
'_e' => 'text_domain',
'_x' => 'text_context_domain',
'_ex' => 'text_context_domain',
'_n' => 'single_plural_number_domain',
'_nx' => 'single_plural_number_context_domain',
'_n_noop' => 'single_plural_domain',
'_nx_noop' => 'single_plural_context_domain'
]
];
public static function fromString($string, Translations $translations, array $options = []) {
$options += static::$options;
$twig = $options['twig'] ?: self::createTwig();
$source = $twig->compileSource(new Twig_Source($string, ''));
$functions = new I18n\PhpFunctionsScanner( $source );
$functions->saveGettextFunctions( $translations, $options );
}
private static function createTwig() {
$twig = new Twig_Environment(new Twig_Loader_Array(['' => '']));
$twig->addExtension(new Twig_Extensions_Extension_I18n());
$timber = new TimberTwig();
$timber->add_timber_functions( $twig );
$timber->add_timber_filters( $twig );
return static::$options['twig'] = $twig;
}
}
- references to
Twig/Timber
- depends upon timber/timber/pull/1753
- Twig parsing by Timber is made possible because custom filters from theme's
functions.php
have been loaded
(But theWP_CLI_Command
class is a mess.)
from i18n-command.
I just read through these issues as well as timber/timber#1754 and https://timber.github.io/docs/guides/internationalization/.
Is Twig so popular in WordPress space and is Timber the de facto standard for it? Where would we draw the line when someone else comes with their templating engine and wants this included in wp i18n make-pot
?
I am not really sure whether this is something WP-CLI should handle.
The Gettext library we use already supports Twig extraction:
https://github.com/oscarotero/Gettext/blob/eea87605224d25125a39a83fbc9bc1c28d16bb7a/src/Extractors/Twig.php
https://github.com/oscarotero/Gettext/blob/master/tests/assets/twig/input.php
If we can easily use that to make it work with Timber, I'm open for looking at it. Otherwise I'm not sure it's worth the effort.
Also, good to keep in mind that with this we'd need to scan (not necessarily load) every PHP file twice, once for good old gettext and once for Twig.
from i18n-command.
I'll try to offer a PR, but I hope bootstrap'ing WP is not a showstopper...
It's needed to:
- load twig and its filters
- load custom twig filters/functions from functions.php (if any)
from i18n-command.
Personally I really don‘t want to load WP just because some projects might use Twig.
People should be able to use this command in environments where they need to create POT files on the fly, e.g. on Travis CI, the WordPress.org infrastructure, or in projects like https://github.com/wearerequired/traduttore
Perhaps a feature flag or a new sub-command could be used for this, but I feel like in that case this should be handled in a new command offered by Timber. That command can of course extend this one‘s code, so there shouldn‘t be much duplicate code.
@schlessera thoughts?
from i18n-command.
I fully understand the concern, but how to handle writing to one common PO file (in one run) while having two different bootstrap levels?
from i18n-command.
You mean one common POT file?
Let's say your custom command is wp timber make-pot
which creates a new file called twig.pot
.
Then, you can run wp i18n make-pot --merge=twig.pot
to create one single POT file containing all translations from Twig, regular PHP code and JavaScript code.
Of course you could combine these commands, e.g. wp timber make-pot && wp i18n make-pot --merge=twig.pot
.
from i18n-command.
(I wondered whether
make-pot --twig
could have be run after_wp_load
while keepmake-pot
before_wp_load).
I wondered the same yesterday, but I'm not sure it's possible. Also, it could lead to unexpected results for users and would need thorough documentation.
It'd be annoying not being able to reuse the hundreds of useful lines already in
that package.
That's why I suggested extending this command. I bet it's possible to extend the MakePotCommand
class in your own code.
Perhaps you'd need to require this package in composer and use WP_CLI::add_command( 'i18n make-pot', 'My_Extended_Makepot_Command' );
to override the existing one.
Either way, you wouldn't have to rewrite things from scratch.
I don't know the internals of WP-CLI that well yet, hence pinging Alain before to get his thoughts to figure out the best way of extension here.
from i18n-command.
Moving argument handling outside __invoke
would ease reuse: class MakeTimberPot extends \WP_CLI\I18n\MakePotCommand
still seems a bit difficult because of __invoke
magic;
from i18n-command.
Makes sense. I‘ll try to have a look over the weekend. If you have a proposed solution in mind, feel free to create a PR :)
from i18n-command.
Could you also drop "final
" from PhpFunctionsScanner
? It's useful to inherit from it.
from i18n-command.
Timber is intended as a library/theme-dependency/... Not being a plugin, it currently can not bundle wp-cli
commands. Renaming as per #62.
Problems with extending MakePotCommand
as a wp-cli subcommand:
-
command line arguments inheritance can be address: #60 -
Allow modification of extracted$translations
before storing the file: #63 - phpdoc from
MakePotCommand
__invoke
isn't inherited.{@inheritdoc}
does not help. - Impossible to extend from subcommand: Either
__invoke
is used and@subcommand foo
is not taken into account, either__invoke
is made no-op but subcommands are not even registered.
from i18n-command.
phpdoc from
MakePotCommand
__invoke
isn't inherited.{@inheritdoc}
does not help.
Looking at https://make.wordpress.org/cli/handbook/commands-cookbook/#wp_cliadd_commands-third-args-parameter and https://github.com/wp-cli/wp-cli/blob/b00cdfa4fcbe54a352ce7bc1431c3fe47989d2ae/php/WP_CLI/Dispatcher/CommandFactory.php it seems like it should be possible to get the doc block from MakePotCommand
via reflection and pass the parsed doc block as a third argument to WP_CLI::add_command()
Impossible to extend from subcommand: Either
__invoke
is used and@subcommand foo
is not taken into account, either__invoke
is made no-op but subcommands are not even registered.
I'm not sure I can follow. Could you perhaps share some example code so I can better understand what's not working?
from i18n-command.
<?php
// functions.php: if ( defined( 'WP_CLI' ) && WP_CLI ) WP_CLI::add_command( 'timber', 'My_Timber' );
class My_Timber extends \WP_CLI\I18n\MakePotCommand {
/**
* {@inheritdoc}
*
* [--twig]
* : Do additional Twig extraction before saving Po file
*/
public function __invoke( $args, $assoc_args ) { // attempt 1
// error: option definition is wrong, inheritdoc won't work, and even less with intend of synopsis extension.
}
/**
* Create a po file ...
*
* ## OPTIONS
*
* <source>
* : Directory to scan for string extraction.
* [...] full synopsis copy/paste from MakePotCommand
*
* ## EXAMPLES
* $ wp timber make-pot . languages/my-plugin.pot
* @subcommand make-pot
* @when init
*/
public function __invoke( $args, $assoc_args ) { // attempt 2
/* - With `wp timber make-pot . languages/my-plugin.pot`
error: Too many positional arguments
Because __invoke() is not even called. [make-pot is not a correctly registered command
- Only `wp timber . languages/my-plugin.pot`
is accepted, which is undesirable */
}
public function __invoke( $args, $assoc_args ) { // attempt 3
// Make it a no-op, but define a subcommand below...
}
/**
* Create a po file ...
*/
public function my_make_pot( $args, $assoc_args ) {
// never registered, because __invoke() is a no-op.
}
}
class My_Timber extends \WP_CLI_Command { // attempt 4
/**
* Create a po file ...
*
* [full synopsis, possibly with additional options, like --twig]
*
* @subcommand make-pot
* @when init
*/
public function my_make_pot( $args, $assoc_args ) {
$twig = \WP_CLI\Utils\get_flag_value( $assoc_args, 'twig', false );
// Continue with bundled command execution.
$main_pot = new \WP_CLI\I18n\MakePotCommand();
$main_pot->handle_arguments( $args, $assoc_args );
// we would like to access $source, $destination, $translations, as processed by MakePotCommand, this is not currently possible
// we would like to append to $main_pot->translations before storing the file, this is not currently possible (#63)
$this->makeTwigPot();
}
}
from i18n-command.
@drzraf Small note regarding the above code: The idea of overriding the existing command was to add the --twig
flag to the existing command. For this to work, you should register the command with the same name as the one you want to extend, so that your extension takes precedence and only one is loaded:
WP_CLI::add_command( 'i18n make-pot', 'TwigAwareMakePotCommand', [array with options] );
I don't know yet how best to handle the synopsis, I'll have to experiment with that myself. But know that you can also define the synopsis through the [array with options]
in the WP_CLI::add_command()
call: https://make.wordpress.org/cli/handbook/commands-cookbook/#wp_cliadd_commands-third-args-parameter
from i18n-command.
If you instead want to make the command be named wp timber make-pot
I'd do something like this:
WP_CLI::add_command( 'timber make-pot', 'My_Timber' );
class My_Timber {
public function __invoke() { /* ... */ }
}
See https://make.wordpress.org/cli/handbook/commands-cookbook/#required-registration-arguments for details about the magic method __invoke()
and how WP-CLI handles public methods.
from i18n-command.
@swissspidy I could inherit the way you suggested (but still copying the full synopsis).
Then I tried synopsis "inheritance". Command creation accept longdesc
(string), which docparser provides.
But it can't be overriden afterward synopsis would completely override longdesc from docparser, so I'm bound to inline dirty preg/replace. @schlessera ?
$reflection = new \ReflectionClass( "WP_CLI\I18n\MakePotCommand" );
$docparser = new \WP_CLI\DocParser( $reflection->getMethod( '__invoke' )->getDocComment() );
$ld = explode("\n", $docparser->get_longdesc());
$new_ld = '';
$isopt = 0;
foreach($ld as $l) {
if($isopt==1 && strpos($l, '##') === 0) {
$new_ld .= <<<EOF
[--twig]
: With Twig
EOF
. PHP_EOL . PHP_EOL;
}
if($l=='## OPTIONS') $isopt=1;
$new_ld .= $l . PHP_EOL;
}
$args = ['shortdesc' => $docparser->get_shortdesc(),
'longdesc' => $new_ld,
'synopsis' => $docparser->get_synopsis(),
'when' => 'init' /*$docparser->get_tag('when')*/ ];
WP_CLI::add_command( 'timber make-pot', 'My_Timber', $args);
Providing a couple of helpers to \WP_CLI\DocParser
could be nice, eg: addOption()
, setExample()
, ... or maybe map DocParser components to Symfony Console/InputOptions :)
from i18n-command.
@drzraf Yes, I agree, this should be easier to do, and methods for WP_CLI\DocParser
seem like a good addition.
I'll accept PRs for that if you care to provide them.
from i18n-command.
Is there anything left here for the WP-CLI / i18-command side of things?
from i18n-command.
Chronologically:
- Need to support Timber Twig templates extraction (¹) => Answered: Twig isn't mainstream enough.
- But there is no easy way to call an out-of-tree extractor from vanilla
make-pot
. - => Try overriding the whole
i18n make-pot
command => difficult and unlikely to work from a plugin/theme. - => Create a custom command that wraps
i18n make-pot
(calledtimber make-pot
) => ok - But still needs full synopsis copy/pasting inside the wrapped command
- => Above #52 (comment) about a helper that let me just append an argument to (inherited)
make-pot
initial synopsis.
These actually are ... a big stack of painful workarounds.
There is definitely things that could be done either on wp-cli core
side, either on i18n-command
side. Naming a few:
- Support Twig extraction out of the box in
i18n-command
- Add some action/filter to
MakePotCommand::extract_strings
to allow custom extractors (+ corresponding argument handling) - Ease
MakePotCommand
class inheritance to allow arguments handling andextract_strings
override. - Allow command inheritance by allowing option/arguments manipulation from PHP code. One way would be providing some DocParser helpers to change synopsis (like the above
simili-addCommandOption()
)
¹ In the same POT file.
from i18n-command.
Not sure I completely understood all issues in here. It's probably true that Twig/Timber is not used enough to deserve being part of the wp-cli core packages, but I think Twig it's used enough (it's the de facto PHP template language in frameworks like symphony or CMS like Drupal) that wp-cli supports enough so it can be extended to leverage Twig extraction (maybe Timber specifics could come later).
from i18n-command.
About Twig: Timber is the implementation of Twig for WordPress (function binding/gettext wrapping/helper about templating and ACF/...)
About string extraction, you may want to look at this stack:
- #59 [closed]
from i18n-command.
Related Issues (20)
- make-pot failing to parse specific JavaScript code HOT 8
- Missing file name and line numbers in POT file HOT 8
- PHP 8.2 - PHP Deprecated: Use of "static" in callables is deprecated in ...i18n-command/src/IterableCodeExtractor.php on line 245/246
- update-po: Doesn't preserve / create X-Domain header of .pot file HOT 3
- sokirka HOT 1
- PHP Warning: mkdir(): Read-only file system in MakePotCommand.php on line 376 HOT 6
- `make-mo`: Add option to specify filename HOT 8
- Add tests verifying generated translation files work as intended
- PHP Warning: foreach() argument must be of type array|object, HOT 3
- Consider adding file reference for strings extracted from file headers
- Document example of how to format the --headers parameter in the `i18n make-pot` command HOT 1
- Are file references for plugin/theme headers supposed to be absolute paths? HOT 1
- wp i18n make-php - Expectations HOT 2
- wp i18n make-pot: Support PHP8 named parameters HOT 1
- Author URI of the plugin translation string in generated POT file HOT 3
- Issues parsing PO files without empty lines HOT 15
- make-php - Make output file content pretty HOT 2
- Incorrect POT license info for themes without a license in the header HOT 1
- PHP deprecated notices in Unit test
- make-pot not working for my project HOT 14
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from i18n-command.