diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 812fdb630f96c75533436ddfe6c4c07d0d72d98e..d3779b48b8587cbc42a9a18023244ae5add91d6b 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -273,6 +273,32 @@ $config = array(
                 $logger->setConfig(\VuFind\Config\Reader::getConfig());
                 return $logger;
             },
+            'mailer' => function ($sm) {
+                $mailer = new \VuFind\Mailer();
+                $mailer->setTranslator($sm->get('Translator'));
+                return $mailer;
+            },
+            'sms' => function ($sm) {
+                $sms = new \VuFind\Mailer\SMS();
+                $sms->setTranslator($sm->get('Translator'));
+                return $sms;
+            },
+            'translator' => function ($sm) {
+                $factory = new \Zend\I18n\Translator\TranslatorServiceFactory();
+                $translator = $factory->createService($sm);
+
+                // Set up the ExtendedIni plugin:
+                $translator->getPluginManager()->setService(
+                    'extendedini', new \VuFind\I18n\Translator\Loader\ExtendedIni()
+                );
+
+                // Set up language caching for better performance:
+                $translator->setCache(
+                    $sm->get('CacheManager')->getCache('language')
+                );
+
+                return $translator;
+            },
             'worldcatconnection' => function ($sm) {
                 $wc = new \VuFind\Connection\WorldCat();
                 $wc->setLogger($sm->get('Logger'));
@@ -288,11 +314,9 @@ $config = array(
             'authmanager' => 'VuFind\Auth\Manager',
             'cart' => 'VuFind\Cart',
             'cachemanager' => 'VuFind\Cache\Manager',
-            'mailer' => 'VuFind\Mailer',
             'recordloader' => 'VuFind\Record\Loader',
             'searchspecsreader' => 'VuFind\Config\SearchSpecsReader',
             'sessionmanager' => 'Zend\Session\SessionManager',
-            'sms' => 'VuFind\Mailer\SMS',
         )
     ),
     'session_plugin_manager' => array(
diff --git a/module/VuFind/src/VuFind/Bootstrap.php b/module/VuFind/src/VuFind/Bootstrap.php
index 177049a83f07bf9ccf17cb18435558bb5be08e58..bc7ed76a8f9dcf3db9cfd7591dba78004af2a56b 100644
--- a/module/VuFind/src/VuFind/Bootstrap.php
+++ b/module/VuFind/src/VuFind/Bootstrap.php
@@ -28,8 +28,7 @@
 namespace VuFind;
 use VuFind\Config\Reader as ConfigReader,
     VuFind\Db\AdapterFactory as DbAdapterFactory,
-    VuFind\Theme\Initializer as ThemeInitializer,
-    VuFind\Translator\Translator, Zend\Console\Console,
+    VuFind\Theme\Initializer as ThemeInitializer, Zend\Console\Console,
     Zend\Db\TableGateway\Feature\GlobalAdapterFeature as DbGlobalAdapter,
     Zend\Mvc\MvcEvent, Zend\Mvc\Router\Http\RouteMatch,
     Zend\ServiceManager\Config as ServiceManagerConfig,
@@ -276,19 +275,18 @@ class Bootstrap
                     : $config->Site->language;
             }
             // Make sure language code is valid, reset to default if bad:
-            $validLanguages = array();
-            foreach ($config->Languages as $key => $value) {
-                $validLanguages[] = $key;
-            }
-            if (!in_array($language, $validLanguages)) {
+            if (!in_array($language, array_keys($config->Languages->toArray()))) {
                 $language = $config->Site->language;
             }
 
-            Translator::init($event, $language);
+            $sm = $event->getApplication()->getServiceManager();
+            $langFile = APPLICATION_PATH  . '/languages/' . $language . '.ini';
+            $sm->get('Translator')
+                ->addTranslationFile('ExtendedIni', $langFile, 'default', $language)
+                ->setLocale($language);
 
             // Send key values to view:
-            $viewModel = $event->getApplication()->getServiceManager()
-                ->get('viewmanager')->getViewModel();
+            $viewModel = $sm->get('viewmanager')->getViewModel();
             $viewModel->setVariable('userLang', $language);
             $viewModel->setVariable('allLangs', $config->Languages);
         };
diff --git a/module/VuFind/src/VuFind/Controller/AbstractBase.php b/module/VuFind/src/VuFind/Controller/AbstractBase.php
index 168fc05f862797c5e03b69068e829c85f80549f4..b63961b3febb168b8695efbb7aca0934e77a4010 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractBase.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractBase.php
@@ -247,6 +247,19 @@ class AbstractBase extends AbstractActionController
         );
     }
 
