ngx-kit 8.4.1 → 9.0.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/dialog/README.md +212 -73
- package/fesm2022/ngx-kit-core.mjs +29 -73
- package/fesm2022/ngx-kit-core.mjs.map +1 -1
- package/fesm2022/ngx-kit-dialog.mjs +10 -85
- package/fesm2022/ngx-kit-dialog.mjs.map +1 -1
- package/fesm2022/ngx-kit-lazy-i18n.mjs +524 -0
- package/fesm2022/ngx-kit-lazy-i18n.mjs.map +1 -0
- package/fesm2022/ngx-kit-menu.mjs +19 -26
- package/fesm2022/ngx-kit-menu.mjs.map +1 -1
- package/lazy-i18n/README.md +455 -0
- package/package.json +45 -2
- package/types/ngx-kit-core.d.ts +1 -7
- package/types/ngx-kit-dialog.d.ts +9 -42
- package/types/ngx-kit-lazy-i18n.d.ts +213 -0
- package/types/ngx-kit-menu.d.ts +9 -9
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, HostListener, Directive, InjectionToken, viewChild, TemplateRef, Component,
|
|
3
|
-
import
|
|
2
|
+
import { input, inject, ApplicationRef, ElementRef, HostListener, Directive, InjectionToken, viewChild, TemplateRef, Component, NgModule } from '@angular/core';
|
|
3
|
+
import { OverlayService } from 'ngx-kit/core';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
5
|
|
|
6
6
|
class NgxMenu {
|
|
7
|
-
el;
|
|
8
|
-
appRef;
|
|
9
|
-
overlayService;
|
|
10
7
|
ngxMenu = input.required(/* @ts-ignore */
|
|
11
8
|
...(ngDevMode ? [{ debugName: "ngxMenu" }] : /* istanbul ignore next */ []));
|
|
12
9
|
placement = input(/* @ts-ignore */
|
|
@@ -14,11 +11,10 @@ class NgxMenu {
|
|
|
14
11
|
alignment = input(/* @ts-ignore */
|
|
15
12
|
...(ngDevMode ? [undefined, { debugName: "alignment" }] : /* istanbul ignore next */ []));
|
|
16
13
|
containerRef;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
14
|
+
overlay = inject(OverlayService);
|
|
15
|
+
appRef = inject(ApplicationRef);
|
|
16
|
+
el = inject(ElementRef);
|
|
17
|
+
constructor() { }
|
|
22
18
|
ngOnDestroy() {
|
|
23
19
|
this.destroyContainer();
|
|
24
20
|
}
|
|
@@ -35,7 +31,7 @@ class NgxMenu {
|
|
|
35
31
|
const t = this.ngxMenu().template();
|
|
36
32
|
if (!t)
|
|
37
33
|
return;
|
|
38
|
-
this.containerRef = this.
|
|
34
|
+
this.containerRef = this.overlay.openTemplate({
|
|
39
35
|
anchor: this.el.nativeElement,
|
|
40
36
|
template: t,
|
|
41
37
|
appRef: this.appRef,
|
|
@@ -44,7 +40,7 @@ class NgxMenu {
|
|
|
44
40
|
panelClass: 'ngx-menu-overlay',
|
|
45
41
|
onClosed: () => {
|
|
46
42
|
this.containerRef = undefined;
|
|
47
|
-
this.
|
|
43
|
+
this.overlay.closeAll();
|
|
48
44
|
},
|
|
49
45
|
configure: (instance, ref) => {
|
|
50
46
|
this.ngxMenu().containerRef = ref;
|
|
@@ -55,7 +51,7 @@ class NgxMenu {
|
|
|
55
51
|
this.containerRef?.close();
|
|
56
52
|
this.containerRef = undefined;
|
|
57
53
|
}
|
|
58
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxMenu, deps: [
|
|
54
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
59
55
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.4", type: NgxMenu, isStandalone: true, selector: "[ngxMenu]", inputs: { ngxMenu: { classPropertyName: "ngxMenu", publicName: "ngxMenu", isSignal: true, isRequired: true, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)" } }, exportAs: ["ngxMenu"], ngImport: i0 });
|
|
60
56
|
}
|
|
61
57
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxMenu, decorators: [{
|
|
@@ -64,15 +60,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
64
60
|
selector: '[ngxMenu]',
|
|
65
61
|
exportAs: 'ngxMenu',
|
|
66
62
|
}]
|
|
67
|
-
}], ctorParameters: () => [
|
|
63
|
+
}], ctorParameters: () => [], propDecorators: { ngxMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngxMenu", required: true }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], alignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignment", required: false }] }], onClick: [{
|
|
68
64
|
type: HostListener,
|
|
69
65
|
args: ['click', ['$event']]
|
|
70
66
|
}] } });
|
|
71
67
|
|
|
72
68
|
class NgxContextMenu {
|
|
73
|
-
el;
|
|
74
|
-
appRef;
|
|
75
|
-
overlayService;
|
|
76
69
|
ngxContextMenu = input.required(/* @ts-ignore */
|
|
77
70
|
...(ngDevMode ? [{ debugName: "ngxContextMenu" }] : /* istanbul ignore next */ []));
|
|
78
71
|
containerRef;
|
|
@@ -81,11 +74,11 @@ class NgxContextMenu {
|
|
|
81
74
|
// که removeEventListener اصلاً نمیتونست تشخیص بده مال همون listener قبلیه،
|
|
82
75
|
// پس listener هیچوقت واقعاً حذف نمیشد (نشتی حافظهی کامل directive + المنت).
|
|
83
76
|
boundOnClick = (ev) => this.onClick(ev);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
el.nativeElement.addEventListener('contextmenu', this.boundOnClick);
|
|
77
|
+
overlay = inject(OverlayService);
|
|
78
|
+
appRef = inject(ApplicationRef);
|
|
79
|
+
el = inject(ElementRef);
|
|
80
|
+
constructor() {
|
|
81
|
+
this.el.nativeElement.addEventListener('contextmenu', this.boundOnClick);
|
|
89
82
|
}
|
|
90
83
|
ngOnDestroy() {
|
|
91
84
|
this.destroyContainer();
|
|
@@ -106,7 +99,7 @@ class NgxContextMenu {
|
|
|
106
99
|
const t = this.ngxContextMenu().template();
|
|
107
100
|
if (!t)
|
|
108
101
|
return;
|
|
109
|
-
this.containerRef = this.
|
|
102
|
+
this.containerRef = this.overlay.openTemplate({
|
|
110
103
|
anchor: this.el.nativeElement,
|
|
111
104
|
point: { x, y },
|
|
112
105
|
template: t,
|
|
@@ -115,7 +108,7 @@ class NgxContextMenu {
|
|
|
115
108
|
placement: 'auto',
|
|
116
109
|
onClosed: () => {
|
|
117
110
|
this.containerRef = undefined;
|
|
118
|
-
this.
|
|
111
|
+
this.overlay.closeAll();
|
|
119
112
|
},
|
|
120
113
|
configure: (instance, ref) => {
|
|
121
114
|
this.ngxContextMenu().containerRef = ref;
|
|
@@ -126,7 +119,7 @@ class NgxContextMenu {
|
|
|
126
119
|
this.containerRef?.close();
|
|
127
120
|
this.containerRef = undefined;
|
|
128
121
|
}
|
|
129
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxContextMenu, deps: [
|
|
122
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxContextMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
130
123
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.4", type: NgxContextMenu, isStandalone: true, selector: "[ngxContextMenu]", inputs: { ngxContextMenu: { classPropertyName: "ngxContextMenu", publicName: "ngxContextMenu", isSignal: true, isRequired: true, transformFunction: null } }, exportAs: ["ngxContextMenu"], ngImport: i0 });
|
|
131
124
|
}
|
|
132
125
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: NgxContextMenu, decorators: [{
|
|
@@ -135,7 +128,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImpor
|
|
|
135
128
|
selector: '[ngxContextMenu]',
|
|
136
129
|
exportAs: 'ngxContextMenu',
|
|
137
130
|
}]
|
|
138
|
-
}], ctorParameters: () => [
|
|
131
|
+
}], ctorParameters: () => [], propDecorators: { ngxContextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngxContextMenu", required: true }] }] } });
|
|
139
132
|
|
|
140
133
|
const NGX_MENU_CONTEXT = new InjectionToken('ngx-menu-context');
|
|
141
134
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngx-kit-menu.mjs","sources":["../../../projects/libs/menu/directives/ngx-menu.directive.ts","../../../projects/libs/menu/directives/ngx-context-menu.directive.ts","../../../projects/libs/menu/tokens/menu-context.token.ts","../../../projects/libs/menu/components/ngx-menu/menu.component.ts","../../../projects/libs/menu/components/ngx-menu-item/menu-item.component.ts","../../../projects/libs/menu/components/ngx-menu-divider/menu-divider.component.ts","../../../projects/libs/menu/ngx-menu.module.ts","../../../projects/libs/menu/ngx-kit-menu.ts"],"sourcesContent":["import {\n Directive,\n OnDestroy,\n ElementRef,\n HostListener,\n input,\n ApplicationRef,\n} from '@angular/core';\nimport { OverlayRef, OverlayService, OverlayInstance, Alignment, Placement } from 'ngx-kit/core';\nimport { NgxMenuPanel } from '../components/ngx-menu/menu.component';\n\n@Directive({\n selector: '[ngxMenu]',\n exportAs: 'ngxMenu',\n})\nexport class NgxMenu implements OnDestroy {\n ngxMenu = input.required<NgxMenuPanel>();\n placement = input<Placement>();\n alignment = input<Alignment>();\n\n private containerRef?: OverlayRef<NgxMenuPanel>;\n\n constructor(\n private el: ElementRef,\n private appRef: ApplicationRef,\n private overlayService: OverlayService,\n ) {}\n\n ngOnDestroy(): void {\n this.destroyContainer();\n }\n\n @HostListener('click', ['$event'])\n onClick(ev: Event) {\n ev.stopPropagation();\n ev.preventDefault();\n this.toggleContainer();\n }\n\n private toggleContainer() {\n if (this.containerRef) {\n this.destroyContainer();\n return;\n }\n const t = this.ngxMenu().template();\n if (!t) return;\n this.containerRef = this.overlayService.openTemplate({\n anchor: this.el.nativeElement,\n template: t,\n appRef: this.appRef,\n alignment: this.alignment() ?? 'start',\n placement: this.placement() ?? 'auto',\n panelClass: 'ngx-menu-overlay',\n onClosed: () => {\n this.containerRef = undefined;\n this.overlayService.closeAll();\n },\n configure: (instance: OverlayInstance, ref: OverlayRef<NgxMenuPanel>) => {\n this.ngxMenu().containerRef = ref;\n },\n });\n }\n\n private destroyContainer() {\n this.containerRef?.close();\n this.containerRef = undefined;\n }\n}\n","import { Directive, OnDestroy, input, ApplicationRef, ElementRef } from '@angular/core';\nimport { OverlayRef, OverlayService, OverlayInstance } from 'ngx-kit/core';\nimport { NgxMenuPanel } from '../components/ngx-menu/menu.component';\n\n@Directive({\n selector: '[ngxContextMenu]',\n exportAs: 'ngxContextMenu',\n})\nexport class NgxContextMenu implements OnDestroy {\n ngxContextMenu = input.required<NgxMenuPanel>();\n\n private containerRef?: OverlayRef<NgxMenuPanel>;\n\n // باید همون رفرنس تابعی که به addEventListener داده میشه رو نگه داریم و به\n // removeEventListener هم بدیم؛ قبلاً هر بار یک آرو فانکشن جدید ساخته میشد\n // که removeEventListener اصلاً نمیتونست تشخیص بده مال همون listener قبلیه،\n // پس listener هیچوقت واقعاً حذف نمیشد (نشتی حافظهی کامل directive + المنت).\n private readonly boundOnClick = (ev: PointerEvent) => this.onClick(ev);\n\n constructor(\n private el: ElementRef<HTMLElement>,\n private appRef: ApplicationRef,\n private overlayService: OverlayService,\n ) {\n el.nativeElement.addEventListener('contextmenu', this.boundOnClick);\n }\n\n ngOnDestroy(): void {\n this.destroyContainer();\n this.el.nativeElement.removeEventListener('contextmenu', this.boundOnClick);\n }\n\n onClick(ev: PointerEvent) {\n if (ev.currentTarget == this.el.nativeElement) {\n ev.stopPropagation();\n ev.preventDefault();\n this.toggleContainer(ev.clientX, ev.clientY);\n }\n }\n\n private toggleContainer(x: number, y: number) {\n if (this.containerRef) {\n this.destroyContainer();\n return;\n }\n const t = this.ngxContextMenu().template();\n if (!t) return;\n this.containerRef = this.overlayService.openTemplate({\n anchor: this.el.nativeElement,\n point: { x, y },\n template: t,\n appRef: this.appRef,\n alignment: 'start',\n placement: 'auto',\n onClosed: () => {\n this.containerRef = undefined;\n this.overlayService.closeAll();\n },\n configure: (instance: OverlayInstance, ref: OverlayRef<NgxMenuPanel>) => {\n this.ngxContextMenu().containerRef = ref;\n },\n });\n }\n\n private destroyContainer() {\n this.containerRef?.close();\n this.containerRef = undefined;\n }\n}\n","import { InjectionToken } from '@angular/core';\n\nexport interface MenuContext {\n close(): void;\n}\n\nexport const NGX_MENU_CONTEXT = new InjectionToken<MenuContext>('ngx-menu-context');\n","import { CommonModule } from '@angular/common';\nimport { Component, input, TemplateRef, viewChild } from '@angular/core';\nimport { OverlayRef } from 'ngx-kit/core';\nimport { MenuContext, NGX_MENU_CONTEXT } from '../../tokens/menu-context.token';\n\n@Component({\n selector: 'ngx-menu,[ngx-menu]',\n template: `\n <ng-template>\n <div class=\"ngx-menu-panel\" [class]=\"class()\">\n <ng-content></ng-content>\n </div>\n </ng-template>\n `,\n exportAs: 'NgxMenu',\n styles: `\n .ngx-menu-panel {\n --ngx-menu-bg: light-dark(#ffffff, #252425);\n --ngx-menu-fg: light-dark(#000000, #ffffff);\n }\n .ngx-menu-panel {\n overflow: auto;\n box-sizing: border-box;\n outline: 0;\n background-color: var(--ngx-menu-bg);\n color: var(--ngx-menu-fg);\n box-shadow: var(--ngx-menu-shadow, 0px 0px 4px rgb(0 0 0 / 30%));\n border-radius: var(--ngx-menu-radius, 10px);\n padding: var(--ngx-menu-padding, 5px);\n min-width: var(--ngx-menu-min-width, 20px);\n min-height: var(--ngx-menu-min-height, 40px);\n will-change: transform, opacity;\n width: 100%;\n height: 100%;\n }\n .ngx-menu-panel.ngx-menu-panel-exit {\n animation: menu-exit 100ms 25ms linear forwards;\n }\n `,\n imports: [CommonModule],\n providers: [{ provide: NGX_MENU_CONTEXT, useExisting: NgxMenuPanel }],\n})\nexport class NgxMenuPanel implements MenuContext {\n template = viewChild<TemplateRef<any>>(TemplateRef);\n class = input<string>();\n\n containerRef?: OverlayRef<NgxMenuPanel>;\n\n close(): void {\n this.containerRef?.close();\n }\n}\n","import { Component, HostListener, inject } from '@angular/core';\nimport { NGX_MENU_CONTEXT } from '../../tokens/menu-context.token';\nimport { NgxMenu } from '../../directives/ngx-menu.directive';\n\n@Component({\n selector: 'ngx-menu-item,[ngx-menu-item]',\n template: `\n <div class=\"ngx-menu-item\">\n <ng-content></ng-content>\n </div>\n `,\n exportAs: 'NgxMenuItem',\n styles: `\n .ngx-menu-item {\n box-sizing: border-box;\n outline: 0;\n padding: var(--ngx-menu-item-padding, 6px 12px);\n transition: all 300ms;\n &:hover {\n cursor: pointer;\n background-color: var(--ngx-menu-item-hover-bg, #9bc6ff68);\n }\n }\n `,\n})\nexport class NgxMenuItem {\n menuContext = inject(NGX_MENU_CONTEXT);\n childMenu = inject(NgxMenu, {\n self: true,\n optional: true,\n });\n\n @HostListener('click')\n onClick() {\n if (this.childMenu?.ngxMenu) {\n return;\n }\n this.menuContext?.close();\n }\n}\n","import { Component } from '@angular/core';\n\n@Component({\n selector: 'ngx-menu-divider,[ngx-menu-divider]',\n template: ``,\n styles: `\n :host {\n display: block;\n border-bottom: var(--ngx-menu-item-border, 1px #43434d3d solid);\n border-bottom-color: var(--ngx-menu-divider-border-color, #43434d3d);\n margin: var(--ngx-menu-divider-margin, 10px calc(var(--ngx-menu-padding, 5px) * -1));\n }\n `,\n})\nexport class NgxMenuDivider {}\n","import { NgModule } from '@angular/core';\nimport { NgxMenuDivider } from './components/ngx-menu-divider/menu-divider.component';\nimport { NgxMenuItem } from './components/ngx-menu-item/menu-item.component';\nimport { NgxMenuPanel } from './components/ngx-menu/menu.component';\nimport { NgxMenu } from './directives/ngx-menu.directive';\nimport { NgxContextMenu } from './directives/ngx-context-menu.directive';\n\nconst d = [NgxMenu, NgxContextMenu, NgxMenuPanel, NgxMenuItem, NgxMenuDivider];\n@NgModule({\n imports: d,\n exports: d,\n})\nexport class NgxMenuModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngx-kit-menu.mjs","sources":["../../../projects/libs/menu/directives/ngx-menu.directive.ts","../../../projects/libs/menu/directives/ngx-context-menu.directive.ts","../../../projects/libs/menu/tokens/menu-context.token.ts","../../../projects/libs/menu/components/ngx-menu/menu.component.ts","../../../projects/libs/menu/components/ngx-menu-item/menu-item.component.ts","../../../projects/libs/menu/components/ngx-menu-divider/menu-divider.component.ts","../../../projects/libs/menu/ngx-menu.module.ts","../../../projects/libs/menu/ngx-kit-menu.ts"],"sourcesContent":["import {\n Directive,\n OnDestroy,\n ElementRef,\n HostListener,\n input,\n ApplicationRef,\n inject,\n} from '@angular/core';\nimport { OverlayRef, OverlayService, OverlayInstance, Alignment, Placement } from 'ngx-kit/core';\nimport { NgxMenuPanel } from '../components/ngx-menu/menu.component';\n\n@Directive({\n selector: '[ngxMenu]',\n exportAs: 'ngxMenu',\n})\nexport class NgxMenu implements OnDestroy {\n ngxMenu = input.required<NgxMenuPanel>();\n placement = input<Placement>();\n alignment = input<Alignment>();\n\n private containerRef?: OverlayRef<NgxMenuPanel>;\n\n protected readonly overlay = inject(OverlayService);\n protected readonly appRef = inject(ApplicationRef);\n protected readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n\n constructor() {}\n\n ngOnDestroy(): void {\n this.destroyContainer();\n }\n\n @HostListener('click', ['$event'])\n onClick(ev: Event) {\n ev.stopPropagation();\n ev.preventDefault();\n this.toggleContainer();\n }\n\n private toggleContainer() {\n if (this.containerRef) {\n this.destroyContainer();\n return;\n }\n const t = this.ngxMenu().template();\n if (!t) return;\n this.containerRef = this.overlay.openTemplate({\n anchor: this.el.nativeElement,\n template: t,\n appRef: this.appRef,\n alignment: this.alignment() ?? 'start',\n placement: this.placement() ?? 'auto',\n panelClass: 'ngx-menu-overlay',\n onClosed: () => {\n this.containerRef = undefined;\n this.overlay.closeAll();\n },\n configure: (instance: OverlayInstance, ref: OverlayRef<NgxMenuPanel>) => {\n this.ngxMenu().containerRef = ref;\n },\n });\n }\n\n private destroyContainer() {\n this.containerRef?.close();\n this.containerRef = undefined;\n }\n}\n","import { Directive, OnDestroy, input, ApplicationRef, ElementRef, inject } from '@angular/core';\nimport { OverlayRef, OverlayService, OverlayInstance } from 'ngx-kit/core';\nimport { NgxMenuPanel } from '../components/ngx-menu/menu.component';\n\n@Directive({\n selector: '[ngxContextMenu]',\n exportAs: 'ngxContextMenu',\n})\nexport class NgxContextMenu implements OnDestroy {\n ngxContextMenu = input.required<NgxMenuPanel>();\n\n private containerRef?: OverlayRef<NgxMenuPanel>;\n\n // باید همون رفرنس تابعی که به addEventListener داده میشه رو نگه داریم و به\n // removeEventListener هم بدیم؛ قبلاً هر بار یک آرو فانکشن جدید ساخته میشد\n // که removeEventListener اصلاً نمیتونست تشخیص بده مال همون listener قبلیه،\n // پس listener هیچوقت واقعاً حذف نمیشد (نشتی حافظهی کامل directive + المنت).\n private readonly boundOnClick = (ev: PointerEvent) => this.onClick(ev);\n\n\n protected readonly overlay = inject(OverlayService);\n protected readonly appRef = inject(ApplicationRef);\n protected readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n \n constructor(\n ) {\n this.el.nativeElement.addEventListener('contextmenu', this.boundOnClick);\n }\n\n ngOnDestroy(): void {\n this.destroyContainer();\n this.el.nativeElement.removeEventListener('contextmenu', this.boundOnClick);\n }\n\n onClick(ev: PointerEvent) {\n if (ev.currentTarget == this.el.nativeElement) {\n ev.stopPropagation();\n ev.preventDefault();\n this.toggleContainer(ev.clientX, ev.clientY);\n }\n }\n\n private toggleContainer(x: number, y: number) {\n if (this.containerRef) {\n this.destroyContainer();\n return;\n }\n const t = this.ngxContextMenu().template();\n if (!t) return;\n this.containerRef = this.overlay.openTemplate({\n anchor: this.el.nativeElement,\n point: { x, y },\n template: t,\n appRef: this.appRef,\n alignment: 'start',\n placement: 'auto',\n onClosed: () => {\n this.containerRef = undefined;\n this.overlay.closeAll();\n },\n configure: (instance: OverlayInstance, ref: OverlayRef<NgxMenuPanel>) => {\n this.ngxContextMenu().containerRef = ref;\n },\n });\n }\n\n private destroyContainer() {\n this.containerRef?.close();\n this.containerRef = undefined;\n }\n}\n","import { InjectionToken } from '@angular/core';\n\nexport interface MenuContext {\n close(): void;\n}\n\nexport const NGX_MENU_CONTEXT = new InjectionToken<MenuContext>('ngx-menu-context');\n","import { CommonModule } from '@angular/common';\nimport { Component, input, TemplateRef, viewChild } from '@angular/core';\nimport { OverlayRef } from 'ngx-kit/core';\nimport { MenuContext, NGX_MENU_CONTEXT } from '../../tokens/menu-context.token';\n\n@Component({\n selector: 'ngx-menu,[ngx-menu]',\n template: `\n <ng-template>\n <div class=\"ngx-menu-panel\" [class]=\"class()\">\n <ng-content></ng-content>\n </div>\n </ng-template>\n `,\n exportAs: 'NgxMenu',\n styles: `\n .ngx-menu-panel {\n --ngx-menu-bg: light-dark(#ffffff, #252425);\n --ngx-menu-fg: light-dark(#000000, #ffffff);\n }\n .ngx-menu-panel {\n overflow: auto;\n box-sizing: border-box;\n outline: 0;\n background-color: var(--ngx-menu-bg);\n color: var(--ngx-menu-fg);\n box-shadow: var(--ngx-menu-shadow, 0px 0px 4px rgb(0 0 0 / 30%));\n border-radius: var(--ngx-menu-radius, 10px);\n padding: var(--ngx-menu-padding, 5px);\n min-width: var(--ngx-menu-min-width, 20px);\n min-height: var(--ngx-menu-min-height, 40px);\n will-change: transform, opacity;\n width: 100%;\n height: 100%;\n }\n .ngx-menu-panel.ngx-menu-panel-exit {\n animation: menu-exit 100ms 25ms linear forwards;\n }\n `,\n imports: [CommonModule],\n providers: [{ provide: NGX_MENU_CONTEXT, useExisting: NgxMenuPanel }],\n})\nexport class NgxMenuPanel implements MenuContext {\n template = viewChild<TemplateRef<any>>(TemplateRef);\n class = input<string>();\n\n containerRef?: OverlayRef<NgxMenuPanel>;\n\n close(): void {\n this.containerRef?.close();\n }\n}\n","import { Component, HostListener, inject } from '@angular/core';\nimport { NGX_MENU_CONTEXT } from '../../tokens/menu-context.token';\nimport { NgxMenu } from '../../directives/ngx-menu.directive';\n\n@Component({\n selector: 'ngx-menu-item,[ngx-menu-item]',\n template: `\n <div class=\"ngx-menu-item\">\n <ng-content></ng-content>\n </div>\n `,\n exportAs: 'NgxMenuItem',\n styles: `\n .ngx-menu-item {\n box-sizing: border-box;\n outline: 0;\n padding: var(--ngx-menu-item-padding, 6px 12px);\n transition: all 300ms;\n &:hover {\n cursor: pointer;\n background-color: var(--ngx-menu-item-hover-bg, #9bc6ff68);\n }\n }\n `,\n})\nexport class NgxMenuItem {\n menuContext = inject(NGX_MENU_CONTEXT);\n childMenu = inject(NgxMenu, {\n self: true,\n optional: true,\n });\n\n @HostListener('click')\n onClick() {\n if (this.childMenu?.ngxMenu) {\n return;\n }\n this.menuContext?.close();\n }\n}\n","import { Component } from '@angular/core';\n\n@Component({\n selector: 'ngx-menu-divider,[ngx-menu-divider]',\n template: ``,\n styles: `\n :host {\n display: block;\n border-bottom: var(--ngx-menu-item-border, 1px #43434d3d solid);\n border-bottom-color: var(--ngx-menu-divider-border-color, #43434d3d);\n margin: var(--ngx-menu-divider-margin, 10px calc(var(--ngx-menu-padding, 5px) * -1));\n }\n `,\n})\nexport class NgxMenuDivider {}\n","import { NgModule } from '@angular/core';\nimport { NgxMenuDivider } from './components/ngx-menu-divider/menu-divider.component';\nimport { NgxMenuItem } from './components/ngx-menu-item/menu-item.component';\nimport { NgxMenuPanel } from './components/ngx-menu/menu.component';\nimport { NgxMenu } from './directives/ngx-menu.directive';\nimport { NgxContextMenu } from './directives/ngx-context-menu.directive';\n\nconst d = [NgxMenu, NgxContextMenu, NgxMenuPanel, NgxMenuItem, NgxMenuDivider];\n@NgModule({\n imports: d,\n exports: d,\n})\nexport class NgxMenuModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;MAgBa,OAAO,CAAA;IAClB,OAAO,GAAG,KAAK,CAAC,QAAQ;gFAAgB;AACxC,IAAA,SAAS,GAAG,KAAK;6FAAa;AAC9B,IAAA,SAAS,GAAG,KAAK;6FAAa;AAEtB,IAAA,YAAY;AAED,IAAA,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC;AAChC,IAAA,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;AAC/B,IAAA,EAAE,GAAG,MAAM,CAA0B,UAAU,CAAC;AAEnE,IAAA,WAAA,GAAA,EAAe;IAEf,WAAW,GAAA;QACT,IAAI,CAAC,gBAAgB,EAAE;IACzB;AAGA,IAAA,OAAO,CAAC,EAAS,EAAA;QACf,EAAE,CAAC,eAAe,EAAE;QACpB,EAAE,CAAC,cAAc,EAAE;QACnB,IAAI,CAAC,eAAe,EAAE;IACxB;IAEQ,eAAe,GAAA;AACrB,QAAA,IAAI,IAAI,CAAC,YAAY,EAAE;YACrB,IAAI,CAAC,gBAAgB,EAAE;YACvB;QACF;QACA,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;AACnC,QAAA,IAAI,CAAC,CAAC;YAAE;QACR,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;AAC5C,YAAA,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,aAAa;AAC7B,YAAA,QAAQ,EAAE,CAAC;YACX,MAAM,EAAE,IAAI,CAAC,MAAM;AACnB,YAAA,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,OAAO;AACtC,YAAA,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,MAAM;AACrC,YAAA,UAAU,EAAE,kBAAkB;YAC9B,QAAQ,EAAE,MAAK;AACb,gBAAA,IAAI,CAAC,YAAY,GAAG,SAAS;AAC7B,gBAAA,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;YACzB,CAAC;AACD,YAAA,SAAS,EAAE,CAAC,QAAyB,EAAE,GAA6B,KAAI;AACtE,gBAAA,IAAI,CAAC,OAAO,EAAE,CAAC,YAAY,GAAG,GAAG;YACnC,CAAC;AACF,SAAA,CAAC;IACJ;IAEQ,gBAAgB,GAAA;AACtB,QAAA,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE;AAC1B,QAAA,IAAI,CAAC,YAAY,GAAG,SAAS;IAC/B;uGAnDW,OAAO,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAP,OAAO,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,iBAAA,EAAA,WAAA,EAAA,UAAA,EAAA,WAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,OAAA,EAAA,iBAAA,EAAA,EAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAP,OAAO,EAAA,UAAA,EAAA,CAAA;kBAJnB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,WAAW;AACrB,oBAAA,QAAQ,EAAE,SAAS;AACpB,iBAAA;;sBAkBE,YAAY;uBAAC,OAAO,EAAE,CAAC,QAAQ,CAAC;;;MCzBtB,cAAc,CAAA;IACzB,cAAc,GAAG,KAAK,CAAC,QAAQ;uFAAgB;AAEvC,IAAA,YAAY;;;;;AAMH,IAAA,YAAY,GAAG,CAAC,EAAgB,KAAK,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;AAGnD,IAAA,OAAO,GAAG,MAAM,CAAC,cAAc,CAAC;AAChC,IAAA,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC;AAC/B,IAAA,EAAE,GAAG,MAAM,CAA0B,UAAU,CAAC;AAEnE,IAAA,WAAA,GAAA;AAEE,QAAA,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,gBAAgB,CAAC,aAAa,EAAE,IAAI,CAAC,YAAY,CAAC;IAC1E;IAEA,WAAW,GAAA;QACT,IAAI,CAAC,gBAAgB,EAAE;AACvB,QAAA,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,mBAAmB,CAAC,aAAa,EAAE,IAAI,CAAC,YAAY,CAAC;IAC7E;AAEA,IAAA,OAAO,CAAC,EAAgB,EAAA;QACtB,IAAI,EAAE,CAAC,aAAa,IAAI,IAAI,CAAC,EAAE,CAAC,aAAa,EAAE;YAC7C,EAAE,CAAC,eAAe,EAAE;YACpB,EAAE,CAAC,cAAc,EAAE;YACnB,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC;QAC9C;IACF;IAEQ,eAAe,CAAC,CAAS,EAAE,CAAS,EAAA;AAC1C,QAAA,IAAI,IAAI,CAAC,YAAY,EAAE;YACrB,IAAI,CAAC,gBAAgB,EAAE;YACvB;QACF;QACA,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE;AAC1C,QAAA,IAAI,CAAC,CAAC;YAAE;QACR,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;AAC5C,YAAA,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,aAAa;AAC7B,YAAA,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE;AACf,YAAA,QAAQ,EAAE,CAAC;YACX,MAAM,EAAE,IAAI,CAAC,MAAM;AACnB,YAAA,SAAS,EAAE,OAAO;AAClB,YAAA,SAAS,EAAE,MAAM;YACjB,QAAQ,EAAE,MAAK;AACb,gBAAA,IAAI,CAAC,YAAY,GAAG,SAAS;AAC7B,gBAAA,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;YACzB,CAAC;AACD,YAAA,SAAS,EAAE,CAAC,QAAyB,EAAE,GAA6B,KAAI;AACtE,gBAAA,IAAI,CAAC,cAAc,EAAE,CAAC,YAAY,GAAG,GAAG;YAC1C,CAAC;AACF,SAAA,CAAC;IACJ;IAEQ,gBAAgB,GAAA;AACtB,QAAA,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE;AAC1B,QAAA,IAAI,CAAC,YAAY,GAAG,SAAS;IAC/B;uGA7DW,cAAc,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAd,cAAc,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,EAAA,cAAA,EAAA,EAAA,iBAAA,EAAA,gBAAA,EAAA,UAAA,EAAA,gBAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,CAAA,gBAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAd,cAAc,EAAA,UAAA,EAAA,CAAA;kBAJ1B,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,kBAAkB;AAC5B,oBAAA,QAAQ,EAAE,gBAAgB;AAC3B,iBAAA;;;ACDM,MAAM,gBAAgB,GAAG,IAAI,cAAc,CAAc,kBAAkB,CAAC;;MCoCtE,YAAY,CAAA;IACvB,QAAQ,GAAG,SAAS,CAAmB,WAAW;iFAAC;AACnD,IAAA,KAAK,GAAG,KAAK;yFAAU;AAEvB,IAAA,YAAY;IAEZ,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE;IAC5B;uGARW,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,qBAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,SAAA,EAFZ,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,UAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAG9B,WAAW,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,CAAA,SAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EApCxC;;;;;;AAMT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,mlBAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EA0BS,YAAY,EAAA,CAAA,EAAA,CAAA;;2FAGX,YAAY,EAAA,UAAA,EAAA,CAAA;kBArCxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,qBAAqB,EAAA,QAAA,EACrB;;;;;;AAMT,EAAA,CAAA,EAAA,QAAA,EACS,SAAS,EAAA,OAAA,EAyBV,CAAC,YAAY,CAAC,EAAA,SAAA,EACZ,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAA,YAAc,EAAE,CAAC,EAAA,MAAA,EAAA,CAAA,mlBAAA,CAAA,EAAA;0FAG9B,WAAW,CAAA,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,KAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,KAAA,EAAA,OAAA,EAAA,QAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;MClBvC,WAAW,CAAA;AACtB,IAAA,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC;AACtC,IAAA,SAAS,GAAG,MAAM,CAAC,OAAO,EAAE;AAC1B,QAAA,IAAI,EAAE,IAAI;AACV,QAAA,QAAQ,EAAE,IAAI;AACf,KAAA,CAAC;IAGF,OAAO,GAAA;AACL,QAAA,IAAI,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE;YAC3B;QACF;AACA,QAAA,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE;IAC3B;uGAbW,WAAW,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAX,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,WAAW,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,+BAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,OAAA,EAAA,WAAA,EAAA,EAAA,EAAA,QAAA,EAAA,CAAA,aAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAnBZ;;;;AAIT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,mNAAA,CAAA,EAAA,CAAA;;2FAeU,WAAW,EAAA,UAAA,EAAA,CAAA;kBArBvB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,+BAA+B,EAAA,QAAA,EAC/B;;;;AAIT,EAAA,CAAA,EAAA,QAAA,EACS,aAAa,EAAA,MAAA,EAAA,CAAA,mNAAA,CAAA,EAAA;;sBAqBtB,YAAY;uBAAC,OAAO;;;MClBV,cAAc,CAAA;uGAAd,cAAc,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAd,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,cAAc,+FAVf,CAAA,CAAE,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,+OAAA,CAAA,EAAA,CAAA;;2FAUD,cAAc,EAAA,UAAA,EAAA,CAAA;kBAZ1B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,qCAAqC,YACrC,CAAA,CAAE,EAAA,MAAA,EAAA,CAAA,+OAAA,CAAA,EAAA;;;ACGd,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,CAAC;MAKjE,aAAa,CAAA;uGAAb,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA;AAAb,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,aAAa,YALf,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,CAAA,EAAA,OAAA,EAAA,CAAlE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,CAAA,EAAA,CAAA;AAKhE,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,aAAa,YALU,YAAY,CAAA,EAAA,CAAA;;2FAKnC,aAAa,EAAA,UAAA,EAAA,CAAA;kBAJzB,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;AACR,oBAAA,OAAO,EAAE,CAAC;AACV,oBAAA,OAAO,EAAE,CAAC;AACX,iBAAA;;;ACXD;;AAEG;;;;"}
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# ngx-kit/lazy-i18n
|
|
2
|
+
|
|
3
|
+
Tiny, signal-based, **lazy-scoped** i18n for Angular.
|
|
4
|
+
Flat keys, route-level scopes, pipe, directives, fallbacks and a key extractor CLI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
- **Route-level scopes, no key prefix.** A module's translations are loaded when its route is created and are used as plain keys (`Request`, not `setting.Request`).
|
|
8
|
+
- **Routes wait for their translations.** One guard on the root route makes navigation resolve only after every scope on the path has loaded. No "missing translation" flashes.
|
|
9
|
+
- **Signals first.** `t()`, `select()`, `translateSignal()` are reactive in `computed`, `effect` and templates. Works zoneless and with `OnPush`.
|
|
10
|
+
- **Fast.** Dictionaries are flattened once into a `Map`, so a lookup is O(1). The pipe is memoised, the structural directive creates its view once, and interpolation skips the regex when a string has no `{{`.
|
|
11
|
+
- **Complete.** Pipe, structural and attribute directives, fallback languages, missing-key handling, custom transpiler (ICU, plurals), custom loaders, RTL/`<html lang dir>` handling, and a **key extractor CLI** that generates your JSON files per scope.
|
|
12
|
+
- Angular 19+ (standalone APIs, `provideAppInitializer`, `provideEnvironmentInitializer`). ESM only. Zero runtime dependencies besides `tslib`/`rxjs`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Contents
|
|
17
|
+
|
|
18
|
+
1. [Install](#install)
|
|
19
|
+
2. [Quick start](#quick-start)
|
|
20
|
+
3. [Concepts](#concepts)
|
|
21
|
+
4. [Using translations](#using-translations)
|
|
22
|
+
5. [Scopes and routing](#scopes-and-routing)
|
|
23
|
+
6. [Language switching](#language-switching)
|
|
24
|
+
7. [Fallbacks and missing keys](#fallbacks-and-missing-keys)
|
|
25
|
+
8. [Interpolation, plurals and ICU](#interpolation-plurals-and-icu)
|
|
26
|
+
9. [Loaders](#loaders)
|
|
27
|
+
10. [Key extractor CLI](#key-extractor-cli)
|
|
28
|
+
11. [Configuration reference](#configuration-reference)
|
|
29
|
+
12. [API reference](#api-reference)
|
|
30
|
+
13. [Testing your app](#testing-your-app)
|
|
31
|
+
14. [Performance notes](#performance-notes)
|
|
32
|
+
15. [Migrating from Transloco](#migrating-from-transloco)
|
|
33
|
+
16. [Limitations](#limitations)
|
|
34
|
+
17. [Developing this library](#developing-this-library)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm i ngx-kit
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
**1. Provide it** (`app.config.ts`)
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
51
|
+
import { provideRouter } from '@angular/router';
|
|
52
|
+
import { provideI18n } from 'ngx-kit/lazy-i18n';
|
|
53
|
+
|
|
54
|
+
export const appConfig: ApplicationConfig = {
|
|
55
|
+
providers: [
|
|
56
|
+
provideRouter(routes),
|
|
57
|
+
provideHttpClient(),
|
|
58
|
+
provideI18n({
|
|
59
|
+
langs: ['fa', 'en'],
|
|
60
|
+
defaultLang: 'fa',
|
|
61
|
+
fallbackLangs: ['en'],
|
|
62
|
+
baseUrl: '/i18n', // GET /i18n/fa and GET /i18n/setting/fa
|
|
63
|
+
}),
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The global file of the initial language is loaded **before** the app bootstraps.
|
|
69
|
+
|
|
70
|
+
**2. Declare scopes on routes and add the guard once** (`app.routes.ts`)
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { i18nReadyGuard, provideI18nScope } from 'ngx-kit/lazy-i18n';
|
|
74
|
+
|
|
75
|
+
export const routes: Routes = [
|
|
76
|
+
{
|
|
77
|
+
path: '',
|
|
78
|
+
canActivateChild: [i18nReadyGuard], // once, on the root path-less route
|
|
79
|
+
children: [
|
|
80
|
+
{
|
|
81
|
+
path: 'setting',
|
|
82
|
+
providers: [provideI18nScope('setting')], // loads /i18n/setting/{lang}
|
|
83
|
+
loadChildren: () => import('./setting/setting.routes'),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: 'orders',
|
|
87
|
+
providers: [provideI18nScope(['orders', 'shared-grid'])], // several scopes are fine
|
|
88
|
+
loadComponent: () => import('./orders/orders.page'),
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Child routes of `setting` need **nothing**. Components never mention a scope.
|
|
96
|
+
|
|
97
|
+
**3. Translate**
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { Component } from '@angular/core';
|
|
101
|
+
import { TR, translateSignal } from 'ngx-kit/lazy-i18n';
|
|
102
|
+
|
|
103
|
+
@Component({
|
|
104
|
+
selector: 'app-requests',
|
|
105
|
+
imports: [TR], // pipe + both directives
|
|
106
|
+
template: `
|
|
107
|
+
<h1>{{ 'Request' | tr }}</h1>
|
|
108
|
+
<p>{{ 'welcome' | tr: { name: user.name } }}</p>
|
|
109
|
+
<input trKey="Search" trAttr="placeholder" />
|
|
110
|
+
<h2>{{ title() }}</h2>
|
|
111
|
+
`,
|
|
112
|
+
})
|
|
113
|
+
export class RequestsComponent {
|
|
114
|
+
user = { name: 'Ali' };
|
|
115
|
+
title = translateSignal('Request');
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Concepts
|
|
120
|
+
|
|
121
|
+
| Term | Meaning |
|
|
122
|
+
|---|---|
|
|
123
|
+
| **Global file** | `GET {baseUrl}/{lang}`, loaded at startup. |
|
|
124
|
+
| **Scope file** | `GET {baseUrl}/{scope}/{lang}`, loaded when a route registers the scope. |
|
|
125
|
+
| **Flat namespace** | All files are merged at the **root** of one language dictionary. Keys never carry the scope name. |
|
|
126
|
+
| **Registered scope** | A scope that any route has requested. Registered scopes stay registered, so they are also loaded when the language changes. |
|
|
127
|
+
| **Revision** | A counter signal that changes whenever the language or any dictionary changes. Pipe and directives depend on it. |
|
|
128
|
+
|
|
129
|
+
Because the namespace is flat, **two scopes must not define the same key with different values**. In dev mode a warning is logged when that happens. Put shared words (`Save`, `Cancel`) in the global file.
|
|
130
|
+
|
|
131
|
+
## Using translations
|
|
132
|
+
|
|
133
|
+
### Pipe
|
|
134
|
+
|
|
135
|
+
```html
|
|
136
|
+
{{ 'orders.title' | tr }}
|
|
137
|
+
{{ 'hello' | tr: { name: user.name } }}
|
|
138
|
+
<button [title]="'common.save' | tr">…</button>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Structural directive
|
|
142
|
+
|
|
143
|
+
The view is created once; `t` is a signal-aware function, so only the bindings that call it update.
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<ng-container *tr="let t; prefix: 'orders'">
|
|
147
|
+
<h1>{{ t('title') }}</h1> <!-- orders.title -->
|
|
148
|
+
<p>{{ t('count', { n: items.length }) }}</p>
|
|
149
|
+
</ng-container>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Context: `$implicit` / `t` (the translate function) and `lang` (a `Signal<string>`).
|
|
153
|
+
|
|
154
|
+
### Attribute directive
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<span trKey="orders.title"></span>
|
|
158
|
+
<input trKey="form.name" trAttr="placeholder" />
|
|
159
|
+
<p trKey="hello" [trParams]="{ name: user.name }"></p>
|
|
160
|
+
<span trKey="title" trPrefix="orders"></span>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`trAttr` sets an attribute instead of the text content.
|
|
164
|
+
|
|
165
|
+
### Signals
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { computed, inject } from '@angular/core';
|
|
169
|
+
import { I18nService, injectT, translateSignal } from 'ngx-kit/lazy-i18n';
|
|
170
|
+
|
|
171
|
+
class Demo {
|
|
172
|
+
private i18n = inject(I18nService);
|
|
173
|
+
title = translateSignal('orders.title');
|
|
174
|
+
hello = translateSignal('hello', () => ({ name: this.name() })); // params may read signals
|
|
175
|
+
t = injectT();
|
|
176
|
+
label = computed(() => this.t('orders.count', { n: this.n() }));
|
|
177
|
+
lang = this.i18n.lang; // Signal<string>
|
|
178
|
+
dir = this.i18n.dir; // Signal<'rtl' | 'ltr'>
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Service
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
i18n.t('key', params?) // reactive when read inside computed/effect/template
|
|
186
|
+
i18n.instant('key', params?) // same lookup, never creates a reactive dependency
|
|
187
|
+
i18n.select('key', params?) // Signal<string>
|
|
188
|
+
i18n.has('key') // true if non-empty in active lang or a fallback
|
|
189
|
+
i18n.getObject('orders') // nested object of everything under "orders."
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Scopes and routing
|
|
193
|
+
|
|
194
|
+
`provideI18nScope('x')` is an environment initializer. Angular creates a route's injector while it recognises the URL, which happens **before guards run**, so loading starts as early as possible. `i18nReadyGuard` then waits until every registered scope has loaded and lets the navigation continue. Lazy-loaded children behave the same way.
|
|
195
|
+
|
|
196
|
+
Rules of thumb:
|
|
197
|
+
|
|
198
|
+
- Put `i18nReadyGuard` **once** on the root path-less route as `canActivateChild`.
|
|
199
|
+
- Put `provideI18nScope(...)` on the route that owns the module.
|
|
200
|
+
- Without the guard, scopes still load, but a component may render before the files arrive. Use `await i18n.whenReady()` if you must translate imperatively at startup.
|
|
201
|
+
- For a scope not tied to a route (a dialog library, for example) call `i18n.registerScope('name')` and `await i18n.whenReady()`.
|
|
202
|
+
- A failed scope request is retried (`retries`, default 1), then logged. It never blocks navigation forever.
|
|
203
|
+
|
|
204
|
+
## Language switching
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
await i18n.use('en');
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`use()` downloads the global file, **all registered scopes** and the fallback languages first, and only then switches `lang`. Nothing flashes untranslated. Calling `use()` repeatedly is safe: the last call wins. The choice is stored in `localStorage` (`storageKey`) and `<html lang dir>` is updated.
|
|
211
|
+
|
|
212
|
+
`i18n.preload('en')` downloads a language without activating it.
|
|
213
|
+
|
|
214
|
+
## Fallbacks and missing keys
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
provideI18n({
|
|
218
|
+
langs: ['fa', 'en'], defaultLang: 'fa',
|
|
219
|
+
fallbackLangs: ['en'], // looked up in order
|
|
220
|
+
missing: (key, lang) => `⚠ ${key}`, // 'key' (default) | 'empty' | function
|
|
221
|
+
warnMissing: true, // console.warn once per key (default: dev mode)
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
A value of `''` counts as **missing**, so the placeholders written by the extractor fall back to the next language automatically.
|
|
226
|
+
|
|
227
|
+
## Interpolation, plurals and ICU
|
|
228
|
+
|
|
229
|
+
Default syntax: `Hello {{ name }}`, nested paths (`{{ user.name }}`) are supported, unknown placeholders are left untouched.
|
|
230
|
+
|
|
231
|
+
Plug in anything else with `transpile`:
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
provideI18n({
|
|
235
|
+
…,
|
|
236
|
+
transpile: (value, params, lang) => {
|
|
237
|
+
// example: tiny plural helper "{n, plural, one {# item} other {# items}}"
|
|
238
|
+
return value.replace(/\{(\w+), plural, one \{([^}]*)\} other \{([^}]*)\}\}/g, (_, k, one, other) => {
|
|
239
|
+
const n = Number(params?.[k] ?? 0);
|
|
240
|
+
const form = new Intl.PluralRules(lang).select(n) === 'one' ? one : other;
|
|
241
|
+
return form.replace('#', String(n));
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Use a real ICU library (for example `intl-messageformat`) inside `transpile` for full ICU support, and cache compiled messages there.
|
|
248
|
+
|
|
249
|
+
## Loaders
|
|
250
|
+
|
|
251
|
+
The default `HttpI18nLoader` uses `HttpClient`, so your interceptors (auth, base URL, retry) apply.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
provideI18n({
|
|
255
|
+
…,
|
|
256
|
+
baseUrl: '/api/translations', // /api/translations/fa, /api/translations/setting/fa
|
|
257
|
+
fileExtension: '.json', // static files: /assets/i18n/fa.json
|
|
258
|
+
// or full control:
|
|
259
|
+
url: (lang, scope) => `/api/i18n?lang=${lang}${scope ? `&scope=${scope}` : ''}`,
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Custom loader (anything that returns a nested or flat object, as `Observable` or `Promise`):
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
@Injectable({ providedIn: 'root' })
|
|
267
|
+
export class MyLoader implements I18nLoader {
|
|
268
|
+
load(lang: string, scope?: string) {
|
|
269
|
+
return fetch(`/x/${scope ?? '_global'}/${lang}`).then((r) => r.json());
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
provideI18n({ …, loader: MyLoader });
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Nested JSON (`{ "a": { "b": "x" } }`) and flat JSON (`{ "a.b": "x" }`) are both accepted.
|
|
276
|
+
|
|
277
|
+
## Key extractor CLI
|
|
278
|
+
|
|
279
|
+
Generates and updates your JSON files from the source code, **per scope**.
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npx ngx-lazy-i18n-extract # reads ./i18n.config.json
|
|
283
|
+
npx ngx-lazy-i18n-extract --check # CI: exit 1 if a used key is missing from the files
|
|
284
|
+
npx ngx-lazy-i18n-extract --remove # also delete keys no longer used
|
|
285
|
+
npx ngx-lazy-i18n-extract --langs fa,en --output src/assets/i18n --input src
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`i18n.config.json`:
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"input": ["src"],
|
|
293
|
+
"output": "src/assets/i18n",
|
|
294
|
+
"langs": ["fa", "en"],
|
|
295
|
+
"scopes": { "setting": "src/app/setting", "orders": "src/app/orders" },
|
|
296
|
+
"extensions": ["html", "ts"],
|
|
297
|
+
"ignore": [],
|
|
298
|
+
"placeholder": "",
|
|
299
|
+
"sort": true,
|
|
300
|
+
"unflat": false,
|
|
301
|
+
"remove": false
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
- A source file belongs to the scope whose directory is the **longest match**; everything else is global.
|
|
306
|
+
- Output: `{output}/{lang}.json` for global, `{output}/{scope}/{lang}.json` for scopes.
|
|
307
|
+
- Existing values are never overwritten. New keys get `placeholder` (empty by default, which makes the runtime fall back).
|
|
308
|
+
- `unflat: true` writes nested objects; a key that cannot be nested (leaf/branch conflict) stays dotted.
|
|
309
|
+
- A second run is idempotent: unchanged files are not rewritten.
|
|
310
|
+
|
|
311
|
+
What it recognises:
|
|
312
|
+
|
|
313
|
+
| Source | Extracted |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `{{ 'a.b' \| tr }}` and `(c ? 'a' : 'b') \| tr` | `a.b`, `a`, `b` |
|
|
316
|
+
| `*tr="let t; prefix: 'orders'"` then `t('title')` | `orders.title` |
|
|
317
|
+
| `trKey="a.b"`, `[trKey]="'a.b'"`, with `trPrefix="p"` | `a.b` / `p.a.b` |
|
|
318
|
+
| `i18n.t('a')`, `.select('a')`, `.has('a')`, `.getObject('a')`, `.instant('a')` | `a` |
|
|
319
|
+
| `translateSignal('a')`, `marker('a')` | `a` |
|
|
320
|
+
| `// tr-keys: a, b` or `<!-- tr-keys(prefix): a, b -->` | declares dynamic keys |
|
|
321
|
+
|
|
322
|
+
Dynamic keys (template strings with `${}`, variables) cannot be known statically. Declare them with a `tr-keys` comment or wrap constants in `marker('key')`:
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
import { marker } from 'ngx-kit/lazy-i18n';
|
|
326
|
+
const STATUS = { open: marker('status.open'), closed: marker('status.closed') };
|
|
327
|
+
// then: i18n.t(STATUS[s])
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Add to `package.json` scripts: `"i18n": "ngx-lazy-i18n-extract"`, `"i18n:check": "ngx-lazy-i18n-extract --check"`.
|
|
331
|
+
|
|
332
|
+
## Configuration reference
|
|
333
|
+
|
|
334
|
+
| Option | Type | Default | Description |
|
|
335
|
+
|---|---|---|---|
|
|
336
|
+
| `langs` | `string[]` | required | Supported languages. |
|
|
337
|
+
| `defaultLang` | `string` | required | Used when nothing is stored or detected. |
|
|
338
|
+
| `fallbackLangs` | `string[]` | `[]` | Languages tried, in order, for missing/empty keys. |
|
|
339
|
+
| `missing` | `'key' \| 'empty' \| (key, lang) => string` | `'key'` | What to render for a missing key. |
|
|
340
|
+
| `warnMissing` | `boolean` | `isDevMode()` | Log each missing key once. |
|
|
341
|
+
| `transpile` | `(value, params, lang) => string` | built-in `{{ }}` | Custom message formatter. |
|
|
342
|
+
| `loader` | `Type<I18nLoader>` | `HttpI18nLoader` | Custom loader class. |
|
|
343
|
+
| `baseUrl` | `string` | `'/i18n'` | HTTP loader base. |
|
|
344
|
+
| `fileExtension` | `string` | `''` | HTTP loader extension (e.g. `.json`). |
|
|
345
|
+
| `url` | `(lang, scope?) => string` | none | HTTP loader full URL control. |
|
|
346
|
+
| `retries` | `number` | `1` | Retries per failed request (250 ms delay). |
|
|
347
|
+
| `storageKey` | `string \| null` | `'lazy-i18n.lang'` | Persist language; `null` disables. |
|
|
348
|
+
| `detectBrowserLang` | `boolean` | `false` | Use `navigator.languages` when nothing is stored. |
|
|
349
|
+
| `manageDocument` | `boolean` | `true` | Keep `<html lang dir>` in sync. |
|
|
350
|
+
|
|
351
|
+
## API reference
|
|
352
|
+
|
|
353
|
+
**Providers / guards:** `provideI18n(config)`, `provideI18nScope(scope | scope[])`, `i18nReadyGuard`
|
|
354
|
+
|
|
355
|
+
**Template:** `TrPipe` (`tr`), `TrDirective` (`*tr`), `TrKeyDirective` (`trKey`), bundle `TR`
|
|
356
|
+
|
|
357
|
+
**Functions:** `translateSignal(key, params?)`, `injectT()`, `marker(key)`, `interpolate(text, params)`, `flatten(dict)`
|
|
358
|
+
|
|
359
|
+
**`I18nService`**
|
|
360
|
+
|
|
361
|
+
| Member | Description |
|
|
362
|
+
|---|---|
|
|
363
|
+
| `lang: Signal<string>` | Active language. |
|
|
364
|
+
| `dir: Signal<'rtl' \| 'ltr'>` | Direction for the active language. |
|
|
365
|
+
| `revision: Signal<number>` | Changes on every language/dictionary change. |
|
|
366
|
+
| `langs: readonly string[]` | Supported languages. |
|
|
367
|
+
| `t(key, params?)` | Translate (reactive). |
|
|
368
|
+
| `instant(key, params?)` | Translate (non-reactive). |
|
|
369
|
+
| `select(key, params?)` | `Signal<string>`; `params` may be a function. |
|
|
370
|
+
| `has(key)` / `getObject(prefix)` | Introspection. |
|
|
371
|
+
| `use(lang)` | Load everything, then activate. |
|
|
372
|
+
| `preload(lang)` | Download without activating. |
|
|
373
|
+
| `registerScope(scope)` | Register and start loading a scope. |
|
|
374
|
+
| `whenReady()` | Resolves when all registered scopes are loaded. Never rejects. |
|
|
375
|
+
| `setTranslation(lang, dict, merge = true)` | Inject translations manually. |
|
|
376
|
+
| `detectInitialLang()` | Stored, browser (opt-in) or default. |
|
|
377
|
+
|
|
378
|
+
## Testing your app
|
|
379
|
+
|
|
380
|
+
Skip the network by providing a loader that returns objects:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
@Injectable({ providedIn: 'root' })
|
|
384
|
+
class TestLoader implements I18nLoader {
|
|
385
|
+
load(lang: string, scope?: string) {
|
|
386
|
+
return of(DICTS[`${lang}|${scope ?? ''}`] ?? {});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
TestBed.configureTestingModule({
|
|
391
|
+
providers: [provideI18n({ langs: ['en'], defaultLang: 'en', loader: TestLoader, storageKey: null })],
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Or call `TestBed.inject(I18nService).setTranslation('en', { a: 'A' })` and `await svc.use('en')`.
|
|
396
|
+
|
|
397
|
+
## Performance notes
|
|
398
|
+
|
|
399
|
+
- **Lookup:** one `Map.get` on the flattened active-language map, plus one per fallback language only on a miss.
|
|
400
|
+
- **Pipe:** impure so it follows signals, but memoised on key, params (shallow compare) and `revision`. Unchanged inputs return the cached string without a lookup.
|
|
401
|
+
- **Directive:** creates its view once. Updates happen through signal reads in the bindings, so nothing is re-created on language change.
|
|
402
|
+
- **Interpolation:** `indexOf('{{')` fast path; no regex for static strings.
|
|
403
|
+
- **Network:** each `(lang, scope)` file is requested at most once (in-flight requests are shared). The language switch loads global + scopes + fallbacks in parallel.
|
|
404
|
+
- **Bundle:** no runtime dependency besides Angular, RxJS and `tslib`. The extractor is a separate Node script and is not bundled into apps.
|
|
405
|
+
|
|
406
|
+
## Migrating from Transloco
|
|
407
|
+
|
|
408
|
+
| Transloco | ngx-kit/lazy-i18n |
|
|
409
|
+
|---|---|
|
|
410
|
+
| `provideTransloco(...)` | `provideI18n(...)` |
|
|
411
|
+
| `provideTranslocoScope('x')` | `provideI18nScope('x')` (route `providers`) |
|
|
412
|
+
| `{{ 'x.key' \| transloco }}` | `{{ 'key' \| tr }}` |
|
|
413
|
+
| `*transloco="let t; read: 'x'"` | `*tr="let t; prefix: 'x'"` |
|
|
414
|
+
| `translateSignal('x.key')` | `translateSignal('key')` |
|
|
415
|
+
| `service.selectTranslate()` | `i18n.select()` / `translateSignal()` |
|
|
416
|
+
| `setActiveLang()` | `await i18n.use()` |
|
|
417
|
+
| `TranslocoLoader` | `I18nLoader` |
|
|
418
|
+
| `transloco-keys-manager` | `ngx-lazy-i18n-extract` |
|
|
419
|
+
|
|
420
|
+
Server files can stay as they are (`/i18n/{scope}/{lang}`). Keys that used to be written with the scope prefix must be written without it, or the scope name must be moved inside your JSON.
|
|
421
|
+
|
|
422
|
+
## Limitations
|
|
423
|
+
|
|
424
|
+
- **Flat namespace.** Cross-scope key collisions are your responsibility (dev-mode warning only).
|
|
425
|
+
- **Extractor is a static scanner.** It uses regular expressions and a small call parser, not the Angular compiler. `t()` calls are attributed to the **nearest preceding** `*tr="let t"` with the same variable name in the same file, so nested `*tr` blocks that reuse the same variable name with different prefixes can be mis-attributed. Use distinct variable names (`let t`, `let tOrders`) or `tr-keys` comments.
|
|
426
|
+
- **SSR.** `localStorage`/`document` access is guarded, but relative HTTP URLs on the server need an absolute-URL interceptor or a custom loader. Transfer-state is not implemented; use `setTranslation()` if you need it.
|
|
427
|
+
- **Pluralisation/ICU** are not built in; plug a formatter into `transpile`.
|
|
428
|
+
- Signal `input()` is deliberately not used for the directives, so they work in JIT test environments and older Angular versions.
|
|
429
|
+
|
|
430
|
+
## Developing this library
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
npm install
|
|
434
|
+
npm test # vitest: service, pipe/directives, routing, extractor
|
|
435
|
+
npm run build # ng-packagr -> dist/
|
|
436
|
+
cd dist && npm publish
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Source layout:
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
src/lib/
|
|
443
|
+
i18n.service.ts state, loading, scopes, fallbacks, language switching
|
|
444
|
+
pipe.ts `tr` pipe (memoised)
|
|
445
|
+
directives.ts *tr and trKey
|
|
446
|
+
routing.ts provideI18nScope, i18nReadyGuard
|
|
447
|
+
provide-i18n.ts provideI18n
|
|
448
|
+
http-loader.ts default loader
|
|
449
|
+
signals.ts translateSignal, injectT
|
|
450
|
+
utils.ts flatten, interpolate, marker
|
|
451
|
+
tools/extractor.mjs key extraction engine
|
|
452
|
+
bin/extract.mjs CLI wrapper
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
MIT licensed.
|