This repository has been archived by the owner on Feb 22, 2019. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First version, extracted from wp-calypso i18n mixin
- Loading branch information
0 parents
commit 357063e
Showing
22 changed files
with
2,228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# OS X thumbnails | ||
.DS_Store | ||
|
||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
|
||
# Runtime data | ||
pids | ||
*.pid | ||
*.seed | ||
|
||
# Directory for instrumented libs generated by jscoverage/JSCover | ||
lib-cov | ||
|
||
# Coverage directory used by tools like istanbul | ||
coverage | ||
|
||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) | ||
.grunt | ||
|
||
# node-waf configuration | ||
.lock-wscript | ||
|
||
# Compiled JS directory | ||
build | ||
|
||
# Dependency directory | ||
node_modules | ||
|
||
# Optional JetBrains IDE directory | ||
.idea | ||
|
||
# Optional npm cache directory | ||
.npm | ||
|
||
# Optional REPL history | ||
.node_repl_history | ||
|
||
# Optional unison directory | ||
.unison |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
I18n | ||
============ | ||
|
||
This lib enables translations, exposing three public methods: | ||
|
||
* [.translate()](#translate-method) | ||
* [.moment()](#moment-method) | ||
* [.numberFormat()](#numberformat-method) | ||
|
||
## Translate Method | ||
|
||
`translate()` accepts up to three arguments (`string`, `string`, `object`), depending on the translation needs. The second and/or third parameter can be omitted: | ||
|
||
``` | ||
/** | ||
* @param {string} original - the string to translate, will be used as single version if plural passed | ||
* @param {string} [plural] - the plural string to translate (if applicable) | ||
* @param {object} [options] - properties describing translation requirements for given text | ||
**/ | ||
``` | ||
|
||
### Options | ||
|
||
The following attributes can be set in the options object to alter the translation type. The attributes can be combined as needed for a particular case. | ||
|
||
* **options.context** [string] provides context for ambiguous terms. For example, "post" could be a noun or a verb. | ||
* **options.comment** [string] comment that will be shown to the translator for anything that may need to be explained about the translation. | ||
* **options.args** [string, array, or object] arguments you would pass into sprintf to be run against the text for string substitution. [See docs](http://www.diveintojavascript.com/projects/javascript-sprintf) | ||
* **options.components** [object] markup must be added as React components and not with string substitution. See [mixing strings and markup](#mixing-strings-and-markup). | ||
|
||
## Usage | ||
|
||
If you pass a single string into `translate`, it will trigger a simple translation without any context, pluralization, sprintf arguments, or comments. You would call it like this. | ||
|
||
```js | ||
var i18n = require( 'lib/i18n' ); | ||
var translation = i18n.translate( 'Some content to translate' ); | ||
``` | ||
|
||
### Strings Only | ||
|
||
Translation strings are extracted from our codebase through a process of [static analysis](http://en.wikipedia.org/wiki/Static_program_analysis) and imported into GlotPress where they are translated ([more on that process here](../../../../server/i18n)). So you must avoid passing a variable, ternary expression, function call, or other form of logic in place of a string value to the `translate` method. The _one_ exception is that you can split a long string into mulitple substrings concatenated with the `+` operator. | ||
|
||
```js | ||
/*----------------- Bad Examples -----------------*/ | ||
|
||
// don't pass a logical expression argument | ||
var translation = i18n.translate( condition ? 'foo' : 'bar' ); | ||
|
||
// don't pass a variable argument | ||
var translation = i18n.translate( foo ); | ||
|
||
// don't pass a function call argument | ||
var translation = i18n.translate( foo( 'bar' ) ); | ||
|
||
/*----------------- Good Examples -----------------*/ | ||
|
||
// do pass a string argument | ||
var example = i18n.translate( 'foo' ); | ||
|
||
// do concatenate long strings with the + operator | ||
var translation = i18n.translate( | ||
'I am the very model of a modern Major-General, ' + | ||
'I\'ve information vegetable, animal, and mineral, ' + | ||
'I know the kings of England, and I quote the fights historical ' + | ||
'from Marathon to Waterloo, in order categorical.' | ||
); | ||
``` | ||
|
||
### String Substitution | ||
|
||
The `translate()` method uses sprintf interpolation for string substitution ([see docs for syntax details](http://www.diveintojavascript.com/projects/javascript-sprintf)). The `option.args` value is used to inject variable content into the string. | ||
|
||
```js | ||
// named arguments (preferred approach) | ||
i18n.translate( 'My %(thing)s has %(number)d corners', { | ||
args: { | ||
thing: 'hat', | ||
number: 3 | ||
} | ||
} ); | ||
// 'My hat has 3 corners' | ||
|
||
// argument array | ||
i18n.translate( 'My %s has %d corners', { | ||
args: [ 'hat', 3 ] | ||
} ); | ||
// 'My hat has 3 corners' | ||
|
||
// single substitution | ||
i18n.translate( 'My %s has 3 corners', { | ||
args: 'hat' | ||
} ); | ||
// 'My hat has 3 corners' | ||
``` | ||
|
||
### Mixing Strings And Markup | ||
|
||
Because React tracks DOM nodes in the virtual DOM for rendering purposes, you cannot use string substitution with html markup as you might in a php scenario, because we don't render arbitrary html into the page, we are creating a virtual DOM in React. | ||
|
||
Instead we use the [interpolate-components module](../../../lib/interpolate-components) to inject components into the string using a component token as a placeholder in the string and a components object, similar to how string substitution works. The result of the `translate()` method can then be inserted as a child into another React component. Component tokens are strings (containing letters, numbers, or underscores only) wrapped inside double-curly braces and have an opening, closing, and self-closing syntax, similar to html. | ||
|
||
**NOTE: Always use a JSX element for passing components. Otherwise you will need to [wrap your React classes with `createFactory`](http://facebook.github.io/react/blog/2014/10/14/introducing-react-elements.html). Any wrapped content inside opening/closing component tokens will be inserted/replaced as the children of that component in the output. Component tokens must be unique:** | ||
|
||
```js | ||
// self-closing component syntax | ||
var example = i18n.translate( 'My hat has {{hatInput/}} corners', { | ||
components: { | ||
hatInput: <input name="hatInput" type="text" /> | ||
} | ||
} ); | ||
|
||
// component that wraps part of the string | ||
var example2 = i18n.translate( 'I feel {{em}}very{{/em}} strongly about this.', { | ||
components: { | ||
em: <em /> | ||
} | ||
} ); | ||
|
||
// components can nest | ||
var example3 = i18n.translate( '{{a}}{{icon/}}click {{em}}here{{/em}}{{/a}} to see examples.', { | ||
components: { | ||
a: <a href="#" />, | ||
em: <em />, | ||
icon: <Icon size="huge" /> | ||
} | ||
} ); | ||
|
||
|
||
``` | ||
|
||
### Pluralization | ||
|
||
You must specify both the singular and plural variants of a string when it contains plurals. If the string uses placeholders that will be replaced with actual values, then both the plural and singular strings should include those placeholders. It might seem redundant, but it is necessary for languages where a singular version may be used for counts other than 1. | ||
|
||
|
||
```js | ||
|
||
// An example where the translated string does not have | ||
// a number represented directly, but still depends on it | ||
var numHats = howManyHats(), // returns integer | ||
content = i18n.translate( | ||
'My hat has three corners.', | ||
'My hats have three corners.', | ||
{ | ||
count: numHats | ||
} | ||
); | ||
|
||
// An example where the translated string includes the actual number it depends on | ||
var numDays = daysUntilExpiration(), // returns integer | ||
content = i18n.translate( | ||
'Your subscription will expire in %(numberOfDays)d day.', | ||
'Your subscription will expire in %(numberOfDays)d days.', | ||
{ | ||
count: numDays, | ||
args: { | ||
numberOfDays: numDays | ||
} | ||
} | ||
); | ||
|
||
``` | ||
|
||
### More translate() Examples | ||
|
||
```js | ||
// simplest case... just a translation, no special options | ||
var content = i18n.translate( 'My hat has three corners.' ); | ||
|
||
// providing context | ||
var content = i18n.translate( 'post', { | ||
context: 'verb' | ||
} ); | ||
|
||
// add a comment to the translator | ||
var content = i18n.translate( 'g:i:s a', { | ||
comment: 'draft saved date format, see http://php.net/date' | ||
} ); | ||
|
||
// sprintf-style string substitution | ||
var city = getCity(), // returns string | ||
zip = getZip(), // returns string | ||
content = i18n.translate( 'Your city is %(city)s, your zip is %(zip)s.', { | ||
args: { | ||
city: city, | ||
zip: zip | ||
} | ||
} ); | ||
|
||
// Mixing strings and markup | ||
// NOTE: This will return a React component, not a string | ||
var component = i18n.translate( 'My hat has {{numHats/}} corners', { | ||
components: { | ||
numHats: <input name="someName" type="text" /> | ||
} | ||
} ); | ||
|
||
// Mixing strings with markup that has nested content | ||
var component = i18n.translate( 'My hat has {{link}}three{{/link}} corners', { | ||
components: { | ||
link: <a href="#three" /> | ||
} | ||
} ); | ||
``` | ||
|
||
See the [test cases](test/test.jsx) for more example usage. | ||
|
||
|
||
### Tests | ||
|
||
When using i18n as a standalone module, your tests need to call `i18n.initialize()` before any of the i18n methods can be used. `initialize()` is normally called during the Delphin boot sequence, which is not run for tests. Not calling this for tests may result in the tests failing with errors. | ||
|
||
```js | ||
var i18n = require( 'lib/mixins/i18n' ); | ||
i18n.initialize(); | ||
``` | ||
|
||
## Moment Method | ||
|
||
This module includes an instantiation of `moment.js` to allow for internationalization of dates and times. We generate a momentjs locale file as part of loading a locale and automatically update the moment instance to use the correct locale and translations. You can use `moment()` from within any component like this: | ||
|
||
```js | ||
var thisMagicMoment = i18n.moment( "2014-07-18T14:59:09-07:00" ).format( 'LLLL' ); | ||
``` | ||
|
||
And you can use it from outside of React like this. | ||
|
||
```js | ||
var i18n = require( 'lib/mixins/i18n' ); | ||
var thisMagicMoment = i18n.moment( "2014-07-18T14:59:09-07:00" ).format( 'LLLL' ); | ||
``` | ||
|
||
## numberFormat Method | ||
|
||
The numberFormat method is also available to format numbers using the loaded locale settings (i.e., locale-specific thousands and decimal separators). You pass in the number (integer or float) and (optionally) the number of decimal places you want (or an options object), and a string is returned with the proper formatting for the currently-loaded locale. You can also override the locale settings for a particular number if necessary by expanding the second argument into an object with the attributes you want to override. | ||
|
||
### Examples | ||
|
||
```js | ||
// These examples assume a 'de' (German) locale to demonstrate | ||
// locale-formatted numbers | ||
i18n.numberFormat( 2500.25 ); // '2.500' | ||
i18n.numberFormat( 2500.1, 2 ); // '2.500,10' | ||
i18n.numberFormat( 2500.33, { decimals: 3, thousandsSep: '*', decPoint: '@'} ); // '2*500@330' | ||
``` | ||
|
||
## Some Background | ||
|
||
I18n loads a language-specific locale json file from WordPress that contains the whitelisted translation strings for Delphin, uses that data to instantiate a [Jed](http://slexaxton.github.io/Jed/) instance, and exposes a single `translate` method with sugared syntax for interacting with Jed. | ||
|
||
Delphin locale files are generated from the WordPress codebase. Locale files are automatically updated as new translations are deployed based on a whitelist file in the WordPress.com codebase. _This means that any translation strings that are added to Delphin will also need to be added to the whitelist file in WordPress.com manually (using equivalent WordPress i18n functions)_. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/** | ||
* Internal dependencies | ||
*/ | ||
var i18n = require( './lib' ), | ||
mixin = require( './lib/mixin' ), | ||
localize = require( './lib/localize' ); | ||
|
||
module.exports = { | ||
moment: i18n.moment, | ||
numberFormat: i18n.numberFormat.bind( i18n ), | ||
translate: i18n.translate.bind( i18n ), | ||
configure: i18n.configure.bind( i18n ), | ||
initialize: i18n.initialize.bind( i18n ), | ||
setLocale: i18n.setLocale.bind( i18n ), | ||
getLocale: i18n.getLocale.bind( i18n ), | ||
getLocaleSlug: i18n.getLocaleSlug.bind( i18n ), | ||
reRenderTranslations: i18n.reRenderTranslations.bind( i18n ), | ||
registerComponentUpdateHook: i18n.registerComponentUpdateHook.bind( i18n ), | ||
registerTranslateHook: i18n.registerTranslateHook.bind( i18n ), | ||
mixin: mixin, | ||
localize: localize, | ||
state: i18n.state, | ||
stateObserver: i18n.stateObserver, | ||
I18N: i18n.I18N | ||
}; | ||
|
Oops, something went wrong.