français

a/X web 2 development

i18n = internationalisation (18 est le nombre de lettres entre le i du début et le n de la fin). On rencontre aussi souvent l10n.

Il s'agit des techniques à mettre en jeu pour qu'un programme, ou un site, soit capable d'afficher ses informations en plusieurs langues. Il y a deux méthodes recommandées :

Fichiers html multiples
C'est la méthode la plus évidente. Elle implique de dupliquer chaque fichier html composant le site, pour chaque nouvelle langue, et de le traduire. Il reste encore à définir une méthode pour accéder au bon jeu de fichier, si possible de manière transparente pour l'utilisateur. Cette méthode est adaptée aux pages contenant beaucoup de texte statique, ne changeant jamais ou très rarement. Elle ne convient pas du tout aux éléments dont le contenu est défini dynamiquement par programme.
Fichiers de traduction gettext
C'est la meilleure méthode pour les éléments dynamiques. Le programme est écrit de manière à générer le texte dans la langue par défaut de l'application (du site), mais il est capable de transposer chaque élément dans toute autre langue disponible. Utiliser gettext avec PHP n'est pas toujours simple, en particulier car il est nécessaire de disposer d'un module particulier sur le serveur, ce qui n'est pas toujours possible. Grâce au Zend Framework il est maintenant possible d'utiliser des fichiers de traductions gettext sans ce module.

Ce site utilise ces deux techniques. La maquette générale de la page est générée par programme, en particulier le menu, alors que le texte de chaque page est écrit en html. Le contrôleur général détermine automatiquement la langue à utiliser selon celle du navigateur de l'internaute et paramètre le module gettext qui transpose les textes programmés. Ensuite, le sous-contrôleur de vue sélectionne le fichier HTML dans cette langue, s'il est disponible.

Il est possible de choisir explicitement une autre langue :

index.php

<?php
if (isset($_SERVER["HTTP_ACCEPT"]) and stristr($_SERVER["HTTP_ACCEPT"],"application/xhtml+xml")) {
	header('Content-type: application/xhtml+xml');
} else 
header ('content-type: text/html; charset="utf-8"');

set_include_path(get_include_path() . PATH_SEPARATOR . '../../../includes/:..');
require_once 'lib/functions.php';

//session
require_once 'Zend/Session/Namespace.php';
$session = new Zend_Session_Namespace();

//languages, define available translation
require_once("Zend/Translate.php");
$tr = new Zend_Translate('gettext', 'i18n/fr.mo', 'fr');
$tr->addTranslation('i18n/en.mo', 'en');

if ($request->lang and in_array($request->lang, $tr->getList())) {
	//lang in request parameter
	$tr->setLocale($request->lang);
	// in session
	$session->lang = $request->lang;
}
elseif (isset($session->lang) ) $tr->setLocale($session->lang); // lang already set in session
else {
	//get best gess language from browser
	require_once 'Zend/Locale.php';
	$locale = new Zend_locale();
	if (in_array($locale->getLanguage(), $tr->getList())) $tr->setLocale($locale->getLanguage());
	else $tr->setLocale('en');
	// in session
	$session->lang = $tr->getLocale();
}

require_once 'hop/hop.php';

// target page
$sujet = $request->get('sujet', 'accueil');
if ($sujet == 'hop') $sujet_lang = "views/{$sujet}_en.html";
else $sujet_lang = "views/{$sujet}_{$session->lang}.html";
include 'adm/compteurs.php';

// cache management
require_once 'Zend/Cache.php';
$ref_file = "views/$sujet.php";
//check for most recently modified file
if (file_exists($sujet_lang)) {
	if (filemtime($sujet_lang) > filemtime($ref_file)) $ref_file = $sujet_lang;
} elseif (file_exists("views/{$sujet}_fr.html") and filemtime("views/{$sujet}_fr.html") > filemtime($ref_file)) $ref_file = "views/{$sujet}_fr.html";
if (filemtime("views/theme.php") > filemtime($ref_file)) $ref_file = "views/theme.php";

