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

i18next / i18next / #51133

17 Mar 2016 09:13AM UTC coverage: 66.667% (+1.9%) from 64.722%
#51133

push

jamuhl
rebuild

514 of 771 relevant lines covered (66.67%)

20.23 hits per line

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

76.72
/src/Translator.js
1
import baseLogger from './logger';
1✔
2
import EventEmitter from './EventEmitter';
1✔
3
import postProcessor from './postProcessor';
1✔
4
import * as compat from './compatibility/v1';
1✔
5
import * as utils from './utils';
1✔
6

7
class Translator extends EventEmitter {
8
  constructor(services, options = {}) {
10✔
9
    super();
10

11
    utils.copy(['resourceStore','languageUtils','pluralResolver','interpolator','backendConnector'], services, this);
10✔
12

13
    this.options = options;
10✔
14
    this.logger = baseLogger.create('translator');
10✔
15
  }
16

17
  changeLanguage(lng) {
18
    if (lng) this.language = lng;
11✔
19
  }
20

21
  exists(key, options = { interpolation: {} }) {
×
22
    if (this.options.compatibilityAPI === 'v1') {
×
23
      options = compat.convertTOptions(options);
×
24
    }
25

26
    return this.resolve(key, options) !== undefined;
×
27
  }
28

29
  extractFromKey(key, options) {
30
    let nsSeparator = options.nsSeparator || this.options.nsSeparator
120✔
31
    if (nsSeparator === undefined ) nsSeparator = ':';
120✔
32

33
    let namespaces = options.ns || this.options.defaultNS;
120✔
34
    if (nsSeparator && key.indexOf(nsSeparator) > -1) {
120✔
35
      const parts = key.split(nsSeparator);
84✔
36
      namespaces = parts[0];
84✔
37
      key = parts[1];
84✔
38
    }
39
    if (typeof namespaces === 'string') namespaces = [namespaces];
120✔
40

41
    return {
120✔
42
      key: key,
43
      namespaces: namespaces
44
    };
45
  }
46

47
  translate(keys, options = {}) {
60✔
48
    if (typeof options !== 'object') {
60✔
49
      options = this.options.overloadTranslationOptionHandler(arguments);
×
50
    } else if (this.options.compatibilityAPI === 'v1') {
60✔
51
      options = compat.convertTOptions(options);
×
52
    }
53

54
    // non valid keys handling
55
    if (keys === undefined || keys === null || keys === '') return '';
60✔
56
    if (typeof keys === 'number') keys = String(keys);
60✔
57
    if (typeof keys === 'string') keys = [keys];
60✔
58

59
    // return key on CIMode
60
    let lng = options.lng || this.language;
60✔
61
    if (lng && lng.toLowerCase() === 'cimode') return keys[keys.length - 1];
60✔
62

63
    // separators
64
    let keySeparator = options.keySeparator || this.options.keySeparator || '.';
60✔
65

66
    // get namespace(s)
67
    let { key, namespaces } = this.extractFromKey(keys[keys.length - 1], options);
68
    let namespace = namespaces[namespaces.length - 1];
60✔
69

70
    // resolve from store
71
    let res = this.resolve(keys, options);
60✔
72

73
    let resType = Object.prototype.toString.apply(res);
60✔
74
    let noObject = ['[object Number]', '[object Function]', '[object RegExp]'];
60✔
75
    let joinArrays = options.joinArrays !== undefined ?  options.joinArrays : this.options.joinArrays;
60✔
76

77
    // object
78
    if (res && typeof res !== 'string' && noObject.indexOf(resType) < 0 && !(joinArrays && resType === '[object Array]')) {
60✔
79
      if (!options.returnObjects && !this.options.returnObjects) {
2✔
80
        this.logger.warn('accessing an object - but returnObjects options is not enabled!');
×
81
        return this.options.returnedObjectHandler ? this.options.returnedObjectHandler(key, res, options): `key '${key} (${this.language})' returned an object instead of string.`;
×
82
      }
83

84
      let copy = (resType === '[object Array]') ? [] : {}; // apply child translation on a copy
2✔
85

86
      for (let m in res) {
2✔
87
        copy[m] = this.translate(`${key}${keySeparator}${m}`, {...{joinArrays: false, ns: namespaces}, ...options});
4✔
88
      }
89
      res = copy;
2✔
90
    }
91
    // array special treatment
92
    else if (joinArrays && resType === '[object Array]') {
58✔
93
      res = res.join(joinArrays);
1✔
94
      if (res) res = this.extendTranslation(res, key, options);
1✔
95
    }
96
    // string, empty or null
97
    else {
98
      let usedDefault = false,
57✔
99
          usedKey = false;
100

101
      // fallback value
102
      if (!this.isValidLookup(res) && options.defaultValue) {
57✔
103
        usedDefault = true;
×
104
        res = options.defaultValue;
×
105
      }
106
      if (!this.isValidLookup(res)) {
57✔
107
        usedKey = true;
×
108
        res = key;
×
109
      }
110

111
      // save missing
112
      if (usedKey || usedDefault) {
57✔
113
        this.logger.log('missingKey', lng, namespace, key, res);
×
114

115
        if (this.options.saveMissing) {
×
116
          var lngs = [];
×
117
          if (this.options.saveMissingTo === 'fallback' && this.options.fallbackLng && this.options.fallbackLng[0]) {
×
118
            for (let i = 0; i < this.options.fallbackLng.length; i++) {
×
119
              lngs.push(this.options.fallbackLng[i]);
×
120
            }
121
          } else if (this.options.saveMissingTo === 'all') {
×
122
            lngs = this.languageUtils.toResolveHierarchy(options.lng || this.language);
×
123
          } else {//(this.options.saveMissingTo === 'current' || (this.options.saveMissingTo === 'fallback' && this.options.fallbackLng[0] === false) ) {
124
            lngs.push(options.lng || this.language);
×
125
          }
126

127
          if (this.options.missingKeyHandler) {
×
128
            this.options.missingKeyHandler(lngs, namespace, key, res);
×
129
          } else if (this.backendConnector && this.backendConnector.saveMissing) {
×
130
            this.backendConnector.saveMissing(lngs, namespace, key, res);
×
131
          }
132

133
          this.emit('missingKey', lngs, namespace, key, res);
×
134
        }
135
      }
136

137
      // extend
138
      res = this.extendTranslation(res, key, options);
57✔
139

140
      // append namespace if still key
141
      if (usedKey && res === key && this.options.appendNamespaceToMissingKey) res = `${namespace}:${key}`;
57✔
142

143
      // parseMissingKeyHandler
144
      if (usedKey && this.options.parseMissingKeyHandler) res = this.options.parseMissingKeyHandler(res);
57✔
145
    }
146

147
    // return
148
    return res;
60✔
149
  }
150

151
  extendTranslation(res, key, options) {
58✔
152
    if (options.interpolation) this.interpolator.init(options);
58✔
153

154
    // interpolate
155
    let data = options.replace && typeof options.replace !== 'string' ? options.replace : options;
58✔
156
    if (this.options.interpolation.defaultVariables) data = {...this.options.interpolation.defaultVariables, ...data};
58✔
157
    res = this.interpolator.interpolate(res, data);
58✔
158

159
    // nesting
160
    res = this.interpolator.nest(res, (...args) => { return this.translate.apply(this, args); }, options);
58✔
161

162
    if (options.interpolation) this.interpolator.reset();
58✔
163

164
    // post process
165
    let postProcess = options.postProcess || this.options.postProcess;
58✔
166
    let postProcessorNames = typeof postProcess === 'string' ? [postProcess] : postProcess;
58✔
167

168
    if (res !== undefined &&
58✔
169
        postProcessorNames &&
170
        postProcessorNames.length &&
171
        options.applyPostProcessor !== false) {
172
      res = postProcessor.handle(postProcessorNames, res, key, options, this);
×
173
    }
174

175
    return res;
58✔
176
  }
177

178
  resolve(keys, options = {}) {
60✔
179
    let found;
60✔
180

181
    if (typeof keys === 'string') keys = [keys];
60✔
182

183
    // forEach possible key
184
    keys.forEach(k => {
60✔
185
      if (this.isValidLookup(found)) return;
60✔
186

187
      let { key, namespaces } = this.extractFromKey(k, options);
188
      if (this.options.fallbackNS) namespaces = namespaces.concat(this.options.fallbackNS);
60✔
189

190
      let needsPluralHandling = options.count !== undefined && typeof options.count !== 'string';
60✔
191
      let needsContextHandling = options.context !== undefined && typeof options.context === 'string' && options.context !== '';
60✔
192

193
      let codes = options.lngs ? options.lngs : this.languageUtils.toResolveHierarchy(options.lng || this.language);
60✔
194

195
      namespaces.forEach(ns => {
60✔
196
        if (this.isValidLookup(found)) return;
60✔
197

198
        codes.forEach(code => {
60✔
199
          if (this.isValidLookup(found)) return;
84✔
200

201
          let finalKey = key;
78✔
202
          let finalKeys = [finalKey];
78✔
203

204
          let pluralSuffix;
78✔
205
          if (needsPluralHandling) pluralSuffix = this.pluralResolver.getSuffix(code, options.count);
78✔
206

207
          // fallback for plural if context not found
208
          if (needsPluralHandling && needsContextHandling) finalKeys.push(finalKey + pluralSuffix);
78✔
209

210
          // get key for context if needed
211
          if (needsContextHandling) finalKeys.push(finalKey += `${this.options.contextSeparator}${options.context}`);
78✔
212

213
          // get key for plural if needed
214
          if (needsPluralHandling) finalKeys.push(finalKey += pluralSuffix);
78✔
215

216
          // iterate over finalKeys starting with most specific pluralkey (-> contextkey only) -> singularkey only
217
          let possibleKey;
78✔
218
          while(possibleKey = finalKeys.pop()) {
78✔
219
            if (this.isValidLookup(found)) continue;
127✔
220
            found = this.getResource(code, ns, possibleKey, options);
95✔
221
          }
222
        });
223
      });
224
    });
225

226
    return found;
60✔
227
  }
228

229
  isValidLookup(res) {
230
    return res !== undefined &&
445✔
231
           !(!this.options.returnNull && res === null) &&
232
           !(!this.options.returnEmptyString && res === '');
233
  }
234

235
  getResource(code, ns, key, options = {}) {
97✔
236
    return this.resourceStore.getResource(code, ns, key, options);
97✔
237
  }
238
}
239

240

241
export default Translator;
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