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

Allow overriding the locale for individual translations #312

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions addon/services/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,21 @@ export default Parent.extend(Evented, {
// A list of found locales.
locales: computed(getLocales),

// @public
// Whether to allow overriding the locale for individual translations.
allowLocaleOverride: undefined,

// @public
//
// Returns the translation `key` interpolated with `data`
// in the current `locale`.
t(key, data = {}) {
Ember.deprecate('locale is a reserved attribute', data['locale'] === undefined, {
id: 'ember-i18n.reserve-locale',
until: '5.0.0'
});

Ember.deprecate('htmlSafe is a reserved attribute', data['htmlSafe'] === undefined, {
id: 'ember-i18n.reserve-htmlSafe',
until: '5.0.0'
});

const locale = this.get('_locale');
const locale = this._getLocaleWithOverride(data);
assert("I18n: Cannot translate when locale is null", locale);
const count = get(data, 'count');

Expand All @@ -42,15 +41,15 @@ export default Parent.extend(Evented, {
const template = locale.getCompiledTemplate(defaults, count);

if (template._isMissing) {
this.trigger('missing', this.get('locale'), key, data);
this.trigger('missing', locale.id, key, data);
}

return template(data);
},

// @public
exists(key, data = {}) {
const locale = this.get('_locale');
const locale = this._getLocaleWithOverride(data);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the symmetry of t and exists now. I'm glad you did that extraction.

assert("I18n: Cannot check existance when locale is null", locale);
const count = get(data, 'count');

Expand Down Expand Up @@ -83,6 +82,10 @@ export default Parent.extend(Evented, {
}
this.set('locale', defaultLocale);
}

if (this.get('allowLocaleOverride') == null) {
this.set('allowLocaleOverride', (ENV.i18n || {}).allowLocaleOverride);
}
}),

// @private
Expand All @@ -96,5 +99,24 @@ export default Parent.extend(Evented, {
const locale = this.get('locale');

return locale ? new Locale(this.get('locale'), getOwner(this)) : null;
})
}),

// @private
// Retrieves the current locale, allowing it to be overridden by a `locale`
// option in the translation data if `allowLocaleOverride` is enabled.
_getLocaleWithOverride: function(data) {
let locale = this.get('_locale');

if (data.locale && this.get('allowLocaleOverride') !== false) {
Ember.warn(
'Specifying an interpolation key named `locale` now overrides the target locale for translation. To silence this warning, set ENV.i18n.allowLocaleOverride to true. To disable this behavior and treat `locale` as a normal interpolation key, set ENV.i18n.allowLocaleOverride to false.',
(this.get('allowLocaleOverride') !== undefined),
{ id: 'ember-i18n.locale-override' }
);
locale = new Locale(data.locale, getOwner(this));
delete data.locale;
}

return locale;
}
});
4 changes: 4 additions & 0 deletions tests/acceptance/t-helper-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ test('updates when the locale changes', function(assert) {
assert.textIs('.no-interpolations', 'texto sin interpolaciones');
});
});

test('can override the target locale', function(assert) {
assert.textIs('.locale-override', 'texto sin interpolaciones');
});
3 changes: 2 additions & 1 deletion tests/dummy/app/locales/en/translations.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ export default {
'no.interpolations.either': 'another text without interpolations',

'with': {
interpolations: 'Clicks: {{clicks}}'
interpolations: 'Clicks: {{clicks}}',
'locale.interpolation': 'Locale: {{locale}}'
},

'pluralized.translation': {
Expand Down
4 changes: 4 additions & 0 deletions tests/dummy/app/templates/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@
<section>
<button class='switch-to-es' {{action "switchLocale" "es"}}>Switch to Spanish</button>
</section>

<section>
<span class='locale-override'>{{t "no.interpolations" locale="es"}}</span>
</section>
6 changes: 6 additions & 0 deletions tests/unit/i18n-exists-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ test('non-existing translations when checked twice do not exist', function (asse
assert.equal(i18n.t('not.existing'), 'Missing translation: not.existing');
assert.equal(i18n.exists('not.existing'), false);
});

test('allows overriding the locale', function(assert) {
const i18n = this.subject({ locale: 'en' });

assert.equal(i18n.exists('no.interpolations.either', { locale: 'es' }), false);
});
60 changes: 53 additions & 7 deletions tests/unit/i18n-t-test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';

const { run, deprecate:originalDeprecate } = Ember;
const { run, deprecate:originalDeprecate, warn:originalWarn } = Ember;
const warnings = Ember.A();

moduleFor('service:i18n', 'I18nService#t', {
integration: true,

beforeEach() {
Ember.warn = function(message, test, options) {
if (!test) { warnings.pushObject(message); }
originalWarn(message, test, options);
};
},

afterEach() {
Ember.deprecate = originalDeprecate;
Ember.warn = originalWarn;
warnings.clear();
}
});

Expand Down Expand Up @@ -40,7 +50,7 @@ test('returns "missing translation" translations', function(assert) {
assert.equal('Missing translation: not.yet.translated', result);
});

test('warns on the presence of htmlSafe and locale', function(assert) {
test('warns on the presence of htmlSafe', function(assert) {
const service = this.subject();
let deprecations = 0;

Expand All @@ -49,20 +59,17 @@ test('warns on the presence of htmlSafe and locale', function(assert) {

const { id } = options;

if (id === 'ember-i18n.reserve-htmlSafe' || id === 'ember-i18n.reserve-locale') {
if (id === 'ember-i18n.reserve-htmlSafe') {
deprecations += 1;
}
};

service.t('not.yet.translated', { htmlSafe: true });
assert.equal(deprecations, 1);

service.t('not.yet.translated', { locale: true });
assert.equal(deprecations, 2);

service.t('not.yet.translated');
service.t('not.yet.translated', { some: 'other key' });
assert.equal(deprecations, 2);
assert.equal(deprecations, 1);
});

test('emits "missing" events', function(assert) {
Expand Down Expand Up @@ -120,3 +127,42 @@ test("check unknown locale", function(assert) {
const result = this.subject({ locale: 'uy' }).t('not.yet.translated', {count: 2});
assert.equal('Missing translation: not.yet.translated', result);
});

test("locale can be overridden and warns by default", function(assert) {
const i18n = this.subject({ locale: 'en' });

const result = i18n.t('no.interpolations', { locale: 'es' });
assert.equal(result, 'texto sin interpolaciones');
assert.equal(warnings.length, 1);
});

test("locale can be overridden and does not warn if allowLocaleOverride is true", function(assert) {
const i18n = this.subject({ locale: 'en', allowLocaleOverride: true });

const result = i18n.t('no.interpolations', { locale: 'es' });
assert.equal(result, 'texto sin interpolaciones');
assert.equal(warnings.length, 0);
});

test("locale can be used as an interpolation key if allowLocaleOverride is false", function(assert) {
const i18n = this.subject({ locale: 'en', allowLocaleOverride: false });

const result = i18n.t('with.locale.interpolation', { locale: 'es' });
assert.equal(result, 'Locale: es');
assert.equal(warnings.length, 0);
});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are awesome :)


test('emits "missing" events with an overridden locale', function(assert) {
const i18n = this.subject({ locale: 'en' });
const calls = [];
function spy() { calls.push(arguments); }
i18n.on('missing', spy);

i18n.t('not.yet.translated', { some: 'data', locale: 'es' });

assert.equal(calls.length, 1);
assert.equal(calls[0].length, 3);
assert.equal(calls[0][0], 'es');
assert.equal(calls[0][1], 'not.yet.translated');
assert.equal(calls[0][2].some, 'data');
});