Skip to content

Commit

Permalink
Merge pull request #8 from koala-interactive/feature/onMissingVariable
Browse files Browse the repository at this point in the history
✨  Introduce onMissingVariable feature
  • Loading branch information
vthibault authored Mar 2, 2019
2 parents 8fec777 + 8509545 commit 7131b8a
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 9 deletions.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<h1>💋 FrenchKiss.js</h1>

[![Build Status](https://travis-ci.com/koala-interactive/frenchkiss.js.svg?branch=master)](https://travis-ci.com/koala-interactive/frenchkiss.js)
[![File size](https://img.shields.io/badge/GZIP%20size-1028%20B-brightgreen.svg)](./dist/umd/frenchkiss.js)
[![File size](https://img.shields.io/badge/GZIP%20size-1087%20B-brightgreen.svg)](./dist/umd/frenchkiss.js)
![](https://img.shields.io/badge/dependencies-none-brightgreen.svg)
![](https://img.shields.io/snyk/vulnerabilities/github/koala-interactive/frenchkiss.js.svg)
[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)
Expand Down Expand Up @@ -57,6 +57,7 @@ Or install using [npm](https://npmjs.org):
- [frenchkiss.unset()](#frenchkiss.unsetlanguage-string)
- [frenchkiss.fallback()](#frenchkissfallbacklanguage-string-string)
- [frenchkiss.onMissingKey()](#frenchkissonMissingKeyfn-Function)
- [frenchkiss.onMissingVariable()](#frenchkissonMissingVariablefn-Function)
- [SELECT expression](#select-expression)
- [PLURAL expression](#plural-expression)
- [Plural category](#plural-category)
Expand Down Expand Up @@ -215,6 +216,31 @@ frenchkiss.t('missingkey'); // => 'An error happened (missingkey)'

---

### frenchkiss.onMissingVariable(fn: Function)

It's possible to handle missing variables, sending errors to your monitoring server or handle it directly by returning something to replace with.

```js
frenchkiss.set('en', {
hello: 'Hello {name} !',
});
frenchkiss.locale('en');

frenchkiss.t('hello'); // => 'Hello !'

frenchkiss.onMissingVariable((variable, key, language) => {
// Send error to your server
sendReport(`Missing the variable "${variable}" in ${language}->${key}.`);

// Returns the text you want
return `[missing:${variable}]`;
});

frenchkiss.t('hello'); // => 'Hello [missing:name] !'
```

---

### SELECT expression

If you need to display different text messages depending on the value of a variable, you need to translate all of those text messages... or you can handle this with a select ICU expression.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@babel/preset-env": "^7.1.6",
"@babel/register": "^7.0.0",
"chai": "^4.2.0",
"chai-spies": "^1.0.0",
"del-cli": "^1.1.0",
"eslint": "^5.12.1",
"eslint-config-prettier": "^4.0.0",
Expand Down
20 changes: 16 additions & 4 deletions src/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ const escapeText = JSON.stringify; // (text) => '"' + text.replace(/(["\\])/g, '

/**
* Helper to bind variable name to value.
* Default to empty string if not defined
* Default to onMissingVariable returns if not defined
*
* Mapping :
* - undefined -> ''
* - null -> ''
* - 0 -> 0
* - 155 -> 155
* - 'test' -> 'test'
* - not defined -> onMissingVariable(value, key, language)
*
* @param {String} text
* @returns {String}
*/
const escapeVariable = text =>
'(p["' + text + '"]||(p["' + text + '"]=="0"?0:""))';
// prettier-ignore
'(p["' + text + '"]||(p["' + text + '"]=="0"?0:"' + text + '" in p?"":v("' + text + '",k,l)))';

/**
* Compile the translation to executable optimized function
Expand All @@ -48,8 +57,11 @@ export function compileCode(text) {
}

return new Function(
'a',
'f',
'a', // params
'f', // plural category function
'k', // key
'l', // language
'v', // missingVariableHandler
'var p=a||{}' +
(size ? ',m=f?{' + pluralCode + '}:{}' : '') +
';return ' +
Expand Down
39 changes: 36 additions & 3 deletions src/frenchkiss.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ let _fallback = '';
*/
let missingKeyHandler = key => key;

/**
* Default function used in case of missing variable
* Returns the value you want
*
* @param {String} variable
* @param {String} key
* @param {String} language
* @returns {String}
*/
let missingVariableHandler = () => '';

/**
* Get back a translation and returns the optimized function
* Store the function in the cache to re-use it
Expand Down Expand Up @@ -54,17 +65,27 @@ export const t = (key, params, language) => {
let fn,
lang = language || _locale;

// Try to get the specified or locale
if (lang) {
fn = getCompiledCode(key, lang);

if (fn) {
return fn(params, _plural[lang], key, lang, missingVariableHandler);
}
}

lang = _fallback;

// Try to get the fallback language
if (!fn && _fallback) {
lang = _fallback;
if (lang) {
fn = getCompiledCode(key, lang);

if (fn) {
return fn(params, _plural[lang], key, lang, missingVariableHandler);
}
}

return fn ? fn(params, _plural[lang]) : missingKeyHandler(key);
return missingKeyHandler(key);
};

/**
Expand All @@ -78,6 +99,17 @@ export const onMissingKey = fn => {
missingKeyHandler = fn;
};

/**
* Set a function to handle missing variable to:
* - Returns the value you want
* - Report the poblem to your server
*
* @param {Function} fn
*/
export const onMissingVariable = fn => {
missingVariableHandler = fn;
};

/**
* Getter/setter for locale
*
Expand Down Expand Up @@ -171,6 +203,7 @@ export default {
store,
t,
onMissingKey,
onMissingVariable,
locale,
fallback,
set,
Expand Down
51 changes: 50 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { expect } from 'chai';
import chai, { expect } from 'chai';
import spies from 'chai-spies';
import i18n from '../src/frenchkiss';

chai.use(spies);

describe('locale', () => {
it('should not bug if no locale', () => {
expect(i18n.t('test')).to.equal('test');
Expand Down Expand Up @@ -237,6 +240,7 @@ describe('t', () => {
describe('onMissingKey', () => {
beforeEach(() => {
i18n.locale('en');
i18n.fallback('xyz');
});

afterEach(() => {
Expand All @@ -247,6 +251,15 @@ describe('onMissingKey', () => {
expect(i18n.t('bogus_key')).to.equal('bogus_key');
});

it('is called with key', () => {
const fn = chai.spy(() => '');

i18n.onMissingKey(fn);
i18n.t('bogus_key');

expect(fn).to.have.been.called.with('bogus_key');
});

it('replace the key with something custom when not found', () => {
i18n.onMissingKey(key => 'missing:' + key);
expect(i18n.t('bogus_key')).to.equal('missing:bogus_key');
Expand All @@ -261,6 +274,42 @@ describe('onMissingKey', () => {
});
});

describe('onMissingVariable', () => {
beforeEach(() => {
i18n.locale('en');
i18n.set('en', {
test: 'Test {value} !',
});
});

afterEach(() => {
i18n.onMissingVariable(() => '');
});

it('returns empty string if variable not found', () => {
expect(i18n.t('test')).to.equal('Test !');
});

it('returns empty string if variable not found', () => {
i18n.onMissingVariable(value => `[${value}]`);
expect(i18n.t('test', { value: '' })).to.equal('Test !');
});

it('call onMissingVariable with parameters', () => {
const fn = chai.spy(() => '');

i18n.onMissingVariable(fn);
i18n.t('test');

expect(fn).to.have.been.called.with('value', 'test', 'en');
});

it('replace the variable with something custom when not found', () => {
i18n.onMissingVariable(value => `[${value}]`);
expect(i18n.t('test')).to.equal('Test [value] !');
});
});

describe('set', () => {
beforeEach(() => {
i18n.locale('en');
Expand Down

0 comments on commit 7131b8a

Please sign in to comment.