discourse/app/assets/javascripts/locales/i18n.js

349 lines
8.7 KiB
JavaScript

// Instantiate the object
var I18n = I18n || {};
// Set default locale to english
I18n.defaultLocale = "en";
// Set default pluralization rule
I18n.pluralizationRules = {
en(n) {
return n === 0 ? ["zero", "none", "other"] : n === 1 ? "one" : "other";
}
};
// Set current locale to null
I18n.locale = null;
I18n.fallbackLocale = null;
// Set the placeholder format. Accepts `{{placeholder}}` and `%{placeholder}`.
I18n.PLACEHOLDER = /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm;
I18n.SEPARATOR = ".";
I18n.noFallbacks = false;
I18n.isValidNode = function(obj, node, undefined) {
return obj[node] !== null && obj[node] !== undefined;
};
I18n.lookup = function(scope, options) {
options = options || {};
var translations = this.prepareOptions(I18n.translations),
locale = options.locale || I18n.currentLocale(),
messages = translations[locale] || {},
currentScope;
options = this.prepareOptions(options);
if (typeof scope === "object") {
scope = scope.join(this.SEPARATOR);
}
if (options.scope) {
scope = options.scope.toString() + this.SEPARATOR + scope;
}
var originalScope = scope;
scope = scope.split(this.SEPARATOR);
if (scope.length > 0 && scope[0] !== "js") {
scope.unshift("js");
}
while (messages && scope.length > 0) {
currentScope = scope.shift();
messages = messages[currentScope];
}
if (messages === undefined && this.extras && this.extras[locale]) {
messages = this.extras[locale];
scope = originalScope.split(this.SEPARATOR);
while (messages && scope.length > 0) {
currentScope = scope.shift();
messages = messages[currentScope];
}
}
if (messages === undefined) {
messages = options.defaultValue;
}
return messages;
};
// Merge several hash options, checking if value is set before
// overwriting any value. The precedence is from left to right.
//
// I18n.prepareOptions({name: "John Doe"}, {name: "Mary Doe", role: "user"});
// #=> {name: "John Doe", role: "user"}
//
I18n.prepareOptions = function() {
var options = {},
opts,
count = arguments.length;
for (var i = 0; i < count; i++) {
opts = arguments[i];
if (!opts) {
continue;
}
for (var key in opts) {
if (!this.isValidNode(options, key)) {
options[key] = opts[key];
}
}
}
return options;
};
I18n.interpolate = function(message, options) {
options = this.prepareOptions(options);
var matches = message.match(this.PLACEHOLDER),
placeholder,
value,
name;
if (!matches) {
return message;
}
for (var i = 0; (placeholder = matches[i]); i++) {
name = placeholder.replace(this.PLACEHOLDER, "$1");
if (typeof options[name] === "string") {
// The dollar sign (`$`) is a special replace pattern, and `$&` inserts
// the matched string. Thus dollars signs need to be escaped with the
// special pattern `$$`, which inserts a single `$`.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_string_as_a_parameter
value = options[name].replace(/\$/g, "$$$$");
} else {
value = options[name];
}
if (!this.isValidNode(options, name)) {
value = "[missing " + placeholder + " value]";
}
var regex = new RegExp(
placeholder.replace(/\{/gm, "\\{").replace(/\}/gm, "\\}")
);
message = message.replace(regex, value);
}
return message;
};
I18n.translate = function(scope, options) {
options = this.prepareOptions(options);
options.needsPluralization = typeof options.count === "number";
options.ignoreMissing = !this.noFallbacks;
var translation = this.findTranslation(scope, options);
if (!this.noFallbacks) {
if (!translation && this.fallbackLocale) {
options.locale = this.fallbackLocale;
translation = this.findTranslation(scope, options);
}
options.ignoreMissing = false;
if (!translation && this.currentLocale() !== this.defaultLocale) {
options.locale = this.defaultLocale;
translation = this.findTranslation(scope, options);
}
if (!translation && this.currentLocale() !== "en") {
options.locale = "en";
translation = this.findTranslation(scope, options);
}
}
try {
return this.interpolate(translation, options);
} catch (error) {
return this.missingTranslation(scope, null, options);
}
};
I18n.findTranslation = function(scope, options) {
var translation = this.lookup(scope, options);
if (translation && options.needsPluralization) {
translation = this.pluralize(translation, scope, options);
}
return translation;
};
I18n.toNumber = function(number, options) {
options = this.prepareOptions(options, this.lookup("number.format"), {
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(this.SEPARATOR),
buffer = [],
formattedNumber;
number = parts[0];
while (number.length > 0) {
buffer.unshift(number.substr(Math.max(0, number.length - 3), 3));
number = number.substr(0, number.length - 3);
}
formattedNumber = buffer.join(options.delimiter);
if (options.precision > 0) {
formattedNumber += options.separator + parts[1];
}
if (negative) {
formattedNumber = "-" + formattedNumber;
}
if (options.strip_insignificant_zeros) {
var regex = {
separator: new RegExp(options.separator.replace(/\./, "\\.") + "$"),
zeros: /0+$/
};
formattedNumber = formattedNumber
.replace(regex.zeros, "")
.replace(regex.separator, "");
}
return formattedNumber;
};
I18n.toHumanSize = function(number, options) {
var kb = 1024,
size = number,
iterations = 0,
unit,
precision;
while (size >= kb && iterations < 4) {
size = size / kb;
iterations += 1;
}
if (iterations === 0) {
unit = this.t("number.human.storage_units.units.byte", { count: size });
precision = 0;
} else {
unit = this.t(
"number.human.storage_units.units." +
[null, "kb", "mb", "gb", "tb"][iterations]
);
precision = size - Math.floor(size) === 0 ? 0 : 1;
}
options = this.prepareOptions(options, {
precision: precision,
format: this.t("number.human.storage_units.format"),
delimiter: ""
});
number = this.toNumber(size, options);
number = options.format.replace("%u", unit).replace("%n", number);
return number;
};
I18n.pluralizer = function(locale) {
var pluralizer = this.pluralizationRules[locale];
if (pluralizer !== undefined) return pluralizer;
return this.pluralizationRules["en"];
};
I18n.findAndTranslateValidNode = function(keys, translation) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (this.isValidNode(translation, key)) return translation[key];
}
return null;
};
I18n.pluralize = function(translation, scope, options) {
if (typeof translation !== "object") return translation;
options = this.prepareOptions(options);
var count = options.count.toString();
var pluralizer = this.pluralizer(options.locale || this.currentLocale());
var key = pluralizer(Math.abs(count));
var keys = typeof key === "object" && key instanceof Array ? key : [key];
var message = this.findAndTranslateValidNode(keys, translation);
if (message !== null || options.ignoreMissing) {
return message;
}
return this.missingTranslation(scope, keys[0]);
};
I18n.missingTranslation = function(scope, key, options) {
var message = "[" + this.currentLocale() + this.SEPARATOR + scope;
if (key) {
message += this.SEPARATOR + key;
}
if (options && options.hasOwnProperty("count")) {
message += " count=" + JSON.stringify(options.count);
}
return message + "]";
};
I18n.currentLocale = function() {
return I18n.locale || I18n.defaultLocale;
};
I18n.enableVerboseLocalization = 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 (value && Object.keys(value).length > 0) {
message += ", parameters: " + JSON.stringify(value);
}
// eslint-disable-next-line no-console
console.info(message);
}
return t.apply(I18n, [scope, value]) + " (#" + current + ")";
};
};
I18n.enableVerboseLocalizationSession = function() {
sessionStorage.setItem("verbose_localization", "true");
I18n.enableVerboseLocalization();
return "Verbose localization is enabled. Close the browser tab to turn it off. Reload the page to see the translation keys.";
};
// shortcuts
I18n.t = I18n.translate;