<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of Plume Framework, a simple PHP Application Framework.
# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
#
# Plume Framework is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Plume Framework 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* Translation utility.
*
* This class provides utilities to load and cache translation
* strings. The functions using the values are directly available when
* loading Pluf. They are __ and _n for simple translations and for
* plural dependent translations respectively.
*
* Based on benchmarking by
* http://mel.melaxis.com/devblog/2006/04/10/benchmarking-php- \
* localization-is-gettext-fast-enough/ the string id system is really
* fast, so here the system is using a .ini file approach with a
* string id cache.
*
* Why reimplementing a gettext system when one is already available?
* It is because the PHP gettext extension requires the corresponding
* locale to be installed system wide to load the corresponding
* translations. If your host has no locales outside English
* installed, you can only provide English to your users. Which is not
* really nice.
*
*/
class Pluf_Translation
{
public static $plural_forms = array(
'fr' => 'plural_2gt1',
'en' => 'plural_2not1', // This is the default.
'de' => 'plural_2not1',
);
public static function loadSetLocale($lang)
{
$GLOBALS['_PX_current_locale'] = $lang;
setlocale(LC_TIME, array($lang.'.UTF-8',
$lang.'_'.strtoupper($lang).'.UTF-8'));
if (isset($GLOBALS['_PX_locale'][$lang])) {
return; // We consider that it was already loaded.
}
$GLOBALS['_PX_locale'][$lang] = array();
foreach (Pluf::f('installed_apps') as $app) {
if (false != ($pofile=Pluf::fileExists($app.'/locale/'.$lang.'/'.strtolower($app).'.po'))) {
$GLOBALS['_PX_locale'][$lang] += Pluf_Translation::readPoFile($pofile);
}
}
}
public static function getLocale()
{
return (isset($GLOBALS['_PX_current_locale'])) ? $GLOBALS['_PX_current_locale'] : "en";
}
/**
* Get the plural form for a given locale.
*/
public static function getPluralForm($locale)
{
if (isset(self::$plural_forms[$locale])) {
return self::$plural_forms[$locale];
}
if (isset(self::$plural_forms[substr($locale, 0, 2)])) {
return self::$plural_forms[substr($locale, 0, 2)];
}
return 'plural_2not1';
}
/**
* Return the "best" accepted language from the list of available
* languages.
*
* Use $_SERVER['HTTP_ACCEPT_LANGUAGE'] if the accepted language
* list is empty. The list must be something like:
* 'da, en-gb;q=0.8, en;q=0.7'
*
* @param array Available languages in the system
* @param string String of comma separated accepted languages ('')
* @return string Language 2 or 5 letter iso code, default is 'en'
*/
public static function getAcceptedLanguage($available, $accepted ='')
{
if (empty($accepted)) {
if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$accepted = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
} else {
return 'en';
}
}
$acceptedlist = explode(',', $accepted);
foreach ($acceptedlist as $lang) {
$lang = explode(';', $lang);
$lang = trim($lang[0]);
if (in_array($lang, $available)) {
return $lang;
}
// for the xx-XX cases we may have only the "main" language
// form like xx is available
$lang = substr($lang, 0, 2);
if (in_array($lang, $available)) {
return $lang;
}
}
$langs = Pluf::f('languages', array('en'));
return $langs[0];
}
/**
* Given a key indexed array, do replacement using the %%key%% in
* the string.
*/
public static function sprintf($string, $words=array())
{
foreach ($words as $key=>$word) {
$string = mb_ereg_replace('%%'.$key.'%%', $word, $string, 'm');
}
return $string;
}
/**
* French, Brazilian Portuguese
*/
public static function plural_2gt1($n)
{
return (int) ($n>1);
}
public static function plural_2not1($n)
{
return (int) ($n!=1);
}
/**
* Read a .po file.
*
* Based on the work by Matthias Bauer with some little cosmetic fixes.
*
* @source http://wordpress-soc-2007.googlecode.com/svn/trunk/moeffju/php-msgfmt/msgfmt-functions.php
* @copyright 2007 Matthias Bauer
* @author Matthias Bauer
* @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License 2.1
* @license http://opensource.org/licenses/apache2.0.php Apache License 2.0
*/
public static function readPoFile($file)
{
if (false !== ($hash=self::getCachedFile($file))) {
return $hash;
}
// read .po file
$fc= file_get_contents($file);
// normalize newlines
$fc= str_replace(array("\r\n", "\r"), array("\n", "\n"), $fc);
// results array
$hash= array ();
// temporary array
$temp= array ();
// state
$state= null;
$fuzzy= false;
// iterate over lines
foreach (explode("\n", $fc) as $line) {
$line= trim($line);
if ($line === '')
continue;
if (false === strpos($line, ' ')) {
$key = $line;
$data = '';
} else {
list ($key, $data)= explode(' ', $line, 2);
}
switch ($key) {
case '#,' : // flag...
$fuzzy= in_array('fuzzy', preg_split('/,\s*/', $data));
case '#' : // translator-comments
case '#.' : // extracted-comments
case '#:' : // reference...
case '#|' : // msgid previous-untranslated-string
case '#~' : // deprecated translations
// start a new entry
if (sizeof($temp) && array_key_exists('msgid', $temp) && array_key_exists('msgstr', $temp)) {
if (!$fuzzy)
$hash[]= $temp;
$temp= array ();
$state= null;
$fuzzy= false;
}
break;
case 'msgctxt' :
// context
case 'msgid' :
// untranslated-string
case 'msgid_plural' :
// untranslated-string-plural
$state= $key;
$temp[$state]= $data;
break;
case 'msgstr' :
// translated-string
$state= 'msgstr';
$temp[$state][]= $data;
break;
default :
if (strpos($key, 'msgstr[') !== False) {
// translated-string-case-n
$state= 'msgstr';
$temp[$state][]= $data;
} else {
// continued lines
switch ($state) {
case 'msgctxt' :
case 'msgid' :
case 'msgid_plural' :
$temp[$state] .= "\n" . $line;
break;
case 'msgstr' :
$temp[$state][sizeof($temp[$state]) - 1] .= "\n" . $line;
break;
default :
// parse error
return False;
}
}
break;
}
}
// add final entry
if ($state == 'msgstr')
$hash[]= $temp;
// Cleanup data, merge multiline entries, reindex hash for ksort
$temp= $hash;
$hash= array ();
foreach ($temp as $entry) {
foreach ($entry as &$v) {
$v = Pluf_Translation_poCleanHelper($v);
if ($v === False) {
// parse error
return False;
}
}
if (isset($entry['msgid_plural'])) {
$hash[$entry['msgid'].'#'.$entry['msgid_plural']]= $entry['msgstr'];
} else {
$hash[$entry['msgid']]= $entry['msgstr'];
}
}
self::cacheFile($file, $hash);
return $hash;
}
/**
* Load optimized version of a language file if available.
*
* @return mixed false or array with value
*/
public static function getCachedFile($file)
{
$phpfile = Pluf::f('tmp_folder').'/Pluf_L10n-'.md5($file).'.phps';
if (file_exists($phpfile)
&& (filemtime($file) < filemtime($phpfile))) {
return include $phpfile;
}
return false;
}
/**
* Cache an optimized version of a language file.
*
* @param string File
* @param array Parsed hash
*/
public static function cacheFile($file, $hash)
{
$phpfile = Pluf::f('tmp_folder').'/Pluf_L10n-'.md5($file).'.phps';
file_put_contents($phpfile,
'<?php return '.var_export($hash, true).'; ?>',
LOCK_EX);
@chmod($phpfile, 0666);
}
}
function Pluf_Translation_poCleanHelper($x)
{
if (is_array($x)) {
foreach ($x as $k => $v) {
$x[$k]= Pluf_Translation_poCleanHelper($v);
}
} else {
if ($x[0] == '"')
$x= substr($x, 1, -1);
$x= str_replace("\"\n\"", '', $x);
$x= str_replace('$', '\\$', $x);
$x= @eval("return \"$x\";");
}
return $x;
}