Skip to content

Commit d92d73b

Browse files
authored
Merge pull request #38 from PolymerLabs/transform-scaffold
Scaffolding for transform mode
2 parents db2805e + 9e3fa3f commit d92d73b

File tree

24 files changed

+468
-12
lines changed

24 files changed

+468
-12
lines changed

config.schema.json

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,22 @@
6565
],
6666
"type": "object"
6767
},
68+
"TransformOutputConfig": {
69+
"additionalProperties": false,
70+
"description": "Configuration specific to the `transform` output mode.",
71+
"properties": {
72+
"mode": {
73+
"enum": [
74+
"transform"
75+
],
76+
"type": "string"
77+
}
78+
},
79+
"required": [
80+
"mode"
81+
],
82+
"type": "object"
83+
},
6884
"XlbConfig": {
6985
"additionalProperties": false,
7086
"description": "Parse an XLB XML file. These files contain translations organized using the\nsame message names that we originally requested.\nConfiguration for XLB interchange format.",
@@ -130,7 +146,14 @@
130146
"description": "Localization interchange format and configuration specific to that format."
131147
},
132148
"output": {
133-
"$ref": "#/definitions/RuntimeOutputConfig",
149+
"anyOf": [
150+
{
151+
"$ref": "#/definitions/RuntimeOutputConfig"
152+
},
153+
{
154+
"$ref": "#/definitions/TransformOutputConfig"
155+
}
156+
],
134157
"description": "Set and configure the output mode."
135158
},
136159
"patches": {

package-lock.json

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"fs-extra": "^9.0.0",
3333
"glob": "^7.1.6",
3434
"jsonschema": "^1.2.6",
35+
"lit-html": "^1.2.1",
3536
"minimist": "^1.2.5",
3637
"parse5": "^6.0.0",
3738
"source-map-support": "^0.5.19",
@@ -46,6 +47,7 @@
4647
"@types/minimist": "^1.2.0",
4748
"@types/node": "^14.0.1",
4849
"@types/parse5": "^5.0.2",
50+
"@types/prettier": "^2.0.1",
4951
"@types/xmldom": "^0.1.29",
5052
"@typescript-eslint/eslint-plugin": "^3.3.0",
5153
"@typescript-eslint/parser": "^3.3.0",
@@ -54,7 +56,6 @@
5456
"diff": "^4.0.2",
5557
"dir-compare": "^2.3.0",
5658
"eslint": "^7.0.0",
57-
"lit-html": "^1.2.1",
5859
"prettier": "^2.0.5",
5960
"rimraf": "^3.0.2",
6061
"typescript-json-schema": "^0.42.0"

src/cli.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as minimist from 'minimist';
1515
import {programFromTsConfig, printDiagnostics} from './typescript';
1616
import {extractMessagesFromProgram} from './program-analysis';
1717
import {runtimeOutput} from './outputters/runtime';
18+
import {transformOutput} from './outputters/transform';
1819
import {makeFormatter} from './formatters';
1920
import {ProgramMessage, Message} from './messages';
2021
import {KnownError, throwUnreachable} from './error';
@@ -94,9 +95,11 @@ async function runAndThrow(config: Config) {
9495

9596
if (config.output.mode === 'runtime') {
9697
runtimeOutput(messages, translationMap, config, config.output);
98+
} else if (config.output.mode === 'transform') {
99+
transformOutput(translationMap, config, program);
97100
} else {
98101
throwUnreachable(
99-
config.output.mode,
102+
config.output,
100103
`Internal error: unknown output mode ${
101104
(config.output as typeof config.output).mode
102105
}`

src/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {Locale} from './locales';
1616
import {KnownError} from './error';
1717
import {FormatConfig} from './formatters';
1818
import {RuntimeOutputConfig} from './outputters/runtime';
19+
import {TransformOutputConfig} from './outputters/transform';
1920

2021
interface ConfigFile {
2122
/**
@@ -49,7 +50,7 @@ interface ConfigFile {
4950
/**
5051
* Set and configure the output mode.
5152
*/
52-
output: RuntimeOutputConfig;
53+
output: RuntimeOutputConfig | TransformOutputConfig;
5354

5455
/**
5556
* Optional string substitutions to apply to specific locale messages. Useful

src/formatters/xliff.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ import {Config} from '../config';
1616
import {Locale} from '../locales';
1717
import {Formatter} from './index';
1818
import {KnownError} from '../error';
19-
import {Bundle, Message, ProgramMessage, Placeholder} from '../messages';
19+
import {
20+
Bundle,
21+
Message,
22+
ProgramMessage,
23+
Placeholder,
24+
makeMessageIdMap,
25+
} from '../messages';
2026
import {
2127
getOneElementByTagNameOrThrow,
2228
getNonEmptyAttributeOrThrow,
@@ -180,10 +186,7 @@ export class XliffFormatter implements Formatter {
180186
targetLocale: Locale,
181187
targetMessages: Message[]
182188
): string {
183-
const translationsByName = new Map<string, Message>();
184-
for (const message of targetMessages) {
185-
translationsByName.set(message.name, message);
186-
}
189+
const translationsByName = makeMessageIdMap(targetMessages);
187190

188191
const doc = new xmldom.DOMImplementation().createDocument('', '', null);
189192
const indent = (node: Element | Document, level = 0) =>

src/messages.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,16 @@ export interface Placeholder {
9191
// END_BOLD, and also allow a way to define them in the template (e.g. a
9292
// "placeholder-name" element attribute or similar).
9393
}
94+
95+
/**
96+
* Given an array of messages, return a new map from message ID to message.
97+
*/
98+
export function makeMessageIdMap<T extends Message>(
99+
messages: T[]
100+
): Map<string, T> {
101+
const map = new Map<string, T>();
102+
for (const msg of messages) {
103+
map.set(msg.name, msg);
104+
}
105+
return map;
106+
}

src/outputters/transform.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2020 The Polymer Project Authors. All rights reserved.
4+
* This code may only be used under the BSD style license found at
5+
* http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6+
* at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7+
* be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8+
* Google as part of the polymer project is also subject to an additional IP
9+
* rights grant found at http://polymer.github.io/PATENTS.txt
10+
*/
11+
12+
import {Message} from '../messages';
13+
import {Locale} from '../locales';
14+
import {Config} from '../config';
15+
import * as ts from 'typescript';
16+
import * as pathLib from 'path';
17+
18+
/**
19+
* Configuration specific to the `transform` output mode.
20+
*/
21+
export interface TransformOutputConfig {
22+
mode: 'transform';
23+
}
24+
25+
/**
26+
* Compile and emit the given TypeScript program using the lit-localize
27+
* transformer.
28+
*/
29+
export function transformOutput(
30+
translationsByLocale: Map<Locale, Message[]>,
31+
config: Config,
32+
program: ts.Program
33+
) {
34+
// TODO(aomarks) It doesn't seem that it's possible for a TypeScript
35+
// transformer to emit a new file, so we just have to emit for each locale.
36+
// Need to do some more investigation into the best way to integrate this
37+
// transformation into a real project so that the user can still use --watch
38+
// and other tsc flags. It would also be nice to support the language server,
39+
// so that diagnostics will show up immediately in the editor.
40+
const opts = program.getCompilerOptions();
41+
const outRoot = opts.outDir || '.';
42+
for (const locale of [config.sourceLocale, ...config.targetLocales]) {
43+
let translations;
44+
if (locale !== config.sourceLocale) {
45+
translations = new Map<string, Message>();
46+
for (const message of translationsByLocale.get(locale) || []) {
47+
translations.set(message.name, message);
48+
}
49+
}
50+
opts.outDir = pathLib.join(outRoot, '/', locale);
51+
program.emit(undefined, undefined, undefined, undefined, {
52+
before: [litLocalizeTransform(translations)],
53+
});
54+
}
55+
}
56+
57+
/**
58+
* Return a TypeScript TransformerFactory for the lit-localize transformer.
59+
*/
60+
export function litLocalizeTransform(
61+
translations: Map<string, Message> | undefined
62+
): ts.TransformerFactory<ts.SourceFile> {
63+
return (context) => {
64+
const transformer = new Transformer(context, translations);
65+
return (file) => ts.visitNode(file, transformer.boundVisitNode);
66+
};
67+
}
68+
69+
/**
70+
* Implementation of the lit-localize TypeScript transformer.
71+
*/
72+
class Transformer {
73+
private context: ts.TransformationContext;
74+
boundVisitNode = this.visitNode.bind(this);
75+
76+
constructor(
77+
context: ts.TransformationContext,
78+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
79+
_translations: Map<string, Message> | undefined
80+
) {
81+
this.context = context;
82+
}
83+
84+
/**
85+
* Top-level delegating visitor for all nodes.
86+
*/
87+
visitNode(node: ts.Node): ts.VisitResult<ts.Node> {
88+
// TODO(aomarks) The transformer!
89+
return ts.visitEachChild(node, this.boundVisitNode, this.context);
90+
}
91+
}

src/tests/e2e-goldens-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export function e2eGoldensTest(
7070
'--no-install',
7171
'prettier',
7272
'--write',
73-
`${outputDir}/**/*.ts`,
73+
`${outputDir}/**/*.{ts,js}`,
7474
]);
7575

7676
if (process.env.UPDATE_TEST_GOLDENS) {

src/tests/transform.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2020 The Polymer Project Authors. All rights reserved.
4+
* This code may only be used under the BSD style license found at
5+
* http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6+
* at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7+
* be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8+
* Google as part of the polymer project is also subject to an additional IP
9+
* rights grant found at http://polymer.github.io/PATENTS.txt
10+
*/
11+
12+
import {e2eGoldensTest} from './e2e-goldens-test';
13+
14+
e2eGoldensTest('transform', ['--config=lit-localize.json']);

0 commit comments

Comments
 (0)