Skip to main content
Module

x/i18next/i18next.js

i18next: learn once - translate everywhere
Go to Latest
File
import baseLogger from './logger.js';import EventEmitter from './EventEmitter.js';import ResourceStore from './ResourceStore.js';import Translator from './Translator.js';import LanguageUtils from './LanguageUtils.js';import PluralResolver from './PluralResolver.js';import Interpolator from './Interpolator.js';import BackendConnector from './BackendConnector.js';import { get as getDefaults, transformOptions } from './defaults.js';import postProcessor from './postProcessor.js';import { defer } from './utils.js';
function noop() {}
class I18n extends EventEmitter { constructor(options = {}, callback) { super(); EventEmitter.call(this) // <=IE10 fix (unable to call parent constructor)
this.options = transformOptions(options); this.services = {}; this.logger = baseLogger; this.modules = { external: [] };
if (callback && !this.isInitialized && !options.isClone) { // https://github.com/i18next/i18next/issues/879 if (!this.options.initImmediate) { this.init(options, callback); return this; } setTimeout(() => { this.init(options, callback); }, 0); } }
init(options = {}, callback) { if (typeof options === 'function') { callback = options; options = {}; } this.options = { ...getDefaults(), ...this.options, ...transformOptions(options) };
this.format = this.options.interpolation.format; if (!callback) callback = noop;
function createClassOnDemand(ClassOrObject) { if (!ClassOrObject) return null; if (typeof ClassOrObject === 'function') return new ClassOrObject(); return ClassOrObject; }
// init services if (!this.options.isClone) { if (this.modules.logger) { baseLogger.init(createClassOnDemand(this.modules.logger), this.options); } else { baseLogger.init(null, this.options); }
const lu = new LanguageUtils(this.options); this.store = new ResourceStore(this.options.resources, this.options);
const s = this.services; s.logger = baseLogger; s.resourceStore = this.store; s.languageUtils = lu; s.pluralResolver = new PluralResolver(lu, { prepend: this.options.pluralSeparator, compatibilityJSON: this.options.compatibilityJSON, simplifyPluralSuffix: this.options.simplifyPluralSuffix, }); s.interpolator = new Interpolator(this.options);
s.backendConnector = new BackendConnector( createClassOnDemand(this.modules.backend), s.resourceStore, s, this.options, ); // pipe events from backendConnector s.backendConnector.on('*', (event, ...args) => { this.emit(event, ...args); });
if (this.modules.languageDetector) { s.languageDetector = createClassOnDemand(this.modules.languageDetector); s.languageDetector.init(s, this.options.detection, this.options); }
if (this.modules.i18nFormat) { s.i18nFormat = createClassOnDemand(this.modules.i18nFormat); if (s.i18nFormat.init) s.i18nFormat.init(this); }
this.translator = new Translator(this.services, this.options); // pipe events from translator this.translator.on('*', (event, ...args) => { this.emit(event, ...args); });
this.modules.external.forEach(m => { if (m.init) m.init(this); }); }
// append api const storeApi = [ 'getResource', 'addResource', 'addResources', 'addResourceBundle', 'removeResourceBundle', 'hasResourceBundle', 'getResourceBundle', 'getDataByLanguage', ]; storeApi.forEach(fcName => { this[fcName] = (...args) => this.store[fcName](...args); });
const deferred = defer();
const load = () => { this.changeLanguage(this.options.lng, (err, t) => { this.isInitialized = true; this.logger.log('initialized', this.options); this.emit('initialized', this.options);
deferred.resolve(t); // not rejecting on err (as err is only a loading translation failed warning) callback(err, t); }); };
if (this.options.resources || !this.options.initImmediate) { load(); } else { setTimeout(load, 0); }
return deferred; }
/* eslint consistent-return: 0 */ loadResources(callback = noop) { if (!this.options.resources || this.options.partialBundledLanguages) { if (this.language && this.language.toLowerCase() === 'cimode') return callback(); // avoid loading resources for cimode
const toLoad = [];
const append = lng => { if (!lng) return; const lngs = this.services.languageUtils.toResolveHierarchy(lng); lngs.forEach(l => { if (toLoad.indexOf(l) < 0) toLoad.push(l); }); };
if (!this.language) { // at least load fallbacks in this case const fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng); fallbacks.forEach(l => append(l)); } else { append(this.language); }
if (this.options.preload) { this.options.preload.forEach(l => append(l)); }
this.services.backendConnector.load(toLoad, this.options.ns, callback); } else { callback(null); } }
reloadResources(lngs, ns, callback) { const deferred = defer(); if (!lngs) lngs = this.languages; if (!ns) ns = this.options.ns; if (!callback) callback = noop; this.services.backendConnector.reload(lngs, ns, err => { deferred.resolve(); // not rejecting on err (as err is only a loading translation failed warning) callback(err); }); return deferred; }
use(module) { if (module.type === 'backend') { this.modules.backend = module; }
if (module.type === 'logger' || (module.log && module.warn && module.error)) { this.modules.logger = module; }
if (module.type === 'languageDetector') { this.modules.languageDetector = module; }
if (module.type === 'i18nFormat') { this.modules.i18nFormat = module; }
if (module.type === 'postProcessor') { postProcessor.addPostProcessor(module); }
if (module.type === '3rdParty') { this.modules.external.push(module); }
return this; }
changeLanguage(lng, callback) { const deferred = defer(); this.emit('languageChanging', lng);
const done = (err, l) => { this.translator.changeLanguage(l);
if (l) { this.emit('languageChanged', l); this.logger.log('languageChanged', l); }
deferred.resolve((...args) => this.t(...args)); if (callback) callback(err, (...args) => this.t(...args)); };
const setLng = l => { if (l) { this.language = l; this.languages = this.services.languageUtils.toResolveHierarchy(l); if (!this.translator.language) this.translator.changeLanguage(l);
if (this.services.languageDetector) this.services.languageDetector.cacheUserLanguage(l); }
this.loadResources(err => { done(err, l); }); };
if (!lng && this.services.languageDetector && !this.services.languageDetector.async) { setLng(this.services.languageDetector.detect()); } else if (!lng && this.services.languageDetector && this.services.languageDetector.async) { this.services.languageDetector.detect(setLng); } else { setLng(lng); }
return deferred; }
getFixedT(lng, ns) { const fixedT = (key, opts, ...rest) => { let options = { ...opts }; if (typeof opts !== 'object') { options = this.options.overloadTranslationOptionHandler([key, opts].concat(rest)); }
options.lng = options.lng || fixedT.lng; options.lngs = options.lngs || fixedT.lngs; options.ns = options.ns || fixedT.ns; return this.t(key, options); }; if (typeof lng === 'string') { fixedT.lng = lng; } else { fixedT.lngs = lng; } fixedT.ns = ns; return fixedT; }
t(...args) { return this.translator && this.translator.translate(...args); }
exists(...args) { return this.translator && this.translator.exists(...args); }
setDefaultNamespace(ns) { this.options.defaultNS = ns; }
loadNamespaces(ns, callback) { const deferred = defer();
if (!this.options.ns) { callback && callback(); return Promise.resolve(); } if (typeof ns === 'string') ns = [ns];
ns.forEach(n => { if (this.options.ns.indexOf(n) < 0) this.options.ns.push(n); });
this.loadResources(err => { deferred.resolve(); if (callback) callback(err); });
return deferred; }
loadLanguages(lngs, callback) { const deferred = defer();
if (typeof lngs === 'string') lngs = [lngs]; const preloaded = this.options.preload || [];
const newLngs = lngs.filter(lng => preloaded.indexOf(lng) < 0); // Exit early if all given languages are already preloaded if (!newLngs.length) { if (callback) callback(); return Promise.resolve(); }
this.options.preload = preloaded.concat(newLngs); this.loadResources(err => { deferred.resolve(); if (callback) callback(err); });
return deferred; }
dir(lng) { if (!lng) lng = this.languages && this.languages.length > 0 ? this.languages[0] : this.language; if (!lng) return 'rtl';
const rtlLngs = [ 'ar', 'shu', 'sqr', 'ssh', 'xaa', 'yhd', 'yud', 'aao', 'abh', 'abv', 'acm', 'acq', 'acw', 'acx', 'acy', 'adf', 'ads', 'aeb', 'aec', 'afb', 'ajp', 'apc', 'apd', 'arb', 'arq', 'ars', 'ary', 'arz', 'auz', 'avl', 'ayh', 'ayl', 'ayn', 'ayp', 'bbz', 'pga', 'he', 'iw', 'ps', 'pbt', 'pbu', 'pst', 'prp', 'prd', 'ur', 'ydd', 'yds', 'yih', 'ji', 'yi', 'hbo', 'men', 'xmn', 'fa', 'jpr', 'peo', 'pes', 'prs', 'dv', 'sam', ];
return rtlLngs.indexOf(this.services.languageUtils.getLanguagePartFromCode(lng)) >= 0 ? 'rtl' : 'ltr'; }
/* eslint class-methods-use-this: 0 */ createInstance(options = {}, callback) { return new I18n(options, callback); }
cloneInstance(options = {}, callback = noop) { const mergedOptions = { ...this.options, ...options, ...{ isClone: true } }; const clone = new I18n(mergedOptions); const membersToCopy = ['store', 'services', 'language']; membersToCopy.forEach(m => { clone[m] = this[m]; }); clone.translator = new Translator(clone.services, clone.options); clone.translator.on('*', (event, ...args) => { clone.emit(event, ...args); }); clone.init(mergedOptions, callback); clone.translator.options = clone.options; // sync options
return clone; }}
export default new I18n();