• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

i18next / i18next / #12544

19 Apr 2018 07:07AM UTC coverage: 87.43% (+2.0%) from 85.448%
#12544

push

jamuhl
rebuild

932 of 1066 relevant lines covered (87.43%)

39.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.61
/src/Interpolator.js
1
import * as utils from './utils.js';
2
import baseLogger from './logger.js';
3

1✔
4
class Interpolator {
5
  constructor(options = {}) {
6
    this.logger = baseLogger.create('interpolator');
7

161✔
8
    this.init(options, true);
9
  }
1✔
10

11
  /* eslint no-param-reassign: 0 */
1✔
12
  init(options = {}, reset) {
13
    if (reset) {
1✔
14
      this.options = options;
15
      this.format = (options.interpolation && options.interpolation.format) || (value => value);
1✔
16
      this.escape = (options.interpolation && options.interpolation.escape) || utils.escape;
17
    }
1✔
18
    if (!options.interpolation) options.interpolation = { escapeValue: true };
19

1✔
20
    const iOpts = options.interpolation;
21

41✔
22
    this.escapeValue = iOpts.escapeValue !== undefined ? iOpts.escapeValue : true;
23

1✔
24
    this.prefix = iOpts.prefix ? utils.regexEscape(iOpts.prefix) : iOpts.prefixEscaped || '{{';
1✔
25
    this.suffix = iOpts.suffix ? utils.regexEscape(iOpts.suffix) : iOpts.suffixEscaped || '}}';
41✔
26

27
    this.formatSeparator = iOpts.formatSeparator ? iOpts.formatSeparator : iOpts.formatSeparator || ',';
41✔
28

29
    this.unescapePrefix = iOpts.unescapeSuffix ? '' : iOpts.unescapePrefix || '-';
41✔
30
    this.unescapeSuffix = this.unescapePrefix ? '' : iOpts.unescapeSuffix || '';
31

41✔
32
    this.nestingPrefix = iOpts.nestingPrefix ? utils.regexEscape(iOpts.nestingPrefix) : iOpts.nestingPrefixEscaped || utils.regexEscape('$t(');
33
    this.nestingSuffix = iOpts.nestingSuffix ? utils.regexEscape(iOpts.nestingSuffix) : iOpts.nestingSuffixEscaped || utils.regexEscape(')');
34

35
    this.maxReplaces = iOpts.maxReplaces ? iOpts.maxReplaces : 1000;
36

37
    // the regexp
1✔
38
    this.resetRegExp();
43✔
39
  }
43✔
40

41
  reset() {
43✔
42
    if (this.options) this.init(this.options);
41✔
43
  }
41✔
44

×
45
  resetRegExp() {
46
    // the regexp
41✔
47
    const regexpStr = `${this.prefix}(.+?)${this.suffix}`;
48
    this.regexp = new RegExp(regexpStr, 'g');
43✔
49

50
    const regexpUnescapeStr = `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`;
43✔
51
    this.regexpUnescape = new RegExp(regexpUnescapeStr, 'g');
52

43✔
53
    const nestingRegexpStr = `${this.nestingPrefix}(.+?)${this.nestingSuffix}`;
54
    this.nestingRegexp = new RegExp(nestingRegexpStr, 'g');
43✔
55
  }
43✔
56

57
  interpolate(str, data, lng) {
43✔
58
    let match;
59
    let value;
43✔
60
    let replaces;
43✔
61

62
    function regexSafe(val) {
43✔
63
      return val.replace(/\$/g, '$$$$');
43✔
64
    }
65

43✔
66
    const handleFormat = (key) => {
67
      if (key.indexOf(this.formatSeparator) < 0) return utils.getPath(data, key);
68

43✔
69
      const p = key.split(this.formatSeparator);
70
      const k = p.shift().trim();
71
      const f = p.join(this.formatSeparator).trim();
1✔
72

1✔
73
      return this.format(utils.getPath(data, k), f, lng);
74
    };
75

1✔
76
    this.resetRegExp();
77

171✔
78
    replaces = 0;
171✔
79
    // unescape if has unescapePrefix/Suffix
80
    /* eslint no-cond-assign: 0 */
171✔
81
    while (match = this.regexpUnescape.exec(str)) {
171✔
82
      value = handleFormat(match[1].trim());
83
      str = str.replace(match[0], value);
171✔
84
      this.regexpUnescape.lastIndex = 0;
171✔
85
      replaces++;
86
      if (replaces >= this.maxReplaces) {
87
        break;
1✔
88
      }
128✔
89
    }
90

128✔
91
    replaces = 0;
128✔
92
    // regular escape on demand
128✔
93
    while (match = this.regexp.exec(str)) {
94
      value = handleFormat(match[1].trim());
1✔
95
      if (value === undefined) {
47✔
96
        if (typeof this.options.missingInterpolationHandler === 'function') {
97
          const temp = this.options.missingInterpolationHandler(str, match);
98
          value = typeof temp === 'string' ? temp : '';
128✔
99
        } else {
53✔
100
          this.logger.warn(`missed to pass in variable ${match[1]} for interpolating ${str}`);
101
          value = '';
5✔
102
        }
5✔
103
      } else if (typeof value !== 'string') {
5✔
104
        value = utils.makeString(value);
105
      }
5✔
106
      value = this.escapeValue ? regexSafe(this.escape(value)) : regexSafe(value);
107
      str = str.replace(match[0], value);
108
      this.regexp.lastIndex = 0;
128✔
109
      replaces++;
110
      if (replaces >= this.maxReplaces) {
128✔
111
        break;
112
      }
113
    }
128✔
114
    return str;
5✔
115
  }
5✔
116

5✔
117
  nest(str, fc, options = {}) {
5✔
118
    let match;
5✔
119
    let value;
×
120

121
    let clonedOptions = { ...options };
122
    clonedOptions.applyPostProcessor = false; // avoid post processing on nested lookup
123

128✔
124
    // if value is something like "myKey": "lorem $(anotherKey, { "count": {{aValueInOptions}} })"
125
    function handleHasOptions(key, inheritedOptions) {
128✔
126
      if (key.indexOf(',') < 0) return key;
48✔
127

47✔
128
      const p = key.split(',');
6✔
129
      key = p.shift();
2✔
130
      let optionsString = p.join(',');
2✔
131
      optionsString = this.interpolate(optionsString, clonedOptions);
132
      optionsString = optionsString.replace(/'/g, '"');
4✔
133

4✔
134
      try {
135
        clonedOptions = JSON.parse(optionsString);
41✔
136

2✔
137
        if (inheritedOptions) clonedOptions = { ...inheritedOptions, ...clonedOptions };
138
      } catch (e) {
47✔
139
        this.logger.error(`failed parsing options string in nesting for key ${key}`, e);
47✔
140
      }
47✔
141

47✔
142
      return key;
47✔
143
    }
1✔
144

145
    // regular escape on demand
146
    while (match = this.nestingRegexp.exec(str)) {
127✔
147
      value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);
148

149
      // is only the nesting key (key1 = '$(key2)') return the value without stringify
1✔
150
      if (value && match[0] === str && typeof value !== 'string') return value;
100✔
151

152
      // no string to include or empty
100✔
153
      if (typeof value !== 'string') value = utils.makeString(value);
100✔
154
      if (!value) {
155
        this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`);
100✔
156
        value = '';
100✔
157
      }
158
      // Nested keys should not be escaped by default #854
159
      // value = this.escapeValue ? regexSafe(utils.escape(value)) : regexSafe(value);
1✔
160
      str = str.replace(match[0], value);
6✔
161
      this.regexp.lastIndex = 0;
162
    }
2✔
163
    return str;
2✔
164
  }
2✔
165
}
2✔
166

2✔
167

168
export default Interpolator;
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc