@angular/localize 21.0.0-next.9 → 21.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/_localize-chunk.mjs +206 -445
- package/fesm2022/_localize-chunk.mjs.map +1 -1
- package/fesm2022/init.mjs +1 -2
- package/fesm2022/init.mjs.map +1 -1
- package/fesm2022/localize.mjs +69 -168
- package/fesm2022/localize.mjs.map +1 -1
- package/package.json +3 -3
- package/schematics/ng-add/ng_add_bundle.cjs +1 -1
- package/types/init.d.ts +1 -1
- package/types/localize.d.ts +1 -1
|
@@ -1,495 +1,256 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v21.0.0-
|
|
2
|
+
* @license Angular v21.0.0-rc.1
|
|
3
3
|
* (c) 2010-2025 Google LLC. https://angular.dev/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* The character used to mark the start and end of a "block" in a `$localize` tagged string.
|
|
9
|
-
* A block can indicate metadata about the message or specify a name of a placeholder for a
|
|
10
|
-
* substitution expressions.
|
|
11
|
-
*
|
|
12
|
-
* For example:
|
|
13
|
-
*
|
|
14
|
-
* ```ts
|
|
15
|
-
* $localize`Hello, ${title}:title:!`;
|
|
16
|
-
* $localize`:meaning|description@@id:source message text`;
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
7
|
const BLOCK_MARKER$1 = ':';
|
|
20
|
-
/**
|
|
21
|
-
* The marker used to separate a message's "meaning" from its "description" in a metadata block.
|
|
22
|
-
*
|
|
23
|
-
* For example:
|
|
24
|
-
*
|
|
25
|
-
* ```ts
|
|
26
|
-
* $localize `:correct|Indicates that the user got the answer correct: Right!`;
|
|
27
|
-
* $localize `:movement|Button label for moving to the right: Right!`;
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
8
|
const MEANING_SEPARATOR = '|';
|
|
31
|
-
/**
|
|
32
|
-
* The marker used to separate a message's custom "id" from its "description" in a metadata block.
|
|
33
|
-
*
|
|
34
|
-
* For example:
|
|
35
|
-
*
|
|
36
|
-
* ```ts
|
|
37
|
-
* $localize `:A welcome message on the home page@@myApp-homepage-welcome: Welcome!`;
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
9
|
const ID_SEPARATOR = '@@';
|
|
41
|
-
/**
|
|
42
|
-
* The marker used to separate legacy message ids from the rest of a metadata block.
|
|
43
|
-
*
|
|
44
|
-
* For example:
|
|
45
|
-
*
|
|
46
|
-
* ```ts
|
|
47
|
-
* $localize `:@@custom-id␟2df64767cd895a8fabe3e18b94b5b6b6f9e2e3f0: Welcome!`;
|
|
48
|
-
* ```
|
|
49
|
-
*
|
|
50
|
-
* Note that this character is the "symbol for the unit separator" (␟) not the "unit separator
|
|
51
|
-
* character" itself, since that has no visual representation. See https://graphemica.com/%E2%90%9F.
|
|
52
|
-
*
|
|
53
|
-
* Here is some background for the original "unit separator character":
|
|
54
|
-
* https://stackoverflow.com/questions/8695118/whats-the-file-group-record-unit-separator-control-characters-and-its-usage
|
|
55
|
-
*/
|
|
56
10
|
const LEGACY_ID_INDICATOR = '\u241F';
|
|
57
11
|
|
|
58
|
-
/**
|
|
59
|
-
* A lazily created TextEncoder instance for converting strings into UTF-8 bytes
|
|
60
|
-
*/
|
|
61
12
|
let textEncoder;
|
|
62
|
-
/**
|
|
63
|
-
* Compute the fingerprint of the given string
|
|
64
|
-
*
|
|
65
|
-
* The output is 64 bit number encoded as a decimal string
|
|
66
|
-
*
|
|
67
|
-
* based on:
|
|
68
|
-
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
|
|
69
|
-
*/
|
|
70
13
|
function fingerprint(str) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
14
|
+
textEncoder ??= new TextEncoder();
|
|
15
|
+
const utf8 = textEncoder.encode(str);
|
|
16
|
+
const view = new DataView(utf8.buffer, utf8.byteOffset, utf8.byteLength);
|
|
17
|
+
let hi = hash32(view, utf8.length, 0);
|
|
18
|
+
let lo = hash32(view, utf8.length, 102072);
|
|
19
|
+
if (hi == 0 && (lo == 0 || lo == 1)) {
|
|
20
|
+
hi = hi ^ 0x130f9bef;
|
|
21
|
+
lo = lo ^ -0x6b5f56d8;
|
|
22
|
+
}
|
|
23
|
+
return BigInt.asUintN(32, BigInt(hi)) << BigInt(32) | BigInt.asUintN(32, BigInt(lo));
|
|
81
24
|
}
|
|
82
25
|
function computeMsgId(msg, meaning = '') {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
((msgFingerprint >> BigInt(63)) & BigInt(1));
|
|
90
|
-
msgFingerprint += fingerprint(meaning);
|
|
91
|
-
}
|
|
92
|
-
return BigInt.asUintN(63, msgFingerprint).toString();
|
|
26
|
+
let msgFingerprint = fingerprint(msg);
|
|
27
|
+
if (meaning) {
|
|
28
|
+
msgFingerprint = BigInt.asUintN(64, msgFingerprint << BigInt(1)) | msgFingerprint >> BigInt(63) & BigInt(1);
|
|
29
|
+
msgFingerprint += fingerprint(meaning);
|
|
30
|
+
}
|
|
31
|
+
return BigInt.asUintN(63, msgFingerprint).toString();
|
|
93
32
|
}
|
|
94
33
|
function hash32(view, length, c) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
34
|
+
let a = 0x9e3779b9,
|
|
35
|
+
b = 0x9e3779b9;
|
|
36
|
+
let index = 0;
|
|
37
|
+
const end = length - 12;
|
|
38
|
+
for (; index <= end; index += 12) {
|
|
39
|
+
a += view.getUint32(index, true);
|
|
40
|
+
b += view.getUint32(index + 4, true);
|
|
41
|
+
c += view.getUint32(index + 8, true);
|
|
42
|
+
const res = mix(a, b, c);
|
|
43
|
+
a = res[0], b = res[1], c = res[2];
|
|
44
|
+
}
|
|
45
|
+
const remainder = length - index;
|
|
46
|
+
c += length;
|
|
47
|
+
if (remainder >= 4) {
|
|
48
|
+
a += view.getUint32(index, true);
|
|
49
|
+
index += 4;
|
|
50
|
+
if (remainder >= 8) {
|
|
51
|
+
b += view.getUint32(index, true);
|
|
52
|
+
index += 4;
|
|
53
|
+
if (remainder >= 9) {
|
|
54
|
+
c += view.getUint8(index++) << 8;
|
|
55
|
+
}
|
|
56
|
+
if (remainder >= 10) {
|
|
57
|
+
c += view.getUint8(index++) << 16;
|
|
58
|
+
}
|
|
59
|
+
if (remainder === 11) {
|
|
60
|
+
c += view.getUint8(index++) << 24;
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
if (remainder >= 5) {
|
|
64
|
+
b += view.getUint8(index++);
|
|
65
|
+
}
|
|
66
|
+
if (remainder >= 6) {
|
|
67
|
+
b += view.getUint8(index++) << 8;
|
|
68
|
+
}
|
|
69
|
+
if (remainder === 7) {
|
|
70
|
+
b += view.getUint8(index++) << 16;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
if (remainder >= 1) {
|
|
75
|
+
a += view.getUint8(index++);
|
|
104
76
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
c += length;
|
|
108
|
-
if (remainder >= 4) {
|
|
109
|
-
a += view.getUint32(index, true);
|
|
110
|
-
index += 4;
|
|
111
|
-
if (remainder >= 8) {
|
|
112
|
-
b += view.getUint32(index, true);
|
|
113
|
-
index += 4;
|
|
114
|
-
// Partial 32-bit word for c
|
|
115
|
-
if (remainder >= 9) {
|
|
116
|
-
c += view.getUint8(index++) << 8;
|
|
117
|
-
}
|
|
118
|
-
if (remainder >= 10) {
|
|
119
|
-
c += view.getUint8(index++) << 16;
|
|
120
|
-
}
|
|
121
|
-
if (remainder === 11) {
|
|
122
|
-
c += view.getUint8(index++) << 24;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
// Partial 32-bit word for b
|
|
127
|
-
if (remainder >= 5) {
|
|
128
|
-
b += view.getUint8(index++);
|
|
129
|
-
}
|
|
130
|
-
if (remainder >= 6) {
|
|
131
|
-
b += view.getUint8(index++) << 8;
|
|
132
|
-
}
|
|
133
|
-
if (remainder === 7) {
|
|
134
|
-
b += view.getUint8(index++) << 16;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
77
|
+
if (remainder >= 2) {
|
|
78
|
+
a += view.getUint8(index++) << 8;
|
|
137
79
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (remainder >= 1) {
|
|
141
|
-
a += view.getUint8(index++);
|
|
142
|
-
}
|
|
143
|
-
if (remainder >= 2) {
|
|
144
|
-
a += view.getUint8(index++) << 8;
|
|
145
|
-
}
|
|
146
|
-
if (remainder === 3) {
|
|
147
|
-
a += view.getUint8(index++) << 16;
|
|
148
|
-
}
|
|
80
|
+
if (remainder === 3) {
|
|
81
|
+
a += view.getUint8(index++) << 16;
|
|
149
82
|
}
|
|
150
|
-
|
|
83
|
+
}
|
|
84
|
+
return mix(a, b, c)[2];
|
|
151
85
|
}
|
|
152
86
|
function mix(a, b, c) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
87
|
+
a -= b;
|
|
88
|
+
a -= c;
|
|
89
|
+
a ^= c >>> 13;
|
|
90
|
+
b -= c;
|
|
91
|
+
b -= a;
|
|
92
|
+
b ^= a << 8;
|
|
93
|
+
c -= a;
|
|
94
|
+
c -= b;
|
|
95
|
+
c ^= b >>> 13;
|
|
96
|
+
a -= b;
|
|
97
|
+
a -= c;
|
|
98
|
+
a ^= c >>> 12;
|
|
99
|
+
b -= c;
|
|
100
|
+
b -= a;
|
|
101
|
+
b ^= a << 16;
|
|
102
|
+
c -= a;
|
|
103
|
+
c -= b;
|
|
104
|
+
c ^= b >>> 5;
|
|
105
|
+
a -= b;
|
|
106
|
+
a -= c;
|
|
107
|
+
a ^= c >>> 3;
|
|
108
|
+
b -= c;
|
|
109
|
+
b -= a;
|
|
110
|
+
b ^= a << 10;
|
|
111
|
+
c -= a;
|
|
112
|
+
c -= b;
|
|
113
|
+
c ^= b >>> 15;
|
|
114
|
+
return [a, b, c];
|
|
181
115
|
}
|
|
182
|
-
// Utils
|
|
183
116
|
var Endian;
|
|
184
117
|
(function (Endian) {
|
|
185
|
-
|
|
186
|
-
|
|
118
|
+
Endian[Endian["Little"] = 0] = "Little";
|
|
119
|
+
Endian[Endian["Big"] = 1] = "Big";
|
|
187
120
|
})(Endian || (Endian = {}));
|
|
188
121
|
|
|
189
|
-
// This module specifier is intentionally a relative path to allow bundling the code directly
|
|
190
|
-
// into the package.
|
|
191
|
-
// @ng_package: ignore-cross-repo-import
|
|
192
|
-
/**
|
|
193
|
-
* Parse a `$localize` tagged string into a structure that can be used for translation or
|
|
194
|
-
* extraction.
|
|
195
|
-
*
|
|
196
|
-
* See `ParsedMessage` for an example.
|
|
197
|
-
*/
|
|
198
122
|
function parseMessage(messageParts, expressions, location, messagePartLocations, expressionLocations = []) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
cleanedMessageParts.push(messagePart);
|
|
123
|
+
const substitutions = {};
|
|
124
|
+
const substitutionLocations = {};
|
|
125
|
+
const associatedMessageIds = {};
|
|
126
|
+
const metadata = parseMetadata(messageParts[0], messageParts.raw[0]);
|
|
127
|
+
const cleanedMessageParts = [metadata.text];
|
|
128
|
+
const placeholderNames = [];
|
|
129
|
+
let messageString = metadata.text;
|
|
130
|
+
for (let i = 1; i < messageParts.length; i++) {
|
|
131
|
+
const {
|
|
132
|
+
messagePart,
|
|
133
|
+
placeholderName = computePlaceholderName(i),
|
|
134
|
+
associatedMessageId
|
|
135
|
+
} = parsePlaceholder(messageParts[i], messageParts.raw[i]);
|
|
136
|
+
messageString += `{$${placeholderName}}${messagePart}`;
|
|
137
|
+
if (expressions !== undefined) {
|
|
138
|
+
substitutions[placeholderName] = expressions[i - 1];
|
|
139
|
+
substitutionLocations[placeholderName] = expressionLocations[i - 1];
|
|
218
140
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
141
|
+
placeholderNames.push(placeholderName);
|
|
142
|
+
if (associatedMessageId !== undefined) {
|
|
143
|
+
associatedMessageIds[placeholderName] = associatedMessageId;
|
|
144
|
+
}
|
|
145
|
+
cleanedMessageParts.push(messagePart);
|
|
146
|
+
}
|
|
147
|
+
const messageId = metadata.customId || computeMsgId(messageString, metadata.meaning || '');
|
|
148
|
+
const legacyIds = metadata.legacyIds ? metadata.legacyIds.filter(id => id !== messageId) : [];
|
|
149
|
+
return {
|
|
150
|
+
id: messageId,
|
|
151
|
+
legacyIds,
|
|
152
|
+
substitutions,
|
|
153
|
+
substitutionLocations,
|
|
154
|
+
text: messageString,
|
|
155
|
+
customId: metadata.customId,
|
|
156
|
+
meaning: metadata.meaning || '',
|
|
157
|
+
description: metadata.description || '',
|
|
158
|
+
messageParts: cleanedMessageParts,
|
|
159
|
+
messagePartLocations,
|
|
160
|
+
placeholderNames,
|
|
161
|
+
associatedMessageIds,
|
|
162
|
+
location
|
|
163
|
+
};
|
|
236
164
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Parse the given message part (`cooked` + `raw`) to extract the message metadata from the text.
|
|
239
|
-
*
|
|
240
|
-
* If the message part has a metadata block this function will extract the `meaning`,
|
|
241
|
-
* `description`, `customId` and `legacyId` (if provided) from the block. These metadata properties
|
|
242
|
-
* are serialized in the string delimited by `|`, `@@` and `␟` respectively.
|
|
243
|
-
*
|
|
244
|
-
* (Note that `␟` is the `LEGACY_ID_INDICATOR` - see `constants.ts`.)
|
|
245
|
-
*
|
|
246
|
-
* For example:
|
|
247
|
-
*
|
|
248
|
-
* ```ts
|
|
249
|
-
* `:meaning|description@@custom-id:`
|
|
250
|
-
* `:meaning|@@custom-id:`
|
|
251
|
-
* `:meaning|description:`
|
|
252
|
-
* `:description@@custom-id:`
|
|
253
|
-
* `:meaning|:`
|
|
254
|
-
* `:description:`
|
|
255
|
-
* `:@@custom-id:`
|
|
256
|
-
* `:meaning|description@@custom-id␟legacy-id-1␟legacy-id-2:`
|
|
257
|
-
* ```
|
|
258
|
-
*
|
|
259
|
-
* @param cooked The cooked version of the message part to parse.
|
|
260
|
-
* @param raw The raw version of the message part to parse.
|
|
261
|
-
* @returns A object containing any metadata that was parsed from the message part.
|
|
262
|
-
*/
|
|
263
165
|
function parseMetadata(cooked, raw) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
166
|
+
const {
|
|
167
|
+
text: messageString,
|
|
168
|
+
block
|
|
169
|
+
} = splitBlock(cooked, raw);
|
|
170
|
+
if (block === undefined) {
|
|
171
|
+
return {
|
|
172
|
+
text: messageString
|
|
173
|
+
};
|
|
174
|
+
} else {
|
|
175
|
+
const [meaningDescAndId, ...legacyIds] = block.split(LEGACY_ID_INDICATOR);
|
|
176
|
+
const [meaningAndDesc, customId] = meaningDescAndId.split(ID_SEPARATOR, 2);
|
|
177
|
+
let [meaning, description] = meaningAndDesc.split(MEANING_SEPARATOR, 2);
|
|
178
|
+
if (description === undefined) {
|
|
179
|
+
description = meaning;
|
|
180
|
+
meaning = undefined;
|
|
267
181
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const [meaningAndDesc, customId] = meaningDescAndId.split(ID_SEPARATOR, 2);
|
|
271
|
-
let [meaning, description] = meaningAndDesc.split(MEANING_SEPARATOR, 2);
|
|
272
|
-
if (description === undefined) {
|
|
273
|
-
description = meaning;
|
|
274
|
-
meaning = undefined;
|
|
275
|
-
}
|
|
276
|
-
if (description === '') {
|
|
277
|
-
description = undefined;
|
|
278
|
-
}
|
|
279
|
-
return { text: messageString, meaning, description, customId, legacyIds };
|
|
182
|
+
if (description === '') {
|
|
183
|
+
description = undefined;
|
|
280
184
|
}
|
|
185
|
+
return {
|
|
186
|
+
text: messageString,
|
|
187
|
+
meaning,
|
|
188
|
+
description,
|
|
189
|
+
customId,
|
|
190
|
+
legacyIds
|
|
191
|
+
};
|
|
192
|
+
}
|
|
281
193
|
}
|
|
282
|
-
/**
|
|
283
|
-
* Parse the given message part (`cooked` + `raw`) to extract any placeholder metadata from the
|
|
284
|
-
* text.
|
|
285
|
-
*
|
|
286
|
-
* If the message part has a metadata block this function will extract the `placeholderName` and
|
|
287
|
-
* `associatedMessageId` (if provided) from the block.
|
|
288
|
-
*
|
|
289
|
-
* These metadata properties are serialized in the string delimited by `@@`.
|
|
290
|
-
*
|
|
291
|
-
* For example:
|
|
292
|
-
*
|
|
293
|
-
* ```ts
|
|
294
|
-
* `:placeholder-name@@associated-id:`
|
|
295
|
-
* ```
|
|
296
|
-
*
|
|
297
|
-
* @param cooked The cooked version of the message part to parse.
|
|
298
|
-
* @param raw The raw version of the message part to parse.
|
|
299
|
-
* @returns A object containing the metadata (`placeholderName` and `associatedMessageId`) of the
|
|
300
|
-
* preceding placeholder, along with the static text that follows.
|
|
301
|
-
*/
|
|
302
194
|
function parsePlaceholder(cooked, raw) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
195
|
+
const {
|
|
196
|
+
text: messagePart,
|
|
197
|
+
block
|
|
198
|
+
} = splitBlock(cooked, raw);
|
|
199
|
+
if (block === undefined) {
|
|
200
|
+
return {
|
|
201
|
+
messagePart
|
|
202
|
+
};
|
|
203
|
+
} else {
|
|
204
|
+
const [placeholderName, associatedMessageId] = block.split(ID_SEPARATOR);
|
|
205
|
+
return {
|
|
206
|
+
messagePart,
|
|
207
|
+
placeholderName,
|
|
208
|
+
associatedMessageId
|
|
209
|
+
};
|
|
210
|
+
}
|
|
311
211
|
}
|
|
312
|
-
/**
|
|
313
|
-
* Split a message part (`cooked` + `raw`) into an optional delimited "block" off the front and the
|
|
314
|
-
* rest of the text of the message part.
|
|
315
|
-
*
|
|
316
|
-
* Blocks appear at the start of message parts. They are delimited by a colon `:` character at the
|
|
317
|
-
* start and end of the block.
|
|
318
|
-
*
|
|
319
|
-
* If the block is in the first message part then it will be metadata about the whole message:
|
|
320
|
-
* meaning, description, id. Otherwise it will be metadata about the immediately preceding
|
|
321
|
-
* substitution: placeholder name.
|
|
322
|
-
*
|
|
323
|
-
* Since blocks are optional, it is possible that the content of a message block actually starts
|
|
324
|
-
* with a block marker. In this case the marker must be escaped `\:`.
|
|
325
|
-
*
|
|
326
|
-
* @param cooked The cooked version of the message part to parse.
|
|
327
|
-
* @param raw The raw version of the message part to parse.
|
|
328
|
-
* @returns An object containing the `text` of the message part and the text of the `block`, if it
|
|
329
|
-
* exists.
|
|
330
|
-
* @throws an error if the `block` is unterminated
|
|
331
|
-
*/
|
|
332
212
|
function splitBlock(cooked, raw) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
213
|
+
if (raw.charAt(0) !== BLOCK_MARKER$1) {
|
|
214
|
+
return {
|
|
215
|
+
text: cooked
|
|
216
|
+
};
|
|
217
|
+
} else {
|
|
218
|
+
const endOfBlock = findEndOfBlock(cooked, raw);
|
|
219
|
+
return {
|
|
220
|
+
block: cooked.substring(1, endOfBlock),
|
|
221
|
+
text: cooked.substring(endOfBlock + 1)
|
|
222
|
+
};
|
|
223
|
+
}
|
|
343
224
|
}
|
|
344
225
|
function computePlaceholderName(index) {
|
|
345
|
-
|
|
226
|
+
return index === 1 ? 'PH' : `PH_${index - 1}`;
|
|
346
227
|
}
|
|
347
|
-
/**
|
|
348
|
-
* Find the end of a "marked block" indicated by the first non-escaped colon.
|
|
349
|
-
*
|
|
350
|
-
* @param cooked The cooked string (where escaped chars have been processed)
|
|
351
|
-
* @param raw The raw string (where escape sequences are still in place)
|
|
352
|
-
*
|
|
353
|
-
* @returns the index of the end of block marker
|
|
354
|
-
* @throws an error if the block is unterminated
|
|
355
|
-
*/
|
|
356
228
|
function findEndOfBlock(cooked, raw) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return cookedIndex;
|
|
363
|
-
}
|
|
229
|
+
for (let cookedIndex = 1, rawIndex = 1; cookedIndex < cooked.length; cookedIndex++, rawIndex++) {
|
|
230
|
+
if (raw[rawIndex] === '\\') {
|
|
231
|
+
rawIndex++;
|
|
232
|
+
} else if (cooked[cookedIndex] === BLOCK_MARKER$1) {
|
|
233
|
+
return cookedIndex;
|
|
364
234
|
}
|
|
365
|
-
|
|
235
|
+
}
|
|
236
|
+
throw new Error(`Unterminated $localize metadata block in "${raw}".`);
|
|
366
237
|
}
|
|
367
238
|
|
|
368
|
-
/**
|
|
369
|
-
* Tag a template literal string for localization.
|
|
370
|
-
*
|
|
371
|
-
* For example:
|
|
372
|
-
*
|
|
373
|
-
* ```ts
|
|
374
|
-
* $localize `some string to localize`
|
|
375
|
-
* ```
|
|
376
|
-
*
|
|
377
|
-
* **Providing meaning, description and id**
|
|
378
|
-
*
|
|
379
|
-
* You can optionally specify one or more of `meaning`, `description` and `id` for a localized
|
|
380
|
-
* string by pre-pending it with a colon delimited block of the form:
|
|
381
|
-
*
|
|
382
|
-
* ```ts
|
|
383
|
-
* $localize`:meaning|description@@id:source message text`;
|
|
384
|
-
*
|
|
385
|
-
* $localize`:meaning|:source message text`;
|
|
386
|
-
* $localize`:description:source message text`;
|
|
387
|
-
* $localize`:@@id:source message text`;
|
|
388
|
-
* ```
|
|
389
|
-
*
|
|
390
|
-
* This format is the same as that used for `i18n` markers in Angular templates. See the
|
|
391
|
-
* [Angular i18n guide](guide/i18n/prepare#mark-text-in-component-template).
|
|
392
|
-
*
|
|
393
|
-
* **Naming placeholders**
|
|
394
|
-
*
|
|
395
|
-
* If the template literal string contains expressions, then the expressions will be automatically
|
|
396
|
-
* associated with placeholder names for you.
|
|
397
|
-
*
|
|
398
|
-
* For example:
|
|
399
|
-
*
|
|
400
|
-
* ```ts
|
|
401
|
-
* $localize `Hi ${name}! There are ${items.length} items.`;
|
|
402
|
-
* ```
|
|
403
|
-
*
|
|
404
|
-
* will generate a message-source of `Hi {$PH}! There are {$PH_1} items`.
|
|
405
|
-
*
|
|
406
|
-
* The recommended practice is to name the placeholder associated with each expression though.
|
|
407
|
-
*
|
|
408
|
-
* Do this by providing the placeholder name wrapped in `:` characters directly after the
|
|
409
|
-
* expression. These placeholder names are stripped out of the rendered localized string.
|
|
410
|
-
*
|
|
411
|
-
* For example, to name the `items.length` expression placeholder `itemCount` you write:
|
|
412
|
-
*
|
|
413
|
-
* ```ts
|
|
414
|
-
* $localize `There are ${items.length}:itemCount: items`;
|
|
415
|
-
* ```
|
|
416
|
-
*
|
|
417
|
-
* **Escaping colon markers**
|
|
418
|
-
*
|
|
419
|
-
* If you need to use a `:` character directly at the start of a tagged string that has no
|
|
420
|
-
* metadata block, or directly after a substitution expression that has no name you must escape
|
|
421
|
-
* the `:` by preceding it with a backslash:
|
|
422
|
-
*
|
|
423
|
-
* For example:
|
|
424
|
-
*
|
|
425
|
-
* ```ts
|
|
426
|
-
* // message has a metadata block so no need to escape colon
|
|
427
|
-
* $localize `:some description::this message starts with a colon (:)`;
|
|
428
|
-
* // no metadata block so the colon must be escaped
|
|
429
|
-
* $localize `\:this message starts with a colon (:)`;
|
|
430
|
-
* ```
|
|
431
|
-
*
|
|
432
|
-
* ```ts
|
|
433
|
-
* // named substitution so no need to escape colon
|
|
434
|
-
* $localize `${label}:label:: ${}`
|
|
435
|
-
* // anonymous substitution so colon must be escaped
|
|
436
|
-
* $localize `${label}\: ${}`
|
|
437
|
-
* ```
|
|
438
|
-
*
|
|
439
|
-
* **Processing localized strings:**
|
|
440
|
-
*
|
|
441
|
-
* There are three scenarios:
|
|
442
|
-
*
|
|
443
|
-
* * **compile-time inlining**: the `$localize` tag is transformed at compile time by a
|
|
444
|
-
* transpiler, removing the tag and replacing the template literal string with a translated
|
|
445
|
-
* literal string from a collection of translations provided to the transpilation tool.
|
|
446
|
-
*
|
|
447
|
-
* * **run-time evaluation**: the `$localize` tag is a run-time function that replaces and
|
|
448
|
-
* reorders the parts (static strings and expressions) of the template literal string with strings
|
|
449
|
-
* from a collection of translations loaded at run-time.
|
|
450
|
-
*
|
|
451
|
-
* * **pass-through evaluation**: the `$localize` tag is a run-time function that simply evaluates
|
|
452
|
-
* the original template literal string without applying any translations to the parts. This
|
|
453
|
-
* version is used during development or where there is no need to translate the localized
|
|
454
|
-
* template literals.
|
|
455
|
-
*
|
|
456
|
-
* @param messageParts a collection of the static parts of the template string.
|
|
457
|
-
* @param expressions a collection of the values of each placeholder in the template string.
|
|
458
|
-
* @returns the translated string, with the `messageParts` and `expressions` interleaved together.
|
|
459
|
-
*
|
|
460
|
-
* @publicApi
|
|
461
|
-
*/
|
|
462
239
|
const $localize = function (messageParts, ...expressions) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return message;
|
|
240
|
+
if ($localize.translate) {
|
|
241
|
+
const translation = $localize.translate(messageParts, expressions);
|
|
242
|
+
messageParts = translation[0];
|
|
243
|
+
expressions = translation[1];
|
|
244
|
+
}
|
|
245
|
+
let message = stripBlock(messageParts[0], messageParts.raw[0]);
|
|
246
|
+
for (let i = 1; i < messageParts.length; i++) {
|
|
247
|
+
message += expressions[i - 1] + stripBlock(messageParts[i], messageParts.raw[i]);
|
|
248
|
+
}
|
|
249
|
+
return message;
|
|
474
250
|
};
|
|
475
251
|
const BLOCK_MARKER = ':';
|
|
476
|
-
/**
|
|
477
|
-
* Strip a delimited "block" from the start of the `messagePart`, if it is found.
|
|
478
|
-
*
|
|
479
|
-
* If a marker character (:) actually appears in the content at the start of a tagged string or
|
|
480
|
-
* after a substitution expression, where a block has not been provided the character must be
|
|
481
|
-
* escaped with a backslash, `\:`. This function checks for this by looking at the `raw`
|
|
482
|
-
* messagePart, which should still contain the backslash.
|
|
483
|
-
*
|
|
484
|
-
* @param messagePart The cooked message part to process.
|
|
485
|
-
* @param rawMessagePart The raw message part to check.
|
|
486
|
-
* @returns the message part with the placeholder name stripped, if found.
|
|
487
|
-
* @throws an error if the block is unterminated
|
|
488
|
-
*/
|
|
489
252
|
function stripBlock(messagePart, rawMessagePart) {
|
|
490
|
-
|
|
491
|
-
? messagePart.substring(findEndOfBlock(messagePart, rawMessagePart) + 1)
|
|
492
|
-
: messagePart;
|
|
253
|
+
return rawMessagePart.charAt(0) === BLOCK_MARKER ? messagePart.substring(findEndOfBlock(messagePart, rawMessagePart) + 1) : messagePart;
|
|
493
254
|
}
|
|
494
255
|
|
|
495
256
|
export { $localize, BLOCK_MARKER$1 as BLOCK_MARKER, computeMsgId, findEndOfBlock, parseMessage, parseMetadata, splitBlock };
|