diff --git a/classes/cronlog-renderer.class.php b/classes/cronlog-renderer.class.php
index 0a730ca0260da8bf15d093f1ac265e446ce3faca..12348bfd08e3ed0039110bb2a4356a27cfed24d0 100644
--- a/classes/cronlog-renderer.class.php
+++ b/classes/cronlog-renderer.class.php
@@ -52,8 +52,8 @@ class cronlogrenderer extends cronlog{
         }
         $iAge=round((date('U')-$iLast)/60);
         return ''
-            . 'Abruf: '.date("Y-m-d H:i:s").' min<br>'
-            . 'letzter Eintrag: vor '.$iAge.' min<br><br>'
+            . sprintf($this->t("request-time"), date("Y-m-d H:i:s")).'<br>'
+            . sprintf($this->t("last-entry"), $iAge).'<br><br>'
             ;
     }
     
@@ -65,11 +65,6 @@ class cronlogrenderer extends cronlog{
      * 
      */
     protected function _filterDatatable($sDatatable, $sFiltertext){
-                /* 
-                <a href="#" title="Filtere nach VHost «crawler.ascii.iml.unibe.ch:443»" 
-                    onclick="$('#tableRenderedallrequestsphp1_filter>INPUT').val('crawler.ascii.iml.unibe.ch:443'); 
-                        $('#tableRenderedallrequestsphp1').dataTable().fnFilter('crawler.ascii.iml.unibe.ch:443'); return false;">crawler.ascii.iml.unibe.ch:443</a>
-                */
         return '$(\'#'.$sDatatable.'\').dataTable().fnFilter(\''.$sFiltertext.'\'); return false;';
     }
 
@@ -97,7 +92,16 @@ class cronlogrenderer extends cronlog{
         // Array ( [SCRIPTNAME] => apt-get update [SCRIPTTTL] => 1440 [SCRIPTSTARTTIME] => 2016-06-21 06:00:02, 1466481602 [SCRIPTLABEL] => apt-get [SCRIPTENDTIME] => 2016-06-21 06:00:49, 1466481649 [SCRIPTEXECTIME] => 47 s [SCRIPTRC] => 0 )
         foreach($aData as $sDtakey=>$aEntry){
             if(!$sTblHead){
-                foreach(array('Startzeit', 'Label', 'Server', 'Dauer', 'TTL', '$?', 'Expired', 'Status' /*, 'Aktionen'*/) as $sKey){
+                foreach(array(
+                    $this->t("col-starting-time"),
+                    $this->t("col-label"),
+                    $this->t("col-server"), 
+                    $this->t("col-duration"), 
+                    $this->t("col-ttl"), 
+                    $this->t("col-rc"), 
+                    $this->t("col-expired"), 
+                    $this->t("col-status"), 
+                    ) as $sKey){
                     $sTblHead.='<th>'.$sKey.'</th>';
                 }
             }
@@ -115,25 +119,29 @@ class cronlogrenderer extends cronlog{
             $sServerFromLogfile=preg_replace('/_.*/', '', basename($aEntry['logfile']));
             if($sServerFromLogfile!=$aEntry['server']){
                 $aErrors[]=[
-                    'Hostname?',
-                    'Der Hostname im Log ['.$sServerFromLogfile.'] stimmt nicht mit Servernamen ['.$aEntry['server'].'] &uuml;berein.',
+                    $this->t('error-host-in-log-differs-servername-label'),
+                    sprintf($this->t('error-host-in-log-differs-servername-description'),$sServerFromLogfile, $aEntry['server']),
                 ];
             }
             
             if(!strstr($sServerFromLogfile, ".")){
                 $aErrors[]=[
-                    'No FQDN',
-                    'Der Hostname im Log ['.$sServerFromLogfile.'] ist kein KFQDN.',
+                    $this->t('error-no-fqdn-label'),
+                    sprintf($this->t('error-no-fqdn-description'),$sServerFromLogfile),
                 ];
             }
             
             if($iNextRunErr < date("U")){
                 $aErrors[]=[
-                    'Abgelaufen',
-                    'Job wurde nicht mehr gestartet oder kein Sync zum Logserver',
+                    $this->t('error-expired-label'),
+                    $this->t('error-expired-description'),
                 ];
             }
             if($aEntry['SCRIPTRC']>0){
+                $aErrors[]=[
+                    sprintf($this->t('error-exitcode-label'), (int)$aEntry['SCRIPTRC']),
+                    $this->t('error-exitcode-description'),
+                ];
                 $aErrors[]=[
                     'Exitcode '.(int)$aEntry['SCRIPTRC'].' (<>0)',
                     'The command finished with a non error status'
@@ -141,8 +149,8 @@ class cronlogrenderer extends cronlog{
             }
             if(!$aEntry['SCRIPTLABEL']){
                 $aErrors[]=[
-                    'Kein Label?',
-                    'No label was detected for this job. Check the scheduled cronjob on server.'
+                    $this->t('error-no-label-label'),
+                    $this->t('error-no-label-description'),
                 ];
             }
             
@@ -165,14 +173,14 @@ class cronlogrenderer extends cronlog{
             $sColStatus='';
             if (count($aErrors)){
                 foreach($aErrors as $aErr){
-                    $sColStatus.='<li><abbr title="'.$aErr[1].'">FEHLER: '.$aErr[0].'</abbr></li>';
+                    $sColStatus.='<li><abbr title="'.$aErr[1].'">'.$this->t('status-error').': '.$aErr[0].'</abbr></li>';
                 }
                 $sColStatus='<ul>'.$sColStatus.'</ul>';
             } else {
-                $sColStatus.='OK';
+                $sColStatus.=$this->t('status-ok');
             }
             // render table of last logfile per cron job
-            $sHtml.='<tr onclick="showFile(\''.$aEntry['logfile'].'\');" title="Klick=['.$aEntry['logfile'].'] anzeigen ">'
+            $sHtml.='<tr onclick="showFile(\''.$aEntry['logfile'].'\');" title="'.sprintf($this->t('row-click-show-logfile'), $aEntry['logfile']).'">'
                     . '<td>'.date("Y-m-d H:i:s", $aEntry['SCRIPTSTARTTIME']).'</td>'
                     // . '<td>'.$aEntry['SCRIPTNAME'].'</td>'
                     . '<td>'.$aEntry['SCRIPTLABEL'].'</td>'
@@ -208,17 +216,17 @@ class cronlogrenderer extends cronlog{
             <!-- START '.__METHOD__.' -->
             '
         
-            . '<h3>Letztes Logfile pro Job</h3>'
-                . '<p class="hint">'
-                    . 'Von jedem Cronjob kann man das jeweils letzte Log im Detail ansehen. Mit Klick in der Tabelle wird die Logdatei ge&ouml;ffnet.'
-                . '</p>'
-                . '<div>'
+            . '<h3>'.$this->t('logs-head').'</h3>'
+            . '<p class="hint">'
+                . $this->t('logs-hint')
+            . '</p>'
+            . '<div>'
                 . $this->_renderAccessAndAge($iLast)
-                . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="'.$this->_filterDatatable($sIdTable, "FEHLER").'">' . $iErrors.'</a> ' : '')
-                . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, "OK").'">' . $iOK.'</a>' : '') 
-                . ($iErrors && $iOK ? ' ... gesamt: <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'">' . count($aData).'</a>' : '' )
+                . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-error')).'">' . $iErrors.'</a> ' : '')
+                . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-ok')).'">' . $iOK.'</a>' : '') 
+                . ($iErrors && $iOK ? ' ... '.$this->t('total').': <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'">' . count($aData).'</a>' : '' )
                 . '<br>'
-                . '</div>'
+            . '</div>'
             . '<table id="'.$sIdTable.'" class="table-striped">'
             . '<thead><tr>'.$sTblHead.'</tr></thead>'
             . '<tbody>'
@@ -262,7 +270,7 @@ class cronlogrenderer extends cronlog{
         $sReturn='';
         $sServer=isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : false;
         $sReturn.='<li class="nav-item"><a class="nav-link nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a></li>'
-            . '<li class="nav-item d-none d-sm-inline-block"><a href="#" class="nav-link">Instances:</a></li>';
+            . '<li class="nav-item d-none d-sm-inline-block"><a href="#" class="nav-link">'.$this->t('instances').':</a></li>';
         
         foreach($this->_aInstances as $sInstance => $sUrl){
             $sHost=parse_url($sUrl, PHP_URL_HOST);
@@ -297,7 +305,14 @@ class cronlogrenderer extends cronlog{
         // job=dok-kvm-instances:host=kalium:start=1538358001:end=1538358001:exectime=0:ttl=60:rc=0
         foreach($aData as $aEntry){
             if(!$sTblHead){
-                foreach(array('Startzeit', /*'Ende',*/ 'Label', 'Server', 'Dauer', 'TTL', '$?') as $sKey){
+                foreach(array(
+                    $this->t("col-starting-time"),
+                    $this->t("col-label"),
+                    $this->t("col-server"), 
+                    $this->t("col-duration"), 
+                    $this->t("col-ttl"), 
+                    $this->t("col-rc"), 
+                ) as $sKey){
                     $sTblHead.='<th>'.$sKey.'</th>';
                 }
             }
@@ -332,17 +347,16 @@ class cronlogrenderer extends cronlog{
         $sHtml='
             <!-- START '.__METHOD__.' -->
             '
-        
-            . '<h3>History</h3>'
-                . '<p class="hint">'
-                    . 'Von den gestarteten Cronjobs werden die Ausf&uuml;hrungszeiten und deren Exitcode f&uuml;r 6 Tage aufgehoben.'
-                . '</p>'
-                . '<div>'
+            . '<h3>'.$this->t('history-head').'</h3>'
+            . '<p class="hint">'
+                . $this->t('history-hint')
+            . '</p>'
+            . '<div>'
                 . $this->_renderAccessAndAge($iLast)
-                . 'gesamt: <strong>' . count($aData).'</strong>'
-                . ($iErrors ? ' (Fehler: <strong>' . $iErrors.'</strong>... OK: <strong>' . $iOK.'</strong>)' : '')
+                . $this->t('total').': <strong>' . count($aData).'</strong>'
+                . ($iErrors ? ' ('.$this->t('status-error').': <strong>' . $iErrors.'</strong>... '.$this->t('status-ok').': <strong>' . $iOK.'</strong>)' : '')
                 . '<br><br>'
-                . '</div>'
+            . '</div>'
             . '<table id="'.$sIdTable.'">'
             . '<thead><tr>'.$sTblHead.'</tr></thead>'
             . '<tbody>'
@@ -447,18 +461,16 @@ class cronlogrenderer extends cronlog{
         
         <!-- START '.__METHOD__.' -->
 
-        <h3>Graph mit Timeline</h3>
-        <p class="hint">
-            Aus der History der letzten 6 Tage werden die Cronjobs mit mehr als '.$this->_iMinTime4Timeline.' Sekunden Laufzeit dargestellt.<br>
-            So kann man ggf. Konflikte und Ungereimtheiten finden.<br>
-            Innerhalb der Timeline kann man mit dem Mausrad den Zoom ver&auml;ndern.
-        </p>
-
-        <p>
-            Jobs mit mehr als '.$this->_iMinTime4Timeline.'s Laufzeit: <strong>'.count($aDataset).'</strong> (ges.: '.count($aData).')<br><br>
-            '. (count($aDataset) ? $this->_renderAccessAndAge($iLast) : '' ).'
-        </p>
         '
+        . '<h3>'.$this->t('timeline-head').'</h3>'
+        . '<p class="hint">'
+            . sprintf($this->t('timeline-hint'), $this->_iMinTime4Timeline)
+        . '</p>
+        <p>'
+            .sprintf($this->t('graph-rendered-jobs'), $this->_iMinTime4Timeline).': <strong>'.count($aDataset).'</strong> '
+            .'('.$this->t('total').': '.count($aData).')<br><br>'
+            .(count($aDataset) ? $this->_renderAccessAndAge($iLast) : '' )
+        .'</p>'
         .(count($aDataset) ?
             '<div id="'.$sDivId.'"></div>
 
@@ -481,7 +493,9 @@ class cronlogrenderer extends cronlog{
               // fix: some timelines do not properly work ... but I make them visible
               $(\'#'.$sDivId.' .vis-timeline\').css(\'visibility\', \'visible\');
             </script>
-            ' : '(kein Graph)<br>')
+            ' 
+            : $this->t('graph-no-data').'<br>'
+        )
         .'
 
         <!-- ENDE '.__METHOD__.'-->
@@ -514,19 +528,19 @@ class cronlogrenderer extends cronlog{
     */
    public function renderLogfile($sLogfile){
         $sHtml=''
-                . '<button style="position: fixed;" onclick="closeOverlay();" class="btn btn-default"><i class="fas fa-chevron-left"></i> back</button><br><br>'
-                . '<h3>Logfile '.basename($sLogfile).'</h3>'
+                . '<button style="position: fixed;" onclick="closeOverlay();" class="btn btn-default"><i class="fas fa-chevron-left"></i> '.$this->t('back').'</button><br><br>'
+                . '<h3>'. $this->t('logfile').' '.basename($sLogfile).'</h3>'
                 ;
         if(!$sLogfile){
-            return $sHtml . 'ERROR: empty filename for log file was given.';
+            return $sHtml . $this->t('error-nologfile');
         }
         if(strstr($sLogfile, '..')){
-            return $sHtml . 'ERROR: wrong log file chars [..] are not allowed.';
+            return $sHtml . $this->t('error-dots-not-allowed');
         }
         // $sMyFile=$this->_getServerlogDir().'/'.$sLogfile;
         $sMyFile=$this->_sDataDir.'/'.$sLogfile;
         if(!file_exists($sMyFile)){
-            return $sHtml . 'ERROR: The requested logfile<br>['.$sMyFile.']<br>does not exist (anymore).';
+            return $sHtml . sprintf($this->t('error-logfile-not-found'), $sMyFile);
         }
 
         
@@ -574,7 +588,7 @@ class cronlogrenderer extends cronlog{
         $iMaxItemsToShow=30;
         $sHtml.='<option value="ALL"'
                 .($sSelectedItem===false || $sSelectedItem==='ALL' ? ' selected="selected"' : '')
-                . '>[ALLE ('.count($this->getServers()).')]</option>';
+                . '>['.$this->t('ALL').' ('.count($this->getServers()).')]</option>';
         foreach($this->getServers() as $sServer=>$aData){
             $sHtml.='<option value="'.$sServer.'"'
                     .($sSelectedItem===$sServer ? ' selected="selected"' : '')
diff --git a/classes/cronlog.class.php b/classes/cronlog.class.php
index d0a0afee369572da935bf05b512af27b9697c586..a5dca3b8391a72241574edcb4a6982912100dd77 100644
--- a/classes/cronlog.class.php
+++ b/classes/cronlog.class.php
@@ -55,6 +55,9 @@ class cronlog {
     protected $_sFileFilter_serverjoblog = '*joblog*.done';
     protected $_sFileFilter_serverlog = '*.log';
 
+    protected $_sLang = ''; // language ... read from config file
+    protected $_aLang = []; // language data
+
     // ----------------------------------------------------------------------
     // MAIN
     // ----------------------------------------------------------------------
@@ -75,6 +78,8 @@ class cronlog {
             $this->_aSkipJoblogs = isset($aCfgTemp['aHidelogs']) && is_array($aCfgTemp['aHidelogs']) ? $aCfgTemp['aHidelogs'] : $this->_aSkipJoblogs;        
             $this->_aInstances = isset($aCfgTemp['instances']) ? $aCfgTemp['instances'] : [];
 
+            $this->_sLang=$aCfgTemp['lang'] ? $aCfgTemp['lang'] : 'en-en';
+            $this->_aLang=$aCfgTemp=include(__DIR__.'/../config/lang_'.$this->_sLang.'.php');
         }
         $this->_sDataDir = str_replace("__APPDIR__", dirname(dirname(__FILE__)), $this->_sDataDir);
         $this->_sDataDir = str_replace('\\', '/', $this->_sDataDir);
@@ -299,6 +304,14 @@ class cronlog {
         return $aReturn;
     }
     
+    /**
+     * translate ... get a language specific text of a given key
+     */
+    public function t($id){
+        return ''
+            .(isset($this->_aLang[$id]) ? $this->_aLang[$id] : '['.$id.'] ???')
+            ;
+    }
     // ----------------------------------------------------------------------
     // public setter
     // ----------------------------------------------------------------------
diff --git a/config/lang_de-de.php b/config/lang_de-de.php
new file mode 100644
index 0000000000000000000000000000000000000000..2383747ccfc34964e205aa47cbd59c1c7952383e
--- /dev/null
+++ b/config/lang_de-de.php
@@ -0,0 +1,86 @@
+<?php
+
+return [
+
+    "id"           => "deutsch",
+
+    // ---------- left bar
+    "search"       => "Suche",
+
+    // ---------- top bar
+    "instances"    => "Instanzen",
+
+    // ---------- content
+
+    "ALL"          => "alle Server",
+
+
+    "request-time" => "Abruf: %s",
+    "last-entry"   => "Letzer Eintrag: vor <strong>%s</strong> min",
+
+    "total" => "Gesamt",
+
+    "logs"         => "Logdateien",
+    "logs-head" => "Letztes Logfile pro Job",
+    "logs-hint" => "Von jedem Cronjob kann man das jeweils letzte Log im Detail ansehen. Mit Klick in der Tabelle wird die Logdatei geöffnet.",
+
+    "history" => "History",
+    "history-head" => "History",
+    "history-hint" => "Von den gestarteten Cronjobs werden die Ausf&uuml;hrungszeiten und deren Exitcode f&uuml;r 6 Tage aufgehoben.",
+
+    "timeline"      => "Timeline",
+    "timeline-head" => "Graph mit Timeline",
+    "timeline-hint" => "Aus der History der letzten 6 Tage werden die Cronjobs mit mehr als %s Sekunden Laufzeit dargestellt.<br>"
+        ."So kann man ggf. Konflikte und Ungereimtheiten finden.<br>"
+        ."Innerhalb der Timeline kann man mit dem Mausrad den Zoom ver&auml;ndern.",
+
+
+    // ---------- table
+    "col-starting-time"   => "Startzeit",
+    "col-label"   => "Label",
+    "col-server"   => "Server",
+    "col-duration"   => "Dauer",
+    "col-ttl"   => "TTL",
+    "col-rc"   => "$?",
+    "col-expired"   => "veraltet?",
+    "col-status"   => "Status",
+    "status-ok"    => "OK",
+    "status-error" => "FEHLER",
+    
+
+    "row-click-show-logfile" => "Klick=[%s] anzeigen",
+    
+    // ---------- errors
+    "error-host-in-log-differs-servername-label"          => "Hostname?",
+    "error-host-in-log-differs-servername-description"    => "Der Hostname im Log [%s] stimmt nicht mit Servernamen [%s] &uuml;berein.",
+    "error-no-fqdn-label"          => "Kein FQDN",
+    "error-no-fqdn-description"    => "Der Hostname im Log [%s] ist kein FQDN.",
+    "error-expired-label"          => "Abgelaufen",
+    "error-expired-description"    => "Job wurde nicht mehr gestartet oder kein Sync zum Logserver.",
+    "error-exitcode-label"          => "Exitcode %s (<> 0)",
+    "error-exitcode-description"    => "Job wurde nicht mit exitcode 0 beendet.",
+    "error-no-label-label"          => "Kein Label?",
+    "error-no-label-description"    => "Es wurde kein Label f&uuml;r den Cronjob erkannt. Bitte Kommandozeile des Jobs pr&uuml;fen.",
+
+    "graph-rendered-jobs" => "Jobs mit mehr als %s s Laufzeit",
+    "graph-no-data" => "(Keine Daten zum Rendern eines Graphen)",
+
+    // ---------- popup with log file
+    "back" => "zur&uuml;ck",
+    "logfile" => "Logdatei",
+
+    "error-no-logfile" => "ERROR: empty filename for log file was given.",
+    "error-dots-not-allowed" => "ERROR: wrong log file chars [..] are not allowed.",
+    "error-logfile-not-found" => "ERROR: The requested logfile<br>[%s]<br>does not exist (anymore).",
+
+    // ---------- navigation
+    // "all" => "ALLE",
+
+    // ---------- javascript texts
+    "JS" => [
+    ],
+
+    "" => "",
+
+
+];
\ No newline at end of file
diff --git a/config/page.tpl.php b/config/page.tpl.php
index 3659b4545b783f4ff1f1201e17d01e6230c1ca9b..9ae00c0e054fd7724d2b601907bc40edc858a3bd 100644
--- a/config/page.tpl.php
+++ b/config/page.tpl.php
@@ -447,6 +447,7 @@
         </footer>
     </div>
 
+    <script>{{INJECT_JS}}</script>
     <script src="{{DIR_ADMINLTEPLUGINS}}/bootstrap/js/bootstrap.bundle.min.js"></script>
     <script src="{{DIR_ADMINLTE}}/js/adminlte.min.js?v=3.2.0"></script>
     <script type="text/javascript" src="js/functions.js"></script>
diff --git a/config/page_replacements.php b/config/page_replacements.php
index c063d2fb8091c7d3e88f3f63cdd0f16a40fd7573..f0e71e92fe7b6b063cb3f8b08147e284f080f275 100644
--- a/config/page_replacements.php
+++ b/config/page_replacements.php
@@ -60,13 +60,13 @@ return [
                             <ul class="navbar-nav">
                                 <li class="nav-item">
                                     <a href="#cronlogs"    onclick="setTab(this); return false;"  class="nav-link active">
-                                        <i class="far fa-file-alt"></i> Logs</a></li>
+                                        <i class="far fa-file-alt"></i> '.$cr->t('logs').'</a></li>
                                 <li class="nav-item">
                                     <a href="#cronhistory" onclick="setTab(this); return false;" class="nav-link">
-                                        <i class="fas fa-history"></i> History</a></li>
+                                        <i class="fas fa-history"></i> '.$cr->t('history').'</a></li>
                                 <li class="nav-item">
                                     <a href="#graph"       onclick="setTab(this); return false;" class="nav-link">
-                                        <i class="far fa-chart-bar"></i> Timeline</a></li>
+                                        <i class="far fa-chart-bar"></i> '.$cr->t('timeline').'</a></li>
                             </ul>
                             </nav>',
     '{{PAGE_HEADER_RIGHT}}'=>'<span id="counter" style="float: right;"></span>',
@@ -92,6 +92,7 @@ return [
                       </div>
           
                       ',
-    '{{PAGE_FOOTER_LEFT}}'=>'2018 -2022 // University of Bern * Institute for Medical education',
+    '{{INJECT_JS}}' => '<!-- TODO -->',
+    '{{PAGE_FOOTER_LEFT}}'=>'2018 - '.date('Y').' // University of Bern * Institute for Medical education',
     '{{PAGE_FOOTER_RIGHT}}'=>'Source: <a href="https://git-repo.iml.unibe.ch/iml-open-source/cronlog-viewer/" target="_blank">git-repo.iml.unibe.ch</a>',
 ];
\ No newline at end of file
diff --git a/index.php b/index.php
index f27f56482ce2b42db4cdac40fcc819bcda69c2a0..9ba69ba3f9b65b29781bf48bf6be16934aa82ed5 100644
--- a/index.php
+++ b/index.php
@@ -1,9 +1,11 @@
 <?php
 
-define("APP_VERSION", '2.0.3');
+define("APP_VERSION", '2.0.4-dev');
 
 require_once('classes/render-adminlte.class.php');
+require_once('classes/cronlog-renderer.class.php');
 $renderAdminLTE=new renderadminlte();
+$cr=new cronlogrenderer();
 
 $aReplace=include("./config/page_replacements.php");
 $sTemplate=file_get_contents('config/page.tpl.php');