It is frequently need to create a site that will support a few languages, for example I’m going to create multilingual site which will support 3 languages for a while. Then I’m going to add a few more.
So, I want to build with 3 ua, ru, en. And in this article I’ll explain to you how to implement it
How does react-router works with multilingual sites
Short description and understanding what is being built. Our site will have links:
site.com – it will redirect to site.com/[locale]/
site.com/en/ – an English version
site.com/ru/ – a Russian version
site.com/ua/ – a Ukrainian version
site.com should redirect users to en or ru or ua
it redirect to ru if locale in the window.localStorage.getItem(‘locale’). If it is not initialized it will be initialized to ‘en‘ window.localStorage.setItem(‘locale’, ‘en’)
all the rest urls such as site.com/de/, site.com/sdf/, site.com/adfsdf… etc. will be redirected to 404 page.
when urls starts from site.com/ru and locale is set up to en it will take locale from the url and change saved local to the local of the url
So let’s get started!
We have to install react-router-dom
1 |
npm i react-router-dom |
and import components
1 2 3 4 5 6 |
import { BrowserRouter as Router, Route, Link, Redirect, Switch, withRouter } from 'react-router-dom' |
Then create 4 components:
Home, About, Contacts and NoFound for a 404 error
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
const Home = (props) => { return <div> <LangButtons {...props} /> <h1>Home</h1> </div> } const About = (props) => { return <div> <LangButtons {...props} /> <h1>About</h1> </div> } const Contacts = (props) => { return <div> <LangButtons {...props} /> <h1>Contacts</h1> </div> } const NoFound = (props) => { return <div> <LangButtons {...props} /> <h1>404</h1> </div> } |
<LangButtons> is a component that renders three buttons en, ru, ua, which will change the language.
then we create LangRouter where put 3 routes to each language with regular expression /en/* /ru/* and /ua/*.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
class LangRouter extends React.Component { constructor(props) { super(props) let locale = window.localStorage.getItem('locale') if (locale === null) { window.localStorage.setItem('locale', 'en') locale = 'ru' } this.state = { locale: locale } } setLocale = (newLocale) => { console.log('newLocale = ', newLocale); window.localStorage.setItem('locale', newLocale) this.setState({ locale: newLocale }) } render() { const { locale } = this.state return ( <Switch> <Route path="/" exact render={propRouter => { return <Redirect to={locale + "/"} /> }} /> <Route path="/en/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> }/> <Route path="/ru/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> <Route path="/ua/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> <Route path="*" render={ propsRouter => <NoFound {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> </Switch> ) } } |
It is a root component which stores locale of our app for all child’s components. In the constructor we check the window.localStorage on ‘local’ and if it is set up then we’ll initialize this.state.locale with its value and pass down. The function setLocal is used to pass downfall as it calls for two way binding. This function will be passed to buttons which will change this.state.locale.
in Route we use render because we have to pass setLocal and this.state.locale to children, so we can’t use component.
Now, let’s create App
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
const App = (props) => { let { locale } = props let urlLocale = props.location.pathname.substring(1, 3) if ( locale !== urlLocale ) { props.setLocale(urlLocale) locale = urlLocale } return ( <div> <Link to={"/" + locale + "/"}>Home</Link> |{' '} <Link to={"/" + locale + "/about"}>About</Link> |{' '} <Link to={"/" + locale + "/contacts"}>Contacts</Link> <Switch> <Route path={"/" + locale + "/"} exact render={propRouter => <Home {...propRouter} {...props} /> } /> <Route path={"/" + locale + "/about"} render={propRouter => <About {...propRouter} {...props} /> } /> <Route path={"/" + locale + "/contacts"} render={propRouter => <Contacts {...propRouter} {...props} /> } /> <Route path="*" render={propRouter => <NoFound {...propRouter} {...props} /> } /> </Switch> </div> ) } |
It is accept props and check locale from the url if they different than the url value will be set up to locale from the parent component by calling function props.setLocale(urlLocale)
Then we see a few <Link> elements and Route where dynamically generated paths. Again we use render because we have to pass data to the children.
The last element is LangButtons.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class LangButtons extends React.Component { setLocale = (newLocale) => { const { locale } = this.props let url = '/' + newLocale + this.props.location.pathname.substring(3) this.props.history.push(url) this.props.setLocale(newLocale) } render() { return ( <div> <button onClick={() => this.setLocale('ru')} >ru</button> | <button onClick={() => this.setLocale('en')} >en</button> | <button onClick={() => this.setLocale('ua')} >ua</button> </div> ) } } |
Each button has it’s own event – handler, which call the props function setLocale and change locale
And here is all the code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter as Router, Route, Link, Redirect, Switch, withRouter } from 'react-router-dom' class LangButtons extends React.Component { setLocale = (newLocale) => { const { locale } = this.props let url = '/' + newLocale + this.props.location.pathname.substring(3) this.props.history.push(url) this.props.setLocale(newLocale) } render() { return ( <div> <button onClick={() => this.setLocale('ru')} >ru</button> | <button onClick={() => this.setLocale('en')} >en</button> | <button onClick={() => this.setLocale('ua')} >ua</button> </div> ) } } const Home = (props) => { return <div> <LangButtons {...props} /> <h1>Home</h1> </div> } const About = (props) => { return <div> <LangButtons {...props} /> <h1>About</h1> </div> } const Contacts = (props) => { return <div> <LangButtons {...props} /> <h1>Contacts</h1> </div> } const NoFound = (props) => { return <div> <LangButtons {...props} /> <h1>404</h1> </div> } const App = (props) => { let { locale } = props let urlLocale = props.location.pathname.substring(1, 3) if ( locale !== urlLocale ) { props.setLocale(urlLocale) locale = urlLocale } return ( <div> <Link to={"/" + locale + "/"}>Home</Link> |{' '} <Link to={"/" + locale + "/about"}>About</Link> |{' '} <Link to={"/" + locale + "/contacts"}>Contacts</Link> <Switch> <Route path={"/" + locale + "/"} exact render={propRouter => <Home {...propRouter} {...props} /> } /> <Route path={"/" + locale + "/about"} render={propRouter => <About {...propRouter} {...props} /> } /> <Route path={"/" + locale + "/contacts"} render={propRouter => <Contacts {...propRouter} {...props} /> } /> <Route path="*" render={propRouter => <NoFound {...propRouter} {...props} /> } /> </Switch> </div> ) } class LangRouter extends React.Component { constructor(props) { super(props) let locale = window.localStorage.getItem('locale') if (locale === null) { window.localStorage.setItem('locale', 'en') locale = 'ru' } this.state = { locale: locale } } setLocale = (newLocale) => { console.log('newLocale = ', newLocale); window.localStorage.setItem('locale', newLocale) this.setState({ locale: newLocale }) } render() { const { locale } = this.state return ( <Switch> <Route path="/" exact render={propRouter => { return <Redirect to={locale + "/"} /> }} /> <Route path="/en/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> }/> <Route path="/ru/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> <Route path="/ua/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> <Route path="*" render={ propsRouter => <NoFound {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> </Switch> ) } } ReactDOM.render( <Router> <LangRouter /> </Router>, document.getElementById('root') ) |
The result is next
a bit refactored variant:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter as Router, Route, Link, Redirect, Switch, withRouter } from 'react-router-dom' const LangButtons = ({ setLocale }) => { return ( <div> <button onClick={() => setLocale('ru')} >ru</button> | <button onClick={() => setLocale('en')} >en</button> | <button onClick={() => setLocale('ua')} >ua</button> </div> ) } const Home = (props) => { return <div> <LangButtons {...props} /> <h1>Home</h1> </div> } const About = (props) => { return <div> <LangButtons {...props} /> <h1>About</h1> </div> } const Contacts = (props) => { return <div> <LangButtons {...props} /> <h1>Contacts</h1> </div> } const NoFound = (props) => { return <div> <LangButtons {...props} /> <h1>404</h1> </div> } const App = (props) => { let { locale } = props let urlLocale = props.location.pathname.substring(1, 3) if ( locale !== urlLocale ) { props.setLocale(urlLocale) locale = urlLocale } return ( <div> <Link to={"/" + locale + "/"}>Home</Link> |{' '} <Link to={"/" + locale + "/about"}>About</Link> |{' '} <Link to={"/" + locale + "/contacts"}>Contacts</Link> <Switch> <Route path={"/" + locale + "/"} exact render={propRouter => <Home {...propRouter} {...props} /> } /> <Route path={"/" + locale + "/about"} render={propRouter => <About {...propRouter} {...props} /> } /> <Route path={"/" + locale + "/contacts"} render={propRouter => <Contacts {...propRouter} {...props} /> } /> <Route path="*" render={propRouter => <NoFound {...propRouter} {...props} /> } /> </Switch> </div> ) } class LangRouter extends React.Component { constructor(props) { super(props) let locale = window.localStorage.getItem('locale') if (locale === null) { window.localStorage.setItem('locale', 'en') locale = 'ru' } this.state = { locale: locale } } setLocale = (newLocale) => { const { locale } = this.state let url = '/' + newLocale + this.props.location.pathname.substring(3) this.props.history.push(url) console.log('newLocale = ', newLocale); window.localStorage.setItem('locale', newLocale) this.setState({ locale: newLocale }) } render() { console.log('this = ', this); const { locale } = this.state return ( <Switch> <Route path="/" exact render={propRouter => { return <Redirect to={locale + "/"} /> }} /> <Route path="/en/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> }/> <Route path="/ru/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> <Route path="/ua/*" render={ propsRouter => <App {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> <Route path="*" render={ propsRouter => <NoFound {...propsRouter} locale={locale} setLocale={this.setLocale} /> } /> </Switch> ) } } LangRouter = withRouter(LangRouter) ReactDOM.render( <Router> <LangRouter /> </Router>, document.getElementById('root') ) |
the end
how to render different components according to selected language? thanks in advance
You have to store somewhere in a global variable locale the current selected language. The main source of truth should be an url. When the page is loaded you get the language from the url and initialize the global variable. Usually, I put the language into the redux store and from there make it available into all the components