+    /**
+     * Translate a string if a translator is available.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    public function translate($msg)
+    {
+        return $this->getServiceLocator()->has('Translator')
+            ? $this->getServiceLocator()->get('Translator')->translate($msg) : $msg;
+    }
+
     /**
      * Convenience method to make invocation of forward() helper less verbose.
      *
diff --git a/module/VuFind/src/VuFind/Controller/AjaxController.php b/module/VuFind/src/VuFind/Controller/AjaxController.php
index d5080ead79976f3ae959d199152b397ea6824080..70deba0f797dba75a3386b03ecb47c34cbd4c219 100644
--- a/module/VuFind/src/VuFind/Controller/AjaxController.php
+++ b/module/VuFind/src/VuFind/Controller/AjaxController.php
@@ -28,8 +28,7 @@
 namespace VuFind\Controller;
 use VuFind\Config\Reader as ConfigReader,
     VuFind\Connection\Manager as ConnectionManager,
-    VuFind\Exception\Auth as AuthException, VuFind\Export,
-    VuFind\Translator\Translator;
+    VuFind\Exception\Auth as AuthException, VuFind\Export;
 
 /**
  * This controller handles global AJAX functionality
@@ -82,13 +81,13 @@ class AjaxController extends AbstractBase
                 $debugMsg = ('development' == APPLICATION_ENV)
                     ? ': ' . $e->getMessage() : '';
                 return $this->output(
-                    Translator::translate('An error has occurred') . $debugMsg,
+                    $this->translate('An error has occurred') . $debugMsg,
                     self::STATUS_ERROR
                 );
             }
         } else {
             return $this->output(
-                Translator::translate('Invalid Method'), self::STATUS_ERROR
+                $this->translate('Invalid Method'), self::STATUS_ERROR
             );
         }
     }
@@ -255,10 +254,10 @@ class AjaxController extends AbstractBase
                 'id'                   => $missingId,
                 'availability'         => 'false',
                 'availability_message' => $messages['unavailable'],
-                'location'             => Translator::translate('Unknown'),
+                'location'             => $this->translate('Unknown'),
                 'locationList'         => false,
                 'reserve'              => 'false',
-                'reserve_message'      => Translator::translate('Not On Reserve'),
+                'reserve_message'      => $this->translate('Not On Reserve'),
                 'callnumber'           => '',
                 'missing_data'         => true,
                 'record_number'        => $recordNumber
@@ -297,7 +296,7 @@ class AjaxController extends AbstractBase
         } else {
             // Message mode?  Return the specified message, translated to the
             // appropriate language.
-            return Translator::translate($msg);
+            return $this->translate($msg);
         }
     }
 
@@ -362,8 +361,8 @@ class AjaxController extends AbstractBase
             'reserve' =>
                 ($record[0]['reserve'] == 'Y' ? 'true' : 'false'),
             'reserve_message' => $record[0]['reserve'] == 'Y'
-                ? Translator::translate('on_reserve')
-                : Translator::translate('Not On Reserve'),
+                ? $this->translate('on_reserve')
+                : $this->translate('Not On Reserve'),
             'callnumber' => htmlentities($callNumber, ENT_COMPAT, 'UTF-8')
         );
     }
@@ -433,8 +432,8 @@ class AjaxController extends AbstractBase
             'reserve' =>
                 ($record[0]['reserve'] == 'Y' ? 'true' : 'false'),
             'reserve_message' => $record[0]['reserve'] == 'Y'
-                ? Translator::translate('on_reserve')
-                : Translator::translate('Not On Reserve'),
+                ? $this->translate('on_reserve')
+                : $this->translate('Not On Reserve'),
             'callnumber' => false
         );
     }
@@ -450,7 +449,7 @@ class AjaxController extends AbstractBase
         $user = $this->getUser();
         if (!$user) {
             return $this->output(
-                Translator::translate('You must be logged in first'),
+                $this->translate('You must be logged in first'),
                 self::STATUS_NEED_AUTH
             );
         }
@@ -461,7 +460,7 @@ class AjaxController extends AbstractBase
         $sources = $this->params()->fromQuery('source', array());
         if (!is_array($ids) || !is_array($sources)) {
             return $this->output(
-                Translator::translate('Argument must be array.'),
+                $this->translate('Argument must be array.'),
                 self::STATUS_ERROR
             );
         }
@@ -581,7 +580,7 @@ class AjaxController extends AbstractBase
             $this->getAuthManager()->login($this->getRequest());
         } catch (AuthException $e) {
             return $this->output(
-                Translator::translate($e->getMessage()),
+                $this->translate($e->getMessage()),
                 self::STATUS_ERROR
             );
         }
@@ -599,7 +598,7 @@ class AjaxController extends AbstractBase
         $user = $this->getUser();
         if ($user === false) {
             return $this->output(
-                Translator::translate('You must be logged in first'),
+                $this->translate('You must be logged in first'),
                 self::STATUS_NEED_AUTH
             );
         }
@@ -615,12 +614,12 @@ class AjaxController extends AbstractBase
             }
         } catch (\Exception $e) {
             return $this->output(
-                Translator::translate('Failed'),
+                $this->translate('Failed'),
                 self::STATUS_ERROR
             );
         }
 
-        return $this->output(Translator::translate('Done'), self::STATUS_OK);
+        return $this->output($this->translate('Done'), self::STATUS_OK);
     }
 
     /**
@@ -645,8 +644,8 @@ class AjaxController extends AbstractBase
 
         // If we don't have any tags, provide a user-appropriate message:
         if (empty($tagList)) {
-            $msg = Translator::translate('No Tags') . ', ' .
-                Translator::translate('Be the first to tag this record') . '!';
+            $msg = $this->translate('No Tags') . ', ' .
+                $this->translate('Be the first to tag this record') . '!';
             return $this->output($msg, self::STATUS_ERROR);
         }
 
@@ -823,7 +822,7 @@ class AjaxController extends AbstractBase
         $user = $this->getUser();
         if (!$user) {
             return $this->output(
-                Translator::translate('You must be logged in first'),
+                $this->translate('You must be logged in first'),
                 self::STATUS_NEED_AUTH
             );
         }
@@ -847,7 +846,7 @@ class AjaxController extends AbstractBase
         $ids = $this->params()->fromPost('ids', array());
         if (empty($ids)) {
             return $this->output(
-                array('result'=>Translator::translate('bulk_error_missing')),
+                array('result'=>$this->translate('bulk_error_missing')),
                 self::STATUS_ERROR
             );
         }
@@ -855,7 +854,7 @@ class AjaxController extends AbstractBase
         $user = $this->getUser();
         if (!$user) {
             return $this->output(
-                Translator::translate('You must be logged in first'),
+                $this->translate('You must be logged in first'),
                 self::STATUS_NEED_AUTH
             );
         }
@@ -867,12 +866,12 @@ class AjaxController extends AbstractBase
             return $this->output(
                 array(
                     'result' => array('list' => $this->params()->fromPost('list')),
-                    'info' => Translator::translate("bulk_save_success")
+                    'info' => $this->translate("bulk_save_success")
                 ), self::STATUS_OK
             );
         } catch (\Exception $e) {
             return $this->output(
-                array('info' => Translator::translate('bulk_save_error')),
+                array('info' => $this->translate('bulk_save_error')),
                 self::STATUS_ERROR
             );
         }
@@ -895,14 +894,14 @@ class AjaxController extends AbstractBase
             switch(get_class($e)) {
             case 'VuFind\Exception\LoginRequired':
                 return $this->output(
-                    Translator::translate('You must be logged in first'),
+                    $this->translate('You must be logged in first'),
                     self::STATUS_NEED_AUTH
                 );
                 break;
             case 'VuFind\Exception\ListPermission':
             case 'VuFind\Exception\MissingField':
                 return $this->output(
-                    Translator::translate($e->getMessage()), self::STATUS_ERROR
+                    $this->translate($e->getMessage()), self::STATUS_ERROR
                 );
             default:
                 throw $e;
@@ -945,11 +944,11 @@ class AjaxController extends AbstractBase
                 $this->params()->fromPost('to'), $record, $this->getViewRenderer()
             );
             return $this->output(
-                Translator::translate('sms_success'), self::STATUS_OK
+                $this->translate('sms_success'), self::STATUS_OK
             );
         } catch (\Exception $e) {
             return $this->output(
-                Translator::translate($e->getMessage()), self::STATUS_ERROR
+                $this->translate($e->getMessage()), self::STATUS_ERROR
             );
         }
     }
@@ -974,11 +973,11 @@ class AjaxController extends AbstractBase
                 $this->getViewRenderer()
             );
             return $this->output(
-                Translator::translate('email_success'), self::STATUS_OK
+                $this->translate('email_success'), self::STATUS_OK
             );
         } catch (\Exception $e) {
             return $this->output(
-                Translator::translate($e->getMessage()), self::STATUS_ERROR
+                $this->translate($e->getMessage()), self::STATUS_ERROR
             );
         }
     }
@@ -1006,11 +1005,11 @@ class AjaxController extends AbstractBase
                 $url, $this->getViewRenderer(), $this->params()->fromPost('subject')
             );
             return $this->output(
-                Translator::translate('email_success'), self::STATUS_OK
+                $this->translate('email_success'), self::STATUS_OK
             );
         } catch (\Exception $e) {
             return $this->output(
-                Translator::translate($e->getMessage()), self::STATUS_ERROR
+                $this->translate($e->getMessage()), self::STATUS_ERROR
             );
         }
     }
@@ -1029,7 +1028,7 @@ class AjaxController extends AbstractBase
             $user = $this->getUser();
             if (!$user) {
                 return $this->output(
-                    Translator::translate('You must be logged in first'),
+                    $this->translate('You must be logged in first'),
                     self::STATUS_NEED_AUTH
                 );
             }
@@ -1041,8 +1040,8 @@ class AjaxController extends AbstractBase
                     $results = $catalog->checkRequestIsValid($id, $data, $patron);
 
                     $msg = $results
-                        ? Translator::translate('request_place_text')
-                        : Translator::translate('hold_error_blocked');
+                        ? $this->translate('request_place_text')
+                        : $this->translate('hold_error_blocked');
                     return $this->output(
                         array('status' => $results, 'msg' => $msg), self::STATUS_OK
                     );
@@ -1053,7 +1052,7 @@ class AjaxController extends AbstractBase
         }
 
         return $this->output(
-            Translator::translate('An error has occurred'), self::STATUS_ERROR
+            $this->translate('An error has occurred'), self::STATUS_ERROR
         );
     }
 
@@ -1067,7 +1066,7 @@ class AjaxController extends AbstractBase
         $user = $this->getUser();
         if ($user === false) {
             return $this->output(
-                Translator::translate('You must be logged in first'),
+                $this->translate('You must be logged in first'),
                 self::STATUS_NEED_AUTH
             );
         }
@@ -1076,7 +1075,7 @@ class AjaxController extends AbstractBase
         $comment = $this->params()->fromPost('comment');
         if (empty($id) || empty($comment)) {
             return $this->output(
-                Translator::translate('An error has occurred'), self::STATUS_ERROR
+                $this->translate('An error has occurred'), self::STATUS_ERROR
             );
         }
 
@@ -1099,7 +1098,7 @@ class AjaxController extends AbstractBase
         $user = $this->getUser();
         if ($user === false) {
             return $this->output(
-                Translator::translate('You must be logged in first'),
+                $this->translate('You must be logged in first'),
                 self::STATUS_NEED_AUTH
             );
         }
@@ -1108,11 +1107,11 @@ class AjaxController extends AbstractBase
         $table = $this->getTable('Comments');
         if (empty($id) || !$table->deleteIfOwnedByUser($id, $user)) {
             return $this->output(
-                Translator::translate('An error has occurred'), self::STATUS_ERROR
+                $this->translate('An error has occurred'), self::STATUS_ERROR
             );
         }
 
-        return $this->output(Translator::translate('Done'), self::STATUS_OK);
+        return $this->output($this->translate('Done'), self::STATUS_OK);
     }
 
     /**
@@ -1141,7 +1140,7 @@ class AjaxController extends AbstractBase
         $user = $this->getUser();
         if ($user === false) {
             return $this->output(
-                Translator::translate('You must be logged in first'),
+                $this->translate('You must be logged in first'),
                 self::STATUS_NEED_AUTH
             );
         }
@@ -1151,14 +1150,14 @@ class AjaxController extends AbstractBase
 
         if (!is_array($ids)) {
             return $this->output(
-                Translator::translate('delete_missing'),
+                $this->translate('delete_missing'),
                 self::STATUS_ERROR
             );
         }
 
         $this->favorites()->delete($ids, $listID, $user);
         return $this->output(
-            array('result' => Translator::translate('fav_delete_success')),
+            array('result' => $this->translate('fav_delete_success')),
             self::STATUS_OK
         );
     }
@@ -1174,7 +1173,7 @@ class AjaxController extends AbstractBase
         $ids = $this->params()->fromPost('ids');
         if (empty($ids)) {
             return $this->output(
-                array('result'=>Translator::translate('bulk_error_missing')),
+                array('result'=>$this->translate('bulk_error_missing')),
                 self::STATUS_ERROR
             );
         }
@@ -1200,7 +1199,7 @@ class AjaxController extends AbstractBase
         );
         return $this->output(
             array(
-                'result' => Translator::translate('Done'),
+                'result' => $this->translate('Done'),
                 'result_additional' => $html
             ), self::STATUS_OK
         );
@@ -1224,7 +1223,7 @@ class AjaxController extends AbstractBase
             ->get('ResolverDriverPluginManager');
         if (!$pluginManager->has($resolverType)) {
             return $this->output(
-                Translator::translate("Could not load driver for $resolverType"),
+                $this->translate("Could not load driver for $resolverType"),
                 self::STATUS_ERROR
             );
         }
@@ -1248,7 +1247,7 @@ class AjaxController extends AbstractBase
                 break;
             case 'getDOI':
                 // Special case -- modify DOI text for special display:
-                $link['title'] = Translator::translate('Get full text');
+                $link['title'] = $this->translate('Get full text');
                 $link['coverage'] = '';
             case 'getFullTxt':
             default:
diff --git a/module/VuFind/src/VuFind/Controller/CartController.php b/module/VuFind/src/VuFind/Controller/CartController.php
index 5caf4cc3a0f67bb7f5e77b8c34cb6b65d8b4c68d..e29392b775c75abc2b2adfba3c4c9f9b35093817 100644
--- a/module/VuFind/src/VuFind/Controller/CartController.php
+++ b/module/VuFind/src/VuFind/Controller/CartController.php
@@ -27,7 +27,7 @@
  */
 namespace VuFind\Controller;
 use VuFind\Exception\Mail as MailException, VuFind\Export,
