Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
darrenlucas committed Jan 27, 2017
0 parents commit bdce51a
Show file tree
Hide file tree
Showing 16 changed files with 694 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["es2015", "react"]
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
lib
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
language: node_js
node_js:
- 4.6.2
15 changes: 15 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ISC License

Copyright (c) 2017, Darren Lucas

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
181 changes: 181 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
react-g11n
==========

An internationalisation and localization library for React.


Installation
------------

npm install react-g11n


Usage
-----

The provider pattern is used to wrap your component and provide a translator
object to child components. Wrap your component in a Localizer as follows:


import { render } from "react-dom"
import { Localizer } from "react-g11n"

import App from "app"

render(
<Localizer locale="cy">
<App />
</Localizer>
}


From within a child component you can gain access to a translator property as
follows:

import React, { PropTypes} from "react"
import { localize } from "react-g11n"

class MyComponent extends React.Component {

render() {
const { translator } = this.props

return (
<h1>{translator.gettext('Hello world!')}</h1>
)

}

MyComponent.propTypes = {
translator: PropTypes.object.isRequired
}

export default localize(MyComponent)


The translate object exposes the a Gettext object with common gettext functions


Working with gettext translation files
--------------------------------------

GNU gettext is an internationalization and localization system commonly used on
Unix like operating systems.

Three types of files are used in the GNU gettext translation framework:

* .pot (Portable Object Template) files

A .pot file is created by a program which searches through a projects source code
and picks out every message identifier passed to gettext translation functions.
The list of message identifiers is placed into a .pot file which serves as a
template for creating .po files.

* .po (Portable Object) files

A .po file is derived from the template and fills out the translations.

* .mo (Machine Object) files

A .po file is compiled into a binary .mo file optimized for reading by a machine.


### Extracting messages

To extract messages from javascript files use jsxgettext which can be installed
from npm:

npm install jsxgettext --save-dev


This will install a command called jsxgettext which can be used to scan javascript
code for calls to gettext and extract translations into a portable object template:

mkdir -p src/locale
node_modules/.bin/jsxgettext src/**/*.js -o src/locale/messages.pot

This will output a .pot file to src/locale/messages.pot


### Initializing a message catalog file

Once messages have been extracted into a .pot file this can be used to generate a
.po file. A .po file contains translations for a set of messages for a particular
locale. The Gettext msginit command can be used to initialize a .po file:

cd src/locale
mkdir -p cy/LC_MESSAGES
msginit -l cy -o cy/LC_MESSAGES/messages.po

This will create a new message catalog .po file which can then be edited by a human
translator using a tool such as Poedit.


### Updating a message catalog file

As more translation strings are added, or as existing strings are changed it will be
neccessary to update existing .po files so that new and changed messages can be
translated.

First regenerate the .pot file as per the Extracting messages section and then use
the msgmerge command from Gettext.

cd src/locale
msgmerge --update cy/LC_MESSAGES/messages.po messages.pot


### Compiling a message catalog file

To convert translation files into a machine readable binary file use the Gettext
msgfmt command to convert .po files into .mo files.

cd src/locale
msgfmt cy/LC_MESSAGES/messages.po -o cy/LC_MESSAGES/messages.mo


### Automating with Make

To make the above commands easier, the can be codified into Makefile targets to
provide the following phony targets:

* extract-messages
* update-catalog
* compile-catalog

An example Makefile would be:

SRC_DIR = src/
POT_FILE = $(SRC_DIR)/locale/messages.pot
PO_FILES := $(shell find $(SRC_DIR) -type f -name '*.po')
MO_FILES := $(patsubst $(SRC_DIR)/%.po,$(SRC_DIR)/%.mo,$(PO_FILES))

JSXGETTEXT_CMD = node_modules/.bin/jsxgettext

.PHONY: extract-messages
extract-messages: ## Extract translations from source code
$(JSXGETTEXT_CMD) $(SRC_DIR)/**/*.js -o $(POT_FILE)

.PHONY: update-catalog
update-catalog: $(PO_FILES) ## Update messages catalogs for all locales

$(PO_FILES): $(POT_FILE)
msgmerge --previous $@ $? -o $@

.PHONY: compile-catalog
compile-catalog: $(MO_FILES) ## Compile messages catalogs for all locales

$(MO_FILES): $(SRC_DIR)/%.mo: $(SRC_DIR)/%.po
mkdir -p $(@D)
msgfmt $< -o $@


A translation workflow would then be:

make extract-messages
make update-catalog

Once translations of the generate .po files has taken place the .mo files can
be compiled with:

make compile-catalog

42 changes: 42 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "react-g11n",
"version": "0.1.0",
"description": "Globalization for react components using gettext",
"main": "./lib/index.js",
"files": [
"README.md",
"LICENSE",
"lib"
],
"scripts": {
"start": "babel src --out-dir lib",
"test": "jest"
},
"repository": {
"type": "git",
"url": "git+https://github.com/darrenlucas/react-g11n.git"
},
"author": "Darren Lucas",
"license": "ISC",
"bugs": {
"url": "https://github.com/darrenlucas/react-g11n/issues"
},
"homepage": "https://github.com/darrenlucas/react-g11n#readme",
"dependencies": {
"gettext-parser": "^1.2.2"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-jest": "^18.0.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"jest": "^18.1.0",
"react": "^15.4.2",
"react-test-renderer": "^15.4.2"
},
"jest": {
"testPathDirs": [
"./test"
]
}
}
119 changes: 119 additions & 0 deletions src/gettext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
let GettextMixin = (superclass) => class extends superclass {

_translate(message) {
if (super.translate) {
return super.translate.apply(this, arguments);
}
return message;
}

_pluralize(singular, plural, number) {
if (super.pluralize) {
return super.pluralize.apply(this, arguments);
}
return this._translate((number != 1) ? singular : plural);
}

/**
* Translate a string using the current domain.
*
* @param {String} message String to be translated
* @return {String} translated string
*/
gettext(message) {
return this._translate(message);
}

/**
* Translate a string using the specified domain.
*
* @param {String} domain The domain to use
* @param {String} message String to be translated
* @return {String} translated string
*/
dgettext(domain, message) {
return this._translate(message, { domain: domain });
}

/**
* Translate a string taking into consideration plural forms.
* Some languages have more than two plural forms.
*
* @param {String} singular Singular form string to be translated
* @param {String} plural Plural form string to be translated
* @param {Number} number Number for the plural
* @return {String} translated string
*/
ngettext(singular, plural, number) {
return this._pluralize(singular, plural, number);
}

/**
* Translate a string taking into consideration plural forms using
* the specified domain
*
* @param {String} domain The domain to use
* @param {String} singular Singular form string to be translated
* @param {String} plural Plural form string to be translated
* @param {Number} number Number for the plural
* @return {String} translated string
*/
dngettext(domain, singular, plural, number) {
return this._pluralize(singular, plural, number, { domain: domain });
}

/**
* Translate a string for the specified context using the current domain.
*
* @param {String} context Translation context
* @param {String} message String to be translated
* @return {String} translated string
*/
pgettext(context, message) {
return this._translate(message, { context: context });
}

/**
* Translate a string using the specified domain and context
*
* @param {String} domain The domain to use
* @param {String} context Translation context
* @param {String} message String to be translated
* @return {String} translated string
*/
dpgettext(domain, context, message) {
return this._translate(message, { domain: domain, context: context });
}

/**
* Translate a string for the specified context taking into consideration
* plural forms.
*
* @param {String} context Translation context
* @param {String} singular Singular form string to be translated
* @param {String} plural Plural form string to be translated
* @param {Number} number Number for the plural
* @return {String} translated string
*/
npgettext(context, singular, plural, number) {
return this._pluralize(singular, plural, number, { context: context });
}

/**
* Translate a string for the specified domain and context taking into
* consideration plural forms.
*
* @param {String} domain The domain to use
* @param {String} context Translation context
* @param {String} singular Singular form string to be translated
* @param {String} plural Plural form string to be translated
* @param {Number} number Number for the plural
* @return {String} translated string
*/
dnpgettext(domain, context, singular, plural, number) {
return this._pluralize(singular, plural, number, { domain: domain, context: context });
}

}

export default GettextMixin;
5 changes: 5 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import localize from './localize';
import Localizer from './localizer';

export { localize as localize };
export default Localizer;
Loading

0 comments on commit bdce51a

Please sign in to comment.