initial commit

This commit is contained in:
Chris Sewell
2012-11-28 03:55:08 -05:00
parent 7adb399b2e
commit cf140a2e97
3247 changed files with 492437 additions and 0 deletions

354
lib/symfony/i18n/Gettext/MO.php Executable file
View File

@ -0,0 +1,354 @@
<?php
/**
* TGettext_MO class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Revision: 1415 $ $Date: 2006-06-11 10:33:51 +0200 (Sun, 11 Jun 2006) $
* @package System.I18N.core
*/
// +----------------------------------------------------------------------+
// | PEAR :: File :: Gettext :: MO |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is available at http://www.php.net/license/3_0.txt |
// | If you did not receive a copy of the PHP license and are unable |
// | to obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Copyright (c) 2004 Michael Wallner <mike@iworks.at> |
// +----------------------------------------------------------------------+
//
// $Id: MO.php 1415 2006-06-11 08:33:51Z fabien $
/**
* File::Gettext::MO
*
* @author Michael Wallner <mike@php.net>
* @license PHP License
*/
require_once dirname(__FILE__).'/TGettext.class.php';
/**
* File_Gettext_MO
*
* GNU MO file reader and writer.
*
* @author Michael Wallner <mike@php.net>
* @version $Revision: 1415 $
* @access public
* @package System.I18N.core
*/
class TGettext_MO extends TGettext
{
/**
* file handle
*
* @access private
* @var resource
*/
protected $_handle = null;
/**
* big endianess
*
* Whether to write with big endian byte order.
*
* @access public
* @var bool
*/
protected $writeBigEndian = false;
/**
* Constructor
*
* @access public
* @return object File_Gettext_MO
* @param string $file path to GNU MO file
*/
function TGettext_MO($file = '')
{
$this->file = $file;
}
/**
* _read
*
* @access private
* @return mixed
* @param int $bytes
*/
function _read($bytes = 1)
{
if (0 < $bytes = abs($bytes)) {
return fread($this->_handle, $bytes);
}
return null;
}
/**
* _readInt
*
* @access private
* @return int
* @param bool $bigendian
*/
function _readInt($bigendian = false)
{
//unpack returns a reference????
$unpacked = unpack($bigendian ? 'N' : 'V', $this->_read(4));
return array_shift($unpacked);
}
/**
* _writeInt
*
* @access private
* @return int
* @param int $int
*/
function _writeInt($int)
{
return $this->_write(pack($this->writeBigEndian ? 'N' : 'V', (int) $int));
}
/**
* _write
*
* @access private
* @return int
* @param string $data
*/
function _write($data)
{
return fwrite($this->_handle, $data);
}
/**
* _writeStr
*
* @access private
* @return int
* @param string $string
*/
function _writeStr($string)
{
return $this->_write($string . "\0");
}
/**
* _readStr
*
* @access private
* @return string
* @param array $params associative array with offset and length
* of the string
*/
function _readStr($params)
{
fseek($this->_handle, $params['offset']);
return $this->_read($params['length']);
}
/**
* Load MO file
*
* @access public
* @return mixed Returns true on success or PEAR_Error on failure.
* @param string $file
*/
function load($file = null)
{
if (!isset($file)) {
$file = $this->file;
}
// open MO file
if (!is_resource($this->_handle = @fopen($file, 'rb'))) {
return false;
}
// lock MO file shared
if (!@flock($this->_handle, LOCK_SH)) {
@fclose($this->_handle);
return false;
}
// read (part of) magic number from MO file header and define endianess
//unpack returns a reference????
$unpacked = unpack('c', $this->_read(4));
switch ($magic = array_shift($unpacked))
{
case -34:
$be = false;
break;
case -107:
$be = true;
break;
default:
return false;
}
// check file format revision - we currently only support 0
if (0 !== ($_rev = $this->_readInt($be))) {
return false;
}
// count of strings in this file
$count = $this->_readInt($be);
// offset of hashing table of the msgids
$offset_original = $this->_readInt($be);
// offset of hashing table of the msgstrs
$offset_translat = $this->_readInt($be);
// move to msgid hash table
fseek($this->_handle, $offset_original);
// read lengths and offsets of msgids
$original = array();
for ($i = 0; $i < $count; $i++) {
$original[$i] = array(
'length' => $this->_readInt($be),
'offset' => $this->_readInt($be)
);
}
// move to msgstr hash table
fseek($this->_handle, $offset_translat);
// read lengths and offsets of msgstrs
$translat = array();
for ($i = 0; $i < $count; $i++) {
$translat[$i] = array(
'length' => $this->_readInt($be),
'offset' => $this->_readInt($be)
);
}
// read all
for ($i = 0; $i < $count; $i++) {
$this->strings[$this->_readStr($original[$i])] =
$this->_readStr($translat[$i]);
}
// done
@flock($this->_handle, LOCK_UN);
@fclose($this->_handle);
$this->_handle = null;
// check for meta info
if (isset($this->strings[''])) {
$this->meta = parent::meta2array($this->strings['']);
unset($this->strings['']);
}
return true;
}
/**
* Save MO file
*
* @access public
* @return mixed Returns true on success or PEAR_Error on failure.
* @param string $file
*/
function save($file = null)
{
if (!isset($file)) {
$file = $this->file;
}
// open MO file
if (!is_resource($this->_handle = @fopen($file, 'wb'))) {
return false;
}
// lock MO file exclusively
if (!@flock($this->_handle, LOCK_EX)) {
@fclose($this->_handle);
return false;
}
// write magic number
if ($this->writeBigEndian) {
$this->_write(pack('c*', 0x95, 0x04, 0x12, 0xde));
} else {
$this->_write(pack('c*', 0xde, 0x12, 0x04, 0x95));
}
// write file format revision
$this->_writeInt(0);
$count = count($this->strings) + ($meta = (count($this->meta) ? 1 : 0));
// write count of strings
$this->_writeInt($count);
$offset = 28;
// write offset of orig. strings hash table
$this->_writeInt($offset);
$offset += ($count * 8);
// write offset transl. strings hash table
$this->_writeInt($offset);
// write size of hash table (we currently ommit the hash table)
$this->_writeInt(0);
$offset += ($count * 8);
// write offset of hash table
$this->_writeInt($offset);
// unshift meta info
if ($meta) {
$meta = '';
foreach ($this->meta as $key => $val) {
$meta .= $key . ': ' . $val . "\n";
}
$strings = array('' => $meta) + $this->strings;
} else {
$strings = $this->strings;
}
// write offsets for original strings
foreach (array_keys($strings) as $o) {
$len = strlen($o);
$this->_writeInt($len);
$this->_writeInt($offset);
$offset += $len + 1;
}
// write offsets for translated strings
foreach ($strings as $t) {
$len = strlen($t);
$this->_writeInt($len);
$this->_writeInt($offset);
$offset += $len + 1;
}
// write original strings
foreach (array_keys($strings) as $o) {
$this->_writeStr($o);
}
// write translated strings
foreach ($strings as $t) {
$this->_writeStr($t);
}
// done
@flock($this->_handle, LOCK_UN);
@fclose($this->_handle);
return true;
}
}

159
lib/symfony/i18n/Gettext/PO.php Executable file
View File

@ -0,0 +1,159 @@
<?php
/**
* TGettext_PO class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Revision: 1415 $ $Date: 2006-06-11 10:33:51 +0200 (Sun, 11 Jun 2006) $
* @package System.I18N.core
*/
// +----------------------------------------------------------------------+
// | PEAR :: File :: Gettext :: PO |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is available at http://www.php.net/license/3_0.txt |
// | If you did not receive a copy of the PHP license and are unable |
// | to obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Copyright (c) 2004 Michael Wallner <mike@iworks.at> |
// +----------------------------------------------------------------------+
//
// $Id: PO.php 1415 2006-06-11 08:33:51Z fabien $
/**
* File::Gettext::PO
*
* @author Michael Wallner <mike@php.net>
* @license PHP License
*/
require_once dirname(__FILE__).'/TGettext.class.php';
/**
* File_Gettext_PO
*
* GNU PO file reader and writer.
*
* @author Michael Wallner <mike@php.net>
* @version $Revision: 1415 $
* @access public
* @package System.I18N.core
*/
class TGettext_PO extends TGettext
{
/**
* Constructor
*
* @access public
* @return object File_Gettext_PO
* @param string path to GNU PO file
*/
function TGettext_PO($file = '')
{
$this->file = $file;
}
/**
* Load PO file
*
* @access public
* @return mixed Returns true on success or PEAR_Error on failure.
* @param string $file
*/
function load($file = null)
{
if (!isset($file)) {
$file = $this->file;
}
// load file
if (!$contents = @file($file)) {
return false;
}
$contents = implode('', $contents);
// match all msgid/msgstr entries
$matched = preg_match_all(
'/(msgid\s+("([^"]|\\\\")*?"\s*)+)\s+' .
'(msgstr\s+("([^"]|\\\\")*?"\s*)+)/',
$contents, $matches
);
unset($contents);
if (!$matched) {
return false;
}
// get all msgids and msgtrs
for ($i = 0; $i < $matched; $i++) {
$msgid = preg_replace(
'/\s*msgid\s*"(.*)"\s*/s', '\\1', $matches[1][$i]);
$msgstr= preg_replace(
'/\s*msgstr\s*"(.*)"\s*/s', '\\1', $matches[4][$i]);
$this->strings[parent::prepare($msgid)] = parent::prepare($msgstr);
}
// check for meta info
if (isset($this->strings[''])) {
$this->meta = parent::meta2array($this->strings['']);
unset($this->strings['']);
}
return true;
}
/**
* Save PO file
*
* @access public
* @return mixed Returns true on success or PEAR_Error on failure.
* @param string $file
*/
function save($file = null)
{
if (!isset($file)) {
$file = $this->file;
}
// open PO file
if (!is_resource($fh = @fopen($file, 'w'))) {
return false;
}
// lock PO file exclusively
if (!flock($fh, LOCK_EX)) {
fclose($fh);
return false;
}
// write meta info
if (count($this->meta)) {
$meta = 'msgid ""' . "\nmsgstr " . '""' . "\n";
foreach ($this->meta as $k => $v) {
$meta .= '"' . $k . ': ' . $v . '\n"' . "\n";
}
fwrite($fh, $meta . "\n");
}
// write strings
foreach ($this->strings as $o => $t) {
fwrite($fh,
'msgid "' . parent::prepare($o, true) . '"' . "\n" .
'msgstr "' . parent::prepare($t, true) . '"' . "\n\n"
);
}
//done
@flock($fh, LOCK_UN);
@fclose($fh);
return true;
}
}

View File

@ -0,0 +1,286 @@
<?php
/**
* TGettext class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Revision: 3152 $ $Date: 2007-01-05 07:16:57 +0100 (Fri, 05 Jan 2007) $
* @package System.I18N.core
*/
// +----------------------------------------------------------------------+
// | PEAR :: File :: Gettext |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is available at http://www.php.net/license/3_0.txt |
// | If you did not receive a copy of the PHP license and are unable |
// | to obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Copyright (c) 2004 Michael Wallner <mike@iworks.at> |
// +----------------------------------------------------------------------+
//
// $Id: TGettext.class.php 3152 2007-01-05 06:16:57Z fabien $
/**
* File::Gettext
*
* @author Michael Wallner <mike@php.net>
* @license PHP License
*/
/**
* Use PHPs builtin error messages
*/
//ini_set('track_errors', true);
/**
* File_Gettext
*
* GNU gettext file reader and writer.
*
* #################################################################
* # All protected members of this class are public in its childs. #
* #################################################################
*
* @author Michael Wallner <mike@php.net>
* @version $Revision: 3152 $
* @access public
* @package System.I18N.core
*/
class TGettext
{
/**
* strings
*
* associative array with all [msgid => msgstr] entries
*
* @access protected
* @var array
*/
protected $strings = array();
/**
* meta
*
* associative array containing meta
* information like project name or content type
*
* @access protected
* @var array
*/
protected $meta = array();
/**
* file path
*
* @access protected
* @var string
*/
protected $file = '';
/**
* Factory
*
* @static
* @access public
* @return object Returns File_Gettext_PO or File_Gettext_MO on success
* or PEAR_Error on failure.
* @param string $format MO or PO
* @param string $file path to GNU gettext file
*/
static function factory($format, $file = '')
{
$format = strToUpper($format);
$filename = dirname(__FILE__).'/'.$format.'.php';
if (is_file($filename) == false)
throw new Exception ("Class file $file not found");
include_once $filename;
$class = 'TGettext_' . $format;
return new $class($file);
}
/**
* poFile2moFile
*
* That's a simple fake of the 'msgfmt' console command. It reads the
* contents of a GNU PO file and saves them to a GNU MO file.
*
* @static
* @access public
* @return mixed Returns true on success or PEAR_Error on failure.
* @param string $pofile path to GNU PO file
* @param string $mofile path to GNU MO file
*/
function poFile2moFile($pofile, $mofile)
{
if (!is_file($pofile)) {
throw new Exception("File $pofile doesn't exist.");
}
include_once dirname(__FILE__).'/PO.php';
$PO = new TGettext_PO($pofile);
if (true !== ($e = $PO->load())) {
return $e;
}
$MO = $PO->toMO();
if (true !== ($e = $MO->save($mofile))) {
return $e;
}
unset($PO, $MO);
return true;
}
/**
* prepare
*
* @static
* @access protected
* @return string
* @param string $string
* @param bool $reverse
*/
function prepare($string, $reverse = false)
{
if ($reverse) {
$smap = array('"', "\n", "\t", "\r");
$rmap = array('\"', '\\n"' . "\n" . '"', '\\t', '\\r');
return (string) str_replace($smap, $rmap, $string);
} else {
$string = preg_replace('/"\s+"/', '', $string);
$smap = array('\\n', '\\r', '\\t', '\"');
$rmap = array("\n", "\r", "\t", '"');
return (string) str_replace($smap, $rmap, $string);
}
}
/**
* meta2array
*
* @static
* @access public
* @return array
* @param string $meta
*/
function meta2array($meta)
{
$array = array();
foreach (explode("\n", $meta) as $info) {
if ($info = trim($info)) {
list($key, $value) = explode(':', $info, 2);
$array[trim($key)] = trim($value);
}
}
return $array;
}
/**
* toArray
*
* Returns meta info and strings as an array of a structure like that:
* <code>
* array(
* 'meta' => array(
* 'Content-Type' => 'text/plain; charset=iso-8859-1',
* 'Last-Translator' => 'Michael Wallner <mike@iworks.at>',
* 'PO-Revision-Date' => '2004-07-21 17:03+0200',
* 'Language-Team' => 'German <mail@example.com>',
* ),
* 'strings' => array(
* 'All rights reserved' => 'Alle Rechte vorbehalten',
* 'Welcome' => 'Willkommen',
* // ...
* )
* )
* </code>
*
* @see fromArray()
* @access protected
* @return array
*/
function toArray()
{
return array('meta' => $this->meta, 'strings' => $this->strings);
}
/**
* fromArray
*
* Assigns meta info and strings from an array of a structure like that:
* <code>
* array(
* 'meta' => array(
* 'Content-Type' => 'text/plain; charset=iso-8859-1',
* 'Last-Translator' => 'Michael Wallner <mike@iworks.at>',
* 'PO-Revision-Date' => date('Y-m-d H:iO'),
* 'Language-Team' => 'German <mail@example.com>',
* ),
* 'strings' => array(
* 'All rights reserved' => 'Alle Rechte vorbehalten',
* 'Welcome' => 'Willkommen',
* // ...
* )
* )
* </code>
*
* @see toArray()
* @access protected
* @return bool
* @param array $array
*/
function fromArray($array)
{
if (!array_key_exists('strings', $array)) {
if (count($array) != 2) {
return false;
} else {
list($this->meta, $this->strings) = $array;
}
} else {
$this->meta = @$array['meta'];
$this->strings = @$array['strings'];
}
return true;
}
/**
* toMO
*
* @access protected
* @return object File_Gettext_MO
*/
function toMO()
{
include_once dirname(__FILE__).'/MO.php';
$MO = new TGettext_MO;
$MO->fromArray($this->toArray());
return $MO;
}
/**
* toPO
*
* @access protected
* @return object File_Gettext_PO
*/
function toPO()
{
include_once dirname(__FILE__).'/PO.php';
$PO = new TGettext_PO;
$PO->fromArray($this->toArray());
return $PO;
}
}

View File

@ -0,0 +1,211 @@
<?php
/**
* sfChoiceFormat class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfChoiceFormat.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* sfChoiceFormat class.
*
* sfChoiceFormat converts between ranges of numeric values and string
* names for those ranges.
*
* A sfChoiceFormat splits the real number line -Inf to +Inf into two or
* more contiguous ranges. Each range is mapped to a string.
* sfChoiceFormat is generally used in a MessageFormat for displaying
* grammatically correct plurals such as "There are 2 files."
*
* <code>
* $string = '[0] are no files |[1] is one file |(1,Inf] are {number} files';
*
* $formatter = new sfMessageFormat(...); //init for a source
* $translated = $formatter->format($string);
*
* $choice = new sfChoiceFormat();
* echo $choice->format($translated, 0); //shows "are no files"
* </code>
*
* The message/string choices are separated by the pipe "|" followed
* by a set notation of the form
* # <t>[1,2]</t> -- accepts values between 1 and 2, inclusive.
* # <t>(1,2)</t> -- accepts values between 1 and 2, excluding 1 and 2.
* # <t>{1,2,3,4}</t> -- only values defined in the set are accepted.
* # <t>[-Inf,0)</t> -- accepts value greater or equal to negative infinity
* and strictly less than 0
* Any non-empty combinations of the delimiters of square and round brackets
* are acceptable.
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 24 20:46:16 EST 2004
* @package System.I18N.core
*/
class sfChoiceFormat
{
/**
* The pattern to validate a set notation
*/
protected $validate = '/[\(\[\{]|[-Inf\d]+|,|[\+Inf\d]+|[\)\]\}]/ms';
/**
* The pattern to parse the formatting string.
*/
protected $parse = '/\s?\|?([\(\[\{]([-Inf\d]+,?[\+Inf\d]*)+[\)\]\}])\s?/';
/**
* The value for positive infinity.
*/
protected $inf;
/**
* Constructor.
*/
public function __construct()
{
$this->inf = -log(0);
}
/**
* Determines if the given number belongs to a given set
*
* @param float the number to test.
* @param string the set, in set notation.
* @return boolean true if number is in the set, false otherwise.
*/
public function isValid($number, $set)
{
$n = preg_match_all($this->validate, $set, $matches, PREG_SET_ORDER);
if ($n < 3)
{
$error = 'Invalid set "%s"';
$error = sprintf($error, $set);
throw new sfException($error);
}
$leftBracket = $matches[0][0];
$rightBracket = $matches[$n - 1][0];
$i = 0;
$elements = array();
foreach ($matches as $match)
{
$string = $match[0];
if ($i != 0 && $i != $n - 1 && $string !== ',')
{
if ($string == '-Inf')
{
$elements[] = -1 * $this->inf;
}
else if ($string == '+Inf' || $string == 'Inf')
{
$elements[] = $this->inf;
}
else
{
$elements[] = floatval($string);
}
}
$i++;
}
$total = count($elements);
$number = floatval($number);
if ($leftBracket == '{' && $rightBracket == '}')
{
return in_array($number, $elements);
}
$left = false;
if ($leftBracket == '[')
{
$left = $number >= $elements[0];
}
else if ($leftBracket == '(')
{
$left = $number > $elements[0];
}
$right = false;
if ($rightBracket==']')
{
$right = $number <= $elements[$total - 1];
}
else if ($rightBracket == ')')
{
$right = $number < $elements[$total - 1];
}
if ($left && $right)
{
return true;
}
return false;
}
/**
* Parses a choice string and get a list of sets and a list of strings corresponding to the sets.
*
* @param string the string containing the choices
* @return array array($sets, $strings)
*/
public function parse($string)
{
$n = preg_match_all($this->parse, $string, $matches, PREG_OFFSET_CAPTURE);
$sets = array();
foreach ($matches[1] as $match)
{
$sets[] = $match[0];
}
$offset = $matches[0];
$strings = array();
for ($i = 0; $i < $n; $i++)
{
$len = strlen($offset[$i][0]);
$begin = $i == 0 ? $len : $offset[$i][1] + $len;
$end = $i == $n - 1 ? strlen($string) : $offset[$i + 1][1];
$strings[] = substr($string, $begin, $end - $begin);
}
return array($sets, $strings);
}
/**
* For the choice string, and a number, find and return the string that satisfied the set within the choices.
*
* @param string the choices string.
* @param float the number to test.
* @return string the choosen string.
*/
public function format($string, $number)
{
list($sets, $strings) = $this->parse($string);
$total = count($sets);
for ($i = 0; $i < $total; $i++)
{
if ($this->isValid($number, $sets[$i]))
{
return $strings[$i];
}
}
return false;
}
}

