Localisation d'un soft: Solutions techniques

Dans le cadre d’un site web: templates HTML: j’ai un template par langue, et à part le code qui utilise les templates, j’ai aucune référence dans mon code à quoi que ce soit comme texte. L’avantage des templates HTML c’est qu’un infographiste sans trop de connaissances arrive à modifier les templates avec un logiciel du genre DreamWeaver.

Dans le cadre d’un logiciel, j’ai jamais fait, mais je pense que ca passerait par un fichier de ressources externes, éditable à la main (ou avec un logiciel spécialisé).

Salut,

Pour être exact, plutôt quelque chose comme ça :
echo Lang::get(‹ page.title ›);
Car le numéro on sait pas trop à quoi il correspond.

Moi j’utilise plusieurs fichiers XML.
Comme ça c’est plus léger, et on s’y retrouve très vite.

[quote]$xml = simplexml_load_file(‹ ./languages/ ›.$user->GetLanguage().‹ .xml ›);

echo $xml->banner;

echo $xml->accueil->welcome;

etc, etc, …[/quote]
Je vois 2 problèmes plus ou moins importants :

  • Tu vas parser ton fichier à chaque page (c’est donc plus lent qu’en générant un cache).
  • Tu n’as aucun contrôle sur les chaînes renvoyées (on pourrait avoir besoin de changer le charset utilisé par exemple). Ou alors il va falloir le faire à chaque fois. Tu auras surement une fonction spéciale pour ça d’ailleurs.

C’est pour ces deux raisons que j’utilise une classe :

[code]class Lang
{
   private $data;

   public static function load($file)
   {
       if( ($data = Cache::getVar($file)) === false)
       {
           // ici on parse le fichier XML, car les données ne sont pas en cache
           $xml = simplexml_load_file($file);
           …
           Cache::setVar($file);
        }
        self::$data = &$data;
   }

   public static function get($key)
   {
       return fonction1(fonction2(fonction3(self::$data[$key])));
   }
}

Lang::load(‹ lang/fr/home.xml ›);

echo Lang::get(‹ page.welcome ›);[/code]
Voila le principe.
C’est comme ça que je fais.

Si vous voulez savoir ce qu on utilise sur un site web qui fait 200000 requete/heure/serveur, c’est ca.

Et on utilise avec une syntaxe du style
GetResourceValue("/myPage_myTextKey"), genre /home_introText.

On stock ca dans un xml, et les equipes de localisation ont des outils pour travailer directement avec ces fichiers. Apres vous pouvez avoir le meme site qui tourne en plusieurs langages, le ‘resource manager’ va choper le texte dans la version ‘compilee’ du bon fichier XML, selon la langue du browser.

[quote name=‹ KiniK › date=’ 1 Jul 2005, 18:22’]Et on utilise avec une syntaxe du style
GetResourceValue(« /myPage_myTextKey »), genre /home_introText.[/quote]
Oui enfin c’est toujours le meme principe: un ID (texte ou numerique) qui est remplace par le vrai texte.

Imaginons un exemple:

ca va donner:

echo GetResourceValue("/home_hello") . $name . GetResourceValue("/home_youare") . $site . GetResourceValue("/home_sitecool_sympa");

(oui je melange tout :stuck_out_tongue: )

et maintenant, le pied de page:

ca va donner:

echo GetResourceValue("/home_youare") . $site . GetResourceValue("/home_sitecool");

Bah deja rien que ca, c’est galere.
En plus, c’est un coup a avoir un meme ID pour plusieurs textes, et c’est bien fun (l’id numerique regle le probleme mais c’est illisible).
Et attention, je dis pas que c’est pas efficace, je dis que c’est pas pratique pour le programmeur

Un vrai bonne solution qui tue ca maman, c’est que l’IDE remplace a la volee les ID numerique/texte par le texte reel qu’on puisse comprendre sans chercher ce que 'ID signifie. Glop? tu nous mets ca dans le prochain VS 2005? :stuck_out_tongue:

LoneWolf
Haha l’idee de folie a faire avant 2 semaines :stuck_out_tongue:

Ha ouais, un truc que j ai oublie de preciser, on met des tokens dans les trucs a localiser.
Genre pour ton exemple: "Salut, bidule, tu es sur %site%, le site qu’il est cool - et c’est sympa d’etre la!"
Parce qu’evidement, selon le langage, la place du/des tokens dans la phrase est completement differente.

