Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ember-i18n

Package Overview
Dependencies
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ember-i18n - npm Package Compare versions

Comparing version 1.3.2 to 2.1.1

bower.json

236

lib/i18n.js
(function(window) {
var I18n, assert, findTemplate, get, isBinding, isTranslatedAttribute, lookupKey, pluralForm;
var I18n, assert, findTemplate, get, set, isBinding, lookupKey, pluralForm,
PlainHandlebars, EmHandlebars, keyExists,
compileTemplate, compileWithHandlebars;
isTranslatedAttribute = /(.+)Translation$/;
PlainHandlebars = window.Handlebars;
EmHandlebars = Ember.Handlebars;
get = EmHandlebars.get;
set = Ember.set;
assert = Ember.assert;
get = Ember.Handlebars.get || Ember.Handlebars.getPath || Ember.getPath;
function warn(msg) { Ember.Logger.warn(msg); }

@@ -11,3 +17,3 @@ if (typeof CLDR !== "undefined" && CLDR !== null) pluralForm = CLDR.pluralForm;

if (pluralForm == null) {
Ember.Logger.warn("CLDR.pluralForm not found. Em.I18n will not support count-based inflection.");
warn("CLDR.pluralForm not found. Ember.I18n will not support count-based inflection.");
}

@@ -28,4 +34,2 @@

assert = Ember.assert != null ? Ember.assert : window.ember_assert;
findTemplate = function(key, setOnMissing) {

@@ -37,3 +41,6 @@ assert("You must provide a translation key string, not %@".fmt(key), typeof key === 'string');

if (result == null) {
result = I18n.translations[key] = I18n.compile("Missing translation: " + key);
result = I18n.translations[key] = function() { return "Missing translation: " + key; };
result._isMissing = true;
warn("Missing translation: " + key);
I18n[(typeof I18n.trigger === 'function' ? 'trigger' : 'fire')]('missing', key); //Support 0.9 style .fire
}

@@ -49,7 +56,65 @@ }

I18n = {
compile: Handlebars.compile,
keyExists = function(key) {
var translation = lookupKey(key, I18n.translations);
return translation != null && !translation._isMissing;
};
function eachTranslatedAttribute(object, fn) {
var isTranslatedAttribute = /(.+)Translation$/,
isTranslatedAttributeMatch;
for (var key in object) {
isTranslatedAttributeMatch = key.match(isTranslatedAttribute);
if (isTranslatedAttributeMatch) {
fn.call(object, isTranslatedAttributeMatch[1], I18n.t(object[key]));
}
}
}
var escapeExpression = EmHandlebars.Utils.escapeExpression;
compileWithHandlebars = (function() {
Ember.warn("Ember.I18n will no longer include Handlebars compilation by default in the future; instead, it will supply its own default compiler. Set Ember.I18n.I18N_COMPILE_WITHOUT_HANDLEBARS to true to opt-in now.");
if (typeof PlainHandlebars.compile === 'function') {
return function compileWithHandlebars(template) {
return PlainHandlebars.compile(template);
};
} else {
return function cannotCompileTemplate() {
throw new Ember.Error('The default Ember.I18n.compile function requires the full Handlebars. Either include the full Handlebars or override Ember.I18n.compile.');
};
}
}());
function compileWithoutHandlebars(template) {
return function (data) {
return template
.replace(/\{\{\{(.*?)\}\}\}/g, function(i, match) {
// tripple curlies -> no-escaping
return get(data, match);
}).replace(/\{\{(.*?)\}\}/g, function(i, match) {
return escapeExpression( get(data, match) );
});
};
}
if (Ember.ENV.I18N_COMPILE_WITHOUT_HANDLEBARS === true) {
compileTemplate = compileWithoutHandlebars;
} else {
compileTemplate = compileWithHandlebars;
}
I18n = Ember.Evented.apply({
compile: compileTemplate,
translations: {},
// Ember.I18n.eachTranslatedAttribute(object, callback)
//
// Iterate over the keys in `object`; for each property that ends in "Translation",
// call `callback` with the property name (minus the "Translation" suffix) and the
// translation whose key is the property's value.
eachTranslatedAttribute: eachTranslatedAttribute,
template: function(key, count) {

@@ -72,21 +137,28 @@ var interpolatedKey, result, suffix;

TranslateableAttributes: Em.Mixin.create({
didInsertElement: function() {
var attribute, isTranslatedAttributeMatch, key, path, result, translatedValue;
result = this._super.apply(this, arguments);
exists: keyExists,
for (key in this) {
path = this[key];
isTranslatedAttributeMatch = key.match(isTranslatedAttribute);
if (isTranslatedAttributeMatch) {
attribute = isTranslatedAttributeMatch[1];
translatedValue = I18n.t(path);
this.$().attr(attribute, translatedValue);
}
}
TranslateableProperties: Ember.Mixin.create({
init: function() {
var result = this._super.apply(this, arguments);
eachTranslatedAttribute(this, function(attribute, translation) {
this.addObserver(attribute + 'Translation', this, function(){
set(this, attribute, I18n.t(this.get(attribute + 'Translation')));
});
set(this, attribute, translation);
});
return result;
}
}),
TranslateableAttributes: Ember.Mixin.create({
didInsertElement: function() {
var result = this._super.apply(this, arguments);
eachTranslatedAttribute(this, function(attribute, translation) {
this.$().attr(attribute, translation);
});
return result;
}
})
};
});

@@ -97,50 +169,83 @@ Ember.I18n = I18n;

Handlebars.registerHelper('t', function(key, options) {
var attrs, context, data, elementID, result, tagName, view;
context = this;
attrs = options.hash;
data = options.data;
view = data.view;
tagName = attrs.tagName || 'span';
delete attrs.tagName;
elementID = "i18n-" + (Ember.uuid++);
function uniqueElementId() {
return ++Ember.uuid;
}
Em.keys(attrs).forEach(function(property) {
var bindPath, currentValue, invoker, isBindingMatch, normalized, normalizedPath, observer, propertyName, root, _ref;
isBindingMatch = property.match(isBinding);
var TranslationView = Ember._MetamorphView.extend({
if (isBindingMatch) {
propertyName = isBindingMatch[1];
bindPath = attrs[property];
currentValue = get(context, bindPath, options);
attrs[propertyName] = currentValue;
invoker = null;
normalized = Ember.Handlebars.normalizePath(context, bindPath, data);
_ref = [normalized.root, normalized.path], root = _ref[0], normalizedPath = _ref[1];
translationKey: null,
observer = function() {
var elem, newValue;
if (view.get('state') !== 'inDOM') {
Em.removeObserver(root, normalizedPath, invoker);
return;
}
newValue = get(context, bindPath, options);
elem = view.$("#" + elementID);
attrs[propertyName] = newValue;
return elem.html(I18n.t(key, attrs));
};
wrappingTagName: Ember.computed(function(propertyName, newValue) {
if (arguments.length > 1 && newValue != null) { return newValue; }
invoker = function() {
return Em.run.once(observer);
};
var useSpanByDefault;
return Em.addObserver(root, normalizedPath, invoker);
if (Ember.FEATURES.hasOwnProperty('I18N_TRANSLATE_HELPER_SPAN')) {
useSpanByDefault = Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN;
} else {
Ember.deprecate('The {{t}} helper will no longer use a <span> tag in future versions of Ember.I18n. Set Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN to false to quiet these warnings and maintain older behavior.');
useSpanByDefault = true;
}
return useSpanByDefault ? 'span' : null;
}),
render: function(buffer) {
var wrappingTagName = this.get('wrappingTagName');
var text = Ember.I18n.t(this.get('translationKey'), this.get('context'));
if (wrappingTagName) { buffer.push('<' + wrappingTagName + ' id="' + uniqueElementId() + '">'); }
buffer.push(text);
if (wrappingTagName) { buffer.push('</' + wrappingTagName + '>'); }
}
});
EmHandlebars.registerHelper('t', function(key, options) {
var context = this;
var data = options.data;
var attrs = options.hash;
var tagName = attrs.tagName;
delete attrs.tagName;
var translationView = TranslationView.create({
context: attrs,
translationKey: key,
wrappingTagName: tagName
});
result = '<%@ id="%@">%@</%@>'.fmt(tagName, elementID, I18n.t(key, attrs), tagName);
return new Handlebars.SafeString(result);
Ember.keys(attrs).forEach(function(property) {
var isBindingMatch = property.match(isBinding);
if (!isBindingMatch) { return; }
var propertyName = isBindingMatch[1];
var bindPath = attrs[property];
var currentValue = get(context, bindPath, options);
attrs[propertyName] = currentValue;
var invoker = null;
var normalized = EmHandlebars.normalizePath(context, bindPath, data);
var _ref = [normalized.root, normalized.path], root = _ref[0], normalizedPath = _ref[1];
var observer = function() {
if (translationView.$() == null) {
Ember.removeObserver(root, normalizedPath, invoker);
return;
}
attrs[propertyName] = get(context, bindPath, options);
translationView.rerender();
};
invoker = function() {
Ember.run.scheduleOnce('afterRender', observer);
};
return Ember.addObserver(root, normalizedPath, invoker);
});
data.view.appendChild(translationView);
});
Handlebars.registerHelper('translateAttr', function(options) {
var attrHelperFunction = function(options) {
var attrs, result;

@@ -150,3 +255,3 @@ attrs = options.hash;

Em.keys(attrs).forEach(function(property) {
Ember.keys(attrs).forEach(function(property) {
var translatedValue;

@@ -157,5 +262,8 @@ translatedValue = I18n.t(attrs[property]);

return new Handlebars.SafeString(result.join(' '));
});
return new EmHandlebars.SafeString(result.join(' '));
};
EmHandlebars.registerHelper('translateAttr', attrHelperFunction);
EmHandlebars.registerHelper('ta', attrHelperFunction);
}).call(undefined, this);
{
"name": "ember-i18n",
"version": "1.3.2",
"version": "2.1.1",
"licence": "APLv2",

@@ -24,4 +24,5 @@ "description": "Internationalization for Ember",

"mocha-phantomjs": "~2.0.1",
"jshint": "~2.0.1"
"jshint": "~2.0.1",
"handlebars": "~1.0.11"
}
}

@@ -7,82 +7,174 @@ ## Ember.I18n

Set `Em.I18n.translations` to an object containing your translation
information. If you want to support inflection based on `count`, you will
Set `Ember.I18n.translations` to an object containing your translation
information. If the values of `Ember.I18n.translations` are `Function`s,
they will be used as-is; if they are `String`s, they will first be
compiled via `Ember.I18n.compile`, which defaults to using
`Handlebars.compile`. (That means that if you haven't precompiled your
translations, you'll need to include the full Handlebars, not just
`handlebars-runtime.js` in your application.)
Set `Ember.ENV.I18N_COMPILE_WITHOUT_HANDLEBARS = true;` before including
ember-i18n to use the new translation compiler that does not depend on the full
Handlebars. The older Handlebars-based compiler has been deprecated and
will be removed in a future release.
If you want to support inflection based on `count`, you will
also need to include the
[CLDR.js pluralization library](https://github.com/jamesarosen/CLDR.js)
and set `CLDR.defaultLocale` to the current locale code (e.g. "de").
and set `CLDR.defaultLanguage` to the current locale code (e.g. "de").
### Examples
#### New: I18N_TRANSLATE_HELPER_SPAN
Given
In previous versions of Ember-I18n, the `{{t}}` helper emitted a `<span>` tag
by default; the tag name could be changed, but the tag could not be removed.
Em.I18n.translations = {
'user.edit.title': 'Edit User',
'user.followers.title.one': 'One Follower',
'user.followers.title.other': 'All {{count}} Followers',
'button.add_user.title': 'Add a user',
'button.add_user.text': 'Add',
'button.add_user.disabled': 'Saving...'
};
Ember-I18n now uses Metamorph tags so it no longer requires a wrapping tag.
Emitting a `<span>` is still the default for backwards-compatibility reasons,
but this will change in the next major release. If you wish to opt to
tagless translations, set
#### A simple translation:
```js
Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN = false;
```
<h2>{{t user.edit.title}}</h2>
The examples below assume this feature flag is set to `true` (the default).
yields
### Examples
<h2><span id="i18n-123">Edit User</span></h2>
Given
```javascript
Em.I18n.translations = {
'user.edit.title': 'Edit User',
'user.followers.title.one': 'One Follower',
'user.followers.title.other': 'All {{count}} Followers',
'button.add_user.title': 'Add a user',
'button.add_user.text': 'Add',
'button.add_user.disabled': 'Saving...'
};
```
#### Remove the `span` by specifying a `tagName`:
#### A simple translation:
```html
<h2>{{t "user.edit.title"}}</h2>
```
yields
```html
<h2>
<script id="metamorph-28-start"></script>
<span id="i18n-123">Edit User</span>
<script id="metamorph-28-end"></script>
</h2>
```
{{t user.edit.title tagName="h2"}}
#### Emit directly into the h2:
```html
{{t "user.edit.title" tagName="h2"}}
```
yields
```html
<script id="metamorph-28-start"></script>
<h2 id="i18n-123">Edit User</h2>
<script id="metamorph-28-end"></script>
```
#### Set interpolated values directly:
```html
<h2>{{t "user.followers.title" count="2"}}</h2>
```
yields
```html
<h2>
<script id="metamorph-28-start"></script>
<span id="i18n-123">All 2 Followers</span>
<script id="metamorph-28-end"></script>
</h2>
```
<h2 id="i18n-123">Edit User</h2>
#### Set interpoloated values directly:
<h2>{{t user.followers.title count="2"}}</h2>
#### Bind interpolated values:
```html
<h2>{{t "user.followers.title" countBinding="user.followers.count"}}</h2>
```
yields
```html
<h2>
<script id="metamorph-28-start"></script>
<span id="i18n-123">All 2 Followers</span>
<script id="metamorph-28-end"></script>
</h2>
```
if `user.getPath('followers.count')` returns `2`.
<h2><span id="i18n-123">All 2 Followers</span></h2>
#### Translate properties on any object:
#### Bind interpolated values:
The `Em.I18n.TranslateableProperties` mixin automatically translates
any property ending in `"Translation"`:
```javascript
userButton = Em.Object.extend(Em.I18n.TranslateableProperties, {
labelTranslation: 'button.add_user.title'
});
<h2>{{t user.followers.title countBinding="user.followers.count"}}</h2>
userButton.get('label');
```
yields
<h2><span id="i18n-123">All 2 Followers</span></h2>
"Add a user"
if `user.getPath('followers.count)` returns `2`.
#### Translate attributes in a view:
Add the mixin `Em.Button.reopen.call(Em.Button, Em.I18n.TranslateableAttributes)` and use like this:
Add the mixin `Em.Button.reopen(Em.I18n.TranslateableAttributes)` and use like this:
{{#view Em.Button titleTranslation="button.add_user.title">
{{t button.add_user.text}}
{{/view}}
```html
{{#view Em.Button titleTranslation="button.add_user.title">
{{t "button.add_user.text"}}
{{/view}}
```
yields
```html
<button title="Add a user">
<script id="metamorph-28-start"></script>
Add
<script id="metamorph-28-end"></script>
</button>
```
<button title="Add a user">
Add
</button>
#### Translate attributes on a plain tag:
<a {{translateAttr title="button.add_user.title"
data-disable-with="button.add_user.disabled"}}>
{{t button.add_user.text}}
</a>
```html
<a {{translateAttr title="button.add_user.title" data-disable-with="button.add_user.disabled"}}>
{{t "button.add_user.text"}}
</a>
```
yields
```html
<a title="Add a user" data-disable-with="Saving...">
<script id="metamorph-28-start"></script>
Add
<script id="metamorph-28-end"></script>
</a>
```
#### Nested Translation Syntax:
<a title="Add a user" data-disable-with="Saving...">
Add
</a>
The above translation data can also be expressed as nested JSON objects:
```javascript
Em.I18n.translations = {
'user': {
'edit': {
'title': 'Edit User'
},
'followers': {
'title': {
'one': 'One Follower',
'other': 'All {{count}} Followers'
}
}
},
'button': {
'add_user': {
'title': 'Add a user',
'text': 'Add',
'disabled': 'Saving...'
}
}
};
```
This format is often smaller and so makes downloading translation packs faster.

@@ -100,14 +192,2 @@ ### Limitations

To build ember-i18n from a clone of this repository, you will need to install
a few Ruby gems defined in the Gemfile and use Rake. These instructions assume
you have a recent version of Ruby and the Bundler gem.
To install the gems, use `bundle install`. This only needs to be done once.
You will also need a coffeescript compiler, if you don't already have one;
`npm install -g coffee-script` should take care of that for you. Then you
should be able to use the following Rake commands:
rake compile # Compiles coffeescript to JS
rake build:latest # Creates a build version, ember-i18n-latest.js, in the dist/ directory
For more detail on running tests and contributing, see [CONTRIBUTING.md](https://github.com/jamesarosen/ember-i18n/blob/master/CONTRIBUTING.md).
For more detail on running tests and contributing, see [CONTRIBUTING.md](https://github.com/jamesarosen/ember-i18n/blob/master/CONTRIBUTING.md).
(function () {
describe('Em.I18n', function () {
var view, controller;
describe('Ember.I18n', function () {
function render(template, options) {
if (options == null) options = {};
options.template = Em.Handlebars.compile(template);
view = Em.View.create({ controller: controller }, options);
Em.run(function() {
view.append();
});
}
beforeEach(function() {
controller = Em.Object.create();
this.originalTranslations = Em.I18n.translations;
Em.I18n.translations = {
'foo.bar': 'A Foobar',
'foo.bar.named': 'A Foobar named {{name}}',
'foo.save.disabled': 'Saving Foo...',
'foos.zero': 'No Foos',
'foos.one': 'One Foo',
'foos.other': 'All {{count}} Foos',
'bars.all': 'All {{count}} Bars',
baz: {
qux: 'A qux appears'
},
fum: {
one: 'A fum',
other: '{{count}} fums'
}
};
CLDR.defaultLanguage = 'ksh';
});
afterEach(function() {
if (view != null) view.destroy();
Em.I18n.translations = this.originalTranslations;
CLDR.defaultLanguage = null;
});
it('exists', function() {
expect(Em.I18n).to.not.equal(undefined);
expect(Ember.I18n).to.not.equal(undefined);
});
describe('.t', function() {
it('translates simple strings', function() {
expect(Em.I18n.t('foo.bar')).to.equal('A Foobar');
});
it('interpolates', function() {
expect(Em.I18n.t('foo.bar.named', {
name: 'Sue'
})).to.equal('A Foobar named Sue');
});
it('uses the "zero" form when the language calls for it', function() {
expect(Em.I18n.t('foos', {
count: 0
})).to.equal('No Foos');
});
it('uses the "one" form when the language calls for it', function() {
expect(Em.I18n.t('foos', {
count: 1
})).to.equal('One Foo');
});
it('interpolates count', function() {
expect(Em.I18n.t('foos', {
count: 21
})).to.equal('All 21 Foos');
});
it("works on keys that don't have count suffixes", function() {
expect(Em.I18n.t('bars.all', {
count: 532
})).to.equal('All 532 Bars');
});
it('warns about missing translations', function() {
expect(Em.I18n.t('nothing.here')).to.equal('Missing translation: nothing.here');
});
describe('using nested objects', function() {
it('works with a simple case', function() {
expect(Em.I18n.t('baz.qux')).to.equal('A qux appears');
});
it('works with counts', function() {
expect(Em.I18n.t('fum', {
count: 1
})).to.equal('A fum');
expect(Em.I18n.t('fum', {
count: 2
})).to.equal('2 fums');
});
});
it('prefers dotted keys to nested ones', function() {
Em.I18n.translations.foo = { bar: 'Nested foo.bar' };
expect(Em.I18n.t('foo.bar')).to.equal('A Foobar');
});
});
describe('{{t}}', function() {
it('outputs simple translated strings', function() {
render('{{t foo.bar}}');
Em.run(function() {
expect(view.$().text()).to.equal('A Foobar');
});
});
it('interpolates values', function() {
render('{{t bars.all count="597"}}');
Em.run(function() {
expect(view.$().text()).to.equal('All 597 Bars');
});
});
it('interpolates bindings', function() {
controller.set('count', 3);
render('{{t bars.all countBinding="count"}}');
Em.run(function() {
expect(view.$().text()).to.equal('All 3 Bars');
});
});
it('responds to updates on bound properties', function() {
controller.set('count', 3);
render('{{t bars.all countBinding="count"}}');
Em.run(function() {
controller.set('count', 4);
});
Em.run(function() {
expect(view.$().text()).to.equal('All 4 Bars');
});
});
it('does not error due to bound properties during a rerender', function() {
controller.set('count', 3);
render('{{t bars.all countBinding="count"}}');
expect(function() {
Em.run(function() {
view.rerender();
controller.set('count', 4);
});
}).to.not['throw']();
});
it('responds to updates on bound properties after a rerender', function() {
controller.set('count', 3);
render('{{t bars.all countBinding="count"}}');
Em.run(function() {
view.rerender();
controller.set('count', 4);
});
Em.run(function() {
expect(view.$().text()).to.equal('All 4 Bars');
});
});
it('obeys a custom tag name', function() {
render('{{t foo.bar tagName="h2"}}');
Em.run(function() {
expect(view.$('h2').html()).to.equal('A Foobar');
});
});
it('handles interpolations from contextual keywords', function() {
render('{{t foo.bar.named nameBinding="view.favouriteBeer" }}', {
favouriteBeer: 'IPA'
});
Em.run(function() {
expect(view.$().text()).to.equal('A Foobar named IPA');
});
});
it('responds to updates on bound keyword properties', function() {
render('{{t foo.bar.named nameBinding="view.favouriteBeer"}}', {
favouriteBeer: 'Lager'
});
expect(view.$().text()).to.equal('A Foobar named Lager');
Em.run(function() {
view.set('favouriteBeer', 'IPA');
});
Em.run(function() {
expect(view.$().text()).to.equal('A Foobar named IPA');
});
});
});
describe('{{{t}}}', function() {
it('does not over-escape translations', function() {
Em.I18n.translations['message.loading'] = '<span class="loading">Loading…</span>';
render('<div>{{{t "message.loading"}}}</div>');
Em.run(function() {
expect(view.$('.loading').length).to.equal(1);
expect(view.$('.loading').text()).to.equal('Loading…');
});
});
});
describe('{{translateAttr}}', function() {
it('outputs translated attribute strings', function() {
render('<a {{translateAttr title="foo.bar" data-disable-with="foo.save.disabled"}}></a>');
Em.run(function() {
expect(view.$('a').attr('title')).to.equal('A Foobar');
expect(view.$('a').attr('data-disable-with')).to.equal('Saving Foo...');
});
});
});
describe('TranslatableAttributes', function() {
it('exists', function() {
expect(Em.I18n.TranslateableAttributes).to.not.equal(undefined);
});
it('translates ___Translation attributes', function() {
Em.View.reopen(Em.I18n.TranslateableAttributes);
render('{{view titleTranslation="foo.bar"}}');
Em.run(function() {
expect(view.$().children().first().attr('title')).to.equal("A Foobar");
});
});
});
});
}).call(this);

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc