diff --git a/classes/Controller.php b/classes/Controller.php
index 48e034a8af5e69fb96a7e3b66e1a7c1fb2e5e0ff..ecaae42bdb26ca8712cb83d4b7ad2e3c9121509b 100644
--- a/classes/Controller.php
+++ b/classes/Controller.php
@@ -18,62 +18,15 @@ use SearchWidget;
 use SelectElement;
 use SelectWidget;
 use Sidebar;
-use StudipController;
+use PluginController;
 use Trails_Flash;
 use URLHelper;
 
-class Controller extends StudipController
+class Controller extends PluginController
 {
     protected $allow_nobody = false;
     protected $temp_storage;
 
-    /**
-     * Constructs the controller and provide translations methods.
-     *
-     * @param object $dispatcher
-     * @see https://stackoverflow.com/a/12583603/982902 if you need to overwrite
-     *      the constructor of the controller
-     */
-    public function __construct($dispatcher)
-    {
-        parent::__construct($dispatcher);
-
-        $this->plugin = $dispatcher->current_plugin;
-
-        // Localization
-        $this->_ = function ($string) use ($dispatcher) {
-            return call_user_func_array(
-                [$dispatcher->current_plugin, '_'],
-                func_get_args()
-            );
-        };
-
-        $this->_n = function ($string0, $tring1, $n) use ($dispatcher) {
-            return call_user_func_array(
-                [$dispatcher->current_plugin, '_n'],
-                func_get_args()
-            );
-        };
-    }
-
-    /**
-     * Intercepts all non-resolvable method calls in order to correctly handle
-     * calls to _ and _n.
-     *
-     * @param string $method
-     * @param array  $arguments
-     * @return mixed
-     * @throws RuntimeException when method is not found
-     */
-    public function __call($method, $arguments)
-    {
-        $variables = get_object_vars($this);
-        if (isset($variables[$method]) && is_callable($variables[$method])) {
-            return call_user_func_array($variables[$method], $arguments);
-        }
-        return parent::__call($method, $arguments);
-    }
-
     public function before_filter(&$action, &$args)
     {
         $this->flash = Trails_Flash::instance();
diff --git a/classes/Plugin.php b/classes/Plugin.php
index 0b9456189eb3b7c89d56794e6525b66324a35fe4..6083d9f42549617a364b88bc8e8eccf48c4847d8 100644
--- a/classes/Plugin.php
+++ b/classes/Plugin.php
@@ -7,15 +7,12 @@ use StudIPPlugin;
 
 abstract class Plugin extends StudIPPlugin
 {
-    const GETTEXT_DOMAIN = 'schwarzes-brett';
+    use \TranslatablePluginTrait;
 
     public function __construct()
     {
         parent::__construct();
 
-        bindtextdomain(static::GETTEXT_DOMAIN, $this->getPluginPath() . '/locale');
-        bind_textdomain_codeset(static::GETTEXT_DOMAIN, 'UTF-8');
-
         foreach (get_class_methods($this) as $method) {
             if (!preg_match('/^on\w+(Did|Will)\w+$/', $method)) {
                 continue;
@@ -26,60 +23,14 @@ abstract class Plugin extends StudIPPlugin
         }
     }
 
-    /**
-     * Plugin localization for a single string.
-     * This method supports sprintf()-like execution if you pass additional
-     * parameters.
-     *
-     * @param String $string String to translate
-     * @return translated string
-     */
-    public function _($string)
+    public static function getTranslationDomain()
     {
-        $result = static::GETTEXT_DOMAIN === null
-                ? $string
-                : dcgettext(static::GETTEXT_DOMAIN, $string, LC_MESSAGES);
-        if ($result === $string) {
-            $result = _($string);
-        }
-
-        if (func_num_args() > 1) {
-            $arguments = array_slice(func_get_args(), 1);
-            $result = vsprintf($result, $arguments);
-        }
-
-        return $result;
+        return 'schwarzes-brett';
     }
 
-    /**
-     * Plugin localization for plural strings.
-     * This method supports sprintf()-like execution if you pass additional
-     * parameters.
-     *
-     * @param String $string0 String to translate (singular)
-     * @param String $string1 String to translate (plural)
-     * @param mixed  $n       Quantity factor (may be an array or array-like)
-     * @return translated string
-     */
-    public function _n($string0, $string1, $n)
+    public function getTranslationPath()
     {
-        if (is_array($n)) {
-            $n = count($n);
-        }
-
-        $result = static::GETTEXT_DOMAIN === null
-                ? $string0
-                : dngettext(static::GETTEXT_DOMAIN, $string0, $string1, $n);
-        if ($result === $string0 || $result === $string1) {
-            $result = ngettext($string0, $string1, $n);
-        }
-
-        if (func_num_args() > 3) {
-            $arguments = array_slice(func_get_args(), 3);
-            $result = vsprintf($result, $arguments);
-        }
-
-        return $result;
+        return $this->getPluginPath() . '/locale';
     }
 
     /**