From e7440a3187a4848b8b224e680a30c0ffb6c86102 Mon Sep 17 00:00:00 2001 From: willianfalbo Date: Thu, 11 Jun 2020 03:27:19 -0300 Subject: [PATCH 1/2] added fallbacks #138 --- README.md | 20 ++ src/lib/interfaces/i18n-options.interface.ts | 2 + src/lib/services/i18n.service.ts | 22 +- tests/i18n-async.spec.ts | 64 +++++ tests/i18n-express.e2e.spec.ts | 286 ++++++++++++++++++ tests/i18n-fastify.e2e.spec.ts | 287 +++++++++++++++++++ tests/i18n-gql.e2e.spec.ts | 152 ++++++++++ tests/i18n.spec.ts | 73 ++++- tests/i18n/fr/test.json | 12 + tests/i18n/pt-BR/test.json | 12 + 10 files changed, 927 insertions(+), 3 deletions(-) create mode 100644 tests/i18n/fr/test.json create mode 100644 tests/i18n/pt-BR/test.json diff --git a/README.md b/README.md index f97af1c1..84da6fcd 100644 --- a/README.md +++ b/README.md @@ -290,6 +290,26 @@ I18nModule.forRootAsync({ }); ``` +#### Using Fallbacks + +To configure multiple fallbacks use `fallbacks` option. You could handle a single language or multiple ones. + +```typescript +I18nModule.forRoot({ + fallbackLanguage: 'en', + fallbacks: { + 'en-CA': 'fr', + 'en-*': 'en', + 'fr-*': 'fr', + 'pt': 'pt-BR', + }, + parser: I18nJsonParser, + parserOptions: { + path: path.join(__dirname, '/i18n/'), + }, +}); +``` + ### Translating with i18n module #### `I18nLang` decorator and `I18nService` diff --git a/src/lib/interfaces/i18n-options.interface.ts b/src/lib/interfaces/i18n-options.interface.ts index fbd70297..dfd15df5 100644 --- a/src/lib/interfaces/i18n-options.interface.ts +++ b/src/lib/interfaces/i18n-options.interface.ts @@ -20,6 +20,7 @@ export type I18nOptionResolver = export interface I18nOptions { fallbackLanguage: string; + fallbacks?: { [key: string]: string }; resolvers?: I18nOptionResolver[]; parser: Type; parserOptions: any; @@ -30,6 +31,7 @@ export interface I18nOptionsFactory { | Promise | I18nOptionsWithoutResolvers; } + export interface I18nAsyncOptions extends Pick { name?: string; useExisting?: Type; diff --git a/src/lib/services/i18n.service.ts b/src/lib/services/i18n.service.ts index b78c2562..8f48dd3f 100644 --- a/src/lib/services/i18n.service.ts +++ b/src/lib/services/i18n.service.ts @@ -34,7 +34,7 @@ export class I18nService { private readonly languagesSubject: BehaviorSubject, @Inject(I18N_TRANSLATIONS_SUBJECT) private readonly translationsSubject: BehaviorSubject, - ) {} + ) { } public async translate( key: string, @@ -53,6 +53,8 @@ export class I18nService { ? this.i18nOptions.fallbackLanguage : lang; + lang = await this.handleFallbacks(lang); + const translationsByLanguage = ( await this.translations.pipe(take(1)).toPromise() )[lang]; @@ -111,4 +113,22 @@ export class I18nService { this.languagesSubject.next(languages); } } + + private async handleFallbacks(lang: string) { + const supportedLanguages = await this.getSupportedLanguages(); + if (this.i18nOptions.fallbacks && !supportedLanguages.includes(lang)) { + const sanitizedLang = + lang.includes('-') + ? lang.substring(0, lang.indexOf('-')).concat('-*') + : lang; + + for (const key in this.i18nOptions.fallbacks) { + if (key === lang || key === sanitizedLang) { + lang = this.i18nOptions.fallbacks[key]; + break; + } + } + } + return lang; + } } diff --git a/tests/i18n-async.spec.ts b/tests/i18n-async.spec.ts index 611195b9..6bd51640 100644 --- a/tests/i18n-async.spec.ts +++ b/tests/i18n-async.spec.ts @@ -75,3 +75,67 @@ describe('i18n module without trailing slash in path', () => { ); }); }); + +describe('i18n async module with fallbacks', () => { + let i18nService: I18nService; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + imports: [ + I18nModule.forRootAsync({ + useFactory: () => { + return { + fallbackLanguage: 'en', + fallbacks: { + 'en-CA': 'fr', + 'en-*': 'en', + 'fr-*': 'fr', + 'pt': 'pt-BR', + }, + parserOptions: { + path: path.join(__dirname, '/i18n/'), + }, + }; + }, + parser: I18nJsonParser, + }), + ], + }).compile(); + + i18nService = module.get(I18nService); + }); + + it('i18n service should be defined', async () => { + expect(i18nService).toBeTruthy(); + }); + + it('i18n service should return correct translation', async () => { + expect(await i18nService.translate('test.HELLO')).toBe( + 'Hello', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'en' })).toBe( + 'Hello', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'en-US' })).toBe( + 'Hello', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'en-CA' })).toBe( + 'Bonjour', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'nl' })).toBe( + 'Hallo', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'fr' })).toBe( + 'Bonjour', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'fr-BE' })).toBe( + 'Bonjour', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'pt' })).toBe( + 'Olá', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'pt-BR' })).toBe( + 'Olá', + ); + }); +}); \ No newline at end of file diff --git a/tests/i18n-express.e2e.spec.ts b/tests/i18n-express.e2e.spec.ts index 7c0e7d7d..81b12556 100644 --- a/tests/i18n-express.e2e.spec.ts +++ b/tests/i18n-express.e2e.spec.ts @@ -21,6 +21,12 @@ describe('i18n module e2e express', () => { imports: [ I18nModule.forRoot({ fallbackLanguage: 'en', + fallbacks: { + 'en-CA': 'fr', + 'en-*': 'en', + 'fr-*': 'fr', + 'pt': 'pt-BR', + }, resolvers: [ { use: QueryResolver, options: ['lang', 'locale', 'l'] }, new HeaderResolver(['x-custom-lang']), @@ -54,6 +60,62 @@ describe('i18n module e2e express', () => { .expect('Hello'); }); + it(`/GET hello/short should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/short should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + it(`/GET hello should return right language when using query resolver`, () => { return request(app.getHttpServer()) .get('/hello?lang=nl') @@ -104,6 +166,62 @@ describe('i18n module e2e express', () => { .expect('Hello'); }); + it(`/GET hello/short/context should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short/context should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short/context should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/context should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/context should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/context should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/short/context should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + it(`/GET hello/context should return right language when using query resolver`, () => { return request(app.getHttpServer()) .get('/hello/context?lang=nl') @@ -141,6 +259,62 @@ describe('i18n module e2e express', () => { .expect('Hallo'); }); + it(`/GET hello/context should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/context should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/context should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/context should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/context should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/context should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/context should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + it(`/GET hello/request-scope should return translation`, () => { return request(app.getHttpServer()) .get('/hello/request-scope') @@ -155,6 +329,62 @@ describe('i18n module e2e express', () => { .expect('Hello'); }); + it(`/GET hello/short/request-scope should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short/request-scope should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short/request-scope should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/request-scope should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/request-scope should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/request-scope should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/short/request-scope should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + it(`/GET hello/request-scope should return right language when using query resolver`, () => { return request(app.getHttpServer()) .get('/hello/request-scope?lang=nl') @@ -192,6 +422,62 @@ describe('i18n module e2e express', () => { .expect('Hallo'); }); + it(`/GET hello/request-scope should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/request-scope should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/request-scope should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/request-scope should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/request-scope should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/request-scope should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/request-scope should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + afterAll(async () => { await app.close(); }); diff --git a/tests/i18n-fastify.e2e.spec.ts b/tests/i18n-fastify.e2e.spec.ts index 05a8e343..362adcff 100644 --- a/tests/i18n-fastify.e2e.spec.ts +++ b/tests/i18n-fastify.e2e.spec.ts @@ -24,6 +24,12 @@ describe('i18n module e2e fastify', () => { imports: [ I18nModule.forRoot({ fallbackLanguage: 'en', + fallbacks: { + 'en-CA': 'fr', + 'en-*': 'en', + 'fr-*': 'fr', + 'pt': 'pt-BR', + }, resolvers: [ { use: QueryResolver, options: ['lang', 'locale', 'l'] }, new HeaderResolver(['x-custom-lang']), @@ -59,6 +65,62 @@ describe('i18n module e2e fastify', () => { .expect('Hello'); }); + it(`/GET hello/short should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/short should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + it(`/GET hello should return right language when using query resolver`, () => { return request(app.getHttpServer()) .get('/hello?lang=nl') @@ -109,6 +171,62 @@ describe('i18n module e2e fastify', () => { .expect('Hello'); }); + it(`/GET hello/short/context should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short/context should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short/context should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/context should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/context should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/context should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/short/context should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/context') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + it(`/GET hello/context should return right language when using query resolver`, () => { return request(app.getHttpServer()) .get('/hello/context?lang=nl') @@ -146,6 +264,62 @@ describe('i18n module e2e fastify', () => { .expect('Hallo'); }); + it(`/GET hello/context should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/context should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/context should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/context should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/context should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/context should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/context should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/context') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + it(`/GET hello/request-scope should return translation`, () => { return request(app.getHttpServer()) .get('/hello/request-scope') @@ -160,6 +334,62 @@ describe('i18n module e2e fastify', () => { .expect('Hello'); }); + it(`/GET hello/short/request-scope should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short/request-scope should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/short/request-scope should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/request-scope should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/request-scope should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/short/request-scope should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/short/request-scope should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/short/request-scope') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + it(`/GET hello/request-scope should return right language when using query resolver`, () => { return request(app.getHttpServer()) .get('/hello/request-scope?lang=nl') @@ -196,6 +426,63 @@ describe('i18n module e2e fastify', () => { .expect(200) .expect('Hallo'); }); + + it(`/GET hello/request-scope should return english translation when sending "en" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'en') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/request-scope should return english translation when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'en-US') + .expect(200) + .expect('Hello'); + }); + + it(`/GET hello/request-scope should return french translation when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'en-CA') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/request-scope should return french translation when sending "fr" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'fr') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/request-scope should return french translation when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'fr-BE') + .expect(200) + .expect('Bonjour'); + }); + + it(`/GET hello/request-scope should return portuguese-brazil translation when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'pt') + .expect(200) + .expect('Olá'); + }); + + it(`/GET hello/request-scope should return portuguese-brazil translation when sending "pt-BR" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .get('/hello/request-scope') + .set('x-custom-lang', 'pt-BR') + .expect(200) + .expect('Olá'); + }); + afterAll(async () => { await app.close(); }); diff --git a/tests/i18n-gql.e2e.spec.ts b/tests/i18n-gql.e2e.spec.ts index a004c96a..af78b5e6 100644 --- a/tests/i18n-gql.e2e.spec.ts +++ b/tests/i18n-gql.e2e.spec.ts @@ -21,6 +21,12 @@ describe('i18n module e2e graphql', () => { imports: [ I18nModule.forRoot({ fallbackLanguage: 'en', + fallbacks: { + 'en-CA': 'fr', + 'en-*': 'en', + 'fr-*': 'fr', + 'pt': 'pt-BR', + }, resolvers: [ new HeaderResolver(['x-custom-lang']), new AcceptLanguageResolver(), @@ -108,6 +114,26 @@ describe('i18n module e2e graphql', () => { }); }); + it(`should query a particular cat in EN when not providing x-custom-lang`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .send({ + operationName: null, + variables: {}, + query: '{cat(id:2){id,name,age,description}}', + }) + .expect(200, { + data: { + cat: { + id: 2, + name: 'bar', + age: 6, + description: 'Cat', + }, + }, + }); + }); + it(`should query a particular cat in EN`, () => { return request(app.getHttpServer()) .post('/graphql') @@ -129,6 +155,132 @@ describe('i18n module e2e graphql', () => { }); }); + it(`should query a particular cat in EN when sending "en-US" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .set('x-custom-lang', 'en-US') + .send({ + operationName: null, + variables: {}, + query: '{cat(id:2){id,name,age,description}}', + }) + .expect(200, { + data: { + cat: { + id: 2, + name: 'bar', + age: 6, + description: 'Cat', + }, + }, + }); + }); + + it(`should query a particular cat in FR when sending "en-CA" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .set('x-custom-lang', 'en-CA') + .send({ + operationName: null, + variables: {}, + query: '{cat(id:2){id,name,age,description}}', + }) + .expect(200, { + data: { + cat: { + id: 2, + name: 'bar', + age: 6, + description: 'Chat', + }, + }, + }); + }); + + it(`should query a particular cat in FR`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .set('x-custom-lang', 'fr') + .send({ + operationName: null, + variables: {}, + query: '{cat(id:2){id,name,age,description}}', + }) + .expect(200, { + data: { + cat: { + id: 2, + name: 'bar', + age: 6, + description: 'Chat', + }, + }, + }); + }); + + it(`should query a particular cat in FR when sending "fr-BE" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .set('x-custom-lang', 'fr-BE') + .send({ + operationName: null, + variables: {}, + query: '{cat(id:2){id,name,age,description}}', + }) + .expect(200, { + data: { + cat: { + id: 2, + name: 'bar', + age: 6, + description: 'Chat', + }, + }, + }); + }); + + it(`should query a particular cat in PT-BR`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .set('x-custom-lang', 'pt-BR') + .send({ + operationName: null, + variables: {}, + query: '{cat(id:2){id,name,age,description}}', + }) + .expect(200, { + data: { + cat: { + id: 2, + name: 'bar', + age: 6, + description: 'Gato', + }, + }, + }); + }); + + it(`should query a particular cat in PT-BR when sending "pt" in x-custom-lang`, () => { + return request(app.getHttpServer()) + .post('/graphql') + .set('x-custom-lang', 'pt') + .send({ + operationName: null, + variables: {}, + query: '{cat(id:2){id,name,age,description}}', + }) + .expect(200, { + data: { + cat: { + id: 2, + name: 'bar', + age: 6, + description: 'Gato', + }, + }, + }); + }); + afterAll(async () => { await app.close(); }); diff --git a/tests/i18n.spec.ts b/tests/i18n.spec.ts index 0b60ffca..14f1b769 100644 --- a/tests/i18n.spec.ts +++ b/tests/i18n.spec.ts @@ -96,7 +96,7 @@ describe('i18n module', () => { ); }); - it('i18n service should return fallback translation if language not registed', async () => { + it('i18n service should return fallback translation if language not registered', async () => { expect(await i18nService.translate('test.ENGLISH', { lang: 'es' })).toBe( 'English', ); @@ -109,7 +109,7 @@ describe('i18n module', () => { }); it('i18n service should return supported languages', async () => { - expect(await i18nService.getSupportedLanguages()).toEqual(['en', 'nl']); + expect(await i18nService.getSupportedLanguages()).toEqual(['en', 'fr', 'nl', 'pt-BR']); }); describe('i18n should refresh manually', () => { @@ -330,3 +330,72 @@ describe('i18n module with parser watch', () => { expect(languages).toContain('de'); }); }); + +describe('i18n module with fallbacks', () => { + let i18nService: I18nService; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + imports: [ + I18nModule.forRoot({ + fallbackLanguage: 'en', + fallbacks: { + 'en-CA': 'fr', + 'en-*': 'en', + 'fr-*': 'fr', + 'pt': 'pt-BR', + }, + parser: I18nJsonParser, + parserOptions: { + path: path.join(__dirname, '/i18n'), + }, + }), + ], + }).compile(); + + i18nService = module.get(I18nService); + }); + + it('i18n service should be defined', async () => { + expect(i18nService).toBeTruthy(); + }); + + it('i18n service should return english translation', async () => { + expect(await i18nService.translate('test.HELLO')).toBe( + 'Hello', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'en' })).toBe( + 'Hello', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'en-US' })).toBe( + 'Hello', + ); + }); + + it('i18n service should return dutch translation', async () => { + expect(await i18nService.translate('test.HELLO', { lang: 'nl' })).toBe( + 'Hallo', + ); + }); + + it('i18n service should return french translation', async () => { + expect(await i18nService.translate('test.HELLO', { lang: 'fr' })).toBe( + 'Bonjour', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'fr-BE' })).toBe( + 'Bonjour', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'en-CA' })).toBe( + 'Bonjour', + ); + }); + + it('i18n service should return portuguese-brazil translation', async () => { + expect(await i18nService.translate('test.HELLO', { lang: 'pt' })).toBe( + 'Olá', + ); + expect(await i18nService.translate('test.HELLO', { lang: 'pt-BR' })).toBe( + 'Olá', + ); + }); +}); diff --git a/tests/i18n/fr/test.json b/tests/i18n/fr/test.json new file mode 100644 index 00000000..364eb721 --- /dev/null +++ b/tests/i18n/fr/test.json @@ -0,0 +1,12 @@ +{ + "HELLO": "Bonjour", + "PRODUCT": { + "NEW": "Nouveau Produit: {name}" + }, + "ARRAY": [ + "UN", + "DEUX", + "TROIS" + ], + "cat": "Chat" +} diff --git a/tests/i18n/pt-BR/test.json b/tests/i18n/pt-BR/test.json new file mode 100644 index 00000000..1b8ab5ce --- /dev/null +++ b/tests/i18n/pt-BR/test.json @@ -0,0 +1,12 @@ +{ + "HELLO": "Olá", + "PRODUCT": { + "NEW": "Novo Produto: {name}" + }, + "ARRAY": [ + "UM", + "DOIS", + "TRÊS" + ], + "cat": "Gato" +} From 407cbf37aac5283b4091410e5b0a1792b01d1900 Mon Sep 17 00:00:00 2001 From: Willian Falbo <5606714+willianfalbo@users.noreply.github.com> Date: Thu, 11 Jun 2020 11:52:49 -0300 Subject: [PATCH 2/2] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 84da6fcd..6da1d2bc 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,8 @@ I18nModule.forRootAsync({ To configure multiple fallbacks use `fallbacks` option. You could handle a single language or multiple ones. +> (note: In this example, the translations `en` `fr` and `pt-BR` are needed to work correctly.) + ```typescript I18nModule.forRoot({ fallbackLanguage: 'en',