views:

369

answers:

2

I'm building a website that needs to support different language translations. I have strings in PHP, JavaScript and Smarty Template files that need to translated.

I want to use something like PHP's gettext() function and have a single language file for each locale.

This is easy when the translatable strings are in the PHP files but I also have text in the Smarty Templates and JavaScript files that also need to be translated.

I really want one single file that holds all the translatable strings.


Update:

I'm wanting to use gettext() but the real issue I have is that it only works with PHP and not JavaScript as the JavaScript is client-side.

+1  A: 

All the web apps I've built support 3 languages, and are designed for more. I store all the texts in the database, and call them Constants.

create table constants (
id integer not null,
fk_constant_group integer not null 
)

create table constants_group (
id integer not null,
name varchar(32)
)

create table languages (
id integer not null,
name varchar(32),
accronym varchar(3),
/*other fields if necessary*/
)

create table constants_value (
id integer not null,
fk_constant integer,
fk_language integer,
value text
)

The constant group is used to group them by module/page. If you are going to display a page you are going to need all the constants, so you use a single query to get all the data in the needed language for one page. An index on the group name would be appropriate. In PHP I wrote something like this:

public static function SetNames()
    {
        $info=debug_backtrace(true);
        $result=self::GetNames($info);
        Factory::getSmarty()->assign("names",$result);
        return $result;
    }

public static function GetNames($info=false) /*$info is the debug_backtrace result*/
    {
        if(!$info)
            $info=debug_backtrace(true);
        $class=$info[1]['class']; /*It's the page name basically*/
        if(isset(self::$m_names[$class]))
            return self::$m_names[$class]; /*Using a static member for caching*/
        global $application;
        $langId=$application->langId;
        $constants=AppFac::getAdo()->GetAll("SELECT 
    X.name as constant_name, 
    XV.value as constant_value 
FROM 
    constants X 
LEFT JOIN constants_values XV ON XV.fk_constant=X.id 
LEFT JOIN constants_groups XG ON X.fk_constant_group=XG.id
WHERE
    XG.name=?
    AND XV.fk_language=?",array($class,$langId)); /*Parametrized query*/
        $result=array();
        foreach($constants as $constant) /*Make constants easily accessible*/
            $result[$constant['constant_name']]=$constant['constant_value'];
        self::$m_names[$class]=$result;
        return $result;
    }

From the PHP module, before fetching or displaying the template, call the SetNames() method, which will automatically determine the name of the class you are calling from, and will use this name to find the constant group and fill in the constants in the language set in the session. After that use the constants from the template like this {$names.label_name} for example, if you are looking for label_name constants for your page.

This way is good because: 1. You can add multiple languages right from the Web interface you build for your app. 2. You can easily organize your texts when they are stored in the database. 3. You can easily add and edit texts right from the interface you build for your app.

If you are looking to translate whole pages instead of constants, you might use Smarty Resources, when you just include a resource in the template, and in PHP you define how to handle that resource. http://www.smarty.net/manual/en/template.resources.php

Alexander
The biggest hurdle is getting the language constants in JavaScript. I can already do most of above using gettext() and .po files.
Camsoft
This is in no way more difficult to use than gettext(). Instead of calling gettext(), you access the array. The same way you build your JS. PHP even has a function that encodes arrays into JSON, ready to use in your JS, so it's even faster and easier than gettext().http://www.php.net/manual/en/function.json-encode.php
Alexander
+2  A: 

You can store your strings in CSV files like so:

"Welcome","Welcome"
"Total Price","Total Price"

Make one file for each language. The first value is the key you'll be using to fetch the string and the second the translation. You can then build a class with functions that load the appropriate file, stores the variables in an array and translate text on request. For example:

protected function loadTranslation($locale) {
    $this->_data=array();
    $localeFilePath="locale/" . $locale . "/whatever.csv";
    if(file_exists($localeFilePath)){
        $localeFileHandle = fopen($localeFilePath, "r");
        while (($line = fgetcsv($localeFileHandle, 1000)) !== FALSE) {
            $this->_data[$line[0]]=$line[1];
        }
        fclose($localeFileHandle);
    }
    return $this;
}

for loading the values and

   public function __($label) {
        $output=$this->_data[$label];
        if(empty($output)) $output=$label;
        return $output;
    }

for translating. You can then, for example, use the following to get the proper string:

echo $this->__('Total Price')

The advantage of using CSVs is that you don't need to bother with the database and of course they can be edited via Excel which will come in real handy when you have to tell some data-entry drone to do a translation.

Furthermore you can write a small script to find strings used by the __() function in your code and then write the appropriate strings in the CSV files so you don't have to waste your time appending strings to each and every CSV file for every string you use in your code.

As for Smarty templates and Javascript files, you'll also have to take your translated strings from PHP. Smarty values come from PHP so use your translator before you assign a value to Smarty. As for javascript, it'll have to be dynamically created by PHP so you can set the translated strings in the appropriate places.

Manos Dilaverakis