summaryrefslogtreecommitdiffstats
path: root/modules/translation
diff options
context:
space:
mode:
Diffstat (limited to 'modules/translation')
-rw-r--r--modules/translation/config.json4
-rw-r--r--modules/translation/module.inc.php597
-rw-r--r--modules/translation/templates/_page.html17
-rw-r--r--modules/translation/templates/edit.html71
-rw-r--r--modules/translation/templates/module-list.html32
-rw-r--r--modules/translation/templates/template-list.html32
6 files changed, 753 insertions, 0 deletions
diff --git a/modules/translation/config.json b/modules/translation/config.json
new file mode 100644
index 00000000..4e7fa5fb
--- /dev/null
+++ b/modules/translation/config.json
@@ -0,0 +1,4 @@
+{
+ "category":"content",
+ "enabled":"true"
+}
diff --git a/modules/translation/module.inc.php b/modules/translation/module.inc.php
new file mode 100644
index 00000000..3548f727
--- /dev/null
+++ b/modules/translation/module.inc.php
@@ -0,0 +1,597 @@
+<?php
+
+class Page_Translation extends Page
+{
+
+ /**
+ * The pages where you can administrate the website translations
+ * @var string|boolean holds the target template
+ * @var string|boolean used to choose which page to load
+ * @var string|boolean used check if there should be an update
+ * @var string|boolean used check if there should be a deletion
+ * @var array|boolean holds the tags of the selected template
+ */
+ private $template = false;
+ private $page = false;
+
+ protected function doPreprocess()
+ {
+ User::load();
+
+ if (!User::hasPermission('superadmin')) {
+ Message::addError('no-permission');
+ Util::redirect('?do=Main');
+ }
+
+ if (Request::post('update')) {
+ $this->updateJson();
+ Util::redirect('?do=Translation');
+ }
+ if (Request::post('delete')) {
+ $this->deleteTag(Request::post('path'), Request::post('delete'));
+ Util::redirect('?do=Translation'); // TODO: Ajax post for delete so we stay on the page
+ }
+
+ $this->template = Request::get('template');
+ $this->page = Request::get('page');
+ }
+
+ protected function doRender()
+ {
+ $langs = Dictionary::getLanguages(true);
+
+ //load the page accordingly to the link
+ switch ($this->page) {
+ case 'messages':
+ //renders the message edit page
+ Render::addTemplate('edit', array(
+ 'path' => 'messages',
+ 'langs' => $langs,
+ 'tags' => $this->loadMessageEditArray()
+ ));
+ break;
+ case 'hardcoded':
+ //renders the hardcoded messages edit page
+ Render::addTemplate('edit', array(
+ 'path' => 'messages-hardcoded',
+ 'langs' => $langs,
+ 'tags' => $this->loadHardcodedStringEditArray()
+ ));
+ break;
+ case 'settings':
+ //renders the settings related edit page
+ Render::addTemplate('edit', array(
+ 'path' => 'cat_setting',
+ 'langs' => $langs,
+ 'tags' => $this->loadCategoriesArray()
+ ));
+ Render::addTemplate('edit', array(
+ 'path' => 'setting',
+ 'langs' => $langs,
+ 'tags' => $this->loadSettingsArray()
+ ));
+ break;
+ case 'config-module':
+ //renders the hardcoded messages edit page
+ Render::addTemplate('edit', array(
+ 'path' => 'config-module',
+ 'langs' => $langs,
+ 'tags' => $this->buildTranslationTable('config-module')
+ ));
+ break;
+ case 'template':
+ $this->template = Util::safePath($this->template);
+ if ($this->template === false) {
+ Message::addError('invalid-path');
+ Util::redirect('?do=Translation');
+ }
+ //renders the tag edition page
+ Render::addTemplate('edit', array(
+ 'path' => 'modules/' . $this->template,
+ 'langs' => $langs,
+ 'tags' => $this->loadTemplateEditArray($this->template)
+ ));
+ break;
+ case 'templates':
+ //renders the template selection page
+ Render::addTemplate('template-list', array(
+ 'table' => $this->loadTemplatesList(),
+ ));
+ break;
+ case 'modules':
+ Render::addTemplate('module-list', array(
+ 'table' => $this->loadModuleTable()
+ ));
+ break;
+ default:
+ //renders main page with selection of what part to edit
+ Render::addTemplate('_page');
+ }
+ }
+
+ private function loadModuleTable(){
+ $table = array();
+
+ $modules = $this->loadModuleList();
+
+ foreach ($modules as $module) {
+ $msgs = $this->checkModuleTranslation($module);
+ $table[] = array(
+ 'module' => $module,
+ 'status' => $msgs
+ );
+ }
+
+ sort($table);
+ return $table;
+ }
+
+ private function loadModuleEdit(){
+ $table = array();
+ $tags = array_flip($this->loadModuleTags($this->module));
+ foreach ($this->langs as $lang) {
+ $tags = array_merge($tags, Dictionary::getArray($this->module,$lang['cc']));
+ }
+ foreach ($tags as $tag => $value) {
+ $langArray = array();
+ $class = '';
+ foreach ($this->langs as $lang) {
+ $translations = Dictionary::getArray($this->module,$lang['cc']);
+ $langArray[] = array(
+ 'lang' => $lang['cc'],
+ 'placeholder' => 'TAG - ' . $lang['name'],
+ 'translation' => $translations[$tag]
+ );
+ if(!in_array($tag, $this->loadModuleTags($this->module)))
+ $class = 'danger';
+ else if(!$translations[$tag])
+ $class = 'warning';
+ }
+ $table[] = array(
+ 'tag' => $tag,
+ 'class' => $class,
+ 'langs' => $langArray
+ );
+ }
+
+ return $table;
+ }
+
+ private function loadModuleList(){
+ // Return an array with the modules and the tags data
+ $list = array();
+ $list = array_diff(scandir('modules/'), array('..', '.'));
+ return $list;
+ }
+
+ private function loadModuleTags($module){
+ // Return an array with the module language tags
+ $path = "modules/" . $module . "templates/";
+ $files = array_diff(scandir($path), array('..', '.'));
+ $tags = array();
+ foreach ($files as $file) {
+ $content = file_get_contents($path . $file);
+ preg_match_all('/{{(lang_.*?)}}/s', $content, $matches);
+ if (isset($matches[1]) && is_array($matches[1])){
+ $tags = array_merge($tags,array_unique($matches[1]));
+ }
+ }
+ return array_unique($tags);
+ }
+
+ private function checkModuleTranslation($module){
+ $tags = $this->loadModuleTags($module);
+ $translation = array();
+ $msgs = '';
+ foreach ($this->langs as $key => $lang) {
+ $translation = Dictionary::getArray($module,$lang['cc']);
+ $matches = 0;
+ $unused = 0;
+ $expected = count($tags);
+ foreach ($translation as $key => $value) {
+ if(!in_array($key, $tags))
+ $unused ++;
+ else if(!empty($value))
+ $matches ++;
+
+ }
+
+ $diff = $expected - $matches;
+ $msg = "";
+ if ($diff > 0)
+ $msg .= $diff . " JSON tag(s) are missing";
+ if ($diff > 0 && $unused > 0)
+ $msg .= "<br>";
+ if ($unused > 0)
+ $msg .= $unused . " JSON tag(s) are not being used";
+ if(!empty($msg))
+ $msgs .= "<div><span class='badge'>{$lang['name']}:</span> $msg</div>";
+ }
+ if(empty($msgs))
+ $msgs = 'OK';
+ return $msgs;
+ }
+ /**
+ * Load the main table with all the website's templates and it's informations
+ * @return array with the templates' information
+ */
+ private function loadTemplatesList()
+ {
+ $table = array();
+
+ //loads every template
+ $files = $this->listTemplates();
+ $langs = Dictionary::getLanguages(true);
+
+ //checks the JSON tags from every language
+ foreach ($files as $file) {
+ $tags = $this->loadTemplateTags($file['path']);
+ // Don't list templates without lang tags
+ if (empty($tags))
+ continue;
+ $msgs = '';
+ foreach ($langs as $lang) {
+ $msg = $this->checkJson($file['path'], $lang['cc'], $tags);
+ if (!empty($msg))
+ $msgs .= "<div><span class='badge'>{$lang['name']}:</span>$msg</div>";
+ }
+ if (empty($msgs))
+ $msgs = 'OK';
+ $table[] = array(
+ 'template' => $file['name'],
+ 'link' => $file['name'],
+ 'status' => $msgs
+ );
+ }
+ sort($table);
+ return $table;
+ }
+
+ /**
+ * Finds and returns all the website's templates
+ * @return array
+ */
+ private function listTemplates()
+ {
+ $files = array();
+ $dir = 'modules/';
+ $objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
+ foreach ($objects as $name => $object) {
+ if (substr($name, -5) === '.html') {
+ $files[] = array(
+ 'path' => substr($name, 0, -5),
+ 'name' => substr($name, strlen($dir), -5)
+ );
+ }
+ }
+ return $files;
+ }
+
+ /**
+ * Finds and returns all PHP files of slxadmin
+ * @return array of all php files
+ */
+ private function listPhp()
+ {
+ $php = array();
+ $dir = '.';
+ $objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
+ foreach ($objects as $name => $object) {
+ if (substr($name, -4) === '.php') {
+ $php[] = $name;
+ }
+ }
+ return $php;
+ }
+
+ /**
+ * Checks the JSON tags from a template
+ * @param string the template's path
+ * @param string the selected language
+ * @param string tags that should be in the json file
+ * @return string|boolean the information about the JSON tags, false if template has no lang-tags
+ */
+ private function checkJson($path, $lang, $expectedTags)
+ {
+ //if there was not a valid template's path
+ if (!$path) {
+ return "Translation missing";
+ }
+ // How many tags do we expect in the translation
+ $htmlCount = count($expectedTags);
+
+ //initialize the count variables
+ $matchCount = 0;
+ $unusedCount = 0;
+
+ //loads the JSON tags and count the matches
+ $json = Dictionary::getArray(substr($path, strlen("modules/")), $lang);
+ //return print_r($json) . "\nvs\n" . print_r($expectedTags);
+ foreach ($json as $key => $value) {
+ if (!in_array($key, $expectedTags)) {
+ $unusedCount++;
+ } else if (!empty($value)) {
+ $matchCount++;
+ }
+ }
+ $diff = $htmlCount - $matchCount;
+
+ if ($diff == 0 && $unusedCount == 0)
+ return '';
+ //build the return string
+ $str = "";
+ if ($diff > 0)
+ $str .= $diff . " JSON tag(s) are missing";
+ if ($diff > 0 && $unusedCount > 0)
+ $str .= "<br>";
+ if ($unusedCount > 0)
+ $str .= $unusedCount . " JSON tag(s) are not being used";
+ return $str;
+ }
+
+ /**
+ * Get array to pass to edit page with all the tags and translations for the given template
+ * @param string $path the template's path
+ * @return array the information about the JSON tags
+ */
+ private function loadTemplateEditArray($path)
+ {
+ $tags = $this->loadTemplateTags($path);
+ if ($tags === false)
+ return false;
+ return $this->buildTranslationTable("modules/" . $path, $tags);
+ }
+
+ /**
+ * Load array of tags used in given template.
+ * @param string $path the path of the template, relative to templates/, without .html extension.
+ * @return array all tags in template
+ */
+ private function loadTemplateTags($path)
+ {
+ $templateFile = "$path.html";
+ //checks if the template is valid
+ if (!file_exists($templateFile)) {
+ Message::addError('invalid-template', $templateFile);
+ return false;
+ }
+
+ //finds every mustache tag within the html template
+ $htmlTemplate = file_get_contents($templateFile);
+ preg_match_all('/{{(lang_.*?)}}/s', $htmlTemplate, $matches);
+ if (!isset($matches[1]) || !is_array($matches[1]))
+ return array();
+ return array_unique($matches[1]);
+ }
+
+ /**
+ * Load array of tags and translations of all messages
+ * @return array the information about the JSON tags
+ */
+ private function loadMessageEditArray()
+ {
+ $tags = $this->loadTagsFromPhp('/Message\s*::\s*add\w+\s*\(\s*[\'"](.*?)[\'"]\s*[\)\,]/i');
+ if ($tags === false)
+ return false;
+ return $this->buildTranslationTable('messages', $tags);
+ }
+
+ /**
+ * Load array of tags and translations of all strings found in the php files.
+ * @return array the information about the JSON tags
+ */
+ private function loadHardcodedStringEditArray()
+ {
+ $tags = $this->loadTagsFromPhp('/Dictionary\s*::\s*translate\s*\(\s*[\'"]([^\'"]*?)[\'"]\s*\)/i');
+ if ($tags === false)
+ return false;
+ return $this->buildTranslationTable('messages-hardcoded', $tags);
+ }
+
+ /**
+ * Load array of tags used in all the php files, by given regexp. Capture group 1 should return
+ * the exact tag name.
+ * @param string $regexp regular expression matching all tags in capture group 1
+ * @return array of all tags found
+ */
+ private function loadTagsFromPhp($regexp)
+ {
+ // Get all php files, so we can find all strings that need to be translated
+ $php = $this->listPhp();
+ $tags = array();
+ // Now find all tags in all php files. Only works for literal usage, not something like $foo = 'bar'; Dictionary::translate($foo);
+ foreach ($php as $file) {
+ $content = @file_get_contents($file);
+ if ($content === false || preg_match_all($regexp, $content, $out) < 1)
+ continue;
+ foreach ($out[1] as $id) {
+ $tags[$id] = true;
+ }
+ }
+ return array_keys($tags);
+ }
+
+ private function buildTranslationTable($path, $requiredTags = false)
+ {
+ // All languages
+ $langArray = Dictionary::getLanguages();
+
+ $tags = array();
+ if ($requiredTags !== false) {
+ foreach ($requiredTags as $tagName) {
+ $tags[$tagName] = array('tag' => $tagName);
+ foreach ($langArray as $lang) {
+ $tags[$tagName]['langs'][$lang]['lang'] = $lang;
+ $tags[$tagName]['missing'] = count($langArray);
+ }
+ }
+ }
+ // Finds every JSON tag within the JSON language files
+ foreach ($langArray as $lang) {
+ $jsonTags = Dictionary::getArray($path, $lang, true);
+ if (!is_array($jsonTags))
+ continue;
+ foreach ($jsonTags as $tag => $translation) {
+ $tags[$tag]['langs'][$lang]['translation'] = $translation;
+ $tags[$tag]['langs'][$lang]['lang'] = $lang;
+ if (strpos($translation, "\n") !== false)
+ $tags[$tag]['langs'][$lang]['big'] = true;
+ $tags[$tag]['tag'] = $tag;
+ if (!isset($tags[$tag]['missing']))
+ $tags[$tag]['missing'] = 0;
+ if (!empty($translation))
+ $tags[$tag]['missing'] --;
+ }
+ }
+ // Fill the blanks
+ foreach ($langArray as $lang) {
+ foreach (array_keys($tags) as $tagName) {
+ if (!isset($tags[$tagName]['langs'][$lang]))
+ $tags[$tagName]['langs'][$lang]['lang'] = $lang;
+ }
+ }
+ // Finally remove $lang from the keys so mustache will iterate over them via {{#..}}
+ foreach ($tags as &$tag) {
+ $tag['langs'] = array_values($tag['langs']);
+ if ($requiredTags !== false)
+ $tag['class'] = $this->getTagColor($tag['missing']);
+ }
+ return array_values($tags);
+ }
+
+ /**
+ * Change the color of the table line according to the tag status
+ * @param string the JSON's path
+ * @param string the selected tag
+ * @return string the css class of the line
+ */
+ private function getTagColor($missingCount)
+ {
+ //return danger in case the tag is not found in the template
+ if ($missingCount < 0)
+ return 'danger';
+
+ //return warning in case at least one of the tag's values is empty
+ if ($missingCount > 0)
+ return 'warning';
+ //if it's ok don't change the class
+ return '';
+ }
+
+ /**
+ * Updates a JSON file with it's new tags or/and tags values
+ */
+ private function updateJson()
+ {
+ $langArray = Dictionary::getLanguages();
+ foreach ($langArray as $lang) {
+ $json[$lang] = array();
+ }
+
+ //find the tag requests to change the file
+ foreach ($_POST as $key => $value) {
+ $str = explode('#', $key, 3);
+ if (count($str) !== 3 || $str[0] !== 'lang')
+ continue;
+ $lang = $str[1];
+ $tag = trim($str[2]);
+ if (!isset($json[$lang])) {
+ Message::addWarning('i18n-invalid-lang', $lang);
+ continue;
+ }
+ if (empty($tag)) {
+ Message::addWarning('i18n-empty-tag');
+ continue;
+ }
+ $value = trim($value);
+ if ($tag !== 'newtag') {
+ if (empty($value)) {
+ unset($json[$lang][$tag]);
+ } else {
+ $json[$lang][$tag] = $value;
+ }
+ } else {
+ if (!empty($value)) // TODO: Error message if new tag's name collides with existing
+ $json[$lang][$_REQUEST['newtag']] = $value;
+ }
+ }
+
+ // JSON_PRETTY_PRINT is only available starting with php 5.4.0.... Use upgradephp's json_encode
+ require_once('inc/up_json_encode.php');
+
+ //saves the new values on the file
+ foreach ($json as $key => $array) {
+ $path = Util::safePath('lang/' . $key . '/' . Request::post('path') . '.json');
+ if ($path === false) {
+ Message::addError('invalid-path');
+ Util::redirect('?do=Translation');
+ }
+ @mkdir(dirname($path), 0755, true);
+ ksort($array); // Sort by key, so the diff on the output is cleaner
+ $json = up_json_encode($array, JSON_PRETTY_PRINT); // Also for better diffability of the json files, we pretty print
+ //exits the function in case the action was unsuccessful
+ if (@file_put_contents($path, $json) === false) {
+ Message::addError('invalid-template');
+ return;
+ }
+ }
+ Message::addSuccess('updated-tags');
+ }
+
+ /**
+ * Delete a specific JSON tag from a JSON files
+ * @var string the JSON's file path
+ * @var the JSON tag to be deleted
+ * @return boolean if the action was not successful
+ */
+ private function deleteTag($path, $tag)
+ {
+ // JSON_PRETTY_PRINT is only available starting with php 5.4.0.... Use upgradephp's json_encode
+ require_once('inc/up_json_encode.php');
+
+ //delete the tag from every language file
+ $langArray = Dictionary::getLanguages();
+ foreach ($langArray as $lang) {
+ $json = Dictionary::getArray($path, $lang);
+ unset($json[$tag]);
+ $result = file_put_contents('lang/' . $lang . '/' . $path . '.json', up_json_encode($json, JSON_PRETTY_PRINT));
+ //add warning and exit in case the action was unsuccessful
+ if ($result === false) {
+ Message::addWarning('unsuccessful-action');
+ return false;
+ }
+ }
+ Message::addSuccess('deleted-tag');
+ }
+
+ /**
+ * Load all settings categories for editing.
+ *
+ * @return array
+ */
+ private function loadCategoriesArray()
+ {
+ $want = array();
+ $res = Database::simpleQuery("SELECT catid FROM cat_setting ORDER BY catid ASC");
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $want[] = 'cat_' . $row['catid'];
+ }
+ return $this->buildTranslationTable('settings/cat_setting', $want);
+ }
+
+ /**
+ * Load all settings categories for editing.
+ *
+ * @return array
+ */
+ private function loadSettingsArray()
+ {
+ $want = array();
+ $res = Database::simpleQuery("SELECT setting FROM setting ORDER BY setting ASC");
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ $want[] = $row['setting'];
+ }
+ return $this->buildTranslationTable('settings/setting', $want);
+ }
+
+}
diff --git a/modules/translation/templates/_page.html b/modules/translation/templates/_page.html
new file mode 100644
index 00000000..52a4c94b
--- /dev/null
+++ b/modules/translation/templates/_page.html
@@ -0,0 +1,17 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_langAdministration}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_adminInfo}}</p>
+ <ul class="nav nav-pills nav-stacked">
+ <!-- new modules page ??? -->
+ <li><a href="?do=Translation&amp;page=modules">{{lang_editModules}}</a></li>
+ <li><a href="?do=Translation&amp;page=templates">{{lang_editTemplates}}</a></li>
+ <li><a href="?do=Translation&amp;page=messages">{{lang_editMessages}}</a></li>
+ <li><a href="?do=Translation&amp;page=hardcoded">{{lang_editHardcoded}}</a></li>
+ <li><a href="?do=Translation&amp;page=settings">{{lang_editSettings}}</a></li>
+ <li><a href="?do=Translation&amp;page=config-module">{{lang_editConfigModule}}</a></li>
+ </ul>
+ </div>
+</div>
diff --git a/modules/translation/templates/edit.html b/modules/translation/templates/edit.html
new file mode 100644
index 00000000..dc01deb6
--- /dev/null
+++ b/modules/translation/templates/edit.html
@@ -0,0 +1,71 @@
+<div class="container">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ {{path}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_templateAdminHelp}}</p>
+ <div class="alert alert-info">
+ {{lang_templateHint}}
+ </div>
+ <form action="?do=Translation" method="post">
+ <input type="hidden" name="path" value="{{path}}">
+ <input type="hidden" name="token" value="{{token}}">
+ <a class="btn btn-primary" href='?do=Translation' >{{lang_back}}</a>
+ <button class="btn btn-primary" type="button" onclick="addTag()" >{{lang_createTag}}</button>
+ <button type="submit" class="btn btn-primary" name="update" value="true">{{lang_save}}</button>
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th>Mustache Tag</th>
+ {{#langs}}
+ <th>{{name}}</th>
+ {{/langs}}
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#tags}}
+ <tr class="{{class}}" id="{{tag}}">
+ <td>{{tag}}</td>
+ {{#langs}}
+ <td>
+ {{^big}}
+ <input type="text" class="form-control switchable" value="{{translation}}" ondblclick="mb(this)" name="lang#{{lang}}#{{tag}}">
+ {{/big}}
+ {{#big}}
+ <textarea rows="3" class="form-control" name="lang#{{lang}}#{{tag}}">{{translation}}</textarea>
+ {{/big}}
+ </td>
+ {{/langs}}
+ <td>
+ <button type="submit" class="btn btn-danger btn-xs" name="delete" value="{{tag}}"><span class="glyphicon glyphicon-remove"></span> {{lang_deleteTAG}}</button>
+ </td>
+ </tr>
+ {{/tags}}
+ <tr id="newTag">
+ </tr>
+ </tbody>
+ </table>
+ <a class="btn btn-primary" href='?do=Translation' >{{lang_back}}</a>
+ <button class="btn btn-primary" type="button" onclick="addTag()" >{{lang_createTag}}</button>
+ <button type="submit" class="btn btn-primary" name="update" value="true">{{lang_save}}</button>
+ </form>
+ </div>
+ </div>
+</div>
+<script type="text/javascript">
+ function addTag()
+ {
+ var target = document.getElementById('newTag');
+ target.innerHTML = "<td> <input type='text' class='form-control' placeholder='{{lang_newTAG}}' name='newtag'> </td> <td style='width:250px;text-align:center;'> <input type='text' class='form-control' placeholder='{{lang_germanTAG}}' name='lang#de#newtag'> </td> <td style='width:250px;text-align:center;'> <input type='text' class='form-control' placeholder='{{lang_englishTAG}}' name='lang#en#newtag'> </td> <td style='width:250px;text-align:center;'> <input type='text' class='form-control' placeholder='{{lang_portugueseTAG}}' name='lang#pt#newtag'> </td><td></td>";
+ }
+
+ function mb(el)
+ {
+ var old = $(el);
+ var ta = $('<textarea name="' + el.name + '" class="form-control" rows="3"></textarea>');
+ ta.val(old.val());
+ old.replaceWith(ta);
+ }
+</script>
diff --git a/modules/translation/templates/module-list.html b/modules/translation/templates/module-list.html
new file mode 100644
index 00000000..037a21bc
--- /dev/null
+++ b/modules/translation/templates/module-list.html
@@ -0,0 +1,32 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_langAdministration}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_adminInfo}}</p>
+ </div>
+</div>
+<div class="panel panel-default">
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th width="350">{{lang_module}}</th>
+ <th>{{lang_status}}</th>
+ <tr>
+ </thead>
+ <tbody>
+ {{#table}}
+ <tr onclick="goTo('{{module}}');">
+ <td>{{module}}</td>
+ <td>{{{status}}}</td>
+ </tr>
+ {{/table}}
+ </tbody>
+ </table>
+</div>
+
+<script>
+function goTo(link){
+ window.location.href = "?do=Translation&page=module&module=" + link;
+}
+</script>
diff --git a/modules/translation/templates/template-list.html b/modules/translation/templates/template-list.html
new file mode 100644
index 00000000..881fc5af
--- /dev/null
+++ b/modules/translation/templates/template-list.html
@@ -0,0 +1,32 @@
+<div class="panel panel-default">
+ <div class="panel-heading">
+ {{lang_langAdministration}}
+ </div>
+ <div class="panel-body">
+ <p>{{lang_adminInfo}}</p>
+ </div>
+</div>
+<div class="panel panel-default">
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th>Template</th>
+ <th>{{lang_status}}</th>
+ <tr>
+ </thead>
+ <tbody>
+ {{#table}}
+ <tr onclick="goTo('{{link}}');">
+ <td>{{template}}</td>
+ <td>{{{status}}}</td>
+ </tr>
+ {{/table}}
+ </tbody>
+ </table>
+</div>
+
+<script>
+function goTo(link){
+ window.location.href = "?do=Translation&page=template&template=" + link;
+}
+</script>