-
-
Notifications
You must be signed in to change notification settings - Fork 834
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
edaf45d
commit b455199
Showing
24 changed files
with
22,614 additions
and
6,813 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,8 @@ | ||
import { RichMessageFormatter, mithrilRichHandler } from '@askvortsov/rich-icu-message-formatter'; | ||
import { pluralTypeHandler, selectTypeHandler } from '@ultraq/icu-message-formatter'; | ||
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 <[email protected]> | ||
*/ | ||
export default class Translator { | ||
constructor() { | ||
/** | ||
|
@@ -18,288 +13,53 @@ export default class Translator { | |
*/ | ||
this.translations = {}; | ||
|
||
this.locale = null; | ||
this.formatter = new RichMessageFormatter(null, this.formatterTypeHandlers(), mithrilRichHandler); | ||
} | ||
|
||
addTranslations(translations) { | ||
Object.assign(this.translations, translations); | ||
formatterTypeHandlers() { | ||
return { | ||
plural: pluralTypeHandler, | ||
select: selectTypeHandler, | ||
}; | ||
} | ||
|
||
trans(id, parameters) { | ||
const translation = this.translations[id]; | ||
|
||
if (translation) { | ||
return this.apply(translation, parameters || {}); | ||
} | ||
|
||
return id; | ||
setLocale(locale) { | ||
this.formatter.locale = locale; | ||
} | ||
|
||
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; | ||
addTranslations(translations) { | ||
Object.assign(this.translations, translations); | ||
} | ||
|
||
apply(translation, input) { | ||
preprocessParameters(parameters) { | ||
// 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 ('user' in parameters) { | ||
const user = extract(parameters, 'user'); | ||
|
||
if (!input.username) input.username = username(user); | ||
if (!parameters.username) parameters.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) { | ||
// Either an opening or closing tag. | ||
if (match[1]) { | ||
open[0].push(input[match[1]]); | ||
} else if (match[3]) { | ||
if (match[2]) { | ||
// Closing tag. We start by removing all raw children (generally in the form of strings) from the temporary | ||
// holding array, then run them through m.fragment to convert them to vnodes. Usually this will just give us a | ||
// text vnode, but using m.fragment as opposed to an explicit conversion should be more flexible. This is necessary because | ||
// otherwise, our generated vnode will have raw strings as its children, and mithril expects vnodes. | ||
// Finally, we add the now-processed vnodes back onto the holding array (which is the same object in memory as the | ||
// children array of the vnode we are currently processing), and remove the reference to the holding array so that | ||
// further text will be added to the full set of returned elements. | ||
const rawChildren = open[0].splice(0, open[0].length); | ||
open[0].push(...m.fragment(rawChildren).children); | ||
open.shift(); | ||
} else { | ||
// If a vnode with a matching tag was provided in the translator input, we use that. Otherwise, we create a new vnode | ||
// with this tag, and an empty children array (since we're expecting to insert children, as that's the point of having this in translator) | ||
let tag = input[match[3]] || { tag: match[3], children: [] }; | ||
open[0].push(tag); | ||
// Insert the tag's children array as the first element of open, so that text in between the opening | ||
// and closing tags will be added to the tag's children, not to the full set of returned elements. | ||
open.unshift(tag.children || tag); | ||
} | ||
} | ||
} else { | ||
// Not an html tag, we add it to open[0], which is either the full set of returned elements (vnodes and text), | ||
// or if an html tag is currently being processed, the children attribute of that html tag's vnode. | ||
open[0].push(part); | ||
} | ||
}); | ||
|
||
return hydrated.filter((part) => part); | ||
return parameters; | ||
} | ||
|
||
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; | ||
} | ||
trans(id, parameters) { | ||
const translation = this.translations[id]; | ||
|
||
convertNumber(number) { | ||
if ('-Inf' === number) { | ||
return Number.NEGATIVE_INFINITY; | ||
} else if ('+Inf' === number || 'Inf' === number) { | ||
return Number.POSITIVE_INFINITY; | ||
if (translation) { | ||
parameters = this.preprocessParameters(parameters || {}); | ||
return this.formatter.rich(translation, parameters); | ||
} | ||
|
||
return parseInt(number, 10); | ||
return id; | ||
} | ||
|
||
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; | ||
} | ||
/** | ||
* @deprecated, remove before stable | ||
*/ | ||
transChoice(id, number, parameters) { | ||
return this.trans(id, parameters); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.