Traduction et localisation, comment s'y prendre ?

ViPHP
ViPHP | 4674 Messages

04 juil. 2007, 16:48

Bonjour,

je m'attaque à un système de traduction et de localisation (en PHP 5).
Alors, je me pose pas mal de questions sur la méthodologie à adapter.

On a en fait 2 problèmes de méthodologie : (1) la localisation : i10n ; (2) la traduction : l18n.

1. La localisation :

Déjà, est-ce que c'est bien ce que je pense : l10n (au sens strict du terme) ? Je ne pense pas me tromper sur le vocabulaire pour l'instant.
Ensuite, qu'est-ce qu'on doit gérer ? J'aimerais ne rien oublier, car j'en ai mal à la tête juste d'y penser, et me plonger dans le code ne me motive pas plus que ça, alors autant ne rien oublier ;-)
Quand je dis : « Qu'est-ce qu'on doit gérer ? », j'entends, qu'est-ce PHP gère et ne gère pas. Donc qu'est-ce qu'on peut utiliser, et qu'est-ce qu'on doit construire ?

Il y a le célèbre setLocal(), mais quoi d'autres ? J'ai vu qu'on pouvait comparer des chaînes localisées, ça veut dire quoi ?

On va également rencontrer le problème de format de dates. Comment est-ce qu'on peut gérer tout ça ?


2. La traduction :

Je pense que la traduction c'est l'i18n (internationalisation), sauf erreur de ma part.

Là encore, beaucoup de problèmes. J'ai beau retourner le problème dans tous les sens, on ne peut que passer par des fichiers de traductions (chaîne de référence => chaîne traduite). J'ai vu qu'il y a différents formats : Gettext (.mo, binaire), TMX (.tmx, basé sur XML), CSV (.csv, texte) etc.

Qu'est-ce que PHP supporte le mieux ? Qu'est-ce qui le plus rapide ? Je pense que le mieux est de supporter tous les formats, mais je préfère d'abord me concentrer sur un seul.

Gettext me paraît le plus adapter, car le plus utiliser. En revanche, XML est plus adapté pour les humains (nous sommes tous des extra-terrestres sur ce forum ;-) ne l'oublions pas !). Comment exploiter le binaire de Gettext ?

Ne doit-on pas faire un lien en la traduction et l'encodage aussi ? Pour le CMS que je compte préparer, je pensais forcer l'UTF-8. Peut être un petit peu contraignant, mais tellement plus simple ... Si vous avez des suggestions à ce sujet, je suis preneur !



J'ai tellement de questions, que je n'ose pas tout poser. J'ai donc 2 problèmes bien distincts. Je ne sais pas trop comment procéder, vous vous en êtes rendu compte :roll:.
J'espère que ce thread sera être utile à plusieurs personnes.
J'aimerais développer quelques choses de professionnels, donc on oublie le simple tableaux de traductions (même si c'est à envisager), je préfère rester sur des standards.
Peut être qu'il serait bon de faire 2 sujets pour chaque problème. Je ne sais pas.

Bref, vous avez mon avenir entre vos mains ! (bon, j'ai essayé ^^)
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Invité
Invité n'ayant pas de compte PHPfrance

04 juil. 2007, 17:02

Je ne m'exprimerai que sur deux points, premièrement le charset. Je soutiens ta décision de forcer l'UTF-8, c'est en effet bien plus pratique que de s'embêter à gérer tous les charsets possibles. Cependant il faudra que dans la documentation de ton CMS tu fasses une section claire et simple expliquant comment éditer un fichier UTF-8 sans y rajouter le fameux BOM. Quant aux fichiers de langues, un fichier XML à parser à chaque fois me semble un mauvaise solution, mais pourquoi ne pas garder l'idée du XML qui est - en effet - facilement compréhensible par les humains ? Dans ce cas la il suffirait de créer un système de cache. Une fois parsé les phrases seraient par exemple incluses dans un fichier PHP en tant que variables.

HF & GL, Nowan :P

ViPHP
ViPHP | 5924 Messages

04 juil. 2007, 17:04

Pour GetText, tu peux l'exploiter en fournissant les outils nécessaires aux utilisateurs pour l'édition des fichiers en format binaire, soit en intégrant les outils de modification dans ton script, soit en crant des scripts ou même des binaires indépendants.

Administrateur PHPfrance
Administrateur PHPfrance | 3131 Messages

04 juil. 2007, 17:38

Tout d'abord, oui pour l'UTF-8. Il faut ignorer tout autre charset sinon c'est l'enfer. Dès qu'on mélange deux charset c'est atroce à gérer, et dès qu'on sort de l'Unicode on est vite limité. Attention à l'utilisation des fonctions php (par exemple strrev n'existe pas en version mb_string).

