mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-02-18 16:27:49 +05:30
#379: split languageSwitcher to smaller components
This commit is contained in:
parent
b19eeb9e94
commit
7dbd569d45
@ -109,7 +109,7 @@ function sortByKeys(object) {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
function trimValueInBrackets(value) {
|
function trimValueInBrackets(value) {
|
||||||
return value.match(/^([^\(]+)/)[0].trim();
|
return value.match(/^([^(]+)/)[0].trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pullReadyLanguages() {
|
async function pullReadyLanguages() {
|
||||||
@ -141,6 +141,7 @@ async function pull() {
|
|||||||
const mapFileContent = {};
|
const mapFileContent = {};
|
||||||
langs.map((elem) => {
|
langs.map((elem) => {
|
||||||
mapFileContent[elem.locale] = {
|
mapFileContent[elem.locale] = {
|
||||||
|
code: elem.locale,
|
||||||
name: ORIGINAL_NAMES_MAP[elem.locale] || trimValueInBrackets(elem.local_name),
|
name: ORIGINAL_NAMES_MAP[elem.locale] || trimValueInBrackets(elem.local_name),
|
||||||
englishName: ENGLISH_NAMES_MAP[elem.locale] || trimValueInBrackets(elem.english_name),
|
englishName: ENGLISH_NAMES_MAP[elem.locale] || trimValueInBrackets(elem.english_name),
|
||||||
progress: parseFloat(elem.translation_progress),
|
progress: parseFloat(elem.translation_progress),
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { FormattedMessage as Message } from 'react-intl';
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
|
import ContactForm from 'components/contact/ContactForm';
|
||||||
|
import LanguageSwitcher from 'components/languageSwitcher';
|
||||||
|
import { create as createPopup } from 'components/ui/popup/actions';
|
||||||
|
|
||||||
import styles from './footerMenu.scss';
|
import styles from './footerMenu.scss';
|
||||||
import messages from './footerMenu.intl.json';
|
import messages from './footerMenu.intl.json';
|
||||||
@ -47,11 +50,6 @@ class FooterMenu extends Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import ContactForm from 'components/contact/ContactForm';
|
|
||||||
import LanguageSwitcher from 'components/languageSwitcher/LanguageSwitcher';
|
|
||||||
import { create as createPopup } from 'components/ui/popup/actions';
|
|
||||||
|
|
||||||
// mark this component, as not pure, because it is stateless,
|
// mark this component, as not pure, because it is stateless,
|
||||||
// but should be re-rendered, if current lang was changed
|
// but should be re-rendered, if current lang was changed
|
||||||
export default connect(null, {
|
export default connect(null, {
|
||||||
|
148
src/components/languageSwitcher/LanguageList.js
Normal file
148
src/components/languageSwitcher/LanguageList.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { TransitionMotion, spring, presets } from 'react-motion';
|
||||||
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import LocaleItem from './LocaleItem';
|
||||||
|
import messages from './languageSwitcher.intl.json';
|
||||||
|
import styles from './languageSwitcher.scss';
|
||||||
|
|
||||||
|
import thatFuckingPumpkin from './images/that_fucking_pumpkin.svg';
|
||||||
|
import mayTheForceBeWithYou from './images/may_the_force_be_with_you.svg';
|
||||||
|
import biteMyShinyMetalAss from './images/bite_my_shiny_metal_ass.svg';
|
||||||
|
import iTookAnArrowInMyKnee from './images/i_took_an_arrow_in_my_knee.svg';
|
||||||
|
|
||||||
|
import type { LocalesMap } from './LanguageSwitcher';
|
||||||
|
|
||||||
|
const itemHeight = 51;
|
||||||
|
|
||||||
|
export default class LanguageList extends React.Component<{
|
||||||
|
userLang: string,
|
||||||
|
langs: LocalesMap,
|
||||||
|
onChangeLang: (lang: string) => void,
|
||||||
|
}> {
|
||||||
|
emptyListStateInner: ?HTMLDivElement;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { userLang, 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}>
|
||||||
|
<div
|
||||||
|
className={classNames(styles.emptyLanguagesListWrapper, {
|
||||||
|
[styles.emptyLanguagesListVisible]: isListEmpty,
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
height: isListEmpty && this.emptyListStateInner ? this.emptyListStateInner.clientHeight : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={(elem: ?HTMLDivElement) => 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={classNames(styles.languageItem, {
|
||||||
|
[styles.activeLanguageItem]: locale === userLang,
|
||||||
|
[styles.firstLanguageItem]: locale === firstLocale,
|
||||||
|
})}
|
||||||
|
onClick={this.onChangeLang(locale)}
|
||||||
|
>
|
||||||
|
<LocaleItem locale={definition} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</TransitionMotion>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmptyCaption() {
|
||||||
|
const emptyCaptions = [
|
||||||
|
{
|
||||||
|
// 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',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return emptyCaptions[Math.floor(Math.random() * emptyCaptions.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeLang(lang: string) {
|
||||||
|
return (event: SyntheticMouseEvent<HTMLElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.props.onChangeLang(lang);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemsWithDefaultStyles = () => this.getItemsWithStyles({ useSpring: false });
|
||||||
|
|
||||||
|
getItemsWithStyles = ({ useSpring }: { useSpring?: bool } = { useSpring: true }) =>
|
||||||
|
Object.keys({...this.props.langs})
|
||||||
|
.reduce((previous, key) => [
|
||||||
|
...previous,
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
data: this.props.langs[key],
|
||||||
|
style: {
|
||||||
|
height: useSpring ? spring(itemHeight, presets.gentle) : itemHeight,
|
||||||
|
opacity: useSpring ? spring(1, presets.gentle) : 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
], []);
|
||||||
|
|
||||||
|
willEnter() {
|
||||||
|
return {
|
||||||
|
height: 0,
|
||||||
|
opacity: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
willLeave() {
|
||||||
|
return {
|
||||||
|
height: spring(0),
|
||||||
|
opacity: spring(0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +1,41 @@
|
|||||||
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { TransitionMotion, spring, presets } from 'react-motion';
|
|
||||||
import { FormattedMessage as Message, intlShape } from 'react-intl';
|
import { FormattedMessage as Message, intlShape } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { localeFlags } from 'components/i18n';
|
|
||||||
import LANGS from 'i18n/index.json';
|
import LANGS from 'i18n/index.json';
|
||||||
|
|
||||||
import formStyles from 'components/ui/form/form.scss';
|
import formStyles from 'components/ui/form/form.scss';
|
||||||
import popupStyles from 'components/ui/popup/popup.scss';
|
import popupStyles from 'components/ui/popup/popup.scss';
|
||||||
import icons from 'components/ui/icons.scss';
|
import icons from 'components/ui/icons.scss';
|
||||||
import styles from './languageSwitcher.scss';
|
import styles from './languageSwitcher.scss';
|
||||||
import messages from './languageSwitcher.intl.json';
|
import messages from './languageSwitcher.intl.json';
|
||||||
|
import LanguageList from './LanguageList';
|
||||||
import thatFuckingPumpkin from './images/that_fucking_pumpkin.svg';
|
|
||||||
import mayTheForceBeWithYou from './images/may_the_force_be_with_you.svg';
|
|
||||||
import biteMyShinyMetalAss from './images/bite_my_shiny_metal_ass.svg';
|
|
||||||
import iTookAnArrowInMyKnee from './images/i_took_an_arrow_in_my_knee.svg';
|
|
||||||
|
|
||||||
const improveTranslationUrl = 'http://ely.by/erickskrauch/posts/174943';
|
const improveTranslationUrl = 'http://ely.by/erickskrauch/posts/174943';
|
||||||
const itemHeight = 51;
|
|
||||||
|
|
||||||
class LanguageSwitcher extends Component {
|
export type LocaleData = {
|
||||||
static displayName = 'LanguageSwitcher';
|
code: string,
|
||||||
|
name: string,
|
||||||
|
englishName: string,
|
||||||
|
progress: number,
|
||||||
|
isReleased: bool,
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
export type LocalesMap = {[code: string]: LocaleData};
|
||||||
onClose: PropTypes.func,
|
|
||||||
userLang: PropTypes.string,
|
|
||||||
changeLang: PropTypes.func,
|
|
||||||
langs: PropTypes.objectOf(PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
englishName: PropTypes.string.isRequired,
|
|
||||||
progress: PropTypes.number.isRequired,
|
|
||||||
isReleased: PropTypes.bool.isRequired,
|
|
||||||
})).isRequired,
|
|
||||||
emptyCaptions: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
src: PropTypes.string.isRequired,
|
|
||||||
caption: PropTypes.string.isRequired,
|
|
||||||
})).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
class LanguageSwitcher extends Component<{
|
||||||
|
onClose: Function,
|
||||||
|
userLang: string,
|
||||||
|
changeLang: (lang: string) => void,
|
||||||
|
langs: LocalesMap,
|
||||||
|
emptyCaptions: Array<{
|
||||||
|
src: string,
|
||||||
|
caption: string,
|
||||||
|
}>,
|
||||||
|
}, {
|
||||||
|
filter: string,
|
||||||
|
filteredLangs: LocalesMap,
|
||||||
|
}> {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
};
|
};
|
||||||
@ -53,35 +48,11 @@ class LanguageSwitcher extends Component {
|
|||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
langs: LANGS,
|
langs: LANGS,
|
||||||
onClose() {},
|
onClose() {},
|
||||||
emptyCaptions: [
|
|
||||||
{
|
|
||||||
// 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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {userLang, onClose, emptyCaptions} = this.props;
|
const { userLang, onClose } = this.props;
|
||||||
const isListEmpty = Object.keys(this.state.filteredLangs).length === 0;
|
const { filteredLangs } = this.state;
|
||||||
const firstLocale = Object.keys(this.state.filteredLangs)[0] || null;
|
|
||||||
const emptyCaption = emptyCaptions[Math.floor(Math.random() * emptyCaptions.length)];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.languageSwitcher}>
|
<div className={styles.languageSwitcher}>
|
||||||
@ -108,53 +79,11 @@ class LanguageSwitcher extends Component {
|
|||||||
<span className={styles.searchIcon} />
|
<span className={styles.searchIcon} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TransitionMotion
|
<LanguageList
|
||||||
defaultStyles={this.getItemsWithDefaultStyles()}
|
userLang={userLang}
|
||||||
styles={this.getItemsWithStyles()}
|
langs={filteredLangs}
|
||||||
willLeave={this.willLeave}
|
onChangeLang={this.onChangeLang}
|
||||||
willEnter={this.willEnter}
|
/>
|
||||||
>
|
|
||||||
{(items) => (
|
|
||||||
<div className={styles.languagesList}>
|
|
||||||
<div
|
|
||||||
className={classNames(styles.emptyLanguagesListWrapper, {
|
|
||||||
[styles.emptyLanguagesListVisible]: isListEmpty,
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
height: isListEmpty ? this.emptyListStateInner.clientHeight : 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
ref={(elem) => 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}) => (
|
|
||||||
<li
|
|
||||||
key={locale}
|
|
||||||
style={style}
|
|
||||||
className={classNames(styles.languageItem, {
|
|
||||||
[styles.activeLanguageItem]: locale === userLang,
|
|
||||||
[styles.firstLanguageItem]: locale === firstLocale,
|
|
||||||
})}
|
|
||||||
onClick={this.onChangeLang(locale)}
|
|
||||||
>
|
|
||||||
{this.renderLanguageItem(locale, definition)}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TransitionMotion>
|
|
||||||
|
|
||||||
<div className={styles.improveTranslates}>
|
<div className={styles.improveTranslates}>
|
||||||
<div className={styles.improveTranslatesIcon} />
|
<div className={styles.improveTranslatesIcon} />
|
||||||
@ -179,57 +108,21 @@ class LanguageSwitcher extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLanguageItem(locale, localeData) {
|
onChangeLang = this.changeLang.bind(this);
|
||||||
const {name, progress, isReleased} = localeData;
|
|
||||||
let progressLabel;
|
|
||||||
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(locale)}')`,
|
|
||||||
}} />
|
|
||||||
<div className={styles.languageCaptions}>
|
|
||||||
<div className={styles.languageName}>
|
|
||||||
{name}
|
|
||||||
</div>
|
|
||||||
<div className={styles.languageSubName}>
|
|
||||||
{localeData.englishName} {progressLabel ? '| ' : ''} {progressLabel}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span className={styles.languageCircle} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onChangeLang(lang) {
|
|
||||||
return (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
this.changeLang(lang);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
changeLang(lang) {
|
changeLang(lang) {
|
||||||
this.props.changeLang(lang);
|
this.props.changeLang(lang);
|
||||||
|
|
||||||
setTimeout(this.props.onClose, 300);
|
setTimeout(this.props.onClose, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterUpdate = (event) => {
|
onFilterUpdate = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
const filter = event.target.value.trim().toLowerCase();
|
const filter = event.currentTarget.value.trim().toLowerCase();
|
||||||
const { langs } = this.props;
|
const { langs } = this.props;
|
||||||
|
|
||||||
const result = Object.keys(langs).reduce((previous, key) => {
|
const result = Object.keys(langs).reduce((previous, key) => {
|
||||||
if (langs[key].englishName.toLowerCase().search(filter) === -1
|
if (langs[key].englishName.toLowerCase().search(filter) === -1
|
||||||
&& langs[key].name.toLowerCase().search(filter) === -1
|
&& langs[key].name.toLowerCase().search(filter) === -1
|
||||||
) {
|
) {
|
||||||
return previous;
|
return previous;
|
||||||
}
|
}
|
||||||
@ -246,12 +139,13 @@ class LanguageSwitcher extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onFilterKeyPress() {
|
onFilterKeyPress() {
|
||||||
return (event) => {
|
return (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||||
if (event.key !== 'Enter' || this.state.filter === '') {
|
if (event.key !== 'Enter' || this.state.filter === '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const locales = Object.keys(this.state.filteredLangs);
|
const locales = Object.keys(this.state.filteredLangs);
|
||||||
|
|
||||||
if (locales.length === 0) {
|
if (locales.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -259,46 +153,6 @@ class LanguageSwitcher extends Component {
|
|||||||
this.changeLang(locales[0]);
|
this.changeLang(locales[0]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getItemsWithDefaultStyles = () => Object.keys(this.props.langs).reduce((previous, key) => {
|
|
||||||
return [
|
|
||||||
...previous,
|
|
||||||
{
|
|
||||||
key,
|
|
||||||
data: this.props.langs[key],
|
|
||||||
style: {
|
|
||||||
height: itemHeight,
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
getItemsWithStyles = () => Object.keys({...this.state.filteredLangs}).reduce((previous, key) => [
|
|
||||||
...previous,
|
|
||||||
{
|
|
||||||
key,
|
|
||||||
data: this.props.langs[key],
|
|
||||||
style: {
|
|
||||||
height: spring(itemHeight, presets.gentle),
|
|
||||||
opacity: spring(1, presets.gentle),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
], []);
|
|
||||||
|
|
||||||
willEnter() {
|
|
||||||
return {
|
|
||||||
height: 0,
|
|
||||||
opacity: 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
willLeave() {
|
|
||||||
return {
|
|
||||||
height: spring(0),
|
|
||||||
opacity: spring(0),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
47
src/components/languageSwitcher/LocaleItem.js
Normal file
47
src/components/languageSwitcher/LocaleItem.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { localeFlags } from 'components/i18n';
|
||||||
|
import { FormattedMessage as Message } from 'react-intl';
|
||||||
|
import messages from './languageSwitcher.intl.json';
|
||||||
|
import styles from './languageSwitcher.scss';
|
||||||
|
import type { LocaleData } from './LanguageSwitcher';
|
||||||
|
|
||||||
|
export default function LocaleItem({
|
||||||
|
locale,
|
||||||
|
}: {
|
||||||
|
locale: LocaleData,
|
||||||
|
}) {
|
||||||
|
const {code, name, englishName, progress, isReleased} = locale;
|
||||||
|
|
||||||
|
let progressLabel;
|
||||||
|
|
||||||
|
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}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className={styles.languageCircle} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { create as createPopup } from 'components/ui/popup/actions';
|
||||||
|
import { localeFlags } from 'components/i18n';
|
||||||
|
import LANGS from 'i18n/index.json';
|
||||||
|
import LanguageSwitcher from '../LanguageSwitcher';
|
||||||
|
import styles from './link.scss';
|
||||||
|
|
||||||
|
function LanguageLink({
|
||||||
|
userLang,
|
||||||
|
showLanguageSwitcherPopup,
|
||||||
|
}: {
|
||||||
|
userLang: string,
|
||||||
|
showLanguageSwitcherPopup: Function,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<span className={styles.languageLink} onClick={showLanguageSwitcherPopup}>
|
||||||
|
<span className={styles.languageIcon} style={{
|
||||||
|
backgroundImage: `url('${localeFlags.getIconUrl(userLang)}')`
|
||||||
|
}} />
|
||||||
|
{LANGS[userLang].name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect((state) => ({
|
||||||
|
userLang: state.user.lang,
|
||||||
|
}), {
|
||||||
|
showLanguageSwitcherPopup: () => createPopup(LanguageSwitcher),
|
||||||
|
})(LanguageLink);
|
21
src/components/languageSwitcher/changeLanguageLink/link.scss
Normal file
21
src/components/languageSwitcher/changeLanguageLink/link.scss
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.languageLink {
|
||||||
|
color: #666;
|
||||||
|
border-bottom: 1px dotted #666;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: .25s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageIcon {
|
||||||
|
$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, .2);
|
||||||
|
|
||||||
|
background-size: cover;
|
||||||
|
}
|
3
src/components/languageSwitcher/index.js
Normal file
3
src/components/languageSwitcher/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// @flow
|
||||||
|
export { default } from './LanguageSwitcher';
|
||||||
|
export { default as ChangeLanguageLink } from './changeLanguageLink/ChangeLanguageLink';
|
@ -76,6 +76,7 @@ $languageListBorderStyle: 1px solid $languageListBorderColor;
|
|||||||
transition: background-color .25s;
|
transition: background-color .25s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $whiteButtonLight;
|
background-color: $whiteButtonLight;
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
|
// @flow
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { FormattedMessage as Message, FormattedRelative as Relative } from 'react-intl';
|
import { FormattedMessage as Message, FormattedRelative as Relative } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
import { ChangeLanguageLink } from 'components/languageSwitcher';
|
||||||
import { localeFlags } from 'components/i18n';
|
|
||||||
import LANGS from 'i18n/index.json';
|
|
||||||
|
|
||||||
import { userShape } from 'components/user/User';
|
|
||||||
|
|
||||||
import ProfileField from './ProfileField';
|
import ProfileField from './ProfileField';
|
||||||
import styles from './profile.scss';
|
import styles from './profile.scss';
|
||||||
import profileForm from './profileForm.scss';
|
import profileForm from './profileForm.scss';
|
||||||
import messages from './Profile.intl.json';
|
import messages from './Profile.intl.json';
|
||||||
|
|
||||||
import RulesPage from 'pages/rules/RulesPage';
|
import RulesPage from 'pages/rules/RulesPage';
|
||||||
|
|
||||||
class Profile extends Component {
|
import type { User } from 'components/user';
|
||||||
static displayName = 'Profile';
|
|
||||||
static propTypes = {
|
class Profile extends Component<{
|
||||||
user: userShape,
|
user: User
|
||||||
createLanguageSwitcherPopup: PropTypes.func.isRequired,
|
}> {
|
||||||
};
|
UUID: ?HTMLDivElement;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { user } = this.props;
|
const { user } = this.props;
|
||||||
@ -30,7 +24,7 @@ class Profile extends Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Message {...messages.accountPreferencesTitle}>
|
<Message {...messages.accountPreferencesTitle}>
|
||||||
{(pageTitle) => (
|
{(pageTitle: string) => (
|
||||||
<h2 className={styles.indexTitle}>
|
<h2 className={styles.indexTitle}>
|
||||||
<Helmet title={pageTitle} />
|
<Helmet title={pageTitle} />
|
||||||
{pageTitle}
|
{pageTitle}
|
||||||
@ -90,14 +84,7 @@ class Profile extends Component {
|
|||||||
|
|
||||||
<ProfileField
|
<ProfileField
|
||||||
label={<Message {...messages.siteLanguage} />}
|
label={<Message {...messages.siteLanguage} />}
|
||||||
value={
|
value={<ChangeLanguageLink />}
|
||||||
<span className={styles.language} onClick={this.onLanguageSwitcher.bind(this)}>
|
|
||||||
<span className={styles.languageIcon} style={{
|
|
||||||
backgroundImage: `url('${localeFlags.getIconUrl(user.lang)}')`
|
|
||||||
}} />
|
|
||||||
{LANGS[user.lang].name}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ProfileField
|
<ProfileField
|
||||||
@ -129,15 +116,17 @@ class Profile extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLanguageSwitcher() {
|
|
||||||
this.props.createLanguageSwitcherPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUUIDMouseOver() {
|
handleUUIDMouseOver() {
|
||||||
|
if (!this.UUID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const el = this.UUID;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
range.selectNodeContents(this.UUID);
|
range.selectNodeContents(el);
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -145,17 +134,11 @@ class Profile extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setUUID(el) {
|
setUUID(el: ?HTMLDivElement) {
|
||||||
this.UUID = el;
|
this.UUID = el;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import LanguageSwitcher from 'components/languageSwitcher/LanguageSwitcher';
|
|
||||||
import { create as createPopup } from 'components/ui/popup/actions';
|
|
||||||
|
|
||||||
export default connect((state) => ({
|
export default connect((state) => ({
|
||||||
user: state.user,
|
user: state.user,
|
||||||
}), {
|
}))(Profile);
|
||||||
createLanguageSwitcherPopup: () => createPopup(LanguageSwitcher),
|
|
||||||
})(Profile);
|
|
||||||
|
@ -68,28 +68,6 @@ $formColumnWidth: 416px;
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.language {
|
|
||||||
color: #666;
|
|
||||||
border-bottom: 1px dotted #666;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: .25s;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.languageIcon {
|
|
||||||
$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, .2);
|
|
||||||
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uuidValue {
|
.uuidValue {
|
||||||
composes: paramName;
|
composes: paramName;
|
||||||
composes: paramValue;
|
composes: paramValue;
|
||||||
|
@ -12,7 +12,7 @@ export type User = {|
|
|||||||
isGuest: bool,
|
isGuest: bool,
|
||||||
isActive: bool,
|
isActive: bool,
|
||||||
isOtpEnabled: bool,
|
isOtpEnabled: bool,
|
||||||
passwordChangedAt: ?number,
|
passwordChangedAt: number,
|
||||||
hasMojangUsernameCollision: bool,
|
hasMojangUsernameCollision: bool,
|
||||||
maskedEmail?: string,
|
maskedEmail?: string,
|
||||||
shouldAcceptRules?: bool,
|
shouldAcceptRules?: bool,
|
||||||
@ -32,7 +32,7 @@ const defaults: User = {
|
|||||||
isActive: false,
|
isActive: false,
|
||||||
isOtpEnabled: false,
|
isOtpEnabled: false,
|
||||||
shouldAcceptRules: false, // whether user need to review updated rules
|
shouldAcceptRules: false, // whether user need to review updated rules
|
||||||
passwordChangedAt: null,
|
passwordChangedAt: 0,
|
||||||
hasMojangUsernameCollision: false,
|
hasMojangUsernameCollision: false,
|
||||||
|
|
||||||
// frontend specific attributes
|
// frontend specific attributes
|
||||||
|
@ -1,71 +1,83 @@
|
|||||||
{
|
{
|
||||||
"be": {
|
"be": {
|
||||||
|
"code": "be",
|
||||||
"name": "Беларуская",
|
"name": "Беларуская",
|
||||||
"englishName": "Belarusian",
|
"englishName": "Belarusian",
|
||||||
"progress": 100,
|
"progress": 100,
|
||||||
"isReleased": true
|
"isReleased": true
|
||||||
},
|
},
|
||||||
"en": {
|
"en": {
|
||||||
|
"code": "en",
|
||||||
"name": "English, UK",
|
"name": "English, UK",
|
||||||
"englishName": "English, UK",
|
"englishName": "English, UK",
|
||||||
"progress": 100,
|
"progress": 100,
|
||||||
"isReleased": true
|
"isReleased": true
|
||||||
},
|
},
|
||||||
"fr": {
|
"fr": {
|
||||||
|
"code": "fr",
|
||||||
"name": "Français",
|
"name": "Français",
|
||||||
"englishName": "French",
|
"englishName": "French",
|
||||||
"progress": 85.6,
|
"progress": 85.6,
|
||||||
"isReleased": true
|
"isReleased": true
|
||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
|
"code": "id",
|
||||||
"name": "Bahasa Indonesia",
|
"name": "Bahasa Indonesia",
|
||||||
"englishName": "Indonesian",
|
"englishName": "Indonesian",
|
||||||
"progress": 96.2,
|
"progress": 96.2,
|
||||||
"isReleased": true
|
"isReleased": true
|
||||||
},
|
},
|
||||||
"lt": {
|
"lt": {
|
||||||
|
"code": "lt",
|
||||||
"name": "Lietuvių",
|
"name": "Lietuvių",
|
||||||
"englishName": "Lithuanian",
|
"englishName": "Lithuanian",
|
||||||
"progress": 92.8,
|
"progress": 92.8,
|
||||||
"isReleased": false
|
"isReleased": false
|
||||||
},
|
},
|
||||||
"pl": {
|
"pl": {
|
||||||
|
"code": "pl",
|
||||||
"name": "Polski",
|
"name": "Polski",
|
||||||
"englishName": "Polish",
|
"englishName": "Polish",
|
||||||
"progress": 81.8,
|
"progress": 81.8,
|
||||||
"isReleased": false
|
"isReleased": false
|
||||||
},
|
},
|
||||||
"pt": {
|
"pt": {
|
||||||
|
"code": "pt",
|
||||||
"name": "Português do Brasil",
|
"name": "Português do Brasil",
|
||||||
"englishName": "Portuguese, Brazilian",
|
"englishName": "Portuguese, Brazilian",
|
||||||
"progress": 96.2,
|
"progress": 96.2,
|
||||||
"isReleased": true
|
"isReleased": true
|
||||||
},
|
},
|
||||||
"ro": {
|
"ro": {
|
||||||
|
"code": "ro",
|
||||||
"name": "Română",
|
"name": "Română",
|
||||||
"englishName": "Romanian",
|
"englishName": "Romanian",
|
||||||
"progress": 81.8,
|
"progress": 81.8,
|
||||||
"isReleased": false
|
"isReleased": false
|
||||||
},
|
},
|
||||||
"ru": {
|
"ru": {
|
||||||
|
"code": "ru",
|
||||||
"name": "Русский",
|
"name": "Русский",
|
||||||
"englishName": "Russian",
|
"englishName": "Russian",
|
||||||
"progress": 100,
|
"progress": 100,
|
||||||
"isReleased": true
|
"isReleased": true
|
||||||
},
|
},
|
||||||
"sl": {
|
"sl": {
|
||||||
|
"code": "sl",
|
||||||
"name": "Slovenščina",
|
"name": "Slovenščina",
|
||||||
"englishName": "Slovenian",
|
"englishName": "Slovenian",
|
||||||
"progress": 82.2,
|
"progress": 82.2,
|
||||||
"isReleased": false
|
"isReleased": false
|
||||||
},
|
},
|
||||||
"uk": {
|
"uk": {
|
||||||
|
"code": "uk",
|
||||||
"name": "Українська",
|
"name": "Українська",
|
||||||
"englishName": "Ukrainian",
|
"englishName": "Ukrainian",
|
||||||
"progress": 85.6,
|
"progress": 85.6,
|
||||||
"isReleased": true
|
"isReleased": true
|
||||||
},
|
},
|
||||||
"vi": {
|
"vi": {
|
||||||
|
"code": "vi",
|
||||||
"name": "Tiếng Việt",
|
"name": "Tiếng Việt",
|
||||||
"englishName": "Vietnamese",
|
"englishName": "Vietnamese",
|
||||||
"progress": 96.2,
|
"progress": 96.2,
|
||||||
|
@ -268,7 +268,7 @@ if (isProduction) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
webpackConfig.entry.vendor = Object.keys(packageJson.dependencies)
|
webpackConfig.entry.vendor = Object.keys(packageJson.dependencies)
|
||||||
.filter((module) => ignoredPlugins.indexOf(module) === -1);
|
.filter((module) => !ignoredPlugins.includes(module));
|
||||||
} else {
|
} else {
|
||||||
webpackConfig.plugins.push(
|
webpackConfig.plugins.push(
|
||||||
new webpack.DllReferencePlugin({
|
new webpack.DllReferencePlugin({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user