react-native-l20n
Experimental adaptation of Mozilla's L20n localization
framework for use within React Native.
npm install --save react-native-l20n
import React, {Component} from 'react';
import {View, Text} from 'react-native';
import L20n, {ftl} from 'react-native-l20n';
const l20n = L20n.create({
en: ftl`
product = react-native-l20n
welcome = Welcome, {$name}, to {product}!
description =
| {product} makes it possible to harness the forward-thinking
| design of Mozilla's L20n in an idiomatic fashion.
stars = This repository has {$count ->
[0] no stars
[1] one star
*[other] {$count} stars
} on GitHub.
`,
es: ftl`
welcome = Bienvenidos, {$name}, a {product}!
`,
});
class Example extends Component {
render() {
return (
<View>
<Text style={{fontWeight: 'bold'}}>
{l20n.welcome({name: 'James'})}
</Text>
<Text>
{l20n.description()}
</Text>
<Text>
{l20n.stars({count: 1000})}
</Text>
</View>
);
}
}
Why L20n?
Mozilla has decades of experience shipping localized products. The design of
L20n reflects this accumulation of experience, and manages to deliver a format
as powerful as ICU MessageFormat
,
but as simple as gettext
.
If these comparisons mean nothing to you, perhaps it will suffice to say that
L20n makes it easy to isolate the strings in your application, perform basic
variable substitutions, and handle language nuances like pluralization, gender,
and declension.
You don't take my word for it, though. Here are three excellent resources for
getting started with L20n:
-
Learn the syntax with this quick step-by-step guide.
-
Tinker with framework with this browser-based IDE.
-
Read about the decisions that underpin the powerful, asymmetric design of the
framework in this blog post.
What's different for React Native?
The main drawback of L20n, from my perspective, is that it takes a heavy
dependency upon the DOM as its formal interface. Just as StyleSheet
brought
the best of CSS for use in React Native, this module decouples L20n from the
DOM and makes it available to your React Native app through a familiar,
idiomatic interface.
The first similarity to StyleSheet
is that L20n translations are meant to be
declared within the component they're used, alongside styles. For example:
const styles = StyleSheet.create({...});
const l20n = L20n.create({...});
As a consequence, nothing in the React Native implementation of L20n is
asynchronous, which means that the interface for accessing translations is
a simple, synchronous function that returns a string, like such:
render() {
return (
<Text style={styles.text}>
{l20n.helloWorld()}
</Text>
);
}
As seen in this example, the React Native implementation of L20n does not
utilize data
attributes (or any annotations in the virtual DOM or JSX) to
look up translations; it's just simple function calls, which means it can be
used with any component, builtin or third party.
My advice is to generally ignore the API documentation on Mozilla's L20n
website with the exception of their guide to FTL, the
L20n translation format.
Finally, it's worth noting that L20n depends upon the ECMAScript
Internationalization API (found in browsers under window.Intl
), which is
provided via polyfill. This module also removes bidirectional isolation
characters which are inserted by L20n, but not supported by either React Native
platform.
API
L20n.create(translations)
translations
is an object that maps locales to translations.
Locales are specified as two-letter ISO 639-1 codes.
Translations are specified in L20n's FTL format.
L20n.create()
returns an object that maps each translation key to a function.
The function can be invoked with a single object argument to provide variables
for substitution into the translated string.
When the function is invoked, a translation for the current locale is used; if
none is available, the default locales are attempted in order. Failing that,
the translation key is returned.
Example:
import L20n from 'react-native-l20n';
const l20n = L20n.create({
en: `key = The value is: {$variable}`
es: `key = El valor es: {$variable}`
});
console.log(l20n.key({variable: 'foo'));
L20.currentLocale
Get or set the current locale.
The locale is specified as a two-letter ISO 639-1 code.
The value is initialized to the locale of the device. If you wish to
programmatically change it, do so before rendering your first component.
L20.defaultLocales
Get or set the default locales.
Locales are specified as two-letter ISO 639-1 codes.
These locales are attempted when a translation isn't available for the current
locale.
Defaults to ['en']
. If you wish to programmatically change it, do so before
rendering your first component.
ftl
ES6 templated string tag for FTL, the L20n
translation format.
The ftl
tag is not required, but enables you to indent your translations,
which is not normally legal. It also removes newlines from piped, multi-line
translations, which emulates the whitespace-collapsing nature of HTML.
Example:
import {ftl} from 'react-native-l20n';
const translations = {
en: ftl`
firstKey = First
secondKey =
| This string spans
| multiple lines.
`,
};
console.log(translations.en);
Future work
One of the creators of L20n, @stasm, has been
exploring proposals for deeper integration of L20n into browser-based React.
A very thorough series of proposals is under discussion on this thread.
The approach of this module is to hew as closely as possible to plain-old
portable JavaScript, with some conveniences added to conform with React Native
idioms. Perhaps L20n will formalize an API that requires neither DOM access or
Node.js builtin modules, which would eliminate the need to vendor a modified
version of the L20n framework. (This would likely involve isolating the FTL
parser and runtime from the rest of L20n.)
Beyond that, there are a number of enhancements that could be added to this
module to mature it into a scalable localization solution:
-
Handle the TODOs
listed at the top of the source.
-
Generalize this module for use in browser-based React or apart from any
framework. This would essentially substitute for the L20n Node.js interface,
which is a bit lacking.
-
Build tooling to collect strings from components, and support
loading/bundling of translations into separate files, apart from the
component definitions.
-
Build a runtime inspector to identify translation keys.
More than anything, I'd appreciate your feedback on what it will take for this
module to become a production-grade solution for your project. Please open an
issue to discuss.