Skip to content

Commit

Permalink
offer possibility to define a language detector detect function as pr…
Browse files Browse the repository at this point in the history
…omise #1879
  • Loading branch information
adrai committed Dec 10, 2022
1 parent 2f4aea3 commit 7e7c751
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,7 @@
## 22.3.0

- offer possibility to define a language detector's detect function as promise

## 22.2.0

- types: extend the init method with a generic parameter for backend options [1878](https://github.com/i18next/i18next/pull/1878), [105](https://github.com/i18next/i18next-http-backend/pull/105), [34](https://github.com/i18next/i18next-fs-backend/pull/34)
Expand Down
10 changes: 7 additions & 3 deletions i18next.js
Expand Up @@ -2212,7 +2212,7 @@
});
if (this.modules.languageDetector) {
s.languageDetector = createClassOnDemand(this.modules.languageDetector);
s.languageDetector.init(s, this.options.detection, this.options);
if (s.languageDetector.init) s.languageDetector.init(s, this.options.detection, this.options);
}
if (this.modules.i18nFormat) {
s.i18nFormat = createClassOnDemand(this.modules.i18nFormat);
Expand Down Expand Up @@ -2405,7 +2405,7 @@
setLngProps(l);
}
if (!_this4.translator.language) _this4.translator.changeLanguage(l);
if (_this4.services.languageDetector) _this4.services.languageDetector.cacheUserLanguage(l);
if (_this4.services.languageDetector && _this4.services.languageDetector.cacheUserLanguage) _this4.services.languageDetector.cacheUserLanguage(l);
}
_this4.loadResources(l, function (err) {
done(err, l);
Expand All @@ -2414,7 +2414,11 @@
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);
if (this.services.languageDetector.detect.length === 0) {
this.services.languageDetector.detect().then(setLng);
} else {
this.services.languageDetector.detect(setLng);
}
} else {
setLng(lng);
}
Expand Down
2 changes: 1 addition & 1 deletion i18next.min.js

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions index.d.ts
Expand Up @@ -1052,10 +1052,10 @@ export interface BackendModule<TOptions = object> extends Module {
*/
export interface LanguageDetectorModule extends Module {
type: 'languageDetector';
init(services: Services, detectorOptions: object, i18nextOptions: InitOptions): void;
init?(services: Services, detectorOptions: object, i18nextOptions: InitOptions): void;
/** Must return detected language */
detect(): string | readonly string[] | undefined;
cacheUserLanguage(lng: string): void;
cacheUserLanguage?(lng: string): void;
}

/**
Expand All @@ -1067,10 +1067,12 @@ export interface LanguageDetectorAsyncModule extends Module {
type: 'languageDetector';
/** Set to true to enable async detection */
async: true;
init(services: Services, detectorOptions: object, i18nextOptions: InitOptions): void;
/** Must call callback passing detected language */
detect(callback: (lng: string | readonly string[] | undefined) => void): void;
cacheUserLanguage(lng: string): void;
init?(services: Services, detectorOptions: object, i18nextOptions: InitOptions): void;
/** Must call callback passing detected language or return a Promise*/
detect(
callback: (lng: string | readonly string[] | undefined) => void | undefined,
): void | Promise<string | readonly string[] | undefined>;
cacheUserLanguage?(lng: string): void | Promise<void>;
}

