@magic-translate/angular 1.0.2 → 1.2.0
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0](https://github.com/magic-translate/magic-translate/compare/angular-v1.1.0...angular-v1.2.0) (2026-11-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **angular:** translate with the secret key on the server ([5f6fae1](https://github.com/magic-translate/magic-translate/commit/5f6fae1fdbaebefe0cb7c62939eb3a03a8419950))
|
|
9
|
+
* id option to edit one use of a text on its own ([9127af3](https://github.com/magic-translate/magic-translate/commit/9127af36de4d63be076e8e192a16584968b4655a))
|
|
10
|
+
|
|
11
|
+
## [1.1.0](https://github.com/magic-translate/magic-translate/compare/angular-v1.0.2...angular-v1.1.0) (2026-11-02)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* **angular:** translate text with variables as one cached template ([2f60135](https://github.com/magic-translate/magic-translate/commit/2f60135f4b7b09719a575bee4176367cea0fc6e7))
|
|
17
|
+
|
|
3
18
|
## [1.0.2](https://github.com/magic-translate/magic-translate/compare/angular-v1.0.1...angular-v1.0.2) (2026-10-29)
|
|
4
19
|
|
|
5
20
|
|
package/README.md
CHANGED
|
@@ -105,6 +105,50 @@ export class Search {}
|
|
|
105
105
|
|
|
106
106
|
The pipe returns the original text until the translation has loaded.
|
|
107
107
|
|
|
108
|
+
### Variables
|
|
109
|
+
|
|
110
|
+
Pass values that change, such as names or counts, in `[v]` and reference them
|
|
111
|
+
as `$name`. The text is translated once as a template, `Hi {0}, you have {1}
|
|
112
|
+
new messages`, and cached, however many different values it is shown with. The
|
|
113
|
+
values themselves are never sent to the API.
|
|
114
|
+
|
|
115
|
+
```html
|
|
116
|
+
<t [v]="{ name: user.name, count: unread() }">
|
|
117
|
+
Hi $name, you have $count new messages
|
|
118
|
+
</t>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A translation can move the values to wherever the language needs them, and the
|
|
122
|
+
text around them can contain HTML:
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<t [v]="{ count: cart.items.length, total: cart.total | currency }">
|
|
126
|
+
You have <b>$count items</b> in your cart, totalling <b>$total</b>.
|
|
127
|
+
</t>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
When only the values change, the cached translation is reused and no request is
|
|
131
|
+
made.
|
|
132
|
+
|
|
133
|
+
- Only keys of `[v]` are replaced, so text like `$5` stays as it is. If two
|
|
134
|
+
keys start the same way, the longest one wins.
|
|
135
|
+
- Values are inserted as text, never as HTML. Put markup in the text around
|
|
136
|
+
them instead: `Hi <b>$name</b>`.
|
|
137
|
+
- Variables are replaced in text only, not in attributes such as `href`.
|
|
138
|
+
- In development mode, a key that is not found in the text logs a warning.
|
|
139
|
+
|
|
140
|
+
The pipe and `translate` take the values in `v` as well:
|
|
141
|
+
|
|
142
|
+
```html
|
|
143
|
+
<input
|
|
144
|
+
[placeholder]="'Search $count products' | t: { v: { count: products.length } }"
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
await magicTranslate.translate('Your order $id has shipped', { v: { id } });
|
|
150
|
+
```
|
|
151
|
+
|
|
108
152
|
### Options per translation
|
|
109
153
|
|
|
110
154
|
Both the component and the pipe can override the target language and the
|
|
@@ -116,6 +160,17 @@ formality:
|
|
|
116
160
|
<input [placeholder]="'Your name' | t: { lang: 'fr', formality: 'formal' }" />
|
|
117
161
|
```
|
|
118
162
|
|
|
163
|
+
An `id` lets you edit one use of a text on its own in the webapp, e.g. to
|
|
164
|
+
translate the same "Save" differently on two buttons. Its edited translation
|
|
165
|
+
stays when the text changes. Ids are unique within your account and at most 128
|
|
166
|
+
characters.
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<t id="checkout-save">Save</t>
|
|
170
|
+
|
|
171
|
+
<button [title]="'Save' | t: { id: 'checkout-save' }"></button>
|
|
172
|
+
```
|
|
173
|
+
|
|
119
174
|
### Switching languages
|
|
120
175
|
|
|
121
176
|
Inject the `MagicTranslate` service to read or change the current language.
|
|
@@ -169,7 +224,15 @@ const message = await magicTranslate.translate('Your order has been placed');
|
|
|
169
224
|
## Server-side rendering
|
|
170
225
|
|
|
171
226
|
Translations are only loaded in the browser. With SSR the server renders the
|
|
172
|
-
original content, and it is translated after hydration.
|
|
227
|
+
original content, and it is translated after hydration. A `<t>` with `[v]`
|
|
228
|
+
renders empty on the server, so `$name` never shows, and appears once the app
|
|
229
|
+
runs in the browser.
|
|
230
|
+
|
|
231
|
+
Code that translates on the server, such as a resolver run while prerendering,
|
|
232
|
+
sends your secret key (`p-...`) from the `MAGIC_TRANSLATE_SECRET_KEY`
|
|
233
|
+
environment variable instead of the API key. Create it on your
|
|
234
|
+
[account page](https://magictranslate.io/account) and keep it out of
|
|
235
|
+
`provideMagicTranslate`, which also runs in the browser.
|
|
173
236
|
|
|
174
237
|
## License
|
|
175
238
|
|
|
@@ -1,10 +1,72 @@
|
|
|
1
1
|
import { DataLoader, cacheKeyFn, defaultLoaderFactory, libraryName, libraryWebsite } from "@magic-translate/core";
|
|
2
2
|
import * as i0 from "@angular/core";
|
|
3
|
-
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Injectable, InjectionToken, PLATFORM_ID, Pipe, SecurityContext, ViewEncapsulation, afterNextRender, computed, effect, inject, input, makeEnvironmentProviders, signal, viewChild } from "@angular/core";
|
|
3
|
+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, Injectable, InjectionToken, PLATFORM_ID, Pipe, SecurityContext, ViewEncapsulation, afterNextRender, computed, effect, inject, input, isDevMode, makeEnvironmentProviders, signal, untracked, viewChild } from "@angular/core";
|
|
4
4
|
import { DomSanitizer } from "@angular/platform-browser";
|
|
5
5
|
import { isPlatformServer } from "@angular/common";
|
|
6
6
|
export * from "@magic-translate/core";
|
|
7
7
|
const MAGIC_TRANSLATE_CONFIG = new InjectionToken("MAGIC_TRANSLATE_CONFIG");
|
|
8
|
+
const PLACEHOLDER = /\{(\d+)\}/g;
|
|
9
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10
|
+
function templater(variables) {
|
|
11
|
+
const names = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
12
|
+
const pattern = names.length > 0 ? new RegExp(`\\$(${names.map(escapeRegExp).join("|")})`, "g") : null;
|
|
13
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
14
|
+
const values = [];
|
|
15
|
+
return {
|
|
16
|
+
values,
|
|
17
|
+
replace(text) {
|
|
18
|
+
if (!pattern) return text;
|
|
19
|
+
return text.replace(pattern, (_, name) => {
|
|
20
|
+
let index = indexes.get(name);
|
|
21
|
+
if (index === void 0) {
|
|
22
|
+
index = values.push(String(variables[name] ?? "")) - 1;
|
|
23
|
+
indexes.set(name, index);
|
|
24
|
+
}
|
|
25
|
+
return `{${index}}`;
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
unused: () => names.filter((name) => !indexes.has(name))
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function warnAboutUnused(unused) {
|
|
32
|
+
if (isDevMode() && unused.length > 0) console.warn(`[${libraryName}]: ${unused.map((name) => `$${name}`).join(", ")} not found in the text. Check the spelling, or remove ${unused.length === 1 ? "it" : "them"} from the variables.`);
|
|
33
|
+
}
|
|
34
|
+
function toTemplate(text, variables) {
|
|
35
|
+
const { replace, values, unused } = templater(variables);
|
|
36
|
+
return {
|
|
37
|
+
template: replace(text),
|
|
38
|
+
values,
|
|
39
|
+
unused: unused()
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function fillPlaceholders(text, values) {
|
|
43
|
+
return text.replace(PLACEHOLDER, (placeholder, index) => Number(index) < values.length ? values[Number(index)] : placeholder);
|
|
44
|
+
}
|
|
45
|
+
function textNodes(root) {
|
|
46
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
47
|
+
const nodes = [];
|
|
48
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) nodes.push(node);
|
|
49
|
+
return nodes;
|
|
50
|
+
}
|
|
51
|
+
function toHtmlTemplate(html, variables) {
|
|
52
|
+
const container = document.createElement("template");
|
|
53
|
+
container.innerHTML = html;
|
|
54
|
+
const { replace, values, unused } = templater(variables);
|
|
55
|
+
textNodes(container.content).forEach((node) => {
|
|
56
|
+
node.data = replace(node.data);
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
template: container.innerHTML,
|
|
60
|
+
values,
|
|
61
|
+
unused: unused()
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function fillElement(element, values) {
|
|
65
|
+
textNodes(element).forEach((node) => {
|
|
66
|
+
const filled = fillPlaceholders(node.data, values);
|
|
67
|
+
if (filled !== node.data) node.data = filled;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
8
70
|
var MagicTranslate = class MagicTranslate {
|
|
9
71
|
config = inject(MAGIC_TRANSLATE_CONFIG);
|
|
10
72
|
languageOverride = signal(null, ...ngDevMode ? [{ debugName: "languageOverride" }] : /* istanbul ignore next */ []);
|
|
@@ -12,12 +74,13 @@ var MagicTranslate = class MagicTranslate {
|
|
|
12
74
|
sourceLanguage = this.config.sourceLanguage;
|
|
13
75
|
language = computed(() => this.languageOverride() ?? this.config.language, ...ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []);
|
|
14
76
|
dataLoader = computed(() => {
|
|
15
|
-
const { debug, apiKey, sourceLanguage, _apiUrl, formality } = this.config;
|
|
77
|
+
const { debug, apiKey, secretKey, sourceLanguage, _apiUrl, formality } = this.config;
|
|
16
78
|
const loaderFactory = this.config._loaderFactory ?? defaultLoaderFactory;
|
|
17
79
|
const language = this.language();
|
|
18
80
|
return new DataLoader(loaderFactory({
|
|
19
81
|
debug: !!debug,
|
|
20
82
|
apiKey,
|
|
83
|
+
secretKey,
|
|
21
84
|
lang: language,
|
|
22
85
|
sourceLang: sourceLanguage,
|
|
23
86
|
apiUrl: _apiUrl,
|
|
@@ -30,11 +93,17 @@ var MagicTranslate = class MagicTranslate {
|
|
|
30
93
|
setLanguage(language) {
|
|
31
94
|
this.languageOverride.set(language);
|
|
32
95
|
}
|
|
33
|
-
translate(text, params = {}) {
|
|
34
|
-
return this.dataLoader().load({
|
|
96
|
+
translate(text, { v, ...params } = {}) {
|
|
97
|
+
if (!v) return this.dataLoader().load({
|
|
35
98
|
text,
|
|
36
99
|
...params
|
|
37
100
|
});
|
|
101
|
+
const { template, values, unused } = toTemplate(text, v);
|
|
102
|
+
warnAboutUnused(unused);
|
|
103
|
+
return this.dataLoader().load({
|
|
104
|
+
text: template,
|
|
105
|
+
...params
|
|
106
|
+
}).then((translation) => fillPlaceholders(translation, values));
|
|
38
107
|
}
|
|
39
108
|
cacheKey(text, params = {}) {
|
|
40
109
|
return cacheKeyFn(this.apiKey, this.sourceLanguage, this.language())({
|
|
@@ -100,6 +169,8 @@ function readSource(element) {
|
|
|
100
169
|
var Translate = class Translate {
|
|
101
170
|
lang = input(void 0, ...ngDevMode ? [{ debugName: "lang" }] : /* istanbul ignore next */ []);
|
|
102
171
|
formality = input(void 0, ...ngDevMode ? [{ debugName: "formality" }] : /* istanbul ignore next */ []);
|
|
172
|
+
id = input(void 0, ...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
|
|
173
|
+
v = input(void 0, ...ngDevMode ? [{ debugName: "v" }] : /* istanbul ignore next */ []);
|
|
103
174
|
magicTranslate = inject(MagicTranslate);
|
|
104
175
|
sanitizer = inject(DomSanitizer);
|
|
105
176
|
sourceRef = viewChild.required("source", ...ngDevMode ? [{ debugName: "sourceRef" }] : /* istanbul ignore next */ []);
|
|
@@ -107,6 +178,7 @@ var Translate = class Translate {
|
|
|
107
178
|
source = signal(null, ...ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []);
|
|
108
179
|
translated = signal(false, ...ngDevMode ? [{ debugName: "translated" }] : /* istanbul ignore next */ []);
|
|
109
180
|
lastRequestId = 0;
|
|
181
|
+
lastTemplate;
|
|
110
182
|
constructor() {
|
|
111
183
|
const destroyRef = inject(DestroyRef);
|
|
112
184
|
afterNextRender(() => {
|
|
@@ -126,21 +198,34 @@ var Translate = class Translate {
|
|
|
126
198
|
const source = this.source();
|
|
127
199
|
const lang = this.lang();
|
|
128
200
|
const formality = this.formality();
|
|
201
|
+
const id = this.id();
|
|
202
|
+
const variables = this.v();
|
|
129
203
|
this.magicTranslate.language();
|
|
130
204
|
if (!source?.html) {
|
|
131
205
|
this.translated.set(false);
|
|
132
206
|
return;
|
|
133
207
|
}
|
|
208
|
+
const { template, values, unused } = variables ? toHtmlTemplate(source.html, variables) : {
|
|
209
|
+
template: source.html,
|
|
210
|
+
values: [],
|
|
211
|
+
unused: []
|
|
212
|
+
};
|
|
213
|
+
if (template !== this.lastTemplate) {
|
|
214
|
+
this.lastTemplate = template;
|
|
215
|
+
warnAboutUnused(unused);
|
|
216
|
+
}
|
|
217
|
+
if (variables && !untracked(this.translated)) this.render(source, template, values);
|
|
134
218
|
const requestId = ++this.lastRequestId;
|
|
135
|
-
this.magicTranslate.translate(
|
|
219
|
+
this.magicTranslate.translate(template, {
|
|
136
220
|
lang,
|
|
137
|
-
formality
|
|
221
|
+
formality,
|
|
222
|
+
id
|
|
138
223
|
}).then((translation) => {
|
|
139
|
-
if (requestId === this.lastRequestId) this.render(source, translation);
|
|
224
|
+
if (requestId === this.lastRequestId) this.render(source, translation, values);
|
|
140
225
|
});
|
|
141
226
|
});
|
|
142
227
|
}
|
|
143
|
-
render(source, translation) {
|
|
228
|
+
render(source, translation, values = []) {
|
|
144
229
|
const target = this.targetRef().nativeElement;
|
|
145
230
|
const html = this.sanitizer.sanitize(SecurityContext.HTML, translation);
|
|
146
231
|
if (html === null) {
|
|
@@ -148,6 +233,7 @@ var Translate = class Translate {
|
|
|
148
233
|
return;
|
|
149
234
|
}
|
|
150
235
|
target.innerHTML = `${source.leadingWhitespace}${html}${source.trailingWhitespace}`;
|
|
236
|
+
if (values.length > 0) fillElement(target, values);
|
|
151
237
|
if (source.scopeAttribute) target.querySelectorAll("*").forEach((el) => el.setAttribute(source.scopeAttribute, ""));
|
|
152
238
|
this.translated.set(true);
|
|
153
239
|
}
|
|
@@ -179,9 +265,26 @@ var Translate = class Translate {
|
|
|
179
265
|
isSignal: true,
|
|
180
266
|
isRequired: false,
|
|
181
267
|
transformFunction: null
|
|
268
|
+
},
|
|
269
|
+
id: {
|
|
270
|
+
classPropertyName: "id",
|
|
271
|
+
publicName: "id",
|
|
272
|
+
isSignal: true,
|
|
273
|
+
isRequired: false,
|
|
274
|
+
transformFunction: null
|
|
275
|
+
},
|
|
276
|
+
v: {
|
|
277
|
+
classPropertyName: "v",
|
|
278
|
+
publicName: "v",
|
|
279
|
+
isSignal: true,
|
|
280
|
+
isRequired: false,
|
|
281
|
+
transformFunction: null
|
|
182
282
|
}
|
|
183
283
|
},
|
|
184
|
-
host: {
|
|
284
|
+
host: {
|
|
285
|
+
properties: { "attr.id": "null" },
|
|
286
|
+
styleAttribute: "display: contents"
|
|
287
|
+
},
|
|
185
288
|
viewQueries: [{
|
|
186
289
|
propertyName: "sourceRef",
|
|
187
290
|
first: true,
|
|
@@ -196,7 +299,9 @@ var Translate = class Translate {
|
|
|
196
299
|
isSignal: true
|
|
197
300
|
}],
|
|
198
301
|
ngImport: i0,
|
|
199
|
-
template: `<span
|
|
302
|
+
template: `<span
|
|
303
|
+
#source
|
|
304
|
+
[style.display]="translated() || v() ? 'none' : 'contents'"
|
|
200
305
|
><ng-content /></span
|
|
201
306
|
><span
|
|
202
307
|
#target
|
|
@@ -216,13 +321,18 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
216
321
|
type: Component,
|
|
217
322
|
args: [{
|
|
218
323
|
selector: "t, mt-translate",
|
|
219
|
-
template: `<span
|
|
324
|
+
template: `<span
|
|
325
|
+
#source
|
|
326
|
+
[style.display]="translated() || v() ? 'none' : 'contents'"
|
|
220
327
|
><ng-content /></span
|
|
221
328
|
><span
|
|
222
329
|
#target
|
|
223
330
|
[style.display]="translated() ? 'contents' : 'none'"
|
|
224
331
|
></span>`,
|
|
225
|
-
host: {
|
|
332
|
+
host: {
|
|
333
|
+
style: "display: contents",
|
|
334
|
+
"[attr.id]": "null"
|
|
335
|
+
},
|
|
226
336
|
encapsulation: ViewEncapsulation.None,
|
|
227
337
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
228
338
|
}]
|
|
@@ -245,6 +355,22 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
245
355
|
required: false
|
|
246
356
|
}]
|
|
247
357
|
}],
|
|
358
|
+
id: [{
|
|
359
|
+
type: i0.Input,
|
|
360
|
+
args: [{
|
|
361
|
+
isSignal: true,
|
|
362
|
+
alias: "id",
|
|
363
|
+
required: false
|
|
364
|
+
}]
|
|
365
|
+
}],
|
|
366
|
+
v: [{
|
|
367
|
+
type: i0.Input,
|
|
368
|
+
args: [{
|
|
369
|
+
isSignal: true,
|
|
370
|
+
alias: "v",
|
|
371
|
+
required: false
|
|
372
|
+
}]
|
|
373
|
+
}],
|
|
248
374
|
sourceRef: [{
|
|
249
375
|
type: i0.ViewChild,
|
|
250
376
|
args: ["source", { isSignal: true }]
|
|
@@ -262,20 +388,26 @@ var TranslatePipe = class TranslatePipe {
|
|
|
262
388
|
isServer = isPlatformServer(inject(PLATFORM_ID));
|
|
263
389
|
key;
|
|
264
390
|
translation;
|
|
265
|
-
transform(text, params = {}) {
|
|
266
|
-
|
|
267
|
-
|
|
391
|
+
transform(text, { v, ...params } = {}) {
|
|
392
|
+
const { template, values, unused } = v ? toTemplate(text, v) : {
|
|
393
|
+
template: text,
|
|
394
|
+
values: [],
|
|
395
|
+
unused: []
|
|
396
|
+
};
|
|
397
|
+
if (!text || this.isServer) return fillPlaceholders(template, values);
|
|
398
|
+
const key = this.magicTranslate.cacheKey(template, params);
|
|
268
399
|
if (key !== this.key) {
|
|
400
|
+
warnAboutUnused(unused);
|
|
269
401
|
this.key = key;
|
|
270
402
|
this.translation = void 0;
|
|
271
|
-
this.magicTranslate.translate(
|
|
403
|
+
this.magicTranslate.translate(template, params).then((translation) => {
|
|
272
404
|
if (this.key === key) {
|
|
273
405
|
this.translation = translation;
|
|
274
406
|
this.changeDetectorRef.markForCheck();
|
|
275
407
|
}
|
|
276
408
|
});
|
|
277
409
|
}
|
|
278
|
-
return this.translation ??
|
|
410
|
+
return fillPlaceholders(this.translation ?? template, values);
|
|
279
411
|
}
|
|
280
412
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
281
413
|
minVersion: "12.0.0",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"magic-translate-angular.mjs","names":[],"sources":["../../src/lib/magic-translate-config.ts","../../src/lib/magic-translate.service.ts","../../src/lib/provide-magic-translate.ts","../../src/lib/translate.component.ts","../../src/lib/translate.pipe.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { MagicTranslateConfig } from '@magic-translate/core';\n\nexport const MAGIC_TRANSLATE_CONFIG = new InjectionToken<MagicTranslateConfig>(\n 'MAGIC_TRANSLATE_CONFIG'\n);\n","import { computed, inject, Injectable, signal } from '@angular/core';\nimport {\n cacheKeyFn,\n DataLoader,\n DataLoaderLoadParams,\n defaultLoaderFactory,\n Language,\n libraryWebsite,\n} from '@magic-translate/core';\nimport { MAGIC_TRANSLATE_CONFIG } from './magic-translate-config';\n\n/**\n * Type alias to make the language enum more flexible to also allow passing language strings\n * instead of just the enum.\n */\nexport type Languages = `${Language}`;\n\nexport type TranslateParams = Omit<DataLoaderLoadParams, 'text'>;\n\n@Injectable()\nexport class MagicTranslate {\n private readonly config = inject(MAGIC_TRANSLATE_CONFIG);\n private readonly languageOverride = signal<Languages | null>(null);\n\n readonly apiKey = this.config.apiKey;\n readonly sourceLanguage = this.config.sourceLanguage;\n\n /**\n * The language content is currently translated to.\n */\n readonly language = computed(\n () => this.languageOverride() ?? this.config.language\n );\n\n readonly dataLoader = computed(() => {\n const { debug, apiKey, sourceLanguage, _apiUrl, formality } = this.config;\n const loaderFactory = this.config._loaderFactory ?? defaultLoaderFactory;\n const language = this.language();\n\n return new DataLoader<DataLoaderLoadParams, string, string>(\n loaderFactory({\n debug: !!debug,\n apiKey,\n lang: language,\n sourceLang: sourceLanguage,\n apiUrl: _apiUrl,\n formality,\n }),\n { cacheKeyFn: cacheKeyFn(apiKey, sourceLanguage, language) }\n );\n });\n\n constructor() {\n if (!this.config.apiKey) {\n throw new Error(`Missing API key. Please get one at ${libraryWebsite}`);\n }\n }\n\n setLanguage(language: Languages): void {\n this.languageOverride.set(language);\n }\n\n translate(text: string, params: TranslateParams = {}): Promise<string> {\n return this.dataLoader().load({ text, ...params });\n }\n\n /** @ignore */\n cacheKey(text: string, params: TranslateParams = {}): string {\n return cacheKeyFn(\n this.apiKey,\n this.sourceLanguage,\n this.language()\n )({ text, ...params });\n }\n}\n","import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { MagicTranslateConfig } from '@magic-translate/core';\nimport { MAGIC_TRANSLATE_CONFIG } from './magic-translate-config';\nimport { MagicTranslate } from './magic-translate.service';\n\n/**\n * Sets up Magic Translate for the application. Add it to the `providers` of\n * your application config.\n */\nexport function provideMagicTranslate(\n config: MagicTranslateConfig\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: MAGIC_TRANSLATE_CONFIG, useValue: config },\n MagicTranslate,\n ]);\n}\n","import {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n effect,\n ElementRef,\n inject,\n input,\n SecurityContext,\n signal,\n viewChild,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { libraryName, TranslateOptions } from '@magic-translate/core';\nimport { Languages, MagicTranslate } from './magic-translate.service';\n\ninterface Source {\n html: string;\n leadingWhitespace: string;\n trailingWhitespace: string;\n // The `_ngcontent-*` attribute of the host template, so its component\n // styles still apply to the translated markup.\n scopeAttribute?: string;\n}\n\n/**\n * Turns the projected content into the HTML string that gets translated,\n * without the comments and attributes Angular adds to the DOM.\n */\nfunction readSource(element: HTMLElement): Source {\n const clone = element.cloneNode(true) as HTMLElement;\n const comments: Node[] = [];\n let scopeAttribute: string | undefined;\n\n const walker = document.createTreeWalker(\n clone,\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT\n );\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === Node.COMMENT_NODE) {\n comments.push(node);\n continue;\n }\n\n const el = node as Element;\n if (el.localName.includes('-')) {\n throw new Error(\n `Components are not allowed inside \\`<t>...</t>\\` components. The component found inside the t component is <${el.localName}>.`\n );\n }\n\n for (const { name } of Array.from(el.attributes)) {\n if (name.startsWith('_ngcontent-')) {\n scopeAttribute = name;\n }\n if (name.startsWith('_ng') || name.startsWith('ng-reflect-')) {\n el.removeAttribute(name);\n }\n }\n }\n comments.forEach((comment) => comment.parentNode?.removeChild(comment));\n\n const [, leadingWhitespace, html, trailingWhitespace] =\n /^(\\s*)([\\s\\S]*?)(\\s*)$/.exec(clone.innerHTML)!;\n\n return { html, leadingWhitespace, trailingWhitespace, scopeAttribute };\n}\n\n@Component({\n selector: 't, mt-translate',\n template: `<span #source [style.display]=\"translated() ? 'none' : 'contents'\"\n ><ng-content /></span\n ><span\n #target\n [style.display]=\"translated() ? 'contents' : 'none'\"\n ></span>`,\n host: { style: 'display: contents' },\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class Translate {\n readonly lang = input<Languages | undefined>(undefined);\n readonly formality = input<TranslateOptions['formality']>(undefined);\n\n private readonly magicTranslate = inject(MagicTranslate);\n private readonly sanitizer = inject(DomSanitizer);\n private readonly sourceRef =\n viewChild.required<ElementRef<HTMLElement>>('source');\n private readonly targetRef =\n viewChild.required<ElementRef<HTMLElement>>('target');\n\n private readonly source = signal<Source | null>(null);\n protected readonly translated = signal(false);\n private lastRequestId = 0;\n\n constructor() {\n const destroyRef = inject(DestroyRef);\n\n afterNextRender(() => {\n const element = this.sourceRef().nativeElement;\n const read = () => this.source.set(readSource(element));\n read();\n\n // Picks up changes to the projected content, such as interpolations.\n const observer = new MutationObserver(read);\n observer.observe(element, {\n subtree: true,\n childList: true,\n characterData: true,\n attributes: true,\n });\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n effect(() => {\n const source = this.source();\n const lang = this.lang();\n const formality = this.formality();\n // a language switch has to re-translate the content\n this.magicTranslate.language();\n\n if (!source?.html) {\n this.translated.set(false);\n return;\n }\n\n // make sure that we only ever use the response of the most recent request.\n const requestId = ++this.lastRequestId;\n this.magicTranslate\n .translate(source.html, { lang, formality })\n .then((translation) => {\n if (requestId === this.lastRequestId) {\n this.render(source, translation);\n }\n });\n });\n }\n\n private render(source: Source, translation: string) {\n const target = this.targetRef().nativeElement;\n const html = this.sanitizer.sanitize(SecurityContext.HTML, translation);\n if (html === null) {\n console.warn(`[${libraryName}]: Could not render translation.`);\n return;\n }\n\n target.innerHTML = `${source.leadingWhitespace}${html}${source.trailingWhitespace}`;\n if (source.scopeAttribute) {\n target\n .querySelectorAll('*')\n .forEach((el) => el.setAttribute(source.scopeAttribute!, ''));\n }\n this.translated.set(true);\n }\n}\n\nexport const T = Translate;\n","import { isPlatformServer } from '@angular/common';\nimport {\n ChangeDetectorRef,\n inject,\n Pipe,\n PipeTransform,\n PLATFORM_ID,\n} from '@angular/core';\nimport { MagicTranslate, TranslateParams } from './magic-translate.service';\n\n/**\n * Translates a plain string, e.g. for attributes:\n * `<input [placeholder]=\"'Search' | t\" />`. Returns the original text until\n * the translation has loaded.\n */\n@Pipe({ name: 't', pure: false })\nexport class TranslatePipe implements PipeTransform {\n private readonly magicTranslate = inject(MagicTranslate);\n private readonly changeDetectorRef = inject(ChangeDetectorRef);\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n private key?: string;\n private translation?: string;\n\n transform(text: string, params: TranslateParams = {}): string {\n if (!text || this.isServer) {\n return text;\n }\n\n const key = this.magicTranslate.cacheKey(text, params);\n if (key !== this.key) {\n this.key = key;\n this.translation = undefined;\n this.magicTranslate.translate(text, params).then((translation) => {\n if (this.key === key) {\n this.translation = translation;\n this.changeDetectorRef.markForCheck();\n }\n });\n }\n\n return this.translation ?? text;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"magic-translate-angular.mjs","names":[],"sources":["../../src/lib/magic-translate-config.ts","../../src/lib/variables.ts","../../src/lib/magic-translate.service.ts","../../src/lib/provide-magic-translate.ts","../../src/lib/translate.component.ts","../../src/lib/translate.pipe.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { MagicTranslateConfig } from '@magic-translate/core';\n\nexport const MAGIC_TRANSLATE_CONFIG = new InjectionToken<MagicTranslateConfig>(\n 'MAGIC_TRANSLATE_CONFIG'\n);\n","import { isDevMode } from '@angular/core';\nimport { libraryName } from '@magic-translate/core';\n\n/**\n * Values that are not translated, referenced as `$name` in the text.\n */\nexport type Variables = Record<string, unknown>;\n\nconst PLACEHOLDER = /\\{(\\d+)\\}/g;\n\nconst escapeRegExp = (text: string) =>\n text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/**\n * Replaces `$name` with numbered placeholders, `{0}`, `{1}`…, in the order the\n * names first appear, so the API sees the same template as from the React\n * client. Only names that are keys of `variables` are replaced, and the longest\n * key wins, so `$counts` is not read as `$count` followed by \"s\".\n *\n * `replace` can be called for several texts, e.g. every text node of an HTML\n * fragment, and numbers the placeholders across all of them.\n */\nexport function templater(variables: Variables) {\n const names = Object.keys(variables).sort((a, b) => b.length - a.length);\n const pattern =\n names.length > 0\n ? new RegExp(`\\\\$(${names.map(escapeRegExp).join('|')})`, 'g')\n : null;\n const indexes = new Map<string, number>();\n const values: string[] = [];\n\n return {\n values,\n replace(text: string): string {\n if (!pattern) {\n return text;\n }\n return text.replace(pattern, (_, name: string) => {\n let index = indexes.get(name);\n if (index === undefined) {\n index = values.push(String(variables[name] ?? '')) - 1;\n indexes.set(name, index);\n }\n return `{${index}}`;\n });\n },\n unused: () => names.filter((name) => !indexes.has(name)),\n };\n}\n\nexport interface Template {\n template: string;\n values: string[];\n // names that are not in the text, most likely misspelled\n unused: string[];\n}\n\nexport function warnAboutUnused(unused: readonly string[]) {\n if (isDevMode() && unused.length > 0) {\n console.warn(\n `[${libraryName}]: ${unused.map((name) => `$${name}`).join(', ')} not found in the text. Check the spelling, or remove ${unused.length === 1 ? 'it' : 'them'} from the variables.`\n );\n }\n}\n\nexport function toTemplate(text: string, variables: Variables): Template {\n const { replace, values, unused } = templater(variables);\n return { template: replace(text), values, unused: unused() };\n}\n\n/**\n * Puts the values back into a translated text. A placeholder without a value\n * is left as it is.\n */\nexport function fillPlaceholders(text: string, values: readonly string[]) {\n return text.replace(PLACEHOLDER, (placeholder, index: string) =>\n Number(index) < values.length ? values[Number(index)] : placeholder\n );\n}\n\nfunction textNodes(root: Node): Text[] {\n const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);\n const nodes: Text[] = [];\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n nodes.push(node as Text);\n }\n return nodes;\n}\n\n/**\n * Like {@link toTemplate}, for HTML. Only text is replaced, never attributes,\n * because only text can be filled in again.\n */\nexport function toHtmlTemplate(html: string, variables: Variables): Template {\n const container = document.createElement('template');\n container.innerHTML = html;\n const { replace, values, unused } = templater(variables);\n textNodes(container.content).forEach((node) => {\n node.data = replace(node.data);\n });\n return { template: container.innerHTML, values, unused: unused() };\n}\n\n/**\n * Fills the placeholders in the text of an element. The values are set as\n * text, so they are never parsed as HTML.\n */\nexport function fillElement(element: Element, values: readonly string[]) {\n textNodes(element).forEach((node) => {\n const filled = fillPlaceholders(node.data, values);\n if (filled !== node.data) {\n node.data = filled;\n }\n });\n}\n","import { computed, inject, Injectable, signal } from '@angular/core';\nimport {\n cacheKeyFn,\n DataLoader,\n DataLoaderLoadParams,\n defaultLoaderFactory,\n Language,\n libraryWebsite,\n} from '@magic-translate/core';\nimport { MAGIC_TRANSLATE_CONFIG } from './magic-translate-config';\nimport {\n fillPlaceholders,\n toTemplate,\n Variables,\n warnAboutUnused,\n} from './variables';\n\n/**\n * Type alias to make the language enum more flexible to also allow passing language strings\n * instead of just the enum.\n */\nexport type Languages = `${Language}`;\n\nexport interface TranslateParams extends Omit<DataLoaderLoadParams, 'text'> {\n /**\n * Values that are not translated, referenced as `$name` in the text. The text\n * is translated once as a template and cached, whatever the values are.\n */\n v?: Variables;\n}\n\n@Injectable()\nexport class MagicTranslate {\n private readonly config = inject(MAGIC_TRANSLATE_CONFIG);\n private readonly languageOverride = signal<Languages | null>(null);\n\n readonly apiKey = this.config.apiKey;\n readonly sourceLanguage = this.config.sourceLanguage;\n\n /**\n * The language content is currently translated to.\n */\n readonly language = computed(\n () => this.languageOverride() ?? this.config.language\n );\n\n readonly dataLoader = computed(() => {\n const { debug, apiKey, secretKey, sourceLanguage, _apiUrl, formality } =\n this.config;\n const loaderFactory = this.config._loaderFactory ?? defaultLoaderFactory;\n const language = this.language();\n\n return new DataLoader<DataLoaderLoadParams, string, string>(\n loaderFactory({\n debug: !!debug,\n apiKey,\n secretKey,\n lang: language,\n sourceLang: sourceLanguage,\n apiUrl: _apiUrl,\n formality,\n }),\n { cacheKeyFn: cacheKeyFn(apiKey, sourceLanguage, language) }\n );\n });\n\n constructor() {\n if (!this.config.apiKey) {\n throw new Error(`Missing API key. Please get one at ${libraryWebsite}`);\n }\n }\n\n setLanguage(language: Languages): void {\n this.languageOverride.set(language);\n }\n\n translate(\n text: string,\n { v, ...params }: TranslateParams = {}\n ): Promise<string> {\n if (!v) {\n return this.dataLoader().load({ text, ...params });\n }\n const { template, values, unused } = toTemplate(text, v);\n warnAboutUnused(unused);\n return this.dataLoader()\n .load({ text: template, ...params })\n .then((translation) => fillPlaceholders(translation, values));\n }\n\n /** @ignore */\n cacheKey(text: string, params: Omit<TranslateParams, 'v'> = {}): string {\n return cacheKeyFn(\n this.apiKey,\n this.sourceLanguage,\n this.language()\n )({ text, ...params });\n }\n}\n","import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { MagicTranslateConfig } from '@magic-translate/core';\nimport { MAGIC_TRANSLATE_CONFIG } from './magic-translate-config';\nimport { MagicTranslate } from './magic-translate.service';\n\n/**\n * Sets up Magic Translate for the application. Add it to the `providers` of\n * your application config.\n */\nexport function provideMagicTranslate(\n config: MagicTranslateConfig\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: MAGIC_TRANSLATE_CONFIG, useValue: config },\n MagicTranslate,\n ]);\n}\n","import {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n effect,\n ElementRef,\n inject,\n input,\n SecurityContext,\n signal,\n untracked,\n viewChild,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { libraryName, TranslateOptions } from '@magic-translate/core';\nimport { Languages, MagicTranslate } from './magic-translate.service';\nimport {\n fillElement,\n toHtmlTemplate,\n Variables,\n warnAboutUnused,\n} from './variables';\n\ninterface Source {\n html: string;\n leadingWhitespace: string;\n trailingWhitespace: string;\n // The `_ngcontent-*` attribute of the host template, so its component\n // styles still apply to the translated markup.\n scopeAttribute?: string;\n}\n\n/**\n * Turns the projected content into the HTML string that gets translated,\n * without the comments and attributes Angular adds to the DOM.\n */\nfunction readSource(element: HTMLElement): Source {\n const clone = element.cloneNode(true) as HTMLElement;\n const comments: Node[] = [];\n let scopeAttribute: string | undefined;\n\n const walker = document.createTreeWalker(\n clone,\n NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT\n );\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.nodeType === Node.COMMENT_NODE) {\n comments.push(node);\n continue;\n }\n\n const el = node as Element;\n if (el.localName.includes('-')) {\n throw new Error(\n `Components are not allowed inside \\`<t>...</t>\\` components. The component found inside the t component is <${el.localName}>.`\n );\n }\n\n for (const { name } of Array.from(el.attributes)) {\n if (name.startsWith('_ngcontent-')) {\n scopeAttribute = name;\n }\n if (name.startsWith('_ng') || name.startsWith('ng-reflect-')) {\n el.removeAttribute(name);\n }\n }\n }\n comments.forEach((comment) => comment.parentNode?.removeChild(comment));\n\n const [, leadingWhitespace, html, trailingWhitespace] =\n /^(\\s*)([\\s\\S]*?)(\\s*)$/.exec(clone.innerHTML)!;\n\n return { html, leadingWhitespace, trailingWhitespace, scopeAttribute };\n}\n\n@Component({\n selector: 't, mt-translate',\n // With variables, the source would show `$name`, so it stays hidden and the\n // text is shown with the values filled in until the translation arrives.\n template: `<span\n #source\n [style.display]=\"translated() || v() ? 'none' : 'contents'\"\n ><ng-content /></span\n ><span\n #target\n [style.display]=\"translated() ? 'contents' : 'none'\"\n ></span>`,\n // `id` is an input, not the element's id, which could clash with the page's.\n host: { style: 'display: contents', '[attr.id]': 'null' },\n encapsulation: ViewEncapsulation.None,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class Translate {\n readonly lang = input<Languages | undefined>(undefined);\n readonly formality = input<TranslateOptions['formality']>(undefined);\n /**\n * Lets this use of the text be edited on its own in the webapp. Its edited\n * translation stays when the text changes.\n */\n readonly id = input<string | undefined>(undefined);\n /**\n * Values that are not translated, referenced as `$name` in the content. The\n * text is translated once as a template and cached, whatever the values are.\n */\n readonly v = input<Variables | undefined>(undefined);\n\n private readonly magicTranslate = inject(MagicTranslate);\n private readonly sanitizer = inject(DomSanitizer);\n private readonly sourceRef =\n viewChild.required<ElementRef<HTMLElement>>('source');\n private readonly targetRef =\n viewChild.required<ElementRef<HTMLElement>>('target');\n\n private readonly source = signal<Source | null>(null);\n protected readonly translated = signal(false);\n private lastRequestId = 0;\n private lastTemplate?: string;\n\n constructor() {\n const destroyRef = inject(DestroyRef);\n\n afterNextRender(() => {\n const element = this.sourceRef().nativeElement;\n const read = () => this.source.set(readSource(element));\n read();\n\n // Picks up changes to the projected content, such as interpolations.\n const observer = new MutationObserver(read);\n observer.observe(element, {\n subtree: true,\n childList: true,\n characterData: true,\n attributes: true,\n });\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n effect(() => {\n const source = this.source();\n const lang = this.lang();\n const formality = this.formality();\n const id = this.id();\n const variables = this.v();\n // a language switch has to re-translate the content\n this.magicTranslate.language();\n\n if (!source?.html) {\n this.translated.set(false);\n return;\n }\n\n const { template, values, unused } = variables\n ? toHtmlTemplate(source.html, variables)\n : { template: source.html, values: [], unused: [] };\n if (template !== this.lastTemplate) {\n this.lastTemplate = template;\n warnAboutUnused(unused);\n }\n if (variables && !untracked(this.translated)) {\n this.render(source, template, values);\n }\n\n // make sure that we only ever use the response of the most recent request.\n const requestId = ++this.lastRequestId;\n this.magicTranslate\n .translate(template, { lang, formality, id })\n .then((translation) => {\n if (requestId === this.lastRequestId) {\n this.render(source, translation, values);\n }\n });\n });\n }\n\n private render(\n source: Source,\n translation: string,\n values: readonly string[] = []\n ) {\n const target = this.targetRef().nativeElement;\n const html = this.sanitizer.sanitize(SecurityContext.HTML, translation);\n if (html === null) {\n console.warn(`[${libraryName}]: Could not render translation.`);\n return;\n }\n\n target.innerHTML = `${source.leadingWhitespace}${html}${source.trailingWhitespace}`;\n if (values.length > 0) {\n fillElement(target, values);\n }\n if (source.scopeAttribute) {\n target\n .querySelectorAll('*')\n .forEach((el) => el.setAttribute(source.scopeAttribute!, ''));\n }\n this.translated.set(true);\n }\n}\n\nexport const T = Translate;\n","import { isPlatformServer } from '@angular/common';\nimport {\n ChangeDetectorRef,\n inject,\n Pipe,\n PipeTransform,\n PLATFORM_ID,\n} from '@angular/core';\nimport { MagicTranslate, TranslateParams } from './magic-translate.service';\nimport { fillPlaceholders, toTemplate, warnAboutUnused } from './variables';\n\n/**\n * Translates a plain string, e.g. for attributes:\n * `<input [placeholder]=\"'Search' | t\" />`. Returns the original text until\n * the translation has loaded.\n *\n * Values that should not be translated go in `v` and are referenced as `$name`:\n * `'Search $count products' | t: { v: { count } }`.\n */\n@Pipe({ name: 't', pure: false })\nexport class TranslatePipe implements PipeTransform {\n private readonly magicTranslate = inject(MagicTranslate);\n private readonly changeDetectorRef = inject(ChangeDetectorRef);\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n\n private key?: string;\n private translation?: string;\n\n transform(text: string, { v, ...params }: TranslateParams = {}): string {\n // Only the template is translated, so new values reuse the translation.\n const { template, values, unused } = v\n ? toTemplate(text, v)\n : { template: text, values: [], unused: [] };\n\n if (!text || this.isServer) {\n return fillPlaceholders(template, values);\n }\n\n const key = this.magicTranslate.cacheKey(template, params);\n if (key !== this.key) {\n warnAboutUnused(unused);\n this.key = key;\n this.translation = undefined;\n this.magicTranslate.translate(template, params).then((translation) => {\n if (this.key === key) {\n this.translation = translation;\n this.changeDetectorRef.markForCheck();\n }\n });\n }\n\n return fillPlaceholders(this.translation ?? template, values);\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magic-translate/angular",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"angular",
|
|
6
6
|
"angular-i18n",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@angular/platform-browser": ">=20.0.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@magic-translate/core": "^1.
|
|
38
|
+
"@magic-translate/core": "^1.6.0",
|
|
39
39
|
"tslib": "^2.8.1"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
@@ -8,33 +8,89 @@ export declare const MAGIC_TRANSLATE_CONFIG: InjectionToken<MagicTranslateConfig
|
|
|
8
8
|
* your application config.
|
|
9
9
|
*/
|
|
10
10
|
export declare function provideMagicTranslate(config: MagicTranslateConfig): EnvironmentProviders;
|
|
11
|
+
/**
|
|
12
|
+
* Values that are not translated, referenced as `$name` in the text.
|
|
13
|
+
*/
|
|
14
|
+
type Variables = Record<string, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Replaces `$name` with numbered placeholders, `{0}`, `{1}`…, in the order the
|
|
17
|
+
* names first appear, so the API sees the same template as from the React
|
|
18
|
+
* client. Only names that are keys of `variables` are replaced, and the longest
|
|
19
|
+
* key wins, so `$counts` is not read as `$count` followed by "s".
|
|
20
|
+
*
|
|
21
|
+
* `replace` can be called for several texts, e.g. every text node of an HTML
|
|
22
|
+
* fragment, and numbers the placeholders across all of them.
|
|
23
|
+
*/
|
|
24
|
+
declare function templater(variables: Variables): {
|
|
25
|
+
values: string[];
|
|
26
|
+
replace(text: string): string;
|
|
27
|
+
unused: () => string[];
|
|
28
|
+
};
|
|
29
|
+
interface Template {
|
|
30
|
+
template: string;
|
|
31
|
+
values: string[];
|
|
32
|
+
unused: string[];
|
|
33
|
+
}
|
|
34
|
+
declare function warnAboutUnused(unused: readonly string[]): void;
|
|
35
|
+
declare function toTemplate(text: string, variables: Variables): Template;
|
|
36
|
+
/**
|
|
37
|
+
* Puts the values back into a translated text. A placeholder without a value
|
|
38
|
+
* is left as it is.
|
|
39
|
+
*/
|
|
40
|
+
declare function fillPlaceholders(text: string, values: readonly string[]): string;
|
|
41
|
+
/**
|
|
42
|
+
* Like {@link toTemplate}, for HTML. Only text is replaced, never attributes,
|
|
43
|
+
* because only text can be filled in again.
|
|
44
|
+
*/
|
|
45
|
+
declare function toHtmlTemplate(html: string, variables: Variables): Template;
|
|
46
|
+
/**
|
|
47
|
+
* Fills the placeholders in the text of an element. The values are set as
|
|
48
|
+
* text, so they are never parsed as HTML.
|
|
49
|
+
*/
|
|
50
|
+
declare function fillElement(element: Element, values: readonly string[]): void;
|
|
11
51
|
/**
|
|
12
52
|
* Type alias to make the language enum more flexible to also allow passing language strings
|
|
13
53
|
* instead of just the enum.
|
|
14
54
|
*/
|
|
15
55
|
export type Languages = `${Language}`;
|
|
16
|
-
export
|
|
56
|
+
export interface TranslateParams extends Omit<DataLoaderLoadParams, 'text'> {
|
|
57
|
+
/**
|
|
58
|
+
* Values that are not translated, referenced as `$name` in the text. The text
|
|
59
|
+
* is translated once as a template and cached, whatever the values are.
|
|
60
|
+
*/
|
|
61
|
+
v?: Variables;
|
|
62
|
+
}
|
|
17
63
|
export declare class MagicTranslate {
|
|
18
64
|
private readonly config;
|
|
19
65
|
private readonly languageOverride;
|
|
20
66
|
readonly apiKey: string;
|
|
21
|
-
readonly sourceLanguage: "af" | "sq" | "am" | "ar" | "hy" | "az" | "bn" | "bs" | "bg" | "ca" | "zh" | "zh-TW" | "
|
|
67
|
+
readonly sourceLanguage: "it" | "hr" | "th" | "tr" | "af" | "sq" | "am" | "ar" | "hy" | "az" | "bn" | "bs" | "bg" | "ca" | "zh" | "zh-TW" | "cs" | "da" | "fa-AF" | "nl" | "en" | "et" | "fa" | "tl" | "fi" | "fr" | "fr-CA" | "ka" | "de" | "el" | "gu" | "ht" | "ha" | "he" | "hi" | "hu" | "is" | "id" | "ga" | "ja" | "kn" | "kk" | "ko" | "lv" | "lt" | "mk" | "ms" | "ml" | "mt" | "mr" | "mn" | "no" | "ps" | "pl" | "pt" | "pt-PT" | "pa" | "ro" | "ru" | "sr" | "si" | "sk" | "sl" | "so" | "es" | "es-MX" | "sw" | "sv" | "ta" | "te" | "uk" | "ur" | "uz" | "vi" | "cy" | undefined;
|
|
22
68
|
/**
|
|
23
69
|
* The language content is currently translated to.
|
|
24
70
|
*/
|
|
25
|
-
readonly language: import("@angular/core").Signal<"af" | "sq" | "am" | "ar" | "hy" | "az" | "bn" | "bs" | "bg" | "ca" | "zh" | "zh-TW" | "
|
|
71
|
+
readonly language: import("@angular/core").Signal<"it" | "hr" | "th" | "tr" | "af" | "sq" | "am" | "ar" | "hy" | "az" | "bn" | "bs" | "bg" | "ca" | "zh" | "zh-TW" | "cs" | "da" | "fa-AF" | "nl" | "en" | "et" | "fa" | "tl" | "fi" | "fr" | "fr-CA" | "ka" | "de" | "el" | "gu" | "ht" | "ha" | "he" | "hi" | "hu" | "is" | "id" | "ga" | "ja" | "kn" | "kk" | "ko" | "lv" | "lt" | "mk" | "ms" | "ml" | "mt" | "mr" | "mn" | "no" | "ps" | "pl" | "pt" | "pt-PT" | "pa" | "ro" | "ru" | "sr" | "si" | "sk" | "sl" | "so" | "es" | "es-MX" | "sw" | "sv" | "ta" | "te" | "uk" | "ur" | "uz" | "vi" | "cy">;
|
|
26
72
|
readonly dataLoader: import("@angular/core").Signal<DataLoader<DataLoaderLoadParams, string, string>>;
|
|
27
73
|
constructor();
|
|
28
74
|
setLanguage(language: Languages): void;
|
|
29
|
-
translate(text: string, params?: TranslateParams): Promise<string>;
|
|
75
|
+
translate(text: string, { v, ...params }?: TranslateParams): Promise<string>;
|
|
30
76
|
/** @ignore */
|
|
31
|
-
cacheKey(text: string, params?: TranslateParams): string;
|
|
77
|
+
cacheKey(text: string, params?: Omit<TranslateParams, 'v'>): string;
|
|
32
78
|
static ɵfac: i0.ɵɵFactoryDeclaration<MagicTranslate, never>;
|
|
33
79
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
34
80
|
}
|
|
35
81
|
export declare class Translate {
|
|
36
|
-
readonly lang: import("@angular/core").InputSignal<"af" | "sq" | "am" | "ar" | "hy" | "az" | "bn" | "bs" | "bg" | "ca" | "zh" | "zh-TW" | "
|
|
82
|
+
readonly lang: import("@angular/core").InputSignal<"it" | "hr" | "th" | "tr" | "af" | "sq" | "am" | "ar" | "hy" | "az" | "bn" | "bs" | "bg" | "ca" | "zh" | "zh-TW" | "cs" | "da" | "fa-AF" | "nl" | "en" | "et" | "fa" | "tl" | "fi" | "fr" | "fr-CA" | "ka" | "de" | "el" | "gu" | "ht" | "ha" | "he" | "hi" | "hu" | "is" | "id" | "ga" | "ja" | "kn" | "kk" | "ko" | "lv" | "lt" | "mk" | "ms" | "ml" | "mt" | "mr" | "mn" | "no" | "ps" | "pl" | "pt" | "pt-PT" | "pa" | "ro" | "ru" | "sr" | "si" | "sk" | "sl" | "so" | "es" | "es-MX" | "sw" | "sv" | "ta" | "te" | "uk" | "ur" | "uz" | "vi" | "cy" | undefined>;
|
|
37
83
|
readonly formality: import("@angular/core").InputSignal<"formal" | "informal" | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* Lets this use of the text be edited on its own in the webapp. Its edited
|
|
86
|
+
* translation stays when the text changes.
|
|
87
|
+
*/
|
|
88
|
+
readonly id: import("@angular/core").InputSignal<string | undefined>;
|
|
89
|
+
/**
|
|
90
|
+
* Values that are not translated, referenced as `$name` in the content. The
|
|
91
|
+
* text is translated once as a template and cached, whatever the values are.
|
|
92
|
+
*/
|
|
93
|
+
readonly v: import("@angular/core").InputSignal<Variables | undefined>;
|
|
38
94
|
private readonly magicTranslate;
|
|
39
95
|
private readonly sanitizer;
|
|
40
96
|
private readonly sourceRef;
|
|
@@ -42,6 +98,7 @@ export declare class Translate {
|
|
|
42
98
|
private readonly source;
|
|
43
99
|
protected readonly translated: import("@angular/core").WritableSignal<boolean>;
|
|
44
100
|
private lastRequestId;
|
|
101
|
+
private lastTemplate?;
|
|
45
102
|
constructor();
|
|
46
103
|
private render;
|
|
47
104
|
static ɵfac: i0.ɵɵFactoryDeclaration<Translate, never>;
|
|
@@ -56,6 +113,16 @@ export declare class Translate {
|
|
|
56
113
|
"required": false;
|
|
57
114
|
"isSignal": true;
|
|
58
115
|
};
|
|
116
|
+
"id": {
|
|
117
|
+
"alias": "id";
|
|
118
|
+
"required": false;
|
|
119
|
+
"isSignal": true;
|
|
120
|
+
};
|
|
121
|
+
"v": {
|
|
122
|
+
"alias": "v";
|
|
123
|
+
"required": false;
|
|
124
|
+
"isSignal": true;
|
|
125
|
+
};
|
|
59
126
|
}, {}, never, ["*"], true, never>;
|
|
60
127
|
}
|
|
61
128
|
export declare const T: typeof Translate;
|
|
@@ -63,6 +130,9 @@ export declare const T: typeof Translate;
|
|
|
63
130
|
* Translates a plain string, e.g. for attributes:
|
|
64
131
|
* `<input [placeholder]="'Search' | t" />`. Returns the original text until
|
|
65
132
|
* the translation has loaded.
|
|
133
|
+
*
|
|
134
|
+
* Values that should not be translated go in `v` and are referenced as `$name`:
|
|
135
|
+
* `'Search $count products' | t: { v: { count } }`.
|
|
66
136
|
*/
|
|
67
137
|
export declare class TranslatePipe implements PipeTransform {
|
|
68
138
|
private readonly magicTranslate;
|
|
@@ -70,8 +140,9 @@ export declare class TranslatePipe implements PipeTransform {
|
|
|
70
140
|
private readonly isServer;
|
|
71
141
|
private key?;
|
|
72
142
|
private translation?;
|
|
73
|
-
transform(text: string, params?: TranslateParams): string;
|
|
143
|
+
transform(text: string, { v, ...params }?: TranslateParams): string;
|
|
74
144
|
static ɵfac: i0.ɵɵFactoryDeclaration<TranslatePipe, never>;
|
|
75
145
|
static ɵpipe: i0.ɵɵPipeDeclaration<TranslatePipe, "t", true>;
|
|
76
146
|
}
|
|
147
|
+
export type { Variables };
|
|
77
148
|
//# sourceMappingURL=magic-translate-angular.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"magic-translate-angular.d.ts","names":[],"sources":["../../src/lib/magic-translate-config.ts","../../src/lib/provide-magic-translate.ts","../../src/lib/magic-translate.service.ts","../../src/lib/translate.component.ts","../../src/lib/translate.pipe.ts"],"mappings":";;;;qBAGa,wBAAsB,eAAA;;;;;wBCMnB,sBACd,QAAQ,uBACP;;;;;
|
|
1
|
+
{"version":3,"file":"magic-translate-angular.d.ts","names":[],"sources":["../../src/lib/magic-translate-config.ts","../../src/lib/provide-magic-translate.ts","../../src/lib/variables.ts","../../src/lib/magic-translate.service.ts","../../src/lib/translate.component.ts","../../src/lib/translate.pipe.ts"],"mappings":";;;;qBAGa,wBAAsB,eAAA;;;;;wBCMnB,sBACd,QAAQ,uBACP;;;;KCLS,YAAY;;;;;;;;;;iBAgBR,UAAU,WAAW;;EAWnB,QAAA;;;UAiBD;EACf;EACA;EAEA;;iBAGc,gBAAgB;iBAQhB,WAAW,cAAc,WAAW,YAAY;;;;;iBAShD,iBAAiB,cAAc;;;;;iBAmB/B,eAAe,cAAc,WAAW,YAAY;;;;;iBAcpD,YAAY,SAAS,SAAS;;;;;YCtFlC,eAAe;iBAEV,wBAAwB,KAAK;;;;;EAK5C,IAAI;;qBAIO;mBACM;mBACA;WAER;WACA;;;;WAKA,kCAAQ;WAIR,oCAAU,OAAA,WAAA;;EA0BnB,YAAY,UAAU;EAItB,UACE,gBACE,MAAM,WAAU,kBACjB;;EAYH,SAAS,cAAc,SAAQ,KAAK;SA3DzB,MAAA,GAAA,qBAAA;;;qBC8DA;WACF,8BAAI;WACJ,mCAAS;;;;;WAKT,4BAAE;;;;;WAKF,2BAAC,YAAA;mBAEO;mBACA;mBACA;mBAEA;mBAGA;qBACE,oCAAU;UACrB;UACA;;UA0DA;SAlFG,MAAA,GAAA,qBAAA;SAAA,MAAA,GAAA,uBAAA;IAAS;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;;;qBA2GT,UAAC;;;;;;;;;qBCrLD,yBAAyB;mBACnB;mBACA;mBACA;UAET;UACA;EAER,UAAU,gBAAgB,MAAM,WAAU;SAR/B,MAAA,GAAA,qBAAA;SAAA,OAAA,GAAA,kBAAA"}
|