Skip to content

Commit 24b7b2b

Browse files
authored
fix(directive): avoid recursive errors when using keys with whitespaces
Co-authored-by: David Störmer <[email protected]> Fixes #998 #1153 #1163
1 parent 34e8add commit 24b7b2b

File tree

2 files changed

+100
-28
lines changed

2 files changed

+100
-28
lines changed

projects/ngx-translate/core/src/lib/translate.directive.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,24 @@ export class TranslateDirective implements AfterViewChecked, OnDestroy {
6969
let node: any = nodes[i];
7070
if (node.nodeType === 3) { // node type 3 is a text node
7171
let key: string;
72-
if (this.key) {
72+
if (forceUpdate) {
73+
node.lastKey = null;
74+
}
75+
if(isDefined(node.lookupKey)) {
76+
key = node.lookupKey;
77+
} else if (this.key) {
7378
key = this.key;
74-
if (forceUpdate) {
75-
node.lastKey = null;
76-
}
7779
} else {
7880
let content = this.getContent(node);
7981
let trimmedContent = content.trim();
8082
if (trimmedContent.length) {
81-
if (node.originalContent && forceUpdate) { // the content seems ok, but the lang has changed
82-
node.lastKey = null;
83+
node.lookupKey = trimmedContent;
84+
// we want to use the content as a key, not the translation value
85+
if (content !== node.currentValue) {
86+
key = trimmedContent;
87+
// the content was changed from the user, we'll use it as a reference if needed
88+
node.originalContent = content || node.originalContent;
89+
} else if (node.originalContent) { // the content seems ok, but the lang has changed
8390
// the current content is the translation, not the key, use the last real content as key
8491
key = node.originalContent.trim();
8592
} else if (content !== node.currentValue) {

projects/ngx-translate/core/tests/translate.directive.spec.ts

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,32 @@ import {TranslateModule, TranslateService} from '../src/public_api';
77
selector: 'hmx-app',
88
changeDetection: ChangeDetectionStrategy.OnPush,
99
template: `
10-
<div #noKey translate>
11-
TEST
12-
</div>
13-
<div #withKey [translate]="'TEST'">Some init content</div>
14-
<div #noContent [translate]="'TEST'"></div>
15-
<div #withOtherElements translate>TEST1 <span>Hey</span> TEST2</div>
16-
<div #withParams [translate]="'TEST'" [translateParams]="value">Some init content</div>
17-
<div #withParamsNoKey translate [translateParams]="value">TEST</div>
10+
<div #noKey translate>TEST</div>
11+
<div #contentAsKey translate>TEST.VALUE</div>
12+
<div #withKey [translate]="'TEST'">Some init content</div>
13+
<div #noContent [translate]="'TEST'"></div>
14+
<div #withOtherElements translate>TEST1 <span>Hey</span> TEST2</div>
15+
<div #withParams [translate]="'TEST'" [translateParams]="value">Some init content</div>
16+
<div #withParamsNoKey translate [translateParams]="value">TEST</div>
17+
<div #leadingSpaceNoKeyNoParams translate> TEST</div>
18+
<div #trailingSpaceNoKeyNoParams translate>TEST </div>
19+
<div #withSpaceAndLineBreakNoKeyNoParams translate>
20+
TEST
21+
</div>
1822
`
1923
})
2024
class App {
2125
viewContainerRef: ViewContainerRef;
2226
@ViewChild('noKey', {static: true}) noKey: ElementRef;
27+
@ViewChild('contentAsKey', {static: true}) contentAsKey: ElementRef;
2328
@ViewChild('withKey', {static: true}) withKey: ElementRef;
2429
@ViewChild('withOtherElements', {static: true}) withOtherElements: ElementRef;
2530
@ViewChild('withParams', {static: true}) withParams: ElementRef;
2631
@ViewChild('withParamsNoKey', {static: true}) withParamsNoKey: ElementRef;
2732
@ViewChild('noContent', {static: true}) noContent: ElementRef;
33+
@ViewChild('leadingSpaceNoKeyNoParams') leadingSpaceNoKeyNoParams: ElementRef;
34+
@ViewChild('trailingSpaceNoKeyNoParams') trailingSpaceNoKeyNoParams: ElementRef;
35+
@ViewChild('withSpaceAndLineBreakNoKeyNoParams') withSpaceAndLineBreakNoKeyNoParams: ElementRef;
2836
value = {value: 'ok'};
2937

3038
constructor(viewContainerRef: ViewContainerRef) {
@@ -43,7 +51,7 @@ describe('TranslateDirective', () => {
4351
],
4452
declarations: [App]
4553
});
46-
translate = TestBed.get(TranslateService);
54+
translate = TestBed.inject(TranslateService);
4755

4856
fixture = (<any>TestBed).createComponent(App);
4957
fixture.detectChanges();
@@ -55,12 +63,21 @@ describe('TranslateDirective', () => {
5563
});
5664

5765
it('should translate a string using the container value', () => {
58-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(' TEST ');
66+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual('TEST');
5967

6068
translate.setTranslation('en', {"TEST": "This is a test"});
6169
translate.use('en');
6270

63-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(' This is a test ');
71+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual('This is a test');
72+
});
73+
74+
it('should translate a string using the container value as a key', () => {
75+
expect(fixture.componentInstance.contentAsKey.nativeElement.innerHTML).toEqual('TEST.VALUE');
76+
77+
translate.setTranslation('en', {"TEST": {"VALUE": "This is a test"}});
78+
translate.use('en');
79+
80+
expect(fixture.componentInstance.contentAsKey.nativeElement.innerHTML).toEqual('This is a test');
6481
});
6582

6683
it('should translate a string using the key value', () => {
@@ -130,57 +147,105 @@ describe('TranslateDirective', () => {
130147
expect(fixture.componentInstance.withParamsNoKey.nativeElement.innerHTML).toEqual('It is changed');
131148
});
132149

150+
it('should update the DOM when the lang changes and the translation key starts with space', () => {
151+
expect(fixture.componentInstance.leadingSpaceNoKeyNoParams.nativeElement.innerHTML).toEqual(' TEST');
152+
153+
const en = "This is a test - with leading spaces in translation key";
154+
const fr = "C'est un test - avec un espace de tête dans la clé de traduction";
155+
const leadingSpaceFromKey = ' ';
156+
translate.setTranslation('en', {"TEST": en});
157+
translate.setTranslation('fr', {"TEST": fr});
158+
159+
translate.use('en');
160+
expect(fixture.componentInstance.leadingSpaceNoKeyNoParams.nativeElement.innerHTML).toEqual(leadingSpaceFromKey + en);
161+
162+
translate.use('fr');
163+
expect(fixture.componentInstance.leadingSpaceNoKeyNoParams.nativeElement.innerHTML).toEqual(leadingSpaceFromKey + fr);
164+
});
165+
166+
it('should update the DOM when the lang changes and the translation key has line breaks and spaces', () => {
167+
expect(fixture.componentInstance.withSpaceAndLineBreakNoKeyNoParams.nativeElement.innerHTML).toEqual(' TEST ');
168+
169+
const en = "This is a test - with trailing spaces in translation key";
170+
const fr = "C'est un test - avec un espace de fuite dans la clé de traduction";
171+
const whiteSpaceFromKey = ' ';
172+
translate.setTranslation('en', {"TEST": en});
173+
translate.setTranslation('fr', {"TEST": fr});
174+
175+
translate.use('en');
176+
expect(fixture.componentInstance.withSpaceAndLineBreakNoKeyNoParams.nativeElement.innerHTML).toEqual(whiteSpaceFromKey + en + whiteSpaceFromKey);
177+
178+
translate.use('fr');
179+
expect(fixture.componentInstance.withSpaceAndLineBreakNoKeyNoParams.nativeElement.innerHTML).toEqual(whiteSpaceFromKey + fr + whiteSpaceFromKey);
180+
});
181+
182+
it('should update the DOM when the lang changes and the translation key ends with space', () => {
183+
expect(fixture.componentInstance.trailingSpaceNoKeyNoParams.nativeElement.innerHTML).toEqual('TEST ');
184+
185+
const en = "This is a test - with spaces and line breaks in translation key";
186+
const fr = "C'est un test - avec des espaces et sauts de lignes dans la clé de traduction";
187+
const trailingSpaceFromKey = ' ';
188+
translate.setTranslation('en', {"TEST": en});
189+
translate.setTranslation('fr', {"TEST": fr});
190+
191+
translate.use('en');
192+
expect(fixture.componentInstance.trailingSpaceNoKeyNoParams.nativeElement.innerHTML).toEqual(en + trailingSpaceFromKey);
193+
194+
translate.use('fr');
195+
expect(fixture.componentInstance.trailingSpaceNoKeyNoParams.nativeElement.innerHTML).toEqual(fr + trailingSpaceFromKey);
196+
});
197+
133198
it('should update the DOM when the lang changes', () => {
134-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(' TEST ');
199+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual('TEST');
135200
expect(fixture.componentInstance.withParams.nativeElement.innerHTML).toEqual('TEST');
136201
expect(fixture.componentInstance.noContent.nativeElement.innerHTML).toEqual('TEST');
137202

138203
translate.setTranslation('en', {"TEST": "This is a test"});
139204
translate.setTranslation('fr', {"TEST": "C'est un test"});
140205

141206
translate.use('en');
142-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(' This is a test ');
207+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual('This is a test');
143208
expect(fixture.componentInstance.withParams.nativeElement.innerHTML).toEqual('This is a test');
144209
expect(fixture.componentInstance.noContent.nativeElement.innerHTML).toEqual('This is a test');
145210

146211
translate.use('fr');
147-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(" C'est un test ");
212+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual("C'est un test");
148213
expect(fixture.componentInstance.withParams.nativeElement.innerHTML).toEqual("C'est un test");
149214
expect(fixture.componentInstance.noContent.nativeElement.innerHTML).toEqual("C'est un test");
150215
});
151216

152217
it('should update the DOM when the lang changes and the translation ends with space', () => {
153-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(' TEST ');
218+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual('TEST');
154219
expect(fixture.componentInstance.withParams.nativeElement.innerHTML).toEqual('TEST');
155220
expect(fixture.componentInstance.noContent.nativeElement.innerHTML).toEqual('TEST');
156221

157-
const en=" This is a test - with spaces ";
158-
const fr=" C'est un test - pardon, je ne parle pas francais :) ";
222+
const en = " This is a test - with spaces ";
223+
const fr = " C'est un test - avec espaces ";
159224

160225
translate.setTranslation('en', {"TEST": en});
161226
translate.setTranslation('fr', {"TEST": fr});
162227

163228
translate.use('en');
164-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(` ${en} `);
229+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(`${en}`);
165230
expect(fixture.componentInstance.withParams.nativeElement.innerHTML).toEqual(en);
166231
expect(fixture.componentInstance.noContent.nativeElement.innerHTML).toEqual(en);
167232

168233
translate.use('fr');
169-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(` ${fr} `);
234+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(`${fr}`);
170235
expect(fixture.componentInstance.withParams.nativeElement.innerHTML).toEqual(fr);
171236
expect(fixture.componentInstance.noContent.nativeElement.innerHTML).toEqual(fr);
172237
});
173238

174239
it('should update the DOM when the default lang changes', () => {
175-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(' TEST ');
240+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual('TEST');
176241

177242
translate.setTranslation('en', {"TEST": "This is a test"});
178243
translate.setTranslation('fr', {"TEST": "C'est un test"});
179244
translate.setDefaultLang('en');
180-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(' This is a test ');
245+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual('This is a test');
181246

182247
translate.setDefaultLang('fr');
183-
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual(" C'est un test ");
248+
expect(fixture.componentInstance.noKey.nativeElement.innerHTML).toEqual("C'est un test");
184249
});
185250

186251
it('should unsubscribe from lang change subscription on destroy', () => {

0 commit comments

Comments
 (0)