diff options
author | theCalcaholic <3knoeppl@informatik.uni-hamburg.de> | 2018-02-26 22:21:59 +0300 |
---|---|---|
committer | nachoparker <nacho@ownyourbits.com> | 2018-02-26 22:29:49 +0300 |
commit | 1a7c8b9b5e4b0cbda0e6c605ccab4d195c593a33 (patch) | |
tree | 6f6a586fe77287ca3d3b927a165bef936ab7589f /ncp-web | |
parent | c4a111c28948d56b759566b756c04a844695d720 (diff) |
ncp-web: add localization (#372)v0.46.35
* Implement localization loader L10N.
Localize a few strings in index.php for testing.
* Determine language from HTTP_ACCEPT_LANGUAGE header.
* Fix parsing of available languages for localization.
* Fix parsing of language translations (for localization).
* Fix function L10N->find_language
* Change variable naming to comply with conventions.
* Add remaining localize calls in index.php.
Add more localization strings.
* Fix styles to prevent check mark from being hidden.
* Handle exception in case localization couldn't be loaded.
* Add module localization support.
* Fix detection of core module in L10N->load
* Remove module specific localization from core l10n file.
* Fix determination of module name in L10N->load.
* Add localization files for more modules.
* Ignore l10n directory in modules path.
* Add support for dropdown lists in module settings.
* Add support for saving dropdown menus.
* Fix regex for recognizing dropdown option lists.
* Fix dropdown list regex not recognizing active option.
* Fix typo in ncp-launcher.php
* Workaround for jquery's 'find' method not working. //TODO: Find proper fix.
* Fix parsing of dropdown lists when updating config.
* Load language setting from config if available.
* add ncp-provisioning to SD card images
* Add intellij config to gitignore
* Fix bug when loading language from nc-webui config.
* Remove redundant language definition.
Replace umlauts by html special char expressions.
* Change selected option markup from `*<option>*` to `_<option>_`.
* Remove ide metadata.
* Add translations for ncp-app nc-backup.
* Add translation hooks to ncp-launcher.php
* Fix type in translations for nc-backup
* Remove translation hooks for field contents in ncp-launcher.php
Diffstat (limited to 'ncp-web')
-rw-r--r-- | ncp-web/L10N.php | 127 | ||||
-rw-r--r-- | ncp-web/index.php | 175 | ||||
-rw-r--r-- | ncp-web/l10n/de.json | 9 | ||||
-rw-r--r-- | ncp-web/l10n/index.html | 0 | ||||
-rw-r--r-- | ncp-web/l10n/index.php | 1 | ||||
-rw-r--r-- | ncp-web/ncp-launcher.php | 39 | ||||
-rw-r--r-- | ncp-web/ncp.css | 2 | ||||
-rw-r--r-- | ncp-web/ncp.js | 15 |
8 files changed, 280 insertions, 88 deletions
diff --git a/ncp-web/L10N.php b/ncp-web/L10N.php new file mode 100644 index 00000000..fc726922 --- /dev/null +++ b/ncp-web/L10N.php @@ -0,0 +1,127 @@ +<?php + +class L10N { + private $translations = []; + const default_language = "en_US"; + private $language; + + public function __construct($desired_languages, $l10n_dir, $modules_path=null) + { + if (!isset($desired_languages)) { + $desired_languages = ""; + } + + $l10n_dir = rtrim($l10n_dir, '/'); + $available_languages = array_filter(scandir($l10n_dir), + function ($s) { + return pathinfo($s, PATHINFO_EXTENSION) == "json"; + }); + $available_languages = array_map( + function ($s) { + return basename($s, ".json"); + }, + $available_languages); + + $desired_lang = $this->load_language_setting($modules_path); + if($desired_lang != "auto") + { + $desired_languages = $desired_lang; + } + $lang = $this->find_language($available_languages, $desired_languages); + $this->language = $lang; + if ($lang === L10N::default_language || !file_exists(join('/', [$l10n_dir, $lang . ".json"])) || !$this->load($lang, $l10n_dir, $modules_path)) { + $this->language = L10N::default_language; + } + } + + function load($lang, $l10n_dir, $modules_path) + { + $modules_path = join('/', [rtrim($modules_path, '/'), $l10n_dir]); + $files = [join('/', [$l10n_dir, $lang . ".json"])]; + if (is_dir($modules_path)) { + foreach (scandir($modules_path) as $dir) { + $file_path = join('/', [$modules_path, trim($dir, '/'), $lang . ".json"]); + if (is_file($file_path)) { + array_push($files, $file_path); + } + } + } + + $jsonError = null; + foreach ($files as $file) { + $module_name = pathinfo(pathinfo($file, PATHINFO_DIRNAME), PATHINFO_BASENAME); + if( $module_name == pathinfo($l10n_dir, PATHINFO_BASENAME)) { + $module_name = "__core__"; + } + $json = json_decode(file_get_contents($file), true); + if (!is_array($json)) { + $jsonError = json_last_error(); + continue; + } + $this->translations[$module_name] = $json['translations']; + } + return ($jsonError == null); + } + + public function __($text, $module="__core__") { + if( isset($this->translations[$module]) && isset($this->translations[$module][$text])) { + return $this->translations[$module][$text]; + } + return $text; + } + + function find_language(array $available_languages, $desired_language) { + + $available_languages = array_flip($available_languages); + $langs = []; + preg_match_all('~([\w-]+)(?:[^,\d]+([\d.]+))?~', strtolower($desired_language), $matches, PREG_SET_ORDER); + + foreach($matches as $match) { + + list($a, $b) = explode('-', $match[1]) + array('', ''); + $value = isset($match[2]) ? (float) $match[2] : 1.0; + + if(isset($available_languages[$match[1]])) { + $langs[$match[1]] = $value; + continue; + } + + if(isset($available_languages[$a])) { + $langs[$a] = $value - 0.1; + } + + } + arsort($langs); + + if(count($langs) == 0) { + return L10N::default_language; + } + return array_keys($langs)[0]; + } + + function load_language_setting($modules_path) { + $webui_config_file = join('/', [rtrim($modules_path, '/'), 'nc-webui.sh']); + $fh = fopen( $webui_config_file ,'r') + or exit( '{ "output": "' . $webui_config_file . ' read error" }' ); + $lang = "auto"; + while ( $line = fgets($fh) ) + { + // drop down menu + if(preg_match('/^LANGUAGE_=\[(([_\w]+,)*[_\w]+)\]$/', $line, $matches)) + { + $options = explode(',', $matches[1]); + foreach($options as $option) + { + if($option[0] == "_" && $option[count($option) - 1] == "_") + { + fclose($fh); + $lang = trim($option,'_'); + } + } + fclose($fh); + return $lang; + } + } + + } +} diff --git a/ncp-web/index.php b/ncp-web/index.php index 52ce02c8..70a6e26d 100644 --- a/ncp-web/index.php +++ b/ncp-web/index.php @@ -8,57 +8,71 @@ --> <!DOCTYPE html> -<html class="ng-csp" data-placeholder-focus="false" lang="en" > +<html class="ng-csp" data-placeholder-focus="false" lang="en"> <head> - <meta charset="utf-8"> - <title>NextCloudPi Panel</title> - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> - <meta name="referrer" content="never"> - <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"> - <meta name="mobile-web-app-capable" content="yes"> -<?php + <meta charset="utf-8"> + <title>NextCloudPi Panel</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <meta name="referrer" content="never"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0"> + <meta name="mobile-web-app-capable" content="yes"> + <?php session_start(); + $modules_path = '/usr/local/etc/nextcloudpi-config.d/'; + $l10nDir = "l10n"; + + // security headers + header("Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; object-src 'self';"); + header("X-XSS-Protection: 1; mode=block"); + header("X-Content-Type-Options: nosniff"); + header("X-Robots-Tag: none"); + header("X-Permitted-Cross-Domain-Policies: none"); + header("X-Frame-Options: DENY"); + header("Cache-Control: max-age=15778463"); + ini_set('session.cookie_httponly', 1); + if (isset($_SERVER['HTTPS'])) + ini_set('session.cookie_secure', 1); + + // HTTP2 push headers + header("Link: </minified.js>; rel=preload; as=script;,</ncp.js>; rel=preload; as=script;,</ncp.css>; rel=preload; as=style;,</img/ncp-logo.svg>; rel=preload; as=image;, </loading-small.gif>; rel=preload; as=image;, rel=preconnect href=ncp-launcher.php;"); - // security headers - header("Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; object-src 'self';"); - header("X-XSS-Protection: 1; mode=block"); - header("X-Content-Type-Options: nosniff"); - header("X-Robots-Tag: none"); - header("X-Permitted-Cross-Domain-Policies: none"); - header("X-Frame-Options: DENY"); - header("Cache-Control: max-age=15778463"); - ini_set('session.cookie_httponly', 1); - if ( isset($_SERVER['HTTPS']) ) - ini_set('session.cookie_secure', 1); - - // HTTP2 push headers - header("Link: </minified.js>; rel=preload; as=script;,</ncp.js>; rel=preload; as=script;,</ncp.css>; rel=preload; as=style;,</img/ncp-logo.svg>; rel=preload; as=image;, </loading-small.gif>; rel=preload; as=image;, rel=preconnect href=ncp-launcher.php;"); -?> -<link rel="icon" type="image/png" href="img/favicon.png" /> -<link rel="stylesheet" href="ncp.css"> + ?> + <link rel="icon" type="image/png" href="img/favicon.png"/> + <link rel="stylesheet" href="ncp.css"> </head> <body id="body-user"> - <noscript> - <div id="nojavascript"> <div>This application requires JavaScript for correct operation. Please <a href="http://enable-javascript.com/" target="_blank" rel="noreferrer">enable JavaScript</a> and reload the page. </div> </div> - </noscript> - <div id="notification-container"> - <?php - exec( "ncp-test-updates" , $output, $ret ); - if ( $ret == 0 ) - { - echo '<div id="notification">'; - echo '<div id="update-notification" class="row type-error closeable">'; - echo "version " . file_get_contents( "/var/run/.ncp-latest-version" ) . " is available"; - echo '<a class="action close icon-close" href="#" alt="Dismiss"></a>'; - echo '</div>'; - echo '</div>'; - } - ?> - </div> +<?php + require("L10N.php"); + try { + $l = new L10N($_SERVER["HTTP_ACCEPT_LANGUAGE"], $l10nDir, $modules_path); + } catch (Exception $e) { + die("<p class='error'>Error while loading localizations!</p>"); + } +?> +<noscript> + <div id="nojavascript"> + <div> + <?php sprintf($l->__("This application requires JavaScript for correct operation. Please %s enable JavaScript %s and reload the page."), + "<a href=\"http://enable-javascript.com/\" target=\"_blank\" rel=\"noreferrer\">", "</a>"); ?> + </div> + </div> +</noscript> +<div id="notification-container"> + <?php + exec("ncp-test-updates", $output, $ret); + if ($ret == 0) { + echo '<div id="notification">'; + echo '<div id="update-notification" class="row type-error closeable">'; + sprintf($l->__("version %s is available"), file_get_contents("/var/run/.ncp-latest-version")); + echo '<a class="action close icon-close" href="#" alt="Dismiss"></a>'; + echo '</div>'; + echo '</div>'; + } + ?> +</div> <?php - if ( file_exists( 'wizard') && !file_exists( 'wizard.cfg' ) ) - { + if (file_exists('wizard') && !file_exists('wizard.cfg')) { echo <<<HTML <div id="first-run-wizard"> <div class='dialog'> @@ -68,14 +82,14 @@ <br> <a href="wizard"><img class="wizard-btn" src="wizard/img/ncp-logo.svg" class="wizard"></a> <br> - <button type="button" class="wizard-btn" id="go-wizard" >run </button> - <button type="button" class="first-run-close" id="skip-wizard" >skip </button> - <button type="button" class="first-run-close" id="close-wizard">close</button> + <button type="button" class="wizard-btn" id="go-wizard" >{$l->__("run")} </button> + <button type="button" class="first-run-close" id="skip-wizard" >{$l->__("skip")} </button> + <button type="button" class="first-run-close" id="close-wizard">{$l->__("close")}</button> <br><br> </div> </div> HTML; - touch( 'wizard.cfg' ); + touch('wizard.cfg'); } ?> @@ -103,14 +117,14 @@ HTML; </div> </a> HTML; -?> - <div id="poweroff"> - <div id="expand"> - <div class="icon-power-white"></div> + ?> + <div id="poweroff"> + <div id="expand"> + <div class="icon-power-white"></div> + </div> + </div> </div> - </div> - </div> - </header> +</header> <div id="content-wrapper"> <div id="content" class="app-files" role="main"> @@ -119,40 +133,38 @@ HTML; <ul id="ncp-options"> <?php - // fill options with contents from directory - $path = '/usr/local/etc/nextcloudpi-config.d/'; - $files = array_diff(scandir($path), array('.', '..','nc-wifi.sh')); + // fill options with contents from directory + $files = array_diff(scandir($modules_path), array('.', '..', 'nc-wifi.sh', 'l10n')); - foreach($files as $file) - { - $script = pathinfo( $file , PATHINFO_FILENAME ); - $txt = file_get_contents( $path . $file ); + foreach ($files as $file) { + $script = pathinfo($file, PATHINFO_FILENAME); + $txt = file_get_contents($modules_path . $file); - $active = ""; - if ( preg_match('/^ACTIVE_=yes$/m', $txt, $matches) ) - $active = " ✓"; + $active = ""; + if (preg_match('/^ACTIVE_=yes$/m', $txt, $matches)) + $active = " ✓"; - echo "<li id=\"$script\" class=\"nav-recent\">"; - echo "<a href=\"#\"> $script$active </a>"; + echo "<li id=\"$script\" class=\"nav-recent\">"; + echo "<a href=\"#\"> {$l->__($script, $script)}$active </a>"; - if ( preg_match('/^DESCRIPTION="(.*)"$/m', $txt, $matches) ) - echo "<input id=\"$script-desc\" type=\"hidden\" value=\"$matches[1]\" />"; + if (preg_match('/^DESCRIPTION="(.*)"$/m', $txt, $matches)) + echo "<input id=\"$script-desc\" type=\"hidden\" value=\"{$l->__($matches[1], $script)}\" />"; - if ( preg_match('/^INFO="(.*)"/msU', $txt, $matches) ) - echo "<input id=\"$script-info\" type=\"hidden\" value=\"$matches[1]\" />"; + if (preg_match('/^INFO="(.*)"/msU', $txt, $matches)) + echo "<input id=\"$script-info\" type=\"hidden\" value=\"{$l->__($matches[1], $script)}\" />"; - if ( preg_match('/^INFOTITLE="(.*)"/msU', $txt, $matches) ) - echo "<input id=\"$script-infotitle\" type=\"hidden\" value=\"$matches[1]\" />"; + if (preg_match('/^INFOTITLE="(.*)"/msU', $txt, $matches)) + echo "<input id=\"$script-infotitle\" type=\"hidden\" value=\"{$l->__($matches[1], $script)}\" />"; - echo "</li>"; - } + echo "</li>"; + } ?> </ul> </div> <div id="app-content"> <div id="app-navigation-toggle" class="icon-menu hidden"></div> - <h2 id="config-box-title">Configure NextCloudPi features</h2> + <h2 id="config-box-title"><?php echo $l->__("Configure NextCloudPi features"); ?></h2> <a href="#" target="_blank"> <div id="config-extra-info" class="icon-info"></div> </a> @@ -162,16 +174,15 @@ HTML; <form> <div id="config-box"></div> <div id="config-button-wrapper"> - <button id="config-button">Run</button> + <button id="config-button"><?php echo $l->__("Run"); ?></button> <img id="loading-gif" src="loading-small.gif"> <div id="circle-retstatus" class="icon-red-circle"></div> </div> </form> <textarea readonly id="details-box" rows="12"></textarea> </div> - </div> - </div> + </div> <div id="poweroff-dialog" class='dialog primary hidden'> <div id='poweroff-option_shutdown' class='button big-button'> @@ -183,11 +194,11 @@ HTML; </div> <?php - include ('csrf.php'); + include('csrf.php'); echo '<input type="hidden" id="csrf-token" name="csrf-token" value="' . getCSRFToken() . '"/>'; ?> - <script src="minified.js"></script> - <script src="ncp.js"></script> + <script src="minified.js"></script> + <script src="ncp.js"></script> </body> </html> diff --git a/ncp-web/l10n/de.json b/ncp-web/l10n/de.json new file mode 100644 index 00000000..59684a9e --- /dev/null +++ b/ncp-web/l10n/de.json @@ -0,0 +1,9 @@ +{"translations": { + "This application requires JavaScript for correct operation. Please %s enable JavaScript %s and reload the page.": + "Diese Anwendung benötigt Javascript um richtig zu funktionieren. Bitte %s aktivieren Sie JavaScript %s und laden die Seite dann neu.", + "version %s is available": "Eine neue Version ist verfügbar: %s.", + "run": "Ausführen", + "skip": "Überspringen", + "close": "Schließen", + "Configure NextCloudPi features": "NextCloudPi Funktionen konfigurieren" +}} diff --git a/ncp-web/l10n/index.html b/ncp-web/l10n/index.html new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ncp-web/l10n/index.html diff --git a/ncp-web/l10n/index.php b/ncp-web/l10n/index.php new file mode 100644 index 00000000..a8143662 --- /dev/null +++ b/ncp-web/l10n/index.php @@ -0,0 +1 @@ +<?php
\ No newline at end of file diff --git a/ncp-web/ncp-launcher.php b/ncp-web/ncp-launcher.php index 7e905e7c..f0bb591d 100644 --- a/ncp-web/ncp-launcher.php +++ b/ncp-web/ncp-launcher.php @@ -11,8 +11,18 @@ include ('csrf.php'); session_start(); +$modules_path = '/usr/local/etc/nextcloudpi-config.d/'; +$l10nDir = "l10n"; ignore_user_abort( true ); + +require("L10N.php"); +try { + $l = new L10N($_SERVER["HTTP_ACCEPT_LANGUAGE"], $l10nDir, $modules_path); +} catch (Exception $e) { + die(json_encode("<p class='error'>Error while loading localizations!</p>")); +} + if ( $_POST['action'] == "cfgreq" ) { if ( !$_POST['ref'] ) exit( '{ "output": "Invalid request" }' ); @@ -43,16 +53,33 @@ if ( $_POST['action'] == "cfgreq" ) if ( $matches[2] == "yes" ) $checked = "checked"; $output = $output . "<tr>"; - $output = $output . "<td><label for=\"$matches[1]\">$matches[1]</label></td>"; + $output = $output . "<td><label for=\"$matches[1]\">". $l->__($matches[1], $_POST['ref']) ."</label></td>"; $output = $output . "<td><input type=\"checkbox\" id=\"$matches[1]\" name=\"$matches[1]\" value=\"$matches[2]\" $checked></td>"; $output = $output . "</tr>"; } - + // drop down menu + else if(preg_match('/^(\w+)_=\[(([_\w]+,)*[_\w]+)\]$/', $line, $matches)) + { + $options = explode(",", $matches[2]); + $output .= "<tr>"; + $output .= "<td><label for=\"$matches[1]\">". $l->__($matches[1], $_POST['ref']) ."</label></td>"; + $output .= "<td><select id=\"$matches[1]\" name=\"$matches[1]\">"; + foreach($options as $option) + { + $output .= "<option value='". trim($option, "_") ."' "; + if( $option[0] == "_" && $option[count($option) - 1] == "_" ) + { + $output .="selected='selected'"; + } + $output .= ">". $l->__(trim($option, "_"), $_POST['ref']) ."</option>"; + } + $output .= "</select></td></tr>"; + } // text field else if ( preg_match('/^(\w+)_=(.*)$/', $line, $matches) ) { $output = $output . "<tr>"; - $output = $output . "<td><label for=\"$matches[1]\">$matches[1]</label></td>"; + $output = $output . "<td><label for=\"$matches[1]\">". $l->__($matches[1], $_POST['ref']) ."</label></td>"; $output = $output . "<td><input type=\"text\" name=\"$matches[1]\" id=\"$matches[1]\" value=\"$matches[2]\" size=\"40\"></td>"; $output = $output . "</tr>"; } @@ -87,7 +114,11 @@ else if ( $_POST['action'] == "launch" && $_POST['config'] ) if ( !empty( $params ) ) foreach( $params as $name => $value ) { - preg_match( '/^[\w.,@_\/-]+$/' , $value , $matches ) + if( is_array($value)) + { + $value = "[". join(",", $value) ."]"; + } + preg_match( '/^[\[\]\w.,@_\/-]+$/' , $value , $matches ) or exit( '{ "output": "Invalid input" , "token": "' . getCSRFToken() . '" }' ); $code = preg_replace( '/\n' . $name . '_=.*' . PHP_EOL . '/' , PHP_EOL . $name . '_=' . $value . PHP_EOL , diff --git a/ncp-web/ncp.css b/ncp-web/ncp.css index 97ac4b23..447a9c47 100644 --- a/ncp-web/ncp.css +++ b/ncp-web/ncp.css @@ -628,7 +628,7 @@ kbd { } #app-navigation li > a { display:block; - width:100%; + width:90%; line-height:44px; min-height:44px; padding:0 12px; diff --git a/ncp-web/ncp.js b/ncp-web/ncp.js index 615d091b..5d60537c 100644 --- a/ncp-web/ncp.js +++ b/ncp-web/ncp.js @@ -96,12 +96,25 @@ $(function() // create configuration object var cfg = {}; - $( 'input' , '#config-box' ).each( function(item){ + $( 'input' , '#config-box' ).each( function(item){ if( item.getAttribute('type') == 'checkbox' ) item.value = item.checked ? 'yes' : 'no'; + cfg[item.name] = item.value; } ); + $( 'select', '#config-box' ).each( function(item) { + var select = { + 'id': item.name, + 'value': [] + }; + $("#" + item.id + '>option').each(function(option) { + select.value.push(option.selected ? "_" + option.value + "_" : "" + option.value); + }); + cfg[select.id] = select.value; + }); + + // reset box $('#details-box').fill(); $('#details-box').show(); |