/
serializer.ts
110 lines (90 loc) 路 3.69 KB
/
serializer.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
110
/**
* @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 * as i18n from '../i18n_ast';
export abstract class Serializer {
// - The `placeholders` and `placeholderToMessage` properties are irrelevant in the input messages
// - The `id` contains the message id that the serializer is expected to use
// - Placeholder names are already map to public names using the provided mapper
abstract write(messages: i18n.Message[], locale: string|null): string;
abstract load(content: string, url: string):
{locale: string|null, i18nNodesByMsgId: {[msgId: string]: i18n.Node[]}};
abstract digest(message: i18n.Message): string;
// Creates a name mapper, see `PlaceholderMapper`
// Returning `null` means that no name mapping is used.
createNameMapper(message: i18n.Message): PlaceholderMapper|null {
return null;
}
}
/**
* A `PlaceholderMapper` converts placeholder names from internal to serialized representation and
* back.
*
* It should be used for serialization format that put constraints on the placeholder names.
*/
export interface PlaceholderMapper {
toPublicName(internalName: string): string|null;
toInternalName(publicName: string): string|null;
}
/**
* A simple mapper that take a function to transform an internal name to a public name
*/
export class SimplePlaceholderMapper extends i18n.RecurseVisitor implements PlaceholderMapper {
private internalToPublic: {[k: string]: string} = {};
private publicToNextId: {[k: string]: number} = {};
private publicToInternal: {[k: string]: string} = {};
// create a mapping from the message
constructor(message: i18n.Message, private mapName: (name: string) => string) {
super();
message.nodes.forEach(node => node.visit(this));
}
toPublicName(internalName: string): string|null {
return this.internalToPublic.hasOwnProperty(internalName) ?
this.internalToPublic[internalName] :
null;
}
toInternalName(publicName: string): string|null {
return this.publicToInternal.hasOwnProperty(publicName) ? this.publicToInternal[publicName] :
null;
}
override visitText(text: i18n.Text, context?: any): any {
return null;
}
override visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): any {
this.visitPlaceholderName(ph.startName);
super.visitTagPlaceholder(ph, context);
this.visitPlaceholderName(ph.closeName);
}
override visitPlaceholder(ph: i18n.Placeholder, context?: any): any {
this.visitPlaceholderName(ph.name);
}
override visitBlockPlaceholder(ph: i18n.BlockPlaceholder, context?: any): any {
this.visitPlaceholderName(ph.startName);
super.visitBlockPlaceholder(ph, context);
this.visitPlaceholderName(ph.closeName);
}
override visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any {
this.visitPlaceholderName(ph.name);
}
// XMB placeholders could only contains A-Z, 0-9 and _
private visitPlaceholderName(internalName: string): void {
if (!internalName || this.internalToPublic.hasOwnProperty(internalName)) {
return;
}
let publicName = this.mapName(internalName);
if (this.publicToInternal.hasOwnProperty(publicName)) {
// Create a new XMB when it has already been used
const nextId = this.publicToNextId[publicName];
this.publicToNextId[publicName] = nextId + 1;
publicName = `${publicName}_${nextId}`;
} else {
this.publicToNextId[publicName] = 1;
}
this.internalToPublic[internalName] = publicName;
this.publicToInternal[publicName] = internalName;
}
}