Maintenant, ca depend des priorites, certains mettent direct les {0}, {1}… pour faire des string.format en sortie, pour pas avoir a manipuler un tetrachiee de string (mais faut que l’equipe de localisation soit d accord, c’est moins intuitif a traduire que un %site%).

Bref, comme d’hab, c’est un rapport perf/usabilite.

Oui et encore une fois, la localisation c’est pas qu’un remplacement de texte:

  • les dates, la maniere dont elles sont formattees
  • les dates, le calendrier lui meme est pas forcement le meme (et oui…)
  • les chiffres, la maniere dont ils sont presentes (virgules, points, groupement des chiffres, etc)
  • le sens du texte, droite a gauche, haut en bas, etc qui affectent le layout
  • les images avec du texte bien sur
  • les tris par ordre alphabetique (tout tri doit etre localise, en effet en langue machin ou on place le a avec un petit rond dessus est pas pareil que d’autre langues qui ont un a avec un petit rond dessus et c’est qu’un exemple trivial, y a des bizzareries dans plein de cultures)
  • les passage automatiques en majuscule/minuscule doivent etre localises
  • on oublie par les support des charset les plus bizzare, incluant par exemple les surrogate characters en chinois qui foutent en l’air tout compte sur string.Length ou Substring et qui doivent donc etre pris en compte directement dans le code…
  • toute comparaison d’egalitee entre deux chaine localisee doivent bien sur etre aussi localise du coup, et attention aux trous de securite qui peuvent en resulter. C’est le fameux coup du Tukish-I par exemple…

Localiser comme il faut, c’est dur. Tres. Avoir une solution qui est pas integree a la plate forme pour un projet plus gros qu’un petit site alacon™ qui gere deux ou trois langues occidentales, c’est du suicide complet :stuck_out_tongue:

[quote name=‹ LoneWolf › date=’ 1 Jul 2005, 20:03’]Imaginons un exemple:

ca va donner:

echo GetResourceValue("/home_hello") . $name . GetResourceValue("/home_youare") . $site . GetResourceValue("/home_sitecool_sympa");

Dans ce cas là, je mets :

echo sprintf(Lang::get('home.hello'), $name, $site); ou echo sprintf(GetResourceValue('home.hello'), $name, $site)
La chaîne est donc :

Salut, %s, tu es sur %s, le site qu'il est cool - et c'est sympa d'etre la!

Bon après moi je me limite à la traduction (chaînes et dates) et aux charsets, donc la lecture gauche / droite etc … :stuck_out_tongue:

ton système de cache, tu le gere avec quelque chose comme ca : http://be.php.net/manual/fr/ref.memcache.php ?

Je vais regarder à mettre en place ce genre de système. Mon site ne sera pas énormément visité donc le parsage de xml à chaque page n’affectera pas grandement les performances à mon avis mais je préfère faire les choses bien :stuck_out_tongue:

Pour le cache, je procède ainsi :

file_put_contents('cache/lang/fr/monFichier.xml', serialize($data));

Et pour récupérer :

$data = unserialize(file_get_contents('cache/lang/fr/monFichier.xml'));

Si ça intéresse quelqu’un, j’ai aussi fait des tests, qui consistent à stocker le tableau contenant les chaînes et leur clés.

Méthode 1 (on retrouve ça pour les fichiers de langues de phpBB par exemple) :

[code]<?php
$lang = array();
$lang[‹ key1 ›] = ‹ value1 ›;
$lang[‹ key2 ›] = ‹ value2 ›;
$lang[‹ key3 ›] = ‹ value3 ›;

// Pour récupérer dans le script :
include « monFichier »;
// ici on peut accéder aux données du tableau $lang …[/code]

Méthode 2 : plus ou moin la même chose, sauf qu’ici on récupère le résultat de la serialization du tableau (qui a lieu leur de la création donc) :

[code]résultat de serialize()

// Pour récupérer dans le script :
$lang = unserialize(file_get_contents(« monFichier »));[/code]

J’ai donc testé le temps qu’il fallait pour récupérer le tableau contenant les données par la méthode 1, et par la méthode 2.
Les résultats m’ont assez étonné : la méthode 2 est beaucoup plus rapide.
Ainsi en ayant un tableau de 100 chaînes de 10 à 80 caractères, et en faisant 100 itérations pour avoir un résultat plus précis, j’ai obtenu environ 0.25s pour la méthode 2 et 1.5s pour la méthode 1. Sur 1000 itérations, 2s environ pour la méthode 2, et 13 pour la méthode 1.

