format.js 2.7 KB

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