diff options
author | Morris Jobke <hey@morrisjobke.de> | 2019-01-29 10:55:30 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-29 10:55:30 +0100 |
commit | 2682d672d8076302da61fef3cc15e4d647d50be5 (patch) | |
tree | 12c09f80ad9636316f8479081cf5370f4353c700 /core/src | |
parent | 34d5001e2f04d45c4b22583f8838930962f25cf4 (diff) | |
parent | d0cd0918b9bd0813b38b861e21714fda18eaa621 (diff) | |
download | nextcloud-server-2682d672d8076302da61fef3cc15e4d647d50be5.tar.gz nextcloud-server-2682d672d8076302da61fef3cc15e4d647d50be5.zip |
Merge pull request #13878 from nextcloud/refactor/oc-appconfig-eventsource-l10n
Move OC.AppConfig, OC.EventSource and OC.L10N to the server bundle
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/OC/appconfig.js | 71 | ||||
-rw-r--r-- | core/src/OC/eventsource.js | 168 | ||||
-rw-r--r-- | core/src/OC/index.js | 6 | ||||
-rw-r--r-- | core/src/OC/l10n.js | 335 | ||||
-rw-r--r-- | core/src/globals.js | 21 |
5 files changed, 601 insertions, 0 deletions
diff --git a/core/src/OC/appconfig.js b/core/src/OC/appconfig.js new file mode 100644 index 00000000000..67e4dac2d4c --- /dev/null +++ b/core/src/OC/appconfig.js @@ -0,0 +1,71 @@ +/** + * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import OCP from '../OCP/index'; + +/** + * @namespace + * @deprecated 16.0.0 Use OCP.AppConfig instead + */ +const AppConfig = { + /** + * @deprecated Use OCP.AppConfig.getValue() instead + */ + getValue:function(app,key,defaultValue,callback){ + OCP.AppConfig.getValue(app, key, defaultValue, { + success: callback + }); + }, + + /** + * @deprecated Use OCP.AppConfig.setValue() instead + */ + setValue:function(app,key,value){ + OCP.AppConfig.setValue(app, key, value); + }, + + /** + * @deprecated Use OCP.AppConfig.getApps() instead + */ + getApps:function(callback){ + OCP.AppConfig.getApps({ + success: callback + }); + }, + + /** + * @deprecated Use OCP.AppConfig.getKeys() instead + */ + getKeys:function(app,callback){ + OCP.AppConfig.getKeys(app, { + success: callback + }); + }, + + /** + * @deprecated Use OCP.AppConfig.deleteKey() instead + */ + deleteKey:function(app,key){ + OCP.AppConfig.deleteKey(app, key); + } + +}; + +export default AppConfig; diff --git a/core/src/OC/eventsource.js b/core/src/OC/eventsource.js new file mode 100644 index 00000000000..4851aa3816d --- /dev/null +++ b/core/src/OC/eventsource.js @@ -0,0 +1,168 @@ +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2012 Robin Appelman icewind1991@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * Wrapper for server side events + * (http://en.wikipedia.org/wiki/Server-sent_events) + * includes a fallback for older browsers and IE + * + * use server side events with caution, too many open requests can hang the + * server + */ + +/* global EventSource */ + +import $ from 'jquery' + +/** + * Create a new event source + * @param {string} src + * @param {object} [data] to be send as GET + * + * @constructs OCEventSource + */ +const OCEventSource = function (src, data) { + var dataStr = ''; + var name; + var joinChar; + this.typelessListeners = []; + this.closed = false; + this.listeners = {}; + if (data) { + for (name in data) { + dataStr += name + '=' + encodeURIComponent(data[name]) + '&'; + } + } + dataStr += 'requesttoken=' + encodeURIComponent(oc_requesttoken); + if (!this.useFallBack && typeof EventSource !== 'undefined') { + joinChar = '&'; + if (src.indexOf('?') === -1) { + joinChar = '?'; + } + this.source = new EventSource(src + joinChar + dataStr); + this.source.onmessage = function (e) { + for (var i = 0; i < this.typelessListeners.length; i++) { + this.typelessListeners[i](JSON.parse(e.data)); + } + }.bind(this); + } else { + var iframeId = 'oc_eventsource_iframe_' + OCEventSource.iframeCount; + OCEventSource.fallBackSources[OCEventSource.iframeCount] = this; + this.iframe = $('<iframe/>'); + this.iframe.attr('id', iframeId); + this.iframe.hide(); + + joinChar = '&'; + if (src.indexOf('?') === -1) { + joinChar = '?'; + } + this.iframe.attr('src', src + joinChar + 'fallback=true&fallback_id=' + OCEventSource.iframeCount + '&' + dataStr); + $('body').append(this.iframe); + this.useFallBack = true; + OCEventSource.iframeCount++; + } + //add close listener + this.listen('__internal__', function (data) { + if (data === 'close') { + this.close(); + } + }.bind(this)); +}; +OCEventSource.fallBackSources = []; +OCEventSource.iframeCount = 0;//number of fallback iframes +OCEventSource.fallBackCallBack = function (id, type, data) { + OCEventSource.fallBackSources[id].fallBackCallBack(type, data); +}; +OCEventSource.prototype = { + typelessListeners: [], + iframe: null, + listeners: {},//only for fallback + useFallBack: false, + /** + * Fallback callback for browsers that don't have the + * native EventSource object. + * + * Calls the registered listeners. + * + * @private + * @param {String} type event type + * @param {Object} data received data + */ + fallBackCallBack: function (type, data) { + var i; + // ignore messages that might appear after closing + if (this.closed) { + return; + } + if (type) { + if (typeof this.listeners.done !== 'undefined') { + for (i = 0; i < this.listeners[type].length; i++) { + this.listeners[type][i](data); + } + } + } else { + for (i = 0; i < this.typelessListeners.length; i++) { + this.typelessListeners[i](data); + } + } + }, + lastLength: 0,//for fallback + /** + * Listen to a given type of events. + * + * @param {String} type event type + * @param {Function} callback event callback + */ + listen: function (type, callback) { + if (callback && callback.call) { + + if (type) { + if (this.useFallBack) { + if (!this.listeners[type]) { + this.listeners[type] = []; + } + this.listeners[type].push(callback); + } else { + this.source.addEventListener(type, function (e) { + if (typeof e.data !== 'undefined') { + callback(JSON.parse(e.data)); + } else { + callback(''); + } + }, false); + } + } else { + this.typelessListeners.push(callback); + } + } + }, + /** + * Closes this event source. + */ + close: function () { + this.closed = true; + if (typeof this.source !== 'undefined') { + this.source.close(); + } + } +}; + +export default OCEventSource; diff --git a/core/src/OC/index.js b/core/src/OC/index.js index f7f5018253c..2ef1055c8b1 100644 --- a/core/src/OC/index.js +++ b/core/src/OC/index.js @@ -22,7 +22,10 @@ import Backbone from 'backbone'; import Apps from './apps' +import AppConfig from './appconfig' import ContactsMenu from './contactsmenu'; +import EventSource from './eventsource' +import L10N from './l10n' import {davCall, davSync} from './backbone-webdav'; // Patch Backbone for DAV @@ -34,6 +37,9 @@ Object.assign(Backbone, { /** @namespace OC */ export default { Apps, + AppConfig, Backbone, ContactsMenu, + EventSource, + L10N, }; diff --git a/core/src/OC/l10n.js b/core/src/OC/l10n.js new file mode 100644 index 00000000000..ac5a7d93875 --- /dev/null +++ b/core/src/OC/l10n.js @@ -0,0 +1,335 @@ +/** + * Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> + * + * This file is licensed under the Affero General Public License version 3 + * or later. + * + * See the COPYING-README file. + * + */ + +import _ from 'underscore' +import $ from 'jquery' +import Handlebars from 'handlebars' + +import OC from './index' + +/** + * L10N namespace with localization functions. + * + * @namespace OC.L10n + */ +const L10n = { + /** + * String bundles with app name as key. + * @type {Object.<String,String>} + */ + _bundles: {}, + + /** + * Plural functions, key is app name and value is function. + * @type {Object.<String,Function>} + */ + _pluralFunctions: {}, + + /** + * Load an app's translation bundle if not loaded already. + * + * @param {String} appName name of the app + * @param {Function} callback callback to be called when + * the translations are loaded + * @return {Promise} promise + */ + load: function(appName, callback) { + // already available ? + if (this._bundles[appName] || OC.getLocale() === 'en') { + var deferred = $.Deferred(); + var promise = deferred.promise(); + promise.then(callback); + deferred.resolve(); + return promise; + } + + var self = this; + var url = OC.filePath(appName, 'l10n', OC.getLocale() + '.json'); + + // load JSON translation bundle per AJAX + return $.get(url) + .then( + function(result) { + if (result.translations) { + self.register(appName, result.translations, result.pluralForm); + } + }) + .then(callback); + }, + + /** + * Register an app's translation bundle. + * + * @param {String} appName name of the app + * @param {Object<String,String>} bundle + * @param {Function|String} [pluralForm] optional plural function or plural string + */ + register: function(appName, bundle, pluralForm) { + var self = this; + if (_.isUndefined(this._bundles[appName])) { + this._bundles[appName] = bundle || {}; + + // generate plural function based on form + this._pluralFunctions[appName] = this._getPlural; + } else { + // Theme overwriting the default language + _.extend(self._bundles[appName], bundle); + } + }, + + /** + * Translate a string + * @param {string} app the id of the app for which to translate the string + * @param {string} text the string to translate + * @param [vars] map of placeholder key to value + * @param {number} [count] number to replace %n with + * @param {array} [options] options array + * @param {bool} [options.escape=true] enable/disable auto escape of placeholders (by default enabled) + * @return {string} + */ + translate: function(app, text, vars, count, options) { + var defaultOptions = { + escape: true + }, + allOptions = options || {}; + _.defaults(allOptions, defaultOptions); + + // TODO: cache this function to avoid inline recreation + // of the same function over and over again in case + // translate() is used in a loop + var _build = function (text, vars, count) { + return text.replace(/%n/g, count).replace(/{([^{}]*)}/g, + function (a, b) { + var r = vars[b]; + if(typeof r === 'string' || typeof r === 'number') { + if(allOptions.escape) { + return DOMPurify.sanitize(escapeHTML(r)); + } else { + return DOMPurify.sanitize(r); + } + } else { + return DOMPurify.sanitize(a); + } + } + ); + }; + var translation = text; + var bundle = this._bundles[app] || {}; + var value = bundle[text]; + if( typeof(value) !== 'undefined' ){ + translation = value; + } + + if(typeof vars === 'object' || count !== undefined ) { + return DOMPurify.sanitize(_build(translation, vars, count)); + } else { + return DOMPurify.sanitize(translation); + } + }, + + /** + * Translate a plural string + * @param {string} app the id of the app for which to translate the string + * @param {string} textSingular the string to translate for exactly one object + * @param {string} textPlural the string to translate for n objects + * @param {number} count number to determine whether to use singular or plural + * @param [vars] map of placeholder key to value + * @param {array} [options] options array + * @param {bool} [options.escape=true] enable/disable auto escape of placeholders (by default enabled) + * @return {string} Translated string + */ + translatePlural: function(app, textSingular, textPlural, count, vars, options) { + var identifier = '_' + textSingular + '_::_' + textPlural + '_'; + var bundle = this._bundles[app] || {}; + var value = bundle[identifier]; + if( typeof(value) !== 'undefined' ){ + var translation = value; + if ($.isArray(translation)) { + var plural = this._pluralFunctions[app](count); + return this.translate(app, translation[plural], vars, count, options); + } + } + + if(count === 1) { + return this.translate(app, textSingular, vars, count, options); + } + else{ + return this.translate(app, textPlural, vars, count, options); + } + }, + + /** + * The plural function taken from symfony + * + * @param {number} number + * @returns {number} + * @private + */ + _getPlural: function(number) { + var 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; + } + } +}; + +export default L10n; + +Handlebars.registerHelper('t', function(app, text) { + return L10n.translate(app, text); +}); + diff --git a/core/src/globals.js b/core/src/globals.js index a9884f335e0..5d077f22486 100644 --- a/core/src/globals.js +++ b/core/src/globals.js @@ -72,3 +72,24 @@ window['moment'] = moment window['OC'] = OC window['OCP'] = OCP window['OCA'] = OCA + +/** + * translate a string + * @param {string} app the id of the app for which to translate the string + * @param {string} text the string to translate + * @param [vars] map of placeholder key to value + * @param {number} [count] number to replace %n with + * @return {string} + */ +window.t = _.bind(OC.L10N.translate, OC.L10N); + +/** + * translate a string + * @param {string} app the id of the app for which to translate the string + * @param {string} text_singular the string to translate for exactly one object + * @param {string} text_plural the string to translate for n objects + * @param {number} count number to determine whether to use singular or plural + * @param [vars] map of placeholder key to value + * @return {string} Translated string + */ +window.n = _.bind(OC.L10N.translatePlural, OC.L10N); |