Skip to content

Commit

Permalink
Replace Node EventEmitter with standard EventTarget
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalduez committed Sep 4, 2024
1 parent ecacffb commit 7c08699
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 50 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Remove usage of Node core lib util module ([#1](https://github.com/Gandi/counterpart/pull/1)).
- Remove usage of `except` helper package ([#1](https://github.com/Gandi/counterpart/pull/2)).
- **BREAKING** Replace usage of Node `EventEmitter` with native `EventTarget` ([#1](https://github.com/Gandi/counterpart/pull/3)).
This make the package able to run server of client side without requiring polyfills.
This change the signature of event listener callbacks.
They now receive a single `CustomEvent` object as argument, instead of unlimited
parameters. So for instance:
```
// Before
instance.onLocaleChange((locale, previous) => {})
```
```
// After
instance.onLocaleChange((event) => {
// event.detail.locale
// event.detail.previous
})
```


[unreleased]: https://github.com/gandi/counterpart/compare/0.18.6...HEAD
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BIN = ./node_modules/.bin

test: lint
test:
@$(BIN)/mocha -t 5000 -b -R spec spec.js

lint: node_modules/
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,12 @@ translate('baz', { fallback: 'default' })
When a translation key cannot be resolved to a translation, regardless of whether a fallback is provided or not, `translate` will emit an event you can listen to:

```js
translate.onTranslationNotFound(function(locale, key, fallback, scope) {
translate.onTranslationNotFound(function(event) {
// do important stuff here...
// event.detail.locale
// event.detail.key
// event.detail.fallback
// event.detail.scope
});
```

Expand Down Expand Up @@ -182,8 +186,10 @@ Note that it is advised to call `setLocale` only once at the start of the applic
In case of a locale change, the `setLocale` function emits an event you can listen to:

```js
translate.onLocaleChange(function(newLocale, oldLocale) {
translate.onLocaleChange(function(event) {
// do important stuff here...
// event.detail.locale
// event.detail.previous
}, [callbackContext]);
```

Expand Down Expand Up @@ -341,8 +347,11 @@ instance.translate('foo');
When a translation fails, `translate` will emit an event you can listen to:

```js
translate.onError(function(err, entry, values) {
translate.onError(function(event) {
// do some error handling here...
// event.detail.error
// event.detail.entry
// event.detail.values
});
```

Expand Down
104 changes: 73 additions & 31 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

var extend = require('extend');
var sprintf = require("sprintf-js").sprintf;
var events = require('events');

var strftime = require('./strftime');

Expand Down Expand Up @@ -46,29 +45,72 @@ function getEntry(translations, keys) {
}, translations);
}

function Counterpart() {
events.EventEmitter.apply(this);

this._registry = {
locale: 'en',
interpolate: true,
fallbackLocales: [],
scope: null,
translations: {},
interpolations: {},
normalizedKeys: {},
separator: '.',
keepTrailingDot: false,
keyTransformer: function(key) { return key; },
generateMissingEntry: function(key) { return 'missing translation: ' + key; }
class Counterpart extends EventTarget {
constructor() {
super();

this._registry = {
locale: 'en',
interpolate: true,
fallbackLocales: [],
scope: null,
translations: {},
interpolations: {},
normalizedKeys: {},
separator: '.',
keepTrailingDot: false,
keyTransformer: function(key) { return key; },
generateMissingEntry: function(key) { return 'missing translation: ' + key; }
};

this.registerTranslations('en', require('./locales/en'));
}

// EventTarget does not (yet) have a native way to retrieve attached listeners.
// See https://github.com/whatwg/dom/issues/412

#events = {};

_addEventListener = (type, listener, options) => {
EventTarget.prototype.addEventListener.call(this, type, listener, options);

(this.#events[type] ||= []).push(listener);

return this;
};

this.registerTranslations('en', require('./locales/en'));
this.setMaxListeners(0);
}
_removeEventListener = (type, listener, options) => {
EventTarget.prototype.removeEventListener.call(this, type, listener, options);

let list = this.#events[type];

if (!list) return this;

let position = -1;

for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener) {
position = i;
break;
}
}

Counterpart.prototype = events.EventEmitter.prototype;
Counterpart.prototype.constructor = events.EventEmitter;
if (position < 0) return this;

if (position === 0) {
list.shift();
}
else {
list.splice(list, position);
}

return this;
};

_listenerCount = (type) => {
return this.#events[type] ? this.#events[type].length : 0;
}
}

Counterpart.prototype.getLocale = function() {
return this._registry.locale;
Expand All @@ -79,7 +121,7 @@ Counterpart.prototype.setLocale = function(value) {

if (previous != value) {
this._registry.locale = value;
this.emit('localechange', value, previous);
this.dispatchEvent(new CustomEvent('localechange', { detail: { locale: value, previous }}));
}

return previous;
Expand Down Expand Up @@ -158,32 +200,32 @@ Counterpart.prototype.registerInterpolations = function(data) {

Counterpart.prototype.onLocaleChange =
Counterpart.prototype.addLocaleChangeListener = function(callback) {
this.addListener('localechange', callback);
this._addEventListener('localechange', callback);
};

Counterpart.prototype.offLocaleChange =
Counterpart.prototype.removeLocaleChangeListener = function(callback) {
this.removeListener('localechange', callback);
this._removeEventListener('localechange', callback);
};

Counterpart.prototype.onTranslationNotFound =
Counterpart.prototype.addTranslationNotFoundListener = function(callback) {
this.addListener('translationnotfound', callback);
this._addEventListener('translationnotfound', callback);
};

Counterpart.prototype.offTranslationNotFound =
Counterpart.prototype.removeTranslationNotFoundListener = function(callback) {
this.removeListener('translationnotfound', callback);
this._removeEventListener('translationnotfound', callback);
};

Counterpart.prototype.onError =
Counterpart.prototype.addErrorListener = function(callback) {
this.addListener('error', callback);
this._addEventListener('error', callback);
};

Counterpart.prototype.offError =
Counterpart.prototype.removeErrorListener = function(callback) {
this.removeListener('error', callback);
this._removeEventListener('error', callback);
};

Counterpart.prototype.translate = function(key, options) {
Expand Down Expand Up @@ -216,7 +258,7 @@ Counterpart.prototype.translate = function(key, options) {
var entry = getEntry(this._registry.translations, keys);

if (entry === null) {
this.emit('translationnotfound', locale, key, options.fallback, scope);
this.dispatchEvent(new CustomEvent('translationnotfound', { detail: { locale, key, fallback: options.fallback, scope }}));

if (options.fallback) {
entry = this._fallback(locale, scope, key, options.fallback, options);
Expand Down Expand Up @@ -354,8 +396,8 @@ Counterpart.prototype._interpolate = function(entry, values) {
try {
return sprintf(entry, extend({}, this._registry.interpolations, values));
} catch (err) {
if (this.listenerCount('error') > 0) {
this.emit('error', err, entry, values);
if (this._listenerCount('error') > 0) {
this.dispatchEvent(new CustomEvent('error', { detail: { error: err, entry, values }}));
} else {
throw err;
}
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 14 additions & 13 deletions spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,9 +607,9 @@ describe('translate', function() {
var oldLocale = instance.getLocale();
var newLocale = oldLocale + 'x';

var handler = function(locale, previousLocale) {
assert.equal(locale, newLocale);
assert.equal(previousLocale, oldLocale);
var handler = function(evt) {
assert.equal(evt.detail.locale, newLocale);
assert.equal(evt.detail.previous, oldLocale);
done();
};

Expand All @@ -619,7 +619,8 @@ describe('translate', function() {
});
});

describe('when called more than 10 times', function() {
// EventTarget does not have a native `setMaxListeners`.
describe.skip('when called more than 10 times', function() {
it('does not let Node issue a warning about a possible memory leak', function() {
var oldConsoleError = console.error;

Expand Down Expand Up @@ -694,11 +695,11 @@ describe('translate', function() {

describe('when called', function() {
it('exposes the current locale, key, fallback and scope as arguments', function(done) {
var handler = function(locale, key, fallback, scope) {
assert.equal('yy', locale);
assert.equal('foo', key);
assert.equal('bar', fallback);
assert.equal('zz', scope);
var handler = function(evt) {
assert.equal('yy', evt.detail.locale);
assert.equal('foo', evt.detail.key);
assert.equal('bar', evt.detail.fallback);
assert.equal('zz', evt.detail.scope);
done();
};

Expand Down Expand Up @@ -763,10 +764,10 @@ describe('translate', function() {

describe('when called', function() {
it('exposes the error, entry and values as arguments', function(done) {
var handler = function(error, entry, values) {
assert.notEqual(undefined, error);
assert.equal('Hello, %(name)s!', entry);
assert.deepEqual({}, values);
var handler = function(evt) {
assert.notEqual(undefined, evt.detail.error);
assert.equal('Hello, %(name)s!', evt.detail.entry);
assert.deepEqual({}, evt.detail.values);
done();
};

Expand Down

0 comments on commit 7c08699

Please sign in to comment.