From c83ba8256f8f7ca51994e9364cefceabb4735d09 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sun, 6 Sep 2015 15:54:43 +0200 Subject: [PATCH] implementing a plural translation solution, currently only the JS part --- i18n/de.json | 43 +++++++++------------ i18n/fr.json | 43 +++++++++------------ i18n/pl.json | 43 +++++++++------------ js/zerobin.js | 102 ++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 130 insertions(+), 101 deletions(-) diff --git a/i18n/de.json b/i18n/de.json index c702dd4..a3a512c 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -57,36 +57,27 @@ "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", + "%d seconds": ["%d Sekunde", "%d Sekunden"], + "%d minutes": ["%d Minute", "%d Minuten"], + "%d hours": ["%d Stunde", "%d Stunden"], + "%d days": ["%d Tag", "%d Tage"], + "%d weeks": ["%d Woche", "%d Wochen"], + "%d months": ["%d Monat", "%d Monate"], + "%d years": ["%d Jahr", "%d Jahre"], "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.", - "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", + "This document will expire in %d seconds.": + ["Dieses Dokument läuft in einer Sekunde ab.", "Dieses Dokument läuft in %d Sekunden ab."], + "This document will expire in %d minutes.": + ["Dieses Dokument läuft in einer Minute ab.", "Dieses Dokument läuft in %d Minuten ab."], + "This document will expire in %d hours.": + ["Dieses Dokument läuft in einer Stunde ab.", "Dieses Dokument läuft in %d Stunden ab."], + "This document will expire in %d days.": + ["Dieses Dokument läuft in einem Tag ab.", "Dieses Dokument läuft in %d Tagen ab."], + "This document will expire in %d months.": + ["Dieses Dokument läuft in einem Monat ab.", "Dieses Dokument läuft in %d Monaten ab."], "Please enter the password for this paste:": "Bitte gib das Passwort für diesen Text ein:", "Could not decrypt data (Wrong key?)": diff --git a/i18n/fr.json b/i18n/fr.json index cb24a1a..08ee78c 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -57,36 +57,27 @@ "Discussion", "Toggle navigation": "Basculer la navigation", - "5 minutes": - "5 minutes", - "10 minutes": - "10 minutes", - "1 hour": - "1 heure", - "1 day": - "1 jour", - "1 week": - "1 semaine", - "1 month": - "1 mois", - "1 year": - "1 an", + "%d seconds": ["%d second", "%d seconds"], + "%d minutes": ["%d minute", "%d minutes"], + "%d hours": ["%d heure", "%d heures"], + "%d days": ["%d jour", "%d jours"], + "%d weeks": ["%d semaine", "%d semaines"], + "%d months": ["%d mois", "%d mois"], + "%d years": ["%d an", "%d ans"], "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.", - "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", + "This document will expire in %d seconds.": + ["This document will expire in %d second.", "This document will expire in %d seconds."], + "This document will expire in %d minutes.": + ["This document will expire in %d minute.", "This document will expire in %d minutes."], + "This document will expire in %d hours.": + ["This document will expire in %d hour.", "This document will expire in %d hours."], + "This document will expire in %d days.": + ["This document will expire in %d day.", "This document will expire in %d days."], + "This document will expire in %d months.": + ["This document will expire in %d month.", "This document will expire in %d months."], "Please enter the password for this paste:": "Please enter the password for this paste:", "Could not decrypt data (Wrong key?)": diff --git a/i18n/pl.json b/i18n/pl.json index c769576..d83211d 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -57,36 +57,27 @@ "Dyskusja", "Toggle navigation": "Przełącz nawigację", - "5 minutes": - "5 minut", - "10 minutes": - "10 minut", - "1 hour": - "1 godzina", - "1 day": - "1 dzień", - "1 week": - "1 tydzień", - "1 month": - "1 miesiąc", - "1 year": - "1 rok", + "%d seconds": ["%d second", "%d second", "%d second"], + "%d minutes": ["%d minut", "%d minut", "%d minut"], + "%d hours": ["%d godzina", "%d godzina", "%d godzina"], + "%d days": ["%d dzień", "%d dzień", "%d dzień"], + "%d weeks": ["%d tydzień", "%d tydzień", "%d tydzień"], + "%d months": ["%d miesiąc", "%d miesiąc", "%d miesiąc"], + "%d years": ["%d rok", "%d rok", "%d rok"], "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.", - "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", + "This document will expire in %d seconds.": + ["This document will expire in %d second.", "This document will expire in %d seconds."], + "This document will expire in %d minutes.": + ["This document will expire in %d minute.", "This document will expire in %d minutes."], + "This document will expire in %d hours.": + ["This document will expire in %d hour.", "This document will expire in %d hours."], + "This document will expire in %d days.": + ["This document will expire in %d day.", "This document will expire in %d days."], + "This document will expire in %d months.": + ["This document will expire in %d month.", "This document will expire in %d months."], "Please enter the password for this paste:": "Please enter the password for this paste:", "Could not decrypt data (Wrong key?)": diff --git a/js/zerobin.js b/js/zerobin.js index 89e58c1..61ea94f 100644 --- a/js/zerobin.js +++ b/js/zerobin.js @@ -20,41 +20,36 @@ $(function() { */ var helper = { /** - * Converts a duration (in seconds) into human readable format. + * Converts a duration (in seconds) into human friendly approximation. * * @param int seconds - * @return string + * @return array */ secondsToHuman: function(seconds) { if (seconds < 60) { - var v = Math.floor(seconds), - format = '%d second' + ((v > 1) ? 's' : ''); - return i18n._(format, v); + var v = Math.floor(seconds); + return [v, 'second']; } if (seconds < 60 * 60) { - var v = Math.floor(seconds / 60), - format = '%d minute' + ((v > 1) ? 's' : ''); - return i18n._(format, v); + var v = Math.floor(seconds / 60); + return [v, 'minute']; } if (seconds < 60 * 60 * 24) { - var v = Math.floor(seconds / (60 * 60)), - format = '%d hour' + ((v > 1) ? 's' : ''); - return i18n._(format, v); + var v = Math.floor(seconds / (60 * 60)); + return [v, 'hour']; } // If less than 2 months, display in days: if (seconds < 60 * 60 * 24 * 60) { - 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)); + return [v, 'day']; } - var v = Math.floor(seconds / (60 * 60 * 24 * 30)), - format = '%d month' + ((v > 1) ? 's' : ''); - return i18n._(format, v); + var v = Math.floor(seconds / (60 * 60 * 24 * 30)); + return [v, 'month']; }, /** @@ -258,7 +253,29 @@ $(function() { * internationalization methods */ var i18n = { - supportedLanguages: ['de', 'fr', 'pl'], // and the built in 'en' + /** + * supported languages, minus the built in 'en' + */ + supportedLanguages: ['de', 'fr', 'pl'], + + /** + * per language functions to use to determine the plural form + * From: http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html + * + * @param int number + * @return int array key + */ + pluralRules: { + de: function(n) { + return (n != 1 ? 1 : 0); + }, + fr: function(n) { + return (n > 1 ? 1 : 0); + }, + pl: function(n) { + return (n == 1 ? 0 : n%10 >= 2 && n %10 <=4 && (n%100 < 10 || n%100 >= 20) ? 1 : 2); + } + }, /** * translate a string, alias for translate() @@ -281,30 +298,64 @@ $(function() { */ translate: function() { - var args = arguments; + var args = arguments, messageId, usesPlurals; if (typeof arguments[0] == 'object') args = arguments[0]; - var messageId = args[0]; + if (usesPlurals = $.isArray(args[0])) + { + // use the first plural form as messageId, otherwise the singular + messageId = (args[0].length > 1 ? args[0][1] : args[0][0]); + } + else + { + messageId = args[0]; + } if (messageId.length == 0) return messageId; if (!this.translations.hasOwnProperty(messageId)) { console.log('Missing translation for: ' + messageId); - this.translations[messageId] = messageId; + this.translations[messageId] = args[0]; + } + if (usesPlurals && $.isArray(this.translations[messageId])) + { + var n = parseInt(args[1] || 1), + key = this.pluralRules[this.language](n), + maxKey = this.translations[messageId].length - 1; + if (key > maxKey) key = maxKey; + args[0] = this.translations[messageId][key]; + args[1] = n; + } + else + { + args[0] = this.translations[messageId]; } - args[0] = this.translations[messageId]; return helper.sprintf(args); }, + /** + * load translations into cache, then execute callback function + * + * @param function callback + */ 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.language = language; i18n.translations = data; callback(); }); }, + /** + * built in language + */ + language: 'en', + + /** + * translation cache + */ translations: {} } @@ -495,8 +546,13 @@ $(function() { // Display paste expiration. if (comments[0].meta.expire_date) { + var expiration = helper.secondsToHuman(comments[0].meta.remaining_time), + expirationLabel = [ + 'This document will expire in %d ' + expiration[1] + '.', + 'This document will expire in %d ' + expiration[1] + 's.' + ]; this.remainingTime.removeClass('foryoureyesonly') - .text(i18n._('This document will expire in %s.', helper.secondsToHuman(comments[0].meta.remaining_time))) + .text(i18n._(expirationLabel, expiration[0])) .removeClass('hidden'); } if (comments[0].meta.burnafterreading)