Building A Translation Component With React And Redux

What Are We Going To Build?

We are going to build a simple component that will display text in the correct language (locale) and is managed by Redux. Updating the locale will trigger a re-render of the Translation component and this will result in a clean language switch without a page reload and without re-rendering any parent components.

The result is viewable on the demo page and you can see the source code on Github.

Requirements

Files We Will Create

  • components
    • Translation
      • actions.js
      • Translation.jsx
      • TranslationContainer.jsx
      • reducer.js
  • constants
    • translations.js

Translation Component

The component itself is just a small render function that will display the translated text. You’ll notice that it’s rendered with a <span> around the translation and this is because JSX enforces you to always have a wrapping element. The upcoming version of React however (with the new React Fiber core) will make it possible to just return a string but until then we’ll just have to do it with out wrapping <span> element.

components/Translation/Translation.jsx

import React, { Component } from 'react';
import { PropTypes } from 'prop-types';

export default class Translation extends Component {

  render() {
    return (<span>{this.props.translation}</span>);
  }
}

Translation.propTypes = {
  translation: PropTypes.string.isRequired,
};

Based on the passed props translationKey and locale (that comes from the application state) we will try to get the correct translation from the translation constants. Whenever one of these props will change, like a language switch, we update the translation.

components/Translation/TranslationContainer.jsx

import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import { connect } from 'react-redux';
import { TRANSLATIONS } from 'constants/translations';
import Translation from './Translation';

class TranslationContainer extends Component {

  constructor(props) {
    super(props);
    this.state = {
      translation: '',
    };
  }

  componentDidMount() {
    this._updateTranslation(this.props.translationKey, this.props.locale);
  }

  componentWillReceiveProps(nextProps) {
    // update the translation if one of the props will change
    if (this.props.translationKey !== nextProps.translationKey || this.props.locale !== nextProps.locale) {
      this._updateTranslation(nextProps.translationKey, nextProps.locale);
    }
  }

  _updateTranslation(translationKey, activeLanguageCode) {
    if (translationKey && activeLanguageCode) {
      try {
        this.setState({ translation: TRANSLATIONS[activeLanguageCode][translationKey] });
      } catch (error) {
        console.log(error);
      }
    }
  }

  render() {
    if (!this.state.translation || this.state.translation === '') return null;
    return (
      <Translation translation={this.state.translation} />
    );
  }
}

function mapStateToProps(state) {
  return {
    locale: state.translation.locale,
  };
}

export default connect(mapStateToProps, null)(TranslationContainer);

TranslationContainer.propTypes = {
  translationKey: PropTypes.string.isRequired,
  locale: PropTypes.string,
};

Action and Reducer to update the application store. For the demo we have set the default locale to en_US but it could also be set to null and wait for another component to dispatch the setLanguage action when it knows which local to use (React Router could be used to check a URL parameter for example).

This is just a basic implementation of Redux, you can check the official Redux Basics documentation for more information.

components/Translation/actions.js

import { SET_LANGUAGE } from 'constants/generalConstants';

export function setLanguage(locale) {
  return {
    type: SET_LANGUAGE,
    locale,
  };
}

components/Translation/reducer.js

import { SET_LANGUAGE } from 'constants/generalConstants';

// Initial state
export const initialState = {
  locale: 'en_US', // default locale
};

// Reducer
export default function reducer(state = initialState, action) {
  switch (action.type) {
    case SET_LANGUAGE:
      return { ...state, locale: action.locale };
    default:
      return state;
  }
}

Make Redux aware of the translationReducer by adding it to the rootReducer (which will be used to create the store).

rootReducer.js

import { combineReducers } from 'redux';
import translationReducer from 'components/Translation/reducer';

export const rootReducer = combineReducers({
  // ... your other reducers here ...
  translation: translationReducer,
});

Translation Constants

Finally, we have the constant containing all the translations. To keep it simple, all translations are now in a single file but it would make sense to split it up into separate files for scalability.

export const TRANSLATIONS = {
  en_US: {
    title: 'Multilingual React Application',
    table_caption: 'Translation Example',
  },
  fr_BE: {
    title: 'Application Multilingue React',
    table_caption: 'Exemple de traduction',
  },
  nl_BE: {
    title: 'Meertalige React Applicatie',
    table_caption: 'Vertaling Voorbeeld',
  },
  zh_CN: {
    title: '多语言 React 申请',
    table_caption: '翻译实例',
  }
};

TL;DR

It’s fairly easy to create your own translation component: