Accueil Blog ⇉ Un site multilingue en PHP ? C'est simple !

PHP

SMARTY

illustration_article

Cet article ne traite que de l'aspect "technique" d'un site multilingue en PHP ; il ne traitera donc pas des différentes options concernant la traduction, le SEO ou autre débat entre deux plugins géniaux pour tel ou tel CMS encore plus génial.
Et attention : ce n'est PAS un routeur.


Préambule



Le (futur) client vient de partir en laissant son cahier des charges pour un proto de gestion multilingue pour son site à venir :
- Système monsite.com/ => fr - monsite.com/en/ =>en, etc...
- Pas d'usine à gaz, un truc simple, facilement compréhensible et surtout, qui marche.
- Pouvoir rajouter ou enlever des langues sans être obligé de tout démonter.
- Correspondance systématique de page en page : monsite.com/page1.php switche vers la page monsite.com/en/page1.php et non vers monsite.com/en/
- Aucun PHP dans le html (son webdesigner doit être fâché avec...)


Et nous avons immédiatement promis une démo grandeur nature dans 3 heures...

Nouvelle version


Dans cette nouvelle version, j'ai simplifié le processus :
- Seule la variable de la langue est traitée QUAND elle existe dans le htaccess.
- On n'appréhende plus la récupération des URL que d'une seule façon (Plus de redirection)

Les écueils et la stratégie




Il va nous falloir gérer principalement 4 questions (dans le désordre) :
1/ Récupérer la langue courante à la connexion utilisateur
2/ Offrir la possibilité de basculer vers une langue à chaque page.
3/ Proposer des URLs propres et "SEO-friendly".
4/ Gérer le changement de contenu en fonction de la langue

help “La seule chose requise est un accès au fichier htaccess, le reste c'est du pur standard.”


