This commit is contained in:
189
react/features/base/i18n/BuiltinLanguages.native.ts
Normal file
189
react/features/base/i18n/BuiltinLanguages.native.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
/**
|
||||
* The builtin languages.
|
||||
*/
|
||||
const _LANGUAGES = {
|
||||
|
||||
// Afrikaans
|
||||
'af': {
|
||||
main: require('../../../../lang/main-af')
|
||||
},
|
||||
|
||||
// Arabic
|
||||
'ar': {
|
||||
main: require('../../../../lang/main-ar')
|
||||
},
|
||||
|
||||
// Bulgarian
|
||||
'bg': {
|
||||
main: require('../../../../lang/main-bg')
|
||||
},
|
||||
|
||||
// Catalan
|
||||
'ca': {
|
||||
main: require('../../../../lang/main-ca')
|
||||
},
|
||||
|
||||
// German
|
||||
'de': {
|
||||
main: require('../../../../lang/main-de')
|
||||
},
|
||||
|
||||
// Esperanto
|
||||
'eo': {
|
||||
main: require('../../../../lang/main-eo')
|
||||
},
|
||||
|
||||
// Spanish
|
||||
'es': {
|
||||
main: require('../../../../lang/main-es')
|
||||
},
|
||||
|
||||
// Spanish (Latin America)
|
||||
'esUS': {
|
||||
main: require('../../../../lang/main-esUS')
|
||||
},
|
||||
|
||||
// Estonian
|
||||
'et': {
|
||||
main: require('../../../../lang/main-et')
|
||||
},
|
||||
|
||||
// Persian
|
||||
'fa': {
|
||||
main: require('../../../../lang/main-fa')
|
||||
},
|
||||
|
||||
// Finnish
|
||||
'fi': {
|
||||
main: require('../../../../lang/main-fi')
|
||||
},
|
||||
|
||||
// French
|
||||
'fr': {
|
||||
main: require('../../../../lang/main-fr')
|
||||
},
|
||||
|
||||
// French (Canadian)
|
||||
'frCA': {
|
||||
main: require('../../../../lang/main-frCA')
|
||||
},
|
||||
|
||||
// Croatian
|
||||
'hr': {
|
||||
main: require('../../../../lang/main-hr')
|
||||
},
|
||||
|
||||
// Hungarian
|
||||
'hu': {
|
||||
main: require('../../../../lang/main-hu')
|
||||
},
|
||||
|
||||
// Italian
|
||||
'it': {
|
||||
main: require('../../../../lang/main-it')
|
||||
},
|
||||
|
||||
// Japanese
|
||||
'ja': {
|
||||
main: require('../../../../lang/main-ja')
|
||||
},
|
||||
|
||||
// Korean
|
||||
'ko': {
|
||||
main: require('../../../../lang/main-ko')
|
||||
},
|
||||
|
||||
// Mongolian
|
||||
'mn': {
|
||||
main: require('../../../../lang/main-mn')
|
||||
},
|
||||
|
||||
// Dutch
|
||||
'nl': {
|
||||
main: require('../../../../lang/main-nl')
|
||||
},
|
||||
|
||||
// Occitan
|
||||
'oc': {
|
||||
main: require('../../../../lang/main-oc')
|
||||
},
|
||||
|
||||
// Polish
|
||||
'pl': {
|
||||
main: require('../../../../lang/main-pl')
|
||||
},
|
||||
|
||||
// Portuguese (Brazil)
|
||||
'ptBR': {
|
||||
main: require('../../../../lang/main-ptBR')
|
||||
},
|
||||
|
||||
// Romanian
|
||||
'ro': {
|
||||
main: require('../../../../lang/main-ro')
|
||||
},
|
||||
|
||||
// Russian
|
||||
'ru': {
|
||||
main: require('../../../../lang/main-ru')
|
||||
},
|
||||
|
||||
// Sardinian (Sardinia)
|
||||
'sc': {
|
||||
main: require('../../../../lang/main-sc')
|
||||
},
|
||||
|
||||
// Slovak
|
||||
'sk': {
|
||||
main: require('../../../../lang/main-sk')
|
||||
},
|
||||
|
||||
// Slovenian
|
||||
'sl': {
|
||||
main: require('../../../../lang/main-sl')
|
||||
},
|
||||
|
||||
// Swedish
|
||||
'sv': {
|
||||
main: require('../../../../lang/main-sv')
|
||||
},
|
||||
|
||||
// Turkish
|
||||
'tr': {
|
||||
main: require('../../../../lang/main-tr')
|
||||
},
|
||||
|
||||
// Ukrainian
|
||||
'uk': {
|
||||
main: require('../../../../lang/main-uk')
|
||||
},
|
||||
|
||||
// Vietnamese
|
||||
'vi': {
|
||||
main: require('../../../../lang/main-vi')
|
||||
},
|
||||
|
||||
// Chinese (Simplified)
|
||||
'zhCN': {
|
||||
main: require('../../../../lang/main-zhCN')
|
||||
},
|
||||
|
||||
// Chinese (Traditional)
|
||||
'zhTW': {
|
||||
main: require('../../../../lang/main-zhTW')
|
||||
}
|
||||
};
|
||||
|
||||
// Register all builtin languages with the i18n library.
|
||||
for (const name in _LANGUAGES) { // eslint-disable-line guard-for-in
|
||||
const { main } = _LANGUAGES[name as keyof typeof _LANGUAGES];
|
||||
|
||||
i18next.addResourceBundle(
|
||||
name,
|
||||
'main',
|
||||
main,
|
||||
/* deep */ true,
|
||||
/* overwrite */ true);
|
||||
}
|
||||
0
react/features/base/i18n/BuiltinLanguages.web.ts
Normal file
0
react/features/base/i18n/BuiltinLanguages.web.ts
Normal file
9
react/features/base/i18n/actionTypes.ts
Normal file
9
react/features/base/i18n/actionTypes.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* The type of (redux) action which signals that i18next has been initialized.
|
||||
*/
|
||||
export const I18NEXT_INITIALIZED = 'I18NEXT_INITIALIZED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that language has been changed.
|
||||
*/
|
||||
export const LANGUAGE_CHANGED = 'LANGUAGE_CHANGED';
|
||||
28
react/features/base/i18n/configLanguageDetector.ts
Normal file
28
react/features/base/i18n/configLanguageDetector.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
declare let config: any;
|
||||
|
||||
/**
|
||||
* Custom language detection, just returns the config property if any.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Does not support caching.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
cacheUserLanguage: Function.prototype,
|
||||
|
||||
/**
|
||||
* Looks the language up in the config.
|
||||
*
|
||||
* @returns {string} The default language if any.
|
||||
*/
|
||||
lookup() {
|
||||
return config.defaultLanguage;
|
||||
},
|
||||
|
||||
/**
|
||||
* Name of the language detector.
|
||||
*/
|
||||
name: 'configLanguageDetector'
|
||||
};
|
||||
67
react/features/base/i18n/customNavigatorDetector.ts
Normal file
67
react/features/base/i18n/customNavigatorDetector.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
declare let navigator: any;
|
||||
|
||||
/**
|
||||
* Custom language detection, just returns the config property if any.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Does not support caching.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
cacheUserLanguage: Function.prototype,
|
||||
|
||||
/**
|
||||
* Looks the language up in the config.
|
||||
*
|
||||
* @returns {string} The default language if any.
|
||||
*/
|
||||
lookup() {
|
||||
let found = [];
|
||||
|
||||
if (typeof navigator !== 'undefined') {
|
||||
if (navigator.languages) {
|
||||
// chrome only; not an array, so can't use .push.apply instead of iterating
|
||||
for (let i = 0; i < navigator.languages.length; i++) {
|
||||
found.push(navigator.languages[i]);
|
||||
}
|
||||
}
|
||||
if (navigator.userLanguage) {
|
||||
found.push(navigator.userLanguage);
|
||||
}
|
||||
if (navigator.language) {
|
||||
found.push(navigator.language);
|
||||
}
|
||||
}
|
||||
|
||||
found = found.map<string>(normalizeLanguage);
|
||||
|
||||
return found.length > 0 ? found : undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Name of the language detector.
|
||||
*/
|
||||
name: 'customNavigatorDetector'
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize language format.
|
||||
*
|
||||
* (en-US => enUS)
|
||||
* (en-gb => enGB)
|
||||
* (es-es => es).
|
||||
*
|
||||
* @param {string} language - Language.
|
||||
* @returns {string} The normalized language.
|
||||
*/
|
||||
function normalizeLanguage(language: string) {
|
||||
const [ lang, variant ] = language.replace('_', '-').split('-');
|
||||
|
||||
if (!variant || lang.toUpperCase() === variant.toUpperCase()) {
|
||||
return lang;
|
||||
}
|
||||
|
||||
return lang + variant.toUpperCase();
|
||||
}
|
||||
150
react/features/base/i18n/dateUtil.ts
Normal file
150
react/features/base/i18n/dateUtil.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import dayjs from 'dayjs';
|
||||
import durationPlugin from 'dayjs/plugin/duration';
|
||||
import localizedFormatPlugin from 'dayjs/plugin/localizedFormat';
|
||||
import relativeTimePlugin from 'dayjs/plugin/relativeTime';
|
||||
|
||||
import i18next from './i18next';
|
||||
|
||||
dayjs.extend(durationPlugin);
|
||||
dayjs.extend(relativeTimePlugin);
|
||||
dayjs.extend(localizedFormatPlugin);
|
||||
|
||||
// Day.js uses static language bundle loading, so in order to support dynamic
|
||||
// language selection in the app we need to load all bundles that we support in
|
||||
// the app.
|
||||
import 'dayjs/locale/af';
|
||||
import 'dayjs/locale/ar';
|
||||
import 'dayjs/locale/be';
|
||||
import 'dayjs/locale/bg';
|
||||
import 'dayjs/locale/ca';
|
||||
import 'dayjs/locale/cs';
|
||||
import 'dayjs/locale/da';
|
||||
import 'dayjs/locale/de';
|
||||
import 'dayjs/locale/el';
|
||||
import 'dayjs/locale/eo';
|
||||
import 'dayjs/locale/es';
|
||||
import 'dayjs/locale/es-us';
|
||||
import 'dayjs/locale/et';
|
||||
import 'dayjs/locale/eu';
|
||||
import 'dayjs/locale/fa';
|
||||
import 'dayjs/locale/fi';
|
||||
import 'dayjs/locale/fr';
|
||||
import 'dayjs/locale/fr-ca';
|
||||
import 'dayjs/locale/gl';
|
||||
import 'dayjs/locale/he';
|
||||
import 'dayjs/locale/hi';
|
||||
import 'dayjs/locale/hr';
|
||||
import 'dayjs/locale/hu';
|
||||
import 'dayjs/locale/hy-am';
|
||||
import 'dayjs/locale/id';
|
||||
import 'dayjs/locale/is';
|
||||
import 'dayjs/locale/it';
|
||||
import 'dayjs/locale/ja';
|
||||
import 'dayjs/locale/ko';
|
||||
import 'dayjs/locale/lt';
|
||||
import 'dayjs/locale/lv';
|
||||
import 'dayjs/locale/ml';
|
||||
import 'dayjs/locale/mn';
|
||||
import 'dayjs/locale/mr';
|
||||
import 'dayjs/locale/nb';
|
||||
import 'dayjs/locale/nl';
|
||||
import 'dayjs/locale/oc-lnc';
|
||||
import 'dayjs/locale/pl';
|
||||
import 'dayjs/locale/pt';
|
||||
import 'dayjs/locale/pt-br';
|
||||
import 'dayjs/locale/ro';
|
||||
import 'dayjs/locale/ru';
|
||||
import 'dayjs/locale/sk';
|
||||
import 'dayjs/locale/sl';
|
||||
import 'dayjs/locale/sq';
|
||||
import 'dayjs/locale/sr';
|
||||
import 'dayjs/locale/sv';
|
||||
import 'dayjs/locale/te';
|
||||
import 'dayjs/locale/tr';
|
||||
import 'dayjs/locale/uk';
|
||||
import 'dayjs/locale/vi';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import 'dayjs/locale/zh-tw';
|
||||
|
||||
const LOCALE_MAPPING: Record<string, string> = {
|
||||
// i18next -> dayjs
|
||||
'hy': 'hy-am',
|
||||
'oc': 'oc-lnc',
|
||||
'zhCN': 'zh-cn',
|
||||
'zhTW': 'zh-tw',
|
||||
'ptBR': 'pt-br',
|
||||
'esUS': 'es-us',
|
||||
'frCA': 'fr-ca'
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a localized date formatter initialized with a specific {@code Date}
|
||||
* or timestamp ({@code number}).
|
||||
*
|
||||
* @private
|
||||
* @param {Date | number} dateOrTimeStamp - The date or unix timestamp (ms)
|
||||
* to format.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getLocalizedDateFormatter(dateOrTimeStamp: Date | number) {
|
||||
return dayjs(dateOrTimeStamp).locale(_getSupportedLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a localized duration formatter initialized with a
|
||||
* specific duration ({@code number}).
|
||||
*
|
||||
* @private
|
||||
* @param {number} duration - The duration (ms)
|
||||
* to format.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getLocalizedDurationFormatter(duration: number) {
|
||||
// If the conference is under an hour long we want to display it without
|
||||
// showing the hour and we want to include the hour if the conference is
|
||||
// more than an hour long
|
||||
|
||||
const d = dayjs.duration(duration);
|
||||
|
||||
if (d.hours() !== 0) {
|
||||
return d.format('H:mm:ss');
|
||||
}
|
||||
|
||||
return d.format('mm:ss');
|
||||
}
|
||||
|
||||
/**
|
||||
* A lenient locale matcher to match language and dialect if possible.
|
||||
*
|
||||
* @private
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getSupportedLocale() {
|
||||
const availableLocales = Object.keys(dayjs.Ls);
|
||||
const i18nLocale = i18next.language;
|
||||
let supportedLocale;
|
||||
|
||||
if (LOCALE_MAPPING[i18nLocale]) {
|
||||
return LOCALE_MAPPING[i18nLocale];
|
||||
}
|
||||
|
||||
if (availableLocales.includes(i18nLocale)) {
|
||||
return i18nLocale;
|
||||
}
|
||||
|
||||
if (i18nLocale) {
|
||||
const localeRegexp = new RegExp('^([a-z]{2,2})(-)*([a-z]{2,2})*$');
|
||||
const localeResult = localeRegexp.exec(i18nLocale.toLowerCase());
|
||||
|
||||
if (localeResult) {
|
||||
const currentLocaleRegexp
|
||||
= new RegExp(
|
||||
`^${localeResult[1]}(-)*${`(${localeResult[3]})*` || ''}`);
|
||||
|
||||
supportedLocale
|
||||
= availableLocales.find(lang => currentLocaleRegexp.exec(lang));
|
||||
}
|
||||
}
|
||||
|
||||
return supportedLocale || 'en';
|
||||
}
|
||||
45
react/features/base/i18n/functions.tsx
Normal file
45
react/features/base/i18n/functions.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
||||
|
||||
import i18next from './i18next';
|
||||
|
||||
/**
|
||||
* Changes the main translation bundle.
|
||||
*
|
||||
* @param {string} language - The language e.g. 'en', 'fr'.
|
||||
* @param {string} url - The url of the translation bundle.
|
||||
* @returns {void}
|
||||
*/
|
||||
export async function changeLanguageBundle(language: string, url: string) {
|
||||
const res = await fetch(url);
|
||||
const bundle = await res.json();
|
||||
|
||||
i18next.addResourceBundle(language, 'main', bundle, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a specific React Component in order to enable translations in it.
|
||||
*
|
||||
* @param {Component} component - The React Component to wrap.
|
||||
* @returns {Component} The React Component which wraps {@link component} and
|
||||
* enables translations in it.
|
||||
*/
|
||||
export function translate<P extends WithTranslation>(component: React.ComponentType<P>) {
|
||||
// Use the default list of namespaces.
|
||||
return withTranslation([ 'main', 'languages', 'countries' ])(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a specific key to text containing HTML via a specific translate
|
||||
* function.
|
||||
*
|
||||
* @param {Function} t - The translate function.
|
||||
* @param {string} key - The key to translate.
|
||||
* @param {Array<*>} options - The options, if any, to pass to {@link t}.
|
||||
* @returns {ReactElement} A ReactElement which depicts the translated HTML
|
||||
* text.
|
||||
*/
|
||||
export function translateToHTML(t: Function, key: string, options: Object = {}) {
|
||||
// eslint-disable-next-line react/no-danger
|
||||
return <span dangerouslySetInnerHTML = {{ __html: t(key, options) }} />;
|
||||
}
|
||||
147
react/features/base/i18n/i18next.ts
Normal file
147
react/features/base/i18n/i18next.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import COUNTRIES_RESOURCES from 'i18n-iso-countries/langs/en.json';
|
||||
import i18next from 'i18next';
|
||||
import I18nextXHRBackend, { HttpBackendOptions } from 'i18next-http-backend';
|
||||
import { merge } from 'lodash-es';
|
||||
|
||||
import LANGUAGES_RESOURCES from '../../../../lang/languages.json';
|
||||
import MAIN_RESOURCES from '../../../../lang/main.json';
|
||||
import TRANSLATION_LANGUAGES_RESOURCES from '../../../../lang/translation-languages.json';
|
||||
|
||||
import { I18NEXT_INITIALIZED, LANGUAGE_CHANGED } from './actionTypes';
|
||||
import languageDetector from './languageDetector';
|
||||
|
||||
/**
|
||||
* Override certain country names.
|
||||
*/
|
||||
const COUNTRIES_RESOURCES_OVERRIDES = {
|
||||
countries: {
|
||||
TW: 'Taiwan'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Merged country names.
|
||||
*/
|
||||
const COUNTRIES = merge({}, COUNTRIES_RESOURCES, COUNTRIES_RESOURCES_OVERRIDES);
|
||||
|
||||
/**
|
||||
* The available/supported languages.
|
||||
*
|
||||
* @public
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
export const LANGUAGES: Array<string> = Object.keys(LANGUAGES_RESOURCES);
|
||||
|
||||
/**
|
||||
* The available/supported translation languages.
|
||||
*
|
||||
* @public
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
export const TRANSLATION_LANGUAGES: Array<string> = Object.keys(TRANSLATION_LANGUAGES_RESOURCES);
|
||||
|
||||
/**
|
||||
* The default language.
|
||||
*
|
||||
* English is the default language.
|
||||
*
|
||||
* @public
|
||||
* @type {string} The default language.
|
||||
*/
|
||||
export const DEFAULT_LANGUAGE = 'en';
|
||||
|
||||
/**
|
||||
* The available/supported translation languages head. (Languages displayed on the top ).
|
||||
*
|
||||
* @public
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
export const TRANSLATION_LANGUAGES_HEAD: Array<string> = [ DEFAULT_LANGUAGE ];
|
||||
|
||||
/**
|
||||
* The options to initialize i18next with.
|
||||
*
|
||||
* @type {i18next.InitOptions}
|
||||
*/
|
||||
const options: i18next.InitOptions = {
|
||||
backend: <HttpBackendOptions>{
|
||||
loadPath: (lng: string[], ns: string[]) => {
|
||||
switch (ns[0]) {
|
||||
case 'countries':
|
||||
case 'main':
|
||||
return 'lang/{{ns}}-{{lng}}.json';
|
||||
default:
|
||||
return 'lang/{{ns}}.json';
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultNS: 'main',
|
||||
fallbackLng: DEFAULT_LANGUAGE,
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react as it escapes by default
|
||||
},
|
||||
load: 'languageOnly',
|
||||
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
|
||||
react: {
|
||||
// re-render when a new resource bundle is added
|
||||
// @ts-expect-error. Fixed in i18next 19.6.1.
|
||||
bindI18nStore: 'added',
|
||||
useSuspense: false
|
||||
},
|
||||
returnEmptyString: false,
|
||||
returnNull: false,
|
||||
|
||||
// XXX i18next modifies the array lngWhitelist so make sure to clone
|
||||
// LANGUAGES.
|
||||
whitelist: LANGUAGES.slice()
|
||||
};
|
||||
|
||||
i18next
|
||||
.use(navigator.product === 'ReactNative' ? {} : I18nextXHRBackend)
|
||||
.use(languageDetector)
|
||||
.init(options);
|
||||
|
||||
// Add default language which is preloaded from the source code.
|
||||
i18next.addResourceBundle(
|
||||
DEFAULT_LANGUAGE,
|
||||
'countries',
|
||||
COUNTRIES,
|
||||
/* deep */ true,
|
||||
/* overwrite */ true);
|
||||
i18next.addResourceBundle(
|
||||
DEFAULT_LANGUAGE,
|
||||
'languages',
|
||||
LANGUAGES_RESOURCES,
|
||||
/* deep */ true,
|
||||
/* overwrite */ true);
|
||||
i18next.addResourceBundle(
|
||||
DEFAULT_LANGUAGE,
|
||||
'translation-languages',
|
||||
TRANSLATION_LANGUAGES_RESOURCES,
|
||||
/* deep */ true,
|
||||
/* overwrite */ true);
|
||||
i18next.addResourceBundle(
|
||||
DEFAULT_LANGUAGE,
|
||||
'main',
|
||||
MAIN_RESOURCES,
|
||||
/* deep */ true,
|
||||
/* overwrite */ true);
|
||||
|
||||
// Add builtin languages.
|
||||
// XXX: Note we are using require here, because we want the side-effects of the
|
||||
// import, but imports can only be placed at the top, and it would be too early,
|
||||
// since i18next is not yet initialized at that point.
|
||||
require('./BuiltinLanguages');
|
||||
|
||||
// Label change through dynamic branding is available only for web
|
||||
if (typeof APP !== 'undefined') {
|
||||
i18next.on('initialized', () => {
|
||||
APP.store.dispatch({ type: I18NEXT_INITIALIZED });
|
||||
});
|
||||
|
||||
i18next.on('languageChanged', () => {
|
||||
APP.store.dispatch({ type: LANGUAGE_CHANGED });
|
||||
});
|
||||
}
|
||||
|
||||
export default i18next;
|
||||
43
react/features/base/i18n/languageDetector.native.ts
Normal file
43
react/features/base/i18n/languageDetector.native.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import LANGUAGES_RESOURCES from '../../../../lang/languages.json';
|
||||
|
||||
const LANGUAGES = Object.keys(LANGUAGES_RESOURCES);
|
||||
|
||||
/**
|
||||
* The singleton language detector for React Native which uses the system-wide
|
||||
* locale.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Does not support caching.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
cacheUserLanguage: Function.prototype,
|
||||
|
||||
detect() {
|
||||
const { LocaleDetector } = NativeModules;
|
||||
const parts = LocaleDetector.locale.replace(/_/, '-').split('-');
|
||||
const [ lang, regionOrScript, region ] = parts;
|
||||
let locale;
|
||||
|
||||
if (parts.length >= 3) {
|
||||
locale = `${lang}${region}`;
|
||||
} else if (parts.length === 2) {
|
||||
locale = `${lang}${regionOrScript}`;
|
||||
} else {
|
||||
locale = lang;
|
||||
}
|
||||
|
||||
if (LANGUAGES.includes(locale)) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
return lang;
|
||||
},
|
||||
|
||||
init: Function.prototype,
|
||||
|
||||
type: 'languageDetector'
|
||||
};
|
||||
45
react/features/base/i18n/languageDetector.web.ts
Normal file
45
react/features/base/i18n/languageDetector.web.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import BrowserLanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
import configLanguageDetector from './configLanguageDetector';
|
||||
import customNavigatorDetector from './customNavigatorDetector';
|
||||
|
||||
/**
|
||||
* The ordered list (by name) of language detectors to be utilized as backends
|
||||
* by the singleton language detector for Web.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
const order = [
|
||||
'querystring',
|
||||
'localStorage'
|
||||
];
|
||||
|
||||
// Allow i18next to detect the system language reported by the Web browser
|
||||
// itself.
|
||||
interfaceConfig.LANG_DETECTION && order.push(customNavigatorDetector.name);
|
||||
|
||||
// Default use configured language
|
||||
order.push(configLanguageDetector.name);
|
||||
|
||||
/**
|
||||
* The singleton language detector for Web.
|
||||
*/
|
||||
const languageDetector
|
||||
= new BrowserLanguageDetector(
|
||||
/* services */ null,
|
||||
/* options */ {
|
||||
caches: [ 'localStorage' ],
|
||||
lookupLocalStorage: 'language',
|
||||
lookupQuerystring: 'lang',
|
||||
order
|
||||
});
|
||||
|
||||
// Add the language detector which looks the language up in the config. Its
|
||||
// order has already been established above.
|
||||
// @ts-ignore
|
||||
languageDetector.addDetector(customNavigatorDetector);
|
||||
|
||||
// @ts-ignore
|
||||
languageDetector.addDetector(configLanguageDetector);
|
||||
|
||||
export default languageDetector;
|
||||
3
react/features/base/i18n/logger.ts
Normal file
3
react/features/base/i18n/logger.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getLogger } from '../logging/functions';
|
||||
|
||||
export default getLogger('features/base/i18n');
|
||||
49
react/features/base/i18n/middleware.ts
Normal file
49
react/features/base/i18n/middleware.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { SET_DYNAMIC_BRANDING_DATA } from '../../dynamic-branding/actionTypes';
|
||||
import { getConferenceState } from '../conference/functions';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import { I18NEXT_INITIALIZED, LANGUAGE_CHANGED } from './actionTypes';
|
||||
import { changeLanguageBundle } from './functions';
|
||||
import i18next from './i18next';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Implements the entry point of the middleware of the feature base/i18n.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case I18NEXT_INITIALIZED:
|
||||
case LANGUAGE_CHANGED:
|
||||
case SET_DYNAMIC_BRANDING_DATA: {
|
||||
const { language } = i18next;
|
||||
const { labels } = action.type === SET_DYNAMIC_BRANDING_DATA
|
||||
? action.value
|
||||
: store.getState()['features/dynamic-branding'];
|
||||
|
||||
if (language && labels?.[language]) {
|
||||
changeLanguageBundle(language, labels[language])
|
||||
.catch(err => {
|
||||
logger.log('Error setting dynamic language bundle', err);
|
||||
});
|
||||
}
|
||||
|
||||
// Update transcription language, if applicable.
|
||||
if (action.type === SET_DYNAMIC_BRANDING_DATA) {
|
||||
const { defaultTranscriptionLanguage } = action.value;
|
||||
|
||||
if (typeof defaultTranscriptionLanguage !== 'undefined') {
|
||||
const { conference } = getConferenceState(store.getState());
|
||||
|
||||
conference?.setTranscriptionLanguage(defaultTranscriptionLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
Reference in New Issue
Block a user