CakePHP URL-based language switching for i18n and l10n (internationalization and localization)

Update (08/03/2012): Dorin M. has refactored this solution for 2.x series, look for the code here. For details of what’s going on, read on below.

———–

I should preface this post by saying that it does not cover the basics of i18n and l10n so, please, first take a look at the manual or one of the helpful cake books
on how to get the basics going.

To better understand the goal and why some things were done the way they were, I’ll summarize the requirements:

  1. The app has to support two languages or more (in this case English and Russian)
  2. Default language is English
  3. The language switching is based on a URL param
  4. The URL format should be: example.com/eng/controller/action
  5. Language choice should persist in the session and a cookie

Just a note here… there are other ways to determine the language requested by the user, for example it could come from a domain name like eng.example.com or rus.example.com. Hopefully the approach outlined here will also be helpful if other methods of language switching are used in your app…

Also, worth while to mention, that having language name in the URL (as opposed to just reading it from the session or cookie) helps with SEO… I won’t bore you here with details, but basically it helps to ensure that a variation of each page, based on the language param in the URL, is properly indexed by the search engines. Thus, each indexed page can be found later in the native language of the user.

Last, but not least, CakePHP uses three letter language name abbreviation, based on this, so I figure, should be fine to use the same in the URL’s.

Alright, so looking at the URL format, instantly raises a question… how do we tack on the language name to the “front” of each URL?

Thankfully the Router accomplishes that pretty easily (in app/config/routes.php):

Router::connect('/:language/:controller/:action/*',
                       array(),
                       array('language' => '[a-z]{3}'));

It takes a ‘language’ parameter and throws it to the front of the URL, just as we need it.

Now, we need to specify a default language to use, I just add this to my app/config/core.php

Configure::write('Config.language', 'eng');

So, when someone comes to the http://example.com/users/home, the site is displayed in English by default. Then we usually see a link somewhere (with a little flag next to it :)), to switch to another language.

In cake we can make those language-switching links like this:

$html->link('Русский', array('language'=>'rus'));

Notice that we set the language param, which we’ll rely on to do our switching. Providing no other params, will simply reload the current page (in the new language) with the param tacked to the front of the URL (more about this later).

Side note, it’s not a good idea to use the __() translation function on language-switching links… If I get to the site and it’s displayed in the language I can’t understand even remotely, the only savior would be a link in my native language, which indicates that i can switch to it (and well, a little flag would help too :))

So now we actually need the code to switch the language, when a user clicks on the link, like above.

It’s best done in the App Controller, kinda like here:

    var $components = array('Session', 'Cookie');

    function beforeFilter() {
        $this->_setLanguage();
    }

    function _setLanguage() {

        if ($this->Cookie->read('lang') && !$this->Session->check('Config.language')) {
            $this->Session->write('Config.language', $this->Cookie->read('lang'));
        }
        else if (isset($this->params['language']) && ($this->params['language']
                 !=  $this->Session->read('Config.language'))) {

            $this->Session->write('Config.language', $this->params['language']);
            $this->Cookie->write('lang', $this->params['language'], false, '20 days');
        }
    }

Let’s take a look at the code quickly and consider some scenarios…

I created a separate method _setLanguage();, the reason I like doing this is that it keeps the beforeFilter() cleaner, which already has enough crap in there usually.
Secondly, it can be overridden in the child controllers, if required.

So let’s consider some user-case scenarios:

  1. The user comes to the site for the very first time

    In this case the default language is read from the core.php file, so the site is set to English

  2. The user starts clicking around the site for the very first time in his native English

    Nothing really needs to be done, so we can happily skip that part

  3. The user comes to the site and has to switch the language to Russian

    Thankfully he sees a link to do so, and clicks on it. Now we check our else if, since no cookie or session with configured language exist yet. We see that the link has a /rus/ param in the URL and it is not yet stored in the session, therefore we write the new value of the default language to the session and the cookie.

  4. The above user browses around the site, leaves, and then comes back

    The session value is still present and therefore the site is automagically translated to Russian. This is good if the user forgot or doesn’t care to use links like example.com/rus/controller/action, because even plain links like example.com/controller/action will display the site in the right language because of the session value.

  5. The above user closes the browser, goes out hunting for wild boars, and comes to the site on some other day

    Now we rely on our previously stored cookie to read in the language and ensure we don’t override anything that might be in the session already. (the first if )

  6. Now if the user decides to read the site in English

    We pretty much follow through the same steps as above.

Now the last thing we need to do is to ensure that a URL param gets automatically added to all the links on the site, if a given language is chosen. Remember, that this is important to have such links for SEO as well.

Well, we’re sure as hell not going to supply the ‘language’ param manually to each link, so let’s override the cake’s default url() method to ensure the language param is now added to all links.

We create app_helper.php in /app/ (same place for app_controller.php and app_model.php), something like this:

class AppHelper extends Helper {

   function url($url = null, $full = false) {
        if(!isset($url['language']) && isset($this->params['language'])) {
          $url['language'] = $this->params['language'];
        }

        return parent::url($url, $full);
   }

}

Basically we check if ‘language’ param is already in the URL if it is, we don’t need to worry about it.
If not, and $this->params[‘language’] is available we pre-pend the required language to the URL.
The rest of the site, and all standard links will now include that ‘language’ param at the front of the URL (again, good for SEO).

And that’s pretty much it, even though the post was a bit long-winded (and beer to you, if you’ve made through the whole thing) it is quite nice to be able to do i18n & l10n in just about 15 lines of code.

A little disclaimer: even though the code seems to work fine, it is still experimental… so if you find some problems I haven’t yet encountered, please be sure to let me know.

P.S. Here’s a sampe test view, from which you can generate your .po files (easily done with cake i18n console command, but this is a topic for another tutorial and there are plenty of them “out there”).

<?php
    __('This is only a test message');
?>

<p>
    <?php echo $html->link(__('Regular link', true), array('action'=>'test')); ?>
</p>

<p>
    <?php echo $html->link(__('Regular link two', true), array('controller'=>'users', 'action'=>'test5', 'some stuff')); ?>
</p>

<p>
    <?php echo $html->link('English', array('language'=>'eng')); ?>
</p>

<p>
    <?php echo $html->link('Русский', array('language'=>'rus')); ?>
</p>

