UNPKG

2.75 kBJavaScriptView Raw
1/* @flow */
2
3import { warn, isObject } from './util'
4
5export default class BaseFormatter {
6 _caches: { [key: string]: Array<Token> }
7
8 constructor () {
9 this._caches = Object.create(null)
10 }
11
12 interpolate (message: string, values: any): Array<any> {
13 if (!values) {
14 return [message]
15 }
16 let tokens: Array<Token> = this._caches[message]
17 if (!tokens) {
18 tokens = parse(message)
19 this._caches[message] = tokens
20 }
21 return compile(tokens, values)
22 }
23}
24
25type Token = {
26 type: 'text' | 'named' | 'list' | 'unknown',
27 value: string
28}
29
30const RE_TOKEN_LIST_VALUE: RegExp = /^(?:\d)+/
31const RE_TOKEN_NAMED_VALUE: RegExp = /^(?:\w)+/
32
33export function parse (format: string): Array<Token> {
34 const tokens: Array<Token> = []
35 let position: number = 0
36
37 let text: string = ''
38 while (position < format.length) {
39 let char: string = format[position++]
40 if (char === '{') {
41 if (text) {
42 tokens.push({ type: 'text', value: text })
43 }
44
45 text = ''
46 let sub: string = ''
47 char = format[position++]
48 while (char !== undefined && char !== '}') {
49 sub += char
50 char = format[position++]
51 }
52 const isClosed = char === '}'
53
54 const type = RE_TOKEN_LIST_VALUE.test(sub)
55 ? 'list'
56 : isClosed && RE_TOKEN_NAMED_VALUE.test(sub)
57 ? 'named'
58 : 'unknown'
59 tokens.push({ value: sub, type })
60 } else if (char === '%') {
61 // when found rails i18n syntax, skip text capture
62 if (format[(position)] !== '{') {
63 text += char
64 }
65 } else {
66 text += char
67 }
68 }
69
70 text && tokens.push({ type: 'text', value: text })
71
72 return tokens
73}
74
75export function compile (tokens: Array<Token>, values: Object | Array<any>): Array<any> {
76 const compiled: Array<any> = []
77 let index: number = 0
78
79 const mode: string = Array.isArray(values)
80 ? 'list'
81 : isObject(values)
82 ? 'named'
83 : 'unknown'
84 if (mode === 'unknown') { return compiled }
85
86 while (index < tokens.length) {
87 const token: Token = tokens[index]
88 switch (token.type) {
89 case 'text':
90 compiled.push(token.value)
91 break
92 case 'list':
93 compiled.push(values[parseInt(token.value, 10)])
94 break
95 case 'named':
96 if (mode === 'named') {
97 compiled.push((values: any)[token.value])
98 } else {
99 if (process.env.NODE_ENV !== 'production') {
100 warn(`Type of token '${token.type}' and format of value '${mode}' don't match!`)
101 }
102 }
103 break
104 case 'unknown':
105 if (process.env.NODE_ENV !== 'production') {
106 warn(`Detect 'unknown' type of token!`)
107 }
108 break
109 }
110 index++
111 }
112
113 return compiled
114}