| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-03-02 09:37:01 -08:00
										 |  |  | import {escapeRegExp} from '@angular/compiler/src/util'; | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  | import {serializeNodes} from '../../../src/i18n/digest'; | 
					
						
							| 
									
										
										
										
											2017-01-25 15:55:17 -08:00
										 |  |  | import * as i18n from '../../../src/i18n/i18n_ast'; | 
					
						
							| 
									
										
										
										
											2016-08-09 21:05:04 -07:00
										 |  |  | import {Xtb} from '../../../src/i18n/serializers/xtb'; | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-01-25 15:55:17 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-16 14:42:55 -08:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |   describe('XTB serializer', () => { | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |     const serializer = new Xtb(); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |     function loadAsMap(xtb: string): {[id: string]: string} { | 
					
						
							| 
									
										
										
										
											2017-02-03 14:29:28 -08:00
										 |  |  |       const {i18nNodesByMsgId} = serializer.load(xtb, 'url'); | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |       const msgMap: {[id: string]: string} = {}; | 
					
						
							|  |  |  |       Object.keys(i18nNodesByMsgId).forEach(id => { | 
					
						
							|  |  |  |         msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       return msgMap; | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('load', () => { | 
					
						
							| 
									
										
										
										
											2016-07-29 13:07:01 -07:00
										 |  |  |       it('should load XTB files with a doctype', () => { | 
					
						
							|  |  |  |         const XTB = `<?xml version="1.0" encoding="UTF-8"?>
 | 
					
						
							|  |  |  | <!DOCTYPE translationbundle [<!ELEMENT translationbundle (translation)*> | 
					
						
							|  |  |  | <!ATTLIST translationbundle lang CDATA #REQUIRED> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <!ELEMENT translation (#PCDATA|ph)*> | 
					
						
							|  |  |  | <!ATTLIST translation id CDATA #REQUIRED> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <!ELEMENT ph EMPTY> | 
					
						
							|  |  |  | <!ATTLIST ph name CDATA #REQUIRED> | 
					
						
							|  |  |  | ]> | 
					
						
							|  |  |  | <translationbundle> | 
					
						
							| 
									
										
										
										
											2016-10-28 19:53:42 -07:00
										 |  |  |   <translation id="8841459487341224498">rab</translation> | 
					
						
							| 
									
										
										
										
											2016-07-29 13:07:01 -07:00
										 |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |         expect(loadAsMap(XTB)).toEqual({'8841459487341224498': 'rab'}); | 
					
						
							| 
									
										
										
										
											2016-07-29 13:07:01 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |       it('should load XTB files without placeholders', () => { | 
					
						
							| 
									
										
										
										
											2016-07-29 13:07:01 -07:00
										 |  |  |         const XTB = `<?xml version="1.0" encoding="UTF-8"?>
 | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | <translationbundle> | 
					
						
							| 
									
										
										
										
											2016-10-28 19:53:42 -07:00
										 |  |  |   <translation id="8841459487341224498">rab</translation> | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |         expect(loadAsMap(XTB)).toEqual({'8841459487341224498': 'rab'}); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-03 14:29:28 -08:00
										 |  |  |       it('should return the target locale', () => { | 
					
						
							|  |  |  |         const XTB = `<?xml version="1.0" encoding="UTF-8"?>
 | 
					
						
							|  |  |  | <translationbundle lang='fr'> | 
					
						
							|  |  |  |   <translation id="8841459487341224498">rab</translation> | 
					
						
							|  |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(serializer.load(XTB, 'url').locale).toEqual('fr'); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2016-10-28 19:53:42 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |       it('should load XTB files with placeholders', () => { | 
					
						
							| 
									
										
										
										
											2016-07-29 13:07:01 -07:00
										 |  |  |         const XTB = `<?xml version="1.0" encoding="UTF-8"?>
 | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | <translationbundle> | 
					
						
							| 
									
										
										
										
											2016-10-28 19:53:42 -07:00
										 |  |  |   <translation id="8877975308926375834"><ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/></translation> | 
					
						
							| 
									
										
										
										
											2016-08-09 21:05:04 -07:00
										 |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |         expect(loadAsMap(XTB)).toEqual({ | 
					
						
							|  |  |  |           '8877975308926375834': '<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>' | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2016-08-09 21:05:04 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should replace ICU placeholders with their translations', () => { | 
					
						
							| 
									
										
										
										
											2016-08-12 21:26:03 -07:00
										 |  |  |         const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
 | 
					
						
							| 
									
										
										
										
											2016-08-09 21:05:04 -07:00
										 |  |  | <translationbundle> | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |   <translation id="7717087045075616176">*<ph name="ICU"/>*</translation> | 
					
						
							|  |  |  |   <translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation> | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |         expect(loadAsMap(XTB)).toEqual({ | 
					
						
							|  |  |  |           '7717087045075616176': `*<ph name="ICU"/>*`, | 
					
						
							|  |  |  |           '5115002811911870583': | 
					
						
							|  |  |  |               `{VAR_PLURAL, plural, =1 {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}}`, | 
					
						
							| 
									
										
										
										
											2016-08-09 21:05:04 -07:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should load complex XTB files', () => { | 
					
						
							| 
									
										
										
										
											2016-08-12 21:26:03 -07:00
										 |  |  |         const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
 | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | <translationbundle> | 
					
						
							| 
									
										
										
										
											2016-10-28 19:53:42 -07:00
										 |  |  |   <translation id="8281795707202401639"><ph name="INTERPOLATION"/><ph name="START_BOLD_TEXT"/>rab<ph name="CLOSE_BOLD_TEXT"/> oof</translation> | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |   <translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation> | 
					
						
							| 
									
										
										
										
											2016-10-28 19:53:42 -07:00
										 |  |  |   <translation id="130772889486467622">oof</translation> | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |   <translation id="4739316421648347533">{VAR_PLURAL, plural, =1 {{VAR_GENDER, gender, male {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}} }}</translation> | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |         expect(loadAsMap(XTB)).toEqual({ | 
					
						
							|  |  |  |           '8281795707202401639': | 
					
						
							|  |  |  |               `<ph name="INTERPOLATION"/><ph name="START_BOLD_TEXT"/>rab<ph name="CLOSE_BOLD_TEXT"/> oof`, | 
					
						
							|  |  |  |           '5115002811911870583': | 
					
						
							|  |  |  |               `{VAR_PLURAL, plural, =1 {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}}`, | 
					
						
							| 
									
										
										
										
											2016-10-28 19:53:42 -07:00
										 |  |  |           '130772889486467622': `oof`, | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |           '4739316421648347533': | 
					
						
							|  |  |  |               `{VAR_PLURAL, plural, =1 {[{VAR_GENDER, gender, male {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}},  ]}}`, | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |         }); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe('errors', () => { | 
					
						
							| 
									
										
										
										
											2017-01-25 15:55:17 -08:00
										 |  |  |       it('should be able to parse non-angular xtb files without error', () => { | 
					
						
							|  |  |  |         const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
 | 
					
						
							|  |  |  | <translationbundle> | 
					
						
							|  |  |  |   <translation id="angular">is great</translation> | 
					
						
							| 
									
										
										
										
											2017-01-31 09:47:10 -08:00
										 |  |  |   <translation id="non angular">is <invalid>less</invalid> {count, plural, =0 {{GREAT}}}</translation> | 
					
						
							| 
									
										
										
										
											2017-01-25 15:55:17 -08:00
										 |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Invalid messages should not cause the parser to throw
 | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |         let i18nNodesByMsgId: {[id: string]: i18n.Node[]} = undefined!; | 
					
						
							| 
									
										
										
										
											2017-02-03 14:29:28 -08:00
										 |  |  |         expect(() => { | 
					
						
							|  |  |  |           i18nNodesByMsgId = serializer.load(XTB, 'url').i18nNodesByMsgId; | 
					
						
							|  |  |  |         }).not.toThrow(); | 
					
						
							| 
									
										
										
										
											2017-01-25 15:55:17 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         expect(Object.keys(i18nNodesByMsgId).length).toEqual(2); | 
					
						
							|  |  |  |         expect(serializeNodes(i18nNodesByMsgId['angular']).join('')).toEqual('is great'); | 
					
						
							|  |  |  |         // Messages that contain unsupported feature should throw on access
 | 
					
						
							|  |  |  |         expect(() => { | 
					
						
							|  |  |  |           const read = i18nNodesByMsgId['non angular']; | 
					
						
							|  |  |  |         }).toThrowError(/xtb parse errors/); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |       it('should throw on nested <translationbundle>', () => { | 
					
						
							|  |  |  |         const XTB = | 
					
						
							|  |  |  |             '<translationbundle><translationbundle></translationbundle></translationbundle>'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(() => { | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |           loadAsMap(XTB); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |         }).toThrowError(/<translationbundle> elements can not be nested/); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should throw when a <translation> has no id attribute', () => { | 
					
						
							| 
									
										
										
										
											2016-07-29 13:07:01 -07:00
										 |  |  |         const XTB = `<translationbundle>
 | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |   <translation></translation> | 
					
						
							|  |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |         expect(() => { | 
					
						
							|  |  |  |           loadAsMap(XTB); | 
					
						
							|  |  |  |         }).toThrowError(/<translation> misses the "id" attribute/); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should throw when a placeholder has no name attribute', () => { | 
					
						
							| 
									
										
										
										
											2016-07-29 13:07:01 -07:00
										 |  |  |         const XTB = `<translationbundle>
 | 
					
						
							| 
									
										
										
										
											2016-10-28 19:53:42 -07:00
										 |  |  |   <translation id="1186013544048295927"><ph /></translation> | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |         expect(() => { | 
					
						
							|  |  |  |           loadAsMap(XTB); | 
					
						
							|  |  |  |         }).toThrowError(/<ph> misses the "name" attribute/); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |       it('should throw on unknown xtb tags', () => { | 
					
						
							|  |  |  |         const XTB = `<what></what>`; | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         expect(() => { | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |           loadAsMap(XTB); | 
					
						
							|  |  |  |         }).toThrowError(new RegExp(escapeRegExp(`Unexpected tag ("[ERROR ->]<what></what>")`))); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2016-08-09 21:05:04 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |       it('should throw on unknown message tags', () => { | 
					
						
							|  |  |  |         const XTB = `<translationbundle>
 | 
					
						
							|  |  |  |   <translation id="1186013544048295927"><b>msg should contain only ph tags</b></translation> | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | </translationbundle>`;
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |         expect(() => { | 
					
						
							|  |  |  |           loadAsMap(XTB); | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |             .toThrowError( | 
					
						
							|  |  |  |                 new RegExp(escapeRegExp(`[ERROR ->]<b>msg should contain only ph tags</b>`))); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it('should throw on duplicate message id', () => { | 
					
						
							|  |  |  |         const XTB = `<translationbundle>
 | 
					
						
							|  |  |  |   <translation id="1186013544048295927">msg1</translation> | 
					
						
							|  |  |  |   <translation id="1186013544048295927">msg2</translation> | 
					
						
							|  |  |  | </translationbundle>`;
 | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-02 17:40:15 -07:00
										 |  |  |         expect(() => { | 
					
						
							|  |  |  |           loadAsMap(XTB); | 
					
						
							|  |  |  |         }).toThrowError(/Duplicated translations for msg 1186013544048295927/); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-08 10:14:18 -07:00
										 |  |  |       it('should throw when trying to save an xtb file', () => { | 
					
						
							|  |  |  |         expect(() => { | 
					
						
							|  |  |  |           serializer.write([], null); | 
					
						
							|  |  |  |         }).toThrowError(/Unsupported/); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2016-07-21 13:56:58 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2017-02-16 17:03:18 +01:00
										 |  |  | } |