gengojs-core
The core of gengo.js that manages i18n and l10n.
Status
As of 8/29/15, I've decided to go through the core and the plugins and refine them so that it will be easier to create the docs and hopefully easier to understand how to create plugins.
Some from the docs below may still work as far as exporting plugins but the idea of internal API still needs some refinement.
Introduction
gengojs-core is the actual core of gengo.js. It serves to be a server agnostic middle-ware supporting the popular servers such as Express, Koa, Hapi, and even more with ease. It is also modular-tastic, and easy to debug with less than 60 lines of code (minus the modules).
To get started, there are three things to know about how the core works:
- Initialize
- Ship
- Parse
Initialize is the starting point of the core. It handles the initialization of the plugin's stack, options, and also the back-end. The reason the back-end is initialized first is because of the possible use of asynchronous programming needs. Note that if you are to create a plugin for the back-end, you will need to load every locale into memory so that the parser can readily use the data.
Ship is a function that applies the API to requests and also to the view.
It begins by getting the locale from the client, letting the router know about
the current path, applying the locale to the localization plugin, and finally
assigning the API such as __
or __l
(can be changed) to the objects
that are provided by the request and response..
Parse is the final step in the core. It is called only when the API such as
__('Hello')
are used. In this step, the parser plugin must return the i18ned string.
So... you may be wondering why is the core a separate module from the rest? The reason is because having the core on its own allows you, developers, to create awesome plugins. I personally feel as if i18n modules are a bit limited in what it can do and myself as well.
Anyways, one thing to note is that this module should not be used on its own. The actual i18n library is gengo.js. If you want to extend the core to support server x, then here is where you want to do that but if you want to create the wrapper for server x, then gengo.js is where you would do that.
Getting Started
How gengo.js works is similar to how Hapi works in terms of creating plugins and how Grunt works in terms of options.
To create plugins, the one thing to keep in mind is core's this
context. When a plugin is initialized,
the core calls the plugin as it binds its context to that plugin (see Creating Plugins). Another thing to keep in mind is dependencies. Dependencies are really
internal API. For example, the parser plugin needs to know about the data. Therefore it is dependent on the
back-end plugin and is expecting the back-end to supply an internal API to retrieve the locale/data. The following shows
the type of plugins that are available for you to create and their dependencies:
Type of Plugins and its Dependencies
- Back-end (Storage)
- None
- Header (Header parsing)
- None
- Router (Path or Sub-domain parsing for data transitions in views)
- None
- Localize (Localization)
this.header.getLocale()
from Header class
- API (Applies the API (such as
__
and__l
) to the objects)this.backend.catalog()
from Backend classthis.header.detectLocale()
from Header classthis.header.getLocale()
from Header classthis.header.setLocale()
from Header classthis.localize
from Localize class
- Parser (i18ns the string)
this.backend.find()
from Back-end classthis.header.getLocale()
from Header classthis.router.toArray()
from Router class
this.router.toDot()
from Router classthis.router.isEnabled()
from Router class
If you noticed, you can pretty much change anything you like. It's designed that way so that if there was something I implemented that you didn't like, you can just create your own plugin for that part or contribute to the default plugins and PR it.
Now to make the internal API work, you would need to expose the internal API at the end of your plugins. The following shows which API needs to attach to the context:
Internal API to Expose By Plugin
- Back-ends
this.backend = [your back-end plugin instance]
- Returns class instance
- Header
this.header = [...]
- Returns class instance
- Router
this.router = [...]
- Returns class instance
- Localize
this.localize = [...]
- Returns class instance
- API
this.api = [...]
- Returns an class instance
For example plugins, see:
- gengojs-default-api
- gengojs-default-backend
- gengojs-default-header
- gengojs-default-localize
- gengojs-default-parser
- gengojs-default-router
To see how it works see Creating Plugins.
API
Core(options, plugins, defaults)
The constructor for the core. Note that there is no need to use new
since that is already done for you.
options
- The options for each plugin type.- Type:
String | Object
- Type:
plugins
- The plugins to use.- Type:
Object | Array | Function
- Type:
defaults
- The default plugins- Type:
Object
- Type:
parse(phrase, ...args)
This function is to be used when the API, __('...')
is called.
phrase
- The phrase to parse.- Type: Depends on the plugin
args
- The arguments passed besides thephrase
.
ship(req, res, next)
This function accepts the request and response objects and a next function is applicable. It then applies the API to the objects.
assign(req, res)
This is the main function that applies the API to the objects within the ship
function. The arguments do not necessarily have to be a request or response object.
Creating Plugins
Creating plugins is quite similar, if not, the same as creating plugins for Hapi. As mentioned above, the core is really all about context. The following shows you the recommended way to create your plugins:
ES5
function MyHeaderClass (options){
// Set
this.getLocale = function(){
// ...
}
}
// Hapi-ish style plugin
module.exports = function() {
var pkg = require('./package');
// ! add type
pkg.type = 'header';
return {
main: function ship(){
// Pass options and expose internal API
this.header = new MyHeaderClass(this.options.header);
},
package: pkg,
// Provide option defaults
defaults: require('./defaults.json')
};
};
ES6
class MyHeaderClass {
constructor(options){
// ...
}
// Set
getLocale(){
// ...
}
}
export default () => {
// Using require because
// 'import' variables
// seem to be constant
var pkg = require('./package');
return {
// Arrow functions do not work
// because the context belongs
// to something else so use traditional
// functions
main: function ship(){
// Pass options and expose internal API
this.header = new MyHeaderClass(this.options.header);
},
package: pkg,
// Provide option defaults
defaults: require('./defaults.json')
};
};
Notes:
-
You may have noticed that defaults are provided in the example. Defaults are required (See Options). If you do not have any defaults, then you can just pass
{}
, and the core will not complain. -
Keep in mind that you are limited to one plugin per type. This was done to prevent problems that may arise when dealing with the core's context.
Exporting Multiple Plugins
Now you may be wondering, Can I release a set of plugins? The answer is YES!. I call these sets, packs or gengo-pack. To create a pack, simply export the individual ships like the following:
ES5
module.exports = function(){
return {
parser: /*parser ship*/,
router: /*router ship*/,
backend: /*backend ship*/,
api: /*api ship*/,
header: /*header ship*/,
localize: /*localize ship*/
}
};
ES6
export default () => {
return {
parser: /*parser ship*/,
router: /*router ship*/,
backend: /*backend ship*/,
api: /*api ship*/,
header: /*header ship*/,
localize: /*localize ship*/
}
}
Testing your plugins
To test your plugins, simply install the core and also the default plugins needed for your plugin. The simplest way to download all the default plugins is by installing gengojs-default-pack
which
contains all the default plugins. Since the pack is an object, you
can simply use it like so:
var pack = require('gengojs-default-pack');
// Use only what you need
var header = pack.header;
var backend = pack.backend;
// or you can just replace the plugin:
pack.backend = myBackendPlugin;
// Then use the core for tests
var core = require('gengojs-core');
var gengo = core({}, pack);
// Test for your plugins existence:
if(!_.isUndefined(gengo.plugins.backends[0]))
// ...
Options
The core doesn't have the best option system but the official way to access options per plugin is by the context as in the example:
function ship(){
// To access the options,
// simply use: this.options[type]:
console.log(this.options.parser);
}
In general, you can access any other plugin's options through the same syntax as in the example, but make sure to provide the defaults when you create your plugins. The core will apply them to the options as soon as it loads the plugin into the stack.
Contributing
Feel free to contribute. To contribute, see the requirements. If you have any suggestions,
create issues at the core's GitHub Issues. Also,
all ES6 modules are located under lib/
.
- Requirements
- Gulp
- Airbnb Javascript Style
- semver versioning
- Fork and Pull
- Your skills
Debug
The core uses gengojs-debug, an extension of debug, to output debugging statements. To debug, simply set the type of debug in the shell:
Unix:
$ DEBUG=gengo.core:*
Windows
$ SET DEBUG=gengo.core:*
The levels used in the core are:
- debug
- error
- info
Develop
# Build modules with gulp for development
gulp
Test
# Build modules with gulp for production
gulp test