begin process at 2012 02 15 22:23:40
  Trouver un code source :
 
dans
 
Accueil > 

Code

 > 

Application

 > [PHP5.1] O-LOC : CLASSE ET BACKOFFICE D'INTERNATIONALISATION

[PHP5.1] O-LOC : CLASSE ET BACKOFFICE D'INTERNATIONALISATION


 Information sur la source

Note :
9,5 / 10 - par 2 personnes
9,50 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10
Catégorie :Application Classé sous :multilingue, internationalisation, localization, traductions, translations Niveau :Expert Date de création :02/11/2007 Date de mise à jour :01/01/2008 10:18:49 Vu / téléchargé :8 277 / 494

Auteur : malalam

Ecrire un message privé
Site perso
Commentaire sur cette source (30)
Ajouter un commentaire et/ou une note

 Description

Cliquez pour voir la capture en taille normale
Ce package comprend 2 outils distincts mais liés.
- la classe oLocale permettant de gérer l'affichage des traducstions sur un site multilingue. Elle se base sur un flux XML ayant une structure simple, mais bien précise. Elle a besoin d'une langue demandée, et d'une langue par défaut (dans les deux cas, la classe les fixe à l'anglais si ces 2 paramètres ne sont pas passés), et d'un chemin où aller chercher les fichiers de traduction. Par défaut, le chemin est 'locales/'. La structure de ce répertoire doit être respectée, par contre.
Si elle ne trouve pas une traduction dans la langue demandée, elle affichera automatiquement la traduction dans la langue par défaut. Elle permet aussi de changer de langue (langue par défaut et langue demandée) en cours de script, sur une même page.
Un exemple d'utilisation basique se situe dans le fichier oloc.example.php.
De toute manière, la classe est simple d'utilisation.
Elle ne possède que 2 méthodes appelables : oLocale::getMsg() permettant d'aller chercher une traduction, et oLocale::getLoc() renvoyant la langue courante.
Et 2 propriétés initialisable : oLocale::LOCALE est la langue courante, oLocale::DEFAULT est la langue par défaut.

- une application back-office pour gérer ses traductions. Elle se lance via le fichier back.office.php. Une aide en ligne sommaire mais complète est disponible sur l'interface.
Ce back-office permet de créer des traductions pour autant de sites que vous le voulez, de gérer des modules de traduction, et des constantes de traduction (chaque message, en fait). Et ce, soit manuellement, soit via l'upload d'un fichier respectant le modèle présent dans le répertoire 'templates/'. Il s'agit d'un fichier Excel.
Le back-office ne supporte que ce template, et un process bien défini : il FAUT utiliser ce template, puis il FAUT utiliser OpenOffice (version 2.3 pour moi, à tester avec d'autres versions) pour sauvegarder ce fichier en HTML (et pas XHTML), et c'est ce fichier HTML obtenu que l'on uploadera dans le back-office. J'ai externalisé tout le process de parsing de fichier, ce qui rend "facile" la création d'un nouveau template si vous désirez changer de process, et donc le parsing à appliquer. Il y a 2 classes pour cela : 'class/class.genparser.php' qui ne devrait pas être touchée, elle gère l'upload et diverses petites choses. Et une classe spécialisée 'class/class.openoffice23htmlparser.php' qui étend genparser. Il vous faudra créer la votre si vous voulez changer de modèle.
Le back-office est livré avec un "site" test, contenant 2 modules, et leurs traductions dans un paquet de langues (Bulgare, Grec, Tchèque, anglais, allemand, français etc...). Vous pouvez donc afficher les traductions, les modifier, etc...afin de tester l'application.
Toute l'interface utilise Ajax (upload mis à part) pour une ergonomie plus agréable.

A noter que pour le moment, je n'ai pas réussi à faire tourner cette application (je parle uniquement du back-office là) sur IE. Il n'y a pas grand chose à modifier mais je n'ai pas encore trouvé l'astuce pour faire ça simplement (IE et le DOM, décidément...).
Testé sur Firefox 2, Opera 9, Flock 1.

Voilà, je pense que c'est tout... ;-)



Source

  • <?php
  • /**
  • Web application localization back-office class
  • Copyright (C) 2007 Johan Barbier <johan.barbier@gmail.com>
  • This program is free software; you can redistribute it and/or
  • modify it under the terms of the GNU General Public License
  • as published by the Free Software Foundation; either version 2
  • of the License, or (at your option) any later version.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • You should have received a copy of the GNU General Public License
  • along with this program; if not, write to the Free Software
  • Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  • */
  • /**
  • * @author Johan Barbier <johan.barbier@gmail.com>
  • * @version 20071031
  • * @desc Web application localization back-office class
  • *
  • * F R E N C H :
  • *
  • * PRINCIPE : Ce back-office permet de générer les "locales" nécessaires à une application web localisée. La localisation obtenur se base sur la classe oLocale présente dans le répertoire class/ du back-office. Cette classe, si elle ne trouve pas de traduction pour une langue donnée utilisera la langue fixée en tant que langue par défaut à la place.
  • * Le répertoire de travail des "locales" pour la classe oLocale présente sur un site est "locales/"..
  • * Le répertoire de travail des "locales" pour le back-office est "websites/". La hiérarchie de ce répertoire est la suivante : websites/nom_site_web/locales/code_pays/modulex.xml
  • * Chaque racine de site (websites/nom_site_web/locales/) possède automatiquement un répertoire default (au même niveau que le répertoire code_pays) contenant la définition des modules et des constantes attribuées au module, sous forme de fichier xml. x modules = x fichiers.
  • * C'est ce répertoire "locales/" sous le "site" qu'il faut copier sur le serveur web, sous le site web de production désiré.
  • *
  • *
  • * Pour créer une localisation, il faut commencer par créer un site web (website). Puis le sélectionner dans la liste déroulante des sites web. Toute autre manipulation passe nécessairement par la sélection d'un site web dans la liste déroulante!
  • *
  • * Ensuite, en travaillant MANUELLEMENT :
  • * On y ajoute ensuite une langue. A ce moment là, la langue est présente dans les listes déroulantes des langues disponibles et des langues de référence disponibles.
  • * On y ajoute un module. De même, le module créé est alors disponible dans la liste déroulante des modules disponibles.
  • * On peut alors commencer à travailler :
  • * Pour modifier ou afficher une traduction, il faut absolument sélectionner un module, une langue de référence, et une langue de travail.
  • * Ceci fait, la liste des constantes avec les traductions dans les 2 langues sélectionnées s'affiche.
  • * Un champ de saisie permet de saisir une nouvelle constante. Attention à bien respecter les règles énoncées dans le paragraphe MODULES ci-dessous.
  • * Si la traduction d'une constante pour une des langues n'est pas disponible, s'affiche alors à la place "NO TRANSLATION FOUND".
  • * Les champs de traduction des 2 langues sont modifiables. Toute modification est répercutée sur le "blur" du champ (à la sortie du champ).
  • * NB : Toujours bien penser à vérifier que l'on est sur le bon site avant de faire quoi que ce soit!
  • *
  • * En travaillant par UPLOAD DE FICHIER :
  • * Il faut toujours créer un site, puis le sélectionner.
  • * Ceci fait, il faut prendre le fichier de traduction qui doit etre conforme au modèle présent dans le répertoire template/, et l'ouvrir avec Open Office. Là, l'enregistrer en tant que fichier HTML (et pas XHTML).
  • * Enfin, l'uploader dans le champ consacré du back-office. Le traitement peut-être long selon la taille du fichier.
  • * Une fois que le back-office a fini d'intégtrer ce fichier, il affichera un message "Fichier intégré avec succès".
  • * On peut alors aller afficher les modules, traductions créés.
  • *
  • *
  • * LANGUES : les langues seront préférablement utilisées avec le code ISO 3166-1 alpha 3 du pays. Lors de l'ajout de traductions via le fichier modèle, si les pays ne sont pas renseignés ainsi, la classe va tenter de retrouver ce code via la fonction countries/get.alpha.cn.php. S'il ne le trouve pas, il utilisera le nom présent dans le fichier. Attention à l'encodage en cas de caractères spécieux, dans ce cas! Le mieux étant de vérifier la bonne écriture des pays dans le fichier fourni.
  • *
  • * WEBSITES : un simple nom de répertoire valide suffira à la création d'un "website" dans le back-office
  • *
  • * MODULE : une chaîne alpha normale, sans caractère spéciaux et surtout pas d'underscore.
  • *
  • * CONSTANTES : les constantes doivent être préfixées par le nom du module auquel elles appartiennent, suivi d'un underscore, puis de n'importe quelle chaîne valide pour le nom d'un élément xml (éviter les caractères spéciaux, donc).
  • *
  • *
  • * E N G L I S H :
  • *
  • * PURPOSE : This back-office allows you to generate localizations needed for a web application. The localization will be used by the oLocale class, see the given example on how to use this latter.
  • * Work folder for localization used by oLocale class is 'locales/'.
  • * Work folder for "locales" used by the back-office is 'websites/'. Structure of this folder is as follows : 'websites/website_name/locales/country_code/module_X.xml'
  • * Root of a given website (websites/website_name/locales/) is automatically given a 'default/' folder (same level as the 'country_code/' folders) in which you find the module's and constants' definition as an xml file. X modules = X xml files.
  • * It is THIS 'locales/' folder you'll have to copy to your localized website ('default/' is of no use on your website, however).
  • *
  • *
  • * To create a localization, you must first create a website. Then, you have to select it in the dedicated combo list. Every thing you do NEEDS a website to be selected first.
  • *
  • * Then, if working MANUALLY :
  • * You add a language. Thie language will then be available in the list of available base languages and reference languages.
  • * You add a module. Just as above, the new module will then be available in the list of available modules for this website.
  • * You can now start working :
  • * In order to display, modify or create a given translation, you must select a module, a base language, and a reference language.
  • * Once done, the list of constants and translations is displayed.
  • * An input allows you to create a new constant. Please respect the rules given in the chapter MODULE of this help.
  • * If a translation is not available, 'NO TRANSLATION FOUND' is displayed.
  • * Translations for both selected languages can be modify. Just click on the cell. Every modification is saved on the 'blur' event of the cell (when you click out of the cell).
  • * NOTE: Just be SURE you are on the correct website before doing anything!
  • *
  • * If working by UPLOADING A FILE :
  • * You still have to create your website, and then select it.
  • * Once done, take your translation file which must be compliant to the one given as an example in the 'templates/' folder. It's an Excel file. With this version of the back-office, it is the only available template (you can build your own, though, you'll find everything needed to do so in the 'class/class.genparser.php' and 'class/class.openoffice23htmlparser.php' files). You must open it in OpenOffice 2.3 and save it as an HTML file (I insist : HTML, not XHTML).
  • * Finally, upload this HTML file in the back-office. It can take a while, depending on the size of your file.
  • * When the back-office is done with the file, it will display a message "File successfully processed", so you will know.
  • * You can then display your uploaded translations.
  • *
  • *
  • * LANGUAGES : for languages name, you should use the ISO 3166-1 Alpha3 code, it's a good one! When you add translations via an uploaded file, if languages name does not use this format, the back-office will try to find it. It uses for that what you will find in the 'countries/' folder : an xml file and a function. If it cannot find it, it will use the name found in the file. In that case, be careful about special characters in the name, such as accents...it can be a problem while creating a folder with this name.
  • *
  • * WEBSITES : a simple valid folder name will be ok.
  • *
  • * MODULE : a normal string, again, without any underscores in it!
  • *
  • * CONSTANTES : constants must be prefixed by their module name followed by an underscore (as you will see in the example file in the 'templates/' folder). Then, any xml valid string for tagname.
  • */
  • class backoffice {
  • /**
  • * Local website path
  • *
  • * @var string
  • */
  • private $sPathLocale;
  • /**
  • * Stored available languages
  • *
  • * @var ArrayIterator
  • */
  • private $oStoredLanguages;
  • /**
  • * Stored available modules
  • *
  • * @var ArrayIterator
  • */
  • private $oStoredModules;
  • /**
  • * Stored available websites
  • * Not used at the time. Might be useful later.
  • *
  • * @var ArrayIterator
  • */
  • private $oStoredWebsites;
  • /**
  • * @desc Constructor. Will initialize backoffice::$sPathLocale property
  • *
  • * @param string (optional) $sPathLocale
  • */
  • public function __construct($sPathLocale = null) {
  • $this->oStoredWebsites = new ArrayIterator(self::getWebsites());
  • if(!is_null($sPathLocale)) {
  • $sPathLocale .= '/locales';
  • if(!is_dir(PATH_ROOT.'websites/'.$sPathLocale)) {
  • throw new boException(str_replace('__PATH__', $sPathLocale, boException::_MISSING_LOCALE_PATH_));
  • }
  • $this->sPathLocale = PATH_ROOT.'websites/'.$sPathLocale.'/';
  • if(!is_dir($this->sPathLocale.'default')) {
  • mkdir($this->sPathLocale.'default', 0755);
  • }
  • }
  • }
  • /**
  • * @desc Just creates the folder 'websites' if not present and returns its contents
  • *
  • * @return array : websites available
  • */
  • public static function getWebsites() {
  • if(!is_dir(PATH_ROOT.'websites')) {
  • mkdir(PATH_ROOT.'websites', 0755);
  • }
  • return self::getDir(PATH_ROOT.'websites');
  • }
  • /**
  • * @desc Returns the contents of a directory
  • *
  • * @param string $sDir : directory to list
  • * @return array : contents of the directory
  • */
  • private static function getDir($sDir) {
  • $aDir = scandir($sDir);
  • $aDir = array_diff($aDir, array('.', '..'));
  • return $aDir;
  • }
  • /**
  • * @desc Initializes available modules and languages
  • *
  • * @return void
  • *
  • */
  • public function init() {
  • $this->getLanguages();
  • $this->getModules();
  • }
  • /**
  • * @desc Gets available languages
  • *
  • * @return ArrayIterator
  • */
  • public function getLanguages() {
  • if(is_null($this->sPathLocale)) {
  • return new ArrayIterator();
  • }
  • $aDir = self::getDir($this->sPathLocale);
  • $aStoredLanguages = array();
  • foreach($aDir as $sDir) {
  • if($sDir !== 'default' && is_dir($this->sPathLocale.$sDir)) {
  • $aStoredLanguages[] = $sDir;
  • }
  • }
  • return $this->oStoredLanguages = new ArrayIterator($aStoredLanguages);
  • }
  • /**
  • * @desc Gets available modules
  • *
  • * @return ArrayIterator
  • */
  • public function getModules() {
  • if(is_null($this->sPathLocale)) {
  • return new ArrayIterator();
  • }
  • $aDir = self::getDir($this->sPathLocale.'default');
  • $aStoredModules = array();
  • foreach($aDir as $sDir) {
  • $sDir = substr($sDir, 0, strrpos($sDir, '.'));
  • $aStoredModules[] = $sDir;
  • }
  • return $this->oStoredModules = new ArrayIterator($aStoredModules);
  • }
  • /**
  • * @desc Builder method for translations list
  • *
  • * @param string $sMod : mdoule asked for
  • * @param string $sLng : base language asked for
  • * @param string $sRef : reference language asked for
  • * @return array : the list of translations for this module, and these languages
  • */
  • public function getTranslationsList($sMod = null, $sLng = null, $sRef = null) {
  • if((!$this->oStoredLanguages instanceof ArrayIterator) || (!$this->oStoredModules instanceof ArrayIterator)) {
  • $this->init();
  • }
  • $aMessages = array();
  • $aMessages['REF'] = $this->loopOnParameters($sMod, $sRef);
  • $aMessages['LNG'] = $this->loopOnParameters($sMod, $sLng);
  • return $aMessages;
  • }
  • /**
  • * @desc Loops on constants in module asked for, and languages
  • * I have made available the possibility to retrieve translations for several modules, even if I do not use it in the current backoffice. I have tried, but it is a bit confusing then. Easy to extend, so.
  • *
  • * @param string $sMod : module asked for
  • * @param string $sLng : current languages on which we loop
  • * @return array : list of translations for modules and languages asked for
  • */
  • private function loopOnParameters($sMod, $sLng) {
  • $aMessages = array();
  • if(!is_null($sMod)) {
  • if(!is_null($sLng)) {
  • $aMessages[$sLng] = $this->getTranslationList($sMod, $sLng);
  • } else {
  • foreach($this->oStoredLanguages as $sLng) {
  • $aMessages[$sLng] = $this->getTranslationList($sMod, $sLng);
  • }
  • }
  • } else {
  • foreach($this->oStoredModules as $sMod) {
  • if(!is_null($sLng)) {
  • if(empty($aMessages[$sLng])) {
  • $aMessages[$sLng] = array();
  • }
  • $aMessages[$sLng] = array_merge($aMessages[$sLng], $this->getTranslationList($sMod, $sLng));
  • } else {
  • foreach($this->oStoredLanguages as $sLng) {
  • if(empty($aMessages[$sLng])) {
  • $aMessages[$sLng] = array();
  • }
  • $aMessages[$sLng] = array_merge($aMessages[$sLng], $this->getTranslationList($sMod, $sLng));
  • }
  • }
  • }
  • }
  • return $aMessages;
  • }
  • /**
  • * @desc Actually fetches list of translation, called by backoffice::loopOnParamaters
  • *
  • * @param string $sMod : current module
  • * @param string $sLng : current language
  • * @return array : list of translations for this module and this language
  • */
  • private function getTranslationList ($sMod, $sLng) {
  • $aMessages = array();
  • $oDefaultXml = simplexml_load_file($this->sPathLocale.'default/'.$sMod.'.xml');
  • if(file_exists($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml')) {
  • $oXml = simplexml_load_file($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml');
  • } else {
  • $oXml = null;
  • }
  • foreach ($oDefaultXml as $clef => $val) {
  • if(!empty($oXml->$clef)) {
  • $aMessages[$sMod][$clef] = (string)$oXml->$clef;
  • } else {
  • $aMessages[$sMod][$clef] = '';
  • }
  • }
  • return $aMessages;
  • }
  • /**
  • * @desc : Adds a new module
  • * Please follow documentation
  • *
  • * @param string $sModule : new module name
  • * @param string $sEncoding : new module encoding
  • */
  • public function addModule($sModule, $sEncoding='UTF-8') {
  • if(empty($sModule)) {
  • throw new boException(boException::_EMPTY_PARAMETER_);
  • }
  • if(file_exists($this->sPathLocale.'default/'.$sModule)) {
  • throw new boException(str_replace('__MOD__', $sModule, boException::_MODULE_ALREADY_EXISTS_));
  • }
  • if(false !== strpos($sModule, '_')) {
  • throw new boException(boException::_INVALID_UNDERSCORE_);
  • }
  • $sXml = '<?xml version="1.0" encoding="'.$sEncoding.'"?>
  • <constants>
  • </constants>';
  • file_put_contents($this->sPathLocale.'default/'.$sModule.'.xml', $sXml);
  • }
  • /**
  • * @desc : Adds a new website
  • * Please follow documentation
  • *
  • * @param string $sWebsite : new website name
  • */
  • public function addWebsite($sWebsite) {
  • if(empty($sWebsite)) {
  • throw new boException(boException::_EMPTY_PARAMETER_);
  • }
  • if(file_exists(PATH_ROOT.'websites/'.$sWebsite)) {
  • throw new boException(str_replace('__WEB__', $sWebsite, boException::_WEBSITE_ALREADY_EXISTS_));
  • }
  • mkdir(PATH_ROOT.'websites/'.$sWebsite);
  • mkdir(PATH_ROOT.'websites/'.$sWebsite.'/locales');
  • }
  • /**
  • * @desc : Adds a new language
  • * Please follow documentation
  • *
  • * @param string $sLanguage : new language name (best is alpha2 or alpha3)
  • */
  • public function addLanguage($sLanguage) {
  • if(empty($sLanguage)) {
  • throw new boException(boException::_EMPTY_PARAMETER_);
  • }
  • if(is_dir($this->sPathLocale.$sLanguage)) {
  • throw new boException(str_replace('__LNG__', $sLanguage, boException::_LANGUAGE_ALREADY_EXISTS_));
  • }
  • mkdir($this->sPathLocale.$sLanguage, 0755);
  • if(!$this->oStoredModules instanceof ArrayIterator) {
  • $this->getModules();
  • }
  • $oXml = new DOMDocument;
  • foreach($this->oStoredModules as $sMod) {
  • $oXml->load($this->sPathLocale.'default/'.$sMod.'.xml');
  • $sEncoding = $oXml->encoding;
  • $sXml = '<?xml version="1.0" encoding="'.$sEncoding.'"?>
  • <translations>
  • </translations>';
  • file_put_contents($this->sPathLocale.'/'.$sLanguage.'/'.$sMod.'.xml', $sXml);
  • }
  • }
  • /**
  • * @desc Small method to parse the html elements ID in the list of translations
  • *
  • * @param string $sClef : html element id
  • * @return array : an array with the parsed ID (constant name, language name, and module name)
  • */
  • private function parseInputId($sClef) {
  • $aProps = explode('@', $sClef);
  • $sConst = $aProps[0];
  • $sLng = $aProps[1];
  • $sMod = substr($sConst, 0, strpos($sConst, '_'));
  • return array($sConst, $sLng, $sMod);
  • }
  • /**
  • * @desc Adds a new constant
  • *
  • * @param string $sConst : new conatnts name
  • * @param string $sMod : module owning the new constant
  • */
  • public function addNewConstant($sConst, $sMod) {
  • if(!file_exists($this->sPathLocale.'default/'.$sMod.'.xml')) {
  • throw new boException(str_replace('__MOD__', $sMod, boException::_DEFAULT_MOD_NOT_FOUND_));
  • }
  • if(substr($sConst, 0, strpos($sConst, '_')) !== $sMod) {
  • throw new boException(str_replace('__CONST__', $sConst, boException::_INVALID_CONST_));
  • }
  • $oXml = new DOMDocument();
  • $oXml->load($this->sPathLocale.'default/'.$sMod.'.xml');
  • $root = $oXml->firstChild;
  • $elem = $oXml->createElement($sConst);
  • $root->appendChild($elem);
  • $oXml->save($this->sPathLocale.'default/'.$sMod.'.xml');
  • }
  • /**
  • * @desc Deletes a constant in all languages
  • *
  • * @param string $sConst : constant name
  • * @param string $sMod : module owning constant
  • */
  • public function delConstant($sConst, $sMod) {
  • if(!file_exists($this->sPathLocale.'default/'.$sMod.'.xml')) {
  • throw new boException(str_replace('__MOD__', $sMod, boException::_DEFAULT_MOD_NOT_FOUND_));
  • }
  • if(substr($sConst, 0, strpos($sConst, '_')) !== $sMod) {
  • throw new boException(str_replace('__CONST__', $sConst, boException::_INVALID_CONST_));
  • }
  • if((!$this->oStoredLanguages instanceof ArrayIterator)) {
  • $this->init();
  • }
  • $oXml = new DOMDocument();
  • $oXml->load($this->sPathLocale.'default/'.$sMod.'.xml');
  • $this->delConstantIn($oXml, $sConst, $sMod, 'default');
  • foreach($this->oStoredLanguages as $sLanguage) {
  • $oXml->load($this->sPathLocale.$sLanguage.'/'.$sMod.'.xml');
  • $this->delConstantIn($oXml, $sConst, $sMod, $sLanguage);
  • }
  • }
  • /**
  • * @desc Deletes a constant in a given language
  • *
  • * @param DOMDocument $oXml : DOMDocument in which the node must be removed
  • * @param string $sConst : Constant to me deleted
  • * @param string $sMod : module owning constant
  • * @param string $sLanguage : Language in which the constant must be deleted
  • */
  • private function delConstantIn($oXml, $sConst, $sMod, $sLanguage) {
  • $root = $oXml->firstChild;
  • $xpath = new DOMXPath($oXml);
  • $nodes = $xpath->query('//'.$sConst);
  • if($nodes->length !== 0) {
  • foreach($nodes as $oNode) {
  • if(!is_null($oNode)) {
  • $root->removeChild($oNode);
  • }
  • }
  • $oXml->save($this->sPathLocale.$sLanguage.'/'.$sMod.'.xml');
  • }
  • }
  • /**
  • * @desc Updates a new translation value
  • *
  • * @param string $sClef : element ID
  • * @param string $sVal : new translation value
  • * @param boolean $bComesFromInput : if sets to true, $sClef does come from HTML, else it comes from uploaded file
  • * @param string $sLng : language of the new translation
  • * @param string $sMod : module of the new translation
  • */
  • public function updateValue($sClef, $sVal, $bComesFromInput = true, $sLng = null, $sMod = null) {
  • if(true === $bComesFromInput) {
  • $aProps = $this->parseInputId($sClef);
  • $sConst = $aProps[0];
  • $sLng = $aProps[1];
  • $sMod = $aProps[2];
  • } else {
  • if(is_null($sLng) || is_null($sMod)) {
  • throw new boException(boException::_EMPTY_PARAMETER_);
  • }
  • $sConst = $sClef;
  • }
  • if(true === $bComesFromInput) {
  • $sVal = html_entity_decode(str_replace(genparser::$aCor, array_keys(genparser::$aCor), utf8_decode($sVal)));
  • $sEntities = htmlentities($sVal);
  • } else {
  • $sEntities = $sVal;
  • }
  • $sVal = str_replace(array_keys(genparser::$aCor), genparser::$aCor, $sEntities);
  • $oXml = new DOMDocument();
  • if(!file_exists($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml')) {
  • $oXml->load($this->sPathLocale.'default/'.$sMod.'.xml');
  • $sEncoding = $oXml->encoding;
  • $sXml = '<?xml version="1.0" encoding="'.$sEncoding.'"?>
  • <translations>
  • </translations>';
  • $oXml->loadXML($sXml);
  • } else {
  • $oXml->load($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml');
  • }
  • $cdata = $oXml->createCDATASection($sVal);
  • $xpath = new DOMXPath($oXml);
  • $nodes = $xpath->query('//'.$sConst);
  • if($nodes->length !== 0) {
  • foreach($nodes as $oNode) {
  • if(!is_null($oNode->firstChild)) {
  • $oNode->removeChild($oNode->firstChild);
  • }
  • $oNode->appendChild($cdata);
  • }
  • } else {
  • $elem = $oXml->createElement($sConst);
  • $elem->appendChild($cdata);
  • $root = $oXml->firstChild;
  • $root->appendChild($elem);
  • }
  • $oXml->save($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml');
  • }
  • /**
  • * @desc Magical getter
  • *
  • * @param string $sProp : property asked for
  • * @return mixe : value of the property asked for
  • */
  • public function __get($sProp) {
  • switch($sProp) {
  • case 'MODULES':
  • return $this->oStoredModules;
  • break;
  • case 'LANGUAGES':
  • return $this->oStoredLanguages;
  • break;
  • case 'WEBSITES':
  • return $this->oStoredWebsites;
  • break;
  • case 'LOCALE_PATH':
  • return $this->sPathLocale;
  • break;
  • default:
  • throw new boException(str_replace('__PROP__', $sLanguage, boException::_PROP_NOT_GETTABLE_));
  • break;
  • }
  • }
  • }
  • ?>
<?php
/**
Web application localization back-office class
Copyright (C) 2007  Johan Barbier <johan.barbier@gmail.com>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
/**
 * @author Johan Barbier <johan.barbier@gmail.com>
 * @version 20071031
 * @desc Web application localization back-office class
 * 
 * F R E N C H : 
 * 
 * PRINCIPE : Ce back-office permet de générer les "locales" nécessaires à une application web localisée. La localisation obtenur se base sur la classe oLocale présente dans le répertoire class/ du back-office. Cette classe, si elle ne trouve pas de traduction pour une langue donnée utilisera la langue fixée en tant que langue par défaut à la place.
 * Le répertoire de travail des "locales" pour la classe oLocale présente sur un site est "locales/"..
 * Le répertoire de travail des "locales" pour le back-office est "websites/". La hiérarchie de ce répertoire est la suivante : websites/nom_site_web/locales/code_pays/modulex.xml
 * Chaque racine de site (websites/nom_site_web/locales/) possède automatiquement un répertoire default (au même niveau que le répertoire code_pays) contenant la définition des modules et des constantes attribuées au module, sous forme de fichier xml. x modules = x fichiers.
 * C'est ce répertoire "locales/" sous le "site" qu'il faut copier sur le serveur web, sous le site web de production désiré. 
 * 
 * 
 * Pour créer une localisation, il faut commencer par créer un site web (website). Puis le sélectionner dans la liste déroulante des sites web. Toute autre manipulation passe nécessairement par la sélection d'un site web dans la liste déroulante!
 * 
 * Ensuite, en travaillant MANUELLEMENT :
 * On y ajoute ensuite une langue. A ce moment là, la langue est présente dans les listes déroulantes des langues disponibles et des langues de référence disponibles.
 * On y ajoute un module. De même, le module créé est alors disponible dans la liste déroulante des modules disponibles.
 * On peut alors commencer à travailler :
 * Pour modifier ou afficher une traduction, il faut absolument sélectionner un module, une langue de référence, et une langue de travail.
 * Ceci fait, la liste des constantes avec les traductions dans les 2 langues sélectionnées s'affiche.
 * Un champ de saisie permet de saisir une nouvelle constante. Attention à bien respecter les règles énoncées dans le paragraphe MODULES ci-dessous. 
 * Si la traduction d'une constante pour une des langues n'est pas disponible, s'affiche alors à la place "NO TRANSLATION FOUND". 
 * Les champs de traduction des 2 langues sont modifiables. Toute modification est répercutée sur le "blur" du champ (à la sortie du champ). 
 * NB : Toujours bien penser à vérifier que l'on est sur le bon site avant de faire quoi que ce soit!
 * 
 * En travaillant par UPLOAD DE FICHIER :
 * Il faut toujours créer un site, puis le sélectionner.
 * Ceci fait, il faut prendre le fichier de traduction qui doit etre conforme au modèle présent dans le répertoire template/, et l'ouvrir avec Open Office. Là, l'enregistrer en tant que fichier HTML (et pas XHTML).
 * Enfin, l'uploader dans le champ consacré du back-office. Le traitement peut-être long selon la taille du fichier.
 * Une fois que le back-office a fini d'intégtrer ce fichier, il affichera un message "Fichier intégré avec succès".
 * On peut alors aller afficher les modules, traductions créés.
 * 
 * 
 * LANGUES : les langues seront préférablement utilisées avec le code ISO 3166-1 alpha 3 du pays. Lors de l'ajout de traductions via le fichier modèle, si les pays ne sont pas renseignés ainsi, la classe va tenter de retrouver ce code via la fonction countries/get.alpha.cn.php. S'il ne le trouve pas, il utilisera le nom présent dans le fichier. Attention à l'encodage en cas de caractères spécieux, dans ce cas! Le mieux étant de vérifier la bonne écriture des pays dans le fichier fourni.
 * 
 * WEBSITES : un simple nom de répertoire valide suffira à la création d'un "website" dans le back-office
 * 
 * MODULE : une chaîne alpha normale, sans caractère spéciaux et surtout pas d'underscore.
 * 
 * CONSTANTES : les constantes doivent être préfixées par le nom du module auquel elles appartiennent, suivi d'un underscore, puis de n'importe quelle chaîne valide pour le nom d'un élément xml (éviter les caractères spéciaux, donc). 
 *
 * 
 * E N G L I S H :
 * 
 * PURPOSE : This back-office allows you to generate localizations needed for a web application. The localization will be used by the oLocale class, see the given example on how to use this latter.
 * Work folder for localization used by oLocale class is 'locales/'.
 * Work folder for "locales" used by the back-office is 'websites/'. Structure of this folder is as follows : 'websites/website_name/locales/country_code/module_X.xml'
 * Root of a given website (websites/website_name/locales/) is automatically given a 'default/' folder (same level as the 'country_code/' folders) in which you find the module's and constants' definition as an xml file. X modules = X xml files. 
 * It is THIS 'locales/' folder you'll have to copy to your localized website ('default/' is of no use on your website, however).
 * 
 * 
 * To create a localization, you must first create a website. Then, you have to select it in the dedicated combo list. Every thing you do NEEDS a website to be selected first.
 * 
 * Then, if working MANUALLY :
 * You add a language. Thie language will then be available in the list of available base languages and reference languages.
 * You add a module. Just as above, the new module will then be available in the list of available modules for this website.
 * You can now start working :
 * In order to display, modify or create a given translation, you must select a module, a base language, and a reference language.
 * Once done, the list of constants and translations is displayed.
 * An input allows you to create a new constant. Please respect the rules given in the chapter MODULE of this help.
 * If a translation is not available, 'NO TRANSLATION FOUND' is displayed.
 * Translations for both selected languages can be modify. Just click on the cell. Every modification is saved on the 'blur' event of the cell (when you click out of the cell).
 * NOTE: Just be SURE you are on the correct website before doing anything!
 * 
 * If working by UPLOADING A FILE :
 * You still have to create your website, and then select it.
 * Once done, take your translation file which must be compliant to the one given as an example in the 'templates/' folder. It's an Excel file. With this version of the back-office, it is the only available template (you can build your own, though, you'll find everything needed to do so in the 'class/class.genparser.php' and 'class/class.openoffice23htmlparser.php' files). You must open it in OpenOffice 2.3 and save it as an HTML file (I insist : HTML, not XHTML).
 * Finally, upload this HTML file in the back-office. It can take a while, depending on the size of your file. 
 * When the back-office is done with the file, it will display a message "File successfully processed", so you will know. 
 * You can then display your uploaded translations.
 * 
 * 
 * LANGUAGES : for languages name, you should use the ISO 3166-1 Alpha3 code, it's a good one! When you add translations via an uploaded file, if languages name does not use this format, the back-office will try to find it. It uses for that what you will find in the 'countries/' folder : an xml file and a function. If it cannot find it, it will use the name found in the file. In that case, be careful about special characters in the name, such as accents...it can be a problem while creating a folder with this name. 
 * 
 * WEBSITES : a simple valid folder name will be ok.
 * 
 * MODULE : a normal string, again, without any underscores in it!
 * 
 * CONSTANTES : constants must be prefixed by their module name followed by an underscore (as you will see in the example file in the 'templates/' folder). Then, any xml valid string for tagname.
 */
class backoffice {
	
	/**
	 * Local website path
	 *
	 * @var string
	 */
	private $sPathLocale;
	
	/**
	 * Stored available languages
	 *
	 * @var ArrayIterator
	 */
	private $oStoredLanguages;
	
	/**
	 * Stored available modules
	 *
	 * @var ArrayIterator
	 */
	private $oStoredModules;
	
	/**
	 * Stored available websites
	 * Not used at the time. Might be useful later.
	 *
	 * @var ArrayIterator
	 */
	private $oStoredWebsites;
	
	/**
	 * @desc Constructor. Will initialize backoffice::$sPathLocale property
	 *
	 * @param string (optional) $sPathLocale
	 */
	public function __construct($sPathLocale = null) {
		$this->oStoredWebsites = new ArrayIterator(self::getWebsites());
		if(!is_null($sPathLocale)) {
			$sPathLocale .= '/locales';
			if(!is_dir(PATH_ROOT.'websites/'.$sPathLocale)) {
				throw new boException(str_replace('__PATH__', $sPathLocale, boException::_MISSING_LOCALE_PATH_)); 
			}
			$this->sPathLocale = PATH_ROOT.'websites/'.$sPathLocale.'/';
			if(!is_dir($this->sPathLocale.'default')) {
				mkdir($this->sPathLocale.'default', 0755);
			}
		}
	}
	
	/**
	 * @desc Just creates the folder 'websites' if not present and returns its contents
	 *
	 * @return array : websites available
	 */
	public static function getWebsites() {
		if(!is_dir(PATH_ROOT.'websites')) {
			mkdir(PATH_ROOT.'websites', 0755);
		}
		return self::getDir(PATH_ROOT.'websites');
	}
	
	/**
	 * @desc Returns the contents of a directory
	 *
	 * @param string $sDir : directory to list
	 * @return array : contents of the directory
	 */
	private static function getDir($sDir) {
		$aDir = scandir($sDir);
		$aDir = array_diff($aDir, array('.', '..'));
		return $aDir;
	}
	
	/**
	 * @desc Initializes available modules and languages
	 * 
	 * @return void
	 *
	 */
	public function init() {
		$this->getLanguages();
		$this->getModules();
	}
	
	/**
	 * @desc Gets available languages
	 *
	 * @return ArrayIterator
	 */
	public function getLanguages() {
		if(is_null($this->sPathLocale)) {
			return new ArrayIterator();
		}
		$aDir = self::getDir($this->sPathLocale);
		$aStoredLanguages = array();
		foreach($aDir as $sDir) {
			if($sDir !== 'default' && is_dir($this->sPathLocale.$sDir)) {
				$aStoredLanguages[] = $sDir;
			}
		}
		return $this->oStoredLanguages = new ArrayIterator($aStoredLanguages);
	}
	
	/**
	 * @desc Gets available modules
	 *
	 * @return ArrayIterator
	 */
	public function getModules() {
		if(is_null($this->sPathLocale)) {
			return new ArrayIterator();
		}
		$aDir = self::getDir($this->sPathLocale.'default');
		$aStoredModules = array();
		foreach($aDir as $sDir) {
			$sDir = substr($sDir, 0, strrpos($sDir, '.'));
			$aStoredModules[] = $sDir;
		}
		return $this->oStoredModules = new ArrayIterator($aStoredModules);
	}
	
	/**
	 * @desc Builder method for translations list
	 *
	 * @param string $sMod : mdoule asked for
	 * @param string $sLng : base language asked for
	 * @param string $sRef : reference language asked for
	 * @return array : the list of translations for this module, and these languages
	 */
	public function getTranslationsList($sMod = null, $sLng = null, $sRef = null) {
		if((!$this->oStoredLanguages instanceof ArrayIterator) || (!$this->oStoredModules instanceof ArrayIterator)) {
			$this->init();
		}
		$aMessages = array();
		$aMessages['REF'] = $this->loopOnParameters($sMod, $sRef);
		$aMessages['LNG'] = $this->loopOnParameters($sMod, $sLng);
		return $aMessages;
	}
	
	/**
	 * @desc Loops on constants in module asked for, and languages
	 * I have made available the possibility to retrieve translations for several modules, even if I do not use it in the current backoffice. I have tried, but it is a bit confusing then. Easy to extend, so.
	 *
	 * @param string $sMod : module asked for
	 * @param string $sLng : current languages on which we loop
	 * @return array : list of translations for modules and languages asked for
	 */
	private function loopOnParameters($sMod, $sLng) {
		$aMessages = array();
		if(!is_null($sMod)) {
			if(!is_null($sLng)) {
				$aMessages[$sLng] = $this->getTranslationList($sMod, $sLng);
			} else {
				foreach($this->oStoredLanguages as $sLng) {
					$aMessages[$sLng] = $this->getTranslationList($sMod, $sLng);
				}
			}
		} else {
			foreach($this->oStoredModules as $sMod) {
				if(!is_null($sLng)) {
					if(empty($aMessages[$sLng])) {
						$aMessages[$sLng] = array();
					}
					$aMessages[$sLng] = array_merge($aMessages[$sLng], $this->getTranslationList($sMod, $sLng));
				} else {
					foreach($this->oStoredLanguages as $sLng) {
						if(empty($aMessages[$sLng])) {
							$aMessages[$sLng] = array();
						}
						$aMessages[$sLng] = array_merge($aMessages[$sLng], $this->getTranslationList($sMod, $sLng));
					}
				}
			}
		}
		return $aMessages;
	}

	/**
	 * @desc Actually fetches list of translation, called by backoffice::loopOnParamaters
	 *
	 * @param string $sMod : current module
	 * @param string $sLng : current language
	 * @return array : list of translations for this module and this language
	 */
	private function getTranslationList ($sMod, $sLng) {
		$aMessages = array();
		$oDefaultXml = simplexml_load_file($this->sPathLocale.'default/'.$sMod.'.xml');
		if(file_exists($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml')) {
			$oXml = simplexml_load_file($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml');
		} else {
			$oXml = null;
		}
		foreach ($oDefaultXml as $clef => $val) {
			if(!empty($oXml->$clef)) {
				$aMessages[$sMod][$clef] = (string)$oXml->$clef;
			} else {
				$aMessages[$sMod][$clef] = '';
			}
		}
		return $aMessages;
	}

	/**
	 * @desc : Adds a new module
	 * Please follow documentation
	 *
	 * @param string $sModule : new module name
	 * @param string $sEncoding : new module encoding
	 */
	public function addModule($sModule, $sEncoding='UTF-8') {
		if(empty($sModule)) {
			throw new boException(boException::_EMPTY_PARAMETER_); 
		}
		if(file_exists($this->sPathLocale.'default/'.$sModule)) {
			throw new boException(str_replace('__MOD__', $sModule, boException::_MODULE_ALREADY_EXISTS_)); 
		}
		if(false !== strpos($sModule, '_')) {
			throw new boException(boException::_INVALID_UNDERSCORE_); 
		}
		$sXml = '<?xml version="1.0" encoding="'.$sEncoding.'"?>
					<constants>
					</constants>';
		file_put_contents($this->sPathLocale.'default/'.$sModule.'.xml', $sXml);
	}
	
	/**
	 * @desc : Adds a new website
	 * Please follow documentation
	 *
	 * @param string $sWebsite : new website name
	 */
	public function addWebsite($sWebsite) {
		if(empty($sWebsite)) {
			throw new boException(boException::_EMPTY_PARAMETER_); 
		}
		if(file_exists(PATH_ROOT.'websites/'.$sWebsite)) {
			throw new boException(str_replace('__WEB__', $sWebsite, boException::_WEBSITE_ALREADY_EXISTS_)); 
		}
		mkdir(PATH_ROOT.'websites/'.$sWebsite);
		mkdir(PATH_ROOT.'websites/'.$sWebsite.'/locales');
	}
	
	/**
	 * @desc : Adds a new language
	 * Please follow documentation
	 *
	 * @param string $sLanguage : new language name (best is alpha2 or alpha3)
	 */
	public function addLanguage($sLanguage) {
		if(empty($sLanguage)) {
			throw new boException(boException::_EMPTY_PARAMETER_); 
		}
		if(is_dir($this->sPathLocale.$sLanguage)) {
			throw new boException(str_replace('__LNG__', $sLanguage, boException::_LANGUAGE_ALREADY_EXISTS_)); 
		}
		mkdir($this->sPathLocale.$sLanguage, 0755);
		if(!$this->oStoredModules instanceof ArrayIterator) {
			$this->getModules();
		}
		$oXml = new DOMDocument;
		
		foreach($this->oStoredModules as $sMod) {
			$oXml->load($this->sPathLocale.'default/'.$sMod.'.xml');
			$sEncoding = $oXml->encoding;
			$sXml = '<?xml version="1.0" encoding="'.$sEncoding.'"?>
					<translations>
					</translations>';
			file_put_contents($this->sPathLocale.'/'.$sLanguage.'/'.$sMod.'.xml', $sXml);
		}
	}
	
	/**
	 * @desc Small method to parse the html elements ID in the list of translations
	 *
	 * @param string $sClef : html element id
	 * @return array : an array with the parsed ID (constant name, language name, and module name)
	 */
	private function parseInputId($sClef) {
		$aProps = explode('@', $sClef);
		$sConst = $aProps[0];
		$sLng = $aProps[1];
		$sMod = substr($sConst, 0, strpos($sConst, '_'));
		return array($sConst, $sLng, $sMod);
	}
	
	/**
	 * @desc Adds a new constant
	 *
	 * @param string $sConst : new conatnts name
	 * @param string $sMod : module owning the new constant
	 */
	public function addNewConstant($sConst, $sMod) {
		if(!file_exists($this->sPathLocale.'default/'.$sMod.'.xml')) {
			throw new boException(str_replace('__MOD__', $sMod, boException::_DEFAULT_MOD_NOT_FOUND_)); 
		}
		if(substr($sConst, 0, strpos($sConst, '_')) !== $sMod) {
			throw new boException(str_replace('__CONST__', $sConst, boException::_INVALID_CONST_)); 
		}
		$oXml = new DOMDocument();
		$oXml->load($this->sPathLocale.'default/'.$sMod.'.xml');
		$root = $oXml->firstChild;
		$elem = $oXml->createElement($sConst);
		$root->appendChild($elem);
		$oXml->save($this->sPathLocale.'default/'.$sMod.'.xml');
	}
	
	/**
	 * @desc Deletes a constant in all languages
	 *
	 * @param string $sConst : constant name
	 * @param string $sMod : module owning constant
	 */
	public function delConstant($sConst, $sMod) {
		if(!file_exists($this->sPathLocale.'default/'.$sMod.'.xml')) {
			throw new boException(str_replace('__MOD__', $sMod, boException::_DEFAULT_MOD_NOT_FOUND_)); 
		}
		if(substr($sConst, 0, strpos($sConst, '_')) !== $sMod) {
			throw new boException(str_replace('__CONST__', $sConst, boException::_INVALID_CONST_)); 
		}
		if((!$this->oStoredLanguages instanceof ArrayIterator)) {
			$this->init();
		}
		$oXml = new DOMDocument();
		$oXml->load($this->sPathLocale.'default/'.$sMod.'.xml');
		$this->delConstantIn($oXml, $sConst, $sMod, 'default');
		foreach($this->oStoredLanguages as $sLanguage) {
			$oXml->load($this->sPathLocale.$sLanguage.'/'.$sMod.'.xml');
			$this->delConstantIn($oXml, $sConst, $sMod, $sLanguage);
		}
	}
	
	/**
	 * @desc Deletes a constant in a given language
	 *
	 * @param DOMDocument $oXml : DOMDocument in which the node must be removed
	 * @param string $sConst : Constant to me deleted
	 * @param string $sMod : module owning constant
	 * @param string $sLanguage : Language in which the constant must be deleted
	 */
	private function delConstantIn($oXml, $sConst, $sMod, $sLanguage) {
		$root = $oXml->firstChild;
		$xpath = new DOMXPath($oXml);
		$nodes = $xpath->query('//'.$sConst);
		if($nodes->length !== 0) {
			foreach($nodes as $oNode) {
				if(!is_null($oNode)) {
					$root->removeChild($oNode);
				}
			} 
		$oXml->save($this->sPathLocale.$sLanguage.'/'.$sMod.'.xml');
		}
	}
	
	/**
	 * @desc Updates a new translation value 
	 *
	 * @param string $sClef : element ID 
	 * @param string $sVal : new translation value
	 * @param boolean $bComesFromInput  : if sets to true, $sClef does come from HTML, else it comes from uploaded file
	 * @param string $sLng : language of the new translation
	 * @param string $sMod : module of the new translation
	 */
	public function updateValue($sClef, $sVal, $bComesFromInput = true, $sLng = null, $sMod = null) {
		if(true === $bComesFromInput) {
			$aProps = $this->parseInputId($sClef);
			$sConst = $aProps[0];
			$sLng = $aProps[1];
			$sMod = $aProps[2];
		} else {
			if(is_null($sLng) || is_null($sMod)) {
				throw new boException(boException::_EMPTY_PARAMETER_);
			}
			$sConst = $sClef;
		}
		if(true === $bComesFromInput) {
			$sVal = html_entity_decode(str_replace(genparser::$aCor, array_keys(genparser::$aCor), utf8_decode($sVal)));
			$sEntities = htmlentities($sVal);
		} else {
			$sEntities = $sVal;
		}
		$sVal = str_replace(array_keys(genparser::$aCor), genparser::$aCor, $sEntities);
		$oXml = new DOMDocument();
		if(!file_exists($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml')) {
			$oXml->load($this->sPathLocale.'default/'.$sMod.'.xml');
			$sEncoding = $oXml->encoding;
			$sXml = '<?xml version="1.0" encoding="'.$sEncoding.'"?>
						<translations>
						</translations>';
			$oXml->loadXML($sXml);
		} else {
			$oXml->load($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml');
		}
		$cdata = $oXml->createCDATASection($sVal);
		$xpath = new DOMXPath($oXml);
		$nodes = $xpath->query('//'.$sConst);
		if($nodes->length !== 0) {
			foreach($nodes as $oNode) {
				if(!is_null($oNode->firstChild)) {
					$oNode->removeChild($oNode->firstChild);
				}
				$oNode->appendChild($cdata);
			} 
		} else {
			$elem = $oXml->createElement($sConst);
			$elem->appendChild($cdata);
			$root = $oXml->firstChild;
			$root->appendChild($elem);
		}
		$oXml->save($this->sPathLocale.'/'.$sLng.'/'.$sMod.'.xml');
	}

	/**
	 * @desc Magical getter
	 *
	 * @param string $sProp : property asked for
	 * @return mixe : value of the property asked for
	 */
	public function __get($sProp) {
		switch($sProp) {
			case 'MODULES':
				return $this->oStoredModules;
				break;
			case 'LANGUAGES':
				return $this->oStoredLanguages;
				break;
			case 'WEBSITES':
				return $this->oStoredWebsites;
				break;
			case 'LOCALE_PATH':
				return $this->sPathLocale;
				break;
			default:
				throw new boException(str_replace('__PROP__', $sLanguage, boException::_PROP_NOT_GETTABLE_)); 
				break;
		}
	}
}
?>

 Conclusion

NB :
J'ai utilisé la librairie javascript Prototype afin de faciliter la programmation du js.
Dans le répertoire 'countries/' se trouve un fichier XML provenant de Wikipedia et contenant les noms de tous les pays dans 7 langues, ainsi que leurs codes ISO 3166-1 alpha2 et alpha3. Vous trouverez au même endroit une petite fonction à moi permettant d'aller chercher les codes en fonction d'un nom de pays dans ce fichier. Ca peut-être utile (c'est utilisé dans le back-office).

Ce code vient d'être nomminé pour les Innovation Awards de décembre 2007 sur phpclasses.org. Si vous voulez voter pour moi :-) http://www.phpclasses.org/vote.html

 Fichier Zip

Les Membres Club peuvent télécharger directement un fichier contenu dans le zip sans télécharger le zip en entier !

Télécharger le zip


 Historique

02 novembre 2007 18:47:06 :
Rajout de la licence
06 novembre 2007 22:41:58 :
Ajout du choix de l'encodage des fichiers de traduction
06 novembre 2007 23:09:46 :
Ajout capture
09 novembre 2007 13:36:11 :
Correction d'un petit bug et ajout d'exemples en chinois et en arabe (je voulais tester si cela fonctionnait, et cela fonctionne sans problème)
09 novembre 2007 13:57:39 :
Oubli
15 novembre 2007 19:26:09 :
Correction de quelques bugs Ajout d'une fonctionnalité : supprimer une "constante" de toutes les langues
01 janvier 2008 10:18:55 :
Juste un peu de pub dans la conclusion :-)

 Sources du même auteur

Source avec Zip ASTUCES/HACK PHP
SQUELETTE DE GESTION DES DROITS
[PHP 5.1] CLASS STRING : NOUVEL EXEMPLE SUR LA SPL
Source avec Zip Source avec une capture [PHP 5.1] PHOTOPHOP (PHPDRAW 2)
Source avec Zip [PHP5] EXCEPTIONERROR PACKAGE : TRANSFORMER TOUTES LES ERREU...

 Sources de la même categorie

Source avec une capture PHPREPOGENERATOR + REPO (WIN) par alvinp
Source avec Zip IPHONE - ICÔNE D'APPEL TÉLÉPHONIQUE SUR L'ÉCRAN D'ACCUEIL par Rainbow
Source avec Zip Source avec une capture [APP WEB]SERVEUREXPLOREUR par thematrix01
Source avec Zip Source avec une capture MY.BOOKMARKS par inwebo
Source avec Zip M.V.C M.E.D par faceme

 Sources en rapport avec celle ci

Source avec Zip [POO][PHP5]UN SITE MULTILANGUE VIA XML par destinyfr
Source avec Zip UN PETIT SITE MULTILANGUE par medbabs
UNE GESTION MULTILINGUE COMPLETE par scriptino
Source avec Zip SITE EN PLUSIEURS LANGUES : EXEMPLE SIMPLE AVEC XML par malalam

Commentaires et avis

Commentaire de codefalse le 02/11/2007 23:22:15 administrateur CS 10/10

pfff c'est nul ... ;)
Ya pas à dire, c'est du bon! tres bon meme ... comme toujours :)
Je met 11 ... ah bah nan, 10 ! :)

Commentaire de neigedhiver le 02/11/2007 23:33:13

Salut,

Intéressante, cette source... D'autant plus, pour moi, que je me creuse la tête ces derniers jours sur une classe d'internationalisation... Je vais donc regarder en détails ce que tu as fait ^^

Petite question : tu ne gères pas la direction "ltr" ou "rtl" ? Ou alors j'ai mal vu ?
Autre chose : penses-tu qu'il soit pertinent de s'intéresser à l'encodage des caractères lors de l'affichage, ou bien est-ce que le tout-utf8 est ta devise ? (comme elle aurait tendance à être la mienne)

C'est tout pour le moment ;o)

Commentaire de malalam le 03/11/2007 01:34:04 administrateur CS

Merci à tous les deux :-)

En l'occurence, là, j'ai géré ça en iso-8859-1 qui suffit parfaitement à la tâche et s'implémente facilement sur des sites européens, qui plus est. Mais je rajouterai une possibilité de choisir l'encodage des fichiers xml, c'est prévu.
Ceci dit, généralement, quand je bosse avec du xml, je bosse en utf-8, oui. Mais là je me suis rendu compte que cela fonctionnait parfaitement avec desflux en iso-8859-1.
Pour les < et >, c'est inutile, car les flux xml générés par le back-office encapsulent leurs   textNode dans des cdata. Les caractères tels que < et > ne sont donc pas interprêté. C'est d'ailleurs tout l'intérêt : cela permet d'y inclure du html...pour la mise en page :-)

J'ai fait ce back-office parce que ces temps-ci, à mon taf, nous faisons face à une forte poussées des sites internationaux. J'en avais marre de gérer ça tant bien que mal, ça prend énormément de temps. J'ai donc décidé d'imposer un type de fichiers que doivent nous fournir nos traducteur (le template xls), et de monter ce back-office qui s'appuie sur la structure xml de ma classe oLocale, permettant de gérer l'affichage des traductions.

Bref pour la 1ère fois, je mets ici une vraie application que j'utilise dans mon taf, et pas simplement un package utilitaire, ce que je ne fais normalement jamais. Mais là, je me suis dit que l'internationalisation, c'était une plaie, et que ça pourrait sans douter aider quelques personnes.

Commentaire de neigedhiver le 03/11/2007 02:42:10

"En l'occurence, là, j'ai géré ça en iso-8859-1 qui suffit parfaitement à la tâche et s'implémente facilement sur des sites européens, qui plus est."
>> Certes. Cependant, question internationalisation,justement, l'utf-8 serait, à mon sens plus approprié, puisque plus universel... M'enfin ça, tu le sais, hein...

"Pour les < et >, c'est inutile, car les flux xml générés par le back-office encapsulent leurs   textNode dans des cdata. Les caractères tels que < et > ne sont donc pas interprêté. C'est d'ailleurs tout l'intérêt : cela permet d'y inclure du html...pour la mise en page :-)"
>> Au premier coup d'oeil, j'ai trouvé que ça alourdissait la lecture du fichier XML. Mais l'avantage que tu cites est loin d'être négligeable...
Cependant, dans une logique MVC, si on sépare correctement le traitement de la présentation, du html dans les fichiers de traduction, c'est superflu... Mais bon... (je chipote, je chipote...)

Bref, merci de partager cette source. Parce que comme toi, je constate que l'internationalisation, c'est un p****n de galère, quand même.

Même si je n'utiliserai probablement pas ta classe telle quelle (et je te prie de m'en excuser ^^), au moins, j'ai un vrai quelque chose à me mettre sous la dent côté inspiration, logique, tout ça.

J'utilise aussi SimpleXML pour la gestion de mes fichiers de langue XML, mais je suis vraiment rebuté par la sortie de la méthode asXML. Y'a bien une source sur la doc de php pour mettre en forme joliment, mais c'est peut-être lourd en terme de perfs.

De mon côté, je suis un peu perdu dans toutes les considérations liées à la localisation : langue (nom court, nom long, nom traduit, ...), fuseau horaire, le format d'affichage de la date, encodage, direction, ... Du mal à faire le tri entre ce qui est nécessaire et superflu...

Et puis pour couronner le tout, j'aimerais avoir une classe d'exception qui traduit les messages d'erreur. Et si possible, qui les affiche correctement avec Smarty... Et si en plus j'y ajoute mon système de plugins...

Tiens une question bête : penses-tu qu'il serait judicieux, dans un contexte d'application distribuée en GPL de gérer plusieurs formats de fichiers de langue (xml, ini, ...) ?

Commentaire de malalam le 03/11/2007 10:55:44 administrateur CS

Vomme je l'ai dit, j'ajouterai le choix de l'encodage à cette application. Néanmoins, étant donnée qu'à l'origine elle est destinée à mon équipe, je l'ai construite de manière à ce qu'elle fonctionne chez moi :-)
Et puis vu le support incroyablement puissant de php pour l'utf-8...ahem...
Je pense que ce back-office sera profondément modifié le jour où php6 et son support en interne de l'unicode sortira. Vivement...:-)

Attention, le xml est un format de description de données. S'il décrit des données html, cela ne va nullement à l'encontre du concept MVC de mettre des balises html dans des cadata. D'ailleurs, les cdata servent plus ou moins à ça. J'ai trouvé largement préférable de se laisser la possibilité de créer des "constantes" de traductions contenant de la mise en page plutôt que d'obliger à découper un texte selon sa mise en page. Cela peut vite devenir très lourd. Rien n'empêche néanmoins de le faire, l'application laisse le choix, simplement.

Attention, ne confonds pas le code que tu vois là, celui que j'ai décide de mettre dans le conteneur de visualisation du code, et la classe utilitaire permettant de gérer l'affichage et le choix des traductions sur une application web : le code que tu vois là est le code du coeur du back-office. Il n'est nullement destiné à être instancié et appelé dans un autre code. C'est une application. On la lance via le fichier back.office.php et on l'utilise.
La classe oLocale, elle, dont le code est beaucoup plus simple, est destinée à être instanciée et utilisée dans un autre code, et sert à choisir et afficher les traductions.Elle se base sur des fichiers xml qui PEUVENT être générés par le back-office puisqu'il utilise la structure attendue. Mais elle peut très bien servir sans le back-office.

Pour simpleXML, pourquoi veux tu utilise simpleXMLElement::asXML() ? Cette méthode sert surtout pour du debug et éventuellement sauvegarder un flux xml avec un bête file_put_contents(). Elle ne sert pas à travailler sur le flux. Si tyu veux afficher le textNode d'un élément de ton flux, tu dois aller le chercher avec xpath par exemple.
Et si tu parles de ce que simpleXMLElement::asXML() donne si on l'utilise pour sauvegardert un flux dans un fichier, bah...je n'ai jamais été d'accord avec ça. Lol. J'ai souvent vu des gens construire des flux XML en y ajoutant des \n afin de le formatter avant de le sauvegarder. Pour moi, ça n'est pas le but. Je peux aussi bien sauvegarder mes flux xml sur une seule ligne. Quand je vais les "regarder", j'utilise alors un logiciel tel que XMLSpy, ou même IE, qui le formattent eux-même pour l'affichage. Les \n, php s'en fout. Il n'en a pas besoin. Ni aucun autre moyen detravailler sur les flux XML d'ailleurs. C'est juste pour nous...et à ce compte, je préfère utiliser les outils de visualisation adéquats.
Mais ça reste un avis personnel :-)

Pour ta dernière question : tout applicatif impose ses normes. Si elles sont judicieuses, les gens s'y plient volontiers. A toi de voir, donc : faire quelque chose proposant un très large éventail de possibilités...mais du coup complexe et pouvant rebuter ceux qui cherchent une application pour les aider à réaliser quelque chose simplement... ou imposer une norme judicieuse et efficace qui satisfera le plus grand nombre, et décevra certains hardcore users qui aiment que les applicatifs se plient à LEURS exigeances.
Le plus souvent, cela va dépendre du contexte de ton applicatif : à quoi est-il réellement destiné. Un framework (au hasard) doit être exhaustif...et simplifier la complexité, si j'ose dire. Un framework reste compliqué à utiliser, il demande de l'investissement. MAIS, l'exhaustivité de ce qu'il propose, il en simplifie quand même l'usage. Bref, pour un framework, il vaut mieux être exhasutif, oui. Pour un CMS, il vaut mieux ne pas l'être et imposer des normes simples et efficaces.

Commentaire de malalam le 03/11/2007 12:04:34 administrateur CS

'tain, je devrais me relire, je fais un nombre de fautes d'orthographe et de frappe impressionnant :-( Désolé!

Commentaire de neigedhiver le 03/11/2007 12:24:55

Salut,

"Néanmoins, étant donnée qu'à l'origine elle est destinée à mon équipe, je l'ai construite de manière à ce qu'elle fonctionne chez moi :-)"
>> Je suis bien d'accord... Je parlais seulement de théorie, pas de mise en application ;)

"Pour simpleXML, pourquoi veux tu utilise simpleXMLElement::asXML() ? Cette méthode sert surtout pour du debug et éventuellement sauvegarder un flux xml avec un bête file_put_contents(). Elle ne sert pas à travailler sur le flux. Si tyu veux afficher le textNode d'un élément de ton flux, tu dois aller le chercher avec xpath par exemple."
>> En lecure, je n'utilise pas xPath, je parcours l'objet pour stocker le contenu XML dans un tableau, plus facile, à mon sens, pour accéder aux chaines traduites.
Par contre, il me semblait que asXML servait à la sortie du XML, éventuellement dans un fichier... Le but de cette fonction ne serait-il alors pas de sauvegarder un fichier ?

"J'ai souvent vu des gens construire des flux XML en y ajoutant des \n afin de le formatter avant de le sauvegarder."
>> Euh... Là, je ne te suis pas... Il faut l'ajouter où le \n, parce que depuis un objet simpleXML, on ne peut pas rajouter, me semble-t-il de caractères en dehors des balises...

"Quand je vais les "regarder", j'utilise alors un logiciel tel que XMLSpy, ou même IE, qui le formattent eux-même pour l'affichage."
>> C'est pas faux... De mon côté, j'envisage une application (même rôle que ton back office) qui permet de visualiser/éditer les fichiers de traduction.
Mais bon, quand on aime avoir accès à tout, c'est aussi bien quand c'est "propre" ;) Parce qu'une application en PHP (par exemple), si le code est mal formé pour une raison x ou y, elle n'affichera rien... Un éditeur de texte affichera toujours le contenu...

Merci pour tes réponses en tout cas, la discussion est intéressante... Vais continuer de potasser mes classes... Je pensais avoir terminé ma classe d'abstraction bdd, mais... en fait non... Dire que ça fait plus d'un an que je suis dessus lol

Commentaire de malalam le 03/11/2007 12:39:50 administrateur CS

SimplaXMLElement::asXML() : si si, le but est bien de sauvegarder tes données dans un fichier. Notamment (en plus du debug). Mais dans ce cas c'est moi qui ne te suis pas : que lui reproches-tu ?

Certaines personnes construisent leur flux XML à partir d'autres données...du coup, ils vont faire ceci :
foreach($aData as $sContentsKey => $sContents) {
  $sXML .= '<'.$sContentsKey.'>'.$sContents.'</'.^sContentsKey.'>'."\n";
}
de manière à ce que, en ouvrant leur fichier xml dans un éditeur lambda, les retours chariot soient visibles. Le retour chariot ne sera PAS pris en compte dans ton flux xml. Ceci dit, ça reste une hérésie pour moi. Si on veut visualiser le flux (je parle bien du flux, pas des données qu'il décrit), autant utiliser une application dédiée telle que XMLSpy.
Si on doit voir ses données, là, ça dépend du contexte. C'est ce que fait mon back-office :
tu ne vois que  les données, le flux est transparent. Tu peux les modifier, en créer...ça reste transparent, tu ne travailles que sur les données, PAS sur le flux.

C'est à l'application PHP de vérifier la conformité des données qu'elle traite. Et évidemment, à l'utilisateur d'utiliser correctement les données selon son application : essaye d'ouvrir un fichier .doc avec Zend Studio...tu verras ce qu'il dit. Ou d'utiliser un dvd playstation3 sur ton pc.  Et mieux, ouvre un fichier .doc sous texpad ou notepad...ah ça, tu verras quelque chose... ;-) Mais pas forcément ce que tu t'attendais à voir. Pourtant, le fichier .doc est "propre"...et l'application textpad très bien codée.
Si tu veux visualiser ton .doc dans de bonnes conditions, tu utilises Word (ou OpenOffice, comme moi).

Commentaire de neigedhiver le 03/11/2007 13:45:45

Ouais... T'as achevé de me convaincre...

Pour terminer, ce que je reproche à SimpleXMLElement::asXML() c'est de ne pas indenter le contenu. Je sais, c'est stupide, mais bon, j'ai cet a priori à la noix...
Mais comme tu m'as convaincu, ben... De toute façon je comptais développer une sorte de back office. A défaut d'application complète, au moins les outils pour gérer tout ça. Bref... Tout ça nourrit ma réflexion, et c'est très bien ;)

Commentaire de malalam le 04/11/2007 12:22:58 administrateur CS

Ah je ne cherchais pas à te convaincre...lol mais bon. Si je t'ai convaincu de ne pas mettre explicitement de \n dans tes flux xml pour les visualiser dans de bonnes conditions, je trouve ça bien :-) C'est vraiment inutile à mon sens. Parfois on le fait sans le gfaire exprès, évidemment (t'as qu'à regarder les flux xml de cette application pour t'en convaincre, si je fais ça :
$xml = '<translations>
</translations>';
je vais avoir un \n au milieu, forcément).
Si je peux me permettre un conseil (qui rebondit sur ta question dans le forum) : utilise simplXML pour parcourir tes flux, c'est plus pratique, mais DOM pour les modifier ou les construire. simpleXML ne permet pas d'ajouter de noeud, ou d'en supprimer. Juste d'en modifier. Et encore, pas beaucoup. Pour l'instant en tous cas, je pense que php6 fera encore un peu grossir simpleXML, comme l'on faites les différentes version de php5 au fil du temps.

Commentaire de malalam le 04/11/2007 12:24:03 administrateur CS

Et toi tu m'as aussi convaincu d'un truc au fait : je vais ajouter plus vite que je ne le prévoyais le choix de l'encodage. Après tout, si je partage, autant laisser la possibilité aux gens d'utiliser ce qu'ils veulent, je n'ai pas à imposer mon choix d'encodage.

Commentaire de neigedhiver le 04/11/2007 12:37:01

SimpleXML permet tout à fait d'ajouter des noeuds, avec la méthode SimpleXMLElement::addChild() qui marche très bien.

Pour l'encodage des caractères, tu as mieux exprimé que moi le pourquoi de sa nécessité ;)
Cela dit, c'est pas parce que tu partages que ça doit être universel... C'est ton choix de faire que ce soit le plus ouvert possible, après, libre à chacun de faire ce qu'il veut et d'ajouter la gestion de l'encodage, ou de n'importe quoi qui lui plait.

Commentaire de stailer le 04/11/2007 23:21:42

J'ai développé quasiment le même système, dans une interface tout en flex, mais le principe est à 90% le même.
Comme mon système tu as le même souci : si il y a 250 traductions dans une appli ça va vite devenir le bordel. Il faudrait donc que l'on puisse chercher une traduc rapidement avec des mots clés sans aller dans le code pour voir à quel module ça correspond.

Pout l'encodage, c'est bien d'obliger personne à utiliser tel ou tel encodage, mais honnêtement l'UTF-8 ne m'a jamais déçu ;)

A part ça excellent, source très complète :)

Commentaire de malalam le 04/11/2007 23:53:50 administrateur CS

Le principe des modules, c'est justement d'organiser les traductions :-) Sans compter les "constantes" de traduction : err_no_file_found, je sais à quoi ça correspond, et que ça se trouve dans le module "err".
Mais oui évidemment, si on a énormément de traductions, ça devient forcément plus problématique. Mais je ne suis pas convaincu par la recherche par mots clefs : il faut connaître les mots existants dans une langue compréhensible! Parce que le bulgare et moi, ça fait 2. Et ne chercher que le français, ou l'anglais, ne sera pas forcément très pertinent. Surtout pour les bulgares ;-) M'enfin oui, pourquoi pas, c'est une bonne idée à creuser. Ce n'est pas très difficile en xml...ça risque juste d'être un peu long, comme traitement.

Tu as fait quoi toi, sur ton appli ? recherche, donc ? Ou tu es en passe de le faire ?

Et merci :-)

Commentaire de stailer le 05/11/2007 00:23:25

je vais faire un système de recherche. Je pense que le plus simple c'est :
1) on est sur un site ou une appli et on constate qu'une phrase doit être reformulée (cas le plus fréquent )
2) on va dans le backoff de la gestion des traductions et on tape par exemple les premiers mots de la recherche,
3) une liste apparait avec toutes les propositions et on change la traduction.

Mais en fait ça fonctionne un petit peu différement de toi : il n'y a pas de notion de module, mais de "balises". Tu ajoutes des balises (avec par exemple le nom de la page ou justement du module) et tu associes une traduction.

Donc à ce niveau, comme je "perds" une couche, c'est un petit peu plus pénible. Mais je pense refaire cette appli en fait.

En revanche j'ai une autre notion : les paramètres. Peut-être que tu as cette fonctionnalité, j'ai pas tout regardé de ton appli.

exemple avec la balise "TEST"
Dans une traduction en FR tu tapes : "Bonjour, nous sommes le [madate], et il fait beau".

Pour l'affichage, 2 solutions : en php dans un controleur (parce que j'ai fait mon framework enfait ) :
$this->getOFTraduction('test', array('madate'=> date('%d / %m / %Y')));

ou alors, pas de php, mais dans un plugin smarty que j'ai développé (rien de génial) :
{ofts balise="test" madate="04 / 11 / 2007"}

ou encore dans un controleur :
$this->getSmarty()->assign('madate', date('%d / %m / %Y') );

et dans le template
{ofts balise="test" madate="$madate"}

on a fait quelques sites avec ce système, ça marche plutôt bien et l'interface est très agréable... y a juste ce problème de recherches.

Commentaire de codefalse le 07/11/2007 15:22:51 administrateur CS