You’d probably want to switch your language, if you are not so good with Russian ;)

  • http://vasilev.name Nasko

    Now, ain’t this a perspective that most I18N/L10N tutorials are completely lacking on? I’ve read numerous articles about gettext in general, and i18n in the CakePHP context, but was completely lost as to how exactly I could automate the generation of links w/ corresponding language parameters. As a result I now have 2 projects, deployed with static /controller/action links, relying on the Config/Cookie setting for the language selection and with no SEO happiness at all…

    This couldn’t have come at a more convenient time. Thanks for this article, tekno! :-)

  • andreas

    I did something similar in one of my cakePHP-apps.
    The reason I switchted to URLs like http://en.domain.com instead of http://domain.com/en/ is:
    You don’t have to mess araound with AppHelper::url().
    (On the other hand you have to take special care of your cookies: set them for *.domain.com not just en.domain.com….)

  • http://www.kosmosgrafisk.dk Kim Biesbjerg

    Hi,

    A couple of things:

    You should also override Controller::redirect in your AppController in the same fashion you override Helper::url in AppHelper. This way redirects work transparently too.

    About SEO.. In your example you will be able to reach the same page on at least two urls:
    1. domain.com/controller/action
    2. 1. domain.com/eng/controller/action

    This is not good for SEO as you might get penalized for duplicate content. What you should do is you should check in beforeFilter if supplied url language param equals the default language set in core.php, and if it does do a 301 (permanent) redirect to domain.com/controller/action (See, the language param has been removed)

    Of course, I also have an opinion on using flags for language representatives: Don’t do it! ;-)

    Flags represents countries; Not languages!

    In Canada spoken languages include French and English – What flag are you going to use? Someone will feel insulted / less comfortable whatever you choose.

    • Steve


      Don’t do it! ;-)
      Flags represents countries; Not languages!

      Nice one, never thought of that, but it is true!

      • teknoid

        @Steve

        This really depends. Using example above it is very possible that a French-Canadian version of the site is different from your English-Canadian site. Perhaps you present a slightly different content, or maybe your e-commerce checkout rules are different.
        Flags as well as your application can represent both content that is country/locale AND language specific.

  • http://teknoid.wordpress.com teknoid

    @Kim Biesbjerg
    I’m not quite sure about any penalties for duplicate content… I don’t have any concrete support that it’s going to have any impact on page ranking. Not having a param in the URL by default, ensures simplicity for the user, which, IMO is quite more important. Either way it’s a simple change and is really up to the requirements.

    Flags… that’s up to the user, hence, as you’ve noticed, I was more or less kidding about it :)

    @andreas
    That approach is just fine as well, but may not be available to some users. Secondly I’m not sure how well that plays with SEO.

    @Nasko
    That’s exactly why I wrote this one up, I saw lots of other tutorials that deals with the basics (some better than others), but nothing that actually showed and explained the functional aspect of this in your app.
    Well, either way… glad to hear it was helpful ;)

  • http://www.bruechner.de powtac

    Great post! Thank you, I was already looking for something like this.

  • http://foomoo.de leo

    Thank’s for the article teknoid! :)
    Here’s another great one about choosing an url-style for multilingual websites: http://h3h.net/2007/01/designing-urls-for-multilingual-web-sites/

  • http://teknoid.wordpress.com teknoid

    @powtac
    Cool, you’re welcome :)

    @leo
    That was an interesting read, thanks.
    It to me it seems that we are lucky to have such an easy alternative in cake, the similar approach on his list required possibly modified dir structure on the server, which would be horrible to maintain.
    Regarding the semantics and simplicity (or as he says aesthetics), having a language URL param seems quite reasonable and clean to me. That being said, my second choice would be language specific domains, as eng.example.com, as IMO, it is quite a nice and simple option, but unfortunately not always available for simple hosts or those who don’t wish to get into DNS management.

  • http://snowdog.pl snowdog

    I have a very similar approach, but to avoid the problem mentioned by Kim Biesbjerg and to make url shorter for some readers I set one primary language for my app and then use urls without language param for the main language.

    For example, if my site is in English, Russian and French i will have urls like:

    – domain.com/controller/action for English
    – domain.com/fr/controller/action for French
    – domain.com/ru/controller/action for Russian

    BTW, I prefer using 2-letters code as more intuitive for most users.

  • holooli

    thanks! great post.

    as Kim said, it’s better to use sub-domains url based translation:
    1- less code
    2- better for SEO, Search engines don’t like duplication.

  • http://teknoid.wordpress.com teknoid

    @holooli

    Unfortunately some users cannot control the domain name to that extend (which was the case for this client). Secondly I’m not positive that it’s better for SEO, and in in all honestly it is good and consistent marketing that wins the SE rankings, SEO from the developer’s perspective can only take you so far.

  • http://teknoid.wordpress.com teknoid

    @snowdog

    It pretty much works the same one this site… I’m gonna put my trust in ISO :)

  • Martin

    Nice article. Definitely an under-documented aspect of language management.

    Just a small note on language codes:
    Just as using a Canadian flag might offend a large part of the population, Using “English” is likely to offend a lot of people. That’s because English is not a language. English is not A language. There is en_gb and en_us for example with differences in spelling like colour vs. color.

    Cake supports these types of language names as well as the simpler eng and en. Using eng or en will select a default version of English if your app does not require the more detailed nuances of the language. Cake recognizes these versions of English (as an example): English (Australian), English (Belize), English (Canadian), English (British), English (Ireland), English (Jamaica), English (New Zealand), English (Trinidad), English (United States), English (South Africa)

    The same setup is of-course available for French and any other language spoken in more than one country. I guess the more slang you want to use the more pronounced the differences are likely to be.

    I just thought it was an aspect worth mentioning in this context since quite a few languages have these kinds of variations.

  • http://teknoid.wordpress.com teknoid

    @Martin
    Thanks for your insight. You are absolutely right about that, but taking a more simplistic approach, let’s consider some other scenarios… For example if I have a Russian flag and a US flag, it accomplishes two things. One is the availability of another language is indicated by a static icon, which could be a good thing if somehow the actual link text did not display properly (due to encoding, etc, etc,). Secondly, the US flag shows further that we are dealing with information for US-based content (rather than British or Irish) and thus, it plays a powerful, yet a simple UI distinction. So again, depending on the context and the application itself, it could be a positive design move or a completely wrong one.

  • http://www.ministryofwebdevelopment.nl primeminister

    He Teknoid! Very good and complete article. The way you determine which language to serve to the client is pretty much like I do in a couple of projects: http://www.cake-toppings.com/2008/08/29/language-detection-and-appropriate-redirects/

    I agree that language flag in domain (fra.domain.com, nl.domain.com) is better for SEO IF (and only if) you place that domain on a server in that country (France or Netherlands) and get content from that server. Then it will count as really language specific and get some extra points in find-ability.

  • Oscar

    Excellent post, thanks!

    I’m considering going with the sub-domain approach though, anyone got tips how to do that?

    Also, couldn’t HTTP Accept-Language be used to do the initial selection automatically as well? I think I read somewhere this could be done directly via Routes, but never found any more info on how to do it.

  • http://teknoid.wordpress.com teknoid

    @Oscar

    Thank you.

    As far as different types of language switching approaches, they are quite nicely outlined in the link posted by leo… please see above.

  • http://teknoid.wordpress.com teknoid

    @primeminister

    Sounds good.
    I do feel that domain named based switching is easier, but this was a better exercise and at some point we might switch to another approach for this client. That, however, becomes much simpler once the foundation is laid out.

  • Oscar

    Ran into some problems today, when trying to combine this method with pretty urls, done like this: http://debuggable.com/posts/new-router-goodies:480f4dd6-4d40-4405-908d-4cd7cbdd56cb

    Does anyone have a nice solution? The url method (i renamed it to prettyUrl since it causes problems with the url rule validation) calls Router directly and the Model doesn’t seem to have a clue about languages.. Meeh. :/

  • Oscar

    Right, figured out a pretty nice solution. prettyUrl() now returns an array with parameters fit for Router::url instead of passing them to Router::url itself. So, instead of returning a string with the url, an array is built and returned which then should be passed to Router::url, easiest via HtmlHelper::link for example. This will then automatically go through our custom url() method which will add the language parameters.

  • http://teknoid.wordpress.com teknoid

    @Oscar

    Well done. Thanks for sharing. ;)

  • Oscar

    Awh crap, teknoid, please edit my comment and change “not returns” to “now returns” :)

  • http://teknoid.wordpress.com teknoid

    @Oscar

    Done

  • http://www.gladbee.net Glad Bee

    Great,thanks!

    • http://teknoid.wordpress.com teknoid

      @Glad Bee

      No problem ;)

  • http://www.wallacemultimedia.net dagod

    Hi, I’m trying to localize the Pages controller, but I’ve a little problem when I create $html->link(‘English’, array(‘language’=>’eng’)); and I’m on http://example.com/pages/home it points to http://example.com/pages/display. It seems that passed params get lost… is it right or I’ve to change something in routes? For the moment I fixed the problem manually adding the pass params: $html->link(‘English’, array(‘language’=>’eng’,join(‘/’,$this->params[‘pass’])))
    Is there a better solution? thank yuo

  • http://www.intrit.com visskiss

    Hey teknoid,

    I set this up as you suggested and it seems to work well with a couple of caveats. I changed the regex for languageto just be a list of acceptable three letter languages (otherwise, the add action was being interpreted as a language. I also added a route for the index page of each other language (like /fre/ for example).

    However, I’m having trouble with the html::link helper, specifically

    $html->link(‘Français’,array(‘language’=>’fre’))

    This works unless there’s a paramater in the current url, so /posts/view/5 should link to ‘/fre/posts/view/5′ but it actually just links to ‘/fre/posts/view’ which in turn redirects.

    Theories? Solutions?

    Thanks again.

  • http://www.intrit.com visskiss

    Well, it’s provisionally fixed. Instead of

    $html->link(’Français’,array(’language’=>’fre’))

    I use

    $html->link(‘English’,array(‘language’=>”)+$this->params[‘pass’])

    which adds the unnamed paramaters located in params->pass (typically [0] => 3 etc) to the link. I also used

    function _setLanguage() {
    if (isset($this->params[‘language’])) {
    $this->Session->write(‘Config.language’, $this->params[‘language’]);
    } else if (isset($this->params[‘named’][‘language’])) {
    $this->params[‘language’]=$this->params[‘named’][‘language’];
    $this->Session->write(‘Config.language’, $this->params[‘language’]);
    } else {
    $this->Session->write(‘Config.language’, ‘eng’);
    }
    $this->set(‘language’,$this->Session->read(‘Config.language’));
    }

    as my set language function so I can use $language in my views if necessary (I use it to suffix sql field names where tables have multiple languages)

  • http://teknoid.wordpress.com teknoid

    @visskiss

    Thanks for sharing. Just curious, why do you need the table suffix in the view?

  • bberg

    @visskiss
    i’m with you. keeping parameter in the url while changing languages is nice. thx for sharing the code.

    @teknoid
    i use $language in the view as a css id for the flags. this way i can modify the current one so the user knows it is already “clicked”. but i don’t see why one would need it for tables suffix there too…
    btw. thanks for the article.

  • bberg

    i must say that the 3-letter language name abbreviation based on the standard iso 639-2 is no good:
    pick brazil. it is commonly abbreviated as “bra”.
    the standard points “bra” to the braj country.
    the 2-letter iso 639-1? no good either:
    in brazil one speaks portuguese. in portugal too. but that’s 2 different languages. this standard points it to “pt” – for portugal only.
    using “pt_br” like “en_us” or “en_gb” would be the solution imho, although it is not always possible.

  • http://teknoid.wordpress.com teknoid

    @bberg

    I’m glad you’ve found the article helpful.
    Also, you are definitely right that the given ISO abbreviation may not be applicable in some cases (for me it worked fine, so I figured to use it)… but hopefully the post shows how to use any type of URL-based language switching to base upon for your specific need.

  • http://kooms.wordpress.com kooms

    Thanks blog owner to write this article!

  • http://teknoid.wordpress.com teknoid

    @kooms

    You’re welcome, glad you’ve found it helpful.

  • http://www.benito.qsl.br Josenivaldo Benito Junior

    Hi,

    For redirect method I overloaded it in the App_controller.php file like this:

    function redirect( $url, $status = NULL, $exit = true ) {
    if (!isset($url[‘language’]) && $this->Session->check(‘Config.language’)) {
    $url[‘language’] = $this->Session->read(‘Config.language’);
    }
    parent::redirect($url,$status,$exit);
    }

    Although after saving session information any URL (with or without language) will work this make sure that things like $this->redirect(array(…)); will not result in a URL without language. This could annoy the user since his URL will have or not the language set after some action. To prevent this is better to have it since first time user set and not change until he sets another language.

    Regards,
    Benito.

  • http://teknoid.wordpress.com teknoid

    @Josenivaldo Benito Junior

    Thanks for sharing.

  • http://www.benito.qsl.br Josenivaldo Benito Junior

    @teknoid

    I still have a doubt I could not solve yet. Is not necessary to use L10n component like cookbook suggests? What would be expected??

    Regards,
    Benito

  • http://teknoid.wordpress.com teknoid

    @Josenivaldo Benito Junior

    No, not for this case.
    It works perfectly well for me, without the use of the component.

  • http://www.benito.qsl.br Josenivaldo Benito Junior

    @teknoid

    Yes, yes, for me it works perfectly well too!

    I am just curious because virtually all I18n articles for CakePHP (and even CookBook) mention the need of L10n component but just set the session “Config.lang” or configure “Config.language ” do the job with no pain. I am wondering what is the difference between use or not the L10n since, as per my understand, the unique function it has is to get locale messages for a given valid locale (what, per my understand, is happening with your simple, few lines, solution).

    Regards,

  • http://teknoid.wordpress.com teknoid

    @Josenivaldo Benito Junior

    L10n will let you to set locale manually amongst other things, but I don’t see any need to use it, when (as you’ve mentioned) the setting can be obtained from Config and Session.

  • ivan

    thanks for this post teknoid, I found t to be very helpful.

    I just had a problem with the login form not working anymore with the Auth component, but I fixed it adding the language param to loginAction.

    $this->Auth->loginAction = array( ‘controller’=>’users’,
    ‘action’=>’login’,
    ‘language’=>$this->Session->read(‘Config.language’));

    I also overloaded the flash function in app_controller so I can keep the language param (the same Josenivaldo did with the redirect function)

    function flash($message, $url, $pause = 1) {
    if (!isset($url[‘language’]) && $this->Session->check(‘Config.language’)) {
    $url[‘language’] = $this->Session->read(‘Config.language’);
    }
    parent::flash($message,$url,$pause);
    }

    Thanks again for this wonderful post.

    • jardos

      @ivan

      thanks for sharing also

    • http://iopener.ca Josh

      Brilliant! I was having this problem with my login action too! One thing I’ve noticed is that after the login action redirects, it seems to strip out the langauge again–the session value is still there, but the redircted URL and all the links on the page don’t have the language prefix anymore. Any suggestions where I can start to look for what might be causing this?

  • http://teknoid.wordpress.com teknoid

    @ivan

    Great tips, thanks for sharing.
    I’m glad you’ve found it helpful to get started.

  • http://www.frankdegraaf.net Phally

    Isn’t it easier to get the language always out the url instead of Cookies/Sessions? If no language was found, redirect to the same page, but with the language in the url. It is not that hard to do, I’ve done it myself. Takes care of duplicate content too.

  • http://teknoid.wordpress.com teknoid

    @Phally

    If I went to http://www.example.com, switched to my native language, it is best to store my preference in the cookie. Because when I come back the next day to http://www.example.com, there is no language param in the URL to extract the language setting from.
    The thing is the language parm may not always be available, so using session/cookie is a perfect fall-back and improves usability as well as SEO.

  • Robin

    Hi teknoid,

    thanks for this pretty code.
    it works great with my “normal” pages.

    Im trying to combine this language switching with multiple pagination (http://debuggable.com/posts/how-to-have-multiple-paginated-widgets-on-the-same-page-with-cakephp:48ad241e-b018-4532-a748-0ec74834cda3) but i go in circle…

    i always get the problem that my paginator links look like this:
    /controller/action/params/page:2/language:deu/model:Project

    and not like desired:
    /deu/controller/action/params/page:2/model:Project

    I think this depends on some function that needs to be overridden in the app_controler, possibly right? But i think the paginate function would get its url from $this->params …i am helpless :/

    hopefully,

    Robin

  • http://teknoid.wordpress.com teknoid

    @Robin

    Have you tried asking at the google group or IRC? I see where the problem is, but don’t have a solution off top of my head.
    I am actually using AJAX-based pagination for this project and do not seem to have a similar issue.

  • لوگوس

    A truly great article. All comments are also very insightful. Perhaps I should combine all these into a tutorial and post it on my blog in my native language (Persian) or even put an English translation out there for others to use. Are you OK with that ?

  • http://teknoid.wordpress.com teknoid

    @لوگوس

    Absolutely, no problem.

    And I’m glad you’ve enjoyed it.

  • kettle

    Hey Teknoid:

    Once again you’ve come thru in a huge way!

    Running into a small issue with the routing though. Seems like I lose url parameters when I try using $html->link(‘eng’, array(‘language’ => ‘eng’))

    So if I’m on this page:
    /rus/posts/view/3

    I end up on
    /eng/posts/view

    after clicking the link above. Would be great to be able to just drop this in my default layout and not have to worry about passing those params.

    Either way, thanks again for this super clear and helpful tutorial I wish I found months ago!

    • kettle

      Argh, sorry, see others have already posted that question (thought I checked thoroughly)! Disregard my post.

      Thanks!

  • http://teknoid.wordpress.com teknoid

    @kettle

    Thanks. Glad you got it figured out.

  • David

    Thanks for the tips, you just resolved me a big trouble :D

  • http://teknoid.wordpress.com teknoid

    @David.

    Good to hear ;)

  • kicaj

    Maybe You know, why when use admin routing cake switch me to default language, and i can’t switch to other languages?

  • http://teknoid.wordpress.com teknoid

    @kicaj

    I do use this approach with admin routing. Not sure what the problem is exactly.

  • kicaj

    Hmm, now I use [named][language] and work perfect

  • http://teknoid.wordpress.com teknoid

    @kicaj

    Good… good… ;)

  • http://n/a Oscar

    Great tutorial!
    As many other people I was having a hard time understanding the whole multilanguage thing!
    Thanks to you I finally achieved a working multilingual site.
    great work!

  • http://teknoid.wordpress.com teknoid

    @Oscar

    Thanks. Glad to hear it was helpful.

  • http://www.nikoz.org NiKoZ

    Thank you!! Thank you!!
    Very useful tutorial (and a lot of suggestions in comments too!!!)

    But… what about POST data?
    if I load a page trough a form using POST method and then I click on the language link to switch lang i’ll get the right page with “no data to search ” message.
    Is there any trick to bring all “$this->data” back??

    Thanks again !!

  • http://teknoid.wordpress.com teknoid

    @NiKoZ

    The only way to do so, is to store the data in the session… and read it back after redirect.

  • tobi_one

    Great post and nice approach!
    Unfortunately I don’t get part of its to work – especially overriding the url method does not work here.

    Two things: the !isset($url[‘language’]) is never true, because when there is no language set, $url[‘language’] is not empty but ‘\’ instead

    If I then append the language with $url[‘language’] = $this->params[‘language’]; the URL is broken, e.g. started out with ‘/vouchers/index’, add language ‘eng’ -> url is ‘evouchers/index’ and not ‘/eng/vouchers/index’

    I have no clue where this comes from! Any ideas?

    Cheers,
    tobi_one

  • Steven Wright

    Hi Teknoid,
    thanks for the article. I am getting ready to implement internationalization into my application. I am confused about one thing though. All the examples (not just yours) show that the views are written in English and wrapped with _(). Would it not be better to have a central file with all your system’s messaging?

    For instance if I need to show the string “Thanks for your submission” I would not want to have to write it out several times throughout my views. It makes it harder to make changes and keep messaging consistent.

    Do you achieve this by creating a .po file for the base language? Hmm, the more I write the more I realize that I am quite unclear on the strategy I need.

    Any advice would be appreciated.
    Thanks.
    Steve

  • http://teknoid.wordpress.com teknoid

    @Steven Wright

    I am not aware of global setting for something like this. Not to say that there isn’t one, I just haven’t come across it.
    Perhaps you could check with the google group or IRC.

    … or maybe someone will chime in with a response here. At any rate, it seems reasonable to have something like this available.

    Oh, and if you do find out… please be kind to share :)

    • Steven Wright

      I think I am completely ass backwards actually. I believe what I have to do is use the language files correctly to achieve this.

  • tobi_one

    Hi,

    I still have the problem that by just adding $url[‘language’] the URL is broken. I used this workaround:

    $urlRoute = Router::parse($url);
    $urlRoute[‘named’][‘language’] = $this->params[‘language’];

    if (!empty($urlRoute[‘controller’])){
    $url = array_merge(array_intersect_key($urlRoute, array_flip(array(‘admin’, ‘controller’, ‘action’))), $urlRoute[‘pass’], $urlRoute[‘named’]);
    }

    I also override the redirect function as Josenivaldo Benito Junior pointed out. I have the same problem with named parameters showing up as /language:eng instead of /eng/… as Robin, but can live with that.

    But one more thing I’m wondering about: how do you handle forms? With all the submit buttons the language parameter gets lost currently at my site. Any ideas?

    Cheers,
    tobi_one

  • http://teknoid.wordpress.com teknoid

    @tobi_one

    Thanks for sharing your solution.

    I’ve had forms on this particular app working without any problem. Nothing, that I did not write about here. I am wondering if some things have changed in the core since the writing of this article, but since I really haven’t worked on this project in a while, it’s pretty hard to guesstimate what could be the issue.

    If by any chance you find a solution, do share as it might help others. Thanks :)

  • tobi_one

    What could work with the forms as a global solution is to redeclare the “create” method of the form helper.

    Or as a quick work around I added the language parameter to all form creation calls like this, e.g.:

    $form->create(‘User’, array(‘action’ => ‘login’,’url’ => array(‘language’ => $html->getLanguage())));

    with $html->getLanguage() being a custom defined helper to return the current language string.

    Cheers,
    tobi_one

  • http://teknoid.wordpress.com teknoid

    @tobi_one

    I’ve just tested again, and the form URL has the parameter passed-in as expected.
    If I use either:

    $form->create();

    or

    $form->create(‘Article’, array(‘action’ => ‘test’));

    The language param appears as expected:

    … action=”/eng/articles/test”

    Perhaps there is a problem, with url() override, in your app. I can’t imagine what else it could be.

  • http://www.yellowdesign.tv Peter

    Hi,

    Brilliant tutorial thanks a lot!!!

    I would like to use this but also route all controllers to the one controller.

    Any suggestions? Something along the lines of.

    Router::connect(‘/*’, array(‘controller’ => ‘home’, ‘action’ => ‘index’));

    Router::connect(‘/:language/:controller/:action/*’,
    array(),
    array(‘language’ => ‘[a-z]{2}’));

  • http://teknoid.wordpress.com teknoid

    @Peter

    I’m probably way off, but why would you want to route all controllers to some single one?

    … and thanks ;)

  • http://www.yellowdesign.tv Peter

    I am creating a dynamic website that the user can manage through our custom content management system. So the user can add new pages and the navigation and content will be dynamic from the database including the language. I could do it through parameters but I want the url to look like a new page (Controller).

    This is what I have come up with so far, but if you have a better solution I would appreciate it.

    Router::connect(‘/:language/*’,
    array(‘controller’ => ‘home’, ‘action’ => ‘index’),
    array(‘language’ => ‘[a-z]{2}’));

    Router::connect(‘/*’, array(‘controller’ => ‘home’, ‘action’ => ‘index’));

    Then I would use the $this->params[‘url’] and split it based on the ‘/’ to get the page the user is looking to access from the database.

    Thanks for your help.
    Peter

  • http://teknoid.wordpress.com teknoid

    @Peter

    Seems like a clean enough approach. I don’t know if I can offer anything better off top of my head…

  • Otto

    I ran into a lot of problems with this approach.

    1) If you have the auth component in use and a users/login action you get in some trouble. I still don’t have any good solution.
    2)you need to modify the url method in your app_helper
    3)you need to modify the rediret method in your app_controller
    4) you need to modify the router::url method. I use router::url e.g. in the activation mail, to avoid hardcoded urls.

    But the login and logout actions are really confusing. Does anyone have a good solution for that. It seems that the auth redirects just dont really work with the language param

    Otto

  • http://exutoire.nicolasgagnon.com Nicolas

    Hi teknoid and thanks for a great article!

    Some people mentioned overloading the app_controller with redirect and flash functions, does that mean that each time a redirect/flash will occur, those functions will be executed instead of the ones in cake/libs/controller/controller.php?

    I just want to make sure I understand to help me debug my app.

    Thanks in advance!

    Nicolas

  • http://exutoire.nicolasgagnon.com Nicolas

    Me again

    Actually, what I want to know is not if they will be executed, but instead if they will be executed first.

    Sorry about that!

  • http://teknoid.wordpress.com teknoid

    @Nicolas

    Since AppController extends Controller any method with the same name as in the parent Controller, will be overridden. If you need to use some functionality from the parent method as well, be sure to use parent::whateverMethodNameYouOverride()

    • http://exutoire.nicolasgagnon.com Nicolas

      Thanks for the clarification!

  • http://teknoid.wordpress.com teknoid

    @Otto

    1. I use Auth in the app as well, no problems here.
    2. Yes, that’s how it is meant to work…
    3. Why?
    4. Use FULL_BASE_URL or the relevant method to the get full URL, instead of Router::url()

    • Otto

      Hi teknoid,

      1) so you’re lucky. If i try to login using http://www.example.com/eng/login it won’t work. Only works if I use http://www.example.com/login (i have some routes changed…)
      2) yep, just to mention :-)
      3) tried again, works now without modifying redirect
      4) I use $html->url() now, with the second parameter set to “true”. Then $html->url() returns the full address like http://www.example.com/whatever. $html->url() is already modified, so this is perfect.

      5) The links that are actually switching the language:

      You use: link(‘Русский’, array(‘language’=>’rus’)); ?>

      But this won’t work, if you have some parameters in the url like here:

      http://www.example.com/eng/profiles/view/123

      If you have link(‘Русский’, array(‘language’=>’rus’)); ?> on top of your site, then this links to:

      http://www.example.com/rus/profiles/view

      So the parameter is missing.

      I changed the way the language switching links work like that:

      $u=array(‘language’=>’rus’);

      foreach($this->params[‘pass’] as $p){
      $u[]=$p;
      }

      echo $html->link(‘Русский’, $u);

      Is there a better solution than this? Would be great, looks ugly.

      By the way. If I forgot to mention: This tutorial is great. Thanks a lot.

      Otto

  • http://www.fc.com.gt Leonel Galan

    teknoid, thanks for this great post. Everything is working as expected. (BTW I tested on the last two stable releases 1.2.5 and 1.2.4.8284)

    I’m using also the Translate behavior (to translate content using the i28n table). The translate behavior needed the language to be written on the Config.language so I added

    – Configure::write(‘Config.language’, $this->Cookie->read(‘lang’));
    – Configure::write(‘Config.language’, $this->params[‘language’]);

    to the if..else statement on the _setLanguage() method.

    Without this two lines, the translation using the po files was done but the content stay on the language readed from Configure::read(‘Config.language’).

    Thanks again for your help, hope this helps others using the Translate Behavior.

  • http://teknoid.wordpress.com teknoid

    @Leonel Galan

    Excellent, thank you for sharing.

  • http://www.fc.com.gt Leonel Galan

    @Kim Biesbjerg

    You wrote: “This is not good for SEO as you might get penalized for duplicate content. What you should do is you should check in beforeFilter if supplied url language param equals the default language set in core.php, and if it does do a 301 (permanent) redirect to domain.com/controller/action (See, the language param has been removed)”

    How can I use the Controller::redirect method to perform this redirection?

  • http://teknoid.wordpress.com teknoid

    @Leonel Galan

    The second param of the redirect() method allows you to set the HTTP header status, please check the API for more detail.

  • http://www.fc.com.gt Leonel Galan

    @teknoid

    Thanks for pointing the API, however I’m not sure what to send on the first parameter $url (mixed) to remove the language from the current URL assuming I’m on the beforeFilter method.

    API: http://api.cakephp.org/class/controller#method-Controllerredirect
    BOOK: http://book.cakephp.org/view/425/redirect

  • http://teknoid.wordpress.com teknoid

    @Leonel Galan

    Something like ‘language’ => false, should probably take care of removing the param. It should be similar to the way you handle ‘admin’ or any other prefix.

  • http://www.fc.com.gt Leonel Galan

    @teknoid

    Thanks again for your help, CakePHP amaze me every day, using: $this->redirect(array(‘language’ => false) + $this->params[‘pass’], 301); did the trick. I used the ‘pass’ to keep the last part of the URL, as others pointed out.

    I’m having ‘trouble’ when using CakePHP admin routes. I put a link on my default layout to change language to english: link(‘English’, array(‘language’=>’eng’) + $this->params[‘pass’]); ?> and it works well on any other pages, but in admin pages the link returned is http://localhost/fc.com.gt/admin/tags/index/language:eng and not http://localhost/fc.com.gt/eng/admin/tags/index as expected.

    I’m new to CakePHP but suspect it has something to do with the “Router::connect(‘/:language/:controller/:action/*’, array(), array(‘language’ => ‘[a-z]{3}’));” not considering the admin part.

  • Nicolas

    Hi again,

    I’ m trying to figure how to redo some of my routes, that were using paramaters, with the language parameter, maybe you will have a solution.

    I used this route before:

    Router::connect(‘/bands/:id/:suffixe’, array(‘controller’=>’bands’, ‘action’=>’detail’), array(‘id’=>$ID, ‘suffixe’=>'[a-zA-Z0-9_-]+’));

    I thought that I just had to add the language parameter like this:
    Router::connect(‘/:language/:controller/:action/*’, array(), array(‘language’ => ‘fr|en’));

    Router::connect(‘/:language/bands/:id/:suffixe’, array(‘controller’=>’bands’, ‘action’=>’detail’), array(‘id’=>$ID, ‘suffixe’=>'[a-zA-Z0-9_-]+’, ‘language’=>’fr|en’));

    The link is ok

    localhost/en/bands/1/band_name

    But when I click it, cake return an error because it sees the ID 1 as the controller action instead of “detail” that was declared in my route…

    Any idea where I’m doing something wrong?

  • http://www.fc.com.gt Leonel Galan

    @Nicolas, please post all the routes defined on routes.php. I had a similar problem (read my post above and the solution below)

    @teknoid
    Hey teknoid I found a solution for the problem I post before. I found the solution on a ticket on cakephp site: https://trac.cakephp.org/ticket/6173.

    I had to add this Router:connect, BEFORE the existing one.

    Router::connect( ‘/:language/admin/:controller/:action/*’,
    array(‘admin’ => true, ‘prefix’ => ‘admin’),
    array(‘language’ => ‘[a-z]{3}’));

    Router::connect( ‘/:language/:controller/:action/*’,
    array(),
    array(‘language’ => ‘[a-z]{3}’));

    If the route was added after, admin was sought as a controller and the controller as the action. With this I’m almost finish with my complete i18n and l10n website with: URL translation, layout translation, content translation, language in URLs (thanks to teknoid), and slugs on each language.

    • Nicolas

      @Leonel Galan

      Placing my route before

      Router::connect( ‘/:language/:controller/:action/*’, array(), array(’language’ => ‘[a-z]{3}’));

      as you did worked for me!!

      Thanks for the tip :)

    • Nicolas

      It seems that every custom routes, for the routing to working correctly, has to be put before this one :

      Router::connect( ‘/:language/:controller/:action/*’,
      array(),
      array(’language’ => ‘[a-z]{3}’));

      • http://www.fc.com.gt Leonel Galan

        I’m not sure about that affirmation. For me, it looks like Cake evaluates routes in order, and routes need to be defined from specific to general.

        I’m glad it worked!

  • http://teknoid.wordpress.com teknoid

    @Leonel Galan

    Thanks for your help and sharing your solutions.

    Teamwork at its best! :)

    @Nicolas

    Glad you were able to solve it, surely it will help others in the future.

  • Steave

    Hey teknoid
    I used plugins in my application. URL for plugins is “/pluginname/controllername/action”
    Router::connect( ‘/:language/:controller/:action/*’, array(), array(’language’ => ‘[a-z]{3}’)); don’t work for plugins.
    Router::connect( ‘/:language/:plugin/:controller/:action/*’, array(), array(’language’ => ‘[a-z]{3}’)); don’t work.
    Any idea where I’m doing something wrong?

    • Steave

      Sorry,
      I found solution.
      I had to add this Router:connect, in this sequence:
      Router::connect( ‘/:language/:controller/:action/*’,
      array(‘plugin’ => null),
      array(’language’ => ‘[a-z]{3}’));
      Router::connect( ‘/:language/:plugin/:controller/:action/*’,
      array(),
      array(’language’ => ‘[a-z]{3}’));

  • http://teknoid.wordpress.com teknoid

    @Steave

    Cool. Thanks for sharing.

  • http://www.sundaypublishing.com toby1kenobi

    Hey, great article, just what I was looking for! One thing, I’m not seeing where the value in core.php is used, am I overlooking something (probably obvious)?

    Toby

  • http://teknoid.wordpress.com teknoid

    @toby1kenobi

    I guess, the core sets the default value for the language at “start-up”… to be honest with you I haven’t bothered to examine the code. (It ain’t broke… don’t fix it). But, I’m sure someone has a better answer.

  • Pingback: CakePHP - PHP Framework, който е точно за теб()

  • sbefort

    Do you use the same models for both English and Russian or do you create separate tables for each language? When a user tries to register and doesn’t pass the model validation, I am wondering how to display different error messages in either English or Chinese.

  • http://www.sundaypublishing.com toby1kenobi

    I do this by overriding the model’s constructor, as described in the sixth post here:

    http://groups.google.com/group/cake-php/browse_thread/thread/fa32c81acc043eef/6a49c6d9fdc6f158?lnk=st&q=#6a49c6d9fdc6f158

    Hope this helps.

  • http://teknoid.wordpress.com teknoid

    @sbefort

    The validation messages (at least in 1.2) need to be translated in the view. So use the __() function and create appropriate translations in the “po” files.

    There is also a translatable behavior, but that is used for translating content from the DB.

    This is not the cleanest approach, and AFAIK, it is being reworked in future versions of cake.

  • http://teknoid.wordpress.com teknoid

    @toby1kenobi

    Interesting approach as well. Thank you for sharing.

  • Mathias

    Hey,

    I enjoyed reading your article. Everything sounded very promising. But after the first steps I just got frustrated.
    I apply the route given above: Router::connect(‘/:language/:controller/:action/*’, array(), array(‘language’ => ‘[a-z]{3}’));
    I reload the page (http://localhost/) and get this error:
    Missing Controller

    Error: Controller could not be found.

    Error: Create the class Controller below in file: app/controllers/controller.php

    When I use this URL ‘http://localhost/eng/pages/display/home’ it works. Am I missing something?

  • Mathias

    Ok, whatever it was, it’s gone. I just did a fresh start. But I still have an issue. The application seems to ignore the app_helper. Do I have to declare it somewhere?
    It doesn’t put the language in front of the other stuff.
    I kinda worked around it with link(__(‘Start’, true), array(‘language’ => Configure::read(‘Config.language’))); ?>

    And here is another thing. The base path ‘/’ is connected to the pages controller and displays the home.ctp. After changing to a different language the “Home-Link” isn’t for example http://localhost/deu/
    it’s http://localhost/deu/pages/display which doesn’t look pretty at all. Any hints how I can fix this?

  • http://teknoid.wordpress.com teknoid

    @Mathias

    1. No, app helper should be found if you named everything correctly.

    2. Haven’t seen this problem, not sure if your routes are setup properly, if so the URL should be parsed by router and displayed without /pages/display.

  • Pingback: CakePHP1.2を使った多言語サポート対応時の覚書き | 無限なる開発ブログ()

  • Tadas

    OMG this post is a live saver!! this post and some of its comments should be in book.cakephp.org its very very good! Thank you very much!

  • http://teknoid.wordpress.com teknoid

    @Tadas

    Thanks, I’m glad it was helpful. You are also welcome to make the addition to the manual ;)

  • http://www.wlab.ca Sebastien G.

    for the duplicate content : I just did this…

    /* app_controller */
    function _setLanguage(){
    if ($this->Cookie->read(‘lang’) && !$this->Session->check(‘Config.language’)) {
    $this->Session->write(‘Config.language’, $this->Cookie->read(‘lang’));
    $this->L10n->get($this->Cookie->read(‘lang’));
    }elseif(isset($this->params[‘language’]) && ($this->params[‘language’] != $this->Session->read(‘Config.language’))) {
    $this->Session->write(‘Config.language’, $this->params[‘language’]);
    $this->Cookie->write(‘lang’, $this->params[‘language’], null, ’20 days’);
    $this->L10n->get($this->params[‘language’]);
    }else{
    $this->params[‘language’] = Configure::read(‘Config.language’);
    }
    }

    /* routes */

    Router::connect(‘/’,array(‘controller’=>’not_logged’,’action’=>’index’));
    Router::connect(‘/:language’,array(‘controller’=>’not_logged’,’action’=>’index’),array(‘language’ => ‘[a-z]{3}’));
    Router::connect(‘/:language/:controller/:action/*’,array(),array(‘language’ => ‘[a-z]{3}’));

    /* index of the site */

    echo $html->meta(
    ‘canonical’,
    $html->url(‘/’.Configure::read(‘Config.language’)),array(),false
    );

    (for canonical link)

  • http://teknoid.wordpress.com teknoid

    @Sebastien G.

    Thanks for sharing, good info.

  • CA

    hi eb,

    i did everything as mentioned in this article.
    but my language links show as http://localhost/controller/action/language:eng
    instead of http://localhost/eng/controller/action/

    where might i have missed ?? please help

  • http://teknoid.wordpress.com teknoid

    @CA

    Most likely in the routes, but it’s hard to guess.

  • CA

    i just download the fresh copy of cake and everything works pretty fine now .. .
    thanks for the article .. :)

  • http://www.gbuvdx.qsl.br Josenivaldo Benito

    Hi Teknoid,

    I just want to share the change a made. I use your procedure for a long time now and need to implement dynamic content translate for some models. During my research I found the article “i18n in CakePHP 1.2 – database content translation, Part 2″ (http://www.palivoda.eu/2008/04/i18n-in-cakephp-12-database-content-translation-part-2/#comment-1283) from Rostislav and decided to follow his approach for dynamic data i18n.

    However, I lately realized the method beforeFilter (where _setLanguage is originally called) is executed after models being instantiated. But sadly true, beforeFilter is the only callback where all things are already done for use (components, models, etc) and we cannot execute _setLanguage properly from class constructor method. The issue is that models have their locale property ($this->Model->locale) set to default language and after that beforeFilter sets the language chosen by user. The result is no dynamic translation.

    I temporally workaround it by parsing the $this->param[‘language’] property and using it to set L10n locale at constructClasses method from app_controller. This ensures that every language passed as a get parameter in URL will be used as locale to models. Another way I foreseen to it is to use loadModels function and do same approach in it but this will be called as many times as your controller have models to attach so I prefered to use constructClasses at app_controller.php which is called once. It is yet necessary to use some mechanism to check available language and prevent user from passing a non-available language to the system which could cause query errors.

    I am still not entirely comfortable with this solution and need to understand better the problem to be confident on it or find another way. Thus I am wide open for suggestions and comments.

    Thanks and regards,
    Benito

  • http://teknoid.wordpress.com teknoid

    @Josenivaldo Benito

    Hi, thank you for sharing your solutions. This is an important part of i18n, and I’ll be exploring new approaches for DB content translation in the near future, so it’ll be good to come back to review this in more detail.

  • un_pro

    I am so lost:
    I have done everything in this article and the modifications offered in the comments, but it doesn’t work, and there are things I don’t understand:

    1) What does it mean if the second parameter here is an empty array?
    Router::connect(‘/:language/:controller/:action/*’,array(),array(‘language’ => ‘[a-z]{3}’));

    2) What does this do exactly: array(‘language’=>’fre’)+$this->params[‘pass’]
    Does the plus sign concatenate arrays? It seems to be doing that, but I can’t find this syntax on any php site.

    3) Could someone explain what’s going on in the AppHelper::url function? And what’s the difference between $url[‘language’] and $this->params[‘language’]. Aren’t they the same?

    I am going nuts

  • http://teknoid.wordpress.com teknoid

    @un_pro

    1. Did you look at the API?

    2. Syntax doesn’t seem to be right. I cannot vouch for someone else’s code, there’d be little time for me to do anything if I could test every suggestion :)

    3. It automatically rewrites all your links, so you don’t have to manually specify param in each link. It takes an argument from the URL if one is present.
    Also, please check the API for details on how the method works.

  • aRagnis

    When using redirect, i get urls like /home/index/language:est/ instead of /est/home/index/

    and i have the following overriding in my app_controller.php

    function redirect($url, $status = NULL, $exit = true ) {
    if(!isset($url[‘language’]) && isset($this->params[‘language’])) {
    $url[‘language’] = $this->params[‘language’];
    }
    parent::redirect($url, $status, $exit);
    }

  • fredi

    Thank you very much for this great article. It is the most helpful one out there.

    One question, would it be better if you don’t check if url has language parameter, and prepend it every link?

  • http://teknoid.wordpress.com teknoid

    @fredi

    You are welcome.
    If we don’t have the param in the URL, then what is there to prepend?

    • fredi

      Oh, I got it. Sorry :)

  • http://rynop.com Ryan

    Awesome article. I have a question about view caching that no one has mentioned yet.

    I want to implement view caching for my multi-lingual site. Cake does this based on URL so I really like your prefix implementation. HOWEVER, your implementation makes it so if they are sessioned w/ rus and go to /controller/action it will display in russian right?

    This wont work as desired if view cach is enabled for the /controller/action – as the lang used to cache the page will always be displayed til it expires.

    I’ve been trying to think of ways to make it so omitting the 3 letter lang code will always default to ‘eng’ (and therefor controller/actions w/o lang prefix will display eng view cache). Was thinking of redirecting to /3letterprefix/controller/action if language param does not exist and lang in session is not eng.

    Do you see any problems with this approach? do you not do any view caching in your apps?

  • Daniel P

    what if i use the url helper, for images? arent they gonna break?

  • http://teknoid.wordpress.com teknoid

    @Daniel P

    Have you tried?

  • fralik

    @Robin

    I had the same problem. Add ‘?’ attribute to your paginator ‘url':
    $paginator->options[‘url’][‘?’] = ‘?name1=value1&name2=value2′;

    @teknoid

    Thank you for the great article!

  • http://teknoid.wordpress.com teknoid

    @fralik

    Glad it helped… and thanks for your suggestion.

  • http://www.hooktstudios.com Jimmy Bourassa

    Hey Teknoid,

    Do you have a fix for prefix routing and the :language ‘type’ of parameter in CakePHP 1.3? The route specified in comment #2216 by Leonel Galan does not work with 1.3

    Thanks,

  • http://teknoid.wordpress.com teknoid

    @Jimmy Bourassa

    I haven’t done this in 1.3 yet.
    But I will post updates once I do.

  • ion

    @Josenivaldo Benito

    Could you share your solution??

    I have the same scenario with Josenivaldo Benito. I’m using the i18n component from here (http://www.palivoda.eu/2008/04/i18n-in-cakephp-12-database-content-translation-part-2/#comment-1283) to have dynamic content translated.

    Great article by the way.!!

  • Josenivaldo Benito

    @ion

    Hi, yes I can share it, however I am in a business trip in South Korea. For a Brazilian guy this means the other side of the world. My personal computer is in Brazil and now I only have the company computer with limited access to my stuff. I will be back to Brazil in next week probably becoming online again at April 3 or 4.

    Is it ok to you?

    Regards,
    Benito

  • ion

    @Josenivaldo Benito

    Actually if you could describe the procedure that would be great. Unless it is a solution with a lot of different places to put code.
    What I’ve found is that there is a function in the i18n behavior called _getLocale which if
    I manage to make it listen to the params[‘language’] or the Cookie or the Session I should get the desired result.
    However I still don’t manage to change the language. I think it is mainly because I have set it up in bootstrap.php (DEFAULT_LANGUAGE). The Config.language paramater does not do anything …

    Thank for the reply anyway
    ion

  • Josenivaldo Benito

    @ion

    To be honesty I can’ t remember :()

    I remember I did something but can’ t remember what. Frankly speaking I even can’t remember just out of my head if I get a final solution or just did a workaround. Really need to deeply dive into that code again.

    My job is related with mobile communication development, not directly related with web design/development. Now I only do some pretty ugly stuff just for fun in my spare time (which is getting rare day to day – sad!). The translation (static and dynamic) was implemented for local HAM club contest I am part of. We need a simple web system to disclose the stations preparing to participate in our contest (annually conducted). I keep a darn simple system up for 4 years now and about 3 years ago I have rewrote it using CakePHP. My intention was to keep it even simple for further updates and new modules development. One year and half things grew up due to Argentina coming to compose our organization and now we have an international contest. That setup the need for two languages living together, portuguese and spanish. At that time I found this article and later final last year I needed to ad the dynamic translation finding for Rostislav’s solution. To summarize things: from last year until now I did not took a look to that source code and many things became fog in my head.

    Sorry, I will not be able to help you before getting in touch with my personal computer/source code and verify what I did. What I can see from the website is that I can setup the language but it stills have problem in showing the language at first access to the first page. I will try to access the provider management area this night from hotel, inside company it is strictly prohibited by the firewall.

    Regards,
    Benito.

  • ion

    @Josenivaldo Benito

    Thank you Josenivaldo very much for your prompt reply. I am so deeply involved with the issue at the moment that I’m getting results every 30′ !!!
    I must say it has been a pain in the ass for me to get both static and dynamic content translation with cakephp. The system is great, even its core localization functions work great out of the box however things get really messy when dealing with dynamic content from a database and really really messy when it comes to HABTM relationships between the models. That was the main reason why I switched to the i18n component and forgot about the core translate behavior in cakephp.
    Right now I’ve managed to switch between languages for both dynamic and static content with a few tweaks but still I get loads of errors when changing urls….
    I’ve been struggling for more than a year on and off with this thing and I hope this one is the last one …

    Anyways thank for your reply
    Regards
    Ion

    • Josenivaldo Benito

      @ion

      Just checking, when did you downloaded the i18n component code? I have exchanged some e-mails with Rostislav (the author) and he made some code improvements making it work correct with containable, etc. If you don’ t have the most recent version it may worthy try update. For sure it will not solve all your issues but it is better to have a new code right?

      Late I send you updates if I can access my provider management area

      • ion

        @Josenivaldo Benito

        Well I got the code a week ago. I’ll download it again. Rostislav has done a great job keeping up with his posts.

        Thanks
        ion

  • Maurits

    I implemented your code and changed:

    $this->Cookie->write(‘lang’, $this->params[‘language’], null, ’20 days’);

    to:

    $this->Cookie->write(‘lang’, $this->params[‘language’], false, ’20 days’);

    Because the third argument is the encryption key (or false) and null will be seen as the encryption key.

  • http://teknoid.wordpress.com teknoid

    @Maurits

    Thanks for bringing this up, there’s a good chance that the API had changed slightly since the writing of this article. (I’ve updated the code to reflect the change).

  • www.phpfly.net hidensoft

    for cakephp 1.3 using this for set language

    function _setLanguage() {
    @$lang = $this->params['named']['language'];
    if ($this->Cookie->read('lang') && !$this->Session->check('Config.language')) {
    $this->Session->write('Config.language', $this->Cookie->read('lang'));
    }
    else if (isset($lang) && ($lang != $this->Session->read('Config.language'))) {
    $this->Session->write('Config.language', $lang);
    $this->Cookie->write('lang', $lang, false, '20 days');
    print($lang);
    }

    }

  • http://teknoid.wordpress.com teknoid

    @hidensoft

    Thanks for sharing.

  • Chris

    For any one struggling to use the Cake Core TranslateBehaviour with multilingual database content and the language is not being set correctly. Try altering the _getLocale cake/libs/models/behaviours/translate.php as follows. (cakephp 1.3)

      function _getLocale(&$model) {

        if (!isset($model->locale) || is_null($model->locale)) {
          if(!class_exists('CakeSession')) {
            App::import('Component', 'CakeSession');
          }
          $CakeSession =& new CakeSession;
          if($CakeSession->check('Config.language')) {
            $this->locale = $CakeSession->read('Config.language');;
          } else {
            $this->locale = Configure::read('Config.language');
          }
          if (!class_exists('I18n')) {
            App::import('Core', 'i18n');
          }
          $I18n =& I18n::getInstance();
          $I18n->l10n->get($this->locale);
          $model->locale = $I18n->l10n->locale;
        }

        return $model->locale;
      }
  • http://teknoid.wordpress.com teknoid

    @Chris

    Thank you for sharing. One thing to note, is that instead of making modifications directly in the core you can move the behavior to your app, and, if you submitted a ticket, wait for the fix ;)

  • Pingback: Internacionalización (i18n) basada en URL para KumbiaPHP « Matando tigres()

  • Pingback: CakePHP Multilanguage เตรียมพร้อมให้เว็บหลายภาษา : : Press@ninenik.com()

  • Kennedy

    Hi.

    I made multi language web site using beforeFilter() as mentioned in this page. In beforeFilter(), I set language name on a variable to use it as a part of class name of CSS at view files.

    However, it seems beforeFilter() is not called when 404. So I think language should set somewhere else that is called always?

  • http://www.web-resources.eu Web Resources.eu

    Bravo !

    This article is realy perfect and what I realy need for my site. I like to use it for a large scale application of my own !

    Bravo again !

  • teknoid

    @Web Resources.eu

    Glad to help ;)

  • ahmad

    Good post and nice way to handle this.
    thanks :)

  • teknoid

    @ahmad

    Good to hear it helped ;)

  • solitud

    Hi, I use some cached elements in my webpage. These elements do a requestAction to a controller. Problem: the language parameter is not submitted when I use requestAction, I have to manually attach them , e.g.:
    echo $this->requestAction(
    array(‘controller’ => ‘charts’, ‘action’ => ‘index’),
    array(
    ‘return’,
    ‘language’ => $this->params[‘language’]));

    Can I overwrite the requestAction function somewhere so that the language param is included everytime?
    Next thing: did someone solve the problem of caching elements with the core cachehelper regarding to their language?
    Thanx solitud

  • Mostafa

    hi,
    i tried out your code in my project but i always have errors , could you send to my email an example files

    Thanks

  • teknoid

    @Mostafa

    Sorry, I cannot share this code.

  • Pepe

    Sorry if this was asked before, I don’t read all the comments ;).
    Following the code on the original post imagine this situation:
    – A user change the language to russian.
    – Some days after he opens the web, the session doesn’t exists but the cookie does, so the language of the cookie is chossen.
    – What if that user has used an url with another language inside, I think it’s desirable to change the language following the url, but the cookie of the last lenguage prevails.

    It works that way or I missed something?.
    Thanks!

    • http://iopener.ca Josh

      I had this problem when I was using $this->params[‘named’][‘lanuage’] instead of just $this->params[‘language’] inside the _setLanguage() method.

      I don’t really have the difference figured out yet, and the latter one works, so that’s the one I’m using :)

  • teknoid

    @Pepe

    URL always takes precedence.
    It’s been a while since I wrote this, so many changes have happened in cake and URL/router handling… but the basic approach still works AFAIK.

  • http://www.spletna-asistenca.si Grega

    Hey!

    I have other question.

    I have page created with php, and all controlers are named in english. I would like to have urls translated to other language.

    For example:

    http://www.site.com/user/register
    insted that i would like to have
    http://www.site.com/uporabnik/registracija

    How could I do that in any other way then changing all the script..?

  • teknoid

    @Grega

    The one way to accomplish this is to create routes with aliases to “Non-English-named” controllers/actions. It would be a sad and daunting task and personally I can give you probably just as many examples as you can to me of international sites using English written URL’s (to me it seems to be the deafacto standard in web development).

  • Rajender120

    @teknoid

    I am new to cakephp and I really loved your blog. It is really very very helpful though I could not understand couple of things.
    Moreover, your post is old, could you please do a similar post with the latest version of cakephp and using the latest technology.
    I implemented it and it worked fine but am not sure if it is very efficient for my cakephp 1.3.6 or if there is any better way available these days. One more concern I have is of app_helper class. Even if I dont include it, everything works good for me.
    Sorry for my shallow knowledge on this topic.
    Waiting for your reply.
    Please help me.

  • teknoid

    @Rajender120

    Sorry, but I am little busy with work to be updating all posts based on the latest cake versions ;)

    Architecturally 1.2 and 1.3 are quite similar… and all-in-all we are talking about 30 lines of code or so. If you feel that something can be adjusted or improved look at test cases and API. Nothing is secret, reading the code is the best way to learn.

  • http://tronprog.blogspot.com/ Lorenzo

    anyone solved the caching issues?
    I’d like to use internalization and make it work together with caching.
    thanks in advance
    Lore

  • teknoid

    @Lorenzo

    What caching issue?

  • m16u31

    hi I have this problem, I have this url

    http://localhost/caketra/articles/view/1

    but when I switch to english or spanish version, I lost the id of my article
    http://localhost/caketra/eng/articles/view
    http://localhost/caketra/spa/articles/view

    i use the same route
    Router::connect(‘/:language/:controller/:action/*’,
    array(),
    array(‘language’ => ‘[a-z]{3}’));

    can you help me??

    by the way, nice article
    pd. sorry my english isnt good

  • Pingback: CakePHP URL-based language switching for i18n and l10n (internationalization and localization) | nuts and bolts of cakephp - dhansson - dhansson()

  • CakeBaker

    Do not forget to also override the link() function:

    class AppHelper extends Helper {

    function url($url = null, $full = false) {
    if(!isset($url[‘language’]) && isset($this->params[‘language’])) {
    $url[‘language’] = $this->params[‘language’];
    }
    return parent::url($url, $full);
    }

    function link($title, $url = null, $options = array(), $confirmMessage = false) {
    if(!isset($url[‘language’]) && isset($this->params[‘language’])) {
    $url[‘language’] = $this->params[‘language’];
    }
    return parent::link($title, $url, $options, $confirmMessage);
    }

    }

  • Pingback: CakePhp localization | SeekPHP.com()

  • J4N

    Hi,

    Thank you very much for this article, it helps me a lot.

    But I have a problem with it: every time my controllers are using named parameters, the language value comes with the named structure instead of the nice url form.

    Like, instead of having

    http://website/eng/products/listing/categorie1:412/categorie2:213/categorie3:11/
    I got this kind of urls: http://website/products/listingcategorie1:412/categorie2:213/categorie3:11/language:eng

    which is really not what I want.

    Any idea about how to avoid this behavior? I suppose this is a routing problem, but I don’t know how to deal with it.

    Thank you very much

  • Pingback: CakePhp: Language based internationalization | Gravity Layouts()

  • Pingback: CakePhp: Language based internationalization()

  • Fouz

    Ya this tutorial is very nice for 2 yrs web developer understand the concept very well, thank x for the post dude !!!!

  • Sylwia

    small change for cake 1.3
    in helper.

    var $helpers = array(‘Session’);

    public function url($url = null, $full = false) {
    if(!isset($url[‘language’]) && isset($this->params[‘language’])) {
    $url[‘language’] = $this->params[‘language’];
    }else if(!isset($url[‘language’]) && $this->Session->check(‘Config.language’)){
    $url[‘language’] = $this->Session->read(‘Config.language’);
    }
    return parent::url($url, $full);
    }

    in routes.php

    Router::connect(‘/’, array(‘controller’ => ‘pages’, ‘action’ => ‘display’, ‘home’));
    Router::connect(‘/pages/*’, array(‘controller’ => ‘pages’, ‘action’ => ‘display’));
    Router::connect(‘/:language/’, array(‘controller’ => ‘pages’, ‘action’ => ‘display’, ‘home’), array(‘language’ => ‘[a-z]{3}’));
    Router::connect(‘/:language/pages/*’, array(‘controller’ => ‘pages’, ‘action’ => ‘display’), array(‘language’ => ‘[a-z]{3}’));
    Router::connect(‘/:language/:controller/:action/*’, array(), array(‘language’ => ‘[a-z]{2}’));

  • anthony lim

    Hi,

    This solution works perfectly in 1.3x
    But when i try to migrate to 2.1x, it seems not working well when user try to go to other pages after they changed the language.

    Anyone can help?

    Thanks in advance.

    Regards,
    Anthony lim

  • teknoid

    @anthony lim
    I have not worked closed with 2.1 to give you specific answer.
    Some of the things have changed in cake since the writing of this… so while the overall concept remains very similar there are definite API changes to consider.

    • http://theworkroom.co Dee Wilcox

      I’m with @anthony — trying to use this in Cake 2.1.x, but the links aren’t firing. I have debug set to 2 in core.php, and no errors. Can you suggest any specific API changes I should check? This is a fantastic solution and absolutely what I am trying to accomplish. If you have a chance to look into compatibility with 2.x, I’m sure other (as well as myself) would really appreciate it.

  • anthony lim

    hi teknoid,

    Ok. :(

    Thanks.

    Reagrds,
    Anthony lim

  • http://dcslab.net Radekk

    Thanks a lot for your work, it saved a lot of time for me.
    to avoid language routing in admin section added to routing this:

    public function redirect($url, $status = null, $exit = true) {
    if(isset($this->params[‘prefix’])&&$this->params[‘prefix’]==’admin’){
    parent::redirect($url, $status, $exit);
    }
    else{
    $url[‘language’]=$this->Session->read(‘Config.language’);
    }
    parent::redirect($url, $status, $exit);
    }

    I’m nube.

    Thanks a lot
    Большое спасибо. (in Russian)

  • http://www.flixya.com/blog/4412038/Chocolate-Chip-Muffin Dominic Flinton

    I like the helpful information you provide in your articles. I will bookmark your weblog and check again here regularly. I am quite certain I’ll learn many new stuff right here! Good luck for the next!

  • Pingback: Internationalization and Localization in CakePHP 2.1 | PHP Developer Resource()

  • Radekk

    Hi Teknoid,
    i met an issue with function url(), my site is located in root/dev and all links that are generated by cake looks like mysite.net/dev/eng/controller/action, is it possible to override the cake’s default url() to avoid “/dev” in links????

  • teknoid

    @Radekk

    Yes, it is shown in the post and you have quite a few examples in the comments as well.

  • Pingback: Multiple languages in a CakePHP 2.* application in 5 steps - Colorblind Programming()

  • http://colorblindprogramming.com Dorin M.

    Just posting a solution for Cake 2.2 based on your version:
    http://colorblindprogramming.com/multiple-languages-in-a-cakephp-2-application-in-5-steps
    Thanks for sharing all the details about i18m and authentication mechanism. Very useful!

  • teknoid

    @Dorin M.

    Thanks, I’ve added your link to the post ;)

  • Mr. T

    Hi,

    I am having same kind of problems as m16u31. Is there any kind of solution for it or am I missing something?

  • Pingback: Multiple language CakePHP menggunakan Localization | Putri handayani's Blog()

  • Pingback: CakePHP get current URL without language prefix | BlogoSfera()

  • Pingback: CakePhp localization - PHP Solutions - Developers Q & A()