2024-03-07 01:28:18 +08:00
|
|
|
import React, { useCallback, useContext, useEffect } from 'react';
|
|
|
|
import { IntlProvider } from 'react-intl';
|
|
|
|
import { LoadingContext } from '/imports/ui/components/common/loading-screen/loading-screen-HOC/component';
|
|
|
|
import useCurrentLocale from '/imports/ui/core/local-states/useCurrentLocale';
|
|
|
|
import logger from './logger';
|
|
|
|
|
|
|
|
interface LocaleJson {
|
|
|
|
[key: string]: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IntlLoaderContainerProps {
|
|
|
|
children: React.ReactNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IntlLoaderProps extends IntlLoaderContainerProps {
|
|
|
|
currentLocale: string;
|
|
|
|
setCurrentLocale: (locale: string) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
const buildFetchLocale = (locale: string) => {
|
|
|
|
const localesPath = 'locales';
|
|
|
|
return new Promise((resolve) => {
|
2024-07-03 01:58:58 +08:00
|
|
|
fetch(`${localesPath}/${locale !== 'index' ? `${locale}.json` : ''}`)
|
2024-03-07 01:28:18 +08:00
|
|
|
.then((response) => {
|
|
|
|
if (!response.ok) {
|
|
|
|
return resolve(false);
|
|
|
|
}
|
|
|
|
return response.json()
|
|
|
|
.then((jsonResponse) => resolve(jsonResponse))
|
|
|
|
.catch(() => {
|
|
|
|
logger.error({ logCode: 'intl_parse_locale_SyntaxError' }, `Could not parse locale file ${locale}.json, invalid json`);
|
|
|
|
resolve(false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2024-03-22 03:41:32 +08:00
|
|
|
const fetchLocaleOptions = (locale: string, init: boolean, localesList: string[] = []) => {
|
2024-07-16 21:40:13 +08:00
|
|
|
const clientSettings = window.meetingClientSettings.public;
|
|
|
|
const { fallbackLocale: fallback, overrideLocale: override } = clientSettings.app.defaultSettings.application;
|
2024-03-22 03:41:32 +08:00
|
|
|
const browserLocale = override && init ? override.split(/[-_]/g) : locale.split(/[-_]/g);
|
2024-07-16 21:40:13 +08:00
|
|
|
const defaultLanguage = fallback;
|
|
|
|
const fallbackOnEmptyString = clientSettings.app.fallbackOnEmptyLocaleString;
|
2024-03-22 03:41:32 +08:00
|
|
|
|
|
|
|
let localeFile = fallback;
|
|
|
|
let normalizedLocale: string = '';
|
|
|
|
|
|
|
|
const usableLocales = localesList
|
|
|
|
.map((file) => file.replace('.json', ''))
|
|
|
|
.reduce((locales: string[], locale: string) => (locale.match(browserLocale[0])
|
|
|
|
? [...locales, locale]
|
|
|
|
: locales), []);
|
|
|
|
|
|
|
|
const regionDefault = usableLocales.find((locale: string) => browserLocale[0] === locale);
|
|
|
|
|
|
|
|
if (browserLocale.length > 1) {
|
|
|
|
// browser asks for specific locale
|
|
|
|
normalizedLocale = `${browserLocale[0]}_${browserLocale[1]?.toUpperCase()}`;
|
|
|
|
|
|
|
|
const normDefault = usableLocales.find((locale) => normalizedLocale === locale);
|
|
|
|
if (normDefault) {
|
|
|
|
localeFile = normDefault;
|
|
|
|
} else if (regionDefault) {
|
|
|
|
localeFile = regionDefault;
|
|
|
|
} else {
|
|
|
|
const specFallback = usableLocales.find((locale) => browserLocale[0] === locale.split('_')[0]);
|
|
|
|
if (specFallback) localeFile = specFallback;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// browser asks for region default locale
|
|
|
|
// eslint-disable-next-line no-lonely-if
|
|
|
|
if (regionDefault && localeFile === fallback && regionDefault !== localeFile) {
|
|
|
|
localeFile = regionDefault;
|
|
|
|
} else {
|
|
|
|
const normFallback = usableLocales.find((locale) => browserLocale[0] === locale.split('_')[0]);
|
|
|
|
if (normFallback) localeFile = normFallback;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
normalizedLocale: localeFile,
|
|
|
|
regionDefaultLocale: (regionDefault && regionDefault !== localeFile) ? regionDefault : '',
|
|
|
|
defaultLocale: defaultLanguage,
|
|
|
|
fallbackOnEmptyLocaleString: fallbackOnEmptyString,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2024-03-07 01:28:18 +08:00
|
|
|
const IntlLoader: React.FC<IntlLoaderProps> = ({
|
|
|
|
children,
|
|
|
|
currentLocale,
|
|
|
|
setCurrentLocale,
|
|
|
|
}) => {
|
|
|
|
const loadingContextInfo = useContext(LoadingContext);
|
|
|
|
|
|
|
|
const [fetching, setFetching] = React.useState(false);
|
|
|
|
const [normalizedLocale, setNormalizedLocale] = React.useState(navigator.language.replace('_', '-'));
|
|
|
|
const [messages, setMessages] = React.useState<LocaleJson>({});
|
|
|
|
const [fallbackOnEmptyLocaleString, setFallbackOnEmptyLocaleString] = React.useState(false);
|
|
|
|
|
|
|
|
const fetchLocalizedMessages = useCallback((locale: string, init: boolean) => {
|
|
|
|
setFetching(true);
|
2024-03-22 03:41:32 +08:00
|
|
|
buildFetchLocale('index')
|
|
|
|
.then((resp) => {
|
2024-07-03 01:58:58 +08:00
|
|
|
const data = fetchLocaleOptions(
|
|
|
|
locale,
|
|
|
|
init,
|
|
|
|
(resp as { name: string }[]).map((l) => l.name),
|
|
|
|
);
|
2024-03-22 03:41:32 +08:00
|
|
|
|
2024-03-07 01:28:18 +08:00
|
|
|
const {
|
|
|
|
defaultLocale,
|
|
|
|
regionDefaultLocale,
|
|
|
|
normalizedLocale,
|
|
|
|
fallbackOnEmptyLocaleString: FOEL,
|
|
|
|
} = data;
|
|
|
|
setFallbackOnEmptyLocaleString(FOEL);
|
|
|
|
const languageSets = Array.from(new Set([
|
|
|
|
defaultLocale,
|
|
|
|
regionDefaultLocale,
|
|
|
|
normalizedLocale,
|
|
|
|
])).filter((locale) => locale);
|
|
|
|
|
|
|
|
Promise.all(languageSets.map((locale) => buildFetchLocale(locale)))
|
|
|
|
.then((resp) => {
|
|
|
|
const typedResp = resp as Array<LocaleJson | boolean>;
|
|
|
|
const foundLocales = typedResp.filter((locale) => locale instanceof Object) as LocaleJson[];
|
|
|
|
if (foundLocales.length === 0) {
|
|
|
|
const error = `${{ logCode: 'intl_fetch_locale_error' }},Could not fetch any locale file for ${languageSets.join(', ')}`;
|
|
|
|
loadingContextInfo.setLoading(false, '');
|
|
|
|
logger.error(error);
|
|
|
|
throw new Error(error);
|
|
|
|
}
|
|
|
|
const mergedLocale = foundLocales
|
|
|
|
.reduce((acc, locale: LocaleJson) => Object.assign(acc, locale), {});
|
|
|
|
const replacedLocale = normalizedLocale.replace('_', '-');
|
|
|
|
setNormalizedLocale(replacedLocale);
|
|
|
|
setCurrentLocale(replacedLocale);
|
|
|
|
setMessages(mergedLocale);
|
|
|
|
if (!init) {
|
|
|
|
loadingContextInfo.setLoading(false, '');
|
|
|
|
}
|
|
|
|
}).catch((error) => {
|
|
|
|
loadingContextInfo.setLoading(false, '');
|
|
|
|
throw new Error(error);
|
|
|
|
});
|
2024-03-22 03:41:32 +08:00
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
loadingContextInfo.setLoading(false, '');
|
|
|
|
throw new Error('unable to fetch localized messages');
|
2024-03-07 01:28:18 +08:00
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const language = navigator.languages ? navigator.languages[0] : navigator.language;
|
|
|
|
fetchLocalizedMessages(language, true);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (currentLocale !== normalizedLocale) {
|
|
|
|
fetchLocalizedMessages(currentLocale, false);
|
|
|
|
}
|
|
|
|
}, [currentLocale]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (fetching) {
|
2024-03-22 03:41:32 +08:00
|
|
|
logger.info('Fetching locale');
|
2024-03-07 01:28:18 +08:00
|
|
|
}
|
|
|
|
}, [fetching]);
|
|
|
|
|
|
|
|
return !fetching || Object.keys(messages).length > 0 ? (
|
|
|
|
<IntlProvider
|
|
|
|
fallbackOnEmptyString={fallbackOnEmptyLocaleString}
|
|
|
|
locale={normalizedLocale.replace('_', '-').replace('@', '-')}
|
|
|
|
messages={messages}
|
|
|
|
>
|
|
|
|
{children}
|
|
|
|
</IntlProvider>
|
|
|
|
) : null;
|
|
|
|
};
|
|
|
|
|
|
|
|
const IntlLoaderContainer: React.FC<IntlLoaderContainerProps> = ({
|
|
|
|
children,
|
|
|
|
}) => {
|
|
|
|
const [currentLocale, setCurrentLocale] = useCurrentLocale();
|
|
|
|
return (
|
|
|
|
<IntlLoader
|
|
|
|
currentLocale={currentLocale}
|
|
|
|
setCurrentLocale={setCurrentLocale}
|
|
|
|
>
|
|
|
|
{children}
|
|
|
|
</IntlLoader>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default IntlLoaderContainer;
|