Skip to content

Commit

Permalink
fix(extractor): support i18n messages with custom id/meaning/description
Browse files Browse the repository at this point in the history
Fixes #9
  • Loading branch information
ocombe committed Jan 5, 2018
1 parent 3a9f9a6 commit 07b0319
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 10 deletions.
1 change: 1 addition & 0 deletions example/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {I18n} from "@ngx-translate/i18n-polyfill";
})
export class AppComponent {
title = "app";
customId = this.i18n({id: "customId", value: "Some value", description: "Custom desc", meaning: "Custom meaning"});

constructor(private i18n: I18n) {
console.log(this.i18n("This is a test {{ok}} !", {ok: "\\o/"}));
Expand Down
14 changes: 13 additions & 1 deletion lib/extractor/src/abstract-ast-parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as ts from "typescript";
import {I18nDef} from "../../src/i18n-polyfill";

// source: https://github.com/biesbjerg/ngx-translate-extract/blob/master/src/parsers/abstract-ast.parser.ts
export abstract class AbstractAstParser {
Expand All @@ -11,7 +12,7 @@ export abstract class AbstractAstParser {
/**
* Get strings from function call's first argument
*/
protected _getCallArgStrings(callNode: ts.CallExpression): string[] {
protected _getCallArgStrings(callNode: ts.CallExpression): (string | I18nDef)[] {
if (!callNode.arguments.length) {
return [];
}
Expand All @@ -23,6 +24,17 @@ export abstract class AbstractAstParser {
return [(firstArg as ts.StringLiteral).text];
case ts.SyntaxKind.ArrayLiteralExpression:
return (firstArg as ts.ArrayLiteralExpression).elements.map((element: ts.StringLiteral) => element.text);
case ts.SyntaxKind.ObjectLiteralExpression:
const i18nDef: I18nDef = {value: ""};
(firstArg as ts.ObjectLiteralExpression).properties.forEach((prop: ts.PropertyAssignment) => {
i18nDef[(prop.name as ts.Identifier).text] = (prop.initializer as ts.StringLiteral).text;
});
if (!i18nDef.value) {
throw new Error(
`An I18nDef requires a value property on '${this.syntaxKindToName(firstArg.kind)}' for ${firstArg}`
);
}
return [i18nDef];
case ts.SyntaxKind.Identifier:
console.log("WARNING: We cannot extract variable values passed to TranslateService (yet)");
break;
Expand Down
11 changes: 6 additions & 5 deletions lib/extractor/src/extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import {xmbLoadToXml, xmbWrite} from "../../src/serializers/xmb";
import {Node} from "../../src/serializers/xml_helper";
import {MessageBundle} from "./message-bundle";
import {XmlMessagesById} from "../../src/serializers/serializer";
import {I18nDef} from "../../src/i18n-polyfill";

export function getAst(paths: string[]): {[url: string]: string[]} {
export function getAst(paths: string[]): {[url: string]: (string | I18nDef)[]} {
const files = [];
paths.forEach(path => {
files.push(...glob.sync(path));
});
const parser = new ServiceParser();
const collection: {[url: string]: string[]} = {};
const collection: {[url: string]: (string | I18nDef)[]} = {};
files.forEach(path => {
if (statSync(path).isDirectory) {
// this._options.verbose && this._out(chalk.gray('- %s'), path);
Expand All @@ -36,8 +37,8 @@ export function getAst(paths: string[]): {[url: string]: string[]} {
export class ServiceParser extends AbstractAstParser {
protected _sourceFile: ts.SourceFile;

public extract(contents: string, path?: string): string[] {
const entries: string[] = [];
public extract(contents: string, path?: string): (string | I18nDef)[] {
const entries: (string | I18nDef)[] = [];

this._sourceFile = this._createSourceFile(path, contents);
const classNodes = this._findClassNodes(this._sourceFile);
Expand Down Expand Up @@ -131,7 +132,7 @@ export class ServiceParser extends AbstractAstParser {
}
}

export function getFileContent(messages: {[url: string]: string[]}, sourcePath: string, format?: string): string {
export function getFileContent(messages: {[url: string]: (string|I18nDef)[]}, sourcePath: string, format?: string): string {
let loadFct: (content: string, url: string) => XmlMessagesById;
let writeFct: (messages: Message[], locale: string | null, existingNodes: Node[]) => string;
let digest: (message: Message) => string;
Expand Down
14 changes: 11 additions & 3 deletions lib/extractor/src/message-bundle.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import {I18nMessagesById, PlaceholderMapper, XmlMessagesById} from "../../src/serializers/serializer";
import {PlaceholderMapper, XmlMessagesById} from "../../src/serializers/serializer";
import * as i18n from "../../src/ast/i18n_ast";
import {Node} from "../../src/serializers/xml_helper";
import {HtmlParser} from "../../src/parser/html";
import {I18nDef} from "../../src/i18n-polyfill";

export class MessageBundle {
messages: i18n.Message[] = [];
private htmlParser = new HtmlParser();

constructor(private locale: string | null = null) {}

updateFromTemplate(template: string, url: string): i18n.Message[] {
const htmlParserResult = this.htmlParser.parse(template, url, true);
updateFromTemplate(template: string | I18nDef, url: string): i18n.Message[] {
const str = typeof template === "string" ? template : template.value;
const htmlParserResult = this.htmlParser.parse(str, url, true);

if (htmlParserResult.errors.length) {
throw htmlParserResult.errors;
Expand All @@ -22,6 +24,12 @@ export class MessageBundle {
throw i18nParserResult.errors;
}

if (typeof template !== "string") {
i18nParserResult.messages[0].id = template.id || "";
i18nParserResult.messages[0].meaning = template.meaning || "";
i18nParserResult.messages[0].description = template.description || "";
}

this.messages = this.messages.concat(i18nParserResult.messages);
return this.messages;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/src/ast/parse_util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {I18nDef} from "../i18n-polyfill";

/**
* @license
* Copyright Google Inc. All Rights Reserved.
Expand Down
24 changes: 23 additions & 1 deletion test/extractor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ describe("Extractor", () => {
const messages = getAst(["example/src/**/*.ts"]);
const url = "example/src/app/app.component.ts";
expect(messages[url]).toBeDefined();
expect(messages[url]).toEqual(["This is a test {{ok}} !", "another test ^_^"]);
expect(messages[url]).toEqual([
{description: "Custom desc", id: "customId", meaning: "Custom meaning", value: "Some value"},
"This is a test {{ok}} !",
"another test ^_^"
]);
});

it("should generate content", () => {
Expand All @@ -15,6 +19,15 @@ describe("Extractor", () => {
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="customId" datatype="html">
<source>Some value</source>
<context-group purpose="location">
<context context-type="sourcefile">example/src/app/app.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
<note priority="1" from="description">Custom desc</note>
<note priority="1" from="meaning">Custom meaning</note>
</trans-unit>
<trans-unit id="78e9f3aab47c6cf393131413e0c51dedaa37766b" datatype="html">
<source>This is a test <x id="INTERPOLATION" equiv-text="{{ok}}"/> !</source>
<context-group purpose="location">
Expand Down Expand Up @@ -69,6 +82,15 @@ describe("Extractor", () => {
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="customId" datatype="html">
<source>Some value</source>
<context-group purpose="location">
<context context-type="sourcefile">example/src/app/app.component.ts</context>
<context context-type="linenumber">1</context>
</context-group>
<note priority="1" from="description">Custom desc</note>
<note priority="1" from="meaning">Custom meaning</note>
</trans-unit>
</body>
</file>
</xliff>
Expand Down
37 changes: 37 additions & 0 deletions test/serializers/xliff.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,43 @@ describe("Xliff serializer", () => {
`);
});

it("should write xliff with i18nDef", () => {
const messageBundle = new MessageBundle("en");
messageBundle.updateFromTemplate(
{
value: "This is a test message {sex, select, other {deeply nested}}",
id: "customId",
meaning: "Custom meaning",
description: "Custom desc"
},
"file.ts"
);
expect(messageBundle.write(xliffWrite, xliffDigest)).toEqual(`<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="customId" datatype="html">
<source>This is a test message <x id="ICU" equiv-text="{sex, select, other {...}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">file.ts</context>
<context context-type="linenumber">1</context>
</context-group>
<note priority="1" from="description">Custom desc</note>
<note priority="1" from="meaning">Custom meaning</note>
</trans-unit>
<trans-unit id="f4661fab0bda1dae3620088f290a8f086a6ca26e" datatype="html">
<source>{VAR_SELECT, select, other {deeply nested} }</source>
<context-group purpose="location">
<context context-type="sourcefile">file.ts</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>
`);
});

it("should write xliff with merged content", () => {
const messageBundle = new MessageBundle("en");
messageBundle.updateFromTemplate("This is a test message {sex, select, other {deeply nested}}", "file.ts");
Expand Down
29 changes: 29 additions & 0 deletions test/serializers/xliff2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,35 @@ describe("Xliff2 serializer", () => {
`);
});

it("should write xliff2 with I18nDef", () => {
const messageBundle = new MessageBundle("en");
messageBundle.updateFromTemplate(
{
value: "This is a test message",
id: "customId",
meaning: "Custom meaning",
description: "Custom desc"
},
"file.ts"
);
expect(messageBundle.write(xliff2Write, xliff2Digest)).toEqual(`<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="2.0" xmlns="urn:oasis:names:tc:xliff:document:2.0" srcLang="en">
<file original="ng.template" id="ngi18n">
<unit id="customId">
<notes>
<note category="description">Custom desc</note>
<note category="meaning">Custom meaning</note>
<note category="location">file.ts:1</note>
</notes>
<segment>
<source>This is a test message</source>
</segment>
</unit>
</file>
</xliff>
`);
});

it("should write xliff2 with merged content", () => {
const messageBundle = new MessageBundle("en");
messageBundle.updateFromTemplate("This is a test message {sex, select, other {deeply nested}}", "file.ts");
Expand Down
39 changes: 39 additions & 0 deletions test/serializers/xmb.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,45 @@ describe("Xmb serializer", () => {
`);
});

it("should write xmb with I18nDef", () => {
const messageBundle = new MessageBundle("en");
messageBundle.updateFromTemplate(
{
value: "This is a test message",
id: "customId",
meaning: "Custom meaning",
description: "Custom desc"
},
"file.ts"
);
expect(messageBundle.write(xmbWrite, xmbDigest, {}, xmbMapper)).toEqual(`<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE messagebundle [
<!ELEMENT messagebundle (msg)*>
<!ATTLIST messagebundle class CDATA #IMPLIED>
<!ELEMENT msg (#PCDATA|ph|source)*>
<!ATTLIST msg id CDATA #IMPLIED>
<!ATTLIST msg seq CDATA #IMPLIED>
<!ATTLIST msg name CDATA #IMPLIED>
<!ATTLIST msg desc CDATA #IMPLIED>
<!ATTLIST msg meaning CDATA #IMPLIED>
<!ATTLIST msg obsolete (obsolete) #IMPLIED>
<!ATTLIST msg xml:space (default|preserve) "default">
<!ATTLIST msg is_hidden CDATA #IMPLIED>
<!ELEMENT source (#PCDATA)>
<!ELEMENT ph (#PCDATA|ex)*>
<!ATTLIST ph name CDATA #REQUIRED>
<!ELEMENT ex (#PCDATA)>
]>
<messagebundle>
<msg id="customId" desc="Custom desc" meaning="Custom meaning"><source>file.ts:1</source>This is a test message</msg>
</messagebundle>
`);
});

it("should write xmb with merged content", () => {
const messageBundle = new MessageBundle("en");
messageBundle.updateFromTemplate("This is a test message {sex, select, other {deeply nested}}", "file.ts");
Expand Down

0 comments on commit 07b0319

Please sign in to comment.