index.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // @flow
  2. 'use strict'
  3. var formats = require('format-message-formats')
  4. var lookupClosestLocale = require('lookup-closest-locale')
  5. var plurals = require('./plurals')
  6. /*::
  7. import type {
  8. AST,
  9. SubMessages
  10. } from '../format-message-parse'
  11. type Locale = string
  12. type Locales = Locale | Locale[]
  13. type Placeholder = any[] // https://github.com/facebook/flow/issues/4050
  14. export type Type = (Placeholder, Locales) => (any, ?Object) => any
  15. export type Types = { [string]: Type }
  16. */
  17. exports = module.exports = function interpret (
  18. ast/*: AST */,
  19. locale/*:: ?: Locales */,
  20. types/*:: ?: Types */
  21. )/*: (args?: Object) => string */ {
  22. return interpretAST(ast, null, locale || 'en', types || {}, true)
  23. }
  24. exports.toParts = function toParts (
  25. ast/*: AST */,
  26. locale/*:: ?: Locales */,
  27. types/*:: ?: Types */
  28. )/*: (args?: Object) => any[] */ {
  29. return interpretAST(ast, null, locale || 'en', types || {}, false)
  30. }
  31. function interpretAST (
  32. elements/*: any[] */,
  33. parent/*: ?Placeholder */,
  34. locale/*: Locales */,
  35. types/*: Types */,
  36. join/*: boolean */
  37. )/*: Function */ {
  38. var parts = elements.map(function (element) {
  39. return interpretElement(element, parent, locale, types, join)
  40. })
  41. if (!join) {
  42. return function format (args) {
  43. return parts.reduce(function (parts, part) {
  44. return parts.concat(part(args))
  45. }, [])
  46. }
  47. }
  48. if (parts.length === 1) return parts[0]
  49. return function format (args) {
  50. var message = ''
  51. for (var e = 0; e < parts.length; ++e) {
  52. message += parts[e](args)
  53. }
  54. return message
  55. }
  56. }
  57. function interpretElement (
  58. element/*: Placeholder */,
  59. parent/*: ?Placeholder */,
  60. locale/*: Locales */,
  61. types/*: Types */,
  62. join/*: boolean */
  63. )/*: Function */ {
  64. if (typeof element === 'string') {
  65. var value/*: string */ = element
  66. return function format () { return value }
  67. }
  68. var id = element[0]
  69. var type = element[1]
  70. if (parent && element[0] === '#') {
  71. id = parent[0]
  72. var offset = parent[2]
  73. var formatter = (types.number || defaults.number)([id, 'number'], locale)
  74. return function format (args) {
  75. return formatter(getArg(id, args) - offset, args)
  76. }
  77. }
  78. // pre-process children
  79. var children
  80. if (type === 'plural' || type === 'selectordinal') {
  81. children = {}
  82. Object.keys(element[3]).forEach(function (key) {
  83. children[key] = interpretAST(element[3][key], element, locale, types, join)
  84. })
  85. element = [element[0], element[1], element[2], children]
  86. } else if (element[2] && typeof element[2] === 'object') {
  87. children = {}
  88. Object.keys(element[2]).forEach(function (key) {
  89. children[key] = interpretAST(element[2][key], element, locale, types, join)
  90. })
  91. element = [element[0], element[1], children]
  92. }
  93. var getFrmt = type && (types[type] || defaults[type])
  94. if (getFrmt) {
  95. var frmt = getFrmt(element, locale)
  96. return function format (args) {
  97. return frmt(getArg(id, args), args)
  98. }
  99. }
  100. return join
  101. ? function format (args) { return String(getArg(id, args)) }
  102. : function format (args) { return getArg(id, args) }
  103. }
  104. function getArg (id/*: string */, args/*: ?Object */)/*: any */ {
  105. if (args && (id in args)) return args[id]
  106. var parts = id.split('.')
  107. var a = args
  108. for (var i = 0, ii = parts.length; a && i < ii; ++i) {
  109. a = a[parts[i]]
  110. }
  111. return a
  112. }
  113. function interpretNumber (element/*: Placeholder */, locales/*: Locales */) {
  114. var style = element[2]
  115. var options = formats.number[style] || formats.parseNumberPattern(style) || formats.number.default
  116. return new Intl.NumberFormat(locales, options).format
  117. }
  118. function interpretDuration (element/*: Placeholder */, locales/*: Locales */) {
  119. var style = element[2]
  120. var options = formats.duration[style] || formats.duration.default
  121. var fs = new Intl.NumberFormat(locales, options.seconds).format
  122. var fm = new Intl.NumberFormat(locales, options.minutes).format
  123. var fh = new Intl.NumberFormat(locales, options.hours).format
  124. var sep = /^fi$|^fi-|^da/.test(String(locales)) ? '.' : ':'
  125. return function (s, args) {
  126. s = +s
  127. if (!isFinite(s)) return fs(s)
  128. var h = ~~(s / 60 / 60) // ~~ acts much like Math.trunc
  129. var m = ~~(s / 60 % 60)
  130. var dur = (h ? (fh(Math.abs(h)) + sep) : '') +
  131. fm(Math.abs(m)) + sep + fs(Math.abs(s % 60))
  132. return s < 0 ? fh(-1).replace(fh(1), dur) : dur
  133. }
  134. }
  135. function interpretDateTime (element/*: Placeholder */, locales/*: Locales */) {
  136. var type = element[1]
  137. var style = element[2]
  138. var options = formats[type][style] || formats.parseDatePattern(style) || formats[type].default
  139. return new Intl.DateTimeFormat(locales, options).format
  140. }
  141. function interpretPlural (element/*: Placeholder */, locales/*: Locales */) {
  142. var type = element[1]
  143. var pluralType = type === 'selectordinal' ? 'ordinal' : 'cardinal'
  144. var offset = element[2]
  145. var children = element[3]
  146. var pluralRules
  147. if (Intl.PluralRules && Intl.PluralRules.supportedLocalesOf(locales).length > 0) {
  148. pluralRules = new Intl.PluralRules(locales, { type: pluralType })
  149. } else {
  150. var locale = lookupClosestLocale(locales, plurals)
  151. var select = (locale && plurals[locale][pluralType]) || returnOther
  152. pluralRules = { select: select }
  153. }
  154. return function (value, args) {
  155. var clause =
  156. children['=' + +value] ||
  157. children[pluralRules.select(value - offset)] ||
  158. children.other
  159. return clause(args)
  160. }
  161. }
  162. function returnOther (/*:: n:number */) { return 'other' }
  163. function interpretSelect (element/*: Placeholder */, locales/*: Locales */) {
  164. var children = element[2]
  165. return function (value, args) {
  166. var clause = children[value] || children.other
  167. return clause(args)
  168. }
  169. }
  170. var defaults/*: Types */ = {
  171. number: interpretNumber,
  172. ordinal: interpretNumber, // TODO: support rbnf
  173. spellout: interpretNumber, // TODO: support rbnf
  174. duration: interpretDuration,
  175. date: interpretDateTime,
  176. time: interpretDateTime,
  177. plural: interpretPlural,
  178. selectordinal: interpretPlural,
  179. select: interpretSelect
  180. }
  181. exports.types = defaults