More localization work. Resolved major issue with JSON parser not parsing files over ~50KB. Switched JSON parser to the one from the Zend Framework (BSD licensed). Forced to split enano.json into five different files.
authorDan
Wed, 26 Dec 2007 00:37:26 -0500
changeset 334 c72b545f1304
parent 333 32429702305e
child 335 67bd3121a12e
More localization work. Resolved major issue with JSON parser not parsing files over ~50KB. Switched JSON parser to the one from the Zend Framework (BSD licensed). Forced to split enano.json into five different files.
ajax.php
images/pm_ajax.gif
images/pm_star_off.png
images/pm_star_on.png
includes/clientside/css/enano-shared.css
includes/clientside/static/l10n.js
includes/clientside/static/misc.js
includes/comment.php
includes/common.php
includes/constants.php
includes/functions.php
includes/json2.php
includes/lang.php
includes/pageutils.php
includes/paths.php
includes/search.php
index.php
install.php
language/english/admin.json
language/english/core.json
language/english/enano.json
language/english/tools.json
language/english/user.json
plugins/PrivateMessages.php
plugins/SpecialAdmin.php
plugins/SpecialCSS.php
plugins/SpecialGroups.php
plugins/SpecialPageFuncs.php
plugins/SpecialSearch.php
plugins/SpecialUpdownload.php
plugins/SpecialUserFuncs.php
plugins/SpecialUserPrefs.php
plugins/admin/PageGroups.php
themes/oxygen/css/bleu.css
--- a/ajax.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/ajax.php	Wed Dec 26 00:37:26 2007 -0500
@@ -47,7 +47,6 @@
     $db->connect();
     
     // result is sent using JSON
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
     $return = Array(
         'mode' => 'success',
         'users_real' => Array()
@@ -61,7 +60,7 @@
         'mode' => 'error',
         'error' => 'Invalid URI'
       );
-      die( $json->encode($return) );
+      die( enano_json_encode($return) );
     }
     $allowanon = ( isset($_GET['allowanon']) && $_GET['allowanon'] == '1' ) ? '' : ' AND user_id > 1';
     $q = $db->sql_query('SELECT username FROM '.table_prefix.'users WHERE ' . ENANO_SQLFUNC_LOWERCASE . '(username) LIKE ' . ENANO_SQLFUNC_LOWERCASE . '(\'%'.$name.'%\')' . $allowanon . ' ORDER BY username ASC;');
@@ -80,7 +79,7 @@
     // all done! :-)
     $db->close();
     
-    echo $json->encode( $return );
+    echo enano_json_encode( $return );
     
     exit;
   }
@@ -285,7 +284,6 @@
       die('GOOD');
       break;
     case 'get_tags':
-      $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
       
       $ret = array('tags' => array(), 'user_level' => $session->user_level, 'can_add' => $session->get_permissions('tag_create'));
       $q = $db->sql_query('SELECT t.tag_id, t.tag_name, pg.pg_target IS NOT NULL AS used_in_acl, t.user_id FROM '.table_prefix.'tags AS t
@@ -321,11 +319,10 @@
         );
       }
       
-      echo $json->encode($ret);
+      echo enano_json_encode($ret);
       
       break;
     case 'addtag':
-      $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
       $resp = array(
           'success' => false,
           'error' => 'No error',
@@ -337,7 +334,7 @@
       if ( !$session->get_permissions('tag_create') )
       {
         $resp['error'] = 'You are not permitted to tag pages.';
-        die($json->encode($resp));
+        die(enano_json_encode($resp));
       }
       
       // sanitize the tag name
@@ -347,7 +344,7 @@
       if ( strlen($tag) < 2 )
       {
         $resp['error'] = 'Tags must consist of at least 2 alphanumeric characters.';
-        die($json->encode($resp));
+        die(enano_json_encode($resp));
       }
       
       // check if tag is already on page
@@ -357,7 +354,7 @@
       if ( $db->numrows() > 0 )
       {
         $resp['error'] = 'This page already has this tag.';
-        die($json->encode($resp));
+        die(enano_json_encode($resp));
       }
       $db->free_result();
       
@@ -369,7 +366,7 @@
       if ( $db->numrows() > 0 && !$can_edit_acl )
       {
         $resp['error'] = 'This tag is used in an ACL page group, and thus can\'t be added to a page by people without administrator privileges.';
-        die($json->encode($resp));
+        die(enano_json_encode($resp));
       }
       $resp['in_acl'] = ( $db->numrows() > 0 );
       $db->free_result();
@@ -383,7 +380,7 @@
       $resp['tag'] = $tag;
       $resp['tag_id'] = $db->insert_id();
       
-      echo $json->encode($resp);
+      echo enano_json_encode($resp);
       break;
     case 'deltag':
       
Binary file images/pm_ajax.gif has changed
Binary file images/pm_star_off.png has changed
Binary file images/pm_star_on.png has changed
--- a/includes/clientside/css/enano-shared.css	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/clientside/css/enano-shared.css	Wed Dec 26 00:37:26 2007 -0500
@@ -193,6 +193,7 @@
 div.search-result span.search-result-info { color: #7777CC; }
 div.search-result span.search-result-url  { color: green; }
 div.search-result span.search-term, div.search-result span.title-search-term { background-color: #FFFFC0; font-weight: bold; }
+div.search-result span.url-search-term { font-weight: bold; }
 div.search-result span.search-result-annotation { font-size: 8pt; }
 div.search-hibar       { border-top: 1px solid #3366CC; margin-top: 10px; color: #000; background-color: #D5DFF3; padding: 3px; vertical-align: middle; }
 div.search-lobar       { background-color: #E5EFFF; margin: 0; padding: 5px; }
@@ -526,3 +527,121 @@
   font-size: 0.7em;
 }
 
+/* Default private message AJAX interface styles (colors and style based on those of Gmail) */
+
+div#privmsgs {
+  /* Neal prefers this border but I personally consider it distasteful because it detracts from the Gmail-ey look.
+  border: 1px solid #c0c0c0; */
+  background-color: white;
+  color: black;
+}
+
+span.pm_link {
+  color: #0000ff;
+  cursor: pointer;
+  text-decoration: underline;
+}
+
+span.pm_link_folder {
+  display: block;
+  text-decoration: none;
+  padding: 3px;
+}
+
+span.pm_link_selected {
+  background-color: #c3d9ff;
+  font-weight: bold;
+  text-decoration: underline;
+}
+
+span.pm_link_selected_trash {
+  background-color: #d9d9db;
+}
+
+div.pm_break {
+  height: 10px;
+}
+
+div.pm_main {
+  background-color: #c3d9ff;
+  padding: 5px 5px 3px 5px;
+  margin-left: 12em;
+  min-height: 16em;
+}
+
+div.pm_main_trash {
+  background-color: #d9d9db;
+}
+
+div.pm_status {
+  display: table;
+  background-color: #cc0000;
+  padding: 3px;
+  margin: 0 auto;
+  color: white;
+}
+
+div.pm_teaser {
+  background-color: white;
+  color: black;
+  text-align: center;
+  padding: 8em 1em 8em 1em;
+}
+
+div.pm_mlist_message {
+  background-color: #e8eef7;
+  color: black;
+  border-bottom: 1px solid #d8d8d8;
+  cursor: pointer;
+}
+
+div.pm_mlist_message span.pm_subject {
+  font-weight: normal;
+  display: inline-block;
+  clip: rect(0px, auto, auto, 0px);
+  overflow: hidden;
+  margin: 0 3px 0 0;
+}
+
+div.pm_mlist_message span.pm_sender {
+  display: inline-block;
+  width: 30%;
+  margin: 0 10px 0% 4px;
+  font-weight: normal;
+  clip: rect(0px, auto, auto, 0px);
+  overflow: hidden;
+}
+
+div.pm_mlist_message span.pm_miniclip {
+  color: #909090;
+  display: inline-block;
+  clip: rect(0px, auto, auto, 0px);
+  overflow: hidden;
+}
+
+div.pm_messagelist_inner {
+  min-height: 12em;
+  background-color: white;
+}
+
+div.pm_mlist_message_unread {
+  background-color: white;
+}
+
+div.pm_mlist_message_unread span.pm_subject {
+  font-weight: bold;
+}
+
+div.pm_mlist_message_unread span.pm_sender {
+  font-weight: bold;
+}
+
+div.pm_mlist_message_selected {
+  background-color: #ffffcc;
+}
+
+span.pm_toolbar_label {
+  color: black;
+  font-weight: bold;
+}
+
--- a/includes/clientside/static/l10n.js	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/clientside/static/l10n.js	Wed Dec 26 00:37:26 2007 -0500
@@ -18,7 +18,7 @@
       return string_id;
     if ( typeof(this.strings[catname][string_name]) != 'string' )
       return string_id;
-    return '[LJS] ' + this.perform_subst(this.strings[catname][string_name], subst);
+    return this.perform_subst(this.strings[catname][string_name], subst) + '**';
   }
   
   this.perform_subst = function(str, subst)
--- a/includes/clientside/static/misc.js	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/clientside/static/misc.js	Wed Dec 26 00:37:26 2007 -0500
@@ -792,3 +792,13 @@
   return ( email.match(/^(?:[\w\d_-]+\.?)+@((?:(?:[\w\d_-]\-?)+\.)+\w{2,4}|localhost)$/) ) ? true : false;
 }
 
+/**
+ * Equivalent of PHP's time()
+ * @return int
+ */
+
+function unix_time()
+{
+  return parseInt((new Date()).getTime()/1000);
+}
+
--- a/includes/comment.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/comment.php	Wed Dec 26 00:37:26 2007 -0500
@@ -80,19 +80,18 @@
   function process_json($json)
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    $parser = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-    $data = $parser->decode($json);
+    $data = enano_json_decode($json);
     $data = decode_unicode_array($data);
     if ( !isset($data['mode']) )
     {
       $ret = Array('mode'=>'error','error'=>'No mode defined!');
-      echo $parser->encode($ret);
+      echo enano_json_encode($ret);
       return $ret;
     }
     if ( getConfig('enable_comments') == '0' )
     {
       $ret = Array('mode'=>'error','error'=>'Comments are not enabled on this site.');
-      echo $parser->encode($ret);
+      echo enano_json_encode($ret);
       return $ret;
     }
     $ret = Array();
@@ -346,7 +345,7 @@
           'mode' => 'error', 
           'error' => 'You are not authorized to moderate comments.'
           );
-          echo $parser->encode($ret);
+          echo enano_json_encode($ret);
           return $ret;
         }
         
@@ -383,7 +382,7 @@
           );
         break;
     }
-    echo $parser->encode($ret);
+    echo enano_json_encode($ret);
     return $ret;
   }
   
--- a/includes/common.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/common.php	Wed Dec 26 00:37:26 2007 -0500
@@ -120,6 +120,7 @@
 require_once(ENANO_ROOT.'/includes/email.php');
 require_once(ENANO_ROOT.'/includes/search.php');
 require_once(ENANO_ROOT.'/includes/json.php');
+require_once(ENANO_ROOT.'/includes/json2.php');
 require_once(ENANO_ROOT.'/includes/wikiengine/Tables.php');
 require_once(ENANO_ROOT.'/includes/pageprocess.php');
 require_once(ENANO_ROOT.'/includes/tagcloud.php');
@@ -348,6 +349,14 @@
 
   // All checks passed! Start the main components up.  
   $session->start();
+  
+  // This is where plugins will want to add pages from 1.1.x on out. You can still add pages at base_classes_initted but the titles won't be localized.
+  $code = $plugins->setHook('session_started');
+  foreach ( $code as $cmd )
+  {
+    eval($cmd);
+  }
+  
   $paths->init();
   
   // We're ready for whatever life throws us now.
@@ -385,7 +394,7 @@
   // A better name for this hook would be common_post. At this point
   // all of Enano is fully initialized and running and you're ready
   // to do whatever you want.
-  $code = $plugins->setHook('session_started');
+  $code = $plugins->setHook('common_post');
   foreach ( $code as $cmd )
   {
     eval($cmd);
--- a/includes/constants.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/constants.php	Wed Dec 26 00:37:26 2007 -0500
@@ -68,6 +68,15 @@
 define('GROUP_HIDDEN', 3);
 define('GROUP_OPEN', 4);
 
+// Private message flags
+define('PM_UNREAD', 1);
+define('PM_STARRED', 2);
+define('PM_SENT', 4);
+define('PM_DRAFT', 8);
+define('PM_ARCHIVE', 16);
+define('PM_TRASH', 32);
+define('PM_DELIVERED', 64);
+
 // Other stuff
 
 define('MAX_PMS_PER_BATCH', 7); // The maximum number of users that users can send PMs to in one go; restriction does not apply to users with mod_misc rights
--- a/includes/functions.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/functions.php	Wed Dec 26 00:37:26 2007 -0500
@@ -3806,6 +3806,58 @@
   return false;
 }
 
+/**
+ * Generates a JSON encoder/decoder singleton.
+ * @return object
+ */
+
+function enano_json_singleton()
+{
+  static $json_obj;
+  if ( !is_object($json_obj) )
+    $json_obj = new Services_JSON(SERVICES_JSON_LOOSE_TYPE | SERVICES_JSON_SUPPRESS_ERRORS);
+  
+  return $json_obj;
+}
+
+/**
+ * Wrapper for JSON encoding.
+ * @param mixed Variable to encode
+ * @return string JSON-encoded string
+ */
+
+function enano_json_encode($data)
+{
+  /*
+  if ( function_exists('json_encode') )
+  {
+    // using PHP5 with JSON support
+    return json_encode($data);
+  }
+  */
+  
+  return Zend_Json::encode($data, true);
+}
+
+/**
+ * Wrapper for JSON decoding.
+ * @param string JSON-encoded string
+ * @return mixed Decoded value
+ */
+
+function enano_json_decode($data)
+{
+  /*
+  if ( function_exists('json_decode') )
+  {
+    // using PHP5 with JSON support
+    return json_decode($data);
+  }
+  */
+  
+  return Zend_Json::decode($data, Zend_Json::TYPE_ARRAY);
+}
+
 //die('<pre>Original:  01010101010100101010100101010101011010'."\nProcessed: ".uncompress_bitfield(compress_bitfield('01010101010100101010100101010101011010')).'</pre>');
 
 ?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/includes/json2.php	Wed Dec 26 00:37:26 2007 -0500