euh, au risque de paraitre hors sujet, pourquoi ne pas utiliser gettext ?

Commentaire de neigedhiver le 07/11/2007 16:01:23

Salut,

Peut-être parce que l'extension gettext n'est pas installée par défaut et que selon les cas c'est tout simplement pas possible de l'installer...

Commentaire de codefalse le 07/11/2007 16:37:34 administrateur CS

okay réponse acceptée ! ;)

Commentaire de codefalse le 07/11/2007 17:45:51 administrateur CS

sinon, si j'ai bien compris ta classe Mister Malalam, tu affiche les erreurs par "mot clés" ? type mx_play1 pour dire veuillez utiliser la souris (ou un truc du genre :p).
Le probleme, c'est qu'il faut savoir que mx_play1 veut dire "veuillez utiliser la souris" nan ?

Pourquoi ne pas se baser sur le principe (grossomodo) de gettext ? genre

eco getMsg ("Welcome to my website"); et dans ton xml tu aurai un truc du genre
<lang>
  <FR>
    <message>
       <original><[CDATA[Welcome to my website]]></original>
       <translated>Bienvenue sur mon site</translated>
    </message>
  </FR>
</lang

En gros ? comme ca tu fait une requete Xpath qui recherche dans /lang/$LANG/message/original/. et qui affiche le ../translated/. ?
(version simplifiée de l'idée biensur :p)

Commentaire de malalam le 07/11/2007 18:38:37 administrateur CS

@codefalse => en effet, je n'ai pas choisi gettext parce que je peux faire la même chose, plus simplement, via xml. Donc pourquoi m'embarquer dans cette extension...
mx_play1 est un simple exemple tiré d'un site dont je n'ai pas défini les "constantes".
Imaginons un module error(déjà, j'ai des modules) :
je vais avoir ces constantes :
error_login_failed
error_file_not_found
erro_page_not_found

etc...
Là, c'est parlant. Pas moins en tous cas que "welcome to my website", et c'est plus court à écrire.
Ensuite, je fais via ma classe oLocale :
echo getMsg('error_file_not_found');

Et basta.

TU crées tes modules et tes constantes, donc TU sais ce que tu y mets. Donc tu les connais.
Le but est qu'elles soient explicites, mais ça, c'est comme tout en programmation : les variables doivent être explicites, les noms de méthodes aussi, etc.

Bref, ma classe oLocale fonctionne exactement comme tu l'indiques (la notion de modules en plus, ce qui permet de scinder un peu tes traductions et de ne pas avoir des fichiers de 3km de long...), et de structurer, évidemment.
Et mon backoffice permet de gérer ces traductions, d'en créer, d'en modifier, etc...ce, manuellement, ou via upload de fichier.

Commentaire de malalam le 07/11/2007 19:47:53 administrateur CS

@stailer => merci, je vais y réflêchir, ça peut être une bonne idée en effet.

Commentaire de codefalse le 07/11/2007 22:26:57 administrateur CS

oué je suis d'accord sur le fait que du coup, tu es précis sur tes messages, mais ca te bloque au niveau quantité, quand tu arrive à 2500 textes (et tu y arrive tres vite ! :p), tu t'y retrouve toujours ? :p

Commentaire de malalam le 07/11/2007 22:44:22 administrateur CS

Je ne vois pas ce que cela change d'écrire, au pire, welcome on my website, ou welcome_on_my_website ? Je ne m'y retrouverais pas moins bien dans le second cas.
Et cela ne bloque pas au niveau de la quantité.
Si j'écris "welcome_wish_to_newcomers" et que derrière, j'ai mon texte de bienvenue aux nouveaux venus sur mon site et que ce texte fait 20 lignes...je pense que je m'y retrouverai bien mieux dans mes traductions que toi qui va écrire echo gettext('mes 20 lignes en entier'). Tes fichiers de traduction risquent d'être vite incompréhensibles.  
C'est un peu comme si tu me disais qu'au lieu de définir la constante de mon sql host ainsi :
define('HOST', 'www.mydbserver-myprovider.com');
je devrais la définir ainsi:
define('www.mydbserver-myprovider.com', 'www.mydbserver-myprovider.com');
Perso, je m'y retrouve mieux avec HOST.
Surtout que si je change la valeur de HOST, j'aurai:
define('HOST', 'www.newmydbserver-newmyprovider.com');
alors que toi tu auras
define('www.mydbserver-myprovider.com', 'www.newmydbserver-newmyprovider.com');

bref, mes constantes ne bougent pas, dans mes traudctions. error_file_not_found définit bien ce que c'est. Même si j'en change la trduction de "Le fichier n'a pas été trouvé", à "Je n'ai pas pu trouver le fichier demandé, vérifiez s'il existe toujours".
Si toi t'as mis comme "constante" : "Le fichier n'a pas été trouvé", c'est carrément source de mélangeage de pédales :-) si tu dois y mettre la 2de traduction par la suite.

Mais ça reste un point de vue hein :-) Je préfère ma façon de faire, sur le coup; mais chacun a sa façon de s'organiser, c'est normal :-)


Commentaire de codefalse le 08/11/2007 00:30:59 administrateur CS

nan mais ui, chuis d'accord avec toi, tu m'a bien démontré ton idée, et elle est très valable !
En fait mon idée se basait sur l'usage de gettext, sauf que cette fonction se base sur les variables systeme, ou les crées le cas échéant, mais du coup, c'est accessible pour tout le systeme, que ici, ce n'est pas le cas, du coup faire gettext ("mon message qui fait 20 lignes") ca perd tout son interet ! en effet ! :)

Sinon, tu pourrais m'expliquer vite fait que font tes modules s'il te plait ? car j'ai un peu du mal à les comprendre, et si je me trompe pas, il n'y en a pas dans le zip (donc impossible à traduire à partir du code :p)

Commentaire de malalam le 08/11/2007 08:00:18 administrateur CS

Si si il y en a 2 de modules :-)
Les dules ne "font" rien. C'est juste pour mieux organiser les traductions.
Je reprends le principe global de la structure de ma gestion des traductions dans le backoffice :
il permet une gestion multisites, donc le premier niveau, c'est le site web. Ensuite, chaque site à son internationalisation, puis ses langues.
Dans le répertoire des langues, il y a plusieurs fichiers xml 2 dans le zip) : ce sont les modules. Et dans les modules (donc dans les fichiers), il y a les traductions liées à ces modules.
Exemples de modules :
"error" : on y regroupera toutes les erreurs : error_file_not_found, error_page_not_found etc.
"menu" : on y regroupera les traductions de notre menu : menu_home, menu_source_codes, menu_forum... par exemple.

