initial work on translations, covering the PHP side of it

This commit is contained in:
El RIDO 2015-09-05 02:24:56 +02:00
parent 28776ac178
commit a2af88a36e
10 changed files with 409 additions and 57 deletions

View file

@ -26,3 +26,7 @@ body {
padding: 5px 0 5px 5px; padding: 5px 0 5px 5px;
white-space: pre-wrap; white-space: pre-wrap;
} }
h4 {
margin-top: 0;
}

78
i18n/de.json Normal file
View file

@ -0,0 +1,78 @@
{
"en": "de",
"Paste does not exist, has expired or has been deleted.":
"Diesen Schnipsel gibt es nicht, er ist abgelaufen oder wurde gelöscht.",
"ZeroBin requires php 5.2.6 or above to work. Sorry.":
"ZeroBin benötigt PHP 5.2.6 oder höher um zu funktionieren, sorry.",
"ZeroBin requires configuration section [%s] to be present in configuration file.":
"ZeroBin benötigt die Konfigurationssektion [%s] in der Konfigurationsdatei um zu funktionieren.",
"Please wait %d seconds between each post.":
"Bitte warte %d Sekunden zwischen dem Absenden.",
"Paste is limited to %s of encrypted data.":
"Schnipsel sind auf %s verschlüsselte Datenmenge beschränkt.",
"Invalid data.":
"Ungültige Daten.",
"You are unlucky. Try again.":
"Du hast Pech. Versuchs nochmal.",
"Error saving comment. Sorry.":
"Fehler beim Speichern des Kommentars, sorry.",
"Error saving paste. Sorry.":
"Fehler beim Speichern des Schnipsels, sorry.",
"Invalid paste ID.":
"Ungültige Schnipsel ID.",
"Paste is not of burn-after-reading type.":
"Schnipsel ist kein \"Einweg\"-Typ.",
"Wrong deletion token. Paste was not deleted.":
"Falscher Lösch-Kode. Schnipsel wurde nicht gelöscht.",
"Paste was properly deleted.":
"Schnipsel wurde erfolgreich gelöscht.",
"ZeroBin": "ZeroBin",
"ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. More information on the <a href=\"https://github.com/elrido/ZeroBin/wiki\">project page</a>.":
"ZeroBin ist ein minimalistischer, quelloffener \"pastebin\"-artiger Dienst bei dem der Server keinerlei Kenntnis der Daten-Schnipsel hat. Die Daten werden <i>im Browser</i> mit 256 Bit AES ver- und entschlüsselt. Weitere Informationen sind auf der <a href=\"https://github.com/elrido/ZeroBin/wiki\">Projektseite</a> zu finden.",
"Because ignorance is bliss":
"Was ich nicht weiss, macht mich nicht heiss",
"Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.":
"Javascript ist eine Voraussetzung um ZeroBin zu nutzen.<br />Bitte entschuldige die Unannehmlichkeiten.",
"ZeroBin requires a modern browser to work.":
"ZeroBin setzt einen modernen Browser voraus um funktionieren zu können.",
"Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
"Du benutzt immer noch den Internet Explorer? Tu Dir einen Gefallen und wechsle zu einem moderneren Browser:",
"New":
"Neu",
"Send":
"Senden",
"Clone":
"Klonen",
"Raw text":
"Reiner Text",
"Expires":
"Ablaufzeit",
"Burn after reading":
"Einweg-Schnipsel",
"Open discussion":
"Diskussion eröffnen",
"Password (recommended)":
"Passwort (empfohlen)",
"Discussion":
"Diskussion",
"Toggle navigation":
"Navigation umschalten",
"5 minutes":
"5 Minuten",
"10 minutes":
"10 Minuten",
"1 hour":
"1 Stunde",
"1 day":
"1 Tag",
"1 week":
"1 Woche",
"1 month":
"1 Monat",
"1 year":
"1 Jahr",
"Never":
"Nie",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.":
"Hinweis: Dies ist ein Versuchsdienst. Daten können jederzeit gelöscht werden. Kätzchen werden sterben wenn Du diesen Dienst missbrauchst."
}

View file

@ -13,5 +13,6 @@
// change this, if your php files and data is outside of your webservers document root // change this, if your php files and data is outside of your webservers document root
define('PATH', ''); define('PATH', '');
define('PUBLIC_PATH', dirname(__FILE__));
require PATH . 'lib/auto.php'; require PATH . 'lib/auto.php';
new zerobin; new zerobin;

View file

@ -1162,4 +1162,9 @@ class RainTpl_SyntaxException extends RainTpl_Exception{
} }
} }
// shorthand translate function for use in templates
function t() {
return call_user_func_array(array('i18n', 'translate'), func_get_args());
}
// -- end // -- end

219
lib/i18n.php Normal file
View file

@ -0,0 +1,219 @@
<?php
/**
* ZeroBin
*
* a zero-knowledge paste bin
*
* @link http://sebsauvage.net/wiki/doku.php?id=php:zerobin
* @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
* @license http://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
* @version 0.20
*/
/**
* i18n
*
* provides internationalization tools like translation, browser language detection, etc.
*/
class i18n
{
/**
* translation cache
*
* @access private
* @static
* @var array
*/
private static $_translations = array();
/**
* translate a string, alias for translate()
*
* @access public
* @static
* @param string $messageId
* @param mixed $args one or multiple parameters injected into placeholders
* @return string
*/
public static function _($messageId)
{
return call_user_func_array(array('i18n', 'translate'), func_get_args());
}
/**
* translate a string
*
* @access public
* @static
* @param string $messageId
* @param mixed $args one or multiple parameters injected into placeholders
* @return string
*/
public static function translate($messageId)
{
if (empty($messageId))
{
return $messageId;
}
if (count(self::$_translations) === 0) {
self::loadTranslations();
}
if (!array_key_exists($messageId, self::$_translations))
{
self::$_translations[$messageId] = $messageId;
}
$args = func_get_args();
$args[0] = self::$_translations[$messageId];
return call_user_func_array('sprintf', $args);
}
/**
* loads translations
*
* From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
*
* @access protected
* @static
* @return void
*/
public static function loadTranslations()
{
// find a matching translation file
$availableLanguages = array();
$path = PUBLIC_PATH . DIRECTORY_SEPARATOR . 'i18n';
$i18n = dir($path);
while (false !== ($file = $i18n->read()))
{
if (preg_match('/^([a-z]{2}).json$/', $file, $match) === 1)
{
$availableLanguages[] = $match[1];
}
}
$match = self::_getMatchingLanguage(
self::getBrowserLanguages(), $availableLanguages
);
// load translations
if ($match != 'en')
{
self::$_translations = json_decode(
file_get_contents($path . DIRECTORY_SEPARATOR . $match . '.json'),
true
);
}
}
/**
* detect the clients supported languages and return them ordered by preference
*
* From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
*
* @return array
*/
public static function getBrowserLanguages()
{
$languages = array();
if (array_key_exists('HTTP_ACCEPT_LANGUAGE', $_SERVER))
{
$languageRanges = explode(',', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']));
foreach ($languageRanges as $languageRange) {
if (preg_match(
'/(\*|[a-zA-Z0-9]{1,8}(?:-[a-zA-Z0-9]{1,8})*)(?:\s*;\s*q\s*=\s*(0(?:\.\d{0,3})|1(?:\.0{0,3})))?/',
trim($languageRange), $match
))
{
if (!isset($match[2]))
{
$match[2] = '1.0';
}
else
{
$match[2] = (string) floatval($match[2]);
}
if (!isset($languages[$match[2]]))
{
$languages[$match[2]] = array();
}
$languages[$match[2]][] = strtolower($match[1]);
}
}
krsort($languages);
}
return $languages;
}
/**
* compares two language preference arrays and returns the preferred match
*
* From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
*
* @param array $acceptedLanguages
* @param array $availableLanguages
* @return string
*/
protected static function _getMatchingLanguage($acceptedLanguages, $availableLanguages) {
$matches = array();
$any = false;
foreach ($acceptedLanguages as $acceptedQuality => $acceptedValues) {
$acceptedQuality = floatval($acceptedQuality);
if ($acceptedQuality === 0.0) continue;
foreach ($availableLanguages as $availableValue)
{
$availableQuality = 1.0;
foreach ($acceptedValues as $acceptedValue)
{
if ($acceptedValue === '*')
{
$any = true;
}
$matchingGrade = self::_matchLanguage($acceptedValue, $availableValue);
if ($matchingGrade > 0)
{
$q = (string) ($acceptedQuality * $availableQuality * $matchingGrade);
if (!isset($matches[$q]))
{
$matches[$q] = array();
}
if (!in_array($availableValue, $matches[$q]))
{
$matches[$q][] = $availableValue;
}
}
}
}
}
if (count($matches) === 0 && $any)
{
if (count($availableLanguages) > 0)
{
$matches['1.0'] = $availableLanguages;
}
}
if (count($matches) === 0)
{
return 'en';
}
krsort($matches);
$topmatches = current($matches);
return current($topmatches);
}
/**
* compare two language IDs and return the degree they match
*
* From: http://stackoverflow.com/questions/3770513/detect-browser-language-in-php#3771447
*
* @param string $a
* @param string $b
* @return float
*/
protected static function _matchLanguage($a, $b) {
$a = explode('-', $a);
$b = explode('-', $b);
for ($i=0, $n=min(count($a), count($b)); $i<$n; $i++)
{
if ($a[$i] !== $b[$i]) break;
}
return $i === 0 ? 0 : (float) $i / count($a);
}
}

View file

@ -93,7 +93,7 @@ class zerobin
{ {
if (version_compare(PHP_VERSION, '5.2.6') < 0) if (version_compare(PHP_VERSION, '5.2.6') < 0)
{ {
throw new Exception('ZeroBin requires php 5.2.6 or above to work. Sorry.', 1); throw new Exception(i18n::_('ZeroBin requires php 5.2.6 or above to work. Sorry.'), 1);
} }
// in case stupid admin has left magic_quotes enabled in php.ini // in case stupid admin has left magic_quotes enabled in php.ini
@ -156,7 +156,7 @@ class zerobin
$this->_conf = parse_ini_file(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', true); $this->_conf = parse_ini_file(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', true);
foreach (array('main', 'model') as $section) { foreach (array('main', 'model') as $section) {
if (!array_key_exists($section, $this->_conf)) { if (!array_key_exists($section, $this->_conf)) {
throw new Exception("ZeroBin requires configuration section [$section] to be present in configuration file.", 2); throw new Exception(i18n::_('ZeroBin requires configuration section [%s] to be present in configuration file.', $section), 2);
} }
} }
$this->_model = $this->_conf['model']['class']; $this->_model = $this->_conf['model']['class'];
@ -184,12 +184,12 @@ class zerobin
* Store new paste or comment * Store new paste or comment
* *
* POST contains: * POST contains:
* data (mandatory) = json encoded SJCL encrypted text (containing keys: iv,salt,ct) * data (mandatory) = json encoded SJCL encrypted text (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* *
* All optional data will go to meta information: * All optional data will go to meta information:
* expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never) * expire (optional) = expiration delay (never,5min,10min,1hour,1day,1week,1month,1year,burn) (default:never)
* opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0) * opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0)
* nickname (optional) = in discussion, encoded SJCL encrypted text nickname of author of comment (containing keys: iv,salt,ct) * nickname (optional) = in discussion, encoded SJCL encrypted text nickname of author of comment (containing keys: iv,v,iter,ks,ts,mode,adata,cipher,salt,ct)
* parentid (optional) = in discussion, which comment this comment replies to. * parentid (optional) = in discussion, which comment this comment replies to.
* pasteid (optional) = in discussion, which paste this comment belongs to. * pasteid (optional) = in discussion, which paste this comment belongs to.
* *
@ -208,9 +208,10 @@ class zerobin
{ {
$this->_return_message( $this->_return_message(
1, 1,
'Please wait ' . i18n::_(
$this->_conf['traffic']['limit'] . 'Please wait %d seconds between each post.',
' seconds between each post.' $this->_conf['traffic']['limit']
)
); );
return; return;
} }
@ -221,9 +222,10 @@ class zerobin
{ {
$this->_return_message( $this->_return_message(
1, 1,
'Paste is limited to ' . i18n::_(
filter::size_humanreadable($sizelimit) . 'Paste is limited to %s of encrypted data.',
' of encrypted data.' filter::size_humanreadable($sizelimit)
)
); );
return; return;
} }
@ -232,15 +234,18 @@ class zerobin
if (!sjcl::isValid($data)) return $this->_return_message(1, 'Invalid data.'); if (!sjcl::isValid($data)) return $this->_return_message(1, 'Invalid data.');
// Read additional meta-information. // Read additional meta-information.
$meta=array(); $meta = array();
// Read expiration date // Read expiration date
if (!empty($_POST['expire'])) if (!empty($_POST['expire']))
{ {
$selected_expire = (string) $_POST['expire']; $selected_expire = (string) $_POST['expire'];
if (array_key_exists($selected_expire, $this->_conf['expire_options'])) { if (array_key_exists($selected_expire, $this->_conf['expire_options']))
{
$expire = $this->_conf['expire_options'][$selected_expire]; $expire = $this->_conf['expire_options'][$selected_expire];
} else { }
else
{
$expire = $this->_conf['expire_options'][$this->_conf['expire']['default']]; $expire = $this->_conf['expire_options'][$this->_conf['expire']['default']];
} }
if ($expire > 0) $meta['expire_date'] = time() + $expire; if ($expire > 0) $meta['expire_date'] = time() + $expire;
@ -575,23 +580,25 @@ class zerobin
// label all the expiration options // label all the expiration options
$expire = array(); $expire = array();
foreach ($this->_conf['expire_options'] as $key => $value) { foreach ($this->_conf['expire_options'] as $key => $value) {
$expire[$key] = array_key_exists($key, $this->_conf['expire_labels']) ? $expire[$key] = i18n::_(
array_key_exists($key, $this->_conf['expire_labels']) ?
$this->_conf['expire_labels'][$key] : $this->_conf['expire_labels'][$key] :
$key; $key
);
} }
$page = new RainTPL; $page = new RainTPL;
$page::$path_replace = false; $page::$path_replace = false;
// we escape it here because ENT_NOQUOTES can't be used in RainTPL templates // we escape it here because ENT_NOQUOTES can't be used in RainTPL templates
$page->assign('CIPHERDATA', htmlspecialchars($this->_data, ENT_NOQUOTES)); $page->assign('CIPHERDATA', htmlspecialchars($this->_data, ENT_NOQUOTES));
$page->assign('ERROR', $this->_error); $page->assign('ERROR', i18n::_($this->_error));
$page->assign('STATUS', $this->_status); $page->assign('STATUS', i18n::_($this->_status));
$page->assign('VERSION', self::VERSION); $page->assign('VERSION', self::VERSION);
$page->assign('DISCUSSION', $this->_getMainConfig('discussion', true)); $page->assign('DISCUSSION', $this->_getMainConfig('discussion', true));
$page->assign('OPENDISCUSSION', $this->_getMainConfig('opendiscussion', true)); $page->assign('OPENDISCUSSION', $this->_getMainConfig('opendiscussion', true));
$page->assign('SYNTAXHIGHLIGHTING', $this->_getMainConfig('syntaxhighlighting', true)); $page->assign('SYNTAXHIGHLIGHTING', $this->_getMainConfig('syntaxhighlighting', true));
$page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_getMainConfig('syntaxhighlightingtheme', '')); $page->assign('SYNTAXHIGHLIGHTINGTHEME', $this->_getMainConfig('syntaxhighlightingtheme', ''));
$page->assign('NOTICE', $this->_getMainConfig('notice', '')); $page->assign('NOTICE', i18n::_($this->_getMainConfig('notice', '')));
$page->assign('BURNAFTERREADINGSELECTED', $this->_getMainConfig('burnafterreadingselected', false)); $page->assign('BURNAFTERREADINGSELECTED', $this->_getMainConfig('burnafterreadingselected', false));
$page->assign('PASSWORD', $this->_getMainConfig('password', true)); $page->assign('PASSWORD', $this->_getMainConfig('password', true));
$page->assign('BASE64JSVERSION', $this->_getMainConfig('base64version', '2.1.9')); $page->assign('BASE64JSVERSION', $this->_getMainConfig('base64version', '2.1.9'));
@ -629,7 +636,7 @@ class zerobin
$result = array('status' => $status); $result = array('status' => $status);
if ($status) if ($status)
{ {
$result['message'] = $message; $result['message'] = i18n::_($message);
} }
else else
{ {

View file

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<title>ZeroBin</title> <title>{function="t('ZeroBin')"}</title>
<link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-3.3.5.css" /> <link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-3.3.5.css" />
<link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-theme-3.3.5.css" /> <link type="text/css" rel="stylesheet" href="css/bootstrap/bootstrap-theme-3.3.5.css" />
<link type="text/css" rel="stylesheet" href="css/bootstrap/zerobin.css?{$VERSION|rawurlencode}" />{if="$SYNTAXHIGHLIGHTING"} <link type="text/css" rel="stylesheet" href="css/bootstrap/zerobin.css?{$VERSION|rawurlencode}" />{if="$SYNTAXHIGHLIGHTING"}
@ -28,31 +28,31 @@
<div class="container"> <div class="container">
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span> <span class="sr-only">{function="t('Toggle navigation')"}</span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="/" onclick="window.location.href=scriptLocation();return false;">ZeroBin</a> <a class="navbar-brand" href="/" onclick="window.location.href=scriptLocation();return false;">{function="t('ZeroBin')"}</a>
</div> </div>
<div id="navbar" class="navbar-collapse collapse"> <div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav pull-right"> <ul class="nav navbar-nav pull-right">
<li> <li>
<button id="newbutton" type="button" class="hidden btn btn-default navbar-btn" onclick="window.location.href=scriptLocation();return false;"> <button id="newbutton" type="button" class="hidden btn btn-default navbar-btn" onclick="window.location.href=scriptLocation();return false;">
<span class="glyphicon glyphicon-file" aria-hidden="true"></span> New <span class="glyphicon glyphicon-file" aria-hidden="true"></span> {function="t('New')"}
</button> </button>
</li> </li>
</ul> </ul>
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="pr"> <li class="pr">
<button id="sendbutton" type="button" class="hidden btn btn-default navbar-btn" onclick="send_data();return false;"> <button id="sendbutton" type="button" class="hidden btn btn-default navbar-btn" onclick="send_data();return false;">
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span> Send <span class="glyphicon glyphicon-upload" aria-hidden="true"></span> {function="t('Send')"}
</button> </button>
<button id="clonebutton" type="button" class="hidden btn btn-default navbar-btn" onclick="clonePaste();return false;"> <button id="clonebutton" type="button" class="hidden btn btn-default navbar-btn" onclick="clonePaste();return false;">
<span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> Clone <span class="glyphicon glyphicon-duplicate" aria-hidden="true"></span> {function="t('Clone')"}
</button> </button>
<button id="rawtextbutton" type="button" class="hidden btn btn-default navbar-btn" onclick="rawText();return false;"> <button id="rawtextbutton" type="button" class="hidden btn btn-default navbar-btn" onclick="rawText();return false;">
<span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> Raw text <span class="glyphicon glyphicon-text-background" aria-hidden="true"></span> {function="t('Raw text')"}
</button> </button>
</li> </li>
<li class="dropdown"> <li class="dropdown">
@ -60,7 +60,7 @@
{loop="EXPIRE"} {loop="EXPIRE"}
<option value="{$key}"{if="$key == $EXPIREDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop} <option value="{$key}"{if="$key == $EXPIREDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
</select> </select>
<a id="expiration" href="#" class="hidden dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Expires: <span id="pasteExpirationDisplay">{$EXPIREDEFAULT}</span> <span class="caret"></span></a> <a id="expiration" href="#" class="hidden dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{function="t('Expires')"}: <span id="pasteExpirationDisplay">{$EXPIRE[$EXPIREDEFAULT]}</span> <span class="caret"></span></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{loop="EXPIRE"} {loop="EXPIRE"}
<li><a href="#" onclick="$('#pasteExpiration').val('{$key}');$('#pasteExpirationDisplay').text('{$value}');return false;">{$value}</a></li>{/loop} <li><a href="#" onclick="$('#pasteExpiration').val('{$key}');$('#pasteExpirationDisplay').text('{$value}');return false;">{$value}</a></li>{/loop}
@ -70,7 +70,7 @@
<div id="burnafterreadingoption" class="navbar-text checkbox hidden"> <div id="burnafterreadingoption" class="navbar-text checkbox hidden">
<label> <label>
<input type="checkbox" id="burnafterreading" name="burnafterreading" {if="$BURNAFTERREADINGSELECTED"} checked="checked"{/if} /> <input type="checkbox" id="burnafterreading" name="burnafterreading" {if="$BURNAFTERREADINGSELECTED"} checked="checked"{/if} />
Burn after reading {function="t('Burn after reading')"}
</label> </label>
</div> </div>
</li>{if="$DISCUSSION"} </li>{if="$DISCUSSION"}
@ -78,13 +78,13 @@
<div id="opendisc" class="navbar-text checkbox hidden"> <div id="opendisc" class="navbar-text checkbox hidden">
<label> <label>
<input type="checkbox" id="opendiscussion" name="opendiscussion" {if="$OPENDISCUSSION"} checked="checked"{/if} /> <input type="checkbox" id="opendiscussion" name="opendiscussion" {if="$OPENDISCUSSION"} checked="checked"{/if} />
Open discussion {function="t('Open discussion')"}
</label> </label>
</div> </div>
</li>{/if}{if="$PASSWORD"} </li>{/if}{if="$PASSWORD"}
<li> <li>
<div id="password" class="navbar-form hidden"> <div id="password" class="navbar-form hidden">
<input type="password" id="passwordinput" placeholder="Password (optional)" class="form-control" size="19"/> <input type="password" id="passwordinput" placeholder="{function="t('Password (recommended)')"}" class="form-control" size="19"/>
</div> </div>
</li>{/if} </li>{/if}
<li> <li>
@ -100,9 +100,9 @@
</div>{/if}{if="strlen($STATUS)"} </div>{/if}{if="strlen($STATUS)"}
<div id="status" role="alert" class="alert alert-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {$STATUS|htmlspecialchars}</div>{/if} <div id="status" role="alert" class="alert alert-success"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {$STATUS|htmlspecialchars}</div>{/if}
<div id="errormessage" role="alert" class="{if="!strlen($ERROR)"}hidden {/if}alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {$ERROR|htmlspecialchars}</div> <div id="errormessage" role="alert" class="{if="!strlen($ERROR)"}hidden {/if}alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {$ERROR|htmlspecialchars}</div>
<div id="noscript" role="alert" class="nonworking alert alert-warning"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.</div> <div id="noscript" role="alert" class="nonworking alert alert-warning"><span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> {function="t('Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.')"}</div>
<div id="oldienotice" role="alert" class="hidden nonworking alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> ZeroBin requires a modern browser to work.</div> <div id="oldienotice" role="alert" class="hidden nonworking alert alert-danger"><span class="glyphicon glyphicon-alert" aria-hidden="true"></span> {function="t('ZeroBin requires a modern browser to work.')"}</div>
<div id="ienotice" role="alert" class="hidden alert alert-warning"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> Still using Internet Explorer? Do yourself a favor, switch to a modern browser: <div id="ienotice" role="alert" class="hidden alert alert-warning"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span> {function="t('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:')"}
<a href="http://www.mozilla.org/firefox/">Firefox</a>, <a href="http://www.mozilla.org/firefox/">Firefox</a>,
<a href="http://www.opera.com/">Opera</a>, <a href="http://www.opera.com/">Opera</a>,
<a href="http://www.google.com/chrome">Chrome</a>, <a href="http://www.google.com/chrome">Chrome</a>,
@ -125,18 +125,16 @@
</section> </section>
<section class="container"> <section class="container">
<div id="discussion" class="hidden"> <div id="discussion" class="hidden">
<h4>Discussion</h4> <h4>{function="t('Discussion')"}</h4>
<div id="comments"></div> <div id="comments"></div>
</div> </div>
</section> </section>
<footer class="container"> <footer class="container">
<div class="row"> <div class="row">
<h4 class="col-md-3 col-xs-8">ZeroBin <small>- Because ignorance is bliss</small></h4> <h4 class="col-md-5 col-xs-8">{function="t('ZeroBin')"} <small>- {function="t('Because ignorance is bliss')"}</small></h4>
<p class="col-md-1 col-xs-4 text-center">{$VERSION}</p> <p class="col-md-1 col-xs-4 text-center">{$VERSION}</p>
<p id="aboutbox" class="col-md-8 col-xs-12"> <p id="aboutbox" class="col-md-6 col-xs-12">
ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data.<br /> {function="t('ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. More information on the <a href="https://github.com/elrido/ZeroBin/wiki">project page</a>.')"}
Data is encrypted/decrypted <em>in the browser</em> using 256 bits AES.<br />
More information on the <a href="https://github.com/elrido/ZeroBin/wiki" target="_blank">project page</a>.
</p> </p>
</div> </div>
</footer> </footer>

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="robots" content="noindex" /> <meta name="robots" content="noindex" />
<title>ZeroBin</title> <title>{function="t('ZeroBin')"}</title>
<link type="text/css" rel="stylesheet" href="css/zerobin.css?{$VERSION|rawurlencode}" />{if="$SYNTAXHIGHLIGHTING"} <link type="text/css" rel="stylesheet" href="css/zerobin.css?{$VERSION|rawurlencode}" />{if="$SYNTAXHIGHLIGHTING"}
<link type="text/css" rel="stylesheet" href="css/prettify/prettify.css?{$VERSION|rawurlencode}" />{if="strlen($SYNTAXHIGHLIGHTINGTHEME)"} <link type="text/css" rel="stylesheet" href="css/prettify/prettify.css?{$VERSION|rawurlencode}" />{if="strlen($SYNTAXHIGHLIGHTINGTHEME)"}
<link type="text/css" rel="stylesheet" href="css/prettify/{$SYNTAXHIGHLIGHTINGTHEME}.css?{$VERSION|rawurlencode}" />{/if}{/if} <link type="text/css" rel="stylesheet" href="css/prettify/{$SYNTAXHIGHLIGHTINGTHEME}.css?{$VERSION|rawurlencode}" />{/if}{/if}
@ -21,17 +21,15 @@
<body> <body>
<header> <header>
<div id="aboutbox"> <div id="aboutbox">
ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. {function="t('ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. More information on the <a href="https://github.com/elrido/ZeroBin/wiki">project page</a>.')"}<br />{if="strlen($NOTICE)"}
Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES.
More information on the <a href="https://github.com/elrido/ZeroBin/wiki">project page</a>.<br />{if="strlen($NOTICE)"}
<span class="blink"></span> {$NOTICE}{/if} <span class="blink"></span> {$NOTICE}{/if}
</div> </div>
<h1 title="ZeroBin" onclick="window.location.href=scriptLocation();return false;">ZeroBin</h1><br /> <h1 title="ZeroBin" onclick="window.location.href=scriptLocation();return false;">{function="t('ZeroBin')"}</h1><br />
<h2>Because ignorance is bliss</h2><br /> <h2>{function="t('Because ignorance is bliss')"}</h2><br />
<h3>{$VERSION}</h3> <h3>{$VERSION}</h3>
<div id="noscript" class="nonworking">Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.</div> <div id="noscript" class="nonworking">{function="t('Javascript is required for ZeroBin to work.<br />Sorry for the inconvenience.')"}</div>
<div id="oldienotice" class="nonworking">ZeroBin requires a modern browser to work.</div> <div id="oldienotice" class="nonworking">{function="t('ZeroBin requires a modern browser to work.')"}</div>
<div id="ienotice">Still using Internet Explorer ? Do yourself a favor, switch to a modern browser: <div id="ienotice">{function="t('Still using Internet Explorer? Do yourself a favor, switch to a modern browser:')"}
<a href="http://www.mozilla.org/firefox/">Firefox</a>, <a href="http://www.mozilla.org/firefox/">Firefox</a>,
<a href="http://www.opera.com/">Opera</a>, <a href="http://www.opera.com/">Opera</a>,
<a href="http://www.google.com/chrome">Chrome</a>, <a href="http://www.google.com/chrome">Chrome</a>,
@ -43,11 +41,11 @@
<div id="status">{$STATUS|htmlspecialchars}</div> <div id="status">{$STATUS|htmlspecialchars}</div>
<div id="errormessage" class="hidden">{$ERROR|htmlspecialchars}</div> <div id="errormessage" class="hidden">{$ERROR|htmlspecialchars}</div>
<div id="toolbar"> <div id="toolbar">
<button id="newbutton" onclick="window.location.href=scriptLocation();return false;" class="hidden"><img src="img/icon_new.png" width="11" height="15" alt="" />New</button> <button id="newbutton" onclick="window.location.href=scriptLocation();return false;" class="hidden"><img src="img/icon_new.png" width="11" height="15" alt="" />{function="t('New')"}</button>
<button id="sendbutton" onclick="send_data();return false;" class="hidden"><img src="img/icon_send.png" width="18" height="15" alt="" />Send</button> <button id="sendbutton" onclick="send_data();return false;" class="hidden"><img src="img/icon_send.png" width="18" height="15" alt="" />{function="t('Send')"}</button>
<button id="clonebutton" onclick="clonePaste();return false;" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" />Clone</button> <button id="clonebutton" onclick="clonePaste();return false;" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" />{function="t('Clone')"}</button>
<button id="rawtextbutton" onclick="rawText();return false;" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" />Raw text</button> <button id="rawtextbutton" onclick="rawText();return false;" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" />{function="t('Raw text')"}</button>
<div id="expiration" class="hidden">Expires: <div id="expiration" class="hidden">{function="t('Expires')"}:
<select id="pasteExpiration" name="pasteExpiration"> <select id="pasteExpiration" name="pasteExpiration">
{loop="EXPIRE"} {loop="EXPIRE"}
<option value="{$key}"{if="$key == $EXPIREDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop} <option value="{$key}"{if="$key == $EXPIREDEFAULT"} selected="selected"{/if}>{$value}</option>{/loop}
@ -56,14 +54,14 @@
<div id="remainingtime" class="hidden"></div> <div id="remainingtime" class="hidden"></div>
<div id="burnafterreadingoption" class="button hidden"> <div id="burnafterreadingoption" class="button hidden">
<input type="checkbox" id="burnafterreading" name="burnafterreading" {if="$BURNAFTERREADINGSELECTED"} checked="checked"{/if} /> <input type="checkbox" id="burnafterreading" name="burnafterreading" {if="$BURNAFTERREADINGSELECTED"} checked="checked"{/if} />
<label for="burnafterreading">Burn after reading</label> <label for="burnafterreading">{function="t('Burn after reading')"}</label>
</div>{if="$DISCUSSION"} </div>{if="$DISCUSSION"}
<div id="opendisc" class="button hidden"> <div id="opendisc" class="button hidden">
<input type="checkbox" id="opendiscussion" name="opendiscussion" {if="$OPENDISCUSSION"} checked="checked"{/if} /> <input type="checkbox" id="opendiscussion" name="opendiscussion" {if="$OPENDISCUSSION"} checked="checked"{/if} />
<label for="opendiscussion" {if="!$OPENDISCUSSION"} style="color: #BBBBBB;"{/if}>Open discussion</label> <label for="opendiscussion" {if="!$OPENDISCUSSION"} style="color: #BBBBBB;"{/if}>{function="t('Open discussion')"}</label>
</div>{/if}{if="$PASSWORD"} </div>{/if}{if="$PASSWORD"}
<div id="password" class="hidden"> <div id="password" class="hidden">
<input id="passwordinput" placeholder="Optional password (recommended)" size="32" /> <input id="passwordinput" placeholder="{function="t('Password (recommended)')"}" size="32" />
</div>{/if} </div>{/if}
</div> </div>
<div id="pasteresult" class="hidden"> <div id="pasteresult" class="hidden">
@ -79,7 +77,7 @@
</section> </section>
<section> <section>
<div id="discussion" class="hidden"> <div id="discussion" class="hidden">
<h4>Discussion</h4> <h4>{function="t('Discussion')"}</h4>
<div id="comments"></div> <div id="comments"></div>
</div> </div>
</section> </section>

View file

@ -3,6 +3,7 @@ error_reporting( E_ALL | E_STRICT );
// change this, if your php files and data is outside of your webservers document root // change this, if your php files and data is outside of your webservers document root
if (!defined('PATH')) define('PATH', '..' . DIRECTORY_SEPARATOR); if (!defined('PATH')) define('PATH', '..' . DIRECTORY_SEPARATOR);
if (!defined('PUBLIC_PATH')) define('PUBLIC_PATH', '..');
require PATH . 'lib/auto.php'; require PATH . 'lib/auto.php';

41
tst/i18n.php Normal file
View file

@ -0,0 +1,41 @@
<?php
class i18nTest extends PHPUnit_Framework_TestCase
{
private $_translations = array();
public function setUp()
{
/* Setup Routine */
$this->_translations = json_decode(
file_get_contents(PATH . 'i18n' . DIRECTORY_SEPARATOR . 'de.json'),
true
);
}
public function tearDown()
{
/* Tear Down Routine */
}
public function testTranslationFallback()
{
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'foobar';
$messageId = 'It does not matter if the message ID exists';
i18n::loadTranslations();
$this->assertEquals($messageId, i18n::_($messageId), 'fallback to en');
}
public function testBrowserLanguageDetection()
{
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'de-CH,de;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
i18n::loadTranslations();
$this->assertEquals($this->_translations['en'], i18n::_('en'), 'browser language de');
}
public function testVariableInjection()
{
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'foobar';
i18n::loadTranslations();
$this->assertEquals('some string + 1', i18n::_('some %s + %d', 'string', 1), 'browser language de');
}
}