Pour ce faire, l'option retenue va être ultra-classique :
- pour le 1 et le 3, un RewriteRule dans le fichier .htaccess fera très bien l'affaire.
- Pour le 2, nous allons créer un petit switcher qui assurera le changement de langue et la récupe de la langue courante à la volée (par exemple, quand un visiteur arrive directement sur monsite.com/es/page1.php elle lui sera servie en espagnol.
- Et pour le 4, nous allons faire intervenir Smarty, qui en plus de ne pas traumatiser son webdesigner (qui est de fait notre nouveau meilleur ami), va permettre de stocker tout le texte statique.

Débutons...

Le htaccess


Nous allons régler de suite la question de la détection et des url, ce sera fait.
Notre URL finie sera (e.g.) : monsite.com/es/page1.php, et pour faire comprendre ceci facilement au serveur, nous allons partir sur un bête passage de paramètres :
monsite.com/page1.php?lang=es, ce qui est clair certes, mais horriblement moche. pour transformer ceci, rajoutons une ligne dans notre fichier htaccess :
RewriteRule ^(en|es|de)/(.*)$ $2?lang=$1 [L,QSA], qui signifie que lorsque le serveur va récupérer notre url contenant le 1er groupe ($1) soit "en/, es/, de/" il va aller rajouter derrière l'url (après $2, soit.*) lang=["le 1er groupe"($1)]; donc par exemple, "de". Bête comme chou.
En inspectant $_GET, nous aurons déjà une valeur lang => xx (sauf pour fr donc)
La langue par défaut n'étant pas dans l'url, on va la gérer dans le PHP (ça créait parfois des bugs dans l'ancien système).

Voilà la question "structurelle" des adresses réglée (c'est un simple rewrite en fait) ; passons maintenant à l'exploitation du code.

le PHP


De façon basique, notre système va être constitué d'une classe mère qui fera le sale boulot et qu'on étendra à la demande.

help “ATTENTION”


La fonction fondamentale qui va (presque) tout faire pour nous est parse_url() ; Il s'agit donc de récupérer l'URL soit via HTTP_REFERER ou autre.
Dans ce tuto, parse_url donnerait : "gb-net.fr" comme host, ce qui serait exact mais ne nous intéresse pas. J'ai donc un peu "triché" en mettant l'adresse des exemples en RewriteBase dans le fichier htaccess pour que ça fonctionne, mais je vous livre un exemple avec une adresse imaginaire : "https://monsite.com/ind1.php" qui elle va donner un bon aperçu sous la forme parsée :

Array
(
[scheme] => https
[host] => monsite.com
[path] => /ind1.php
)

et vous pouvez consulter ce fichier en stand-alone :
ici
Ici nous obtenons diverses infos :
- Les adresses des fichiers
- Les paramètres passés
- Et surtout, la langue demandée.

Exemple ultra-basique qui fait cependant tout le job :


Création du langage courant d'après l'URL récupérée

<?php

class LS
{

public function index()
{

// "fake" URL pour illustration
$uri = "https://monsite.com/ind1.php";

// Nouvelle variable de langage courant obtenue dans l'URL si $_GET['lang'] existe, sinon valeur langage par défaut (ici "fr")
$newLang = (isset($_GET['lang']) ? $_GET['lang'] : "fr");

//Tableau de correspondance entre langues et données d'URL
$languages = [
'en' => 'en/',
'de' => 'de/',
'es' => 'es/',
'fr' => ''
];

//On parse l'URL ("fake" ici)
$url = parse_url($uri);

//Insertion des valeurs du tableau $languages -> On enlève le slash de l'index "path" de l'url parsée (cf le tableau $languages)
$newUrl = $url['scheme'] . '://' . $url['host'] . '/' . $languages[$newLang] . str_replace('/', '', $url['path']);

//On retourne la valeur langue récupérée et la nouvelle URL (pour l'exemple)
return [$newLang, $newUrl];
}

}
?>


A partir de ces quelques lignes, nous allons pour voir construire un genre de menu généré en fonction de la page elle-même ainsi (enfin) qu'un changement de langue effectif :


Et le code :

<?php

class LS2
{
public function index()
{
$fke = "http://gbnet2.fr/assets/articles/19/samples/sample2/"; //adresse réelle sinon rien ne marchera
$her = [];
$fake = [];
$uri = "https://monsite.com/ind2.php";
$newLang = (isset($_GET['lang']) ? $_GET['lang'] : "fr");
$languages = [
'en' => 'en/',
'de' => 'de/',
'es' => 'es/',
'fr' => ''
];
$url = parse_url($uri);

//On ne se sert plus de cette ligne sauf si on veut récupérer l'adresse courante
// $newUrl = $url['scheme'] . '://' . $url['host'] . '/' . $languages[$newLang] . str_replace('/', '', $url['path']);

//On parcourt le tableau des languages
foreach($languages as $k=>$v) {

//On range dans un tableau chaque clé (la langue ici) associée à son URL respective à partir du parse_url()
$her[] = [$k, $url['scheme'] . '://' . $url['host'] . '/' . $v . str_replace('/', '', $url['path'])];
}
//Emplacement réel qui prend la place de $her pour la démo
foreach($languages as $k=>$v) {
$fake[] = [$k, $fke . $v . str_replace('/', '', $url['path'])];
}

//On retourne la langue courante (toujours utile) et le tableau clés/urls
return [$newLang, $fake];
}
}
?>


Ce qui donne pas-à-pas :
Le tableau $fake tel qu'on le récupère :


Array <!-- Il n'y aura qu'à boucler sur ce tableau pour construire le menu-->
(
[0] => Array
(
[0] => en
[1] => http://gbnet2.fr/assets/articles/19/samples/sample2/en/ind2.php
)

[1] => Array
(
[0] => de
[1] => http://gbnet2.fr/assets/articles/19/samples/sample2/de/ind2.php
)

[2] => Array
(
[0] => es
[1] => http://gbnet2.fr/assets/articles/19/samples/sample2/es/ind2.php
)

[3] => Array
(
[0] => fr
[1] => http://gbnet2.fr/assets/articles/19/samples/sample2/ind2.php
)

)


Et à titre d'illustration, le code pour l'afficher comme au-dessus :

 <?php
include "LS2.php";
$ls = new LS2();
$result = $ls->index();
$opt = "virtuelle";
$text_fr = "Je suis toujours la même page avec quelques petites choses en plus et ma langue est";
$text_en = "I'm still the same page with a few more things and my language is";
$text_es = "Sigo en la misma página con algunas cosas más y mi idioma es";
$text_de = "Ich bin immer noch auf der gleichen Seite mit ein paar weiteren Dingen und meine Sprache ist es";
$text = "text_".$result[0];

switch($result[0]) {
case "fr" : $opt = "réelle";
break;
}
echo "<pre>".$$text." :[";
print_r($result[0]);
echo "] ".PHP_EOL;

echo "<pre>";
print_r("Menu valide obtenu de la page");
echo "</pre>";
foreach($result[1] as $k) {
echo '<a href="'.$k[1].'">'.$k[0].'</a>&nbsp;&nbsp;';
}
?>


Notez que ce tableau permet également de créer les balises hreflang

Voilà l'idée ; à ce stade, le principe de gestion des langues est globalement acquis ; comme vous le voyez, j'ai évidemment puissamment triché en mettant en dur les textes (notre ami webdesigner va en tomber tout raide) aussi, on ne va pas rester comme ça, et c'est là que va intervenir Smarty.

La finition avec Smarty


On le trouve ici https://www.smarty.net/
L'installation est très simple, et une fois les répertoires et fichiers en ordre de route, je vous recommande d'aller jusqu'au chapitre "extended setup", c'est mieux pour obtenir une fois pour toute les chemins requis.
(Je me borne ici à ce dont j'ai besoin, Smarty est assez fourni, la doc est très simple à comprendre et il y a vraiment peu de pièges)
Pour impressionner définitivement notre client, nous allons faire deux pages : un index et une autre page qui contiendra un appel ajax qui renverra, lui, des variables de langues.

Tout d'abord, créons un répertoire "lang" dans Smarty et dedans, mettons nos fichiers, "en.conf", "de.conf", "es.conf" et "fr.conf". Ce sont les fichiers où l'on va stocker les textes statiques (je n'ai pas précisé mais il va de soi que les textes dynamiques viennent d'ailleurs, genre bdd) ; stockage qui se fait de cette façon :
fr.conf:
fle = Je suis une jolie fleur
en.conf:
fle = I am a beautiful flower
Ces fichiers vont se récupérer dans la classe via configLoad("path/to/*.conf") par exemple (ce qui donnera :

$this->smarty->configLoad("smarty/lang/".$this->data['newLang'].".conf");

-- on peut également les récupérer directement dans la template -- et les y intégrer comme ceci : {#fle#}
Pour ce faire, nous allons modifier quelque peu nos fichiers, en particulier pour que les infos de la classe LS3 parviennent jusqu'à la template:

<?php

//on récupère notre smarty
require_once "smarty/setup.php";


class LS3
{

//On rend publiques quelques variables, en particulier $data, le tableau qui va convoyer les données vers le fichier tpl
//public $newLang; on n'en a plus besoin, elle sera insérée dans le tableau $data.

//Le tableau qui enverra à la template et qui contiendra l'essentiel de nos données
public $data;
public $smarty;

//On crée un constructeur
public function __construct()
{
$this->data['newLang'] = (isset($_GET['lang']) ? $_GET['lang'] : "fr");
$this->smarty = new Smarty_GBNet();

//On charge le fichier lang correspondant à la langue courante
$this->smarty->configLoad("smarty/lang/".$this->data['newLang'].".conf");
}

//$name : facultatif, une url réelle permet de s'en passer, c'est pour suppléer cette absence que je m'en sers ici
public function index($name)
{

$fke = "http://gbnet2.fr/assets/articles/19/samples/sample3/";
$her = [];
$fake = [];
$uri = "https://monsite.com/".$name;

$languages = [
'en' => 'en/',
'de' => 'de/',
'es' => 'es/',
'fr' => ''
];
$url = parse_url($uri);
// $newUrl = $url['scheme'] . '://' . $url['host'] . '/' . $languages[$newLang] . str_replace('/', '', $url['path']);

foreach($languages as $k=>$v) {
$her[] = [$k, $url['scheme'] . '://' . $url['host'] . '/' . $v . str_replace('/', '', $url['path'])];
}
foreach($languages as $k=>$v) {
$fake[] = [$k, $fke . $v . str_replace('/', '', $url['path'])];
}
$this->data['fake'] = $fake;

//On retourne l'ensemble du tableau $data
return $this->data;
}
}

?>


Notre fichier php courant :

<?php
include "LS3.php";

class ind3 extends LS3 {

//Le nom du fichier concerné
public $filename = "ind3.php";

public function __construct()
{
//Récupération des données
parent::__construct();
$this->data = parent::index($this->filename);
}

public function main()
{

$this->data['bjr'] = "Bonjour";

//On assigne les données à la template
$this->smarty->assign($this->data);

//On charge la template en question
$this->smarty->display("smarty/templates/index.tpl");
}
}

$ind = new ind3();
$ind->main();
?>



et le fichier tpl :


<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html" />
</head>
<body>
<span class="text12">{$bjr}, ma langue est "{$newLang}"</span> <!--On affiche les données : $bjr a été créé dans le fichier ind3 et $newLang dans la classe LS3
</body>
</html>


Si vous obtenez quelque chose de lisible, c'est que ça marche :



help “Gooooooo...!”


Il ne nous reste plus qu'à appliquer ce principe partout sur notre démo... Et voilà notre présentation !


La page est visible ici
Je ne détaille pas l'ensemble du code car une fois affichée une variable comme au-dessus, l'essentiel est fait, mais voici quelques petits trucs qui peuvent être utiles.

help “Les délimiteurs”


Les délimiteurs par défaut sont '{' et '}' ce qui peut poser problème avec Javascript en particulier ; vous avez deux solutions :
- Encadrer le JS par {literal}fonction myFunction(){
//...;
}
{/literal}
ce qui désactive l'interprétation du JS par Smarty. Bon, c'est très pénible de mon point de vue, heureusement, une alternative existe :
- Dans le fichier /libs/Smarty.class.php, on trouve ceci :

 public $left_delimiter = "{";
public $right_delimiter = "}";


Il suffit donc de changer les délimiteurs une fois pour toutes pour ne plus avoir à se soucier de la question (perso, je mets {% et %}.

help “Le cache”


Smarty présente un système de cache ultra-performant qui peut être utilisé en multilingue statique en créant un répertoire cache par langue dans lequel il ira chercher la bonne template compilée qu'on lui indiquera par exemple dans le fichier setup.php en faisant quelque chose comme ça :

  $this->setCacheDir('App/Config/smarty/Cache/'._CONST_LANG_.'/');

où la constante est définie avant.
Attention, avec les sites dynamiques, pas de cache. - ou alors en potassant bien la doc et les options -

help “Les templates”


Un avantage non négligeable est qu'on peut charger les templates ou bien via la classe, ou bien par une autre template ; ça permet principalement de découper le code en petits morceaux et de ne pas avoir à s'appuyer d'énormes quantités de lignes ; par exemple, le menu ici est chargé via les deux templates principales sous la forme{include file="menu.tpl"} ; n'hésitez pas à consulter la doc pour connaître les avantages des différentes façons de faire

help “Optimisation”


On sait que dans les urls la langue est toujours sous la forme de deux lettres, on sait également que les fichiers lg.conf sont tous situés dans le même répertoire. Rien ne nous empêche d'automatiser à 1000% notre process (cf le point 3 du CC) ; pour cela, il suffit de boucler dans le répertoire des fichiers .conf (avec qqch comme

glob($dir, "*.conf");

, à partir de ça on obtient le tableau des fichiers, on supprime les ".conf" et ça nous fait un tableau de langages ; pour coller à notre tableau $languages on définit une langue par défaut et on mettra une condition dans la boucle pour que la clé soit associée à une valeur "", et enfin ouvrir notre htaccess pour qu'il accepte toutes les combinaisons répondant à un schéma général :RewriteRule ^([a-z]{2})/(.*)$ $2?lang=$1 [L,QSA] ou [a-z] n'accepte que les lettres minuscules et {2} au nombre exact de deux.
De cette façon, on ne touche même plus au code, il suffit d'ajouter un fichier pour ajouter une langue. (voir pour un éventuel drapeau)



Voilà donc une idée générale pour cette gestion des langues ; j'ai essayé d'être le plus clair possible mais si un ou plusieurs points demeurent obscurs, n'hésitez pas à me contacter, ou dessous, ou via mon mail ou le formulaire de la page d'accueil gb-net.fr/contact/ !

*****Note importante******
Je me répète, mais pour les besoins des démos, je n'ai pas pu passer par parse_url(), il est important de garder en tête que dans des conditions normales https://monsite/lg/monurl, il est considérablement plus simple de l'utiliser comme indiqué dans les premiers exemples.



Modifié le 18-01-2024
Article précédent


HTML : Aligner le dernier élément d'une liste
Article suivant


Faire une transition CSS height: auto avec display:grid

Commentaires

 Cédric  2020-09-17

Excellent article !

j'aurais bien aimé savoir comment faire pour obtenir la date (comme dans le 2è exemple avec les fleurs) avec Samrty ?

Si vous préférez, mon email : clacant334@gmail.com

merci d'avance

 GBNet  2020-09-17

Bonjour Cédric et merci pour votre commentaire !

Tout simplement avec sprintf() et strftime() plus une config locale.
Par exemple :
dans le fichier lang de smarty:
maDate = Nous sommes au mois de %s

et donc en PHP ça donnera un truc comme :
setlocale(LC_ALL, 'fr_FR', 'fr');
echo sprintf($smarty->getConfigVars('maDate'), strftime('%B %G'));


où strftime formate la date d'après la locale indiquée, et sprintf l'incruste dans la variable de smarty.
Les docs ici :
strftime
sprintf
setlocale

Cordialement
Guillaume

 GBNet  2022-02-21

A noter qu'avec PHP8.1 strftime() est rendu obsolète, j'ai donc rajouté en fin d'article ce qui est recommandé en terme de remplacement.

 David J.  2023-01-13

Salut,

Quand on essaie de changer de langue dans l'exemple avec la redirection, ça donne "File not found".

Sinon, c'est un très bon article, surtout la partie avec Smarty que je connaissais de nom, mais sans l'avoir essayé ; je vais m'y mettre.
Cord.
David

 GBNet  2023-01-29

Bonjour David,
Merci pour le retour (et l'appréciation)

En effet, un petit ratage suite à un changement de serveur, le mal est (en principe) réparé.

Cordialement
Guillaume

 Pauline  2023-02-13

Bonjour,

Merci pour cet article ! Mon site est configuré comme ça mais je ne sais pas comment gérer son référencement maintenant... :(

Merci d'avance !

Pauline

 GBNet  2023-02-13

Bonjour Pauline,

Vous pouvez le gérer comme n'importe quel site multilingue, avec les précautions d'usage inhérentes à ce genre de structure, en particulier un soin rigoureux à apporter aux balises hreflang.

Si vous le souhaitez, vous pouvez me passer son adresse par email, contact@gb-net.fr, je ferai un petit check-up.

Cdt
Guillaume

 M.A  2023-02-25

Bonjour,
J'ai utilisé votre tuto pour mon site internet en utilisant la méthode du HTACCESS et ça fonctionne super bien.

Mais si je veux faire en sorte que ma page1.php soit redirigée vers dossier/id-alias.html, j'ai la page "Internal Server Error".

Exemple : RewriteRule ^dossier/([0-9]+)-([a-z0-9\-]+).html$ page1.php?lang=fr&id=$1&alias=$2 [L]

Comment remédier à ça ?
Merci par avance,
Cordialement

 GBNet  2023-02-27

Hello MA,
Il me semble vaguement que le groupe lang=fr n'est pas traité dans la règle et donc que ça ne correspond pas au schéma annoncé. Perso je le ferais en deux lignes distinctes, le traitement de ?id&alias, et la langue.

Si vous voulez un coup de main, on peut continuer contact@gb-net.fr

Cdt
Guillaume

 Fred  2024-02-01

Bonjour,
J'utilise votre tutoriel, mais il y a un truc que je ne comprend pas:
Je redirige systématiquement vers /fr/ via le htaccess (site en dev) mon index, jusque la tout va bien, c'est ce que je veux.
Mais si je passe en EN via le fameux get, j'ai systématiquement ?lang=en en fin d'URL, peu importe la page. Le parametre $_GET ne passe pas.
Je precise que je suis une truffe en PHP mais j'm'y colle !

 GBNet  2024-02-23

Bonjour Fred,
Sans doute à voir avec le htaccess, mais puisque les exemples sont devenus caducs avec la nouvelle configuration, je vais en profiter pour reposter une autre manière de faire, plus simple, où la gestion de la langue courante sera gérée dans le PHP.
Donnez-moi quelques jours pour peaufiner la chose !

 GBNet  2024-02-27

Chose promise chose due, je viens de modifier l'article.
Cette nouvelle version est plus simple :
La langue courante est traitée dans le PHP et plus dans le htaccess.
Le "switcher" ne récupère plus la langue que d'une seule façon, j'ai essayé d'améliorer la gestion de Smarty en fin d'article.

 Gaby  2024-03-11

Super tuto !
Juste une remarque, je n'avais pas bien fait attention au "$$text" avec les 2 "$" et j'ai bien galéré à trouver où était mon erreur mais à part ça c'est super

Publier un commentaire :



capcha   



Raccourcis : php css html sql js img

Prévisualiser
GB-Net.fr 2024



fleche haut
Résultats de votre recherche pour :
reduce close