BACK TO BLOG

Multilingual custom navigation links

Published on September 09, 2020

Shopify offers a nice and easy way to create menu for your app via extensions.
One issue though, it is not possible to translate it.
In this post, learn how to create your own multilingual custom navigation links for your app!

What you'll need

Before starting, install the needed packages.

  • @shopify/polaris
  • i18next
  • react-i18next

Via command line:
npm install --save @shopify/polaris i18next react-i18next

Set up the translation tool

Now we need to set up i18next and react-i18next so that we can translate our navigation links (and the whole application).

In your index.js file, import i18next and initReactI18next:

import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import fr from './locales/fr.json'; // Our translation file.

Initialize i18next with your configuration then render your app:

i18next
    // Bind react-i18next to the instance.
    // Will be useful in other components to get the current language instance.
    .use(initReactI18next)
    // Initialize i18next as you wish, just demo below.
    .init({
        debug: false,
        resources: {
            fr: fr
        },
        lng: 'en',
        fallbackLng: 'en',
        interpolation: {
            escapeValue: false
        },
        keySeparator: false,
        nsSeparator: false
    })
    .then(() => {
        ReactDOM.render(
            <React.StrictMode>
                <Navigation/>
            </React.StrictMode>,
            document.getElementById('root')
        );
    });

Set up the navigation links

You probably noticed from the i18next initialization, we render a component called Navigation.
This component will be in charge of displaying our custom multilingual navigation links.

What the magic trick ? Nothing special really, a simple Polaris Tabs component.

Here is the list of needed imports:

import React, {useState, useCallback} from 'react';
import {
    AppProvider,
    Frame,
    Page,
    Tabs,
    FooterHelp,
    Link,
    Button,
    Popover,
    ActionList
} from '@shopify/polaris';
import enTranslations from '@shopify/polaris/locales/en.json';
import frTranslations from '@shopify/polaris/locales/fr.json';
import {useTranslation} from 'react-i18next';
import Configuration from './pages/Configuration';
import Customers from './pages/Customers';
import Sales from './pages/Sales';

And here the first part of our component:

function Navigation() {
    // t() will be in charge of translations
    // i18n() will handle language changes
    const [t, i18n] = useTranslation();

    // Our selected tab.
    const [selected, setSelected] = useState(0);
    const handleTabChange = useCallback(
        (selectedTabIndex) => setSelected(selectedTabIndex),
        [],
    );

    // Handle the language selector popover.
    const [active, setActive] = useState(false);
    const toggleActive = useCallback(
        () => setActive((active) => !active),
        []
    );

    // Shopify components translations.
    // Will be used in the AppProvider component.
    const polarisTranslation = {
        fr: frTranslations,
        en: enTranslations
    }

    // Our navigation links.
    // See that \"content\" is translated thanks to t() function.
    const tabs = [
        {
            id: 'configuration',
            content: t('Configuration'),
            panelID: 'configuration-content'
        },
        {
            id: 'customers',
            content: t('Customers'),
            panelID: 'customers-content'
        },
        {
            id: 'sales',
            content: t('Sales'),
            panelID: 'sales-content'
        }
    ];

    const activator = (
        <Button onClick={toggleActive} disclosure plain>
            {t('Change the current language')}
        </Button>
    );

    // Our components, indexed the same as our tabs.
    const pages = [
        <Configuration/>,
        <Customers/>,
        <Sales/>
    ];

    // Wrap our tabs in an AppProvider with the current language translation
    // and a Frame.
    // In recent version of Polaris, you'll also need to use a Provider
    // component, not used here for the sake of simplicity.
    // Then the Tabs component will handle your different navigation links
    // and their corresponding components, here wrapped in a Page
    // component.
    // 
    // For this example, we add a FooterHelp where you can change the
    // language directly to see it in action.
    // In real life application you'll simply need to use the browser
    // language and create a form to let merchants select their preferred
    // language.
    return (
        <AppProvider i18n={polarisTranslation[i18n.language]}>
            <Frame>
                <Tabs tabs={tabs} selected={selected} onSelect={handleTabChange}>
                    <Page fullWidth>
                        {pages[selected]}
                    </Page>
                </Tabs>
                <FooterHelp>
                    <div style={{textAlign: 'center'}}>
                        <Popover active={active} activator={activator} onClose={toggleActive}>
                            <ActionList
                                items={[
                                    {
                                        content: t('English'),
                                        onAction: () => i18n.changeLanguage('en'),
                                        disabled: i18n.language === 'en'
                                    },
                                    {
                                        content: t('French'),
                                        onAction: () => i18n.changeLanguage('fr'),
                                        disabled: i18n.language === 'fr'
                                    },
                                ]}
                            />
                        </Popover>
                    </div>
                </FooterHelp>
            </Frame>
        </AppProvider>
    );
}

export default Navigation;

Demo

A demonstration of this code in action is available here (hosted on Netlify).

Final thoughts

As you can see, it is really not that hard to implement your own navigation links and translate them.

An other way of doing it would be to use a Stack component instead, with some CSS, so that you could also add buttons in the right side of navigation links (like when you add primaryAction and/or secondaryActions to a Page component and it adds the corresponding action as buttons in the navigation link bar).
It might be a good subject for an other blog post.

If you want to try it on your own or copy the code for your own application, the source code is available on Github.