I usually use frameworks like Yii(2), Zend or Laravel to build pages but a customer forced us to use Wordpress this time.

I integrated Symfony/Twig as my template engine but now I have trouble with localization/translation. Because no matter what I do my strings won't be translated or even found by Wordpress.

Like in Laravel I created a Twig extension to translate the messages

class TranslateExtension extends \Twig_Extension {
    public function getFunctions(){
        return array(
            '__' => new \Twig_SimpleFunction('__', array($this, 'translate'))

    public function getName(){
        return 'TranslateExtension';

    public function translate($string, $handle){
        return __($string, $handle);

So I can do this in my template {{ __('Some strings here', 'plugin-handle') }} but these are not translated or even found by Loco translate creating a custom entry in the .pofile and compiling them into .mo files does not work either.

Can someone please explain me how this works? Nearly all answers/tutorials are about using POedit and insert the translations there but there are no "Add new translation" buttons and when I include the strings manually in my .po files and compile them WP still does not care about those.

If there is no way to use the WP method I'll include my custom functions to translate the strings without Wordpress

Maybe someone can find my mistake when I provide some more information This is how my po file looks like in /languages/cardio-de_DE.po

"Project-Id-Version: Cardio Plugin\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-11-30 16:19+0000\n"
"PO-Revision-Date: 2017-12-07 12:07+0100\n"
"Last-Translator: ******"
"Language-Team: German\n"
"Language: de_DE\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.5\n"
msgid "test"
msgstr "Fooo"

With Poedit I save the file and convert it to .mo format, then I upload it in the same directory like the po file
In my template I do {{ __("test", 'cardio') }} which is returns basically __("test", "cardio") from php but the output is just test and not Foo as expected

  • 1
    I don't know anything about "Loco translate" (for which you also won't get support here as 3rd party plugin questions are off topic). But I assume that the parser that searches for strings to translate in your templates probably just doesn't consider twig files. By trying to shoehorn Twig into WP you're getting yourself into a world of pain. Trust me, I've been there. ;)
    – kraftner
    Commented Dec 7, 2017 at 9:55
  • Well until now I ran fine with twig but I have to admit I don't really use Wordpress functions at all and included my custom query builders, template services, record services, form builders and so on.... Seems like I have to include my custom translation logic as well. Thanks for your answer Commented Dec 7, 2017 at 10:07
  • Okay I see I can't convince you to do it the WP way. I'll let you try this path yourself, I also didn't believe anyone. ;) So all you need for now is some way to automatically generate a *.pot file from your *.twig templates. Then you're approach should work.
    – kraftner
    Commented Dec 7, 2017 at 10:34
  • I know this does not belong here and I should create a new question but could you eventually explain why using twig is not a good way? I don't see any troubles at the moment except this one and after I find a way the page is finished Commented Dec 7, 2017 at 10:38
  • Sure, head over to chat.stackexchange.com/rooms/6/the-loop and ping me.
    – kraftner
    Commented Dec 7, 2017 at 12:55

2 Answers 2


I have my own implementation of twig in WordPress as plugin and my translations are working. You can check my code.

Keep in mind couple of things when when you will test the code:

  • be sure that your WordPress have set locale which you want translate to
  • be sure that you your mo file is compiled from newest version of po file
  • be sure that your mo file exist and is loaded by load_plugin_textdomain function

You can debug which translation files WordPress are loading using script below.

function wpse_287988_debug_mofiles( $mofile, $domain ) {


    return $mofile;

add_filter( 'load_textdomain_mofile', 'wpse_287988_debug_mofiles', 10, 2);

function wpse_287988_terminate() {

add_filter( 'wp_loaded', 'wpse_287988_terminate' );

Working twig implementation:

 * Load composer autloader
require_once dirname(__FILE__) . '/vendor/autoload.php';

// Main class

class WPSE_287988_Twig {

     * Templates path
    private $templates_path;

     * Templates path
    private $options;

     * Twig instance
    private $twig;

     * Twig class constructor
    public function __construct() {

        $this->templates_path = array();
        $this->options = array();



     * Render method
    public function render( $template, $variables = array() ) {

        return $this->twig->render( $template, $variables );

     * Initialize twig options
    private function initialize_twig_options() {

         * Resolve twig templates path
        $plugins_dir = plugin_dir_path( __FILE__ );

        $this->templates_path[] = $plugins_dir;
        $this->templates_path[] = $plugins_dir . 'templates';

        foreach ($this->templates_path as $path) {

            if ( ! file_exists($path) ) {

         * Resolve twig env options, disable cache
        $this->options['cache'] = false;

     * Initialize twig 
    private function initialize_twig() {

        $loader       = new Twig_Loader_Filesystem( $this->templates_path );
        $this->twig   = new Twig_Environment($loader, $this->options );

     * Initialize additional twig funcitons
    public function initialize_twig_functions() {

         * Add gettext __ functions to twig functions.
        $function = new Twig_Function('__', '__');


     * Load the plugin translations
    public function load_plugins_textdomain() {

        $textdomain = 'wpse_287988';

        load_plugin_textdomain( $textdomain, false, basename( dirname( __FILE__ ) ) . '/languages' );

     * Define hooks required by twig class
    private function define_hooks()  {

        add_action( 'plugins_loaded', array( $this, 'load_plugins_textdomain' ) );

// End of main class

// Initialize class

function wpse_287988_twig() {

    static $plugin;

    if ( isset( $plugin ) && $plugin instanceof WPSE_287988_Twig ) {
        return $plugin;

    $plugin = new WPSE_287988_Twig();

    return $plugin;


// End of class initialization

// Testing code

function wpse_287988_test_render() {

    $twig = wpse_287988_twig();
    echo $twig->render('template.html.twig');


add_action('init', 'wpse_287988_test_render');

// End of testing code

My template.html.twig file:

{% set text = "Foo" %}

{{ __(text, 'wpse_287988') }}

I keep my translations in languages directory in the main directory of my plugin. My translations files are named from textdomain and locale: wpse_287988-pl_PL.po and wpse_287988-pl_PL.mo.

  • Thanks for your comment but that does not work either Commented Dec 7, 2017 at 10:53
  • I added some code to my answer to show which file WordPress is loading for translation. Please make sure that this files exists in your file sytstem.
    – kierzniak
    Commented Dec 7, 2017 at 11:00
  • My files are in the output with the correct path Commented Dec 7, 2017 at 11:05
  • Do you use some WP ready Twig lib/plugin or custom implementation? I will try write my own and let you know.
    – kierzniak
    Commented Dec 7, 2017 at 11:10
  • Thank you very much. I don't use anything from Wordpress and build my custom implementation Commented Dec 7, 2017 at 11:13

Use a custom Twig filter.

  1. Create the filter for the WordPress gettext function (__($text, $domain)):
    $wpGetText = new \Twig\TwigFilter('__', function ($text) {
      return __($text, 'my_domain');
  2. Load the filter into your Twig environment:
    $templateEngine = new \Twig\Environment($loader);
  3. Use the filter in your Twig template:
    <h1>{{ 'Hello World'|__ }}</h1>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.