There is probably a myriad of ways to go about setting up a multi-language site with React and Prismic. However, since the online examples of this are a little thin I figured I’d do a quick run-through of the approach we took when building this manipulated media site for Reuters.
Prismic is a CMS that is completely free to use for one team member and scales very affordably for small teams. It also has a very intuitive setup for working with multiple languages. You’ll first want to connect Prismic to your existing react application by following the steps here and then you can start adding translations from the Prismic GUI under settings.
If you followed the steps from the article above you will now have a react app set up and an entry point to Prismic that will look something like this:
index.js
import "regenerator-runtime/runtime"; import { call, put, takeEvery } from "redux-saga/effects"; import { push } from "connected-react-router"; import Prismic from "prismic-javascript"; const prismicClient = Prismic.client("https://<project-name>.prismic.io/api/v2");
We are going to utilize the Prismic.Predicates method to help us retrieve each newly requested language:
let glossary = {}; let entries = yield prismicClient.query( Prismic.Predicates.at("document.type", "glossary"), { lang: lang, } ); yield put({ type: "GLOSSARY_RETURNED", entries, }); }
To manage state on the front end we’re going to use Redux and dispatchPageRequest() to update the selected language and then a helper function from `connected-react-router` to redirect the page.
Here’s an example of a Redux dispatch method (you will likely need multiple methods for each section of your application)
export function dispatchPageRequest(langCode, changeRoute, index, currentLang) { return { type: "PAGES_REQUESTED", langCode, changeRoute, index, currentLang }; } export function dispatchLandingDataRequest( langCode, changeRoute, index, currentLang ) { return { type: "LANDING_DATA", langCode, changeRoute, index, currentLang }; } export function dispatchNavRequest() { return { type: "NAVTEXT_REQUESTED" }; } export function dispatchGlossaryRequest(langCode) { return { type: "GLOSSARY_REQUESTED", langCode }; } export function dispatchSectionRequest(id, langCode, index) { return { type: "SECTION_REQUESTED", id, langCode, index }; } export function dispatchQuizSectionRequest(id, langCode, index) { return { type: "QUIZ_SECTION_REQUESTED", id, langCode, index }; }
For this site, we had 16 different languages to render so in order to sort through them we created a fetchPages method that would switch the lang code depending on which was passed in. The cases in this conditional needed to match up exactly with what was being returned from Prismic.
export function* fetchPages(action) { let lang = "en-us"; if (action.langCode) { switch (action.langCode) { case "es": lang = "es-es"; break; case "fr": lang = "fr-fr"; break; case "ar": lang = "ar-ae"; break; case "ru": lang = "ru"; break; case "he": lang = "he"; break; case "bu": lang = "bu-my"; break; case "zh": lang = "zh-cn"; break; case "da": lang = "da-dk"; break; case "de": lang = "de-de"; break; case "hi": lang = "hi-in"; break; case "id": lang = "id"; break; case "ja": lang = "ja-jp"; break; case "pt": lang = "pt-br"; break; case "sv": lang = "sv-se"; break; case "tr": lang = "tr"; break; } }
In addition to Redux, Redux-saga was used as middleware, which was accessing the Redux state and managing any change asynchronously. We’re going to import some helper functions from this middleware, call, put and takeEvery. The takeEvery method, helps us fetch the new languages concurrently.
Ok, we’re just about there! Let's look at how we can take the user input and follow it back to updating Redux. For our purposes we wanted different actions to occur with different events. Here is what the hover state looked like:
The inbuilt onMouseEnter() and onMouseLeave() methods are going to do just nicely to help up pull this off. Each button has an id that lines up with one of those lang codes and then when you hover, a functions is triggered that filters through the data and sets the state of the component.
onMouseEnter(event) { const langCode = event.target.id; if (this.props.landingData) { const langData = this.props.landingData.filter((data) => { if (data.lang === langCode) { return data; } }); this.setState({ upper_text: langData[0].data.description[0].text, title: langData[0].data.title, manipulated_image: langData[0].data.manipulated_image.url, sponsored_by: langData[0].data.sponsored_by, }); } }
When you hover away from the button we access our nested state with the spread operator and reset it based on the default props.
onMouseLeave(event) { this.setState({ ...this.state, upper_text: this.props.page.data.description[0].text, title: this.props.page.data.title, manipulated_image: this.props.page.data.manipulated_image.url, sponsored_by: this.props.page.data.sponsored_by, landingData: this.props.page.data.landingData, id: this.props.page.lang, }); }
Let's cover the click event where we want to access the Redux store and rehydrate the application with the selected data. When a user clicks the dispatchPageRequest() method we wrote earlier will be called and the langCode from that button id will be passed along. The return will have a type of ‘PAGES_REQUESTED’. Specifying the type like this allows the react-saga middleware to handle this update concurrently as I mentioned earlier.
export function dispatchPageRequest(langCode, changeRoute, index, currentLang) { return { type: "PAGES_REQUESTED", langCode, changeRoute, index, currentLang };}
In the meantime we want to be checking for a click event and if the props have been updated on the landing page. We’ll do this in the componentDidUpdate() lifecycle method, which will continue to check recursively for when there is a change.
componentDidUpdate(prevProps) { if ( this.props.hasClicked !== prevProps.hasClicked || this.props.langCode !== prevProps.langCode ) { if (this.props.hasClicked) { scrollIntoView(document.getElementById("intro"), { time: 3000, }); } } if (this.state.video === "" && this.props.page.data !== undefined) { this.setState({ video: this.props.page.data ? this.props.page.data.landing_video : "", }); } }
This has been a pretty broad overview of our solution for implementing multiple languages in a react application. If you have other ways of dealing with multilingual sites I’d love to hear your approach.