Skip to content

Commit

Permalink
feat(i18n): allow multiple attributes with the same key by listing th…
Browse files Browse the repository at this point in the history
…em comma-seperated

translations via the t-attribute now accept multiple attributes with either the same or various bound values by separating them via comma or semi-colons.
  • Loading branch information
doktordirk authored and zewa666 committed Jun 6, 2018
1 parent b9d147a commit 666cfba
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 78 deletions.
10 changes: 9 additions & 1 deletion doc/article/en-US/i18n-with-aurelia.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,15 @@ The following example will not change the content of the element, but will set i

#### Specifying multiple attributes

Multiple attributes can be specified by separating them with a semicolon.
Multiple attributes for the same key can be specified by separating them with a comma.

```HTML
<input t="[placeholder,aria-placeholder]placeholder">
```

When the locale changes it will set the `placeholder` and the `aria-placeholder` of the input element to the translated value of `placeholder`.

Multiple attributes for different keys can be specified by separating them with a semicolon.

```HTML
<span t="[html]title;[class]title-class">Title</span>
Expand Down
159 changes: 83 additions & 76 deletions src/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export class I18N {
while (i--) {
let key = keys[i];
// remove the optional attribute
let re = /\[([a-z\-]*)\]/ig;
let re = /\[([a-z\-, ]*)\]/ig;

let m;
let attr = 'text';
Expand All @@ -185,92 +185,99 @@ export class I18N {
}
}

if (!node._textContent) node._textContent = node.textContent;
if (!node._innerHTML) node._innerHTML = node.innerHTML;

// 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) {
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.`);
}
let attrs = attr.split(',');
let j = attrs.length;

if (this.i18next.options.skipTranslationOnMissingKey &&
this.tr(key, params) === key) {
i18nLogger.warn(`Couldn't find translation for key: ${key}`);
return;
}
while (j--) {
attr = attrs[j].trim();

if (!node._textContent) node._textContent = node.textContent;
if (!node._innerHTML) node._innerHTML = node.innerHTML;

//handle various attributes
//anything other than text,prepend,append or html will be added as an attribute on the element.
switch (attr) {
case 'text':
let newChild = DOM.createTextNode(this.tr(key, params));
if (node._newChild && node._newChild.parentNode === node) {
node.removeChild(node._newChild);
// 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) {
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.`);
}

node._newChild = newChild;
while (node.firstChild) {
node.removeChild(node.firstChild);
if (this.i18next.options.skipTranslationOnMissingKey &&
this.tr(key, params) === key) {
i18nLogger.warn(`Couldn't find translation for key: ${key}`);
return;
}
node.appendChild(node._newChild);
break;
case 'prepend':
let prependParser = DOM.createElement('div');
prependParser.innerHTML = this.tr(key, params);
for (let ni = node.childNodes.length - 1; ni >= 0; ni--) {
if (node.childNodes[ni]._prepended) {
node.removeChild(node.childNodes[ni]);

//handle various attributes
//anything other than text,prepend,append or html will be added as an attribute on the element.
switch (attr) {
case 'text':
let newChild = DOM.createTextNode(this.tr(key, params));
if (node._newChild && node._newChild.parentNode === node) {
node.removeChild(node._newChild);
}
}

for (let pi = prependParser.childNodes.length - 1; pi >= 0; pi--) {
prependParser.childNodes[pi]._prepended = true;
if (node.firstChild) {
node.insertBefore(prependParser.childNodes[pi], node.firstChild);
} else {
node.appendChild(prependParser.childNodes[pi]);
node._newChild = newChild;
while (node.firstChild) {
node.removeChild(node.firstChild);
}
}
break;
case 'append':
let appendParser = DOM.createElement('div');
appendParser.innerHTML = this.tr(key, params);
for (let ni = node.childNodes.length - 1; ni >= 0; ni--) {
if (node.childNodes[ni]._appended) {
node.removeChild(node.childNodes[ni]);
node.appendChild(node._newChild);
break;
case 'prepend':
let prependParser = DOM.createElement('div');
prependParser.innerHTML = this.tr(key, params);
for (let ni = node.childNodes.length - 1; ni >= 0; ni--) {
if (node.childNodes[ni]._prepended) {
node.removeChild(node.childNodes[ni]);
}
}
}

while (appendParser.firstChild) {
appendParser.firstChild._appended = true;
node.appendChild(appendParser.firstChild);
}
break;
case 'html':
node.innerHTML = this.tr(key, params);
break;
default: //normal html attribute
if (node.au &&
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));
}
for (let pi = prependParser.childNodes.length - 1; pi >= 0; pi--) {
prependParser.childNodes[pi]._prepended = true;
if (node.firstChild) {
node.insertBefore(prependParser.childNodes[pi], node.firstChild);
} else {
node.appendChild(prependParser.childNodes[pi]);
}
}
break;
case 'append':
let appendParser = DOM.createElement('div');
appendParser.innerHTML = this.tr(key, params);
for (let ni = node.childNodes.length - 1; ni >= 0; ni--) {
if (node.childNodes[ni]._appended) {
node.removeChild(node.childNodes[ni]);
}
}

while (appendParser.firstChild) {
appendParser.firstChild._appended = true;
node.appendChild(appendParser.firstChild);
}
break;
case 'html':
node.innerHTML = this.tr(key, params);
break;
default: //normal html attribute
if (node.au &&
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));
}

break;
break;
}
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion test/unit/fixtures/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ <h1 t="title" id="test1">WrongTitle</h1>
<p t="[html]description2;[class]description-class" id="test-multiple">
Wrong Description <b>with some bold</b>
</p>


<p t="[text,alt, aria-label]description" id="test-multiple-attributes">
Wrong Description
</p>

<img data-src="wrong-testimage-english.jpg" t="testimage" id="test-img"/>

</template>
18 changes: 18 additions & 0 deletions test/unit/i18n.update-translations.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,24 @@ describe('testing i18n translation update', () => {
});
});

it('should set multiple attributes when separated with a comma', done => {
let el = template.querySelector('#test-multiple-attributes');
expect(el.innerHTML.trim()).toBe('Wrong Description');
expect(el.getAttribute('alt')).toBe(null);
expect(el.getAttribute('aria-label')).toBe(null);
sut.setLocale('de').then(() => {
expect(el.innerHTML.trim()).toBe('Beschreibung');
expect(el.getAttribute('alt')).toBe('Beschreibung');
expect(el.getAttribute('aria-label')).toBe('Beschreibung');
return sut.setLocale('en');
}).then(() => {
expect(el.innerHTML.trim()).toBe('Description');
expect(el.getAttribute('alt')).toBe('Description');
expect(el.getAttribute('aria-label')).toBe('Description');
done();
});
});

it('should set the src attribute for images', done => {
let el = template.querySelector('#test-img');
expect(el.getAttribute('src')).toBeNull();
Expand Down

0 comments on commit 666cfba

Please sign in to comment.