FIX: eradicate I18n fallback issues 💣
FIX: client's translation overrides were not working when the current locale was missing a key FIX: ExtraLocalesController.show was not properly handling multiple translations FIX: JsLocaleHelper#output_locale was not properly handling multiple translations FIX: ExtraLocalesController.show's spec which was randomly failing FIX: JsLocaleHelper#output_locale was muting cached translations hashes REFACTOR: move 'enableVerboseLocalization' to the 'localization' initializer REFACTOR: remove unused I18n.js methods (getFallbacks, localize, parseDate, toTime, strftime, toCurrency, toPercentage) REFACTOR: remove all I18n.pluralizationRules and instead use MessageFormat's pluralization rules TEST: add tests for localization initializer TEST: add tests for I18n.js
This commit is contained in:
parent
1060239e2d
commit
a2c04be718
|
@ -4,10 +4,31 @@ export default {
|
|||
name: 'localization',
|
||||
after: 'inject-objects',
|
||||
|
||||
initialize: function(container) {
|
||||
enableVerboseLocalization() {
|
||||
let counter = 0;
|
||||
let keys = {};
|
||||
let t = I18n.t;
|
||||
|
||||
I18n.noFallbacks = true;
|
||||
|
||||
I18n.t = I18n.translate = function(scope, value){
|
||||
let current = keys[scope];
|
||||
if (!current) {
|
||||
current = keys[scope] = ++counter;
|
||||
let message = "Translation #" + current + ": " + scope;
|
||||
if (!_.isEmpty(value)) {
|
||||
message += ", parameters: " + JSON.stringify(value);
|
||||
}
|
||||
Em.Logger.info(message);
|
||||
}
|
||||
return t.apply(I18n, [scope, value]) + " (#" + current + ")";
|
||||
};
|
||||
},
|
||||
|
||||
initialize(container) {
|
||||
const siteSettings = container.lookup('site-settings:main');
|
||||
if (siteSettings.verbose_localization) {
|
||||
I18n.enable_verbose_localization();
|
||||
this.enableVerboseLocalization();
|
||||
}
|
||||
|
||||
// Merge any overrides into our object
|
||||
|
@ -16,24 +37,26 @@ export default {
|
|||
const v = overrides[k];
|
||||
|
||||
// Special case: Message format keys are functions
|
||||
if (/\_MF$/.test(k)) {
|
||||
if (/_MF$/.test(k)) {
|
||||
k = k.replace(/^[a-z_]*js\./, '');
|
||||
I18n._compiledMFs[k] = new Function('transKey', `return (${v})(transKey);`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
k = k.replace('admin_js', 'js');
|
||||
|
||||
const segs = k.split('.');
|
||||
|
||||
let node = I18n.translations[I18n.locale];
|
||||
let i = 0;
|
||||
for (; node && i<segs.length-1; i++) {
|
||||
|
||||
for (; i < segs.length - 1; i++) {
|
||||
if (!(segs[i] in node)) node[segs[i]] = {};
|
||||
node = node[segs[i]];
|
||||
}
|
||||
|
||||
if (node && i === segs.length-1) {
|
||||
node[segs[segs.length-1]] = v;
|
||||
}
|
||||
node[segs[segs.length-1]] = v;
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
//= depend_on 'client.ar.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:ar) %>
|
||||
|
||||
I18n.pluralizationRules['ar'] = function (n) {
|
||||
if (n == 0) return "zero";
|
||||
if (n == 1) return "one";
|
||||
if (n == 2) return "two";
|
||||
if (n%100 >= 3 && n%100 <= 10) return "few";
|
||||
if (n%100 >= 11 && n%100 <= 99) return "many";
|
||||
return "other";
|
||||
};
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
//= depend_on 'client.cs.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:cs) %>
|
||||
|
||||
I18n.pluralizationRules['cs'] = function (n) {
|
||||
if (n == 0) return ["zero", "none", "other"];
|
||||
if (n == 1) return "one";
|
||||
if (n >= 2 && n <= 4) return "few";
|
||||
return "other";
|
||||
};
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
//= depend_on 'client.fa_IR.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:fa_IR) %>
|
||||
|
||||
I18n.pluralizationRules['fa_IR'] = function (n) {
|
||||
return "other";
|
||||
};
|
||||
|
|
|
@ -1,48 +1,17 @@
|
|||
/*global I18n:true */
|
||||
|
||||
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
|
||||
if (!Array.prototype.indexOf) {
|
||||
Array.prototype.indexOf = function (searchElement, fromIndex) {
|
||||
if ( this === undefined || this === null ) {
|
||||
throw new TypeError( '"this" is null or not defined' );
|
||||
}
|
||||
|
||||
var length = this.length >>> 0; // Hack to convert object.length to a UInt32
|
||||
|
||||
fromIndex = +fromIndex || 0;
|
||||
|
||||
if (Math.abs(fromIndex) === Infinity) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
|
||||
if (fromIndex < 0) {
|
||||
fromIndex += length;
|
||||
if (fromIndex < 0) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (;fromIndex < length; fromIndex++) {
|
||||
if (this[fromIndex] === searchElement) {
|
||||
return fromIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
// Instantiate the object
|
||||
var I18n = I18n || {};
|
||||
|
||||
// Set default locale to english
|
||||
I18n.defaultLocale = "en";
|
||||
|
||||
// Set default handling of translation fallbacks to false
|
||||
I18n.fallbacks = false;
|
||||
|
||||
// Set default separator
|
||||
I18n.defaultSeparator = ".";
|
||||
// Set default pluralization rule
|
||||
I18n.pluralizationRules = {
|
||||
en: function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : n === 1 ? "one" : "other";
|
||||
}
|
||||
};
|
||||
|
||||
// Set current locale to null
|
||||
I18n.locale = null;
|
||||
|
@ -50,44 +19,10 @@ I18n.locale = null;
|
|||
// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
|
||||
I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
|
||||
|
||||
I18n.fallbackRules = {};
|
||||
I18n.SEPARATOR = ".";
|
||||
|
||||
I18n.noFallbacks = false;
|
||||
|
||||
I18n.pluralizationRules = {
|
||||
en: function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : n === 1 ? "one" : "other";
|
||||
},
|
||||
"zh_CN": function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : "other";
|
||||
},
|
||||
"zh_TW": function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : "other";
|
||||
},
|
||||
"ko": function(n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : "other";
|
||||
}
|
||||
};
|
||||
|
||||
I18n.getFallbacks = function(locale) {
|
||||
if (locale === I18n.defaultLocale) {
|
||||
return [];
|
||||
} else if (!I18n.fallbackRules[locale]) {
|
||||
var rules = [],
|
||||
components = locale.split("-");
|
||||
|
||||
for (var l = 1; l < components.length; l++) {
|
||||
rules.push(components.slice(0, l).join("-"));
|
||||
}
|
||||
|
||||
rules.push(I18n.defaultLocale);
|
||||
|
||||
I18n.fallbackRules[locale] = rules;
|
||||
}
|
||||
|
||||
return I18n.fallbackRules[locale];
|
||||
};
|
||||
|
||||
I18n.isValidNode = function(obj, node, undefined) {
|
||||
return obj[node] !== null && obj[node] !== undefined;
|
||||
};
|
||||
|
@ -95,25 +30,24 @@ I18n.isValidNode = function(obj, node, undefined) {
|
|||
function checkExtras(origScope, sep, extras) {
|
||||
if (!extras || extras.length === 0) { return; }
|
||||
|
||||
for (var i=0; i<extras.length; i++) {
|
||||
for (var i = 0; i < extras.length; i++) {
|
||||
var messages = extras[i];
|
||||
scope = origScope.split(sep);
|
||||
if (scope[0] === 'js') {
|
||||
scope.shift();
|
||||
}
|
||||
|
||||
if (scope[0] === 'js') { scope.shift(); }
|
||||
|
||||
while (messages && scope.length > 0) {
|
||||
currentScope = scope.shift();
|
||||
messages = messages[currentScope];
|
||||
}
|
||||
if (messages !== undefined) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
if (messages !== undefined) { return messages; }
|
||||
}
|
||||
}
|
||||
|
||||
I18n.lookup = function(scope, options) {
|
||||
options = options || {};
|
||||
|
||||
var lookupInitialScope = scope,
|
||||
translations = this.prepareOptions(I18n.translations),
|
||||
locale = options.locale || I18n.currentLocale(),
|
||||
|
@ -123,42 +57,23 @@ I18n.lookup = function(scope, options) {
|
|||
options = this.prepareOptions(options);
|
||||
|
||||
if (typeof scope === "object") {
|
||||
scope = scope.join(this.defaultSeparator);
|
||||
scope = scope.join(this.SEPARATOR);
|
||||
}
|
||||
|
||||
if (options.scope) {
|
||||
scope = options.scope.toString() + this.defaultSeparator + scope;
|
||||
scope = options.scope.toString() + this.SEPARATOR + scope;
|
||||
}
|
||||
|
||||
var origScope = "" + scope;
|
||||
|
||||
scope = origScope.split(this.defaultSeparator);
|
||||
scope = origScope.split(this.SEPARATOR);
|
||||
|
||||
while (messages && scope.length > 0) {
|
||||
currentScope = scope.shift();
|
||||
messages = messages[currentScope];
|
||||
}
|
||||
|
||||
if (messages === undefined) {
|
||||
messages = checkExtras(origScope, this.defaultSeparator, this.extras);
|
||||
}
|
||||
|
||||
|
||||
if (messages === undefined) {
|
||||
if (I18n.fallbacks) {
|
||||
var fallbacks = this.getFallbacks(locale);
|
||||
for (var fallback = 0; fallback < fallbacks.length; fallbacks++) {
|
||||
messages = I18n.lookup(lookupInitialScope, this.prepareOptions({locale: fallbacks[fallback]}, options));
|
||||
if (messages !== undefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (messages === undefined && this.isValidNode(options, "defaultValue")) {
|
||||
messages = options.defaultValue;
|
||||
}
|
||||
}
|
||||
messages = messages || checkExtras(origScope, this.SEPARATOR, this.extras) || options.defaultValue;
|
||||
|
||||
return messages;
|
||||
};
|
||||
|
@ -193,14 +108,13 @@ I18n.prepareOptions = function() {
|
|||
|
||||
I18n.interpolate = function(message, options) {
|
||||
options = this.prepareOptions(options);
|
||||
|
||||
var matches = message.match(this.PLACEHOLDER),
|
||||
placeholder,
|
||||
value,
|
||||
name;
|
||||
|
||||
if (!matches) {
|
||||
return message;
|
||||
}
|
||||
if (!matches) { return message; }
|
||||
|
||||
for (var i = 0; placeholder = matches[i]; i++) {
|
||||
name = placeholder.replace(this.PLACEHOLDER, "$1");
|
||||
|
@ -219,18 +133,19 @@ I18n.interpolate = function(message, options) {
|
|||
};
|
||||
|
||||
I18n.translate = function(scope, options) {
|
||||
|
||||
options = this.prepareOptions(options);
|
||||
|
||||
var translation = this.lookup(scope, options);
|
||||
// Fallback to the default locale
|
||||
if (!translation && this.currentLocale() !== this.defaultLocale && !this.noFallbacks) {
|
||||
options.locale = this.defaultLocale;
|
||||
translation = this.lookup(scope, options);
|
||||
}
|
||||
if (!translation && this.currentLocale() !== 'en' && !this.noFallbacks) {
|
||||
options.locale = 'en';
|
||||
translation = this.lookup(scope, options);
|
||||
|
||||
if (!this.noFallbacks) {
|
||||
if (!translation && this.currentLocale() !== this.defaultLocale) {
|
||||
options.locale = this.defaultLocale;
|
||||
translation = this.lookup(scope, options);
|
||||
}
|
||||
if (!translation && this.currentLocale() !== 'en') {
|
||||
options.locale = 'en';
|
||||
translation = this.lookup(scope, options);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -248,158 +163,16 @@ I18n.translate = function(scope, options) {
|
|||
}
|
||||
};
|
||||
|
||||
I18n.localize = function(scope, value) {
|
||||
switch (scope) {
|
||||
case "currency":
|
||||
return this.toCurrency(value);
|
||||
case "number":
|
||||
scope = this.lookup("number.format");
|
||||
return this.toNumber(value, scope);
|
||||
case "percentage":
|
||||
return this.toPercentage(value);
|
||||
default:
|
||||
if (scope.match(/^(date|time)/)) {
|
||||
return this.toTime(scope, value);
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
I18n.parseDate = function(date) {
|
||||
var matches, convertedDate;
|
||||
|
||||
// we have a date, so just return it.
|
||||
if (typeof date === "object") {
|
||||
return date;
|
||||
}
|
||||
|
||||
// it matches the following formats:
|
||||
// yyyy-mm-dd
|
||||
// yyyy-mm-dd[ T]hh:mm::ss
|
||||
// yyyy-mm-dd[ T]hh:mm::ss
|
||||
// yyyy-mm-dd[ T]hh:mm::ssZ
|
||||
// yyyy-mm-dd[ T]hh:mm::ss+0000
|
||||
//
|
||||
matches = date.toString().match(/(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2}))?(Z|\+0000)?/);
|
||||
|
||||
if (matches) {
|
||||
for (var i = 1; i <= 6; i++) {
|
||||
matches[i] = parseInt(matches[i], 10) || 0;
|
||||
}
|
||||
|
||||
// month starts on 0
|
||||
matches[2] -= 1;
|
||||
|
||||
if (matches[7]) {
|
||||
convertedDate = new Date(Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]));
|
||||
} else {
|
||||
convertedDate = new Date(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6]);
|
||||
}
|
||||
} else if (typeof date === "number") {
|
||||
// UNIX timestamp
|
||||
convertedDate = new Date();
|
||||
convertedDate.setTime(date);
|
||||
} else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) {
|
||||
// a valid javascript format with timezone info
|
||||
convertedDate = new Date();
|
||||
convertedDate.setTime(Date.parse(date));
|
||||
} else {
|
||||
// an arbitrary javascript string
|
||||
convertedDate = new Date();
|
||||
convertedDate.setTime(Date.parse(date));
|
||||
}
|
||||
|
||||
return convertedDate;
|
||||
};
|
||||
|
||||
I18n.toTime = function(scope, d) {
|
||||
var date = this.parseDate(d),
|
||||
format = this.lookup(scope);
|
||||
|
||||
if (date.toString().match(/invalid/i)) {
|
||||
return date.toString();
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
return date.toString();
|
||||
}
|
||||
|
||||
return this.strftime(date, format);
|
||||
};
|
||||
|
||||
I18n.strftime = function(date, format) {
|
||||
var options = this.lookup("date");
|
||||
|
||||
if (!options) {
|
||||
return date.toString();
|
||||
}
|
||||
|
||||
options.meridian = options.meridian || ["AM", "PM"];
|
||||
|
||||
var weekDay = date.getDay(),
|
||||
day = date.getDate(),
|
||||
year = date.getFullYear(),
|
||||
month = date.getMonth() + 1,
|
||||
hour = date.getHours(),
|
||||
hour12 = hour,
|
||||
meridian = hour > 11 ? 1 : 0,
|
||||
secs = date.getSeconds(),
|
||||
mins = date.getMinutes(),
|
||||
offset = date.getTimezoneOffset(),
|
||||
absOffsetHours = Math.floor(Math.abs(offset / 60)),
|
||||
absOffsetMinutes = Math.abs(offset) - (absOffsetHours * 60),
|
||||
timezoneoffset = (offset > 0 ? "-" : "+") + (absOffsetHours.toString().length < 2 ? "0" + absOffsetHours : absOffsetHours) + (absOffsetMinutes.toString().length < 2 ? "0" + absOffsetMinutes : absOffsetMinutes);
|
||||
|
||||
if (hour12 > 12) {
|
||||
hour12 = hour12 - 12;
|
||||
} else if (hour12 === 0) {
|
||||
hour12 = 12;
|
||||
}
|
||||
|
||||
var padding = function(n) {
|
||||
var s = "0" + n.toString();
|
||||
return s.substr(s.length - 2);
|
||||
};
|
||||
|
||||
var f = format;
|
||||
f = f.replace("%a", options.abbr_day_names[weekDay]);
|
||||
f = f.replace("%A", options.day_names[weekDay]);
|
||||
f = f.replace("%b", options.abbr_month_names[month]);
|
||||
f = f.replace("%B", options.month_names[month]);
|
||||
f = f.replace("%d", padding(day));
|
||||
f = f.replace("%e", day);
|
||||
f = f.replace("%-d", day);
|
||||
f = f.replace("%H", padding(hour));
|
||||
f = f.replace("%-H", hour);
|
||||
f = f.replace("%I", padding(hour12));
|
||||
f = f.replace("%-I", hour12);
|
||||
f = f.replace("%m", padding(month));
|
||||
f = f.replace("%-m", month);
|
||||
f = f.replace("%M", padding(mins));
|
||||
f = f.replace("%-M", mins);
|
||||
f = f.replace("%p", options.meridian[meridian]);
|
||||
f = f.replace("%S", padding(secs));
|
||||
f = f.replace("%-S", secs);
|
||||
f = f.replace("%w", weekDay);
|
||||
f = f.replace("%y", padding(year));
|
||||
f = f.replace("%-y", padding(year).replace(/^0+/, ""));
|
||||
f = f.replace("%Y", year);
|
||||
f = f.replace("%z", timezoneoffset);
|
||||
|
||||
return f;
|
||||
};
|
||||
|
||||
I18n.toNumber = function(number, options) {
|
||||
options = this.prepareOptions(
|
||||
options,
|
||||
this.lookup("number.format"),
|
||||
{precision: 3, separator: ".", delimiter: ",", strip_insignificant_zeros: false}
|
||||
{precision: 3, separator: this.SEPARATOR, delimiter: ",", strip_insignificant_zeros: false}
|
||||
);
|
||||
|
||||
var negative = number < 0,
|
||||
string = Math.abs(number).toFixed(options.precision).toString(),
|
||||
parts = string.split("."),
|
||||
parts = string.split(this.SEPARATOR),
|
||||
precision,
|
||||
buffer = [],
|
||||
formattedNumber;
|
||||
|
@ -437,23 +210,6 @@ I18n.toNumber = function(number, options) {
|
|||
return formattedNumber;
|
||||
};
|
||||
|
||||
I18n.toCurrency = function(number, options) {
|
||||
options = this.prepareOptions(
|
||||
options,
|
||||
this.lookup("number.currency.format"),
|
||||
this.lookup("number.format"),
|
||||
{unit: "$", precision: 2, format: "%u%n", delimiter: ",", separator: "."}
|
||||
);
|
||||
|
||||
number = this.toNumber(number, options);
|
||||
number = options.format
|
||||
.replace("%u", options.unit)
|
||||
.replace("%n", number)
|
||||
;
|
||||
|
||||
return number;
|
||||
};
|
||||
|
||||
I18n.toHumanSize = function(number, options) {
|
||||
var kb = 1024,
|
||||
size = number,
|
||||
|
@ -488,18 +244,6 @@ I18n.toHumanSize = function(number, options) {
|
|||
return number;
|
||||
};
|
||||
|
||||
I18n.toPercentage = function(number, options) {
|
||||
options = this.prepareOptions(
|
||||
options,
|
||||
this.lookup("number.percentage.format"),
|
||||
this.lookup("number.format"),
|
||||
{precision: 3, separator: ".", delimiter: ""}
|
||||
);
|
||||
|
||||
number = this.toNumber(number, options);
|
||||
return number + "%";
|
||||
};
|
||||
|
||||
I18n.pluralizer = function(locale) {
|
||||
var pluralizer = this.pluralizationRules[locale];
|
||||
if (pluralizer !== undefined) return pluralizer;
|
||||
|
@ -534,52 +278,14 @@ I18n.pluralize = function(count, scope, options) {
|
|||
};
|
||||
|
||||
I18n.missingTranslation = function(scope, key) {
|
||||
var message = '[' + this.currentLocale() + "." + scope;
|
||||
if (key) { message += "." + key; }
|
||||
var message = '[' + this.currentLocale() + this.SEPARATOR + scope;
|
||||
if (key) { message += this.SEPARATOR + key; }
|
||||
return message + ']';
|
||||
};
|
||||
|
||||
I18n.currentLocale = function() {
|
||||
return (I18n.locale || I18n.defaultLocale);
|
||||
return I18n.locale || I18n.defaultLocale;
|
||||
};
|
||||
|
||||
// shortcuts
|
||||
I18n.t = I18n.translate;
|
||||
I18n.l = I18n.localize;
|
||||
I18n.p = I18n.pluralize;
|
||||
|
||||
I18n.enable_verbose_localization = function(){
|
||||
var counter = 0;
|
||||
var keys = {};
|
||||
var t = I18n.t;
|
||||
|
||||
I18n.noFallbacks = true;
|
||||
|
||||
I18n.t = I18n.translate = function(scope, value){
|
||||
var current = keys[scope];
|
||||
if(!current) {
|
||||
current = keys[scope] = ++counter;
|
||||
var message = "Translation #" + current + ": " + scope;
|
||||
if (!_.isEmpty(value)) {
|
||||
message += ", parameters: " + JSON.stringify(value);
|
||||
}
|
||||
Em.Logger.info(message);
|
||||
}
|
||||
return t.apply(I18n, [scope, value]) + " (t" + current + ")";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
I18n.verbose_localization_session = function(){
|
||||
sessionStorage.setItem("verbose_localization", "true");
|
||||
I18n.enable_verbose_localization();
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if(sessionStorage && sessionStorage.getItem("verbose_localization")) {
|
||||
I18n.enable_verbose_localization();
|
||||
}
|
||||
} catch(e){
|
||||
// we don't care really, can happen if cookies disabled
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
//= depend_on 'client.ja.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:ja) %>
|
||||
|
||||
I18n.pluralizationRules['ja'] = function (n) {
|
||||
return n === 0 ? ["zero", "none", "other"] : "other";
|
||||
};
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
//= depend_on 'client.ro.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:ro) %>
|
||||
|
||||
I18n.pluralizationRules['ro'] = function (n) {
|
||||
if (n == 1) return "one";
|
||||
if (n === 0 || n % 100 >= 1 && n % 100 <= 19) return "few";
|
||||
return "other";
|
||||
};
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
//= depend_on 'client.ru.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:ru) %>
|
||||
|
||||
I18n.pluralizationRules['ru'] = function (n) {
|
||||
if (n % 10 == 1 && n % 100 != 11) return "one";
|
||||
if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) return "few";
|
||||
return "other";
|
||||
};
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
//= depend_on 'client.sk.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:sk) %>
|
||||
|
||||
I18n.pluralizationRules['sk'] = function (n) {
|
||||
if (n == 1) return "one";
|
||||
if (n >= 2 && n <= 4) return "few";
|
||||
return "other";
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//= depend_on 'client.tr_TR.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:tr_TR) %>
|
||||
|
||||
I18n.pluralizationRules['tr_TR'] = function(n) { return "other"; }
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
//= depend_on 'client.uk.yml'
|
||||
//= require locales/i18n
|
||||
<%= JsLocaleHelper.output_locale(:uk) %>
|
||||
|
||||
I18n.pluralizationRules['uk'] = function (n) {
|
||||
if (n == 0) return ["zero", "none", "other"];
|
||||
if (n % 10 == 1 && n % 100 != 11) return "one";
|
||||
if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) return "few";
|
||||
return "other"; // TODO: should be "many" but is not defined in translations
|
||||
};
|
||||
|
|
|
@ -5,25 +5,30 @@ class ExtraLocalesController < ApplicationController
|
|||
|
||||
def show
|
||||
bundle = params[:bundle]
|
||||
raise Discourse::InvalidAccess.new unless bundle =~ /^[a-z]+$/
|
||||
raise Discourse::InvalidAccess.new unless bundle =~ /^(admin|wizard)$/
|
||||
|
||||
locale_str = I18n.locale.to_s
|
||||
bundle_str = "#{bundle}_js"
|
||||
|
||||
translations = JsLocaleHelper.translations_for(locale_str)
|
||||
for_key = translations[locale_str]["#{bundle}_js"]
|
||||
|
||||
for_key = {}
|
||||
translations.values.each { |v| for_key.deep_merge!(v[bundle_str]) if v.has_key?(bundle_str) }
|
||||
|
||||
js = ""
|
||||
|
||||
if for_key.present?
|
||||
if plugin_for_key = JsLocaleHelper.plugin_translations(locale_str)["#{bundle}_js"]
|
||||
if plugin_for_key = JsLocaleHelper.plugin_translations(locale_str)[bundle_str]
|
||||
for_key.deep_merge!(plugin_for_key)
|
||||
end
|
||||
|
||||
js = <<~JS
|
||||
(function() {
|
||||
if (window.I18n) {
|
||||
window.I18n.extras = window.I18n.extras || [];
|
||||
window.I18n.extras.push(#{for_key.to_json});
|
||||
}
|
||||
})();
|
||||
js = <<~JS.squish
|
||||
(function() {
|
||||
if (window.I18n) {
|
||||
window.I18n.extras = window.I18n.extras || [];
|
||||
window.I18n.extras.push(#{for_key.to_json});
|
||||
}
|
||||
})();
|
||||
JS
|
||||
end
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ module I18n
|
|||
end
|
||||
|
||||
# load it
|
||||
I18n.backend.load_translations(I18n.load_path.grep Regexp.new("\\.#{locale}\\.yml$"))
|
||||
I18n.backend.load_translations(I18n.load_path.grep(/\.#{Regexp.escape locale}\.yml$/))
|
||||
|
||||
@loaded_locales << locale
|
||||
end
|
||||
|
@ -125,7 +125,7 @@ module I18n
|
|||
end
|
||||
|
||||
def client_overrides_json(locale)
|
||||
client_json = (overrides_by_locale(locale) || {}).select {|k, _| k.starts_with?('js.') || k.starts_with?('admin_js.')}
|
||||
client_json = (overrides_by_locale(locale) || {}).select { |k, _| k[/^(admin_js|js)\./] }
|
||||
MultiJson.dump(client_json)
|
||||
end
|
||||
|
||||
|
|
|
@ -7,9 +7,6 @@ module I18n
|
|||
include I18n::Backend::Pluralization
|
||||
|
||||
def available_locales
|
||||
# in case you are wondering this is:
|
||||
# Dir.glob( File.join(Rails.root, 'config', 'locales', 'client.*.yml') )
|
||||
# .map {|x| x.split('.')[-2]}.sort
|
||||
LocaleSiteSetting.supported_locales.map(&:to_sym)
|
||||
end
|
||||
|
||||
|
@ -53,6 +50,7 @@ module I18n
|
|||
end
|
||||
|
||||
protected
|
||||
|
||||
def find_results(regexp, results, translations, path=nil)
|
||||
return results if translations.blank?
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ module JsLocaleHelper
|
|||
translations = {}
|
||||
|
||||
Dir["#{Rails.root}/plugins/*/config/locales/client.#{locale_str}.yml"].each do |file|
|
||||
if plugin_translations = YAML::load(File.open(file))[locale_str]
|
||||
if plugin_translations = YAML.load_file(file)[locale_str]
|
||||
translations.deep_merge!(plugin_translations)
|
||||
end
|
||||
end
|
||||
|
@ -26,7 +26,7 @@ module JsLocaleHelper
|
|||
locale_str = locale.to_s
|
||||
|
||||
# load default translations
|
||||
translations = YAML::load(File.open("#{Rails.root}/config/locales/client.#{locale_str}.yml"))
|
||||
translations = YAML.load_file("#{Rails.root}/config/locales/client.#{locale_str}.yml")
|
||||
|
||||
# merge translations (plugin translations overwrite default translations)
|
||||
translations[locale_str]['js'].deep_merge!(plugin_translations(locale_str)['js']) if translations[locale_str] && plugin_translations(locale_str) && plugin_translations(locale_str)['js']
|
||||
|
@ -35,23 +35,22 @@ module JsLocaleHelper
|
|||
end
|
||||
end
|
||||
|
||||
# purpose-built recursive algorithm ahoy!
|
||||
def self.deep_delete_matches(deleting_from, *checking_hashes)
|
||||
# deeply removes keys from "deleting_from" that are already present in "checking_hashes"
|
||||
def self.deep_delete_matches(deleting_from, checking_hashes)
|
||||
checking_hashes.compact!
|
||||
|
||||
new_hash = deleting_from.dup
|
||||
deleting_from.each do |key, value|
|
||||
if value.is_a? Hash
|
||||
# Recurse
|
||||
new_at_key = deep_delete_matches(deleting_from[key], *(checking_hashes.map {|h| h[key]}))
|
||||
if value.is_a?(Hash)
|
||||
new_at_key = deep_delete_matches(deleting_from[key], checking_hashes.map { |h| h[key] })
|
||||
if new_at_key.empty?
|
||||
new_hash.delete key
|
||||
new_hash.delete(key)
|
||||
else
|
||||
new_hash[key] = new_at_key
|
||||
end
|
||||
else
|
||||
if checking_hashes.any? {|h| h.include? key}
|
||||
new_hash.delete key
|
||||
if checking_hashes.any? { |h| h.include?(key) }
|
||||
new_hash.delete(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -66,8 +65,8 @@ module JsLocaleHelper
|
|||
loaded_locales = []
|
||||
|
||||
locales.map(&:to_s).each do |locale|
|
||||
all_translations[locale] = JsLocaleHelper.load_translations locale
|
||||
merged_translations[locale] = deep_delete_matches(all_translations[locale][locale], *loaded_locales.map { |l| merged_translations[l] })
|
||||
all_translations[locale] = load_translations(locale)
|
||||
merged_translations[locale] = deep_delete_matches(all_translations[locale][locale], loaded_locales.map { |l| merged_translations[l] })
|
||||
loaded_locales << locale
|
||||
end
|
||||
merged_translations
|
||||
|
@ -75,12 +74,11 @@ module JsLocaleHelper
|
|||
end
|
||||
|
||||
def self.translations_for(locale_str)
|
||||
locale_sym = locale_str.to_sym
|
||||
|
||||
current_locale = I18n.locale
|
||||
I18n.locale = locale_sym
|
||||
locale_sym = locale_str.to_sym
|
||||
site_locale = SiteSetting.default_locale.to_sym
|
||||
|
||||
site_locale = SiteSetting.default_locale.to_sym
|
||||
I18n.locale = locale_sym
|
||||
|
||||
translations =
|
||||
if Rails.env.development?
|
||||
|
@ -100,19 +98,23 @@ module JsLocaleHelper
|
|||
|
||||
def self.output_locale(locale)
|
||||
locale_str = locale.to_s
|
||||
translations = translations_for(locale_str).dup
|
||||
translations = Marshal.load(Marshal.dump(translations_for(locale_str)))
|
||||
|
||||
translations[locale_str].keys.each do |k|
|
||||
translations[locale_str].delete(k) unless k == "js"
|
||||
translations.keys.each do |locale|
|
||||
translations[locale].keys.each do |k|
|
||||
translations[locale].delete(k) unless k == "js"
|
||||
end
|
||||
end
|
||||
|
||||
message_formats = strip_out_message_formats!(translations[locale_str]['js'])
|
||||
|
||||
result = generate_message_format(message_formats, locale_str)
|
||||
|
||||
# I18n
|
||||
result << "I18n.translations = #{translations.to_json};\n"
|
||||
result << "I18n.locale = '#{locale_str}';\n"
|
||||
# loading moment here cause we must customize it
|
||||
result << "I18n.pluralizationRules.#{locale_str} = MessageFormat.locale.#{locale_str};\n" if locale_str != "en"
|
||||
|
||||
# moment
|
||||
result << File.read("#{Rails.root}/lib/javascripts/moment.js")
|
||||
result << moment_locale(locale_str)
|
||||
result << moment_formats
|
||||
|
@ -136,33 +138,25 @@ module JsLocaleHelper
|
|||
def self.moment_locale(locale_str)
|
||||
# moment.js uses a different naming scheme for locale files
|
||||
locale_str = locale_str.tr('_', '-').downcase
|
||||
filename = Rails.root + "lib/javascripts/moment_locale/#{locale_str}.js"
|
||||
filename = "#{Rails.root}/lib/javascripts/moment_locale/#{locale_str}.js"
|
||||
|
||||
unless File.exists?(filename)
|
||||
# try the language without the territory
|
||||
locale_str = locale_str.partition('-').first
|
||||
filename = Rails.root + "lib/javascripts/moment_locale/#{locale_str}.js"
|
||||
end
|
||||
# try the language without the territory
|
||||
locale_str = locale_str.split("-")[0]
|
||||
filename = "#{Rails.root}/lib/javascripts/moment_locale/#{locale_str}.js" unless File.exists?(filename)
|
||||
|
||||
if File.exists?(filename)
|
||||
File.read(filename) << "\n"
|
||||
end || ""
|
||||
File.exists?(filename) ? File.read(filename) << "\n" : ""
|
||||
end
|
||||
|
||||
def self.generate_message_format(message_formats, locale_str)
|
||||
formats = message_formats.map { |k,v| k.inspect << " : " << compile_message_format(locale_str, v) }.join(", ")
|
||||
|
||||
filename = "#{Rails.root}/lib/javascripts/locale/#{locale_str}.js"
|
||||
filename = "#{Rails.root}/lib/javascripts/locale/en.js" unless File.exists?(filename)
|
||||
|
||||
result = "MessageFormat = {locale: {}};\n"
|
||||
|
||||
formats = message_formats.map{|k,v| k.inspect << " : " << compile_message_format(locale_str,v)}.join(", ")
|
||||
result << "I18n._compiledMFs = {#{formats}};\n\n"
|
||||
|
||||
filename = Rails.root + "lib/javascripts/locale/#{locale_str}.js"
|
||||
filename = Rails.root + "lib/javascripts/locale/en.js" unless File.exists?(filename)
|
||||
result << File.read(filename) << "\n\n"
|
||||
|
||||
result << File.read("#{Rails.root}/lib/javascripts/messageformat-lookup.js")
|
||||
|
||||
result
|
||||
result << "I18n._compiledMFs = {#{formats}};\n"
|
||||
result << File.read(filename) << "\n"
|
||||
result << File.read("#{Rails.root}/lib/javascripts/messageformat-lookup.js") << "\n"
|
||||
end
|
||||
|
||||
def self.reset_context
|
||||
|
@ -174,7 +168,7 @@ module JsLocaleHelper
|
|||
@mutex.synchronize do
|
||||
yield @ctx ||= begin
|
||||
ctx = MiniRacer::Context.new
|
||||
ctx.load(Rails.root + 'lib/javascripts/messageformat.js')
|
||||
ctx.load("#{Rails.root}/lib/javascripts/messageformat.js")
|
||||
ctx
|
||||
end
|
||||
end
|
||||
|
@ -182,7 +176,7 @@ module JsLocaleHelper
|
|||
|
||||
def self.compile_message_format(locale, format)
|
||||
with_context do |ctx|
|
||||
path = Rails.root + "lib/javascripts/locale/#{locale}.js"
|
||||
path = "#{Rails.root}/lib/javascripts/locale/#{locale}.js"
|
||||
ctx.load(path) if File.exists?(path)
|
||||
ctx.eval("mf = new MessageFormat('#{locale}');")
|
||||
ctx.eval("mf.precompile(mf.parse(#{format.inspect}))")
|
||||
|
|
|
@ -15,6 +15,7 @@ describe JsLocaleHelper do
|
|||
@loaded_merges = nil
|
||||
end
|
||||
end
|
||||
|
||||
JsLocaleHelper.extend StubLoadTranslations
|
||||
|
||||
after do
|
||||
|
@ -22,64 +23,68 @@ describe JsLocaleHelper do
|
|||
JsLocaleHelper.clear_cache!
|
||||
end
|
||||
|
||||
it 'should be able to generate translations' do
|
||||
expect(JsLocaleHelper.output_locale('en').length).to be > 0
|
||||
describe "#output_locale" do
|
||||
|
||||
it "doesn't change the cached translations hash" do
|
||||
I18n.locale = :fr
|
||||
expect(JsLocaleHelper.output_locale('fr').length).to be > 0
|
||||
expect(JsLocaleHelper.translations_for('fr')['fr'].keys).to contain_exactly("js", "admin_js", "wizard_js")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def setup_message_format(format)
|
||||
@ctx = MiniRacer::Context.new
|
||||
@ctx.eval('MessageFormat = {locale: {}};')
|
||||
@ctx.load(Rails.root + 'lib/javascripts/locale/en.js')
|
||||
compiled = JsLocaleHelper.compile_message_format('en', format)
|
||||
@ctx.eval("var test = #{compiled}")
|
||||
end
|
||||
context "message format" do
|
||||
|
||||
def localize(opts)
|
||||
@ctx.eval("test(#{opts.to_json})")
|
||||
end
|
||||
def setup_message_format(format)
|
||||
@ctx = MiniRacer::Context.new
|
||||
@ctx.eval('MessageFormat = {locale: {}};')
|
||||
@ctx.load(Rails.root + 'lib/javascripts/locale/en.js')
|
||||
compiled = JsLocaleHelper.compile_message_format('en', format)
|
||||
@ctx.eval("var test = #{compiled}")
|
||||
end
|
||||
|
||||
it 'handles plurals' do
|
||||
setup_message_format('{NUM_RESULTS, plural,
|
||||
one {1 result}
|
||||
other {# results}
|
||||
}')
|
||||
expect(localize(NUM_RESULTS: 1)).to eq('1 result')
|
||||
expect(localize(NUM_RESULTS: 2)).to eq('2 results')
|
||||
end
|
||||
def localize(opts)
|
||||
@ctx.eval("test(#{opts.to_json})")
|
||||
end
|
||||
|
||||
it 'handles double plurals' do
|
||||
setup_message_format('{NUM_RESULTS, plural,
|
||||
one {1 result}
|
||||
other {# results}
|
||||
} and {NUM_APPLES, plural,
|
||||
one {1 apple}
|
||||
other {# apples}
|
||||
}')
|
||||
it 'handles plurals' do
|
||||
setup_message_format('{NUM_RESULTS, plural,
|
||||
one {1 result}
|
||||
other {# results}
|
||||
}')
|
||||
expect(localize(NUM_RESULTS: 1)).to eq('1 result')
|
||||
expect(localize(NUM_RESULTS: 2)).to eq('2 results')
|
||||
end
|
||||
|
||||
expect(localize(NUM_RESULTS: 1, NUM_APPLES: 2)).to eq('1 result and 2 apples')
|
||||
expect(localize(NUM_RESULTS: 2, NUM_APPLES: 1)).to eq('2 results and 1 apple')
|
||||
end
|
||||
it 'handles double plurals' do
|
||||
setup_message_format('{NUM_RESULTS, plural,
|
||||
one {1 result}
|
||||
other {# results}
|
||||
} and {NUM_APPLES, plural,
|
||||
one {1 apple}
|
||||
other {# apples}
|
||||
}')
|
||||
|
||||
it 'handles select' do
|
||||
setup_message_format('{GENDER, select, male {He} female {She} other {They}} read a book')
|
||||
expect(localize(GENDER: 'male')).to eq('He read a book')
|
||||
expect(localize(GENDER: 'female')).to eq('She read a book')
|
||||
expect(localize(GENDER: 'none')).to eq('They read a book')
|
||||
end
|
||||
expect(localize(NUM_RESULTS: 1, NUM_APPLES: 2)).to eq('1 result and 2 apples')
|
||||
expect(localize(NUM_RESULTS: 2, NUM_APPLES: 1)).to eq('2 results and 1 apple')
|
||||
end
|
||||
|
||||
it 'can strip out message formats' do
|
||||
hash = {"a" => "b", "c" => { "d" => {"f_MF" => "bob"} }}
|
||||
expect(JsLocaleHelper.strip_out_message_formats!(hash)).to eq({"c.d.f_MF" => "bob"})
|
||||
expect(hash["c"]["d"]).to eq({})
|
||||
end
|
||||
it 'handles select' do
|
||||
setup_message_format('{GENDER, select, male {He} female {She} other {They}} read a book')
|
||||
expect(localize(GENDER: 'male')).to eq('He read a book')
|
||||
expect(localize(GENDER: 'female')).to eq('She read a book')
|
||||
expect(localize(GENDER: 'none')).to eq('They read a book')
|
||||
end
|
||||
|
||||
it 'handles message format special keys' do
|
||||
ctx = MiniRacer::Context.new
|
||||
ctx.eval("I18n = {};")
|
||||
it 'can strip out message formats' do
|
||||
hash = {"a" => "b", "c" => { "d" => {"f_MF" => "bob"} }}
|
||||
expect(JsLocaleHelper.strip_out_message_formats!(hash)).to eq({"c.d.f_MF" => "bob"})
|
||||
expect(hash["c"]["d"]).to eq({})
|
||||
end
|
||||
|
||||
JsLocaleHelper.set_translations 'en', {
|
||||
"en" =>
|
||||
{
|
||||
it 'handles message format special keys' do
|
||||
JsLocaleHelper.set_translations('en', {
|
||||
"en" => {
|
||||
"js" => {
|
||||
"hello" => "world",
|
||||
"test_MF" => "{HELLO} {COUNT, plural, one {1 duck} other {# ducks}}",
|
||||
|
@ -87,26 +92,29 @@ describe JsLocaleHelper do
|
|||
"simple_MF" => "{COUNT, plural, one {1} other {#}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
ctx.eval(JsLocaleHelper.output_locale('en'))
|
||||
ctx = MiniRacer::Context.new
|
||||
ctx.eval("I18n = { pluralizationRules: {} };")
|
||||
ctx.eval(JsLocaleHelper.output_locale('en'))
|
||||
|
||||
expect(ctx.eval('I18n.translations["en"]["js"]["hello"]')).to eq("world")
|
||||
expect(ctx.eval('I18n.translations["en"]["js"]["test_MF"]')).to eq(nil)
|
||||
expect(ctx.eval('I18n.translations["en"]["js"]["hello"]')).to eq("world")
|
||||
expect(ctx.eval('I18n.translations["en"]["js"]["test_MF"]')).to eq(nil)
|
||||
|
||||
expect(ctx.eval('I18n.messageFormat("test_MF", { HELLO: "hi", COUNT: 3 })')).to eq("hi 3 ducks")
|
||||
expect(ctx.eval('I18n.messageFormat("error_MF", { HELLO: "hi", COUNT: 3 })')).to match(/Invalid Format/)
|
||||
expect(ctx.eval('I18n.messageFormat("missing", {})')).to match(/missing/)
|
||||
expect(ctx.eval('I18n.messageFormat("simple_MF", {})')).to match(/COUNT/) # error
|
||||
end
|
||||
expect(ctx.eval('I18n.messageFormat("test_MF", { HELLO: "hi", COUNT: 3 })')).to eq("hi 3 ducks")
|
||||
expect(ctx.eval('I18n.messageFormat("error_MF", { HELLO: "hi", COUNT: 3 })')).to match(/Invalid Format/)
|
||||
expect(ctx.eval('I18n.messageFormat("missing", {})')).to match(/missing/)
|
||||
expect(ctx.eval('I18n.messageFormat("simple_MF", {})')).to match(/COUNT/) # error
|
||||
end
|
||||
|
||||
it 'load pluralizations rules before precompile' do
|
||||
message = JsLocaleHelper.compile_message_format('ru', 'format')
|
||||
expect(message).not_to match 'Plural Function not found'
|
||||
it 'load pluralizations rules before precompile' do
|
||||
message = JsLocaleHelper.compile_message_format('ru', 'format')
|
||||
expect(message).not_to match 'Plural Function not found'
|
||||
end
|
||||
end
|
||||
|
||||
it 'performs fallbacks to english if a translation is not available' do
|
||||
JsLocaleHelper.set_translations 'en', {
|
||||
JsLocaleHelper.set_translations('en', {
|
||||
"en" => {
|
||||
"js" => {
|
||||
"only_english" => "1-en",
|
||||
|
@ -115,8 +123,9 @@ describe JsLocaleHelper do
|
|||
"all_three" => "7-en",
|
||||
}
|
||||
}
|
||||
}
|
||||
JsLocaleHelper.set_translations 'ru', {
|
||||
})
|
||||
|
||||
JsLocaleHelper.set_translations('ru', {
|
||||
"ru" => {
|
||||
"js" => {
|
||||
"only_site" => "2-ru",
|
||||
|
@ -125,8 +134,9 @@ describe JsLocaleHelper do
|
|||
"all_three" => "7-ru",
|
||||
}
|
||||
}
|
||||
}
|
||||
JsLocaleHelper.set_translations 'uk', {
|
||||
})
|
||||
|
||||
JsLocaleHelper.set_translations('uk', {
|
||||
"uk" => {
|
||||
"js" => {
|
||||
"only_user" => "4-uk",
|
||||
|
@ -135,7 +145,7 @@ describe JsLocaleHelper do
|
|||
"all_three" => "7-uk",
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expected = {
|
||||
"none" => "[uk.js.none]",
|
||||
|
@ -157,9 +167,9 @@ describe JsLocaleHelper do
|
|||
ctx.eval(JsLocaleHelper.output_locale(I18n.locale))
|
||||
ctx.eval('I18n.defaultLocale = "ru";')
|
||||
|
||||
# Test - unneeded translations are not emitted
|
||||
expect(ctx.eval('I18n.translations.en.js').keys).to eq(["only_english"])
|
||||
expect(ctx.eval('I18n.translations.ru.js').keys).to eq(["only_site", "english_and_site"])
|
||||
expect(ctx.eval('I18n.translations.en.js').keys).to contain_exactly("only_english")
|
||||
expect(ctx.eval('I18n.translations.ru.js').keys).to contain_exactly("only_site", "english_and_site")
|
||||
expect(ctx.eval('I18n.translations.uk.js').keys).to contain_exactly("all_three", "english_and_user", "only_user", "site_and_user")
|
||||
|
||||
expected.each do |key, expect|
|
||||
expect(ctx.eval("I18n.t(#{"js.#{key}".inspect})")).to eq(expect)
|
||||
|
|
|
@ -3,10 +3,6 @@ require 'rails_helper'
|
|||
describe ExtraLocalesController do
|
||||
|
||||
context 'show' do
|
||||
before do
|
||||
I18n.locale = :en
|
||||
I18n.reload!
|
||||
end
|
||||
|
||||
it "needs a valid bundle" do
|
||||
get :show, bundle: 'made-up-bundle'
|
||||
|
@ -19,19 +15,23 @@ describe ExtraLocalesController do
|
|||
expect(response).to_not be_success
|
||||
end
|
||||
|
||||
it "should include plugin translations" do
|
||||
skip "FIXME: Randomly failing"
|
||||
JsLocaleHelper.expects(:plugin_translations).with(I18n.locale.to_s).returns({
|
||||
"admin_js" => {
|
||||
"admin" => {
|
||||
"site_settings" => {
|
||||
"categories" => {
|
||||
"github_badges" => "Github Badges"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).at_least_once
|
||||
it "includes plugin translations" do
|
||||
I18n.locale = :en
|
||||
I18n.reload!
|
||||
|
||||
JsLocaleHelper.expects(:plugin_translations)
|
||||
.with(I18n.locale.to_s)
|
||||
.returns({
|
||||
"admin_js" => {
|
||||
"admin" => {
|
||||
"site_settings" => {
|
||||
"categories" => {
|
||||
"github_badges" => "Github Badges"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).at_least_once
|
||||
|
||||
get :show, bundle: "admin"
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import PreloadStore from 'preload-store';
|
||||
import LocalizationInitializer from 'discourse/initializers/localization';
|
||||
|
||||
module("initializer:localization", {
|
||||
_locale: I18n.locale,
|
||||
_translations: I18n.translations,
|
||||
|
||||
setup() {
|
||||
I18n.locale = "fr";
|
||||
|
||||
I18n.translations = {
|
||||
"fr": {
|
||||
"js": {
|
||||
"composer": {
|
||||
"reply": "Répondre"
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"js": {
|
||||
"topic": {
|
||||
"reply": {
|
||||
"help": "begin composing a reply to this topic"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
teardown() {
|
||||
I18n.locale = this._locale;
|
||||
I18n.translations = this._translations;
|
||||
}
|
||||
});
|
||||
|
||||
test("translation overrides", function() {
|
||||
PreloadStore.store('translationOverrides', {"js.composer.reply":"WAT","js.topic.reply.help":"foobar"});
|
||||
LocalizationInitializer.initialize(this.registry);
|
||||
|
||||
equal(I18n.t("composer.reply"), "WAT", "overrides existing translation in current locale");
|
||||
equal(I18n.t("topic.reply.help"), "foobar", "overrides translation in default locale");
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
module("lib:i18n", {
|
||||
_locale: I18n.locale,
|
||||
_translations: I18n.translations,
|
||||
|
||||
setup() {
|
||||
I18n.locale = "fr";
|
||||
|
||||
I18n.translations = {
|
||||
"fr": {
|
||||
"js": {
|
||||
"hello": "Bonjour",
|
||||
"topic": {
|
||||
"reply": {
|
||||
"title": "Répondre",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"js": {
|
||||
"hello": {
|
||||
"world": "Hello World!"
|
||||
},
|
||||
"topic": {
|
||||
"reply": {
|
||||
"help": "begin composing a reply to this topic"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
teardown() {
|
||||
I18n.locale = this._locale;
|
||||
I18n.translations = this._translations;
|
||||
}
|
||||
});
|
||||
|
||||
test("defaults", function() {
|
||||
equal(I18n.defaultLocale, "en", "it has English as default locale");
|
||||
ok(I18n.pluralizationRules["en"], "it has English pluralizer");
|
||||
});
|
||||
|
||||
test("translations", function() {
|
||||
equal(I18n.t("topic.reply.title"), "Répondre", "uses locale translations when they exist");
|
||||
equal(I18n.t("topic.reply.help"), "begin composing a reply to this topic", "fallbacks to English translations");
|
||||
equal(I18n.t("hello.world"), "Hello World!", "doesn't break if a key is overriden in a locale");
|
||||
});
|
||||
|
||||
test("extra translations", function() {
|
||||
I18n.extras = [{ "admin": { "title": "Discourse Admin" }}];
|
||||
|
||||
equal(I18n.t("admin.title"), "Discourse Admin", "it check extra translations when they exists");
|
||||
});
|
|
@ -41,7 +41,6 @@
|
|||
//
|
||||
//= require jquery.magnific-popup-min.js
|
||||
|
||||
window.TestPreloadStore = require('preload-store').default;
|
||||
window.inTestEnv = true;
|
||||
|
||||
// Stop the message bus so we don't get ajax calls
|
||||
|
|
Loading…
Reference in New Issue