bigbluebutton-Github/bigbluebutton-html5/imports/startup/client/intlLoader.tsx

196 lines
6.6 KiB
TypeScript
Raw Normal View History

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` : ''}`)
.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);
});
});
});
};
const fetchLocaleOptions = (locale: string, init: boolean, localesList: string[] = []) => {
const clientSettings = window.meetingClientSettings.public;
const { fallbackLocale: fallback, overrideLocale: override } = clientSettings.app.defaultSettings.application;
const browserLocale = override && init ? override.split(/[-_]/g) : locale.split(/[-_]/g);
const defaultLanguage = fallback;
const fallbackOnEmptyString = clientSettings.app.fallbackOnEmptyLocaleString;
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,
};
};
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);
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),
);
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);
});
})
.catch(() => {
loadingContextInfo.setLoading(false, '');
throw new Error('unable to fetch localized messages');
});
}, []);
useEffect(() => {
const language = navigator.languages ? navigator.languages[0] : navigator.language;
fetchLocalizedMessages(language, true);
}, []);
useEffect(() => {
if (currentLocale !== normalizedLocale) {
fetchLocalizedMessages(currentLocale, false);
}
}, [currentLocale]);
useEffect(() => {
if (fetching) {
logger.info('Fetching locale');
}
}, [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;