██████╗ ██████╗ ███████╗ █████╗ ██████╗████████╗ ██╗ ███╗ ██╗██╗ ██╗███╗ ██╗███████╗
██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝ ██║ ████╗ ██║██║ ██║████╗ ██║██╔════╝
██████╔╝██████╔╝█████╗ ███████║██║ ██║ █████╗ ██║18 ██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗
██╔═══╝ ██╔══██╗██╔══╝ ██╔══██║██║ ██║ ╚════╝ ██║ ██║╚██╗██║██║ ██║██║╚██╗██║██╔══╝
██║ ██║ ██║███████╗██║ ██║╚██████╗ ██║ ██║ ██║ ╚████║███████╗██║██║ ╚████║███████╗
╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝
keep your translations in line - with preact!
preact-i18nline brings I18nline
to Preact via the html translate
attribute.
I18n doesn't get any easier than this.
preact-i18nline 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; I18nline will do it for you.
This project is a port of react-i18nliner by Jon Jensen to Preact, a 3kB alternative to React.
preact-i18nline 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.
preact-i18nline enhances I18nline, 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™.
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"
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>
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>
npm install -S i18nline preact-i18nline
In your package.json
, create an object named "i18n"
and
specify your project's global configuration settings there. Or,
if you prefer, you can create a .i18nrc
options file in the root
of your project.
The typical configuration you'd want for a preact project would look something like this:
{
"plugins": [
"preact-i18nline"
],
"outputFile": "my/translations/en.json"
}
Important here is that you register preact-i18nline
as a plugin.
This will ensure that when you export strings for translation, all of your
new translate="yes"
stuff will get picked up.
Refer to the i18nline config docs for details on the configuration options.
Preprocess all your js and jsx files with preact-i18nline
How you hook up the preprocessor will depend on how you bundle your assets:
Add this loader to your config, e.g.
{
module: {
loaders: [
{ test: /\.js$/, loader: "preact-i18nline/webpack-loader" }
...
],
},
...
}
TODO: example not ported over yet Check out this example app to see how everything is wired together.
Use this transform, e.g.
$ browserify -t preact-i18nline/browserify-transform app.js > bundle.js
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.
Both i18nline and preact-i18nline add some extensions to i18n.js to
help with the runtime processing of the translations. You can require
I18n via preact-i18nline to get a I18n
object that has all extensions
applied already:
var I18n = require("preact-i18nline/i18n");
Alternatively, you can apply the extensions manually:
var I18n = // get it from somewhere, script tag, whatever
require('i18nline/lib/extensions/i18n_js')(I18n);
require('preact-i18nline/dist/extensions/i18n_js')(I18n);
Since preact-i18nline is just an i18nline plugin, you can use the i18nline
CLI to extract translations from your codebase; it will pick up normal
I18n.t
usage, as well as your new translate="yes"
components. The
easiest way to do this is to add a "scripts"
section to your package.json
and call i18nline from there:
package.json
{
"i18n": {
"plugins": {
"preact-i18nline"
}
},
"scripts": {
"translations": "i18nline export"
}
}
Then you can simply invoke it via NPM as usual:
$ npm run translations
Once you've gotten all your translations back from the translators,
simply stick them 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?"
},
...
}
In addition to the i18nline configuration, preact-i18nline adds some options specific to JSX processing:
An array of strings, or a string with (a comma separated list of)
tag names that should be translated automatically. Defaults to []
.
package.json
{
"i18n": {
"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:
class T extends Component {
render() {
return <span {...this.props} />;
}
}
An array of strings, or a string with (a comma separated list of)
tag names that should not be translated automatically.
Defaults to []
.
Similarly to autoTranslateTags
, if you have certain tags you
don't want to translate automatically, (e.g. <code>
),
you can specify those in a similar manner.
package.json
{
"i18n": {
"neverTranslateTags": ["code"],
}
}
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.
i18nline 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>
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: A subsequent release of preact-i18nline may add a check for
this situation that will cause an i18nline:check
failure.
You've been warned :)
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.
Copyright (c) 2016 by Stijn de Witt and Jon Jensen, released under the MIT license