@magic-translate/angular 1.0.1 → 1.1.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,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.0](https://github.com/magic-translate/magic-translate/compare/angular-v1.0.2...angular-v1.1.0) (2026-11-02)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **angular:** translate text with variables as one cached template ([2f60135](https://github.com/magic-translate/magic-translate/commit/2f60135f4b7b09719a575bee4176367cea0fc6e7))
|
|
9
|
+
|
|
10
|
+
## [1.0.2](https://github.com/magic-translate/magic-translate/compare/angular-v1.0.1...angular-v1.0.2) (2026-10-29)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* improve npm descriptions, keywords and docs links ([af9e428](https://github.com/magic-translate/magic-translate/commit/af9e428de50a60ffc0246f8575cf9adab4f7c927))
|
|
16
|
+
|
|
3
17
|
## [1.0.1](https://github.com/magic-translate/magic-translate/compare/angular-v1.0.0...angular-v1.0.1) (2026-10-23)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
Angular client for [Magic Translate](https://magictranslate.io). Write your
|
|
7
7
|
templates in one language and Magic Translate translates them on the fly.
|
|
8
8
|
|
|
9
|
+
No translation keys and no language files: the text stays in your templates,
|
|
10
|
+
and adding a language is one line.
|
|
11
|
+
|
|
9
12
|
Requires Angular 20 or newer. Works with and without zone.js.
|
|
10
13
|
|
|
14
|
+
Full docs: [magictranslate.io/docs/angular](https://magictranslate.io/docs/angular)
|
|
15
|
+
|
|
11
16
|
## Install
|
|
12
17
|
|
|
13
18
|
```bash
|
|
@@ -100,6 +105,50 @@ export class Search {}
|
|
|
100
105
|
|
|
101
106
|
The pipe returns the original text until the translation has loaded.
|
|
102
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
|
+
|
|
103
152
|
### Options per translation
|
|
104
153
|
|
|
105
154
|
Both the component and the pipe can override the target language and the
|
|
@@ -164,7 +213,9 @@ const message = await magicTranslate.translate('Your order has been placed');
|
|
|
164
213
|
## Server-side rendering
|
|
165
214
|
|
|
166
215
|
Translations are only loaded in the browser. With SSR the server renders the
|
|
167
|
-
original content, and it is translated after hydration.
|
|
216
|
+
original content, and it is translated after hydration. A `<t>` with `[v]`
|
|
217
|
+
renders empty on the server, so `$name` never shows, and appears once the app
|
|
218
|
+
runs in the browser.
|
|
168
219
|
|
|
169
220
|
## License
|
|
170
221
|
|
|
@@ -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 */ []);
|
|
@@ -30,11 +92,17 @@ var MagicTranslate = class MagicTranslate {
|
|
|
30
92
|
setLanguage(language) {
|
|
31
93
|
this.languageOverride.set(language);
|
|
32
94
|
}
|
|
33
|
-
translate(text, params = {}) {
|
|
34
|
-
return this.dataLoader().load({
|
|
95
|
+
translate(text, { v, ...params } = {}) {
|
|
96
|
+
if (!v) return this.dataLoader().load({
|
|
35
97
|
text,
|
|
36
98
|
...params
|
|
37
99
|
});
|
|
100
|
+
const { template, values, unused } = toTemplate(text, v);
|
|
101
|
+
warnAboutUnused(unused);
|
|
102
|
+
return this.dataLoader().load({
|
|
103
|
+
text: template,
|
|
104
|
+
...params
|
|
105
|
+
}).then((translation) => fillPlaceholders(translation, values));
|
|
38
106
|
}
|
|
39
107
|
cacheKey(text, params = {}) {
|
|
40
108
|
return cacheKeyFn(this.apiKey, this.sourceLanguage, this.language())({
|
|
@@ -100,6 +168,7 @@ function readSource(element) {
|
|
|
100
168
|
var Translate = class Translate {
|
|
101
169
|
lang = input(void 0, ...ngDevMode ? [{ debugName: "lang" }] : /* istanbul ignore next */ []);
|
|
102
170
|
formality = input(void 0, ...ngDevMode ? [{ debugName: "formality" }] : /* istanbul ignore next */ []);
|
|
171
|
+
v = input(void 0, ...ngDevMode ? [{ debugName: "v" }] : /* istanbul ignore next */ []);
|
|
103
172
|
magicTranslate = inject(MagicTranslate);
|
|
104
173
|
sanitizer = inject(DomSanitizer);
|
|
105
174
|
sourceRef = viewChild.required("source", ...ngDevMode ? [{ debugName: "sourceRef" }] : /* istanbul ignore next */ []);
|
|
@@ -107,6 +176,7 @@ var Translate = class Translate {
|
|
|
107
176
|
source = signal(null, ...ngDevMode ? [{ debugName: "source" }] : /* istanbul ignore next */ []);
|
|
108
177
|
translated = signal(false, ...ngDevMode ? [{ debugName: "translated" }] : /* istanbul ignore next */ []);
|
|
109
178
|
lastRequestId = 0;
|
|
179
|
+
lastTemplate;
|
|
110
180
|
constructor() {
|
|
111
181
|
const destroyRef = inject(DestroyRef);
|
|
112
182
|
afterNextRender(() => {
|
|
@@ -126,21 +196,32 @@ var Translate = class Translate {
|
|
|
126
196
|
const source = this.source();
|
|
127
197
|
const lang = this.lang();
|
|
128
198
|
const formality = this.formality();
|
|
199
|
+
const variables = this.v();
|
|
129
200
|
this.magicTranslate.language();
|
|
130
201
|
if (!source?.html) {
|
|
131
202
|
this.translated.set(false);
|
|
132
203
|
return;
|
|
133
204
|
}
|
|
205
|
+
const { template, values, unused } = variables ? toHtmlTemplate(source.html, variables) : {
|
|
206
|
+
template: source.html,
|
|
207
|
+
values: [],
|
|
208
|
+
unused: []
|
|
209
|
+
};
|
|
210
|
+
if (template !== this.lastTemplate) {
|
|
211
|
+
this.lastTemplate = template;
|
|
212
|
+
warnAboutUnused(unused);
|
|
213
|
+
}
|
|
214
|
+
if (variables && !untracked(this.translated)) this.render(source, template, values);
|
|
134
215
|
const requestId = ++this.lastRequestId;
|
|
135
|
-
this.magicTranslate.translate(
|
|
216
|
+
this.magicTranslate.translate(template, {
|
|
136
217
|
lang,
|
|
137
218
|
formality
|
|
138
219
|
}).then((translation) => {
|
|
139
|
-
if (requestId === this.lastRequestId) this.render(source, translation);
|
|
220
|
+
if (requestId === this.lastRequestId) this.render(source, translation, values);
|
|
140
221
|
});
|
|
141
222
|
});
|
|
142
223
|
}
|
|
143
|
-
render(source, translation) {
|
|
224
|
+
render(source, translation, values = []) {
|
|
144
225
|
const target = this.targetRef().nativeElement;
|
|
145
226
|
const html = this.sanitizer.sanitize(SecurityContext.HTML, translation);
|
|
146
227
|
if (html === null) {
|
|
@@ -148,6 +229,7 @@ var Translate = class Translate {
|
|
|
148
229
|
return;
|
|
149
230
|
}
|
|
150
231
|
target.innerHTML = `${source.leadingWhitespace}${html}${source.trailingWhitespace}`;
|
|
232
|
+
if (values.length > 0) fillElement(target, values);
|
|
151
233
|
if (source.scopeAttribute) target.querySelectorAll("*").forEach((el) => el.setAttribute(source.scopeAttribute, ""));
|
|
152
234
|
this.translated.set(true);
|
|
153
235
|
}
|
|
@@ -179,6 +261,13 @@ var Translate = class Translate {
|
|
|
179
261
|
isSignal: true,
|
|
180
262
|
isRequired: false,
|
|
181
263
|
transformFunction: null
|
|
264
|
+
},
|
|
265
|
+
v: {
|
|
266
|
+
classPropertyName: "v",
|
|
267
|
+
publicName: "v",
|
|
268
|
+
isSignal: true,
|
|
269
|
+
isRequired: false,
|
|
270
|
+
transformFunction: null
|
|
182
271
|
}
|
|
183
272
|
},
|
|
184
273
|
host: { styleAttribute: "display: contents" },
|
|
@@ -196,7 +285,9 @@ var Translate = class Translate {
|
|
|
196
285
|
isSignal: true
|
|
197
286
|
}],
|
|
198
287
|
ngImport: i0,
|
|
199
|
-
template: `<span
|
|
288
|
+
template: `<span
|
|
289
|
+
#source
|
|
290
|
+
[style.display]="translated() || v() ? 'none' : 'contents'"
|
|
200
291
|
><ng-content /></span
|
|
201
292
|
><span
|
|
202
293
|
#target
|
|
@@ -216,7 +307,9 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
216
307
|
type: Component,
|
|
217
308
|
args: [{
|
|
218
309
|
selector: "t, mt-translate",
|
|
219
|
-
template: `<span
|
|
310
|
+
template: `<span
|
|
311
|
+
#source
|
|
312
|
+
[style.display]="translated() || v() ? 'none' : 'contents'"
|
|
220
313
|
><ng-content /></span
|
|
221
314
|
><span
|
|
222
315
|
#target
|
|
@@ -245,6 +338,14 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
245
338
|
required: false
|
|
246
339
|
}]
|
|
247
340
|
}],
|
|
341
|
+
v: [{
|
|
342
|
+
type: i0.Input,
|
|
343
|
+
args: [{
|
|
344
|
+
isSignal: true,
|
|
345
|
+
alias: "v",
|
|
346
|
+
required: false
|
|
347
|
+
}]
|
|
348
|
+
}],
|
|
248
349
|
sourceRef: [{
|
|
249
350
|
type: i0.ViewChild,
|
|
250
351
|
args: ["source", { isSignal: true }]
|
|
@@ -262,20 +363,26 @@ var TranslatePipe = class TranslatePipe {
|
|
|
262
363
|
isServer = isPlatformServer(inject(PLATFORM_ID));
|
|
263
364
|
key;
|
|
264
365
|
translation;
|
|
265
|
-
transform(text, params = {}) {
|
|
266
|
-
|
|
267
|
-
|
|
366
|
+
transform(text, { v, ...params } = {}) {
|
|
367
|
+
const { template, values, unused } = v ? toTemplate(text, v) : {
|
|
368
|
+
template: text,
|
|
369
|
+
values: [],
|
|
370
|
+
unused: []
|
|
371
|
+
};
|
|
372
|
+
if (!text || this.isServer) return fillPlaceholders(template, values);
|
|
373
|
+
const key = this.magicTranslate.cacheKey(template, params);
|
|
268
374
|
if (key !== this.key) {
|
|
375
|
+
warnAboutUnused(unused);
|
|
269
376
|
this.key = key;
|
|
270
377
|
this.translation = void 0;
|
|
271
|
-
this.magicTranslate.translate(
|
|
378
|
+
this.magicTranslate.translate(template, params).then((translation) => {
|
|
272
379
|
if (this.key === key) {
|
|
273
380
|
this.translation = translation;
|
|
274
381
|
this.changeDetectorRef.markForCheck();
|
|
275
382
|
}
|
|
276
383
|
});
|
|
277
384
|
}
|
|
278
|
-
return this.translation ??
|
|
385
|
+
return fillPlaceholders(this.translation ?? template, values);
|
|
279
386
|
}
|
|
280
387
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
281
388
|
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, 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(\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 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 * 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 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 })\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,21 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magic-translate/angular",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"angular",
|
|
6
|
-
"
|
|
6
|
+
"angular-i18n",
|
|
7
|
+
"angular-translation",
|
|
8
|
+
"signals",
|
|
7
9
|
"i18n",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
+
"internationalization",
|
|
11
|
+
"localization",
|
|
12
|
+
"l10n",
|
|
13
|
+
"translation",
|
|
14
|
+
"translate",
|
|
15
|
+
"ai-translation",
|
|
16
|
+
"machine-translation",
|
|
17
|
+
"multilingual",
|
|
18
|
+
"magic-translate"
|
|
10
19
|
],
|
|
11
|
-
"description": "
|
|
20
|
+
"description": "AI-powered i18n for Angular. Wrap text in <T> and it is translated automatically, with no translation keys or language files.",
|
|
12
21
|
"author": "i42 GmbH <info@i42.ch>",
|
|
13
22
|
"repository": {
|
|
14
23
|
"type": "git",
|
|
15
24
|
"url": "git+https://github.com/magic-translate/magic-translate.git",
|
|
16
25
|
"directory": "packages/angular"
|
|
17
26
|
},
|
|
18
|
-
"homepage": "https://magictranslate.io",
|
|
27
|
+
"homepage": "https://magictranslate.io/docs/angular",
|
|
19
28
|
"license": "MIT",
|
|
20
29
|
"engines": {
|
|
21
30
|
"node": ">=22.22.1"
|
|
@@ -26,7 +35,7 @@
|
|
|
26
35
|
"@angular/platform-browser": ">=20.0.0"
|
|
27
36
|
},
|
|
28
37
|
"dependencies": {
|
|
29
|
-
"@magic-translate/core": "^1.
|
|
38
|
+
"@magic-translate/core": "^1.5.0",
|
|
30
39
|
"tslib": "^2.8.1"
|
|
31
40
|
},
|
|
32
41
|
"publishConfig": {
|
|
@@ -8,33 +8,84 @@ 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
|
+
* Values that are not translated, referenced as `$name` in the content. The
|
|
86
|
+
* text is translated once as a template and cached, whatever the values are.
|
|
87
|
+
*/
|
|
88
|
+
readonly v: import("@angular/core").InputSignal<Variables | undefined>;
|
|
38
89
|
private readonly magicTranslate;
|
|
39
90
|
private readonly sanitizer;
|
|
40
91
|
private readonly sourceRef;
|
|
@@ -42,6 +93,7 @@ export declare class Translate {
|
|
|
42
93
|
private readonly source;
|
|
43
94
|
protected readonly translated: import("@angular/core").WritableSignal<boolean>;
|
|
44
95
|
private lastRequestId;
|
|
96
|
+
private lastTemplate?;
|
|
45
97
|
constructor();
|
|
46
98
|
private render;
|
|
47
99
|
static ɵfac: i0.ɵɵFactoryDeclaration<Translate, never>;
|
|
@@ -56,6 +108,11 @@ export declare class Translate {
|
|
|
56
108
|
"required": false;
|
|
57
109
|
"isSignal": true;
|
|
58
110
|
};
|
|
111
|
+
"v": {
|
|
112
|
+
"alias": "v";
|
|
113
|
+
"required": false;
|
|
114
|
+
"isSignal": true;
|
|
115
|
+
};
|
|
59
116
|
}, {}, never, ["*"], true, never>;
|
|
60
117
|
}
|
|
61
118
|
export declare const T: typeof Translate;
|
|
@@ -63,6 +120,9 @@ export declare const T: typeof Translate;
|
|
|
63
120
|
* Translates a plain string, e.g. for attributes:
|
|
64
121
|
* `<input [placeholder]="'Search' | t" />`. Returns the original text until
|
|
65
122
|
* the translation has loaded.
|
|
123
|
+
*
|
|
124
|
+
* Values that should not be translated go in `v` and are referenced as `$name`:
|
|
125
|
+
* `'Search $count products' | t: { v: { count } }`.
|
|
66
126
|
*/
|
|
67
127
|
export declare class TranslatePipe implements PipeTransform {
|
|
68
128
|
private readonly magicTranslate;
|
|
@@ -70,8 +130,9 @@ export declare class TranslatePipe implements PipeTransform {
|
|
|
70
130
|
private readonly isServer;
|
|
71
131
|
private key?;
|
|
72
132
|
private translation?;
|
|
73
|
-
transform(text: string, params?: TranslateParams): string;
|
|
133
|
+
transform(text: string, { v, ...params }?: TranslateParams): string;
|
|
74
134
|
static ɵfac: i0.ɵɵFactoryDeclaration<TranslatePipe, never>;
|
|
75
135
|
static ɵpipe: i0.ɵɵPipeDeclaration<TranslatePipe, "t", true>;
|
|
76
136
|
}
|
|
137
|
+
export type { Variables };
|
|
77
138
|
//# 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;;EAwBnB,YAAY,UAAU;EAItB,UACE,gBACE,MAAM,WAAU,kBACjB;;EAYH,SAAS,cAAc,SAAQ,KAAK;SAzDzB,MAAA,GAAA,qBAAA;;;qBC6DA;WACF,8BAAI;WACJ,mCAAS;;;;;WAKT,2BAAC,YAAA;mBAEO;mBACA;mBACA;mBAEA;mBAGA;qBACE,oCAAU;UACrB;UACA;;UAyDA;SA5EG,MAAA,GAAA,qBAAA;SAAA,MAAA,GAAA,uBAAA;IAAS;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;;;qBAqGT,UAAC;;;;;;;;;qBC9KD,yBAAyB;mBACnB;mBACA;mBACA;UAET;UACA;EAER,UAAU,gBAAgB,MAAM,WAAU;SAR/B,MAAA,GAAA,qBAAA;SAAA,OAAA,GAAA,kBAAA"}
|