forked from angular/angular
/
source_file_translation_handler.ts
109 lines (102 loc) · 4.59 KB
/
source_file_translation_handler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* 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
*/
import {absoluteFrom, AbsoluteFsPath, FileSystem, PathSegment} from '@angular/compiler-cli/src/ngtsc/file_system';
import {parseSync, transformFromAstSync} from '@babel/core';
import {File, Program} from '@babel/types';
import {Diagnostics} from '../../diagnostics';
import {TranslatePluginOptions} from '../../source_file_utils';
import {OutputPathFn} from '../output_path';
import {TranslationBundle, TranslationHandler} from '../translator';
import {makeEs2015TranslatePlugin} from './es2015_translate_plugin';
import {makeEs5TranslatePlugin} from './es5_translate_plugin';
import {makeLocalePlugin} from './locale_plugin';
/**
* Translate a file by inlining all messages tagged by `$localize` with the appropriate translated
* message.
*/
export class SourceFileTranslationHandler implements TranslationHandler {
private sourceLocaleOptions:
TranslatePluginOptions = {...this.translationOptions, missingTranslation: 'ignore'};
constructor(private fs: FileSystem, private translationOptions: TranslatePluginOptions = {}) {}
canTranslate(relativeFilePath: PathSegment|AbsoluteFsPath, _contents: Uint8Array): boolean {
return this.fs.extname(relativeFilePath) === '.js';
}
translate(
diagnostics: Diagnostics, sourceRoot: AbsoluteFsPath, relativeFilePath: PathSegment,
contents: Uint8Array, outputPathFn: OutputPathFn, translations: TranslationBundle[],
sourceLocale?: string): void {
const sourceCode = Buffer.from(contents).toString('utf8');
// A short-circuit check to avoid parsing the file into an AST if it does not contain any
// `$localize` identifiers.
if (!sourceCode.includes('$localize')) {
for (const translation of translations) {
this.writeSourceFile(
diagnostics, outputPathFn, translation.locale, relativeFilePath, contents);
}
if (sourceLocale !== undefined) {
this.writeSourceFile(diagnostics, outputPathFn, sourceLocale, relativeFilePath, contents);
}
} else {
const ast = parseSync(sourceCode, {sourceRoot, filename: relativeFilePath});
if (!ast) {
diagnostics.error(
`Unable to parse source file: ${this.fs.join(sourceRoot, relativeFilePath)}`);
return;
}
// Output a translated copy of the file for each locale.
for (const translationBundle of translations) {
this.translateFile(
diagnostics, ast, translationBundle, sourceRoot, relativeFilePath, outputPathFn,
this.translationOptions);
}
if (sourceLocale !== undefined) {
// Also output a copy of the file for the source locale.
// There will be no translations - by definition - so we "ignore" `missingTranslations`.
this.translateFile(
diagnostics, ast, {locale: sourceLocale, translations: {}}, sourceRoot,
relativeFilePath, outputPathFn, this.sourceLocaleOptions);
}
}
}
private translateFile(
diagnostics: Diagnostics, ast: File|Program, translationBundle: TranslationBundle,
sourceRoot: AbsoluteFsPath, filename: PathSegment, outputPathFn: OutputPathFn,
options: TranslatePluginOptions) {
const translated = transformFromAstSync(ast, undefined, {
compact: true,
generatorOpts: {minified: true},
plugins: [
makeLocalePlugin(translationBundle.locale),
makeEs2015TranslatePlugin(diagnostics, translationBundle.translations, options),
makeEs5TranslatePlugin(diagnostics, translationBundle.translations, options),
],
filename,
});
if (translated && translated.code) {
this.writeSourceFile(
diagnostics, outputPathFn, translationBundle.locale, filename, translated.code);
const outputPath = absoluteFrom(outputPathFn(translationBundle.locale, filename));
this.fs.ensureDir(this.fs.dirname(outputPath));
this.fs.writeFile(outputPath, translated.code);
} else {
diagnostics.error(`Unable to translate source file: ${this.fs.join(sourceRoot, filename)}`);
return;
}
}
private writeSourceFile(
diagnostics: Diagnostics, outputPathFn: OutputPathFn, locale: string,
relativeFilePath: PathSegment, contents: string|Uint8Array): void {
try {
const outputPath = absoluteFrom(outputPathFn(locale, relativeFilePath));
this.fs.ensureDir(this.fs.dirname(outputPath));
this.fs.writeFile(outputPath, contents);
} catch (e) {
diagnostics.error(e.message);
}
}
}