View File

@ -0,0 +1,682 @@
<?php
/**
* sfCultureInfo class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfCultureInfo.class.php 4439 2007-06-27 14:24:44Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* sfCultureInfo class.
*
* Represents information about a specific culture including the
* names of the culture, the calendar used, as well as access to
* culture-specific objects that provide methods for common operations,
* such as formatting dates, numbers, and currency.
*
* The sfCultureInfo class holds culture-specific information, such as the
* associated language, sublanguage, country/region, calendar, and cultural
* conventions. This class also provides access to culture-specific
* instances of sfDateTimeFormatInfo and sfNumberFormatInfo. These objects
* contain the information required for culture-specific operations,
* such as formatting dates, numbers and currency.
*
* The culture names follow the format "<languagecode>_<country/regioncode>",
* where <languagecode> is a lowercase two-letter code derived from ISO 639
* codes. You can find a full list of the ISO-639 codes at
* http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt
*
* The <country/regioncode2> is an uppercase two-letter code derived from
* ISO 3166. A copy of ISO-3166 can be found at
* http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
*
* For example, Australian English is "en_AU".
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Sat Dec 04 13:41:46 EST 2004
* @package System.I18N.core
*/
class sfCultureInfo
{
/**
* ICU data filename extension.
* @var string
*/
protected $dataFileExt = '.dat';
/**
* The ICU data array.
* @var array
*/
protected $data = array();
/**
* The current culture.
* @var string
*/
protected $culture;
/**
* Directory where the ICU data is stored.
* @var string
*/
protected $dataDir;
/**
* A list of ICU date files loaded.
* @var array
*/
protected $dataFiles = array();
/**
* The current date time format info.
* @var sfDateTimeFormatInfo
*/
protected $dateTimeFormat;
/**
* The current number format info.
* @var sfNumberFormatInfo
*/
protected $numberFormat;
/**
* A list of properties that are accessable/writable.
* @var array
*/
protected $properties = array();
/**
* Culture type, all.
* @see getCultures()
* @var int
*/
const ALL = 0;
/**
* Culture type, neutral.
* @see getCultures()
* @var int
*/
const NEUTRAL = 1;
/**
* Culture type, specific.
*
* @see getCultures()
* @var int
*/
const SPECIFIC = 2;
/**
* Displays the culture name.
*
* @return string the culture name.
* @see getName()
*/
public function __toString()
{
return $this->getName();
}
/**
* Allows functions that begins with 'set' to be called directly
* as an attribute/property to retrieve the value.
*
* @return mixed
*/
public function __get($name)
{
$getProperty = 'get'.$name;
if (in_array($getProperty, $this->properties))
{
return $this->$getProperty();
}
else
{
throw new sfException(sprintf('Property %s does not exists.', $name));
}
}
/**
* Allows functions that begins with 'set' to be called directly
* as an attribute/property to set the value.
*/
public function __set($name, $value)
{
$setProperty = 'set'.$name;
if (in_array($setProperty, $this->properties))
{
$this->$setProperty($value);
}
else
{
throw new sfException(sprintf('Property %s can not be set.', $name));
}
}
/**
* Initializes a new instance of the sfCultureInfo class based on the
* culture specified by name. E.g. <code>new sfCultureInfo('en_AU');</code>
* The culture indentifier must be of the form
* "<language>_(country/region/variant)".
*
* @param string a culture name, e.g. "en_AU".
* @return return new sfCultureInfo.
*/
public function __construct($culture = 'en')
{
$this->properties = get_class_methods($this);
if (empty($culture))
{
$culture = 'en';
}
$this->dataDir = $this->dataDir();
$this->dataFileExt = $this->fileExt();
$this->setCulture($culture);
$this->loadCultureData('root');
$this->loadCultureData($culture);
}
/**
* Gets the default directory for the ICU data.
* The default is the "data" directory for this class.
*
* @return string directory containing the ICU data.
*/
protected static function dataDir()
{
return sfConfig::get('sf_symfony_data_dir').'/i18n/';
}
/**
* Gets the filename extension for ICU data. Default is ".dat".
*
* @return string filename extension for ICU data.
*/
protected static function fileExt()
{
return '.dat';
}
/**
* Determines if a given culture is valid. Simply checks that the
* culture data exists.
*
* @param string a culture
* @return boolean true if valid, false otherwise.
*/
static public function validCulture($culture)
{
if (preg_match('/^[a-z]{2}(_[A-Z]{2,5}){0,2}$/', $culture))
{
return is_file(self::dataDir().$culture.self::fileExt());
}
return false;
}
/**
* Sets the culture for the current instance. The culture indentifier
* must be of the form "<language>_(country/region)".
*
* @param string culture identifier, e.g. "fr_FR_EURO".
*/
protected function setCulture($culture)
{
if (!empty($culture))
{
if (!preg_match('/^[a-z]{2}(_[A-Z]{2,5}){0,2}$/', $culture))
{
throw new sfException(sprintf('Invalid culture supplied: %s', $culture));
}
}
$this->culture = $culture;
}
/**
* Loads the ICU culture data for the specific culture identifier.
*
* @param string the culture identifier.
*/
protected function loadCultureData($culture)
{
$file_parts = explode('_', $culture);
$current_part = $file_parts[0];
$files = array($current_part);
for ($i = 1, $max = count($file_parts); $i < $max; $i++)
{
$current_part .= '_'.$file_parts[$i];
$files[] = $current_part;
}
foreach ($files as $file)
{
$filename = $this->dataDir.$file.$this->dataFileExt;
if (is_file($filename) == false)
{
throw new sfException(sprintf('Data file for "%s" was not found.', $file));
}
if (in_array($filename, $this->dataFiles) == false)
{
array_unshift($this->dataFiles, $file);
$data = &$this->getData($filename);
$this->data[$file] = &$data;
if (isset($data['__ALIAS']))
{
$this->loadCultureData($data['__ALIAS'][0]);
}
unset($data);
}
}
}
/**
* Gets the data by unserializing the ICU data from disk.
* The data files are cached in a static variable inside
* this function.
*
* @param string the ICU data filename
* @return array ICU data
*/
protected function &getData($filename)
{
static $data = array();
static $files = array();
if (!isset($files[$filename]))
{
$data[$filename] = unserialize(file_get_contents($filename));
$files[$filename] = true;
}
return $data[$filename];
}
/**
* Finds the specific ICU data information from the data.
* The path to the specific ICU data is separated with a slash "/".
* E.g. To find the default calendar used by the culture, the path
* "calendar/default" will return the corresponding default calendar.
* Use merge=true to return the ICU including the parent culture.
* E.g. The currency data for a variant, say "en_AU" contains one
* entry, the currency for AUD, the other currency data are stored
* in the "en" data file. Thus to retrieve all the data regarding
* currency for "en_AU", you need to use findInfo("Currencies,true);.
*
* @param string the data you want to find.
* @param boolean merge the data from its parents.
* @return mixed the specific ICU data.
*/
protected function findInfo($path = '/', $merge = false)
{
$result = array();
foreach ($this->dataFiles as $section)
{
$info = $this->searchArray($this->data[$section], $path);
if ($info)
{
if ($merge)
{
$result = array_merge($info, $result);
}
else
{
return $info;
}
}
}
return $result;
}
/**
* Searches the array for a specific value using a path separated using
* slash "/" separated path. e.g to find $info['hello']['world'],
* the path "hello/world" will return the corresponding value.
*
* @param array the array for search
* @param string slash "/" separated array path.
* @return mixed the value array using the path
*/
protected function searchArray($info, $path = '/')
{
$index = explode('/', $path);
$array = $info;
for ($i = 0, $max = count($index); $i < $max; $i++)
{
$k = $index[$i];
if ($i < $max - 1 && isset($array[$k]))
{
$array = $array[$k];
}
else if ($i == $max - 1 && isset($array[$k]))
{
return $array[$k];
}
}
}
/**
* Gets the culture name in the format
* "<languagecode2>_(country/regioncode2)".
*
* @return string culture name.
*/
public function getName()
{
return $this->culture;
}
/**
* Gets the sfDateTimeFormatInfo that defines the culturally appropriate
* format of displaying dates and times.
*
* @return sfDateTimeFormatInfo date time format information for the culture.
*/
public function getDateTimeFormat()
{
if (is_null($this->dateTimeFormat))
{
$calendar = $this->getCalendar();
$info = $this->findInfo("calendar/{$calendar}", true);
$this->setDateTimeFormat(new sfDateTimeFormatInfo($info));
}
return $this->dateTimeFormat;
}
/**
* Sets the date time format information.
*
* @param sfDateTimeFormatInfo the new date time format info.
*/
public function setDateTimeFormat($dateTimeFormat)
{
$this->dateTimeFormat = $dateTimeFormat;
}
/**
* Gets the default calendar used by the culture, e.g. "gregorian".
*
* @return string the default calendar.
*/
public function getCalendar()
{
$info = $this->findInfo('calendar/default');
return $info[0];
}
/**
* Gets the culture name in the language that the culture is set
* to display. Returns <code>array('Language','Country');</code>
* 'Country' is omitted if the culture is neutral.
*
* @return array array with language and country as elements, localized.
*/
public function getNativeName()
{
$lang = substr($this->culture, 0, 2);
$reg = substr($this->culture, 3, 2);
$language = $this->findInfo("Languages/{$lang}");
$region = $this->findInfo("Countries/{$reg}");
if ($region)
{
return $language[0].' ('.$region[0].')';
}
else
{
return $language[0];
}
}
/**
* Gets the culture name in English.
* Returns <code>array('Language','Country');</code>
* 'Country' is omitted if the culture is neutral.
*
* @return array array with language and country as elements.
*/
public function getEnglishName()
{
$lang = substr($this->culture, 0, 2);
$reg = substr($this->culture, 3, 2);
$culture = $this->getInvariantCulture();
$language = $culture->findInfo("Languages/{$lang}");
$region = $culture->findInfo("Countries/{$reg}");
return $region ? $language[0].' ('.$region[0].')' : $language[0];
}
/**
* Gets the sfCultureInfo that is culture-independent (invariant).
* Any changes to the invariant culture affects all other
* instances of the invariant culture.
* The invariant culture is assumed to be "en";
*
* @return sfCultureInfo invariant culture info is "en".
*/
static function getInvariantCulture()
{
static $invariant;
if (is_null($invariant))
{
$invariant = new sfCultureInfo();
}
return $invariant;
}
/**
* Gets a value indicating whether the current sfCultureInfo
* represents a neutral culture. Returns true if the culture
* only contains two characters.
*
* @return boolean true if culture is neutral, false otherwise.
*/
public function getIsNeutralCulture()
{
return strlen($this->culture) == 2;
}
/**
* Gets the sfNumberFormatInfo that defines the culturally appropriate
* format of displaying numbers, currency, and percentage.
*
* @return sfNumberFormatInfo the number format info for current culture.
*/
public function getNumberFormat()
{
if (is_null($this->numberFormat))
{
$elements = $this->findInfo('NumberElements');
$patterns = $this->findInfo('NumberPatterns');
$currencies = $this->getCurrencies();
$data = array('NumberElements' => $elements, 'NumberPatterns' => $patterns, 'Currencies' => $currencies);
$this->setNumberFormat(new sfNumberFormatInfo($data));
}
return $this->numberFormat;
}
/**
* Sets the number format information.
*
* @param sfNumberFormatInfo the new number format info.
*/
public function setNumberFormat($numberFormat)
{
$this->numberFormat = $numberFormat;
}
/**
* Gets the sfCultureInfo that represents the parent culture of the
* current sfCultureInfo
*
* @return sfCultureInfo parent culture information.
*/
public function getParent()
{
if (strlen($this->culture) == 2)
{
return $this->getInvariantCulture();
}
return new sfCultureInfo(substr($this->culture, 0, 2));
}
/**
* Gets the list of supported cultures filtered by the specified
* culture type. This is an EXPENSIVE function, it needs to traverse
* a list of ICU files in the data directory.
* This function can be called statically.
*
* @param int culture type, sfCultureInfo::ALL, sfCultureInfo::NEUTRAL
* or sfCultureInfo::SPECIFIC.
* @return array list of culture information available.
*/
static function getCultures($type = sfCultureInfo::ALL)
{
$dataDir = sfCultureInfo::dataDir();
$dataExt = sfCultureInfo::fileExt();
$dir = dir($dataDir);
$neutral = array();
$specific = array();
while (false !== ($entry = $dir->read()))
{
if (is_file($dataDir.$entry) && substr($entry, -4) == $dataExt && $entry != 'root'.$dataExt)
{
$culture = substr($entry, 0, -4);
if (strlen($culture) == 2)
{
$neutral[] = $culture;
}
else
{
$specific[] = $culture;
}
}
}
$dir->close();
switch ($type)
{
case sfCultureInfo::ALL:
$all = array_merge($neutral, $specific);
sort($all);
return $all;
break;
case sfCultureInfo::NEUTRAL:
return $neutral;
break;
case sfCultureInfo::SPECIFIC:
return $specific;
break;
}
}
/**
* Simplifies a single element array into its own value.
* E.g. <code>array(0 => array('hello'), 1 => 'world');</code>
* becomes <code>array(0 => 'hello', 1 => 'world');</code>
*
* @param array with single elements arrays
* @return array simplified array.
*/
protected function simplify($array)
{
for ($i = 0, $max = count($array); $i < $max; $i++)
{
$key = key($array);
if (is_array($array[$key]) && count($array[$key]) == 1)
{
$array[$key] = $array[$key][0];
}
next($array);
}
return $array;
}
/**
* Gets a list of countries in the language of the localized version.
*
* @return array a list of localized country names.
*/
public function getCountries()
{
return $this->simplify($this->findInfo('Countries', true));
}
/**
* Gets a list of currencies in the language of the localized version.
*
* @return array a list of localized currencies.
*/
public function getCurrencies()
{
return $this->findInfo('Currencies', true);
}
/**
* Gets a list of languages in the language of the localized version.
*
* @return array list of localized language names.
*/
public function getLanguages()
{
return $this->simplify($this->findInfo('Languages', true));
}
/**
* Gets a list of scripts in the language of the localized version.
*
* @return array list of localized script names.
*/
public function getScripts()
{
return $this->simplify($this->findInfo('Scripts', true));
}
/**
* Gets a list of timezones in the language of the localized version.
*
* @return array list of localized timezones.
*/
public function getTimeZones()
{
return $this->simplify($this->findInfo('zoneStrings', true));
}
}

View File

@ -0,0 +1,798 @@
<?php
/**
* sfDateFormat class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfDateFormat.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* Gets the encoding utilities
*/
require_once(dirname(__FILE__).'/util.php');
/**
* sfDateFormat class.
*
* The sfDateFormat class allows you to format dates and times with
* predefined styles in a locale-sensitive manner. Formatting times
* with the sfDateFormat class is similar to formatting dates.
*
* Formatting dates with the sfDateFormat class is a two-step process.
* First, you create a formatter with the getDateInstance method.
* Second, you invoke the format method, which returns a string containing
* the formatted date.
*
* DateTime values are formatted using standard or custom patterns stored
* in the properties of a DateTimeFormatInfo.
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Sat Dec 04 14:10:49 EST 2004
* @package System.I18N.core
*/
class sfDateFormat
{
/**
* A list of tokens and their function call.
* @var array
*/
protected $tokens = array(
'G'=>'Era',
'y'=>'year',
'M'=>'mon',
'd'=>'mday',
'h'=>'Hour12',
'H'=>'hours',
'm'=>'minutes',
's'=>'seconds',
'E'=>'wday',
'D'=>'yday',
'F'=>'DayInMonth',
'w'=>'WeekInYear',
'W'=>'WeekInMonth',
'a'=>'AMPM',
'k'=>'HourInDay',
'K'=>'HourInAMPM',
'z'=>'TimeZone'
);
/**
* A list of methods, to be used by the token function calls.
* @var array
*/
protected $methods = array();
/**
* The sfDateTimeFormatInfo, containing culture specific patterns and names.
* @var sfDateTimeFormatInfo
*/
protected $formatInfo;
/**
* Initializes a new sfDateFormat.
*
* @param mixed either, null, a sfCultureInfo instance, a DateTimeFormatInfo instance, or a locale.
* @return sfDateFormat instance
*/
function __construct($formatInfo = null)
{
if (is_null($formatInfo))
{
$this->formatInfo = sfDateTimeFormatInfo::getInvariantInfo();
}
else if ($formatInfo instanceof sfCultureInfo)
{
$this->formatInfo = $formatInfo->DateTimeFormat;
}
else if ($formatInfo instanceof sfDateTimeFormatInfo)
{
$this->formatInfo = $formatInfo;
}
else
{
$this->formatInfo = sfDateTimeFormatInfo::getInstance($formatInfo);
}
$this->methods = get_class_methods($this);
}
/**
* Guesses a date without calling strtotime.
*
* @author Olivier Verdier <Olivier.Verdier@gmail.com>
* @param mixed the time as integer or string in strtotime format.
* @param string the input pattern; default is sql date or timestamp
* @return array same array as the getdate function
*/
public function getDate($time, $pattern = null)
{
if (is_null($time))
{
return null;
}
// if the type is not a php timestamp
$isString = (string) $time !== (string) (int) $time;
if ($isString)
{
if (!$pattern)
{
if (strlen($time) == 10)
{
$pattern = 'i';
}
else // otherwise, default:
{
$pattern = 'I';
}
}
$pattern = $this->getPattern($pattern);
$tokens = $this->getTokens($pattern);
$pregPattern = '';
$matchNames = array();
foreach ($tokens as $token)
{
if ($matchName = $this->getFunctionName($token))
{
$pregPattern .= '(\d+)';
$matchNames[] = $matchName;
}
else
{
$pregPattern .= '[^\d]+';
}
}
preg_match('@'.$pregPattern.'@', $time, $matches);
array_shift($matches);
if (count($matchNames) == count($matches))
{
$date = array_combine($matchNames, $matches);
// guess the date if input with two digits
if (strlen($date['year']) == 2)
{
$date['year'] = date('Y', mktime(0, 0, 0, 1, 1, $date['year']));
}
$date = array_map('intval', $date);
}
}
// the last attempt has failed we fall back on the default method
if (!isset($date))
{
if ($isString)
{
$numericalTime = @strtotime($time);
if ($numericalTime === false)
{
throw new sfException(sprintf('Impossible to parse date "%s" with format "%s".', $time, $pattern));
}
}
else
{
$numericalTime = $time;
}
$date = @getdate($numericalTime);
}
// we set default values for the time
foreach (array('hours', 'minutes', 'seconds') as $timeDiv)
{
if (!isset($date[$timeDiv]))
{
$date[$timeDiv] = 0;
}
}
return $date;
}
/**
* Formats a date according to the pattern.
*
* @param mixed the time as integer or string in strtotime format.
* @return string formatted date time.
*/
public function format($time, $pattern = 'F', $inputPattern = null, $charset = 'UTF-8')
{
$date = $this->getDate($time, $inputPattern);
if (is_null($pattern))
{
$pattern = 'F';
}
$pattern = $this->getPattern($pattern);
$tokens = $this->getTokens($pattern);
for ($i = 0, $max = count($tokens); $i < $max; $i++)
{
$pattern = $tokens[$i];
if ($pattern{0} == "'" && $pattern{strlen($pattern) - 1} == "'")
{
$tokens[$i] = str_replace('``````', '\'', preg_replace('/(^\')|(\'$)/', '', $pattern));
}
else if ($pattern == '``````')
{
$tokens[$i] = '\'';
}
else
{
$function = ucfirst($this->getFunctionName($pattern));
if ($function != null)
{
$fName = 'get'.$function;
if (in_array($fName, $this->methods))
{
$tokens[$i] = $this->$fName($date, $pattern);
}
else
{
throw new sfException(sprintf('Function %s not found.', $function));
}
}
}
}
return I18N_toEncoding(implode('', $tokens), $charset);
}
/**
* For a particular token, get the corresponding function to call.
*
* @param string token
* @return mixed the function if good token, null otherwise.
*/
protected function getFunctionName($token)
{
if (isset($this->tokens[$token{0}]))
{
return $this->tokens[$token{0}];
}
}
/**
* Gets the pattern from DateTimeFormatInfo or some predefined patterns.
* If the $pattern parameter is an array of 2 element, it will assume
* that the first element is the date, and second the time
* and try to find an appropriate pattern and apply
* DateTimeFormatInfo::formatDateTime
* See the tutorial documentation for futher details on the patterns.
*
* @param mixed a pattern.
* @return string a pattern.
* @see DateTimeFormatInfo::formatDateTime()
*/
public function getPattern($pattern)
{
if (is_array($pattern) && count($pattern) == 2)
{
return $this->formatInfo->formatDateTime($this->getPattern($pattern[0]), $this->getPattern($pattern[1]));
}
switch ($pattern)
{
case 'd':
return $this->formatInfo->ShortDatePattern;
break;
case 'D':
return $this->formatInfo->LongDatePattern;
break;
case 'p':
return $this->formatInfo->MediumDatePattern;
break;
case 'P':
return $this->formatInfo->FullDatePattern;
break;
case 't':
return $this->formatInfo->ShortTimePattern;
break;
case 'T':
return $this->formatInfo->LongTimePattern;
break;
case 'q':
return $this->formatInfo->MediumTimePattern;
break;
case 'Q':
return $this->formatInfo->FullTimePattern;
break;
case 'f':
return $this->formatInfo->formatDateTime($this->formatInfo->LongDatePattern, $this->formatInfo->ShortTimePattern);
break;
case 'F':
return $this->formatInfo->formatDateTime($this->formatInfo->LongDatePattern, $this->formatInfo->LongTimePattern);
break;
case 'g':
return $this->formatInfo->formatDateTime($this->formatInfo->ShortDatePattern, $this->formatInfo->ShortTimePattern);
break;
case 'G':
return $this->formatInfo->formatDateTime($this->formatInfo->ShortDatePattern, $this->formatInfo->LongTimePattern);
break;
case 'i':
return 'yyyy-MM-dd';
break;
case 'I':
return 'yyyy-MM-dd HH:mm:ss';
break;
case 'M':
case 'm':
return 'MMMM dd';
break;
case 'R':
case 'r':
return 'EEE, dd MMM yyyy HH:mm:ss';
break;
case 's':
return 'yyyy-MM-ddTHH:mm:ss';
break;
case 'u':
return 'yyyy-MM-dd HH:mm:ss z';
break;
case 'U':
return 'EEEE dd MMMM yyyy HH:mm:ss';
break;
case 'Y':
case 'y':
return 'yyyy MMMM';
break;
default :
return $pattern;
}
}
/**
* Returns an easy to parse input pattern
* yy is replaced by yyyy and h by H
*
* @param string pattern.
* @return string input pattern
*/
public function getInputPattern($pattern)
{
$pattern = $this->getPattern($pattern);
$pattern = strtr($pattern, array('yyyy' => 'Y', 'h'=>'H', 'z'=>'', 'a'=>''));
$pattern = strtr($pattern, array('yy'=>'yyyy', 'Y'=>'yyyy'));
return trim($pattern);
}
/**
* Tokenizes the pattern. The tokens are delimited by group of
* similar characters, e.g. 'aabb' will form 2 tokens of 'aa' and 'bb'.
* Any substrings, starting and ending with a single quote (')
* will be treated as a single token.
*
* @param string pattern.
* @return array string tokens in an array.
*/
protected function getTokens($pattern)
{
$char = null;
$tokens = array();
$token = null;
$text = false;
for ($i = 0, $max = strlen($pattern); $i < $max; $i++)
{
if ($char == null || $pattern{$i} == $char || $text)
{
$token .= $pattern{$i};
}
else
{
$tokens[] = str_replace("''", "'", $token);
$token = $pattern{$i};
}
if ($pattern{$i} == "'" && $text == false)
{
$text = true;
}
else if ($text && $pattern{$i} == "'" && $char == "'")
{
$text = true;
}
else if ($text && $char != "'" && $pattern{$i} == "'")
{
$text = false;
}
$char = $pattern{$i};
}
$tokens[] = $token;
return $tokens;
}
// makes a unix date from our incomplete $date array
protected function getUnixDate($date)
{
return getdate(mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']));
}
/**
* Gets the year.
* "yy" will return the last two digits of year.
* "yyyy" will return the full integer year.
*
* @param array getdate format.
* @param string a pattern.
* @return string year
*/
protected function getYear($date, $pattern = 'yyyy')
{
$year = $date['year'];
switch ($pattern)
{
case 'yy':
return substr($year, 2);
case 'yyyy':
return $year;
default:
throw new sfException('The pattern for year is either "yy" or "yyyy".');
}
}
/**
* Gets the month.
* "M" will return integer 1 through 12
* "MM" will return the narrow month name, e.g. "J"
* "MMM" will return the abrreviated month name, e.g. "Jan"
* "MMMM" will return the month name, e.g. "January"
*
* @param array getdate format.
* @param string a pattern.
* @return string month name
*/
protected function getMon($date, $pattern = 'M')
{
$month = $date['mon'];
switch ($pattern)
{
case 'M':
return $month;
case 'MM':
return str_pad($month, 2, '0', STR_PAD_LEFT);
case 'MMM':
return $this->formatInfo->AbbreviatedMonthNames[$month - 1];
break;
case 'MMMM':
return $this->formatInfo->MonthNames[$month - 1];
default:
throw new sfException('The pattern for month is "M", "MM", "MMM", or "MMMM".');
}
}
/**
* Gets the day of the week.
* "E" will return integer 0 (for Sunday) through 6 (for Saturday).
* "EE" will return the narrow day of the week, e.g. "M"
* "EEE" will return the abrreviated day of the week, e.g. "Mon"
* "EEEE" will return the day of the week, e.g. "Monday"
*
* @param array getdate format.
* @param string a pattern.
* @return string day of the week.
*/
protected function getWday($date, $pattern = 'EEEE')
{
// if the $date comes from our home-made get date
if (!isset($date['wday']))
{
$date = $this->getUnixDate($date);
}
$day = $date['wday'];
switch ($pattern)
{
case 'E':
return $day;
break;
case 'EE':
return $this->formatInfo->NarrowDayNames[$day];
case 'EEE':
return $this->formatInfo->AbbreviatedDayNames[$day];
break;
case 'EEEE':
return $this->formatInfo->DayNames[$day];
break;
default:
throw new sfException('The pattern for day of the week is "E", "EE", "EEE", or "EEEE".');
}
}
/**
* Gets the day of the month.
* "d" for non-padding, "dd" will always return 2 characters.
*
* @param array getdate format.
* @param string a pattern.
* @return string day of the month
*/
protected function getMday($date, $pattern = 'd')
{
$day = $date['mday'];
switch ($pattern)
{
case 'd':
return $day;
case 'dd':
return str_pad($day, 2, '0', STR_PAD_LEFT);
case 'dddd':
return $this->getWday($date);
default:
throw new sfException('The pattern for day of the month is "d", "dd" or "dddd".');
}
}
/**
* Gets the era. i.e. in gregorian, year > 0 is AD, else BC.
*
* @todo How to support multiple Eras?, e.g. Japanese.
* @param array getdate format.
* @param string a pattern.
* @return string era
*/
protected function getEra($date, $pattern = 'G')
{
if ($pattern != 'G')
{
throw new sfException('The pattern for era is "G".');
}
return $this->formatInfo->getEra($date['year'] > 0 ? 1 : 0);
}
/**
* Gets the hours in 24 hour format, i.e. [0-23].
* "H" for non-padding, "HH" will always return 2 characters.
*
* @param array getdate format.
* @param string a pattern.
* @return string hours in 24 hour format.
*/
protected function getHours($date, $pattern = 'H')
{
$hour = $date['hours'];
switch ($pattern)
{
case 'H':
return $hour;
case 'HH':
return str_pad($hour, 2, '0', STR_PAD_LEFT);
default:
throw new sfException('The pattern for 24 hour format is "H" or "HH".');
}
}
/**
* Get the AM/PM designator, 12 noon is PM, 12 midnight is AM.
*
* @param array getdate format.
* @param string a pattern.
* @return string AM or PM designator
*/
protected function getAMPM($date, $pattern = 'a')
{
if ($pattern != 'a')
{
throw new sfException('The pattern for AM/PM marker is "a".');
}
return $this->formatInfo->AMPMMarkers[intval($date['hours'] / 12)];
}
/**
* Gets the hours in 12 hour format.
* "h" for non-padding, "hh" will always return 2 characters.
*
* @param array getdate format.
* @param string a pattern.
* @return string hours in 12 hour format.
*/
protected function getHour12($date, $pattern = 'h')
{
$hour = $date['hours'];
$hour = ($hour == 12 | $hour == 0) ? 12 : $hour % 12;
switch ($pattern)
{
case 'h':
return $hour;
case 'hh':
return str_pad($hour, 2, '0', STR_PAD_LEFT);
default:
throw new sfException('The pattern for 24 hour format is "H" or "HH".');
}
}
/**
* Gets the minutes.
* "m" for non-padding, "mm" will always return 2 characters.
*
* @param array getdate format.
* @param string a pattern.
* @return string minutes.
*/
protected function getMinutes($date, $pattern = 'm')
{
$minutes = $date['minutes'];
switch ($pattern)
{
case 'm':
return $minutes;
case 'mm':
return str_pad($minutes, 2, '0', STR_PAD_LEFT);
default:
throw new sfException('The pattern for minutes is "m" or "mm".');
}
}
/**
* Gets the seconds.
* "s" for non-padding, "ss" will always return 2 characters.
*
* @param array getdate format.
* @param string a pattern.
* @return string seconds
*/
protected function getSeconds($date, $pattern = 's')
{
$seconds = $date['seconds'];
switch ($pattern)
{
case 's':
return $seconds;
case 'ss':
return str_pad($seconds, 2, '0', STR_PAD_LEFT);
default:
throw new sfException('The pattern for seconds is "s" or "ss".');
}
}
/**
* Gets the timezone from the server machine.
*
* @todo How to get the timezone for a different region?
* @param array getdate format.
* @param string a pattern.
* @return string time zone
*/
protected function getTimeZone($date, $pattern = 'z')
{
if ($pattern != 'z')
{
throw new sfException('The pattern for time zone is "z".');
}
return @date('T', @mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']));
}
/**
* Gets the day in the year, e.g. [1-366]
*
* @param array getdate format.
* @param string a pattern.
* @return int hours in AM/PM format.
*/
protected function getYday($date, $pattern = 'D')
{
if ($pattern != 'D')
{
throw new sfException('The pattern for day in year is "D".');
}
return $date['yday'];
}
/**
* Gets day in the month.
*
* @param array getdate format.
* @param string a pattern.
* @return int day in month
*/
protected function getDayInMonth($date, $pattern = 'FF')
{
switch ($pattern)
{
case 'F':
return @date('j', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
break;
case 'FF':
return @date('d', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
break;
default:
throw new sfException('The pattern for day in month is "F" or "FF".');
}
}
/**
* Gets the week in the year.
*
* @param array getdate format.
* @param string a pattern.
* @return int week in year
*/
protected function getWeekInYear($date, $pattern = 'w')
{
if ($pattern != 'w')
{
throw new sfException('The pattern for week in year is "w".');
}
return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']));
}
/**
* Gets week in the month.
*
* @param array getdate format.
* @return int week in month
*/
protected function getWeekInMonth($date, $pattern = 'W')
{
if ($pattern != 'W')
{
throw new sfException('The pattern for week in month is "W".');
}
return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])) - date('W', mktime(0, 0, 0, $date['mon'], 1, $date['year']));
}
/**
* Gets the hours [1-24].
*
* @param array getdate format.
* @param string a pattern.
* @return int hours [1-24]
*/
protected function getHourInDay($date, $pattern = 'k')
{
if ($pattern != 'k')
{
throw new sfException('The pattern for hour in day is "k".');
}
return $date['hours'] + 1;
}
/**
* Gets the hours in AM/PM format, e.g [1-12]
*
* @param array getdate format.
* @param string a pattern.
* @return int hours in AM/PM format.
*/
protected function getHourInAMPM($date, $pattern = 'K')
{
if ($pattern != 'K')
{
throw new sfException('The pattern for hour in AM/PM is "K".');
}
return ($date['hours'] + 1) % 12;
}
}

View File

@ -0,0 +1,546 @@
<?php
/**
* sfDateTimeFormatInfo class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfDateTimeFormatInfo.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* Defines how DateTime values are formatted and displayed, depending
* on the culture.
*
* This class contains information, such as date patterns, time patterns,
* and AM/PM designators.
*
* To create a sfDateTimeFormatInfo for a specific culture, create a
* sfCultureInfo for that culture and retrieve the sfCultureInfo.sfDateTimeFormat
* property. For example:
* <code>
* $culture = new sfCultureInfo('en_AU');
* $dtfi = $culture->DateTimeFormat;
* </code>
*
* To create a sfDateTimeFormatInfo for the invariant culture, use
* <code>
* sfDateTimeFormatInfo::getInstance($culture=null);
* </code>
* you may pass a sfCultureInfo parameter $culture to get the sfDateTimeFormatInfo
* for a specific culture.
*
* sfDateTime values are formatted using standard or custom patterns stored in
* the properties of a sfDateTimeFormatInfo.
*
* The standard patterns can be replaced with custom patterns by setting the
* associated properties of sfDateTimeFormatInfo.
*
* The following table lists the standard format characters for each standard
* pattern and the associated sfDateTimeFormatInfo property that can be set to
* modify the standard pattern. The format characters are case-sensitive;
* for example, 'g' and 'G' represent slightly different patterns.
*
* <code>
* Format Character Associated Property Example Format Pattern (en-US)
* --------------------------------------------------------------------------
* d ShortDatePattern MM/dd/yyyy
* D LongDatePattern dddd, dd MMMM yyyy
* F FullDateTimePattern dddd, dd MMMM yyyy HH:mm:ss
* m, M MonthDayPattern MMMM dd
* r, R RFC1123Pattern ddd, dd MMM yyyy HH':'mm':'ss 'GMT'
* s SortableDateTimePattern yyyy'-'MM'-'dd'T'HH':'mm':'ss
* t ShortTimePattern HH:mm
* T LongTimePattern HH:mm:ss
* Y YearMonthPattern yyyy MMMM
* --------------------------------------------------------------------------
* </code>
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 03 22:30:31 EST 2004
* @package System.I18N.core
*/
class sfDateTimeFormatInfo
{
/**
* ICU date time formatting data.
*/
protected $data = array();
/**
* A list of properties that are accessable/writable.
*/
protected $properties = array();
/**
* Allows functions that begins with 'set' to be called directly
* as an attribute/property to retrieve the value.
*
* @return mixed
*/
function __get($name)
{
$getProperty = 'get'.$name;
if (in_array($getProperty, $this->properties))
{
return $this->$getProperty();
}
else
{
throw new sfException(sprintf('Property %s does not exists.', $name));
}
}
/**
* Allows functions that begins with 'set' to be called directly
* as an attribute/property to set the value.
*/
function __set($name, $value)
{
$setProperty = 'set'.$name;
if (in_array($setProperty, $this->properties))
{
$this->$setProperty($value);
}
else
{
throw new sfException(sprintf('Property %s can not be set.', $name));
}
}
/**
* Initializes a new writable instance of the sfDateTimeFormatInfo class
* that is dependent on the ICU data for date time formatting
* information. <b>N.B.</b>You should not initialize this class directly
* unless you know what you are doing. Please use use
* sfDateTimeFormatInfo::getInstance() to create an instance.
*
* @param array ICU data for date time formatting.
* @see getInstance()
*/
function __construct($data = array())
{
$this->properties = get_class_methods($this);
if (empty($data))
{
throw new sfException('Please provide the ICU data to initialize.');
}
$this->data = $data;
}
/**
* Gets the internal ICU data for date time formatting.
*
* @return array ICU date time formatting data.
*/
protected function getData()
{
return $this->data;
}
/**
* Gets the default sfDateTimeFormatInfo that is culture-independent (invariant).
*
* @return sfDateTimeFormatInfo default sfDateTimeFormatInfo.
*/
static function getInvariantInfo()
{
static $invariant;
if (is_null($invariant))
{
$culture = sfCultureInfo::getInvariantCulture();
$invariant = $culture->DateTimeFormat;
}
return $invariant;
}
/**
* Returns the sfDateTimeFormatInfo associated with the specified culture.
*
* @param sfCultureInfo the culture that gets the sfDateTimeFormat property.
* @return sfDateTimeFormatInfo sfDateTimeFormatInfo for the specified
* culture.
*/
static function getInstance($culture = null)
{
if ($culture instanceof sfCultureInfo)
{
return $culture->DateTimeFormat;
}
else if (is_string($culture))
{
$cultureInfo = new sfCultureInfo($culture);
return $cultureInfo->DateTimeFormat;
}
else
{
$cultureInfo = sfCultureInfo::getInvariantCulture();
return $cultureInfo->DateTimeFormat;
}
}
/**
* A one-dimensional array of type String containing
* the culture-specific abbreviated names of the days
* of the week. The array for InvariantInfo contains
* "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", and "Sat".
*
* @return array abbreviated day names
*/
function getAbbreviatedDayNames()
{
return $this->data['dayNames']['format']['abbreviated'];
}
/**
* Sets the abbreviated day names. The value should be
* an array of string starting with Sunday and ends in Saturady.
* For example,
* <code>array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");</code>
*
* @param array abbreviated day names.
*/
function setAbbreviatedDayNames($value)
{
$this->data['dayNames']['format']['abbreviated'] = $value;
}
/**
* A one-dimensional array of type String containing
* the culture-specific narrow names of the days
* of the week. The array for InvariantInfo contains
* "S", "M", "T", "W", "T", "F", and "S".
*
* @return array narrow day names
*/
function getNarrowDayNames()
{
return $this->data['dayNames']['format']['narrow'];
}
/**
* Sets the narrow day names. The value should be
* an array of string starting with Sunday and ends in Saturady.
* For example,
* <code>array("S", "M", "T", "W", "T", "F", "S");</code>
*
* @param array narrow day names.
*/
function setNarrowDayNames($value)
{
$this->data['dayNames']['format']['narrow'] = $value;
}
/**
* A one-dimensional array of type String containing the
* culture-specific full names of the days of the week.
* The array for InvariantInfo contains "Sunday", "Monday",
* "Tuesday", "Wednesday", "Thursday", "Friday", and "Saturday".
*
* @return array day names
*/
function getDayNames()
{
return $this->data['dayNames']['format']['wide'];
}
/**
* Sets the day names. The value should be
* an array of string starting with Sunday and ends in Saturady.
* For example,
* <code>array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
* "Friday", "Saturday".);</code>
*
* @param array day names.
*/
function setDayNames($value)
{
$this->data['dayNames']['format']['wide'] = $value;
}
/**
* A one-dimensional array of type String containing the
* culture-specific narrow names of the months. The array
* for InvariantInfo contains "J", "F", "M", "A", "M", "J",
* "J", "A", "S", "O", "N", and "D".
*
* @return array narrow month names.
*/
function getNarrowMonthNames()
{
return $this->data['monthNames']['format']['narrow'];
}
/**
* Sets the narrow month names. The value should be
* an array of string starting with J and ends in D.
* For example,
* <code>array("J","F","M","A","M","J","J","A","S","O","N","D");</code>
*
* @param array month names.
*/
function setNarrowMonthNames($value)
{
$this->data['monthNames']['format']['narrow'] = $value;
}
/**
* A one-dimensional array of type String containing the
* culture-specific abbreviated names of the months. The array
* for InvariantInfo contains "Jan", "Feb", "Mar", "Apr", "May",
* "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", and "Dec".
*
* @return array abbreviated month names.
*/
function getAbbreviatedMonthNames()
{
return $this->data['monthNames']['format']['abbreviated'];
}
/**
* Sets the abbreviated month names. The value should be
* an array of string starting with Jan and ends in Dec.
* For example,
* <code>array("Jan", "Feb", "Mar", "Apr", "May", "Jun",
* "Jul", "Aug", "Sep","Oct","Nov","Dec");</code>
*
* @param array month names.
*/
function setAbbreviatedMonthNames($value)
{
$this->data['monthNames']['format']['abbreviated'] = $value;
}
/**
* A one-dimensional array of type String containing the
* culture-specific full names of the months. The array for
* InvariantInfo contains "January", "February", "March", "April",
* "May", "June", "July", "August", "September", "October", "November",
* and "December"
*
* @return array month names.
*/
function getMonthNames()
{
return $this->data['monthNames']['format']['wide'];
}
/**
* Sets the month names. The value should be
* an array of string starting with Janurary and ends in December.
* For example,
* <code>array("January", "February", "March", "April", "May", "June",
* "July", "August", "September","October","November","December");</code>
*
* @param array month names.
*/
function setMonthNames($value)
{
$this->data['monthNames']['format']['wide'] = $value;
}
/**
* A string containing the name of the era.
*
* @param int era The integer representing the era.
* @return string the era name.
*/
function getEra($era)
{
return $this->data['eras']['abbreviated'][$era];
}
/**
* The string designator for hours that are "ante meridiem" (before noon).
* The default for InvariantInfo is "AM".
*
* @return string AM designator.
*/
function getAMDesignator()
{
$result = $this->getAMPMMarkers();
return $result[0];
}
/**
* Sets the AM Designator. For example, 'AM'.
*
* @param string AM designator.
*/
function setAMDesignator($value)
{
$markers = $this->getAMPMMarkers();
$markers[0] = $value;
$this->setAMPMMarkers($markers);
}
/**
* The string designator for hours that are "post meridiem" (after noon).
* The default for InvariantInfo is "PM".
*
* @return string PM designator.
*/
function getPMDesignator()
{
$result = $this->getAMPMMarkers();
return $result[1];
}
/**
* Sets the PM Designator. For example, 'PM'.
*
* @param string PM designator.
*/
function setPMDesignator($value)
{
$markers = $this->getAMPMMarkers();
$markers[1] = $value;
$this->setAMPMMarkers($markers);
}
/**
* Gets the AM and PM markers array.
* Default InvariantInfo for AM and PM is <code>array('AM','PM');</code>
*
* @return array AM and PM markers
*/
function getAMPMMarkers()
{
return $this->data['AmPmMarkers'];
}
/**
* Sets the AM and PM markers array.
* For example <code>array('AM','PM');</code>
*
* @param array AM and PM markers
*/
function setAMPMMarkers($value)
{
$this->data['AmPmMarkers'] = $value;
}
/**
* Returns the full time pattern "HH:mm:ss z" (default).
* This is culture sensitive.
*
* @return string pattern "HH:mm:ss z".
*/
function getFullTimePattern()
{
return $this->data['DateTimePatterns'][0];
}
/**
* Returns the long time pattern "HH:mm:ss z" (default).
* This is culture sensitive.
*
* @return string pattern "HH:mm:ss z".
*/
function getLongTimePattern()
{
return $this->data['DateTimePatterns'][1];
}
/**
* Returns the medium time pattern "HH:mm:ss" (default).
* This is culture sensitive.
*
* @return string pattern "HH:mm:ss".
*/
function getMediumTimePattern()
{
return $this->data['DateTimePatterns'][2];
}
/**
* Returns the short time pattern "HH:mm" (default).
* This is culture sensitive.
*
* @return string pattern "HH:mm".
*/
function getShortTimePattern()
{
return $this->data['DateTimePatterns'][3];
}
/**
* Returns the full date pattern "EEEE, yyyy MMMM dd" (default).
* This is culture sensitive.
* @return string pattern "EEEE, yyyy MMMM dd".
*/
function getFullDatePattern()
{
return $this->data['DateTimePatterns'][4];
}
/**
* Returns the long date pattern "yyyy MMMM d" (default).
* This is culture sensitive.
* @return string pattern "yyyy MMMM d".
*/
function getLongDatePattern()
{
return $this->data['DateTimePatterns'][5];
}
/**
* Returns the medium date pattern "yyyy MMMM d" (default).
* This is culture sensitive.
* @return string pattern "yyyy MMM d".
*/
function getMediumDatePattern()
{
return $this->data['DateTimePatterns'][6];
}
/**
* Returns the short date pattern "yy/MM/dd" (default).
* This is culture sensitive.
*
* @return string pattern "yy/MM/dd".
*/
function getShortDatePattern()
{
return $this->data['DateTimePatterns'][7];
}
/**
* Returns the date time order pattern, "{1} {0}" (default).
* This is culture sensitive.
*
* @return string pattern "{1} {0}".
*/
function getDateTimeOrderPattern()
{
return $this->data['DateTimePatterns'][8];
}
/**
* Formats the date and time in a culture sensitive paterrn.
* The default is "Date Time".
*
* @return string date and time formated
*/
function formatDateTime($date, $time)
{
return str_replace(array('{0}','{1}'), array($time, $date), $this->getDateTimeOrderPattern());
}
}

198
lib/symfony/i18n/sfI18N.class.php Executable file
View File

@ -0,0 +1,198 @@
<?php
/*
* This file is part of the symfony package.
* (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
*
* @package symfony
* @subpackage i18n
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @version SVN: $Id: sfI18N.class.php 4340 2007-06-23 06:47:05Z fabien $
*/
class sfI18N
{
protected
$context = null,
$globalMessageSource = null,
$messageSource = null,
$messageFormat = null;
static protected
$instance = null;
static public function getInstance()
{
if (!isset(self::$instance))
{
$class = __CLASS__;
self::$instance = new $class();
}
return self::$instance;
}
public function initialize($context)
{
$this->context = $context;
$this->globalMessageSource = $this->createMessageSource(sfConfig::get('sf_app_i18n_dir'));
$this->globalMessageFormat = $this->createMessageFormat($this->globalMessageSource);
}
public function setMessageSourceDir($dir, $culture)
{
$this->messageSource = $this->createMessageSource($dir);
$this->messageSource->setCulture($culture);
$this->messageFormat = $this->createMessageFormat($this->messageSource);
$this->globalMessageSource->setCulture($culture);
$this->globalMessageFormat = $this->createMessageFormat($this->globalMessageSource);
}
public function createMessageSource($dir)
{
if (in_array(sfConfig::get('sf_i18n_source'), array('Creole', 'MySQL', 'SQLite')))
{
$messageSource = sfMessageSource::factory(sfConfig::get('sf_i18n_source'), sfConfig::get('sf_i18n_database', 'default'));
}
else
{
$messageSource = sfMessageSource::factory(sfConfig::get('sf_i18n_source'), $dir);
}
if (sfConfig::get('sf_i18n_cache'))
{
$subdir = str_replace(str_replace('/', DIRECTORY_SEPARATOR, sfConfig::get('sf_root_dir')), '', $dir);
$cacheDir = str_replace('/', DIRECTORY_SEPARATOR, sfConfig::get('sf_i18n_cache_dir').$subdir);
$cache = new sfMessageCache();
$cache->initialize(array(
'cacheDir' => $cacheDir,
'lifeTime' => 86400,
));
$messageSource->setCache($cache);
}
return $messageSource;
}
public function createMessageFormat($source)
{
$messageFormat = new sfMessageFormat($source, sfConfig::get('sf_charset'));
if (sfConfig::get('sf_debug') && sfConfig::get('sf_i18n_debug'))
{
$messageFormat->setUntranslatedPS(array(sfConfig::get('sf_i18n_untranslated_prefix'), sfConfig::get('sf_i18n_untranslated_suffix')));
}
return $messageFormat;
}
public function setCulture($culture)
{
if ($this->messageSource)
{
$this->messageSource->setCulture($culture);
$this->messageFormat = $this->createMessageFormat($this->messageSource);
}
$this->globalMessageSource->setCulture($culture);
$this->globalMessageFormat = $this->createMessageFormat($this->globalMessageSource);
}
public function getMessageSource()
{
return $this->messageSource;
}
public function getGlobalMessageSource()
{
return $this->globalMessageSource;
}
public function getMessageFormat()
{
return $this->messageFormat;
}
public function getGlobalMessageFormat()
{
return $this->globalMessageFormat;
}
public function __($string, $args = array(), $catalogue = 'messages')
{
$retval = $this->messageFormat->formatExists($string, $args, $catalogue);
if (!$retval)
{
$retval = $this->globalMessageFormat->format($string, $args, $catalogue);
}
return $retval;
}
public static function getCountry($iso, $culture)
{
$c = new sfCultureInfo($culture);
$countries = $c->getCountries();
return (array_key_exists($iso, $countries)) ? $countries[$iso] : '';
}
public static function getNativeName($culture)
{
$cult = new sfCultureInfo($culture);
return $cult->getNativeName();
}
// Return timestamp from a date formatted with a given culture
public static function getTimestampForCulture($date, $culture)
{
list($d, $m, $y) = self::getDateForCulture($date, $culture);
return mktime(0, 0, 0, $m, $d, $y);
}
// Return a d, m and y from a date formatted with a given culture
public static function getDateForCulture($date, $culture)
{
if (!$date) return 0;
$dateFormatInfo = @sfDateTimeFormatInfo::getInstance($culture);
$dateFormat = $dateFormatInfo->getShortDatePattern();
// We construct the regexp based on date format
$dateRegexp = preg_replace('/[dmy]+/i', '(\d+)', $dateFormat);
// We parse date format to see where things are (m, d, y)
$a = array(
'd' => strpos($dateFormat, 'd'),
'm' => strpos($dateFormat, 'M'),
'y' => strpos($dateFormat, 'y'),
);
$tmp = array_flip($a);
ksort($tmp);
$i = 0;
$c = array();
foreach ($tmp as $value) $c[++$i] = $value;
$datePositions = array_flip($c);
// We find all elements
if (preg_match("~$dateRegexp~", $date, $matches))
{
// We get matching timestamp
return array($matches[$datePositions['d']], $matches[$datePositions['m']], $matches[$datePositions['y']]);
}
else
{
return null;
}
}
}

View File

@ -0,0 +1,126 @@
<?php
/**
* sfIMessageSource interface file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfIMessageSource.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* sfIMessageSource interface.
*
* All messages source used by MessageFormat must be of sfIMessageSource.
* It defines a set of operations to add and retrieve messages from the
* message source. In addition, message source can load a particular
* catalogue.
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 24 17:40:19 EST 2004
* @package System.I18N.core
*/
interface sfIMessageSource
{
/**
* Loads the translation table for this particular catalogue.
* The translation should be loaded in the following order.
* # [1] call getCatalogueList($catalogue) to get a list of variants for for the specified $catalogue.
* # [2] for each of the variants, call getSource($variant) to get the resource, could be a file or catalogue ID.
* # [3] verify that this resource is valid by calling isValidSource($source)
* # [4] try to get the messages from the cache
* # [5] if a cache miss, call load($source) to load the message array
* # [6] store the messages to cache.
* # [7] continue with the foreach loop, e.g. goto [2].
*
* @param string a catalogue to load
* @return boolean true if loaded, false otherwise.
*/
function load($catalogue = 'messages');
/**
* Gets the translation table. This includes all the loaded sections.
* It must return a 2 level array of translation strings.
* # "catalogue+variant" the catalogue and its variants.
* # "source string" translation keys, and its translations.
* <code>
* array('catalogue+variant' =>
* array('source string' => 'target string', ...)
* ...),
* ...);
* </code>
*
* @return array 2 level array translation table.
*/
function read();
/**
* Saves the list of untranslated blocks to the translation source.
* If the translation was not found, you should add those
* strings to the translation source via the <b>append()</b> method.
*
* @param string the catalogue to add to
* @return boolean true if saved successfuly, false otherwise.
*/
function save($catalogue = 'messages');
/**
* Adds a untranslated message to the source. Need to call save()
* to save the messages to source.
*
* @param string message to add
* @return void
*/
function append($message);
/**
* Deletes a particular message from the specified catalogue.
*
* @param string the source message to delete.
* @param string the catalogue to delete from.
* @return boolean true if deleted, false otherwise.
*/
function delete($message, $catalogue = 'messages');
/**
* Updates the translation.
*
* @param string the source string.
* @param string the new translation string.
* @param string comments
* @param string the catalogue of the translation.
* @return boolean true if translation was updated, false otherwise.
*/
function update($text, $target, $comments, $catalogue = 'messages');
/**
* Returns a list of catalogue as key and all it variants as value.
*
* @return array list of catalogues
*/
function catalogues();
/**
* Set the culture for this particular message source.
*
* @param string the Culture name.
*/
function setCulture($culture);
/**
* Get the culture identifier for the source.
*
* @return string culture identifier.
*/
function getCulture();
}

View File

@ -0,0 +1,139 @@
<?php
/**
* Translation table cache.
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfMessageCache.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* Cache the translation table into the file system.
* It can cache each cataloug+variant or just the whole section.
*
* @package System.I18N.core
* @author $Author: weizhuo $
* @version $Id: sfMessageCache.class.php 4340 2007-06-23 06:47:05Z fabien $
*/
class sfMessageCache
{
/**
* Cache Lite instance.
*/
protected $cache;
/**
* Cache life time, default is 1 year.
*/
protected $lifetime = 3153600;
/**
* Creates a new Translation cache.
*
* @param string $cacheDir Directory to store the cache files.
*/
public function initialize($options = array())
{
$this->cache = new sfFileCache();
$this->cache->initialize($options);
}
/**
* Gets the cache life time.
*
* @return int Cache life time.
*/
public function getLifeTime()
{
return $this->lifetime;
}
/**
* Sets the cache life time.
*
* @param int $time Cache life time.
*/
public function setLifeTime($time)
{
$this->lifetime = intval($time);
}
/**
* Gets the cache file ID based section and locale.
*
* @param string $catalogue The translation section.
* @param string $culture The translation locale, e.g. "en_AU".
*/
protected function getID($catalogue, $culture)
{
return $culture;
}
/**
* Gets the cache file GROUP based section and locale.
*
* @param string $catalogue The translation section.
* @param string $culture The translation locale, e.g. "en_AU".
*/
protected function getGroup($catalogue, $culture)
{
return $catalogue;
}
/**
* Gets the data from the cache.
*
* @param string $catalogue The translation section.
* @param string $culture The translation locale, e.g. "en_AU".
* @param string $filename If the source is a file, this file's modified time is newer than the cache's modified time, no cache hit.
* @return mixed Boolean FALSE if no cache hit. Otherwise, translation
* table data for the specified section and locale.
*/
public function get($catalogue, $culture, $lastmodified = 0)
{
$ID = $this->getID($catalogue, $culture);
$group = $this->getGroup($catalogue, $culture);
if ($lastmodified <= 0 || $lastmodified > $this->cache->lastModified($ID, $group))
{
return false;
}
return unserialize($this->cache->get($ID, $group));
}
/**
* Saves the data to cache for the specified section and locale.
*
* @param array $data The data to save.
* @param string $catalogue The translation section.
* @param string $culture The translation locale, e.g. "en_AU".
*/
public function save($data, $catalogue, $culture)
{
$ID = $this->getID($catalogue, $culture);
$group = $this->getGroup($catalogue, $culture);
return $this->cache->set($ID, $group, serialize($data));
}
/**
* Cleans up the cache for the specified section and locale.
*
* @param string $catalogue The translation section.
* @param string $culture The translation locale, e.g. "en_AU".
*/
public function clean($catalogue, $culture)
{
$group = $this->getGroup($catalogue, $culture);
$this->cache->clean($group);
}
/**
* Flushes the cache. Deletes all the cache files.
*/
public function clear()
{
$this->cache->clean();
}
}

View File

@ -0,0 +1,301 @@
<?php
/**
* sfMessageFormat class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfMessageFormat.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* Gets the encoding utilities
*/
require_once(dirname(__FILE__).'/util.php');
/**
* sfMessageFormat class.
*
* Format a message, that is, for a particular message find the
* translated message. The following is an example using
* a SQLite database to store the translation message.
* Create a new message format instance and echo "Hello"
* in simplified Chinese. This assumes that the world "Hello"
* is translated in the database.
*
* <code>
* $source = sfMessageSource::factory('SQLite', 'sqlite://messages.db');
* $source->setCulture('zh_CN');
* $source->setCache(new sfMessageCache('./tmp'));
*
* $formatter = new sfMessageFormat($source);
*
* echo $formatter->format('Hello');
* </code>
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 24 20:46:16 EST 2004
* @package System.I18N.core
*/
class sfMessageFormat
{
/**
* The message source.
* @var sfMessageSource
*/
protected $source;
/**
* A list of loaded message catalogues.
* @var array
*/
protected $catalogues = array();
/**
* The translation messages.
* @var array
*/
protected $messages = array();
/**
* A list of untranslated messages.
* @var array
*/
protected $untranslated = array();
/**
* The prefix and suffix to append to untranslated messages.
* @var array
*/
protected $postscript = array('', '');
/**
* Set the default catalogue.
* @var string
*/
public $Catalogue;
/**
* Output encoding charset
* @var string
*/
protected $charset = 'UTF-8';
/**
* Constructor.
* Create a new instance of sfMessageFormat using the messages
* from the supplied message source.
*
* @param MessageSource the source of translation messages.
* @param string charset for the message output.
*/
function __construct(sfIMessageSource $source, $charset = 'UTF-8')
{
$this->source = $source;
$this->setCharset($charset);
}
/**
* Sets the charset for message output.
*
* @param string charset, default is UTF-8
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Gets the charset for message output. Default is UTF-8.
*
* @return string charset, default UTF-8
*/
public function getCharset()
{
return $this->charset;
}
/**
* Loads the message from a particular catalogue. A listed
* loaded catalogues is kept to prevent reload of the same
* catalogue. The load catalogue messages are stored
* in the $this->message array.
*
* @param string message catalogue to load.
*/
protected function loadCatalogue($catalogue)
{
if (in_array($catalogue, $this->catalogues))
{
return;
}
if ($this->source->load($catalogue))
{
$this->messages[$catalogue] = $this->source->read();
$this->catalogues[] = $catalogue;
}
}
/**
* Formats the string. That is, for a particular string find
* the corresponding translation. Variable subsitution is performed
* for the $args parameter. A different catalogue can be specified
* using the $catalogue parameter.
* The output charset is determined by $this->getCharset();
*
* @param string the string to translate.
* @param array a list of string to substitute.
* @param string get the translation from a particular message
* @param string charset, the input AND output charset catalogue.
* @return string translated string.
*/
public function format($string, $args = array(), $catalogue = null, $charset = null)
{
if (empty($charset))
{
$charset = $this->getCharset();
}
$s = $this->formatString(I18N_toUTF8($string, $charset), $args, $catalogue);
return I18N_toEncoding($s, $charset);
}
public function formatExists($string, $args = array(), $catalogue = null, $charset = null)
{
if (empty($charset))
{
$charset = $this->getCharset();
}
$s = $this->getFormattedString(I18N_toUTF8($string, $charset), $args, $catalogue);
return I18N_toEncoding($s, $charset);
}
/**
* Do string translation.
*
* @param string the string to translate.
* @param array a list of string to substitute.
* @param string get the translation from a particular message catalogue.
* @return string translated string.
*/
protected function formatString($string, $args = array(), $catalogue = null)
{
if (empty($args))
{
$args = array();
}
$target = $this->getFormattedString($string, $args, $catalogue);
// well we did not find the translation string.
if (!$target)
{
$this->source->append($string);
$target = $this->postscript[0].$this->replaceArgs($string, $args).$this->postscript[1];
}
return $target;
}
protected function getFormattedString($string, $args = array(), $catalogue = null)
{
if (empty($catalogue))
{
$catalogue = empty($this->catalogue) ? 'messages' : $this->catalogue;
}
if (empty($args))
{
$args = array();
}
$this->loadCatalogue($catalogue);
foreach ($this->messages[$catalogue] as $variant)
{
// foreach of the translation units
foreach ($variant as $source => $result)
{
// we found it, so return the target translation
if ($source == $string)
{
// check if it contains only strings.
if (is_string($result))
{
$target = $result;
}
else
{
$target = $result[0];
}
// found, but untranslated
if (empty($target))
{
return $this->postscript[0].$this->replaceArgs($string, $args).$this->postscript[1];
}
else
{
return $this->replaceArgs($target, $args);
}
}
}
}
return null;
}
protected function replaceArgs($string, $args)
{
// replace object with strings
foreach ($args as $key => $value)
{
if (is_object($value) && method_exists($value, '__toString'))
{
$args[$key] = $value->__toString();
}
}
return strtr($string, $args);
}
/**
* Gets the message source.
*
* @return MessageSource
*/
function getSource()
{
return $this->source;
}
/**
* Sets the prefix and suffix to append to untranslated messages.
* e.g. $postscript=array('[T]','[/T]'); will output
* "[T]Hello[/T]" if the translation for "Hello" can not be determined.
*
* @param array first element is the prefix, second element the suffix.
*/
function setUntranslatedPS($postscript)
{
if (is_array($postscript) && count($postscript) >= 2)
{
$this->postscript[0] = $postscript[0];
$this->postscript[1] = $postscript[1];
}
}
}

View File

@ -0,0 +1,325 @@
<?php
/**
* sfMessageSource class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfMessageSource.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* Abstract sfMessageSource class.
*
* The base class for all sfMessageSources. Message sources must be instantiated
* using the factory method. The default valid sources are
*
* # XLIFF -- using XML XLIFF format to store the translation messages.
* # SQLite -- Store the translation messages in a SQLite database.
* # MySQL -- Using a MySQL database to store the messages.
* # gettext -- Translated messages are stored in the gettext format.
*
* A custom message source can be instantiated by specifying the filename
* parameter to point to the custom class file. E.g.
* <code>
* $resource = '...'; //custom message source resource
* $classfile = '../sfMessageSource_MySource.php'; //custom message source
* $source = sfMessageSource::factory('MySource', $resource, $classfile);
* </code>
*
* If you are writting your own message sources, pay attention to the
* loadCatalogue method. It details how the resources are loaded and cached.
* See also the existing message source types as examples.
*
* The following example instantiates a MySQL message source, set the culture,
* set the cache handler, and use the source in a message formatter.
* The messages are store in a database named "messages". The source parameter
* for the actory method is a PEAR DB style DSN.
* <code>
* $dsn = 'mysql://username:password@localhost/messages';
* $source = sfMessageSource::factory('MySQL', $dsn);
*
* //set the culture and cache, store the cache in the /tmp directory.
* $source->setCulture('en_AU')l
* $source->setCache(new sfMessageCache('/tmp'));
*
* $formatter = new sfMessageFormat($source);
* </code>
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 24 19:55:49 EST 2004
* @package System.I18N.core
*/
abstract class sfMessageSource implements sfIMessageSource
{
/**
* The culture name for this message source.
* @var string
*/
protected $culture;
/**
* Array of translation messages.
* @var array
*/
protected $messages = array();
/**
* The source of message translations.
* @var string
*/
protected $source;
/**
* The translation cache.
* @var sfMessageCache
*/
protected $cache;
protected $untranslated = array();
/**
* Private constructor. sfMessageSource must be initialized using
* the factory method.
*/
private function __construct()
{
//throw new sfException('Please use the factory method to instantiate.');
}
/**
* Factory method to instantiate a new sfMessageSource depending on the
* source type. The built-in source types are 'XLIFF', 'SQLite',
* 'MySQL', 'gettext' and Creole. The source parameter is dependent on the
* source type. For 'gettext' and 'XLIFF', it should point to the directory
* where the messages are stored. For database types, e.g. 'SQLite' and
* 'MySQL', it should be a PEAR DB style DSN string.
*
* Custom message source are possible by supplying the a filename parameter
* in the factory method.
*
* @param string the message source type.
* @param string the location of the resource.
* @param string the filename of the custom message source.
* @return sfMessageSource a new message source of the specified type.
* @throws sfException
*/
static function factory($type, $source = '.', $filename = '')
{
if ($filename)
{
if (!is_file($filename))
{
throw new sfException(sprintf("File %s not found.", $filename));
}
include_once($filename);
}
$class = 'sfMessageSource_'.$type;
if (!class_exists($class))
{
throw new sfException(sprintf('Unable to find type "%s".', $type));
}
return new $class($source);
}
/**
* Loads a particular message catalogue. Use read() to
* to get the array of messages. The catalogue loading sequence
* is as follows:
*
* # [1] Call getCatalogueList($catalogue) to get a list of variants for for the specified $catalogue.
* # [2] For each of the variants, call getSource($variant) to get the resource, could be a file or catalogue ID.
* # [3] Verify that this resource is valid by calling isValidSource($source)
* # [4] Try to get the messages from the cache
* # [5] If a cache miss, call load($source) to load the message array
* # [6] Store the messages to cache.
* # [7] Continue with the foreach loop, e.g. goto [2].
*
* @param string a catalogue to load
* @return boolean true if loaded, false otherwise.
* @see read()
*/
function load($catalogue = 'messages')
{
$variants = $this->getCatalogueList($catalogue);
$this->messages = array();
foreach ($variants as $variant)
{
$source = $this->getSource($variant);
if ($this->isValidSource($source) == false)
{
continue;
}
$loadData = true;
if ($this->cache)
{
$data = $this->cache->get($variant, $this->culture, $this->getLastModified($source));
if (is_array($data))
{
$this->messages[$variant] = $data;
$loadData = false;
}
unset($data);
}
if ($loadData)
{
$data = &$this->loadData($source);
if (is_array($data))
{
$this->messages[$variant] = $data;
if ($this->cache)
{
$this->cache->save($data, $variant, $this->culture);
}
}
unset($data);
}
}
return true;
}
/**
* Gets the array of messages.
*
* @param parameter
* @return array translation messages.
*/
public function read()
{
return $this->messages;
}
/**
* Gets the cache handler for this source.
*
* @return sfMessageCache cache handler
*/
public function getCache()
{
return $this->cache;
}
/**
* Sets the cache handler for caching the messages.
*
* @param sfMessageCache the cache handler.
*/
public function setCache(sfMessageCache $cache)
{
$this->cache = $cache;
}
/**
* Adds a untranslated message to the source. Need to call save()
* to save the messages to source.
*
* @param string message to add
*/
public function append($message)
{
if (!in_array($message, $this->untranslated))
{
$this->untranslated[] = $message;
}
}
/**
* Sets the culture for this message source.
*
* @param string culture name
*/
public function setCulture($culture)
{
$this->culture = $culture;
}
/**
* Gets the culture identifier for the source.
*
* @return string culture identifier.
*/
public function getCulture()
{
return $this->culture;
}
/**
* Gets the last modified unix-time for this particular catalogue+variant.
*
* @param string catalogue+variant
* @return int last modified in unix-time format.
*/
protected function getLastModified($source)
{
return 0;
}
/**
* Loads the message for a particular catalogue+variant.
* This methods needs to implemented by subclasses.
*
* @param string catalogue+variant.
* @return array of translation messages.
*/
protected function &loadData($variant)
{
return array();
}
/**
* Gets the source, this could be a filename or database ID.
*
* @param string catalogue+variant
* @return string the resource key
*/
protected function getSource($variant)
{
return $variant;
}
/**
* Determines if the source is valid.
*
* @param string catalogue+variant
* @return boolean true if valid, false otherwise.
*/
protected function isValidSource($source)
{
return false;
}
/**
* Gets all the variants of a particular catalogue.
* This method must be implemented by subclasses.
*
* @param string catalogue name
* @return array list of all variants for this catalogue.
*/
protected function getCatalogueList($catalogue)
{
return array();
}
}

View File

@ -0,0 +1,511 @@
<?php
/**
* sfMessageSource_MySQL class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfMessageSource_MySQL.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* Get the I18N utility file, contains the DSN parser.
*/
require_once(dirname(__FILE__).'/util.php');
/**
* sfMessageSource_MySQL class.
*
* Retrieve the message translation from a MySQL database.
*
* See the MessageSource::factory() method to instantiate this class.
*
* MySQL schema:
*
* CREATE TABLE `catalogue` (
* `cat_id` int(11) NOT NULL auto_increment,
* `name` varchar(100) NOT NULL default '',
* `source_lang` varchar(100) NOT NULL default '',
* `target_lang` varchar(100) NOT NULL default '',
* `date_created` int(11) NOT NULL default '0',
* `date_modified` int(11) NOT NULL default '0',
* `author` varchar(255) NOT NULL default '',
* PRIMARY KEY (`cat_id`)
* ) TYPE=InnoDB;
*
* CREATE TABLE `trans_unit` (
* `msg_id` int(11) NOT NULL auto_increment,
* `cat_id` int(11) NOT NULL default '1',
* `id` varchar(255) NOT NULL default '',
* `source` text NOT NULL,
* `target` text NOT NULL,
* `comments` text NOT NULL,
* `date_added` int(11) NOT NULL default '0',
* `date_modified` int(11) NOT NULL default '0',
* `author` varchar(255) NOT NULL default '',
* `translated` tinyint(1) NOT NULL default '0',
* PRIMARY KEY (`msg_id`)
* ) TYPE=InnoDB;
*
* Propel schema (in .xml format):
*
* <database ...>
* ...
* <table name="catalogue">
* <column name="cat_id" type="integer" required="true" primaryKey="true" autoincrement="true" />
* <column name="name" type="varchar" size="100" />
* <column name="source_lang" type="varchar" size="100" />
* <column name="target_lang" type="varchar" size="100" />
* <column name="date_created" type="timestamp" />
* <column name="date_modified" type="timestamp" />
* <column name="author" type="varchar" size="255" />
* </table>
*
* <table name="trans_unit">
* <column name="msg_id" type="integer" required="true" primaryKey="true" autoincrement="true" />
* <column name="cat_id" type="integer" />
* <foreign-key foreignTable="catalogue" onDelete="cascade">
* <reference local="cat_id" foreign="cat_id"/>
* </foreign-key>
* <column name="id" type="varchar" size="255" />
* <column name="source" type="longvarchar" />
* <column name="target" type="longvarchar" />
* <column name="comments" type="longvarchar" />
* <column name="date_created" type="timestamp" />
* <column name="date_modified" type="timestamp" />
* <column name="author" type="varchar" size="255" />
* <column name="translated" type="integer" />
* </table>
* ...
* </database>
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 24 16:58:58 EST 2004
* @package System.I18N.core
*/
class sfMessageSource_MySQL extends sfMessageSource
{
/**
* The datasource string, full DSN to the database.
* @var string
*/
protected $source;
/**
* The DSN array property, parsed by PEAR's DB DSN parser.
* @var array
*/
protected $dsn;
/**
* A resource link to the database
* @var db
*/
protected $db;
/**
* Constructor.
* Creates a new message source using MySQL.
*
* @param string MySQL datasource, in PEAR's DB DSN format.
* @see MessageSource::factory();
*/
function __construct($source)
{
$this->source = (string) $source;
$this->dsn = parseDSN($this->source);
$this->db = $this->connect();
}
/**
* Destructor, closes the database connection.
*/
function __destruct()
{
@mysql_close($this->db);
}
/**
* Connects to the MySQL datasource
*
* @return resource MySQL connection.
* @throws sfException, connection and database errors.
*/
protected function connect()
{
$dsninfo = $this->dsn;
if (isset($dsninfo['protocol']) && $dsninfo['protocol'] == 'unix')
{
$dbhost = ':'.$dsninfo['socket'];
}
else
{
$dbhost = $dsninfo['hostspec'] ? $dsninfo['hostspec'] : 'localhost';
if (!empty($dsninfo['port']))
{
$dbhost = ':' . $dsninfo['socket'];
}
}
$user = $dsninfo['username'];
$pw = $dsninfo['password'];
$connect_function = 'mysql_connect';
if ($dbhost && $user && $pw)
{
$conn = @$connect_function($dbhost, $user, $pw);
}
elseif ($dbhost && $user)
{
$conn = @$connect_function($dbhost, $user);
}
elseif ($dbhost)
{
$conn = @$connect_function($dbhost);
}
else
{
$conn = false;
}
if (empty($conn))
{
throw new sfException(sprintf('Error in connecting to %s.', $dsninfo));
}
if ($dsninfo['database'])
{
if (!@mysql_select_db($dsninfo['database'], $conn))
{
throw new sfException(sprintf('Error in connecting database, dsn: %s.', $dsninfo));
}
}
else
{
throw new sfException('Please provide a database for message translation.');
}
return $conn;
}
/**
* Gets the database connection.
*
* @return db database connection.
*/
public function connection()
{
return $this->db;
}
/**
* Gets an array of messages for a particular catalogue and cultural variant.
*
* @param string the catalogue name + variant
* @return array translation messages.
*/
protected function &loadData($variant)
{
$variant = mysql_real_escape_string($variant, $this->db);
$statement =
"SELECT t.id, t.source, t.target, t.comments
FROM trans_unit t, catalogue c
WHERE c.cat_id = t.cat_id
AND c.name = '{$variant}'
ORDER BY id ASC";
$rs = mysql_query($statement, $this->db);
$result = array();
while ($row = mysql_fetch_array($rs, MYSQL_NUM))
{
$source = $row[1];
$result[$source][] = $row[2]; //target
$result[$source][] = $row[0]; //id
$result[$source][] = $row[3]; //comments
}
return $result;
}
/**
* Gets the last modified unix-time for this particular catalogue+variant.
* We need to query the database to get the date_modified.
*
* @param string catalogue+variant
* @return int last modified in unix-time format.
*/
protected function getLastModified($source)
{
$source = mysql_real_escape_string($source, $this->db);
$rs = mysql_query("SELECT date_modified FROM catalogue WHERE name = '{$source}'", $this->db);
$result = $rs ? intval(mysql_result($rs, 0)) : 0;
return $result;
}
/**
* Checks if a particular catalogue+variant exists in the database.
*
* @param string catalogue+variant
* @return boolean true if the catalogue+variant is in the database, false otherwise.
*/
protected function isValidSource($variant)
{
$variant = mysql_real_escape_string ($variant, $this->db);
$rs = mysql_query("SELECT COUNT(*) FROM catalogue WHERE name = '{$variant}'", $this->db);
$row = mysql_fetch_array($rs, MYSQL_NUM);
$result = $row && $row[0] == '1';
return $result;
}
/**
* Gets all the variants of a particular catalogue.
*
* @param string catalogue name
* @return array list of all variants for this catalogue.
*/
protected function getCatalogueList($catalogue)
{
$variants = explode('_', $this->culture);
$catalogues = array($catalogue);
$variant = null;
for ($i = 0, $max = count($variants); $i < $max; $i++)
{
if (strlen($variants[$i]) > 0)
{
$variant .= $variant ? '_'.$variants[$i] : $variants[$i];
$catalogues[] = $catalogue.'.'.$variant;
}
}
return array_reverse($catalogues);
}
/**
* Retrieves catalogue details, array($cat_id, $variant, $count).
*
* @param string catalogue
* @return array catalogue details, array($cat_id, $variant, $count).
*/
protected function getCatalogueDetails($catalogue = 'messages')
{
if (empty($catalogue))
{
$catalogue = 'messages';
}
$variant = $catalogue.'.'.$this->culture;
$name = mysql_real_escape_string($this->getSource($variant), $this->db);
$rs = mysql_query("SELECT cat_id FROM catalogue WHERE name = '{$name}'", $this->db);
if (mysql_num_rows($rs) != 1)
{
return false;
}
$cat_id = intval(mysql_result($rs, 0));
// first get the catalogue ID
$rs = mysql_query("SELECT COUNT(*) FROM trans_unit WHERE cat_id = {$cat_id}", $this->db);
$count = intval(mysql_result($rs, 0));
return array($cat_id, $variant, $count);
}
/**
* Updates the catalogue last modified time.
*
* @return boolean true if updated, false otherwise.
*/
protected function updateCatalogueTime($cat_id, $variant)
{
$time = time();
$result = mysql_query("UPDATE catalogue SET date_modified = {$time} WHERE cat_id = {$cat_id}", $this->db);
if (!empty($this->cache))
{
$this->cache->clean($variant, $this->culture);
}
return $result;
}
/**
* Saves the list of untranslated blocks to the translation source.
* If the translation was not found, you should add those
* strings to the translation source via the <b>append()</b> method.
*
* @param string the catalogue to add to
* @return boolean true if saved successfuly, false otherwise.
*/
function save($catalogue = 'messages')
{
$messages = $this->untranslated;
if (count($messages) <= 0)
{
return false;
}
$details = $this->getCatalogueDetails($catalogue);
if ($details)
{
list($cat_id, $variant, $count) = $details;
}
else
{
return false;
}
if ($cat_id <= 0)
{
return false;
}
$inserted = 0;
$time = time();
foreach ($messages as $message)
{
$count++;
$inserted++;
$message = mysql_real_escape_string($message, $this->db);
$statement = "INSERT INTO trans_unit
(cat_id,id,source,date_added) VALUES
({$cat_id}, {$count},'{$message}',$time)";
mysql_query($statement, $this->db);
}
if ($inserted > 0)
{
$this->updateCatalogueTime($cat_id, $variant);
}
return $inserted > 0;
}
/**
* Deletes a particular message from the specified catalogue.
*
* @param string the source message to delete.
* @param string the catalogue to delete from.
* @return boolean true if deleted, false otherwise.
*/
function delete($message, $catalogue = 'messages')
{
$details = $this->getCatalogueDetails($catalogue);
if ($details)
{
list($cat_id, $variant, $count) = $details;
}
else
{
return false;
}
$text = mysql_real_escape_string($message, $this->db);
$statement = "DELETE FROM trans_unit WHERE cat_id = {$cat_id} AND source = '{$message}'";
$deleted = false;
mysql_query($statement, $this->db);
if (mysql_affected_rows($this->db) == 1)
{
$deleted = $this->updateCatalogueTime($cat_id, $variant);
}
return $deleted;
}
/**
* Updates the translation.
*
* @param string the source string.
* @param string the new translation string.
* @param string comments
* @param string the catalogue of the translation.
* @return boolean true if translation was updated, false otherwise.
*/
function update($text, $target, $comments, $catalogue = 'messages')
{
$details = $this->getCatalogueDetails($catalogue);
if ($details)
{
list($cat_id, $variant, $count) = $details;
}
else
{
return false;
}
$comments = mysql_real_escape_string($comments, $this->db);
$target = mysql_real_escape_string($target, $this->db);
$text = mysql_real_escape_string($text, $this->db);
$time = time();
$statement = "UPDATE trans_unit SET target = '{$target}', comments = '{$comments}', date_modified = '{$time}' WHERE cat_id = {$cat_id} AND source = '{$text}'";
$updated = false;
mysql_query($statement, $this->db);
if (mysql_affected_rows($this->db) == 1)
{
$updated = $this->updateCatalogueTime($cat_id, $variant);
}
return $updated;
}
/**
* Returns a list of catalogue as key and all it variants as value.
*
* @return array list of catalogues
*/
function catalogues()
{
$statement = 'SELECT name FROM catalogue ORDER BY name';
$rs = mysql_query($statement, $this->db);
$result = array();
while($row = mysql_fetch_array($rs, MYSQL_NUM))
{
$details = explode('.', $row[0]);
if (!isset($details[1]))
{
$details[1] = null;
}
$result[] = $details;
}
return $result;
}
}

View File

@ -0,0 +1,429 @@
<?php
/**
* sfMessageSource_SQLite class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfMessageSource_SQLite.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* Get the I18N utility file, contains the DSN parser.
*/
require_once(dirname(__FILE__).'/util.php');
/**
* sfMessageSource_SQLite class.
*
* Retrieve the message translation from a SQLite database.
*
* See the MessageSource::factory() method to instantiate this class.
*
* SQLite schema:
*
* CREATE TABLE catalogue (
* cat_id INTEGER PRIMARY KEY,
* name VARCHAR NOT NULL,
* source_lang VARCHAR ,
* target_lang VARCHAR ,
* date_created INT,
* date_modified INT,
* author VARCHAR);
*
* CREATE TABLE trans_unit (
* msg_id INTEGER PRIMARY KEY,
* cat_id INTEGER NOT NULL DEFAULT '1',
* id VARCHAR,
* source TEXT,
* target TEXT,
* comments TEXT,
* date_added INT,
* date_modified INT,
* author VARCHAR,
* translated INT(1) NOT NULL DEFAULT '0');
*
* Propel schema (in .xml format):
*
* <database ...>
* ...
* <table name="catalogue">
* <column name="cat_id" type="integer" required="true" primaryKey="true" autoincrement="true" />
* <column name="name" type="varchar" size="100" />
* <column name="source_lang" type="varchar" size="100" />
* <column name="target_lang" type="varchar" size="100" />
* <column name="date_created" type="timestamp" />
* <column name="date_modified" type="timestamp" />
* <column name="author" type="varchar" size="255" />
* </table>
*
* <table name="trans_unit">
* <column name="msg_id" type="integer" required="true" primaryKey="true" autoincrement="true" />
* <column name="cat_id" type="integer" />
* <foreign-key foreignTable="catalogue" onDelete="cascade">
* <reference local="cat_id" foreign="cat_id"/>
* </foreign-key>
* <column name="id" type="varchar" size="255" />
* <column name="source" type="longvarchar" />
* <column name="target" type="longvarchar" />
* <column name="comments" type="longvarchar" />
* <column name="date_created" type="timestamp" />
* <column name="date_modified" type="timestamp" />
* <column name="author" type="varchar" size="255" />
* <column name="translated" type="integer" />
* </table>
* ...
* </database>
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 24 16:58:58 EST 2004
* @package System.I18N.core
*/
class sfMessageSource_SQLite extends sfMessageSource
{
/**
* The SQLite datasource, the filename of the database.
* @var string
*/
protected $source;
/**
* Constructor.
* Creates a new message source using SQLite.
* @see MessageSource::factory();
* @param string SQLite datasource, in PEAR's DB DSN format.
*/
function __construct($source)
{
$dsn = parseDSN((string) $source);
$this->source = $dsn['database'];
}
/**
* Gets an array of messages for a particular catalogue and cultural variant.
*
* @param string the catalogue name + variant
* @return array translation messages.
*/
protected function &loadData($variant)
{
$variant = sqlite_escape_string($variant);
$statement =
"SELECT t.id, t.source, t.target, t.comments
FROM trans_unit t, catalogue c
WHERE c.cat_id = t.cat_id
AND c.name = '{$variant}'
ORDER BY id ASC";
$db = sqlite_open($this->source);
$rs = sqlite_query($statement, $db);
$result = array();
while($row = sqlite_fetch_array($rs, SQLITE_NUM))
{
$source = $row[1];
$result[$source][] = $row[2]; //target
$result[$source][] = $row[0]; //id
$result[$source][] = $row[3]; //comments
}
sqlite_close($db);
return $result;
}
/**
* Gets the last modified unix-time for this particular catalogue+variant.
* We need to query the database to get the date_modified.
*
* @param string catalogue+variant
* @return int last modified in unix-time format.
*/
protected function getLastModified($source)
{
$source = sqlite_escape_string($source);
$db = sqlite_open($this->source);
$rs = sqlite_query("SELECT date_modified FROM catalogue WHERE name = '{$source}'", $db);
$result = $rs ? intval(sqlite_fetch_single($rs)) : 0;
sqlite_close($db);
return $result;
}
/**
* Checks if a particular catalogue+variant exists in the database.
*
* @param string catalogue+variant
* @return boolean true if the catalogue+variant is in the database, false otherwise.
*/
protected function isValidSource($variant)
{
$variant = sqlite_escape_string($variant);
$db = sqlite_open($this->source);
$rs = sqlite_query("SELECT COUNT(*) FROM catalogue WHERE name = '{$variant}'", $db);
$result = $rs && intval(sqlite_fetch_single($rs));
sqlite_close($db);
return $result;
}
/**
* Gets all the variants of a particular catalogue.
*
* @param string catalogue name
* @return array list of all variants for this catalogue.
*/
protected function getCatalogueList($catalogue)
{
$variants = explode('_', $this->culture);
$catalogues = array($catalogue);
$variant = null;
for ($i = 0, $max = count($variants); $i < $max; $i++)
{
if (strlen($variants[$i]) > 0)
{
$variant .= ($variant) ? '_'.$variants[$i] : $variants[$i];
$catalogues[] = $catalogue.'.'.$variant;
}
}
return array_reverse($catalogues);
}
/**
* Retrieves catalogue details, array($cat_id, $variant, $count).
*
* @param string catalogue
* @return array catalogue details, array($cat_id, $variant, $count).
*/
protected function getCatalogueDetails($catalogue = 'messages')
{
if (empty($catalogue))
{
$catalogue = 'messages';
}
$variant = $catalogue.'.'.$this->culture;
$name = sqlite_escape_string($this->getSource($variant));
$db = sqlite_open($this->source);
$rs = sqlite_query("SELECT cat_id FROM catalogue WHERE name = '{$name}'", $db);
if (sqlite_num_rows($rs) != 1)
{
return false;
}
$cat_id = intval(sqlite_fetch_single($rs));
//first get the catalogue ID
$rs = sqlite_query("SELECT count(msg_id) FROM trans_unit WHERE cat_id = {$cat_id}", $db);
$count = intval(sqlite_fetch_single($rs));
sqlite_close($db);
return array($cat_id, $variant, $count);
}
/**
* Updates the catalogue last modified time.
*
* @return boolean true if updated, false otherwise.
*/
protected function updateCatalogueTime($cat_id, $variant, $db)
{
$time = time();
$result = sqlite_query("UPDATE catalogue SET date_modified = {$time} WHERE cat_id = {$cat_id}", $db);
if (!empty($this->cache))
{
$this->cache->clean($variant, $this->culture);
}
return $result;
}
/**
* Saves the list of untranslated blocks to the translation source.
* If the translation was not found, you should add those
* strings to the translation source via the <b>append()</b> method.
*
* @param string the catalogue to add to
* @return boolean true if saved successfuly, false otherwise.
*/
function save($catalogue='messages')
{
$messages = $this->untranslated;
if (count($messages) <= 0)
{
return false;
}
$details = $this->getCatalogueDetails($catalogue);
if ($details)
{
list($cat_id, $variant, $count) = $details;
}
else
{
return false;
}
if ($cat_id <= 0)
{
return false;
}
$inserted = 0;
$db = sqlite_open($this->source);
$time = time();
foreach ($messages as $message)
{
$message = sqlite_escape_string($message);
$statement = "INSERT INTO trans_unit (cat_id,id,source,date_added) VALUES ({$cat_id}, {$count},'{$message}',$time)";
if (sqlite_query($statement, $db))
{
$count++;
$inserted++;
}
}
if ($inserted > 0)
{
$this->updateCatalogueTime($cat_id, $variant, $db);
}
sqlite_close($db);
return $inserted > 0;
}
/**
* Updates the translation.
*
* @param string the source string.
* @param string the new translation string.
* @param string comments
* @param string the catalogue of the translation.
* @return boolean true if translation was updated, false otherwise.
*/
function update($text, $target, $comments, $catalogue = 'messages')
{
$details = $this->getCatalogueDetails($catalogue);
if ($details)
{
list($cat_id, $variant, $count) = $details;
}
else
{
return false;
}
$comments = sqlite_escape_string($comments);
$target = sqlite_escape_string($target);
$text = sqlite_escape_string($text);
$time = time();
$db = sqlite_open($this->source);
$statement = "UPDATE trans_unit SET target = '{$target}', comments = '{$comments}', date_modified = '{$time}' WHERE cat_id = {$cat_id} AND source = '{$text}'";
$updated = false;
if (sqlite_query($statement, $db))
{
$updated = $this->updateCatalogueTime($cat_id, $variant, $db);
}
sqlite_close($db);
return $updated;
}
/**
* Deletes a particular message from the specified catalogue.
*
* @param string the source message to delete.
* @param string the catalogue to delete from.
* @return boolean true if deleted, false otherwise.
*/
function delete($message, $catalogue = 'messages')
{
$details = $this->getCatalogueDetails($catalogue);
if ($details)
{
list($cat_id, $variant, $count) = $details;
}
else
{
return false;
}
$db = sqlite_open($this->source);
$text = sqlite_escape_string($message);
$statement = "DELETE FROM trans_unit WHERE cat_id = {$cat_id} AND source = '{$message}'";
$deleted = false;
if (sqlite_query($statement, $db))
{
$deleted = $this->updateCatalogueTime($cat_id, $variant, $db);
}
sqlite_close($db);
return $deleted;
}
/**
* Returns a list of catalogue as key and all it variants as value.
*
* @return array list of catalogues
*/
function catalogues()
{
$db = sqlite_open($this->source);
$statement = 'SELECT name FROM catalogue ORDER BY name';
$rs = sqlite_query($statement, $db);
$result = array();
while ($row = sqlite_fetch_array($rs, SQLITE_NUM))
{
$details = explode('.',$row[0]);
if (!isset($details[1]))
{
$details[1] = null;
}
$result[] = $details;
}
sqlite_close($db);
return $result;
}
}

View File

@ -0,0 +1,523 @@
<?php
/**
* sfMessageSource_XLIFF class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfMessageSource_XLIFF.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* sfMessageSource_XLIFF class.
*
* Using XML XLIFF format as the message source for translation.
* Details and example of XLIFF can be found in the following URLs.
*
* # http://www.opentag.com/xliff.htm
* # http://www-106.ibm.com/developerworks/xml/library/x-localis2/
*
* See the MessageSource::factory() method to instantiate this class.
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004
* @package System.I18N.core
*/
class sfMessageSource_XLIFF extends sfMessageSource
{
/**
* Message data filename extension.
* @var string
*/
protected $dataExt = '.xml';
/**
* Separator between culture name and source.
* @var string
*/
protected $dataSeparator = '.';
/**
* Constructor.
*
* @param string the directory where the messages are stored.
* @see MessageSource::factory();
*/
function __construct($source)
{
$this->source = (string) $source;
}
/**
* Loads the messages from a XLIFF file.
*
* @param string XLIFF file.
* @return array of messages.
*/
protected function &loadData($filename)
{
//load it.
$XML = simplexml_load_file($filename);
if (!$XML)
{
return false;
}
$translationUnit = $XML->xpath('//trans-unit');
$translations = array();
foreach ($translationUnit as $unit)
{
$source = (string) $unit->source;
$translations[$source][] = (string) $unit->target;
$translations[$source][]= (string) $unit['id'];
$translations[$source][]= (string) $unit->note;
}
return $translations;
}
/**
* Gets the last modified unix-time for this particular catalogue+variant.
* Just use the file modified time.
*
* @param string catalogue+variant
* @return int last modified in unix-time format.
*/
protected function getLastModified($source)
{
return is_file($source) ? filemtime($source) : 0;
}
/**
* Gets the XLIFF file for a specific message catalogue and cultural variant.
*
* @param string message catalogue
* @return string full path to the XLIFF file.
*/
protected function getSource($variant)
{
return $this->source.'/'.$variant;
}
/**
* Determines if the XLIFF file source is valid.
*
* @param string XLIFF file
* @return boolean true if valid, false otherwise.
*/
protected function isValidSource($source)
{
return is_file($source);
}
/**
* Gets all the variants of a particular catalogue.
*
* @param string catalogue name
* @return array list of all variants for this catalogue.
*/
protected function getCatalogueList($catalogue)
{
$variants = explode('_', $this->culture);
$source = $catalogue.$this->dataExt;
$catalogues = array($source);
$variant = null;
for ($i = 0, $max = count($variants); $i < $max; $i++)
{
if (strlen($variants[$i]) > 0)
{
$variant .= $variant ? '_'.$variants[$i] : $variants[$i];
$catalogues[] = $catalogue.$this->dataSeparator.$variant.$this->dataExt;
}
}
$byDir = $this->getCatalogueByDir($catalogue);
$catalogues = array_merge($byDir, array_reverse($catalogues));
return $catalogues;
}
/**
* Traverses through the directory structure to find the catalogues.
* This should only be called by getCatalogueList()
*
* @param string a particular catalogue.
* @return array a list of catalogues.
* @see getCatalogueList()
*/
protected function getCatalogueByDir($catalogue)
{
$variants = explode('_', $this->culture);
$catalogues = array();
$variant = null;
for ($i = 0, $max = count($variants); $i < $max; $i++)
{
if (strlen($variants[$i]) > 0)
{
$variant .= $variant ? '_'.$variants[$i] : $variants[$i];
$catalogues[] = $variant.'/'.$catalogue.$this->dataExt;
}
}
return array_reverse($catalogues);
}
/**
* Returns a list of catalogue and its culture ID.
* E.g. array('messages', 'en_AU')
*
* @return array list of catalogues
* @see getCatalogues()
*/
public function catalogues()
{
return $this->getCatalogues();
}
/**
* Returns a list of catalogue and its culture ID. This takes care
* of directory structures.
* E.g. array('messages', 'en_AU')
*
* @return array list of catalogues
*/
protected function getCatalogues($dir = null, $variant = null)
{
$dir = $dir ? $dir : $this->source;
$files = scandir($dir);
$catalogue = array();
foreach ($files as $file)
{
if (is_dir($dir.'/'.$file) && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/', $file))
{
$catalogue = array_merge($catalogue, $this->getCatalogues($dir.'/'.$file, $file));
}
$pos = strpos($file,$this->dataExt);
if ($pos > 0 && substr($file, -1 * strlen($this->dataExt)) == $this->dataExt)
{
$name = substr($file, 0, $pos);
$dot = strrpos($name, $this->dataSeparator);
$culture = $variant;
$cat = $name;
if (is_int($dot))
{
$culture = substr($name, $dot + 1,strlen($name));
$cat = substr($name, 0, $dot);
}
$details[0] = $cat;
$details[1] = $culture;
$catalogue[] = $details;
}
}
sort($catalogue);
return $catalogue;
}
/**
* Gets the variant for a catalogue depending on the current culture.
*
* @param string catalogue
* @return string the variant.
* @see save()
* @see update()
* @see delete()
*/
protected function getVariants($catalogue = 'messages')
{
if (is_null($catalogue))
{
$catalogue = 'messages';
}
foreach ($this->getCatalogueList($catalogue) as $variant)
{
$file = $this->getSource($variant);
if (is_file($file))
{
return array($variant, $file);
}
}
return false;
}
/**
* Saves the list of untranslated blocks to the translation source.
* If the translation was not found, you should add those
* strings to the translation source via the <b>append()</b> method.
*
* @param string the catalogue to add to
* @return boolean true if saved successfuly, false otherwise.
*/
public function save($catalogue = 'messages')
{
$messages = $this->untranslated;
if (count($messages) <= 0)
{
return false;
}
$variants = $this->getVariants($catalogue);
if ($variants)
{
list($variant, $filename) = $variants;
}
else
{
return false;
}
if (is_writable($filename) == false)
{
throw new sfException(sprintf("Unable to save to file %s, file must be writable.", $filename));
}
// create a new dom, import the existing xml
$dom = new DOMDocument();
$dom->load($filename);
// find the body element
$xpath = new DomXPath($dom);
$body = $xpath->query('//body')->item(0);
// find the biggest "id" used
$lastNodes = $xpath->query('//trans-unit[not(@id <= preceding-sibling::trans-unit/@id) and not(@id <= following-sibling::trans-unit/@id)]');
if (null !== $last = $lastNodes->item(0))
{
$count = intval($last->getAttribute('id'));
}
else
{
$count = 0;
}
// for each message add it to the XML file using DOM
foreach ($messages as $message)
{
$unit = $dom->createElement('trans-unit');
$unit->setAttribute('id', ++$count);
$source = $dom->createElement('source', $message);
$target = $dom->createElement('target', '');
$unit->appendChild($dom->createTextNode("\n"));
$unit->appendChild($source);
$unit->appendChild($dom->createTextNode("\n"));
$unit->appendChild($target);
$unit->appendChild($dom->createTextNode("\n"));
$body->appendChild($dom->createTextNode("\n"));
$body->appendChild($unit);
$body->appendChild($dom->createTextNode("\n"));
}
$fileNode = $xpath->query('//file')->item(0);
$fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
// save it and clear the cache for this variant
$dom->save($filename);
if (!empty($this->cache))
{
$this->cache->clean($variant, $this->culture);
}
return true;
}
/**
* Updates the translation.
*
* @param string the source string.
* @param string the new translation string.
* @param string comments
* @param string the catalogue to save to.
* @return boolean true if translation was updated, false otherwise.
*/
public function update($text, $target, $comments, $catalogue = 'messages')
{
$variants = $this->getVariants($catalogue);
if ($variants)
{
list($variant, $filename) = $variants;
}
else
{
return false;
}
if (is_writable($filename) == false)
{
throw new sfException(sprintf("Unable to update file %s, file must be writable.", $filename));
}
// create a new dom, import the existing xml
$dom = new DOMDocument();
$dom->load($filename);
// find the body element
$xpath = new DomXPath($dom);
$units = $xpath->query('//trans-unit');
// for each of the existin units
foreach ($units as $unit)
{
$found = false;
$targetted = false;
$commented = false;
//in each unit, need to find the source, target and comment nodes
//it will assume that the source is before the target.
foreach ($unit->childNodes as $node)
{
// source node
if ($node->nodeName == 'source' && $node->firstChild->wholeText == $text)
{
$found = true;
}
// found source, get the target and notes
if ($found)
{
// set the new translated string
if ($node->nodeName == 'target')
{
$node->nodeValue = $target;
$targetted = true;
}
// set the notes
if (!empty($comments) && $node->nodeName == 'note')
{
$node->nodeValue = $comments;
$commented = true;
}
}
}
// append a target
if ($found && !$targetted)
{
$unit->appendChild($dom->createElement('target', $target));
}
// append a note
if ($found && !$commented && !empty($comments))
{
$unit->appendChild($dom->createElement('note',$comments));
}
// finished searching
if ($found)
{
break;
}
}
$fileNode = $xpath->query('//file')->item(0);
$fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
if ($dom->save($filename) > 0)
{
if (!empty($this->cache))
{
$this->cache->clean($variant, $this->culture);
}
return true;
}
return false;
}
/**
* Deletes a particular message from the specified catalogue.
*
* @param string the source message to delete.
* @param string the catalogue to delete from.
* @return boolean true if deleted, false otherwise.
*/
public function delete($message, $catalogue='messages')
{
$variants = $this->getVariants($catalogue);
if ($variants)
{
list($variant, $filename) = $variants;
}
else
{
return false;
}
if (is_writable($filename) == false)
{
throw new sfException(sprintf("Unable to modify file %s, file must be writable.", $filename));
}
// create a new dom, import the existing xml
$dom = new DOMDocument();
$dom->load($filename);
// find the body element
$xpath = new DomXPath($dom);
$units = $xpath->query('//trans-unit');
// for each of the existin units
foreach ($units as $unit)
{
//in each unit, need to find the source, target and comment nodes
//it will assume that the source is before the target.
foreach ($unit->childNodes as $node)
{
// source node
if ($node->nodeName == 'source' && $node->firstChild->wholeText == $message)
{
// we found it, remove and save the xml file.
$unit->parentNode->removeChild($unit);
$fileNode = $xpath->query('//file')->item(0);
$fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
if ($dom->save($filename) > 0)
{
if (!empty($this->cache))
{
$this->cache->clean($variant, $this->culture);
}
return true;
}
else
{
return false;
}
}
}
}
return false;
}
}

View File

@ -0,0 +1,466 @@
<?php
/**
* sfMessageSource_gettext class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfMessageSource_gettext.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* sfMessageSource_gettext class.
*
* Using Gettext MO format as the message source for translation.
* The gettext classes are based on PEAR's gettext MO and PO classes.
*
* See the MessageSource::factory() method to instantiate this class.
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004
* @package System.I18N.core
*/
class sfMessageSource_gettext extends sfMessageSource
{
/**
* Message data filename extension.
* @var string
*/
protected $dataExt = '.mo';
/**
* PO data filename extension
* @var string
*/
protected $poExt = '.po';
/**
* Separator between culture name and source.
* @var string
*/
protected $dataSeparator = '.';
function __construct($source)
{
$this->source = (string) $source;
}
/**
* Loads the messages from a MO file.
*
* @param string MO file.
* @return array of messages.
*/
protected function &loadData($filename)
{
$mo = TGettext::factory('MO',$filename);
$mo->load();
$result = $mo->toArray();
$results = array();
$count = 0;
foreach ($result['strings'] as $source => $target)
{
$results[$source][] = $target; //target
$results[$source][] = $count++; //id
$results[$source][] = ''; //comments
}
return $results;
}
/**
* Determines if the MO file source is valid.
*
* @param string MO file
* @return boolean true if valid, false otherwise.
*/
protected function isValidSource($filename)
{
return is_file($filename);
}
/**
* Gets the MO file for a specific message catalogue and cultural variant.
*
* @param string message catalogue
* @return string full path to the MO file.
*/
protected function getSource($variant)
{
return $this->source.'/'.$variant;
}
/**
* Gets the last modified unix-time for this particular catalogue+variant.
* Just use the file modified time.
*
* @param string catalogue+variant
* @return int last modified in unix-time format.
*/
protected function getLastModified($source)
{
return is_file($source) ? filemtime($source) : 0;
}
/**
* Gets all the variants of a particular catalogue.
*
* @param string catalogue name
* @return array list of all variants for this catalogue.
*/
protected function getCatalogueList($catalogue)
{
$variants = explode('_', $this->culture);
$source = $catalogue.$this->dataExt;
$catalogues = array($source);
$variant = null;
for ($i = 0, $max = count($variants); $i < $max; $i++)
{
if (strlen($variants[$i]) > 0)
{
$variant .= $variant ? '_'.$variants[$i] : $variants[$i];
$catalogues[] = $catalogue.$this->dataSeparator.$variant.$this->dataExt;
}
}
$byDir = $this->getCatalogueByDir($catalogue);
$catalogues = array_merge($byDir,array_reverse($catalogues));
return $catalogues;
}
/**
* Traverses through the directory structure to find the catalogues.
* This should only be called by getCatalogueList()
*
* @param string a particular catalogue.
* @return array a list of catalogues.
* @see getCatalogueList()
*/
protected function getCatalogueByDir($catalogue)
{
$variants = explode('_', $this->culture);
$catalogues = array();
$variant = null;
for($i = 0, $max = count($variants); $i < $max; $i++)
{
if (strlen($variants[$i]) > 0)
{
$variant .= $variant ? '_'.$variants[$i] : $variants[$i];
$catalogues[] = $variant.'/'.$catalogue.$this->dataExt;
}
}
return array_reverse($catalogues);
}
/**
* Gets the variant for a catalogue depending on the current culture.
*
* @param string catalogue
* @return string the variant.
* @see save()
* @see update()
* @see delete()
*/
protected function getVariants($catalogue = 'messages')
{
if (empty($catalogue))
{
$catalogue = 'messages';
}
foreach ($this->getCatalogueList($catalogue) as $variant)
{
$file = $this->getSource($variant);
$po = $this->getPOFile($file);
if (is_file($file) || is_file($po))
{
return array($variant, $file, $po);
}
}
return false;
}
protected function getPOFile($MOFile)
{
return substr($MOFile, 0, strlen($MOFile) - strlen($this->dataExt)).$this->poExt;
}
/**
* Saves the list of untranslated blocks to the translation source.
* If the translation was not found, you should add those
* strings to the translation source via the <b>append()</b> method.
*
* @param string the catalogue to add to
* @return boolean true if saved successfuly, false otherwise.
*/
function save($catalogue = 'messages')
{
$messages = $this->untranslated;
if (count($messages) <= 0)
{
return false;
}
$variants = $this->getVariants($catalogue);
if ($variants)
{
list($variant, $MOFile, $POFile) = $variants;
}
else
{
return false;
}
if (is_writable($MOFile) == false)
{
throw new sfException(sprintf("Unable to save to file %s, file must be writable.", $MOFile));
}
if (is_writable($POFile) == false)
{
throw new sfException(sprintf("Unable to save to file %s, file must be writable.", $POFile));
}
// set the strings as untranslated.
$strings = array();
foreach ($messages as $message)
{
$strings[$message] = '';
}
// load the PO
$po = TGettext::factory('PO',$POFile);
$po->load();
$result = $po->toArray();
$existing = count($result['strings']);
// add to strings to the existing message list
$result['strings'] = array_merge($result['strings'],$strings);
$new = count($result['strings']);
if ($new > $existing)
{
// change the date 2004-12-25 12:26
$result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
$po->fromArray($result);
$mo = $po->toMO();
if ($po->save() && $mo->save($MOFile))
{
if (!empty($this->cache))
{
$this->cache->clean($variant, $this->culture);
}
return true;
}
else
{
return false;
}
}
return false;
}
/**
* Deletes a particular message from the specified catalogue.
*
* @param string the source message to delete.
* @param string the catalogue to delete from.
* @return boolean true if deleted, false otherwise.
*/
function delete($message, $catalogue = 'messages')
{
$variants = $this->getVariants($catalogue);
if ($variants)
{
list($variant, $MOFile, $POFile) = $variants;
}
else
{
return false;
}
if (is_writable($MOFile) == false)
{
throw new sfException(sprintf("Unable to modify file %s, file must be writable.", $MOFile));
}
if (is_writable($POFile) == false)
{
throw new sfException(sprintf("Unable to modify file %s, file must be writable.", $POFile));
}
$po = TGettext::factory('PO', $POFile);
$po->load();
$result = $po->toArray();
foreach ($result['strings'] as $string => $value)
{
if ($string == $message)
{
$result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
unset($result['strings'][$string]);
$po->fromArray($result);
$mo = $po->toMO();
if ($po->save() && $mo->save($MOFile))
{
if (!empty($this->cache))
{
$this->cache->clean($variant, $this->culture);
}
return true;
}
else
{
return false;
}
}
}
return false;
}
/**
* Updates the translation.
*
* @param string the source string.
* @param string the new translation string.
* @param string comments
* @param string the catalogue of the translation.
* @return boolean true if translation was updated, false otherwise.
*/
function update($text, $target, $comments, $catalogue = 'messages')
{
$variants = $this->getVariants($catalogue);
if ($variants)
{
list($variant, $MOFile, $POFile) = $variants;
}
else
{
return false;
}
if (is_writable($MOFile) == false)
{
throw new sfException(sprintf("Unable to update file %s, file must be writable.", $MOFile));
}
if (is_writable($POFile) == false)
{
throw new sfException(sprintf("Unable to update file %s, file must be writable.", $POFile));
}
$po = TGettext::factory('PO',$POFile);
$po->load();
$result = $po->toArray();
foreach ($result['strings'] as $string => $value)
{
if ($string == $text)
{
$result['strings'][$string] = $target;
$result['meta']['PO-Revision-Date'] = @date('Y-m-d H:i:s');
$po->fromArray($result);
$mo = $po->toMO();
if ($po->save() && $mo->save($MOFile))
{
if (!empty($this->cache))
{
$this->cache->clean($variant, $this->culture);
}
return true;
}
else
{
return false;
}
}
}
return false;
}
/**
* Returns a list of catalogue as key and all it variants as value.
*
* @return array list of catalogues
*/
function catalogues()
{
return $this->getCatalogues();
}
/**
* Returns a list of catalogue and its culture ID. This takes care
* of directory structures.
* E.g. array('messages','en_AU')
*
* @return array list of catalogues
*/
protected function getCatalogues($dir = null, $variant = null)
{
$dir = $dir ? $dir : $this->source;
$files = scandir($dir);
$catalogue = array();
foreach ($files as $file)
{
if (is_dir($dir.'/'.$file) && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/', $file))
{
$catalogue = array_merge($catalogue, $this->getCatalogues($dir.'/'.$file, $file));
}
$pos = strpos($file, $this->dataExt);
if ($pos > 0 && substr($file, -1 * strlen($this->dataExt)) == $this->dataExt)
{
$name = substr($file, 0, $pos);
$dot = strrpos($name, $this->dataSeparator);
$culture = $variant;
$cat = $name;
if (is_int($dot))
{
$culture = substr($name, $dot + 1, strlen($name));
$cat = substr($name, 0, $dot);
}
$details[0] = $cat;
$details[1] = $culture;
$catalogue[] = $details;
}
}
sort($catalogue);
return $catalogue;
}
}

View File

@ -0,0 +1,316 @@
<?php
/**
* sfNumberFormat class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfNumberFormat.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* Get the encoding utilities
*/
require_once(dirname(__FILE__).'/util.php');
/**
* sfNumberFormat class.
*
* sfNumberFormat formats decimal numbers in any locale. The decimal
* number is formatted according to a particular pattern. These
* patterns can arise from the sfNumberFormatInfo object which is
* culturally sensitive. The sfNumberFormat class can be instantiated in
* many ways. E.g.
*
* <code>
* //create a invariant number formatter.
* $formatter = new sfNumberFormat();
*
* //create a number format for the french language locale.
* $fr = new sfNumberFormat('fr');
*
* //create a number format base on a sfNumberFormatInfo instance $numberInfo.
* $format = new sfNumberFormat($numberInfo);
* </code>
*
* A normal decimal number can also be displayed as a currency
* or as a percentage. For example
* <code>
* $format->format(1234.5); //Decimal number "1234.5"
* $format->format(1234.5,'c'); //Default currency "$1234.50"
* $format->format(0.25, 'p') //Percent "25%"
* </code>
*
* Currency is formated using the localized currency pattern. For example
* to format the number as Japanese Yen:
* <code>
* $ja = new sfNumberFormat('ja_JP');
*
* //Japanese currency pattern, and using Japanese Yen symbol
* $ja->format(123.14,'c','JPY'); //ï¿?123 (Yen 123)
* </code>
* For each culture, the symbol for each currency may be different.
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Fri Dec 10 18:10:20 EST 2004
* @package System.I18N.core
*/
class sfNumberFormat
{
/**
* The DateTimeFormatInfo, containing culture specific patterns and names.
* @var DateTimeFormatInfo
*/
protected $formatInfo;
/**
* Creates a new number format instance. The constructor can be instantiated
* with a string that represent a culture/locale. Similarly, passing
* a sfCultureInfo or sfNumberFormatInfo instance will instantiated a instance
* for that particular culture.
*
* @param mixed either null, a sfCultureInfo, a sfNumberFormatInfo, or string
* @return sfNumberFormat
*/
function __construct($formatInfo = null)
{
if (is_null($formatInfo))
{
$this->formatInfo = sfNumberFormatInfo::getInvariantInfo();
}
else if ($formatInfo instanceof sfCultureInfo)
{
$this->formatInfo = $formatInfo->sfNumberFormat;
}
else if ($formatInfo instanceof sfNumberFormatInfo)
{
$this->formatInfo = $formatInfo;
}
else
{
$this->formatInfo = sfNumberFormatInfo::getInstance($formatInfo);
}
}
/**
* Formats the number for a certain pattern. The valid patterns are
* 'c', 'd', 'e', 'p' or a custom pattern, such as "#.000" for
* 3 decimal places.
*
* @param mixed the number to format.
* @param string the format pattern, either, 'c', 'd', 'e', 'p'
* or a custom pattern. E.g. "#.000" will format the number to
* 3 decimal places.
* @param string 3-letter ISO 4217 code. For example, the code
* "USD" represents the US Dollar and "EUR" represents the Euro currency.
* @return string formatted number string
*/
function format($number, $pattern = 'd', $currency = 'USD', $charset = 'UTF-8')
{
$this->setPattern($pattern);
if (strtolower($pattern) == 'p')
{
$number = $number * 100;
}
$string = (string) $number;
list($number, $decimal) = $this->formatDecimal($string);
$integer = $this->formatInteger(abs($number));
$result = (strlen($decimal) > 0) ? $integer.$decimal : $integer;
// get the suffix
if ($number >= 0)
{
$suffix = $this->formatInfo->PositivePattern;
}
else if ($number < 0)
{
$suffix = $this->formatInfo->NegativePattern;
}
else
{
$suffix = array('', '');
}
// append and prepend suffix
$result = $suffix[0].$result.$suffix[1];
// replace currency sign
$symbol = @$this->formatInfo->getCurrencySymbol($currency);
if (is_null($symbol))
{
$symbol = $currency;
}
$result = str_replace('¤', $symbol, $result);
return I18N_toEncoding($result, $charset);
}
/**
* Formats the integer, perform groupings and string padding.
*
* @param string the decimal number in string form.
* @return string formatted integer string with grouping
*/
protected function formatInteger($string)
{
$string = (string) $string;
$dp = strpos($string, '.');
if (is_int($dp))
{
$string = substr($string, 0, $dp);
}
$integer = '';
$len = strlen($string);
$groupSeparator = $this->formatInfo->GroupSeparator;
$groupSize = $this->formatInfo->GroupSizes;
$firstGroup = true;
$multiGroup = is_int($groupSize[1]);
$count = 0;
if (is_int($groupSize[0]))
{
// now for the integer groupings
for ($i = 0; $i < $len; $i++)
{
$char = $string{$len - $i - 1};
if ($multiGroup && $count == 0)
{
if ($i != 0 && $i % $groupSize[0] == 0)
{
$integer = $groupSeparator.$integer;
$count++;
}
}
else if ($multiGroup && $count >= 1)
{
if ($i != 0 && ($i-$groupSize[0])%$groupSize[1] == 0)
{
$integer = $groupSeparator.$integer;
$count++;
}
}
else
{
if ($i != 0 && $i % $groupSize[0] == 0)
{
$integer = $groupSeparator.$integer;
$count++;
}
}
$integer = $char.$integer;
}
}
else
{
$integer = $string;
}
return $integer;
}
/**
* Formats the decimal places.
*
* @param string the decimal number in string form.
* @return string formatted decimal places.
*/
protected function formatDecimal($string)
{
$dp = strpos($string, '.');
$decimal = '';
$decimalDigits = $this->formatInfo->DecimalDigits;
$decimalSeparator = $this->formatInfo->DecimalSeparator;
if (is_int($dp))
{
if ($decimalDigits == -1)
{
$decimal = substr($string, $dp + 1);
}
else if (is_int($decimalDigits))
{
$string = $float = round((float) $string, $decimalDigits);
if (strpos((string) $float, '.') === false)
{
$decimal = str_pad($decimal, $decimalDigits, '0');
}
else
{
$decimal = substr($float, strpos($float,'.') + 1);
if (strlen($decimal)<$decimalDigits)
{
$decimal = str_pad($decimal, $decimalDigits, '0');
}
}
}
else
{
return array($string, $decimal);
}
return array($string, $decimalSeparator.$decimal);
}
else if ($decimalDigits > 0)
{
return array($string, $decimalSeparator.str_pad($decimal, $decimalDigits, '0'));
}
return array($string, $decimal);
}
/**
* Sets the pattern to format against. The default patterns
* are retrieved from the sfNumberFormatInfo instance.
*
* @param string the requested patterns.
* @return string a number format pattern.
*/
protected function setPattern($pattern)
{
switch ($pattern)
{
case 'c':
case 'C':
$this->formatInfo->setPattern(sfNumberFormatInfo::CURRENCY);
break;
case 'd':
case 'D':
$this->formatInfo->setPattern(sfNumberFormatInfo::DECIMAL);
break;
case 'e':
case 'E':
$this->formatInfo->setPattern(sfNumberFormatInfo::SCIENTIFIC);
break;
case 'p':
case 'P':
$this->formatInfo->setPattern(sfNumberFormatInfo::PERCENTAGE);
break;
default:
$this->formatInfo->setPattern($pattern);
break;
}
}
}

View File

@ -0,0 +1,681 @@
<?php
/**
* sfNumberFormatInfo class file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
*
* To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: sfNumberFormatInfo.class.php 4340 2007-06-23 06:47:05Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* sfNumberFormatInfo class
*
* Defines how numeric values are formatted and displayed,
* depending on the culture. Numeric values are formatted using
* standard or custom patterns stored in the properties of a
* sfNumberFormatInfo.
*
* This class contains information, such as currency, decimal
* separators, and other numeric symbols.
*
* To create a sfNumberFormatInfo for a specific culture,
* create a sfCultureInfo for that culture and retrieve the
* sfCultureInfo->NumberFormat property. Or use
* sfNumberFormatInfo::getInstance($culture).
* To create a sfNumberFormatInfo for the invariant culture, use the
* InvariantInfo::getInvariantInfo().
*
*
* @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version v1.0, last update on Sun Dec 05 14:48:26 EST 2004
* @package System.I18N.core
*/
class sfNumberFormatInfo
{
/**
* ICU number formatting data.
* @var array
*/
protected $data = array();
/**
* A list of properties that are accessable/writable.
* @var array
*/
protected $properties = array();
/**
* The number pattern.
* @var array
*/
protected $pattern = array();
const DECIMAL = 0;
const CURRENCY = 1;
const PERCENTAGE = 2;
const SCIENTIFIC = 3;
/**
* Allows functions that begins with 'set' to be called directly
* as an attribute/property to retrieve the value.
*
* @return mixed
*/
function __get($name)
{
$getProperty = 'get'.$name;
if (in_array($getProperty, $this->properties))
{
return $this->$getProperty();
}
else
{
throw new sfException(sprintf('Property %s does not exists.', $name));
}
}
/**
* Allows functions that begins with 'set' to be called directly
* as an attribute/property to set the value.
*/
function __set($name, $value)
{
$setProperty = 'set'.$name;
if (in_array($setProperty, $this->properties))
{
$this->$setProperty($value);
}
else
{
throw new sfException(sprintf('Property %s can not be set.', $name));
}
}
/**
* Initializes a new writable instance of the sfNumberFormatInfo class
* that is dependent on the ICU data for number, decimal, and currency
* formatting information. <b>N.B.</b>You should not initialize this
* class directly unless you know what you are doing. Please use use
* sfNumberFormatInfo::getInstance() to create an instance.
*
* @param array ICU data for date time formatting.
* @see getInstance()
*/
function __construct($data = array(), $type = sfNumberFormatInfo::DECIMAL)
{
$this->properties = get_class_methods($this);
if (empty($data))
{
throw new sfException('Please provide the ICU data to initialize.');
}
$this->data = $data;
$this->setPattern($type);
}
/**
* Sets the pattern for a specific number pattern. The validate patterns
* sfNumberFormatInfo::DECIMAL, sfNumberFormatInfo::CURRENCY,
* sfNumberFormatInfo::PERCENTAGE, or sfNumberFormatInfo::SCIENTIFIC
*
* @param int pattern type.
*/
function setPattern($type = sfNumberFormatInfo::DECIMAL)
{
if (is_int($type))
{
$this->pattern = $this->parsePattern($this->data['NumberPatterns'][$type]);
}
else
{
$this->pattern = $this->parsePattern($type);
}
$this->pattern['negInfty'] = $this->data['NumberElements'][6].$this->data['NumberElements'][9];
$this->pattern['posInfty'] = $this->data['NumberElements'][11].$this->data['NumberElements'][9];
}
function getPattern()
{
return $this->pattern;
}
/**
* Gets the default sfNumberFormatInfo that is culture-independent (invariant).
*
* @return sfNumberFormatInfo default sfNumberFormatInfo.
*/
public function getInvariantInfo($type = sfNumberFormatInfo::DECIMAL)
{
static $invariant;
if (is_null($invariant))
{
$culture = sfCultureInfo::getInvariantCulture();
$invariant = $culture->NumberFormat;
$invariant->setPattern($type);
}
return $invariant;
}
/**
* Returns the sfNumberFormatInfo associated with the specified culture.
*
* @param sfCultureInfo the culture that gets the sfNumberFormat property.
* @param int the number formatting type, it should be
* sfNumberFormatInfo::DECIMAL, sfNumberFormatInfo::CURRENCY,
* sfNumberFormatInfo::PERCENTAGE, or sfNumberFormatInfo::SCIENTIFIC
* @return sfNumberFormatInfo sfNumberFormatInfo for the specified culture.
* @see getCurrencyInstance();
* @see getPercentageInstance();
* @see getScientificInstance();
*/
public static function getInstance($culture = null, $type = sfNumberFormatInfo::DECIMAL)
{
if ($culture instanceof sfCultureInfo)
{
$formatInfo = $culture->getNumberFormat();
$formatInfo->setPattern($type);
return $formatInfo;
}
else if (is_string($culture))
{
$sfCultureInfo = new sfCultureInfo($culture);
$formatInfo = $sfCultureInfo->getNumberFormat();
$formatInfo->setPattern($type);
return $formatInfo;
}
else
{
$sfCultureInfo = new sfCultureInfo();
$formatInfo = $sfCultureInfo->getNumberFormat();
$formatInfo->setPattern($type);
return $formatInfo;
}
}
/**
* Returns the currency format info associated with the specified culture.
*
* @param sfCultureInfo the culture that gets the NumberFormat property.
* @return sfNumberFormatInfo sfNumberFormatInfo for the specified culture.
*/
public static function getCurrencyInstance($culture = null)
{
return self::getInstance($culture, self::CURRENCY);
}
/**
* Returns the percentage format info associated with the specified culture.
*
* @param sfCultureInfo the culture that gets the NumberFormat property.
* @return sfNumberFormatInfo sfNumberFormatInfo for the specified culture.
*/
public static function getPercentageInstance($culture = null)
{
return self::getInstance($culture, self::PERCENTAGE);
}
/**
* Returns the scientific format info associated with the specified culture.
*
* @param sfCultureInfo the culture that gets the NumberFormat property.
* @return sfNumberFormatInfo sfNumberFormatInfo for the specified culture.
*/
public static function getScientificInstance($culture = null)
{
return self::getInstance($culture, self::SCIENTIFIC);
}
/**
* Parses the given pattern and return a list of known properties.
*
* @param string a number pattern.
* @return array list of pattern properties.
*/
protected function parsePattern($pattern)
{
$pattern = explode(';', $pattern);
$negative = null;
if (count($pattern) > 1)
{
$negative = $pattern[1];
}
$pattern = $pattern[0];
$comma = ',';
$dot = '.';
$digit = '0';
$hash = '#';
// find the first group point, and decimal point
$groupPos1 = strrpos($pattern, $comma);
$decimalPos = strrpos($pattern, $dot);
$groupPos2 = false;
$groupSize1 = false;
$groupSize2 = false;
$decimalPoints = is_int($decimalPos) ? -1 : false;
$info['negPref'] = $this->data['NumberElements'][6];
$info['negPost'] = '';
$info['negative'] = $negative;
$info['positive'] = $pattern;
// find the negative prefix and postfix
if ($negative)
{
$prefixPostfix = $this->getPrePostfix($negative);
$info['negPref'] = $prefixPostfix[0];
$info['negPost'] = $prefixPostfix[1];
}
$posfix = $this->getPrePostfix($pattern);
$info['posPref'] = $posfix[0];
$info['posPost'] = $posfix[1];
if (is_int($groupPos1))
{
// get the second group
$groupPos2 = strrpos(substr($pattern, 0, $groupPos1), $comma);
// get the number of decimal digits
if (is_int($decimalPos))
{
$groupSize1 = $decimalPos - $groupPos1 - 1;
}
else
{
// no decimal point, so traverse from the back
// to find the groupsize 1.
for ($i = strlen($pattern) - 1; $i >= 0; $i--)
{
if ($pattern{$i} == $digit || $pattern{$i} == $hash)
{
$groupSize1 = $i - $groupPos1;
break;
}
}
}
// get the second group size
if (is_int($groupPos2))
{
$groupSize2 = $groupPos1 - $groupPos2 - 1;
}
}
if (is_int($decimalPos))
{
for ($i = strlen($pattern) - 1; $i >= 0; $i--)
{
if ($pattern{$i} == $dot)
{
break;
}
if ($pattern{$i} == $digit)
{
$decimalPoints = $i - $decimalPos;
break;
}
}
}
$info['groupPos1'] = $groupPos1;
$info['groupSize1'] = $groupSize1;
$info['groupPos2'] = $groupPos2;
$info['groupSize2'] = $groupSize2;
$info['decimalPos'] = $decimalPos;
$info['decimalPoints'] = $decimalPoints;
return $info;
}
/**
* Gets the prefix and postfix of a pattern.
*
* @param string pattern
* @return array of prefix and postfix, array(prefix,postfix).
*/
protected function getPrePostfix($pattern)
{
$regexp = '/[#,\.0]+/';
$result = preg_split($regexp, $pattern);
return array($result[0], $result[1]);
}
/**
* Indicates the number of decimal places.
*
* @return int number of decimal places.
*/
function getDecimalDigits()
{
return $this->pattern['decimalPoints'];
}
/**
* Sets the number of decimal places.
*
* @param int number of decimal places.
*/
function setDecimalDigits($value)
{
return $this->pattern['decimalPoints'] = $value;
}
/**
* Gets the string to use as the decimal separator.
*
* @return string decimal separator.
*/
function getDecimalSeparator()
{
return $this->data['NumberElements'][0];
}
/**
* Sets the string to use as the decimal separator.
*
* @param string the decimal point
*/
function setDecimalSeparator($value)
{
return $this->data['NumberElements'][0] = $value;
}
/**
* Gets the string that separates groups of digits to the left
* of the decimal in currency values.
*
* @param parameter
* @return string currency group separator.
*/
function getGroupSeparator()
{
return $this->data['NumberElements'][1];
}
/**
* Sets the string to use as the group separator.
*
* @param string the group separator.
*/
function setGroupSeparator($value)
{
return $this->data['NumberElements'][1] = $value;
}
/**
* Gets the number of digits in each group to the left of the decimal
* There can be two grouping sizes, this fucntion
* returns <b>array(group1, group2)</b>, if there is only 1 grouping size,
* group2 will be false.
*
* @return array grouping size(s).
*/
function getGroupSizes()
{
$group1 = $this->pattern['groupSize1'];
$group2 = $this->pattern['groupSize2'];
return array($group1, $group2);
}
/**
* Sets the number of digits in each group to the left of the decimal.
* There can be two grouping sizes, the value should
* be an <b>array(group1, group2)</b>, if there is only 1 grouping size,
* group2 should be false.
*
* @param array grouping size(s).
*/
function setGroupSizes($groupSize)
{
$this->pattern['groupSize1'] = $groupSize[0];
$this->pattern['groupSize2'] = $groupSize[1];
}
/**
* Gets the format pattern for negative values.
* The negative pattern is composed of a prefix, and postfix.
* This function returns <b>array(prefix, postfix)</b>.
*
* @return arary negative pattern.
*/
function getNegativePattern()
{
$prefix = $this->pattern['negPref'];
$postfix = $this->pattern['negPost'];
return array($prefix, $postfix);
}
/**
* Sets the format pattern for negative values.
* The negative pattern is composed of a prefix, and postfix in the form
* <b>array(prefix, postfix)</b>.
*
* @param arary negative pattern.
*/
function setNegativePattern($pattern)
{
$this->pattern['negPref'] = $pattern[0];
$this->pattern['negPost'] = $pattern[1];
}
/**
* Gets the format pattern for positive values.
* The positive pattern is composed of a prefix, and postfix.
* This function returns <b>array(prefix, postfix)</b>.
*
* @return arary positive pattern.
*/
function getPositivePattern()
{
$prefix = $this->pattern['posPref'];
$postfix = $this->pattern['posPost'];
return array($prefix, $postfix);
}
/**
* Sets the format pattern for positive values.
* The positive pattern is composed of a prefix, and postfix in the form
* <b>array(prefix, postfix)</b>.
*
* @param arary positive pattern.
*/
function setPositivePattern($pattern)
{
$this->pattern['posPref'] = $pattern[0];
$this->pattern['posPost'] = $pattern[1];
}
/**
* Gets the string to use as the currency symbol.
*
* @return string currency symbol.
*/
function getCurrencySymbol($currency = 'USD')
{
if (isset($this->pattern['symbol']))
{
return $this->pattern['symbol'];
}
else
{
return $this->data['Currencies'][$currency][0];
}
}
/**
* Sets the string to use as the currency symbol.
*
* @param string currency symbol.
*/
function setCurrencySymbol($symbol)
{
$this->pattern['symbol'] = $symbol;
}
/**
* Gets the string that represents negative infinity.
*
* @return string negative infinity.
*/
function getNegativeInfinitySymbol()
{
return $this->pattern['negInfty'];
}
/**
* Sets the string that represents negative infinity.
*
* @param string negative infinity.
*/
function setNegativeInfinitySymbol($value)
{
$this->pattern['negInfty'] = $value;
}
/**
* Gets the string that represents positive infinity.
*
* @return string positive infinity.
*/
function getPositiveInfinitySymbol()
{
return $this->pattern['posInfty'];
}
/**
* Sets the string that represents positive infinity.
*
* @param string positive infinity.
*/
function setPositiveInfinitySymbol($value)
{
$this->pattern['posInfty'] = $value;
}
/**
* Gets the string that denotes that the associated number is negative.
*
* @return string negative sign.
*/
function getNegativeSign()
{
return $this->data['NumberElements'][6];
}
/**
* Sets the string that denotes that the associated number is negative.
*
* @param string negative sign.
*/
function setNegativeSign($value)
{
$this->data['NumberElements'][6] = $value;
}
/**
* Gets the string that denotes that the associated number is positive.
*
* @return string positive sign.
*/
function getPositiveSign()
{
return $this->data['NumberElements'][11];
}
/**
* Sets the string that denotes that the associated number is positive.
*
* @param string positive sign.
*/
function setPositiveSign($value)
{
$this->data['NumberElements'][11] = $value;
}
/**
* Gets the string that represents the IEEE NaN (not a number) value.
*
* @return string NaN symbol.
*/
function getNaNSymbol()
{
return $this->data['NumberElements'][10];
}
/**
* Sets the string that represents the IEEE NaN (not a number) value.
*
* @param string NaN symbol.
*/
function setNaNSymbol($value)
{
$this->data['NumberElements'][10] = $value;
}
/**
* Gets the string to use as the percent symbol.
*
* @return string percent symbol.
*/
function getPercentSymbol()
{
return $this->data['NumberElements'][3];
}
/**
* Sets the string to use as the percent symbol.
*
* @param string percent symbol.
*/
function setPercentSymbol($value)
{
$this->data['NumberElements'][3] = $value;
}
/**
* Gets the string to use as the per mille symbol.
*
* @return string percent symbol.
*/
function getPerMilleSymbol()
{
return $this->data['NumberElements'][8];
}
/**
* Sets the string to use as the per mille symbol.
*
* @param string percent symbol.
*/
function setPerMilleSymbol($value)
{
$this->data['NumberElements'][8] = $value;
}
}

177
lib/symfony/i18n/util.php Executable file
View File

@ -0,0 +1,177 @@
<?php
/**
* I18N Utility file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the BSD License.
*
* Copyright(c) 2004 by Wei Zhuo. All rights reserved.
*
* To contact the author write to <weizhuo[at]gmail[dot]com>
* The latest version of PRADO can be obtained from:
* {@link http://prado.sourceforge.net/}
*
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
* @version $Id: util.php 2757 2006-11-18 10:11:00Z fabien $
* @package symfony
* @subpackage i18n
*/
/**
* For a given DSN (database connection string), return some information
* about the DSN. This function comes from PEAR's DB package.
* @param string DSN format, similar to PEAR's DB
* @return array DSN information.
*/
function parseDSN($dsn)
{
if (is_array($dsn)) {
return $dsn;
}
$parsed = array(
'phptype' => false,
'dbsyntax' => false,
'username' => false,
'password' => false,
'protocol' => false,
'hostspec' => false,
'port' => false,
'socket' => false,
'database' => false
);
// Find phptype and dbsyntax
if (($pos = strpos($dsn, '://')) !== false) {
$str = substr($dsn, 0, $pos);
$dsn = substr($dsn, $pos + 3);
} else {
$str = $dsn;
$dsn = NULL;
}
// Get phptype and dbsyntax
// $str => phptype(dbsyntax)
if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
$parsed['phptype'] = $arr[1];
$parsed['dbsyntax'] = (empty($arr[2])) ? $arr[1] : $arr[2];
} else {
$parsed['phptype'] = $str;
$parsed['dbsyntax'] = $str;
}
if (empty($dsn)) {
return $parsed;
}
// Get (if found): username and password
// $dsn => username:password@protocol+hostspec/database
if (($at = strrpos($dsn,'@')) !== false) {
$str = substr($dsn, 0, $at);
$dsn = substr($dsn, $at + 1);
if (($pos = strpos($str, ':')) !== false) {
$parsed['username'] = rawurldecode(substr($str, 0, $pos));
$parsed['password'] = rawurldecode(substr($str, $pos + 1));
} else {
$parsed['username'] = rawurldecode($str);
}
}
// Find protocol and hostspec
// $dsn => proto(proto_opts)/database
if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
$proto = $match[1];
$proto_opts = (!empty($match[2])) ? $match[2] : false;
$dsn = $match[3];
// $dsn => protocol+hostspec/database (old format)
} else {
if (strpos($dsn, '+') !== false) {
list($proto, $dsn) = explode('+', $dsn, 2);
}
if (strpos($dsn, '/') !== false) {
list($proto_opts, $dsn) = explode('/', $dsn, 2);
} else {
$proto_opts = $dsn;
$dsn = null;
}
}
// process the different protocol options
$parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
$proto_opts = rawurldecode($proto_opts);
if ($parsed['protocol'] == 'tcp') {
if (strpos($proto_opts, ':') !== false) {
list($parsed['hostspec'], $parsed['port']) = explode(':', $proto_opts);
} else {
$parsed['hostspec'] = $proto_opts;
}
} elseif ($parsed['protocol'] == 'unix') {
$parsed['socket'] = $proto_opts;
}
// Get dabase if any
// $dsn => database
if (!empty($dsn)) {
// /database
if (($pos = strpos($dsn, '?')) === false) {
$parsed['database'] = $dsn;
// /database?param1=value1&param2=value2
} else {
$parsed['database'] = substr($dsn, 0, $pos);
$dsn = substr($dsn, $pos + 1);
if (strpos($dsn, '&') !== false) {
$opts = explode('&', $dsn);
} else { // database?param1=value1
$opts = array($dsn);
}
foreach ($opts as $opt) {
list($key, $value) = explode('=', $opt);
if (!isset($parsed[$key])) { // don't allow params overwrite
$parsed[$key] = rawurldecode($value);
}
}
}
}
return $parsed;
}
/**
* Convert strings to UTF-8 via iconv. NB, the result may not by UTF-8
* if the conversion failed.
* @param string string to convert to UTF-8
* @return string UTF-8 encoded string, original string if iconv failed.
*/
function I18N_toUTF8($string, $from)
{
$from = strtoupper($from);
if ($from != 'UTF-8')
{
$s = iconv($from,'UTF-8',$string); //to UTF-8
return $s !== false ? $s : $string; //it could return false
}
return $string;
}
/**
* Convert UTF-8 strings to a different encoding. NB. The result
* may not have been encoded if iconv fails.
* @param string the UTF-8 string for conversion
* @return string encoded string.
*/
function I18N_toEncoding($string, $to)
{
$to = strtoupper($to);
if ($to != 'UTF-8')
{
$s = iconv('UTF-8', $to, $string);
return $s !== false ? $s : $string;
}
return $string;
}