One of the very painful taks you may face using symfony2 is the extration of all you translation message from your twig templates. This is much more annoying knowing that symfony1.4 did the job for you with a simple command, which does not exist in symfony2.

Today I will give give you a command for symfony2 that checks all your twig messages, combine them with your already existing messages in your yaml translations files and save the new ones. It is a recent work for me and it just works with twig/yml files.

The Command
I embedded it in a Bundle on github: https://github.com/michelsalib/ExtraToolsBundle.

You just need to get it, register the namespace and the bundle.

The name of the command is [cci]bcc:trans:update locale bundleName[/cci], where the [cci]locale[/cci] is the targeted locale (en, fr, es…) and the [cci]bundleName[/cci] is the name of the targeted bundle. You have several options:
– –dump-messages to display your final messages
– –force to update/write your translation files, it also perform a backup of the old ones
– –prefix=”…” if you want to change the prefix use for your new messages, by default [cci]__ [/cci] is used

Some example:
– To extract the messages of your bundle and display them in the console:
[cci]bcc:trans:update –dump-messages fr MyBundle[/cci]

– You can save them:
[cci]bcc:trans:update –force fr MyBundle[/cci]

– In another language:
[cci]bcc:trans:update –force en MyBundle[/cci]

– Or if you want to chaneg the prefix used to generate the new messages:
[cci]bcc:trans:update –force –prefix=’myprefix’ en MyBundle[/cci]

Behind the scene
The trick behind the code is how to properly crawl your twig templates.

First to get them:
[cc lang=”php”]
// get bundle directory
$foundBundle = $this->getApplication()->getKernel()->getBundle(‘MyBundle’);
// get twig templates
$finder = new Finder();
$files = $finder->files()->name(‘*.html.twig’)->in($foundBundle->getPath() . ‘/Resources/views/’);
// iterate over the files
foreach ($files as $file) {
$path = $file->getPathname();
// parse the files
}
[/cc]

Here you just need to use the kernel to retrieve the bundle data from a simple bundle name. When you have the bundle path, you can use the finder to get all the file ending by [cci].html.twig[/cci] in the relative [cci]/Resources/views/[/cci] directory.

You can now parse the twig files:
[cc lang=”php”]
$tree = $twig->parse($twig->tokenize(file_get_contents($path)));
[/cc]

Then you get a tree composed of [cci]Twig_Node[/cci]. The rest is just an algorithmic problem using recursion and type checking to find [cci]SymfonyBridgeTwigNodeTransNode[/cci] (for [cci]{% trans %}…{% end trans %} syntax[/cci]) and [cci]Twig_Node_Print[/cci] that contains trans filter (for [cci]{{ … | trans }}[/cci] syntax).

When you finally have all your messages, you might want to save them into yaml. For that you have two very simple static functions:
[cc lang=”php”]
// get a yaml file into a php array
$array = SymfonyComponentYamlYaml::load($path);
// transform an array into a yaml string
$yml = SymfonyComponentYamlYaml::dump($array);
[/cc]

Wrap up
I hope this command will help you building better symfony2 app, by automatizing some work. Don’t hesitate to report me bugs, suggestions or to fork me on github!

9 thoughts on “Symfony2: A translation message extractor command

  1. Hi.
    I wonder is there a way to extract messages out of PHP class fields (like validation constraints messages, in particular), as opposed to Twig templates?
    And another one — is there any progress in implementing “transchoice” extraction?

    1. Hi Kilar,

      Thanks for your improvement proposal, about the extraction of validation message. Actually, I think it could be done. In fact I am waiting for the current features to get into the official symfony/symfnoy repo and then I will surely look into this kind of improvement.

      About the transchoice, I think I saw it working the other day (because transchoice is a subtype of trans it works smoothly).

      1. Thanks for your reply, Michel.

        I actually expirience some troubles with “transchoise”, but now when you point that out, I think it might be gettext-specific. That is, when you have a message like “one.apple”/”many.apples” you expect it to be parsed into something like:
        msgid “one.apple”
        msgid_plural “many.apples”
        msgstr[0] “I have an apple”
        msgstr[1] “I have %count% apples”
        and that’s what you get with good old Twig’s “trans”/”plural” tags.

        The formula
        msgid “{1}one.apple|]1,+Inf]many.apples”
        msgstr “{1}I have an apple|]1,+Inf]I have %count% apples”
        just doesn’t work here because this transition may be way too complex to handle it this way.

        The number of plural forms is language-specific and is specified in “Plural-Forms” section at the top of of PO-file, along with the pluralization rules. Say, for English localization it’s gotta be
        “Plural-Forms: nplurals=2; plural=n != 1;n”
        and for Russian one it’s
        “Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100=20) ? 1 : 2;n”
        (so there are 3 plural forms, as opposed to 2 in English)
        Yet your PotFormatter::format method just puts
        “Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;n”
        there and then it comes down to printing pairs of “msgid/msgstr”s.

        I appriciate you pointing me the right direction.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s