2024-04-16 11:47:24 +01:00
|
|
|
import i18n, { InitOptions, TFunction } from 'i18next';
|
2023-07-14 12:24:50 +02:00
|
|
|
import LanguageDetector, { DetectorOptions } from 'i18next-browser-languagedetector';
|
2022-10-06 16:34:04 +01:00
|
|
|
import React from 'react';
|
2022-10-07 11:46:28 +01:00
|
|
|
import { Trans as I18NextTrans, initReactI18next } from 'react-i18next'; // eslint-disable-line no-restricted-imports
|
2022-06-17 13:41:03 +01:00
|
|
|
|
2024-04-16 09:53:23 +01:00
|
|
|
import { DEFAULT_LANGUAGE, VALID_LANGUAGES } from './constants';
|
|
|
|
|
import { loadTranslations } from './loadTranslations';
|
2021-12-15 16:00:37 +00:00
|
|
|
|
2024-04-16 11:47:24 +01:00
|
|
|
let tFunc: TFunction<string[], undefined> | undefined;
|
|
|
|
|
|
2024-03-18 11:00:43 +00:00
|
|
|
export function initializeI18n(language: string): Promise<{ language: string | undefined }> {
|
2023-02-08 13:15:37 +00:00
|
|
|
// This is a placeholder so we can put a 'comment' in the message json files.
|
2023-07-11 16:37:01 +01:00
|
|
|
// Starts with an underscore so it's sorted to the top of the file. Even though it is in a comment the following line is still extracted
|
|
|
|
|
// t('_comment', 'This file is the source of truth for English strings. Edit this to change plurals and other phrases for the UI.');
|
2023-02-08 13:15:37 +00:00
|
|
|
|
2023-07-14 12:24:50 +02:00
|
|
|
const options: InitOptions = {
|
|
|
|
|
// We don't bundle any translations, we load them async
|
|
|
|
|
partialBundledLanguages: true,
|
|
|
|
|
resources: {},
|
|
|
|
|
|
|
|
|
|
// If translations are empty strings (no translation), fall back to the default value in source code
|
|
|
|
|
returnEmptyString: false,
|
2024-03-18 11:00:43 +00:00
|
|
|
|
|
|
|
|
// Required to ensure that `resolvedLanguage` is set property when an invalid language is passed (such as through 'detect')
|
|
|
|
|
supportedLngs: VALID_LANGUAGES,
|
|
|
|
|
fallbackLng: DEFAULT_LANGUAGE,
|
2023-07-14 12:24:50 +02:00
|
|
|
};
|
2024-03-18 11:00:43 +00:00
|
|
|
let i18nInstance = i18n;
|
2023-07-14 12:24:50 +02:00
|
|
|
if (language === 'detect') {
|
2024-03-18 11:00:43 +00:00
|
|
|
i18nInstance = i18nInstance.use(LanguageDetector);
|
2023-07-14 12:24:50 +02:00
|
|
|
const detection: DetectorOptions = { order: ['navigator'], caches: [] };
|
|
|
|
|
options.detection = detection;
|
|
|
|
|
} else {
|
|
|
|
|
options.lng = VALID_LANGUAGES.includes(language) ? language : undefined;
|
|
|
|
|
}
|
2024-03-18 11:00:43 +00:00
|
|
|
|
|
|
|
|
const loadPromise = i18nInstance
|
2022-10-06 16:34:04 +01:00
|
|
|
.use(loadTranslations)
|
|
|
|
|
.use(initReactI18next) // passes i18n down to react-i18next
|
2023-07-14 12:24:50 +02:00
|
|
|
.init(options);
|
2024-03-18 11:00:43 +00:00
|
|
|
|
2024-04-16 11:47:24 +01:00
|
|
|
tFunc = i18n.t;
|
|
|
|
|
|
2024-03-18 11:00:43 +00:00
|
|
|
return loadPromise.then(() => {
|
|
|
|
|
return {
|
|
|
|
|
language: i18nInstance.resolvedLanguage,
|
|
|
|
|
};
|
|
|
|
|
});
|
2021-12-15 16:00:37 +00:00
|
|
|
}
|
|
|
|
|
|
2022-10-06 16:34:04 +01:00
|
|
|
export function changeLanguage(locale: string) {
|
2023-07-14 12:24:50 +02:00
|
|
|
const validLocale = VALID_LANGUAGES.includes(locale) ? locale : undefined;
|
2022-10-06 16:34:04 +01:00
|
|
|
return i18n.changeLanguage(validLocale);
|
2021-12-15 16:00:37 +00:00
|
|
|
}
|
2022-06-17 13:41:03 +01:00
|
|
|
|
2022-10-06 16:34:04 +01:00
|
|
|
export const Trans: typeof I18NextTrans = (props) => {
|
2024-01-16 12:22:32 +01:00
|
|
|
return <I18NextTrans shouldUnescape {...props} />;
|
2022-10-06 16:34:04 +01:00
|
|
|
};
|
2021-12-15 16:00:37 +00:00
|
|
|
|
2024-04-16 11:47:24 +01:00
|
|
|
// Wrap t() to provide default namespaces and enforce a consistent API
|
2022-10-06 16:34:04 +01:00
|
|
|
export const t = (id: string, defaultMessage: string, values?: Record<string, unknown>) => {
|
2024-04-16 11:47:24 +01:00
|
|
|
if (!tFunc) {
|
|
|
|
|
if (process.env.NODE_ENV !== 'test') {
|
|
|
|
|
console.warn(
|
|
|
|
|
't() was called before i18n was initialized. This is probably caused by calling t() in the root module scope, instead of lazily on render'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
|
|
|
throw new Error('t() was called before i18n was initialized');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tFunc = i18n.t;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-04 15:28:07 +00:00
|
|
|
return tFunc(id, defaultMessage, values);
|
2022-10-06 16:34:04 +01:00
|
|
|
};
|
2022-07-25 13:44:11 +01:00
|
|
|
|
2022-10-06 16:34:04 +01:00
|
|
|
export const i18nDate = (value: number | Date | string, format: Intl.DateTimeFormatOptions = {}): string => {
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
return i18nDate(new Date(value), format);
|
|
|
|
|
}
|
2023-07-14 12:24:50 +02:00
|
|
|
const dateFormatter = new Intl.DateTimeFormat(i18n.language, format);
|
2022-10-06 16:34:04 +01:00
|
|
|
return dateFormatter.format(value);
|
|
|
|
|
};
|