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-i18nliner

v0.2.3

Published

i18n made simple

Downloads

2,559

Readme

react-i18nliner

react-i18nliner brings I18nliner to React via the html translate attribute. I18n doesn't get any easier than this.

TL;DR

react-i18nliner lets you do this:

<p translate="yes">
  Hey {this.props.user.name}!
  Although I am <Link to="route">linking to something</Link> and
  have some <strong>bold text</strong>, the translators will see
  <strong><em>absolutely no markup</em></strong> and will only have a
  single string to translate :o
</p>

Write your components as you normally would, and just put a translate="yes" attribute on any element/component that needs to be localized. Seriously.

And because the default translation is inline, it will be used as a fallback if a translation is missing or hasn't happened yet.

Best of all, you don't need to maintain separate translation files anymore; I18nliner will do it for you.

How does it work?

react-i18nliner preprocesses your JSX, transforming it into something truly localizable. It infers placeholders for expressions and wrappers for elements/components, and separates the localizable string. At runtime, it localizes the string, interpolating the wrappers and placeholders into the correct locations.

Localizable strings are detected both from the text nodes, as well as from translatable attributes within the translate="yes" element.

react-i18nliner enhances I18nliner, so that it can extract any of these translate="yes" strings from your codebase (in addition to regular I18n.t calls). Once you get everything translated, just stick it on I18n.translations and everything will Just Work™.

Examples

Placeholders

A placeholder will be created for the input:

<label translate="yes">
  Create <input /> new accounts
</label>

As well as for arbitrary JSX expressions:

<div translate="yes">
  Welcome back, {user.name}.
</div>

By default, placeholder keys will be inferred from the content, so a translator would see "Create %{input} keys" and "Welcome back, %{user_name}". For complicated expressions, these placeholder keys can get a bit long/gnarly. Having to retranslate strings that "changed" just because you refactored some code is terrible, so you can use keys to be a bit more explicit:

<label translate="yes">
  Create <input key="numAccounts" onChange={this.addAccounts} /> new
  accounts
</label>

In this case the extracted string would just be "Create %{num_accounts} new accounts"

Wrappers

Translators won't see any components or markup; they will be replaced with a simple wrapper notation. In this example, the extracted string would be "That is *not* the right answer":

<div translate="yes">
  That is <b>not</b> the right answer
</div>

Attributes

In addition to the "Edit your settings *here*" string, the "Your Account" will also be preprocessed, since it is a valid translatable attribute within a translated element.

<div translate="yes">
  Edit your settings <a href="/foo" title="Your Account">here</a>
</div>

Installation

1. get i18n-js and i18nliner

Get i18n-js and i18nliner installed per these instructions.

2. add react-i18nliner

npm install react-i18nliner --save

And make sure your .i18nrc file has:

{
  "plugins": [
    "react-i18nliner"
  ]
}

This will ensure that when you export strings for translation, all of your new translate="yes" stuff will get picked up.

3. preprocess all your js files with react-i18nliner

How you hook up the preprocessor will depend on how you bundle your assets:

webpack

Add this loader to your config, e.g.

{
  module: {
    loaders: [
      { test: /\.js$/, loader: "react-i18nliner/webpack-loader" }
      ...
    ],
  },
  ...
}

Check out this example app to see how everything is wired together.

browserify

Use this transform, e.g.

$ browserify -t react-i18nliner/browserify-transform app.js > bundle.js

something else?

It's not too hard to roll your own; as you can see in the loader and transform above, the heavy lifting is done by preprocess. So whether you use ember-cli, sprockets, grunt concat, etc., it's relatively painless to add a little glue code that runs preprocess on each source file.

4. add the react-i18nliner runtime extensions to i18n-js

Assuming you have a cjs-style app, do something like this:

var I18n = require("./path/to/cjs'd/i18n");
require("i18nliner/dist/lib/extensions/i18n_js")(I18n);
require("react-i18nliner/dist/extensions/i18n_js")(I18n);

If you're using AMD/<script>/something else, see the i18nliner-js README for hints; these extensions can be set up exactly the same way as i18nliner-js's.

Working with translations

Since react-i18nliner is just an i18nliner plugin, you can use the i18nliner bin / grunt task to extract translations from your codebase; it will pick up normal I18n.t usage, as well as your new translate="yes" components.

Once you've gotten all your translations back from the translators, simply stick them the giant blob 'o json on I18n.translations; it expects the translations to be of the format:

I18n.translations = {
  "en": {
    "some_key": "Hello World",
    "another_key": "What's up?"
  }
  "es": {
    "some_key": "Hola mundo",
    "another_key": "¿Qué tal?"
  },
  ...
}

Configuration / Advanced Settings

If you have certain tags that you always want to translate (e.g. <h1>), you can specify them in your .i18nrc via autoTranslateTags, e.g.

{
  "autoTranslateTags": ["h1", "h2", "h3", "h4", "h5", "h6", "p"]
}

These tags will have an implicit translate="yes", keeping your markup simple.

Note that this works for both regular HTML elements, as well as for your own custom components. For example, if you decided you wanted to use a <T> component everywhere instead of translate="yes", you could add it to autoTranslateTags, and its runtime implementation could be as simple as:

const T = React.createClass({
  render() {
    return <span {...this.props} />;
  }
})

Similarly, if you have certain tags you don't want to auto-translate (e.g. <code>), you can specify those in a similar manner:

{
  "neverTranslateTags": ["code"],
}

Then if those are ever nested in a larger translatable element, they will be assumed to be untranslatable, and a placeholder will be created for them.

Gotchas

What about pluralization? Or gender?

i18nliner does support basic pluralization (via i18n-js), but you need to use pure js for that, e.g.

<div>
  {I18n.t({one: "You have 1 item", other: "You have %{count} items"}, {count: theCount})}
</div>

i18n-js doesn't support gender-based localizations, but I do plan on making i18nliner work with other backends soon (e.g. i18next, FormatJS). Watch this space, or better yet, create a pull request ;)

Every JSX expression makes a placeholder

This kind of gets to a general rule of i18n: don't concatenate strings. For example,

return (<b translate="yes">
         You are {this.props.isAuthorized ? "authorized" : "NOT authorized"}
        </b>);

The extracted string will be "You are %{opaque_placeholder}" and the translators won't get a chance to translate the two inner strings (much less without context). So don't do that; whenever you have logically different sentences/phrases, internationalize them separately, e.g.

return (this.props.isAuthorized ?
         <b translate="yes">You are authorized</b> :
         <b translate="yes">You are NOT authorized</b>);

NOTE: in a subsequent release of react-i18nliner, the former example will cause an i18nliner:check failure. You've been warned :)

Cloning this project under Windows

This project's eslint settings force a check on the use of linefeed characters that will fail when the project is cloned with the git core.autocrlf setting set to true, which is the default on Windows. So make sure to change that setting beforehand. The easiest way to do this is probably to git init a new repo for this project and change the setting, and only then add this repo as a remote and pull from it.

Related Projects

License

Copyright (c) 2015 Jon Jensen, released under the MIT license