Localizing JavaScript Strings in a PHP MVC Framework

Roland Clemenceau

Today, I am going to show you how to efficiently localize your Javascript strings within your PHP Framework. You can download a working implementation of this tutorial here.

There are actually several methods to localize Javascript strings in a PHP environment. Technically speaking it is possible to duplicate your Javascript file, naming it after the target language, and loading the needed version each time the user selects a new language on site. But this surely represents a method that one could hardly call good practice, even though it would ‘work’.

The main disadvantage of using a method like this is that every time you will need to modify your JavaScript code then you will have to perform the modification for each language. This is only prone to error, if not bringing you extra unwanted work.

There is also the possibility to have your literal strings directly called by means of PHP variables embedded within your JavaScript code, but depending on your framework architecture, this is not always an option that’s available.

So I’m going to show you a method that will work for sure and which will be easy to maintain as well.

Remember, you can download a working example right here.

So let’s start…

In the example attached to this tutorial, I have set up a button that triggers a Javascript function called trigger_msg():

echo '<input type="button" value="'.$t_launch_message.'" class="trigger" onclick="return trigger_msg();" />';

The trigger_msg() function is found in /public/JS/main.js :

function trigger_msg(){
    return alert(translate(LOCALIZATION.here_is_how_it_works));

– We call the translate() function found in /languages/translate.js and pass, as a parameter, the name of the element we need that’s contained in the array named LOCALIZATION.
– We’re using the syntax translate(name_of_language_array.name_of_element, extra_parameter1, extra_paramater2, etc…) thus using commas only to separate extra parameters.
– Parameters may of course be literals if surrounded by quotes

Before we take a deeper look at the translate() function, here’s what the LOCALIZATION array found in /languages/current_language/JS/current_language.js looks like:

    here_is_how_it_works :  'Voici comment cela fonctionne.\nAppuyez sur le bouton suivant afin de voir comment ça se passe avec des paramètres.',
    who_does_not_know_are_and_that_the_sky_is :  'Qui ne sait pas que %s x %s font %s,\net que le ciel est %s?',
    blue : 'bleu'

Within our array element definitions you can see ‘%s’ is being used, that is the expression we use to hold our extra parameters. We will come to that a little later though.
Note that you may insert HTML style tags, eg. <b>, etc., within your array element definitions if you use a custom dialog box and it will work nicely.

Now time to concentrate on our translate() function:

(function () {
    if (!window.translate){
      window.translate = function(){
        var html = [ ]; 
        var arguments = arguments;
        var string = arguments[0];

        var objIndex = 0;
        var reg = /%s/;
        var parts = [ ];

        for ( var m = reg.exec(string); m; m = reg.exec(string) ) {  
          // m[0][0] gives undefined in IE
          parts.push(string.substr(0, m[0][0] === "%" ? m.index : m.index));
          string = string.substr( m.index+m[0].length );

        for (var i = 0; i &lt; parts.length; ++i){
            var part = parts[i];
            if (part &amp;&amp; part == "%s"){
              var object = arguments[++objIndex];
              if (object == undefined) {

        return html.join('');

This function below constitutes the core of our JavaScript localization scheme.

Basically in the variable called string we store the arguments captured from the trigger_msg() function, parse them in our first for loop, filter them using a regular expression that’s held in the variable named reg, and push the resulting parts in an array called parts[]. Then we reassemble those parts into an array called html[] that our function returns.

The variable named reg holds a simple regular expression ‘/%s/’, the %s being the syntax we have chosen to use to define our parameters as discussed above.

The trigger_msg_with_params() function in /public/JS/main.js shows just how to use parameters when localizing your strings. Indeed, there are times in a system when a literal string that needs to be translated may contain values which will depend on user input, and this function comes in handy by allowing to not having to re-use our code so much:

function trigger_msg_with_params(){
    var param1 = 5382;
    var param2 = 9408;
    var param3 = param1 * param2;
    var param4 = translate(LOCALIZATION.blue);
    return alert(translate(LOCALIZATION.who_does_not_know_are_and_that_the_sky_is, param1, param2, param3, param4));

You can see each defined parameter, eg. var param1, could well have been a parameter passed to the function call too. Defined parameters can also be actual calls to the translate() function. All of which, again, turns out to be very helpful.

That’s all there is to it. This system represents an efficient and reliable way to translate your JavaScript strings across your PHP Framework and allows for a great deal of suppleness.

You are more than welcome to leave your comments or questions. Stay tuned for more tutorials.

Free JavaScript: Novice to Ninja Sample

Get a free 32-page chapter of JavaScript: Novice to Ninja and receive updates on exclusive offers from SitePoint.

  • Anonymous

    What about gettext and PoEdit, which is a more complete, flexible and standard solution? It seems you may be reinventing the wheel. I have found Jed to work just fine for Javascript code, although the po->json conversion wasn’t perfect.

  • Anonymous

    @arnauvp, the point here being to show how to effectively get this done without using plugins or third part code, for pedagogic purposes only.

  • Anonymous

    The use of object keys with underscores isn’t a natural way to translate. Javascript allows you to use strings as an object key using the array notation.

    Your translate function has nothing to do with the translation, it is the sprintf php function. And it is only the basic implementation.
    .It seems you are not aware of the replace function in javascript? That is why your function has so much code.

    I created an example, http://jsfiddle.net/KqhuU/, to show my view on basic javascript localisation.

    Adding html to a string using a formating function is bad because then you have to add markup in your code. Maintaining that is harder than maintaining html templates.

  • Raphael Yancey

    I’m using Zend Framework and gettext/PoEdit and had the same problem. I found a way to use translations into my scripts by creating a “translations.phtml” view file in each of my controllers view folders. In this file, I set global Javascript vars with the translations that I can use in any .js file across the page.

    It looks like this:
    var addButtonLabel = < ?php echo _("addButtonLabel"); ?>;

    Since Zend renders the view file, it gets the translation and I’m good to go, with a clean and centralized translations file for each of my controllers!

    • Anonymous

      @Raphael Yancey, this might not be the most optimized method, since you have to go to each view folder every time you need to maintain translation, or because of the scope of you translation variables

      • Raphael Yancey

        You can set only one translation file if you prefer, and include it in each of your views (manually or auto). Still, I think it’s important to not centralize everything if you want to keep a hand on it. The problem was that a included JS file from the controller isn’t rendered and, so, isn’t translated if there is translation vars in it. But, it doesn’t really matter since it fits our each needs!

  • Anonymous

    @David Duymelinck, thank you for sharing your own method. Now, while both methods work well, the method i am presenting here was originally intended for a lightweight PHP-MVC framework i have designed for tutorial purposes only.
    Although i may agree this piece code may still be improved in some ways, as all pieces of code do, it still performs its expected task very well and this at a totally acceptable speed, suitable for production.

  • Tomas Mertens
    • Anonymous

      @Tomas Mertens, i’m not familiar with CodeIgniter but thanks for sharing

  • Anonymous

    In my opinion, there’s something not too logical/optimal about having to include the same translation file in each view, in terms of overall project ergonomy, and indeed, using controllers for that purpose won’t work and should not work, as it’s just not meant for that. I’ve never really played with the Zend Framework before, so i can’t tell if the method you use i best, but in any case I would assume centralizing data is a key to more efficient data handling.

  • Anonymous

    Your article has as much to do with php than your translate function with translations. The serverside language doesn’t matter because the code you show is all javascript. It’s an introduction to define the problem you need to solve clientside.

    I didn’t see at first but your localisation variable is not an array but an object. This is an important difference in javascript.

    I disagree with your statement that your method is suitable for production. Calling your translate function even if the string needs no manipulation is just a waste of resources. It’s like calling strtolower on a lowercased constant.

    I think it’s great you want to share your knowledge but share the knowledge you fully understand. I could have ignored your article but that leaves the bad information without correction and that is bad for everyone reading it.

    • Anonymous

      This article is titled localizing javascript strings in a php-mvc-framework environment, not localizing javascript string using php in a php-mvc-environment.

  • Anonymous

    and calling the translate function this way makes sense to me

  • Anonymous

    Passed that you’re entitled to an opinion. Now i do not see the point of dignifying this one here, so i will just leave it where it is.