-    VuFind\Translator\Translator, Zend\Session\Container as SessionContainer;
+    Zend\Session\Container as SessionContainer;
 
 /**
  * Book Bag / Bulk Action Controller
@@ -114,9 +114,9 @@ class CartController extends AbstractBase
             } else {
                 $addItems = $this->getCart()->addItems($ids);
                 if (!$addItems['success']) {
-                    $msg = Translator::translate('bookbag_full_msg') . ". "
+                    $msg = $this->translate('bookbag_full_msg') . ". "
                         . $addItems['notAdded'] . " "
-                        . Translator::translate('items_already_in_bookbag') . ".";
+                        . $this->translate('items_already_in_bookbag') . ".";
                     $this->flashMessenger()->setNamespace('info')
                         ->addMessage($msg);
                 }
diff --git a/module/VuFind/src/VuFind/Controller/HelpController.php b/module/VuFind/src/VuFind/Controller/HelpController.php
index df92cb9dcd4aef1f4c84839a209c2af4467f35d0..e530f7370e6522a3feb38e6f982fdf23033b5a7f 100644
--- a/module/VuFind/src/VuFind/Controller/HelpController.php
+++ b/module/VuFind/src/VuFind/Controller/HelpController.php
@@ -28,7 +28,7 @@
  * @link     http://vufind.org   Main Site
  */
 namespace VuFind\Controller;
-use VuFind\Config\Reader as ConfigReader, VuFind\Translator\Translator;
+use VuFind\Config\Reader as ConfigReader;
 
 /**
  * Home action for Help module
@@ -62,8 +62,8 @@ class HelpController extends AbstractBase
         // Construct two possible template names -- the help screen in the
         // current selected language and help in English (most likely to exist).
         // The code will attempt to display the most appropriate existing help screen
-        $translator = Translator::getTranslator();
-        $language = is_object($translator) ? $translator->getLocale() : 'en';
+        $language = $this->getServiceLocator()->has('Translator')
+            ? $this->getServiceLocator()->get('Translator')->getLocale() : 'en';
 
         $tpl_lang = 'HelpTranslations/' . $language
             . '/' . $safe_topic . '.phtml';
diff --git a/module/VuFind/src/VuFind/Controller/Plugin/Favorites.php b/module/VuFind/src/VuFind/Controller/Plugin/Favorites.php
index ba832b9714addbbe52e7f1f92a8003be910d6dea..485eb7a04d07967964c9b4c74f9e8222d83bc01c 100644
--- a/module/VuFind/src/VuFind/Controller/Plugin/Favorites.php
+++ b/module/VuFind/src/VuFind/Controller/Plugin/Favorites.php
@@ -27,7 +27,7 @@
  */
 namespace VuFind\Controller\Plugin;
 use VuFind\Exception\LoginRequired as LoginRequiredException, VuFind\Tags,
-    VuFind\Translator\Translator, Zend\Mvc\Controller\Plugin\AbstractPlugin;
+    Zend\Mvc\Controller\Plugin\AbstractPlugin;
 
 /**
  * Zend action helper to perform favorites-related actions
@@ -65,7 +65,7 @@ class Favorites extends AbstractPlugin
         $table = $this->getController()->getTable('UserList');
         if (empty($listId) || $listId == 'NEW') {
             $list = $table->getNew($user);
-            $list->title = Translator::translate('My Favorites');
+            $list->title = $this->getController()->translate('My Favorites');
             $list->save($user);
         } else {
             $list = $table->getExisting($listId);
diff --git a/module/VuFind/src/VuFind/Controller/Plugin/Holds.php b/module/VuFind/src/VuFind/Controller/Plugin/Holds.php
index 37fb08546b573f5887f647206e20dedbb91668c5..9e992e2b0a6e06f2e35fcd3762715725b0cdce03 100644
--- a/module/VuFind/src/VuFind/Controller/Plugin/Holds.php
+++ b/module/VuFind/src/VuFind/Controller/Plugin/Holds.php
@@ -27,8 +27,7 @@
  */
 namespace VuFind\Controller\Plugin;
 use VuFind\Crypt\HMAC, VuFind\Date\Converter as DateConverter,
-    VuFind\Translator\Translator, Zend\Mvc\Controller\Plugin\AbstractPlugin,
-    Zend\Session\Container;
+    Zend\Mvc\Controller\Plugin\AbstractPlugin, Zend\Session\Container;
 
 /**
  * Zend action helper to perform renewal-related actions
@@ -163,9 +162,10 @@ class Holds extends AbstractPlugin
                 if ($cancelResults['count'] > 0) {
                     // TODO : add a mechanism for inserting tokens into translated
                     // messages so we can avoid a double translation here.
+                    $msg = $this->getController()
+                        ->translate('hold_cancel_success_items');
                     $flashMsg->setNamespace('info')->addMessage(
-                        $cancelResults['count'] . ' ' .
-                        Translator::translate('hold_cancel_success_items')
+                        $cancelResults['count'] . ' ' . $msg
                     );
                 }
                 return $cancelResults;
diff --git a/module/VuFind/src/VuFind/Translator/Loader/ExtendedIni.php b/module/VuFind/src/VuFind/I18n/Translator/Loader/ExtendedIni.php
similarity index 98%
rename from module/VuFind/src/VuFind/Translator/Loader/ExtendedIni.php
rename to module/VuFind/src/VuFind/I18n/Translator/Loader/ExtendedIni.php
index db1505afbec6ac3d85c5ff1174d67b7ab898e0a4..3fba44b1fadca9c3b99cdd331bee7cf634cb6625 100644
--- a/module/VuFind/src/VuFind/Translator/Loader/ExtendedIni.php
+++ b/module/VuFind/src/VuFind/I18n/Translator/Loader/ExtendedIni.php
@@ -25,7 +25,7 @@
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org   Main Site
  */
-namespace VuFind\Translator\Loader;
+namespace VuFind\I18n\Translator\Loader;
 use Zend\I18n\Exception\InvalidArgumentException,
     Zend\I18n\Translator\Loader\FileLoaderInterface,
     Zend\I18n\Translator\TextDomain;
