Feature(langauge): Make client history language aware
Remove need for language aware links
This commit is contained in:
@ -10,6 +10,7 @@ import {
|
|||||||
} from 'react-router-redux';
|
} from 'react-router-redux';
|
||||||
import { render } from 'redux-epic';
|
import { render } from 'redux-epic';
|
||||||
import { createHistory } from 'history';
|
import { createHistory } from 'history';
|
||||||
|
import useLangRoutes from './use-lang-routes.js';
|
||||||
|
|
||||||
import createApp from '../common/app';
|
import createApp from '../common/app';
|
||||||
import provideStore from '../common/app/provide-store';
|
import provideStore from '../common/app/provide-store';
|
||||||
@ -36,7 +37,7 @@ initialState.app.csrfToken = csrfToken;
|
|||||||
|
|
||||||
const serviceOptions = { xhrPath: '/services', context: { _csrf: csrfToken } };
|
const serviceOptions = { xhrPath: '/services', context: { _csrf: csrfToken } };
|
||||||
|
|
||||||
const history = createHistory();
|
const history = useLangRoutes(createHistory)();
|
||||||
|
|
||||||
const devTools = window.devToolsExtension ? window.devToolsExtension() : f => f;
|
const devTools = window.devToolsExtension ? window.devToolsExtension() : f => f;
|
||||||
const adjustUrlOnReplay = !!window.devToolsExtension;
|
const adjustUrlOnReplay = !!window.devToolsExtension;
|
||||||
|
44
client/use-lang-routes.js
Normal file
44
client/use-lang-routes.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { addLang, getLangFromPath } from '../common/app/utils/lang.js';
|
||||||
|
|
||||||
|
function addLangToLocation(location, lang) {
|
||||||
|
if (!location) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
if (typeof location === 'string') {
|
||||||
|
return addLang(location, lang);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...location,
|
||||||
|
pathname: addLang(location.pathname, lang)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLangFromLocation(location) {
|
||||||
|
if (!location) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
if (typeof location === 'string') {
|
||||||
|
return getLangFromPath(location);
|
||||||
|
}
|
||||||
|
return getLangFromPath(location.pathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function useLangRoutes(createHistory) {
|
||||||
|
return (options = {}) => {
|
||||||
|
let lang = 'en';
|
||||||
|
const history = createHistory(options);
|
||||||
|
const unsubscribeFromHistory = history.listen(nextLocation => {
|
||||||
|
lang = getLangFromLocation(nextLocation);
|
||||||
|
});
|
||||||
|
const push = location => history.push(addLangToLocation(location, lang));
|
||||||
|
const replace = location => history.replace(
|
||||||
|
addLangToLocation(location, lang)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...history,
|
||||||
|
push,
|
||||||
|
replace,
|
||||||
|
unsubscribe() { unsubscribeFromHistory(); }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@ -36,15 +36,15 @@ const mapStateToProps = createSelector(
|
|||||||
isMapAlreadyLoaded,
|
isMapAlreadyLoaded,
|
||||||
showChallengeComplete
|
showChallengeComplete
|
||||||
) => ({
|
) => ({
|
||||||
shouldShowSignIn,
|
|
||||||
isSignedIn: !!username,
|
|
||||||
username,
|
username,
|
||||||
points,
|
points,
|
||||||
picture,
|
picture,
|
||||||
toast,
|
toast,
|
||||||
|
shouldShowSignIn,
|
||||||
isMapDrawerOpen,
|
isMapDrawerOpen,
|
||||||
isMapAlreadyLoaded,
|
isMapAlreadyLoaded,
|
||||||
showChallengeComplete
|
showChallengeComplete,
|
||||||
|
isSignedIn: !!username
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ const initialState = {
|
|||||||
title: 'Learn To Code | Free Code Camp',
|
title: 'Learn To Code | Free Code Camp',
|
||||||
shouldShowSignIn: false,
|
shouldShowSignIn: false,
|
||||||
user: '',
|
user: '',
|
||||||
|
lang: '',
|
||||||
csrfToken: '',
|
csrfToken: '',
|
||||||
windowHeight: 0,
|
windowHeight: 0,
|
||||||
navHeight: 0,
|
navHeight: 0,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import LangLink from '../../../../utils/Language-Link.jsx';
|
import { Link } from 'react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import FA from 'react-fontawesome';
|
import FA from 'react-fontawesome';
|
||||||
import PureComponent from 'react-pure-render/component';
|
import PureComponent from 'react-pure-render/component';
|
||||||
@ -61,7 +61,7 @@ export class Block extends PureComponent {
|
|||||||
className={ challengeClassName }
|
className={ challengeClassName }
|
||||||
key={ title }
|
key={ title }
|
||||||
>
|
>
|
||||||
<LangLink to={ `/challenges/${blockName}/${dashedName}` }>
|
<Link to={ `/challenges/${blockName}/${dashedName}` }>
|
||||||
<span
|
<span
|
||||||
onClick={ () => updateCurrentChallenge(challenge) }
|
onClick={ () => updateCurrentChallenge(challenge) }
|
||||||
>
|
>
|
||||||
@ -73,7 +73,7 @@ export class Block extends PureComponent {
|
|||||||
''
|
''
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</LangLink>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { modernChallenges, map, challenges } from './challenges';
|
import { modernChallenges, map, challenges } from './challenges';
|
||||||
import NotFound from '../components/NotFound/index.jsx';
|
import NotFound from '../components/NotFound/index.jsx';
|
||||||
import { addLang } from '../utils/add-lang';
|
import { addLang } from '../utils/lang';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
path: '/:lang',
|
path: '/:lang',
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { Link } from 'react-router';
|
|
||||||
import { addLang } from './add-lang';
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({ lang: state.app.lang });
|
|
||||||
|
|
||||||
export class LangLink extends React.Component {
|
|
||||||
static displayName = 'LangLink';
|
|
||||||
static propTypes = {
|
|
||||||
to: PropTypes.string,
|
|
||||||
lang: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
to,
|
|
||||||
lang,
|
|
||||||
...props
|
|
||||||
} = this.props;
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to={ addLang(to, lang) }
|
|
||||||
{ ...props }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(LangLink);
|
|
@ -1,13 +0,0 @@
|
|||||||
import supportedLanguages from '../../utils/supported-languages';
|
|
||||||
|
|
||||||
const toLowerCase = String.prototype.toLowerCase;
|
|
||||||
export function addLang(url, lang) {
|
|
||||||
const maybeLang = toLowerCase.call(url.split('/')[1]);
|
|
||||||
if (supportedLanguages[maybeLang]) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
if (supportedLanguages[lang]) {
|
|
||||||
return `/${lang}${url}`;
|
|
||||||
}
|
|
||||||
return `/en${url}`;
|
|
||||||
}
|
|
39
common/app/utils/lang.js
Normal file
39
common/app/utils/lang.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import
|
||||||
|
supportedLanguages,
|
||||||
|
{ langTagRegex }
|
||||||
|
from '../../utils/supported-languages';
|
||||||
|
|
||||||
|
const toLowerCase = String.prototype.toLowerCase;
|
||||||
|
|
||||||
|
export function getLangFromPath(path) {
|
||||||
|
const maybeLang = toLowerCase.call(path.split('/')[1]);
|
||||||
|
if (supportedLanguages[maybeLang]) {
|
||||||
|
return maybeLang;
|
||||||
|
}
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addLang(path, lang, primaryLang) {
|
||||||
|
// if maybeLang is supported continue
|
||||||
|
// if maybeLang is unsupported lang, remove and use lang
|
||||||
|
// if maybeLang is not lang tag, add lang tag.
|
||||||
|
// if both primary and lang are not lang tags default en
|
||||||
|
const maybeLang = toLowerCase.call(path.split('/')[1]);
|
||||||
|
const restUrl = path.split('/').slice(2).join('/');
|
||||||
|
|
||||||
|
if (supportedLanguages[maybeLang]) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
langTagRegex.test(maybeLang) &&
|
||||||
|
!supportedLanguages[maybeLang]
|
||||||
|
) {
|
||||||
|
return `/${primaryLang || lang }/${restUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportedLanguages[primaryLang || lang]) {
|
||||||
|
return `/${primaryLang || lang}${path}`;
|
||||||
|
}
|
||||||
|
return `/en${path}`;
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
export const langTagRegex = /^[a-z]{2}(?:-[a-zA-Z]{2,3})?$/;
|
||||||
export default {
|
export default {
|
||||||
en: 'English',
|
en: 'English',
|
||||||
es: 'Spanish'
|
es: 'Spanish'
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import supportedLanguages from '../../common/utils/supported-languages';
|
import
|
||||||
|
supportedLanguages,
|
||||||
|
{ langTagRegex }
|
||||||
|
from '../../common/utils/supported-languages';
|
||||||
import passthroughs from '../utils/lang-passthrough-urls';
|
import passthroughs from '../utils/lang-passthrough-urls';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
|
||||||
const log = debug('fcc:middlewares:lang');
|
const log = debug('fcc:middlewares:lang');
|
||||||
const langTagRegex = /^[a-z]{2}(?:-[a-zA-Z]{2,3})?$/;
|
|
||||||
const toLowerCase = String.prototype.toLowerCase;
|
const toLowerCase = String.prototype.toLowerCase;
|
||||||
|
|
||||||
// redirect(statusOrUrl: String|Number, url?: String) => Void
|
// redirect(statusOrUrl: String|Number, url?: String) => Void
|
||||||
|
Reference in New Issue
Block a user