diff --git a/css/bootstrap/zerobin.css b/css/bootstrap/zerobin.css
index c797639f..3b660a31 100644
--- a/css/bootstrap/zerobin.css
+++ b/css/bootstrap/zerobin.css
@@ -26,3 +26,7 @@ body {
padding: 5px 0 5px 5px;
white-space: pre-wrap;
}
+
+h4 {
+ margin-top: 0;
+}
diff --git a/i18n/de.json b/i18n/de.json
new file mode 100644
index 00000000..65fa633b
--- /dev/null
+++ b/i18n/de.json
@@ -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 in the browser using 256 bits AES. More information on the project page.":
+ "ZeroBin ist ein minimalistischer, quelloffener \"pastebin\"-artiger Dienst bei dem der Server keinerlei Kenntnis der Daten-Schnipsel hat. Die Daten werden im Browser mit 256 Bit AES ver- und entschlüsselt. Weitere Informationen sind auf der Projektseite zu finden.",
+ "Because ignorance is bliss":
+ "Was ich nicht weiss, macht mich nicht heiss",
+ "Javascript is required for ZeroBin to work.
Sorry for the inconvenience.":
+ "Javascript ist eine Voraussetzung um ZeroBin zu nutzen.
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."
+}
diff --git a/index.php b/index.php
index 572738cd..5ad75f73 100644
--- a/index.php
+++ b/index.php
@@ -13,5 +13,6 @@
// change this, if your php files and data is outside of your webservers document root
define('PATH', '');
+define('PUBLIC_PATH', dirname(__FILE__));
require PATH . 'lib/auto.php';
new zerobin;
diff --git a/lib/RainTPL.php b/lib/RainTPL.php
index 8edb708d..2250b1d6 100644
--- a/lib/RainTPL.php
+++ b/lib/RainTPL.php
@@ -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
diff --git a/lib/i18n.php b/lib/i18n.php
new file mode 100644
index 00000000..73934a94
--- /dev/null
+++ b/lib/i18n.php
@@ -0,0 +1,219 @@
+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);
+ }
+}
diff --git a/lib/zerobin.php b/lib/zerobin.php
index 3485fee9..6f9b12ca 100644
--- a/lib/zerobin.php
+++ b/lib/zerobin.php
@@ -93,7 +93,7 @@ class zerobin
{
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
@@ -156,7 +156,7 @@ class zerobin
$this->_conf = parse_ini_file(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', true);
foreach (array('main', 'model') as $section) {
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'];
@@ -184,12 +184,12 @@ class zerobin
* Store new paste or comment
*
* 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:
* 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)
- * 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.
* pasteid (optional) = in discussion, which paste this comment belongs to.
*
@@ -208,9 +208,10 @@ class zerobin
{
$this->_return_message(
1,
- 'Please wait ' .
- $this->_conf['traffic']['limit'] .
- ' seconds between each post.'
+ i18n::_(
+ 'Please wait %d seconds between each post.',
+ $this->_conf['traffic']['limit']
+ )
);
return;
}
@@ -221,9 +222,10 @@ class zerobin
{
$this->_return_message(
1,
- 'Paste is limited to ' .
- filter::size_humanreadable($sizelimit) .
- ' of encrypted data.'
+ i18n::_(
+ 'Paste is limited to %s of encrypted data.',
+ filter::size_humanreadable($sizelimit)
+ )
);
return;
}
@@ -232,15 +234,18 @@ class zerobin
if (!sjcl::isValid($data)) return $this->_return_message(1, 'Invalid data.');
// Read additional meta-information.
- $meta=array();
+ $meta = array();
// Read expiration date
if (!empty($_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];
- } else {
+ }
+ else
+ {
$expire = $this->_conf['expire_options'][$this->_conf['expire']['default']];
}
if ($expire > 0) $meta['expire_date'] = time() + $expire;
@@ -575,23 +580,25 @@ class zerobin
// label all the expiration options
$expire = array();
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] :
- $key;
+ $key
+ );
}
$page = new RainTPL;
$page::$path_replace = false;
// we escape it here because ENT_NOQUOTES can't be used in RainTPL templates
$page->assign('CIPHERDATA', htmlspecialchars($this->_data, ENT_NOQUOTES));
- $page->assign('ERROR', $this->_error);
- $page->assign('STATUS', $this->_status);
+ $page->assign('ERROR', i18n::_($this->_error));
+ $page->assign('STATUS', i18n::_($this->_status));
$page->assign('VERSION', self::VERSION);
$page->assign('DISCUSSION', $this->_getMainConfig('discussion', true));
$page->assign('OPENDISCUSSION', $this->_getMainConfig('opendiscussion', true));
$page->assign('SYNTAXHIGHLIGHTING', $this->_getMainConfig('syntaxhighlighting', true));
$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('PASSWORD', $this->_getMainConfig('password', true));
$page->assign('BASE64JSVERSION', $this->_getMainConfig('base64version', '2.1.9'));
@@ -629,7 +636,7 @@ class zerobin
$result = array('status' => $status);
if ($status)
{
- $result['message'] = $message;
+ $result['message'] = i18n::_($message);
}
else
{
diff --git a/tpl/bootstrap.html b/tpl/bootstrap.html
index d4ba2948..28f05180 100644
--- a/tpl/bootstrap.html
+++ b/tpl/bootstrap.html
@@ -5,7 +5,7 @@
-