Le XML plus lisible pour les humains ? C'est une légende urbaine ça... Je n'ai jamais vu quelqu'un qui soit heureux de lire du XML par rapport à un fichier ini par exemple. Le XML est verbeux et illisible, tu seras de toute façon obligé de te taper une interface d'administration car le fichier sera inutilisable tel quel par un profane.

Personnellement je m'étais fait un système inspiré de gettext mais en plus «transparent» car il ne passe pas par des fichiers binaires à compiler soi-même (ce qui peut être assez pénible) :

- Une constante qui définit la «langue de référence».
- Une variable de session “lang” pour stocker le choix de l'utilisateur.
- Un fichier ##.lng dans un dossier défini qui contient les traductions.
- Une fonction __() (pour ne pas entrer en conflit avec ()).

Au lieu d'affecter des idefiants ts aux chaines (ce que je trouve absolument débile et illisible dans l'appli finale) je fais comme dans gettext : on prend le message dans la langue de référence, et et si l'utilisateur a choisi une autre langue alors on cherche la traduction.
<?php echo __('Hello World') ?>
Le fichier de traduction est d'un format simplissime :
- Le message dans la langue de référence
- Le message traduit en-dessous
Les lignes vides sont ignorées, et on prévoit bien sûr d'ignorer les lignes qui commencent par # (commentaires).
Ce format a l'immense avantage d'être extrêmement lisible, par contre si on zappe une ligne ça décale tout et ça n'a plus aucun sens :( (on n'a rien sans rien)

Exemple de fichier “fr.lng”

Code : Tout sélectionner

Hello World Salut le Monde Hey, who are you ? Hé, t'es qui ? # etc...
Et l'exemple précédent, si $_SESSION['lang'] == 'fr', donnera «Salut le Monde».

Pour des raisons évidentes de performances ce fichier n'est parsé qu'une fois et et transformé en tableau associatif (phase de compilation transparente) qu'on stocke dans un fichier “compiled” sous le dossier contenant les fichier de traduction.

Je me suis même permis la fantaisie de logger toute lacune de traduction (vu que tout passer par la fonction __()) afin d'aider à la complétion de ces fichiers.
Comment ils sont vraiment lisibles par n'importe qui, on on peut les laisser à l'édition directe par le client final, ils s'en sort ;)

J'avais trouvé pas mal d'avantages à ce système, mais il n'est pas «fool-proof» du tout, et si on veut s'assurer de la robustesse, on est bien obligé de passer par une interface d'admin :(

La fonction __ est très simple à écrire :
function __($message, $lang = null) {
  static $translations = array(); // traductions mises en cache dans la fonction (chargé une seule fois en mémoire)
  if (!$lang) { // paramètre par défaut : on prend la langue de l'utilisateur
    $lang = @$_SESSION['lang'];
  }
  if (!isset($translations[$lang])) { // Il faut charger les traductions
    if (!is_file(DIR_LANGS.$lang.'.lng')) { // Le fichier de traduction n'existe pas
      return $message;
    }
    $not_compiled = !is_file(DIR_LANGS_COMPILED.$lang.'.php');
    $compiled_expired = @filemtime(DIR_LANGS_COMPILED.$lang.'.php') < filemtime(DIR_LANGS.$lang.'.lng');
    if ($not_compiled || $compiled_expired) { // le fichier compilé n'existe pas ou le fichier de traduction a été modifié depuis
      compile_lang($lang);
    }
    $translations[$lang] = include DIR_LANGS_COMPILED.$lang.'.php';
  }
  if (!isset($translations[$lang][$message])) { // Le fichier de traduction existe, mais ce message n'est pas traduit
    return $message;
  } else { // on retourne la traduction
    return $translations[$lang][$message];
  }
}
Et la phase de compilation est un simple parsing pour générer un tableau stocké dans un fichier avec var_export :
function compile_lang($lang) {
  $lines = @file(DIR_LANGS.$lang.'.lng');
  if (is_array($lines)) {
    $original_message = null;
    $translations = array();
    foreach ($lines as $line) {
      $line = trim($line);
      if ($line == '' || $line{0} == '#') { // commentaire ou ligne vide
        continue;
      }
      if ($original_message === null) { // il s'agit d'un message à traduire
        $original_message = $line;
      } else { // il s'agit de la traduction du message à traduire précédemment rencontré
        $translations[$original_message] = $line;
      }
    }
    // on a généré le tableau, on stocke le résultat ("compilation")
    $code = '<'.'?php return ' . var_export($translations, true) . '; ?'.'>';
    file_put_contents(DIR_LANGS_COMPILED.$lang.'.php', $code);
  }
}
Je n'ai pas eu de souci particulier de performances, mais ce système n'a jamais été utilisé que sur des intranets (donc assez peu de visites par rapport à un gros site communautaire).
Les projets sur lesquels je bosse actuellement tournent avec Copix, Symfony, et un autre framework de notre boite qui tous trois intègrent leur système de traduction donc je ne vais pas le ressortir avant un petit moment a priori :lol:

Eléphant du PHP | 199 Messages

04 juil. 2007, 18:48

Je ne me suis jamais vraiment penché sur la question donc je ne saurais pas trop te répondre :-/ Cela dit la solution proposée par naholyr me semble intéressante :)
Klomac - Blog Lambda

ViPHP
ViPHP | 4674 Messages

04 juil. 2007, 19:01

Hehe ok merci pour cette grosse contribution, c'est exactement ce que j'attendais.

Alors le système est bon pour la traduction, dans le sens où : oui je suis d'accord de mettre une phrase à la place d'un id, ou tag de références pour chaque traduction. Mais ça pose un problème, on est obligé de taper la phrase avec la même case (minuscule/majuscule), sinon on ne trouvera pas l'entrée dans notre tableau.

Bon, on peut toujours utiliser array_map et faire une comparaison binaire, mais ça risque de prendre un peu de temps :s, ou alors on met tout en minuscule. Comme ça, c'est plus rapide. Je doute que ce soit une bonne idée pour l'utilisateur de bases.

On pourrait partir sur un fichier .ini. Comme il serait interpréter par PHP, ce sera nettement plus rapide. Ensuite, on enregistre le tableau en cache, et plus de soucis.

Je ferais des adaptateurs pour Gettex, TMX etc., après. Je commence sur cette idée.

Maintenant, reste à résoudre le problème de la localisation.

PS : je suis content de voir que mon idée de forcer l'UTF-8 n'est pas aussi farfelue que ça :)
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Mammouth du PHP | 693 Messages

04 juil. 2007, 19:11

Le systeme de langue de Phpbb est un simple tableau associatif. Pour chaque clef on associe une traduction et chaque langue a on fichier propre. Ensuite, lors de la cration de la page, le parser inclut le fichier corespondant à la langue. Ensuire, on y accède comme tout les tableaux.

ViPHP
ViPHP | 4674 Messages

04 juil. 2007, 19:20

Oui mais PHPbb n'est pas une si grosse application que ça.

http://unicode.org/cldr/data/common/sup ... alData.xml ça peut être utile pour les petits curieux :) (je sens que j'ai pas fini de le lire celui là hehe).
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Mammouth du PHP | 693 Messages

04 juil. 2007, 19:51

ok, je retourner faire mes bidouilles sur mon ordi. Moi qui pensait que phpbb était une assez grosse application, vous me dites que c'est pas le cas, je me sens tout de suite... heu... coment dire... campagnard. :D

ViPHP
ViPHP | 4674 Messages

04 juil. 2007, 20:54

Haha :D pardon, faut pas le prendre mal ^^

Je vois plus PHPbb comme un gros module (un forum) que comme un réel programme Web. C'est une application certes, mais on en a d'autre, autrement plus compliquées et plus intéressantes à étudier. Je ne dis pas que PHPbb est de la merde, loin de là, mais c'est mon point de vue :P Coder PHPbb, ça se fait. C'est long (et peut être chiant), mais ça se fait :)

Si tu voyais où j'habite, tu te sentirais moins campagnard ;-)
Modifié en dernier par Hywan le 07 juil. 2007, 18:48, modifié 1 fois.
« Un handicap est le résultat d'une rencontre entre une déficience ou différence et une incapacité de la société à répondre à celle-ci. »

