1 |
|
2 |
|
3 | import { warn, isObject } from './util'
|
4 |
|
5 | export 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 |
|
25 | type Token = {
|
26 | type: 'text' | 'named' | 'list' | 'unknown',
|
27 | value: string
|
28 | }
|
29 |
|
30 | const RE_TOKEN_LIST_VALUE: RegExp = /^(?:\d)+/
|
31 | const RE_TOKEN_NAMED_VALUE: RegExp = /^(?:\w)+/
|
32 |
|
33 | export 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 |
|
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 |
|
75 | export 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 | }
|