diff --git a/module/VuFind/src/VuFind/ILS/Driver/Amicus.php b/module/VuFind/src/VuFind/ILS/Driver/Amicus.php
index 9236af8768bee4fb8ae274ceb1de7f85077becd5..044fae5a16d797fe237edd6885436f80d159b915 100644
--- a/module/VuFind/src/VuFind/ILS/Driver/Amicus.php
+++ b/module/VuFind/src/VuFind/ILS/Driver/Amicus.php
@@ -27,7 +27,9 @@
  */
 namespace VuFind\ILS\Driver;
 use PDO, PDOException, VuFind\Config\Reader as ConfigReader,
-    VuFind\Exception\ILS as ILSException, VuFind\Translator\Translator;
+    VuFind\Exception\ILS as ILSException,
+    Zend\ServiceManager\ServiceLocatorAwareInterface,
+    Zend\ServiceManager\ServiceLocatorInterface;
 
 /**
  * Amicus ILS Driver
@@ -38,10 +40,29 @@ use PDO, PDOException, VuFind\Config\Reader as ConfigReader,
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/building_an_ils_driver Wiki
  */
-class Amicus extends AbstractBase
+class Amicus extends AbstractBase implements ServiceLocatorAwareInterface
 {
+    /**
+     * Service locator
+     *
+     * @var ServiceLocatorInterface
+     */
+    protected $serviceLocator;
+
+    /**
+     * Database connection
+     *
+     * @var PDO
+     */
     protected $db;
-    protected $statusRankings = false;        // used by _pickStatus() method
+
+    /**
+     * Stored status rankings from the database; initialized to false but populated
+     * by the pickStatus() method.
+     *
+     * @var array|bool
+     */
+    protected $statusRankings = false;
 
     /**
      * Initialize the driver.
@@ -304,6 +325,7 @@ class Amicus extends AbstractBase
         $prestados = 0;
         $reservados = 0;
         $possibleQueries = array($items);
+
         // Loop through the possible queries and try each in turn -- the first one
         // that yields results will cause the function to return.
         foreach ($possibleQueries as $sql) {
@@ -323,9 +345,9 @@ class Amicus extends AbstractBase
                 $reservados = $this->sacaReservas($row['CPY_ID_NBR']);
                 if (!isset($data[$row['BIB_ITM_NBR']])) {
                     if ($multiple != 1 ) {
-                        $multiple = Translator::translate("Multiple Locations");
-                        $textoLoc = Translator::translate("Multiple");
-                        $textoSign = Translator::translate("Multiple Locations");
+                        $multiple = $this->translate("Multiple Locations");
+                        $textoLoc = $this->translate("Multiple");
+                        $textoSign = $this->translate("Multiple Locations");
                         $data[$row['BIB_ITM_NBR']] = array(
                             'id' => $id,
                             'status' => $prestados,
@@ -365,9 +387,9 @@ class Amicus extends AbstractBase
                     'id' => $id,
                     'status' => $prestados,
                     'status_array' => array($prestados),
-                    'location' => Translator::translate("No copies"),
+                    'location' => $this->translate("No copies"),
                     'reserve' => $reservados,
-                    'callnumber' => Translator::translate("No copies")
+                    'callnumber' => $this->translate("No copies")
                 );
                 break;
             }
@@ -956,4 +978,41 @@ class Amicus extends AbstractBase
 
         return $list;
     }
+
+    /**
+     * Set the service locator.
+     *
+     * @param ServiceLocatorInterface $serviceLocator Locator to register
+     *
+     * @return Amicus
+     */
+    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
+    {
+        $this->serviceLocator = $serviceLocator;
+        return $this;
+    }
+
+    /**
+     * Get the service locator.
+     *
+     * @return \Zend\ServiceManager\ServiceLocatorInterface
+     */
+    public function getServiceLocator()
+    {
+        return $this->serviceLocator;
+    }
+
+    /**
+     * Translate a string if a translator is available.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    protected function translate($msg)
+    {
+        $sm = $this->getServiceLocator()->getServiceLocator();
+        return $sm->has('Translator')
+            ? $sm->get('Translator')->translate($msg) : $msg;
+    }
 }
diff --git a/module/VuFind/src/VuFind/ILS/Driver/NoILS.php b/module/VuFind/src/VuFind/ILS/Driver/NoILS.php
index e48b2691e1a10247214a438b91f15165ff9ae147..eb22db3852e0b4b62b6a9f2e588114b4672c0ec4 100644
--- a/module/VuFind/src/VuFind/ILS/Driver/NoILS.php
+++ b/module/VuFind/src/VuFind/ILS/Driver/NoILS.php
@@ -28,7 +28,7 @@
  */
 namespace VuFind\ILS\Driver;
 use VuFind\Config\Reader as ConfigReader, VuFind\Exception\ILS as ILSException,
-    VuFind\Translator\Translator, Zend\ServiceManager\ServiceLocatorAwareInterface,
+    Zend\ServiceManager\ServiceLocatorAwareInterface,
     Zend\ServiceManager\ServiceLocatorInterface;
 
 /**
@@ -107,7 +107,7 @@ class NoILS extends AbstractBase implements ServiceLocatorAwareInterface
         $useStatus = isset($this->config['settings']['useStatus'])
             ? $this->config['settings']['useStatus'] : 'none';
         if ($useStatus == "custom") {
-            $status = Translator::translate($this->config['Status']['status']);
+            $status = $this->translate($this->config['Status']['status']);
             return array(
                 array(
                     'id' => $id,
@@ -116,11 +116,11 @@ class NoILS extends AbstractBase implements ServiceLocatorAwareInterface
                     'use_unknown_message' =>
                         $this->config['Status']['use_unknown_message'],
                     'status_array' => array($status),
-                    'location' => Translator::translate(
+                    'location' => $this->translate(
                         $this->config['Status']['location']
                     ),
                     'reserve' => $this->config['Status']['reserve'],
-                    'callnumber' => Translator::translate(
+                    'callnumber' => $this->translate(
                         $this->config['Status']['callnumber']
                     )
                 )
@@ -181,20 +181,20 @@ class NoILS extends AbstractBase implements ServiceLocatorAwareInterface
             return array(
                 array(
                     'id' => $id,
-                    'number' => Translator::translate(
+                    'number' => $this->translate(
                         $this->config['Holdings']['number']
                     ),
                     'availability' => $this->config['Holdings']['availability'],
-                    'status' => Translator::translate(
+                    'status' => $this->translate(
                         $this->config['Holdings']['status']
                     ),
                     'use_unknown_message' =>
                         $this->config['Holdings']['use_unknown_message'],
-                    'location' => Translator::translate(
+                    'location' => $this->translate(
                         $this->config['Holdings']['location']
                     ),
                     'reserve' => $this->config['Holdings']['reserve'],
-                    'callnumber' => Translator::translate(
+                    'callnumber' => $this->translate(
                         $this->config['Holdings']['callnumber']
                     ),
                     'barcode' => $this->config['Holdings']['barcode'],
@@ -360,4 +360,18 @@ class NoILS extends AbstractBase implements ServiceLocatorAwareInterface
     {
         return $this->serviceLocator;
     }
+
+    /**
+     * Translate a string if a translator is available.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    protected function translate($msg)
+    {
+        $sm = $this->getServiceLocator()->getServiceLocator();
+        return $sm->has('Translator')
+            ? $sm->get('Translator')->translate($msg) : $msg;
+    }
 }
diff --git a/module/VuFind/src/VuFind/ILS/Driver/Voyager.php b/module/VuFind/src/VuFind/ILS/Driver/Voyager.php
index d94d171b627a870ecdefd4059e14e84583835a89..fc88b5afa8eeedea981d126c74dbb1164a91f7a4 100644
--- a/module/VuFind/src/VuFind/ILS/Driver/Voyager.php
+++ b/module/VuFind/src/VuFind/ILS/Driver/Voyager.php
@@ -29,7 +29,9 @@
 namespace VuFind\ILS\Driver;
 use File_MARC, PDO, PDOException, VuFind\Config\Reader as ConfigReader,
     VuFind\Date\Converter as DateConverter, VuFind\Exception\Date as DateException,
-    VuFind\Exception\ILS as ILSException, VuFind\Translator\Translator,
+    VuFind\Exception\ILS as ILSException,
+    Zend\ServiceManager\ServiceLocatorAwareInterface,
+    Zend\ServiceManager\ServiceLocatorInterface,
     Zend\Validator\EmailAddress as EmailAddressValidator;
 
 /**
@@ -42,11 +44,42 @@ use File_MARC, PDO, PDOException, VuFind\Config\Reader as ConfigReader,
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/building_an_ils_driver Wiki
  */
-class Voyager extends AbstractBase
+class Voyager extends AbstractBase implements ServiceLocatorAwareInterface
 {
+    /**
+     * Service locator
+     *
+     * @var ServiceLocatorInterface
+     */
+    protected $serviceLocator;
+
+    /**
+     * Database connection
+     *
+     * @var PDO
+     */
     protected $db;
+
+    /**
+     * Name of database
+     *
+     * @var string
+     */
     protected $dbName;
-    protected $statusRankings = false;        // used by pickStatus() method
+
+    /**
+     * Stored status rankings from the database; initialized to false but populated
+     * by the pickStatus() method.
+     *
+     * @var array|bool
+     */
+    protected $statusRankings = false;
+
+    /**
+     * Date formatting object
+     *
+     * @var DateConverter
+     */
     protected $dateFormat;
 
     /**
@@ -1173,7 +1206,7 @@ class Voyager extends AbstractBase
      */
     protected function processFinesData($sqlRow)
     {
-        $dueDate = Translator::translate("not_applicable");
+        $dueDate = $this->translate("not_applicable");
         // Convert Voyager Format to display format
         if (!empty($sqlRow['DUEDATE'])) {
             $dueDate = $this->dateFormat->convertToDisplayDate(
@@ -1181,7 +1214,7 @@ class Voyager extends AbstractBase
             );
         }
 
-        $createDate = Translator::translate("not_applicable");
+        $createDate = $this->translate("not_applicable");
         // Convert Voyager Format to display format
         if (!empty($sqlRow['CREATEDATE'])) {
             $createDate = $this->dateFormat->convertToDisplayDate(
@@ -1189,7 +1222,7 @@ class Voyager extends AbstractBase
             );
         }
 
-        $chargeDate = Translator::translate("not_applicable");
+        $chargeDate = $this->translate("not_applicable");
         // Convert Voyager Format to display format
         if (!empty($sqlRow['CHARGEDATE'])) {
             $chargeDate = $this->dateFormat->convertToDisplayDate(
@@ -1309,7 +1342,7 @@ class Voyager extends AbstractBase
     protected function processMyHoldsData($sqlRow)
     {
         $available = ($sqlRow['HOLD_RECALL_STATUS'] == 2) ? true : false;
-        $expireDate = Translator::translate("Unknown");
+        $expireDate = $this->translate("Unknown");
         // Convert Voyager Format to display format
         if (!empty($sqlRow['EXPIRE_DATE'])) {
             $expireDate = $this->dateFormat->convertToDisplayDate(
@@ -1317,7 +1350,7 @@ class Voyager extends AbstractBase
             );
         }
 
-        $createDate = Translator::translate("Unknown");
+        $createDate = $this->translate("Unknown");
         // Convert Voyager Format to display format
         if (!empty($sqlRow['CREATE_DATE'])) {
             $createDate = $this->dateFormat->convertToDisplayDate(
@@ -1947,4 +1980,41 @@ class Voyager extends AbstractBase
 
         return $list;
     }
+
+    /**
+     * Set the service locator.
+     *
+     * @param ServiceLocatorInterface $serviceLocator Locator to register
+     *
+     * @return Amicus
+     */
+    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
+    {
+        $this->serviceLocator = $serviceLocator;
+        return $this;
+    }
+
+    /**
+     * Get the service locator.
+     *
+     * @return \Zend\ServiceManager\ServiceLocatorInterface
+     */
+    public function getServiceLocator()
+    {
+        return $this->serviceLocator;
+    }
+
+    /**
+     * Translate a string if a translator is available.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    protected function translate($msg)
+    {
+        $sm = $this->getServiceLocator()->getServiceLocator();
+        return $sm->has('Translator')
+            ? $sm->get('Translator')->translate($msg) : $msg;
+    }
 }
diff --git a/module/VuFind/src/VuFind/ILS/Driver/VoyagerRestful.php b/module/VuFind/src/VuFind/ILS/Driver/VoyagerRestful.php
index e420e666f0d0b397f7169b9e6f3cfa09af5a78ed..98710a053dc936d0c90164c133654ee74b4eddd4 100644
--- a/module/VuFind/src/VuFind/ILS/Driver/VoyagerRestful.php
+++ b/module/VuFind/src/VuFind/ILS/Driver/VoyagerRestful.php
@@ -28,12 +28,10 @@
  * @link     http://vufind.org/wiki/building_an_ils_driver Wiki
  */
 namespace VuFind\ILS\Driver;
-use PDOException, VuFind\Date\Converter as DateConverter,
-    VuFind\Exception\Date as DateException,
+use PDOException, VuFind\Exception\Date as DateException,
     VuFind\Exception\ILS as ILSException,
     VuFind\Http\Client as HttpClient,
-    VuFind\ILS\Connection as ILSConnection,
-    VuFind\Translator\Translator;
+    VuFind\ILS\Connection as ILSConnection;
 
 /**
  * Voyager Restful ILS Driver
diff --git a/module/VuFind/src/VuFind/Mailer.php b/module/VuFind/src/VuFind/Mailer.php
index 7b25274084fd5ca05de243c6a6b7862d72de0e95..69b6df75855655401eb0f2b33812f0a6a31f5988 100644
--- a/module/VuFind/src/VuFind/Mailer.php
+++ b/module/VuFind/src/VuFind/Mailer.php
@@ -27,8 +27,7 @@
  */
 namespace VuFind;
 use VuFind\Config\Reader as ConfigReader, VuFind\Exception\Mail as MailException,
-    VuFind\Translator\Translator, Zend\Mail\Message, Zend\Mail\Transport\Smtp,
-    Zend\Mail\Transport\SmtpOptions;
+    Zend\Mail\Message, Zend\Mail\Transport\Smtp, Zend\Mail\Transport\SmtpOptions;
 
 /**
  * VuFind Mailer Class
@@ -41,9 +40,27 @@ use VuFind\Config\Reader as ConfigReader, VuFind\Exception\Mail as MailException
  */
 class Mailer
 {
+    /**
+     * Configuration
+     *
+     * @var \Zend\Config\Config
+     */
     protected $config;
+
+    /**
+     * Mail transport
+     *
+     * @var \Zend\Mail\Transport\TransportInterface
+     */
     protected $transport;
 
+    /**
+     * Translator (or null if unavailable)
+     *
+     * @var \Zend\I18n\Translator\Translator
+     */
+    protected $translator = null;
+
     /**
      * Constructor
      *
@@ -60,6 +77,31 @@ class Mailer
         $this->config = is_null($config) ? ConfigReader::getConfig() : $config;
     }
 
+    /**
+     * Translate a string if a translator is provided.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    public function translate($msg)
+    {
+        return (null !== $this->translator)
+            ? $this->translator->translate($msg) : $msg;
+    }
+
+    /**
+     * Set a translator
+     *
+     * @param \Zend\I18n\Translator\Translator $translator Translator
+     *
+     * @return void
+     */
+    public function setTranslator(\Zend\I18n\Translator\Translator $translator)
+    {
+        $this->translator = $translator;
+    }
+
     /**
      * Get the mail transport object.
      *
@@ -174,7 +216,7 @@ class Mailer
         if (is_null($subject)) {
             $subject = 'Library Catalog Search Result';
         }
-        $subject = Translator::translate($subject);
+        $subject = $this->translate($subject);
         $body = $view->partial(
             'Email/share-link.phtml',
             array(
@@ -200,7 +242,7 @@ class Mailer
      */
     public function sendRecord($to, $from, $msg, $record, $view)
     {
-        $subject = Translator::translate('Library Catalog Record') . ': '
+        $subject = $this->translate('Library Catalog Record') . ': '
             . $record->getBreadcrumb();
         $body = $view->partial(
             'Email/record.phtml',
diff --git a/module/VuFind/src/VuFind/Recommend/AuthorInfo.php b/module/VuFind/src/VuFind/Recommend/AuthorInfo.php
index 8f9d68f3aaef25d75f6799efdf15abd23e0e01b2..a8ee990144a71c6a0d26be51efa0ffcc9709bfcf 100644
--- a/module/VuFind/src/VuFind/Recommend/AuthorInfo.php
+++ b/module/VuFind/src/VuFind/Recommend/AuthorInfo.php
@@ -26,8 +26,7 @@
  * @link     http://vufind.org/wiki/building_a_recommendations_module Wiki
  */
 namespace VuFind\Recommend;
-use VuFind\Config\Reader as ConfigReader, VuFind\Http\Client as HttpClient,
-    VuFind\Translator\Translator;
+use VuFind\Config\Reader as ConfigReader, VuFind\Http\Client as HttpClient;
 
 /**
  * AuthorInfo Recommendations Module
@@ -42,9 +41,20 @@ use VuFind\Config\Reader as ConfigReader, VuFind\Http\Client as HttpClient,
  * @link     http://vufind.org/wiki/building_a_recommendations_module Wiki
  * @view     AuthorInfoFacets.phtml
  */
-class AuthorInfo implements RecommendInterface
+class AuthorInfo extends AbstractSearchManagerAwareModule
 {
+    /**
+     * Saved search results
+     *
+     * @var \VuFind\Search\Base\Results
+     */
     protected $searchObject;
+
+    /**
+     * Selected language
+     *
+     * @var string
+     */
     protected $lang;
 
     /**
@@ -58,10 +68,21 @@ class AuthorInfo implements RecommendInterface
      */
     public function setConfig($settings)
     {
-        $translator = Translator::getTranslator();
+        $translator = $this->getTranslator();
         $this->lang = is_object($translator) ? $translator->getLocale() : 'en';
     }
 
+    /**
+     * Get translator object.
+     *
+     * @return \Zend\I18n\Translator\Translator
+     */
+    public function getTranslator()
+    {
+        $sm = $this->getServiceLocator()->getServiceLocator();
+        return $sm->has('Translator') ? $sm->get('Translator') : null;
+    }
+
     /**
      * init
      *
@@ -315,7 +336,7 @@ class AuthorInfo implements RecommendInterface
 
         // Fix pronunciation guides
         $pattern[] = '/({{)pron-en\|([^}]*)(}})/Us';
-        $replacement[] = Translator::translate("pronounced") . " /$2/";
+        $replacement[] = $this->getTranslator()->translate("pronounced") . " /$2/";
 
         // Fix dashes
         $pattern[] = '/{{ndash}}/';
diff --git a/module/VuFind/src/VuFind/Recommend/ResultGoogleMapAjax.php b/module/VuFind/src/VuFind/Recommend/ResultGoogleMapAjax.php
index 3b27714378e637c2ffadf11f9407d4e346f978e3..a972a6f5d4a0f086aabe7cb27f79d150751f59b4 100644
--- a/module/VuFind/src/VuFind/Recommend/ResultGoogleMapAjax.php
+++ b/module/VuFind/src/VuFind/Recommend/ResultGoogleMapAjax.php
@@ -26,7 +26,6 @@
  * @link     http://vufind.org/wiki/building_a_recommendations_module Wiki
  */
 namespace VuFind\Recommend;
-use VuFind\Translator\Translator;
 
 /**
  * AuthorInfo Recommendations Module
@@ -42,8 +41,13 @@ use VuFind\Translator\Translator;
  * @link     http://vufind.org/wiki/building_a_recommendations_module Wiki
  * @view     AuthorInfoFacets.phtml
  */
-class ResultGoogleMapAjax implements RecommendInterface
+class ResultGoogleMapAjax extends AbstractSearchManagerAwareModule
 {
+    /**
+     * Saved search results
+     *
+     * @var \VuFind\Search\Base\Results
+     */
     protected $searchObject;
 
     /**
@@ -95,6 +99,17 @@ class ResultGoogleMapAjax implements RecommendInterface
         $this->searchObject = $results;
     }
 
+    /**
+     * Get translator object.
+     *
+     * @return \Zend\I18n\Translator\Translator
+     */
+    public function getTranslator()
+    {
+        $sm = $this->getServiceLocator()->getServiceLocator();
+        return $sm->has('Translator') ? $sm->get('Translator') : null;
+    }
+
     /**
      * getUserLang
      *
@@ -102,7 +117,7 @@ class ResultGoogleMapAjax implements RecommendInterface
      */
     public function userLang()
     {
-        $translator = Translator::getTranslator();
+        $translator = $this->getTranslator();
         return is_object($translator) ? $translator->getLocale() : 'en';
     }
 
diff --git a/module/VuFind/src/VuFind/RecordDriver/AbstractBase.php b/module/VuFind/src/VuFind/RecordDriver/AbstractBase.php
index 46d6930761b0a5cb69f58cbf3e5b00d481d364ab..b43bae688193291940296aee6eff12c495ed5d72 100644
--- a/module/VuFind/src/VuFind/RecordDriver/AbstractBase.php
+++ b/module/VuFind/src/VuFind/RecordDriver/AbstractBase.php
@@ -28,8 +28,7 @@
 namespace VuFind\RecordDriver;
 use VuFind\Config\Reader as ConfigReader,
     VuFind\Exception\LoginRequired as LoginRequiredException,
-    VuFind\Tags, VuFind\Translator\Translator,
-    VuFind\XSLT\Import\VuFind as ArticleStripper,
+    VuFind\Tags, VuFind\XSLT\Import\VuFind as ArticleStripper,
     Zend\ServiceManager\ServiceLocatorInterface,
     Zend\ServiceManager\ServiceLocatorAwareInterface;
 
@@ -185,7 +184,7 @@ abstract class AbstractBase implements ServiceLocatorAwareInterface
         $table = $this->getDbTable('UserList');
         if (empty($listId) || $listId == 'NEW') {
             $list = $table->getNew($user);
-            $list->title = Translator::translate('My Favorites');
+            $list->title = $this->translate('My Favorites');
             $list->save($user);
         } else {
             $list = $table->getExisting($listId);
@@ -471,4 +470,18 @@ abstract class AbstractBase implements ServiceLocatorAwareInterface
         return $this->getServiceLocator()->getServiceLocator()
             ->get('DbTablePluginManager')->get($table);
     }
+
+    /**
+     * Translate a string if a translator is available.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    public function translate($msg)
+    {
+        $sm = $this->getServiceLocator()->getServiceLocator();
+        return $sm->has('Translator')
+            ? $sm->get('Translator')->translate($msg) : $msg;
+    }
 }
diff --git a/module/VuFind/src/VuFind/RecordDriver/Summon.php b/module/VuFind/src/VuFind/RecordDriver/Summon.php
index 71ab60c36c9075e2916b50fbdf33cbbf318569c6..03b7b9aecd2bb9fcbb47debc581bc098205495c9 100644
--- a/module/VuFind/src/VuFind/RecordDriver/Summon.php
+++ b/module/VuFind/src/VuFind/RecordDriver/Summon.php
@@ -26,9 +26,7 @@
  * @link     http://vufind.org/wiki/other_than_marc Wiki
  */
 namespace VuFind\RecordDriver;
-use VuFind\Config\Reader as ConfigReader,
-    VuFind\Date\Converter as DateConverter,
-    VuFind\Translator\Translator;
+use VuFind\Config\Reader as ConfigReader, VuFind\Date\Converter as DateConverter;
 
 /**
  * Model for Summon records.
@@ -464,7 +462,7 @@ class Summon extends SolrDefault
             return array(
                 array(
                     'url' => $this->fields['link'],
-                    'desc' => Translator::translate('Get full text')
+                    'desc' => $this->translate('Get full text')
                 )
             );
         }
@@ -560,7 +558,7 @@ class Summon extends SolrDefault
         $str = '';
         $vol = $this->getContainerVolume();
         if (!empty($vol)) {
-            $str .= Translator::translate('citation_volume_abbrev')
+            $str .= $this->translate('citation_volume_abbrev')
                 . ' ' . $vol;
         }
         $no = $this->getContainerIssue();
@@ -568,7 +566,7 @@ class Summon extends SolrDefault
             if (strlen($str) > 0) {
                 $str .= '; ';
             }
-            $str .= Translator::translate('citation_issue_abbrev')
+            $str .= $this->translate('citation_issue_abbrev')
                 . ' ' . $no;
         }
         $start = $this->getContainerStartPage();
@@ -578,10 +576,10 @@ class Summon extends SolrDefault
             }
             $end = $this->getContainerEndPage();
             if ($start == $end) {
-                $str .= Translator::translate('citation_singlepage_abbrev')
+                $str .= $this->translate('citation_singlepage_abbrev')
                     . ' ' . $start;
             } else {
-                $str .= Translator::translate('citation_multipage_abbrev')
+                $str .= $this->translate('citation_multipage_abbrev')
                     . ' ' . $start . ' - ' . $end;
             }
         }
diff --git a/module/VuFind/src/VuFind/Search/Base/Options.php b/module/VuFind/src/VuFind/Search/Base/Options.php
index 8324a3ee2de2521a87cc13bb0745e8a5d07d3421..e47e1affefb730ea5868bcfc3c15e48b746398ed 100644
--- a/module/VuFind/src/VuFind/Search/Base/Options.php
+++ b/module/VuFind/src/VuFind/Search/Base/Options.php
@@ -26,7 +26,9 @@
  * @link     http://www.vufind.org  Main Page
  */
 namespace VuFind\Search\Base;
-use VuFind\Translator\Translator, Zend\Session\Container as SessionContainer;
+use Zend\ServiceManager\ServiceLocatorAwareInterface,
+    Zend\ServiceManager\ServiceLocatorInterface,
+    Zend\Session\Container as SessionContainer;
 
 /**
  * Abstract options search model.
@@ -39,7 +41,7 @@ use VuFind\Translator\Translator, Zend\Session\Container as SessionContainer;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://www.vufind.org  Main Page
  */
-abstract class Options
+abstract class Options implements ServiceLocatorAwareInterface
 {
     // Available sort options
     protected $sortOptions = array();
@@ -62,10 +64,18 @@ abstract class Options
     protected $defaultView = 'list';
     protected $viewOptions = array();
 
-    // Facet settings
+    /**
+     * Facet settings
+     *
+     * @var array
+     */
     protected $translatedFacets = array();
 
-    // Spelling
+    /**
+     * Spelling setting
+     *
+     * @var bool
+     */
     protected $spellcheck = true;
 
     // Shard settings
@@ -73,16 +83,41 @@ abstract class Options
     protected $defaultSelectedShards = array();
     protected $visibleShardCheckboxes = false;
 
-    // Highlighting
+    /**
+     * Highlighting setting
+     *
+     * @var bool
+     */
     protected $highlight = false;
 
-    // Autocomplete setting
+    /**
+     * Autocomplete setting
+     *
+     * @var bool
+     */
     protected $autocompleteEnabled = false;
 
-    // Configuration files to read search settings from
+    /**
+     * Configuration file to read search settings from
+     *
+     * @var string
+     */
     protected $searchIni = 'searches';
+
+    /**
+     * Configuration file to read facet settings from
+     *
+     * @var string
+     */
     protected $facetsIni = 'facets';
 
+    /**
+     * Service locator
+     *
+     * @var ServiceLocatorInterface
+     */
+    protected $serviceLocator;
+
     /**
      * Constructor
      */
@@ -308,9 +343,9 @@ abstract class Options
     public function getHumanReadableFieldName($field)
     {
         if (isset($this->basicHandlers[$field])) {
-            return Translator::translate($this->basicHandlers[$field]);
+            return $this->translate($this->basicHandlers[$field]);
         } else if (isset($this->advancedHandlers[$field])) {
-            return Translator::translate($this->advancedHandlers[$field]);
+            return $this->translate($this->advancedHandlers[$field]);
         } else {
             return $field;
         }
@@ -510,4 +545,40 @@ abstract class Options
         // No limit by default:
         return -1;
     }
+
+    /**
+     * Set the service locator.
+     *
+     * @param ServiceLocatorInterface $serviceLocator Locator to register
+     *
+     * @return Params
+     */
+    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
+    {
+        $this->serviceLocator = $serviceLocator;
+        return $this;
+    }
+
+    /**
+     * Get the service locator.
+     *
+     * @return \Zend\ServiceManager\ServiceLocatorInterface
+     */
+    public function getServiceLocator()
+    {
+        return $this->serviceLocator;
+    }
+
+    /**
+     * Translate a string if a translator is available.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    public function translate($msg)
+    {
+        return $this->getServiceLocator()->has('Translator')
+            ? $this->getServiceLocator()->get('Translator')->translate($msg) : $msg;
+    }
 }
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Base/Params.php b/module/VuFind/src/VuFind/Search/Base/Params.php
index c90df097bd9758687b0848827782104db9bddbdf..6215d78808a06b729cf5d792687d40246d4d2163 100644
--- a/module/VuFind/src/VuFind/Search/Base/Params.php
+++ b/module/VuFind/src/VuFind/Search/Base/Params.php
@@ -26,7 +26,7 @@
  * @link     http://www.vufind.org  Main Page
  */
 namespace VuFind\Search\Base;
-use VuFind\Config\Reader as ConfigReader, VuFind\Translator\Translator,
+use VuFind\Config\Reader as ConfigReader,
     Zend\ServiceManager\ServiceLocatorAwareInterface,
     Zend\ServiceManager\ServiceLocatorInterface;
 
@@ -625,10 +625,10 @@ class Params implements ServiceLocatorAwareInterface
                 && $search['group'][0]['bool'] == 'NOT'
             ) {
                 $excludes[]
-                    = join(' ' . Translator::translate('OR') . ' ', $thisGroup);
+                    = join(' ' . $this->translate('OR') . ' ', $thisGroup);
             } else if (isset($search['group'][0]['bool'])) {
                 $groups[] = join(
-                    " " . Translator::translate($search['group'][0]['bool'])." ",
+                    " " . $this->translate($search['group'][0]['bool'])." ",
                     $thisGroup
                 );
             }
@@ -640,7 +640,7 @@ class Params implements ServiceLocatorAwareInterface
             $output .= "(" .
                 join(
                     ") " .
-                    Translator::translate($this->searchTerms[0]['join']) . " (",
+                    $this->translate($this->searchTerms[0]['join']) . " (",
                     $groups
                 ) .
                 ")";
@@ -649,8 +649,8 @@ class Params implements ServiceLocatorAwareInterface
         // Concatenate exclusion after that
         if (count($excludes) > 0) {
             $output .= ' ' .
-                Translator::translate('NOT') . ' ((' .
-                join(') ' . Translator::translate('OR') . ' (', $excludes) . "))";
+                $this->translate('NOT') . ' ((' .
+                join(') ' . $this->translate('OR') . ' (', $excludes) . "))";
         }
 
         return $output;
@@ -1070,7 +1070,7 @@ class Params implements ServiceLocatorAwareInterface
                     $list[$facetLabel][] = array(
                         'value'       => $value,
                         'displayText' =>
-                            $translate ? Translator::translate($value) : $value,
+                            $translate ? $this->translate($value) : $value,
                         'field'       => $field
                     );
                 }
@@ -1564,4 +1564,17 @@ class Params implements ServiceLocatorAwareInterface
     {
         return $this->getServiceLocator()->get('DbTablePluginManager')->get($table);
     }
+
+    /**
+     * Translate a string if a translator is available.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    public function translate($msg)
+    {
+        return $this->getServiceLocator()->has('Translator')
+            ? $this->getServiceLocator()->get('Translator')->translate($msg) : $msg;
+    }
 }
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Base/Results.php b/module/VuFind/src/VuFind/Search/Base/Results.php
index 55c232ae82d84bbc5a7611da5f7d2a67a5c1c2a1..eda31e089c9ebff0d16dbd5a79e056e0c91e04fa 100644
--- a/module/VuFind/src/VuFind/Search/Base/Results.php
+++ b/module/VuFind/src/VuFind/Search/Base/Results.php
@@ -613,4 +613,17 @@ abstract class Results implements ServiceLocatorAwareInterface
     {
         return $this->getServiceLocator()->get('DbTablePluginManager')->get($table);
     }
+
+    /**
+     * Translate a string if a translator is available.
+     *
+     * @param string $msg Message to translate
+     *
+     * @return string
+     */
+    public function translate($msg)
+    {
+        return $this->getServiceLocator()->has('Translator')
+            ? $this->getServiceLocator()->get('Translator')->translate($msg) : $msg;
+    }
 }
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Search/Favorites/Results.php b/module/VuFind/src/VuFind/Search/Favorites/Results.php
index 992f8e6c9e1abc28a186763a23ff7a7dc9b4f489..3a113508a1846ced2d8b3c55b87707adc0eddb6e 100644
--- a/module/VuFind/src/VuFind/Search/Favorites/Results.php
+++ b/module/VuFind/src/VuFind/Search/Favorites/Results.php
@@ -27,8 +27,7 @@
  */
 namespace VuFind\Search\Favorites;
 use VuFind\Exception\ListPermission as ListPermissionException,
-    VuFind\Search\Base\Results as BaseResults,
-    VuFind\Translator\Translator;
+    VuFind\Search\Base\Results as BaseResults;
 
 /**
  * Search Favorites Results
@@ -137,7 +136,7 @@ class Results extends BaseResults
             && (!$this->user || $list->user_id != $this->user->id)
         ) {
             throw new ListPermissionException(
-                Translator::translate('list_access_denied')
+                $this->translate('list_access_denied')
             );
         }
 
diff --git a/module/VuFind/src/VuFind/Search/Solr/Results.php b/module/VuFind/src/VuFind/Search/Solr/Results.php
index bf43b6a528dc2baf362f76437ab5743ef02c169a..393dffcdd0ef6b7ed4c69dded94cad3f0c57eebf 100644
--- a/module/VuFind/src/VuFind/Search/Solr/Results.php
+++ b/module/VuFind/src/VuFind/Search/Solr/Results.php
@@ -29,8 +29,7 @@ namespace VuFind\Search\Solr;
 use VuFind\Config\Reader as ConfigReader,
     VuFind\Connection\Manager as ConnectionManager,
     VuFind\Exception\RecordMissing as RecordMissingException,
-    VuFind\Search\Base\Results as BaseResults,
-    VuFind\Translator\Translator;
+    VuFind\Search\Base\Results as BaseResults;
 
 /**
  * Solr Search Parameters
@@ -432,7 +431,7 @@ class Results extends BaseResults
                 $currentSettings = array();
                 $currentSettings['value'] = $facet[0];
                 $currentSettings['displayText']
-                    = $translate ? Translator::translate($facet[0]) : $facet[0];
+                    = $translate ? $this->translate($facet[0]) : $facet[0];
                 $currentSettings['count'] = $facet[1];
                 $currentSettings['isApplied']
                     = $this->getParams()->hasFilter("$field:".$facet[0]);
diff --git a/module/VuFind/src/VuFind/Search/Summon/Results.php b/module/VuFind/src/VuFind/Search/Summon/Results.php
index c9f37078256c9b53368d4a7854c6f36b352d3420..ffd5424e7fbeedd061ee1409af25822480d2e7c7 100644
--- a/module/VuFind/src/VuFind/Search/Summon/Results.php
+++ b/module/VuFind/src/VuFind/Search/Summon/Results.php
@@ -30,8 +30,7 @@ use VuFind\Config\Reader as ConfigReader,
     VuFind\Connection\Summon as SummonConnection,
     VuFind\Connection\Summon\Query as SummonQuery,
     VuFind\Exception\RecordMissing as RecordMissingException,
-    VuFind\Search\Base\Results as BaseResults,
-    VuFind\Translator\Translator;
+    VuFind\Search\Base\Results as BaseResults;
 
 /**
  * Summon Search Parameters
@@ -243,7 +242,7 @@ class Results extends BaseResults
 
                         // Create display value:
                         $current['counts'][$facetIndex]['displayText'] = $translate
-                            ? Translator::translate($facetDetails['value'])
+                            ? $this->translate($facetDetails['value'])
                             : $facetDetails['value'];
                     }
 
diff --git a/module/VuFind/src/VuFind/Theme/Root/Helper/DisplayLanguageOption.php b/module/VuFind/src/VuFind/Theme/Root/Helper/DisplayLanguageOption.php
index 0ff6252bfa1fd72d7ce2086b1bbcb880bf044d70..639da8a7563a42564bca3d47d7b856cfdef366f4 100644
--- a/module/VuFind/src/VuFind/Theme/Root/Helper/DisplayLanguageOption.php
+++ b/module/VuFind/src/VuFind/Theme/Root/Helper/DisplayLanguageOption.php
@@ -26,7 +26,6 @@
  * @link     http://vufind.org/wiki/building_a_recommendations_module Wiki
  */
 namespace VuFind\Theme\Root\Helper;
-use VuFind\Translator\Translator, Zend\View\Helper\AbstractHelper;
 
 /**
  * DisplayLanguageOption view helper
@@ -37,22 +36,32 @@ use VuFind\Translator\Translator, Zend\View\Helper\AbstractHelper;
  * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
  * @link     http://vufind.org/wiki/building_a_recommendations_module Wiki
  */
-class DisplayLanguageOption extends AbstractHelper
+class DisplayLanguageOption extends AbstractServiceLocator
 {
-    protected $translator;
+    /**
+     * Translator (or null if unavailable)
+     *
+     * @var \Zend\I18n\Translator\Translator
+     */
+    protected $translator = null;
 
     /**
-     * Constructor
+     * Get translator object.
+     *
+     * @return \Zend\I18n\Translator\Translator
      */
-    public function __construct()
+    public function getTranslator()
     {
-        $this->translator = clone(Translator::getTranslator());
-        $this->translator->addTranslationFile(
-            'ExtendedIni',
-            APPLICATION_PATH  . '/languages/native.ini',
-            'default', 'native'
-        );
-        $this->translator->setLocale('native');
+        if (null === $this->translator) {
+            $this->translator = clone($this->getServiceLocator()->get('Translator'));
+            $this->translator->addTranslationFile(
+                'ExtendedIni',
+                APPLICATION_PATH  . '/languages/native.ini',
+                'default', 'native'
+            );
+            $this->translator->setLocale('native');
+        }
+        return $this->translator;
     }
 
     /**
@@ -64,6 +73,6 @@ class DisplayLanguageOption extends AbstractHelper
      */
     public function __invoke($str)
     {
-        return $this->view->escapeHtml($this->translator->translate($str));
+        return $this->view->escapeHtml($this->getTranslator()->translate($str));
     }
 }
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Translator/Translator.php b/module/VuFind/src/VuFind/Translator/Translator.php
deleted file mode 100644
index 5bbe710b97a0c9bf749b72ab87d8b66fe2f443de..0000000000000000000000000000000000000000
--- a/module/VuFind/src/VuFind/Translator/Translator.php
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-/**
- * VuFind Translator
- *
- * PHP version 5
- *
- * Copyright (C) Villanova University 2010.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
- *
- * 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 more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * @category VuFind2
- * @package  Translator
- * @author   Demian Katz <demian.katz@villanova.edu>
- * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link     http://www.vufind.org  Main Page
- */
-namespace VuFind\Translator;
-use VuFind\Translator\Loader\ExtendedIni as ExtendedIniLoader,
-    Zend\I18n\Translator\TranslatorServiceFactory;
-
-/**
- * Wrapper class to handle text translation.
- *
- * TODO -- eliminate this (using DI?)
- *
- * @category VuFind2
- * @package  Translator
- * @author   Demian Katz <demian.katz@villanova.edu>
- * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
- * @link     http://www.vufind.org  Main Page
- */
-class Translator
-{
-    protected static $translator = null;
-
-    /**
-     * Set the translator object.
-     *
-     * @param \Zend\I18n\Translator\Translator $translator Translator object.
-     *
-     * @return void
-     */
-    public static function setTranslator($translator)
-    {
-        static::$translator = $translator;
-    }
-
-    /**
-     * Retrieve the translator object.
-     *
-     * @return \Zend\I18n\Translator\Translator
-     */
-    public static function getTranslator()
-    {
-        return static::$translator;
-    }
-
-    /**
-     * Initialize the translator.
-     *
-     * @param \Zend\Mvc\MvcEvent $event    Zend MVC Event object
-     * @param string             $language Selected language.
-     *
-     * @return void
-     */
-    public static function init($event, $language)
-    {
-        // Set up the actual translator object:
-        $factory = new TranslatorServiceFactory();
-        $serviceManager = $event->getApplication()->getServiceManager();
-        $translator = $factory->createService($serviceManager);
-        $translator->addTranslationFile(
-            'ExtendedIni',
-            APPLICATION_PATH  . '/languages/' . $language . '.ini',
-            'default', $language
-        );
-        $translator->setLocale($language);
-        $serviceManager->setService('translator', $translator);
-
-        // Set up the ExtendedIni plugin:
-        $pluginManager = $translator->getPluginManager();
-        $pluginManager->setService('extendedini', new ExtendedIniLoader());
-
-        // Set up language caching for better performance:
-        $translator
-            ->setCache($serviceManager->get('CacheManager')->getCache('language'));
-
-        // Store the translator object in the VuFind Translator wrapper:
-        self::setTranslator($translator);
-    }
-
-    /**
-     * Translate a string using the Translate view helper.
-     *
-     * @param string $str String to translate
-     *
-     * @return string
-     */
-    public static function translate($str)
-    {
-        if (is_object(static::$translator)) {
-            return static::$translator->translate($str);
-        } else {
-            throw new \Exception('Translator not initialized');
-        }
-    }
-}
\ No newline at end of file