Skip to content

Commit

Permalink
refactor: isolate i18n configuration in api
Browse files Browse the repository at this point in the history
  • Loading branch information
bpetetot authored Nov 12, 2024
1 parent 498e1de commit 5285d9e
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 46 deletions.
2 changes: 1 addition & 1 deletion api/src/shared/domain/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const LOCALE = {
SPANISH_SPOKEN: 'es',
};

const SUPPORTED_LOCALES = ['en', 'es', 'fr', 'fr-BE', 'fr-FR', 'nl-BE'];
const SUPPORTED_LOCALES = ['en', 'es', 'fr', 'fr-BE', 'fr-FR', 'nl-BE', 'nl'];

const ORGANIZATION_FEATURE = {
MISSIONS_MANAGEMENT: {
Expand Down
54 changes: 54 additions & 0 deletions api/src/shared/infrastructure/i18n/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import path from 'node:path';

import { I18n } from 'i18n';

import { SUPPORTED_LOCALES } from '../../domain/constants.js';
import { logger } from '../utils/logger.js';

const __dirname = import.meta.dirname;
const translationsFolder = path.resolve(path.join(__dirname, '../../../../translations'));

const supportedLocales = SUPPORTED_LOCALES.map((supportedLocale) => supportedLocale.toLowerCase());

const DEFAULT_LOCALE = 'fr';

export const options = {
locales: ['en', 'fr', 'es', 'nl'],
fallbacks: { 'en-*': 'en', 'fr-*': 'fr', 'es-*': 'es', 'nl-*': 'nl' },
defaultLocale: DEFAULT_LOCALE,
directory: translationsFolder,
queryParameter: 'lang',
languageHeaderField: 'Accept-Language',
objectNotation: true,
updateFiles: false,
mustacheConfig: {
tags: ['{', '}'],
disable: false,
},
};

// This is an optimization to avoid settings a new instance each time
// we need to use i18n.
const i18nInstances = {};

/**
* @param {string} locale a locale (language or BCP 47 format)
* @returns i18n instance correctly setup with the language
*/
export function getI18n(locale) {
if (!locale || !supportedLocales.includes(locale?.toLowerCase())) {
return getI18n(DEFAULT_LOCALE);
}

if (!i18nInstances[locale]) {
const i18n = new I18n(options);
i18n.setLocale(locale);
// we freeze the setLocale to avoid changing i18n locale for an instance
i18n.setLocale = () => {
logger.warn('Cannot change i18n locale instance, use getI18n(locale) instead.');
};
i18nInstances[locale] = i18n;
}

return i18nInstances[locale];
}
19 changes: 3 additions & 16 deletions api/src/shared/infrastructure/plugins/i18n.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import * as url from 'node:url';

import hapiI18n from 'hapi-i18n';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));

import { options } from '../i18n/i18n.js';

const plugin = hapiI18n;
const options = {
locales: ['en', 'fr', 'es', 'nl'],
directory: __dirname + '../../../../translations',
defaultLocale: 'fr',
queryParameter: 'lang',
languageHeaderField: 'Accept-Language',
objectNotation: true,
updateFiles: false,
mustacheConfig: {
tags: ['{', '}'],
disable: false,
},
};

export { options, plugin };
59 changes: 59 additions & 0 deletions api/tests/shared/unit/infrastructure/i18n/i18n_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { getI18n, options } from '../../../../../src/shared/infrastructure/i18n/i18n.js';
import { expect } from '../../../../test-helper.js';

describe('Unit | Shared | Infrastucture | i18n', function () {
describe('default i18n options', function () {
it('returns i18n options', function () {
expect(options).to.have.property('locales').that.includes('en', 'fr', 'es', 'nl');
expect(options).to.have.property('directory').that.is.a('string');
expect(options).to.have.property('defaultLocale', 'fr');
expect(options).to.have.property('queryParameter', 'lang');
expect(options).to.have.property('languageHeaderField', 'Accept-Language');
expect(options).to.have.property('objectNotation', true);
expect(options).to.have.property('updateFiles', false);
expect(options)
.to.have.property('mustacheConfig')
.to.deep.equal({
tags: ['{', '}'],
disable: false,
});
});
});

describe('getI18n', function () {
it('returns an instance of i18n with default locale', function () {
const i18n = getI18n();
expect(i18n.getLocale()).to.equal('fr');
});

it('returns an instance of i18n with the specified locale', function () {
const locale = 'es';
const i18n = getI18n(locale);
expect(i18n.getLocale()).to.equal(locale);
});

context('when the locale is BCP 47 format', function () {
it('returns the correct locale instance of i18n', function () {
const locale = 'nl-BE';
const i18n = getI18n(locale);
expect(i18n.getLocale()).to.equal('nl');
});
});

context('when the locale is not supported', function () {
it('returns the default locale instance of i18n', function () {
const locale = 'foo';
const i18n = getI18n(locale);
expect(i18n.getLocale()).to.equal('fr');
});
});

context('when the i18n setLocale is called on an i18n instance', function () {
it('does not change the instance locale', function () {
const i18n1 = getI18n('fr');
i18n1.setLocale('en');
expect(i18n1.getLocale()).to.equal('fr');
});
});
});
});
20 changes: 0 additions & 20 deletions api/tests/tooling/i18n/i18n.js

This file was deleted.

9 changes: 0 additions & 9 deletions api/tests/tooling/i18n/i18n_test.js

This file was deleted.

0 comments on commit 5285d9e

Please sign in to comment.