Giter Site home page Giter Site logo

lioness's Introduction



Lioness
npm version

Lioness is a React library for efficiently implementing Gettext localization in your app with little effort.

It utilises node-gettext as translations tool, but this ought to be modularized in the future.

<T
  message="You have one thingy, {{ itemLink:check it out }}"
  messagePlural="You have {{ count }} thingies, {{ listLink:check them out }}"
  count={items.length}
  itemLink={<a href={`/thingies/${items[0].id}`} />}
  listLink={<a href="/thingies" />}
/>
// items.length === 1 => Du har en grej, <a href="/thingies/281">kolla in den här<a/>.
// items.length === 7 => Du har 7 grejer, <a href="/thingies">kolla in dem här<a/>.

Table of contents

Features

  • Context and plural
  • String interpolation using a {{ variable }} style syntax
  • Component interpolation with translatable child content using a {{ link:Link text here }} style syntax
  • Locale switching on the fly

Installation

npm install --save lioness

# ...or the shorter...
npm i -S lioness

Usage

This is an example app showing how to translate some text:

import React from 'react'
import ReactDOM from 'react-dom'
import { LionessProvider, T } from 'lioness'

// messages.json is a JSON file with all translations concatenated into one.
// The format must conform to what node-gettext expects.
//
// See https://github.com/alexanderwallin/node-gettext#Gettext+addTranslations
import messages from './translations/messages.json'

function App({ name, numPotatoes }) {
  return (
    <LionessProvider
      messages={messages}
      locale="sv-SE"
      debug={/* true | false | null */}
    >
      <div className="App">
        <h1><T>Potato inventory</T></h1>
        {/* => <h1><span>Potatisinventarie</span></h1> */}

        <T
          message="Dear {{ name }}, there is one potato left"
          messagePlural="Dear {{ name }}, there are {{ count }} potatoes left"
          count={numPotatoes}
          name={name}
        />
        {/* => <span>Kära Ragnhild, det finns 2 potatisar kvar</span> */}

        <T
          message="By more potatoes {{ link:here }}!"
          link={<a href="http://potatoes.com/buy" />}
        />
        {/* => <span>Köp mer potatis <a href="http://potatoes.com/buy">här</a>!</span> */}
      </div>
    </LionessProvider>
  )
}

ReactDOM.render(
  <App name="Ragnhild" numPotatoes={Math.round(Math.random() * 3))} />,
  document.querySelector('.app-root')
)

Using <T />

<T /> exposes a set of props that make it easy to translate and interpolate your content. Being a React component, it works perfect for when you are composing your UI, like with the example above.

Using withTranslators(Component)

Sometimes, you will need to just translate and interpolate pure strings, without rendering components. To do this you can hook up your components with translator functions using the withTranslators(Component) composer function.

withTranslators(Component) will provide any component you feed it with a set of translator functions as props. Those props are: t, tn, tp, tnp, tc, tcn, tcp and tcnp.

import { withTranslators } from 'lioness'

function PotatoNotification({ notificationCode, t }) {
  let message = ''

  if (notificationCode === 'POTATOES_RECEIVED') {
    message = t(`You have received potatoes`)
  } else if (notificationCode === 'POTATOES_STOLEN') {
    message = t(`Someone stole all your potatoes :(`)
  }

  return <span>{message}</span>
}

export default withTranslators(PotatoNotification)
// .babelrc
{
  ...
  "plugins": [
    ["react-gettext-parser", {
      "output": "gettext.pot",
      "funcArgumentsMap": {
        "tc": ["msgid", null],
        "tcn": ["msgid", "msgid_plural", null, null],
        "tcp": ["msgctxt", "msgid", null],
        "tcnp": ["msgctxt", "msgid", "msgid_plural", null, null],

        "t": ["msgid"],
        "tn": ["msgid", "msgid_plural", null],
        "tp": ["msgctxt", "msgid"],
        "tnp": ["msgctxt", "msgid", "msgid_plural", null]
      },
      "componentPropsMap": {
        "T": {
          "message": "msgid",
          "messagePlural": "msgid_plural",
          "context": "msgctxt",
          "comment": "comment"
        }
      }
    }]
  ]
  ...
}

Locale switching

Lioness makes it possible to change locale and have all the application's translations instantly update to those of the new locale. <LionessProvider> will trigger a re-render of all <T> components and components wrapped in withTranslators() whenever its locale or messages props change.

Note: For performance reasons, and in favour of immutability, this check is done using shallow equality, which means you need to pass an entirely new object reference as messages for it to trigger the re-render. If this is an issue for you, simply make sure you create a new object when you get new messages, for instace by using something like messages = Object.assign({}, messages).

API

The following table indicates how gettext strings map to parameters in withTranslations and props for <T />

Gettext withTranslations <T />
msgctxt context context
msgid message | one message
msgid_plural other messagePlural

withTranslations(Component)

Provides Component with the lioness context variables as props. These are locale, t, tn, tp, tnp, tc, tcn, tcp and tcnp.

As a little helper, here's what the letters stand for:

Letter Meaning Parameters
t translate a message message
c ...with injected React components -
n ...with pluralisation one, other, count
p ...in a certain gettext context context
  • locale

    The currently set locale passed to <LionessProvider />.

  • t(message, scope = {})

    Translates and interpolates message.

  • tn(one, other, count, scope = {})

    Translates and interpolates a pluralised message.

  • tp(context, message, scope = {})

    Translates and interpolates a message in a given context.

  • tnp(context, one, other, count, scope = {})

    Translates and interpolates a pluralised message in a given context.

  • tc(message, scope = {})

    Translates and interpolates a message.

  • tcn(one, other, count, scope = {})

    Translates and interpolates a pluralised message.

  • tcp(context, message, scope = {})

    Translates and interpolates a message in a given context.

  • tcnp(context, one, other, count, scope = {})

    Translates and interpolates a plural message in a given context.

<LionessProvider />

A higher-order component that provides the translation functions and state to <T /> through context.

Props:

  • messages – An object containing translations for all languages. It should have the format created by gettext-parser
  • locale – The currently selected locale (which should correspond to a key in messages)
  • gettextInstance - A custom node-gettext instance. If you provide the messages and/or local props they will be passed on to this instance.
  • transformInput – A function (input: String) => String that you can use to transform a string before <T /> sends it to the translation function. One use case is normalising strings when something like prettier puts child content in <T /> on new lines, with lots of indentation. The default is a function that simply returns the input as is.

Contributing

All PRs that passes the tests are very much appreciated! 🎂

See also

  • node-gettext - A JavaScript implementation of Gettext.
  • gettext-parser – A parser between JSON and .po/.mo files. The JSON has the format required by this library.
  • react-gettext-parser – A utility that extracts translatable content from JavaScript code.
  • narp – A workflow utility for extracting, uploading, downloading and integrating translations.

lioness's People

Contributors

alexanderwallin avatar alexeychikk avatar dependabot[bot] avatar ilyalesik avatar iszak avatar vinks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

lioness's Issues

A parser between JSON and .po/.mo files is not obvious.

Hello 😄

This issue is not an issue in fact. I have some thoughts about compiling "po" files to "JSON".
Yesterday I came across with setting up full sequence for i18n my app. And everything was OK but not a step of PO to JSON. It took me several hours to set it up.
There are a few misconceptions for Gulp users.
First, gulp plugin https://www.npmjs.com/package/gulp-po2json does wrong JSON format.
Second, gettext-parser hasn't gulp plugin 😸

OK, it figured it out or maybe I googled not in a right way 😄
I started to read about gulp plugins and how to build it. Buffers, streams and bla bla bla. Two hours and I am ready 🚀
But it appears, that gettext-parser builds the wrong JSON too! It was very funny. This JSON hadn't a root node with a locale which was required.
Finally, I got what I wanted.
Something like this:

gulp.task('build:po2json', function () {
	return gulp.src(['translations/**/*.po'])
		.pipe(through2.obj(function (chunk, enc, callback) {
			if ( chunk.isBuffer ) {
				let po = gettextParser.po.parse(chunk.contents.toString());
				let poObj = {
					[po.headers.language]: {...po}
				};

				chunk.contents = new Buffer(JSON.stringify(poObj));
				chunk.path = replaceExt(chunk.path, '.json');
			}

			callback(null, chunk);
		}))
		.pipe(gulp.dest('translations/'));
});

And now my question: 😄
Is this theme regard common instruction and should it be presented here?
If it took me (as a newbie) for a few hours, it could be helpful for somebody too.
What do you think?

Translate outside of context

Hi there,

I'm looking for the possibility to use translations outside of the LionessProvider context. Is that feasible?

Thanks,
Stefan

How to use lioness outside of a component

Question is simple. Let's say a component have a default prop with some text as value. How to use lioness on them?
Thank you, and sorry for posting this on the issues page.

automatic extration using component interpolation

Not really an issue but more of a question on extracting things.

I've got the workflow of extracting strings working reasonably well except for when things are getting interpolated inside a translation.

{{ link:Link text here }}

I'm using the same configuration for react-gettext-parser:

funcArgumentsMap: {
    tc: ["msgid", null],
    tcn: ["msgid", "msgid_plural", null, null],
    tcp: ["msgctxt", "msgid", null],
    tcnp: ["msgctxt", "msgid", "msgid_plural", null, null],
    t: ["msgid"],
    tn: ["msgid", "msgid_plural", null],
    tp: ["msgctxt", "msgid"],
    tnp: ["msgctxt", "msgid", "msgid_plural", null]
  },
  componentPropsMap: {
    T: {
      message: "msgid",
      messagePlural: "msgid_plural",
      context: "msgctxt",
      comment: "comment"
    }
  }

Was just wondering if there's a magic sauce to extract those interpolated strings into the extraction process.

Html interpolation

I have this string

Go to <a href="{{ link }}>{{ link }}</a> and insert the code {{ code }}

How should I write the and the translation string?

If I write it like this

<T
      message="Go to {{ link }} and insert the code {{ code }}"
      link={<a href={url}>{url}</a>}
      code={code}
 />

but it does not work. The problem, of course, is the anchor

How to produce the json

Hello, first af all thanks for this component!
I have a question. I have a folder with several po files. How can I produce a json file to feed the provider? The documentation is not clear (also on node-gettext project). I can create a gettext instance with all the messages, but then the provider needs a json and I cannot get it from the gettText instance.
Then I saw that internally the provider transforms messages into a gettext instace again, so why can't it accept an instance itself?

Set default locale and do not perform translation.

Is it possible to set a current locale as default and do not perform translation if the original app language is the same as the current locale?

For example, all app strings are written in en_US and the current locale is en_US. Do I need to create a separate .po file for en_US locale and then transform messages.json and load into LionessProvider in this case?

What's the relation between Lioness and react-targem?

It seems like both projects are very similar and even share some contributors. I would like to understand the history behind this proyect because it fits my needs and I like the approach but I don't want to invest using a library that might be discontinued in the short term.

Thanks!

Finalize demo page

  • Demonstrate all features
  • Fix broken elements
  • Look over npm scripts and build environment

How to translate strings with interpolation without spans

Given we have the higher order function withTranslators which exposes the translation functions e.g. tcpn if I want to translate a string that may not render as HTML (e.g. document.title) tcpn will interpolate with <span />'s which is invalid in the given example.

I suggest a few proposals;

  • By default the tc, tcn, tcp, tcpn don't bind the interpolator but instead it's done in the <T /> component as this is likely the use case. If there comes a case where a user wants to use those functions but also interpolate with the span they can import interpolateComponents (perhaps we could add this as a default export to facilitate this more) and pass it as the first parameter to those functions since they will be unbound.

  • Alternative is to expose the gettext instance via the context to allow users to do their own interpolation, I am not in favour of this as it adds yet another property to the context and provides a leaky implementation since in all circumstances currently gettext instance is hidden.

In either case, it's probably worth while to add a interpolate function which provides a span-less implementation and export it because both of these solutions will require it, otherwise the end user will have to re-implement it.

Thoughts?

`withTranslators` behavior without a `LionessProvider`

It seems like, when a component built from withTranslators is not a child of a LionessProvider, it is passed null as props for t, tc, tc...

I would find it helpfull to pass a "friendlier" default (eg the identity function), for example in tests or storybooks. Is there a fundamental reason not to do that ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.