@@ -0,0 +1,1015 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Json
+ * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+/**
+ * Encode PHP constructs to JSON
+ *
+ * @category   Zend
+ * @package    Zend_Json
+ * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Json_Encoder
+{
+    /**
+     * Whether or not to check for possible cycling
+     *
+     * @var boolean
+     */
+    protected $_cycleCheck;
+
+    /**
+     * Array of visited objects; used to prevent cycling.
+     *
+     * @var array
+     */
+    protected $_visited = array();
+
+    /**
+     * Constructor
+     *
+     * @param boolean $cycleCheck Whether or not to check for recursion when encoding
+     * @return void
+     */
+    protected function __construct($cycleCheck = false)
+    {
+        $this->_cycleCheck = $cycleCheck;
+    }
+
+    /**
+     * Use the JSON encoding scheme for the value specified
+     *
+     * @param mixed $value The value to be encoded
+     * @param boolean $cycleCheck Whether or not to check for possible object recursion when encoding
+     * @return string  The encoded value
+     */
+    public static function encode($value, $cycleCheck = false)
+    {
+        $encoder = new Zend_Json_Encoder(($cycleCheck) ? true : false);
+
+        return $encoder->_encodeValue($value);
+    }
+
+    /**
+     * Recursive driver which determines the type of value to be encoded
+     * and then dispatches to the appropriate method. $values are either
+     *    - objects (returns from {@link _encodeObject()})
+     *    - arrays (returns from {@link _encodeArray()})
+     *    - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()})
+     *
+     * @param $value mixed The value to be encoded
+     * @return string Encoded value
+     */
+    protected function _encodeValue(&$value)
+    {
+        if (is_object($value)) {
+            return $this->_encodeObject($value);
+        } else if (is_array($value)) {
+            return $this->_encodeArray($value);
+        }
+
+        return $this->_encodeDatum($value);
+    }
+
+
+
+    /**
+     * Encode an object to JSON by encoding each of the public properties
+     *
+     * A special property is added to the JSON object called '__className'
+     * that contains the name of the class of $value. This is used to decode
+     * the object on the client into a specific class.
+     *
+     * @param $value object
+     * @return string
+     * @throws Zend_Json_Exception If recursive checks are enabled and the object has been serialized previously
+     */
+    protected function _encodeObject(&$value)
+    {
+        if ($this->_cycleCheck) {
+            if ($this->_wasVisited($value)) {
+                throw new Zend_Json_Exception(
+                    'Cycles not supported in JSON encoding, cycle introduced by '
+                    . 'class "' . get_class($value) . '"'
+                );
+            }
+
+            $this->_visited[] = $value;
+        }
+
+        $props = '';
+        foreach (get_object_vars($value) as $name => $propValue) {
+            if (isset($propValue)) {
+                $props .= ','
+                        . $this->_encodeValue($name)
+                        . ':'
+                        . $this->_encodeValue($propValue);
+            }
+        }
+
+        return '{"__className":"' . get_class($value) . '"'
+                . $props . '}';
+    }
+
+
+    /**
+     * Determine if an object has been serialized already
+     *
+     * @param mixed $value
+     * @return boolean
+     */
+    protected function _wasVisited(&$value)
+    {
+        if (in_array($value, $this->_visited, true)) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * JSON encode an array value
+     *
+     * Recursively encodes each value of an array and returns a JSON encoded
+     * array string.
+     *
+     * Arrays are defined as integer-indexed arrays starting at index 0, where
+     * the last index is (count($array) -1); any deviation from that is
+     * considered an associative array, and will be encoded as such.
+     *
+     * @param $array array
+     * @return string
+     */
+    protected function _encodeArray(&$array)
+    {
+        $tmpArray = array();
+
+        // Check for associative array
+        if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) {
+            // Associative array
+            $result = '{';
+            foreach ($array as $key => $value) {
+                $key = (string) $key;
+                $tmpArray[] = $this->_encodeString($key)
+                            . ':'
+                            . $this->_encodeValue($value);
+            }
+            $result .= implode(',', $tmpArray);
+            $result .= '}';
+        } else {
+            // Indexed array
+            $result = '[';
+            $length = count($array);
+            for ($i = 0; $i < $length; $i++) {
+                $tmpArray[] = $this->_encodeValue($array[$i]);
+            }
+            $result .= implode(',', $tmpArray);
+            $result .= ']';
+        }
+
+        return $result;
+    }
+
+
+    /**
+     * JSON encode a basic data type (string, number, boolean, null)
+     *
+     * If value type is not a string, number, boolean, or null, the string
+     * 'null' is returned.
+     *
+     * @param $value mixed
+     * @return string
+     */
+    protected function _encodeDatum(&$value)
+    {
+        $result = 'null';
+
+        if (is_int($value) || is_float($value)) {
+            $result = (string)$value;
+        } elseif (is_string($value)) {
+            $result = $this->_encodeString($value);
+        } elseif (is_bool($value)) {
+            $result = $value ? 'true' : 'false';
+        }
+
+        return $result;
+    }
+
+
+    /**
+     * JSON encode a string value by escaping characters as necessary
+     *
+     * @param $value string
+     * @return string
+     */
+    protected function _encodeString(&$string)
+    {
+        // Escape these characters with a backslash:
+        // " \ / \n \r \t \b \f
+        $search  = array('\\', "\n", "\t", "\r", "\b", "\f", '"');
+        $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"');
+        $string  = str_replace($search, $replace, $string);
+
+        // Escape certain ASCII characters:
+        // 0x08 => \b
+        // 0x0c => \f
+        $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);
+
+        return '"' . $string . '"';
+    }
+
+
+    /**
+     * Encode the constants associated with the ReflectionClass
+     * parameter. The encoding format is based on the class2 format
+     *
+     * @param $cls ReflectionClass
+     * @return string Encoded constant block in class2 format
+     */
+    private static function _encodeConstants(ReflectionClass $cls)
+    {
+        $result    = "constants : {";
+        $constants = $cls->getConstants();
+
+        $tmpArray = array();
+        if (!empty($constants)) {
+            foreach ($constants as $key => $value) {
+                $tmpArray[] = "$key: " . self::encode($value);
+            }
+
+            $result .= implode(', ', $tmpArray);
+        }
+
+        return $result . "}";
+    }
+
+
+    /**
+     * Encode the public methods of the ReflectionClass in the
+     * class2 format
+     *
+     * @param $cls ReflectionClass
+     * @return string Encoded method fragment
+     *
+     */
+    private static function _encodeMethods(ReflectionClass $cls)
+    {
+        $methods = $cls->getMethods();
+        $result = 'methods:{';
+
+        $started = false;
+        foreach ($methods as $method) {
+            if (! $method->isPublic() || !$method->isUserDefined()) {
+                continue;
+            }
+
+            if ($started) {
+                $result .= ',';
+            }
+            $started = true;
+
+            $result .= '' . $method->getName(). ':function(';
+
+            if ('__construct' != $method->getName()) {
+                $parameters  = $method->getParameters();
+                $paramCount  = count($parameters);
+                $argsStarted = false;
+
+                $argNames = "var argNames=[";
+                foreach ($parameters as $param) {
+                    if ($argsStarted) {
+                        $result .= ',';
+                    }
+
+                    $result .= $param->getName();
+
+                    if ($argsStarted) {
+                        $argNames .= ',';
+                    }
+
+                    $argNames .= '"' . $param->getName() . '"';
+
+                    $argsStarted = true;
+                }
+                $argNames .= "];";
+
+                $result .= "){"
+                         . $argNames
+                         . 'var result = ZAjaxEngine.invokeRemoteMethod('
+                         . "this, '" . $method->getName()
+                         . "',argNames,arguments);"
+                         . 'return(result);}';
+            } else {
+                $result .= "){}";
+            }
+        }
+
+        return $result . "}";
+    }
+
+
+    /**
+     * Encode the public properties of the ReflectionClass in the class2
+     * format.
+     *
+     * @param $cls ReflectionClass
+     * @return string Encode properties list
+     *
+     */
+    private static function _encodeVariables(ReflectionClass $cls)
+    {
+        $properties = $cls->getProperties();
+        $propValues = get_class_vars($cls->getName());
+        $result = "variables:{";
+        $cnt = 0;
+
+        $tmpArray = array();
+        foreach ($properties as $prop) {
+            if (! $prop->isPublic()) {
+                continue;
+            }
+
+            $tmpArray[] = $prop->getName()
+                        . ':'
+                        . self::encode($propValues[$prop->getName()]);
+        }
+        $result .= implode(',', $tmpArray);
+
+        return $result . "}";
+    }
+
+    /**
+     * Encodes the given $className into the class2 model of encoding PHP
+     * classes into JavaScript class2 classes.
+     * NOTE: Currently only public methods and variables are proxied onto
+     * the client machine
+     *
+     * @param $className string The name of the class, the class must be
+     * instantiable using a null constructor
+     * @param $package string Optional package name appended to JavaScript
+     * proxy class name
+     * @return string The class2 (JavaScript) encoding of the class
+     * @throws Zend_Json_Exception
+     */
+    public static function encodeClass($className, $package = '')
+    {
+        $cls = new ReflectionClass($className);
+        if (! $cls->isInstantiable()) {
+            throw new Zend_Json_Exception("$className must be instantiable");
+        }
+
+        return "Class.create('$package$className',{"
+                . self::_encodeConstants($cls)    .","
+                . self::_encodeMethods($cls)      .","
+                . self::_encodeVariables($cls)    .'});';
+    }
+
+
+    /**
+     * Encode several classes at once
+     *
+     * Returns JSON encoded classes, using {@link encodeClass()}.
+     *
+     * @param array $classNames
+     * @param string $package
+     * @return string
+     */
+    public static function encodeClasses(array $classNames, $package = '')
+    {
+        $result = '';
+        foreach ($classNames as $className) {
+            $result .= self::encodeClass($className, $package);
+        }
+
+        return $result;
+    }
+
+}
+
+/**
+ * Decode JSON encoded string to PHP variable constructs
+ *
+ * @category   Zend
+ * @package    Zend_Json
+ * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Json_Decoder
+{
+    /**
+     * Parse tokens used to decode the JSON object. These are not
+     * for public consumption, they are just used internally to the
+     * class.
+     */
+    const EOF          = 0;
+    const DATUM        = 1;
+    const LBRACE       = 2;
+    const LBRACKET     = 3;
+    const RBRACE       = 4;
+    const RBRACKET     = 5;
+    const COMMA        = 6;
+    const COLON        = 7;
+
+    /**
+     * Use to maintain a "pointer" to the source being decoded
+     *
+     * @var string
+     */
+    protected $_source;
+
+    /**
+     * Caches the source length
+     *
+     * @var int
+     */
+    protected $_sourceLength;
+
+    /**
+     * The offset within the souce being decoded
+     *
+     * @var int
+     *
+     */
+    protected $_offset;
+
+    /**
+     * The current token being considered in the parser cycle
+     *
+     * @var int
+     */
+    protected $_token;
+
+    /**
+     * Flag indicating how objects should be decoded
+     *
+     * @var int
+     * @access protected
+     */
+    protected $_decodeType;
+
+    /**
+     * Constructor
+     *
+     * @param string $source String source to decode
+     * @param int $decodeType How objects should be decoded -- see
+     * {@link Zend_Json::TYPE_ARRAY} and {@link Zend_Json::TYPE_OBJECT} for
+     * valid values
+     * @return void
+     */
+    protected function __construct($source, $decodeType)
+    {
+        
+        // eliminate comments
+        $source = preg_replace(array(
+
+                  // eliminate single line comments in '// ...' form
+                  '#^\s*//(.+)$#m',
+    
+                  // eliminate multi-line comments in '/* ... */' form, at start of string
+                  '#^\s*/\*(.+)\*/#Us',
+    
+                  // eliminate multi-line comments in '/* ... */' form, at end of string
+                  '#/\*(.+)\*/\s*$#Us'
+    
+              ), '', $source);
+        
+        // Set defaults
+        $this->_source       = $source;
+        $this->_sourceLength = strlen($source);
+        $this->_token        = self::EOF;
+        $this->_offset       = 0;
+
+        // Normalize and set $decodeType
+        if (!in_array($decodeType, array(Zend_Json::TYPE_ARRAY, Zend_Json::TYPE_OBJECT)))
+        {
+            $decodeType = Zend_Json::TYPE_ARRAY;
+        }
+        $this->_decodeType   = $decodeType;
+
+        // Set pointer at first token
+        $this->_getNextToken();
+    }
+
+    /**
+     * Decode a JSON source string
+     *
+     * Decodes a JSON encoded string. The value returned will be one of the
+     * following:
+     *        - integer
+     *        - float
+     *        - boolean
+     *        - null
+     *      - StdClass
+     *      - array
+     *         - array of one or more of the above types
+     *
+     * By default, decoded objects will be returned as associative arrays; to
+     * return a StdClass object instead, pass {@link Zend_Json::TYPE_OBJECT} to
+     * the $objectDecodeType parameter.
+     *
+     * Throws a Zend_Json_Exception if the source string is null.
+     *
+     * @static
+     * @access public
+     * @param string $source String to be decoded
+     * @param int $objectDecodeType How objects should be decoded; should be
+     * either or {@link Zend_Json::TYPE_ARRAY} or
+     * {@link Zend_Json::TYPE_OBJECT}; defaults to TYPE_ARRAY
+     * @return mixed
+     * @throws Zend_Json_Exception
+     */
+    public static function decode($source = null, $objectDecodeType = Zend_Json::TYPE_ARRAY)
+    {
+        if (null === $source) {
+            throw new Zend_Json_Exception('Must specify JSON encoded source for decoding');
+        } elseif (!is_string($source)) {
+            throw new Zend_Json_Exception('Can only decode JSON encoded strings');
+        }
+
+        $decoder = new self($source, $objectDecodeType);
+
+        return $decoder->_decodeValue();
+    }
+
+
+    /**
+     * Recursive driving rountine for supported toplevel tops
+     *
+     * @return mixed
+     */
+    protected function _decodeValue()
+    {
+        switch ($this->_token) {
+            case self::DATUM:
+                $result  = $this->_tokenValue;
+                $this->_getNextToken();
+                return($result);
+                break;
+            case self::LBRACE:
+                return($this->_decodeObject());
+                break;
+            case self::LBRACKET:
+                return($this->_decodeArray());
+                break;
+            default:
+                return null;
+                break;
+        }
+    }
+
+    /**
+     * Decodes an object of the form:
+     *  { "attribute: value, "attribute2" : value,...}
+     *
+     * If ZJsonEnoder or ZJAjax was used to encode the original object
+     * then a special attribute called __className which specifies a class
+     * name that should wrap the data contained within the encoded source.
+     *
+     * Decodes to either an array or StdClass object, based on the value of
+     * {@link $_decodeType}. If invalid $_decodeType present, returns as an
+     * array.
+     *
+     * @return array|StdClass
+     */
+    protected function _decodeObject()
+    {
+        $members = array();
+        $tok = $this->_getNextToken();
+
+        while ($tok && $tok != self::RBRACE) {
+            if ($tok != self::DATUM || ! is_string($this->_tokenValue)) {
+                throw new Zend_Json_Exception('Missing key in object encoding: ' . $this->_source);
+            }
+
+            $key = $this->_tokenValue;
+            $tok = $this->_getNextToken();
+
+            if ($tok != self::COLON) {
+                throw new Zend_Json_Exception('Missing ":" in object encoding: ' . $this->_source);
+            }
+
+            $tok = $this->_getNextToken();
+            $members[$key] = $this->_decodeValue();
+            $tok = $this->_token;
+
+            if ($tok == self::RBRACE) {
+                break;
+            }
+
+            if ($tok != self::COMMA) {
+                throw new Zend_Json_Exception('Missing "," in object encoding: ' . $this->_source);
+            }
+
+            $tok = $this->_getNextToken();
+        }
+
+        switch ($this->_decodeType) {
+            case Zend_Json::TYPE_OBJECT:
+                // Create new StdClass and populate with $members
+                $result = new StdClass();
+                foreach ($members as $key => $value) {
+                    $result->$key = $value;
+                }
+                break;
+            case Zend_Json::TYPE_ARRAY:
+            default:
+                $result = $members;
+                break;
+        }
+
+        $this->_getNextToken();
+        return $result;
+    }
+
+    /**
+     * Decodes a JSON array format:
+     *    [element, element2,...,elementN]
+     *
+     * @return array
+     */
+    protected function _decodeArray()
+    {
+        $result = array();
+        $starttok = $tok = $this->_getNextToken(); // Move past the '['
+        $index  = 0;
+
+        while ($tok && $tok != self::RBRACKET) {
+            $result[$index++] = $this->_decodeValue();
+
+            $tok = $this->_token;
+
+            if ($tok == self::RBRACKET || !$tok) {
+                break;
+            }
+
+            if ($tok != self::COMMA) {
+                throw new Zend_Json_Exception('Missing "," in array encoding: ' . $this->_source);
+            }
+
+            $tok = $this->_getNextToken();
+        }
+
+        $this->_getNextToken();
+        return($result);
+    }
+
+
+    /**
+     * Removes whitepsace characters from the source input
+     */
+    protected function _eatWhitespace()
+    {
+        if (preg_match(
+                '/([\t\b\f\n\r ])*/s',
+                $this->_source,
+                $matches,
+                PREG_OFFSET_CAPTURE,
+                $this->_offset)
+            && $matches[0][1] == $this->_offset)
+        {
+            $this->_offset += strlen($matches[0][0]);
+        }
+    }
+
+
+    /**
+     * Retrieves the next token from the source stream
+     *
+     * @return int Token constant value specified in class definition
+     */
+    protected function _getNextToken()
+    {
+        $this->_token      = self::EOF;
+        $this->_tokenValue = null;
+        $this->_eatWhitespace();
+        
+        if ($this->_offset >= $this->_sourceLength) {
+            return(self::EOF);
+        }
+
+        $str        = $this->_source;
+        $str_length = $this->_sourceLength;
+        $i          = $this->_offset;
+        $start      = $i;
+        
+        switch ($str{$i}) {
+            case '{':
+               $this->_token = self::LBRACE;
+               break;
+            case '}':
+                $this->_token = self::RBRACE;
+                break;
+            case '[':
+                $this->_token = self::LBRACKET;
+                break;
+            case ']':
+                $this->_token = self::RBRACKET;
+                break;
+            case ',':
+                $this->_token = self::COMMA;
+                break;
+            case ':':
+                $this->_token = self::COLON;
+                break;
+            case  '"':
+                $result = '';
+                do {
+                    $i++;
+                    if ($i >= $str_length) {
+                        break;
+                    }
+
+                    $chr = $str{$i};
+                    if ($chr == '\\') {
+                        $i++;
+                        if ($i >= $str_length) {
+                            break;
+                        }
+                        $chr = $str{$i};
+                        switch ($chr) {
+                            case '"' :
+                                $result .= '"';
+                                break;
+                            case '\\':
+                                $result .= '\\';
+                                break;
+                            case '/' :
+                                $result .= '/';
+                                break;
+                            case 'b' :
+                                $result .= chr(8);
+                                break;
+                            case 'f' :
+                                $result .= chr(12);
+                                break;
+                            case 'n' :
+                                $result .= chr(10);
+                                break;
+                            case 'r' :
+                                $result .= chr(13);
+                                break;
+                            case 't' :
+                                $result .= chr(9);
+                                break;
+                            case '\'' :
+                                $result .= '\'';
+                                break;
+                            default:
+                                throw new Zend_Json_Exception("Illegal escape "
+                                    .  "sequence '" . $chr . "'");
+                            }
+                    } elseif ($chr == '"') {
+                        break;
+                    } else {
+                        $result .= $chr;
+                    }
+                } while ($i < $str_length);
+
+                $this->_token = self::DATUM;
+                //$this->_tokenValue = substr($str, $start + 1, $i - $start - 1);
+                $this->_tokenValue = $result;
+                break;
+            case  "'":
+                $result = '';
+                do {
+                    $i++;
+                    if ($i >= $str_length) {
+                        break;
+                    }
+
+                    $chr = $str{$i};
+                    if ($chr == '\\') {
+                        $i++;
+                        if ($i >= $str_length) {
+                            break;
+                        }
+                        $chr = $str{$i};
+                        switch ($chr) {
+                            case "'" :
+                                $result .= "'";
+                                break;
+                            case '\\':
+                                $result .= '\\';
+                                break;
+                            case '/' :
+                                $result .= '/';
+                                break;
+                            case 'b' :
+                                $result .= chr(8);
+                                break;
+                            case 'f' :
+                                $result .= chr(12);
+                                break;
+                            case 'n' :
+                                $result .= chr(10);
+                                break;
+                            case 'r' :
+                                $result .= chr(13);
+                                break;
+                            case 't' :
+                                $result .= chr(9);
+                                break;
+                            case '"' :
+                                $result .= '"';
+                                break;
+                            default:
+                                throw new Zend_Json_Exception("Illegal escape "
+                                    .  "sequence '" . $chr . "'");
+                            }
+                    } elseif ($chr == "'") {
+                        break;
+                    } else {
+                        $result .= $chr;
+                    }
+                } while ($i < $str_length);
+
+                $this->_token = self::DATUM;
+                //$this->_tokenValue = substr($str, $start + 1, $i - $start - 1);
+                $this->_tokenValue = $result;
+                break;
+            case 't':
+                if (($i+ 3) < $str_length && substr($str, $start, 4) == "true") {
+                    $this->_token = self::DATUM;
+                }
+                $this->_tokenValue = true;
+                $i += 3;
+                break;
+            case 'f':
+                if (($i+ 4) < $str_length && substr($str, $start, 5) == "false") {
+                    $this->_token = self::DATUM;
+                }
+                $this->_tokenValue = false;
+                $i += 4;
+                break;
+            case 'n':
+                if (($i+ 3) < $str_length && substr($str, $start, 4) == "null") {
+                    $this->_token = self::DATUM;
+                }
+                $this->_tokenValue = NULL;
+                $i += 3;
+                break;
+              case ' ':
+                break;
+        }
+
+        if ($this->_token != self::EOF) {
+            $this->_offset = $i + 1; // Consume the last token character
+            return($this->_token);
+        }
+
+        $chr = $str{$i};
+        if ($chr == '-' || $chr == '.' || ($chr >= '0' && $chr <= '9')) {
+            if (preg_match('/-?([0-9])*(\.[0-9]*)?((e|E)((-|\+)?)[0-9]+)?/s',
+                $str, $matches, PREG_OFFSET_CAPTURE, $start) && $matches[0][1] == $start) {
+
+                $datum = $matches[0][0];
+
+                if (is_numeric($datum)) {
+                    if (preg_match('/^0\d+$/', $datum)) {
+                        throw new Zend_Json_Exception("Octal notation not supported by JSON (value: $datum)");
+                    } else {
+                        $val  = intval($datum);
+                        $fVal = floatval($datum);
+                        $this->_tokenValue = ($val == $fVal ? $val : $fVal);
+                    }
+                } else {
+                    throw new Zend_Json_Exception("Illegal number format: $datum");
+                }
+
+                $this->_token = self::DATUM;
+                $this->_offset = $start + strlen($datum);
+            }
+        } else {
+            throw new Zend_Json_Exception("Illegal Token at pos $i: $chr\nContext:\n--------------------------------------------------" . substr($str, $i) . "\n--------------------------------------------------");
+        }
+
+        return($this->_token);
+    }
+}
+
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Json
+ * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+
+/**
+ * @category   Zend
+ * @package    Zend_Json
+ * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Json_Exception extends Zend_Exception
+{}
+
+/**
+ * @category   Zend
+ * @package    Zend
+ * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Exception extends Exception
+{}
+
+/**
+ * Class for encoding to and decoding from JSON.
+ *
+ * @category   Zend
+ * @package    Zend_Json
+ * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Json
+{
+    /**
+     * How objects should be encoded -- arrays or as StdClass. TYPE_ARRAY is 1
+     * so that it is a boolean true value, allowing it to be used with
+     * ext/json's functions.
+     */
+    const TYPE_ARRAY  = 1;
+    const TYPE_OBJECT = 0;
+
+    /**
+     * @var bool
+     */
+    public static $useBuiltinEncoderDecoder = true;
+
+    /**
+     * Decodes the given $encodedValue string which is
+     * encoded in the JSON format
+     *
+     * Uses ext/json's json_decode if available.
+     *
+     * @param string $encodedValue Encoded in JSON format
+     * @param int $objectDecodeType Optional; flag indicating how to decode
+     * objects. See {@link ZJsonDecoder::decode()} for details.
+     * @return mixed
+     */
+    public static function decode($encodedValue, $objectDecodeType = Zend_Json::TYPE_ARRAY)
+    {
+        if (function_exists('json_decode') && self::$useBuiltinEncoderDecoder !== true) {
+            return json_decode($encodedValue, $objectDecodeType);
+        }
+
+        return Zend_Json_Decoder::decode($encodedValue, $objectDecodeType);
+    }
+
+
+    /**
+     * Encode the mixed $valueToEncode into the JSON format
+     *
+     * Encodes using ext/json's json_encode() if available.
+     *
+     * NOTE: Object should not contain cycles; the JSON format
+     * does not allow object reference.
+     *
+     * NOTE: Only public variables will be encoded
+     *
+     * @param mixed $valueToEncode
+     * @param boolean $cycleCheck Optional; whether or not to check for object recursion; off by default
+     * @return string JSON encoded object
+     */
+    public static function encode($valueToEncode, $cycleCheck = false)
+    {
+        if (function_exists('json_encode') && self::$useBuiltinEncoderDecoder !== true) {
+            return json_encode($valueToEncode);
+        }
+
+        return Zend_Json_Encoder::encode($valueToEncode, $cycleCheck);
+    }
+}
+
+?>
--- a/includes/lang.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/lang.php	Wed Dec 26 00:37:26 2007 -0500
@@ -216,8 +216,7 @@
     }
     else
     {
-      $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-      $langdata = $json->decode($contents);
+      $langdata = enano_json_decode($contents);
     
       if ( !is_array($langdata) )
         $db->_die('lang.php - invalid language file');
@@ -299,11 +298,34 @@
     $contents = preg_replace('/^([^{]+)\{/', '{', $contents);
     $contents = preg_replace('/\}([^}]+)$/', '}', $contents);
     
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-    $langdata = $json->decode($contents);
+    // Correct syntax to be nice to the json parser
+    
+    // eliminate comments
+    $contents = preg_replace(array(
+            // eliminate single line comments in '// ...' form
+            '#^\s*//(.+)$#m',
+            // eliminate multi-line comments in '/* ... */' form, at start of string
+            '#^\s*/\*(.+)\*/#Us',
+            // eliminate multi-line comments in '/* ... */' form, at end of string
+            '#/\*(.+)\*/\s*$#Us'
+          ), '', $contents);
+    
+    $contents = preg_replace('/([,\{\[])([\s]*?)([a-z0-9_]+)([\s]*?):/', '\\1\\2"\\3" :', $contents);
+    
+    try
+    {
+      $langdata = enano_json_decode($contents);
+    }
+    catch(Zend_Json_Exception $e)
+    {
+      $db->_die('lang.php - Exception caught by JSON parser');
+      exit;
+    }
     
     if ( !is_array($langdata) )
-      $db->_die('lang.php - invalid language file');
+    {
+      $db->_die('lang.php - invalid or non-well-formed language file');
+    }
     
     if ( !isset($langdata['categories']) || !isset($langdata['strings']) )
       $db->_die('lang.php - language file does not contain the proper items');
--- a/includes/pageutils.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/pageutils.php	Wed Dec 26 00:37:26 2007 -0500
@@ -1608,10 +1608,9 @@
    
   function getstyles()
   {
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
     
     if ( !preg_match('/^([a-z0-9_-]+)$/', $_GET['id']) )
-      return $json->encode(false);
+      return enano_json_encode(false);
     
     $dir = './themes/' . $_GET['id'] . '/css/';
     $list = Array();
@@ -1631,10 +1630,10 @@
     }
     else
     {
-      return($json->encode(Array('mode' => 'error', 'error' => $dir.' is not a dir')));
+      return(enano_json_encode(Array('mode' => 'error', 'error' => $dir.' is not a dir')));
     }
     
-    return $json->encode($list);
+    return enano_json_encode($list);
   }
   
   /**
@@ -2188,10 +2187,9 @@
   function acl_json($parms = '{ }')
   {
     global $db, $session, $paths, $template, $plugins; // Common objects
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-    $parms = $json->decode($parms);
+    $parms = enano_json_decode($parms);
     $ret = PageUtils::acl_editor($parms);
-    $ret = $json->encode($ret);
+    $ret = enano_json_encode($ret);
     return $ret;
   }
   
--- a/includes/paths.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/paths.php	Wed Dec 26 00:37:26 2007 -0500
@@ -418,8 +418,16 @@
   
   function add_page($flags)
   {
+    global $lang;
     $flags['urlname_nons'] = $flags['urlname'];
     $flags['urlname'] = $this->nslist[$flags['namespace']] . $flags['urlname']; // Applies the User:/File:/etc prefixes to the URL names
+    
+    if ( is_object($lang) )
+    {
+      if ( preg_match('/^[a-z0-9]+_[a-z0-9_]+$/', $flags['name']) )
+        $flags['name'] = $lang->get($flags['name']);
+    }
+    
     $pages_len = sizeof($this->pages)/2;
     $this->pages[$pages_len] = $flags;
     $this->pages[$flags['urlname']] =& $this->pages[$pages_len];
--- a/includes/search.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/includes/search.php	Wed Dec 26 00:37:26 2007 -0500
@@ -112,10 +112,11 @@
  * @param string Search query
  * @param string Will be filled with any warnings encountered whilst parsing the query
  * @param bool Case sensitivity - defaults to false
+ * @param array|reference Will be filled with the parsed list of words.
  * @return array
  */
 
-function perform_search($query, &$warnings, $case_sensitive = false)
+function perform_search($query, &$warnings, $case_sensitive = false, &$word_list)
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
   $warnings = array();