Le principe étant qu'un nom de module doit être un nom de fichier valide SANS underscore.
Un nom de "constante" de traduction doit être un nom d'élément xml valide, préfixé par le nom de son module suivi d'un underscore, puis ce que l'on veut.  Le choix de préfixer la constante par le nom de son module, là encore; c'est juste pour de la lisibilité : quand tu mates ton code, tu sais tout de suite quels modules sont utilisés par tes traductions.

Dans le zip, il y a 2 modules donc : "err" et "mx".

Pour gettext, si je ne remets pas en question l'intérêt de l'extension, je trouve sa mise en place trop complexe. Le fait que ce ne soit pas en built-in dans php oblige éventuellement à recompiler php...déjà, sur un serveur en prod, c'est chiant. Ensuite, faire un backoffice de gestion est plus compliqué, vu la structure des fichiers de traduction. Et au final, ça n'apporte pas grand chose de plus.
  

Commentaire de kankrelune le 08/11/2007 12:22:43 9/10

j'aime bien... le principe est bon... la réalisation aussi... .. .

J'ai longtemps hésité à utiliser les fichiers xml pour l'internationalisation de mes codes mais je travail avec des constantes et je ne peut me résoudre à les abandoner... lol... non c'est surtout que les constantes ont l'avantage de pouvoir être appelées de partout à tout niveau sans avoir à passer par la globalisation d'une variable, d'un objet ou d'avoir à passer par un singleton... mais le xml me séduit quand même comme format de stockage... plus souple... ou alors il faudrait que la class fasse les define() au moment du chargement du fichier mais je trouve ça moins propre et le problème après c'est que tu peux plus changer de langue en cours de route... .. .

