mirror of
https://github.com/flarum/framework.git
synced 2024-12-11 21:43:38 +08:00
291 lines
7.3 KiB
JavaScript
291 lines
7.3 KiB
JavaScript
import User from './models/User';
|
|
import username from './helpers/username';
|
|
import extract from './utils/extract';
|
|
|
|
/**
|
|
* Translator with the same API as Symfony's.
|
|
*
|
|
* Derived from https://github.com/willdurand/BazingaJsTranslationBundle
|
|
* which is available under the MIT License.
|
|
* Copyright (c) William Durand <william.durand1@gmail.com>
|
|
*/
|
|
export default class Translator {
|
|
constructor() {
|
|
/**
|
|
* A map of translation keys to their translated values.
|
|
*
|
|
* @type {Object}
|
|
* @public
|
|
*/
|
|
this.translations = {};
|
|
|
|
this.locale = null;
|
|
}
|
|
|
|
addTranslations(translations) {
|
|
Object.assign(this.translations, translations);
|
|
}
|
|
|
|
trans(id, parameters) {
|
|
const translation = this.translations[id];
|
|
|
|
if (translation) {
|
|
return this.apply(translation, parameters || {});
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
transChoice(id, number, parameters) {
|
|
let translation = this.translations[id];
|
|
|
|
if (translation) {
|
|
number = parseInt(number, 10);
|
|
|
|
translation = this.pluralize(translation, number);
|
|
|
|
return this.apply(translation, parameters || {});
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
apply(translation, input) {
|
|
// If we've been given a user model as one of the input parameters, then
|
|
// we'll extract the username and use that for the translation. In the
|
|
// future there should be a hook here to inspect the user and change the
|
|
// translation key. This will allow a gender property to determine which
|
|
// translation key is used.
|
|
if ('user' in input) {
|
|
const user = extract(input, 'user');
|
|
|
|
if (!input.username) input.username = username(user);
|
|
}
|
|
|
|
translation = translation.split(new RegExp('({[a-z0-9_]+}|</?[a-z0-9_]+>)', 'gi'));
|
|
|
|
const hydrated = [];
|
|
const open = [hydrated];
|
|
|
|
translation.forEach((part) => {
|
|
const match = part.match(new RegExp('{([a-z0-9_]+)}|<(/?)([a-z0-9_]+)>', 'i'));
|
|
|
|
if (match) {
|
|
if (match[1]) {
|
|
open[0].push(input[match[1]]);
|
|
} else if (match[3]) {
|
|
if (match[2]) {
|
|
open.shift();
|
|
} else {
|
|
let tag = input[match[3]] || { tag: match[3], children: [] };
|
|
open[0].push(tag);
|
|
open.unshift(tag.children || tag);
|
|
}
|
|
}
|
|
} else {
|
|
open[0].push(part);
|
|
}
|
|
});
|
|
|
|
return hydrated.filter((part) => part);
|
|
}
|
|
|
|
pluralize(translation, number) {
|
|
const sPluralRegex = new RegExp(/^\w+\: +(.+)$/),
|
|
cPluralRegex = new RegExp(/^\s*((\{\s*(\-?\d+[\s*,\s*\-?\d+]*)\s*\})|([\[\]])\s*(-Inf|\-?\d+)\s*,\s*(\+?Inf|\-?\d+)\s*([\[\]]))\s?(.+?)$/),
|
|
iPluralRegex = new RegExp(/^\s*(\{\s*(\-?\d+[\s*,\s*\-?\d+]*)\s*\})|([\[\]])\s*(-Inf|\-?\d+)\s*,\s*(\+?Inf|\-?\d+)\s*([\[\]])/),
|
|
standardRules = [],
|
|
explicitRules = [];
|
|
|
|
translation.split('|').forEach((part) => {
|
|
if (cPluralRegex.test(part)) {
|
|
const matches = part.match(cPluralRegex);
|
|
explicitRules[matches[0]] = matches[matches.length - 1];
|
|
} else if (sPluralRegex.test(part)) {
|
|
const matches = part.match(sPluralRegex);
|
|
standardRules.push(matches[1]);
|
|
} else {
|
|
standardRules.push(part);
|
|
}
|
|
});
|
|
|
|
explicitRules.forEach((rule, e) => {
|
|
if (iPluralRegex.test(e)) {
|
|
const matches = e.match(iPluralRegex);
|
|
|
|
if (matches[1]) {
|
|
const ns = matches[2].split(',');
|
|
|
|
for (let n in ns) {
|
|
if (number == ns[n]) {
|
|
return explicitRules[e];
|
|
}
|
|
}
|
|
} else {
|
|
var leftNumber = this.convertNumber(matches[4]);
|
|
var rightNumber = this.convertNumber(matches[5]);
|
|
|
|
if (
|
|
('[' === matches[3] ? number >= leftNumber : number > leftNumber) &&
|
|
(']' === matches[6] ? number <= rightNumber : number < rightNumber)
|
|
) {
|
|
return explicitRules[e];
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return standardRules[this.pluralPosition(number, this.locale)] || standardRules[0] || undefined;
|
|
}
|
|
|
|
convertNumber(number) {
|
|
if ('-Inf' === number) {
|
|
return Number.NEGATIVE_INFINITY;
|
|
} else if ('+Inf' === number || 'Inf' === number) {
|
|
return Number.POSITIVE_INFINITY;
|
|
}
|
|
|
|
return parseInt(number, 10);
|
|
}
|
|
|
|
pluralPosition(number, locale) {
|
|
if ('pt_BR' === locale) {
|
|
locale = 'xbr';
|
|
}
|
|
|
|
if (locale.length > 3) {
|
|
locale = locale.split('_')[0];
|
|
}
|
|
|
|
switch (locale) {
|
|
case 'bo':
|
|
case 'dz':
|
|
case 'id':
|
|
case 'ja':
|
|
case 'jv':
|
|
case 'ka':
|
|
case 'km':
|
|
case 'kn':
|
|
case 'ko':
|
|
case 'ms':
|
|
case 'th':
|
|
case 'vi':
|
|
case 'zh':
|
|
return 0;
|
|
|
|
case 'af':
|
|
case 'az':
|
|
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 '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 'tr':
|
|
case 'ur':
|
|
case 'zu':
|
|
return number == 1 ? 0 : 1;
|
|
|
|
case 'am':
|
|
case 'bh':
|
|
case 'fil':
|
|
case 'fr':
|
|
case 'gun':
|
|
case 'hi':
|
|
case 'ln':
|
|
case 'mg':
|
|
case 'nso':
|
|
case 'xbr':
|
|
case 'ti':
|
|
case 'wa':
|
|
return number === 0 || number == 1 ? 0 : 1;
|
|
|
|
case 'be':
|
|
case 'bs':
|
|
case 'hr':
|
|
case 'ru':
|
|
case 'sr':
|
|
case 'uk':
|
|
return number % 10 == 1 && number % 100 != 11 ? 0 : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20) ? 1 : 2;
|
|
|
|
case 'cs':
|
|
case 'sk':
|
|
return number == 1 ? 0 : number >= 2 && number <= 4 ? 1 : 2;
|
|
|
|
case 'ga':
|
|
return number == 1 ? 0 : number == 2 ? 1 : 2;
|
|
|
|
case 'lt':
|
|
return number % 10 == 1 && number % 100 != 11 ? 0 : number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20) ? 1 : 2;
|
|
|
|
case 'sl':
|
|
return number % 100 == 1 ? 0 : number % 100 == 2 ? 1 : number % 100 == 3 || number % 100 == 4 ? 2 : 3;
|
|
|
|
case 'mk':
|
|
return number % 10 == 1 ? 0 : 1;
|
|
|
|
case 'mt':
|
|
return number == 1 ? 0 : number === 0 || (number % 100 > 1 && number % 100 < 11) ? 1 : number % 100 > 10 && number % 100 < 20 ? 2 : 3;
|
|
|
|
case 'lv':
|
|
return number === 0 ? 0 : number % 10 == 1 && number % 100 != 11 ? 1 : 2;
|
|
|
|
case 'pl':
|
|
return number == 1 ? 0 : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 12 || number % 100 > 14) ? 1 : 2;
|
|
|
|
case 'cy':
|
|
return number == 1 ? 0 : number == 2 ? 1 : number == 8 || number == 11 ? 2 : 3;
|
|
|
|
case 'ro':
|
|
return number == 1 ? 0 : number === 0 || (number % 100 > 0 && number % 100 < 20) ? 1 : 2;
|
|
|
|
case 'ar':
|
|
return number === 0 ? 0 : number == 1 ? 1 : number == 2 ? 2 : number >= 3 && number <= 10 ? 3 : number >= 11 && number <= 99 ? 4 : 5;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
}
|