diff --git a/backend/helper/RendererHelper.php b/backend/helper/RendererHelper.php
index 54ff2d3a22ac0a58b999c5c60e094b24562e15ba..5a06c0e60f12ba288505d36ed7c43e6eeb6e0c57 100644
--- a/backend/helper/RendererHelper.php
+++ b/backend/helper/RendererHelper.php
@@ -3,12 +3,15 @@
 namespace QuasselRestSearch;
 
 require_once 'ViewHelper.php';
+require_once 'TranslationHelper.php';
 
 class RendererHelper {
     private $config;
+    private $translator;
 
     public function __construct(Config $config) {
         $this->config = $config;
+        $this->translator = new TranslationHelper($config);
     }
 
     public function renderError($e) {
@@ -31,7 +34,8 @@ class RendererHelper {
     }
 
     public function renderPage(string $template, array $vars = null) {
-        $viewHelper = new ViewHelper($vars);
+        $translation = $this->translator->loadTranslation($this->translator->findMatchingLanguage($_SERVER['HTTP_ACCEPT_LANGUAGE']));
+        $viewHelper = new ViewHelper($translation, $vars);
         $viewHelper->render($template);
     }
 
diff --git a/backend/helper/TranslationHelper.php b/backend/helper/TranslationHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..6e5cdbc422eb451a8c0840d41ccb8d40a19cba74
--- /dev/null
+++ b/backend/helper/TranslationHelper.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace QuasselRestSearch;
+
+class TranslationHelper {
+    protected $template_dir;
+
+    public function __construct($translation) {
+        $this->setPath('../../translations/');
+    }
+
+    public function setPath(string $path) {
+        $this->template_dir = realpath(dirname(__FILE__) . '/' . $path);
+    }
+
+    public function findMatchingLanguage(string $language_str) : string {
+        $languages = explode(",", $language_str);
+        foreach ($languages as $language) {
+            $language = explode(";", $language)[0];
+            if ($this->exists($language)) {
+                return $language;
+            }
+        }
+        return "en";
+    }
+
+    public function exists($language) : bool {
+        return file_exists($this->path($language));
+    }
+
+    private function path($language) : string {
+        return $this->template_dir . '/' . $language . '.json';
+    }
+
+    public function loadTranslation($language) : array {
+        return json_decode(file_get_contents($this->path($language)), true);
+    }
+}
\ No newline at end of file
diff --git a/backend/helper/ViewHelper.php b/backend/helper/ViewHelper.php
index 7ba94c2a4f1d766061d3efa8f3382315a06f5c8c..ff7e78adcf431c560083d36ade96e64a204dc619 100644
--- a/backend/helper/ViewHelper.php
+++ b/backend/helper/ViewHelper.php
@@ -4,9 +4,11 @@ namespace QuasselRestSearch;
 
 class ViewHelper {
     protected $template_dir;
+    protected $translation;
     protected $vars = [];
 
-    public function __construct($vars = null) {
+    public function __construct($translation, $vars = null) {
+        $this->translation = $translation;
         $this->setPath('../../templates/');
         if ($vars !== null) {
             $this->vars = $vars;
@@ -18,6 +20,15 @@ class ViewHelper {
     }
 
     public function render($template_file) {
+        $translation = $this->translation;
+        $t = function ($path) use ($translation) {
+            $arr = explode(".", $path);
+            $var = $translation;
+            foreach ($arr as $key)
+                $var = $var[$key];
+            echo $var;
+        };
+
         $path = $this->template_dir . '/' . $template_file . '.phtml';
         if (file_exists($path)) {
             include $path;
diff --git a/res/search.css b/res/search.css
index 60d5ddd7c1a150e6d96187e9ea8bac7b892a90f0..4739af0cfbfded8c8756e2f8eed6676e8007ba18 100644
--- a/res/search.css
+++ b/res/search.css
@@ -120,6 +120,13 @@ nav input[type=text]::-moz-placeholder {
     -moz-osx-font-smoothing: grayscale;
 }
 
+nav input[type=text]::-webkit-input-placeholder {
+    color: #ffffff;
+    opacity: 1;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+}
+
 nav input[type=text]:focus::-moz-placeholder {
     color: #757575;
     opacity: 1;
@@ -127,6 +134,13 @@ nav input[type=text]:focus::-moz-placeholder {
     -moz-osx-font-smoothing: grayscale;
 }
 
+nav input[type=text]:focus::-webkit-input-placeholder {
+    color: #757575;
+    opacity: 1;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+}
+
 #searchbar {
     max-width: 1136px;
     margin: 10px auto;
diff --git a/res/search.js b/res/search.js
index 0c96b81ca741e5cbf82584329aff94b2bd2bdcc0..ddf2db75dea1f36f4beaabc7397041c8e17380b3 100644
--- a/res/search.js
+++ b/res/search.js
@@ -1,5 +1,3 @@
-var translation = translations.en;
-
 var state = {
     query: "",
     selected_history_entry: -1,
diff --git a/res/translations.js b/res/translations.js
deleted file mode 100644
index a963c48e24621c2c3344b80d43da971293a53308..0000000000000000000000000000000000000000
--- a/res/translations.js
+++ /dev/null
@@ -1,15 +0,0 @@
-var translations = {};
-
-translations.en = {
-    results: {
-        show_more: "Show More Results",
-        load_more: "Load More Results"
-    },
-    context: {
-        load_later: "Load Later Context",
-        load_earlier: "Load Earlier Context"
-    },
-    history: {
-        error_unavailable: "No search history available"
-    }
-};
\ No newline at end of file
diff --git a/templates/login.phtml b/templates/login.phtml
index a73a601ac3b7fb06ac5f41a718d31334e7194e88..ecf708414123e2d3bfa2f0dbfd07865c168d19c3 100644
--- a/templates/login.phtml
+++ b/templates/login.phtml
@@ -2,7 +2,7 @@
 <html>
 <head>
     <meta charset="utf-8">
-    <title>Quassel Search</title>
+    <title><?php $t('title'); ?></title>
 
     <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
     <meta name="apple-mobile-web-app-capable" content="yes"/>
@@ -15,8 +15,8 @@
 </head>
 <body>
 
-<h1>Quassel Search</h1>
-<h2>You have to login to access this page</h2>
+<h1><?php $t('title'); ?></h1>
+<h2><?php $t('login.description'); ?></h2>
 
 <form method="post" action="login.php?action=login">
     <input name="username" type="text" placeholder="Username">
diff --git a/templates/search.phtml b/templates/search.phtml
index 9d16a1050f6db2bcfe9ddfa65477887daf02164e..423d31c939e8c19f87409add26f610d7e022a909 100644
--- a/templates/search.phtml
+++ b/templates/search.phtml
@@ -2,7 +2,7 @@
 <html>
 <head>
     <meta charset="utf-8">
-    <title>Quassel Search</title>
+    <title><?php $t('title'); ?></title>
 
     <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, width=device-width">
     <meta name="apple-mobile-web-app-capable" content="yes"/>
@@ -17,12 +17,12 @@
 <nav>
     <div class="wrapper">
         <div id="searchbar">
-            <input name="q" id="q" placeholder="Search" type="text">
+            <input name="q" id="q" placeholder="<?php $t('search'); ?>" type="text">
             <div id="searchicon" class="icon">search</div>
         </div>
     </div>
     <div id="actions">
-        <a title="Logout" href="login.php?action=logout">
+        <a title="<?php $t('logout'); ?>" href="login.php?action=logout">
             <span class="icon">account_circle</span>
         </a>
     </div>
@@ -38,7 +38,9 @@
 <div id="bg"></div>
 
 <script src="res/jquery.js"></script>
-<script src="res/translations.js"></script>
+<script>
+    var translation = <?php echo json_encode($translation); ?>
+</script>
 <script src="res/sendercolor.js"></script>
 <script src="res/search.js"></script>
 </body>
diff --git a/translations/de.json b/translations/de.json
new file mode 100644
index 0000000000000000000000000000000000000000..e834c6a9ffe5f631a62bce66452767b1ca54c2c5
--- /dev/null
+++ b/translations/de.json
@@ -0,0 +1,19 @@
+{
+  "results": {
+    "show_more": "Show More Results",
+    "load_more": "Load More Results"
+  },
+  "context": {
+    "load_later": "Load Later Context",
+    "load_earlier": "Load Earlier Context"
+  },
+  "history": {
+    "error_unavailable": "No search history available"
+  },
+  "login": {
+    "description": "You have to login to access this page"
+  },
+  "search": "Suchen",
+  "logout": "Abmelden",
+  "title": "QuasselSearch"
+}
\ No newline at end of file
diff --git a/translations/en.json b/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..2008ab311306f60d805e4741780d72ecaef1a99c
--- /dev/null
+++ b/translations/en.json
@@ -0,0 +1,19 @@
+{
+  "results": {
+    "show_more": "Show More Results",
+    "load_more": "Load More Results"
+  },
+  "context": {
+    "load_later": "Load Later Context",
+    "load_earlier": "Load Earlier Context"
+  },
+  "history": {
+    "error_unavailable": "No search history available"
+  },
+  "login": {
+    "description": "You have to login to access this page"
+  },
+  "search": "Search",
+  "logout": "Logout",
+  "title": "QuasselSearch"
+}
\ No newline at end of file