Mon choix est donc fait, pour les caches ou autre :stuck_out_tongue:
En plus c’est plus pratique de faire des serialize() unserialize() pour le stockage.

[quote name=‹ GloP › date=’ 1 Jul 2005, 22:34’]Oui et encore une fois, la localisation c’est pas qu’un remplacement de texte:

  • les dates, la maniere dont elles sont formattees
  • les dates, le calendrier lui meme est pas forcement le meme (et oui…)
  • les chiffres, la maniere dont ils sont presentes (virgules, points, groupement des chiffres, etc)
  • le sens du texte, droite a gauche, haut en bas, etc qui affectent le layout
  • les images avec du texte bien sur
  • les tris par ordre alphabetique (tout tri doit etre localise, en effet en langue machin ou on place le a avec un petit rond dessus est pas pareil que d’autre langues qui ont un a avec un petit rond dessus et c’est qu’un exemple trivial, y a des bizzareries dans plein de cultures)
  • les passage automatiques en majuscule/minuscule doivent etre localises
  • on oublie par les support des charset les plus bizzare, incluant par exemple les surrogate characters en chinois qui foutent en l’air tout compte sur string.Length ou Substring et qui doivent donc etre pris en compte directement dans le code…
  • toute comparaison d’egalitee entre deux chaine localisee doivent bien sur etre aussi localise du coup, et attention aux trous de securite qui peuvent en resulter. C’est le fameux coup du Tukish-I par exemple…
    Localiser comme il faut, c’est dur. Tres. Avoir une solution qui est pas integree a la plate forme pour un projet plus gros qu’un petit site alacon™ qui gere deux ou trois langues occidentales, c’est du suicide complet :stuck_out_tongue:
    [right][post=« 373770 »]<{POST_SNAPBACK}>[/post][/right][/quote]

Je plussois totalement.
La localisation est clairement un truc hypr dur à faire correctement en php.

A+ FCH

nic58, je viens de me faire un petit test pour un cache et ca a l’air de marcher assez bien :stuck_out_tongue:

j’ai fais ca (code de test, pas à utiliser):

[code]class cache {

public static function SetVar ($key, $var) {

 
 file_put_contents($key.’.cached’, serialize($var));
}

public static function GetVar ($key) {

 
 return unserialize(file_get_contents($key.’.cached’));
}
}[/code]

[code] require_once(‹ config.php ›);
require_once(‹ classes/cache.class.php ›);

function Xml2Php (SimpleXMLElement $xml) {

 
 $result = array();
 
 foreach($xml->children() as $name => $child)
 $result[$name] = Xml2Php($child);
   
 if (count($result) == 0) $result = (string)$xml;
 
 return $result;
 
}

$time1 = microtime(true);

for($i=0; $i<1000; $i++) {

 $test = simplexml_load_file($_SERVER[‹ DOCUMENT_ROOT ›].$GLOBALS[‹ config ›][‹ path ›].’/languages/french/main.xml’);
 echo $test->menu->welcome;
}

$time2 = microtime(true);

$myxml = simplexml_load_file($_SERVER['DOCUMENT_ROOT'].$GLOBALS['config']['path'].'/languages/french/main.xml');
&nbsp;
cache::SetVar('lang_fr', Xml2Php($myxml));

for($i=0; $i<1000; $i++) {

 $test = cache::GetVar(‹ lang_fr ›);
 echo $test[‹ menu ›][‹ welcome ›];
}

$time3 = microtime(true);

echo '<br/>no cache : '.($time2-$time1).'<br/>cache : '.($time3-$time2);[/code]

Ca me donne au final :

no cache : 0.227208971977
cache : 0.0642119646072

Grosso modo 4 fois plus rapide.
Je vais voir si je peux améliorer tout ca et en faire quelque chose d’utilisable :P.

Tiens en parlant de comparaison de chaine et de localisation…

http://msdn.microsoft.com/netframework/def…ingsinNET20.asp

Impeck Asarnil :stuck_out_tongue:

Regardes aussi tu as des requettes assez grosses que tu peux mettre en cache (ça peut être valable si elles ne changent pas à chaque page, et à chaque utilisateur).