UNPKG

4.23 kBJavaScriptView Raw
1/* globals Intl */
2'use strict'
3
4var assign = require('object-assign')
5var parse = require('format-message-parse')
6var interpret = require('format-message-interpret')
7var plurals = require('format-message-interpret/plurals')
8var lookupClosestLocale = require('lookup-closest-locale')
9var formats = require('format-message-formats')
10
11function namespace () {
12 var cache = {}
13
14 var nsFormats = assign({}, formats)
15 var currentLocale = 'en'
16 var translations = null
17 var generateId = function (pattern) { return pattern }
18 var missingReplacement = null
19 var missingTranslation = 'warning'
20
21 function formatMessage (msg, args, locale) {
22 locale = locale || currentLocale
23 var pattern = typeof msg === 'string' ? msg : msg.default
24 var id = (typeof msg === 'object' && msg.id) || generateId(pattern)
25 var key = locale + ':' + id
26 var format = cache[key] ||
27 (cache[key] = generateFormat(pattern, id, locale))
28 if (typeof format === 'string') return format
29 return format(args)
30 }
31
32 function generateFormat (pattern, id, locale) {
33 pattern = translate(pattern, id, locale)
34 return interpret(locale, parse(pattern))
35 }
36
37 function translate (pattern, id, locale) {
38 if (!translations) return pattern
39
40 locale = lookupClosestLocale(locale, translations)
41 var translated = translations[locale] && translations[locale][id]
42 if (translated && translated.message) translated = translated.message
43 if (translated != null) return translated
44
45 var replacement = missingReplacement || pattern
46 if (typeof replacement === 'function') {
47 replacement = replacement(pattern, id, locale) || pattern
48 }
49 var message = 'Translation for "' + id + '" in "' + locale + '" is missing'
50
51 if (missingTranslation === 'ignore') {
52 // do nothing
53 } else if (missingTranslation === 'warning') {
54 if (typeof console !== 'undefined') console.warn(message)
55 } else { // 'error'
56 throw new Error(message)
57 }
58
59 return replacement
60 }
61
62 formatMessage.setup = function setup (opt) {
63 opt = opt || {}
64 if (opt.locale) currentLocale = opt.locale
65 if ('translations' in opt) translations = opt.translations
66 if (opt.generateId) generateId = opt.generateId
67 if ('missingReplacement' in opt) missingReplacement = opt.missingReplacement
68 if (opt.missingTranslation) missingTranslation = opt.missingTranslation
69 if (opt.formats) {
70 if (opt.formats.number) assign(nsFormats.number, opt.formats.number)
71 if (opt.formats.date) assign(nsFormats.date, opt.formats.date)
72 if (opt.formats.time) assign(nsFormats.time, opt.formats.time)
73 }
74 return {
75 locale: currentLocale,
76 translations: translations,
77 generateId: generateId,
78 missingReplacement: missingReplacement,
79 missingTranslation: missingTranslation,
80 formats: nsFormats
81 }
82 }
83
84 function helper (type, value, style, locale) {
85 locale = locale || currentLocale
86 var options = nsFormats[type][style] || nsFormats[type].default
87 var cache = options.cache || (options.cache = {})
88 var format = cache[locale] || (cache[locale] = type === 'number'
89 ? Intl.NumberFormat(locale, options).format
90 : Intl.DateTimeFormat(locale, options).format
91 )
92 return format(value)
93 }
94
95 formatMessage.number = helper.bind(null, 'number')
96 formatMessage.date = helper.bind(null, 'date')
97 formatMessage.time = helper.bind(null, 'time')
98
99 formatMessage.select = function (value, options) {
100 return options[value] || options.other
101 }
102
103 function selectPlural (pluralType, value, offset, options, locale) {
104 if (typeof offset === 'object') { // offset is optional
105 locale = options
106 options = offset
107 offset = 0
108 }
109
110 var closest = lookupClosestLocale(locale || currentLocale, plurals)
111 var plural = plurals[closest][pluralType]
112 if (!plural) return options.other
113
114 return (
115 options['=' + +value] ||
116 options[plural(value - offset)] ||
117 options.other
118 )
119 }
120
121 formatMessage.plural = selectPlural.bind(null, 'cardinal')
122 formatMessage.selectordinal = selectPlural.bind(null, 'ordinal')
123
124 return formatMessage
125}
126
127module.exports = exports = namespace()
128exports.namespace = namespace