mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-31 14:11:58 +05:30
Change prettier rules
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import React, { MouseEventHandler } from 'react';
|
||||
import {
|
||||
TransitionMotion,
|
||||
spring,
|
||||
presets,
|
||||
TransitionStyle,
|
||||
TransitionPlainStyle,
|
||||
PlainStyle,
|
||||
Style,
|
||||
TransitionMotion,
|
||||
spring,
|
||||
presets,
|
||||
TransitionStyle,
|
||||
TransitionPlainStyle,
|
||||
PlainStyle,
|
||||
Style,
|
||||
} from 'react-motion';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import clsx from 'clsx';
|
||||
@@ -22,161 +22,157 @@ import biteMyShinyMetalAss from './images/bite_my_shiny_metal_ass.svg';
|
||||
import iTookAnArrowInMyKnee from './images/i_took_an_arrow_in_my_knee.svg';
|
||||
|
||||
interface EmptyCaption {
|
||||
src: string;
|
||||
caption: string;
|
||||
src: string;
|
||||
caption: string;
|
||||
}
|
||||
|
||||
const emptyCaptions: ReadonlyArray<EmptyCaption> = [
|
||||
{
|
||||
// Homestuck
|
||||
src: thatFuckingPumpkin,
|
||||
caption: 'That fucking pumpkin',
|
||||
},
|
||||
{
|
||||
// Star Wars
|
||||
src: mayTheForceBeWithYou,
|
||||
caption: 'May The Force Be With You',
|
||||
},
|
||||
{
|
||||
// Futurama
|
||||
src: biteMyShinyMetalAss,
|
||||
caption: 'Bite my shiny metal ass',
|
||||
},
|
||||
{
|
||||
// The Elder Scrolls V: Skyrim
|
||||
src: iTookAnArrowInMyKnee,
|
||||
caption: 'I took an arrow in my knee',
|
||||
},
|
||||
{
|
||||
// Homestuck
|
||||
src: thatFuckingPumpkin,
|
||||
caption: 'That fucking pumpkin',
|
||||
},
|
||||
{
|
||||
// Star Wars
|
||||
src: mayTheForceBeWithYou,
|
||||
caption: 'May The Force Be With You',
|
||||
},
|
||||
{
|
||||
// Futurama
|
||||
src: biteMyShinyMetalAss,
|
||||
caption: 'Bite my shiny metal ass',
|
||||
},
|
||||
{
|
||||
// The Elder Scrolls V: Skyrim
|
||||
src: iTookAnArrowInMyKnee,
|
||||
caption: 'I took an arrow in my knee',
|
||||
},
|
||||
];
|
||||
|
||||
const itemHeight = 51;
|
||||
|
||||
export default class LanguageList extends React.Component<{
|
||||
selectedLocale: string;
|
||||
langs: LocalesMap;
|
||||
onChangeLang: (lang: string) => void;
|
||||
selectedLocale: string;
|
||||
langs: LocalesMap;
|
||||
onChangeLang: (lang: string) => void;
|
||||
}> {
|
||||
emptyListStateInner: HTMLDivElement | null;
|
||||
emptyListStateInner: HTMLDivElement | null;
|
||||
|
||||
render() {
|
||||
const { selectedLocale, langs } = this.props;
|
||||
const isListEmpty = Object.keys(langs).length === 0;
|
||||
const firstLocale = Object.keys(langs)[0] || null;
|
||||
const emptyCaption = this.getEmptyCaption();
|
||||
render() {
|
||||
const { selectedLocale, langs } = this.props;
|
||||
const isListEmpty = Object.keys(langs).length === 0;
|
||||
const firstLocale = Object.keys(langs)[0] || null;
|
||||
const emptyCaption = this.getEmptyCaption();
|
||||
|
||||
return (
|
||||
<TransitionMotion
|
||||
defaultStyles={this.getItemsWithDefaultStyles()}
|
||||
styles={this.getItemsWithStyles()}
|
||||
willLeave={this.willLeave}
|
||||
willEnter={this.willEnter}
|
||||
>
|
||||
{(items) => (
|
||||
<div className={styles.languagesList} data-testid="language-list">
|
||||
<div
|
||||
className={clsx(styles.emptyLanguagesListWrapper, {
|
||||
[styles.emptyLanguagesListVisible]: isListEmpty,
|
||||
})}
|
||||
style={{
|
||||
height:
|
||||
isListEmpty && this.emptyListStateInner
|
||||
? this.emptyListStateInner.clientHeight
|
||||
: 0,
|
||||
}}
|
||||
return (
|
||||
<TransitionMotion
|
||||
defaultStyles={this.getItemsWithDefaultStyles()}
|
||||
styles={this.getItemsWithStyles()}
|
||||
willLeave={this.willLeave}
|
||||
willEnter={this.willEnter}
|
||||
>
|
||||
<div
|
||||
ref={(elem: HTMLDivElement | null) =>
|
||||
(this.emptyListStateInner = elem)
|
||||
}
|
||||
className={styles.emptyLanguagesList}
|
||||
>
|
||||
<img
|
||||
src={emptyCaption.src}
|
||||
alt={emptyCaption.caption}
|
||||
className={styles.emptyLanguagesListCaption}
|
||||
/>
|
||||
<div className={styles.emptyLanguagesListSubtitle}>
|
||||
<Message {...messages.weDoNotSupportThisLang} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{(items) => (
|
||||
<div className={styles.languagesList} data-testid="language-list">
|
||||
<div
|
||||
className={clsx(styles.emptyLanguagesListWrapper, {
|
||||
[styles.emptyLanguagesListVisible]: isListEmpty,
|
||||
})}
|
||||
style={{
|
||||
height:
|
||||
isListEmpty && this.emptyListStateInner ? this.emptyListStateInner.clientHeight : 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={(elem: HTMLDivElement | null) => (this.emptyListStateInner = elem)}
|
||||
className={styles.emptyLanguagesList}
|
||||
>
|
||||
<img
|
||||
src={emptyCaption.src}
|
||||
alt={emptyCaption.caption}
|
||||
className={styles.emptyLanguagesListCaption}
|
||||
/>
|
||||
<div className={styles.emptyLanguagesListSubtitle}>
|
||||
<Message {...messages.weDoNotSupportThisLang} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{items.map(({ key: locale, data: definition, style }) => (
|
||||
<div
|
||||
key={locale}
|
||||
style={style}
|
||||
className={clsx(styles.languageItem, {
|
||||
[styles.activeLanguageItem]: locale === selectedLocale,
|
||||
[styles.firstLanguageItem]: locale === firstLocale,
|
||||
})}
|
||||
onClick={this.onChangeLang(locale)}
|
||||
>
|
||||
<LocaleItem locale={definition} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
);
|
||||
}
|
||||
{items.map(({ key: locale, data: definition, style }) => (
|
||||
<div
|
||||
key={locale}
|
||||
style={style}
|
||||
className={clsx(styles.languageItem, {
|
||||
[styles.activeLanguageItem]: locale === selectedLocale,
|
||||
[styles.firstLanguageItem]: locale === firstLocale,
|
||||
})}
|
||||
onClick={this.onChangeLang(locale)}
|
||||
>
|
||||
<LocaleItem locale={definition} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TransitionMotion>
|
||||
);
|
||||
}
|
||||
|
||||
getEmptyCaption(): EmptyCaption {
|
||||
return emptyCaptions[Math.floor(Math.random() * emptyCaptions.length)];
|
||||
}
|
||||
getEmptyCaption(): EmptyCaption {
|
||||
return emptyCaptions[Math.floor(Math.random() * emptyCaptions.length)];
|
||||
}
|
||||
|
||||
onChangeLang(lang: string): MouseEventHandler<HTMLDivElement> {
|
||||
return (event) => {
|
||||
event.preventDefault();
|
||||
onChangeLang(lang: string): MouseEventHandler<HTMLDivElement> {
|
||||
return (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.onChangeLang(lang);
|
||||
this.props.onChangeLang(lang);
|
||||
};
|
||||
}
|
||||
|
||||
getItemsWithDefaultStyles = (): Array<TransitionPlainStyle> => {
|
||||
return Object.keys({ ...this.props.langs }).reduce(
|
||||
(previous, key) => [
|
||||
...previous,
|
||||
{
|
||||
key,
|
||||
data: this.props.langs[key],
|
||||
style: {
|
||||
height: itemHeight,
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
[] as Array<TransitionPlainStyle>,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
getItemsWithDefaultStyles = (): Array<TransitionPlainStyle> => {
|
||||
return Object.keys({ ...this.props.langs }).reduce(
|
||||
(previous, key) => [
|
||||
...previous,
|
||||
{
|
||||
key,
|
||||
data: this.props.langs[key],
|
||||
style: {
|
||||
height: itemHeight,
|
||||
getItemsWithStyles = (): Array<TransitionStyle> => {
|
||||
return Object.keys({ ...this.props.langs }).reduce(
|
||||
(previous, key) => [
|
||||
...previous,
|
||||
{
|
||||
key,
|
||||
data: this.props.langs[key],
|
||||
style: {
|
||||
height: spring(itemHeight, presets.gentle),
|
||||
opacity: spring(1, presets.gentle),
|
||||
},
|
||||
},
|
||||
],
|
||||
[] as Array<TransitionStyle>,
|
||||
);
|
||||
};
|
||||
|
||||
willEnter(): PlainStyle {
|
||||
return {
|
||||
height: 0,
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
[] as Array<TransitionPlainStyle>,
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
getItemsWithStyles = (): Array<TransitionStyle> => {
|
||||
return Object.keys({ ...this.props.langs }).reduce(
|
||||
(previous, key) => [
|
||||
...previous,
|
||||
{
|
||||
key,
|
||||
data: this.props.langs[key],
|
||||
style: {
|
||||
height: spring(itemHeight, presets.gentle),
|
||||
opacity: spring(1, presets.gentle),
|
||||
},
|
||||
},
|
||||
],
|
||||
[] as Array<TransitionStyle>,
|
||||
);
|
||||
};
|
||||
|
||||
willEnter(): PlainStyle {
|
||||
return {
|
||||
height: 0,
|
||||
opacity: 1,
|
||||
};
|
||||
}
|
||||
|
||||
willLeave(): Style {
|
||||
return {
|
||||
height: spring(0),
|
||||
opacity: spring(0),
|
||||
};
|
||||
}
|
||||
willLeave(): Style {
|
||||
return {
|
||||
height: spring(0),
|
||||
opacity: spring(0),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -16,164 +16,158 @@ import { RootState } from 'app/reducers';
|
||||
const translateUrl = 'http://ely.by/translate';
|
||||
|
||||
export interface LocaleData {
|
||||
code: string;
|
||||
name: string;
|
||||
englishName: string;
|
||||
progress: number;
|
||||
isReleased: boolean;
|
||||
code: string;
|
||||
name: string;
|
||||
englishName: string;
|
||||
progress: number;
|
||||
isReleased: boolean;
|
||||
}
|
||||
|
||||
export type LocalesMap = Record<string, LocaleData>;
|
||||
|
||||
type OwnProps = {
|
||||
onClose: () => void;
|
||||
langs: LocalesMap;
|
||||
emptyCaptions: Array<{
|
||||
src: string;
|
||||
caption: string;
|
||||
}>;
|
||||
onClose: () => void;
|
||||
langs: LocalesMap;
|
||||
emptyCaptions: Array<{
|
||||
src: string;
|
||||
caption: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
interface Props extends OwnProps {
|
||||
intl: IntlShape;
|
||||
selectedLocale: string;
|
||||
changeLang: (lang: string) => void;
|
||||
intl: IntlShape;
|
||||
selectedLocale: string;
|
||||
changeLang: (lang: string) => void;
|
||||
}
|
||||
|
||||
class LanguageSwitcher extends React.Component<
|
||||
Props,
|
||||
{
|
||||
filter: string;
|
||||
filteredLangs: LocalesMap;
|
||||
}
|
||||
Props,
|
||||
{
|
||||
filter: string;
|
||||
filteredLangs: LocalesMap;
|
||||
}
|
||||
> {
|
||||
state = {
|
||||
filter: '',
|
||||
filteredLangs: this.props.langs,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
langs: LANGS,
|
||||
onClose() {},
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedLocale, onClose, intl } = this.props;
|
||||
const { filteredLangs } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.languageSwitcher}
|
||||
data-testid="language-switcher"
|
||||
data-e2e-active-locale={selectedLocale}
|
||||
>
|
||||
<div className={popupStyles.popup}>
|
||||
<div className={popupStyles.header}>
|
||||
<h2 className={popupStyles.headerTitle}>
|
||||
<Message {...messages.siteLanguage} />
|
||||
</h2>
|
||||
<span
|
||||
className={clsx(icons.close, popupStyles.close)}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.languageSwitcherBody}>
|
||||
<div className={styles.searchBox}>
|
||||
<input
|
||||
className={clsx(
|
||||
formStyles.lightTextField,
|
||||
formStyles.greenTextField,
|
||||
)}
|
||||
placeholder={intl.formatMessage(messages.startTyping)}
|
||||
onChange={this.onFilterUpdate}
|
||||
onKeyPress={this.onFilterKeyPress()}
|
||||
autoFocus
|
||||
/>
|
||||
<span className={styles.searchIcon} />
|
||||
</div>
|
||||
|
||||
<LanguageList
|
||||
selectedLocale={selectedLocale}
|
||||
langs={filteredLangs}
|
||||
onChangeLang={this.onChangeLang}
|
||||
/>
|
||||
|
||||
<div className={styles.improveTranslates}>
|
||||
<div className={styles.improveTranslatesIcon} />
|
||||
<div className={styles.improveTranslatesContent}>
|
||||
<div className={styles.improveTranslatesTitle}>
|
||||
<Message {...messages.improveTranslates} />
|
||||
</div>
|
||||
<div className={styles.improveTranslatesText}>
|
||||
<Message {...messages.improveTranslatesDescription} />{' '}
|
||||
<a href={translateUrl} target="_blank">
|
||||
<Message {...messages.improveTranslatesParticipate} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onChangeLang = this.changeLang.bind(this);
|
||||
|
||||
changeLang(lang: string) {
|
||||
this.props.changeLang(lang);
|
||||
|
||||
setTimeout(this.props.onClose, 300);
|
||||
}
|
||||
|
||||
onFilterUpdate = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const filter = event.currentTarget.value.trim().toLowerCase();
|
||||
const { langs } = this.props;
|
||||
|
||||
const result = Object.keys(langs).reduce((previous, key) => {
|
||||
if (
|
||||
langs[key].englishName.toLowerCase().indexOf(filter) === -1 &&
|
||||
langs[key].name.toLowerCase().indexOf(filter) === -1
|
||||
) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
previous[key] = langs[key];
|
||||
|
||||
return previous;
|
||||
}, {} as typeof langs);
|
||||
|
||||
this.setState({
|
||||
filter,
|
||||
filteredLangs: result,
|
||||
});
|
||||
};
|
||||
|
||||
onFilterKeyPress() {
|
||||
return (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key !== 'Enter' || this.state.filter === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const locales = Object.keys(this.state.filteredLangs);
|
||||
|
||||
if (locales.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.changeLang(locales[0]);
|
||||
state = {
|
||||
filter: '',
|
||||
filteredLangs: this.props.langs,
|
||||
};
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
langs: LANGS,
|
||||
onClose() {},
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedLocale, onClose, intl } = this.props;
|
||||
const { filteredLangs } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.languageSwitcher}
|
||||
data-testid="language-switcher"
|
||||
data-e2e-active-locale={selectedLocale}
|
||||
>
|
||||
<div className={popupStyles.popup}>
|
||||
<div className={popupStyles.header}>
|
||||
<h2 className={popupStyles.headerTitle}>
|
||||
<Message {...messages.siteLanguage} />
|
||||
</h2>
|
||||
<span className={clsx(icons.close, popupStyles.close)} onClick={onClose} />
|
||||
</div>
|
||||
|
||||
<div className={styles.languageSwitcherBody}>
|
||||
<div className={styles.searchBox}>
|
||||
<input
|
||||
className={clsx(formStyles.lightTextField, formStyles.greenTextField)}
|
||||
placeholder={intl.formatMessage(messages.startTyping)}
|
||||
onChange={this.onFilterUpdate}
|
||||
onKeyPress={this.onFilterKeyPress()}
|
||||
autoFocus
|
||||
/>
|
||||
<span className={styles.searchIcon} />
|
||||
</div>
|
||||
|
||||
<LanguageList
|
||||
selectedLocale={selectedLocale}
|
||||
langs={filteredLangs}
|
||||
onChangeLang={this.onChangeLang}
|
||||
/>
|
||||
|
||||
<div className={styles.improveTranslates}>
|
||||
<div className={styles.improveTranslatesIcon} />
|
||||
<div className={styles.improveTranslatesContent}>
|
||||
<div className={styles.improveTranslatesTitle}>
|
||||
<Message {...messages.improveTranslates} />
|
||||
</div>
|
||||
<div className={styles.improveTranslatesText}>
|
||||
<Message {...messages.improveTranslatesDescription} />{' '}
|
||||
<a href={translateUrl} target="_blank">
|
||||
<Message {...messages.improveTranslatesParticipate} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onChangeLang = this.changeLang.bind(this);
|
||||
|
||||
changeLang(lang: string) {
|
||||
this.props.changeLang(lang);
|
||||
|
||||
setTimeout(this.props.onClose, 300);
|
||||
}
|
||||
|
||||
onFilterUpdate = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const filter = event.currentTarget.value.trim().toLowerCase();
|
||||
const { langs } = this.props;
|
||||
|
||||
const result = Object.keys(langs).reduce((previous, key) => {
|
||||
if (
|
||||
langs[key].englishName.toLowerCase().indexOf(filter) === -1 &&
|
||||
langs[key].name.toLowerCase().indexOf(filter) === -1
|
||||
) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
previous[key] = langs[key];
|
||||
|
||||
return previous;
|
||||
}, {} as typeof langs);
|
||||
|
||||
this.setState({
|
||||
filter,
|
||||
filteredLangs: result,
|
||||
});
|
||||
};
|
||||
|
||||
onFilterKeyPress() {
|
||||
return (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key !== 'Enter' || this.state.filter === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const locales = Object.keys(this.state.filteredLangs);
|
||||
|
||||
if (locales.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.changeLang(locales[0]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(
|
||||
connect(
|
||||
(state: RootState) => ({
|
||||
selectedLocale: state.i18n.locale,
|
||||
}),
|
||||
{
|
||||
changeLang,
|
||||
},
|
||||
)(LanguageSwitcher),
|
||||
connect(
|
||||
(state: RootState) => ({
|
||||
selectedLocale: state.i18n.locale,
|
||||
}),
|
||||
{
|
||||
changeLang,
|
||||
},
|
||||
)(LanguageSwitcher),
|
||||
);
|
||||
|
@@ -7,44 +7,42 @@ import styles from './languageSwitcher.scss';
|
||||
import { LocaleData } from './LanguageSwitcher';
|
||||
|
||||
interface Props {
|
||||
locale: LocaleData;
|
||||
locale: LocaleData;
|
||||
}
|
||||
|
||||
const LocaleItem: ComponentType<Props> = ({
|
||||
locale: { code, name, englishName, progress, isReleased },
|
||||
}) => {
|
||||
let progressLabel: ReactNode;
|
||||
const LocaleItem: ComponentType<Props> = ({ locale: { code, name, englishName, progress, isReleased } }) => {
|
||||
let progressLabel: ReactNode;
|
||||
|
||||
if (progress !== 100) {
|
||||
progressLabel = (
|
||||
<Message
|
||||
{...messages.translationProgress}
|
||||
values={{
|
||||
progress,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (!isReleased) {
|
||||
progressLabel = <Message {...messages.mayBeInaccurate} />;
|
||||
}
|
||||
if (progress !== 100) {
|
||||
progressLabel = (
|
||||
<Message
|
||||
{...messages.translationProgress}
|
||||
values={{
|
||||
progress,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (!isReleased) {
|
||||
progressLabel = <Message {...messages.mayBeInaccurate} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.languageFlex}>
|
||||
<div
|
||||
className={styles.languageIco}
|
||||
style={{
|
||||
backgroundImage: `url('${localeFlags.getIconUrl(code)}')`,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.languageCaptions}>
|
||||
<div className={styles.languageName}>{name}</div>
|
||||
<div className={styles.languageSubName}>
|
||||
{englishName} {progressLabel ? '| ' : ''} {progressLabel}
|
||||
return (
|
||||
<div className={styles.languageFlex}>
|
||||
<div
|
||||
className={styles.languageIco}
|
||||
style={{
|
||||
backgroundImage: `url('${localeFlags.getIconUrl(code)}')`,
|
||||
}}
|
||||
/>
|
||||
<div className={styles.languageCaptions}>
|
||||
<div className={styles.languageName}>{name}</div>
|
||||
<div className={styles.languageSubName}>
|
||||
{englishName} {progressLabel ? '| ' : ''} {progressLabel}
|
||||
</div>
|
||||
</div>
|
||||
<span className={styles.languageCircle} />
|
||||
</div>
|
||||
</div>
|
||||
<span className={styles.languageCircle} />
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default LocaleItem;
|
||||
|
@@ -10,34 +10,32 @@ import { RootState } from 'app/reducers';
|
||||
import styles from './link.scss';
|
||||
|
||||
const LanguageLink: ComponentType = () => {
|
||||
const dispatch = useDispatch();
|
||||
const showLanguageSwitcherPopup = useCallback(() => {
|
||||
dispatch(createPopup({ Popup: LanguageSwitcher }));
|
||||
}, [dispatch]);
|
||||
const dispatch = useDispatch();
|
||||
const showLanguageSwitcherPopup = useCallback(() => {
|
||||
dispatch(createPopup({ Popup: LanguageSwitcher }));
|
||||
}, [dispatch]);
|
||||
|
||||
const userLang = useSelector((state: RootState) => state.user.lang);
|
||||
const interfaceLocale = useSelector((state: RootState) => state.i18n.locale);
|
||||
const userLang = useSelector((state: RootState) => state.user.lang);
|
||||
const interfaceLocale = useSelector((state: RootState) => state.i18n.locale);
|
||||
|
||||
const localeDefinition = LANGS[userLang] || LANGS[interfaceLocale];
|
||||
const localeDefinition = LANGS[userLang] || LANGS[interfaceLocale];
|
||||
|
||||
return (
|
||||
<span
|
||||
className={clsx(styles.languageLink, {
|
||||
[styles.mark]: userLang !== interfaceLocale,
|
||||
})}
|
||||
onClick={showLanguageSwitcherPopup}
|
||||
>
|
||||
<span
|
||||
className={styles.languageIcon}
|
||||
style={{
|
||||
backgroundImage: `url('${localeFlags.getIconUrl(
|
||||
localeDefinition.code,
|
||||
)}')`,
|
||||
}}
|
||||
/>
|
||||
{localeDefinition.name}
|
||||
</span>
|
||||
);
|
||||
return (
|
||||
<span
|
||||
className={clsx(styles.languageLink, {
|
||||
[styles.mark]: userLang !== interfaceLocale,
|
||||
})}
|
||||
onClick={showLanguageSwitcherPopup}
|
||||
>
|
||||
<span
|
||||
className={styles.languageIcon}
|
||||
style={{
|
||||
backgroundImage: `url('${localeFlags.getIconUrl(localeDefinition.code)}')`,
|
||||
}}
|
||||
/>
|
||||
{localeDefinition.name}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageLink;
|
||||
|
@@ -1,35 +1,35 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
|
||||
.languageLink {
|
||||
position: relative;
|
||||
position: relative;
|
||||
|
||||
color: #666;
|
||||
border-bottom: 1px dotted #666;
|
||||
text-decoration: none;
|
||||
transition: 0.25s;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
border-bottom: 1px dotted #666;
|
||||
text-decoration: none;
|
||||
transition: 0.25s;
|
||||
cursor: pointer;
|
||||
|
||||
&.mark {
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '*';
|
||||
color: $red;
|
||||
font-size: 75%;
|
||||
top: -0.2em;
|
||||
&.mark {
|
||||
&:after {
|
||||
position: absolute;
|
||||
content: '*';
|
||||
color: $red;
|
||||
font-size: 75%;
|
||||
top: -0.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.languageIcon {
|
||||
$height: 13px;
|
||||
$height: 13px;
|
||||
|
||||
position: relative;
|
||||
top: 1px;
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
height: $height;
|
||||
width: $height * 4 / 3;
|
||||
box-shadow: 0 0 1px rgba(#000, 0.2);
|
||||
position: relative;
|
||||
top: 1px;
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
height: $height;
|
||||
width: $height * 4 / 3;
|
||||
box-shadow: 0 0 1px rgba(#000, 0.2);
|
||||
|
||||
background-size: cover;
|
||||
background-size: cover;
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"siteLanguage": "Site language",
|
||||
"startTyping": "Start typing…",
|
||||
"translationProgress": "{progress}% translated",
|
||||
"mayBeInaccurate": "May be inaccurate",
|
||||
"weDoNotSupportThisLang": "Sorry, we do not support this language",
|
||||
"improveTranslates": "Improve Ely.by translation",
|
||||
"improveTranslatesDescription": "Ely.by’s localization is a community effort. If you want to improve the translation of Ely.by, we'd love your help.",
|
||||
"improveTranslatesParticipate": "Click here to participate."
|
||||
"siteLanguage": "Site language",
|
||||
"startTyping": "Start typing…",
|
||||
"translationProgress": "{progress}% translated",
|
||||
"mayBeInaccurate": "May be inaccurate",
|
||||
"weDoNotSupportThisLang": "Sorry, we do not support this language",
|
||||
"improveTranslates": "Improve Ely.by translation",
|
||||
"improveTranslatesDescription": "Ely.by’s localization is a community effort. If you want to improve the translation of Ely.by, we'd love your help.",
|
||||
"improveTranslatesParticipate": "Click here to participate."
|
||||
}
|
||||
|
@@ -3,229 +3,229 @@
|
||||
@import '~app/components/ui/popup/popup.scss';
|
||||
|
||||
@mixin hideFooter {
|
||||
@media (max-height: 455px) {
|
||||
@content;
|
||||
}
|
||||
@media (max-height: 455px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
.languageSwitcher {
|
||||
composes: popupWrapper from '~app/components/ui/popup/popup.scss';
|
||||
composes: popupWrapper from '~app/components/ui/popup/popup.scss';
|
||||
|
||||
@include popupBounding(400px);
|
||||
@include popupBounding(400px);
|
||||
}
|
||||
|
||||
.languageSwitcherBody {
|
||||
composes: body from '~app/components/ui/popup/popup.scss';
|
||||
composes: body from '~app/components/ui/popup/popup.scss';
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 132px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 132px);
|
||||
|
||||
@media screen and (min-height: 630px) {
|
||||
max-height: 500px;
|
||||
}
|
||||
@media screen and (min-height: 630px) {
|
||||
max-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.searchBox {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
composes: search from '~app/components/ui/icons.scss';
|
||||
composes: search from '~app/components/ui/icons.scss';
|
||||
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 12px;
|
||||
font-size: 22px;
|
||||
color: #edebe5;
|
||||
pointer-events: none; // Иконка чисто декоративная, так что клик должен проходить сквозь неё
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 12px;
|
||||
font-size: 22px;
|
||||
color: #edebe5;
|
||||
pointer-events: none; // Иконка чисто декоративная, так что клик должен проходить сквозь неё
|
||||
}
|
||||
|
||||
$languageListBorderColor: #eee;
|
||||
$languageListBorderStyle: 1px solid $languageListBorderColor;
|
||||
|
||||
.languagesList {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
border-top: $languageListBorderStyle;
|
||||
border-bottom: $languageListBorderStyle;
|
||||
margin-bottom: 20px;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
border-top: $languageListBorderStyle;
|
||||
border-bottom: $languageListBorderStyle;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #d8d5d0;
|
||||
}
|
||||
|
||||
@include hideFooter {
|
||||
& {
|
||||
margin-bottom: 0;
|
||||
&::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #d8d5d0;
|
||||
}
|
||||
|
||||
@include hideFooter {
|
||||
& {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.languageItem {
|
||||
font-family: $font-family-title;
|
||||
transition: background-color 0.25s;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
font-family: $font-family-title;
|
||||
transition: background-color 0.25s;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: $whiteButtonLight;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $whiteButtonLight;
|
||||
}
|
||||
}
|
||||
|
||||
.languageFlex {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-top: $languageListBorderStyle;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-top: $languageListBorderStyle;
|
||||
|
||||
.firstLanguageItem & {
|
||||
border-top: none;
|
||||
}
|
||||
.firstLanguageItem & {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.languageIco {
|
||||
display: inline-block;
|
||||
margin-right: 7px;
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
box-shadow: 0 0 1px rgba(#000, 0.2);
|
||||
display: inline-block;
|
||||
margin-right: 7px;
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
box-shadow: 0 0 1px rgba(#000, 0.2);
|
||||
|
||||
background: no-repeat;
|
||||
background-size: cover;
|
||||
background: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.languageCaptions {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.languageName {
|
||||
font-size: 15px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 15px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.languageSubName {
|
||||
font-size: 11px;
|
||||
color: #ccc;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 11px;
|
||||
color: #ccc;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// Реализация радио кнопки. Когда у нас будет нормальный компонент радио кнопок, нужно будет перейти на него
|
||||
.languageCircle {
|
||||
composes: checkmark from '~app/components/ui/icons.scss';
|
||||
composes: checkmark from '~app/components/ui/icons.scss';
|
||||
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
right: -32px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
right: -32px;
|
||||
|
||||
font-size: 10px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
border: 2px solid #dcd8cd;
|
||||
border-radius: 50%;
|
||||
font-size: 10px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
border: 2px solid #dcd8cd;
|
||||
border-radius: 50%;
|
||||
|
||||
transition: 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
|
||||
&:before {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.languageItem:hover & {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.activeLanguageItem & {
|
||||
border-color: $green;
|
||||
background: $green;
|
||||
right: 0;
|
||||
transition: 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
|
||||
&:before {
|
||||
opacity: 1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.languageItem:hover & {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.activeLanguageItem & {
|
||||
border-color: $green;
|
||||
background: $green;
|
||||
right: 0;
|
||||
|
||||
&:before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emptyLanguagesListWrapper {
|
||||
$transitionTime: 0.5s;
|
||||
$transitionTime: 0.5s;
|
||||
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition: height $transitionTime;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition: height $transitionTime;
|
||||
|
||||
&.emptyLanguagesListVisible {
|
||||
opacity: 1;
|
||||
transition: $transitionTime;
|
||||
}
|
||||
&.emptyLanguagesListVisible {
|
||||
opacity: 1;
|
||||
transition: $transitionTime;
|
||||
}
|
||||
}
|
||||
|
||||
.emptyLanguagesList {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.emptyLanguagesListCaption {
|
||||
height: 20px;
|
||||
max-width: 100%;
|
||||
margin-bottom: 5px;
|
||||
height: 20px;
|
||||
max-width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.emptyLanguagesListSubtitle {
|
||||
font-family: $font-family-title;
|
||||
color: #ccc;
|
||||
font-size: 13px;
|
||||
font-family: $font-family-title;
|
||||
color: #ccc;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.improveTranslates {
|
||||
border: 1px solid #dedede;
|
||||
background: #f3f1ed;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid #dedede;
|
||||
background: #f3f1ed;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
||||
@include hideFooter {
|
||||
& {
|
||||
display: none;
|
||||
@include hideFooter {
|
||||
& {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.improveTranslatesIcon {
|
||||
composes: translate from '~app/components/ui/icons.scss';
|
||||
composes: translate from '~app/components/ui/icons.scss';
|
||||
|
||||
color: lighter($blue);
|
||||
font-size: 22px;
|
||||
margin-right: 10px;
|
||||
color: lighter($blue);
|
||||
font-size: 22px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.improveTranslatesContent {
|
||||
}
|
||||
|
||||
.improveTranslatesTitle {
|
||||
font-family: $font-family-title;
|
||||
font-size: 13px;
|
||||
margin-bottom: 3px;
|
||||
font-family: $font-family-title;
|
||||
font-size: 13px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.improveTranslatesText {
|
||||
font-size: 10px;
|
||||
color: #9a9a9a;
|
||||
line-height: 1.2;
|
||||
font-size: 10px;
|
||||
color: #9a9a9a;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
Reference in New Issue
Block a user