| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- // @flow
- 'use strict'
- var formats = require('format-message-formats')
- var lookupClosestLocale = require('lookup-closest-locale')
- var plurals = require('./plurals')
- /*::
- import type {
- AST,
- SubMessages
- } from '../format-message-parse'
- type Locale = string
- type Locales = Locale | Locale[]
- type Placeholder = any[] // https://github.com/facebook/flow/issues/4050
- export type Type = (Placeholder, Locales) => (any, ?Object) => any
- export type Types = { [string]: Type }
- */
- exports = module.exports = function interpret (
- ast/*: AST */,
- locale/*:: ?: Locales */,
- types/*:: ?: Types */
- )/*: (args?: Object) => string */ {
- return interpretAST(ast, null, locale || 'en', types || {}, true)
- }
- exports.toParts = function toParts (
- ast/*: AST */,
- locale/*:: ?: Locales */,
- types/*:: ?: Types */
- )/*: (args?: Object) => any[] */ {
- return interpretAST(ast, null, locale || 'en', types || {}, false)
- }
- function interpretAST (
- elements/*: any[] */,
- parent/*: ?Placeholder */,
- locale/*: Locales */,
- types/*: Types */,
- join/*: boolean */
- )/*: Function */ {
- var parts = elements.map(function (element) {
- return interpretElement(element, parent, locale, types, join)
- })
- if (!join) {
- return function format (args) {
- return parts.reduce(function (parts, part) {
- return parts.concat(part(args))
- }, [])
- }
- }
- if (parts.length === 1) return parts[0]
- return function format (args) {
- var message = ''
- for (var e = 0; e < parts.length; ++e) {
- message += parts[e](args)
- }
- return message
- }
- }
- function interpretElement (
- element/*: Placeholder */,
- parent/*: ?Placeholder */,
- locale/*: Locales */,
- types/*: Types */,
- join/*: boolean */
- )/*: Function */ {
- if (typeof element === 'string') {
- var value/*: string */ = element
- return function format () { return value }
- }
- var id = element[0]
- var type = element[1]
- if (parent && element[0] === '#') {
- id = parent[0]
- var offset = parent[2]
- var formatter = (types.number || defaults.number)([id, 'number'], locale)
- return function format (args) {
- return formatter(getArg(id, args) - offset, args)
- }
- }
- // pre-process children
- var children
- if (type === 'plural' || type === 'selectordinal') {
- children = {}
- Object.keys(element[3]).forEach(function (key) {
- children[key] = interpretAST(element[3][key], element, locale, types, join)
- })
- element = [element[0], element[1], element[2], children]
- } else if (element[2] && typeof element[2] === 'object') {
- children = {}
- Object.keys(element[2]).forEach(function (key) {
- children[key] = interpretAST(element[2][key], element, locale, types, join)
- })
- element = [element[0], element[1], children]
- }
- var getFrmt = type && (types[type] || defaults[type])
- if (getFrmt) {
- var frmt = getFrmt(element, locale)
- return function format (args) {
- return frmt(getArg(id, args), args)
- }
- }
- return join
- ? function format (args) { return String(getArg(id, args)) }
- : function format (args) { return getArg(id, args) }
- }
- function getArg (id/*: string */, args/*: ?Object */)/*: any */ {
- if (args && (id in args)) return args[id]
- var parts = id.split('.')
- var a = args
- for (var i = 0, ii = parts.length; a && i < ii; ++i) {
- a = a[parts[i]]
- }
- return a
- }
- function interpretNumber (element/*: Placeholder */, locales/*: Locales */) {
- var style = element[2]
- var options = formats.number[style] || formats.parseNumberPattern(style) || formats.number.default
- return new Intl.NumberFormat(locales, options).format
- }
- function interpretDuration (element/*: Placeholder */, locales/*: Locales */) {
- var style = element[2]
- var options = formats.duration[style] || formats.duration.default
- var fs = new Intl.NumberFormat(locales, options.seconds).format
- var fm = new Intl.NumberFormat(locales, options.minutes).format
- var fh = new Intl.NumberFormat(locales, options.hours).format
- var sep = /^fi$|^fi-|^da/.test(String(locales)) ? '.' : ':'
- return function (s, args) {
- s = +s
- if (!isFinite(s)) return fs(s)
- var h = ~~(s / 60 / 60) // ~~ acts much like Math.trunc
- var m = ~~(s / 60 % 60)
- var dur = (h ? (fh(Math.abs(h)) + sep) : '') +
- fm(Math.abs(m)) + sep + fs(Math.abs(s % 60))
- return s < 0 ? fh(-1).replace(fh(1), dur) : dur
- }
- }
- function interpretDateTime (element/*: Placeholder */, locales/*: Locales */) {
- var type = element[1]
- var style = element[2]
- var options = formats[type][style] || formats.parseDatePattern(style) || formats[type].default
- return new Intl.DateTimeFormat(locales, options).format
- }
- function interpretPlural (element/*: Placeholder */, locales/*: Locales */) {
- var type = element[1]
- var pluralType = type === 'selectordinal' ? 'ordinal' : 'cardinal'
- var offset = element[2]
- var children = element[3]
- var pluralRules
- if (Intl.PluralRules && Intl.PluralRules.supportedLocalesOf(locales).length > 0) {
- pluralRules = new Intl.PluralRules(locales, { type: pluralType })
- } else {
- var locale = lookupClosestLocale(locales, plurals)
- var select = (locale && plurals[locale][pluralType]) || returnOther
- pluralRules = { select: select }
- }
- return function (value, args) {
- var clause =
- children['=' + +value] ||
- children[pluralRules.select(value - offset)] ||
- children.other
- return clause(args)
- }
- }
- function returnOther (/*:: n:number */) { return 'other' }
- function interpretSelect (element/*: Placeholder */, locales/*: Locales */) {
- var children = element[2]
- return function (value, args) {
- var clause = children[value] || children.other
- return clause(args)
- }
- }
- var defaults/*: Types */ = {
- number: interpretNumber,
- ordinal: interpretNumber, // TODO: support rbnf
- spellout: interpretNumber, // TODO: support rbnf
- duration: interpretDuration,
- date: interpretDateTime,
- time: interpretDateTime,
- plural: interpretPlural,
- selectordinal: interpretPlural,
- select: interpretSelect
- }
- exports.types = defaults
|