@ tchaOo°

Commentaire de malalam le 08/11/2007 12:47:11 administrateur CS

Il y a un autre truc qui m'a fait penché pour le xml : ma classe gère l'absence de traduction.
Sur les sites internationaux où l'on a utilisé des constantes, si par malheur on oubli une constante dans une trad, on a une belle erreur undefined constant bla bla.
Là, avec ce principe, ce n'est plus le cas : plus d'erreur, et la traduction dans la langue par défaut est affichée (bon, il faut qu'elle soit définie dans la langue par défaut quand même lol, mais c'est une sécurité en plus). D'ailleurs je pense ajouter de ne rien afficher si ni la langue demandée ni la langue par défaut n'ont las traduction demandée (je ne sais plus si je l'ai fait ou non dans oLocale : le backoffice lui, gère ça très bien).

Merci en tous cas :-)

Commentaire de FhX le 03/01/2008 15:52:52

Mala ? J'ai pensé à un truc.

Pourquoi ne pas créer une classe dynamique au moment du parsing XML ? Ou alors, remplir des données dans une classe :

<?xml>
<root>
<translate id="nom_de_la_constante">La traduction qui va avec</translate>
<translate id="nom_de_la_constante_no2">L'autre traduction </translate>
</root>

<?php
// Eventuellement y mettre un Singleton au fesses, c'est plus sympa :D
class Language {
  private $tab = array();
  private $init = false;

