diff --git a/i18n/de.json b/i18n/de.json
index f58e3d95..c702dd46 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -74,5 +74,63 @@
"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."
+ "Hinweis: Dies ist ein Versuchsdienst. Daten können jederzeit gelöscht werden. Kätzchen werden sterben wenn Du diesen Dienst missbrauchst.",
+ "This document will expire in %s.":
+ "Dieses Dokument läuft in %s ab.",
+ "%d second": "einer Sekunde",
+ "%d seconds": "%d Sekunden",
+ "%d minute": "einer Minute",
+ "%d minutes": "%d Minuten",
+ "%d hour": "einer Stunde",
+ "%d hours": "%d Stunden",
+ "%d day": "einem Tag",
+ "%d days": "%d Tagen",
+ "%d month": "einem Monat",
+ "%d months": "%d Monaten",
+ "Please enter the password for this paste:":
+ "Bitte gib das Passwort für diesen Text ein:",
+ "Could not decrypt data (Wrong key?)":
+ "Konnte Daten nicht entschlüsseln (Falscher Schlüssel?)",
+ "Could not delete the paste, it was not stored in burn after reading mode.":
+ "Konnte den Text nicht löschen, er wurde nicht im Einmal-Modus gespeichert.",
+ "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.":
+ "DIESER TEXT IST NUR FÜR DICH GEDACHT. Schliesse das Fenster nicht, diese Nachricht kann nur einmal geöffnet werden.",
+ "Could not decrypt comment; Wrong key?":
+ "Konnte Kommentar nicht entschlüsseln; Falscher Schlüssel?",
+ "Reply":
+ "Antworten",
+ "Anonymous":
+ "Anonym",
+ "Anonymous avatar (Vizhash of the IP address)":
+ "Anonymer Avatar (Vizhash der IP-Addresse)",
+ "Add comment":
+ "Kommentar hinzufügen",
+ "Optional nickname...":
+ "Optionales Pseudonym...",
+ "Post comment":
+ "Kommentar absenden",
+ "Sending comment...":
+ "Sende Kommentar...",
+ "Comment posted.":
+ "Kommentar gesendet.",
+ "Could not refresh display: %s":
+ "Konnte Ansicht nicht aktualisieren: %s",
+ "unknown status":
+ "Unbekannter Grund",
+ "server error or not responding":
+ "Fehler auf dem Server oder keine Antwort vom Server",
+ "Could not post comment: %s":
+ "Konnte Kommentar nicht senden: %s",
+ "Sending paste (Please move your mouse for more entropy)...":
+ "Sende Text (Bitte bewege Deine Maus um die Entropie zu erhöhen)...",
+ "Sending paste...":
+ "Sende Text...",
+ "Your paste is %s (Hit [Ctrl]+[c] to copy)":
+ "Dein Text ist unter %s zu finden (Drücke [Strg]+[c] um den Link zu kopieren)",
+ "Delete data":
+ "Lösche Daten",
+ "Could not create paste: %s":
+ "Konnte Text nicht erstellen: %s",
+ "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)":
+ "Konnte Text nicht entschlüsseln: Der Schlüssel fehlt in der Adresse (Hast Du eine Umleitung oder einen URL-Verkürzer benutzt, der Teile der Adresse entfernt?)"
}
diff --git a/i18n/fr.json b/i18n/fr.json
index 1faf38c2..cb24a1ae 100644
--- a/i18n/fr.json
+++ b/i18n/fr.json
@@ -74,5 +74,63 @@
"Never":
"Jamais",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.":
- "Note : Ceci est un service de test : les données peuvent être supprimées à tout moment. Des chatons mourront si vous utilisez ce service de manière abusive."
+ "Note : Ceci est un service de test : les données peuvent être supprimées à tout moment. Des chatons mourront si vous utilisez ce service de manière abusive.",
+ "This document will expire in %s.":
+ "This document will expire in %s.",
+ "%d second": "%d second",
+ "%d seconds": "%d seconds",
+ "%d minute": "%d minute",
+ "%d minutes": "%d minutes",
+ "%d hour": "%d hour",
+ "%d hours": "%d hours",
+ "%d day": "%d day",
+ "%d days": "%d days",
+ "%d month": "%d month",
+ "%d months": "%d months",
+ "Please enter the password for this paste:":
+ "Please enter the password for this paste:",
+ "Could not decrypt data (Wrong key?)":
+ "Could not decrypt data (Wrong key?)",
+ "Could not delete the paste, it was not stored in burn after reading mode.":
+ "Could not delete the paste, it was not stored in burn after reading mode.",
+ "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.":
+ "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.",
+ "Could not decrypt comment; Wrong key?":
+ "Could not decrypt comment; Wrong key?",
+ "Reply":
+ "Reply",
+ "Anonymous":
+ "Anonymous",
+ "Anonymous avatar (Vizhash of the IP address)":
+ "Anonymous avatar (Vizhash of the IP address)",
+ "Add comment":
+ "Add comment",
+ "Optional nickname...":
+ "Optional nickname...",
+ "Post comment":
+ "Post comment",
+ "Sending comment...":
+ "Sending comment...",
+ "Comment posted.":
+ "Comment posted.",
+ "Could not refresh display: %s":
+ "Could not refresh display: %s",
+ "unknown status":
+ "unknown status",
+ "server error or not responding":
+ "server error or not responding",
+ "Could not post comment: %s":
+ "Could not post comment: %s",
+ "Sending paste (Please move your mouse for more entropy)...":
+ "Sending paste (Please move your mouse for more entropy)...",
+ "Sending paste...":
+ "Sending paste...",
+ "Your paste is %s (Hit [Ctrl]+[c] to copy)":
+ "Your paste is %s (Hit [Ctrl]+[c] to copy)",
+ "Delete data":
+ "Delete data",
+ "Could not create paste: %s":
+ "Could not create paste: %s",
+ "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)":
+ "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)"
}
diff --git a/i18n/pl.json b/i18n/pl.json
index 2bdb8465..c769576c 100644
--- a/i18n/pl.json
+++ b/i18n/pl.json
@@ -74,5 +74,63 @@
"Never":
"Nigdy",
"Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.":
- "Notka: To jest usługa testowa. Dane mogą zostać usunięte w dowolnym momencie. Kociątka umrą, jeśli nadużyjesz tej usługi."
+ "Notka: To jest usługa testowa. Dane mogą zostać usunięte w dowolnym momencie. Kociątka umrą, jeśli nadużyjesz tej usługi.",
+ "This document will expire in %s.":
+ "This document will expire in %s.",
+ "%d second": "%d second",
+ "%d seconds": "%d seconds",
+ "%d minute": "%d minute",
+ "%d minutes": "%d minutes",
+ "%d hour": "%d hour",
+ "%d hours": "%d hours",
+ "%d day": "%d day",
+ "%d days": "%d days",
+ "%d month": "%d month",
+ "%d months": "%d months",
+ "Please enter the password for this paste:":
+ "Please enter the password for this paste:",
+ "Could not decrypt data (Wrong key?)":
+ "Could not decrypt data (Wrong key?)",
+ "Could not delete the paste, it was not stored in burn after reading mode.":
+ "Could not delete the paste, it was not stored in burn after reading mode.",
+ "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.":
+ "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.",
+ "Could not decrypt comment; Wrong key?":
+ "Could not decrypt comment; Wrong key?",
+ "Reply":
+ "Reply",
+ "Anonymous":
+ "Anonymous",
+ "Anonymous avatar (Vizhash of the IP address)":
+ "Anonymous avatar (Vizhash of the IP address)",
+ "Add comment":
+ "Add comment",
+ "Optional nickname...":
+ "Optional nickname...",
+ "Post comment":
+ "Post comment",
+ "Sending comment...":
+ "Sending comment...",
+ "Comment posted.":
+ "Comment posted.",
+ "Could not refresh display: %s":
+ "Could not refresh display: %s",
+ "unknown status":
+ "unknown status",
+ "server error or not responding":
+ "server error or not responding",
+ "Could not post comment: %s":
+ "Could not post comment: %s",
+ "Sending paste (Please move your mouse for more entropy)...":
+ "Sending paste (Please move your mouse for more entropy)...",
+ "Sending paste...":
+ "Sending paste...",
+ "Your paste is %s (Hit [Ctrl]+[c] to copy)":
+ "Your paste is %s (Hit [Ctrl]+[c] to copy)",
+ "Delete data":
+ "Delete data",
+ "Could not create paste: %s":
+ "Could not create paste: %s",
+ "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)":
+ "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)"
}
diff --git a/js/zerobin.js b/js/zerobin.js
index 71bea9d5..89e58c1f 100644
--- a/js/zerobin.js
+++ b/js/zerobin.js
@@ -9,17 +9,16 @@
* @version 0.20
*/
+'use strict';
+
// Immediately start random number generator collector.
sjcl.random.startCollectors();
-$(function(){
- 'use strict';
-
+$(function() {
/**
* static helper methods
*/
var helper = {
-
/**
* Converts a duration (in seconds) into human readable format.
*
@@ -30,27 +29,32 @@ $(function(){
{
if (seconds < 60)
{
- var v = Math.floor(seconds);
- return v + ' second' + ((v > 1) ? 's' : '');
+ var v = Math.floor(seconds),
+ format = '%d second' + ((v > 1) ? 's' : '');
+ return i18n._(format, v);
}
if (seconds < 60 * 60)
{
- var v = Math.floor(seconds / 60);
- return v + ' minute' + ((v > 1) ? 's' : '');
+ var v = Math.floor(seconds / 60),
+ format = '%d minute' + ((v > 1) ? 's' : '');
+ return i18n._(format, v);
}
if (seconds < 60 * 60 * 24)
{
- var v = Math.floor(seconds / (60 * 60));
- return v + ' hour' + ((v > 1) ? 's' : '');
+ var v = Math.floor(seconds / (60 * 60)),
+ format = '%d hour' + ((v > 1) ? 's' : '');
+ return i18n._(format, v);
}
// If less than 2 months, display in days:
if (seconds < 60 * 60 * 24 * 60)
{
- var v = Math.floor(seconds / (60 * 60 * 24));
- return v + ' day' + ((v > 1) ? 's' : '');
+ var v = Math.floor(seconds / (60 * 60 * 24)),
+ format = '%d day' + ((v > 1) ? 's' : '');
+ return i18n._(format, v);
}
- var v = Math.floor(seconds / (60 * 60 * 24 * 30));
- return v + ' month' + ((v > 1) ? 's' : '');
+ var v = Math.floor(seconds / (60 * 60 * 24 * 30)),
+ format = '%d month' + ((v > 1) ? 's' : '');
+ return i18n._(format, v);
},
/**
@@ -125,7 +129,7 @@ $(function(){
* Convert all applicable characters to HTML entities
*
* @param string str
- * @returns string encoded string
+ * @return string encoded string
*/
htmlEntities: function(str)
{
@@ -172,7 +176,7 @@ $(function(){
*/
setElementText: function(element, text)
{
- // For IE<10: Doesn't support white-space:pre-wrap; so we have to do this BIG UGLY STINKING THING.
+ // For IE<10: Doesn't support white-space:pre-wrap; so we have to do this...
if ($('#oldienotice').is(':visible')) {
var html = this.htmlEntities(text).replace(/\n/ig,'\r\n
');
element.html('
'+html+''); @@ -197,21 +201,113 @@ $(function(){ */ urls2links: function(element) { + var markup = '$1'; element.html( element.html().replace( /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+-]+(?![\w\s?&.\/;#~%"=-]*>))/ig, - '$1' + markup ) ); element.html( element.html().replace( /((magnet):[\w?=&.\/-;#@~%+-]+)/ig, - '$1' + markup ) ); + }, + + /** + * minimal sprintf emulation for %s and %d formats + * From: http://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format#4795914 + * + * @param string format + * @param mixed args one or multiple parameters injected into format string + * @return string + */ + sprintf: function() + { + var args = arguments; + if (typeof arguments[0] == 'object') args = arguments[0]; + var string = args[0], + i = 1; + return string.replace(/%((%)|s|d)/g, function (m) { + // m is the matched format, e.g. %s, %d + var val = null; + if (m[2]) { + val = m[2]; + } else { + val = args[i]; + // A switch statement so that the formatter can be extended. + switch (m) { + case '%d': + val = parseFloat(val); + if (isNaN(val)) { + val = 0; + } + break; + // Default is %s + } + ++i; + } + return val; + }); } }; + /** + * internationalization methods + */ + var i18n = { + supportedLanguages: ['de', 'fr', 'pl'], // and the built in 'en' + + /** + * translate a string, alias for translate() + * + * @param string $messageId + * @param mixed args one or multiple parameters injected into placeholders + * @return string + */ + _: function() + { + return this.translate(arguments); + }, + + /** + * translate a string + * + * @param string $messageId + * @param mixed args one or multiple parameters injected into placeholders + * @return string + */ + translate: function() + { + var args = arguments; + if (typeof arguments[0] == 'object') args = arguments[0]; + var messageId = args[0]; + if (messageId.length == 0) return messageId; + if (!this.translations.hasOwnProperty(messageId)) + { + console.log('Missing translation for: ' + messageId); + this.translations[messageId] = messageId; + } + args[0] = this.translations[messageId]; + return helper.sprintf(args); + }, + + loadTranslations: function(callback) + { + var language = (navigator.language || navigator.userLanguage).substring(0, 2); + // note that 'en' is built in, so no translation is necessary + if (this.supportedLanguages.indexOf(language) == -1) return; + $.getJSON('i18n/' + language + '.json', function(data) { + i18n.translations = data; + callback(); + }); + }, + + translations: {} + } + /** * filter methods */ @@ -296,8 +392,8 @@ $(function(){ scriptLocation: function() { var scriptLocation = window.location.href.substring(0,window.location.href.length - - window.location.search.length - window.location.hash.length); - var hashIndex = scriptLocation.indexOf('#'); + - window.location.search.length - window.location.hash.length), + hashIndex = scriptLocation.indexOf('#'); if (hashIndex !== -1) { scriptLocation = scriptLocation.substring(0, hashIndex); @@ -323,25 +419,18 @@ $(function(){ */ pageKey: function() { - var key = window.location.hash.substring(1); // Get key + // Some web 2.0 services and redirectors add data AFTER the anchor + // (such as &utm_source=...). We will strip any additional data. - // Some stupid web 2.0 services and redirectors add data AFTER the anchor - // (such as &utm_source=...). - // We will strip any additional data. + var key = window.location.hash.substring(1), // Get key + i = key.indexOf('='); // First, strip everything after the equal sign (=) which signals end of base64 string. - var i = key.indexOf('='); - if (i > -1) - { - key = key.substring(0, i + 1); - } + if (i > -1) key = key.substring(0, i + 1); // If the equal sign was not present, some parameters may remain: i = key.indexOf('&'); - if (i > -1) - { - key = key.substring(0, i); - } + if (i > -1) key = key.substring(0, i); // Then add trailing equal sign if it's missing if (key.charAt(key.length - 1) !== '=') key += '='; @@ -357,7 +446,7 @@ $(function(){ */ requestPassword: function() { - var password = prompt('Please enter the password for this paste:', ''); + var password = prompt(i18n._('Please enter the password for this paste:'), ''); if (password == null) throw 'password prompt canceled'; if (password.length == 0) return this.requestPassword(); return password; @@ -398,7 +487,7 @@ $(function(){ this.clearText.addClass('hidden'); this.prettyMessage.addClass('hidden'); this.cloneButton.addClass('hidden'); - this.showError('Could not decrypt data (Wrong key?)'); + this.showError(i18n._('Could not decrypt data (Wrong key?)')); return; } } @@ -407,18 +496,17 @@ $(function(){ if (comments[0].meta.expire_date) { this.remainingTime.removeClass('foryoureyesonly') - .text('This document will expire in ' + helper.secondsToHuman(comments[0].meta.remaining_time) + '.') + .text(i18n._('This document will expire in %s.', helper.secondsToHuman(comments[0].meta.remaining_time))) .removeClass('hidden'); } if (comments[0].meta.burnafterreading) { - var parent = this; $.get(this.scriptLocation() + '?pasteid=' + this.pasteID() + '&deletetoken=burnafterreading', 'json') .fail(function() { - parent.showError('Could not delete the paste, it was not stored in burn after reading mode.'); + zerobin.showError(i18n._('Could not delete the paste, it was not stored in burn after reading mode.')); }); this.remainingTime.addClass('foryoureyesonly') - .text('FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.') + .text(i18n._('FOR YOUR EYES ONLY. Don\'t close this window, this message can\'t be displayed again.')) .removeClass('hidden'); // Discourage cloning (as it can't really be prevented). this.cloneButton.addClass('hidden'); @@ -434,7 +522,7 @@ $(function(){ { var place = this.comments; var comment=comments[i]; - var cleartext='[Could not decrypt comment; Wrong key?]'; + var cleartext = '[' + i18n._('Could not decrypt comment; Wrong key?') + ']'; try { cleartext = filter.decipher(key, password, comment.data); @@ -451,7 +539,7 @@ $(function(){ } var divComment = $('