Image de l'article Internationaliser un site (partie 2)

Internationaliser un site (partie 2)

Dans la première partie de cet article je vous avais montré une solution simple pour faire un site multi-langues en utilisant des variables.

Cette solution facile à mettre en place présente néanmoins quelques inconvénients. Notamment si un texte n'a pas été traduit.

En effet, en PHP, lorsque vous affichez une clé de tableau inexistante, rien ne se passe. Visuellement il n'est pas possible de voir si un texte n'est pas traduit dans une ou toutes les langues.


$text = array();
echo '<h1>'.$text['titre'].'</h1>';
// <h1></h1>

De plus il n'est pas possible de passer des variables dans le texte. Imaginons que vous vouliez afficher le nombre de produit enregistrés dans une catégorie.


$produit = 52;
$catégorie = 'Croquette pour chat';
echo '<p>Il y a '.$produit.' produits dans la catégorie "'.$catégorie.'" !!</p>';
// <p>Il y a 52 produits dans la catégorie "Croquette pour chat" !!</p>

Pour afficher ce résultat sur un site multi-langues vous devrez morceler votre phrase :


$text1 = "Il y a ";
$text2 = " produits dans la catégorie ";
$text3 = " !!";
echo '<p>'.$text1.$produit.text2.$catégorie.text3.'</p>';
// <p>Il y a 52 produits dans la catégorie Croquette pour chat</p>

$text1 = "There are ";
$text2 = " products in the category ";
$text3 = " ¿?";
echo '<p>'.$text1.$produit.text2.$catégorie.text3.'</p>';
// <p>There are 52 products in the category Croquette pour chat ¿?</p>

Pouah ! C'est pas concevable de travailler comme ça ! Heureusement pour vous j'ai conçu la class IziTranslate.

Faite un site multi-langue facile avec la class IziTranslate

Pour palier à ces manques j'ai codé la class IziTranslate. Son fonctionnement est simple :

  1. Créez une instance de la class.
  2. Ajoutez les différentes langues que vous souhaitez utiliser (1 ou 2, inutile d'en charger 50).
  3. Définissez la langue de référence.
  4. Définissez la langue d'affichage souhaitée.

La class vous permet d'intégrer des variables dans vos textes. Si un texte vient à manquer dans la langue en cours, la class cherchera automatiquement le texte dans la langue de référence.

Le code documenté


// class IziTranslate
class IziTranslate{
	protected $txt = array(); // tableau contenant les textes
	protected $langLst = array(); // tableau contenant la liste des langues
	protected $langRef = ''; // identifiant de la langue de référence
	protected $lang = ''; // identifiant de la langue qui sera renvoyée
	protected $pathLang = ''; // chemin vers le dossier de langue
	protected $debugMode = 0; // mode debug

Constructeur

Initialise la class IziTranslate.

Argument :


function __construct($aPath){
	$this->pathLang = $aPath;
}

Fonction AddLanguage

Ajoute une langue. Pour changer de langue il faudra utiliser la fonction SetLanguage.

Arguments :


public function AddLanguage($aId, $aName=''){
	// vérifie si la langue est déjà enregistré
	if(isset($this->langLst[$aId]) || empty($aId)) return 0;
	// enregistre la langue et son nom
	$this->langLst[$aId] = $aName;
	$this->txt[$aId] = array();
	// défini la langue en cours si ce n'est pas déjà fait
	if($this->lang=='') $this->lang = $aId;
	// défini la langue de référence si ce n'est pas déjà fait
	if($this->langRef=='') $this->langRef = $aId;
	return 1;
}

Fonction SetLanguage

Définit la langue en cours.

Argument :


public function SetLanguage($aId){
	// vérifie si la langue est déjà enregistré
	if(!empty($this->langLst[$aId])) return 0;
		$this->lang = $aId;
	return 1;
}

Fonction LoadTxt

Charge un fichier dans toutes les langues. Pour cette class j'ai fait le choix de fichier de texte au format JSON. Le contenu de ces fichiers de texte est un tableau associatif php. Vous pouvez charger plusieurs fichiers, cela ne posera pas de problème tant que les identifiants de texte sont unique.

Argument :


public function LoadTxt($aPath){
	foreach($this->langLst as $k=>$v){
		$p = $this->pathLang.'/'.$k.'/'.$aPath;
		// verifie si le fichier existe
		if(file_exists($p)){
			// charge le fichier au format JSON, le décode et l'intégre aux texte déjà chargés
			$this->txt[$k] = array_merge($this->txt[$k], json_decode(file_get_contents($p), true));
		}
	}
	return 1;
}

Fonction SetRefLanguage

Définit la langue de référence

Argument :


public function SetRefLanguage($aId){
	// vérifie si la langue existe
	if(empty($this->langLst[$aId])) return 0;
	// enregistre
	$this->langRef = $aId;
	return 1;
}

Fonction GetTxt

Renvoi un texte d'après son identifiant. Vous pouvez passer des valeurs de remplacement. Pour cela la fonction va chercher des occurences de type pourcentage+nombre+pourcentage. Vous pouvez passer une chaine de caractère si il n'y a qu'une valeur à remplacer ou un tableau.

Arguments:


public function GetTxt($aId, $aReplace=''){
	// renvoi un texte dans la langue en cours
	// si le texte n'existe pas dans la langue en cours,
	// la class essaye de le renvoyer dans la langue de référence
	$r = (empty($this->txt[$this->lang][$aId]) ? $this->txt[$this->langRef][$aId]:$this->txt[$this->lang][$aId]);
	if(empty($r)){
		// si le texte n'existe pas non plus dans la langue de référence
		// et que le mode debug est activé, renvoi un texte d'erreur
		if($this->debugMode) $r = '[KEY NOT FOUND : '.$aId.']';
		
	}else if(isset($aReplace)){

		// il y a des valeurs de remplacement
		// la fonction accepte un tableau (array) ou un texte (string)
		// génère le tableau ou le texte à rechercher
		if(is_array($aReplace)){
			$nb = count($aReplace);
			$s = array();
			for($i=1; $i<=$nb; $i++){
				$s[] = '%'.$i.'%';
			}
		}else $s = '%1%';
		$r = str_replace($s, $aReplace, $r);
	}
	return $r;
}

Fonction DebutModeActive

Active le mode debug


public function DebutModeActive(){
	$this->debugMode = 1;
}
}

Utilisation de la class IziTranslate

Imaginons une structure avec un dossier lang tel que :

exemple de hierarchie d'un site multi langue

Pour l'exemple voici le contenu du fichier lang/fr/lang.json :


{"title":"Les chaussures de la vallée","prodTitle1":"Nous avons %1% produits dans cette catégorie","prodTitle2":"Nous avons %1% produits dans la catégorie %2%..."}

Notez que le texte prodTitle1 possède une valeur de remplacement (%1%) et prodTitle2 en possède deux (%1% et %2%). Vous pouvez utiliser plusieurs fois la même valeurs de remplacement.


// import la classe IziTranslate
require 'IziTranslate.class.php';

// initialise la langue
$lang = isset($_GET['lang']) ? $_GET['lang']:'fr';

// crée l'objet
// paramètre :
//		- le nom du dossier qui contient les sous dossiers de langue
$IziTranslate = new IziTranslate('lang');

// ajoute les langues
// paramètres :
//		- le nom du sous dossier qui contient la langue et qui servira d'identifiant
//		- le nom de la langue (facultatif)
$IziTranslate->AddLanguage('fr');

// si la langue du document n'est pas le français, on rajoute la langue
if($lang!='fr') $IziTranslate->AddLanguage($lang);

// définit la langue de référence
// paramètre :
//		- l'identifiant de la langue
$IziTranslate->SetRefLanguage('fr');

// définit la langue en cours
// paramètre :
//		- l'identifiant de la langue
$IziTranslate->SetLanguage($lang);

// active le mode débug
$IziTranslate->DebutModeActive();

// charge les fichiers de texte
// paramètre :
//		- le nom du fichier dans le sous dossier de la langue
$IziTranslate->LoadTxt('lang.json');

Pour faciliter l'usage de la class, on crée une fonction globale qui va appeller l'objet. Vous pouvez appeller cette fonction comme vous voulez, l'essentiel c'est que le nom soit suffisemment court pour ne pas surcharger le code.


// fonction d'appel global
// cela simplifie la lecture du code d'utiliser une fonction plutôt que d'utiliser un objet
function lTxt($id, $replace=''){
	global $IziTranslate;
	return $IziTranslate->GetTxt($id, $replace);
}

Pour afficher un texte, on appelle notre fonction globale et on passe en argument l'identifiant du texte.


echo '<h1>'.lTxt('title').'</h1>';
// <h1>Les chaussures de la vallée</h1>

Vous pouvez passer une variable pour que la class l'intégre dans le texte.


echo '<p>'.lTxt('prodTitle1', 35).'</p>';
// <p>Nous avons 35 produits dans cette catégorie</p>

Vous pouvez même passer plusieurs variables sous forme de tableau.


echo '<p>'.lTxt('prodTitle2', array(35, "chaussure de ville")).'</p>';
// <p>Nous avons 35 produits dans la catégorie chaussure de ville...</p>

Le mode debug permet d'afficher une erreur si un texte n'est trouvé ni dans la langue demandée, ni dans la langue de référence.


$obj->DebutModeActive();
$html .= '<p>'.lTxt('cette clé n existe pas').'</p>';
// <p>[KEY NOT FOUND : cette clé n existe pas]</p>

Récupitulatif complet de la class IziTranslate

Voici le code de la class IziTranslate, vous n'avez qu'à le copier coller dans un fichier que vous nommerez 'IziTranslate.class.php'


// class IziTranslate
// https://blog.niap3d.com/fr/1,10,news-45-Internationaliser-un-site-partie-2-.html
class IziTranslate{
	protected $txt = array();
	protected $langLst = array();
	protected $langRef = '';
	protected $lang = '';
	protected $pathLang = '';
	protected $debugMode = 0;
	//
	// constructeur
	//
	// argument :
	//		$aPath = chemin vers le dossier qui contient les sous dossiers de langues
	//
	function __construct($aPath){
		$this->pathLang = $aPath;
	}
	//
	// ajoute une langue
	//
	// argument :
	//		$aId = identifiant de la langue. l'identifiant est aussi le nom du dossier qui contient les textes
	//		$aName = nom de la langue [facultatif]
	//
	public function AddLanguage($aId, $aName=''){
		// vérifie si la langue est déjà enregistré
		if(isset($this->langLst[$aId]) || empty($aId)) return 0;
		// enregistre la langue et son nom
		$this->langLst[$aId] = $aName;
		$this->txt[$aId] = array();
		// défini la langue en cours si ce n'est pas déjà fait
		if($this->lang=='') $this->lang = $aId;
		// défini la langue de référence si ce n'est pas déjà fait
		if($this->langRef=='') $this->langRef = $aId;
		return 1;
	}
	//
	// définit la langue en cours
	//
	// argument :
	//		$aId = identifiant de la langue
	//
	public function SetLanguage($aId){
		// vérifie si la langue est déjà enregistré
		if(!empty($this->langLst[$aId])) return 0;
		$this->lang = $aId;
		return 1;
	}
	//
	// charge un fichier dans toutes les langues
	//
	// argument :
	//		$aPath = fichier à charger
	//
	public function LoadTxt($aPath){
		foreach($this->langLst as $k=>$v){
			$p = $this->pathLang.'/'.$k.'/'.$aPath;
			// verifie si le fichier existe
			if(file_exists($p)){
				// charge le fichier au format JSON, le décode et l'intégre aux texte déjà chargés
				$this->txt[$k] = array_merge($this->txt[$k], json_decode(file_get_contents($p), true));
			}
		}
		return 1;
	}
	//
	// définit la langue de référence
	//
	// argument :
	//		$aId = identifiant de la langue
	//
	public function SetRefLanguage($aId){
		// vérifie si la langue existe
		if(empty($this->langLst[$aId])) return 0;
		// enregistre
		$this->langRef = $aId;
		return 1;
	}
	//
	// renvoi un texte
	//
	// argument :
	//		$aId = identifiant du texte
	//		$aReplace = tableau de remplacement
	//
	public function GetTxt($aId, $aReplace=''){
		// renvoi un texte dans la langue en cours
		// si le texte n'existe pas dans la langue en cours,
		// la class essaye de le renvoyer dans la langue de référence
		$r = (empty($this->txt[$this->lang][$aId]) ? $this->txt[$this->langRef][$aId]:$this->txt[$this->lang][$aId]);
		if(empty($r)){
			// si le texte n'existe pas non plus dans la langue de référence
			// et que le mode debug est activé, renvoi un texte d'erreur
			if($this->debugMode) $r = '[KEY NOT FOUND : '.$aId.']';

		}else if(isset($aReplace)){
			// il y a des valeurs de remplacement
			// la fonction accepte un tableau (array) ou un texte (string)
			// génère le tableau ou le texte à rechercher
			if(is_array($aReplace)){
				$nb = count($aReplace);
				$s = array();
				for($i=1; $i<=$nb; $i++){
					$s[] = '%'.$i.'%';
				}
			}else $s = '%1%';
			$r = str_replace($s, $aReplace, $r);
		}
		return $r;
	}
	//
	// active le mode debug
	//
	public function DebutModeActive(){
		$this->debugMode = 1;
	}
}

Historique

14/09/15

Bug à la ligne 98 :

}else if($aReplace!=''){

Si la valeur de remplacement est 0, la fonction ne fonctionnait pas et renvoyer le texte sans faire de remplacement.
Il suffit de remplacer la ligne 98 par :

}else if(isset($aReplace)){

Le prochaine article vous présentera une class pour faciliter la gestion des fichiers textes et leur traduction.

Si vous avez des questions ou besoin d'éclaircissement n'hésitez pas, les commentaires sont là pour ça.

Accédez aux différentes parties de l'article

 

Image Viewer