Use locale/get response to suggest homepage and language switch (#839)
* Use locale/get response to suggest homepage and language switch * Fix language modal condition * Fixes from review * Fixes from review * Fix gdpr * string * Fix multiple options behavior * Fix gdpr and use only one fetch * Only show if no languages set or loaded * pt-br * Fix ad * Fix homepage select * Fix zh langs
This commit is contained in:
parent
f839e0c35d
commit
712e02db16
20 changed files with 684 additions and 99 deletions
|
@ -18,6 +18,7 @@ SOCKETY_SERVER_API=wss://sockety.odysee.com/ws
|
|||
RECSYS_ENDPOINT=https://recsys.odysee.tv/v1/lvv
|
||||
THUMBNAIL_CDN_URL=https://thumbnails.odycdn.com/optimize/
|
||||
THUMBNAIL_CARDS_CDN_URL=https://cards.odycdn.com/
|
||||
LOCALE_API=https://api.odysee.com/locale/get
|
||||
THUMBNAIL_HEIGHT=220
|
||||
THUMBNAIL_WIDTH=390
|
||||
THUMBNAIL_QUALITY=85
|
||||
|
|
|
@ -31,6 +31,7 @@ module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/ui/rewards\1'
|
|||
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/ui/i18n\1'
|
||||
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/ui/effects\1'
|
||||
module.name_mapper='^comments\(.*\)$' -> '<PROJECT_ROOT>/ui/comments\1'
|
||||
module.name_mapper='^locale\(.*\)$' -> '<PROJECT_ROOT>/ui/locale\1'
|
||||
module.name_mapper='^config\(.*\)$' -> '<PROJECT_ROOT>/config\1'
|
||||
module.name_mapper='^web\/component\(.*\)$' -> '<PROJECT_ROOT>/web/component\1'
|
||||
module.name_mapper='^web\/effects\(.*\)$' -> '<PROJECT_ROOT>/web/effects\1'
|
||||
|
|
|
@ -16,6 +16,7 @@ const config = {
|
|||
SEARCH_SERVER_API_ALT: process.env.SEARCH_SERVER_API_ALT,
|
||||
COMMENT_SERVER_API: process.env.COMMENT_SERVER_API,
|
||||
SOCKETY_SERVER_API: process.env.SOCKETY_SERVER_API,
|
||||
LOCALE_API: process.env.LOCALE_API,
|
||||
WELCOME_VERSION: process.env.WELCOME_VERSION,
|
||||
DOMAIN: process.env.DOMAIN,
|
||||
SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL,
|
||||
|
|
|
@ -2209,5 +2209,12 @@
|
|||
"The minimum duration must not exceed Feb 8th, 2022.": "The minimum duration must not exceed Feb 8th, 2022.",
|
||||
"No limit": "No limit",
|
||||
"Search results are being filtered by language. Click here to change the setting.": "Search results are being filtered by language. Click here to change the setting.",
|
||||
"There are language translations available for your location! Do you want to switch from English?": "There are language translations available for your location! Do you want to switch from English?",
|
||||
"A homepage and language translations are available for your location! Do you want to switch?": "A homepage and language translations are available for your location! Do you want to switch?",
|
||||
"Switch Now": "Switch Now",
|
||||
"Both": "Both",
|
||||
"Only Language": "Only Language",
|
||||
"Only Homepage": "Only Homepage",
|
||||
"Choose Your Preference": "Choose Your Preference",
|
||||
"--end--": "--end--"
|
||||
}
|
||||
|
|
|
@ -34,9 +34,13 @@ import {
|
|||
} from 'web/effects/use-degraded-performance';
|
||||
import LANGUAGE_MIGRATIONS from 'constants/language-migrations';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import { fetchLocaleApi } from 'locale';
|
||||
import getLanguagesForCountry from 'constants/country_languages';
|
||||
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
|
||||
|
||||
const FileDrop = lazyImport(() => import('component/fileDrop' /* webpackChunkName: "fileDrop" */));
|
||||
const NagContinueFirstRun = lazyImport(() => import('component/nagContinueFirstRun' /* webpackChunkName: "nagCFR" */));
|
||||
const NagLocaleSwitch = lazyImport(() => import('component/nagLocaleSwitch' /* webpackChunkName: "nagLocaleSwitch" */));
|
||||
const OpenInAppLink = lazyImport(() => import('web/component/openInAppLink' /* webpackChunkName: "openInAppLink" */));
|
||||
const NagDataCollection = lazyImport(() => import('web/component/nag-data-collection' /* webpackChunkName: "nagDC" */));
|
||||
const NagDegradedPerformance = lazyImport(() =>
|
||||
|
@ -132,6 +136,10 @@ function App(props: Props) {
|
|||
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
||||
const previousRewardApproved = usePrevious(isRewardApproved);
|
||||
|
||||
const [gdprRequired, setGdprRequired] = usePersistedState('gdprRequired');
|
||||
const [localeLangs, setLocaleLangs] = React.useState();
|
||||
const [localeSwitchDismissed] = usePersistedState('locale-switch-dismissed', false);
|
||||
|
||||
const [showAnalyticsNag, setShowAnalyticsNag] = usePersistedState('analytics-nag', true);
|
||||
const [lbryTvApiStatus, setLbryTvApiStatus] = useState(STATUS_OK);
|
||||
|
||||
|
@ -212,6 +220,11 @@ function App(props: Props) {
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (localeLangs) {
|
||||
const noLanguageSet = language === 'en' && languages.length === 1;
|
||||
return <NagLocaleSwitch localeLangs={localeLangs} noLanguageSet={noLanguageSet} />;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -312,6 +325,7 @@ function App(props: Props) {
|
|||
fetchModBlockedList();
|
||||
fetchModAmIList();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hasMyChannels, hasNoChannels, hasActiveChannelClaim, setActiveChannelIfNotSet, setIncognito]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -396,40 +410,42 @@ function App(props: Props) {
|
|||
// OneTrust asks to add this
|
||||
secondScript.innerHTML = 'function OptanonWrapper() { }';
|
||||
|
||||
const getLocaleEndpoint = 'https://api.odysee.com/locale/get';
|
||||
let gdprRequired;
|
||||
try {
|
||||
gdprRequired = localStorage.getItem('gdprRequired');
|
||||
} catch (err) {
|
||||
if (err) return;
|
||||
}
|
||||
// gdpr is known to be required, add script
|
||||
if (gdprRequired === 'true') {
|
||||
if (gdprRequired) {
|
||||
// $FlowFixMe
|
||||
document.head.appendChild(script);
|
||||
// $FlowFixMe
|
||||
document.head.appendChild(secondScript);
|
||||
}
|
||||
|
||||
fetchLocaleApi().then((response) => {
|
||||
if (!localeLangs && !localeSwitchDismissed) {
|
||||
const countryCode = response?.data?.country;
|
||||
const langs = getLanguagesForCountry(countryCode);
|
||||
|
||||
const supportedLangs = [];
|
||||
langs.forEach((lang) => lang !== 'en' && SUPPORTED_LANGUAGES[lang] && supportedLangs.push(lang));
|
||||
|
||||
if (supportedLangs.length > 0) setLocaleLangs(supportedLangs);
|
||||
}
|
||||
|
||||
// haven't done a gdpr check, do it now
|
||||
if (gdprRequired === null) {
|
||||
(async function () {
|
||||
const response = await fetch(getLocaleEndpoint);
|
||||
const json = await response.json();
|
||||
const gdprRequiredBasedOnLocation = json.data.gdpr_required;
|
||||
if (gdprRequired === null || gdprRequired === undefined) {
|
||||
const gdprRequiredBasedOnLocation = response?.data?.gdpr_required;
|
||||
|
||||
// note we need gdpr and load script
|
||||
if (gdprRequiredBasedOnLocation) {
|
||||
localStorage.setItem('gdprRequired', 'true');
|
||||
setGdprRequired(true);
|
||||
// $FlowFixMe
|
||||
document.head.appendChild(script);
|
||||
// $FlowFixMe
|
||||
document.head.appendChild(secondScript);
|
||||
// note we don't need gdpr, save to session
|
||||
} else if (gdprRequiredBasedOnLocation === false) {
|
||||
localStorage.setItem('gdprRequired', 'false');
|
||||
setGdprRequired(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
try {
|
||||
|
@ -438,9 +454,11 @@ function App(props: Props) {
|
|||
// $FlowFixMe
|
||||
document.head.removeChild(secondScript);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// ready for sync syncs, however after signin when hasVerifiedEmail, that syncs too.
|
||||
|
|
|
@ -7,6 +7,8 @@ import Button from 'component/button';
|
|||
|
||||
type Props = {
|
||||
message: string | Node,
|
||||
action?: Node,
|
||||
closeTitle?: string,
|
||||
actionText?: string,
|
||||
href?: string,
|
||||
type?: string,
|
||||
|
@ -17,9 +19,20 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function Nag(props: Props) {
|
||||
const { message, actionText, href, onClick, onClose, type, inline, relative } = props;
|
||||
const {
|
||||
message,
|
||||
action: customAction,
|
||||
closeTitle,
|
||||
actionText,
|
||||
href,
|
||||
onClick,
|
||||
onClose,
|
||||
type,
|
||||
inline,
|
||||
relative,
|
||||
} = props;
|
||||
|
||||
const buttonProps = onClick ? { onClick } : { href };
|
||||
const buttonProps = onClick ? { onClick } : href ? { href } : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -31,7 +44,10 @@ export default function Nag(props: Props) {
|
|||
})}
|
||||
>
|
||||
<div className="nag__message">{message}</div>
|
||||
{(href || onClick) && (
|
||||
|
||||
{customAction}
|
||||
|
||||
{buttonProps && (
|
||||
<Button
|
||||
className={classnames('nag__button', {
|
||||
'nag__button--helpful': type === 'helpful',
|
||||
|
@ -42,12 +58,14 @@ export default function Nag(props: Props) {
|
|||
{actionText}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onClose && (
|
||||
<Button
|
||||
className={classnames('nag__button nag__close', {
|
||||
'nag__button--helpful': type === 'helpful',
|
||||
'nag__button--error': type === 'error',
|
||||
})}
|
||||
title={closeTitle}
|
||||
icon={ICONS.REMOVE}
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import React from 'react';
|
||||
// $FlowFixMe
|
||||
import homepages from 'homepages';
|
||||
import LANGUAGES from 'constants/languages';
|
||||
import { getLanguageEngName } from 'constants/languages';
|
||||
import { FormField } from 'component/common/form';
|
||||
import { getDefaultHomepageKey } from 'util/default-languages';
|
||||
|
||||
|
@ -31,7 +31,7 @@ function SelectHomepage(props: Props) {
|
|||
>
|
||||
{Object.keys(homepages).map((hp) => (
|
||||
<option key={'hp' + hp} value={hp}>
|
||||
{`${LANGUAGES[hp][1]}`}
|
||||
{`${getLanguageEngName(hp)}`}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
|
|
12
ui/component/nagLocaleSwitch/index.js
Normal file
12
ui/component/nagLocaleSwitch/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doSetLanguage, doSetHomepage } from 'redux/actions/settings';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import NagLocaleSwitch from './view';
|
||||
|
||||
const perform = {
|
||||
doSetLanguage,
|
||||
doSetHomepage,
|
||||
doOpenModal,
|
||||
};
|
||||
|
||||
export default connect(null, perform)(NagLocaleSwitch);
|
194
ui/component/nagLocaleSwitch/view.jsx
Normal file
194
ui/component/nagLocaleSwitch/view.jsx
Normal file
|
@ -0,0 +1,194 @@
|
|||
// @flow
|
||||
import { FormField } from 'component/common/form';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import HOMEPAGE_LANGUAGES, { getHomepageLanguage } from 'constants/homepage_languages';
|
||||
import Nag from 'component/common/nag';
|
||||
import React from 'react';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import { getLanguageEngName, getLanguageName } from 'constants/languages';
|
||||
|
||||
const LOCALE_OPTIONS = {
|
||||
BOTH: 'both',
|
||||
LANG: 'lang',
|
||||
HOME: 'home',
|
||||
};
|
||||
|
||||
type Props = {
|
||||
localeLangs: Array<string>,
|
||||
noLanguageSet: boolean,
|
||||
// redux
|
||||
doSetLanguage: (string) => void,
|
||||
doSetHomepage: (string) => void,
|
||||
doOpenModal: (string, {}) => void,
|
||||
};
|
||||
|
||||
export default function NagLocaleSwitch(props: Props) {
|
||||
const { localeLangs, noLanguageSet, doSetLanguage, doSetHomepage, doOpenModal } = props;
|
||||
|
||||
const [switchOption, setSwitchOption] = React.useState(LOCALE_OPTIONS.BOTH);
|
||||
const [localeSwitchDismissed, setLocaleSwitchDismissed] = usePersistedState('locale-switch-dismissed', false);
|
||||
|
||||
const hasHomepageForLang = localeLangs.some((lang) => getHomepageLanguage(lang));
|
||||
const message = __(
|
||||
// If no homepage, only suggest language switch
|
||||
!hasHomepageForLang
|
||||
? 'There are language translations available for your location! Do you want to switch from English?'
|
||||
: 'A homepage and language translations are available for your location! Do you want to switch?'
|
||||
);
|
||||
|
||||
if (localeSwitchDismissed || (!noLanguageSet && !hasHomepageForLang)) return null;
|
||||
|
||||
function dismissNag() {
|
||||
setLocaleSwitchDismissed(true);
|
||||
}
|
||||
|
||||
function handleSwitch() {
|
||||
const homepages = [];
|
||||
localeLangs.forEach((lang) => {
|
||||
const homepageLanguage = getHomepageLanguage(lang);
|
||||
|
||||
if (homepageLanguage && !homepages.includes(homepageLanguage)) {
|
||||
homepages.push(homepageLanguage);
|
||||
}
|
||||
});
|
||||
|
||||
const homeSwitchSelected = switchOption === LOCALE_OPTIONS.BOTH || switchOption === LOCALE_OPTIONS.HOME;
|
||||
const multipleHomepages = homeSwitchSelected && homepages.length > 1;
|
||||
const langSwitchSelected = switchOption === LOCALE_OPTIONS.BOTH || switchOption === LOCALE_OPTIONS.LANG;
|
||||
const multipleLangs = langSwitchSelected && localeLangs.length > 1;
|
||||
|
||||
// if language or homepage has more than 1 option, modal for selection
|
||||
// if some has only one option, still show the selection for confirmation of what's being switched
|
||||
if (multipleHomepages || multipleLangs) {
|
||||
doOpenModal(MODALS.CONFIRM, {
|
||||
title: __('Choose Your Preference'),
|
||||
body: (
|
||||
<>
|
||||
{langSwitchSelected && <LanguageSelect langs={localeLangs} />}
|
||||
{homeSwitchSelected && <HomepageSelect homepages={homepages} />}
|
||||
</>
|
||||
),
|
||||
onConfirm: (closeModal) => {
|
||||
if (langSwitchSelected) {
|
||||
// $FlowFixMe
|
||||
const selection = document.querySelector('.language-switch.checked').id.split(' ')[1];
|
||||
doSetLanguage(selection);
|
||||
}
|
||||
if (homeSwitchSelected) {
|
||||
// $FlowFixMe
|
||||
const selection = document.querySelector('.homepage-switch.checked').id.split(' ')[1];
|
||||
let homepageSelection = '';
|
||||
Object.values(HOMEPAGE_LANGUAGES).some((lang, index) => {
|
||||
if (lang === selection) {
|
||||
homepageSelection = Object.keys(HOMEPAGE_LANGUAGES)[index];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
doSetHomepage(homepageSelection);
|
||||
}
|
||||
dismissNag();
|
||||
closeModal();
|
||||
},
|
||||
});
|
||||
|
||||
// if selected switch has only one option, just make the switch
|
||||
} else {
|
||||
const onlyLanguage = localeLangs[0];
|
||||
|
||||
if (langSwitchSelected) doSetLanguage(onlyLanguage);
|
||||
if (homeSwitchSelected) doSetHomepage(onlyLanguage);
|
||||
|
||||
dismissNag();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Nag
|
||||
message={message}
|
||||
type="helpful"
|
||||
action={
|
||||
// Menu field only needed if there is a homepage + language to choose, otherwise
|
||||
// there is only 1 option to switch, so use the nag button
|
||||
hasHomepageForLang && (
|
||||
<FormField
|
||||
className="nag__select"
|
||||
type="select"
|
||||
value={switchOption}
|
||||
onChange={(e) => setSwitchOption(e.target.value)}
|
||||
>
|
||||
<option value={LOCALE_OPTIONS.BOTH}>{__('Both')}</option>
|
||||
<option value={LOCALE_OPTIONS.LANG}>{__('Only Language')}</option>
|
||||
<option value={LOCALE_OPTIONS.HOME}>{__('Only Homepage')}</option>
|
||||
</FormField>
|
||||
)
|
||||
}
|
||||
actionText={__('Switch Now')}
|
||||
onClick={handleSwitch}
|
||||
onClose={dismissNag}
|
||||
closeTitle={__('Dismiss')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type HomepageProps = {
|
||||
homepages: Array<string>,
|
||||
};
|
||||
|
||||
const HomepageSelect = (props: HomepageProps) => {
|
||||
const { homepages } = props;
|
||||
|
||||
const [selection, setSelection] = React.useState(homepages[0]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{__('Homepage')}</h1>
|
||||
|
||||
{homepages.map((homepage) => (
|
||||
<FormField
|
||||
type="radio"
|
||||
className={`homepage-switch ${selection === homepage ? 'checked' : ''}`}
|
||||
name={`homepage_switch ${homepage}`}
|
||||
key={homepage}
|
||||
label={homepage}
|
||||
checked={selection === homepage}
|
||||
onChange={() => setSelection(homepage)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
type LangProps = {
|
||||
langs: Array<string>,
|
||||
};
|
||||
|
||||
const LanguageSelect = (props: LangProps) => {
|
||||
const { langs } = props;
|
||||
|
||||
const [selection, setSelection] = React.useState(langs[0]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{__('Language')}</h1>
|
||||
|
||||
{langs.map((lang) => {
|
||||
const language = getLanguageEngName(lang);
|
||||
const languageName = getLanguageName(lang);
|
||||
const label = language === languageName ? language : `${language} - ${languageName}`;
|
||||
|
||||
return (
|
||||
<FormField
|
||||
type="radio"
|
||||
className={`language-switch ${selection === lang ? 'checked' : ''}`}
|
||||
name={`language_switch ${lang}`}
|
||||
key={lang}
|
||||
label={label}
|
||||
checked={selection === lang}
|
||||
onChange={() => setSelection(lang)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
279
ui/constants/country_languages.js
Normal file
279
ui/constants/country_languages.js
Normal file
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Upstream source: https://wiki.openstreetmap.org/wiki/Nominatim/Country_Codes
|
||||
*/
|
||||
|
||||
const COUNTRY_LANGUAGES = {
|
||||
ad: 'ca',
|
||||
ae: 'ar',
|
||||
af: 'fa,ps',
|
||||
ag: 'en',
|
||||
ai: 'en',
|
||||
al: 'sq',
|
||||
am: 'hy',
|
||||
ao: 'pt',
|
||||
aq: '',
|
||||
ar: 'es',
|
||||
as: 'en,sm',
|
||||
at: 'de',
|
||||
au: 'en',
|
||||
aw: 'nl,pap',
|
||||
ax: 'sv',
|
||||
az: 'az',
|
||||
ba: 'bs,hr,sr',
|
||||
bb: 'en',
|
||||
bd: 'bn',
|
||||
be: 'nl,fr,de',
|
||||
bf: 'fr',
|
||||
bg: 'bg',
|
||||
bh: 'ar',
|
||||
bi: 'fr',
|
||||
bj: 'fr',
|
||||
bl: 'fr',
|
||||
bm: 'en',
|
||||
bn: 'ms',
|
||||
bo: 'es,qu,ay',
|
||||
bq: 'nl',
|
||||
br: 'pt',
|
||||
bs: 'en',
|
||||
bt: 'dz',
|
||||
bv: 'no',
|
||||
bw: 'en,tn',
|
||||
by: 'be,ru',
|
||||
bz: 'en',
|
||||
ca: 'en,fr',
|
||||
cc: 'en',
|
||||
cd: 'fr',
|
||||
cf: 'fr',
|
||||
cg: 'fr',
|
||||
ch: 'de,fr,it,rm',
|
||||
ci: 'fr',
|
||||
ck: 'en,rar',
|
||||
cl: 'es',
|
||||
cm: 'fr,en',
|
||||
cn: 'zh',
|
||||
co: 'es',
|
||||
cr: 'es',
|
||||
cu: 'es',
|
||||
cv: 'pt',
|
||||
cw: 'nl,en',
|
||||
cx: 'en',
|
||||
cy: 'el,tr',
|
||||
cz: 'cs',
|
||||
de: 'de',
|
||||
dj: 'fr,ar',
|
||||
dk: 'da',
|
||||
dm: 'en',
|
||||
do: 'es',
|
||||
dz: 'ar,ber',
|
||||
ec: 'es',
|
||||
ee: 'et',
|
||||
eg: 'ar',
|
||||
eh: 'ar,es,fr',
|
||||
er: 'ti,ar,en',
|
||||
es: 'es',
|
||||
et: 'am,om',
|
||||
fi: 'fi,sv,se',
|
||||
fj: 'en',
|
||||
fk: 'en',
|
||||
fm: 'en',
|
||||
fo: 'fo',
|
||||
fr: 'fr',
|
||||
ga: 'fr',
|
||||
gb: 'en',
|
||||
gd: 'en',
|
||||
ge: 'ka',
|
||||
gf: 'fr',
|
||||
gg: 'en',
|
||||
gh: 'en',
|
||||
gi: 'en',
|
||||
gl: 'kl,da',
|
||||
gm: 'en',
|
||||
gn: 'fr',
|
||||
gp: 'fr',
|
||||
gq: 'es',
|
||||
gr: 'el',
|
||||
gs: 'en',
|
||||
gt: 'es',
|
||||
gu: 'en,ch',
|
||||
gw: 'pt',
|
||||
gy: 'en',
|
||||
hk: 'zh,en',
|
||||
hm: 'en',
|
||||
hn: 'es',
|
||||
hr: 'hr',
|
||||
ht: 'fr,ht',
|
||||
hu: 'hu',
|
||||
id: 'id',
|
||||
ie: 'en,ga',
|
||||
il: 'he',
|
||||
im: 'en',
|
||||
in: 'hi,en',
|
||||
io: 'en',
|
||||
iq: 'ar,ku',
|
||||
ir: 'fa',
|
||||
is: 'is',
|
||||
it: 'it',
|
||||
je: 'en',
|
||||
jm: 'en',
|
||||
jo: 'ar',
|
||||
jp: 'ja',
|
||||
ke: 'sw,en',
|
||||
kg: 'ky,ru',
|
||||
kh: 'km',
|
||||
ki: 'en',
|
||||
km: 'bnt,ar,fr',
|
||||
kn: 'en',
|
||||
kp: 'ko',
|
||||
kr: 'ko',
|
||||
kw: 'ar',
|
||||
ky: 'en',
|
||||
kz: 'kk,ru',
|
||||
la: 'lo',
|
||||
lb: 'ar,fr',
|
||||
lc: 'en',
|
||||
li: 'de',
|
||||
lk: 'si,ta',
|
||||
lr: 'en',
|
||||
ls: 'en,st',
|
||||
lt: 'lt',
|
||||
lu: 'lb,fr,de',
|
||||
lv: 'lv',
|
||||
ly: 'ar,ber',
|
||||
ma: 'ar,ber',
|
||||
mc: 'fr',
|
||||
md: 'ru,uk,ro',
|
||||
me: 'sr,sh',
|
||||
mf: 'fr',
|
||||
mg: 'mg,fr',
|
||||
mh: 'en,mh',
|
||||
mk: 'mk',
|
||||
ml: 'fr',
|
||||
mm: 'my',
|
||||
mn: 'mn',
|
||||
mo: 'zh,pt',
|
||||
mp: 'ch',
|
||||
mq: 'fr',
|
||||
mr: 'ar,fr',
|
||||
ms: 'en',
|
||||
mt: 'mt,en',
|
||||
mu: 'mfe,fr,en',
|
||||
mv: 'dv',
|
||||
mw: 'en,ny',
|
||||
mx: 'es',
|
||||
my: 'en,ms,zh,ta',
|
||||
mz: 'pt',
|
||||
na: 'en,sf,de',
|
||||
nc: 'fr',
|
||||
ne: 'fr',
|
||||
nf: 'en,pih',
|
||||
ng: 'en',
|
||||
ni: 'es',
|
||||
nl: 'nl',
|
||||
no: 'nb,nn,no',
|
||||
np: 'ne',
|
||||
nr: 'na,en',
|
||||
nu: 'niu,en',
|
||||
nz: 'mi,en',
|
||||
om: 'ar',
|
||||
pa: 'es',
|
||||
pe: 'es',
|
||||
pf: 'fr',
|
||||
pg: 'en,tpi,ho',
|
||||
ph: 'en,tl',
|
||||
pk: 'en,ur',
|
||||
pl: 'pl',
|
||||
pm: 'fr',
|
||||
pn: 'en,pih',
|
||||
pr: 'es,en',
|
||||
ps: 'ar,he',
|
||||
pt: 'pt',
|
||||
pw: 'en,pau,ja,sov,tox',
|
||||
py: 'es,gn',
|
||||
qa: 'ar',
|
||||
re: 'fr',
|
||||
ro: 'ro',
|
||||
rs: 'sr',
|
||||
ru: 'ru',
|
||||
rw: 'rw,fr,en',
|
||||
sa: 'ar',
|
||||
sb: 'en',
|
||||
sc: 'fr,en,crs',
|
||||
sd: 'ar,en',
|
||||
se: 'sv',
|
||||
sg: 'en,ms,zh,ta',
|
||||
sh: 'en',
|
||||
si: 'sl',
|
||||
sj: 'no',
|
||||
sk: 'sk',
|
||||
sl: 'en',
|
||||
sm: 'it',
|
||||
sn: 'fr',
|
||||
so: 'so,ar',
|
||||
sr: 'nl',
|
||||
st: 'pt',
|
||||
ss: 'en',
|
||||
sv: 'es',
|
||||
sx: 'nl,en',
|
||||
sy: 'ar',
|
||||
sz: 'en,ss',
|
||||
tc: 'en',
|
||||
td: 'fr,ar',
|
||||
tf: 'fr',
|
||||
tg: 'fr',
|
||||
th: 'th',
|
||||
tj: 'tg,ru',
|
||||
tk: 'tkl,en,sm',
|
||||
tl: 'pt,tet',
|
||||
tm: 'tk',
|
||||
tn: 'ar',
|
||||
to: 'en',
|
||||
tr: 'tr',
|
||||
tt: 'en',
|
||||
tv: 'en',
|
||||
tw: 'zh',
|
||||
tz: 'sw,en',
|
||||
ua: 'uk',
|
||||
ug: 'en,sw',
|
||||
um: 'en',
|
||||
us: 'en',
|
||||
uy: 'es',
|
||||
uz: 'uz,kaa',
|
||||
va: 'it',
|
||||
vc: 'en',
|
||||
ve: 'es',
|
||||
vg: 'en',
|
||||
vi: 'en',
|
||||
vn: 'vi',
|
||||
vu: 'bi,en,fr',
|
||||
wf: 'fr',
|
||||
ws: 'sm,en',
|
||||
ye: 'ar',
|
||||
yt: 'fr',
|
||||
za: 'af,zu,xh',
|
||||
zm: 'en',
|
||||
zw: 'en,sn,nd',
|
||||
};
|
||||
|
||||
export default function getLanguagesForCountry(countryCode) {
|
||||
const country = countryCode.toLowerCase();
|
||||
const countryLanguages = COUNTRY_LANGUAGES[country];
|
||||
|
||||
if (!countryLanguages || countryLanguages.length === 0) return null;
|
||||
|
||||
const languages = countryLanguages.split(',');
|
||||
|
||||
// ----overrides----
|
||||
if (country === 'br') return ['pt-BR'];
|
||||
|
||||
const zhCountries = ['cn', 'hk', 'tw'];
|
||||
const zhLangs = ['zh-Hans', 'zh-Hant'];
|
||||
|
||||
if (zhCountries.includes(country)) return zhLangs;
|
||||
if (languages.includes('zh')) {
|
||||
languages.filter((lang) => lang === 'zh');
|
||||
languages.push(...zhLangs);
|
||||
}
|
||||
// -----------------
|
||||
|
||||
return languages;
|
||||
}
|
21
ui/constants/homepage_languages.js
Normal file
21
ui/constants/homepage_languages.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { getLanguageEngName } from 'constants/languages';
|
||||
|
||||
const HOMEPAGE_LANGUAGES = {
|
||||
en: getLanguageEngName('en'),
|
||||
fr: getLanguageEngName('fr'),
|
||||
es: getLanguageEngName('es'),
|
||||
de: getLanguageEngName('de'),
|
||||
zh: getLanguageEngName('zh'),
|
||||
ru: getLanguageEngName('ru'),
|
||||
'pt-BR': getLanguageEngName('pt-BR'),
|
||||
};
|
||||
|
||||
export function getHomepageLanguage(code) {
|
||||
// -----override-----
|
||||
if (code === 'zh-Hans' || code === 'zh-Hant') return HOMEPAGE_LANGUAGES.zh;
|
||||
// ------------------
|
||||
|
||||
return HOMEPAGE_LANGUAGES[code] || null;
|
||||
}
|
||||
|
||||
export default HOMEPAGE_LANGUAGES;
|
|
@ -187,4 +187,12 @@ const LANGUAGES = {
|
|||
zu: ['Zulu', 'isiZulu'],
|
||||
};
|
||||
|
||||
export function getLanguageEngName(code) {
|
||||
return LANGUAGES[code][0];
|
||||
}
|
||||
|
||||
export function getLanguageName(code) {
|
||||
return LANGUAGES[code][1];
|
||||
}
|
||||
|
||||
export default LANGUAGES;
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import LANGUAGES from './languages';
|
||||
import { getLanguageName } from './languages';
|
||||
|
||||
const SEARCHABLE_LANGUAGES = {
|
||||
en: LANGUAGES.en[1],
|
||||
hr: LANGUAGES.hr[1],
|
||||
nl: LANGUAGES.nl[1],
|
||||
fr: LANGUAGES.fr[1],
|
||||
de: LANGUAGES.de[1],
|
||||
it: LANGUAGES.it[1],
|
||||
pl: LANGUAGES.pl[1],
|
||||
pt: LANGUAGES.pt[1],
|
||||
ru: LANGUAGES.ru[1],
|
||||
es: LANGUAGES.es[1],
|
||||
tr: LANGUAGES.tr[1],
|
||||
cs: LANGUAGES.cs[1],
|
||||
en: getLanguageName('en'),
|
||||
hr: getLanguageName('hr'),
|
||||
nl: getLanguageName('nl'),
|
||||
fr: getLanguageName('fr'),
|
||||
de: getLanguageName('de'),
|
||||
it: getLanguageName('it'),
|
||||
pl: getLanguageName('pl'),
|
||||
pt: getLanguageName('pt'),
|
||||
ru: getLanguageName('ru'),
|
||||
es: getLanguageName('es'),
|
||||
tr: getLanguageName('tr'),
|
||||
cs: getLanguageName('cs'),
|
||||
};
|
||||
|
||||
// Properties: language code (e.g. 'ja')
|
||||
|
|
|
@ -1,47 +1,48 @@
|
|||
import LANGUAGES from './languages';
|
||||
import { getLanguageName } from './languages';
|
||||
|
||||
// supported_browser_languages
|
||||
const SUPPORTED_LANGUAGES = {
|
||||
af: LANGUAGES.af[1],
|
||||
en: LANGUAGES.en[1],
|
||||
da: LANGUAGES.da[1],
|
||||
'zh-Hans': LANGUAGES['zh-Hans'][1],
|
||||
'zh-Hant': LANGUAGES['zh-Hant'][1],
|
||||
hr: LANGUAGES.hr[1],
|
||||
nl: LANGUAGES.nl[1],
|
||||
no: LANGUAGES.no[1],
|
||||
fi: LANGUAGES.fi[1],
|
||||
fr: LANGUAGES.fr[1],
|
||||
de: LANGUAGES.de[1],
|
||||
gu: LANGUAGES.gu[1],
|
||||
hi: LANGUAGES.hi[1],
|
||||
hu: LANGUAGES.hu[1],
|
||||
id: LANGUAGES.id[1],
|
||||
ja: LANGUAGES.ja[1],
|
||||
jv: LANGUAGES.jv[1],
|
||||
it: LANGUAGES.it[1],
|
||||
ms: LANGUAGES.ms[1],
|
||||
ml: LANGUAGES.ml[1],
|
||||
mr: LANGUAGES.mr[1],
|
||||
pa: LANGUAGES.pa[1],
|
||||
pl: LANGUAGES.pl[1],
|
||||
pt: LANGUAGES.pt[1],
|
||||
'pt-BR': LANGUAGES['pt-BR'][1],
|
||||
ro: LANGUAGES.ro[1],
|
||||
ru: LANGUAGES.ru[1],
|
||||
sr: LANGUAGES.sr[1],
|
||||
sk: LANGUAGES.sk[1],
|
||||
th: LANGUAGES.th[1],
|
||||
ur: LANGUAGES.ur[1],
|
||||
ca: LANGUAGES.ca[1],
|
||||
es: LANGUAGES.es[1],
|
||||
sv: LANGUAGES.sv[1],
|
||||
tl: LANGUAGES.tl[1],
|
||||
tr: LANGUAGES.tr[1],
|
||||
cs: LANGUAGES.cs[1],
|
||||
kn: LANGUAGES.kn[1],
|
||||
uk: LANGUAGES.uk[1],
|
||||
vi: LANGUAGES.vi[1],
|
||||
ar: LANGUAGES.ar[1],
|
||||
af: getLanguageName('af'),
|
||||
en: getLanguageName('en'),
|
||||
da: getLanguageName('da'),
|
||||
'zh-Hans': getLanguageName('zh-Hans'),
|
||||
'zh-Hant': getLanguageName('zh-Hant'),
|
||||
hr: getLanguageName('hr'),
|
||||
nl: getLanguageName('nl'),
|
||||
no: getLanguageName('no'),
|
||||
fi: getLanguageName('fi'),
|
||||
fr: getLanguageName('fr'),
|
||||
de: getLanguageName('de'),
|
||||
gu: getLanguageName('gu'),
|
||||
hi: getLanguageName('hi'),
|
||||
hu: getLanguageName('hu'),
|
||||
id: getLanguageName('id'),
|
||||
ja: getLanguageName('ja'),
|
||||
jv: getLanguageName('jv'),
|
||||
it: getLanguageName('it'),
|
||||
ms: getLanguageName('ms'),
|
||||
ml: getLanguageName('ml'),
|
||||
mr: getLanguageName('mr'),
|
||||
pa: getLanguageName('pa'),
|
||||
pl: getLanguageName('pl'),
|
||||
pt: getLanguageName('pt'),
|
||||
'pt-BR': getLanguageName('pt-BR'),
|
||||
ro: getLanguageName('ro'),
|
||||
ru: getLanguageName('ru'),
|
||||
sr: getLanguageName('sr'),
|
||||
sk: getLanguageName('sk'),
|
||||
th: getLanguageName('th'),
|
||||
ur: getLanguageName('ur'),
|
||||
ca: getLanguageName('ca'),
|
||||
es: getLanguageName('es'),
|
||||
sv: getLanguageName('sv'),
|
||||
tl: getLanguageName('tl'),
|
||||
tr: getLanguageName('tr'),
|
||||
cs: getLanguageName('cs'),
|
||||
kn: getLanguageName('kn'),
|
||||
uk: getLanguageName('uk'),
|
||||
vi: getLanguageName('vi'),
|
||||
ar: getLanguageName('ar'),
|
||||
};
|
||||
|
||||
// Properties: language code (e.g. 'ja')
|
||||
|
|
6
ui/locale.js
Normal file
6
ui/locale.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
// @flow
|
||||
import { LOCALE_API } from 'config';
|
||||
|
||||
export async function fetchLocaleApi() {
|
||||
return fetch(LOCALE_API).then((res) => res.json());
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
|
||||
import ModalConfirm from './view';
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
doHideModal: () => dispatch(doHideModal()),
|
||||
});
|
||||
const perform = {
|
||||
doHideModal,
|
||||
};
|
||||
|
||||
export default connect(null, perform)(ModalConfirm);
|
||||
|
|
|
@ -12,14 +12,15 @@ type Props = {
|
|||
body?: string | Node,
|
||||
labelOk?: string,
|
||||
labelCancel?: string,
|
||||
onConfirm: (closeModal: () => void, setIsBusy: (boolean) => void) => void,
|
||||
hideCancel?: boolean,
|
||||
onConfirm: (closeModal: () => void, setIsBusy: (boolean) => void) => void,
|
||||
// --- perform ---
|
||||
doHideModal: () => void,
|
||||
};
|
||||
|
||||
export default function ModalConfirm(props: Props) {
|
||||
const { title, subtitle, body, labelOk, labelCancel, onConfirm, hideCancel, doHideModal } = props;
|
||||
const { title, subtitle, body, labelOk, labelCancel, hideCancel, onConfirm, doHideModal } = props;
|
||||
|
||||
const [isBusy, setIsBusy] = React.useState(false);
|
||||
|
||||
function handleOnClick() {
|
||||
|
@ -28,14 +29,6 @@ export default function ModalConfirm(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
function getOkLabel() {
|
||||
return isBusy ? <Spinner type="small" /> : labelOk || __('OK');
|
||||
}
|
||||
|
||||
function getCancelLabel() {
|
||||
return labelCancel || __('Cancel');
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen type="card" onAborted={doHideModal}>
|
||||
<Card
|
||||
|
@ -43,12 +36,18 @@ export default function ModalConfirm(props: Props) {
|
|||
subtitle={subtitle}
|
||||
body={body}
|
||||
actions={
|
||||
<>
|
||||
<div className="section__actions">
|
||||
<Button button="primary" label={getOkLabel()} disabled={isBusy} onClick={handleOnClick} />
|
||||
{!hideCancel && <Button button="link" label={getCancelLabel()} disabled={isBusy} onClick={doHideModal} />}
|
||||
<Button
|
||||
button="primary"
|
||||
label={isBusy ? <Spinner type="small" /> : labelOk || __('OK')}
|
||||
disabled={isBusy}
|
||||
onClick={handleOnClick}
|
||||
/>
|
||||
|
||||
{!hideCancel && (
|
||||
<Button button="link" label={labelCancel || __('Cancel')} disabled={isBusy} onClick={doHideModal} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
|
|
|
@ -176,7 +176,9 @@ function DiscoverPage(props: Props) {
|
|||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isAuthenticated || !SHOW_ADS || window.location.pathname === `/$/${PAGES.WILD_WEST}`) {
|
||||
const hasAdOnPage = document.querySelector('.homepageAdContainer');
|
||||
|
||||
if (hasAdOnPage || isAuthenticated || !SHOW_ADS || window.location.pathname === `/$/${PAGES.WILD_WEST}`) {
|
||||
return;
|
||||
}
|
||||
injectAd();
|
||||
|
|
|
@ -109,6 +109,11 @@
|
|||
z-index: 10000;
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
|
||||
.radio,
|
||||
.radio + h1 {
|
||||
margin-top: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
.modal--card-internal {
|
||||
|
|
|
@ -112,3 +112,16 @@ $nag-error-z-index: 999;
|
|||
stroke-width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.nag__select {
|
||||
display: inline;
|
||||
color: var(--color-text);
|
||||
|
||||
select {
|
||||
width: unset;
|
||||
min-width: 10rem;
|
||||
padding-right: unset;
|
||||
margin: 0px var(--spacing-s);
|
||||
height: var(--height-button-mobile);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue