From 95aab958aa97da13730ccbb90fd2bbf7f0d88b4f Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Mon, 20 Jun 2016 21:01:14 -0700 Subject: [PATCH] Feature(langauge): Make client history language aware Remove need for language aware links --- client/index.js | 3 +- client/use-lang-routes.js | 44 +++++++++++++++++++ common/app/App.jsx | 6 +-- common/app/redux/reducer.js | 1 + .../challenges/components/map/Block.jsx | 6 +-- common/app/routes/index.js | 2 +- common/app/utils/Language-Link.jsx | 30 ------------- common/app/utils/add-lang.js | 13 ------ common/app/utils/lang.js | 39 ++++++++++++++++ common/utils/supported-languages.js | 1 + server/middlewares/add-lang.js | 6 ++- 11 files changed, 98 insertions(+), 53 deletions(-) create mode 100644 client/use-lang-routes.js delete mode 100644 common/app/utils/Language-Link.jsx delete mode 100644 common/app/utils/add-lang.js create mode 100644 common/app/utils/lang.js diff --git a/client/index.js b/client/index.js index 296870365f..ed75f99364 100644 --- a/client/index.js +++ b/client/index.js @@ -10,6 +10,7 @@ import { } from 'react-router-redux'; import { render } from 'redux-epic'; import { createHistory } from 'history'; +import useLangRoutes from './use-lang-routes.js'; import createApp from '../common/app'; import provideStore from '../common/app/provide-store'; @@ -36,7 +37,7 @@ initialState.app.csrfToken = csrfToken; const serviceOptions = { xhrPath: '/services', context: { _csrf: csrfToken } }; -const history = createHistory(); +const history = useLangRoutes(createHistory)(); const devTools = window.devToolsExtension ? window.devToolsExtension() : f => f; const adjustUrlOnReplay = !!window.devToolsExtension; diff --git a/client/use-lang-routes.js b/client/use-lang-routes.js new file mode 100644 index 0000000000..d18921521b --- /dev/null +++ b/client/use-lang-routes.js @@ -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(); } + }; + }; +} diff --git a/common/app/App.jsx b/common/app/App.jsx index e6554b7711..307f9e9044 100644 --- a/common/app/App.jsx +++ b/common/app/App.jsx @@ -36,15 +36,15 @@ const mapStateToProps = createSelector( isMapAlreadyLoaded, showChallengeComplete ) => ({ - shouldShowSignIn, - isSignedIn: !!username, username, points, picture, toast, + shouldShowSignIn, isMapDrawerOpen, isMapAlreadyLoaded, - showChallengeComplete + showChallengeComplete, + isSignedIn: !!username }) ); diff --git a/common/app/redux/reducer.js b/common/app/redux/reducer.js index 066bac4c13..c26f11cfa5 100644 --- a/common/app/redux/reducer.js +++ b/common/app/redux/reducer.js @@ -5,6 +5,7 @@ const initialState = { title: 'Learn To Code | Free Code Camp', shouldShowSignIn: false, user: '', + lang: '', csrfToken: '', windowHeight: 0, navHeight: 0, diff --git a/common/app/routes/challenges/components/map/Block.jsx b/common/app/routes/challenges/components/map/Block.jsx index 57837f68a1..8126df25ea 100644 --- a/common/app/routes/challenges/components/map/Block.jsx +++ b/common/app/routes/challenges/components/map/Block.jsx @@ -1,5 +1,5 @@ import React, { PropTypes } from 'react'; -import LangLink from '../../../../utils/Language-Link.jsx'; +import { Link } from 'react-router'; import { connect } from 'react-redux'; import FA from 'react-fontawesome'; import PureComponent from 'react-pure-render/component'; @@ -61,7 +61,7 @@ export class Block extends PureComponent { className={ challengeClassName } key={ title } > - + updateCurrentChallenge(challenge) } > @@ -73,7 +73,7 @@ export class Block extends PureComponent { '' } - +

); }); diff --git a/common/app/routes/index.js b/common/app/routes/index.js index 0d2d3928b1..36a4bd2d55 100644 --- a/common/app/routes/index.js +++ b/common/app/routes/index.js @@ -1,6 +1,6 @@ import { modernChallenges, map, challenges } from './challenges'; import NotFound from '../components/NotFound/index.jsx'; -import { addLang } from '../utils/add-lang'; +import { addLang } from '../utils/lang'; export default { path: '/:lang', diff --git a/common/app/utils/Language-Link.jsx b/common/app/utils/Language-Link.jsx deleted file mode 100644 index 8caf5f2610..0000000000 --- a/common/app/utils/Language-Link.jsx +++ /dev/null @@ -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 ( - - ); - } -} - -export default connect(mapStateToProps)(LangLink); diff --git a/common/app/utils/add-lang.js b/common/app/utils/add-lang.js deleted file mode 100644 index ef66a4a2b5..0000000000 --- a/common/app/utils/add-lang.js +++ /dev/null @@ -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}`; -} diff --git a/common/app/utils/lang.js b/common/app/utils/lang.js new file mode 100644 index 0000000000..f727331f66 --- /dev/null +++ b/common/app/utils/lang.js @@ -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}`; +} diff --git a/common/utils/supported-languages.js b/common/utils/supported-languages.js index 536ad617f4..b074d02c43 100644 --- a/common/utils/supported-languages.js +++ b/common/utils/supported-languages.js @@ -1,3 +1,4 @@ +export const langTagRegex = /^[a-z]{2}(?:-[a-zA-Z]{2,3})?$/; export default { en: 'English', es: 'Spanish' diff --git a/server/middlewares/add-lang.js b/server/middlewares/add-lang.js index e59f58078d..a1c7916382 100644 --- a/server/middlewares/add-lang.js +++ b/server/middlewares/add-lang.js @@ -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 debug from 'debug'; const log = debug('fcc:middlewares:lang'); -const langTagRegex = /^[a-z]{2}(?:-[a-zA-Z]{2,3})?$/; const toLowerCase = String.prototype.toLowerCase; // redirect(statusOrUrl: String|Number, url?: String) => Void