@@ -522,7 +523,7 @@
     if ( isset($scores[$idstring]) )
     {
       $page_data[$idstring] = array(
-          'page_name' => $page['name'],
+          'page_name' => highlight_search_result($page['name'], $word_list, $case_sensitive),
           'page_text' => '',
           'page_id' => $page['urlname_nons'],
           'namespace' => $page['namespace'],
@@ -592,6 +593,11 @@
     // if ( $score > $divisor )
     //   $score = $divisor;
     $datum['score'] = round($score / $divisor, 2) * 100;
+    
+    // Highlight the URL
+    $datum['url_highlight'] = makeUrlComplete($datum['namespace'], $datum['page_id']);
+    $datum['url_highlight'] = preg_replace('/\?.+$/', '', $datum['url_highlight']);
+    $datum['url_highlight'] = highlight_search_result($datum['url_highlight'], $word_list, $case_sensitive);
 
     // Store it in our until-now-unused results array
     $results[] = $datum;
@@ -613,6 +619,8 @@
 
 function parse_search_query($query, &$warnings)
 {
+  global $lang;
+  
   $stopwords = get_stopwords();
   $ret = array(
     'any' => array(),
@@ -663,7 +671,7 @@
 
     if ( $ticker == 20 )
     {
-      $warnings[] = 'Some of your search terms were excluded because searches are limited to 20 terms to prevent excessive server load.';
+      $warnings[] = $lang->get('search_err_query_too_many_terms');
       break;
     }
 
@@ -672,13 +680,13 @@
       $word = substr ( $atom, 2, ( strlen( $atom ) - 3 ) );
       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
       {
-        $warnings[] = 'One or more of your search terms was excluded because either it was less than 2 characters in length or is a common word (a stopword) that is typically found on a large number of pages. Examples of stopwords include "the", "this", "which", "with", etc.';
+        $warnings[] = $lang->get('search_err_query_has_stopwords');
         $ticker--;
         continue;
       }
       if(in_array($word, $ret['req']))
       {
-        $warnings[] = 'One or more of your search terms was excluded because duplicate terms were encountered.';
+        $warnings[] = $lang->get('search_err_query_dup_terms');
         $ticker--;
         continue;
       }
@@ -689,13 +697,13 @@
       $word = substr ( $atom, 2, ( strlen( $atom ) - 3 ) );
       if ( strlen ( $word ) < 4 )
       {
-        $warnings[] = 'One or more of your search terms was excluded because terms must be at least 4 characters in length.';
+        $warnings[] = $lang->get('search_err_query_term_too_short');
         $ticker--;
         continue;
       }
       if(in_array($word, $ret['not']))
       {
-        $warnings[] = 'One or more of your search terms was excluded because duplicate terms were encountered.';
+        $warnings[] = $lang->get('search_err_query_dup_terms');
         $ticker--;
         continue;
       }
@@ -706,13 +714,13 @@
       $word = substr ( $atom, 1 );
       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
       {
-        $warnings[] = 'One or more of your search terms was excluded because either it was less than 2 characters in length or is a common word (a stopword) that is typically found on a large number of pages. Examples of stopwords include "the", "this", "which", "with", etc.';
+        $warnings[] = $lang->get('search_err_query_has_stopwords');
         $ticker--;
         continue;
       }
       if(in_array($word, $ret['req']))
       {
-        $warnings[] = 'One or more of your search terms was excluded because duplicate terms were encountered.';
+        $warnings[] = $lang->get('search_err_query_dup_terms');
         $ticker--;
         continue;
       }
@@ -723,13 +731,13 @@
       $word = substr ( $atom, 1 );
       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
       {
-        $warnings[] = 'One or more of your search terms was excluded because either it was less than 2 characters in length or is a common word (a stopword) that is typically found on a large number of pages. Examples of stopwords include "the", "this", "which", "with", etc.';
+        $warnings[] = $lang->get('search_err_query_has_stopwords');
         $ticker--;
         continue;
       }
       if(in_array($word, $ret['not']))
       {
-        $warnings[] = 'One or more of your search terms was excluded because duplicate terms were encountered.';
+        $warnings[] = $lang->get('search_err_query_dup_terms');
         $ticker--;
         continue;
       }
@@ -740,13 +748,13 @@
       $word = substr ( $atom, 1, ( strlen ( $atom ) - 2 ) );
       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
       {
-        $warnings[] = 'One or more of your search terms was excluded because either it was less than 2 characters in length or is a common word (a stopword) that is typically found on a large number of pages. Examples of stopwords include "the", "this", "which", "with", etc.';
+        $warnings[] = $lang->get('search_err_query_has_stopwords');
         $ticker--;
         continue;
       }
       if(in_array($word, $ret['any']))
       {
-        $warnings[] = 'One or more of your search terms was excluded because duplicate terms were encountered.';
+        $warnings[] = $lang->get('search_err_query_dup_terms');
         $ticker--;
         continue;
       }
@@ -757,13 +765,13 @@
       $word = $atom;
       if ( strlen ( $word ) < 2 || in_array($word, $stopwords) )
       {
-        $warnings[] = 'One or more of your search terms was excluded because either it was less than 2 characters in length or is a common word (a stopword) that is typically found on a large number of pages. Examples of stopwords include "the", "this", "which", "with", etc.';
+        $warnings[] = $lang->get('search_err_query_has_stopwords');
         $ticker--;
         continue;
       }
       if(in_array($word, $ret['any']))
       {
-        $warnings[] = 'One or more of your search terms was excluded because duplicate terms were encountered.';
+        $warnings[] = $lang->get('search_err_query_dup_terms');
         $ticker--;
         continue;
       }
--- a/index.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/index.php	Wed Dec 26 00:37:26 2007 -0500
@@ -20,10 +20,10 @@
  
   // Set up gzip encoding before any output is sent
   
-  $aggressive_optimize_html = false;
+  $aggressive_optimize_html = true;
   
   global $do_gzip;
-  $do_gzip = false;
+  $do_gzip = true;
   
   if(isset($_SERVER['PATH_INFO'])) $v = $_SERVER['PATH_INFO'];
   elseif(isset($_GET['title'])) $v = $_GET['title'];
--- a/install.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/install.php	Wed Dec 26 00:37:26 2007 -0500
@@ -1071,8 +1071,7 @@
     break;
   case 'langjs':
     header('Content-type: text/javascript');
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-    $lang_js = $json->encode($lang->strings);
+    $lang_js = enano_json_encode($lang->strings);
     // use EEOF here because jEdit misinterprets "typ'eof'"
     echo <<<EEOF
 if ( typeof(enano_lang) != 'object' )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/language/english/admin.json	Wed Dec 26 00:37:26 2007 -0500
@@ -0,0 +1,130 @@
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.1
+ * Copyright (C) 2006-2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute 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.
+ *
+ * This program 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 details.
+ */
+
+// This is the main language file for Enano. Feel free to use it as a base for your own translations.
+// All text in this file before the first left curly brace and all text after the last curly brace will
+// be trimmed. So you can use a limited amount of Javascript in this so that the language can be imported
+// via Javascript as well.
+
+var enano_lang = {
+  categories: [
+    'adm', 'acl', 'adminusers'
+  ],
+  strings: {
+    meta: {
+      adm: 'Administration panel nav menu',
+      acl: 'Access control list editor',
+      adminusers: 'ACP: User management'
+    },
+    adm: {
+      cat_general: 'General',
+      cat_content: 'Content',
+      cat_appearance: 'Appearance',
+      cat_users: 'Users',
+      cat_security: 'Security',
+      cat_plugins: 'Plugin configuration',
+      
+      page_general_config: 'General configuration',
+      page_file_uploads: 'File uploads',
+      page_file_types: 'Allowed file types',
+      page_plugins: 'Manage plugins',
+      page_db_backup: 'Backup database',
+      
+      page_manager: 'Manage pages',
+      page_editor: 'Edit page content',
+      page_pg_groups: 'Manage page groups',
+      
+      page_themes: 'Manage themes',
+      
+      page_users: 'Manage users',
+      page_user_groups: 'Edit user groups',
+      page_coppa: 'COPPA support',
+      page_mass_email: 'Mass e-mail',
+      
+      page_security_log: 'Security log',
+      page_ban_control: 'Ban control',
+      
+      btn_home: 'Administration panel home',
+      btn_logout: 'Log out of admin panel',
+      btn_keepalive_off: 'Turn on keep-alive',
+      btn_keepalive_on: 'Turn off keep-alive',
+      btn_keepalive_about: 'About keep-alive',
+      btn_keepalive_loading: 'Loading keep-alive button...',
+      
+      err_not_auth_title: 'Error: Not authenticated',
+      err_not_auth_body: 'It looks like your administration session is invalid or you are not authorized to access this administration page. Please <a href="%login_link%">re-authenticate</a> to continue.',
+    },
+    acl: {
+      err_access_denied: 'You are not authorized to view or edit access control lists.',
+      err_missing_template: 'It seems that (a) the file acledit.tpl is missing from this theme, and (b) the JSON response is working.',
+      err_user_not_found: 'The username you entered was not found.',
+      err_bad_group_id: 'The group ID you submitted is not valid.',
+      err_demo: 'Editing access control lists is disabled in the administration demo.',
+      err_zero_list: 'Supplied rule list has a length of zero',
+      err_pleaseselect_targettype: 'Please select a target type.',
+      err_pleaseselect_username: 'Please enter a username.',
+      
+      radio_usergroup: 'A usergroup',
+      radio_user: 'A specific user',
+      radio_scope_thispage: 'Only this page',
+      radio_scope_wholesite: 'The entire website',
+      radio_scope_pagegroup: 'A group of pages',
+      
+      lbl_scope: 'What should this access rule control?',
+      lbl_welcome_title: 'Manage page access',
+      lbl_welcome_body: 'Please select who should be affected by this access rule.',
+      lbl_editwin_title_create: 'Create access rule',
+      lbl_editwin_title_edit: 'Editing permissions',
+      lbl_editwin_body: 'This panel allows you to edit what the %target_type% "<b>%target%</b>" can do on <b>%scope_type%</b>. Unless you set a permission to "Deny", these permissions may be overridden by other rules.',
+      lbl_deleterule: 'Delete this rule',
+      lbl_save_success_title: 'Permissions updated',
+      lbl_save_success_body: 'The permissions for %target_name% on this page have been updated successfully. If you changed permissions that affect your user account, you may not see changes until you reload the page.',
+      lbl_delete_success_title: 'Rule deleted',
+      lbl_delete_success_body: 'The access rules for %target_name% on this page have been deleted.',
+      lbl_field_deny: 'Deny',
+      lbl_field_disallow: 'Disallow',
+      lbl_field_wikimode: 'Wiki mode',
+      lbl_field_allow: 'Allow',
+      lbl_help: '<p><b>Permission types:</b></p><ul><li><b>Allow</b> means that the user is allowed to access the item</li><li><b>Wiki mode</b> means the user can access the item if wiki mode is active (per-page wiki mode is taken into account)</li><li><b>Disallow</b> means the user is denied access unless something allows it.</li><li><b>Deny</b> means that the user is denied access to the item. This setting overrides all other permissions.</li></ul>',
+      
+      scope_type_wholesite: 'this entire site',
+      scope_type_thispage: 'this page',
+      scope_type_pagegroup: 'this group of pages',
+      
+      target_type_user: 'user',
+      target_type_group: 'group',
+      
+      msg_guest_howto: 'To edit permissions for guests, select "a specific user", and enter Anonymous as the username.',
+      msg_deleterule_confirm: 'Do you really want to delete this rule?',
+      msg_closeacl_confirm: 'Do you really want to close the ACL manager?',
+      
+      btn_success_dismiss: 'dismiss',
+      btn_success_close: 'close manager',
+      btn_deleterule: 'Delete rule',
+      btn_createrule: 'Create rule',
+      btn_returnto_editor: 'Return to ACL editor',
+      btn_returnto_userscope: 'Return to user/scope selection',
+    },
+    adminusers: {
+      avatar_heading: 'Avatar settings',
+      avatar_image_none: 'This user does not currently have an avatar.',
+      avatar_lbl_change: 'Change avatar:',
+      avatar_lbl_keep: 'Keep current setting',
+      avatar_lbl_remove: 'Delete this user\'s avatar',
+      avatar_lbl_set_http: 'Replace avatar using a new image from a URL',
+      avatar_lbl_set_file: 'Replace avatar using a new image from my computer',
+    }
+  }
+};
+
+// All done! :-)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/language/english/core.json	Wed Dec 26 00:37:26 2007 -0500
@@ -0,0 +1,411 @@
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.1
+ * Copyright (C) 2006-2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute 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.
+ *
+ * This program 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 details.
+ */
+
+// This is the main language file for Enano. Feel free to use it as a base for your own translations.
+// All text in this file before the first left curly brace and all text after the last curly brace will
+// be trimmed. So you can use a limited amount of Javascript in this so that the language can be imported
+// via Javascript as well.
+
+var enano_lang = {
+  categories: [
+    'page', 'comment', 'onpage', 'etc', 'editor', 'history', 'catedit', 'tags', 'delvote', 'ajax', 'sidebar', 'perm'
+  ],
+  strings: {
+    meta: {
+      page: 'Page creation and control',
+      comment: 'Comment display',
+      onpage: 'On-page buttons and controls',
+      etc: 'Miscellaneous strings',
+      editor: 'Page editor interface',
+      history: 'Page history and log viewer',
+      catedit: 'Categorization box and editor',
+      tags: 'Page tagging interface',
+      delvote: 'Page deletion vote interface',
+      ajax: 'On-page AJAX applets',
+      sidebar: 'Default sidebar blocks and buttons',
+      perm: 'Page actions (for ACLs)',
+      plural: 's',
+      enano_about_poweredby: '<p>This website is powered by <a href="http://enanocms.org/">Enano</a>, the lightweight and open source CMS that everyone can use. Enano is copyright &copy; 2006-2007 Dan Fuhry. For legal information, along with a list of libraries that Enano uses, please see <a href="http://enanocms.org/Legal_information">Legal Information</a>.</p><p>The developers and maintainers of Enano strongly believe that software should not only be free to use, but free to be modified, distributed, and used to create derivative works. For more information about Free Software, check out the <a href="http://en.wikipedia.org/wiki/Free_Software" onclick="window.open(this.href); return false;">Wikipedia page</a> or the <a href="http://www.fsf.org/" onclick="window.open(this.href); return false;">Free Software Foundation\'s</a> homepage.</p>',
+      enano_about_gpl: '<p>This program 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.</p><p>This program 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 details.</p><p>You should have received <a href="%gpl_link%">a copy of the GNU General Public License</a> along with this program; if not, write to:</p><p style="margin-left 2em;">Free Software Foundation, Inc.,<br />51 Franklin Street, Fifth Floor<br />Boston, MA 02110-1301, USA</p><p>Alternatively, you can <a href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">read it online</a>.</p>',
+      enano_about_lbl_enanoversion: '<a href="http://enanocms.org">Enano</a> version:',
+      enano_about_lbl_webserver: 'Web server:',
+      enano_about_lbl_serverplatform: 'Server platform:',
+      enano_about_lbl_phpversion: '<a href="http://www.php.net/">PHP</a> version:',
+      enano_about_lbl_mysqlversion: '<a href="http://www.mysql.com/">MySQL</a> version:',
+    },
+    page: {
+      protect_lbl_success_title: 'Page protected',
+      protect_lbl_success_body: 'The protection setting has been applied. <a href="%page_link%">Return to the page</a>.',
+      protect_err_need_reason: 'Error: you must enter a reason for protecting this page.',
+      protect_lbl_reason: 'Reason for protecting the page:',
+      protect_lbl_level: 'Protecion level to be applied:',
+      protect_lbl_level_none: 'No protection',
+      protect_lbl_level_semi: 'Semi-protection',
+      protect_lbl_level_full: 'Full protection',
+      protect_btn_submit: 'Protect page',
+      
+      rename_err_need_name: 'Error: you must enter a new name for this page.',
+      rename_lbl: 'Please enter a new name for this page:',
+      rename_btn_submit: 'Rename page',
+      rename_success_title: 'Page renamed',
+      
+      flushlogs_warning_stern: '<h3>You are about to <span style="color: red;">destroy</span> all logged edits and actions on this page.</h3><p>Unlike deleting or editing this page, this action is <u>not reversible</u>! You should only do this if you are desparate for database space.</p><p>Do you really want to continue?</p>',
+      flushlogs_btn_submit: 'Flush logs',
+      
+      delvote_warning_stern: '<h3>Your vote counts.</h3><p>If you think that this page is not relavent to the content on this site, or if it looks like this page was only created in an attempt to spam the site, you can request that this page be deleted by an administrator.</p><p>After you vote, you should leave a comment explaining the reason for your vote, especially if you are the first person to vote against this page.</p>',
+      
+      delvote_count_zero: 'So far, no one has voted for the deletion of this page.',
+      delvote_count_one: 'So far, one person has voted to delete this page.',
+      delvote_count_plural: 'So far, %delvotes% people have voted to delete this page.',
+      delvote_btn_submit: 'Vote to delete this page',
+      delvote_reset_btn_submit: 'Reset votes',
+      
+      delete_warning_stern: '<h3>You are about to <span style="color: red;">destroy</span> this page.</h3><p>While the deletion of the page itself is completely reversible, it is impossible to recover any comments or category information on this page. If this is a file page, the file along with all older revisions of it will be permanently deleted. Also, any custom information that this page is tagged with, such as a custom name, protection status, or additional settings such as whether to allow comments, will be permanently lost.</p><p>Are you <u>absolutely sure</u> that you want to continue?<br />You will not be asked again.</p>',
+      delete_btn_submit: 'Delete this page',
+      delete_lbl_reason: 'Reason for deleting:',
+      
+      wikimode_success_redirect: 'Wiki mode for this page has been set. Redirecting you to the page...',
+      wikimode_level_on: 'Wiki features will be enabled.',
+      wikimode_level_off: 'Wiki features will be disabled.',
+      wikimode_level_global: 'Wiki features will be synchronized to the global setting.',
+      wikimode_heading: 'You are changing wiki mode for this page.',
+      wikimode_warning: 'If you want to continue, please click the button below.',
+      wikimode_blurb_disable: 'Because this will disable the wiki behavior on this page, several features, most notably the ability for users to vote to have this page deleted, will be disabled as they are not relevant to non-wiki pages. In addition, users will not be able to edit this page unless an ACL rule specifically permits them.',
+      wikimode_blurb_enable: 'Because this will enable the wiki behavior on this page, users will gain the ability to freely edit this page unless an ACL rule specifically denies them. If your site is public and gets good traffic, you should be aware of the possiblity of vandalism, and you need to be ready to revert malicious edits to this page.',
+      wikimode_btn_submit: 'Set wiki mode',
+      
+      detag_err_page_exists: 'The detag action is only valid for pages that have been deleted in the past.',
+      detag_success_title: 'Page detagged',
+      detag_success_body: 'All stale tags have been removed from this page.',
+      
+      err_custompage_function_missing_title: 'Page backend not found',
+      err_custompage_function_missing_body: 'The administration page you are looking for was properly registered using the page API, but the backend function (<tt>%function_name%</tt>) was not found. If this is a plugin page, then this is almost certainly a bug with the plugin.',
+      err_redirects_exceeded: 'The maximum number of internal redirects has been exceeded.',
+      err_redirect_to_nonexistent: 'This page redirects to another page that doesn\'t exist.',
+      err_redirect_infinite_loop: 'This page infinitely redirects with another page (or another series of pages), and the infinite redirect was trapped.',
+      err_redirect_to_special: 'This page redirects to a Special or Administration page, which is not allowed.',
+      err_access_denied_title: 'You don\'t have permission to view this page.',
+      err_access_denied_body: '<p>Your user account doesn\'t have the necessary permission to view this page. There are a number of possible reasons for this:</p>
+                               <ul>
+                                 <li>You aren\'t logged in. Some pages are restricted to logged-in users.</li>
+                                 <li>The page you\'re trying to view is protected so that only members of a specific usergroup are allowed to read it.</li>
+                               </ul>
+                               <p>If you would like to inquire further about this message, you may contact the %site_administration%.</p>',
+      err_access_denied_siteadmin: 'site administrator',
+      msg_this_is_a_redirector: '<b>This page is a <i>redirector</i>.</b><br />
+                    This means that this page will not show its own content by default. Instead it will display the contents of the page it redirects to.<br /><br />
+                    To create a redirect page, make the <i>first characters</i> in the page content <tt>#redirect [[Page_ID]]</tt>. For more information, see the
+                    Enano <a href="http://enanocms.org/Help:Wiki_formatting" onclick="window.open(this.href); return false;">Wiki formatting guide</a>.<br /><br />
+                    This page redirects to %redirect_target%.'
+    },
+    comment: {
+      lbl_subject: 'Subject',
+      lbl_mod_options: 'Moderator options:',
+      heading: 'Article comments',
+      btn_send_privmsg: 'Send private message',
+      btn_add_buddy: 'Add to buddy list',
+      btn_edit: 'edit',
+      btn_delete: 'delete',
+      btn_mod_approve: 'Approve',
+      btn_mod_unapprove: 'Unapprove',
+      btn_mod_delete: 'Delete',
+      btn_save: 'save',
+      
+      msg_comment_posted: 'Your comment has been posted. If it does not appear right away, it is probably awaiting approval.',
+      
+      msg_count_zero: 'There are <span id="comment_count_inner">no</span> comments on this %page_type%.',
+      msg_count_one: 'There is <span id="comment_count_inner">1</span> comment on this %page_type%.',
+      msg_count_plural: 'There are <span id="comment_count_inner">%num_comments%</span> comments on this %page_type%.',
+      
+      msg_count_unapp_mod: '<span id="comment_count_unapp_inner">%num_unapp%</span> of those are unapproved.',
+      msg_count_unapp_one: 'However, there is <span id="comment_count_unapp_inner">1</span> additional comment awaiting approval.',
+      msg_count_unapp_plural: 'However, there are <span id="comment_count_unapp_inner">%num_unapp%</span> additional comments awaiting approval.',
+      
+      msg_note_unapp: '(Unapproved)',
+      
+      msg_delete_confirm: 'Do you really want to delete this comment?',
+      
+      postform_title: 'Got something to say?',
+      postform_blurb: 'If you have comments or suggestions on this article, you can shout it out here.',
+      postform_blurb_unapp: 'Before your post will be visible to the public, a moderator will have to approve it.',
+      postform_blurb_captcha: 'Because you are not logged in, you will need to enter a visual confirmation before your comment will be posted.',
+      postform_blurb_link: 'Leave a comment...',
+      postform_field_name: 'Your name/screen name:',
+      postform_field_subject: 'Comment subject:',
+      postform_field_comment: 'Comment:',
+      postform_field_captcha_title: 'Visual confirmation:',
+      postform_field_captcha_blurb: 'Please enter the confirmation code seen in the image on the right into the box. If you cannot read the code, please click on the image to generate a new one. This helps to prevent automated bot posting.',
+      postform_field_captcha_label: 'Confirmation code:',
+      postform_field_captcha_cantread_js: 'If you can\'t read the code, click on the image to generate a new one.',
+      postform_field_captcha_cantread_nojs: 'If you can\'t read the code, please refresh this page to generate a new one.',
+      postform_btn_submit: 'Submit comment',
+      
+      on_friend_list: 'On your friend list',
+      on_foe_list: 'On your foe list',
+    },
+    onpage: {
+      lbl_pagetools: 'Page tools',
+      lbl_page_article: 'article',
+      lbl_page_admin: 'administration page',
+      lbl_page_system: 'system message',
+      lbl_page_file: 'uploaded file',
+      lbl_page_help: 'documentation page',
+      lbl_page_user: 'user page',
+      lbl_page_special: 'special page',
+      lbl_page_template: 'template',
+      lbl_page_project: 'project page',
+      lbl_page_category: 'category',
+      
+      btn_discussion: 'discussion (%num_comments%)',
+      btn_discussion_unapp: 'discussion (%num_comments% total, %num_unapp% unapp.)',
+      btn_edit: 'edit this page',
+      btn_viewsource: 'view source',
+      btn_history: 'history',
+      btn_moreoptions: 'more options',
+      
+      btn_rename: 'rename',
+      btn_printable: 'view printable version',
+      btn_votedelete: 'vote to delete this page',
+      btn_votedelete_reset: 'reset deletion votes',
+      lbl_wikimode: 'page wiki mode:',
+      btn_wikimode_on: 'on',
+      btn_wikimode_off: 'off',
+      btn_wikimode_global: 'global',
+      lbl_protect: 'protection:',
+      btn_protect_on: 'on',
+      btn_protect_off: 'off',
+      btn_protect_semi: 'semi',
+      btn_clearlogs: 'clear page logs',
+      btn_deletepage: 'delete this page',
+      btn_deletepage_votes: ' (<b>%num_votes%</b> vote%plural%)',
+      lbl_password: 'page password:',
+      btn_password_set: 'set',
+      btn_acl: 'manage page access',
+      btn_admin: 'administrative options',
+      
+      tip_article: 'View the page contents, all of the page contents, and nothing but the page contents (alt-a)',
+      tip_comments: 'View the comments that other users have posted about this page (alt-c)',
+      tip_edit: 'Edit the contents of this page (alt-e)',
+      tip_viewsource: 'View the source code (wiki markup) that this page uses (alt-e)',
+      tip_history: 'View a log of actions taken on this page (alt-h)',
+      tip_rename: 'Change the display name of this page (alt-r)',
+      tip_delvote: 'Vote to have this page deleted (alt-d)',
+      tip_resetvotes: 'Clear the list of votes for deletion against this page (alt-y)',
+      tip_printable: 'View a version of this page that is suitable for printing',
+      tip_protect_on: 'Prevents all non-administrators from editing this page. [alt-i]',
+      tip_protect_off: 'Allows everyone to edit this page. [alt-o]',
+      tip_protect_semi: 'Allows only users who have been registered for 4 days to edit this page. [alt-p]',
+      tip_flushlogs: 'Remove all edit and action logs for this page from the database. IRREVERSIBLE! (alt-l)',
+      tip_deletepage: 'Delete this page. This is always reversible unless the logs are cleared. (alt-k)',
+      tip_adminoptions: 'Administrative options for this page',
+      tip_moreoptions: 'Additional options for working with this page',
+      tip_password: 'Require a password in order for this page to be viewed',
+      tip_aclmanager: 'Manage who can do what with this page (alt-m)',
+    },
+    editor: {
+      msg_revert_confirm: 'Do you really want to revert your changes?',
+      msg_discard_confirm: 'Do you really want to discard your changes?',
+      msg_unload: 'If you do, any changes that you have made to this page will be lost.',
+      btn_graphical: 'graphical editor',
+      btn_wikitext: 'wikitext editor',
+      lbl_edit_summary: 'Edit summary:',
+      lbl_minor_edit: 'This is a minor edit',
+      btn_save: 'Save changes',
+      btn_preview: 'Show preview',
+      btn_revert: 'Revert changes',
+      btn_cancel: 'Cancel and return to page',
+      btn_closeviewer: 'Close viewer',
+      preview_blurb: '<b>Reminder:</b> This is only a preview - your changes to this page have not yet been saved.',
+      msg_save_success_title: 'Changes saved',
+      msg_save_success_body: 'Your changes to this page have been saved. Redirecting...',
+    },
+    history: {
+      summary_clearlogs: 'Automatic backup created when logs were purged',
+      page_subtitle: 'History of edits and actions',
+      heading_edits: 'Edits:',
+      heading_other: 'Other changes:',
+      no_entries: 'No history entries in this category.',
+      btn_compare: 'Compare selected revisions',
+      col_diff: 'Diff',
+      col_datetime: 'Date/time',
+      col_user: 'User',
+      col_summary: 'Edit summary',
+      col_minor: 'Minor',
+      col_actions: 'Actions',
+      col_action_taken: 'Action taken',
+      col_extra: 'Extra info',
+      extra_reason: 'Reason:',
+      extra_oldtitle: 'Old title:',
+      tip_rdns: 'Click cell background for reverse DNS info',
+      action_view: 'View',
+      action_contrib: 'User contribs',
+      action_restore: 'Restore',
+      action_revert: 'Revert action',
+      log_protect: 'Protected page',
+      log_unprotect: 'Unprotected page',
+      log_semiprotect: 'Semi-protected page',
+      log_rename: 'Renamed page',
+      log_create: 'Created page',
+      log_delete: 'Deleted page',
+      log_uploadnew: 'Uploaded new file version',
+      lbl_comparingrevisions: 'Comparing revisions:',
+    },
+    catedit: {
+      title: 'Select which categories this page should be included in.',
+      no_categories: 'There are no categories on this site yet.',
+      catbox_lbl_categories: 'Categories:',
+      catbox_lbl_uncategorized: '(Uncategorized)',
+      catbox_link_edit: 'edit categorization',
+      catbox_link_showcategorization: 'show page categorization',
+    },
+    tags: {
+      catbox_link: 'show page tags',
+      lbl_page_tags: 'Page tags:',
+      lbl_no_tags: 'No tags on this page',
+      btn_add_tag: '(add a tag)',
+      lbl_add_tag: 'Add a tag:',
+      btn_add: '+ Add',
+    },
+    delvote: {
+      lbl_votes_one: 'There is one user that thinks this page should be deleted.',
+      lbl_votes_plural: 'There are %num_users% users that think this page should be deleted.',
+      lbl_users_that_voted: 'Users that voted:',
+      btn_deletepage: 'Delete page',
+      btn_resetvotes: 'Reset votes',
+    },
+    ajax: {
+      // Client-side messages
+      protect_prompt_reason: 'Reason for (un)protecting:',
+      rename_prompt: 'What title should this page be renamed to?\nNote: This does not and will never change the URL of this page, that must be done from the admin panel.',
+      delete_prompt_reason: 'Please enter your reason for deleting this page.',
+      delete_confirm: 'You are about to REVERSIBLY delete this page. Do you REALLY want to do this?\n\n(Comments and categorization data, as well as any attached files, will be permanently lost)',
+      delvote_confirm: 'Are you sure that you want to vote that this page be deleted?',
+      delvote_reset_confirm: 'This action will reset the number of votes against this page to zero. Do you really want to do this?',
+      clearlogs_confirm: 'You are about to DESTROY all log entries for this page. As opposed to (example) deleting this page, this action is completely IRREVERSIBLE and should not be used except in dire circumstances. Do you REALLY want to do this?',
+      clearlogs_confirm_nag: 'You\'re ABSOLUTELY sure???',
+      changestyle_select: '[Select]',
+      changestyle_title: 'Change your theme',
+      changestyle_pleaseselect_theme: 'Please select a theme from the list.',
+      changestyle_lbl_theme: 'Theme:',
+      changestyle_lbl_style: 'Style:',
+      changestyle_success: 'Your theme preference has been changed.\nWould you like to reload the page now to see the changes?',
+      killphp_confirm: 'Are you really sure you want to do this? Some pages might not function if this emergency-only feature is activated.',
+      killphp_success: 'Embedded PHP in pages has been disabled.',
+      lbl_moreoptions_nojs: 'More options for this page',
+      
+      // Server-side responses
+      rename_too_short: 'The name you entered is too short. Please enter a longer name for this page.',
+      rename_success: 'The page "%page_name_old%" has been renamed to "%page_name_new%". You are encouraged to leave a comment explaining your action.\n\nYou will see the change take effect the next time you reload this page.',
+      clearlogs_success: 'The logs for this page have been cleared. A backup of this page has been added to the logs table so that this page can be restored in case of vandalism or spam later.',
+      delete_need_reason: 'Invalid reason for deletion passed. Please enter a reason for deleting this page.',
+      delete_success: 'This page has been deleted. Note that there is still a log of edits and actions in the database, and anyone with admin rights can raise this page from the dead unless the log is cleared. If the deleted file is an image, there may still be cached thumbnails of it in the cache/ directory, which is inaccessible to users.',
+      delvote_success: 'Your vote to have this page deleted has been cast.\nYou are encouraged to leave a comment explaining the reason for your vote.',
+      delvote_already_voted: 'It appears that you have already voted to have this page deleted.',
+      delvote_reset_success: 'The number of votes for having this page deleted has been reset to zero.',
+      password_success: 'The password for this page has been set.',
+      password_disable_success: 'The password for this page has been disabled.',
+      
+    },
+    sidebar: {
+      title_navigation: 'Navigation',
+      title_tools: 'Tools',
+      title_search: 'Search',
+      title_links: 'Links',
+      
+      btn_home: 'Home',
+      btn_createpage: 'Create a page',
+      btn_uploadfile: 'Upload file',
+      btn_specialpages: 'Special pages',
+      btn_administration: 'Administration',
+      btn_editsidebar: 'Edit the sidebar',
+      btn_search_go: 'Go',
+      
+      btn_userpage: 'User page',
+      btn_mycontribs: 'My contributions',
+      btn_preferences: 'Preferences',
+      btn_privatemessages: 'Private messages',
+      btn_groupcp: 'Group control panel',
+      btn_register: 'Create an account',
+      btn_login: 'Log in',
+      btn_logout: 'Log out',
+      btn_changestyle: 'Change theme',
+    },
+    perm: {
+      read: 'Read page(s)',
+      post_comments: 'Post comments',
+      edit_comments: 'Edit own comments',
+      edit_page: 'Edit page',
+      view_source: 'View source',
+      mod_comments: 'Moderate comments',
+      history_view: 'View history/diffs',
+      history_rollback: 'Rollback history',
+      history_rollback_extra: 'Undelete page(s)',
+      protect: 'Protect page(s)',
+      rename: 'Rename page(s)',
+      clear_logs: 'Clear page logs (dangerous)',
+      vote_delete: 'Vote to delete',
+      vote_reset: 'Reset delete votes',
+      delete_page: 'Delete page(s)',
+      tag_create: 'Tag page(s)',
+      tag_delete_own: 'Remove own page tags',
+      tag_delete_other: 'Remove others\' page tags',
+      set_wiki_mode: 'Set per-page wiki mode',
+      password_set: 'Set password',
+      password_reset: 'Disable/reset password',
+      mod_misc: 'Super moderator (generate SQL backtraces, view IP addresses, and send large numbers of private messages)',
+      edit_cat: 'Edit categorization',
+      even_when_protected: 'Allow editing, renaming, and categorization even when protected',
+      upload_files: 'Upload files',
+      upload_new_version: 'Upload new versions of files',
+      create_page: 'Create pages',
+      php_in_pages: 'Embed PHP code in pages',
+      edit_acl: 'Edit access control lists',
+    },
+    etc: {
+      redirect_title: 'Redirecting...',
+      redirect_body: 'Please wait while you are redirected.',
+      redirect_timeout: 'If you are not redirected within %timeout% seconds, please <a href="%redirect_url%">click here</a>.',
+      // Generic "Save Changes" button
+      save_changes: 'Save changes',
+      // Generic "Cancel changes" button
+      cancel_changes: 'Cancel changes',
+      // Generic wizard buttons
+      wizard_next: 'Next >',
+      wizard_back: '< Back',
+      wizard_previous: '< Previous',
+      // Generic "Notice:" label
+      lbl_notice: 'Notice:',
+      // Generic "Access denied"
+      access_denied: 'Access to the specified file, resource, or action is denied.',
+      access_denied_short: 'Access denied',
+      return_to_page: 'Return to the page',
+      invalid_request_short: 'Invalid request',
+      // Message box buttons
+      ok: 'OK',
+      cancel: 'Cancel',
+      yes: 'Yes',
+      no: 'No',
+      unit_bytes: 'bytes',
+      unit_kilobytes: 'kilobytes',
+      unit_megabytes: 'megabytes',
+      unit_gigabytes: 'gigabytes',
+      unit_terabytes: 'terabytes',
+      unit_kilobytes_short: 'KB',
+      unit_megabytes_short: 'MB',
+      unit_gigabytes_short: 'GB',
+      unit_terabytes_short: 'TB',
+    }
+  }
+};
+
+// All done! :-)
+
--- a/language/english/enano.json	Fri Dec 21 19:08:27 2007 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,639 +0,0 @@
-/*
- * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
- * Version 1.1.1
- * Copyright (C) 2006-2007 Dan Fuhry
- *
- * This program is Free Software; you can redistribute 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.
- *
- * This program 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 details.
- */
-
-// This is the main language file for Enano. Feel free to use it as a base for your own translations.
-// All text in this file before the first left curly brace and all text after the last curly brace will
-// be trimmed. So you can use a limited amount of Javascript in this so that the language can be imported
-// via Javascript as well.
-
-var enano_lang = {
-  categories: [
-    'adm', 'meta', 'user', 'page', 'comment', 'onpage', 'etc', 'editor', 'history', 'catedit', 'tags', 'delvote', 'ajax', 'sidebar', 'acl',
-    'perm', 'usercp', 'adminusers',
-  ],
-  strings: {
-    meta: {
-      adm: 'Administration panel nav menu',
-      meta: 'Language category strings',
-      user: 'Login, logout, and authentication',
-      page: 'Page creation and control',
-      comment: 'Comment display',
-      onpage: 'On-page buttons and controls',
-      etc: 'Miscellaneous strings',
-      editor: 'Page editor interface',
-      history: 'Page history and log viewer',
-      catedit: 'Categorization box and editor',
-      tags: 'Page tagging interface',
-      delvote: 'Page deletion vote interface',
-      ajax: 'On-page AJAX applets',
-      sidebar: 'Default sidebar blocks and buttons',
-      acl: 'Access control list editor',
-      perm: 'Page actions (for ACLs)',
-      usercp: 'User control panel',
-      adminusers: 'ACP: User management',
-      plural: 's',
-      enano_about_poweredby: '<p>This website is powered by <a href="http://enanocms.org/">Enano</a>, the lightweight and open source CMS that everyone can use. Enano is copyright &copy; 2006-2007 Dan Fuhry. For legal information, along with a list of libraries that Enano uses, please see <a href="http://enanocms.org/Legal_information">Legal Information</a>.</p><p>The developers and maintainers of Enano strongly believe that software should not only be free to use, but free to be modified, distributed, and used to create derivative works. For more information about Free Software, check out the <a href="http://en.wikipedia.org/wiki/Free_Software" onclick="window.open(this.href); return false;">Wikipedia page</a> or the <a href="http://www.fsf.org/" onclick="window.open(this.href); return false;">Free Software Foundation\'s</a> homepage.</p>',
-      enano_about_gpl: '<p>This program 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.</p><p>This program 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 details.</p><p>You should have received <a href="%gpl_link%">a copy of the GNU General Public License</a> along with this program; if not, write to:</p><p style="margin-left 2em;">Free Software Foundation, Inc.,<br />51 Franklin Street, Fifth Floor<br />Boston, MA 02110-1301, USA</p><p>Alternatively, you can <a href="http://www.gnu.org/licenses/old-licenses/gpl-2.0.html">read it online</a>.</p>',
-      enano_about_lbl_enanoversion: '<a href="http://enanocms.org">Enano</a> version:',
-      enano_about_lbl_webserver: 'Web server:',
-      enano_about_lbl_serverplatform: 'Server platform:',
-      enano_about_lbl_phpversion: '<a href="http://www.php.net/">PHP</a> version:',
-      enano_about_lbl_mysqlversion: '<a href="http://www.mysql.com/">MySQL</a> version:',
-    },
-    user: {
-      login_message_short: 'Please enter your username and password to log in.',
-      login_message_short_elev: 'Please re-enter your login details',
-      login_body: 'Logging in enables you to use your preferences and access member information. If you don\'t have a username and password here, you can <a href="%reg_link%">create an account</a>.',
-      login_body_elev: 'You are requesting that a sensitive operation be performed. To continue, please re-enter your password to confirm your identity.',
-      login_field_username: 'Username',
-      login_field_password: 'Password',
-      login_forgotpass_blurb: 'Forgot your password? <a href="%forgotpass_link%">No problem.</a>',
-      login_createaccount_blurb: 'Maybe you need to <a href="%reg_link%">create an account</a>.',
-      login_field_captcha: 'Code in image',
-      login_nocrypt_title: 'Important note regarding cryptography:',
-      login_nocrypt_body: 'Some countries do not allow the import or use of cryptographic technology. If you live in one of the countries listed below, you should <a href="%nocrypt_link%">log in without using encryption</a>.',
-      login_nocrypt_countrylist: 'This restriction applies to the following countries: Belarus, China, India, Israel, Kazakhstan, Mongolia, Pakistan, Russia, Saudi Arabia, Singapore, Tunisia, Venezuela, and Vietnam.',
-      login_usecrypt_title: 'Encryption is currently turned off.',
-      login_usecrypt_body: 'If you are not in one of the countries listed below, you should <a href="%usecrypt_link%">enable encryption</a> to secure the logon process.',
-      login_usecrypt_countrylist: 'The cryptography restriction applies to the following countries: Belarus, China, India, Israel, Kazakhstan, Mongolia, Pakistan, Russia, Saudi Arabia, Singapore, Tunisia, Venezuela, and Vietnam.',
-      login_success_title: 'Login successful',
-      login_success_body: 'You have successfully logged into the %config.site_name% site as "%username%". Redirecting to %redir_target%...',
-      login_success_body_mainpage: 'the main page',
-      login_success_short: 'Success.',
-      
-      login_ajax_fetching_key: 'Fetching an encryption key...',
-      login_ajax_prompt_title: 'Please enter your username and password to continue.',
-      login_ajax_prompt_title_elev: 'You are requesting a sensitive operation.',
-      login_ajax_prompt_body_elev: 'Please re-enter your login details, to verify your identity.',
-      login_ajax_link_fullform: 'Trouble logging in? Try the <a href="%link_full_form%">full login form</a>.',
-      login_ajax_link_forgotpass: 'Did you <a href="%forgotpass_link%">forget your password</a>?',
-      login_ajax_loggingin: 'Logging in...',
-      
-      err_key_not_found: 'Enano couldn\'t look up the encryption key used to encrypt your password. This most often happens if a cache rotation occurred during your login attempt, or if you refreshed the login page.',
-      err_key_not_found_cleared: 'It seems that the list of encryption keys used for login information has reached its maximum length, thus preventing new keys from being inserted. The list has been automatically cleared. Please try logging in again; if you are still unable to log in, please contact the site administration.',
-      err_key_wrong_length: 'The encryption key was the wrong length.',
-      err_too_big_for_britches: 'You are trying to authenticate at a level that your user account does not permit.',
-      err_invalid_credentials: 'You have entered an invalid username or password. Please enter your login details again.',
-      err_invalid_credentials_lockout: ' You have used up %fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will be locked out from logging in for %config.lockout_duration% minutes.',
-      err_invalid_credentials_lockout_captcha: ' You have used up %lockout_fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will have to enter a visual confirmation code while logging in, effective for %config.lockout_duration% minutes.',
-      err_backend_fail: 'You entered the right credentials and everything was validated, but for some reason Enano couldn\'t register your session. This is an internal problem with the site and you are encouraged to contact site administration.',
-      err_locked_out: 'You have used up all %config.lockout_threshold% allowed login attempts. Please wait %time_rem% minute%plural% before attempting to log in again%captcha_blurb%.',
-      err_locked_out_captcha_blurb: ', or enter the visual confirmation code shown above in the appropriate box',
-      
-      logout_success_title: 'Logged out',
-      logout_success_body: 'You have been successfully logged out, and all cookies have been cleared. You will now be transferred to the main page.',
-      logout_confirm_title: 'Are you sure you want to log out?',
-      logout_confirm_body: 'If you log out, you will no longer be able to access your user preferences, your private messages, or certain areas of this site until you log in again.',
-      logout_confirm_title_elev: 'Are you sure you want to de-authenticate?',
-      logout_confirm_body_elev: 'If you de-authenticate, you will no longer be able to use the administration panel until you re-authenticate again. You may do so at any time using the Administration button on the sidebar.',
-      logout_err_title: 'An error occurred during the logout process.',
-      // Unused at this point
-      logout_err_not_loggedin: 'You don\'t seem to be logged in.',
-      
-      keepalive_info_title: 'About the keep-alive feature',
-      keepalive_info_body: 'Keep-alive is a new Enano feature that keeps your administrative session from timing out while you are using the administration panel. This feature can be useful if you are editing a large page or doing something in the administration interface that will take longer than 15 minutes.<br /><br />For security reasons, Enano mandates that high-privilege logins last only 15 minutes, with the time being reset each time a page is loaded (or, more specifically, each time the session API is started). The consequence of this is that if you are performing an action in the administration panel that takes more than 15 minutes, your session may be terminated. The keep-alive feature attempts to relieve this by sending a "ping" to the server every 10 minutes.<br /><br />Please note that keep-alive state is determined by a cookie. Thus, if you log out and then back in as a different administrator, keep-alive will use the same setting that was used when you were logged in as the first administrative user. In the same way, if you log into the administration panel under your account from another computer, keep-alive will be set to "off".<br /><br /><b>For more information:</b><br /><a href="http://docs.enanocms.org/Help:Appendix_B" onclick="window.open(this.href); return false;">Overview of Enano\'s security model</a>',
-      
-      type_guest: 'Guest',
-      type_member: 'Member',
-      type_mod: 'Moderator',
-      type_admin: 'Administrator',
-      
-      msg_elev_timed_out: '<b>Your administrative session has timed out.</b> <a href="%login_link%">Log in again</a>',
-      
-      reg_err_captcha: 'The confirmation code you entered was incorrect.',
-      reg_err_disabled_title: 'Registration disabled',
-      reg_err_disabled_body: 'The administrator has disabled the registration of new accounts on this site.',
-      reg_err_disabled_body_adminblurb: 'Oops...it seems that you <em>are</em> the administrator...hehe...you can also <a href="%reg_link%">force account registration to work</a>.',
-      reg_err_username_invalid: 'Your username must be at least two characters in length and may not contain any of the following characters: &lt; &gt; _ &amp; ? \' " % / \\.',
-      // Not exactly an error
-      reg_err_password_good: 'The password you entered is valid.',
-      reg_err_alert_password_tooshort: 'Your password must be 6 characters or greater in length.',
-      reg_err_alert_password_nomatch: 'The passwords you entered do not match.',
-      reg_err_missing_key: 'Couldn\'t look up public encryption key',
-      
-      reg_msg_greatercontrol: 'A user account enables you to have greater control over your browsing experience.',
-      reg_msg_table_title: 'Create a user account',
-      reg_msg_table_subtitle: 'Please tell us a little bit about yourself.',
-      reg_msg_username_checking: 'Checking availability...',
-      reg_msg_username_available: 'This username is available.',
-      reg_msg_username_unavailable: 'This username is already taken.',
-      reg_msg_password_length: 'Your password must be at least six characters in length.',
-      reg_msg_password_score: 'It needs to score at least <b>%config.pw_strength_minimum%</b> for your registration to be accepted.',
-      reg_msg_password_needmatch: 'The passwords you entered do not match.',
-      reg_msg_email_activuser: 'An e-mail with an account activation key will be sent to this address, so please ensure that it is correct.',
-      reg_msg_realname_optional: 'Giving your real name is totally optional. If you choose to provide your real name, it will be used to provide attribution for any edits or contributions you may make to this site.',
-      reg_msg_captcha_pleaseenter: 'Please enter the code shown in the image to the right into the text box. This process helps to ensure that this registration is not being performed by an automated bot. If the image to the right is illegible, you can <a %regen_flags%>generate a new image</a>.',
-      reg_msg_captcha_blind: 'If you are visually impaired or otherwise cannot read the text shown to the right, please contact the site management and they will create an account for you.',
-      reg_msg_success_title: 'Registration successful',
-      reg_msg_success_body: 'Thank you for registering, your user account has been created.',
-      reg_msg_success_activ_none: 'You may now <a href="%login_link%">log in</a> with the username and password that you created.',
-      reg_msg_success_activ_user: 'Because this site requires account activation, you have been sent an e-mail with further instructions. Please follow the instructions in that e-mail to continue your registration.',
-      reg_msg_success_activ_admin: 'Because this site requires administrative account activation, you cannot use your account at the moment. A notice has been sent to the site administration team that will alert them that your account has been created.',
-      reg_msg_success_activ_coppa: 'However, in compliance with the Childrens\' Online Privacy Protection Act, you must have your parent or legal guardian activate your account. Please ask them to check their e-mail for further information.',
-      
-      reg_lbl_field_username: 'Preferred username:',
-      reg_lbl_field_password: 'Password:',
-      reg_lbl_field_password_confirm: 'Enter your password again to confirm.',
-      reg_lbl_field_email: 'E-mail address:',
-      reg_lbl_field_email_coppa: 'Your parent or guardian\'s e-mail address:',
-      reg_lbl_field_realname: 'Real name:',
-      reg_lbl_field_captcha: 'Visual confirmation',
-      reg_lbl_field_captcha_code: 'Code:',
-      
-      reg_coppa_title: 'Before you can register, please tell us your age.',
-      reg_coppa_link_atleast13: 'I was born <b>on or before</b> %yo13_date% and am <b>at least</b> 13 years of age',
-      reg_coppa_link_not13: 'I was born <b>after</b> %yo13_date% and am <b>less than</b> 13 years of age',
-    },
-    usercp: {
-      avatar_err_disabled_title: 'Avatar support is disabled.',
-      avatar_err_disabled_body: 'The administrator has not enabled avatar support for this site.',
-      avatar_table_title: 'Avatar settings',
-      avatar_label_current: 'Current avatar:',
-      avatar_image_alt: '%username%\'s avatar',
-      avatar_image_none: 'You don\'t have an avatar currently.',
-      avatar_lbl_change: 'Change your avatar:',
-      avatar_lbl_keep: 'Keep my current avatar',
-      avatar_lbl_remove: 'Delete my avatar',
-      avatar_lbl_set_http: 'Upload a new avatar from the Web',
-      avatar_lbl_set_file: 'Upload a new avatar from my computer',
-      avatar_lbl_url: 'URL to image:',
-      avatar_lbl_url_desc: 'This must start with the <tt>http://</tt> prefix and must be a valid URL. The image will be copied from the existing URL to this server - dynamic avatars are not supported.',
-      avatar_lbl_file: 'Upload file:',
-      avatar_lbl_file_desc: 'Your browser needs to support file uploads for this option to work.',
-      avatar_limits: 'The image cannot be more than %config.avatar_max_size% bytes in size. The maximum dimensions are %config.avatar_max_width% &#215; %config.avatar_max_height% pixels. Allowed formats are PNG, GIF, and JPEG.',
-      avatar_delete_success: 'Your avatar has been deleted.',
-      avatar_bad_write: 'Either the remote server had trouble finding the image, or your image exceeded the allowed file size.',
-      avatar_bad_filetype: 'The file you selected is invalid. You must choose a file in PNG, JPEG, or GIF format.',
-      avatar_disallowed_animation: 'You have chosen an animated image, which is not allowed. Please choose a non-animated image.',
-      avatar_corrupt_image: 'The image you selected is corrupt. Please choose another image.',
-      avatar_too_large: 'The image you uploaded exceeds the maximum dimensions (%config.avatar_max_width% &#215; %config.avatar_max_height%px) allowed on this site. Please choose another image.',
-      avatar_move_failed: 'Your image was accepted, but there was a problem moving the image file to the correct location.',
-      avatar_upload_success: 'Your avatar has been updated.',
-      avatar_file_too_large: 'The image you uploaded exceeds the maximum file size allowed for avatars on this site.',
-    },
-    onpage: {
-      lbl_pagetools: 'Page tools',
-      lbl_page_article: 'article',
-      lbl_page_admin: 'administration page',
-      lbl_page_system: 'system message',
-      lbl_page_file: 'uploaded file',
-      lbl_page_help: 'documentation page',
-      lbl_page_user: 'user page',
-      lbl_page_special: 'special page',
-      lbl_page_template: 'template',
-      lbl_page_project: 'project page',
-      lbl_page_category: 'category',
-      
-      btn_discussion: 'discussion (%num_comments%)',
-      btn_discussion_unapp: 'discussion (%num_comments% total, %num_unapp% unapp.)',
-      btn_edit: 'edit this page',
-      btn_viewsource: 'view source',
-      btn_history: 'history',
-      btn_moreoptions: 'more options',
-      
-      btn_rename: 'rename',
-      btn_printable: 'view printable version',
-      btn_votedelete: 'vote to delete this page',
-      btn_votedelete_reset: 'reset deletion votes',
-      lbl_wikimode: 'page wiki mode:',
-      btn_wikimode_on: 'on',
-      btn_wikimode_off: 'off',
-      btn_wikimode_global: 'global',
-      lbl_protect: 'protection:',
-      btn_protect_on: 'on',
-      btn_protect_off: 'off',
-      btn_protect_semi: 'semi',
-      btn_clearlogs: 'clear page logs',
-      btn_deletepage: 'delete this page',
-      btn_deletepage_votes: ' (<b>%num_votes%</b> vote%plural%)',
-      lbl_password: 'page password:',
-      btn_password_set: 'set',
-      btn_acl: 'manage page access',
-      btn_admin: 'administrative options',
-      
-      tip_article: 'View the page contents, all of the page contents, and nothing but the page contents (alt-a)',
-      tip_comments: 'View the comments that other users have posted about this page (alt-c)',
-      tip_edit: 'Edit the contents of this page (alt-e)',
-      tip_viewsource: 'View the source code (wiki markup) that this page uses (alt-e)',
-      tip_history: 'View a log of actions taken on this page (alt-h)',
-      tip_rename: 'Change the display name of this page (alt-r)',
-      tip_delvote: 'Vote to have this page deleted (alt-d)',
-      tip_resetvotes: 'Clear the list of votes for deletion against this page (alt-y)',
-      tip_printable: 'View a version of this page that is suitable for printing',
-      tip_protect_on: 'Prevents all non-administrators from editing this page. [alt-i]',
-      tip_protect_off: 'Allows everyone to edit this page. [alt-o]',
-      tip_protect_semi: 'Allows only users who have been registered for 4 days to edit this page. [alt-p]',
-      tip_flushlogs: 'Remove all edit and action logs for this page from the database. IRREVERSIBLE! (alt-l)',
-      tip_deletepage: 'Delete this page. This is always reversible unless the logs are cleared. (alt-k)',
-      tip_adminoptions: 'Administrative options for this page',
-      tip_moreoptions: 'Additional options for working with this page',
-      tip_password: 'Require a password in order for this page to be viewed',
-      tip_aclmanager: 'Manage who can do what with this page (alt-m)',
-      
-    },
-    comment: {
-      lbl_subject: 'Subject',
-      lbl_mod_options: 'Moderator options:',
-      heading: 'Article comments',
-      btn_send_privmsg: 'Send private message',
-      btn_add_buddy: 'Add to buddy list',
-      btn_edit: 'edit',
-      btn_delete: 'delete',
-      btn_mod_approve: 'Approve',
-      btn_mod_unapprove: 'Unapprove',
-      btn_mod_delete: 'Delete',
-      btn_save: 'save',
-      
-      msg_comment_posted: 'Your comment has been posted. If it does not appear right away, it is probably awaiting approval.',
-      
-      msg_count_zero: 'There are <span id="comment_count_inner">no</span> comments on this %page_type%.',
-      msg_count_one: 'There is <span id="comment_count_inner">1</span> comment on this %page_type%.',
-      msg_count_plural: 'There are <span id="comment_count_inner">%num_comments%</span> comments on this %page_type%.',
-      
-      msg_count_unapp_mod: '<span id="comment_count_unapp_inner">%num_unapp%</span> of those are unapproved.',
-      msg_count_unapp_one: 'However, there is <span id="comment_count_unapp_inner">1</span> additional comment awaiting approval.',
-      msg_count_unapp_plural: 'However, there are <span id="comment_count_unapp_inner">%num_unapp%</span> additional comments awaiting approval.',
-      
-      msg_note_unapp: '(Unapproved)',
-      
-      msg_delete_confirm: 'Do you really want to delete this comment?',
-      
-      postform_title: 'Got something to say?',
-      postform_blurb: 'If you have comments or suggestions on this article, you can shout it out here.',
-      postform_blurb_unapp: 'Before your post will be visible to the public, a moderator will have to approve it.',
-      postform_blurb_captcha: 'Because you are not logged in, you will need to enter a visual confirmation before your comment will be posted.',
-      postform_blurb_link: 'Leave a comment...',
-      postform_field_name: 'Your name/screen name:',
-      postform_field_subject: 'Comment subject:',
-      postform_field_comment: 'Comment:',
-      postform_field_captcha_title: 'Visual confirmation:',
-      postform_field_captcha_blurb: 'Please enter the confirmation code seen in the image on the right into the box. If you cannot read the code, please click on the image to generate a new one. This helps to prevent automated bot posting.',
-      postform_field_captcha_label: 'Confirmation code:',
-      postform_field_captcha_cantread_js: 'If you can\'t read the code, click on the image to generate a new one.',
-      postform_field_captcha_cantread_nojs: 'If you can\'t read the code, please refresh this page to generate a new one.',
-      postform_btn_submit: 'Submit comment',
-      
-      on_friend_list: 'On your friend list',
-      on_foe_list: 'On your foe list',
-    },
-    adm: {
-      cat_general: 'General',
-      cat_content: 'Content',
-      cat_appearance: 'Appearance',
-      cat_users: 'Users',
-      cat_security: 'Security',
-      cat_plugins: 'Plugin configuration',
-      
-      page_general_config: 'General configuration',
-      page_file_uploads: 'File uploads',
-      page_file_types: 'Allowed file types',
-      page_plugins: 'Manage plugins',
-      page_db_backup: 'Backup database',
-      
-      page_manager: 'Manage pages',
-      page_editor: 'Edit page content',
-      page_pg_groups: 'Manage page groups',
-      
-      page_themes: 'Manage themes',
-      
-      page_users: 'Manage users',
-      page_user_groups: 'Edit user groups',
-      page_coppa: 'COPPA support',
-      page_mass_email: 'Mass e-mail',
-      
-      page_security_log: 'Security log',
-      page_ban_control: 'Ban control',
-      
-      btn_home: 'Administration panel home',
-      btn_logout: 'Log out of admin panel',
-      btn_keepalive_off: 'Turn on keep-alive',
-      btn_keepalive_on: 'Turn off keep-alive',
-      btn_keepalive_about: 'About keep-alive',
-      btn_keepalive_loading: 'Loading keep-alive button...',
-      
-      err_not_auth_title: 'Error: Not authenticated',
-      err_not_auth_body: 'It looks like your administration session is invalid or you are not authorized to access this administration page. Please <a href="%login_link%">re-authenticate</a> to continue.',
-    },
-    editor: {
-      msg_revert_confirm: 'Do you really want to revert your changes?',
-      msg_discard_confirm: 'Do you really want to discard your changes?',
-      msg_unload: 'If you do, any changes that you have made to this page will be lost.',
-      btn_graphical: 'graphical editor',
-      btn_wikitext: 'wikitext editor',
-      lbl_edit_summary: 'Edit summary:',
-      lbl_minor_edit: 'This is a minor edit',
-      btn_save: 'Save changes',
-      btn_preview: 'Show preview',
-      btn_revert: 'Revert changes',
-      btn_cancel: 'Cancel and return to page',
-      btn_closeviewer: 'Close viewer',
-      preview_blurb: '<b>Reminder:</b> This is only a preview - your changes to this page have not yet been saved.',
-      msg_save_success_title: 'Changes saved',
-      msg_save_success_body: 'Your changes to this page have been saved. Redirecting...',
-    },
-    history: {
-      summary_clearlogs: 'Automatic backup created when logs were purged',
-      page_subtitle: 'History of edits and actions',
-      heading_edits: 'Edits:',
-      heading_other: 'Other changes:',
-      no_entries: 'No history entries in this category.',
-      btn_compare: 'Compare selected revisions',
-      col_diff: 'Diff',
-      col_datetime: 'Date/time',
-      col_user: 'User',
-      col_summary: 'Edit summary',
-      col_minor: 'Minor',
-      col_actions: 'Actions',
-      col_action_taken: 'Action taken',
-      col_extra: 'Extra info',
-      extra_reason: 'Reason:',
-      extra_oldtitle: 'Old title:',
-      tip_rdns: 'Click cell background for reverse DNS info',
-      action_view: 'View',
-      action_contrib: 'User contribs',
-      action_restore: 'Restore',
-      action_revert: 'Revert action',
-      log_protect: 'Protected page',
-      log_unprotect: 'Unprotected page',
-      log_semiprotect: 'Semi-protected page',
-      log_rename: 'Renamed page',
-      log_create: 'Created page',
-      log_delete: 'Deleted page',
-      log_uploadnew: 'Uploaded new file version',
-      lbl_comparingrevisions: 'Comparing revisions:',
-    },
-    page: {
-      protect_lbl_success_title: 'Page protected',
-      protect_lbl_success_body: 'The protection setting has been applied. <a href="%page_link%">Return to the page</a>.',
-      protect_err_need_reason: 'Error: you must enter a reason for protecting this page.',
-      protect_lbl_reason: 'Reason for protecting the page:',
-      protect_lbl_level: 'Protecion level to be applied:',
-      protect_lbl_level_none: 'No protection',
-      protect_lbl_level_semi: 'Semi-protection',
-      protect_lbl_level_full: 'Full protection',
-      protect_btn_submit: 'Protect page',
-      
-      rename_err_need_name: 'Error: you must enter a new name for this page.',
-      rename_lbl: 'Please enter a new name for this page:',
-      rename_btn_submit: 'Rename page',
-      rename_success_title: 'Page renamed',
-      
-      flushlogs_warning_stern: '<h3>You are about to <span style="color: red;">destroy</span> all logged edits and actions on this page.</h3><p>Unlike deleting or editing this page, this action is <u>not reversible</u>! You should only do this if you are desparate for database space.</p><p>Do you really want to continue?</p>',
-      flushlogs_btn_submit: 'Flush logs',
-      
-      delvote_warning_stern: '<h3>Your vote counts.</h3><p>If you think that this page is not relavent to the content on this site, or if it looks like this page was only created in an attempt to spam the site, you can request that this page be deleted by an administrator.</p><p>After you vote, you should leave a comment explaining the reason for your vote, especially if you are the first person to vote against this page.</p>',
-      
-      delvote_count_zero: 'So far, no one has voted for the deletion of this page.',
-      delvote_count_one: 'So far, one person has voted to delete this page.',
-      delvote_count_plural: 'So far, %delvotes% people have voted to delete this page.',
-      delvote_btn_submit: 'Vote to delete this page',
-      delvote_reset_btn_submit: 'Reset votes',
-      
-      delete_warning_stern: '<h3>You are about to <span style="color: red;">destroy</span> this page.</h3><p>While the deletion of the page itself is completely reversible, it is impossible to recover any comments or category information on this page. If this is a file page, the file along with all older revisions of it will be permanently deleted. Also, any custom information that this page is tagged with, such as a custom name, protection status, or additional settings such as whether to allow comments, will be permanently lost.</p><p>Are you <u>absolutely sure</u> that you want to continue?<br />You will not be asked again.</p>',
-      delete_btn_submit: 'Delete this page',
-      delete_lbl_reason: 'Reason for deleting:',
-      
-      wikimode_success_redirect: 'Wiki mode for this page has been set. Redirecting you to the page...',
-      wikimode_level_on: 'Wiki features will be enabled.',
-      wikimode_level_off: 'Wiki features will be disabled.',
-      wikimode_level_global: 'Wiki features will be synchronized to the global setting.',
-      wikimode_heading: 'You are changing wiki mode for this page.',
-      wikimode_warning: 'If you want to continue, please click the button below.',
-      wikimode_blurb_disable: 'Because this will disable the wiki behavior on this page, several features, most notably the ability for users to vote to have this page deleted, will be disabled as they are not relevant to non-wiki pages. In addition, users will not be able to edit this page unless an ACL rule specifically permits them.',
-      wikimode_blurb_enable: 'Because this will enable the wiki behavior on this page, users will gain the ability to freely edit this page unless an ACL rule specifically denies them. If your site is public and gets good traffic, you should be aware of the possiblity of vandalism, and you need to be ready to revert malicious edits to this page.',
-      wikimode_btn_submit: 'Set wiki mode',
-      
-      detag_err_page_exists: 'The detag action is only valid for pages that have been deleted in the past.',
-      detag_success_title: 'Page detagged',
-      detag_success_body: 'All stale tags have been removed from this page.',
-      
-      err_custompage_function_missing_title: 'Page backend not found',
-      err_custompage_function_missing_body: 'The administration page you are looking for was properly registered using the page API, but the backend function (<tt>%function_name%</tt>) was not found. If this is a plugin page, then this is almost certainly a bug with the plugin.',
-      err_redirects_exceeded: 'The maximum number of internal redirects has been exceeded.',
-      err_redirect_to_nonexistent: 'This page redirects to another page that doesn\'t exist.',
-      err_redirect_infinite_loop: 'This page infinitely redirects with another page (or another series of pages), and the infinite redirect was trapped.',
-      err_redirect_to_special: 'This page redirects to a Special or Administration page, which is not allowed.',
-      err_access_denied_title: 'You don\'t have permission to view this page.',
-      err_access_denied_body: '<p>Your user account doesn\'t have the necessary permission to view this page. There are a number of possible reasons for this:</p>
-                               <ul>
-                                 <li>You aren\'t logged in. Some pages are restricted to logged-in users.</li>
-                                 <li>The page you\'re trying to view is protected so that only members of a specific usergroup are allowed to read it.</li>
-                               </ul>
-                               <p>If you would like to inquire further about this message, you may contact the %site_administration%.</p>',
-      err_access_denied_siteadmin: 'site administrator',
-      msg_this_is_a_redirector: '<b>This page is a <i>redirector</i>.</b><br />
-                    This means that this page will not show its own content by default. Instead it will display the contents of the page it redirects to.<br /><br />
-                    To create a redirect page, make the <i>first characters</i> in the page content <tt>#redirect [[Page_ID]]</tt>. For more information, see the
-                    Enano <a href="http://enanocms.org/Help:Wiki_formatting" onclick="window.open(this.href); return false;">Wiki formatting guide</a>.<br /><br />
-                    This page redirects to %redirect_target%.'
-    },
-    catedit: {
-      title: 'Select which categories this page should be included in.',
-      no_categories: 'There are no categories on this site yet.',
-      catbox_lbl_categories: 'Categories:',
-      catbox_lbl_uncategorized: '(Uncategorized)',
-      catbox_link_edit: 'edit categorization',
-      catbox_link_showcategorization: 'show page categorization',
-    },
-    tags: {
-      catbox_link: 'show page tags',
-      lbl_page_tags: 'Page tags:',
-      lbl_no_tags: 'No tags on this page',
-      btn_add_tag: '(add a tag)',
-      lbl_add_tag: 'Add a tag:',
-      btn_add: '+ Add',
-    },
-    delvote: {
-      lbl_votes_one: 'There is one user that thinks this page should be deleted.',
-      lbl_votes_plural: 'There are %num_users% users that think this page should be deleted.',
-      lbl_users_that_voted: 'Users that voted:',
-      btn_deletepage: 'Delete page',
-      btn_resetvotes: 'Reset votes',
-    },
-    ajax: {
-      // Client-side messages
-      protect_prompt_reason: 'Reason for (un)protecting:',
-      rename_prompt: 'What title should this page be renamed to?\nNote: This does not and will never change the URL of this page, that must be done from the admin panel.',
-      delete_prompt_reason: 'Please enter your reason for deleting this page.',
-      delete_confirm: 'You are about to REVERSIBLY delete this page. Do you REALLY want to do this?\n\n(Comments and categorization data, as well as any attached files, will be permanently lost)',
-      delvote_confirm: 'Are you sure that you want to vote that this page be deleted?',
-      delvote_reset_confirm: 'This action will reset the number of votes against this page to zero. Do you really want to do this?',
-      clearlogs_confirm: 'You are about to DESTROY all log entries for this page. As opposed to (example) deleting this page, this action is completely IRREVERSIBLE and should not be used except in dire circumstances. Do you REALLY want to do this?',
-      clearlogs_confirm_nag: 'You\'re ABSOLUTELY sure???',
-      changestyle_select: '[Select]',
-      changestyle_title: 'Change your theme',
-      changestyle_pleaseselect_theme: 'Please select a theme from the list.',
-      changestyle_lbl_theme: 'Theme:',
-      changestyle_lbl_style: 'Style:',
-      changestyle_success: 'Your theme preference has been changed.\nWould you like to reload the page now to see the changes?',
-      killphp_confirm: 'Are you really sure you want to do this? Some pages might not function if this emergency-only feature is activated.',
-      killphp_success: 'Embedded PHP in pages has been disabled.',
-      lbl_moreoptions_nojs: 'More options for this page',
-      
-      // Server-side responses
-      rename_too_short: 'The name you entered is too short. Please enter a longer name for this page.',
-      rename_success: 'The page "%page_name_old%" has been renamed to "%page_name_new%". You are encouraged to leave a comment explaining your action.\n\nYou will see the change take effect the next time you reload this page.',
-      clearlogs_success: 'The logs for this page have been cleared. A backup of this page has been added to the logs table so that this page can be restored in case of vandalism or spam later.',
-      delete_need_reason: 'Invalid reason for deletion passed. Please enter a reason for deleting this page.',
-      delete_success: 'This page has been deleted. Note that there is still a log of edits and actions in the database, and anyone with admin rights can raise this page from the dead unless the log is cleared. If the deleted file is an image, there may still be cached thumbnails of it in the cache/ directory, which is inaccessible to users.',
-      delvote_success: 'Your vote to have this page deleted has been cast.\nYou are encouraged to leave a comment explaining the reason for your vote.',
-      delvote_already_voted: 'It appears that you have already voted to have this page deleted.',
-      delvote_reset_success: 'The number of votes for having this page deleted has been reset to zero.',
-      password_success: 'The password for this page has been set.',
-      password_disable_success: 'The password for this page has been disabled.',
-      
-    },
-    sidebar: {
-      title_navigation: 'Navigation',
-      title_tools: 'Tools',
-      title_search: 'Search',
-      title_links: 'Links',
-      
-      btn_home: 'Home',
-      btn_createpage: 'Create a page',
-      btn_uploadfile: 'Upload file',
-      btn_specialpages: 'Special pages',
-      btn_administration: 'Administration',
-      btn_editsidebar: 'Edit the sidebar',
-      btn_search_go: 'Go',
-      
-      btn_userpage: 'User page',
-      btn_mycontribs: 'My contributions',
-      btn_preferences: 'Preferences',
-      btn_privatemessages: 'Private messages',
-      btn_groupcp: 'Group control panel',
-      btn_register: 'Create an account',
-      btn_login: 'Log in',
-      btn_logout: 'Log out',
-      btn_changestyle: 'Change theme',
-    },
-    acl: {
-      err_access_denied: 'You are not authorized to view or edit access control lists.',
-      err_missing_template: 'It seems that (a) the file acledit.tpl is missing from this theme, and (b) the JSON response is working.',
-      err_user_not_found: 'The username you entered was not found.',
-      err_bad_group_id: 'The group ID you submitted is not valid.',
-      err_demo: 'Editing access control lists is disabled in the administration demo.',
-      err_zero_list: 'Supplied rule list has a length of zero',
-      err_pleaseselect_targettype: 'Please select a target type.',
-      err_pleaseselect_username: 'Please enter a username.',
-      
-      radio_usergroup: 'A usergroup',
-      radio_user: 'A specific user',
-      radio_scope_thispage: 'Only this page',
-      radio_scope_wholesite: 'The entire website',
-      radio_scope_pagegroup: 'A group of pages',
-      
-      lbl_scope: 'What should this access rule control?',
-      lbl_welcome_title: 'Manage page access',
-      lbl_welcome_body: 'Please select who should be affected by this access rule.',
-      lbl_editwin_title_create: 'Create access rule',
-      lbl_editwin_title_edit: 'Editing permissions',
-      lbl_editwin_body: 'This panel allows you to edit what the %target_type% "<b>%target%</b>" can do on <b>%scope_type%</b>. Unless you set a permission to "Deny", these permissions may be overridden by other rules.',
-      lbl_deleterule: 'Delete this rule',
-      lbl_save_success_title: 'Permissions updated',
-      lbl_save_success_body: 'The permissions for %target_name% on this page have been updated successfully. If you changed permissions that affect your user account, you may not see changes until you reload the page.',
-      lbl_delete_success_title: 'Rule deleted',
-      lbl_delete_success_body: 'The access rules for %target_name% on this page have been deleted.',
-      lbl_field_deny: 'Deny',
-      lbl_field_disallow: 'Disallow',
-      lbl_field_wikimode: 'Wiki mode',
-      lbl_field_allow: 'Allow',
-      lbl_help: '<p><b>Permission types:</b></p><ul><li><b>Allow</b> means that the user is allowed to access the item</li><li><b>Wiki mode</b> means the user can access the item if wiki mode is active (per-page wiki mode is taken into account)</li><li><b>Disallow</b> means the user is denied access unless something allows it.</li><li><b>Deny</b> means that the user is denied access to the item. This setting overrides all other permissions.</li></ul>',
-      
-      scope_type_wholesite: 'this entire site',
-      scope_type_thispage: 'this page',
-      scope_type_pagegroup: 'this group of pages',
-      
-      target_type_user: 'user',
-      target_type_group: 'group',
-      
-      msg_guest_howto: 'To edit permissions for guests, select "a specific user", and enter Anonymous as the username.',
-      msg_deleterule_confirm: 'Do you really want to delete this rule?',
-      msg_closeacl_confirm: 'Do you really want to close the ACL manager?',
-      
-      btn_success_dismiss: 'dismiss',
-      btn_success_close: 'close manager',
-      btn_deleterule: 'Delete rule',
-      btn_createrule: 'Create rule',
-      btn_returnto_editor: 'Return to ACL editor',
-      btn_returnto_userscope: 'Return to user/scope selection',
-    },
-    perm: {
-      read: 'Read page(s)',
-      post_comments: 'Post comments',
-      edit_comments: 'Edit own comments',
-      edit_page: 'Edit page',
-      view_source: 'View source',
-      mod_comments: 'Moderate comments',
-      history_view: 'View history/diffs',
-      history_rollback: 'Rollback history',
-      history_rollback_extra: 'Undelete page(s)',
-      protect: 'Protect page(s)',
-      rename: 'Rename page(s)',
-      clear_logs: 'Clear page logs (dangerous)',
-      vote_delete: 'Vote to delete',
-      vote_reset: 'Reset delete votes',
-      delete_page: 'Delete page(s)',
-      tag_create: 'Tag page(s)',
-      tag_delete_own: 'Remove own page tags',
-      tag_delete_other: 'Remove others\' page tags',
-      set_wiki_mode: 'Set per-page wiki mode',
-      password_set: 'Set password',
-      password_reset: 'Disable/reset password',
-      mod_misc: 'Super moderator (generate SQL backtraces, view IP addresses, and send large numbers of private messages)',
-      edit_cat: 'Edit categorization',
-      even_when_protected: 'Allow editing, renaming, and categorization even when protected',
-      upload_files: 'Upload files',
-      upload_new_version: 'Upload new versions of files',
-      create_page: 'Create pages',
-      php_in_pages: 'Embed PHP code in pages',
-      edit_acl: 'Edit access control lists',
-    },
-    adminusers: {
-      avatar_heading: 'Avatar settings',
-      avatar_image_none: 'This user does not currently have an avatar.',
-      avatar_lbl_change: 'Change avatar:',
-      avatar_lbl_keep: 'Keep current setting',
-      avatar_lbl_remove: 'Delete this user\'s avatar',
-      avatar_lbl_set_http: 'Replace avatar using a new image from a URL',
-      avatar_lbl_set_file: 'Replace avatar using a new image from my computer',
-    },
-    etc: {
-      redirect_title: 'Redirecting...',
-      redirect_body: 'Please wait while you are redirected.',
-      redirect_timeout: 'If you are not redirected within %timeout% seconds, please <a href="%redirect_url%">click here</a>.',
-      // Generic "Save Changes" button
-      save_changes: 'Save changes',
-      // Generic "Cancel changes" button
-      cancel_changes: 'Cancel changes',
-      // Generic wizard buttons
-      wizard_next: 'Next >',
-      wizard_back: '< Back',
-      wizard_previous: '< Previous',
-      // Generic "Notice:" label
-      lbl_notice: 'Notice:',
-      // Generic "Access denied"
-      access_denied: 'Access to the specified file, resource, or action is denied.',
-      access_denied_short: 'Access denied',
-      return_to_page: 'Return to the page',
-      invalid_request_short: 'Invalid request',
-      // Message box buttons
-      ok: 'OK',
-      cancel: 'Cancel',
-      yes: 'Yes',
-      no: 'No'
-    }
-  }
-};
-
-// All done! :-)
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/language/english/tools.json	Wed Dec 26 00:37:26 2007 -0500
@@ -0,0 +1,84 @@
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.1
+ * Copyright (C) 2006-2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute 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.
+ *
+ * This program 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 details.
+ */
+
+// This is the main language file for Enano. Feel free to use it as a base for your own translations.
+// All text in this file before the first left curly brace and all text after the last curly brace will
+// be trimmed. So you can use a limited amount of Javascript in this so that the language can be imported
+// via Javascript as well.
+
+var enano_lang = {
+  categories: [
+    'search', 'specialpage'
+  ],
+  strings: {
+    meta: {
+      search: 'Search page',
+      specialpage: 'Special pages',
+    },
+    specialpage: {
+      administration: 'Administration',
+      manage_sidebar: 'Manage the Sidebar',
+      css: 'Templated style sheet generator',
+      groupcp: 'Group Membership',
+      create_page: 'Create page',
+      all_pages: 'All pages',
+      special_pages: 'List of special pages',
+      about_enano: 'About Enano',
+      gnu_gpl: 'GNU General Public License',
+      tag_cloud: 'Tag cloud',
+      search_rebuild: 'Rebuild search index',
+      search: 'Search',
+      upload_file: 'Upload file',
+      download_file: 'Download file',
+      log_in: 'Log in',
+      log_out: 'Log out',
+      register: 'Register',
+      preferences: 'Edit Profile',
+      contributions: 'User contributions',
+      change_theme: 'Change my preferred theme',
+      activate_account: 'Activate user account',
+      captcha: 'CAPTCHA image generator',
+      password_reset: 'Reset forgotten password',
+      member_list: 'Member list',
+      language_export: 'Language exporter',
+      private_messages: 'Private Messages',
+    },
+    search: {
+      th_advanced_search: 'Advanced Search',
+      
+      err_query_title: 'Some problems were encountered during your search.',
+      err_query_body: 'There was a problem with your search query, and as a result there may be a reduced number of search results.',
+      err_query_too_many_terms: 'Some of your search terms were excluded because searches are limited to 20 terms to prevent excessive server load.',
+      err_query_has_stopwords: 'One or more of your search terms was excluded because either it was less than 2 characters in length or is a common word (a stopword) that is typically found on a large number of pages. Examples of stopwords include "the", "this", "which", "with", etc.',
+      err_query_dup_terms: 'One or more of your search terms was excluded because duplicate terms were encountered.',
+      err_query_term_too_short: 'One or more of your search terms was excluded because terms must be at least 4 characters in length.',
+      
+      btn_search: 'Search',
+      // note the case difference with th_advanced_search
+      btn_advanced_search: 'Advanced search',
+      
+      msg_no_results: 'No results.',
+      msg_result_detail: 'Results <b>%start_string%</b> - <b>%per_string%</b> of about <b>%num_results%</b> for <b>%q_trim%</b> in %search_time%s.',
+      
+      lbl_site_search: 'Site search',
+      lbl_relevance: 'Relevance:',
+      lbl_field_any: 'Search for pages with <b>any of these words</b>:',
+      lbl_field_exact: 'with <b>this exact phrase</b>:',
+      lbl_field_none: 'with <b>none of these words</b>:',
+      lbl_field_all: 'with <b>all of these words</b>:',
+      lbl_field_casesensitive: 'Case-sensitive search:',
+    }
+  }
+};
+
+// All done! :-)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/language/english/user.json	Wed Dec 26 00:37:26 2007 -0500
@@ -0,0 +1,309 @@
+/*
+ * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
+ * Version 1.1.1
+ * Copyright (C) 2006-2007 Dan Fuhry
+ *
+ * This program is Free Software; you can redistribute 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.
+ *
+ * This program 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 details.
+ */
+
+// This is the main language file for Enano. Feel free to use it as a base for your own translations.
+// All text in this file before the first left curly brace and all text after the last curly brace will
+// be trimmed. So you can use a limited amount of Javascript in this so that the language can be imported
+// via Javascript as well.
+
+var enano_lang = {
+  categories: [
+    'user', 'usercp', 'groupcp', 'privmsgs'
+  ],
+  strings: {
+    meta: {
+      user: 'Login, logout, and authentication',
+      usercp: 'User control panel',
+      groupcp: 'Group control panel',
+      privmsgs: 'Private message and buddy list CP'
+    },
+    user: {
+      login_message_short: 'Please enter your username and password to log in.',
+      login_message_short_elev: 'Please re-enter your login details',
+      login_body: 'Logging in enables you to use your preferences and access member information. If you don\'t have a username and password here, you can <a href="%reg_link%">create an account</a>.',
+      login_body_elev: 'You are requesting that a sensitive operation be performed. To continue, please re-enter your password to confirm your identity.',
+      login_field_username: 'Username',
+      login_field_password: 'Password',
+      login_forgotpass_blurb: 'Forgot your password? <a href="%forgotpass_link%">No problem.</a>',
+      login_createaccount_blurb: 'Maybe you need to <a href="%reg_link%">create an account</a>.',
+      login_field_captcha: 'Code in image',
+      login_nocrypt_title: 'Important note regarding cryptography:',
+      login_nocrypt_body: 'Some countries do not allow the import or use of cryptographic technology. If you live in one of the countries listed below, you should <a href="%nocrypt_link%">log in without using encryption</a>.',
+      login_nocrypt_countrylist: 'This restriction applies to the following countries: Belarus, China, India, Israel, Kazakhstan, Mongolia, Pakistan, Russia, Saudi Arabia, Singapore, Tunisia, Venezuela, and Vietnam.',
+      login_usecrypt_title: 'Encryption is currently turned off.',
+      login_usecrypt_body: 'If you are not in one of the countries listed below, you should <a href="%usecrypt_link%">enable encryption</a> to secure the logon process.',
+      login_usecrypt_countrylist: 'The cryptography restriction applies to the following countries: Belarus, China, India, Israel, Kazakhstan, Mongolia, Pakistan, Russia, Saudi Arabia, Singapore, Tunisia, Venezuela, and Vietnam.',
+      login_success_title: 'Login successful',
+      login_success_body: 'You have successfully logged into the %config.site_name% site as "%username%". Redirecting to %redir_target%...',
+      login_success_body_mainpage: 'the main page',
+      login_success_short: 'Success.',
+      
+      login_ajax_fetching_key: 'Fetching an encryption key...',
+      login_ajax_prompt_title: 'Please enter your username and password to continue.',
+      login_ajax_prompt_title_elev: 'You are requesting a sensitive operation.',
+      login_ajax_prompt_body_elev: 'Please re-enter your login details, to verify your identity.',
+      login_ajax_link_fullform: 'Trouble logging in? Try the <a href="%link_full_form%">full login form</a>.',
+      login_ajax_link_forgotpass: 'Did you <a href="%forgotpass_link%">forget your password</a>?',
+      login_ajax_loggingin: 'Logging in...',
+      
+      err_key_not_found: 'Enano couldn\'t look up the encryption key used to encrypt your password. This most often happens if a cache rotation occurred during your login attempt, or if you refreshed the login page.',
+      err_key_not_found_cleared: 'It seems that the list of encryption keys used for login information has reached its maximum length, thus preventing new keys from being inserted. The list has been automatically cleared. Please try logging in again; if you are still unable to log in, please contact the site administration.',
+      err_key_wrong_length: 'The encryption key was the wrong length.',
+      err_too_big_for_britches: 'You are trying to authenticate at a level that your user account does not permit.',
+      err_invalid_credentials: 'You have entered an invalid username or password. Please enter your login details again.',
+      err_invalid_credentials_lockout: ' You have used up %fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will be locked out from logging in for %config.lockout_duration% minutes.',
+      err_invalid_credentials_lockout_captcha: ' You have used up %lockout_fails% out of %config.lockout_threshold% login attempts. After you have used up all %config.lockout_threshold% login attempts, you will have to enter a visual confirmation code while logging in, effective for %config.lockout_duration% minutes.',
+      err_backend_fail: 'You entered the right credentials and everything was validated, but for some reason Enano couldn\'t register your session. This is an internal problem with the site and you are encouraged to contact site administration.',
+      err_locked_out: 'You have used up all %config.lockout_threshold% allowed login attempts. Please wait %time_rem% minute%plural% before attempting to log in again%captcha_blurb%.',
+      err_locked_out_captcha_blurb: ', or enter the visual confirmation code shown above in the appropriate box',
+      
+      logout_success_title: 'Logged out',
+      logout_success_body: 'You have been successfully logged out, and all cookies have been cleared. You will now be transferred to the main page.',
+      logout_confirm_title: 'Are you sure you want to log out?',
+      logout_confirm_body: 'If you log out, you will no longer be able to access your user preferences, your private messages, or certain areas of this site until you log in again.',
+      logout_confirm_title_elev: 'Are you sure you want to de-authenticate?',
+      logout_confirm_body_elev: 'If you de-authenticate, you will no longer be able to use the administration panel until you re-authenticate again. You may do so at any time using the Administration button on the sidebar.',
+      logout_err_title: 'An error occurred during the logout process.',
+      // Unused at this point
+      logout_err_not_loggedin: 'You don\'t seem to be logged in.',
+      
+      keepalive_info_title: 'About the keep-alive feature',
+      keepalive_info_body: 'Keep-alive is a new Enano feature that keeps your administrative session from timing out while you are using the administration panel. This feature can be useful if you are editing a large page or doing something in the administration interface that will take longer than 15 minutes.<br /><br />For security reasons, Enano mandates that high-privilege logins last only 15 minutes, with the time being reset each time a page is loaded (or, more specifically, each time the session API is started). The consequence of this is that if you are performing an action in the administration panel that takes more than 15 minutes, your session may be terminated. The keep-alive feature attempts to relieve this by sending a "ping" to the server every 10 minutes.<br /><br />Please note that keep-alive state is determined by a cookie. Thus, if you log out and then back in as a different administrator, keep-alive will use the same setting that was used when you were logged in as the first administrative user. In the same way, if you log into the administration panel under your account from another computer, keep-alive will be set to "off".<br /><br /><b>For more information:</b><br /><a href="http://docs.enanocms.org/Help:Appendix_B" onclick="window.open(this.href); return false;">Overview of Enano\'s security model</a>',
+      
+      type_guest: 'Guest',
+      type_member: 'Member',
+      type_mod: 'Moderator',
+      type_admin: 'Administrator',
+      
+      msg_elev_timed_out: '<b>Your administrative session has timed out.</b> <a href="%login_link%">Log in again</a>',
+      
+      reg_err_captcha: 'The confirmation code you entered was incorrect.',
+      reg_err_disabled_title: 'Registration disabled',
+      reg_err_disabled_body: 'The administrator has disabled the registration of new accounts on this site.',
+      reg_err_disabled_body_adminblurb: 'Oops...it seems that you <em>are</em> the administrator...hehe...you can also <a href="%reg_link%">force account registration to work</a>.',
+      reg_err_username_invalid: 'Your username must be at least two characters in length and may not contain any of the following characters: &lt; &gt; _ &amp; ? \' " % / \\.',
+      // Not exactly an error
+      reg_err_password_good: 'The password you entered is valid.',
+      reg_err_alert_password_tooshort: 'Your password must be 6 characters or greater in length.',
+      reg_err_alert_password_nomatch: 'The passwords you entered do not match.',
+      reg_err_missing_key: 'Couldn\'t look up public encryption key',
+      
+      reg_msg_greatercontrol: 'A user account enables you to have greater control over your browsing experience.',
+      reg_msg_table_title: 'Create a user account',
+      reg_msg_table_subtitle: 'Please tell us a little bit about yourself.',
+      reg_msg_username_checking: 'Checking availability...',
+      reg_msg_username_available: 'This username is available.',
+      reg_msg_username_unavailable: 'This username is already taken.',
+      reg_msg_password_length: 'Your password must be at least six characters in length.',
+      reg_msg_password_score: 'It needs to score at least <b>%config.pw_strength_minimum%</b> for your registration to be accepted.',
+      reg_msg_password_needmatch: 'The passwords you entered do not match.',
+      reg_msg_email_activuser: 'An e-mail with an account activation key will be sent to this address, so please ensure that it is correct.',
+      reg_msg_realname_optional: 'Giving your real name is totally optional. If you choose to provide your real name, it will be used to provide attribution for any edits or contributions you may make to this site.',
+      reg_msg_captcha_pleaseenter: 'Please enter the code shown in the image to the right into the text box. This process helps to ensure that this registration is not being performed by an automated bot. If the image to the right is illegible, you can <a %regen_flags%>generate a new image</a>.',
+      reg_msg_captcha_blind: 'If you are visually impaired or otherwise cannot read the text shown to the right, please contact the site management and they will create an account for you.',
+      reg_msg_success_title: 'Registration successful',
+      reg_msg_success_body: 'Thank you for registering, your user account has been created.',
+      reg_msg_success_activ_none: 'You may now <a href="%login_link%">log in</a> with the username and password that you created.',
+      reg_msg_success_activ_user: 'Because this site requires account activation, you have been sent an e-mail with further instructions. Please follow the instructions in that e-mail to continue your registration.',
+      reg_msg_success_activ_admin: 'Because this site requires administrative account activation, you cannot use your account at the moment. A notice has been sent to the site administration team that will alert them that your account has been created.',
+      reg_msg_success_activ_coppa: 'However, in compliance with the Childrens\' Online Privacy Protection Act, you must have your parent or legal guardian activate your account. Please ask them to check their e-mail for further information.',
+      
+      reg_lbl_field_username: 'Preferred username:',
+      reg_lbl_field_password: 'Password:',
+      reg_lbl_field_password_confirm: 'Enter your password again to confirm.',
+      reg_lbl_field_email: 'E-mail address:',
+      reg_lbl_field_email_coppa: 'Your parent or guardian\'s e-mail address:',
+      reg_lbl_field_realname: 'Real name:',
+      reg_lbl_field_captcha: 'Visual confirmation',
+      reg_lbl_field_captcha_code: 'Code:',
+      
+      reg_coppa_title: 'Before you can register, please tell us your age.',
+      reg_coppa_link_atleast13: 'I was born <b>on or before</b> %yo13_date% and am <b>at least</b> 13 years of age',
+      reg_coppa_link_not13: 'I was born <b>after</b> %yo13_date% and am <b>less than</b> 13 years of age',
+    },
+    usercp: {
+      avatar_err_disabled_title: 'Avatar support is disabled.',
+      avatar_err_disabled_body: 'The administrator has not enabled avatar support for this site.',
+      avatar_table_title: 'Avatar settings',
+      avatar_label_current: 'Current avatar:',
+      avatar_image_alt: '%username%\'s avatar',
+      avatar_image_none: 'You don\'t have an avatar currently.',
+      avatar_lbl_change: 'Change your avatar:',
+      avatar_lbl_keep: 'Keep my current avatar',
+      avatar_lbl_remove: 'Delete my avatar',
+      avatar_lbl_set_http: 'Upload a new avatar from the Web',
+      avatar_lbl_set_file: 'Upload a new avatar from my computer',
+      avatar_lbl_url: 'URL to image:',
+      avatar_lbl_url_desc: 'This must start with the <tt>http://</tt> prefix and must be a valid URL. The image will be copied from the existing URL to this server - dynamic avatars are not supported.',
+      avatar_lbl_file: 'Upload file:',
+      avatar_lbl_file_desc: 'Your browser needs to support file uploads for this option to work.',
+      avatar_limits: 'The image cannot be more than %config.avatar_max_size% bytes in size. The maximum dimensions are %config.avatar_max_width% &#215; %config.avatar_max_height% pixels. Allowed formats are PNG, GIF, and JPEG.',
+      avatar_delete_success: 'Your avatar has been deleted.',
+      avatar_bad_write: 'Either the remote server had trouble finding the image, or your image exceeded the allowed file size.',
+      avatar_bad_filetype: 'The file you selected is invalid. You must choose a file in PNG, JPEG, or GIF format.',
+      avatar_disallowed_animation: 'You have chosen an animated image, which is not allowed. Please choose a non-animated image.',
+      avatar_corrupt_image: 'The image you selected is corrupt. Please choose another image.',
+      avatar_too_large: 'The image you uploaded exceeds the maximum dimensions (%config.avatar_max_width% &#215; %config.avatar_max_height%px) allowed on this site. Please choose another image.',
+      avatar_move_failed: 'Your image was accepted, but there was a problem moving the image file to the correct location.',
+      avatar_upload_success: 'Your avatar has been updated.',
+      avatar_file_too_large: 'The image you uploaded exceeds the maximum file size allowed for avatars on this site.',
+    },
+    groupcp: {
+      status_mod: 'You are a moderator of this group.',
+      status_member: 'You are a member of this group.',
+      status_not_member: 'You are not a member of this group.',
+      
+      err_state_system_group: 'Because this is a system group, you can\'t make it open or allow membership requests.',
+      err_user_not_found: 'The username you entered could not be found.',
+      
+      type_hidden: 'Hidden group',
+      type_closed: 'Closed group',
+      type_request: 'Members can request to join',
+      type_open: 'Anyone can join',
+      
+      lbl_current_memberships: 'Current group memberships:',
+      lbl_non_memberships: 'Groups you are outside of:',
+      lbl_group_name: 'Group name:',
+      lbl_status: 'Membership status:',
+      lbl_state: 'Group state:',
+      lbl_make_mod: 'User is a group moderator',
+      lbl_username: 'Username:',
+      lbl_moderator: 'Group moderator:',
+      
+      msg_membership_requested: 'A request has been sent to the moderator(s) of this group to add you.',
+      msg_status_pending: '(Your request to join is awaiting approval)',
+      msg_no_mods: 'This group has no moderators.',
+      msg_no_members: 'This group has no members.',
+      msg_system_group: '(system group)',
+      msg_pending_updated: 'Pending members status updated successfully.',
+      msg_state_updated: 'The group state was updated.',
+      msg_user_already_in_mod_updated: 'The user "%username%" is already in this group, so their moderator status was updated.',
+      msg_user_already_in: 'The user "%username%" is already in this group.',
+      msg_user_added: 'The user "%username%" has been added to this usergroup.',
+      msg_self_added: 'You have been added to this group.',
+      
+      btn_view: 'View information',
+      btn_request_join: 'Request membership',
+      btn_join: 'Join this group',
+      btn_approve_pending: 'Approve membership',
+      btn_reject_pending: 'Reject membership',
+      btn_remove_selected: 'Remove selected users',
+      btn_add_member: 'Add member',
+      
+      grp_administrators: 'Administrators',
+      grp_moderators: 'Moderators',
+      
+      th_select_group: 'Group membership details',
+      th_group_info: 'Group information',
+      th_pending_memberships: 'Pending memberships',
+      th_group_members: 'Group members',
+      th_group_mods: 'Group moderators',
+      th_add_member: 'Add a new member to this group',
+      th_username: 'Username',
+      th_email: 'E-mail',
+      th_reg_time: 'Registered',
+      th_comments: 'Total comments',
+      th_select: 'Select',
+      th_remove: 'Remove?',
+    },
+    privmsgs: {
+      err_need_login: 'You need to be <a href="%login_link%">logged in</a> to view private messages.',
+      err_not_authorized_read: 'You are not authorized to view this message.',
+      err_not_authorized_edit: 'You are not authorized to alter this message.',
+      err_send_submit: 'Your message could not be sent because the following problems were encountered:',
+      err_need_username: 'Please enter the username to which you want to send your message.',
+      err_need_subject: 'Please enter a subject for your message.',
+      err_need_message: 'Please enter a message to send.',
+      err_limit_exceeded_title: 'Recipient limit exceeded',
+      err_limit_exceeded_body: 'You can only send this message to a maximum of %limit% users.',
+      err_folder_not_exist: 'The folder "%folder_name%" does not exist. Return to your <a href="%inbox_url%">inbox</a>.',
+      
+      msg_message_status: 'Message status',
+      msg_message_moved: 'Your message has been moved to the folder "%folder%".',
+      msg_message_deleted: 'The message has been deleted.',
+      msg_message_sent: 'Your message has been sent. You may edit the message if you wish; one copy for each recipient will be in your outbox until each recipient has read it. Return to your <a href="%inbox_link%">inbox</a>.',
+      msg_draft_saved: 'Your message has been saved to your Drafts folder.',
+      msg_no_messages: 'No messages in this folder.',
+      
+      lbl_message_from: 'Private message from %sender%',
+      lbl_subject: 'Subject:',
+      lbl_date: 'Date:',
+      lbl_message: 'Message:',
+      lbl_compose_th: 'Compose new private message',
+      lbl_compose_to: 'To:',
+      lbl_compose_to_max: 'Separate multiple names with a single comma; you may send this message to up to <b>%limit%</b> users.',
+      lbl_edit_th: 'Edit draft',
+      
+      btn_send_reply: 'Send reply',
+      btn_archive: 'Archive message',
+      btn_return_to_inbox: 'Return to inbox',
+      btn_send: 'Send message',
+      btn_savedraft: 'Save as draft',
+      btn_archive_selected: 'Archive selected',
+      btn_delete_selected: 'Delete selected',
+      btn_delete_all: 'Delete all',
+      btn_compose: 'New message',
+      
+      sidebar_th_privmsgs: 'Private messages',
+      folder_inbox: 'Inbox',
+      folder_outbox: 'Outbox',
+      folder_sent: 'Sent items',
+      folder_drafts: 'Drafts',
+      folder_archive: 'Archive',
+      sidebar_th_buddies: 'Buddies',
+      sidebar_friend_list: 'Friend list',
+      sidebar_foe_list: 'Foe list',
+      
+      folder_th_foldername: 'Folder:',
+      folder_th_to: 'To',
+      folder_th_from: 'From',
+      folder_th_subject: 'Subject',
+      folder_th_date: 'Date',
+      folder_th_mark: 'Mark',
+      
+      // AJAX interface
+      ajax_err_need_js: 'It looks like your browser doesn\'t have support for Javascript. You\'ll need to have Javascript support in order to use the new Private Message interface. You can also switch to the <a href="%basic_link%">old interface</a>, which doesn\'t require Javascript support.',
+      ajax_err_json: 'The server had a problem processing your request.',
+      
+      ajax_btn_compose: 'Compose message',
+      ajax_btn_archive: 'Archive',
+      ajax_btn_unarchive: 'Restore to inbox',
+      ajax_btn_mark_read: 'Mark as read',
+      ajax_btn_mark_unread: 'Mark as unread',
+      ajax_btn_delete: 'Move to trash',
+      ajax_btn_delete_forever: 'Delete forever',
+      ajax_btn_refresh: 'Refresh',
+      ajax_btn_reply: 'Reply',
+      
+      ajax_folder_inbox: 'Inbox',
+      ajax_folder_starred: 'Starred',
+      ajax_folder_sent: 'Sent messages',
+      ajax_folder_drafts: 'Drafts',
+      ajax_folder_archive: 'Archive',
+      ajax_folder_trash: 'Trash',
+      
+      ajax_msg_loading: 'Loading...',
+      
+      ajax_lbl_sender: '%sender% to %recipient%',
+      ajax_me: 'me',
+      
+      ajax_teaser_inbox: 'No new mail.',
+      ajax_teaser_starred: 'You haven\'t starred any messages yet. Starring a message lets you give it a special status that separates it from the rest of your mail so it\'s easier to find. You can star a message by clicking the small gray star next to a message in your inbox or archive.',
+      ajax_teaser_sent: 'You haven\'t sent any messages yet. When you send a message, a copy of it will appear here.',
+      
+      ajax_no_subject: '[No subject]',
+    }
+  }
+};
+
+// All done! :-)
+
--- a/plugins/PrivateMessages.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/PrivateMessages.php	Wed Dec 26 00:37:26 2007 -0500
@@ -22,10 +22,10 @@
  
 global $db, $session, $paths, $template, $plugins; // Common objects
 
-$plugins->attachHook('base_classes_initted', '
+$plugins->attachHook('session_started', '
   global $paths;
     $paths->add_page(Array(
-      \'name\'=>\'Private Messages\',
+      \'name\'=>\'specialpage_private_messages\',
       \'urlname\'=>\'PrivateMessages\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
@@ -35,9 +35,10 @@
 function page_Special_PrivateMessages()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
+  global $lang;
   if ( !$session->user_logged_in )
   {
-    die_friendly('Access denied', '<p>You need to <a href="'.makeUrlNS('Special', 'Login/'.$paths->page).'">log in</a> to view your private messages.</p>');
+    die_friendly($lang->get('etc_access_denied_short'), '<p>' . $lang->get('privmsgs_err_need_login', array('login_link' => makeUrlNS('Special', 'Login/' . $paths->page))) . '</p>');
   }
   $argv = Array();
   $argv[] = $paths->getParam(0);
@@ -67,7 +68,7 @@
       $db->free_result();
       if ( ($r['message_to'] != $session->username && $r['message_from'] != $session->username ) || $r['folder_name']=='drafts' )
       {
-        die_friendly('Access denied', '<p>You are not authorized to view this message.</p>');
+        die_friendly($lang->get('etc_access_denied_short'), '<p>' . $lang->get('privmsgs_err_not_authorized_read') . '</p>');
       }
       if ( $r['message_to'] == $session->username )
       {
@@ -83,17 +84,17 @@
       ?>
         <br />
         <div class="tblholder"><table border="0" width="100%" cellspacing="1" cellpadding="4">
-          <tr><th colspan="2">Private message from <?php echo $r['message_from']; ?></th></tr>
-          <tr><td class="row1">Subject:</td><td class="row1"><?php echo $r['subject']; ?></td></tr>
-          <tr><td class="row2">Date:</td><td class="row2"><?php echo date('M j, Y G:i', $r['date']); ?></td></tr>
-          <tr><td class="row1">Message:</td><td class="row1"><?php echo RenderMan::render($r['message_text']);
+          <tr><th colspan="2"><?php echo $lang->get('privmsgs_lbl_message_from', array('sender' => htmlspecialchars($r['message_from']))); ?></th></tr>
+          <tr><td class="row1"><?php echo $lang->get('privmsgs_lbl_subject') ?></td><td class="row1"><?php echo $r['subject']; ?></td></tr>
+          <tr><td class="row2"><?php echo $lang->get('privmsgs_lbl_date') ?></td><td class="row2"><?php echo date('M j, Y G:i', $r['date']); ?></td></tr>
+          <tr><td class="row1"><?php echo $lang->get('privmsgs_lbl_message') ?></td><td class="row1"><?php echo RenderMan::render($r['message_text']);
           if ( $r['signature'] != '' )
           {
             echo '<hr style="margin-left: 1em; width: 200px;" />';
             echo RenderMan::render($r['signature']);
           }
           ?></td></tr>
-          <tr><td colspan="2" class="row3"><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Compose/ReplyTo/'.$id); ?>">Send reply</a>  |  <a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Delete/'.$id); ?>">Delete message</a>  |  <?php if($r['folder_name'] != 'archive') { ?><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Move/'.$id.'/Archive'); ?>">Archive message</a>  |  <?php } ?><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Folder/Inbox') ?>">Return to inbox</a></td></tr>
+          <tr><td colspan="2" class="row3"><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Compose/ReplyTo/'.$id); ?>"><?php echo $lang->get('privmsgs_btn_send_reply'); ?></a>  |  <a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Delete/'.$id); ?>">Delete message</a>  |  <?php if($r['folder_name'] != 'archive') { ?><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Move/'.$id.'/Archive'); ?>"><?php echo $lang->get('privmsgs_btn_archive'); ?></a>  |  <?php } ?><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Folder/Inbox') ?>"><?php echo $lang->get('privmsgs_btn_return_to_inbox'); ?></a></td></tr>
         </table></div>
       <?php
       $template->footer();              
@@ -113,7 +114,7 @@
       $db->free_result();
       if ( $r['message_to'] != $session->username )
       {
-        die_friendly('Access denied', '<p>You are not authorized to alter this message.</p>');
+        die_friendly($lang->get('etc_access_denied_short'), '<p>' . $lang->get('privmsgs_err_not_authorized_edit') . '</p>');
       }
       $fname = $argv[2];
       if ( !$fname || ( $fname != 'Inbox' && $fname != 'Outbox' && $fname != 'Sent' && $fname != 'Drafts' && $fname != 'Archive' ) )
@@ -126,7 +127,7 @@
       {
         $db->_die('The message was not successfully moved.');
       }
-      die_friendly('Message status', '<p>Your message has been moved to the folder "'.$fname.'".</p><p><a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">Return to inbox</a></p>');
+      die_friendly($lang->get('privmsgs_msg_message_status'), '<p>' . $lang->get('privmsgs_msg_message_moved', array('folder' => $fname)) . '</p><p><a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">' . $lang->get('privmsgs_btn_return_to_inbox') . '</a></p>');
       break;
     case 'Delete':
       $id = $argv[1];
@@ -142,7 +143,7 @@
       $r = $db->fetchrow();
       if ( $r['message_to'] != $session->username )
       {
-        die_friendly('Access denied', '<p>You are not authorized to delete this message.</p>');
+        die_friendly($lang->get('etc_access_denied_short'), '<p>You are not authorized to delete this message.</p>');
       }
       $q = $db->sql_query('DELETE FROM '.table_prefix.'privmsgs WHERE message_id='.$id.';');
       if ( !$q )
@@ -150,55 +151,99 @@
         $db->_die('The message was not successfully deleted.');
       }
       $db->free_result();
-      die_friendly('Message status', '<p>The message has been deleted.</p><p><a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">Return to inbox</a></p>');
+      die_friendly($lang->get('privmsgs_msg_message_status'), '<p>' . $lang->get('privmsgs_msg_message_deleted') . '</p><p><a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">' . $lang->get('privmsgs_btn_return_to_inbox') . '</a></p>');
       break;
     case 'Compose':
       if ( $argv[1]=='Send' && isset($_POST['_send']) )
       {
         // Check each POST DATA parameter...
-        if(!isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == '')) die_friendly('Sending of message failed', '<p>Please enter the username to which you want to send your message.</p>');
-        if(!isset($_POST['subject']) || ( isset($_POST['subject']) && $_POST['subject'] == '')) die_friendly('Sending of message failed', '<p>Please enter a subject for your message.</p>');
-        if(!isset($_POST['message']) || ( isset($_POST['message']) && $_POST['message'] == '')) die_friendly('Sending of message failed', '<p>Please enter a message to send.</p>');
-        $namelist = $_POST['to'];
-        $namelist = str_replace(', ', ',', $namelist);
-        $namelist = explode(',', $namelist);
-        foreach($namelist as $n) { $n = $db->escape($n); }
-        $subject = RenderMan::preprocess_text($_POST['subject']);
-        $message = RenderMan::preprocess_text($_POST['message']);
-        $base_query = 'INSERT INTO '.table_prefix.'privmsgs(message_from,message_to,date,subject,message_text,folder_name,message_read) VALUES';
-        foreach($namelist as $n)
+        $errors = array();
+        if(!isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_username');
+        }
+        if(!isset($_POST['subject']) || ( isset($_POST['subject']) && $_POST['subject'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_subject');
+        }
+        if(!isset($_POST['message']) || ( isset($_POST['message']) && $_POST['message'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_message');
+        }
+        if ( count($errors) < 1 )
         {
-          $base_query .= '(\''.$session->username.'\', \''.$n.'\', '.time().', \''.$subject.'\', \''.$message.'\', \'inbox\', 0),';
+          $namelist = $_POST['to'];
+          $namelist = str_replace(', ', ',', $namelist);
+          $namelist = explode(',', $namelist);
+          foreach($namelist as $n) { $n = $db->escape($n); }
+          $subject = RenderMan::preprocess_text($_POST['subject']);
+          $message = RenderMan::preprocess_text($_POST['message']);
+          $base_query = 'INSERT INTO '.table_prefix.'privmsgs(message_from,message_to,date,subject,message_text,folder_name,message_read) VALUES';
+          foreach($namelist as $n)
+          {
+            $base_query .= '(\''.$session->username.'\', \''.$n.'\', '.time().', \''.$subject.'\', \''.$message.'\', \'inbox\', 0),';
+          }
+          $base_query = substr($base_query, 0, strlen($base_query)-1) . ';';
+          $result = $db->sql_query($base_query);
+          $db->free_result();
+          if ( !$result )
+          {
+            $db->_die('The message could not be sent.');
+          }
+          else
+          {
+            die_friendly($lang->get('privmsgs_msg_message_status'), '<p>' . $lang->get('privmsgs_msg_message_sent', array('inbox_link' => makeUrlNS('Special', 'PrivateMessages/Folder/Inbox'))) . '</p>');
+          }
+          return;
         }
-        $base_query = substr($base_query, 0, strlen($base_query)-1) . ';';
-        $result = $db->sql_query($base_query);
-        $db->free_result();
-        if(!$result) $db->_die('The message could not be sent.');
-        else die_friendly('Message status', '<p>Your message has been sent. You may edit the message if you wish; one copy for each recipient will be in your outbox until each recipient has read it. Return to your <a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">inbox</a>.</p>');
-        return;
-      } elseif($argv[1]=='Send' && isset($_POST['_savedraft'])) {
-        // Check each POST DATA parameter...
-        if(!isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == '')) die_friendly('Sending of message failed', '<p>Please enter the username to which you want to send your message.</p>');
-        if(!isset($_POST['subject']) || ( isset($_POST['subject']) && $_POST['subject'] == '')) die_friendly('Sending of message failed', '<p>Please enter a subject for your message.</p>');
-        if(!isset($_POST['message']) || ( isset($_POST['message']) && $_POST['message'] == '')) die_friendly('Sending of message failed', '<p>Please enter a message to send.</p>');
-        $namelist = $_POST['to'];
-        $namelist = str_replace(', ', ',', $namelist);
-        $namelist = explode(',', $namelist);
-        foreach($namelist as $n) { $n = $db->escape($n); }
-        if(count($namelist) > MAX_PMS_PER_BATCH && $session->get_permssions('mod_misc')) die_friendly('Limit exceeded', '<p>You can only send this message to a maximum of '.MAX_PMS_PER_BATCH.' users.</p>');
-        $subject = $db->escape($_POST['subject']);
-        $message = RenderMan::preprocess_text($_POST['message']);
-        $base_query = 'INSERT INTO '.table_prefix.'privmsgs(message_from,message_to,date,subject,message_text,folder_name,message_read) VALUES';
-        foreach($namelist as $n)
+      }
+      else if ( $argv[1] == 'Send' && isset($_POST['_savedraft'] ) )
+      {
+        $errors = array();
+        if ( !isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == '') )
+        {
+          $errors[] = $lang->get('privmsgs_err_need_username');
+        }
+        if ( !isset($_POST['subject']) || ( isset($_POST['subject']) && $_POST['subject'] == '') )
+        {
+          $errors[] = $lang->get('privmsgs_err_need_subject');
+        }
+        if ( !isset($_POST['message']) || ( isset($_POST['message']) && $_POST['message'] == '') )
+        {
+          $errors[] = $lang->get('privmsgs_err_need_message');
+        }
+        if ( count($errors) < 1 )
         {
-          $base_query .= '(\''.$session->username.'\', \''.$n.'\', '.time().', \''.$subject.'\', \''.$message.'\', \'drafts\', 0),';
+          $namelist = $_POST['to'];
+          $namelist = str_replace(', ', ',', $namelist);
+          $namelist = explode(',', $namelist);
+          foreach($namelist as $n)
+          {
+            $n = $db->escape($n);
+          }
+          if ( count($namelist) > MAX_PMS_PER_BATCH && !$session->get_permssions('mod_misc') )
+          {
+            die_friendly($lang->get('privmsgs_err_limit_exceeded_title'), '<p>' . $lang->get('privmsgs_err_limit_exceeded_body', array('limit' => MAX_PMS_PER_BATCH)) . '</p>');
+          }
+          $subject = $db->escape($_POST['subject']);
+          $message = RenderMan::preprocess_text($_POST['message']);
+          $base_query = 'INSERT INTO '.table_prefix.'privmsgs(message_from,message_to,date,subject,message_text,folder_name,message_read) VALUES';
+          foreach($namelist as $n)
+          {
+            $base_query .= '(\''.$session->username.'\', \''.$n.'\', '.time().', \''.$subject.'\', \''.$message.'\', \'drafts\', 0),';
+          }
+          $base_query = substr($base_query, 0, strlen($base_query) - 1) . ';';
+          $result = $db->sql_query($base_query);
+          $db->free_result();
+          if ( !$result )
+          {
+            $db->_die('The message could not be saved.');
+          }
         }
-        $base_query = substr($base_query, 0, strlen($base_query)-1) . ';';
-        $result = $db->sql_query($base_query);
-        $db->free_result();
-        if(!$result) $db->_die('The message could not be saved.');
-      } elseif(isset($_POST['_inbox'])) {
-        header('Location: '.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox'));
+      }
+      else if(isset($_POST['_inbox']))
+      {
+        redirect(makeUrlNS('Special', 'PrivateMessages/Folder/Inbox'), '', '', 0);
       }
       if($argv[1] == 'ReplyTo' && preg_match('#^([0-9]+)$#', $argv[2]))
       {
@@ -207,12 +252,17 @@
         $subj = '';
         $id = $argv[2];
         $q = $db->sql_query('SELECT p.message_from, p.message_to, p.subject, p.message_text, p.date, p.folder_name, u.signature FROM '.table_prefix.'privmsgs AS p LEFT JOIN '.table_prefix.'users AS u ON (p.message_from=u.username) WHERE message_id='.$id.';');
-        if(!$q) $db->_die('The message data could not be selected.');
+        if ( !$q )
+          $db->_die('The message data could not be selected.');
+        
         $r = $db->fetchrow();
         $db->free_result();
-        if( ($r['message_to'] != $session->username && $r['message_from'] != $session->username ) || $r['folder_name']=='drafts' ) die_friendly('Access denied', '<p>You are not authorized to view the contents of this message.</p>');
+        if ( ($r['message_to'] != $session->username && $r['message_from'] != $session->username ) || $r['folder_name'] == 'drafts' )
+        {
+          die_friendly($lang->get('etc_access_denied_short'), '<p>You are not authorized to view the contents of this message.</p>');
+        }
         $subj = 'Re: ' . $r['subject'];
-        $text = "\n\n\nOn ".date('M j, Y G:i', $r['date']).", ".$r['message_from']." wrote:\n> ".str_replace("\n", "\n> ", $r['message_text']); // Way less complicated than using a regex ;-)
+        $text = "\n\n\nOn " . date('M j, Y G:i', $r['date']) . ", " . $r['message_from'] . " wrote:\n> " . str_replace("\n", "\n> ", $r['message_text']); // Way less complicated than using a regex ;-)
         
         $tbuf = $text;
         while( preg_match("/\n([\> ]*?)\> \>/", $text) )
@@ -224,26 +274,47 @@
         }
         
         $to = $r['message_from'];
-      } else {
-        if(( $argv[1]=='to' || $argv[1]=='To' ) && $argv[2]) $to = $argv[2];
-        else $to = '';
+      }
+      else
+      {
+        if ( ( $argv[1]=='to' || $argv[1]=='To' ) && $argv[2] )
+        {
+          $to = htmlspecialchars($argv[2]);
+        }
+        else
+        {
+          $to = '';
+        }
         $text = '';
         $subj = '';
       }
         $template->header();
         userprefs_show_menu();
-        echo '<form action="'.makeUrlNS('Special', 'PrivateMessages/Compose/Send').'" method="post" onsubmit="if(!submitAuthorized) return false;">';
+        if ( isset($errors) && count($errors) > 0 )
+        {
+          echo '<div class="warning-box">
+                  ' . $lang->get('privmsgs_err_send_submit') . '
+                  <ul>
+                    <li>' . implode('</li><li>', $errors) . '</li>
+                  </ul>
+                </div>';
+        }
+        echo '<form action="'.makeUrlNS('Special', 'PrivateMessages/Compose/Send').'" method="post">';
+        
+        if ( isset($_POST['_savedraft']) )
+        {
+          echo '<div class="info-box">' . $lang->get('privmsgs_msg_draft_saved') . '</div>';
+        }
         ?>
         <br />
         <div class="tblholder"><table border="0" width="100%" cellspacing="1" cellpadding="4">
           <tr>
-            <th colspan="2">Compose new private message</th>
+            <th colspan="2"><?php echo $lang->get('privmsgs_lbl_compose_th'); ?></th>
           </tr>
           <tr>
             <td class="row1">
-              To:<br />
-              <small>Separate multiple names with a single comma; you<br />
-                     may send this message to up to <b><?php echo (string)MAX_PMS_PER_BATCH; ?></b> users.</small>
+              <?php echo $lang->get('privmsgs_lbl_compose_to'); ?><br />
+              <small><?php echo $lang->get('privmsgs_lbl_compose_to_max', array('limit' => MAX_PMS_PER_BATCH)); ?></small>
             </td>
             <td class="row1">
               <?php echo $template->username_field('to', (isset($_POST['_savedraft'])) ? $_POST['to'] : $to ); ?>
@@ -251,12 +322,37 @@
           </tr>
           <tr>
             <td class="row2">
-              Subject:
+              <?php echo $lang->get('privmsgs_lbl_subject'); ?>
             </td>
             <td class="row2">
-              <input name="subject" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['subject']); else echo $subj; ?>" /></td></tr>
-          <tr><td class="row1">Message:</td><td class="row1" style="min-width: 80%;"><textarea rows="20" cols="40" name="message" style="width: 100%;"><?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['message']); else echo $text; ?></textarea></td></tr>
-          <tr><th colspan="2"><input type="submit" name="_send" value="Send message" />  <input type="submit" name="_savedraft" value="Save as draft" /> <input type="submit" name="_inbox" value="Back to Inbox" /></th></tr>
+              <input name="subject" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['subject']); else echo $subj; ?>" />
+            </td>
+          </tr>
+          <tr>
+            <td class="row1">
+              <?php echo $lang->get('privmsgs_lbl_message'); ?>
+            </td>
+            <td class="row1" style="min-width: 80%;">
+              <?php
+                if ( isset($_POST['_savedraft']) )
+                {
+                  $content = htmlspecialchars($_POST['message']);
+                }
+                else
+                {
+                  $content =& $text;
+                }
+                echo $template->tinymce_textarea('message', $content, 20, 40);
+              ?>
+            </td>
+          </tr>
+          <tr>
+            <th class="subhead" colspan="2">
+              <input type="submit" name="_send" value="<?php echo $lang->get('privmsgs_btn_send'); ?>" />
+              <input type="submit" name="_savedraft" value="<?php echo $lang->get('privmsgs_btn_savedraft'); ?>" />
+              <input type="submit" name="_inbox" value="<?php echo $lang->get('privmsgs_btn_return_to_inbox'); ?>" />
+            </th>
+          </tr>
         </table></div>
         <?php
         echo '</form>';
@@ -264,61 +360,162 @@
       break;
     case 'Edit':
       $id = $argv[1];
-      if(!preg_match('#^([0-9]+)$#', $id)) die_friendly('Message error', '<p>Invalid message ID</p>');
+      if ( !preg_match('#^([0-9]+)$#', $id) )
+      {
+        die_friendly('Message error', '<p>Invalid message ID</p>');
+      }
       $q = $db->sql_query('SELECT message_from, message_to, subject, message_text, date, folder_name, message_read FROM '.table_prefix.'privmsgs WHERE message_id='.$id.'');
-      if(!$q) $db->_die('The message data could not be selected.');
+      if ( !$q )
+      {
+        $db->_die('The message data could not be selected.');
+      }
       $r = $db->fetchrow();
       $db->free_result();
-      if($r['message_from'] != $session->username || $r['message_read'] == 1 ) die_friendly('Access denied', '<p>You are not authorized to edit this message.</p>');
+      if ( $r['message_from'] != $session->username || $r['message_read'] == 1 )
+      {
+        die_friendly($lang->get('etc_access_denied_short'), '<p>You are not authorized to edit this message.</p>');
+      }
       $fname = $argv[2];
       
       if(isset($_POST['_send']))
       {
         // Check each POST DATA parameter...
-        if(!isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == '')) die_friendly('Sending of message failed', '<p>Please enter the username to which you want to send your message.</p>');
-        if(!isset($_POST['subject']) || ( isset($_POST['subject']) && $_POST['subject'] == '')) die_friendly('Sending of message failed', '<p>Please enter a subject for your message.</p>');
-        if(!isset($_POST['message']) || ( isset($_POST['message']) && $_POST['message'] == '')) die_friendly('Sending of message failed', '<p>Please enter a message to send.</p>');
-        $namelist = $_POST['to'];
-        $namelist = str_replace(', ', ',', $namelist);
-        $namelist = explode(',', $namelist);
-        foreach($namelist as $n) { $n = $db->escape($n); }
-        $subject = RenderMan::preprocess_text($_POST['subject']);
-        $message = RenderMan::preprocess_text($_POST['message']);
-        $base_query = 'UPDATE '.table_prefix.'privmsgs SET subject=\''.$subject.'\',message_to=\''.$namelist[0].'\',message_text=\''.$message.'\',folder_name=\'inbox\' WHERE message_id='.$id.';';
-        $result = $db->sql_query($base_query);
-        $db->free_result();
-        if(!$result) $db->_die('The message could not be sent.');
-        else die_friendly('Message status', '<p>Your message has been sent. You may edit the message if you wish; one copy for each recipient will be in your outbox until each recipient has read it. Return to your <a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">inbox</a>.</p>');
-        return;
-      } elseif(isset($_POST['_savedraft'])) {
+        $errors = array();
+        if(!isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_username');
+        }
+        if(!isset($_POST['subject']) || ( isset($_POST['subject']) && $_POST['subject'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_subject');
+        }
+        if(!isset($_POST['message']) || ( isset($_POST['message']) && $_POST['message'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_message');
+        }
+        if ( count($errors) < 1 )
+        {
+          $namelist = $_POST['to'];
+          $namelist = str_replace(', ', ',', $namelist);
+          $namelist = explode(',', $namelist);
+          foreach ($namelist as $n)
+          {
+            $n = $db->escape($n);
+          }
+          $subject = RenderMan::preprocess_text($_POST['subject']);
+          $message = RenderMan::preprocess_text($_POST['message']);
+          $base_query = 'UPDATE '.table_prefix.'privmsgs SET subject=\''.$subject.'\',message_to=\''.$namelist[0].'\',message_text=\''.$message.'\',folder_name=\'inbox\' WHERE message_id='.$id.';';
+          $result = $db->sql_query($base_query);
+          $db->free_result();
+          if ( !$result )
+          {
+            $db->_die('The message could not be sent.');
+          }
+          else
+          {
+            die_friendly($lang->get('privmsgs_msg_message_status'), '<p>' . $lang->get('privmsgs_msg_message_sent', array('inbox_link' => makeUrlNS('Special', 'PrivateMessages/Folder/Inbox'))) . '</p>');
+          }
+          return;
+        }
+      }
+      else if ( isset($_POST['_savedraft']) )
+      {
         // Check each POST DATA parameter...
-        if(!isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == '')) die_friendly('Sending of message failed', '<p>Please enter the username to which you want to send your message.</p>');
-        if(!isset($_POST['subject']) || ( isset($_POST['subject']) && $_POST['subject'] == '')) die_friendly('Sending of message failed', '<p>Please enter a subject for your message.</p>');
-        if(!isset($_POST['message']) || ( isset($_POST['message']) && $_POST['message'] == '')) die_friendly('Sending of message failed', '<p>Please enter a message to send.</p>');
-        $namelist = $_POST['to'];
-        $namelist = str_replace(', ', ',', $namelist);
-        $namelist = explode(',', $namelist);
-        foreach($namelist as $n) { $n = $db->escape($n); }
-        $subject = $db->escape($_POST['subject']);
-        $message = RenderMan::preprocess_text($_POST['message']);
-        $base_query = 'UPDATE '.table_prefix.'privmsgs SET subject=\''.$subject.'\',message_to=\''.$namelist[0].'\',message_text=\''.$message.'\' WHERE message_id='.$id.';';
-        $result = $db->sql_query($base_query);
-        $db->free_result();
-        if(!$result) $db->_die('The message could not be saved.');
+        $errors = array();
+        if(!isset($_POST['to']) || ( isset($_POST['to']) && $_POST['to'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_username');
+        }
+        if(!isset($_POST['subject']) || ( isset($_POST['subject']) && $_POST['subject'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_subject');
+        }
+        if(!isset($_POST['message']) || ( isset($_POST['message']) && $_POST['message'] == ''))
+        {
+          $errors[] = $lang->get('privmsgs_err_need_message');
+        }
+        if ( count($errors) < 1 )
+        {
+          $namelist = $_POST['to'];
+          $namelist = str_replace(', ', ',', $namelist);
+          $namelist = explode(',', $namelist);
+          foreach ( $namelist as $n )
+          {
+            $n = $db->escape($n);
+          }
+          $subject = $db->escape($_POST['subject']);
+          $message = RenderMan::preprocess_text($_POST['message']);
+          $base_query = 'UPDATE '.table_prefix.'privmsgs SET subject=\''.$subject.'\',message_to=\''.$namelist[0].'\',message_text=\''.$message.'\' WHERE message_id='.$id.';';
+          $result = $db->sql_query($base_query);
+          $db->free_result();
+          if ( !$result )
+          {
+            $db->_die('The message could not be saved.');
+          }
+        }
       }
-        if($argv[1]=='to' && $argv[2]) $to = $argv[2];
-        else $to = '';
+        if ( $argv[1]=='to' && $argv[2] )
+        {
+          $to = htmlspecialchars($argv[2]);
+        }
+        else
+        {
+          $to = '';
+        }
         $template->header();
         userprefs_show_menu();
         echo '<form action="'.makeUrlNS('Special', 'PrivateMessages/Edit/'.$id).'" method="post">';
+        
+        if ( isset($_POST['_savedraft']) )
+        {
+          echo '<div class="info-box">' . $lang->get('privmsgs_msg_draft_saved') . '</div>';
+        }
         ?>
         <br />
         <div class="tblholder"><table border="0" width="100%" cellspacing="1" cellpadding="4">
-          <tr><th colspan="2">Edit draft</th></tr>
-          <tr><td class="row1">To:<br /><small>Separate multiple names with a single comma</small></td><td class="row1"><input name="to" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['to']); else echo $r['message_to']; ?>" /></td></tr>
-          <tr><td class="row2">Subject:</td><td class="row2"><input name="subject" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['subject']); else echo $r['subject']; ?>" /></td></tr>
-          <tr><td class="row1">Message:</td><td class="row1"><textarea rows="20" cols="40" name="message" style="width: 100%;"><?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['message']); else echo $r['message_text']; ?></textarea></td></tr>
-          <tr><th colspan="2"><input type="submit" name="_send" value="Send message" />  <input type="submit" name="_savedraft" value="Save as draft" /></th></tr>
+          <tr><th colspan="2"><?php echo $lang->get('privmsgs_lbl_edit_th'); ?></th></tr>
+          <tr>
+            <td class="row1">
+              <?php echo $lang->get('privmsgs_lbl_compose_to'); ?><br />
+              <small><?php echo $lang->get('privmsgs_lbl_compose_to_max', array('limit' => MAX_PMS_PER_BATCH)); ?></small>
+            </td>
+            <td class="row1">
+              <?php echo $template->username_field('to', (isset($_POST['_savedraft'])) ? $_POST['to'] : $r['message_to'] ); ?>
+            </td>
+          </tr>
+          <tr>
+            <td class="row2">
+              <?php echo $lang->get('privmsgs_lbl_subject'); ?>
+            </td>
+            <td class="row2">
+              <input name="subject" type="text" size="30" value="<?php if(isset($_POST['_savedraft'])) echo htmlspecialchars($_POST['subject']); else echo $r['subject']; ?>" />
+            </td>
+          </tr>
+          <tr>
+            <td class="row1">
+              <?php echo $lang->get('privmsgs_lbl_message'); ?>
+            </td>
+            <td class="row1" style="min-width: 80%;">
+              <?php
+                if ( isset($_POST['_savedraft']) )
+                {
+                  $content = htmlspecialchars($_POST['message']);
+                }
+                else
+                {
+                  $content =& $r['message_text'];
+                }
+                echo $template->tinymce_textarea('message', $content, 20, 40);
+              ?>
+            </td>
+          </tr>
+          
+          <tr>
+            <th class="subhead" colspan="2">
+              <input type="submit" name="_send" value="<?php echo $lang->get('privmsgs_btn_send'); ?>" />
+              <input type="submit" name="_savedraft" value="<?php echo $lang->get('privmsgs_btn_savedraft'); ?>" />
+            </th>
+          </tr>
         </table></div>
         <?php
         echo '</form>';
@@ -330,7 +527,10 @@
       switch($argv[1])
       {
         default:
-          echo '<p>The folder "'.$argv[1].'" does not exist. Return to your <a href="'.makeUrlNS('Special', 'PrivateMessages/Folder/Inbox').'">inbox</a>.</p>';
+          echo '<p>' . $lang->get('privmsgs_err_folder_not_exist', array(
+              'folder_name' => htmlspecialchars($argv[1]),
+              'inbox_url' => makeUrlNS('Special', 'PrivateMessages/Folder/Inbox')
+            )) . '</p>';
           break;
         case 'Inbox':
         case 'Outbox':
@@ -342,15 +542,15 @@
           <tr>
           <td style="padding: 0px; width: 120px;" valign="top"  >
           <div class="tblholder" style="width: 120px;"><table border="0" width="120" cellspacing="1" cellpadding="4">
-          <tr><th><small>Private messages</small></th></tr>
-          <tr><td class="row1"><small><a href="<?php echo $session->append_sid('Inbox'); ?>">Inbox</a>    </small></td></tr>
-          <tr><td class="row2"><small><a href="<?php echo $session->append_sid('Outbox'); ?>">Outbox</a>  </small></td></tr>
-          <tr><td class="row1"><small><a href="<?php echo $session->append_sid('Sent'); ?>">Sent Items</a></small></td></tr>
-          <tr><td class="row2"><small><a href="<?php echo $session->append_sid('Drafts'); ?>">Drafts</a>  </small></td></tr>
-          <tr><td class="row1"><small><a href="<?php echo $session->append_sid('Archive'); ?>">Archive</a></small></td></tr>
-          <tr><th><small>Buddies</small></th></tr>
-          <tr><td class="row2"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/FriendList'); ?>">Friend list</a></small></td></tr>
-          <tr><td class="row1"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/FoeList'); ?>">Foe list</a></small></td></tr>
+          <tr><th><small><?php echo $lang->get('privmsgs_sidebar_th_privmsgs'); ?></small></th></tr>
+          <tr><td class="row1"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Folder/Inbox'); ?>"><?php echo $lang->get('privmsgs_folder_inbox'); ?></a></small></td></tr>
+          <tr><td class="row2"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Folder/Outbox'); ?>"><?php echo $lang->get('privmsgs_folder_outbox'); ?></a></small></td></tr>
+          <tr><td class="row1"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Folder/Sent'); ?>"><?php echo $lang->get('privmsgs_folder_sent'); ?></a></small></td></tr>
+          <tr><td class="row2"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Folder/Drafts'); ?>"><?php echo $lang->get('privmsgs_folder_drafts'); ?></a></small></td></tr>
+          <tr><td class="row1"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/Folder/Archive'); ?>"><?php echo $lang->get('privmsgs_folder_archive'); ?></a></small></td></tr>
+          <tr><th><small><?php echo $lang->get('privmsgs_sidebar_th_buddies'); ?></small></th></tr>
+          <tr><td class="row2"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/FriendList'); ?>"><?php echo $lang->get('privmsgs_sidebar_friend_list'); ?></a></small></td></tr>
+          <tr><td class="row1"><small><a href="<?php echo makeUrlNS('Special', 'PrivateMessages/FoeList'); ?>"><?php echo $lang->get('privmsgs_sidebar_foe_list'); ?></a></small></td></tr>
           </table></div>
           </td>
           <td valign="top">
@@ -373,36 +573,88 @@
               $q = $db->sql_query('SELECT p.message_id, p.message_from, p.message_to, p.date, p.subject, p.message_read FROM '.table_prefix.'privmsgs AS p WHERE p.folder_name=\''.$fname.'\' AND p.message_from=\''.$session->username.'\' ORDER BY date DESC;');
               break;
           }
-          if($argv[1] == 'Drafts' || $argv[1] == 'Outbox') $act = 'Edit';
-          else $act = 'View';
-          if(!$q) $db->_die('The private message data could not be selected.');
-          echo '<form action="'.makeUrlNS('Special', 'PrivateMessages/PostHandler').'" method="post"><div class="tblholder"><table border="0" width="100%" cellspacing="1" cellpadding="4"><tr><th colspan="4" style="text-align: left;">Folder: '.$argv[1].'</th></tr><tr><th class="subhead">';
-          if($fname == 'drafts' || $fname == 'Outbox') echo 'To'; else echo 'From';
-          echo '</th><th class="subhead">Subject</th><th class="subhead">Date</th><th class="subhead">Mark</th></tr>';
+          if ( !$q )
+          {
+            $db->_die('The private message data could not be selected.');
+          }
+          if ( $argv[1] == 'Drafts' || $argv[1] == 'Outbox' )
+          {
+            $act = 'Edit';
+          }
+          else
+          {
+            $act = 'View';
+          }
+          echo '<form action="'.makeUrlNS('Special', 'PrivateMessages/PostHandler').'" method="post">
+                  <div class="tblholder">
+                    <table border="0" width="100%" cellspacing="1" cellpadding="4">
+                      <tr>
+                        <th colspan="4" style="text-align: left;">' . $lang->get('privmsgs_folder_th_foldername') . ' ' . $lang->get('privmsgs_folder_' . strtolower($argv[1])) . '</th>
+                      </tr>
+                    <tr>
+                      <th class="subhead">';
+          if ( $fname == 'drafts' || $fname == 'Outbox' )
+          {
+            echo $lang->get('privmsgs_folder_th_to');
+          }
+          else
+          {
+            echo $lang->get('privmsgs_folder_th_from');
+          }
+          echo '</th>
+                <th class="subhead">' . $lang->get('privmsgs_folder_th_subject') . '</th>
+                <th class="subhead">' . $lang->get('privmsgs_folder_th_date') . '</th>
+                <th class="subhead">' . $lang->get('privmsgs_folder_th_mark') . '</th>
+              </tr>';
           if($db->numrows() < 1)
-            echo '<tr><td style="text-align: center;" class="row1" colspan="4">No messages in this folder.</td></tr>';
-          else {
+          {
+            echo '<tr><td style="text-align: center;" class="row1" colspan="4">' . $lang->get('privmsgs_msg_no_messages') . '</td></tr>';
+          }
+          else
+          {
             $cls = 'row2';
-            while($r = $db->fetchrow())
+            while ( $r = $db->fetchrow() )
             {
               if($cls == 'row2') $cls='row1';
               else $cls = 'row2';
               $mto = str_replace(' ', '_', $r['message_to']);
               $mfr = str_replace(' ', '_', $r['message_from']);
               echo '<tr><td class="'.$cls.'"><a href="'.makeUrlNS('User', ( $fname == 'drafts') ? $mto : $mfr).'">';
-              if($fname == 'drafts' || $fname == 'outbox') echo $r['message_to']; else echo $r['message_from'];
+              if ( $fname == 'drafts' || $fname == 'outbox' )
+              {
+                echo $r['message_to'];
+              }
+              else
+              {
+                echo $r['message_from'];
+              }
+              
               echo '</a></td><td class="'.$cls.'"><a href="'.makeUrlNS('Special', 'PrivateMessages/'.$act.'/'.$r['message_id']).'">';
-              if($r['message_read'] == 0) echo '<b>';
+              
+              if ( $r['message_read'] == 0 )
+              {
+                echo '<b>';
+              }
               echo $r['subject'];
-              if($r['message_read'] == 0) echo '</b>';
+              if ( $r['message_read'] == 0 )
+              {
+                echo '</b>';
+              }
               echo '</a></td><td class="'.$cls.'">'.date('M j, Y G:i', $r['date']).'</td><td class="'.$cls.'" style="text-align: center;"><input name="marked_'.$r['message_id'].'" type="checkbox" /></td></tr>';
             }
             $db->free_result();
           }
-          echo '<tr><th style="text-align: right;" colspan="4"><input type="hidden" name="folder" value="'.$fname.'" /><input type="submit" name="archive" value="Archive selected" /> <input type="submit" name="delete" value="Delete selected" /> <input type="submit" name="deleteall" value="Delete all" /></th></tr>';
+          echo '<tr>
+                  <th style="text-align: right;" colspan="4">
+                    <input type="hidden" name="folder" value="'.$fname.'" />
+                    <input type="submit" name="archive" value="' . $lang->get('privmsgs_btn_archive_selected') . '" />
+                    <input type="submit" name="delete" value="' . $lang->get('privmsgs_btn_delete_selected') . '" />
+                    <input type="submit" name="deleteall" value="' . $lang->get('privmsgs_btn_delete_all') . '" />
+                  </th>
+                </tr>';
           echo '</table></div></form>
           <br />
-          <a href="'.makeUrlNS('Special', 'PrivateMessages/Compose/').'">New message</a>
+          <a href="'.makeUrlNS('Special', 'PrivateMessages/Compose/').'">' . $lang->get('privmsgs_btn_compose') . '</a>
           </td></tr></table>';
           break;
       }
--- a/plugins/SpecialAdmin.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/SpecialAdmin.php	Wed Dec 26 00:37:26 2007 -0500
@@ -22,17 +22,17 @@
  
 global $db, $session, $paths, $template, $plugins; // Common objects
 
-$plugins->attachHook('base_classes_initted', '
+$plugins->attachHook('session_started', '
   global $paths;
     $paths->add_page(Array(
-      \'name\'=>\'Administration\',
+      \'name\'=>\'specialpage_administration\',
       \'urlname\'=>\'Administration\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Manage the Sidebar\',
+      \'name\'=>\'specialpage_manage_sidebar\',
       \'urlname\'=>\'EditSidebar\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
--- a/plugins/SpecialCSS.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/SpecialCSS.php	Wed Dec 26 00:37:26 2007 -0500
@@ -22,10 +22,10 @@
  
 global $db, $session, $paths, $template, $plugins; // Common objects
 
-$plugins->attachHook('base_classes_initted', '
+$plugins->attachHook('session_started', '
   global $paths;
     $paths->add_page(Array(
-      \'name\'=>\'CSS\',
+      \'name\'=>\'specialpage_css\',
       \'urlname\'=>\'CSS\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>0,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
--- a/plugins/SpecialGroups.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/SpecialGroups.php	Wed Dec 26 00:37:26 2007 -0500
@@ -20,10 +20,10 @@
  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
  */
 
-$plugins->attachHook('base_classes_initted', '
+$plugins->attachHook('session_started', '
   global $paths;
     $paths->add_page(Array(
-      \'name\'=>\'Group Membership\',
+      \'name\'=>\'specialpage_groupcp\',
       \'urlname\'=>\'Usergroups\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
@@ -34,6 +34,7 @@
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
   global $email; // Import e-mail encryption functions
+  global $lang;
   
   if ( !$session->user_logged_in )
   {
@@ -43,6 +44,7 @@
   }
   
   $template->header();
+  userprefs_show_menu();
   if ( isset($_POST['do_view']) || isset($_POST['do_view_n']) || ( isset($_GET['act']) && isset($_POST['group_id']) ) )
   {
     $gid = ( isset ( $_POST['do_view_n'] ) ) ? intval($_POST['group_id_n']) : intval($_POST['group_id']);
@@ -102,20 +104,20 @@
     }
     
     $status = ( $is_member && $is_mod )
-      ? 'You are a moderator of this group.'
+      ? $lang->get('groupcp_status_mod')
       : ( ( $is_member && !$is_mod ) 
-        ? 'You are a member of this group.'
-        : 'You are not a member of this group.'
+        ? $lang->get('groupcp_status_member')
+        : $lang->get('groupcp_status_not_member')
         );
       
     $can_do_admin_stuff = ( $is_mod || $session->user_level >= USER_LEVEL_ADMIN );
       
     switch ( $row['group_type'] )
     {
-      case GROUP_HIDDEN:  $g_state = 'Hidden group';                break;
-      case GROUP_CLOSED:  $g_state = 'Closed group';                break;
-      case GROUP_REQUEST: $g_state = 'Members can request to join'; break;
-      case GROUP_OPEN:    $g_state = 'Anyone can join';             break;
+      case GROUP_HIDDEN:  $g_state = $lang->get('groupcp_type_hidden'); break;
+      case GROUP_CLOSED:  $g_state = $lang->get('groupcp_type_closed'); break;
+      case GROUP_REQUEST: $g_state = $lang->get('groupcp_type_request'); break;
+      case GROUP_OPEN:    $g_state = $lang->get('groupcp_type_open'); break;
     }
     
     if ( isset($_GET['act']) && $can_do_admin_stuff )
@@ -139,7 +141,7 @@
           $r = $db->fetchrow();
           if ( $r['system_group'] == 1 && ( intval($_POST['group_state']) == GROUP_OPEN || intval($_POST['group_state']) == GROUP_REQUEST ) )
           {
-            echo '<div class="error-box" style="margin-left: 0;">Because this is a system group, you can\'t make it open or allow membership requests.</div>';
+            echo '<div class="error-box" style="margin-left: 0;">' . $lang->get('groupcp_err_state_system_group') . '</div>';
             $error = true;
           }
           if ( !$error )
@@ -148,7 +150,7 @@
             if (!$q)
               $db->_die('SpecialGroups.php, line ' . __LINE__);
             $row['group_type'] = $_POST['group_state'];
-            echo '<div class="info-box" style="margin-left: 0;">The group state was updated.</div>';
+            echo '<div class="info-box" style="margin-left: 0;">' . $lang->get('groupcp_msg_state_updated') . '</div>';
           }
           break;
         case 'adduser':
@@ -160,7 +162,7 @@
             $db->_die('SpecialGroups.php, line ' . __LINE__);
           if ($db->numrows() < 1)
           {
-            echo '<div class="error-box">The username you entered could not be found.</div>';
+            echo '<div class="error-box">' . $lang->get('groupcp_err_user_not_found') . '</div>';
             break;
           }
           $r = $db->fetchrow();
@@ -184,11 +186,11 @@
                 if ( $member['member_id'] == $r['member_id'] )
                   $members[$i]['is_mod'] = (int)$mod;
               }
-              echo '<div class="info-box">The user "' . $username . '" is already in this group, so their moderator status was updated.</div>';
+              echo '<div class="info-box">' . $lang->get('groupcp_msg_user_already_in_mod_updated', array('username' => $username)) . '</div>';
             }
             else
             {
-              echo '<div class="info-box">The user "' . $username . '" is already in this group.</div>';
+              echo '<div class="info-box">' . $lang->get('groupcp_msg_user_already_in', array('username' => $username)) . '</div>';
             }
             break;
           }
@@ -198,7 +200,7 @@
           $q = $db->sql_query('INSERT INTO '.table_prefix.'group_members(group_id,user_id,is_mod) VALUES(' . intval($_POST['group_id']) . ', ' . $uid . ', ' . $mod . ');');
           if (!$q)
             $db->_die('SpecialGroups.php, line ' . __LINE__);
-          echo '<div class="info-box">The user "' . $username . '" has been added to this usergroup.</div>';
+          echo '<div class="info-box">' . $lang->get('groupcp_msg_user_added', array('username' => $username)) . '</div>';
           
           $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,COUNT(c.comment_id) AS num_comments
                                  FROM '.table_prefix.'users AS u
@@ -255,17 +257,17 @@
               }
             }
           }
-          echo '<div class="info-box">Pending members status updated successfully.</div>';
+          echo '<div class="info-box">' . $lang->get('groupcp_msg_pending_updated') . '</div>';
           break;
       }
     }
     
-    if ( isset($_GET['act']) && $_GET['act'] == 'update' && !$is_member && $row['group_type'] == GROUP_OPEN )
+    if ( isset($_GET['act']) && $_GET['act'] == 'update' && !$is_member && $row['group_type'] == GROUP_OPEN && !$can_do_admin_stuff )
     {
       $q = $db->sql_query('INSERT INTO '.table_prefix.'group_members(group_id,user_id) VALUES(' . $gid . ', ' . $session->user_id . ');');
       if (!$q)
         $db->_die('SpecialGroups.php, line ' . __LINE__);
-      echo '<div class="info-box">You have been added to this group.</div>';
+      echo '<div class="info-box">' . $lang->get('groupcp_msg_self_added') . '</div>';
       
       $q = $db->sql_query('SELECT u.username,u.email,u.reg_time,m.member_id,m.user_id,m.is_mod,COUNT(c.comment_id) AS num_comments
                              FROM '.table_prefix.'users AS u
@@ -288,55 +290,60 @@
       
     }
     
-    if ( isset($_GET['act']) && $_GET['act'] == 'update' && !$is_member && $row['group_type'] == GROUP_REQUEST && !$is_pending )
+    if ( isset($_GET['act']) && $_GET['act'] == 'update' && !$is_member && $row['group_type'] == GROUP_REQUEST && !$is_pending && !$can_do_admin_stuff )
     {
       $q = $db->sql_query('INSERT INTO '.table_prefix.'group_members(group_id,user_id,pending) VALUES(' . $gid . ', ' . $session->user_id . ', 1);');
       if (!$q)
         $db->_die('SpecialGroups.php, line ' . __LINE__);
-      echo '<div class="info-box">A request has been sent to the moderator(s) of this group to add you.</div>';
+      echo '<div class="info-box">' . $lang->get('groupcp_msg_membership_requested') . '</div>';
     }
     
     $state_btns = ( $can_do_admin_stuff ) ?
-                  '<label><input type="radio" name="group_state" value="' . GROUP_HIDDEN . '" ' . (( $row['group_type'] == GROUP_HIDDEN ) ? 'checked="checked"' : '' ) . ' /> Hidden group</label>
-                   <label><input type="radio" name="group_state" value="' . GROUP_CLOSED . '" ' . (( $row['group_type'] == GROUP_CLOSED ) ? 'checked="checked"' : '' ) . ' /> Closed group</label>
-                   <label><input type="radio" name="group_state" value="' . GROUP_REQUEST. '" ' . (( $row['group_type'] == GROUP_REQUEST) ? 'checked="checked"' : '' ) . ' /> Members can request to join</label>
-                   <label><input type="radio" name="group_state" value="' . GROUP_OPEN   . '" ' . (( $row['group_type'] == GROUP_OPEN   ) ? 'checked="checked"' : '' ) . ' /> Anybody can join</label>'
+                  '<label><input type="radio" name="group_state" value="' . GROUP_HIDDEN . '" ' . (( $row['group_type'] == GROUP_HIDDEN ) ? 'checked="checked"' : '' ) . ' /> ' . $lang->get('groupcp_type_hidden') . '</label>
+                   <label><input type="radio" name="group_state" value="' . GROUP_CLOSED . '" ' . (( $row['group_type'] == GROUP_CLOSED ) ? 'checked="checked"' : '' ) . ' /> ' . $lang->get('groupcp_type_closed') . '</label>
+                   <label><input type="radio" name="group_state" value="' . GROUP_REQUEST. '" ' . (( $row['group_type'] == GROUP_REQUEST) ? 'checked="checked"' : '' ) . ' /> ' . $lang->get('groupcp_type_request') . '</label>
+                   <label><input type="radio" name="group_state" value="' . GROUP_OPEN   . '" ' . (( $row['group_type'] == GROUP_OPEN   ) ? 'checked="checked"' : '' ) . ' /> ' . $lang->get('groupcp_type_open') . '</label>'
                    : $g_state;
     if ( !$can_do_admin_stuff && $row['group_type'] == GROUP_REQUEST && !$is_member )
     {
       if ( $is_pending )
-        $state_btns .= ' (Your request to join is awaiting approval)';
+        $state_btns .= ' ' . $lang->get('groupcp_msg_status_pending');
       else
-        $state_btns .= ' <input type="submit" value="Request membership" />';
+        $state_btns .= ' <input type="submit" value="' . $lang->get('groupcp_btn_request_join') . '" />';
     }
     
     if ( !$can_do_admin_stuff && $row['group_type'] == GROUP_OPEN && !$is_member )
     {
-      $state_btns .= ' <input type="submit" value="Join this group" />';
+      $state_btns .= ' <input type="submit" value="' . $lang->get('groupcp_btn_join') . '" />';
     }
     
+    $g_name_local = 'groupcp_grp_' . strtolower($row['group_name']);
+    $str = $lang->get($g_name_local);
+    if ( $str != $g_name_local )
+      $row['group_name'] = $str;
+    
     echo '<form action="' . makeUrl($paths->page, 'act=update') . '" method="post" enctype="multipart/form-data">
           <div class="tblholder">
             <table border="0" cellspacing="1" cellpadding="4">
               <tr>
-                <th colspan="2">Group information</th>
+                <th colspan="2">' . $lang->get('groupcp_th_group_info') . '</th>
               </tr>
               <tr>
-                <td class="row2">Group name:</td>
-                <td class="row1">' . $row['group_name'] . ( $row['system_group'] == 1 ? ' (system group)' : '' ) . '</td>
+                <td class="row2">' . $lang->get('groupcp_lbl_group_name') . '</td>
+                <td class="row1">' . $row['group_name'] . ( $row['system_group'] == 1 ? ' ' . $lang->get('groupcp_msg_system_group') : '' ) . '</td>
               </tr>
               <tr>
-                <td class="row2">Membership status:</td>
+                <td class="row2">' . $lang->get('groupcp_lbl_status') . '</td>
                 <td class="row1">' . $status . '</td>
               </tr>
               <tr>
-                <td class="row2">Group state:</td>
+                <td class="row2">' . $lang->get('groupcp_lbl_state') . '</td>
                 <td class="row1">' . $state_btns . '</td>
               </tr>   
               ' . ( ( $is_mod || $session->user_level >= USER_LEVEL_ADMIN ) ? '
               <tr>
                 <th class="subhead" colspan="2">
-                  <input type="submit" value="Save changes" />
+                  <input type="submit" value="' . $lang->get('etc_save_changes') . '" />
                 </th>
               </tr>
               ' : '' ) . '
@@ -348,15 +355,15 @@
     {
       echo '<form action="' . makeUrl($paths->page, 'act=pending') . '" method="post" enctype="multipart/form-data">
             <input name="group_id" value="' . $gid . '" type="hidden" />
-            <h2>Pending memberships</h2>
+            <h2>' . $lang->get('groupcp_th_pending_memberships') . '</h2>
             <div class="tblholder">
             <table border="0" cellspacing="1" cellpadding="4">
               <tr>
-                <th>Username</th>
-                <th>E-mail</th>
-                <th>Registered</th>
-                <th>Total comments</th>
-                <th>Select</th>
+                <th>' . $lang->get('groupcp_th_username') . '</th>
+                <th>' . $lang->get('groupcp_th_email') . '</th>
+                <th>' . $lang->get('groupcp_th_reg_time') . '</th>
+                <th>' . $lang->get('groupcp_th_comments') . '</th>
+                <th>' . $lang->get('groupcp_th_select') . '</th>
               </tr>';
       $cls = 'row2';
       foreach ( $pending as $member )
@@ -378,26 +385,26 @@
             </div>
             <div style="margin: 10px 0 0 auto;">
               With selected: 
-              <input type="submit" name="do_appr_pending" value="Approve membership" />
-              <input type="submit" name="do_reject_pending" value="Reject membership" />
+              <input type="submit" name="do_appr_pending" value="' . $lang->get('groupcp_btn_approve_pending') . '" />
+              <input type="submit" name="do_reject_pending" value="' . $lang->get('groupcp_btn_reject_pending') . '" />
             </div>
             </form>';
     }
     echo '<form action="' . makeUrl($paths->page, 'act=del_users') . '" method="post" enctype="multipart/form-data">
-          <h2>Group members</h2>
+          <h2>' . $lang->get('groupcp_th_group_members') . '</h2>
           <div class="tblholder">
             <table border="0" cellspacing="1" cellpadding="4">
               <tr>
-                <th>Username</th>
-                <th>E-mail</th>
-                <th>Registered</th>
-                <th>Total comments</th>
-                ' . ( ( $can_do_admin_stuff ) ? "
-                <th>Remove?</th>
-                " : '' ) . '
+                <th>' . $lang->get('groupcp_th_username') . '</th>
+                <th>' . $lang->get('groupcp_th_email') . '</th>
+                <th>' . $lang->get('groupcp_th_reg_time') . '</th>
+                <th>' . $lang->get('groupcp_th_comments') . '</th>
+                ' . ( ( $can_do_admin_stuff ) ? '
+                <th>' . $lang->get('groupcp_th_remove') . '</th>
+                ' : '' ) . '
               </tr>
               <tr>
-                <th colspan="5" class="subhead">Group moderators</th>
+                <th colspan="5" class="subhead">' . $lang->get('groupcp_th_group_mods') . '</th>
               </tr>';
     $mod_printed = false;
     $mem_printed = false;
@@ -425,8 +432,8 @@
             </tr>";
     }
     if (!$mod_printed)
-      echo '<tr><td class="' . $cls . '" colspan="5">This group has no moderators.</td></th>';
-    echo '<tr><th class="subhead" colspan="5">Group members</th></tr>';
+      echo '<tr><td class="' . $cls . '" colspan="5">' . $lang->get('groupcp_msg_no_mods') . '</td></th>';
+    echo '<tr><th class="subhead" colspan="5">' . $lang->get('groupcp_th_group_members') . '</th></tr>';
     foreach ( $members as $member )
     {
       if ( $member['is_mod'] == 1 )
@@ -449,12 +456,12 @@
             </tr>";
     }
     if (!$mem_printed)
-      echo '<tr><td class="' . $cls . '" colspan="5">This group has no members.</td></th>';
+      echo '<tr><td class="' . $cls . '" colspan="5">' . $lang->get('groupcp_msg_no_members') . '</td></th>';
     echo '  </table>
           </div>';
     if ( $can_do_admin_stuff )
     {
-      echo "<div style='margin: 10px 0 0 auto;'><input type='submit' name='do_del_user' value='Remove selected users' /></div>";
+      echo "<div style='margin: 10px 0 0 auto;'><input type='submit' name='do_del_user' value=\"" . $lang->get('groupcp_btn_remove_selected') . "\" /></div>";
     }
     echo '<input name="group_id" value="' . $gid . '" type="hidden" />
           </form>';
@@ -464,17 +471,17 @@
               <div class="tblholder">
                 <table border="0" cellspacing="1" cellpadding="4">
                   <tr>
-                    <th colspan="2">Add a new member to this group</th>
+                    <th colspan="2">' . $lang->get('groupcp_th_add_member') . '</th>
                   </tr>
                   <tr>
-                    <td class="row2">Username:</td><td class="row1">' . $template->username_field('add_username') . '</td>
+                    <td class="row2">' . $lang->get('groupcp_lbl_username') . '</td><td class="row1">' . $template->username_field('add_username') . '</td>
                   </tr>
                   <tr>
-                    <td class="row2">Group moderator:</td><td class="row1"><label><input type="checkbox" name="add_mod" /> User is a group moderator</label></td>
+                    <td class="row2">' . $lang->get('groupcp_lbl_moderator') . '</td><td class="row1"><label><input type="checkbox" name="add_mod" /> ' . $lang->get('groupcp_lbl_make_mod') . '</label></td>
                   </tr>
                   <tr>
                     <th class="subhead" colspan="2">
-                      <input type="submit" value="Add member" />
+                      <input type="submit" value="' . $lang->get('groupcp_btn_add_member') . '" />
                     </th>
                   </tr>
                 </table>
@@ -489,11 +496,11 @@
     echo '<div class="tblholder">
           <table border="0" style="width: 100%;" cellspacing="1" cellpadding="4">
             <tr>
-              <th colspan="2">Group membership details</th>
+              <th colspan="2">' . $lang->get('groupcp_th_select_group') . '</th>
             </tr>
             <tr>
               <td class="row2" style="text-align: right; width: 50%;">
-                Current group memberships:
+                ' . $lang->get('groupcp_lbl_current_memberships') . '
               </td>
               <td class="row1" style="width: 50%;">';
     $taboo = Array('Everyone');
@@ -505,11 +512,15 @@
         $taboo[] = $group;
         if ( $group != 'Everyone' )
         {
+          $g_name_local = 'groupcp_grp_' . strtolower($group);
+          $str = $lang->get($g_name_local);
+          if ( $str != $g_name_local )
+            $group = $str;
           echo '<option value="' . $id . '">' . $group . '</option>';
         }
       }
       echo '</select>
-            <input type="submit" name="do_view" value="View information" />';
+            <input type="submit" name="do_view" value="' . $lang->get('groupcp_btn_view') . '" />';
     }
     else
     {
@@ -530,7 +541,7 @@
     {
       echo '<tr>
               <td class="row2" style="text-align: right;">
-                Non-memberships:
+                ' . $lang->get('groupcp_lbl_non_memberships') . '
               </td>
               <td class="row1">
               <select name="group_id_n">';
@@ -542,7 +553,7 @@
         }
       }
       echo '</select>
-            <input type="submit" name="do_view_n" value="View information" />
+            <input type="submit" name="do_view_n" value="' . $lang->get('groupcp_btn_view') . '" />
           </td>
         </tr>
       ';
--- a/plugins/SpecialPageFuncs.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/SpecialPageFuncs.php	Wed Dec 26 00:37:26 2007 -0500
@@ -22,45 +22,45 @@
  
 global $db, $session, $paths, $template, $plugins; // Common objects
 
-$plugins->attachHook('base_classes_initted', '
+$plugins->attachHook('session_started', '
   global $paths;
     $paths->add_page(Array(
-      \'name\'=>\'Create page\',
+      \'name\'=>\'specialpage_create_page\',
       \'urlname\'=>\'CreatePage\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'All pages\',
+      \'name\'=>\'specialpage_all_pages\',
       \'urlname\'=>\'AllPages\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'List of special pages\',
+      \'name\'=>\'specialpage_special_pages\',
       \'urlname\'=>\'SpecialPages\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'About Enano\',
+      \'name\'=>\'specialpage_about_enano\',
       \'urlname\'=>\'About_Enano\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'GNU General Public License\',
+      \'name\'=>\'specialpage_gnu_gpl\',
       \'urlname\'=>\'GNU_General_Public_License\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Tag cloud\',
+      \'name\'=>\'specialpage_tag_cloud\',
       \'urlname\'=>\'TagCloud\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
--- a/plugins/SpecialSearch.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/SpecialSearch.php	Wed Dec 26 00:37:26 2007 -0500
@@ -20,17 +20,17 @@
  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
  */
 
-$plugins->attachHook('base_classes_initted', '
+$plugins->attachHook('session_started', '
   global $paths;
     $paths->add_page(Array(
-      \'name\'=>\'Rebuild search index\',
+      \'name\'=>\'specialpage_search_rebuild\',
       \'urlname\'=>\'SearchRebuild\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Search\',
+      \'name\'=>\'specialpage_search\',
       \'urlname\'=>\'Search\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
@@ -40,7 +40,10 @@
 function page_Special_SearchRebuild()
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
-  if(!$session->get_permissions('mod_misc')) die_friendly('Unauthorized', '<p>You need to be an administrator to rebuild the search index</p>');
+  if ( !$session->get_permissions('mod_misc') )
+  {
+    die_friendly('Unauthorized', '<p>You need to be an administrator to rebuild the search index</p>');
+  }
   $template->header();
   @set_time_limit(0);
   if($paths->rebuild_search_index(true))
@@ -54,6 +57,8 @@
 {
   global $db, $session, $paths, $template, $plugins; // Common objects
   global $aggressive_optimize_html;
+  global $lang;
+  
   $aggressive_optimize_html = false;
   
   if ( !$q = $paths->getParam(0) )
@@ -95,7 +100,7 @@
   
   $qin = ( isset($q) ) ? str_replace('"', '\"', htmlspecialchars($q)) : '';
   $search_form = '<form action="' . makeUrlNS('Special', 'Search') . '">
-  <input type="text" tabindex="1" name="q" size="50" value="' . $qin . '" />&nbsp;<input tabindex="2" type="submit" value="Search" />&nbsp;<a href="' . makeUrlNS('Special', 'Search') . '">Advanced search</a>
+  <input type="text" tabindex="1" name="q" size="50" value="' . $qin . '" />&nbsp;<input tabindex="2" type="submit" value="' . $lang->get('search_btn_search') . '" />&nbsp;<a href="' . makeUrlNS('Special', 'Search') . '">' . $lang->get('search_btn_advanced_search') . '</a>
   ' . ( $session->auth_level > USER_LEVEL_MEMBER ? '<input type="hidden" name="auth" value="' . $session->sid_super . '" />' : '' ) . '
   </form>';
   
@@ -103,7 +108,7 @@
   {
     $search_start = microtime_float();
     
-    $results = perform_search($q, $warn, ( isset($_GET['match_case']) ));
+    $results = perform_search($q, $warn, ( isset($_GET['match_case']) ), $word_list);
     $warn = array_unique($warn);
     
     if ( file_exists( ENANO_ROOT . '/themes/' . $template->theme . '/search-result.tpl' ) )
@@ -122,7 +127,7 @@
           {PAGE_TEXT}
           <span class="search-result-url">{PAGE_URL}</span> - 
           <!-- BEGINNOT special_page --><span class="search-result-info">{PAGE_LENGTH} {PAGE_LENGTH_UNIT}</span> -<!-- END special_page --> 
-          <span class="search-result-info">Relevance: {RELEVANCE_SCORE}%</span>
+          <span class="search-result-info">{lang:search_lbl_relevance} {RELEVANCE_SCORE}%</span>
         </p>
       </div>
       
@@ -138,22 +143,25 @@
       if ( !empty($result['page_text']) )
         $result['page_text'] .= '<br />';
       $result['page_name'] = str_replace(array('<highlight>', '</highlight>'), array('<span class="title-search-term">', '</span>'), $result['page_name']);
+      $result['url_highlight'] = str_replace(array('<highlight>', '</highlight>'), array('<span class="url-search-term">', '</span>'), $result['url_highlight']);
       if ( $result['page_length'] >= 1048576 )
       {
         $result['page_length'] = round($result['page_length'] / 1048576, 1);
-        $length_unit = 'MB';
+        $length_unit = $lang->get('etc_unit_megabytes_short');
       }
       else if ( $result['page_length'] >= 1024 )
       {
         $result['page_length'] = round($result['page_length'] / 1024, 1);
-        $length_unit = 'KB';
+        $length_unit = $lang->get('etc_unit_kilobytes_short');
       }
       else
       {
-        $length_unit = 'bytes';
+        $length_unit = $lang->get('etc_unit_bytes');
       }
-      $url = makeUrlComplete($result['namespace'], $result['page_id']);
-      $url = preg_replace('/\?.+$/', '', $url);
+      //$url = makeUrlComplete($result['namespace'], $result['page_id']);
+      //$url = preg_replace('/\?.+$/', '', $url);
+      $url = $result['url_highlight'];
+      
       $parser->assign_vars(array(
          'PAGE_TITLE' => $result['page_name'],
          'PAGE_TEXT' => $result['page_text'],
@@ -193,13 +201,20 @@
     $q_trim = ( strlen($q) > 30 ) ? substr($q, 0, 27) . '...' : $q;
     $q_trim = htmlspecialchars($q_trim);
     
-    $result_string = ( count($results) > 0 ) ? "Results <b>$start_string</b> - <b>$per_string</b> of about <b>$num_results</b> for <b>" . $q_trim . "</b> in {$search_time}s." : 'No results.';
+    $result_detail = $lang->get('search_msg_result_detail', array(
+        'start_string' => $start_string,
+        'per_string' => $per_string,
+        'q_trim' => $q_trim,
+        'num_results' => $num_results,
+        'search_time' => $search_time
+      ));
+    $result_string = ( count($results) > 0 ) ? $result_detail : $lang->get('search_msg_no_results');
     
     echo '<div class="search-hibar">
             <div style="float: right;">
               ' . $result_string . '
             </div>
-            <b>Site search</b>
+            <b>' . $lang->get('search_lbl_site_search') . '</b>
           </div>
           <div class="search-lobar">
             ' . $search_form . '
@@ -208,8 +223,8 @@
     if ( count($warn) > 0 )
     {
       echo '<div class="warning-box" style="margin: 10px 0 0 0;">';
-      echo '<b>Some problems were encountered during your search.</b><br />
-            There was a problem with your search query, and as a result there may be a reduced number of search results.';
+      echo '<b>' . $lang->get('search_err_query_title') . '</b><br />
+            ' . $lang->get('search_err_query_body');
       echo '<ul><li>' . implode('</li><li>', $warn) . '</li></ul>';
       echo '</div>';
     }
@@ -254,26 +269,26 @@
       endif; ?>
       <div class="tblholder">
         <table border="0" style="width: 100%;" cellspacing="1" cellpadding="4">
-          <tr><th colspan="2">Advanced Search</th></tr>
+          <tr><th colspan="2"><?php echo $lang->get('search_th_advanced_search'); ?></th></tr>
           <tr>
-            <td class="row1">Search for pages with <b>any of these words</b>:</td>
+            <td class="row1"><?php echo $lang->get('search_lbl_field_any'); ?></td>
             <td class="row1"><input type="text" name="words_any" size="40" /></td>
           </tr>
           <tr>
-            <td class="row2">with <b>this exact phrase</b>:</td>
+            <td class="row2"><?php echo $lang->get('search_lbl_field_exact'); ?></td>
             <td class="row2"><input type="text" name="exact_phrase" size="40" /></td>
           </tr>
           <tr>
-            <td class="row1">with <b>none of these words</b>:</td>
+            <td class="row1"><?php echo $lang->get('search_lbl_field_none'); ?></td>
             <td class="row1"><input type="text" name="exclude_words" size="40" /></td>
           </tr>
           <tr>
-            <td class="row2">with <b>all of these words</b>:</td>
+            <td class="row2"><?php echo $lang->get('search_lbl_field_all'); ?></td>
             <td class="row2"><input type="text" name="require_words" size="40" /></td>
           </tr>
           <tr>
             <td class="row1">
-              <label for="chk_case">Case-sensitive search:</label>
+              <label for="chk_case"><?php echo $lang->get('search_lbl_field_casesensitive'); ?></label>
             </td>
             <td class="row1">
               <input type="checkbox" name="match_case" id="chk_case" />
@@ -281,7 +296,7 @@
           </tr>
           <tr>
             <th colspan="2" class="subhead">
-              <input type="submit" name="do_search" value="Search" />
+              <input type="submit" name="do_search" value="<?php echo $lang->get('search_btn_search'); ?>" />
             </td>
           </tr>
         </table>
--- a/plugins/SpecialUpdownload.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/SpecialUpdownload.php	Wed Dec 26 00:37:26 2007 -0500
@@ -23,17 +23,17 @@
  
 global $db, $session, $paths, $template, $plugins; // Common objects
 
-$plugins->attachHook('base_classes_initted', '
+$plugins->attachHook('session_started', '
   global $paths;
     $paths->add_page(Array(
-      \'name\'=>\'Upload file\',
+      \'name\'=>\'specialpage_upload_file\',
       \'urlname\'=>\'UploadFile\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Download file\',
+      \'name\'=>\'download_file\',
       \'urlname\'=>\'DownloadFile\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
--- a/plugins/SpecialUserFuncs.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/SpecialUserFuncs.php	Wed Dec 26 00:37:26 2007 -0500
@@ -22,77 +22,77 @@
  
 global $db, $session, $paths, $template, $plugins; // Common objects
 
-$plugins->attachHook('base_classes_initted', '
+$plugins->attachHook('session_started', '
   global $paths;
     $paths->add_page(Array(
-      \'name\'=>\'Log in\',
+      \'name\'=>\'specialpage_log_in\',
       \'urlname\'=>\'Login\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     $paths->add_page(Array(
-      \'name\'=>\'Log out\',
+      \'name\'=>\'specialpage_log_out\',
       \'urlname\'=>\'Logout\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     $paths->add_page(Array(
-      \'name\'=>\'Register\',
+      \'name\'=>\'specialpage_register\',
       \'urlname\'=>\'Register\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     $paths->add_page(Array(
-      \'name\'=>\'Edit Profile\',
+      \'name\'=>\'specialpage_preferences\',
       \'urlname\'=>\'Preferences\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Contributions\',
+      \'name\'=>\'specialpage_contributions\',
       \'urlname\'=>\'Contributions\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Change style\',
+      \'name\'=>\'specialpage_change_theme\',
       \'urlname\'=>\'ChangeStyle\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Activate user account\',
+      \'name\'=>\'specialpage_activate_account\',
       \'urlname\'=>\'ActivateAccount\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Captcha\',
+      \'name\'=>\'specialpage_captcha\',
       \'urlname\'=>\'Captcha\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Forgot password\',
+      \'name\'=>\'specialpage_password_reset\',
       \'urlname\'=>\'PasswordReset\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
     
     $paths->add_page(Array(
-      \'name\'=>\'Member list\',
+      \'name\'=>\'specialpage_member_list\',
       \'urlname\'=>\'Memberlist\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>1,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
       ));
       
     $paths->add_page(Array(
-      \'name\'=>\'Language exporter\',
+      \'name\'=>\'specialpage_language_export\',
       \'urlname\'=>\'LangExportJSON\',
       \'namespace\'=>\'Special\',
       \'special\'=>0,\'visible\'=>0,\'comments_on\'=>0,\'protected\'=>1,\'delvotes\'=>0,\'delvote_ips\'=>\'\',
@@ -168,8 +168,7 @@
       unset($x, $y);
     }
     
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-    $response = $json->encode($response);
+    $response = enano_json_encode($response);
     echo $response;
     return null;
   }
@@ -369,8 +368,7 @@
   if ( isset($_GET['act']) && $_GET['act'] == 'ajaxlogin' )
   {
     $plugins->attachHook('login_password_reset', 'SpecialLogin_SendResponse_PasswordReset($row[\'user_id\'], $row[\'temp_password\']);');
-    $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
-    $data = $json->decode($_POST['params']);
+    $data = enano_json_decode($_POST['params']);
     $captcha_hash = ( isset($data['captcha_hash']) ) ? $data['captcha_hash'] : false;
     $captcha_code = ( isset($data['captcha_code']) ) ? $data['captcha_code'] : false;
     $level = ( isset($data['level']) ) ? intval($data['level']) : USER_LEVEL_MEMBER;
@@ -397,7 +395,7 @@
           'captcha' => $captcha
         );
     }
-    $response = $json->encode($response);
+    $response = enano_json_encode($response);
     echo $response;
     $db->close();
     exit;
@@ -445,7 +443,6 @@
 
 function SpecialLogin_SendResponse_PasswordReset($user_id, $passkey)
 {
-  $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
   
   $response = Array(
       'result' => 'success_reset',
@@ -453,7 +450,7 @@
       'temppass' => $passkey
     );
   
-  $response = $json->encode($response);
+  $response = enano_json_encode($response);
   echo $response;
   
   $db->close();
@@ -1722,7 +1719,6 @@
   else
     $lang_local = new Language($lang_id);
   
-  $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
   
   $timestamp = date('D, j M Y H:i:s T', $lang_local->lang_timestamp);
   header("Last-Modified: $timestamp");
@@ -1733,7 +1729,7 @@
   echo "if ( typeof(enano_lang) != 'object' )
   var enano_lang = new Object();
 
-enano_lang[{$lang->lang_id}] = " . $json->encode($lang_local->strings) . ";";
+enano_lang[{$lang->lang_id}] = " . enano_json_encode($lang_local->strings) . ";";
   
 }
 
--- a/plugins/SpecialUserPrefs.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/SpecialUserPrefs.php	Wed Dec 26 00:37:26 2007 -0500
@@ -103,6 +103,7 @@
   userprefs_menu_add('Profile/membership', 'Edit e-mail address and password', makeUrlNS('Special', 'Preferences/EmailPassword') . '" onclick="ajaxLoginNavTo(\'Special\', \'Preferences/EmailPassword\', '.USER_LEVEL_CHPREF.'); return false;');
   userprefs_menu_add('Profile/membership', 'Edit signature', makeUrlNS('Special', 'Preferences/Signature'));
   userprefs_menu_add('Profile/membership', 'Edit public profile', makeUrlNS('Special', 'Preferences/Profile'));
+  userprefs_menu_add('Profile/membership', 'Group memberships', makeUrlNS('Special', 'Usergroups'));
   if ( getConfig('avatar_enable') == '1' )
   {
     userprefs_menu_add('Profile/membership', 'Avatar settings', makeUrlNS('Special', 'Preferences/Avatar'));
@@ -112,6 +113,14 @@
   userprefs_menu_add('Private messages', 'Sent items', makeUrlNS('Special', 'PrivateMessages/Folder/Sent'));
   userprefs_menu_add('Private messages', 'Drafts', makeUrlNS('Special', 'PrivateMessages/Folder/Drafts'));
   userprefs_menu_add('Private messages', 'Archive', makeUrlNS('Special', 'PrivateMessages/Folder/Archive'));
+  /*
+  userprefs_menu_add('Private messages', 'Inbox', makeUrlNS('Special',      'Private_Messages#folder:inbox'));
+  userprefs_menu_add('Private messages', 'Starred', makeUrlNS('Special',     'Private_Messages#folder:starred'));
+  userprefs_menu_add('Private messages', 'Sent items', makeUrlNS('Special', 'Private_Messages#folder:sent'));
+  userprefs_menu_add('Private messages', 'Drafts', makeUrlNS('Special',     'Private_Messages#folder:drafts'));
+  userprefs_menu_add('Private messages', 'Archive', makeUrlNS('Special',    'Private_Messages#folder:archive'));
+  userprefs_menu_add('Private messages', 'Trash', makeUrlNS('Special',    'Private_Messages#folder:trash'));
+  */
   
   $userprefs_menu_links['Profile/membership'] = makeUrlNS('Special', 'Preferences');
   $userprefs_menu_links['Private messages']  = makeUrlNS('Special', 'PrivateMessages');
@@ -123,7 +132,7 @@
   }
 }
 
-$plugins->attachHook('session_started', 'userprefs_menu_init();');
+$plugins->attachHook('common_post', 'userprefs_menu_init();');
 
 function page_Special_Preferences()
 {
--- a/plugins/admin/PageGroups.php	Fri Dec 21 19:08:27 2007 -0500
+++ b/plugins/admin/PageGroups.php	Wed Dec 26 00:37:26 2007 -0500
@@ -481,7 +481,6 @@
      
       if ( isset($_POST['action']['edit']['add_page']) && isset($_GET['src']) && $_GET['src'] == 'ajax' )
       {
-        $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
         $return = array('successful' => false);
         
         //
@@ -493,7 +492,7 @@
         if ( !$edit_id )
         {
           $return = array('mode' => 'error', 'text' => 'Hack attempt');
-          echo $json->encode($return);
+          echo enano_json_encode($return);
           return;
         }
         
@@ -502,7 +501,7 @@
         if ( empty($page) )
         {
           $return = array('mode' => 'error', 'text' => 'Please enter a page title.');
-          echo $json->encode($return);
+          echo enano_json_encode($return);
           return;
         }
         
@@ -511,7 +510,7 @@
         if ( !isPage($page) )
         {
           $return = array('mode' => 'error', 'text' => 'The page you are trying to add (' . htmlspecialchars($page) . ') does not exist.');
-          echo $json->encode($return);
+          echo enano_json_encode($return);
           return;
         }
         */
@@ -522,7 +521,7 @@
         if ( !isset($paths->namespace[$namespace]) )
         {
           $return = array('mode' => 'error', 'text' => 'Invalid namespace return from RenderMan::strToPageID()');
-          echo $json->encode($return);
+          echo enano_json_encode($return);
           return;
         }
         
@@ -530,13 +529,13 @@
         if ( !$q )
         {
           $return = array('mode' => 'error', 'text' => $db->get_error());
-          echo $json->encode($return);
+          echo enano_json_encode($return);
           return;
         }
         if ( $db->numrows() > 0 )
         {
           $return = array('mode' => 'error', 'text' => 'The page you are trying to add is already in this group.');
-          echo $json->encode($return);
+          echo enano_json_encode($return);
           return;
         }
         
@@ -544,7 +543,7 @@
         if ( !$q )
         {
           $return = array('mode' => 'error', 'text' => $db->get_error());
-          echo $json->encode($return);
+          echo enano_json_encode($return);
           return;
         }
         
@@ -552,7 +551,7 @@
         
         $return = array('mode' => 'info', 'text' => 'The page has been added to the specified group.', 'successful' => true, 'title' => $title, 'member_id' => $db->insert_id());
         
-        echo $json->encode($return);
+        echo enano_json_encode($return);
         return;
       }
       
--- a/themes/oxygen/css/bleu.css	Fri Dec 21 19:08:27 2007 -0500
+++ b/themes/oxygen/css/bleu.css	Wed Dec 26 00:37:26 2007 -0500
@@ -251,6 +251,12 @@
   filter: alpha(opacity=50);
 }
 
+input[type ^="button"][disabled ^="disabled"], input[type ^="submit"][disabled ^="disabled"], button[disabled ^="disabled"], .btn-disabled {
+  color: #808080 !important;
+  background-image: none !important;
+  background-color: #e0e0e0 !important;
+}
+
 /* JWS window theming */
 div.jswindow                      { border: 2px solid #7090B0; border-top: 5px solid #7090B0; padding: 0px; font-family: Trebuchet MS, tahoma, verdana, arial, sans-serif; font-size: 9pt; display: none; position: absolute; background-color: #FFFFFF; }
 div.titlebar                      { background-color: #7090B0; color: #FFFFFF; font-family: Trebuchet MS, tahoma, verdana, arial, sans-serif; font-size: 9pt; padding-bottom: 4px; cursor: default; }