   public function setInit($bool) {
    $this->init = $bool;
   }

   public function __set($id, $traduc) {
     if ( $this->init) $this->$tab[$id] = $traduc;
   }

   public function __get($id) {
     if ( !$this->init ) return $this->tab[$id];
   }

}

// Tu fais ton parsing XML, et tu introduis ton couple ID/traduc dans la classe
// Puis, l'appel ce fait comme ceci :
$lang = new Language(...);
echo $lang->ROOT;
echo $lang->WELCOME_HOME;
echo $lang->KIKOO;

// ET puis voila :) Eventuellement, la classe de langage peut se charger du parsing XML à l'init :
$lang = new Language('fr');
// Et on charge les paramètres français depuis le fichier XML francais.
Encore mieux, si on doit faire du multilangage sur un site :
$lang_fr = new Factory_Language('fr');
$lang_de = new Factory_Language('de');
// Et quand on est dans une classe, on rappèle la factory qui se charge de faire un singleton :
class x {
public function x() {
  $lang_fr = new Factory_Language('fr');
  // stuf...
}
}

Une solution rapide et assez efficace. Reste à savoir si ca tiens la route sur x00000 de visiteurs à la secondes :)

Commentaire de malalam le 04/01/2008 19:03:39 administrateur CS

Salut FhX :-)
Ca fait plaisir de te recroiser!
C'est une très bonne idée ce que tu proposes là! Ce serait plus joli à utiliser en effet, et plus pratique. Je vais me pencher là-dessus, merci :-)

Commentaire de stu76 le 28/01/2008 14:40:27

Juste pour dire BRAVO, nickel comme code je comprends pas tout. Mais je trouve super sympa de ta part de partager un tel code.

++

 Ajouter un commentaire


Discussions en rapport avec ce code source dans le forum

site multilingue avec les sessions [ par rastagnol ] bonjour ! je souhaiterais faire un site multilingue avec les sessions mais je ne sais pas trop comment m'y prendre.mon index est une page ou on peut c SITE MULTILINGUE...Je suis à la dérive !!! [ par kenny18 ] salut,Dans le cadre d'un test, je dois cr&#233;er un site multilingue. J'ai modifi&#233; &#224; ma sauce le script retrouv&#233; sur phpdebutant.org. MySQL et Multilingue [ par WhiteDwarf ] Salut, je reviens avec mes problèmes de gestion multilangue Alors voila le problème est simple, je récupère nom, prénom, mot de passe... dans un formu interface multilingue [ par farfoura25 ] salut,je travaille sur un site multilingue et je veux que la mise à jour soit automatique dans les différentes langues mais je sais pas comment commen Menu Multilingue en base de donnée à 2 ou 3 niveaux [ par Bringdal ] Bonjour,Je veux créer un menu multilingue en base de donnée à 2 ou 3 niveaux présenté sous cette forme :-Catégorie1-Catégorie2    -Sous_Catégorie1    site multilingue [ par gapson ] [^^confus2]bjr j'ai un open source php qui à été fait avec une architecture MVC. maitenant je veux l'adapter et le transformer en site multilingue et problème php Multilingue [ par bugs2011 ] Bonjour, quand je lance la page "index.php" du site Multilingue j'ai 2 messages d'erreur : Notice: Undefined index: lang in C:\Program Files (x86) site multilingue et multiextension [ par samt01 ] Bonjour, Voilà j'ai plusieurs url avec des extensions différentes : www.example.com www.example.fr www.example.it www.example.es et j'aimerais, en site multilingue [ par fankamdenise ] Bonjour Je voudrais concevoir un site multilingue ou du moins a deux langues francais et anglais. voici ce que j'utilise comme code: Dans ma page i


Nos sponsors


Sondage...

Comparez les prix

CalendriCode

Février 2012
LMMJVSD
  12345
6789101112
13141516171819
20212223242526
272829    

Consulter la suite du CalendriCode

 
Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils.
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés

Google Coop CodeS-SourceS Google Coop CodeS-SourceS
Temps d'éxécution de la page : 4,056 sec (4)

Nous contacter | Annoncer sur CodeS-SourceS | Mentions légales