diff --git a/composer.json b/composer.json
index 4ca2d1092b73f04f4311d985ebd36bcd3608783c..8c300f4ac3178a468d446810813036cea791a1e1 100644
--- a/composer.json
+++ b/composer.json
@@ -31,7 +31,6 @@
         "robrichards/xmlseclibs": "~2.0",
         "whitehat101/apr1-md5": "~1.0",
         "twig/twig": "~1.0",
-        "twig/extensions": "^1.3",
         "gettext/gettext": "^3.5"
     },
     "require-dev": {
diff --git a/lib/SimpleSAML/Locale/Localization.php b/lib/SimpleSAML/Locale/Localization.php
index 7fbef2b3af7d8e2cba0e8ec68ec876ffbca85474..03a7576bb89b7e3c09eacf9923f905b2ed5fdc00 100644
--- a/lib/SimpleSAML/Locale/Localization.php
+++ b/lib/SimpleSAML/Locale/Localization.php
@@ -9,6 +9,9 @@
 
 namespace SimpleSAML\Locale;
 
+use Gettext\Translations;
+use Gettext\Translator;
+
 class Localization
 {
 
@@ -22,13 +25,24 @@ class Localization
     /**
      * The default gettext domain.
      */
-    private $domain = 'ssp';
+    const DEFAULT_DOMAIN = 'ssp';
+
+    /**
+     * Default 1i18n backend
+     */
+    const DEFAULT_I18NBACKEND = 'twig.gettextgettext';
 
     /*
-     * The locale directory
+     * The default locale directory
      */
     private $localeDir;
 
+    /*
+     * Where specific domains are stored
+     */
+    private $localeDomainMap = array();
+
+
     /**
      * Constructor
      *
@@ -39,40 +53,95 @@ class Localization
         $this->configuration = $configuration;
         $this->localeDir = $this->configuration->resolvePath('locales');
         $this->language = new Language($configuration);
+        $this->langcode = $this->language->getPosixLanguage($this->language->getLanguage());
         $this->i18nBackend = $this->configuration->getString('language.i18n.backend', null);
         $this->setupL10N();
     }
 
+    /*
+     * Add a new translation domain
+     * (We're assuming that each domain only exists in one place)
+     *
+     * @param string $localeDir Location of translations
+     * @param string $domain Domain at location
+     */
+    private function addDomain($localeDir, $domain)
+    {
+        $this->localeDomainMap[$domain] = $localeDir;
+    }
+
+
+    /**
+     * Load translation domain from Gettext/Gettext using .po
+     *
+     * @param string $domain Name of domain
+     */
+    private function loadGettextGettextFromPO($domain = self::DEFAULT_DOMAIN) {
+        $langcode = explode('_', $this->langcode)[0];
+        $localeDir = $this->localeDomainMap[$domain];
+        $poPath = $localeDir.'/'.$langcode.'/LC_MESSAGES/'.$domain.'.po';
+        $translations = Translations::fromPoFile($poPath);
+        $t = new Translator();
+        $t->loadTranslations($translations);
+        $t->register();
+    }
+
+
+    /**
+     * Test to check if backend is set to default
+     *
+     * (if false: backend unset/there's an error)
+     */
+    public function isI18NBackendDefault()
+    {
+        if ($this->i18nBackend === $this::DEFAULT_I18NBACKEND) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Set up L18N if configured or fallback to old system
+     */
     private function setupL10N() {
         // use old system
-        if (is_null($this->i18nBackend)) {
+        if (! $this->isI18NBackendDefault()) {
+            \SimpleSAML\Logger::debug("Localization: using old system");
             return;
         }
-        $encoding = "UTF-8";
-        $langcode = $this->language->getPosixLanguage($this->language->getLanguage());
-        // use gettext and Twig.I18n
-        if ($this->i18nBackend == 'twig.i18n') {
-            putenv('LC_ALL='.$langcode);
-            setlocale(LC_ALL, $langcode);
-            bindtextdomain($this->domain, $this->localeDir);
-            bind_textdomain_codeset($this->domain, $encoding);
-        }
+        // setup default domain
+        $this->addDomain($this->localeDir, self::DEFAULT_DOMAIN);
+        $this->activateDomain(self::DEFAULT_DOMAIN);
     }
 
 
+    /**
+     * Set which translation domain to use
+     *
+     * @param string $domain Name of domain
+     */
     public function activateDomain($domain)
     {
-        if ($this->i18nBackend == 'twig.i18n') {
-            textdomain($domain);
-        }
+        \SimpleSAML\Logger::debug("Localization: activate domain");
+        $this->loadGettextGettextFromPO($domain);
+        $this->currentDomain = $domain;
     }
 
+    /**
+     * Get current translation domain
+     */
+    public function getCurrentDomain()
+    {
+        return $this->currentDomain ? $this->currentDomain : self::DEFAULT_DOMAIN;
+    }
 
+    /**
+     * Go back to default translation domain
+     */
     public function restoreDefaultDomain()
     {
-        if ($this->i18nBackend == 'twig.i18n') {
-            textdomain($this->domain);
-        }
+        $this->loadGettextGettextFromPO(self::DEFAULT_DOMAIN);
+        $this->currentDomain = self::DEFAULT_DOMAIN;
     }
 }
-
diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php
index 1bad9c98d65d8c3774678713d25241ddbd7a2d08..f8ad7e968d4ba26adcc163c487bee2cd47f13361 100644
--- a/lib/SimpleSAML/XHTML/Template.php
+++ b/lib/SimpleSAML/XHTML/Template.php
@@ -7,6 +7,8 @@
  * @author Andreas Ă…kre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
  * @package SimpleSAMLphp
  */
+
+
 class SimpleSAML_XHTML_Template
 {
 
@@ -154,9 +156,15 @@ class SimpleSAML_XHTML_Template
         }
 
         $twig = new \Twig_Environment($loader, array('cache' => $cache, 'auto_reload' => $auto_reload));
-        if ($this->localization->i18nBackend == 'twig.i18n') {
-            $this->localization->activateDomain('ssp');
-            $twig->addExtension(new \Twig_Extensions_Extension_I18n());
+        // set up translation
+        if ($this->localization->i18nBackend == 'twig.gettextgettext') {
+            /* if something like pull request #166 is ever merged with
+             * twig.extensions.i18n, use the line below:
+             * $twig->addExtension(new \Twig_Extensions_Extension_I18n('__', 'n__'));
+             * instead of the two lines after this comment
+             */
+            $twig->addFilter(new Twig_SimpleFilter('trans', '__'));
+            $twig->addTokenParser(new \SimpleSAML_Twig_TokenParser_Trans());
         }
         return $twig;
     }
@@ -264,6 +272,7 @@ class SimpleSAML_XHTML_Template
      */
     private function twigDefaultContext()
     {
+        $this->data['localeBackend'] = $this->configuration->getString('language.i18n.backend', 'ssp');
         $this->data['currentLanguage'] = $this->translator->getLanguage()->getLanguage();
         // show language bar by default
         if (!isset($this->data['hideLanguageBar'])) {
diff --git a/templates/sandbox.twig b/templates/sandbox.twig
index 97f0b64fdd52646ab138b18212d713a7f25165f5..3e1b94627f3076a27442e2af94c56923ed8a0a92 100644
--- a/templates/sandbox.twig
+++ b/templates/sandbox.twig
@@ -2,7 +2,10 @@
 {% block content %}
     <p>This page exists as a sandbox to play with twig without affecting anything else. The template is in ./templates.</p>
     <p>{{ sometext }}</p>
+    <h2>And now for some localization</h2>
+    <p>Locale backend in use: {{ localeBackend }}</p>
     <p>Original: Hello, Untranslated World!</p>
     <p>Translated: {% trans 'Hello, Untranslated World!' %}</p>
+    <p>Filtertrans-test: {{ 'Hello, Untranslated World!'|trans }}</p>
     <p>Current locale set: {{ currentLanguage }}</p>
 {% endblock content %}