Hoa : http://hoa-project.net (sur @hoaproject).

Mammouth du PHP | 693 Messages

04 juil. 2007, 20:58

Je le prend pas mal du tout.

C'est juste qu'on est pas dans la même cathégorie :P

ViPHP
ViPHP | 5924 Messages

04 juil. 2007, 21:27

Je ne dis pas que PHPbb est de la merde
Moi je le dis :P :mrgreen:

Avatar du membre
Administrateur PHPfrance
Administrateur PHPfrance | 13231 Messages

04 juil. 2007, 21:30

il ne faut pas être aussi catégorique ... ;)

C'est une usine à gaz qui à ses défauts, mais qui rend d'énormes services tout de même.

M'enfin bref, tu as le droit d'avoir tes opinions, c'est normal ;)
Connaître son ignorance est la meilleure part de la connaissance
Pour un code lisible : n'hésitez pas à sauter des lignes et indenter

twitter - site perso - Github - Zend Certified Engineer

Eléphant du PHP | 377 Messages

05 juil. 2007, 00:19

Juste une petite précision : tu fais une erreur au niveau l10n et i18n
l'internationalisation (pfiou...) c'est le fait de "préparer son programme afin qu'il soit traduisible"
Donc, dans la pratique, remplacer "<span>Mon texte en français</span>" par "<span><?=_("Mon texte dans n'importe quelle langue") ?></span>"
Puis, une fois que ceci est fait, passer le tout dans la moulinette de xgettext ou equivalent pour récupérer ton fichier .po

Seconde partie : la localisation. Là, beaucoup moins de problèmes, tu prends ton joli fichier .po que tu envoies à tes gentils traducteurs et qui le traduisent (ils peuvent bien faire ça, tu as fait le plus dur ;))

Sinon, moi j'utilise gettext, je pars du principe que même si il peut paraitre lourd, il a été développé par des gens biens plus au fait de ce genre de choses que moi, et je n'ai aucune intention de réinventer la roue.
En plus il gère un nombre impressionnant de choses auxquelles je n'aurais même jamais pensé tout seul dans mon coin

Enfin, si tu te décides à partir dans cette direction, voici un lien qui m'a été utile
http://www.mandragor.org/tutoriels/gettext/0

Voilou ;)
Petit scarabée deviendra grand

Administrateur PHPfrance
Administrateur PHPfrance | 3131 Messages

05 juil. 2007, 07:41

Mais ça pose un problème, on est obligé de taper la phrase avec la même case (minuscule/majuscule), sinon on ne trouvera pas l'entrée dans notre tableau.
Être sensible à la casse me paraît vital en effet :? On ne peut pas être sûr que la ponctuation ou la gestion de la casse sera la même d'une langue à l'autre, donc il faut rester sensible à la casse. C'est d'ailleurs le cas de gettext et de TMX.
Non la grosse faiblesse de mon système, ou plutôt la grosse force de gettext (je ne connais pas le détail de TMX) c'est la gestion des pluriels. Par exemple en français on dira "0 objets" (on met au pluriel) alors qu'en anglais ce sera "0 object" (on laisse au singulier). Dans un système où on ne peut gérer les pluriels on devra se contenter d'une traduction générique "%u object(s)" => "%u objet(s)", alors que gettext permettra de gérer ça finement (je n'ai plus le détail de la méthode en tête, mais ça se fait dans des directives de config au début du fichier .po).

Si tu tiens à être insensible à la casse, il faut simplement mettre les clés du tableau en basse casse et comparer avec le message en basse casse :
function __($message, $lang = null) {
  $message = mb_strtolower($message, mb_detect_encoding($message));
  ...
}
function compile_lang($lang) {
  ...
        $translations[mb_strtolower($original_message, mb_detect_encoding($original_message))] = $line;
  ...
}
Ainsi s'il y a l'entrée "Object" => "Objet", alors "objeCt" sera aussi traduit par "Objet", mais est-ce bien élégant ? Quant à reporter la casse du message à traduire sur le message traduit, comment le faire alors qu'il n'y aura pas le même nombre de lettres, et pas les mêmes mots dans le même ordre (ni au même nombre non plus) dans le message traduit ?