Autoescape of placeholders in t() and p() - for JS
* add disableEscape parameter to disable this functionality * drop usage of escapeHTML() that is now done inside t() * add unit test for escaped and not escaped placeholder * proper JSDoc
This commit is contained in:
parent
622c4cf779
commit
bfdf0db7c0
|
@ -180,7 +180,7 @@
|
||||||
fileSize = '<td class="filesize">' + OC.Util.humanFileSize(summary.totalSize) + '</td>';
|
fileSize = '<td class="filesize">' + OC.Util.humanFileSize(summary.totalSize) + '</td>';
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = t('files', '{dirs} and {files}', infoVars);
|
var info = t('files', '{dirs} and {files}', infoVars, null, {'escape': false});
|
||||||
|
|
||||||
var $summary = $('<td><span class="info">'+info+'</span></td>'+fileSize+'<td class="date"></td>');
|
var $summary = $('<td><span class="info">'+info+'</span></td>'+fileSize+'<td class="date"></td>');
|
||||||
|
|
||||||
|
|
|
@ -62,8 +62,8 @@ OC.L10N = {
|
||||||
* Register an app's translation bundle.
|
* Register an app's translation bundle.
|
||||||
*
|
*
|
||||||
* @param {String} appName name of the app
|
* @param {String} appName name of the app
|
||||||
* @param {Object<String,String>} strings bundle
|
* @param {Object<String,String>} bundle
|
||||||
* @param {{Function|String}} [pluralForm] optional plural function or plural string
|
* @param {Function|String} [pluralForm] optional plural function or plural string
|
||||||
*/
|
*/
|
||||||
register: function(appName, bundle, pluralForm) {
|
register: function(appName, bundle, pluralForm) {
|
||||||
this._bundles[appName] = bundle || {};
|
this._bundles[appName] = bundle || {};
|
||||||
|
@ -129,9 +129,17 @@ OC.L10N = {
|
||||||
* @param {string} text the string to translate
|
* @param {string} text the string to translate
|
||||||
* @param [vars] map of placeholder key to value
|
* @param [vars] map of placeholder key to value
|
||||||
* @param {number} [count] number to replace %n with
|
* @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}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
translate: function(app, text, vars, count) {
|
translate: function(app, text, vars, count, options) {
|
||||||
|
var defaultOptions = {
|
||||||
|
escape: true
|
||||||
|
},
|
||||||
|
allOptions = options || {};
|
||||||
|
_.defaults(allOptions, defaultOptions);
|
||||||
|
|
||||||
// TODO: cache this function to avoid inline recreation
|
// TODO: cache this function to avoid inline recreation
|
||||||
// of the same function over and over again in case
|
// of the same function over and over again in case
|
||||||
// translate() is used in a loop
|
// translate() is used in a loop
|
||||||
|
@ -139,7 +147,15 @@ OC.L10N = {
|
||||||
return text.replace(/%n/g, count).replace(/{([^{}]*)}/g,
|
return text.replace(/%n/g, count).replace(/{([^{}]*)}/g,
|
||||||
function (a, b) {
|
function (a, b) {
|
||||||
var r = vars[b];
|
var r = vars[b];
|
||||||
return typeof r === 'string' || typeof r === 'number' ? r : a;
|
if(typeof r === 'string' || typeof r === 'number') {
|
||||||
|
if(allOptions.escape) {
|
||||||
|
return escapeHTML(r);
|
||||||
|
} else {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -160,13 +176,15 @@ OC.L10N = {
|
||||||
/**
|
/**
|
||||||
* Translate a plural string
|
* Translate a plural string
|
||||||
* @param {string} app the id of the app for which to translate the 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} textSingular the string to translate for exactly one object
|
||||||
* @param {string} text_plural the string to translate for n objects
|
* @param {string} textPlural the string to translate for n objects
|
||||||
* @param {number} count number to determine whether to use singular or plural
|
* @param {number} count number to determine whether to use singular or plural
|
||||||
* @param [vars] map of placeholder key to value
|
* @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
|
* @return {string} Translated string
|
||||||
*/
|
*/
|
||||||
translatePlural: function(app, textSingular, textPlural, count, vars) {
|
translatePlural: function(app, textSingular, textPlural, count, vars, options) {
|
||||||
var identifier = '_' + textSingular + '_::_' + textPlural + '_';
|
var identifier = '_' + textSingular + '_::_' + textPlural + '_';
|
||||||
var bundle = this._bundles[app] || {};
|
var bundle = this._bundles[app] || {};
|
||||||
var value = bundle[identifier];
|
var value = bundle[identifier];
|
||||||
|
@ -174,15 +192,15 @@ OC.L10N = {
|
||||||
var translation = value;
|
var translation = value;
|
||||||
if ($.isArray(translation)) {
|
if ($.isArray(translation)) {
|
||||||
var plural = this._pluralFunctions[app](count);
|
var plural = this._pluralFunctions[app](count);
|
||||||
return this.translate(app, translation[plural.plural], vars, count);
|
return this.translate(app, translation[plural.plural], vars, count, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(count === 1) {
|
if(count === 1) {
|
||||||
return this.translate(app, textSingular, vars, count);
|
return this.translate(app, textSingular, vars, count, options);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return this.translate(app, textPlural, vars, count);
|
return this.translate(app, textPlural, vars, count, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -255,7 +255,7 @@ OC.Share={
|
||||||
message = this._formatSharedByOwner(owner);
|
message = this._formatSharedByOwner(owner);
|
||||||
}
|
}
|
||||||
else if (recipients) {
|
else if (recipients) {
|
||||||
message = t('core', 'Shared with {recipients}', {recipients: escapeHTML(recipients)});
|
message = t('core', 'Shared with {recipients}', {recipients: recipients});
|
||||||
}
|
}
|
||||||
action.html(' <span>' + message + '</span>').prepend(img);
|
action.html(' <span>' + message + '</span>').prepend(img);
|
||||||
if (owner) {
|
if (owner) {
|
||||||
|
@ -355,9 +355,9 @@ OC.Share={
|
||||||
var html = '<div id="dropdown" class="drop shareDropDown" data-item-type="'+itemType+'" data-item-source="'+itemSource+'">';
|
var html = '<div id="dropdown" class="drop shareDropDown" data-item-type="'+itemType+'" data-item-source="'+itemSource+'">';
|
||||||
if (data !== false && data.reshare !== false && data.reshare.uid_owner !== undefined) {
|
if (data !== false && data.reshare !== false && data.reshare.uid_owner !== undefined) {
|
||||||
if (data.reshare.share_type == OC.Share.SHARE_TYPE_GROUP) {
|
if (data.reshare.share_type == OC.Share.SHARE_TYPE_GROUP) {
|
||||||
html += '<span class="reshare">'+t('core', 'Shared with you and the group {group} by {owner}', {group: escapeHTML(data.reshare.share_with), owner: escapeHTML(data.reshare.displayname_owner)})+'</span>';
|
html += '<span class="reshare">'+t('core', 'Shared with you and the group {group} by {owner}', {group: data.reshare.share_with, owner: data.reshare.displayname_owner})+'</span>';
|
||||||
} else {
|
} else {
|
||||||
html += '<span class="reshare">'+t('core', 'Shared with you by {owner}', {owner: escapeHTML(data.reshare.displayname_owner)})+'</span>';
|
html += '<span class="reshare">'+t('core', 'Shared with you by {owner}', {owner: data.reshare.displayname_owner})+'</span>';
|
||||||
}
|
}
|
||||||
html += '<br />';
|
html += '<br />';
|
||||||
}
|
}
|
||||||
|
@ -395,7 +395,7 @@ OC.Share={
|
||||||
|
|
||||||
var defaultExpireMessage = '';
|
var defaultExpireMessage = '';
|
||||||
if ((itemType === 'folder' || itemType === 'file') && oc_appconfig.core.defaultExpireDateEnforced) {
|
if ((itemType === 'folder' || itemType === 'file') && oc_appconfig.core.defaultExpireDateEnforced) {
|
||||||
defaultExpireMessage = t('core', 'The public link will expire no later than {days} days after it is created', {'days': escapeHTML(oc_appconfig.core.defaultExpireDate)}) + '<br/>';
|
defaultExpireMessage = t('core', 'The public link will expire no later than {days} days after it is created', {'days': oc_appconfig.core.defaultExpireDate}) + '<br/>';
|
||||||
}
|
}
|
||||||
|
|
||||||
html += '<label for="linkText" class="hidden-visually">'+t('core', 'Link')+'</label>';
|
html += '<label for="linkText" class="hidden-visually">'+t('core', 'Link')+'</label>';
|
||||||
|
@ -622,7 +622,7 @@ OC.Share={
|
||||||
if (collectionList.length > 0) {
|
if (collectionList.length > 0) {
|
||||||
$(collectionList).append(', '+shareWithDisplayName);
|
$(collectionList).append(', '+shareWithDisplayName);
|
||||||
} else {
|
} else {
|
||||||
var html = '<li style="clear: both;" data-collection="'+item+'">'+t('core', 'Shared in {item} with {user}', {'item': escapeHTML(item), user: escapeHTML(shareWithDisplayName)})+'</li>';
|
var html = '<li style="clear: both;" data-collection="'+item+'">'+t('core', 'Shared in {item} with {user}', {'item': item, user: shareWithDisplayName})+'</li>';
|
||||||
$('#shareWithList').prepend(html);
|
$('#shareWithList').prepend(html);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -42,6 +42,16 @@ describe('OC.L10N tests', function() {
|
||||||
t(TEST_APP, 'Hello {name}, the weather is {weather}', {name: 'Steve', weather: t(TEST_APP, 'sunny')})
|
t(TEST_APP, 'Hello {name}, the weather is {weather}', {name: 'Steve', weather: t(TEST_APP, 'sunny')})
|
||||||
).toEqual('Hallo Steve, das Wetter ist sonnig');
|
).toEqual('Hallo Steve, das Wetter ist sonnig');
|
||||||
});
|
});
|
||||||
|
it('returns text with escaped placeholder', function() {
|
||||||
|
expect(
|
||||||
|
t(TEST_APP, 'Hello {name}', {name: '<strong>Steve</strong>'})
|
||||||
|
).toEqual('Hello <strong>Steve</strong>');
|
||||||
|
});
|
||||||
|
it('returns text with not escaped placeholder', function() {
|
||||||
|
expect(
|
||||||
|
t(TEST_APP, 'Hello {name}', {name: '<strong>Steve</strong>'}, null, {escape: false})
|
||||||
|
).toEqual('Hello <strong>Steve</strong>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('plurals', function() {
|
describe('plurals', function() {
|
||||||
function checkPlurals() {
|
function checkPlurals() {
|
||||||
|
|
|
@ -201,7 +201,7 @@ DeleteHandler.prototype.deleteEntry = function(keepNotification) {
|
||||||
dh.removeCallback(dh.oidToDelete);
|
dh.removeCallback(dh.oidToDelete);
|
||||||
dh.canceled = true;
|
dh.canceled = true;
|
||||||
} else {
|
} else {
|
||||||
OC.dialogs.alert(result.data.message, t('settings', 'Unable to delete {objName}', {objName: escapeHTML(dh.oidToDelete)}));
|
OC.dialogs.alert(result.data.message, t('settings', 'Unable to delete {objName}', {objName: dh.oidToDelete}));
|
||||||
dh.undoCallback(dh.oidToDelete);
|
dh.undoCallback(dh.oidToDelete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue