npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-t7e

v0.4.2

Published

[![Build Status](https://travis-ci.com/woutervanvliet/react-t7e.svg?branch=master)](https://travis-ci.com/woutervanvliet/react-t7e)

Downloads

224

Readme

React T7E

Build Status

The simplest way to do translations in React that I know of.

Motivation

Translations should be simple, use an implementation that's intuitive and almost as easy to use as just inserting strings. Inspiration for the API comes from the WordPress/PHP implementation of gettext. Code that's familiar to many.

Example

Let's start with a fully functional example. Different ways to use it, including tips on how to create your own translation engine, or .po/.mo files will follow afterwards.

import { 
  useTranslate,
  TranslationProvider,
  MoEngine,
} from 'react-t7e';


function MyComponent(props) {
    const { _, _n } = useTranslate()
    return (
        <div>
            <h1>{_('Hello World')}</h1>
            <h2>{_(
              'You are using version {version} if this application', 
              undefined, 
              { version: '100' },
            )}</h2>
            <p>{_n('The has one fish.', 'The world has %d fish in the sea.', props.fishCount)}</p>
        </div>
    )
}

fetch('/language/' + language + '.mo')
    .then((response) => response.arrayBuffer())
    .then((moData) => {
        const engine = new MoEngine(moData);
        ReactDOM.render(
            <TranslationProvider engine={engine}>
                <MyComponent fishCount={12} />
            </TranslationProvider>,
            document.getElementById('app'),
        )
    })

API

import { _, _n, TranslationContext, TranslationProvider, MoEngine, T, useTranslate } from 'react-t7e';

Used types

The following types are used internally, and referenced here in the documentation:

type Replacements = {
  [key: string]: string | number,
}

interface TranslateEngine {
  translate(source: string, sourcePlural?: string, count?: number, context?: string): string;
}

_(source: string, context?: string, replacements?: Replacements )

Translates a string from your source language to the target language, returns ReactNode.

_n(source: string, sourcePlural: string, count: number, context?: string, replacements?: Replacements )

Translates a string with plural forms from your source language to the target language, returns ReactNode.

<TranslationProvider engine={engine: TranslationEngine} />

Sets the translation engine for this part of the React render tree (all child components from here). One engine is provided in this module (MoEngine).

<TranslationContext>{({ _n, _ }) => React.Node}</TranslationContext>

Gives you direct access to translation functions returning strings rather than ReactNodes, use this if you need to have a property (such as title or aria-label) translated or for any other reason need a translation as string rather than ReactNode. Signatures are the same as main _ and _n in the main export.

MoEngine(data: ByteArray, domain: string)

Engine that uses the contents of a .mo file for translations.

Guide

You can get started with almost no setup, if all you need is a simple way to handle plurality, or if you intend to make your app translatable later.

import { _, _n } from 'react-t7e';

function MyComponent(props) {
    return (
        <div>
            <h1>{_('Hello World')}</h1>
            <p>{_n('The world has one fish.', 'The world has %d fish in the sea.', props.fishCount)}</p>
        </div>
    )
}

This just works. But you probably want to be able to use it for actual translations. You need a TranslationProvider and a TranslationEngine for that. The engine should be an instance with, at least, a single translate method. Such as:

class ShoutEngine {
    translate(
        source: string,
        sourcePlural?: string,
        count?: number,
        context?: string
    ) {
        let result = count === 1 || !sourcePlural ? source : sourcePlural;

        return result
            .toUpperCase()
            .replace(
                /%([DF])/g,
                (match, letter: string) => `%${letter.toLowerCase()}`
            );
    }
}

Instead of changing all characters to their uppercase equivalent, you could for example lookup your translation strings in an object that you have loaded somehow.

And then you'll use your newly created translation engine like so:

import { TranslationProvider } from 'react-t7e';

const engine = new ShoutEngine();

function App() {
    return (
        <TranslationProvider engine={engine}>
            <MyComponent />
        </TranslationProvider>
    )
}

But to make it really useful, I prefer to hook up Jed and jed-gettext-parser.

import ReactDOM from 'react-dom';
import { MoEngine, TranslationProvider } from 'react-t7e';

const language = 'nl_NL';

fetch('/language/' + language + '.mo')
    .then((response) => response.arrayBuffer())
    .then((moData) => {
        const engine = new MoEngine(moData);
        ReactDOM.render(
            <TranslationProvider engine={engine}>
                <MyComponent />
            </TranslationProvider>,
            document.getElementById('app'),
        )
    })

You can choose to the data from your .mo files any way you want, send another engine down the TranslationProvider to change the language, wrap multiple TranslationProvider's if you want some part of your app to read messages from another language or write your own engine to read translations from somewhere else.

Placeholders and formatting

Translation without some form of placeholders is practically useless. This module supports very basic placeholders, and count formatting (in the _n function).

Simply pass an object with key-value pairs as 3rd argument to _ or 5th argument to _n, and use the keys between curly braces in your source string to get it replaced. Or use either %d or %f to get your count argument as an integer or floating point (two positions) in your result string. This intentionally looks like printf, but only these two formatting instructions are supported. More may be added in a future version.

const withPlaceholder = _('Hello {name}, how are you?', 'Standard greeting, in menu', {
    name: this.props.name,
});

const withNumberFormatting = _n(
    'Hello {name}, you have one message', 
    'Hello {name}, you have %d messages', 
    props.messageCount, 
    'Standard greeting, in menu', {
        name: props.name,
    },
);

Alternative syntax

The exported _ and _n functions simply return a React.Node with the props filled in based on your positional arguments. This makes it easy to scan your code for translatable strings, but isn't all that React-like. If you prefer, you can directly import the T component and use that.

Pull requests to document how to generate .pot files from this are very much welcome.

How to create .mo files

.mo files are compiled .po files. An editor such as PoEdit can be used to create both. It can also be used to generate a template (.pot) file by scanning your code, but that can be tricky and it needs to understand your exact setup of JavaScript to really work. Intead, I recommend babel-gettext-extractor to generate a full POT file every time you build your code. Configuration of the plugin may look like this:

[
    "babel-gettext-extractor",
    {
        baseDirectory: process.cwd(),
        headers: {
            "content-type": "text/plain; charset=UTF-8",
            "plural-forms": "nplurals=2; plural=(n!=1);"
        },
        fileName: 'translation-template.pot',
        functionNames: {
            _: ['msgid', 'msgctxt'],
            _n: ['msgid', 'msgid_plural', 'count', 'msgctxt'],
        },
    }
]

Tips for loading and selecting translations

As shown in the example, you can load .mo files with the Fetch API. But that's not the only way. The only thing that matters, is that you get the contents of your .mo file as ArrayBuffer.

If you use webpack, consider arraybuffer-loader and you can use it like so:

import ReactDOM from 'react-dom';
import { MoEngine, TranslationProvider } from 'react-t7e';
import moData from './nl_NL.mo';
const engine = new MoEngine(moData);

ReactDOM.render(
    <TranslationProvider engine={engine}>
        <MyComponent />
    </TranslationProvider>,
    document.getElementById('app'),
)

Hook up something like react-loadable and things get even more fun #codesplitting.

import { MoEngine, TranslationProvider } from 'react-t7e';

const render = (moData, props) => (
    <TranslationProvider engine={new MoEngine(moData.default)}>
        {props.children}
    </TranslationProvider>
);

const Loading = () => null;
const i18n = {
    nl: Loadable({
        loader: () => import("./nl_NL.mo"),
        render: render,
        loading: Loading
    }),
    da: Loadable({
        loader: () => import("./da_DK.mo"),
        render: render,
        loading: Loading
    })
};

const languageCode = 'nl';
const Language = i18n[languageCode];

ReactDOM.render(
    <Language>
        <MyComponent />
    </Language>,
    document.getElementById('app'),
)

Server-side rendering

Works just the same. This module uses the stable React.createContext API to send the translation engine around.

Todo

  • Write tests
  • Support for translation domains
  • Improve documentation