$cache_page = Zend_Cache::factory('File', 'File', array('lifetime' => null, 'master_file' => $ref_file), array('cache_dir' => 'cache')); //, 'automatic_serialization' => true
$cache_page_id = "theme_{$sujet}_{$session->lang}";
if (!(isset($session->style) and $session->style != 'views/a_x.css') and $cache_page->test($cache_page_id)) {
	$page_h =  $cache_page->load($cache_page_id);
} else {
	// page generation
	$page = include 'views/theme.php';
	//save in cache
	if (!$cache_page->test($cache_page_id)) $cache_page->save($page_h = $page->render(), $cache_page_id); //render and save if needed
	//no cache
	if (isset($session->style) and $session->style != 'views/a_x.css') {
		$page->head->feuille_style->set_href($session->style);
		if ($session->style == 'views/vide.css') $page->head->hop_menu->set_href($session->style);
		$page_h = $page->render();
	}
}

// output html
echo $page_h;
?>
	

views/theme.php

<?php

// génération page
// require_once 'hop/hop.php';

$page = Hop_page::factory(array('title' =>"a/X : {$tr->_("développement web 2")} - {$tr->_($sujet)}", 'lang' => $session->lang));

$page->head->add($hop->meta('keywords')->auto_name()->set_attr('content', $tr->_("développement web, indépendant, web2, web 2, xhtml, php, javascript, ajax, jquery, prototype, mysql")));
$page->head->add($hop->link('feuille_style')->auto_id()
	->set_rel('stylesheet')->set_type('text/css')->set_media('screen')->set_href('views/a_x.css'));

$page->head
	->add($hop->link()->rel('alternate')->type('text/html')->hreflang('fr')->href('?lang=fr')->title('français'))
	->add($hop->link()->rel('alternate')->type('text/html')->hreflang('en')->href('?lang=en')->title('English'))
	->add($hop->script()->set_type('text/javascript')->set_src('lib_js/prototype.js'))
	->add($hop->script()->set_type('text/javascript')->set_src('lib_js/effects.js'))
	->add($hop->script()->set_type('text/javascript')->set_src('lib_js/dragdrop.js'))
	->add($hop->script()->set_type('text/javascript')->set_src('js/a_x.js'))
	->add($hop->script()->set_type('text/javascript')->set_src('js/ajax_menu.js'))
;

//SVG vector image
//commented out : Safari is the only browser to display and scale
// $page->body->add($hop->object('fond_logo')->auto_id()
// 	->set_type('image/svg+xml')
// 	->set_data('views/logo.svg')
// 	);

//titre page
$page->body
	->add($hop->p($hop->a($tr->getLocale() == 'en' ? 'français' : 'English')->set_href($tr->getLocale() == 'en' ? '?lang=fr' : '?lang=en'), 'lang')->auto_id())
	->add($hop->h1('<span class="a_X">a/X</span> ' . $tr->_("développement web 2"),'titre_page')->auto_id())
	;

// menu
$menu = Hop_menu::factory(array(
	'id' => 'menu_g',
	'page' => $page,
	'css' => 'views/hop_menu_IE6.css',
	'items' => array(
		'accueil' => $hop->a($tr->_("accueil"))->set_href('accueil.html'),
		'contributions' => $tr->_("contributions")
		// ,'web2' => $hop->a("Ajax, web 2")->set_href('web2.html'),
		// 'xhtml' => $hop->a("XHTML")->set_href('xhtml.html'),
		// 'css' => $hop->a("CSS")->set_href('css.html'),
		// 'javascript' => $hop->a("JavaScript")->set_href('javascript.html'),
		// 'php' => $hop->a("PHP")->set_href('php.html'),
		// 'zend' => $hop->a("Zend framework")->set_href('zend.html'),
		// 'mvc' => $hop->a("MVC")->set_href('mvc.html'),
		// 'mysql' => $hop->a("MySQL")->set_href('mysql.html'),
		// 'i18n' => $hop->a("i18n")->set_href('i18n.html'),
		// 'spip' => $hop->a("CMS, SPIP")->set_href('spip.html')
		)))
	->sub_menu(Hop_menu::factory(array(
		'id'=>'logiciels',
		'items' => array(
			'hop' => $hop->a("Ho<sub>in</sub>P")->set_href('hop.html')
			// ,'menus' => $hop->a($tr->_("menus en CSS"))->set_href('menus.html')
			)))
	, 'contributions')
	->auto_id()
	;
if (isset($hop->pool()->$sujet)) $hop->pool()->$sujet->add_class('actif'); //pool access for submenu items
$page->head->hop_menu->auto_id(); //for javascript vivibility
//menu div
$page->body->add($hop->div('menu')->auto_id()->add($menu));

