From 056a74e3230071191fc0c19cfb8c17d2f8c8ab12 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 9 Oct 2018 10:14:36 +0200 Subject: [PATCH] Fix plural function to be hardcoded No more weird eval to construct a plural function. We just use the plural function from symfony. Signed-off-by: Roeland Jago Douma --- core/js/l10n.js | 218 ++++++++++++++++++++++++-------- core/js/tests/specs/l10nSpec.js | 22 ---- 2 files changed, 164 insertions(+), 76 deletions(-) diff --git a/core/js/l10n.js b/core/js/l10n.js index 77f771a20b..52dd136e8f 100644 --- a/core/js/l10n.js +++ b/core/js/l10n.js @@ -70,65 +70,14 @@ OC.L10N = { if (_.isUndefined(this._bundles[appName])) { this._bundles[appName] = bundle || {}; - if (_.isFunction(pluralForm)) { - this._pluralFunctions[appName] = pluralForm; - } else { - // generate plural function based on form - this._pluralFunctions[appName] = this._generatePluralFunction(pluralForm); - } + // generate plural function based on form + this._pluralFunctions[appName] = this._getPlural; } else { // Theme overwriting the default language _.extend(self._bundles[appName], bundle); } }, - /** - * Generates a plural function based on the given plural form. - * If an invalid form has been given, returns a default function. - * - * @param {String} pluralForm plural form - */ - _generatePluralFunction: function(pluralForm) { - // default func - var func = function (n) { - var p = (n !== 1) ? 1 : 0; - return { 'nplural' : 2, 'plural' : p }; - }; - - if (!pluralForm) { - console.warn('Missing plural form in language file'); - return func; - } - - /** - * code below has been taken from jsgettext - which is LGPL licensed - * https://developer.berlios.de/projects/jsgettext/ - * http://cvs.berlios.de/cgi-bin/viewcvs.cgi/jsgettext/jsgettext/lib/Gettext.js - */ - var pf_re = new RegExp('^(\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;a-zA-Z0-9_\\(\\)])+)', 'm'); - if (pf_re.test(pluralForm)) { - //ex english: "Plural-Forms: nplurals=2; plural=(n != 1);\n" - //pf = "nplurals=2; plural=(n != 1);"; - //ex russian: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 or n%100>=20) ? 1 : 2) - //pf = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)"; - var pf = pluralForm; - if (! /;\s*$/.test(pf)) { - pf = pf.concat(';'); - } - /* We used to use eval, but it seems IE has issues with it. - * We now use "new Function", though it carries a slightly - * bigger performance hit. - var code = 'function (n) { var plural; var nplurals; '+pf+' return { "nplural" : nplurals, "plural" : (plural === true ? 1 : plural ? plural : 0) }; };'; - Gettext._locale_data[domain].head.plural_func = eval("("+code+")"); - */ - var code = 'var plural; var nplurals; '+pf+' return { "nplural" : nplurals, "plural" : (plural === true ? 1 : plural ? plural : 0) };'; - func = new Function("n", code); - } else { - console.warn('Invalid plural form in language file: "' + pluralForm + '"'); - } - return func; - }, - /** * Translate a string * @param {string} app the id of the app for which to translate the string @@ -198,7 +147,7 @@ OC.L10N = { var translation = value; if ($.isArray(translation)) { var plural = this._pluralFunctions[app](count); - return this.translate(app, translation[plural.plural], vars, count, options); + return this.translate(app, translation[plural], vars, count, options); } } @@ -208,6 +157,167 @@ OC.L10N = { else{ return this.translate(app, textPlural, vars, count, options); } + }, + + /** + * The plural function taken from symfony + * + * @param {number} number + * @returns {number} + * @private + */ + _getPlural: function(number) { + locale = OC.getLocale(); + if ('pt_BR' === locale) { + // temporary set a locale for brazilian + locale = 'xbr'; + } + + if (typeof locale === 'undefined') { + return (1 == number) ? 0 : 1; + } + + if (locale.length > 3) { + locale = locale.substring(0, locale.lastIndexOf('_')); + } + + /* + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + switch (locale) { + case 'az': + case 'bo': + case 'dz': + case 'id': + case 'ja': + case 'jv': + case 'ka': + case 'km': + case 'kn': + case 'ko': + case 'ms': + case 'th': + case 'tr': + case 'vi': + case 'zh': + return 0; + + case 'af': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'oc': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return (1 == number) ? 0 : 1; + + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'hy': + case 'ln': + case 'mg': + case 'nso': + case 'xbr': + case 'ti': + case 'wa': + return ((0 == number) || (1 == number)) ? 0 : 1; + + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sh': + case 'sr': + case 'uk': + return ((1 == number % 10) && (11 != number % 100)) ? 0 : (((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 10) || (number % 100 >= 20))) ? 1 : 2); + + case 'cs': + case 'sk': + return (1 == number) ? 0 : (((number >= 2) && (number <= 4)) ? 1 : 2); + + case 'ga': + return (1 == number) ? 0 : ((2 == number) ? 1 : 2); + + case 'lt': + return ((1 == number % 10) && (11 != number % 100)) ? 0 : (((number % 10 >= 2) && ((number % 100 < 10) || (number % 100 >= 20))) ? 1 : 2); + + case 'sl': + return (1 == number % 100) ? 0 : ((2 == number % 100) ? 1 : (((3 == number % 100) || (4 == number % 100)) ? 2 : 3)); + + case 'mk': + return (1 == number % 10) ? 0 : 1; + + case 'mt': + return (1 == number) ? 0 : (((0 == number) || ((number % 100 > 1) && (number % 100 < 11))) ? 1 : (((number % 100 > 10) && (number % 100 < 20)) ? 2 : 3)); + + case 'lv': + return (0 == number) ? 0 : (((1 == number % 10) && (11 != number % 100)) ? 1 : 2); + + case 'pl': + return (1 == number) ? 0 : (((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 12) || (number % 100 > 14))) ? 1 : 2); + + case 'cy': + return (1 == number) ? 0 : ((2 == number) ? 1 : (((8 == number) || (11 == number)) ? 2 : 3)); + + case 'ro': + return (1 == number) ? 0 : (((0 == number) || ((number % 100 > 0) && (number % 100 < 20))) ? 1 : 2); + + case 'ar': + return (0 == number) ? 0 : ((1 == number) ? 1 : ((2 == number) ? 2 : (((number % 100 >= 3) && (number % 100 <= 10)) ? 3 : (((number % 100 >= 11) && (number % 100 <= 99)) ? 4 : 5)))); + + default: + return 0; + } } }; diff --git a/core/js/tests/specs/l10nSpec.js b/core/js/tests/specs/l10nSpec.js index 219c934871..01ec69aaf4 100644 --- a/core/js/tests/specs/l10nSpec.js +++ b/core/js/tests/specs/l10nSpec.js @@ -87,7 +87,6 @@ describe('OC.L10N tests', function() { spyOn(console, 'warn'); OC.L10N.register(TEST_APP, { }); - expect(console.warn).toHaveBeenCalled(); expect( n(TEST_APP, 'download %n file', 'download %n files', 0) ).toEqual('download 0 files'); @@ -107,26 +106,6 @@ describe('OC.L10N tests', function() { '_download %n file_::_download %n files_': ['%n Datei herunterladen', '%n Dateien herunterladen'] }); - expect(console.warn).toHaveBeenCalled(); - checkPlurals(); - }); - it('generates plural with generated function when forms is specified', function() { - OC.L10N.register(TEST_APP, { - '_download %n file_::_download %n files_': - ['%n Datei herunterladen', '%n Dateien herunterladen'] - }, 'nplurals=2; plural=(n != 1);'); - checkPlurals(); - }); - it('generates plural with function when forms is specified as function', function() { - OC.L10N.register(TEST_APP, { - '_download %n file_::_download %n files_': - ['%n Datei herunterladen', '%n Dateien herunterladen'] - }, function(n) { - return { - nplurals: 2, - plural: (n !== 1) ? 1 : 0 - }; - }); checkPlurals(); }); }); @@ -164,7 +143,6 @@ describe('OC.L10N tests', function() { OC.L10N.register(TEST_APP, { 'Hello world!': 'Hallo Welt!' }); - expect(console.warn).toHaveBeenCalled(); OC.L10N.load(TEST_APP, callbackStub).then(promiseStub); expect(callbackStub.calledOnce).toEqual(true); expect(promiseStub.calledOnce).toEqual(true);