0

I'm trying to translate the breadcrumbs sitting in the module/Application/config/module.config.php configuration file like:

'navigation' => array(
    'default' => array(
        array(
            'label' => \Application\Util\Translator::translate('Home'),
            'route' => 'home',
            'resource' => 'route/home',
            'pages' => array(
            ),
        ),

The breadcrumb Home should display as Accueil in french.

The translator works just fine for the rest of the application. But none of the breadcrumbs are translated. The language resource file has been verified and poedit-ed again and again.

In the very same configuration file, is the translator

configuration:
    'service_manager' => array(
        'factories' => array(
            'account_navigation' => 'Application\Navigation\Service\AccountNavigationFactory',
            'navigation' => 'Zend\Navigation\Service\DefaultNavigationFactory',
            'translator' => 'Zend\I18n\Translator\TranslatorServiceFactory',
            'Application\Collector\RouteCollector'  => 'Application\Service\RouteCollectorServiceFactory',
        ),
    ),
    'translator' => array(
        'locale' => 'fr_FR',  // langue par défaut
        'translation_file_patterns' => array(
            array(
                'type'     => 'gettext',
                'base_dir' => __DIR__ . '/../language',
                'pattern'  => '%s.mo',
            ),
        ),
        'translation_files' => array(
            array(
                'type'     => 'phpArray',
                'filename' => __DIR__ . '/../../../vendor/zendframework/zendframework/resources/languages/fr/Zend_Validate.php',
                'locale'   => 'fr_FR'
            ),
            array(
                'type'     => 'gettext',
                'filename' => __DIR__ . '/../../../vendor/zf-commons/zfc-user/src/ZfcUser/language/fr_FR.mo',
                'locale'   => 'fr_FR'
            ),
            array(
                'type'     => 'phpArray',
                'filename' => __DIR__ . '/../language/fr_FR.php',
                'locale'   => 'fr_FR'
            )
        )
    ),

My application bootstrap looks like:

class Module
{

    public function onBootstrap(MvcEvent $e)
    {
        $eventManager = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();

        $sm = $e->getParam('application')->getServiceManager();

        // Add ACL information to the Navigation view helper
        $authorize = $sm->get('BjyAuthorize\Service\Authorize');
        $acl = $authorize->getAcl();
        $role = $authorize->getIdentity();
        \Zend\View\Helper\Navigation::setDefaultAcl($acl);
        \Zend\View\Helper\Navigation::setDefaultRole($role);

        $id = $sm->get('zfcuser_auth_service')->getIdentity();
        if (! is_null($id)) {
            $em = $sm->get('Doctrine\ORM\EntityManager');
            $usr = $em->find('Application\Entity\User', $id->getId());

            $blameableListener = new \Gedmo\Blameable\BlameableListener();
            $blameableListener->setUserValue($usr);
            $em->getEventManager()->addEventSubscriber($blameableListener);
        } else {
            // redirection
            if (isset($_SERVER['SERVER_NAME']) && preg_match('/monxxxxx/', $_SERVER['SERVER_NAME'])) {
                $strategy = new RedirectionStrategy();
                $strategy->setRedirectRoute('driver/login');
                $eventManager->attach($strategy);
            }
        }
        if ($sm->has('MvcTranslator')) {
            \Zend\Validator\AbstractValidator::setDefaultTranslator($sm->get('MvcTranslator'));
        }

        Locale::setDefault('fr_FR');

        // custom layout
        $e->getApplication()->getEventManager()->getSharedManager()->attach('Zend\Mvc\Controller\AbstractController', 'dispatch', function($e) {
            $route = $e->getRouteMatch();
            $controller = $e->getTarget();
            // change layout for login
            if($route->getParam('controller') == 'zfcuser' && $route->getParam('action') == 'login'){
                $controller->layout('layout/login');
            }
        }, 100);

        // Log the exceptions
        $application   = $e->getApplication();
        $sm = $application->getServiceManager();
        $sharedManager = $application->getEventManager()->getSharedManager();
        $sharedManager->attach('Zend\Mvc\Application', 'dispatch.error',
          function($e) use ($sm) {
            if ($e->getParam('exception') && strpos($e->getParam('exception'), 'are not authorized') === false) {  
              $sm->get('Zend\Log\Logger')->crit($e->getParam('exception'));

              $toEmail = "xxxxx@xxxxx.com";
              $toName = "Service IT";
              $subject = "Exception error";
              $body = $e->getParam('exception');
              \Application\Util\Common::sendMail($toEmail, $toName, $subject, $body);
            }
          }
        );
    }

    public function getConfig()
    {
        return include __DIR__ . '/config/module.config.php';
    }

    public function getAutoloaderConfig()
    {
        return array(
            'Zend\Loader\StandardAutoloader' => array(
                'namespaces' => array(
                    __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__
                )
            )
        );
    }

    public function init($moduleManager)
    {
         $moduleManager->loadModule('ZfcUser');
    }
}

I'm on ZF2 2.2.5.

Stephane
  • 11,836
  • 25
  • 112
  • 175

2 Answers2

1

Might be a silly question, but do you have the PHP gettext module (verified via your phpinfo())? If yes, you could much easier use _('Translatable string') for strings needing translation. You'd have a .mo and .po file in a language folder (next to src, public, etc.).

(Asking the above because you seem to be putting a lot of effort into this, even though your code shows you use gettext as a translator, which should allow you to already translate static strings, such as home in a config file)

As an example the picture below, it is the basic setup for one of my own ZF2 vendor modules:

File structure setup

Next, you'd need Poedit (Free version should be plenty.).

NOTE: Make sure to always use Poedit to edit the .mo/.po files.

Use Poedit to open the files in the language folder, no need to "translate" strings that are the same string as the language they should be, unless you're using shortcuts for strings (ie. "index" string should display "Show overview", but please don't :p)

Within Poedit you have a few options to add additional strings that it searches for in your files and you can also add additional file extensions. I would suggest you have it look for these strings:

  • $this->translate()
  • _()

and these file extensions:

  • .php
  • .phtml

That way you've covered the basics. Of course your setup may be different, so modify as needed.

Lastly, you need to register the usage of gettext PHP extensions in you module config. You need to do this for every module, as you need to give a language folder path, as that is where your module 'should' be translated (though feel free to make it global and have a huge translation file if you must, not recommended).

Add the following bit to your module.config.php file:

'translator' => [
    'locale' => 'en_US', // This line needs to be in the "root" config, feel free to override with/per module settings and/or user settings
    'translation_file_patterns' => [
        [
            'type'     => 'gettext',
            'base_dir' => __DIR__ . '/../language',
            'pattern'  => '%s.mo',
        ],
    ],
],

With all of the above, translating your breadcrumbs should be done like so:

'navigation' => array(
    'default' => array(
        array(
            'label' => _('Home'),
            'route' => 'home',
            'resource' => 'route/home',
            'pages' => array(
            ),
        ),
rkeet
  • 3,406
  • 2
  • 23
  • 49
  • I'm always writing in the .po file and then opening it with poedit and saving it. The translation works fine on all pages of the application, except for the breadcrumbs which are not translated. It seems to be an issue of the translator not being loaded soon enough. – Stephane Oct 10 '17 at 15:21
  • Have you tried it using `_('The string')` though? As `gettext` is a PHP module it's loaded together with PHP and therefore always available. The function you use, `\Application\Util\Translator::translate('Home')` could indeed be translated *after* the `Util\Translator` gets loaded, and thus not translate the string. – rkeet Oct 10 '17 at 16:17
  • I can see in the phpinfo output that there is the module `GetText` by Alex Plotnick and when I open poedit on my resource `module/Application/language/fr_FR.po` file it lists some keywords in the catalog properties: `translate, setLabel, addSuccessMessage, addMessage, addErrorMessage, addInfoMessage, sprintf` but using _('Home') in the breadcrumb errors on an undefined function. – Stephane Oct 11 '17 at 11:00
  • Hmm, than it could be the OS you're working on that could be causing an additional problem, as the normal `_('something')` **should** work. Have a look at these Q&A's on SO: [this](https://stackoverflow.com/questions/23170819/how-to-debug-gettext-not-working-in-php) and [this](https://stackoverflow.com/questions/3398113/php-gettext-problems/3535866#3535866). They might contain a hint pointing you in the right direction, all about Locale handling on the machine your on/working with (ie a Docker setup) and combined PHP with locale installation settings and such. – rkeet Oct 11 '17 at 11:42
  • The `gettext` module was in fact not installed on my php server. I rebuilt and it is now installed and accepts the `_('...')` construct. But it is still displaying the language key sitting inside the construct instead of its translation. For example, it displays `Home` instead of `Accueil` on the `'label' => _('Home'),` statement. – Stephane Oct 11 '17 at 12:14
  • If gettext is installed and works for other strings (other than `home`), check the translation file in Poedit, you might not have translated it or the translation is the same as the string-to-translate. Make sure to re-save the translation files. – rkeet Oct 11 '17 at 12:17
  • I opened the `module/Application/language/fr_FR.po` file and checked the `Home` was in there and translated, and saved it again with poedit. But it is still displaying `Home`. I tried displaying another key and it also fails the same. It feels like the language resources file is not loaded before the breadcrumbs are loaded. – Stephane Oct 11 '17 at 12:22
  • Just to be sure, can you check the language which is set with the `[setLocale()](https://stackoverflow.com/a/33025911/1155833)` function? – rkeet Oct 11 '17 at 17:25
  • Here iit is: `currentLocale: LC_CTYPE=fr_FR.UTF-8;LC_NUMERIC=C;LC_TIME=C;LC_COLLATE=C;LC_MONETARY=C;LC_MESSAGES=C;LC_PAPER=C;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=C;LC_IDENTIFICATION=C` from the `error_log` of `setlocale(LC_ALL, 0);`. – Stephane Oct 11 '17 at 17:29
  • Honestly, that looks OK to me. Also, as it still doesn't work, I'm sorta out of ideas. Well, I do have a last desperate throw of the dice: Have you not declared, in a static way, the first breadcrumb to be "Home" in the helper, and have you not translated it? That's my last idea (then of course, as "home" exists in the breadcrumbs array, it's not added again when it's found in the config (where it **is** translated), as it already exists (added **without** being translated). Example in next comment (char limit) – rkeet Oct 12 '17 at 14:36
  • My `Breadcrumbs` class function `__invoke()` function starts out like this: `public function __invoke(array $breadcrumbs = []) { /* Always prepend the Home breadcrumb. */ $breadcrumbs = [$this->getTranslator()->translate('Home') => '/'] + $breadcrumbs;` As such, it translates the `Home` "key". Does yours do the same/similar? As I said, that was my last idea. If this does not fix it for you, I hope someone else can help you out. (edit: changed comment block due to comment single line on SO) – rkeet Oct 12 '17 at 14:38
  • The breadcrumbs were defined long ago and they used to be translated. Now, after a year's coding on the application with ZF2 version upgrade it doesn't. Go figure.. Thanks anyway for the effort. I shall post here in case I can somehow solve this. – Stephane Oct 12 '17 at 15:58
  • @Stephane Did you manage to get it sorted? (I'm bored and reading back through unanswered questions :p) Reading the above, does you Poedit also search to translate the `$this->getTranslator()->translate()` string? (So, expanded from (`$this->translate()`) Also, is "Home" as a string (explicitely as text) set somewhere during the rendering of your breadcrumbs? As in, some child view, view helper or renderer overwriting the translated string? - Just some random thoughts in case you haven't got it sorted out yet – rkeet Feb 08 '18 at 04:01
  • No, I could not solve that issue and resorted to translate the wholes breadcrumbs directly into the page. We had a deadline to go in prod and we only use one language anyway. – Stephane Feb 09 '18 at 08:07
0

It seems that namespace Zend\View\Helper\Navigation\Breadcrumbs; is not instanciated correctly in your service manager. Therefore, the translator will not work because it also provided by the service manager as a dependancy

What you should do is to verify if Zend\View\Helper\Navigation\Breadcrumbs::line#113 is actually parsed when you load your breadcrumbs. At least this one.

You can also check the method htmlify of AbstractHelper to test if the translator is enabled.

Greco Jonathan
  • 2,517
  • 2
  • 29
  • 54
  • Here is the content of line number 113: `$html = $translator->translate($html, $this->getTranslatorTextDomain());` of the `vendor/zendframework/zendframework/library/Zend/View/Helper/Navigation/Breadcrumbs.php` file. May I ask what you mean with verifying it is parsed ? Cheers. – Stephane Oct 13 '17 at 17:06