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 Feb 27, 2017
1 parent 32c2fd5 commit b829cc0
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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
22 changes: 19 additions & 3 deletions modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ 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,1</source>translate me</msg>
<msg id="3492007542396725315"><source>src/basic.ts:5,5</source><source>src/entry_components.ts:1,1</source>Welcome</msg>
<msg id="3772663375917578720"><source>node_modules/third_party/other_comp.d.ts:1,1</source>other-3rdP-component</msg>
</messagebundle>
`;

Expand All @@ -47,16 +47,32 @@ 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>
<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
5 changes: 3 additions & 2 deletions modules/@angular/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
28 changes: 26 additions & 2 deletions modules/@angular/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,29 @@ 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,
// adding 1 to make the index 1 based
startLine: nodes[0].sourceSpan.start.line + 1,
startCol: nodes[0].sourceSpan.start.col,
endLine: nodes[nodes.length - 1].sourceSpan.end.line + 1,
endCol: nodes[0].sourceSpan.start.col
}];
} else {
this.sources = [];
}
}
}

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

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

visitIcuPlaceholder(ph: IcuPlaceholder, context?: any): any{};
}
}
19 changes: 17 additions & 2 deletions modules/@angular/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,11 +66,24 @@ 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);
}

// filter the message source paths relative to another path
filterMessageSourcePaths(sources: i18n.MessageSpan[], filter: string) {
sources.forEach(
(source: i18n.MessageSpan) => { source.filePath = source.filePath.replace(filter, ''); });
return sources;
}
}

// Transform an i18n AST by renaming the placeholder nodes with the given mapper
Expand Down
18 changes: 17 additions & 1 deletion modules/@angular/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
11 changes: 10 additions & 1 deletion modules/@angular/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,16 @@ 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}`)]));
});

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 modules/@angular/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 modules/@angular/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,3</source>i18n attribute on tags</msg>
<msg id="3707494640264351337"><source>file.ts:5,5</source>nested</msg>
<msg id="5539162898278769904" meaning="different meaning"><source>file.ts:7,7</source>nested</msg>
<msg id="3780349238193953556"><source>file.ts:9,9</source><source>file.ts:10,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,13</source>on not translatable node</msg>
<msg id="8670732454866344690"><source>file.ts:14,14</source>on translatable node</msg>
<msg id="4593805537723189714"><source>file.ts:19,19</source><source>file.ts:36,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,22</source><source>file.ts:25,25</source>{VAR_SELECT, select, m {male} f {female} }</msg>
<msg id="4851788426695310455"><source>file.ts:28,28</source><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="9013357158046221374"><source>file.ts:29,29</source>sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="8324617391167353662"><source>file.ts:30,30</source><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
<msg id="7685649297917455806"><source>file.ts:35,35</source><source>file.ts:53,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,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,41</source>with an explicit ID</msg>
<msg id="i18n17"><source>file.ts:42,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,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,55</source><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></msg>`;

const HTML = `
<div>
Expand Down
Loading

0 comments on commit b829cc0

Please sign in to comment.