Compare commits
4 Commits
master
...
feature-pl
Author | SHA1 | Date |
---|---|---|
|
68d3ae4dd4 | |
|
86598190f2 | |
|
a2d4083e95 | |
|
5d5371e739 |
122
README.md
122
README.md
|
@ -11,7 +11,7 @@ koa locales, i18n solution for koa:
|
|||
|
||||
1. All locales resources location on `options.dirs`.
|
||||
2. resources file supports: `*.js`, `*.json`, `*.yml`, `*.yaml` and `*.properties`, see [examples](test/locales/).
|
||||
3. One api: `__(key[, value, ...])`.
|
||||
3. Api: `__(key[, value, ...])`, `__n(key, count[, value, ...])`.
|
||||
4. Auto detect request locale from `query`, `cookie` and `header: Accept-Language`.
|
||||
|
||||
## Installation
|
||||
|
@ -42,8 +42,10 @@ Patch locales functions to koa app.
|
|||
- {Application} app: koa app instance.
|
||||
- {Object} options: optional params.
|
||||
- {String} functionName: locale function name patch on koa context. Optional, default is `__`.
|
||||
- {String} functionnName: locale function (with plurals management) name patch on koa context. Optional, default is `__n`.
|
||||
- {String} dirs: locales resources store directories. Optional, default is `['$PWD/locales']`.
|
||||
- {String} defaultLocale: default locale. Optional, default is `en-US`.
|
||||
- {Boolean} fallbackToDefaultLocale: fall back to default local if current translation is not found. Optional, default is `false`.
|
||||
- {String} queryField: locale field name on query. Optional, default is `locale`.
|
||||
- {String} cookieField: locale field name on cookie. Optional, default is `locale`.
|
||||
- {String} cookieDomain: domain on cookie. Optional, default is `''`.
|
||||
|
@ -99,6 +101,86 @@ __('{a} {a} {b} {b} {b}', {a: 'foo', b: 'bar'})
|
|||
'foo foo bar bar bar'
|
||||
```
|
||||
|
||||
### `context.__n(key, count[, value1[, value2, ...]])`
|
||||
|
||||
Get current request locale text with plural.
|
||||
|
||||
Works with package [make-plural](https://github.com/eemeli/make-plural/tree/master/packages/plurals) package. Support approximately 200 languages.
|
||||
|
||||
```js
|
||||
async function home(ctx) {
|
||||
ctx.body = {
|
||||
message: ctx.__n({one: 'I have one apple.', other: 'I have {0} apples.', 2, 2),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Note:**
|
||||
The `count` parameter is not part of the displayed values. To have the number
|
||||
`count` displayed, one must add this parameter to the list of values.
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
```js
|
||||
// With english locale
|
||||
__n({one: 'I have one cat.', other: 'I have not one cat.'}, 0)
|
||||
=>
|
||||
'I have not one cat.'
|
||||
|
||||
// With french locale
|
||||
// Note that the parameter 0 is put two times:
|
||||
// The first one is used as the count parameter and the second is displayed as
|
||||
// first value ({0}).
|
||||
__n({one: "J'ai {0} chat.", other: "J'ai {0} chats."}, 0, 0)
|
||||
=>
|
||||
"J'ai 0 chat."
|
||||
|
||||
// If the targeted plural is not found (here 'one'), 'other' is selected.
|
||||
__n({other: '{a} {a} {b} {b} {b}'}, 1, {a: 'foo', b: 'bar'})
|
||||
=>
|
||||
'foo foo bar bar bar'
|
||||
```
|
||||
|
||||
Russian json file (from [i18n](https://github.com/mashpie/i18n-node#i18n__n)
|
||||
example):
|
||||
```json
|
||||
{
|
||||
"cat_key": {
|
||||
"one": "%d кошка",
|
||||
"few": "%d кошки",
|
||||
"many": "%d кошек",
|
||||
"other": "%d кошка",
|
||||
}
|
||||
}
|
||||
```
|
||||
Use:
|
||||
```js
|
||||
__n('cat_key', 0, 0);
|
||||
=>
|
||||
'0 кошек'
|
||||
|
||||
__n('cat_key', 1, 1);
|
||||
=>
|
||||
'1 кошка'
|
||||
|
||||
__n('cat_key', 2, 2);
|
||||
=>
|
||||
'2 кошки'
|
||||
|
||||
__n('cat_key', 5, 5);
|
||||
=>
|
||||
'5 кошек'
|
||||
|
||||
__n('cat_key', 6, 6);
|
||||
=>
|
||||
'6 кошек'
|
||||
|
||||
__n('cat_key', 21, 21);
|
||||
=>
|
||||
'21 кошка'
|
||||
```
|
||||
|
||||
### `context.__getLocale()`
|
||||
|
||||
Get locale from query / cookie and header.
|
||||
|
@ -149,6 +231,44 @@ app.use(async (ctx, next) => {
|
|||
});
|
||||
```
|
||||
|
||||
### `app.__n(locale, key, count[, value1[, value2, ...]])`
|
||||
|
||||
Get the given locale text with plural management on application level.
|
||||
|
||||
```js
|
||||
console.log(app.__n('zh', {other: 'Hello'}, 0));
|
||||
// stdout '你好' for Chinese
|
||||
```
|
||||
|
||||
## Usage on template
|
||||
|
||||
```js
|
||||
this.state.__n = this.__n.bind(this);
|
||||
```
|
||||
|
||||
[Nunjucks] example:
|
||||
|
||||
```html
|
||||
{{ __n({other: 'Hello, %s'}, 2, user.name) }}
|
||||
```
|
||||
|
||||
[Pug] example:
|
||||
|
||||
```pug
|
||||
p= __n({other: 'Hello, %s'}, 2, user.name)
|
||||
```
|
||||
|
||||
[Koa-pug] integration:
|
||||
|
||||
You can set the property *locals* on the KoaPug instance, where the default locals are stored.
|
||||
|
||||
```js
|
||||
app.use(async (ctx, next) => {
|
||||
koaPug.locals.__n = ctx.__n.bind(ctx);
|
||||
await next();
|
||||
});
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
If you are interested on knowing what locale was chosen and why you can enable the debug messages from [debug].
|
||||
|
|
127
index.js
127
index.js
|
@ -10,9 +10,11 @@ const path = require('path');
|
|||
const ms = require('humanize-ms');
|
||||
const assign = require('object-assign');
|
||||
const yaml = require('js-yaml');
|
||||
const MakePlural = require('make-plural');
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
defaultLocale: 'en-US',
|
||||
fallbackToDefaultLocale: 'false',
|
||||
queryField: 'locale',
|
||||
cookieField: 'locale',
|
||||
localeAlias: {},
|
||||
|
@ -21,11 +23,13 @@ const DEFAULT_OPTIONS = {
|
|||
dir: undefined,
|
||||
dirs: [path.join(process.cwd(), 'locales')],
|
||||
functionName: '__',
|
||||
functionnName: '__n'
|
||||
};
|
||||
|
||||
module.exports = function (app, options) {
|
||||
options = assign({}, DEFAULT_OPTIONS, options);
|
||||
const defaultLocale = formatLocale(options.defaultLocale);
|
||||
const fallbackToDefaultLocale = options.fallbackToDefaultLocale;
|
||||
const queryField = options.queryField;
|
||||
const cookieField = options.cookieField;
|
||||
const cookieDomain = options.cookieDomain;
|
||||
|
@ -35,7 +39,9 @@ module.exports = function (app, options) {
|
|||
const localeDir = options.dir;
|
||||
const localeDirs = options.dirs;
|
||||
const functionName = options.functionName;
|
||||
const functionnName = options.functionnName;
|
||||
const resources = {};
|
||||
const PluralsForLocale = {};
|
||||
|
||||
/**
|
||||
* @Deprecated Use options.dirs instead.
|
||||
|
@ -81,13 +87,16 @@ module.exports = function (app, options) {
|
|||
function gettext(locale, key, value) {
|
||||
if (arguments.length === 0 || arguments.length === 1) {
|
||||
// __()
|
||||
// --('en')
|
||||
// __('en')
|
||||
return '';
|
||||
}
|
||||
|
||||
const resource = resources[locale] || {};
|
||||
|
||||
let text = resource[key];
|
||||
if (text === undefined && fallbackToDefaultLocale === true) {
|
||||
text = resources[defaultLocale][key];
|
||||
}
|
||||
if (text === undefined) {
|
||||
text = key;
|
||||
}
|
||||
|
@ -132,6 +141,96 @@ module.exports = function (app, options) {
|
|||
}
|
||||
|
||||
app[functionName] = gettext;
|
||||
|
||||
|
||||
function gettextn(locale, key, count, value) {
|
||||
if (arguments.length === 0 || arguments.length === 1) {
|
||||
// __n()
|
||||
// __n('en')
|
||||
return '';
|
||||
}
|
||||
|
||||
if (arguments.length === 2) {
|
||||
// __n('en', key)
|
||||
return gettext(locale, key);
|
||||
}
|
||||
|
||||
const resource = resources[locale] || {};
|
||||
|
||||
// enforce number
|
||||
count = Number(count);
|
||||
|
||||
//**************************************************************//
|
||||
// Directly adapted from __n function of i18n module :
|
||||
// https://github.com/mashpie/i18n-node/blob/master/i18n.js
|
||||
var p;
|
||||
// create a new Plural for locale
|
||||
// and try to cache instance
|
||||
if (PluralsForLocale[locale]) {
|
||||
p = PluralsForLocale[locale];
|
||||
} else {
|
||||
// split locales with a region code
|
||||
var lc = locale.toLowerCase().split(/[_-\s]+/)
|
||||
.filter(function(el){ return true && el; });
|
||||
// take the first part of locale, fallback to full locale
|
||||
p = MakePlural[lc[0] || locale];
|
||||
PluralsForLocale[locale] = p;
|
||||
}
|
||||
// fallback to 'other' on case of missing translations
|
||||
let text = resource[key+"."+p(count)] || resource[key+".other"];
|
||||
//**************************************************************//
|
||||
|
||||
if (text === undefined && isObject(key)) {
|
||||
text = key[p(count)] || key["other"];
|
||||
}
|
||||
if (text === undefined && fallbackToDefaultLocale === true) {
|
||||
text = resources[defaultLocale][key+"."+p(count)] || resources[defaultLocale][key+".other"];
|
||||
}
|
||||
if (text === undefined) {
|
||||
text = key;
|
||||
}
|
||||
|
||||
debugSilly('%s: %j => %j', locale, key, text);
|
||||
if (!text) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (arguments.length === 3) {
|
||||
// __(locale, key, count)
|
||||
return text;
|
||||
}
|
||||
if (arguments.length === 4) {
|
||||
if (isObject(value)) {
|
||||
// __(locale, key, count, object)
|
||||
// __('zh', {"one": '{a} {b} {b} {a}', "other": '{b} {a} {a} {b}'}, 1, {a: 'foo', b: 'bar'})
|
||||
// =>
|
||||
// foo bar bar foo
|
||||
return formatWithObject(text, value);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
// __(locale, key, array)
|
||||
// __('zh', {"one": '{0} {1} {1} {0}', "other": '{1} {0} {0} {1}'}, 2, ['foo', 'bar'])
|
||||
// =>
|
||||
// bar foo foo bar
|
||||
return formatWithArray(text, value);
|
||||
}
|
||||
|
||||
// __(locale, key, count, value)
|
||||
return util.format(text, value);
|
||||
}
|
||||
|
||||
// __(locale, key, count, value1, ...)
|
||||
const args = new Array(arguments.length - 2);
|
||||
args[0] = text;
|
||||
for(let i = 3; i < arguments.length; i++) {
|
||||
args[i - 2] = arguments[i];
|
||||
}
|
||||
return util.format.apply(util, args);
|
||||
|
||||
}
|
||||
|
||||
app[functionnName] = gettextn;
|
||||
|
||||
app.context[functionName] = function (key, value) {
|
||||
if (arguments.length === 0) {
|
||||
|
@ -153,6 +252,32 @@ module.exports = function (app, options) {
|
|||
}
|
||||
return gettext.apply(this, args);
|
||||
};
|
||||
|
||||
app.context[functionnName] = function (key, count, value) {
|
||||
if (arguments.length === 0) {
|
||||
// __n()
|
||||
return '';
|
||||
}
|
||||
|
||||
const locale = this.__getLocale();
|
||||
if (arguments.length === 1) {
|
||||
// __n(key)
|
||||
return gettext(locale, key);
|
||||
}
|
||||
|
||||
if (arguments.length === 2) {
|
||||
return gettextn(locale, key, count);
|
||||
}
|
||||
if (arguments.length === 3) {
|
||||
return gettextn(locale, key, count, value);
|
||||
}
|
||||
const args = new Array(arguments.length + 1);
|
||||
args[0] = locale;
|
||||
for(let i = 0; i < arguments.length; i++) {
|
||||
args[i + 1] = arguments[i];
|
||||
}
|
||||
return gettextn.apply(this, args);
|
||||
};
|
||||
|
||||
// 1. query: /?locale=en-US
|
||||
// 2. cookie: locale=zh-TW
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"humanize-ms": "^1.2.0",
|
||||
"ini": "^1.3.4",
|
||||
"js-yaml": "^3.13.1",
|
||||
"make-plural": "^6.2.1",
|
||||
"npminstall": "^3.23.0",
|
||||
"object-assign": "^4.1.0"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue