@volvo-cars/react-shared-translations

React translation library that provides localized translation for packages

Usage no npm install needed!

<script type="module">
  import volvoCarsReactSharedTranslations from 'https://cdn.skypack.dev/@volvo-cars/react-shared-translations';
</script>

README

React Shared Translations

@volvo-cars/react-shared-translations

This package is meant to be used as a wrapper for the other i18n supported @volvo-cars packages. It helps serve localized translations for all the supported locales in the packages that serve them. It serves translations without affecting your bundle sizes and keeps them to a minimum by only providing the necessary translations. It supports both server-side and client-side rendering.

Installation

💡 This package includes Typescript definitions

Getting started

The idea is that you'll wrap your React application with a TranslatorProvider and feed it a locale and a translator props. When the React is done rendering your application, the translator will then have translator.cache showing all the translations strings used in this rendering in the current locale. If you are doing server-side rendering you'll then use the translator cache and render a TranslatorCache component and serve it on your page. This component will generate JSON data that will be used by your application client-side.

Next.js Example

A complete example of implementing package translations in your Next.js app can be found here.

_document.tsx

import React from 'react';
import NextDocument, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from 'next/document';
import {
  getServerTranslator,
  TranslatorCache,
} from '@volvo-cars/react-shared-translations/src/server';
import { Translator } from '@volvo-cars/react-shared-translations';
class Document extends NextDocument<{ translator: Translator }> {
  static async getInitialProps(ctx: DocumentContext) {
    const { renderPage } = ctx;

    // We create our server translator which will hold the cache in translator.cache when the
    // app finishes rendering
    const translator = getServerTranslator();
    const originalRenderPage = renderPage;
    // We modify renderPage and pass the translator to _app.tsx to be then passsed to the provider
    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App: any) =>
          function EnhancedApp(props) {
            //                         ↓↓↓ pass the translator as a prop to _app.tsx
            return <App {...props} translator={translator} />;
          },
      });
    const initialProps = await NextDocument.getInitialProps(ctx);
    return {
      ...initialProps,
      // pass the translator as prop to Document to be used down below in render
      translator,
    };
  }

  render() {
    const { translator } = this.props;
    return (
      <Html>
        <Head />
        <body>
          /*Pass the translator to TranslatorCache to render the
          Translator.cache to the HTML state */
          <TranslatorCache translator={translator} />
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default Document;

_app.tsx

import React from 'react';
import { NextComponentType } from 'next';
import Providers from 'src/Providers';
import { AppProps } from 'next/app';
import type { ServerTranslator } from '@volvo-cars/react-shared-translations/src/server';
import {
  TranslatorProvider,
  getTranslator,
} from '@volvo-cars/react-shared-translations';

// Create a browser translator instance to be used client-side
const browserTranslator = getTranslator();
function App({
  Component,
  pageProps,
  translator,
}: {
  Component: NextComponentType;
  pageProps: AppProps['pageProps'];
  translator: ServerTranslator;
}) {
  return (
    // Pass the translator and locale to TranslatorProvider
    // translator is only defined server-side, so we add
    // browserTranslator as fallback for the browser
    <TranslatorProvider
      locale="sv-SE"
      translator={translator || browserTranslator}
    >
      <Component {...pageProps} />
    </TranslatorProvider>
  );
}
export default App;

Express Example

server.tsx

import React from 'react';
import ReactDomServer from 'react-dom/server';
import express from 'express';
import App from './components/App.tsx';
import htmlTemplate from './build/index.html';
import {
  getServerTranslator,
  TranslatorCache,
} from '@volvo-cars/react-shared-translations/src/server';

const app = express();
const port = 3000;

app.get('/', (req, res) => {
  const translator = getServerTranslator();
  const content = ReactDomServer.renderToString(
    <App locale="sv-SE" translator={translator} />
  );
  const html = html.replace(
    '<div id="root"></div>',
    `${ReactDomServer.renderToString(
      <TranslatorCache translator={translator} />
    )}
  <div id="root">${content}</div>`
  );
  res.send(html);
});

app.listen(port, () => {
  console.info(`Listening on port ${port}`);
});

client.tsx

import React from 'react';
import ReactDOM from 'react-dom'
import App from './components/App.tsx';
import {
  TranslatorProvider,
  getTranslator,
} from '@volvo-cars/react-shared-translations';

const browserTranslator = getTranslator();
ReactDOM.hydrate(
<App locale="sv-SE" translator={browserTranslator}/>
,document.getElementById('app');
)

App.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.tsx';
import {
  TranslatorProvider,
  getTranslator,
  Translator,
} from '@volvo-cars/react-shared-translations';

const App: React.FC<{ translator: Translator; locale: string }> = ({
  translator,
  locale,
  children,
}) => {
  return (
    <TranslatorProvider locale={locale} translator={translator}>
      <div>My app</div>
    </TranslatorProvider>
  );
};

Done, you should be able to see all localized strings when using the supported packages

Package Developers

The following section is for developers that work on @volvo-cars packages and want to localize strings in their packages.

A convention needs to be followed in the packages that will serve translated strings and it goes as follows:

  • useTranslate hook must be called with packageName.itemKey Ex:

    const showMore = useTranslate('react-faqs.showMore');
    

    We now know that the required string exists in the @volvo-cars/react-faqs package in the showMore item

  • Translation files must be inside a i18n directory with ${locale}.json as file names inside the package that needs them, Ex:

    └── react-faqs/
    ├── i18n/
    │ └── en.json
    │ └── sv-SE.json
    │ └── da-DK.json
    │ └── en-US.json
    

useTranslate takes an optional second argument that decides if the translation should happen or not:

const showMoreText = useTranslate('react-faqs.showMore', { disabled: true });

The above will return undefined as long as disabled is true. This is to allow you to override the translation with something else like a prop provided by the consumers of your package.