diff --git a/app/coffeescripts/handlebars_helpers.coffee b/app/coffeescripts/handlebars_helpers.coffee
index 50fd3cc99d4..31886931d05 100644
--- a/app/coffeescripts/handlebars_helpers.coffee
+++ b/app/coffeescripts/handlebars_helpers.coffee
@@ -27,9 +27,8 @@ define [
wrappers[new Array(parseInt(key.replace('w', '')) + 2).join('*')] = value
delete options[key]
options.wrapper = wrappers if wrappers['*']
- options.needsEscaping = true
options = $.extend(options, this) unless this instanceof String or typeof this is 'string'
- I18n.scoped(scope).t(translationKey, defaultValue, options)
+ htmlEscape I18n.scoped(scope).t(translationKey, defaultValue, options)
hiddenIf : (condition) -> " display:none; " if condition
diff --git a/app/coffeescripts/util/dateSelect.coffee b/app/coffeescripts/util/dateSelect.coffee
index 44e52cf7779..512adb6d20f 100644
--- a/app/coffeescripts/util/dateSelect.coffee
+++ b/app/coffeescripts/util/dateSelect.coffee
@@ -39,7 +39,7 @@ define [
year = (new Date()).getFullYear()
position = {year: 1, month: 2, day: 3}
- dateSettings = I18n.lookup('#date')
+ dateSettings = I18n.lookup('date')
if options.type is 'birthdate'
_.defaults options,
diff --git a/app/coffeescripts/util/listWithOthers.coffee b/app/coffeescripts/util/listWithOthers.coffee
index 62ca6eb3afa..68acb5b024d 100644
--- a/app/coffeescripts/util/listWithOthers.coffee
+++ b/app/coffeescripts/util/listWithOthers.coffee
@@ -9,7 +9,7 @@ define [
if strings.length > cutoff
strings = strings[0...cutoff].concat([strings[cutoff...strings.length]])
$.toSentence(for strOrArray in strings
- if typeof strOrArray is 'string' or strOrArray._icHTMLSafe
+ if typeof strOrArray is 'string' or strOrArray instanceof h.SafeString
"#{h(strOrArray)}"
else
"""
diff --git a/client_apps/canvas_quiz_statistics/test/unit/dev/i18n_test.js b/client_apps/canvas_quiz_statistics/test/unit/dev/i18n_test.js
index ae8a5e16467..57a13018857 100644
--- a/client_apps/canvas_quiz_statistics/test/unit/dev/i18n_test.js
+++ b/client_apps/canvas_quiz_statistics/test/unit/dev/i18n_test.js
@@ -1,6 +1,14 @@
define(function(require) {
var I18n = require('i18n!something');
describe('I18n.t', function() {
+ it('should work with just a string default', function() {
+ expect(I18n.t('Foo')).toBe('Foo');
+ });
+
+ it('should work with a default object and an options object', function() {
+ expect(I18n.t({one: "1 person", other: "%{count} people"}, {count: 2})).toBe('2 people');
+ });
+
it('should work with two params', function() {
expect(I18n.t('foo', 'Foo')).toBe('Foo');
});
@@ -38,4 +46,4 @@ define(function(require) {
})).toBe('3 students');
});
});
-});
\ No newline at end of file
+});
diff --git a/client_apps/canvas_quiz_statistics/vendor/js/require/i18n.js b/client_apps/canvas_quiz_statistics/vendor/js/require/i18n.js
index af1f27580d3..cf8e94c5517 100644
--- a/client_apps/canvas_quiz_statistics/vendor/js/require/i18n.js
+++ b/client_apps/canvas_quiz_statistics/vendor/js/require/i18n.js
@@ -1,5 +1,7 @@
define([], function() {
var INTERPOLATER = /\%\{([^\}]+)\}/g;
+ var KEY_PATTERN = /^\#?\w+(\.\w+)+$/; // handle our absolute keys
+ var COUNT_KEY_MAP = ["zero", "one"];
var i18n = {
interpolate: function(contents, options) {
@@ -15,56 +17,51 @@ define([], function() {
return contents;
},
- load : function(name, req, onLoad) {
+ isKeyProvided: function(keyOrDefault, defaultOrOptions, maybeOptions) {
+ if (typeof keyOrDefault === 'object')
+ return false;
+ if (typeof defaultOrOptions === 'string')
+ return true;
+ if (maybeOptions)
+ return true;
+ if (typeof keyOrDefault === 'string' && keyOrDefault.match(this.keyPattern))
+ return true;
+ return false;
+ },
+
+ inferArguments: function(args) {
+ var hasKey = this.isKeyProvided.apply(this, args);
+ if (hasKey) args = args.slice(1);
+ return args;
+ },
+
+ load: function(name, req, onLoad) {
// Development only.
// This gets replaced by Canvas I18n when embedded.
//
+ // Adapted/simplified from i18nliner-js and canvas' i18nObj
+ //
// Returns the defaultValue you provide with variables interpolated,
// if specified.
//
// See the project README for i18n work.
- var t = function(__key__, defaultValue, options) {
- var value;
- if (arguments.length === 2) {
- if (typeof defaultValue === 'string') {
- options = { defaultValue: defaultValue };
- }
- else if (typeof defaultValue === 'object') {
- options = defaultValue;
- }
- else {
- throw new Error("Bad I18n.t() call, expected an options object or a defaultValue string.");
- }
- }
- else if (arguments.length === 3 && !options.defaultValue) {
- options.defaultValue = defaultValue;
+ var t = function() {
+ var args = i18n.inferArguments([].slice.call(arguments));
+ var defaultValue = args[0];
+ var options = args[1] || {};
+ var countKey;
+
+ if (typeof defaultValue !== 'string' && typeof defaultValue !== 'object') {
+ throw new Error("Bad I18n.t() call, expected a default string or object.");
}
if (options.hasOwnProperty('count') && typeof defaultValue === 'object') {
- switch(options.count) {
- case 0:
- if (defaultValue.zero) {
- options.defaultValue = defaultValue.zero;
- }
- break;
-
- case 1:
- if (defaultValue.one) {
- options.defaultValue = defaultValue.one;
- }
- break;
-
- default:
- if (defaultValue.other) {
- options.defaultValue = defaultValue.other;
- }
- }
+ countKey = COUNT_KEY_MAP[options.count];
+ defaultValue = defaultValue[countKey] || defaultValue.other;
}
- value = i18n.interpolate(''+options.defaultValue, options);
-
- return value;
+ return i18n.interpolate(''+defaultValue, options);
};
var l = function(scope, value) {
@@ -79,4 +76,4 @@ define([], function() {
};
return i18n;
-});
\ No newline at end of file
+});
diff --git a/public/javascripts/i18nObj.js b/public/javascripts/i18nObj.js
index 2e4144b447e..a9f4fd6f2cd 100644
--- a/public/javascripts/i18nObj.js
+++ b/public/javascripts/i18nObj.js
@@ -1,23 +1,13 @@
define([
- 'vendor/i18n',
+ 'vendor/i18n_js_extension',
'jquery',
'str/htmlEscape',
- 'str/pluralize',
- 'str/escapeRegex',
'compiled/str/i18nLolcalize',
'vendor/date' /* Date.parse, Date.UTC */
-], function(I18n, $, htmlEscape, pluralize, escapeRegex, i18nLolcalize) {
-
-// Export globally for tinymce/specs
-window.I18n = I18n;
+], function(I18n, $, htmlEscape, i18nLolcalize) {
I18n.locale = document.documentElement.getAttribute('lang');
-// Set the placeholder format. Accepts `%{placeholder}` and %h{placeholder}.
-// %h{placeholder} indicate it is an htmlSafe value, (e.g. an input) and
-// anything not already safe should be html-escaped
-I18n.PLACEHOLDER = /%h?\{(.*?)\}/gm;
-
I18n.isValidNode = function(obj, node) {
// handle names like "foo.bar.baz"
var nameParts = node.split('.');
@@ -61,23 +51,20 @@ I18n.lookup = function(scope, options) {
return messages;
};
-I18n.interpolate = function(message, options) {
- var placeholder, value, name, matches, needsEscaping = false, htmlSafe;
-
+// i18nliner-js overrides interpolate with a wrapper-and-html-safety-aware
+// version, so we need to override the now-renamed original
+I18n.interpolateWithoutHtmlSafety = function(message, options) {
options = this.prepareOptions(options);
- if (options.wrapper) {
- needsEscaping = true;
- message = this.applyWrappers(message, options.wrapper);
- }
- if (options.needsEscaping) {
- needsEscaping = true;
+ var matches = message.match(this.PLACEHOLDER);
+
+ if (!matches) {
+ return message;
}
- matches = message.match(this.PLACEHOLDER) || [];
+ var placeholder, value, name;
for (var i = 0; placeholder = matches[i]; i++) {
name = placeholder.replace(this.PLACEHOLDER, "$1");
- htmlSafe = (placeholder[1] === 'h'); // e.g. %h{input}
// handle names like "foo.bar.baz"
var nameParts = name.split('.');
@@ -89,14 +76,6 @@ I18n.interpolate = function(message, options) {
if (!this.isValidNode(options, name)) {
value = "[missing " + placeholder + " value]";
}
- if (needsEscaping) {
- if (!value._icHTMLSafe && !htmlSafe) {
- value = htmlEscape(value);
- }
- } else if (value._icHTMLSafe || htmlSafe) {
- needsEscaping = true;
- message = htmlEscape(message);
- }
regex = new RegExp(placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}"));
message = message.replace(regex, value);
@@ -105,31 +84,6 @@ I18n.interpolate = function(message, options) {
return message;
};
-I18n.wrapperRegexes = {};
-
-I18n.applyWrappers = function(string, wrappers) {
- var keys = [];
- var key;
-
- string = htmlEscape(string);
- if (typeof(wrappers) == "string") {
- wrappers = {'*': wrappers};
- }
- for (key in wrappers) {
- keys.push(key);
- }
- keys.sort().reverse();
- for (var i=0, l=keys.length; i < l; i++) {
- key = keys[i];
- if (!this.wrapperRegexes[key]) {
- var escapedKey = escapeRegex(key);
- this.wrapperRegexes[key] = new RegExp(escapedKey + "([^" + escapedKey + "]*)" + escapedKey, "g");
- }
- string = string.replace(this.wrapperRegexes[key], wrappers[key]);
- }
- return string;
-};
-
var _localize = I18n.localize;
I18n.localize = function(scope, value) {
var result = _localize.call(this, scope, value);
@@ -261,11 +215,18 @@ I18n.strftime = function(date, format) {
return f;
};
+I18n.Utils.HtmlSafeString = htmlEscape.SafeString; // this is what we use elsewhere in canvas, so make i18nliner use it too
+I18n.CallHelpers.keyPattern = /^\#?\w+(\.\w+)+$/ // handle our absolute keys
+I18n.CallHelpers.normalizeKey = function(key, options) {
+ if (key[0] === '#') {
+ key = key.slice(1);
+ delete options.scope;
+ }
+ return key;
+}
-
-var normalizeDefault = function(str) { return str };
if (window.ENV && window.ENV.lolcalize) {
- normalizeDefault = i18nLolcalize;
+ I18n.CallHelpers.normalizeDefault = i18nLolcalize;
}
I18n.scoped = function(scope, callback) {
@@ -279,36 +240,26 @@ I18n.scope = function(scope) {
this.scope = scope;
};
I18n.scope.prototype = {
- resolveScope: function(key) {
- if (typeof(key) == "object") {
- key = key.join(I18n.defaultSeparator);
- }
- if (key[0] == '#') {
- return key.replace(/^#/, '');
- } else {
- return this.scope + I18n.defaultSeparator + key;
+ HtmlSafeString: I18n.HtmlSafeString,
+
+ translate: function() {
+ var args = [].slice.call(arguments);
+ var options = args[args.length - 1];
+ if (!(options instanceof Object)) {
+ options = {}
+ args.push(options);
}
+ options.scope = this.scope;
+ return I18n.translate.apply(I18n, args);
},
- translate: function(scope, defaultValue, options) {
- options = options || {};
- if (typeof(options.count) != 'undefined' && typeof(defaultValue) == "string" && defaultValue.match(/^[\w\-]+$/)) {
- defaultValue = pluralize.withCount(options.count, defaultValue);
- }
- options.defaultValue = normalizeDefault(defaultValue);
- return I18n.translate(this.resolveScope(scope), options);
- },
- localize: function(scope, value) {
- return I18n.localize(this.resolveScope(scope), value);
- },
- pluralize: function(count, scope, options) {
- return I18n.pluralize(count, this.resolveScope(scope), options);
+ localize: function(key, date) {
+ if (key[0] === '#') key = key.slice(1);
+ return I18n.localize(key, date);
},
beforeLabel: function(text) {
return this.t("#before_label_wrapper", "%{text}:", {'text': text});
},
- lookup: function(scope, options) {
- return I18n.lookup(this.resolveScope(scope), options);
- },
+ lookup: I18n.lookup.bind(I18n),
toTime: I18n.toTime.bind(I18n),
toNumber: I18n.toNumber.bind(I18n),
toCurrency: I18n.toCurrency.bind(I18n),
diff --git a/public/javascripts/jquery.instructure_misc_helpers.js b/public/javascripts/jquery.instructure_misc_helpers.js
index 3730783e935..3e484bddcd2 100644
--- a/public/javascripts/jquery.instructure_misc_helpers.js
+++ b/public/javascripts/jquery.instructure_misc_helpers.js
@@ -66,12 +66,10 @@ define([
};
// useful for i18n, e.g. t('key', 'pick one: %{select}', {select: $.raw('
- Yes, that's true, he would
+ Yes, that's true, he would
HTML
end