diff --git a/doc/article/en-US/i18n-with-aurelia.md b/doc/article/en-US/i18n-with-aurelia.md index 333d1db2..6c72130a 100644 --- a/doc/article/en-US/i18n-with-aurelia.md +++ b/doc/article/en-US/i18n-with-aurelia.md @@ -438,6 +438,9 @@ property plugin `instance.setup` function parameter. +> Passing the option `skipTranslationOnMissingKey` during plugin initialization, will keep your original contents in place and instead add a warning in the console +about trying to update an element without a matching key. + Any element in your views that has one of those attributes, will be translated when the locale is changed. diff --git a/package.json b/package.json index d754751d..bb5302bf 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "url": "https://github.com/aurelia/i18n/issues" }, "scripts": { + "build": "gulp build", "precommit": "gulp lint", "test": "karma start" }, diff --git a/src/i18n.js b/src/i18n.js index 9ef793c8..1fbec491 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -1,9 +1,9 @@ /*eslint no-cond-assign: 0*/ import * as LogManager from 'aurelia-logging'; import i18next from 'i18next'; -import {DOM, PLATFORM} from 'aurelia-pal'; -import {EventAggregator} from 'aurelia-event-aggregator'; -import {BindingSignaler} from 'aurelia-templating-resources'; +import { DOM, PLATFORM } from 'aurelia-pal'; +import { EventAggregator } from 'aurelia-event-aggregator'; +import { BindingSignaler } from 'aurelia-templating-resources'; export class I18N { static inject = [EventAggregator, BindingSignaler]; @@ -27,6 +27,7 @@ export class I18N { setup(options?): Promise { const defaultOptions = { + skipTranslationOnMissingKey: false, compatibilityAPI: 'v1', compatibilityJSON: 'v1', lng: 'en', @@ -53,7 +54,7 @@ export class I18N { } setLocale(locale): Promise { - return new Promise( resolve => { + return new Promise(resolve => { let oldLocale = this.getLocale(); this.i18next.changeLanguage(locale, (err, tr) => { this.ea.publish('i18n:locale:changed', { oldValue: oldLocale, newValue: locale }); @@ -76,7 +77,7 @@ export class I18N { let comparer = nf.format(10000 / 3); let thousandSeparator = comparer[1]; - let decimalSeparator = comparer[5]; + let decimalSeparator = comparer[5]; if (thousandSeparator === '.') { thousandSeparator = '\\.'; @@ -190,18 +191,25 @@ export class I18N { // convert to camelCase const attrCC = attr.replace(/-([a-z])/g, function(g) { return g[1].toUpperCase(); }); const reservedNames = ['prepend', 'append', 'text', 'html']; + const i18nLogger = LogManager.getLogger('i18n'); + if (reservedNames.indexOf(attr) > -1 && - node.au && - node.au.controller && - node.au.controller.viewModel && - attrCC in node.au.controller.viewModel) { - const i18nLogger = LogManager.getLogger('i18n'); + node.au && + node.au.controller && + node.au.controller.viewModel && + attrCC in node.au.controller.viewModel) { i18nLogger.warn(`Aurelia I18N reserved attribute name\n [${reservedNames.join(', ')}]\n Your custom element has a bindable named ${attr} which is a reserved word.\n If you'd like Aurelia I18N to translate your bindable instead, please consider giving it another name.`); } + if (this.i18next.options.skipTranslationOnMissingKey && + this.tr(key, params) === key) { + i18nLogger.warn(`Couldn't find translation for key: ${key}`); + return; + } + //handle various attributes //anything other than text,prepend,append or html will be added as an attribute on the element. switch (attr) { @@ -254,9 +262,9 @@ If you'd like Aurelia I18N to translate your bindable instead, please consider g break; default: //normal html attribute if (node.au && - node.au.controller && - node.au.controller.viewModel && - attrCC in node.au.controller.viewModel) { + node.au.controller && + node.au.controller.viewModel && + attrCC in node.au.controller.viewModel) { node.au.controller.viewModel[attrCC] = this.tr(key, params); } else { node.setAttribute(attr, this.tr(key, params)); diff --git a/test/unit/relative.time.spec.js b/test/unit/relative.time.spec.js index 6cde107c..0094e217 100644 --- a/test/unit/relative.time.spec.js +++ b/test/unit/relative.time.spec.js @@ -223,6 +223,7 @@ describe('testing relative time support', () => { signaler.signal('aurelia-relativetime-signal'); expect(elem.innerHTML).toBe('1 second ago'); + component.dispose(); done(); }, 1000); }); diff --git a/test/unit/skiptranslation.spec.js b/test/unit/skiptranslation.spec.js new file mode 100644 index 00000000..5019ada6 --- /dev/null +++ b/test/unit/skiptranslation.spec.js @@ -0,0 +1,34 @@ +import { StageComponent } from 'aurelia-testing'; +import { bootstrap } from 'aurelia-bootstrapper'; + +import { bootstrapTestEnvironment } from './staging-helpers'; + +describe('staged tests', () => { + it('should keep original value instead of fallback to key, when defined via options', done => { + const target = 'fallback-target'; + const component = StageComponent + .withResources('test/unit/mocks/rt-vm') + .inView('
Hello!
') + .boundTo({ mydate: new Date() }); + + bootstrapTestEnvironment(component, { + resources: { + en: { + translation: { + hello: undefined + } + } + }, + skipTranslationOnMissingKey: true + }); + + component.create(bootstrap) + .then(() => { + const elem = document.getElementById(target); + expect(elem.innerHTML).toBe('Hello!'); + + component.dispose(); + done(); + }); + }); +}); diff --git a/test/unit/staging-helpers.js b/test/unit/staging-helpers.js index 42b253d7..e6ee3f9e 100644 --- a/test/unit/staging-helpers.js +++ b/test/unit/staging-helpers.js @@ -1,6 +1,6 @@ import { Backend } from '../../src/aurelia-i18n-loader'; -export function bootstrapTestEnvironment(component, resources) { +export function bootstrapTestEnvironment(component, config) { component.bootstrap((aurelia) => { aurelia.use .standardConfiguration() @@ -10,11 +10,11 @@ export function bootstrapTestEnvironment(component, resources) { // register backend plugin instance.i18next.use(Backend.with(aurelia.loader)); - return instance.setup({ + + return instance.setup(Object.assign({ backend: { // <-- configure backend settings loadPath: './locales/{{lng}}/{{ns}}.json' // <-- XHR settings for where to get the files from }, - resources, interpolation: { prefix: '{{', suffix: '}}' @@ -24,7 +24,7 @@ export function bootstrapTestEnvironment(component, resources) { defaultNS: 'translation', fallbackLng: 'en', debug: false - }); + }, config)); }); }); } diff --git a/test/unit/t-attribute.spec.js b/test/unit/t-attribute.spec.js index cdce631a..bc8b87c3 100644 --- a/test/unit/t-attribute.spec.js +++ b/test/unit/t-attribute.spec.js @@ -12,13 +12,18 @@ describe('t-attribute', () => { .inView(`
`) .boundTo({ integer: 1 }); - bootstrapTestEnvironment(component, {en: { translation: { '1': expectedValue}}}); + bootstrapTestEnvironment(component, { + resources: { + en: { translation: { '1': expectedValue } } + } + }); component.create(bootstrap) .then(() => { const elem = document.getElementById(target); expect(elem.innerHTML).toBe(expectedValue); + component.dispose(); done(); }); });