React-router: Explain language-selection as topmost route

Created on 3 Mar 2015  路  5Comments  路  Source: ReactTraining/react-router

I'm trying to build a multilingual site where the language preference is part of the URL, e.g.

http://example.com/en/<somepage>  (English)
http://example.com/jp/<somepage>  (Japanese)
http://example.com/../            (etc)

The language selection is supposed to be dynamic and new languages will be added as time goes (the en and jp part should be variable).

I went through documentation and examples but couldn't find such a case. How can I configure my routes in such a way? Is it documented somewhere? Was this discussed before (it seems like a common pattern used on many sites).

Most helpful comment

Overview covers nesting, although for a different use case.

Your route config might look like:

var routes = (
  <Route name="app" path="/:lang" handler={App}>
    <Route name="something" handler={SomePage}/>
    <Route name="other" handler={SomeOtherPage}/>
    <DefaultRoute handler={YetAnotherPage}/>
  </Route>
);

+

Router.run(routes, function (Handler, state) {
  React.render(<Handler params={state.params}/>, rootEl);
});

+

var App = React.createClass({
  render() {
    return <RouteHandler lang={this.props.params.lang} />
  }
}

This way all your nested route handlers (SomePage, etc) will get lang prop.

All 5 comments

Overview covers nesting, although for a different use case.

Your route config might look like:

var routes = (
  <Route name="app" path="/:lang" handler={App}>
    <Route name="something" handler={SomePage}/>
    <Route name="other" handler={SomeOtherPage}/>
    <DefaultRoute handler={YetAnotherPage}/>
  </Route>
);

+

Router.run(routes, function (Handler, state) {
  React.render(<Handler params={state.params}/>, rootEl);
});

+

var App = React.createClass({
  render() {
    return <RouteHandler lang={this.props.params.lang} />
  }
}

This way all your nested route handlers (SomePage, etc) will get lang prop.

How does the proposed solution above handle the default language case, ie having:

http://example.com/...    (english) <-- no :lang param right?
http://example.com/fr/... (french)
http://example.com/de/... (german)

I found a router setup that suggests a solution here: http://stackoverflow.com/a/35075669/1253297.

I modified it to support the behavior I was looking for and came up with this:

export default function createRoutes() {
  const langs = ['fr', 'de'];
  const subRoutes = (
    <Route>
      <IndexRoute component={Home}/>
      <Route path="about" component={About}/>
      <Route path=":article" component={Article}/>
      <Route path="*" component={NotFound}/>
    </Route>
  );

  return (
    <Route path="/" component={App}>
      {langs.map(lang => (
        <Route key={lang} path={lang}>
          {subRoutes}
        </Route>
      ))}
      {subRoutes}
    </Route>
  );
}

This setup seems to work in my application so far, but it does feel inelegant.
I would love to know what you think of it

_Edit:_ now I am stumped again because I am not seeing how the solution above exposes which is the current lang to the rest of the app.

@Maximilianos You would get that as a param on your route components. A component at the top of your subRoutes tree could set that in some global or on context when it mounts or receives props.

Hey @timdorr thanks for help. I was just looking at getting the lang from the passed props like you suggest, but unlike gaearon's solution, because I am not using a dynamic segment in my url for the language (no :lang) I don't get that bit of info passed down to my rendering components params.

The best I seem to be able to get is the props.location.pathname but extracting the lang from this prop seems hacky.

Is there a way to define a prop on a route that would get passed down to its children?
If not do you think using the onEnter hook is a valid place to set this global state?
Using redux I could potentially use something like:

export default function createRoutes(store) {
  const subRoutes = (
    <Route>
      <IndexRoute component={Home}/>
      <Route path="about" component={About}/>
      <Route path=":article" component={Article}/>
      <Route path="*" component={NotFound}/>
    </Route>
  );

  return (
    <Route path="/" component={App}>
      <Route path="fr" onEnter={() => store.dispatch(switchLang('fr'))}>
        {subRoutes}
      </Route>
      <Route path="de" onEnter={() => store.dispatch(switchLang('de'))}>
        {subRoutes}
      </Route>
      {subRoutes}
    </Route>
  );
}

_* I removed the map operation from the configuration to make it slighty easier to read, otherwise it is the same as before_

Was this page helpful?
0 / 5 - 0 ratings