Zend Framework 2 Translate, I18n, Locale

Translanslation in ZF2 is handled pretty easily and one has several ways to do it. In this blog i will follow the ZendSkeletonApplications approach by using translation files in *.mo-format. How do we create those files? How do we tell the application to use the files? What options do we have to set the locale to use? I will answer all these questions for you to internationalize your modules.

Use translations in your view-scripts

Before we create our language (translation) files, let’s first take a look at what we have at our hands to actually use the translation. In ZF2 we have a couple of i18n view helpers available to use. Let’s take a look at an example that I’ve created in a project recently.

In this view script you see three different translation view helpers used. The default translate(), the currencyFormat() and the dateFormat(). This is the most basic usage of view helpers and with this, you will be able to do a couple of things, but you will run into trouble when you create your own .mo/.po files.

Set up your zf2 module for translation

Let’s now take a look at the steps we have to do to set up translation for our modules. First thing would be to ensure that we have a path available for our translation files to be stored into. I suggest going with the suggested layout from the ZendSkeletonApplication to make your Modules as accessible to other developers as possible.

Now that we’ve taken care of that, let’s configure our module to load translation files from said directory.

Please take a quick look at the highlighted lines. First one would be to set your modules namespace into your configuration file. Personally i do this a lot, as it helps in quite a few configuration issues. Makes Copy&Paste’ing a little easier for future modules ;)

The important part however are lines 12-14. Here we define three things.

  • The directory to load translation files from
  • How the files are named
  • The Text-Domain of the translation

Pretty much like what is done within the ZendSkeletonApplication isn’t it? Not really. text_domain ??? This is the part where i had my problems, too. Luckily Ludwig_ from #zftalk@freenode.net was able to help me out. Each translation file that is loaded needs to have a text_domain added. If no text_domain is added, ‘default’ will be assumed. One might think that this is no problem, but actually there can only be one translation file for each text_domain. Since there can be only one text_domain i choose my modules __NAMESPACE__ to be the text_domain, too. As that should pretty much be unique. With this configuration in place, the language files will be loaded.

Now we actually need to change our view-script, too. The translate()-view-helper needs to know about the text_domain, too. Once again i’ll go with the __NAMESPACE__ to make it a little easier for me to type.

Set up locale in ZF2

When working with I18n-Features, we have several ways to set up the locale to use. One way would be to hard code this in our views like the following.

It’s quite obvious that this is more than unlikely to be a handy solution and will probably only be used in very rare cases where you have to ensure a very general name not to get translated. So we have to look for another option. We can set up a locale at the configuration files, too. For this we need to add the locale key to our translator configuration.

Once again this feature kind of sucks. We need to make the locale variable. For this we have several features, too. Personally i like to go with the clients HTTP_ACCEPT_LANGUAGE. Of course you could extend this by first going with the HTTP_ACCEPT_LANGUAGE and later check if there’s a session variable set (or some user preferences at the database), for now let’s just go with it though. Time to edit our Module.php

Within our modules Bootstrap we now set our default Locale to that of the HTTP_ACCEPT_LANGUAGE. I’d argue that in a lot of cases this is a good thing and doesn’t need to be changed often. As mentioned previously, you could potentionally look up $_SESSION-Variable or DB-Entries for user-specific Locale-settings and overwrite the current locale to make it better.

In case no valid locale can be built from the HTTP_ACCEPT_LANGUAGE (you never know what those hackers do!) we still have a fallback defined, in this example ‘en_US’.

All set and done, let’s resume

Let’s take a quick look at the steps we’ve taken to internationalize our Module.

  1. We’ve set up a folger for language files
  2. We’ve set up configuration to load language files from said folder
  3. We’ve set up the translator to load a Locale depending on users HTTP_ACCEPT_LANGUAGE
  4. Furthermore we know how to force specific Locale-Settings
  5. We’ve learned about the text_domain
  6. We’ve also learned about how to use translation features in our view-scripts

Now there is only one thing left to do and that is to actually create the language files. As this is a little off-topic i have created a separate post for this. Please take a look at Using PoEdit to create Translation files. It’s a very easy and straight-forward process that doesn’t consume much time at all.

All in all i hope that i was able to let you in on how to internationalize your modules. If you encoutner any errors, let me know about it and i will try my best to help you out. Just drop me a comment and we’ll see where it goes from there.

ERROR: Class Intl | IntlDateFormatter not found…

A thing i forgot to mention earlier was, that you really need to have the intl-extension running on your webserver. Linux users should find the official documentation helpful enough. Windows users should locate their respective php.ini and search for ‘intl’, remove the semikolon ‘;’ from the line with the extension

Clip to Evernote
52 Responses to Zend Framework 2 Translate, I18n, Locale
  1. Martin Reply

    Hi Sam,

    Thanks for the amazing tutorial :)
    Would you have perhaps a zip file with a full Skeleton Application or an example one to download? I’m sure I’m doing everything right, even read all the comments and SO questions and I still can’t get things to be translated.

    Thanks a lot!
    Martin

  2. Bineet chaubey Reply

    i am using language translations in zend 2 learning project when i am using

    $this->add(array(
    ‘name’ => “username”,
    ‘type’ => ‘Zend\Form\Element\Text’,
    ‘options’ => array(
    ‘label’ => _(‘Your Username’), // i found fatal error at this position
    ),
    ));

    fatal error : Call to undefined function Incuser\Form\_()

    _() for translate a form label give me fatal error
    i am using php 5.3.1 right now .

    Please help me , i am unable to figure out this issue.

    • Sam Reply

      You need to define this function before you call the framework. It should be a global function, you could define it within index.php for example

  3. Io Reply

    How to get current locale in view-scripts?

  4. manou Reply

    Hi,

    Following the tutorial I can’t get the translation on the application I am developing working. I have not error at all, just it is as if I have not added any i18n functionality.

    I describe the problem at stackoverflow: http://stackoverflow.com/questions/21186559/translation-is-not-working-with-zf2

    I’ll appreciate if someone could help to find out what I am skipping.

    Thanks in advance.

  5. MiquelFA Reply

    It worked for me on Firefox, Safari, and IE, but it seems that Chrome is not sending correctly the HTTP_ACCEPT_LANGUAGE.

    I’ve seen the Thomas Szteliga solution but it didn’t work too, any idea?

  6. Gress Reply

    Have you tried localization platforms to do the i18 with? I am using https://poeditor.com/ and my translators are really pleased.

  7. Sandeep Reply

    Hi,

    I am trying to use same method as shown by you above, but it is not working for me. I am using skeleton application.

    Indexcontroller, indexaction

    =================================
    Below is my view

    translate(“Translate me”, __NAMESPACE__ , ‘de_DE’); ?>

    ================================

    Module.config

    ================================
    ‘translator’ => array(
    ‘translation_file_patterns’ => array(
    array(
    ‘type’ => ‘gettext’,
    ‘base_dir’ => __DIR__ . ‘/../language’,
    ‘pattern’ => ‘%s.mo’,
    ‘text_domain’ => __NAMESPACE__,
    ),
    ),
    ),

    =================================

    Please help me what i am missing.

    Thank you
    Sandeep

    • Sam Reply

      Looks clean the way you wrote it, are you certain that the Locale that you’re adressing is properly read? I.e. there is a ‘de_DE.mo’ available?
      Furthermore i suggest to check out the Module SlmLocale, it’s quite awesome :)

  8. Dusan Reply

    Hi,

    Can translation be turned of without changing the template?
    I am calling it like this in template
    translate(‘Article details’);?>

    I have cleared the translation file, but still i am woried about performance, so i am interested how it can be turned of?

    Thanks

    • Sam Reply

      If you don’t want translation, don’t use “$this->translate()” – use your IDE to remove the function and just keep the strings :S

      • Dusan

        I plan to use translation later, so i don’t want to remove “$this->translate()”. But meanwhile i would like to disable it, so it doesn’t affect my website performance, without changing the template.

  9. Robert Reply

    I am getting this error any help about it ?
    Notice: Undefined index: HTTP_ACCEPT_LANGUAGE, maybe a no valid locale

    • Sam Reply

      Sounds more like your browser doesn’t send the HTTP_ACCEPT_LANGUAGE. I’ve just pointed this out at another comment: this approach i’ve displayed is a bare minimum of a possibly working solution. One simply may have to make this more bulletproof (checking if accept language is set and THEN using it – not just using it, to give but one example) ;-)

  10. SuckyStrike Reply

    Hi !

    Just to point out that
    \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE'])
    does not always returns a ZF expected locale.
    With this header
    fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
    I get fr as a locale.

    One solution could be to duplicate my fr_FR.mo and rename it to fr.mo, but is there a way to specify multiple locale that point to one .mo file ?

    • Sam Reply

      That is interesting! I would consider this to be a bug in either the Locale class or by the browser that sends a stupid accept-language-string :S
      This idea of setting the locale is only a barebone of what one could achieve. If the Accept_Language is not to be trusted you may have to build more security-handling into it ;)

      • Thomas Szteliga

        I’m had the same problem. To be more precise, my browser is sending pl,en-US and I tried to: $translator->setLocale( \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE'])); in onBootstrap(), but my language files were named pl_PL.mo and I was always seeing the fallback language.

        When I added ‘_PL’, this way \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']) . '_PL' it worked, but it makes no sense at all this way ;) I was thinking about a workaround, to have the language file named as expected pl_PL.mo (not pl.mo).

        I came to a workaround by checking, if \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']) returns a value without a dash, if that’s true, then I simply “recreate” the locale by using the returned value twice with a dash in the middle ;)

        This works for me:

      • Sam

        Hey, thanks for your findings. Indeed just relying on acceptFromHttp isn’t really a good thing as apparently there’s no real consent on what format the return of HTTP_ACCEPT_LANGUAGE actually has… Your share appears to be better than just my version tho!

    • manou Reply

      I’ve appreciated that aswell, btw even if
      $_SERVER['HTTP_ACCEPT_LANGUAGE']

      returns a chain as SuckyStrike showed after instanciate the translate object the locale is well setted:

      [locale:protected] => es_ES

  11. David Reply

    How do you manage translations of
    multioptions in multicheckbox/select/radio form elements ?

    And how to display selected element into the view ?

    For example: gender (male/female)

  12. Shachish Mishra Reply

    Hi

    I am new in zend framework . and I want to implement two language(English and Turkish) in my websites

    Please tell me step by step to implements the two language

  13. Sandeep Singh Reply

    Hi,

    I am really really new to zf2 . Suppose if i want to convert ‘my foo’ to ‘My Foo’ using language file. what should i write in the language file.

    Here is what i can see in application module

    #: ../view/layout/layout.phtml:36
    msgid “Home”
    msgstr “”

    #: ../view/layout/layout.phtml:50
    msgid “All rights reserved.”
    msgstr “”

    #: ../view/application/index/index.phtml:2
    #, php-format
    msgid “Welcome to %sZend Framework 2%s”
    msgstr “”

    I know this is a simple, but i cant really find it by myself

  14. Christian Reply

    Hi Sam,

    thanks a lot for your great post.
    Your solution is working fine in Chrome but while testing with FF V.17 the locale is set to “de” and not to “de_DE”, so the filepattern fails and I always see the fallback language :-(
    Have you an idea how to solve this problem?

    Regards, Christian

    • Sam Reply

      Well, you could check if the locale is of stringLength 2, and if so simply copy the locale with strtoupper() and concatenate it with an underscore. But i haven’t tried this as i didn’t run into such troubles yet.

  15. lstem Reply

    Hello,

    I’m writing an eCommerce application that will soon be used by multiple country and we would like our URL’s to be i18n.
    We use ZF2, let me give you an example of what we would like to do.

    The route /offer/view/1 if linked to the Controller/Action OfferController.viewAction()

    1 – I would like to be able to tackle this URL if I do
    GET http:\\mywebsite dot fr\offre\1
    GET http:\\mywebsite dot co dot uk\offer\/1
    GET http:\\mywebsite dot it\offerta\1
    2 – I would like that in my views, the URL are written in the language depending of the locale
    3 – I would like the URL translation to be declared in only one place. (I don’t want to need to declare my translations on mod_rewrite conf, plus on a .po on the application side)

    Do you have an idea how I could do ?

    • Sam Reply

      Hmm, this is a tricky one and there may be a solution available within ZF2 core. I’d suggest getting in contact on #zftalk @ freenode.net with DASPRiD. He may have a more awesome idea.

      My approach would be:
      1 – Create your normal shopping modules based on the english Locale.
      1.1- Make sure that all routes are appended with the locale: e.g ‘shopping’ would be ‘shopping-en_EN’
      2 – I’d create another module ‘InternationlizedRoutes’, in those i’d define the routes all over again, just with another locale ‘shopping-de_DE’
      3 – I’d write a view-helper for $this->myUrl(‘shopping’) that would append the locale to the url. Take note that using this approach child routes could look like this: ‘shopping-en_EN/viewarticle-en_EN’ (though this is just the route-names! not the URLs)

      I hope this could give you a nice idea. I assume that approach could be working, but still i suggest heading over to stackoverflow.com with such a question :) More knowledgable people there ;)

    • Sam Reply

      I just found a thing you’d find interesting. Check this presentation: http://evan.pro/zf2-router-talk.html#slide19

  16. Grrompf Reply

    I really appreciate your overview giving me a good start. Btw, since making use of the Text_Domain can become really annoying. Therefore, you can easily setup the ‘__NAMESPACE__’ in your defaults of the route.
    Since the translator falls back to the default text_domain, this is a more convenient approach.


    'defaults' => array(
    '__NAMESPACE__'=>'Imprint',
    'controller' => 'Imprint\Controller\Imprint',
    'action' => 'index',
    ),

    • Grrompf Reply

      found out that this wasn’t the clue. Even using the view helper isn’t satiesfying. I do aggree that using the text_domain in the translation method ist probably the most suitable

  17. Mathieu Reply

    Thank for this good topic.

    I have start a test with the ZendSkeletonApplication.
    All work fine for application index.ptml, layout and error.

    But in my module “Album”, I haven’t translation.
    I have duplicate “en_US.po” in Album\language\Album-en_US.po. Same things with other files. And eit it with poedit.

    In my views Album\view\index I use $this->translate(‘My albums’, __NAMESPACE__);

    I don’t know what is wrong.
    What is a text_domain?
    After search on web, I find the text_domain is just the prefix of the translation file. So I have rename my files “en_US.mo/po” in “Album-en_US.mo/po”, but it’s not greater. I have test with album in lower case

    There is a configuration to set in poedit?

    Thanks for your help

    • Sam Reply

      Basically with a text-domain you tell the translator at which translation file he has to look. Please redo the section “set up of translation”. Your translation file should only be called “en_US.po”, not Album-en_US.po. Or you would nee to change the configuration settings to reflect this change like

      • Mathieu

        Thank for your respone.

        In my first test, po files was well named \Album\language\En_us.mo, without the “album” prefix.

        I think my problem is that mo file have not text_domain.
        In your topic you write:
        Each translation file that is loaded needs to have a text_domain added

        Sorry for my low knowledges, but what is a text_domain, and how can I add it on mo files.

        Thanks in advance.

      • Sam

        Once again, please carefully read the SET UP part of my post. Text-Domain basically is like a Namespace for translation files.

        Also please consider that the files are case-sensitive. Meaning ‘En_us’ is wrong, it has to be ‘en_US’

      • Mathieu

        Hi,

        I rewrite all my project, and I have always the problem.

        In my module, if I write:

        and for translation I use:

        My site is well translated.

        Have you got an idea, why __NAMESPACE__ doen’t work?

        I continue my search.

  18. pyc Reply

    Hi :)
    Thanks for this tuto.

    I am currently working to integer translator to my website, i am a beginner and i don’t understand interest to keep a module specific language folder.

    Isn’t a good idea to create a folder ‘languages’ in /public (with scripts etc.) ?
    With this, i just have to configure Poedit to check all my repos, not each module :/

    Can you enlighten me about this plz ? :$ …

    • Sam Reply

      What you’re asking for is entirely possible. After all, you define the path to the language files within configuration, you can have it anywhere you like. The reasoning behind the default approach lies within the nature of a module. A module is an encapsulated aggregation of files that perform a specific task or feature. Translation files are within that scope, so naturally they are resident inside the module itself and not somewhere globally. But as mentioned, simply define your configuration from any module to load files from the folder you need.

  19. Vin Reply

    Great tutorial. As I am beginner to Zend it was of great help. Just had a question that I am going to implement a Multi Language module using zend 2 and your article will def. help me. How can we translate or take the aaproach for doind this for dyannamic content? Soory may be a silly question.

    • Sam Reply

      Translating dynamic content – i.e. an blog article – would be something of a CMS’s task. To output the contents language that the user wants. Translation as with Zend\I18n should – in my opinion – only be used for nearly static strings or string-parts.

      • Vin

        Ok. I will be having a CMS to do this. So, you reckon that Zend\I18n should be used for static content only? Can you suggest me your ideas or tutorial how can I make the dynamic content multilanguage in terms of DB.
        Thanks

      • Sam

        Well, this is a concern that is out of my knowledge to be honest. I don’t have that much of I18n experience. In theory you’d simply do something like ‘select content where locale = $zf2locale’. It’s a question I’d post on SO to get some insight.

  20. Jaaki Reply

    Hi,

    Thanks for the very good article. I did according to your instructions and it works with views. I have problems to translate forms. How this can be done with ZF2? Any idea.

    I have created a form but I don’t find a way to get those label text to translate.

    BR,

    Jaaki

    • Sam Reply

      You’re welcome. This will be one of my next articles in roughly a week or two. Short version: you pass a translator object to the formLabel viewHelper or you don’t use the formCollection or formRow helpers :) See the Documentation for instant help :)

      • Optimus

        Thanks Sam for nice article. A bit confused as mentioned on your given link – “FormLabel will translate the label contents during it’s rendering.” means we don’t need to use translate method in forms. I don’t understand, how poeditor will fetch strings (to add/update catalog) from form labels (form classes) until we don’t use ‘translate()’ method inside form class itself?

      • Sam

        Well, you could configure PoEdit to check within .php Files inside your Forms folder with the syntax

        That way it should find the strings.

      • Optimus

        Thanks for quick reply! I found another way… just wrap text strings with _(‘my string’) in form classes and add ‘_’ to sources keywords in poedit. Btwn ‘=> wont work, giving error while updating catalog.

  21. Raku Reply

    Hi,

    thanks a lot for the translation start-up help!

    Do you by any chance know how to get the translation function to work in Controller and Model scripts as well? Even the documentations sample application has language stings in those so I find it quite essential. Unfortunately I haven’t found anything useful on that topic yet.

    Best regards.

    • Sam Reply

      Indeed there is a way to do such. For this you would simply import the view helper class to your files like this:

      There may be a better solution, but this is working fine for me so far ;)

      • pyc

        Personnaly, to translate something from controller i directly used my translation service. Is there any problem with this ?


        $translatorService = $this->getServiceLocator()->get('');
        $string = $translatorService->translate(""));

Leave a Reply

Your email address will not be published. Please enter your name, email and a comment.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">