// subject zone
$page->body->add($hop->div('travail')->auto_id()->add(include('cache_sujet.php')));

//footer
$page->body->add($hop->p('<a href="http://validator.w3.org/check?uri=referer"><img src="http://www.w3.org/Icons/valid-xhtml10-blue" alt="Valid XHTML 1.0 Strict" height="15" width="44" /></a>')
	->set_class('pied'));

// return as object
return $page; 

?>
	

views/i18n.php

<?php
require_once 'lib/widgets.php';

$xhtml = new Hop_node;
if (file_exists("views/{$sujet}_{$session->lang}.html")) $f_source = "views/{$sujet}_{$session->lang}.html";
else $f_source = "views/{$sujet}_fr.html";

$xhtml->add(new Hop_node_from_file($f_source));

$xhtml->add(div_masque('index', htmlspecialchars(file_get_contents("index.php")), "index.php"));
$xhtml->add(div_masque('theme', htmlspecialchars(file_get_contents("views/theme.php")), 'views/theme.php'));
$xhtml->add(div_masque('i18n_', htmlspecialchars(file_get_contents("views/i18n.php")), "views/i18n.php"));
$xhtml->add(div_masque('en_po', htmlspecialchars(file_get_contents("i18n/en.po")), "i18n/en.po"));
$xhtml->add(div_masque('makefile', htmlspecialchars('
	appli = a_X

	php = $(wildcard $(appli)/*.php) $(wildcard $(appli)/views/*.php)

	trads_bin = $(patsubst %.po, %.mo , $(wildcard $(appli)/i18n/*.po))

	%.po : global.pot
		msgmerge $@ $< -o $@

	%.mo : %.po
		[ -f $@ ] && rm $@; msgfmt --no-hash $< -o $@

	gettext : $(trads_bin)

	global.pot : $(php)
		xgettext --foreign-user --from-code utf-8 -k_ -o $@ $(php) # 2> xgettext_out.pro  --no-location
		#grep -vE "attention: co|langage C" xgettext_out.pro || true
'), "makefile"));

$xhtml->add(div_masque('html_', htmlspecialchars(file_get_contents($f_source)), $f_source));

return $xhtml;

?>
	

i18n/en.po

# a_X Web2 development.
# This file is put in the public domain.
# FIRST AUTHOR Max Barel <a_x_nospam_@ac-mb.info>, 2007.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2007-11-25 23:38+0100\n"
"PO-Revision-Date: 2007-08-07 17:00+0200\n"
"Last-Translator: Max Barel <a_x_nospam_@ac-mb.info>\n"
"Language-Team: Max Barel <a_x_nospam_@ac-mb.info>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: a_X/views/javascript.php:20
msgid "extrait de"
msgstr "excerpt from"

#: a_X/views/theme.php:8
msgid ""
"développement web, indépendant, web2, web 2, xhtml, php, javascript, ajax, "
"jquery, prototype, mysql"
msgstr "web develpment, free-lance, web2, web 2, xhtml, php, javascript, ajax, "
"jquery, prototype, mysql"

#: a_X/views/theme.php:28
msgid "développement web 2"
msgstr "web 2 development"

#: a_X/views/theme.php:37
msgid "accueil"
msgstr "home"

#: a_X/views/theme.php:38
msgid "contributions"
msgstr ""

#: a_X/views/theme.php:54
msgid "menus en CSS"
msgstr "CSS menus"

#: a_X/views/xhtml.php:11
msgid "rendu d'objet HoP"
msgstr "rendered HoP object"

#~ msgid "logiciels"
#~ msgstr "software"

	

makefile

	appli = a_X

	php = $(wildcard $(appli)/*.php) $(wildcard $(appli)/views/*.php)

	trads_bin = $(patsubst %.po, %.mo , $(wildcard $(appli)/i18n/*.po))

	%.po : global.pot
		msgmerge $@ $< -o $@

	%.mo : %.po
		[ -f $@ ] && rm $@; msgfmt --no-hash $< -o $@

	gettext : $(trads_bin)

	global.pot : $(php)
		xgettext --foreign-user --from-code utf-8 -k_ -o $@ $(php) # 2> xgettext_out.pro  --no-location
		#grep -vE "attention: co|langage C" xgettext_out.pro || true

	

views/i18n_fr.html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  <title>i18n_fr</title>
  <meta name="generator" content="Amaya, see http://www.w3.org/Amaya/" />
</head>

<body>
	<p><em>i18n</em> = internationalisation (18 est le nombre de lettres entre le
	i du début et le n de la fin). On rencontre aussi souvent <abbr
	title="localization">l10n.</abbr></p>

	<p>Il s'agit des techniques à mettre en jeu pour qu'un programme, ou un
	site, soit capable d'afficher ses informations en plusieurs langues. Il y a
	deux méthodes recommandées :</p>
	<dl class="clear">
	<dt>Fichiers html multiples</dt>
	<dd>C'est la méthode la plus évidente. Elle implique de dupliquer
	      chaque fichier html composant le site, pour chaque nouvelle langue, et de
	      le traduire. Il reste encore à définir une méthode pour accéder au
	      bon jeu de fichier, si possible de manière transparente pour
	      l'utilisateur. Cette méthode est adaptée aux pages contenant beaucoup
	      de texte statique, ne changeant jamais ou très rarement. Elle ne
	      convient pas du tout aux éléments dont le contenu est défini
	      dynamiquement par programme.</dd>
	<dt>Fichiers de traduction gettext</dt>
	<dd>C'est la meilleure méthode pour les éléments dynamiques. Le
	      programme est écrit de manière à générer le texte dans la langue
	      par défaut de l'application (du site), mais il est capable de
	      transposer chaque élément dans toute autre langue disponible. Utiliser
	      <code>gettext</code> avec <code>PHP</code> n'est pas toujours simple,
	      en particulier car il est nécessaire de disposer d'un module
	      particulier sur le serveur, ce qui n'est pas toujours possible. Grâce
	      au <span class="contrib">Zend Framework</span> il est maintenant possible d'utiliser des fichiers de
	      traductions <code>gettext</code> sans ce module.</dd>
	</dl>
	<p>Ce site utilise ces deux techniques. La maquette générale de la page est
	générée par programme, en particulier le menu, alors que le texte de
	chaque page est écrit en html. Le contrôleur général détermine
	automatiquement la langue à utiliser selon celle du navigateur de
	l'internaute et paramètre le module <code>gettext</code> qui transpose
	les textes programmés. Ensuite, le sous-contrôleur de vue sélectionne le
	fichier <abbr title="HyperText Markup Language">HTML</abbr> dans cette langue, s'il est disponible.</p>
	<p>Il est possible de choisir explicitement une autre langue :</p>
	<form action="index.php">
		<div>
			<select onchange="this.form.submit()" name="lang">
				<option value=""></option>
				<option value="fr">Voir le site en français</option>
				<option value="en">This site in English (few pages translated)</option>
			</select>
		</div>
	</form>
	<ul>
		<li><a href="#index" onclick="Element.show($('index'))">index.php</a>: selection de la langue</li>
		<li><a href="#theme" onclick="Element.show($('theme'))">views/theme.php</a>:
			les expressions de la forme <code>\$tr->_("accueil")</code> sont des chaines de caractères traduite par le module <code>gettext</code>.</li>
		<li><a href="#en_po" onclick="Element.show($('en_po'))">Le fichier de traduction en anglais</a>:
			Ce fichier est automatiquement généré et mis à jour par les outils <code>gettext</code>. Il faut ensuite, bien sûr,
			ajouter les nouvelles traductions et mettre à jour celles qui ont changé et qui sont automatiquement repérées.</li>
		<li><a href="#makefile" onclick="Element.show($('makefile'))">le makefile</a>:
			Il montre l'utilisation des outils <code>gettext</code> pour générer et mettre à jour les fichiers de traduction à partir des sources.</li>
		<li><a href="#i18n_" onclick="Element.show($('i18n_'))">le sous contrôleur de page</a>:
			sélectionne le fichier <abbr title="HyperText Markup Language">HTML</abbr> approprié (français par défaut).</li>
		<li><a href="#html_" onclick="Element.show($('html_'))">Un fichier html</a>:
			le texte en <abbr title="HyperText Markup Language">HTML</abbr>, dans la langue sélectionnée.</li>
	</ul>

	<p></p>
</body>
</html>

	

Valid XHTML 1.0 Strict