Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.

Commit

Permalink
Merge pull request #112 from jamesarosen/tagless
Browse files Browse the repository at this point in the history
Support turning off <span>s by default
  • Loading branch information
jamesarosen committed Aug 8, 2014
2 parents d7100a4 + 033a809 commit 811ce6b
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 59 deletions.
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ also need to include the
[CLDR.js pluralization library](https://github.com/jamesarosen/CLDR.js)
and set `CLDR.defaultLanguage` to the current locale code (e.g. "de").

#### New: I18N_TRANSLATE_HELPER_SPAN

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.

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

```js
Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN = false;
```

The examples below assume this feature flag is set to `true` (the default).

### Examples

Given
Expand All @@ -35,37 +51,55 @@ Em.I18n.translations = {
'button.add_user.disabled': 'Saving...'
};
```

#### A simple translation:
```html
<h2>{{t "user.edit.title"}}</h2>
```
yields
```html
<h2><span id="i18n-123">Edit User</span></h2>
<h2>
<script id="metamorph-28-start"></script>
<span id="i18n-123">Edit User</span>
<script id="metamorph-28-end"></script>
</h2>
```
#### Remove the `span` by specifying a `tagName`:

#### 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><span id="i18n-123">All 2 Followers</span></h2>
<h2>
<script id="metamorph-28-start"></script>
<span id="i18n-123">All 2 Followers</span>
<script id="metamorph-28-end"></script>
</h2>
```

#### Bind interpolated values:
```html
<h2>{{t "user.followers.title" countBinding="user.followers.count"}}</h2>
```
yields
```html
<h2><span id="i18n-123">All 2 Followers</span></h2>
<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`.

Expand Down Expand Up @@ -96,7 +130,9 @@ Add the mixin `Em.Button.reopen(Em.I18n.TranslateableAttributes)` and use like t
yields
```html
<button title="Add a user">
<script id="metamorph-28-start"></script>
Add
<script id="metamorph-28-end"></script>
</button>
```

Expand All @@ -109,7 +145,9 @@ yields
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:
Expand Down
107 changes: 68 additions & 39 deletions lib/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,51 +159,80 @@
return ++Ember.uuid;
}

var TranslationView = Ember._MetamorphView.extend({

translationKey: null,

wrappingTagName: Ember.computed(function(propertyName, newValue) {
if (arguments.length > 1 && newValue != null) { return newValue; }

var useSpanByDefault;

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 attrs, context, data, elementID, result, tagName, view;
context = this;
attrs = options.hash;
data = options.data;
view = data.view;
tagName = attrs.tagName || 'span';
var context = this;
var data = options.data;
var attrs = options.hash;
var tagName = attrs.tagName;
delete attrs.tagName;
elementID = uniqueElementId();

var translationView = TranslationView.create({
context: attrs,
translationKey: key,
wrappingTagName: tagName
});

Ember.keys(attrs).forEach(function(property) {
var bindPath, currentValue, invoker, isBindingMatch, normalized, normalizedPath, observer, propertyName, root, _ref;
isBindingMatch = property.match(isBinding);

if (isBindingMatch) {
propertyName = isBindingMatch[1];
bindPath = attrs[property];
currentValue = get(context, bindPath, options);
attrs[propertyName] = currentValue;
invoker = null;
normalized = EmHandlebars.normalizePath(context, bindPath, data);
_ref = [normalized.root, normalized.path], root = _ref[0], normalizedPath = _ref[1];

observer = function() {
var elem, newValue;
if (view.$() == null) {
Ember.removeObserver(root, normalizedPath, invoker);
return;
}
newValue = get(context, bindPath, options);
elem = view.$("#" + elementID);
attrs[propertyName] = newValue;
return elem.html(I18n.t(key, attrs));
};

invoker = function() {
Ember.run.scheduleOnce('afterRender', observer);
};

return Ember.addObserver(root, normalizedPath, invoker);
}
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);
});

result = '<%@ id="%@">%@</%@>'.fmt(tagName, elementID, I18n.t(key, attrs), tagName);
return new EmHandlebars.SafeString(result);
data.view.appendChild(translationView);
});

var attrHelperFunction = function(options) {
Expand Down
2 changes: 2 additions & 0 deletions spec/spec_support.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
mocha.globals([ 'jQuery*' ]);
}

Ember.FEATURES = Ember.FEATURES || {};

function renderTemplate(template, options) {
if (options == null) options = {};
options.template = Ember.Handlebars.compile(template);
Expand Down
1 change: 1 addition & 0 deletions spec/suite.hdbs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<script src="./translationExistsSpec.js"></script>
<script src="./tSpec.js"></script>
<script src="./translateHelperSpec.js"></script>
<script src="./taglessTranslateHelperSpec.js"></script>
<script src="./translateAttributeHelperSpec.js"></script>
<script src="./eachTranslatedAttributeSpec.js"></script>
<script src="./translateablePropertiesSpec.js"></script>
Expand Down
62 changes: 62 additions & 0 deletions spec/taglessTranslateHelperSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
describe("{{t}}", function() {

describe("with Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN on", function() {

beforeEach(function() {
Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN = true;
});

it("emits a <span> by default", function() {
var view = this.renderTemplate('{{t "foo.bar"}}');
expect(view.$('span').text()).to.equal('A Foobar');
});

it("includes an element ID for backwards compatibility", function() {
var view = this.renderTemplate('{{t "foo.bar"}}');
expect(view.$('span').attr('id')).to.not.equal(undefined);
});

it('obeys a custom tag name', function() {
var view = this.renderTemplate('{{t "foo.bar" tagName="h2"}}');
expect(view.$('h2').html()).to.equal('A Foobar');
});

});

describe("with Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN off", function() {

beforeEach(function() {
Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN = false;
});

it("doesn't emit a <span> by default", function() {
var view = this.renderTemplate('{{t "foo.bar"}}');
expect(view.$('span').length).to.equal(0);
expect(view.$().text()).to.equal('A Foobar');
});

it("interpolates values", function() {
var view = this.renderTemplate('{{t "bars.all" count="597"}}');
expect(view.$().text()).to.equal('All 597 Bars');
});

it("still supports setting a tagName", function() {
var view = this.renderTemplate('{{t "foo.bar" tagName="span"}}');
expect(view.$('span').text()).to.equal('A Foobar');
});

it("includes an element ID when tagName is specified for backwards compatibility", function() {
var view = this.renderTemplate('{{t "foo.bar" tagName="span"}}');
expect(view.$('span').attr('id')).to.not.equal(undefined);
});

it("updates text", function() {
var view = this.renderTemplate('{{t "bars.all" countBinding="view.count"}}', { count: 992 });
expect(view.$().text()).not.to.equal('All 993 Bars');
Ember.run(view, 'set', 'count', 993);
expect(view.$().text()).to.equal('All 993 Bars');
});

});

});
22 changes: 6 additions & 16 deletions spec/translateHelperSpec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
describe('{{t}}', function() {

beforeEach(function() {
// compatibility mode:
Ember.FEATURES.I18N_TRANSLATE_HELPER_SPAN = true;
});

it('outputs simple translated strings', function() {
var view = this.renderTemplate('{{t "foo.bar"}}');

Expand Down Expand Up @@ -59,22 +65,6 @@ describe('{{t}}', function() {
});
});

it('uses a span by default', function() {
var view = this.renderTemplate('{{t "foo.bar"}}');

Ember.run(function() {
expect(view.$('span').html()).to.equal('A Foobar');
});
});

it('obeys a custom tag name', function() {
var view = this.renderTemplate('{{t "foo.bar" tagName="h2"}}');

Ember.run(function() {
expect(view.$('h2').html()).to.equal('A Foobar');
});
});

it('handles interpolations from contextual keywords', function() {
var view = this.renderTemplate('{{t "foo.bar.named" nameBinding="view.favouriteBeer" }}', {
favouriteBeer: 'IPA'
Expand Down

0 comments on commit 811ce6b

Please sign in to comment.