views:

324

answers:

7

I am doing a PHP web site, without using any framework. I need that the site is available in several languages, and I was reading about it and it seems to be a little bit confusing. There are several solutions but all seem to depend on a specific framework.

What you think of using a simple translation function like the one shown below?

I mean, I would like to know what can be a disadvantage of using such code. Here it is (this is just a simple and incomplete sample):

class Translator{

    private $translations;

    public function __construct(){
        $this->translations = array(
            'Inbox'  => array(
                'en' => 'Inbox',
                'fr' => 'the french word for this'
            ),
            'Messages' => array(
                'en' => 'Messages',
                'fr' => 'the french word for this'
            )
            //And so on...
        );
    }

    public function translate($word,$lang){
        echo $this->translations[$word][$lang];
    }
}
+6  A: 

It does not look bad. I've seen this used many times.

I would however separate the different strings in one file per language. At least, or if the files get large, one file per module per language.

Then your translation class can load and cache the language files (if you don't rely on any other caching system) every time a new language is to be used.

A little example of what i mean

class Translator {
    private $lang = array();
    private function findString($str,$lang) {
        if (array_key_exists($str, $this->lang[$lang])) {
            return $this->lang[$lang][$str];
        }
        return $str;
    }
    private function splitStrings($str) {
        return explode('=',trim($str));
    }
    public function __($str,$lang) {
        if (!array_key_exists($lang, $this->lang)) {
            if (file_exists($lang.'.txt')) {
                $strings = array_map(array($this,'splitStrings'),file($lang.'.txt'));
                foreach ($strings as $k => $v) {
                    $this->lang[$lang][$v[0]] = $v[1];
                }
                return $this->findString($str, $lang);
            }
            else {
                return $str;
            }
        }
        else {
            return $this->findString($str, $lang);
        }
    }
}

This will look for .txt files named after the language having entries such as this

Foo=FOO
Bar=BAR

It always falls back to the original string in case it does not find any translation.

It's a very simple example. But there is nothing wrong in my opinion with doing this by yourself if you have no need for a bigger framework.

To use it in a much simpler way you can always do this and create a file called 'EN_Example.txt'

class Example extends Translator {
    private $lang = 'EN';
    private $package = 'Example';
    public function __($str) {
        return parent::__($str, $this->lang . '_' . $this->package);
    }
}

Sometimes you wish to translate strings that contain variables. One such approach is this which i find simple enough to use from time to time.

// Translate string "Fox=FOX %s %s"
$e = new Example();
// Translated string with substituted arguments
$s = printf($e->__('Fox'),'arg 1','arg 2');

To further integrate variable substitution the printf functionality can be put inside the __() function like this

public function __() {
    if (func_num_args() < 1) {
        return false;
    }
    $args = func_get_args();
    $str = array_shift($args);
    if (count($args)) {
        return vsprintf(parent::__($str, $this->lang . '_' . $this->package),$args);
    }
    else {
        return parent::__($str, $this->lang . '_' . $this->package);
    }
}
Peter Lindqvist
A: 

It's fine to not use a framework. The only problem I see with your function is that it's loading a lot of data into memory. I would recommend having arrays for each language, that way you would only need to load the language that is being used.

Scott
I am sorry my lack of experience but how can I manage different arrays from one class ? Thanks
Sandro
@Sandro, you would want to have a switch case or if-else block on the language which would run an `include` for the language file for a given language. This way you could store language files separately.
cballou
+1  A: 

The advantage with using a class or functions for this is that you can change the storage of the languages as the project grows. If you only have a few strings, there is absolutely no problems with your solution.

If you have a lot of strings it could take time, memory and harddrive resources to load the language arrays on all page loads. Then you probably want to split it up to different files, or maybe even use a database backend. If using i database, consider using caching (for example memcached) so you don't need to query the database hundreds of times with every page load.

You can also check out gettext which uses precompiled language files which are really fast.

Emil Vikström
+1  A: 

There are a few things it appears you haven't considered:

  • Are you simply translating single words? What about sentence structure and syntax that differs between languages?
  • What do you do when a word or sentence hasn't been translated into a language yet?
  • Does your translations support variables? The order of words in a sentence can differ in different languages, and if you have a variable it usually won't be good enough simply to split the word around the sentence.

There are a two solutions that I've used and would recommend for PHP:

  • gettext - well supported in multiple languages
  • intsmarty - based on Smarty templates
Vegard Larsen
This seems like less of a "translation" scenario, and more of a "alternative language implementation pack" scenario, at which point it's all just a bunch of predetermined strings on the backend. Syntax concerns should then be irrelevant.
Dereleased
+1  A: 

I'd have thought it might be easier to simply use an include for each language, the contents of which could simply be a list of defines.

By doing this, you'd avoid both the overhead of including all the language data and the overhead of calling your 'translate' function on a regular basis.

Then again, this approach will limit things in terms of future flexability. (This may not be a factor though.)

middaparka
didnt think about that! that seems even simpler! And that seems to solve the problems mentioned before... thanks
Sandro
It's by no means a perfect solution, but it's probably about as simple as it can get. Just remember - UTF8 is your friend. :-)
middaparka
And define is your enemy... :-)
Peter Lindqvist
I've worked with translation done with defines before, take a look at osCommerce or any derivative thereof. This system breaks down fairly quick.
Peter Lindqvist
@Peter Lindqvist. Ugh - you said the 'o' word. (I shudder at the past memory.) As I said, it's by no means a perfect solution. It is however simple and may suffice for a small project.
middaparka
@middaparka Yeah, you're right. But i would simply refuse based on previous experience. Sometimes the simple methods are best, but sometimes they aren't sufficient enough.
Peter Lindqvist
A: 

When I had a problem like this (but for a very small site, just a few pages) a long time ago, I created a file named langpack.php and any string of text on my site had to be run through that. Now, I would use a similar approach, but split over multiple files.

Example OOP Approach

langpack.php

abstract class langpack {
    public static $language = array();

    public static get($n) {
        return isset(self::$language[$n]) ? self::$language[$n] : null;
    }
}

english.php

final class English extends langpack {
    public static $language = array(
        'inbox' => 'Inbox',
        'messages' => 'Messages',
        'downloadError' => 'There was an error downloading your files',
    );
}

french.php

final class French extends langpack {
    public static $language = array(
        'inbox' => 'Inbioux',
        'messages' => 'Omelette du Fromage',
        'downloadError' => 'C\'est la vie',
    );
}

You should get the idea from there. Implement an autoloader in a config file and then loading the language should be something you could easily do from the session, URL, or whatever, by using PHP's variable nature in conjunction with class instantiation, something like this:

$langpack = new $_SESSION['language'];
echo $langpack::get('inbox');

Of course, all this could be done with simple arrays, and accessed in an imperative style (with absolute references handled via $GLOBALS) to reduce some overhead and perhaps even make the mechanisms by which this is all handled a bit more transparent, but hey, that wouldn't be very OO, would it?

Dereleased
__get is a performance killer!! I used to do something like that for my config class, but found it was a lot faster to just go through the $data array and make all the variables public
AntonioCS
Also! From the manual Note: Properties cannot be declared final, only classes and methods may be declared as final.
AntonioCS
Hah, I made two mistakes in the original version; correct both, and removed the magic function. Also, performance killer is a bit of an overstatement, unless you're running on a box which measures performances in individual MHz.
Dereleased
It is indeed a performance killer. I profiled some web site I did and noticed that the access time, using the __get method, was very very high when compared with the normal access
AntonioCS
Dereleased
A: 

Is using constants (defines) a bad practice?

That's how I have it setup. It was just to have multi langua support.

I have one portuguese file and an english files filled with:

define('CONST','Meaning');

Maybe this is a bit a memory hog, but I can access from every where I want :)

I may change to a oop approach, but for now I have this.

AntonioCS
Yes, that is a bad approach. Try to define a translation for the word TRUE or GROUPING. These already exist. You want to prevent name clashes. One choice would be to prefix all constants, but you can never be sure a third party lib does not define the same constant. As a rule of thumb: place as little as possible in the global scope. Capsule what belongs together. See http://www.php.net/manual/en/reserved.constants.php and http://www.php.net/manual/en/userlandnaming.php.
Gordon