/**
Expand Down
10 changes: 7 additions & 3 deletions src/i18next.js
Expand Up @@ -136,7 +136,7 @@ class I18n extends EventEmitter {

if (this.modules.languageDetector) {
s.languageDetector = createClassOnDemand(this.modules.languageDetector);
s.languageDetector.init(s, this.options.detection, this.options);
if (s.languageDetector.init) s.languageDetector.init(s, this.options.detection, this.options);
}

if (this.modules.i18nFormat) {
Expand Down Expand Up @@ -355,7 +355,7 @@ class I18n extends EventEmitter {
}
if (!this.translator.language) this.translator.changeLanguage(l);

if (this.services.languageDetector) this.services.languageDetector.cacheUserLanguage(l);
if (this.services.languageDetector && this.services.languageDetector.cacheUserLanguage) this.services.languageDetector.cacheUserLanguage(l);
}

this.loadResources(l, err => {
Expand All @@ -366,7 +366,11 @@ class I18n extends EventEmitter {
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);
if (this.services.languageDetector.detect.length === 0) {
this.services.languageDetector.detect().then(setLng);
} else {
this.services.languageDetector.detect(setLng);
}
} else {
setLng(lng);
}
Expand Down
119 changes: 119 additions & 0 deletions test/languageDetector.spec.js
@@ -0,0 +1,119 @@
import { createInstance } from '../src/index.js';
import { expect } from 'chai';

describe('LanguageDetector with different signatures', () => {
describe('LanguageDetector with minimal sync signature', () => {
let detectingLanguage = 'de-CH';
let i18n = createInstance();
i18n.use({
type: 'languageDetector',
detect: () => detectingLanguage,
});

describe('#init', () => {
it('should work as usual', () => {
i18n.init({ initImmediate: false });
expect(i18n.language).to.eql('de-CH');
});
});

describe('#changeLanguage', () => {
it('should detect the language accordingly', () => {
detectingLanguage = 'it';
i18n.changeLanguage();
expect(i18n.language).to.eql('it');
});
});
});

describe('LanguageDetector with full sync signature', () => {
let detectingLanguage = 'de-CH';
let cachedLng;
let i18n = createInstance();
i18n.use({
type: 'languageDetector',
init: () => {},
detect: () => detectingLanguage,
cacheUserLanguage: (l) => (cachedLng = l),
});

describe('#init', () => {
it('should work as usual', () => {
i18n.init({ initImmediate: false });
expect(i18n.language).to.eql('de-CH');
expect(cachedLng).to.eql('de-CH');
});
});

describe('#changeLanguage', () => {
it('should detect the language accordingly', () => {
detectingLanguage = 'it';
i18n.changeLanguage();
expect(i18n.language).to.eql('it');
expect(cachedLng).to.eql('it');
});
});
});

describe('LanguageDetector with async callback signature', () => {
let detectingLanguage = 'de-CH';
let i18n = createInstance();
i18n.use({
type: 'languageDetector',
async: true,
detect: (clb) => clb(detectingLanguage),
});

describe('#init', () => {
it('should work as usual', (done) => {
i18n.init({}, (err) => {
expect(i18n.language).to.eql('de-CH');
done(err);
});
});
});

describe('#changeLanguage', () => {
it('should detect the language accordingly', (done) => {
detectingLanguage = 'it';
i18n.changeLanguage(undefined, (err) => {
expect(i18n.language).to.eql('it');
done(err);
});
});
});
});

describe('LanguageDetector with async promise signature', () => {
let detectingLanguage = 'de-CH';
let cachedLng;
let i18n = createInstance();
i18n.use({
type: 'languageDetector',
async: true,
detect: async () => Promise.resolve(detectingLanguage),
cacheUserLanguage: async (l) => (cachedLng = l),
});

describe('#init', () => {
it('should work as usual', (done) => {
i18n.init({}, (err) => {
expect(i18n.language).to.eql('de-CH');
expect(cachedLng).to.eql('de-CH');
done(err);
});
});
});

describe('#changeLanguage', () => {
it('should detect the language accordingly', (done) => {
detectingLanguage = 'it';
i18n.changeLanguage(undefined, (err) => {
expect(i18n.language).to.eql('it');
expect(cachedLng).to.eql('it');
done(err);
});
});
});
});
});
36 changes: 36 additions & 0 deletions test/typescript/languageDetector.test.ts
@@ -0,0 +1,36 @@
import { createInstance, LanguageDetectorAsyncModule, LanguageDetectorModule } from 'i18next';

let i18n = createInstance();
i18n
.use<LanguageDetectorModule>({
type: 'languageDetector',
detect: () => 'en',
})
.init();

i18n = createInstance();
i18n
.use<LanguageDetectorAsyncModule>({
type: 'languageDetector',
async: true,
detect: (clb) => clb('en'),
})
.init();

i18n = createInstance();
i18n
.use<LanguageDetectorAsyncModule>({
type: 'languageDetector',
async: true,
detect: async () => 'en',
})
.init();

i18n = createInstance();
i18n
.use({
type: 'languageDetector',
async: true,
detect: (clb: (lng: string) => void) => clb('en'),
})
.init();

0 comments on commit 7e7c751

Please sign in to comment.