Skip to content

Commit

Permalink
feat(compiler): add source files to xmb/xliff translations
Browse files Browse the repository at this point in the history
  • Loading branch information
ocombe committed Mar 13, 2017
1 parent 6c8638c commit b77ca01
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ComponentFactoryResolver, Injec

import {BasicComp} from './basic';

@Component({selector: 'cmp-entryComponents', template: '', entryComponents: [BasicComp]})
@Component({
selector: 'cmp-entryComponents',
template: '<p i18n>Welcome</p>',
entryComponents: [BasicComp]
})
export class CompWithEntryComponents {
constructor(public cfr: ComponentFactoryResolver) {}
}
Expand Down
28 changes: 23 additions & 5 deletions packages/compiler-cli/integrationtest/test/i18n_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT ex (#PCDATA)>
]>
<messagebundle>
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
<msg id="3492007542396725315">Welcome</msg>
<msg id="3772663375917578720">other-3rdP-component</msg>
<msg id="8136548302122759730" desc="desc" meaning="meaning"><source>src/basic.ts:1</source>translate me</msg>
<msg id="3492007542396725315"><source>src/basic.ts:5</source><source>src/entry_components.ts:1</source>Welcome</msg>
<msg id="126808141597411718"><source>node_modules/third_party/other_comp.d.ts:1,2</source>other-3rdP-component
multi-lines</msg>
</messagebundle>
`;

Expand All @@ -47,16 +48,33 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
<source>translate me</source>
<target/>
<context-group purpose="location">
<context context-type="sourcefile">src/basic.ts</context>
<context context-type="linenumber">1</context>
</context-group>
<note priority="1" from="description">desc</note>
<note priority="1" from="meaning">meaning</note>
</trans-unit>
<trans-unit id="65cc4ab3b4c438e07c89be2b677d08369fb62da2" datatype="html">
<source>Welcome</source>
<target/>
<context-group purpose="location">
<context context-type="sourcefile">src/basic.ts</context>
<context context-type="linenumber">5</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/entry_components.ts</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<trans-unit id="b0a17f08a4bd742b2acf39780c257c2f519d33ed" datatype="html">
<source>other-3rdP-component
multi-lines</source>
<target/>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/third_party/other_comp.d.ts</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
</body>
</file>
Expand Down
3 changes: 2 additions & 1 deletion packages/compiler-cli/integrationtest/test/ng_module_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ describe('NgModule', () => {
const fixture = createComponent(ComponentUsingThirdParty);
const thirdPComps = fixture.nativeElement.children;
expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component');
expect(thirdPComps[1].children[0].children[0].data).toEqual('other-3rdP-component');
expect(thirdPComps[1].children[0].children[0].data).toEqual(`other-3rdP-component
multi-lines`);
});

// https://github.com/angular/angular/issues/12428
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {Component} from '@angular/core';

@Component({
selector: 'another-third-party-comp',
template: '<div i18n>other-3rdP-component</div>',
template: `<div i18n>other-3rdP-component
multi-lines</div>`,
})
export class AnotherThirdpartyComponent {
}
}
5 changes: 3 additions & 2 deletions packages/compiler-cli/src/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ export class Extractor {
default:
serializer = new compiler.Xliff();
}

return bundle.write(serializer);
return bundle.write(
serializer,
(sourcePath: string) => sourcePath.replace(path.join(this.options.basePath, '/'), ''));
}

getExtension(formatName: string): string {
Expand Down
27 changes: 25 additions & 2 deletions packages/compiler/src/i18n/i18n_ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import {ParseSourceSpan} from '../parse_util';

export class Message {
sources: MessageSpan[];

/**
* @param nodes message AST
* @param placeholders maps placeholder names to static content
Expand All @@ -20,7 +22,28 @@ export class Message {
constructor(
public nodes: Node[], public placeholders: {[phName: string]: string},
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
public description: string, public id: string) {}
public description: string, public id: string) {
if (nodes.length) {
this.sources = [{
filePath: nodes[0].sourceSpan.start.file.url,
startLine: nodes[0].sourceSpan.start.line + 1,
startCol: nodes[0].sourceSpan.start.col + 1,
endLine: nodes[nodes.length - 1].sourceSpan.end.line + 1,
endCol: nodes[0].sourceSpan.start.col + 1
}];
} else {
this.sources = [];
}
}
}

// line and columns indexes are 1 based
export interface MessageSpan {
filePath: string;
startLine: number;
startCol: number;
endLine: number;
endCol: number;
}

export interface Node {
Expand Down Expand Up @@ -131,4 +154,4 @@ export class RecurseVisitor implements Visitor {
visitPlaceholder(ph: Placeholder, context?: any): any{};

visitIcuPlaceholder(ph: IcuPlaceholder, context?: any): any{};
}
}
12 changes: 10 additions & 2 deletions packages/compiler/src/i18n/message_bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class MessageBundle {
// The public (serialized) format might be different, see the `write` method.
getMessages(): i18n.Message[] { return this._messages; }

write(serializer: Serializer): string {
write(serializer: Serializer, filterSources?: (path: string) => string): string {
const messages: {[id: string]: i18n.Message} = {};
const mapperVisitor = new MapPlaceholderNames();

Expand All @@ -56,6 +56,8 @@ export class MessageBundle {
const id = serializer.digest(message);
if (!messages.hasOwnProperty(id)) {
messages[id] = message;
} else {
messages[id].sources.push(...message.sources);
}
});

Expand All @@ -64,7 +66,13 @@ export class MessageBundle {
const mapper = serializer.createNameMapper(messages[id]);
const src = messages[id];
const nodes = mapper ? mapperVisitor.convert(src.nodes, mapper) : src.nodes;
return new i18n.Message(nodes, {}, {}, src.meaning, src.description, id);
let transformedMessage = new i18n.Message(nodes, {}, {}, src.meaning, src.description, id);
transformedMessage.sources = src.sources;
if (filterSources) {
transformedMessage.sources.forEach(
(source: i18n.MessageSpan) => source.filePath = filterSources(source.filePath));
}
return transformedMessage;
});

return serializer.write(msgList, this._locale);
Expand Down
18 changes: 17 additions & 1 deletion packages/compiler/src/i18n/serializers/xliff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const _FILE_TAG = 'file';
const _SOURCE_TAG = 'source';
const _TARGET_TAG = 'target';
const _UNIT_TAG = 'trans-unit';
const _CONTEXT_GROUP_TAG = 'context-group';
const _CONTEXT_TAG = 'context';

// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
Expand All @@ -34,10 +36,24 @@ export class Xliff extends Serializer {
const transUnits: xml.Node[] = [];

messages.forEach(message => {
let contextTags: xml.Node[] = [];
message.sources.forEach((source: i18n.MessageSpan) => {
let contextGroupTag = new xml.Tag(_CONTEXT_GROUP_TAG, {purpose: 'location'});
contextGroupTag.children.push(
new xml.CR(10),
new xml.Tag(
_CONTEXT_TAG, {'context-type': 'sourcefile'}, [new xml.Text(source.filePath)]),
new xml.CR(10), new xml.Tag(
_CONTEXT_TAG, {'context-type': 'linenumber'},
[new xml.Text(`${source.startLine}`)]),
new xml.CR(8));
contextTags.push(new xml.CR(8), contextGroupTag);
});

const transUnit = new xml.Tag(_UNIT_TAG, {id: message.id, datatype: 'html'});
transUnit.children.push(
new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)),
new xml.CR(8), new xml.Tag(_TARGET_TAG));
new xml.CR(8), new xml.Tag(_TARGET_TAG), ...contextTags);

if (message.description) {
transUnit.children.push(
Expand Down
12 changes: 11 additions & 1 deletion packages/compiler/src/i18n/serializers/xmb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const _MESSAGES_TAG = 'messagebundle';
const _MESSAGE_TAG = 'msg';
const _PLACEHOLDER_TAG = 'ph';
const _EXEMPLE_TAG = 'ex';
const _SOURCE_TAG = 'source';

const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
<!ATTLIST messagebundle class CDATA #IMPLIED>
Expand Down Expand Up @@ -54,8 +55,17 @@ export class Xmb extends Serializer {
attrs['meaning'] = message.meaning;
}

let sourceTags: xml.Tag[] = [];
message.sources.forEach((source: i18n.MessageSpan) => {
sourceTags.push(new xml.Tag(_SOURCE_TAG, {}, [
new xml.Text(
`${source.filePath}:${source.startLine}${source.endLine !== source.startLine ? ',' + source.endLine : ''}`)
]));
});

rootNode.children.push(
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes)));
new xml.CR(2),
new xml.Tag(_MESSAGE_TAG, attrs, [...sourceTags, ...visitor.serialize(message.nodes)]));
});

rootNode.children.push(new xml.CR());
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/test/i18n/digest_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function main(): void {
placeholderToMessage: {},
meaning: '',
description: '',
sources: [],
})).toEqual('i');
});
});
Expand Down
42 changes: 21 additions & 21 deletions packages/compiler/test/i18n/integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function main() {
it('should extract from templates', () => {
const catalog = new MessageBundle(new HtmlParser, [], {});
const serializer = new Xmb();
catalog.updateFromTemplate(HTML, '', DEFAULT_INTERPOLATION_CONFIG);
catalog.updateFromTemplate(HTML, 'file.ts', DEFAULT_INTERPOLATION_CONFIG);

expect(catalog.write(serializer)).toContain(XMB);
});
Expand Down Expand Up @@ -168,32 +168,32 @@ const XTB = `
<translation id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></translation>
</translationbundle>`;

const XMB = ` <msg id="615790887472569365">i18n attribute on tags</msg>
<msg id="3707494640264351337">nested</msg>
<msg id="5539162898278769904" meaning="different meaning">nested</msg>
<msg id="3780349238193953556"><ph name="START_ITALIC_TEXT"><ex>&lt;i&gt;</ex></ph>with placeholders<ph name="CLOSE_ITALIC_TEXT"><ex>&lt;/i&gt;</ex></ph></msg>
<msg id="5525133077318024839">on not translatable node</msg>
<msg id="8670732454866344690">on translatable node</msg>
<msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
<msg id="1746565782635215">
const XMB = ` <msg id="615790887472569365"><source>file.ts:3</source>i18n attribute on tags</msg>
<msg id="3707494640264351337"><source>file.ts:5</source>nested</msg>
<msg id="5539162898278769904" meaning="different meaning"><source>file.ts:7</source>nested</msg>
<msg id="3780349238193953556"><source>file.ts:9</source><source>file.ts:10</source><ph name="START_ITALIC_TEXT"><ex>&lt;i&gt;</ex></ph>with placeholders<ph name="CLOSE_ITALIC_TEXT"><ex>&lt;/i&gt;</ex></ph></msg>
<msg id="5525133077318024839"><source>file.ts:13</source>on not translatable node</msg>
<msg id="8670732454866344690"><source>file.ts:14</source>on translatable node</msg>
<msg id="4593805537723189714"><source>file.ts:19</source><source>file.ts:36</source>{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
<msg id="1746565782635215"><source>file.ts:21,23</source><source>file.ts:24,26</source>
<ph name="ICU"><ex>ICU</ex></ph>
</msg>
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
<msg id="4851788426695310455"><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
<msg id="7685649297917455806">in a translatable section</msg>
<msg id="2387287228265107305">
<msg id="5868084092545682515"><source>file.ts:22</source><source>file.ts:25</source>{VAR_SELECT, select, m {male} f {female} }</msg>
<msg id="4851788426695310455"><source>file.ts:28</source><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="9013357158046221374"><source>file.ts:29</source>sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="8324617391167353662"><source>file.ts:30</source><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
<msg id="7685649297917455806"><source>file.ts:35</source><source>file.ts:53</source>in a translatable section</msg>
<msg id="2387287228265107305"><source>file.ts:33,37</source>
<ph name="START_HEADING_LEVEL1"><ex>&lt;h1&gt;</ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex>&lt;/h1&gt;</ex></ph>
<ph name="START_TAG_DIV"><ex>&lt;div&gt;</ex></ph><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
<ph name="START_TAG_DIV_1"><ex>&lt;div&gt;</ex></ph><ph name="ICU"><ex>ICU</ex></ph><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
</msg>
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg>
<msg id="i18n16">with an explicit ID</msg>
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
<msg id="4085484936881858615" desc="desc">{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
<msg id="4035252431381981115">foo<ph name="START_LINK"><ex>&lt;a&gt;</ex></ph>bar<ph name="CLOSE_LINK"><ex>&lt;/a&gt;</ex></ph></msg>
<msg id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></msg>`;
<msg id="1491627405349178954"><source>file.ts:39</source>it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg>
<msg id="i18n16"><source>file.ts:41</source>with an explicit ID</msg>
<msg id="i18n17"><source>file.ts:42</source>{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph>} }</msg>
<msg id="4085484936881858615" desc="desc"><source>file.ts:45,51</source>{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
<msg id="4035252431381981115"><source>file.ts:53</source>foo<ph name="START_LINK"><ex>&lt;a&gt;</ex></ph>bar<ph name="CLOSE_LINK"><ex>&lt;/a&gt;</ex></ph></msg>
<msg id="5339604010413301604"><source>file.ts:55</source><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></msg>`;

const HTML = `
<div>
Expand Down
Loading

0 comments on commit b77ca01

Please sign in to comment.