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

i18next / i18next / #12469

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

push

jamuhl
rebuild

932 of 1066 relevant lines covered (87.43%)

38.46 hits per line

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

62.66
/src/i18next.js
1
import baseLogger from './logger.js';
2
import EventEmitter from './EventEmitter.js';
3
import ResourceStore from './ResourceStore.js';
1✔
4
import Translator from './Translator.js';
5
import LanguageUtils from './LanguageUtils.js';
6
import PluralResolver from './PluralResolver.js';
7
import Interpolator from './Interpolator.js';
1✔
8
import BackendConnector from './BackendConnector.js';
9
import { get as getDefaults, transformOptions } from './defaults.js';
340✔
10
import postProcessor from './postProcessor.js';
11

1✔
12
function noop() {}
13

1✔
14
class I18n extends EventEmitter {
15
  constructor(options = {}, callback) {
1✔
16
    super();
17
    this.options = transformOptions(options);
1✔
18
    this.services = {};
19
    this.logger = baseLogger;
1✔
20
    this.modules = { external: [] };
21

1✔
22
    if (callback && !this.isInitialized && !options.isClone) {
23
      // https://github.com/i18next/i18next/issues/879
1✔
24
      if (!this.options.initImmediate) return this.init(options, callback);
25
      setTimeout(() => {
1✔
26
        this.init(options, callback);
27
      }, 0);
1✔
28
    }
29
  }
1✔
30

31
  init(options = {}, callback) {
1✔
32
    if (typeof options === 'function') {
33
      callback = options;
1✔
34
      options = {};
35
    }
1✔
36
    this.options = { ...getDefaults(), ...this.options, ...transformOptions(options) };
37

1✔
38
    this.format = this.options.interpolation.format;
39
    if (!callback) callback = noop;
1✔
40

41
    function createClassOnDemand(ClassOrObject) {
1✔
42
      if (!ClassOrObject) return null;
43
      if (typeof ClassOrObject === 'function') return new ClassOrObject();
1✔
44
      return ClassOrObject;
45
    }
1✔
46

47
    // init services
1✔
48
    if (!this.options.isClone) {
49
      if (this.modules.logger) {
9✔
50
        baseLogger.init(createClassOnDemand(this.modules.logger), this.options);
51
      } else {
5✔
52
        baseLogger.init(null, this.options);
53
      }
5✔
54

55
      const lu = new LanguageUtils(this.options);
6✔
56
      this.store = new ResourceStore(this.options.resources, this.options);
57

1✔
58
      const s = this.services;
59
      s.logger = baseLogger;
1✔
60
      s.resourceStore = this.store;
61
      s.languageUtils = lu;
1✔
62
      s.pluralResolver = new PluralResolver(lu, { prepend: this.options.pluralSeparator, compatibilityJSON: this.options.compatibilityJSON, simplifyPluralSuffix: this.options.simplifyPluralSuffix });
1✔
63
      s.interpolator = new Interpolator(this.options);
64

1✔
65
      s.backendConnector = new BackendConnector(createClassOnDemand(this.modules.backend), s.resourceStore, s, this.options);
5✔
66
      // pipe events from backendConnector
5✔
67
      s.backendConnector.on('*', (event, ...args) => {
68
        this.emit(event, ...args);
5✔
69
      });
70

5✔
71
      if (this.modules.languageDetector) {
72
        s.languageDetector = createClassOnDemand(this.modules.languageDetector);
5✔
73
        s.languageDetector.init(s, this.options.detection, this.options);
5✔
74
      }
5✔
75

5✔
76
      if (this.modules.i18nFormat) {
77
        s.i18nFormat = createClassOnDemand(this.modules.i18nFormat);
5✔
78
        if (s.i18nFormat.init) s.i18nFormat.init(this);
1✔
79
      }
80

81
      this.translator = new Translator(this.services, this.options);
1✔
82
      // pipe events from translator
×
83
      this.translator.on('*', (event, ...args) => {
×
84
        this.emit(event, ...args);
85
      });
86

4✔
87
      this.modules.external.forEach((m) => {
88
        if (m.init) m.init(this);
89
      });
1✔
90
    }
4✔
91

92
    // append api
4✔
93
    const storeApi = ['getResource', 'addResource', 'addResources', 'addResourceBundle', 'removeResourceBundle', 'hasResourceBundle', 'getResourceBundle'];
4✔
94
    storeApi.forEach((fcName) => {
95
      this[fcName] = (...args) => this.store[fcName](...args);
4✔
96
    });
×
97

×
98
    const load = () => {
99
      this.changeLanguage(this.options.lng, (err, t) => {
4✔
100
        this.isInitialized = true;
101
        this.logger.log('initialized', this.options);
4✔
102
        this.emit('initialized', this.options);
4✔
103

104
        callback(err, t);
1✔
105
      });
2✔
106
    };
×
107

×
108
    if (this.options.resources || !this.options.initImmediate) {
109
      load();
110
    } else {
111
      setTimeout(load, 0);
4✔
112
    }
2✔
113

×
114
    return this;
115
  }
2✔
116

117
  /* eslint consistent-return: 0 */
118
  loadResources(callback = noop) {
2✔
119
    if (!this.options.resources) {
2✔
120
      if (this.language && this.language.toLowerCase() === 'cimode') return callback(); // avoid loading resources for cimode
121

2✔
122
      const toLoad = [];
2✔
123

2✔
124
      const append = (lng) => {
2✔
125
        if (!lng) return;
2✔
126
        const lngs = this.services.languageUtils.toResolveHierarchy(lng);
2✔
127
        lngs.forEach((l) => {
128
          if (toLoad.indexOf(l) < 0) toLoad.push(l);
2✔
129
        });
130
      };
2✔
131

×
132
      if (!this.language) {
×
133
        // at least load fallbacks in this case
134
        const fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
135
        fallbacks.forEach(l => append(l));
×
136
      } else {
137
        append(this.language);
138
      }
2✔
139

×
140
      if (this.options.preload) {
×
141
        this.options.preload.forEach(l => append(l));
142
      }
143

2✔
144
      this.services.backendConnector.load(toLoad, this.options.ns, callback);
×
145
    } else {
×
146
      callback(null);
147
    }
148
  }
2✔
149

150
  reloadResources(lngs, ns) {
2✔
151
    if (!lngs) lngs = this.languages;
×
152
    if (!ns) ns = this.options.ns;
×
153
    this.services.backendConnector.reload(lngs, ns);
154
  }
155

×
156
  use(module) {
157
    if (module.type === 'backend') {
158
      this.modules.backend = module;
2✔
159
    }
×
160

161
    if (module.type === 'logger' || (module.log && module.warn && module.error)) {
162
      this.modules.logger = module;
163
    }
164

4✔
165
    if (module.type === 'languageDetector') {
4✔
166
      this.modules.languageDetector = module;
28✔
167
    }
×
168

169
    if (module.type === 'i18nFormat') {
×
170
      this.modules.i18nFormat = module;
171
    }
172

173
    if (module.type === 'postProcessor') {
4✔
174
      postProcessor.addPostProcessor(module);
4✔
175
    }
4✔
176

4✔
177
    if (module.type === '3rdParty') {
4✔
178
      this.modules.external.push(module);
179
    }
4✔
180

181
    return this;
182
  }
183

4✔
184
  changeLanguage(lng, callback) {
×
185
    const done = (err, l) => {
186
      this.translator.changeLanguage(l);
4✔
187

188
      if (l) {
189
        this.emit('languageChanged', l);
4✔
190
        this.logger.log('languageChanged', l);
191
      }
192

193
      if (callback) callback(err, (...args) => this.t(...args));
194
    };
195

1✔
196
    const setLng = (l) => {
6✔
197
      if (l) {
198
        this.language = l;
6✔
199
        this.languages = this.services.languageUtils.toResolveHierarchy(l);
200
        if (!this.translator.language) this.translator.changeLanguage(l);
6✔
201

6✔
202
        if (this.services.languageDetector) this.services.languageDetector.cacheUserLanguage(l);
203
      }
6✔
204

205
      this.loadResources((err) => {
6✔
206
        done(err, l);
6✔
207
      });
6✔
208
    };
6✔
209

12✔
210
    if (!lng && this.services.languageDetector && !this.services.languageDetector.async) {
211
      setLng(this.services.languageDetector.detect());
212
    } else if (!lng && this.services.languageDetector && this.services.languageDetector.async) {
213
      this.services.languageDetector.detect(setLng);
6✔
214
    } else {
215
      setLng(lng);
×
216
    }
×
217
  }
×
218

219
  getFixedT(lng, ns) {
220
    const fixedT = (key, opts, ...rest) => {
6✔
221
      let options = { ...opts };
222
      if (typeof opts !== 'object') {
223
        options = this.options.overloadTranslationOptionHandler([key, opts].concat(rest));
6✔
224
      }
×
225

×
226
      options.lng = options.lng || fixedT.lng;
227
      options.lngs = options.lngs || fixedT.lngs;
228
      options.ns = options.ns || fixedT.ns;
229
      return this.t(key, options);
6✔
230
    };
231
    if (typeof lng === 'string') {
×
232
      fixedT.lng = lng;
233
    } else {
234
      fixedT.lngs = lng;
235
    }
1✔
236
    fixedT.ns = ns;
×
237
    return fixedT;
×
238
  }
×
239

240
  t(...args) {
241
    return this.translator && this.translator.translate(...args);
1✔
242
  }
×
243

×
244
  exists(...args) {
245
    return this.translator && this.translator.exists(...args);
246
  }
×
247

×
248
  setDefaultNamespace(ns) {
249
    this.options.defaultNS = ns;
250
  }
×
251

×
252
  loadNamespaces(ns, callback) {
253
    if (!this.options.ns) return callback && callback();
254
    if (typeof ns === 'string') ns = [ns];
×
255

×
256
    ns.forEach((n) => {
257
      if (this.options.ns.indexOf(n) < 0) this.options.ns.push(n);
258
    });
×
259

×
260
    this.loadResources(callback);
261
  }
262

×
263
  loadLanguages(lngs, callback) {
×
264
    if (typeof lngs === 'string') lngs = [lngs];
265
    const preloaded = this.options.preload || [];
266

×
267
    const newLngs = lngs.filter(lng => preloaded.indexOf(lng) < 0);
268
    // Exit early if all given languages are already preloaded
269
    if (!newLngs.length) return callback();
1✔
270

6✔
271
    this.options.preload = preloaded.concat(newLngs);
272
    this.loadResources(callback);
6✔
273
  }
6✔
274

275
  dir(lng) {
6✔
276
    if (!lng) lng = this.languages && this.languages.length > 0 ? this.languages[0] : this.language;
4✔
277
    if (!lng) return 'rtl';
4✔
278

279
    const rtlLngs = ['ar', 'shu', 'sqr', 'ssh', 'xaa', 'yhd', 'yud', 'aao', 'abh', 'abv', 'acm',
280
      'acq', 'acw', 'acx', 'acy', 'adf', 'ads', 'aeb', 'aec', 'afb', 'ajp', 'apc', 'apd', 'arb',
6✔
281
      'arq', 'ars', 'ary', 'arz', 'auz', 'avl', 'ayh', 'ayl', 'ayn', 'ayp', 'bbz', 'pga', 'he',
×
282
      'iw', 'ps', 'pbt', 'pbu', 'pst', 'prp', 'prd', 'ur', 'ydd', 'yds', 'yih', 'ji', 'yi', 'hbo',
283
      'men', 'xmn', 'fa', 'jpr', 'peo', 'pes', 'prs', 'dv', 'sam'
284
    ];
285

6✔
286
    return rtlLngs.indexOf(this.services.languageUtils.getLanguagePartFromCode(lng)) >= 0 ? 'rtl' : 'ltr';
6✔
287
  }
4✔
288

4✔
289
  /* eslint class-methods-use-this: 0 */
4✔
290
  createInstance(options = {}, callback) {
291
    return new I18n(options, callback);
4✔
292
  }
293

294
  cloneInstance(options = {}, callback = noop) {
6✔
295
    const mergedOptions = { ...this.options, ...options, ...{ isClone: true } };
6✔
296
    const clone = new I18n(mergedOptions);
297
    const membersToCopy = ['store', 'services', 'language'];
298
    membersToCopy.forEach((m) => {
299
      clone[m] = this[m];
6✔
300
    });
×
301
    clone.translator = new Translator(clone.services, clone.options);
6✔
302
    clone.translator.on('*', (event, ...args) => {
×
303
      clone.emit(event, ...args);
304
    });
6✔
305
    clone.init(mergedOptions, callback);
306
    clone.translator.options = clone.options; // sync options
307

308
    return clone;
1✔
309
  }
×
310
}
311

×
312
export default new I18n();
×
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