mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-11-10 18:12:10 +05:30
#84: language switching on frontend
This commit is contained in:
parent
f496f87a8e
commit
ecf41dd725
@ -3,6 +3,7 @@ import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import { Button } from 'components/ui/form';
|
||||
import { LangMenu } from 'components/langMenu';
|
||||
|
||||
import styles from './appInfo.scss';
|
||||
import messages from './AppInfo.intl.json';
|
||||
@ -36,6 +37,10 @@ export default class AppInfo extends Component {
|
||||
<div className={styles.goToAuth}>
|
||||
<Button onClick={onGoToAuth} label={messages.goToAuth} />
|
||||
</div>
|
||||
|
||||
<div className={styles.langMenu}>
|
||||
<LangMenu />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -50,3 +50,10 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.langMenu {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
23
src/components/i18n/IntlProvider.jsx
Normal file
23
src/components/i18n/IntlProvider.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { IntlProvider as OrigIntlProvider } from 'react-intl';
|
||||
|
||||
class IntlProvider extends Component {
|
||||
static displayName = 'IntlProvider';
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
messages: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||
children: PropTypes.element
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<OrigIntlProvider {...this.props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
export default connect(({i18n}) => i18n)(IntlProvider);
|
18
src/components/i18n/actions.js
Normal file
18
src/components/i18n/actions.js
Normal file
@ -0,0 +1,18 @@
|
||||
import i18n from 'services/i18n';
|
||||
|
||||
export const SET_LOCALE = 'SET_LOCALE';
|
||||
export function setLocale(locale) {
|
||||
return (dispatch) => i18n.require(
|
||||
i18n.detectLanguage(locale)
|
||||
).then(({locale, messages}) => {
|
||||
dispatch({
|
||||
type: SET_LOCALE,
|
||||
payload: {
|
||||
locale,
|
||||
messages
|
||||
}
|
||||
});
|
||||
|
||||
return locale;
|
||||
});
|
||||
}
|
5
src/components/i18n/index.js
Normal file
5
src/components/i18n/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import IntlProvider from './IntlProvider';
|
||||
|
||||
export {
|
||||
IntlProvider
|
||||
};
|
9
src/components/i18n/reducer.js
Normal file
9
src/components/i18n/reducer.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { SET_LOCALE } from './actions';
|
||||
|
||||
export default function(state = {}, {type, payload}) {
|
||||
if (type === SET_LOCALE) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
140
src/components/langMenu/LangMenu.jsx
Normal file
140
src/components/langMenu/LangMenu.jsx
Normal file
@ -0,0 +1,140 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
|
||||
import icons from 'components/ui/icons.scss';
|
||||
|
||||
import styles from './langMenu.scss';
|
||||
import messages from './langMenu.intl.json';
|
||||
|
||||
const LANGS = {
|
||||
en: 'English',
|
||||
ru: 'Русский'
|
||||
};
|
||||
|
||||
export default class LangMenu extends Component {
|
||||
static displayName = 'LangMenu';
|
||||
static propTypes = {
|
||||
showCurrentLang: PropTypes.bool,
|
||||
toggleRef: PropTypes.func,
|
||||
userLang: PropTypes.string,
|
||||
changeLang: PropTypes.func
|
||||
};
|
||||
static defaultProps = {
|
||||
toggleRef: () => {},
|
||||
showCurrentLang: false
|
||||
};
|
||||
|
||||
state = {
|
||||
isActive: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.onBodyClick);
|
||||
this.props.toggleRef(this.toggle.bind(this));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('click', this.onBodyClick);
|
||||
this.props.toggleRef(null);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {userLang, showCurrentLang} = this.props;
|
||||
const {isActive} = this.state;
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, {
|
||||
[styles.withCurrentLang]: showCurrentLang
|
||||
})}>
|
||||
<div className={styles.menuContainer}>
|
||||
<ul className={classNames(styles.menu, {
|
||||
[styles.menuActive]: isActive
|
||||
})}>
|
||||
{Object.keys(LANGS).map((lang) => (
|
||||
<li className={classNames(styles.menuItem, {
|
||||
[styles.activeMenuItem]: lang === userLang
|
||||
})} onClick={this.onChangeLang(lang)} key={lang}>
|
||||
{this.renderLangLabel(lang)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={styles.triggerContainer} onClick={this.onToggle}>
|
||||
<a className={styles.trigger} href="#">
|
||||
{showCurrentLang
|
||||
? this.renderLangLabel(userLang) : (
|
||||
<span>
|
||||
<span className={icons.globe} />
|
||||
{' '}
|
||||
<Message {...messages.siteLanguage} />
|
||||
{' '}
|
||||
<span className={isActive ? styles.triggerArrowBottom : styles.triggerArrowTop} />
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLangLabel(lang) {
|
||||
const langLabel = LANGS[lang];
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span className={styles[`lang${lang[0].toUpperCase() + lang.slice(1)}`]} />
|
||||
{langLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
onChangeLang(lang) {
|
||||
return (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.props.changeLang(lang);
|
||||
this.setState({
|
||||
isActive: false
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
onBodyClick = (event) => {
|
||||
if (this.state.isActive) {
|
||||
const el = ReactDOM.findDOMNode(this);
|
||||
|
||||
if (!el.contains(event.target) && el !== event.taget) {
|
||||
event.preventDefault();
|
||||
|
||||
// add a small delay for the case someone have alredy called toggle
|
||||
setTimeout(() => this.state.isActive && this.toggle(), 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onToggle = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.toggle();
|
||||
};
|
||||
|
||||
toggle = () => {
|
||||
// add small delay to skip click event on body
|
||||
setTimeout(() => this.setState({
|
||||
isActive: !this.state.isActive
|
||||
}), 0);
|
||||
};
|
||||
}
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { changeLang } from 'components/user/actions';
|
||||
|
||||
export default connect((state) => ({
|
||||
userLang: state.user.lang
|
||||
}), {
|
||||
changeLang
|
||||
})(LangMenu);
|
5
src/components/langMenu/index.js
Normal file
5
src/components/langMenu/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import LangMenu from './LangMenu';
|
||||
|
||||
export {
|
||||
LangMenu
|
||||
};
|
3
src/components/langMenu/langMenu.intl.json
Normal file
3
src/components/langMenu/langMenu.intl.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"siteLanguage": "Site language"
|
||||
}
|
119
src/components/langMenu/langMenu.scss
Normal file
119
src/components/langMenu/langMenu.scss
Normal file
@ -0,0 +1,119 @@
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menuContainer {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
width: 150px;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
background: #fff;
|
||||
border: 5px solid #ddd8ce;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
text-align: left;
|
||||
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: 0.2s ease;
|
||||
transform: scale(0.1);
|
||||
transform-origin: center bottom;
|
||||
}
|
||||
|
||||
.menuActive {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.withCurrentLang {
|
||||
.triggerContainer {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menuContainer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
padding: 10px;
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.activeMenuItem {
|
||||
background: #efffef;
|
||||
}
|
||||
|
||||
.langIco {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
width: 20px;
|
||||
height: 10px;
|
||||
|
||||
background: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.langEn {
|
||||
composes: langIco;
|
||||
|
||||
background-image: url('icons/flag_en.svg');
|
||||
}
|
||||
|
||||
.langRu {
|
||||
composes: langIco;
|
||||
|
||||
background-image: url('icons/flag_ru.svg');
|
||||
}
|
||||
|
||||
.trigger {
|
||||
color: #666;
|
||||
border-bottom: 1px dotted #666;
|
||||
text-decoration: none;
|
||||
transition: .25s;
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: #777;
|
||||
color: #777;
|
||||
}
|
||||
}
|
||||
|
||||
.triggerContainer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.triggerArrow {
|
||||
font-size: 8px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.triggerArrowTop {
|
||||
composes: triggerArrow;
|
||||
composes: arrowTop from 'components/ui/icons.scss';
|
||||
}
|
||||
|
||||
.triggerArrowBottom {
|
||||
composes: triggerArrow;
|
||||
composes: arrowBottom from 'components/ui/icons.scss';
|
||||
}
|
@ -4,6 +4,8 @@ import { FormattedMessage as Message, FormattedRelative as Relative, FormattedHT
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import { userShape } from 'components/user/User';
|
||||
import { LangMenu } from 'components/langMenu';
|
||||
import langMenuMessages from 'components/langMenu/langMenu.intl.json';
|
||||
|
||||
import ProfileField from './ProfileField';
|
||||
import styles from './profile.scss';
|
||||
@ -74,6 +76,12 @@ export default class Profile extends Component {
|
||||
) : ''}
|
||||
/>
|
||||
|
||||
<ProfileField
|
||||
label={<Message {...langMenuMessages.siteLanguage} />}
|
||||
value={<LangMenu toggleRef={(toggle) => this.langMenuToggle = toggle} showCurrentLang />}
|
||||
onChange={() => this.langMenuToggle()}
|
||||
/>
|
||||
|
||||
<ProfileField
|
||||
label={<Message {...messages.twoFactorAuth} />}
|
||||
value={<Message {...messages.disabled} />}
|
||||
@ -82,7 +90,6 @@ export default class Profile extends Component {
|
||||
<ProfileField
|
||||
label={'UUID'}
|
||||
value={user.uuid}
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,13 +9,23 @@ export default class ProfileField extends Component {
|
||||
static propTypes = {
|
||||
label: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
|
||||
link: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
value: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
|
||||
warningMessage: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||
readonly: PropTypes.bool
|
||||
warningMessage: React.PropTypes.oneOfType([PropTypes.string, PropTypes.element])
|
||||
};
|
||||
|
||||
render() {
|
||||
const {label, value, warningMessage, readonly, link = '#'} = this.props;
|
||||
const {label, value, warningMessage, link, onChange} = this.props;
|
||||
|
||||
let Action = null;
|
||||
|
||||
if (link) {
|
||||
Action = (props) => <Link to={link} {...props} />;
|
||||
}
|
||||
|
||||
if (onChange) {
|
||||
Action = (props) => <a onClick={onChange} {...props} href="#" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.paramItem}>
|
||||
@ -23,13 +33,11 @@ export default class ProfileField extends Component {
|
||||
<div className={styles.paramName}>{label}:</div>
|
||||
<div className={styles.paramValue}>{value}</div>
|
||||
|
||||
{readonly ? '' : (
|
||||
<div className={styles.paramAction}>
|
||||
<Link to={link}>
|
||||
<span className={styles.paramEditIcon} />
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{Action ? (
|
||||
<Action to={link} className={styles.paramAction}>
|
||||
<span className={styles.paramEditIcon} />
|
||||
</Action>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{warningMessage ? (
|
||||
|
@ -74,6 +74,7 @@
|
||||
|
||||
.paramAction {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.paramEditIcon {
|
||||
|
@ -261,7 +261,7 @@
|
||||
}
|
||||
|
||||
[type="submit"] {
|
||||
background: url('images/loader_button.gif') #95a5a6 center center;
|
||||
background: url('./images/loader_button.gif') #95a5a6 center center;
|
||||
|
||||
cursor: default;
|
||||
color: #fff;
|
||||
|
@ -8,3 +8,13 @@
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.arrowTop {
|
||||
composes: arrow;
|
||||
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.arrowBottom {
|
||||
composes: arrow;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { routeActions } from 'react-router-redux';
|
||||
|
||||
import request from 'services/request';
|
||||
import accounts from 'services/api/accounts';
|
||||
import { setLocale } from 'components/i18n/actions';
|
||||
|
||||
export const UPDATE = 'USER_UPDATE';
|
||||
/**
|
||||
@ -15,6 +16,25 @@ export function updateUser(payload) {
|
||||
};
|
||||
}
|
||||
|
||||
export const CHANGE_LANG = 'USER_CHANGE_LANG';
|
||||
export function changeLang(lang) {
|
||||
return (dispatch, getState) => dispatch(setLocale(lang))
|
||||
.then((lang) => {
|
||||
const {user: {isGuest, lang: oldLang}} = getState();
|
||||
|
||||
if (!isGuest && oldLang !== lang) {
|
||||
accounts.changeLang(lang);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: CHANGE_LANG,
|
||||
payload: {
|
||||
lang
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const SET = 'USER_SET';
|
||||
export function setUser(payload) {
|
||||
return {
|
||||
@ -26,6 +46,7 @@ export function setUser(payload) {
|
||||
export function logout() {
|
||||
return (dispatch) => {
|
||||
dispatch(setUser({isGuest: true}));
|
||||
dispatch(changeLang());
|
||||
dispatch(routeActions.push('/login'));
|
||||
};
|
||||
}
|
||||
@ -35,6 +56,8 @@ export function fetchUserData() {
|
||||
accounts.current()
|
||||
.then((resp) => {
|
||||
dispatch(updateUser(resp));
|
||||
|
||||
return dispatch(changeLang(resp.lang));
|
||||
})
|
||||
.catch((resp) => {
|
||||
/*
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { authenticate } from 'components/user/actions';
|
||||
import { authenticate, changeLang } from 'components/user/actions';
|
||||
|
||||
/**
|
||||
* Initializes User state with the fresh data
|
||||
@ -8,15 +8,15 @@ import { authenticate } from 'components/user/actions';
|
||||
* @return {Promise} a promise, that resolves in User state
|
||||
*/
|
||||
export function factory(store) {
|
||||
const state = store.getState();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (state.user.token) {
|
||||
const {user} = store.getState();
|
||||
|
||||
if (user.token) {
|
||||
// authorizing user if it is possible
|
||||
store.dispatch(authenticate(state.user.token))
|
||||
.then(() => resolve(store.getState().user), reject);
|
||||
} else {
|
||||
resolve(state.user);
|
||||
return store.dispatch(authenticate(user.token)).then(resolve, reject);
|
||||
}
|
||||
|
||||
// auto-detect guests language
|
||||
store.dispatch(changeLang()).then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { UPDATE, SET } from './actions';
|
||||
import { UPDATE, SET, CHANGE_LANG } from './actions';
|
||||
|
||||
import User from './User';
|
||||
|
||||
@ -8,6 +8,16 @@ export default function user(
|
||||
{type, payload = null}
|
||||
) {
|
||||
switch (type) {
|
||||
case CHANGE_LANG:
|
||||
if (!payload || !payload.lang) {
|
||||
throw new Error('payload.lang is required for user reducer');
|
||||
}
|
||||
|
||||
return new User({
|
||||
...state,
|
||||
lang: payload.lang
|
||||
});
|
||||
|
||||
case UPDATE:
|
||||
if (!payload) {
|
||||
throw new Error('payload is required for user reducer');
|
||||
|
@ -66,6 +66,7 @@
|
||||
"components.auth.register.termsOfService": "terms of service",
|
||||
"components.auth.register.yourEmail": "Your E-mail",
|
||||
"components.auth.register.yourNickname": "Your nickname",
|
||||
"components.langMenu.siteLanguage": "Site language",
|
||||
"components.profile.accountDescription": "Ely.by account allows you to get access to many Minecraft resources. Please, take care of your account safety. Use secure password and change it regularly.",
|
||||
"components.profile.accountPreferencesTitle": "Ely.by account preferences",
|
||||
"components.profile.changePassword.achievementLossWarning": "Are you cherish your game achievements, right?",
|
||||
|
@ -66,6 +66,7 @@
|
||||
"components.auth.register.termsOfService": "правилами сервиса",
|
||||
"components.auth.register.yourEmail": "Ваш E-mail",
|
||||
"components.auth.register.yourNickname": "Желаемый ник",
|
||||
"components.langMenu.siteLanguage": "Язык сайта",
|
||||
"components.profile.accountDescription": "Благодаря аккаунту Ely.by вы можете получить доступ ко многим ресурсам, связанным с Minecraft. Берегите свой аккаунт, используйте надёжный пароль и регулярно его меняйте.",
|
||||
"components.profile.accountPreferencesTitle": "Настройки аккаунта Ely.by",
|
||||
"components.profile.changePassword.achievementLossWarning": "Вы ведь дорожите своими игровыми достижениями?",
|
||||
|
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 522 B |
Before Width: | Height: | Size: 265 B After Width: | Height: | Size: 265 B |
41
src/icons/webfont/globe.svg
Normal file
41
src/icons/webfont/globe.svg
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
|
||||
<path d="M94.9,47.9c0-0.2,0-0.4,0-0.7c0-0.5-0.1-0.9-0.1-1.4c0-0.2,0-0.5-0.1-0.7c-0.1-0.6-0.1-1.1-0.2-1.7
|
||||
c-0.1-0.4-0.1-0.9-0.2-1.3c0-0.1,0-0.3-0.1-0.4c-2.6-14-11.6-25.8-23.9-32l0,0h-0.1c-3-1.5-6.3-2.7-9.7-3.5h-0.1
|
||||
c-0.7-0.2-1.5-0.3-2.2-0.5c-0.2,0-0.3-0.1-0.5-0.1c-0.6-0.1-1.3-0.2-1.9-0.3c-0.2,0-0.4-0.1-0.7-0.1c-0.6-0.1-1.2-0.1-1.9-0.2
|
||||
c-0.2,0-0.4,0-0.7,0c-0.8,0-1.7-0.1-2.5-0.1C25.1,4.9,5,25,5,49.9s20.1,45,45,45s45-20.1,45-45C95,49.2,95,48.5,94.9,47.9z
|
||||
M17.4,72.2l-2.3-3.6C15,68.5,15,68.3,15,68.2v-1.3c0-0.1,0-0.3-0.1-0.4l-1.2-1.9c-0.4-0.6-1.2-0.4-1.4,0.2l-0.5,1.9h-0.3
|
||||
c-0.9-2.1-1.6-4.2-2.2-6.4l2.9,1.7c0.2,0.1,0.5,0.1,0.7,0l1-0.5c0.3-0.2,0.7-0.1,0.9,0.1l1.7,1.8c0.2,0.2,0.6,0.3,0.9,0.1l0.7-0.4
|
||||
l-2-3.7c-0.1-0.1-0.2-0.2-0.3-0.3l-4.6-2.9c-0.4-0.2-0.9-0.1-1.1,0.3L10,56.7c-0.1,0.3-0.4,0.4-0.7,0.4H8.8c-0.1,0-0.1,0-0.2,0
|
||||
C8.2,54.7,8,52.3,8,49.9c0-12.7,5.6-24,14.5-31.7h2.1c0.1,0,0.2,0,0.3,0.1l1.3,0.6c0.2,0.1,0.4,0.4,0.4,0.6L26.2,26
|
||||
c0,0.1,0,0.2-0.1,0.3l-2.8,4.8c-0.1,0.2-0.1,0.4,0,0.6l1.6,3.1c0,0.1,0.1,0.2,0.1,0.3v2.1c0,0.1,0,0.2,0.1,0.3l2.2,3.9l0.9-0.6
|
||||
l-1.7-3.2l0.5-0.3l2.4,3.7c0.1,0.1,0.1,0.2,0.1,0.4v2c0,0.2,0.1,0.5,0.3,0.6l3.1,1.8c0.1,0.1,0.2,0.1,0.3,0.1h2.1
|
||||
c0.2,0,0.4,0.1,0.5,0.3l0.7,1c0.1,0.2,0.3,0.3,0.5,0.3h1.3c0.1,0,0.1,0,0.2,0l0.6,0.2c0.2,0.1,0.4,0.2,0.4,0.4l0.5,1.5
|
||||
c0.1,0.2,0.2,0.3,0.3,0.4l3.6,1.8c0.2,0.1,0.4,0.3,0.4,0.6v1c0,0.1,0,0.3-0.1,0.4l-1.4,2c-0.1,0.1-0.1,0.3-0.1,0.4v3.5
|
||||
c0,0.1,0,0.2,0.1,0.3l3,5.5c0,0.1,0.1,0.1,0.2,0.2l3.5,3c0.2,0.1,0.2,0.3,0.2,0.5L49.6,72c0,0.1,0,0.3,0.1,0.4l0.6,0.9
|
||||
c0.1,0.1,0.1,0.2,0.1,0.4l0.2,7.9c0,0.1,0.1,0.3,0.1,0.5l1.2,3.7c0,0.1,0,0.1,0.1,0.2l2.1,3.4c0.1,0.2,0.3,0.3,0.6,0.3h0.8
|
||||
c0.2,0,0.3,0.1,0.5,0.2l0.5,0.5c0.1,0.1,0.3,0.2,0.5,0.2h2.5l-2.4-1.8c-0.2-0.1-0.3-0.3-0.3-0.6v-2.8c0-0.1,6.3-11.6,7.5-13.8
|
||||
c0.1-0.2,0.3-0.3,0.6-0.3H66c0.3,0,0.5-0.2,0.6-0.4l1.7-3.8c0-0.1,0.1-0.2,0.1-0.3v-2.7c0-0.1,0-0.2,0.1-0.3l1.2-2
|
||||
c0.1-0.1,0.1-0.3,0.1-0.5l-0.3-1.2c-0.2-0.9-0.9-1.7-1.8-1.9l-5.4-2.3c-0.1,0-0.2-0.1-0.3-0.2l-1.3-1.6L59.4,52
|
||||
c-0.1-0.2-0.3-0.3-0.5-0.3H57c-0.2,0-0.4-0.1-0.5-0.2l-1.8-2.2c-0.1-0.2-0.3-0.2-0.5-0.2L46.9,49c-0.2,0-0.4,0.1-0.6,0.2l-1,0.8
|
||||
c-0.2,0.2-0.5,0.2-0.7,0.1L42.2,49c-0.2-0.1-0.4-0.3-0.4-0.6v-2c0-0.4-0.3-0.6-0.7-0.6h-1.9l-0.3-0.6c-0.1-0.2-0.2-0.3-0.4-0.3
|
||||
l-2.1-0.5c-0.1,0-0.2-0.1-0.2-0.1l-1.3-0.9c-0.2-0.2-0.3-0.4-0.3-0.7l1.2-4.5c0.1-0.2,0.2-0.4,0.5-0.5l4.9-0.9c0.1,0,0.2,0,0.3,0
|
||||
l1.5,0.7c0.2,0.1,0.3,0.2,0.4,0.4l0.4,1.6c0.1,0.2,0.3,0.4,0.5,0.3l0.5-0.1c0.2-0.1,0.3-0.3,0.3-0.5l-0.2-2.4c0-0.3,0.1-0.5,0.3-0.6
|
||||
l3.4-2.1c0.2-0.1,0.3-0.3,0.3-0.6v-1c0-0.2,0.1-0.4,0.3-0.5l5.5-3.9C54.8,28,54.9,28,55,28l4-0.6v-1h-0.7c-0.4,0-0.7-0.3-0.7-0.7
|
||||
l0.1-0.9c0-0.3,0.3-0.6,0.6-0.6h0.9c0.4,0,0.7,0.3,0.7,0.6v0.9c0,0.4,0.3,0.6,0.7,0.6H63c0.4,0,0.7-0.3,0.6-0.7l-0.2-4.1
|
||||
c0-0.4-0.3-0.6-0.7-0.6h-1.3c-0.2,0-0.4-0.1-0.5-0.3L58.1,17c-0.1-0.2-0.3-0.2-0.5-0.3h-1.7c-0.3,0-0.5,0.2-0.6,0.4l-1.1,2.6
|
||||
c0,0.1-0.1,0.2-0.2,0.3l-3,2.7c-0.3,0.2-0.7,0.2-0.9,0l-2.8-3c-0.3-0.3-0.2-0.8,0.1-1l3.1-2.2c0.1-0.1,0.3-0.1,0.4-0.1h1.6
|
||||
c0,0,0,0,0.1,0l2.9-0.2l0.1-0.8c0-0.1,0.1-0.2,0.1-0.3l1.5-2.1c0.1-0.1,0.2-0.2,0.4-0.3l0.8-0.2c0.2,0,0.4,0,0.6,0.1l0.6,0.5
|
||||
c0.3,0.3,0.3,0.8,0,1l-0.9,0.7c-0.3,0.2-0.3,0.7,0,1l1.6,1.5c0.2,0.2,0.6,0.3,0.9,0l1-0.8c0.1-0.1,0.2-0.3,0.2-0.5v-0.7
|
||||
c0-0.4,0.3-0.7,0.7-0.7h1.5v-0.7l-2.4-2.3c-0.1-0.1-0.3-0.2-0.5-0.2h-1.9v-0.5l2-1.3c2.8,0.8,5.6,1.9,8.1,3.3
|
||||
c-0.1,0.1-0.1,0.1-0.2,0.2L69,13.4c-0.2,0.1-0.4,0.4-0.4,0.6l0.1,2.6c0,0.3,0.2,0.5,0.4,0.6l2.2,0.8c0.3,0.1,0.6,0,0.8-0.2l1.7-2.5
|
||||
c5.1,3.5,9.3,8.1,12.4,13.4l-0.5,0.7c-0.1,0.1-0.1,0.3-0.1,0.4v2.3c0,0.4,0.3,0.8,0.8,0.8h1.4v0.8c0,0.3-0.2,0.6-0.4,0.7l-2.3,1
|
||||
c-0.1,0.1-0.2,0.1-0.3,0.2l-4.4,5.8c-0.1,0.1-0.1,0.3-0.1,0.4v5.8c0,0.2,0.1,0.4,0.2,0.5l4.3,4.9c0.1,0.2,0.3,0.3,0.6,0.3h4.8
|
||||
c0.1,0,0.2,0,0.3-0.1l1.3-0.7c-1.4,22-19.6,39.4-41.9,39.4c-13.5,0-25.4-6.3-33.1-16.2l0.2-0.1c0.2-0.1,0.4-0.4,0.4-0.6v-2.4
|
||||
C17.5,72.5,17.5,72.3,17.4,72.2z M50,7.9c3.3,0,6.5,0.4,9.6,1.1l-0.2,0.4c-0.1,0.2-0.4,0.3-0.6,0.3l-2.1-0.2c-0.4,0-0.8,0.3-0.7,0.7
|
||||
l0.1,1.2h-1L55,10.7l-4.9,0.1v1.1l0.9-0.1v1.3c0,0.4-0.3,0.7-0.6,0.7h-2.2v-0.5l0.9-0.3L49,11.4c0-0.3-0.3-0.6-0.7-0.6h-2.7
|
||||
c-0.1,0-0.2,0-0.4-0.1l-1.3-0.8c-0.2-0.1-0.4-0.1-0.6-0.1l-3,1l0.3,0.6c0.1,0.2,0.3,0.4,0.6,0.4h1.5l-0.1,0.5L36,12.6l-4.6-0.4
|
||||
C37,9.5,43.3,7.9,50,7.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
19
src/index.js
19
src/index.js
@ -15,10 +15,8 @@ import thunk from 'redux-thunk';
|
||||
import { Router, browserHistory } from 'react-router';
|
||||
import { syncHistory, routeReducer } from 'react-router-redux';
|
||||
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { factory as userFactory } from 'components/user/factory';
|
||||
import i18n from 'services/i18n';
|
||||
import { IntlProvider } from 'components/i18n';
|
||||
import reducers from 'reducers';
|
||||
import routesFactory from 'routes';
|
||||
|
||||
@ -35,20 +33,15 @@ const store = applyMiddleware(
|
||||
)(createStore)(reducer);
|
||||
|
||||
userFactory(store)
|
||||
.then(({lang}) =>
|
||||
i18n.require(
|
||||
i18n.detectLanguage(lang)
|
||||
)
|
||||
)
|
||||
.then(({locale, messages}) => {
|
||||
.then(() => {
|
||||
ReactDOM.render(
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<ReduxProvider store={store}>
|
||||
<ReduxProvider store={store}>
|
||||
<IntlProvider>
|
||||
<Router history={browserHistory}>
|
||||
{routesFactory(store)}
|
||||
</Router>
|
||||
</ReduxProvider>
|
||||
</IntlProvider>,
|
||||
</IntlProvider>
|
||||
</ReduxProvider>,
|
||||
document.getElementById('app')
|
||||
);
|
||||
|
||||
|
@ -36,4 +36,5 @@ p {
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import auth from 'components/auth/reducer';
|
||||
import user from 'components/user/reducer';
|
||||
import i18n from 'components/i18n/reducer';
|
||||
import popup from 'components/ui/popup/reducer';
|
||||
|
||||
export default {
|
||||
auth,
|
||||
user,
|
||||
i18n,
|
||||
popup
|
||||
};
|
||||
|
@ -25,5 +25,12 @@ export default {
|
||||
'/api/accounts/change-username',
|
||||
{username, password}
|
||||
);
|
||||
},
|
||||
|
||||
changeLang(lang) {
|
||||
return request.post(
|
||||
'/api/accounts/change-lang',
|
||||
{lang}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -2,6 +2,10 @@ import { addLocaleData } from 'react-intl';
|
||||
import enLocaleData from 'react-intl/locale-data/en';
|
||||
import ruLocaleData from 'react-intl/locale-data/ru';
|
||||
|
||||
// till we have not so many locales, we can require their data at once
|
||||
addLocaleData(enLocaleData);
|
||||
addLocaleData(ruLocaleData);
|
||||
|
||||
const SUPPORTED_LANGUAGES = ['ru', 'en'];
|
||||
const DEFAULT_LANGUAGE = 'en';
|
||||
function getUserLanguages(userSelectedLang = []) {
|
||||
@ -23,10 +27,6 @@ export default {
|
||||
},
|
||||
|
||||
require(locale) {
|
||||
// till we have not so many locales, we can require their data at once
|
||||
addLocaleData(enLocaleData);
|
||||
addLocaleData(ruLocaleData);
|
||||
|
||||
return new Promise(require(`bundle!i18n/${locale}.json`))
|
||||
.then((messages) => ({locale, messages}));
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ var webpackConfig = {
|
||||
loader: 'babel'
|
||||
},
|
||||
{
|
||||
test: /\.(png|gif|jpg)$/,
|
||||
test: /\.(png|gif|jpg|svg)$/,
|
||||
loader: 'url?limit=1000'
|
||||
},
|
||||
{ // TODO: увы, эта штука пока не работает. Хеш добавляется через ./webpack/node-sass-iconfont-importer
|
||||
@ -197,9 +197,9 @@ var webpackConfig = {
|
||||
//
|
||||
// Например:
|
||||
// file: components/ui/foo.scss
|
||||
// images/foo.png -> components/ui/images/foo.png
|
||||
// ./images/foo.png -> components/ui/images/foo.png
|
||||
|
||||
if (url[0] !== '/') {
|
||||
if (url.indexOf('./') === 0) {
|
||||
var relativeToRoot = dirname.split(rootPath + '/')[1];
|
||||
|
||||
return path.join(relativeToRoot, url);
|
||||
|
Loading…
Reference in New Issue
Block a user