]> git.wh0rd.org - tt-rss.git/commitdiff
move gettext/ to lib
authorAndrew Dolgov <fox@bah.spb.su>
Thu, 22 Jan 2009 13:38:21 +0000 (14:38 +0100)
committerAndrew Dolgov <fox@bah.spb.su>
Thu, 22 Jan 2009 13:38:21 +0000 (14:38 +0100)
functions.php
gettext/README [deleted file]
gettext/gettext.inc [deleted file]
gettext/gettext.php [deleted file]
gettext/streams.php [deleted file]
lib/gettext/README [new file with mode: 0644]
lib/gettext/gettext.inc [new file with mode: 0644]
lib/gettext/gettext.php [new file with mode: 0644]
lib/gettext/streams.php [new file with mode: 0644]

index 8f207cbdd64cd42b6c3edcc8c5b3288be8aa1bfb..fbf14ab505761dc0db0bb63bb2dd413b00b8aebd 100644 (file)
@@ -38,7 +38,7 @@
 
        if (ENABLE_TRANSLATIONS == true) { // If translations are enabled.
                require_once "accept-to-gettext.php";
-               require_once "gettext/gettext.inc";
+               require_once "lib/gettext/gettext.inc";
 
                function startup_gettext() {
        
diff --git a/gettext/README b/gettext/README
deleted file mode 100644 (file)
index c7525e2..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-PHP-gettext 1.0
-
-Copyright 2003, 2006 -- Danilo "angry with PHP[1]" Segan
-Licensed under GPLv2 (or any later version, see COPYING)
-
-[1] PHP is actually cyrillic, and translates roughly to 
-    "works-doesn't-work" (UTF-8: Ради-Не-Ради)
-
-
-Introduction
-
-    How many times did you look for a good translation tool, and
-    found out that gettext is best for the job? Many times.
-
-    How many times did you try to use gettext in PHP, but failed
-    miserably, because either your hosting provider didn't support
-    it, or the server didn't have adequate locale? Many times.
-
-    Well, this is a solution to your needs. It allows using gettext
-    tools for managing translations, yet it doesn't require gettext
-    library at all. It parses generated MO files directly, and thus
-    might be a bit slower than the (maybe provided) gettext library.
-
-    PHP-gettext is a simple reader for GNU gettext MO files. Those
-    are binary containers for translations, produced by GNU msgfmt.
-
-Why?
-
-    I got used to having gettext work even without gettext
-    library. It's there in my favourite language Python, so I was
-    surprised that I couldn't find it in PHP. I even Googled for it,
-    but to no avail.
-
-    So, I said, what the heck, I'm going to write it for this
-    disguisting language of PHP, because I'm often constrained to it.
-
-Features
-
-  o Support for simple translations
-    Just define a simple alias for translate() function (suggested
-    use of _() or gettext(); see provided example).
-
-  o Support for ngettext calls (plural forms, see a note under bugs)
-    You may also use plural forms. Translations in MO files need to
-    provide this, and they must also provide "plural-forms" header.
-    Please see 'info gettext' for more details.
-
-  o Support for reading straight files, or strings (!!!)
-    Since I can imagine many different backends for reading in the MO
-    file data, I used imaginary abstract class StreamReader to do all
-    the input (check streams.php). For your convenience, I've already
-    provided two classes for reading files: FileReader and
-    StringReader (CachedFileReader is a combination of the two: it 
-    loads entire file contents into a string, and then works on that). 
-    See example below for usage. You can for instance use StringReader 
-    when you read in data from a database, or you can create your own 
-    derivative of StreamReader for anything you like. 
-    
-
-Bugs
-
-    Plural-forms field in MO header (translation for empty string,
-    i.e. "") is treated according to PHP syntactic rules (it's
-    eval()ed). Since these should actually follow C syntax, there are
-    some problems.
-
-    For instance, I'm used to using this:
-      Plural-Forms: nplurals=3;    plural=n%10==1 && n%100!=11 ? 0 : \
-         n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
-    but it fails with PHP (it sets $plural=2 instead of 0 for $n==1).
-
-    The fix is usually simple, but I'm lazy to go into the details of
-    PHP operator precedence, and maybe try to fix it. In here, I had
-    to put everything after the first ':' in parenthesis:
-      Plural-Forms: nplurals=3;    plural=n%10==1 && n%100!=11 ? 0 : \
-         (n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
-    That works, and I'm satisfied.
-
-    Besides this one, there are probably a bunch of other bugs, since
-    I hate PHP (did I mention it already? no? strange), and don't
-    know it very well. So, feel free to fix any of those and report
-    them back to me at <danilo@kvota.net>.
-
-Usage
-
-    Put files streams.php and gettext.php somewhere you can load them
-    from, and require 'em in where you want to use them.
-
-    Then, create one 'stream reader' (a class that provides functions
-    like read(), seekto(), currentpos() and length()) which will
-    provide data for the 'gettext_reader', with eg.
-      $streamer = new FileStream('data.mo');
-
-    Then, use that as a parameter to gettext_reader constructor:
-      $wohoo = new gettext_reader($streamer);
-
-    If you want to disable pre-loading of entire message catalog in 
-    memory (if, for example, you have a multi-thousand message catalog 
-    which you'll use only occasionally), use "false" for second 
-    parameter to gettext_reader constructor:
-      $wohoo = new gettext_reader($streamer, false);
-
-    From now on, you have all the benefits of gettext data at your
-    disposal, so may run: 
-      print $wohoo->translate("This is a test");
-      print $wohoo->ngettext("%d bird", "%d birds", $birds);
-
-    You might need to pass parameter "-k" to xgettext to make it
-    extract all the strings. In above example, try with 
-      xgettext -ktranslate -kngettext:1,2 file.php
-    what should create messages.po which contains two messages for
-    translation.
-
-    I suggest creating simple aliases for these functions (see
-    example/pigs.php for how do I do it, which means it's probably a
-    bad way).
-
-
-Usage with gettext.inc (standard gettext interfaces emulation)
-
-    Check example in examples/pig_dropin.php, basically you include 
-    gettext.inc and use all the standard gettext interfaces as 
-    documented on:
-
-       http://www.php.net/gettext
-
-    The only catch is that you can check return value of setlocale()
-    to see if your locale is system supported or not.
-
-
-Example
-
-    See in examples/ subdirectory. There are a couple of files.
-    pigs.php is an example, serbian.po is a translation to Serbian
-    language, and serbian.mo is generated with
-       msgfmt -o serbian.mo serbian.po
-    There is also simple "update" script that can be used to generate
-    POT file and to update the translation using msgmerge.
-
-Interesting TODO:
-
-  o Try to parse "plural-forms" header field, and to follow C syntax
-    rules. This won't be easy.
-
-Boring TODO:
-
-  o Learn PHP and fix bugs, slowness and other stuff resulting from
-    my lack of knowledge (but *maybe*, it's not my knowledge that is
-    bad, but PHP itself ;-).  
-
-    (This is mostly done thanks to Nico Kaiser.)
-
-  o Try to use hash tables in MO files: with pre-loading, would it 
-    be useful at all?
-
-Never-asked-questions:
-
-  o Why did you mark this as version 1.0 when this is the first code
-    release?
-
-    Well, it's quite simple. I consider that the first released thing
-    should be labeled "version 1" (first, right?). Zero is there to
-    indicate that there's zero improvement and/or change compared to 
-    "version 1".
-
-    I plan to use version numbers 1.0.* for small bugfixes, and to
-    release 1.1 as "first stable release of version 1".
-
-    This may trick someone that this is actually useful software, but
-    as with any other free software, I take NO RESPONSIBILITY for
-    creating such a masterpiece that will smoke crack, trash your
-    hard disk, and make lasers in your CD device dance to the tune of
-    Mozart's 40th Symphony (there is one like that, right?).
-
-  o Can I...?
-    
-    Yes, you can. This is free software (as in freedom, free speech),
-    and you might do whatever you wish with it, provided you do not
-    limit freedom of others (GPL).
-
-    I'm considering licensing this under LGPL, but I *do* want
-    *every* PHP-gettext user to contribute and respect ideas of free
-    software, so don't count on it happening anytime soon.
-
-    I'm sorry that I'm taking away your freedom of taking others'
-    freedom away, but I believe that's neglible as compared to what
-    freedoms you could take away. ;-)
-
-    Uhm, whatever.
diff --git a/gettext/gettext.inc b/gettext/gettext.inc
deleted file mode 100644 (file)
index eb94b25..0000000
+++ /dev/null
@@ -1,318 +0,0 @@
-<?php
-/*
-   Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
-   
-   Drop in replacement for native gettext.
-   
-   This file is part of PHP-gettext.
-
-   PHP-gettext is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-
-   PHP-gettext is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with PHP-gettext; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
-/*
-LC_CTYPE               0
-LC_NUMERIC     1
-LC_TIME                        2
-LC_COLLATE     3
-LC_MONETARY    4
-LC_MESSAGES    5
-LC_ALL                 6
-*/
-
-require('streams.php');
-require('gettext.php');
-
-
-// Variables
-
-global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE;
-$text_domains = array();
-$default_domain = 'messages';
-$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
-$EMULATEGETTEXT = 0;
-$CURRENTLOCALE = '';
-
-
-// Utility functions
-
-/**
- * Utility function to get a StreamReader for the given text domain.
- */
-function _get_reader($domain=null, $category=5, $enable_cache=true) {
-       global $text_domains, $default_domain, $LC_CATEGORIES;
-       if (!isset($domain)) $domain = $default_domain;
-       if (!isset($text_domains[$domain]->l10n)) {
-               // get the current locale
-               $locale = _setlocale(LC_MESSAGES, 0);
-               $p = isset($text_domains[$domain]->path) ? $text_domains[$domain]->path : './';
-               $path = $p . "$locale/". $LC_CATEGORIES[$category] ."/$domain.mo";
-               if (file_exists($path)) {
-                       $input = new FileReader($path);
-               }
-               else {
-                       $input = null;
-               }
-               $text_domains[$domain]->l10n = new gettext_reader($input, $enable_cache);
-       }
-       return $text_domains[$domain]->l10n;
-}
-
-/**
- * Returns whether we are using our emulated gettext API or PHP built-in one.
- */
-function locale_emulation() {
-    global $EMULATEGETTEXT;
-    return $EMULATEGETTEXT;
-}
-
-/**
- * Checks if the current locale is supported on this system.
- */
-function _check_locale() {
-    global $EMULATEGETTEXT;
-    return !$EMULATEGETTEXT;
-}
-
-/**
- * Get the codeset for the given domain.
- */
-function _get_codeset($domain=null) {
-       global $text_domains, $default_domain, $LC_CATEGORIES;
-       if (!isset($domain)) $domain = $default_domain;
-       return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
-}
-
-/**
- * Convert the given string to the encoding set by bind_textdomain_codeset.
- */
-function _encode($text) {
-       $source_encoding = mb_detect_encoding($text);
-       $target_encoding = _get_codeset();
-       if ($source_encoding != $target_encoding) {
-               return mb_convert_encoding($text, $target_encoding, $source_encoding);
-       }
-       else {
-               return $text;
-       }
-}
-
-
-
-
-// Custom implementation of the standard gettext related functions
-
-/**
- * Sets a requested locale, if needed emulates it.
- */
-function _setlocale($category, $locale) {
-    global $CURRENTLOCALE, $EMULATEGETTEXT;
-    if ($locale === 0) { // use === to differentiate between string "0"
-        if ($CURRENTLOCALE != '') 
-            return $CURRENTLOCALE;
-        else 
-            // obey LANG variable, maybe extend to support all of LC_* vars
-            // even if we tried to read locale without setting it first
-            return _setlocale($category, $CURRENTLOCALE);
-    } else {
-        $ret = 0;
-        if (function_exists('setlocale')) // I don't know if this ever happens ;)
-           $ret = setlocale($category, $locale);
-        if (($ret and $locale == '') or ($ret == $locale)) {
-            $EMULATEGETTEXT = 0;
-            $CURRENTLOCALE = $ret;
-        } else {
-           if ($locale == '') // emulate variable support
-               $CURRENTLOCALE = getenv('LANG');
-           else
-               $CURRENTLOCALE = $locale;
-            $EMULATEGETTEXT = 1;
-        }
-        return $CURRENTLOCALE;
-    }
-}
-
-/**
- * Sets the path for a domain.
- */
-function _bindtextdomain($domain, $path) {
-       global $text_domains;
-       // ensure $path ends with a slash
-       if ($path[strlen($path) - 1] != '/') $path .= '/';
-       elseif ($path[strlen($path) - 1] != '\\') $path .= '\\';
-       $text_domains[$domain]->path = $path;
-}
-
-/**
- * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
- */
-function _bind_textdomain_codeset($domain, $codeset) {
-       global $text_domains;
-       $text_domains[$domain]->codeset = $codeset;
-}
-
-/**
- * Sets the default domain.
- */
-function _textdomain($domain) {
-       global $default_domain;
-       $default_domain = $domain;
-}
-
-/**
- * Lookup a message in the current domain.
- */
-function _gettext($msgid) {
-       $l10n = _get_reader();
-       //return $l10n->translate($msgid);
-       return _encode($l10n->translate($msgid));
-}
-/**
- * Alias for gettext.
- */
-function __($msgid) {
-       return _gettext($msgid);
-}
-/**
- * Plural version of gettext.
- */
-function _ngettext($single, $plural, $number) {
-       $l10n = _get_reader();
-       //return $l10n->ngettext($single, $plural, $number);
-       return _encode($l10n->ngettext($single, $plural, $number));
-}
-
-/**
- * Override the current domain.
- */
-function _dgettext($domain, $msgid) {
-       $l10n = _get_reader($domain);
-       //return $l10n->translate($msgid);
-       return _encode($l10n->translate($msgid));
-}
-/**
- * Plural version of dgettext.
- */
-function _dngettext($domain, $single, $plural, $number) {
-       $l10n = _get_reader($domain);
-       //return $l10n->ngettext($single, $plural, $number);
-       return _encode($l10n->ngettext($single, $plural, $number));
-}
-
-/**
- * Overrides the domain and category for a single lookup.
- */
-function _dcgettext($domain, $msgid, $category) {
-       $l10n = _get_reader($domain, $category);
-       //return $l10n->translate($msgid);
-       return _encode($l10n->translate($msgid));
-}
-/**
- * Plural version of dcgettext.
- */
-function _dcngettext($domain, $single, $plural, $number, $category) {
-       $l10n = _get_reader($domain, $category);
-       //return $l10n->ngettext($single, $plural, $number);
-       return _encode($l10n->ngettext($single, $plural, $number));
-}
-
-
-
-// Wrappers to use if the standard gettext functions are available, but the current locale is not supported by the system.
-// Use the standard impl if the current locale is supported, use the custom impl otherwise.
-
-function T_setlocale($category, $locale) {
-    return _setlocale($category, $locale);
-}
-
-function T_bindtextdomain($domain, $path) {
-       if (_check_locale()) return bindtextdomain($domain, $path);
-       else return _bindtextdomain($domain, $path);
-}
-function T_bind_textdomain_codeset($domain, $codeset) {
-    // bind_textdomain_codeset is available only in PHP 4.2.0+
-       if (_check_locale() and function_exists('bind_textdomain_codeset')) return bind_textdomain_codeset($domain, $codeset);
-       else return _bind_textdomain_codeset($domain, $codeset);
-}
-function T_textdomain($domain) {
-       if (_check_locale()) return textdomain($domain);
-       else return _textdomain($domain);
-}
-function T_gettext($msgid) {
-       if (_check_locale()) return gettext($msgid);
-       else return _gettext($msgid);
-}
-function T_($msgid) {
-       if (_check_locale()) return _($msgid);
-       return __($msgid);
-}
-function T_ngettext($single, $plural, $number) {
-       if (_check_locale()) return ngettext($single, $plural, $number);
-       else return _ngettext($single, $plural, $number);
-}
-function T_dgettext($domain, $msgid) {
-       if (_check_locale()) return dgettext($domain, $msgid);
-       else return _dgettext($domain, $msgid);
-}
-function T_dngettext($domain, $single, $plural, $number) {
-       if (_check_locale()) return dngettext($domain, $single, $plural, $number);
-       else return _dngettext($domain, $single, $plural, $number);
-}
-function T_dcgettext($domain, $msgid, $category) {
-       if (_check_locale()) return dcgettext($domain, $msgid, $category);
-       else return _dcgettext($domain, $msgid, $category);
-}
-function T_dcngettext($domain, $single, $plural, $number, $category) {
-       if (_check_locale()) return dcngettext($domain, $single, $plural, $number, $category);
-       else return _dcngettext($domain, $single, $plural, $number, $category);
-}
-
-
-
-// Wrappers used as a drop in replacement for the standard gettext functions
-
-if (!function_exists('gettext')) {
-       function bindtextdomain($domain, $path) {
-               return _bindtextdomain($domain, $path);
-       }
-       function bind_textdomain_codeset($domain, $codeset) {
-               return _bind_textdomain_codeset($domain, $codeset);
-       }
-       function textdomain($domain) {
-               return _textdomain($domain);
-       }
-       function gettext($msgid) {
-               return _gettext($msgid);
-       }
-       function _($msgid) {
-               return __($msgid);
-       }
-       function ngettext($single, $plural, $number) {
-               return _ngettext($single, $plural, $number);
-       }
-       function dgettext($domain, $msgid) {
-               return _dgettext($domain, $msgid);
-       }
-       function dngettext($domain, $single, $plural, $number) {
-               return _dngettext($domain, $single, $plural, $number);
-       }
-       function dcgettext($domain, $msgid, $category) {
-               return _dcgettext($domain, $msgid, $category);
-       }
-       function dcngettext($domain, $single, $plural, $number, $category) {
-               return _dcngettext($domain, $single, $plural, $number, $category);
-       }
-}
-
-?>
\ No newline at end of file
diff --git a/gettext/gettext.php b/gettext/gettext.php
deleted file mode 100644 (file)
index ad94a98..0000000
+++ /dev/null
@@ -1,358 +0,0 @@
-<?php
-/*
-   Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
-   Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
-   
-   This file is part of PHP-gettext.
-
-   PHP-gettext is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-
-   PHP-gettext is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with PHP-gettext; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
-/**
- * Provides a simple gettext replacement that works independently from
- * the system's gettext abilities.
- * It can read MO files and use them for translating strings.
- * The files are passed to gettext_reader as a Stream (see streams.php)
- * 
- * This version has the ability to cache all strings and translations to
- * speed up the string lookup.
- * While the cache is enabled by default, it can be switched off with the
- * second parameter in the constructor (e.g. whenusing very large MO files
- * that you don't want to keep in memory)
- */
-class gettext_reader {
-  //public:
-   var $error = 0; // public variable that holds error code (0 if no error)
-   
-   //private:
-  var $BYTEORDER = 0;        // 0: low endian, 1: big endian
-  var $STREAM = NULL;
-  var $short_circuit = false;
-  var $enable_cache = false;
-  var $originals = NULL;      // offset of original table
-  var $translations = NULL;    // offset of translation table
-  var $pluralheader = NULL;    // cache header field for plural forms
-  var $total = 0;          // total string count
-  var $table_originals = NULL;  // table for original strings (offsets)
-  var $table_translations = NULL;  // table for translated strings (offsets)
-  var $cache_translations = NULL;  // original -> translation mapping
-
-
-  /* Methods */
-  
-    
-  /**
-   * Reads a 32bit Integer from the Stream
-   * 
-   * @access private
-   * @return Integer from the Stream
-   */
-  function readint() {
-      if ($this->BYTEORDER == 0) {
-        // low endian
-        return array_shift(unpack('V', $this->STREAM->read(4)));
-      } else {
-        // big endian
-        return array_shift(unpack('N', $this->STREAM->read(4)));
-      }
-    }
-
-  /**
-   * Reads an array of Integers from the Stream
-   * 
-   * @param int count How many elements should be read
-   * @return Array of Integers
-   */
-  function readintarray($count) {
-    if ($this->BYTEORDER == 0) {
-        // low endian
-        return unpack('V'.$count, $this->STREAM->read(4 * $count));
-      } else {
-        // big endian
-        return unpack('N'.$count, $this->STREAM->read(4 * $count));
-      }
-  }
-  
-  /**
-   * Constructor
-   * 
-   * @param object Reader the StreamReader object
-   * @param boolean enable_cache Enable or disable caching of strings (default on)
-   */
-  function gettext_reader($Reader, $enable_cache = true) {
-    // If there isn't a StreamReader, turn on short circuit mode.
-    if (! $Reader || isset($Reader->error) ) {
-      $this->short_circuit = true;
-      return;
-    }
-    
-    // Caching can be turned off
-    $this->enable_cache = $enable_cache;
-
-    // $MAGIC1 = (int)0x950412de; //bug in PHP 5
-    $MAGIC1 = (int) - 1794895138;
-    // $MAGIC2 = (int)0xde120495; //bug
-    $MAGIC2 = (int) - 569244523;
-
-    $this->STREAM = $Reader;
-    $magic = $this->readint();
-    if ($magic == $MAGIC1) {
-      $this->BYTEORDER = 0;
-    } elseif ($magic == $MAGIC2) {
-      $this->BYTEORDER = 1;
-    } else {
-      $this->error = 1; // not MO file
-      return false;
-    }
-    
-    // FIXME: Do we care about revision? We should.
-    $revision = $this->readint();
-    
-    $this->total = $this->readint();
-    $this->originals = $this->readint();
-    $this->translations = $this->readint();
-  }
-  
-  /**
-   * Loads the translation tables from the MO file into the cache
-   * If caching is enabled, also loads all strings into a cache
-   * to speed up translation lookups
-   * 
-   * @access private
-   */
-  function load_tables() {
-    if (is_array($this->cache_translations) &&
-      is_array($this->table_originals) &&
-      is_array($this->table_translations))
-      return;
-    
-    /* get original and translations tables */
-    $this->STREAM->seekto($this->originals);
-    $this->table_originals = $this->readintarray($this->total * 2);
-    $this->STREAM->seekto($this->translations);
-    $this->table_translations = $this->readintarray($this->total * 2);
-    
-    if ($this->enable_cache) {
-      $this->cache_translations = array ();
-      /* read all strings in the cache */
-      for ($i = 0; $i < $this->total; $i++) {
-        $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
-        $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
-        $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
-        $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
-        $this->cache_translations[$original] = $translation;
-      }
-    }
-  }
-  
-  /**
-   * Returns a string from the "originals" table
-   * 
-   * @access private
-   * @param int num Offset number of original string
-   * @return string Requested string if found, otherwise ''
-   */
-  function get_original_string($num) {
-    $length = $this->table_originals[$num * 2 + 1];
-    $offset = $this->table_originals[$num * 2 + 2];
-    if (! $length)
-      return '';
-    $this->STREAM->seekto($offset);
-    $data = $this->STREAM->read($length);
-    return (string)$data;
-  }
-  
-  /**
-   * Returns a string from the "translations" table
-   * 
-   * @access private
-   * @param int num Offset number of original string
-   * @return string Requested string if found, otherwise ''
-   */
-  function get_translation_string($num) {
-    $length = $this->table_translations[$num * 2 + 1];
-    $offset = $this->table_translations[$num * 2 + 2];
-    if (! $length)
-      return '';
-    $this->STREAM->seekto($offset);
-    $data = $this->STREAM->read($length);
-    return (string)$data;
-  }
-  
-  /**
-   * Binary search for string
-   * 
-   * @access private
-   * @param string string
-   * @param int start (internally used in recursive function)
-   * @param int end (internally used in recursive function)
-   * @return int string number (offset in originals table)
-   */
-  function find_string($string, $start = -1, $end = -1) {
-    if (($start == -1) or ($end == -1)) {
-      // find_string is called with only one parameter, set start end end
-      $start = 0;
-      $end = $this->total;
-    }
-    if (abs($start - $end) <= 1) {
-      // We're done, now we either found the string, or it doesn't exist
-      $txt = $this->get_original_string($start);
-      if ($string == $txt)
-        return $start;
-      else
-        return -1;
-    } else if ($start > $end) {
-      // start > end -> turn around and start over
-      return $this->find_string($string, $end, $start);
-    } else {
-      // Divide table in two parts
-      $half = (int)(($start + $end) / 2);
-      $cmp = strcmp($string, $this->get_original_string($half));
-      if ($cmp == 0)
-        // string is exactly in the middle => return it
-        return $half;
-      else if ($cmp < 0)
-        // The string is in the upper half
-        return $this->find_string($string, $start, $half);
-      else
-        // The string is in the lower half
-        return $this->find_string($string, $half, $end);
-    }
-  }
-  
-  /**
-   * Translates a string
-   * 
-   * @access public
-   * @param string string to be translated
-   * @return string translated string (or original, if not found)
-   */
-  function translate($string) {
-    if ($this->short_circuit)
-      return $string;
-    $this->load_tables();     
-    
-    if ($this->enable_cache) {
-      // Caching enabled, get translated string from cache
-      if (array_key_exists($string, $this->cache_translations))
-        return $this->cache_translations[$string];
-      else
-        return $string;
-    } else {
-      // Caching not enabled, try to find string
-      $num = $this->find_string($string);
-      if ($num == -1)
-        return $string;
-      else
-        return $this->get_translation_string($num);
-    }
-  }
-
-  /**
-   * Get possible plural forms from MO header
-   * 
-   * @access private
-   * @return string plural form header
-   */
-  function get_plural_forms() {
-    // lets assume message number 0 is header  
-    // this is true, right?
-    $this->load_tables();
-    
-    // cache header field for plural forms
-    if (! is_string($this->pluralheader)) {
-      if ($this->enable_cache) {
-        $header = $this->cache_translations[""];
-      } else {
-        $header = $this->get_translation_string(0);
-      }
-      if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
-        $expr = $regs[1];
-      else
-        $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
-      $this->pluralheader = $expr;
-    }
-    return $this->pluralheader;
-  }
-
-  /**
-   * Detects which plural form to take
-   * 
-   * @access private
-   * @param n count
-   * @return int array index of the right plural form
-   */
-  function select_string($n) {
-    $string = $this->get_plural_forms();
-    $string = str_replace('nplurals',"\$total",$string);
-    $string = str_replace("n",$n,$string);
-    $string = str_replace('plural',"\$plural",$string);
-    
-    $total = 0;
-    $plural = 0;
-
-    eval("$string");
-    if ($plural >= $total) $plural = $total - 1;
-    return $plural;
-  }
-
-  /**
-   * Plural version of gettext
-   * 
-   * @access public
-   * @param string single
-   * @param string plural
-   * @param string number
-   * @return translated plural form
-   */
-  function ngettext($single, $plural, $number) {
-    if ($this->short_circuit) {
-      if ($number != 1)
-        return $plural;
-      else
-        return $single;
-    }
-
-    // find out the appropriate form
-    $select = $this->select_string($number); 
-    
-    // this should contains all strings separated by NULLs
-    $key = $single.chr(0).$plural;
-    
-    
-    if ($this->enable_cache) {
-      if (! array_key_exists($key, $this->cache_translations)) {
-        return ($number != 1) ? $plural : $single;
-      } else {
-        $result = $this->cache_translations[$key];
-        $list = explode(chr(0), $result);
-        return $list[$select];
-      }
-    } else {
-      $num = $this->find_string($key);
-      if ($num == -1) {
-        return ($number != 1) ? $plural : $single;
-      } else {
-        $result = $this->get_translation_string($num);
-        $list = explode(chr(0), $result);
-        return $list[$select];
-      }
-    }
-  }
-
-}
-
-?>
diff --git a/gettext/streams.php b/gettext/streams.php
deleted file mode 100644 (file)
index d57aac6..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-<?php
-/*
-   Copyright (c) 2003, 2005 Danilo Segan <danilo@kvota.net>.
-
-   This file is part of PHP-gettext.
-
-   PHP-gettext is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-
-   PHP-gettext is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with PHP-gettext; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
-
-
-// Simple class to wrap file streams, string streams, etc.
-// seek is essential, and it should be byte stream
-class StreamReader {
-  // should return a string [FIXME: perhaps return array of bytes?]
-  function read($bytes) {
-    return false;
-  }
-  
-  // should return new position
-  function seekto($position) {
-    return false;
-  }
-  
-  // returns current position
-  function currentpos() {
-    return false;
-  }
-  
-  // returns length of entire stream (limit for seekto()s)
-  function length() {
-    return false;
-  }
-}
-
-class StringReader {
-  var $_pos;
-  var $_str;
-
-  function StringReader($str='') {
-    $this->_str = $str;
-    $this->_pos = 0;
-  }
-
-  function read($bytes) {
-    $data = substr($this->_str, $this->_pos, $bytes);
-    $this->_pos += $bytes;
-    if (strlen($this->_str)<$this->_pos)
-      $this->_pos = strlen($this->_str);
-
-    return $data;
-  }
-
-  function seekto($pos) {
-    $this->_pos = $pos;
-    if (strlen($this->_str)<$this->_pos)
-      $this->_pos = strlen($this->_str);
-    return $this->_pos;
-  }
-
-  function currentpos() {
-    return $this->_pos;
-  }
-
-  function length() {
-    return strlen($this->_str);
-  }
-
-}
-
-
-class FileReader {
-  var $_pos;
-  var $_fd;
-  var $_length;
-
-  function FileReader($filename) {
-    if (file_exists($filename)) {
-
-      $this->_length=filesize($filename);
-      $this->_pos = 0;
-      $this->_fd = fopen($filename,'rb');
-      if (!$this->_fd) {
-       $this->error = 3; // Cannot read file, probably permissions
-       return false;
-      }
-    } else {
-      $this->error = 2; // File doesn't exist
-      return false;
-    }
-  }
-
-  function read($bytes) {
-    if ($bytes) {
-      fseek($this->_fd, $this->_pos);
-
-      // PHP 5.1.1 does not read more than 8192 bytes in one fread()
-      // the discussions at PHP Bugs suggest it's the intended behaviour
-      while ($bytes > 0) {
-        $chunk  = fread($this->_fd, $bytes);
-        $data  .= $chunk;
-        $bytes -= strlen($chunk);
-      }
-      $this->_pos = ftell($this->_fd);
-      
-      return $data;
-    } else return '';
-  }
-
-  function seekto($pos) {
-    fseek($this->_fd, $pos);
-    $this->_pos = ftell($this->_fd);
-    return $this->_pos;
-  }
-
-  function currentpos() {
-    return $this->_pos;
-  }
-
-  function length() {
-    return $this->_length;
-  }
-
-  function close() {
-    fclose($this->_fd);
-  }
-
-}
-
-// Preloads entire file in memory first, then creates a StringReader 
-// over it (it assumes knowledge of StringReader internals)
-class CachedFileReader extends StringReader {
-  function CachedFileReader($filename) {
-    if (file_exists($filename)) {
-
-      $length=filesize($filename);
-      $fd = fopen($filename,'rb');
-
-      if (!$fd) {
-       $this->error = 3; // Cannot read file, probably permissions
-       return false;
-      }
-      $this->_str = fread($fd, $length);
-      fclose($fd);
-
-    } else {
-      $this->error = 2; // File doesn't exist
-      return false;
-    }
-  }
-}
-
-
-?>
\ No newline at end of file
diff --git a/lib/gettext/README b/lib/gettext/README
new file mode 100644 (file)
index 0000000..c7525e2
--- /dev/null
@@ -0,0 +1,189 @@
+PHP-gettext 1.0
+
+Copyright 2003, 2006 -- Danilo "angry with PHP[1]" Segan
+Licensed under GPLv2 (or any later version, see COPYING)
+
+[1] PHP is actually cyrillic, and translates roughly to 
+    "works-doesn't-work" (UTF-8: Ради-Не-Ради)
+
+
+Introduction
+
+    How many times did you look for a good translation tool, and
+    found out that gettext is best for the job? Many times.
+
+    How many times did you try to use gettext in PHP, but failed
+    miserably, because either your hosting provider didn't support
+    it, or the server didn't have adequate locale? Many times.
+
+    Well, this is a solution to your needs. It allows using gettext
+    tools for managing translations, yet it doesn't require gettext
+    library at all. It parses generated MO files directly, and thus
+    might be a bit slower than the (maybe provided) gettext library.
+
+    PHP-gettext is a simple reader for GNU gettext MO files. Those
+    are binary containers for translations, produced by GNU msgfmt.
+
+Why?
+
+    I got used to having gettext work even without gettext
+    library. It's there in my favourite language Python, so I was
+    surprised that I couldn't find it in PHP. I even Googled for it,
+    but to no avail.
+
+    So, I said, what the heck, I'm going to write it for this
+    disguisting language of PHP, because I'm often constrained to it.
+
+Features
+
+  o Support for simple translations
+    Just define a simple alias for translate() function (suggested
+    use of _() or gettext(); see provided example).
+
+  o Support for ngettext calls (plural forms, see a note under bugs)
+    You may also use plural forms. Translations in MO files need to
+    provide this, and they must also provide "plural-forms" header.
+    Please see 'info gettext' for more details.
+
+  o Support for reading straight files, or strings (!!!)
+    Since I can imagine many different backends for reading in the MO
+    file data, I used imaginary abstract class StreamReader to do all
+    the input (check streams.php). For your convenience, I've already
+    provided two classes for reading files: FileReader and
+    StringReader (CachedFileReader is a combination of the two: it 
+    loads entire file contents into a string, and then works on that). 
+    See example below for usage. You can for instance use StringReader 
+    when you read in data from a database, or you can create your own 
+    derivative of StreamReader for anything you like. 
+    
+
+Bugs
+
+    Plural-forms field in MO header (translation for empty string,
+    i.e. "") is treated according to PHP syntactic rules (it's
+    eval()ed). Since these should actually follow C syntax, there are
+    some problems.
+
+    For instance, I'm used to using this:
+      Plural-Forms: nplurals=3;    plural=n%10==1 && n%100!=11 ? 0 : \
+         n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
+    but it fails with PHP (it sets $plural=2 instead of 0 for $n==1).
+
+    The fix is usually simple, but I'm lazy to go into the details of
+    PHP operator precedence, and maybe try to fix it. In here, I had
+    to put everything after the first ':' in parenthesis:
+      Plural-Forms: nplurals=3;    plural=n%10==1 && n%100!=11 ? 0 : \
+         (n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
+    That works, and I'm satisfied.
+
+    Besides this one, there are probably a bunch of other bugs, since
+    I hate PHP (did I mention it already? no? strange), and don't
+    know it very well. So, feel free to fix any of those and report
+    them back to me at <danilo@kvota.net>.
+
+Usage
+
+    Put files streams.php and gettext.php somewhere you can load them
+    from, and require 'em in where you want to use them.
+
+    Then, create one 'stream reader' (a class that provides functions
+    like read(), seekto(), currentpos() and length()) which will
+    provide data for the 'gettext_reader', with eg.
+      $streamer = new FileStream('data.mo');
+
+    Then, use that as a parameter to gettext_reader constructor:
+      $wohoo = new gettext_reader($streamer);
+
+    If you want to disable pre-loading of entire message catalog in 
+    memory (if, for example, you have a multi-thousand message catalog 
+    which you'll use only occasionally), use "false" for second 
+    parameter to gettext_reader constructor:
+      $wohoo = new gettext_reader($streamer, false);
+
+    From now on, you have all the benefits of gettext data at your
+    disposal, so may run: 
+      print $wohoo->translate("This is a test");
+      print $wohoo->ngettext("%d bird", "%d birds", $birds);
+
+    You might need to pass parameter "-k" to xgettext to make it
+    extract all the strings. In above example, try with 
+      xgettext -ktranslate -kngettext:1,2 file.php
+    what should create messages.po which contains two messages for
+    translation.
+
+    I suggest creating simple aliases for these functions (see
+    example/pigs.php for how do I do it, which means it's probably a
+    bad way).
+
+
+Usage with gettext.inc (standard gettext interfaces emulation)
+
+    Check example in examples/pig_dropin.php, basically you include 
+    gettext.inc and use all the standard gettext interfaces as 
+    documented on:
+
+       http://www.php.net/gettext
+
+    The only catch is that you can check return value of setlocale()
+    to see if your locale is system supported or not.
+
+
+Example
+
+    See in examples/ subdirectory. There are a couple of files.
+    pigs.php is an example, serbian.po is a translation to Serbian
+    language, and serbian.mo is generated with
+       msgfmt -o serbian.mo serbian.po
+    There is also simple "update" script that can be used to generate
+    POT file and to update the translation using msgmerge.
+
+Interesting TODO:
+
+  o Try to parse "plural-forms" header field, and to follow C syntax
+    rules. This won't be easy.
+
+Boring TODO:
+
+  o Learn PHP and fix bugs, slowness and other stuff resulting from
+    my lack of knowledge (but *maybe*, it's not my knowledge that is
+    bad, but PHP itself ;-).  
+
+    (This is mostly done thanks to Nico Kaiser.)
+
+  o Try to use hash tables in MO files: with pre-loading, would it 
+    be useful at all?
+
+Never-asked-questions:
+
+  o Why did you mark this as version 1.0 when this is the first code
+    release?
+
+    Well, it's quite simple. I consider that the first released thing
+    should be labeled "version 1" (first, right?). Zero is there to
+    indicate that there's zero improvement and/or change compared to 
+    "version 1".
+
+    I plan to use version numbers 1.0.* for small bugfixes, and to
+    release 1.1 as "first stable release of version 1".
+
+    This may trick someone that this is actually useful software, but
+    as with any other free software, I take NO RESPONSIBILITY for
+    creating such a masterpiece that will smoke crack, trash your
+    hard disk, and make lasers in your CD device dance to the tune of
+    Mozart's 40th Symphony (there is one like that, right?).
+
+  o Can I...?
+    
+    Yes, you can. This is free software (as in freedom, free speech),
+    and you might do whatever you wish with it, provided you do not
+    limit freedom of others (GPL).
+
+    I'm considering licensing this under LGPL, but I *do* want
+    *every* PHP-gettext user to contribute and respect ideas of free
+    software, so don't count on it happening anytime soon.
+
+    I'm sorry that I'm taking away your freedom of taking others'
+    freedom away, but I believe that's neglible as compared to what
+    freedoms you could take away. ;-)
+
+    Uhm, whatever.
diff --git a/lib/gettext/gettext.inc b/lib/gettext/gettext.inc
new file mode 100644 (file)
index 0000000..eb94b25
--- /dev/null
@@ -0,0 +1,318 @@
+<?php
+/*
+   Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
+   
+   Drop in replacement for native gettext.
+   
+   This file is part of PHP-gettext.
+
+   PHP-gettext is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PHP-gettext is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with PHP-gettext; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+/*
+LC_CTYPE               0
+LC_NUMERIC     1
+LC_TIME                        2
+LC_COLLATE     3
+LC_MONETARY    4
+LC_MESSAGES    5
+LC_ALL                 6
+*/
+
+require('streams.php');
+require('gettext.php');
+
+
+// Variables
+
+global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE;
+$text_domains = array();
+$default_domain = 'messages';
+$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
+$EMULATEGETTEXT = 0;
+$CURRENTLOCALE = '';
+
+
+// Utility functions
+
+/**
+ * Utility function to get a StreamReader for the given text domain.
+ */
+function _get_reader($domain=null, $category=5, $enable_cache=true) {
+       global $text_domains, $default_domain, $LC_CATEGORIES;
+       if (!isset($domain)) $domain = $default_domain;
+       if (!isset($text_domains[$domain]->l10n)) {
+               // get the current locale
+               $locale = _setlocale(LC_MESSAGES, 0);
+               $p = isset($text_domains[$domain]->path) ? $text_domains[$domain]->path : './';
+               $path = $p . "$locale/". $LC_CATEGORIES[$category] ."/$domain.mo";
+               if (file_exists($path)) {
+                       $input = new FileReader($path);
+               }
+               else {
+                       $input = null;
+               }
+               $text_domains[$domain]->l10n = new gettext_reader($input, $enable_cache);
+       }
+       return $text_domains[$domain]->l10n;
+}
+
+/**
+ * Returns whether we are using our emulated gettext API or PHP built-in one.
+ */
+function locale_emulation() {
+    global $EMULATEGETTEXT;
+    return $EMULATEGETTEXT;
+}
+
+/**
+ * Checks if the current locale is supported on this system.
+ */
+function _check_locale() {
+    global $EMULATEGETTEXT;
+    return !$EMULATEGETTEXT;
+}
+
+/**
+ * Get the codeset for the given domain.
+ */
+function _get_codeset($domain=null) {
+       global $text_domains, $default_domain, $LC_CATEGORIES;
+       if (!isset($domain)) $domain = $default_domain;
+       return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
+}
+
+/**
+ * Convert the given string to the encoding set by bind_textdomain_codeset.
+ */
+function _encode($text) {
+       $source_encoding = mb_detect_encoding($text);
+       $target_encoding = _get_codeset();
+       if ($source_encoding != $target_encoding) {
+               return mb_convert_encoding($text, $target_encoding, $source_encoding);
+       }
+       else {
+               return $text;
+       }
+}
+
+
+
+
+// Custom implementation of the standard gettext related functions
+
+/**
+ * Sets a requested locale, if needed emulates it.
+ */
+function _setlocale($category, $locale) {
+    global $CURRENTLOCALE, $EMULATEGETTEXT;
+    if ($locale === 0) { // use === to differentiate between string "0"
+        if ($CURRENTLOCALE != '') 
+            return $CURRENTLOCALE;
+        else 
+            // obey LANG variable, maybe extend to support all of LC_* vars
+            // even if we tried to read locale without setting it first
+            return _setlocale($category, $CURRENTLOCALE);
+    } else {
+        $ret = 0;
+        if (function_exists('setlocale')) // I don't know if this ever happens ;)
+           $ret = setlocale($category, $locale);
+        if (($ret and $locale == '') or ($ret == $locale)) {
+            $EMULATEGETTEXT = 0;
+            $CURRENTLOCALE = $ret;
+        } else {
+           if ($locale == '') // emulate variable support
+               $CURRENTLOCALE = getenv('LANG');
+           else
+               $CURRENTLOCALE = $locale;
+            $EMULATEGETTEXT = 1;
+        }
+        return $CURRENTLOCALE;
+    }
+}
+
+/**
+ * Sets the path for a domain.
+ */
+function _bindtextdomain($domain, $path) {
+       global $text_domains;
+       // ensure $path ends with a slash
+       if ($path[strlen($path) - 1] != '/') $path .= '/';
+       elseif ($path[strlen($path) - 1] != '\\') $path .= '\\';
+       $text_domains[$domain]->path = $path;
+}
+
+/**
+ * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
+ */
+function _bind_textdomain_codeset($domain, $codeset) {
+       global $text_domains;
+       $text_domains[$domain]->codeset = $codeset;
+}
+
+/**
+ * Sets the default domain.
+ */
+function _textdomain($domain) {
+       global $default_domain;
+       $default_domain = $domain;
+}
+
+/**
+ * Lookup a message in the current domain.
+ */
+function _gettext($msgid) {
+       $l10n = _get_reader();
+       //return $l10n->translate($msgid);
+       return _encode($l10n->translate($msgid));
+}
+/**
+ * Alias for gettext.
+ */
+function __($msgid) {
+       return _gettext($msgid);
+}
+/**
+ * Plural version of gettext.
+ */
+function _ngettext($single, $plural, $number) {
+       $l10n = _get_reader();
+       //return $l10n->ngettext($single, $plural, $number);
+       return _encode($l10n->ngettext($single, $plural, $number));
+}
+
+/**
+ * Override the current domain.
+ */
+function _dgettext($domain, $msgid) {
+       $l10n = _get_reader($domain);
+       //return $l10n->translate($msgid);
+       return _encode($l10n->translate($msgid));
+}
+/**
+ * Plural version of dgettext.
+ */
+function _dngettext($domain, $single, $plural, $number) {
+       $l10n = _get_reader($domain);
+       //return $l10n->ngettext($single, $plural, $number);
+       return _encode($l10n->ngettext($single, $plural, $number));
+}
+
+/**
+ * Overrides the domain and category for a single lookup.
+ */
+function _dcgettext($domain, $msgid, $category) {
+       $l10n = _get_reader($domain, $category);
+       //return $l10n->translate($msgid);
+       return _encode($l10n->translate($msgid));
+}
+/**
+ * Plural version of dcgettext.
+ */
+function _dcngettext($domain, $single, $plural, $number, $category) {
+       $l10n = _get_reader($domain, $category);
+       //return $l10n->ngettext($single, $plural, $number);
+       return _encode($l10n->ngettext($single, $plural, $number));
+}
+
+
+
+// Wrappers to use if the standard gettext functions are available, but the current locale is not supported by the system.
+// Use the standard impl if the current locale is supported, use the custom impl otherwise.
+
+function T_setlocale($category, $locale) {
+    return _setlocale($category, $locale);
+}
+
+function T_bindtextdomain($domain, $path) {
+       if (_check_locale()) return bindtextdomain($domain, $path);
+       else return _bindtextdomain($domain, $path);
+}
+function T_bind_textdomain_codeset($domain, $codeset) {
+    // bind_textdomain_codeset is available only in PHP 4.2.0+
+       if (_check_locale() and function_exists('bind_textdomain_codeset')) return bind_textdomain_codeset($domain, $codeset);
+       else return _bind_textdomain_codeset($domain, $codeset);
+}
+function T_textdomain($domain) {
+       if (_check_locale()) return textdomain($domain);
+       else return _textdomain($domain);
+}
+function T_gettext($msgid) {
+       if (_check_locale()) return gettext($msgid);
+       else return _gettext($msgid);
+}
+function T_($msgid) {
+       if (_check_locale()) return _($msgid);
+       return __($msgid);
+}
+function T_ngettext($single, $plural, $number) {
+       if (_check_locale()) return ngettext($single, $plural, $number);
+       else return _ngettext($single, $plural, $number);
+}
+function T_dgettext($domain, $msgid) {
+       if (_check_locale()) return dgettext($domain, $msgid);
+       else return _dgettext($domain, $msgid);
+}
+function T_dngettext($domain, $single, $plural, $number) {
+       if (_check_locale()) return dngettext($domain, $single, $plural, $number);
+       else return _dngettext($domain, $single, $plural, $number);
+}
+function T_dcgettext($domain, $msgid, $category) {
+       if (_check_locale()) return dcgettext($domain, $msgid, $category);
+       else return _dcgettext($domain, $msgid, $category);
+}
+function T_dcngettext($domain, $single, $plural, $number, $category) {
+       if (_check_locale()) return dcngettext($domain, $single, $plural, $number, $category);
+       else return _dcngettext($domain, $single, $plural, $number, $category);
+}
+
+
+
+// Wrappers used as a drop in replacement for the standard gettext functions
+
+if (!function_exists('gettext')) {
+       function bindtextdomain($domain, $path) {
+               return _bindtextdomain($domain, $path);
+       }
+       function bind_textdomain_codeset($domain, $codeset) {
+               return _bind_textdomain_codeset($domain, $codeset);
+       }
+       function textdomain($domain) {
+               return _textdomain($domain);
+       }
+       function gettext($msgid) {
+               return _gettext($msgid);
+       }
+       function _($msgid) {
+               return __($msgid);
+       }
+       function ngettext($single, $plural, $number) {
+               return _ngettext($single, $plural, $number);
+       }
+       function dgettext($domain, $msgid) {
+               return _dgettext($domain, $msgid);
+       }
+       function dngettext($domain, $single, $plural, $number) {
+               return _dngettext($domain, $single, $plural, $number);
+       }
+       function dcgettext($domain, $msgid, $category) {
+               return _dcgettext($domain, $msgid, $category);
+       }
+       function dcngettext($domain, $single, $plural, $number, $category) {
+               return _dcngettext($domain, $single, $plural, $number, $category);
+       }
+}
+
+?>
\ No newline at end of file
diff --git a/lib/gettext/gettext.php b/lib/gettext/gettext.php
new file mode 100644 (file)
index 0000000..ad94a98
--- /dev/null
@@ -0,0 +1,358 @@
+<?php
+/*
+   Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
+   Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
+   
+   This file is part of PHP-gettext.
+
+   PHP-gettext is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PHP-gettext is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with PHP-gettext; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+/**
+ * Provides a simple gettext replacement that works independently from
+ * the system's gettext abilities.
+ * It can read MO files and use them for translating strings.
+ * The files are passed to gettext_reader as a Stream (see streams.php)
+ * 
+ * This version has the ability to cache all strings and translations to
+ * speed up the string lookup.
+ * While the cache is enabled by default, it can be switched off with the
+ * second parameter in the constructor (e.g. whenusing very large MO files
+ * that you don't want to keep in memory)
+ */
+class gettext_reader {
+  //public:
+   var $error = 0; // public variable that holds error code (0 if no error)
+   
+   //private:
+  var $BYTEORDER = 0;        // 0: low endian, 1: big endian
+  var $STREAM = NULL;
+  var $short_circuit = false;
+  var $enable_cache = false;
+  var $originals = NULL;      // offset of original table
+  var $translations = NULL;    // offset of translation table
+  var $pluralheader = NULL;    // cache header field for plural forms
+  var $total = 0;          // total string count
+  var $table_originals = NULL;  // table for original strings (offsets)
+  var $table_translations = NULL;  // table for translated strings (offsets)
+  var $cache_translations = NULL;  // original -> translation mapping
+
+
+  /* Methods */
+  
+    
+  /**
+   * Reads a 32bit Integer from the Stream
+   * 
+   * @access private
+   * @return Integer from the Stream
+   */
+  function readint() {
+      if ($this->BYTEORDER == 0) {
+        // low endian
+        return array_shift(unpack('V', $this->STREAM->read(4)));
+      } else {
+        // big endian
+        return array_shift(unpack('N', $this->STREAM->read(4)));
+      }
+    }
+
+  /**
+   * Reads an array of Integers from the Stream
+   * 
+   * @param int count How many elements should be read
+   * @return Array of Integers
+   */
+  function readintarray($count) {
+    if ($this->BYTEORDER == 0) {
+        // low endian
+        return unpack('V'.$count, $this->STREAM->read(4 * $count));
+      } else {
+        // big endian
+        return unpack('N'.$count, $this->STREAM->read(4 * $count));
+      }
+  }
+  
+  /**
+   * Constructor
+   * 
+   * @param object Reader the StreamReader object
+   * @param boolean enable_cache Enable or disable caching of strings (default on)
+   */
+  function gettext_reader($Reader, $enable_cache = true) {
+    // If there isn't a StreamReader, turn on short circuit mode.
+    if (! $Reader || isset($Reader->error) ) {
+      $this->short_circuit = true;
+      return;
+    }
+    
+    // Caching can be turned off
+    $this->enable_cache = $enable_cache;
+
+    // $MAGIC1 = (int)0x950412de; //bug in PHP 5
+    $MAGIC1 = (int) - 1794895138;
+    // $MAGIC2 = (int)0xde120495; //bug
+    $MAGIC2 = (int) - 569244523;
+
+    $this->STREAM = $Reader;
+    $magic = $this->readint();
+    if ($magic == $MAGIC1) {
+      $this->BYTEORDER = 0;
+    } elseif ($magic == $MAGIC2) {
+      $this->BYTEORDER = 1;
+    } else {
+      $this->error = 1; // not MO file
+      return false;
+    }
+    
+    // FIXME: Do we care about revision? We should.
+    $revision = $this->readint();
+    
+    $this->total = $this->readint();
+    $this->originals = $this->readint();
+    $this->translations = $this->readint();
+  }
+  
+  /**
+   * Loads the translation tables from the MO file into the cache
+   * If caching is enabled, also loads all strings into a cache
+   * to speed up translation lookups
+   * 
+   * @access private
+   */
+  function load_tables() {
+    if (is_array($this->cache_translations) &&
+      is_array($this->table_originals) &&
+      is_array($this->table_translations))
+      return;
+    
+    /* get original and translations tables */
+    $this->STREAM->seekto($this->originals);
+    $this->table_originals = $this->readintarray($this->total * 2);
+    $this->STREAM->seekto($this->translations);
+    $this->table_translations = $this->readintarray($this->total * 2);
+    
+    if ($this->enable_cache) {
+      $this->cache_translations = array ();
+      /* read all strings in the cache */
+      for ($i = 0; $i < $this->total; $i++) {
+        $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
+        $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
+        $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
+        $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
+        $this->cache_translations[$original] = $translation;
+      }
+    }
+  }
+  
+  /**
+   * Returns a string from the "originals" table
+   * 
+   * @access private
+   * @param int num Offset number of original string
+   * @return string Requested string if found, otherwise ''
+   */
+  function get_original_string($num) {
+    $length = $this->table_originals[$num * 2 + 1];
+    $offset = $this->table_originals[$num * 2 + 2];
+    if (! $length)
+      return '';
+    $this->STREAM->seekto($offset);
+    $data = $this->STREAM->read($length);
+    return (string)$data;
+  }
+  
+  /**
+   * Returns a string from the "translations" table
+   * 
+   * @access private
+   * @param int num Offset number of original string
+   * @return string Requested string if found, otherwise ''
+   */
+  function get_translation_string($num) {
+    $length = $this->table_translations[$num * 2 + 1];
+    $offset = $this->table_translations[$num * 2 + 2];
+    if (! $length)
+      return '';
+    $this->STREAM->seekto($offset);
+    $data = $this->STREAM->read($length);
+    return (string)$data;
+  }
+  
+  /**
+   * Binary search for string
+   * 
+   * @access private
+   * @param string string
+   * @param int start (internally used in recursive function)
+   * @param int end (internally used in recursive function)
+   * @return int string number (offset in originals table)
+   */
+  function find_string($string, $start = -1, $end = -1) {
+    if (($start == -1) or ($end == -1)) {
+      // find_string is called with only one parameter, set start end end
+      $start = 0;
+      $end = $this->total;
+    }
+    if (abs($start - $end) <= 1) {
+      // We're done, now we either found the string, or it doesn't exist
+      $txt = $this->get_original_string($start);
+      if ($string == $txt)
+        return $start;
+      else
+        return -1;
+    } else if ($start > $end) {
+      // start > end -> turn around and start over
+      return $this->find_string($string, $end, $start);
+    } else {
+      // Divide table in two parts
+      $half = (int)(($start + $end) / 2);
+      $cmp = strcmp($string, $this->get_original_string($half));
+      if ($cmp == 0)
+        // string is exactly in the middle => return it
+        return $half;
+      else if ($cmp < 0)
+        // The string is in the upper half
+        return $this->find_string($string, $start, $half);
+      else
+        // The string is in the lower half
+        return $this->find_string($string, $half, $end);
+    }
+  }
+  
+  /**
+   * Translates a string
+   * 
+   * @access public
+   * @param string string to be translated
+   * @return string translated string (or original, if not found)
+   */
+  function translate($string) {
+    if ($this->short_circuit)
+      return $string;
+    $this->load_tables();     
+    
+    if ($this->enable_cache) {
+      // Caching enabled, get translated string from cache
+      if (array_key_exists($string, $this->cache_translations))
+        return $this->cache_translations[$string];
+      else
+        return $string;
+    } else {
+      // Caching not enabled, try to find string
+      $num = $this->find_string($string);
+      if ($num == -1)
+        return $string;
+      else
+        return $this->get_translation_string($num);
+    }
+  }
+
+  /**
+   * Get possible plural forms from MO header
+   * 
+   * @access private
+   * @return string plural form header
+   */
+  function get_plural_forms() {
+    // lets assume message number 0 is header  
+    // this is true, right?
+    $this->load_tables();
+    
+    // cache header field for plural forms
+    if (! is_string($this->pluralheader)) {
+      if ($this->enable_cache) {
+        $header = $this->cache_translations[""];
+      } else {
+        $header = $this->get_translation_string(0);
+      }
+      if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
+        $expr = $regs[1];
+      else
+        $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
+      $this->pluralheader = $expr;
+    }
+    return $this->pluralheader;
+  }
+
+  /**
+   * Detects which plural form to take
+   * 
+   * @access private
+   * @param n count
+   * @return int array index of the right plural form
+   */
+  function select_string($n) {
+    $string = $this->get_plural_forms();
+    $string = str_replace('nplurals',"\$total",$string);
+    $string = str_replace("n",$n,$string);
+    $string = str_replace('plural',"\$plural",$string);
+    
+    $total = 0;
+    $plural = 0;
+
+    eval("$string");
+    if ($plural >= $total) $plural = $total - 1;
+    return $plural;
+  }
+
+  /**
+   * Plural version of gettext
+   * 
+   * @access public
+   * @param string single
+   * @param string plural
+   * @param string number
+   * @return translated plural form
+   */
+  function ngettext($single, $plural, $number) {
+    if ($this->short_circuit) {
+      if ($number != 1)
+        return $plural;
+      else
+        return $single;
+    }
+
+    // find out the appropriate form
+    $select = $this->select_string($number); 
+    
+    // this should contains all strings separated by NULLs
+    $key = $single.chr(0).$plural;
+    
+    
+    if ($this->enable_cache) {
+      if (! array_key_exists($key, $this->cache_translations)) {
+        return ($number != 1) ? $plural : $single;
+      } else {
+        $result = $this->cache_translations[$key];
+        $list = explode(chr(0), $result);
+        return $list[$select];
+      }
+    } else {
+      $num = $this->find_string($key);
+      if ($num == -1) {
+        return ($number != 1) ? $plural : $single;
+      } else {
+        $result = $this->get_translation_string($num);
+        $list = explode(chr(0), $result);
+        return $list[$select];
+      }
+    }
+  }
+
+}
+
+?>
diff --git a/lib/gettext/streams.php b/lib/gettext/streams.php
new file mode 100644 (file)
index 0000000..d57aac6
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+/*
+   Copyright (c) 2003, 2005 Danilo Segan <danilo@kvota.net>.
+
+   This file is part of PHP-gettext.
+
+   PHP-gettext is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PHP-gettext is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with PHP-gettext; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+
+// Simple class to wrap file streams, string streams, etc.
+// seek is essential, and it should be byte stream
+class StreamReader {
+  // should return a string [FIXME: perhaps return array of bytes?]
+  function read($bytes) {
+    return false;
+  }
+  
+  // should return new position
+  function seekto($position) {
+    return false;
+  }
+  
+  // returns current position
+  function currentpos() {
+    return false;
+  }
+  
+  // returns length of entire stream (limit for seekto()s)
+  function length() {
+    return false;
+  }
+}
+
+class StringReader {
+  var $_pos;
+  var $_str;
+
+  function StringReader($str='') {
+    $this->_str = $str;
+    $this->_pos = 0;
+  }
+
+  function read($bytes) {
+    $data = substr($this->_str, $this->_pos, $bytes);
+    $this->_pos += $bytes;
+    if (strlen($this->_str)<$this->_pos)
+      $this->_pos = strlen($this->_str);
+
+    return $data;
+  }
+
+  function seekto($pos) {
+    $this->_pos = $pos;
+    if (strlen($this->_str)<$this->_pos)
+      $this->_pos = strlen($this->_str);
+    return $this->_pos;
+  }
+
+  function currentpos() {
+    return $this->_pos;
+  }
+
+  function length() {
+    return strlen($this->_str);
+  }
+
+}
+
+
+class FileReader {
+  var $_pos;
+  var $_fd;
+  var $_length;
+
+  function FileReader($filename) {
+    if (file_exists($filename)) {
+
+      $this->_length=filesize($filename);
+      $this->_pos = 0;
+      $this->_fd = fopen($filename,'rb');
+      if (!$this->_fd) {
+       $this->error = 3; // Cannot read file, probably permissions
+       return false;
+      }
+    } else {
+      $this->error = 2; // File doesn't exist
+      return false;
+    }
+  }
+
+  function read($bytes) {
+    if ($bytes) {
+      fseek($this->_fd, $this->_pos);
+
+      // PHP 5.1.1 does not read more than 8192 bytes in one fread()
+      // the discussions at PHP Bugs suggest it's the intended behaviour
+      while ($bytes > 0) {
+        $chunk  = fread($this->_fd, $bytes);
+        $data  .= $chunk;
+        $bytes -= strlen($chunk);
+      }
+      $this->_pos = ftell($this->_fd);
+      
+      return $data;
+    } else return '';
+  }
+
+  function seekto($pos) {
+    fseek($this->_fd, $pos);
+    $this->_pos = ftell($this->_fd);
+    return $this->_pos;
+  }
+
+  function currentpos() {
+    return $this->_pos;
+  }
+
+  function length() {
+    return $this->_length;
+  }
+
+  function close() {
+    fclose($this->_fd);
+  }
+
+}
+
+// Preloads entire file in memory first, then creates a StringReader 
+// over it (it assumes knowledge of StringReader internals)
+class CachedFileReader extends StringReader {
+  function CachedFileReader($filename) {
+    if (file_exists($filename)) {
+
+      $length=filesize($filename);
+      $fd = fopen($filename,'rb');
+
+      if (!$fd) {
+       $this->error = 3; // Cannot read file, probably permissions
+       return false;
+      }
+      $this->_str = fread($fd, $length);
+      fclose($fd);
+
+    } else {
+      $this->error = 2; // File doesn't exist
+      return false;
+    }
+  }
+}
+
+
+?>
\ No newline at end of file