@flusys/ng-localization 9.1.0 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,865 @@
1
+ import { LanguageApiService, TranslationApiService, TranslationKeyApiService } from "./flusys-ng-localization.mjs";
2
+ import * as i0 from "@angular/core";
3
+ import { Component, DestroyRef, computed, effect, inject, signal } from "@angular/core";
4
+ import { TranslatePipe, TranslateService } from "@flusys/ng-shared";
5
+ import { finalize, forkJoin, from } from "rxjs";
6
+ import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
7
+ import { CardComponent, FButtonDirective, InputTextDirective, MessageService, ProgressSpinnerComponent, SelectComponent, TagComponent, TextareaDirective, UiIconComponent } from "@flusys/ng-ui";
8
+ import * as i1 from "@angular/forms";
9
+ import { FormsModule } from "@angular/forms";
10
+ import { FormField, disabled, form, required } from "@angular/forms/signals";
11
+ import { ActivatedRoute, Router } from "@angular/router";
12
+ var TranslationKeyFormPage = class TranslationKeyFormPage {
13
+ router = inject(Router);
14
+ route = inject(ActivatedRoute);
15
+ languageService = inject(LanguageApiService);
16
+ translationKeyService = inject(TranslationKeyApiService);
17
+ translationService = inject(TranslationApiService);
18
+ messageService = inject(MessageService);
19
+ translateAdapter = inject(TranslateService, { optional: true });
20
+ destroyRef = inject(DestroyRef);
21
+ routeParams = toSignal(this.route.paramMap);
22
+ loadTriggered = false;
23
+ isSaving = signal(false, ...ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []);
24
+ isLoadingLanguages = signal(false, ...ngDevMode ? [{ debugName: "isLoadingLanguages" }] : /* istanbul ignore next */ []);
25
+ existingKey = signal(null, ...ngDevMode ? [{ debugName: "existingKey" }] : /* istanbul ignore next */ []);
26
+ isEditMode = computed(() => !!this.existingKey(), ...ngDevMode ? [{ debugName: "isEditMode" }] : /* istanbul ignore next */ []);
27
+ isReadonly = computed(() => this.existingKey()?.isReadonly ?? false, ...ngDevMode ? [{ debugName: "isReadonly" }] : /* istanbul ignore next */ []);
28
+ languages = signal([], ...ngDevMode ? [{ debugName: "languages" }] : /* istanbul ignore next */ []);
29
+ modules = signal([], ...ngDevMode ? [{ debugName: "modules" }] : /* istanbul ignore next */ []);
30
+ translations = signal([], ...ngDevMode ? [{ debugName: "translations" }] : /* istanbul ignore next */ []);
31
+ translate(key, vars) {
32
+ return this.translateAdapter?.translate(key, vars) ?? key;
33
+ }
34
+ moduleOptions = computed(() => this.modules().map((m) => ({
35
+ label: m,
36
+ value: m
37
+ })), ...ngDevMode ? [{ debugName: "moduleOptions" }] : /* istanbul ignore next */ []);
38
+ formModel = signal({
39
+ key: "",
40
+ module: "",
41
+ defaultMessage: "",
42
+ description: "",
43
+ variables: ""
44
+ }, ...ngDevMode ? [{ debugName: "formModel" }] : /* istanbul ignore next */ []);
45
+ formTree = form(this.formModel, (f) => {
46
+ required(f.key, { message: this.translate("localization.validation.key.required") });
47
+ required(f.defaultMessage, { message: this.translate("localization.validation.default.message.required") });
48
+ disabled(f.key, { when: () => this.isReadonly() });
49
+ });
50
+ isFormValid = computed(() => {
51
+ const model = this.formModel();
52
+ return model.key.trim().length > 0 && model.module.trim().length > 0 && model.defaultMessage.trim().length > 0;
53
+ }, ...ngDevMode ? [{ debugName: "isFormValid" }] : /* istanbul ignore next */ []);
54
+ constructor() {
55
+ effect(() => {
56
+ const params = this.routeParams();
57
+ if (!params || this.loadTriggered) return;
58
+ this.loadTriggered = true;
59
+ const id = params.get("id");
60
+ if (id && id !== "new") this.loadTranslationKey(id);
61
+ });
62
+ }
63
+ ngOnInit() {
64
+ this.isLoadingLanguages.set(true);
65
+ forkJoin({
66
+ languages: from(this.languageService.getAll({
67
+ pagination: {
68
+ currentPage: 0,
69
+ pageSize: 100
70
+ },
71
+ filter: {},
72
+ sort: {
73
+ serial: "ASC",
74
+ name: "ASC"
75
+ }
76
+ })),
77
+ modules: this.translationKeyService.getModules()
78
+ }).pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isLoadingLanguages.set(false))).subscribe({ next: ({ languages, modules }) => {
79
+ if (languages.success && languages.data) {
80
+ const activeLanguages = languages.data.filter((l) => l.isActive);
81
+ this.languages.set(activeLanguages);
82
+ this.initializeTranslations(activeLanguages);
83
+ }
84
+ if (modules.success && modules.data) this.modules.set(modules.data);
85
+ } });
86
+ }
87
+ initializeTranslations(languages) {
88
+ if (this.translations().length === 0) this.translations.set(languages.map((lang) => ({
89
+ languageId: lang.id,
90
+ value: ""
91
+ })));
92
+ }
93
+ updateModule(value) {
94
+ this.formModel.update((m) => ({
95
+ ...m,
96
+ module: value ?? ""
97
+ }));
98
+ }
99
+ getTranslationValue(languageId) {
100
+ return this.translations().find((t) => t.languageId === languageId)?.value ?? "";
101
+ }
102
+ setTranslationValue(languageId, value) {
103
+ this.translations.update((list) => {
104
+ if (list.find((t) => t.languageId === languageId)) return list.map((t) => t.languageId === languageId ? {
105
+ ...t,
106
+ value
107
+ } : t);
108
+ return [...list, {
109
+ languageId,
110
+ value
111
+ }];
112
+ });
113
+ }
114
+ loadTranslationKey(id) {
115
+ this.isSaving.set(true);
116
+ from(this.translationKeyService.findById(id)).pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isSaving.set(false))).subscribe({
117
+ next: (response) => {
118
+ if (response.success && response.data) {
119
+ this.existingKey.set(response.data);
120
+ this.formModel.set(this.toFormModel(response.data));
121
+ this.loadExistingTranslations(id);
122
+ }
123
+ },
124
+ error: () => this.router.navigate(["/localization/keys"])
125
+ });
126
+ }
127
+ loadExistingTranslations(keyId) {
128
+ this.translationService.getByKeyId(keyId).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ next: (response) => {
129
+ if (response.success && response.data) this.translations.update((list) => {
130
+ const updated = [...list];
131
+ response.data.forEach((t) => {
132
+ const existing = updated.find((tr) => tr.languageId === t.languageId);
133
+ if (existing) {
134
+ existing.value = t.value;
135
+ existing.translationId = t.id;
136
+ } else updated.push({
137
+ languageId: t.languageId,
138
+ value: t.value,
139
+ translationId: t.id
140
+ });
141
+ });
142
+ return updated;
143
+ });
144
+ } });
145
+ }
146
+ toFormModel(key) {
147
+ return {
148
+ key: key.key,
149
+ module: key.module,
150
+ defaultMessage: key.defaultMessage,
151
+ description: key.description ?? "",
152
+ variables: key.variables?.join(", ") ?? ""
153
+ };
154
+ }
155
+ onSubmit() {
156
+ if (!this.isFormValid()) return;
157
+ this.isSaving.set(true);
158
+ const model = this.formModel();
159
+ const variables = model.variables.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
160
+ if (this.isEditMode()) this.updateKey(model, variables);
161
+ else this.createKey(model, variables);
162
+ }
163
+ createKey(model, variables) {
164
+ const createDto = {
165
+ key: model.key,
166
+ module: model.module,
167
+ defaultMessage: model.defaultMessage,
168
+ description: model.description || void 0,
169
+ variables: variables.length > 0 ? variables : void 0
170
+ };
171
+ from(this.translationKeyService.insert(createDto)).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
172
+ next: (response) => {
173
+ if (response.success && response.data) this.saveTranslations(response.data.id, response.messageKey, response.messageVariables);
174
+ else this.isSaving.set(false);
175
+ },
176
+ error: () => this.isSaving.set(false)
177
+ });
178
+ }
179
+ updateKey(model, variables) {
180
+ const updateDto = {
181
+ id: this.existingKey().id,
182
+ key: model.key,
183
+ module: model.module,
184
+ defaultMessage: model.defaultMessage,
185
+ description: model.description || void 0,
186
+ variables: variables.length > 0 ? variables : void 0
187
+ };
188
+ from(this.translationKeyService.update(updateDto)).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
189
+ next: (response) => {
190
+ if (response.success) this.saveTranslations(this.existingKey().id, response.messageKey, response.messageVariables);
191
+ else this.isSaving.set(false);
192
+ },
193
+ error: () => this.isSaving.set(false)
194
+ });
195
+ }
196
+ saveTranslations(keyId, messageKey, messageVariables) {
197
+ const translationsToSave = this.translations().filter((t) => t.value?.trim()).map((t) => ({
198
+ id: t.translationId,
199
+ languageId: t.languageId,
200
+ translationKeyId: keyId,
201
+ value: t.value,
202
+ isVerified: false
203
+ }));
204
+ if (translationsToSave.length === 0) {
205
+ this.onSaveSuccess(messageKey, messageVariables);
206
+ return;
207
+ }
208
+ this.translationService.bulkUpdate(translationsToSave).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
209
+ next: () => this.onSaveSuccess(messageKey, messageVariables),
210
+ error: () => this.onSaveSuccess(messageKey, messageVariables)
211
+ });
212
+ }
213
+ onSaveSuccess(messageKey, messageVariables) {
214
+ this.messageService.add({
215
+ severity: "success",
216
+ summary: this.translate("shared.success"),
217
+ detail: messageKey ? this.translate(messageKey, messageVariables) : ""
218
+ });
219
+ this.isSaving.set(false);
220
+ this.router.navigate(["/localization/keys"]);
221
+ }
222
+ onCancel() {
223
+ this.router.navigate(["/localization/keys"]);
224
+ }
225
+ static ɵfac = i0.ɵɵngDeclareFactory({
226
+ minVersion: "12.0.0",
227
+ version: "22.2.1",
228
+ ngImport: i0,
229
+ type: TranslationKeyFormPage,
230
+ deps: [],
231
+ target: i0.ɵɵFactoryTarget.Component
232
+ });
233
+ static ɵcmp = i0.ɵɵngDeclareComponent({
234
+ minVersion: "17.0.0",
235
+ version: "22.2.1",
236
+ type: TranslationKeyFormPage,
237
+ isStandalone: true,
238
+ selector: "lib-translation-key-form-page",
239
+ providers: [MessageService],
240
+ ngImport: i0,
241
+ template: `
242
+ <f-card>
243
+ <ng-template #header>
244
+ <div class="flex justify-between items-center p-4">
245
+ <h2 class="text-xl font-semibold m-0">
246
+ {{
247
+ isEditMode()
248
+ ? ('localization.key.edit' | translate)
249
+ : ('localization.key.new' | translate)
250
+ }}
251
+ </h2>
252
+ </div>
253
+ </ng-template>
254
+
255
+ <form (ngSubmit)="onSubmit()" class="flex flex-col gap-6">
256
+ <!-- Key Info Section -->
257
+ <div class="p-4 surface-ground rounded-lg">
258
+ <h4 class="font-semibold mt-0 mb-4">
259
+ {{ 'localization.key.about' | translate }}
260
+ </h4>
261
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
262
+ <!-- Key Name -->
263
+ <div class="flex flex-col gap-2">
264
+ <label for="key" class="font-medium"
265
+ >{{ 'localization.key.name' | translate }} *</label
266
+ >
267
+ <input
268
+ fInputText
269
+ id="key"
270
+ [formField]="formTree.key"
271
+ [placeholder]="'localization.placeholder.key.name' | translate"
272
+ />
273
+ </div>
274
+
275
+ <!-- Module -->
276
+ <div class="flex flex-col gap-2">
277
+ <label for="module" class="font-medium"
278
+ >{{ 'localization.key.module' | translate }} *</label
279
+ >
280
+ <f-select
281
+ id="module"
282
+ [ngModel]="formModel().module"
283
+ (ngModelChange)="updateModule($event)"
284
+ [ngModelOptions]="{ standalone: true }"
285
+ [options]="moduleOptions()"
286
+ optionLabel="label"
287
+ optionValue="value"
288
+ [editable]="true"
289
+ [disabled]="isReadonly()"
290
+ [placeholder]="'localization.key.select.module' | translate"
291
+ class="w-full"
292
+ />
293
+ </div>
294
+
295
+ @if (isReadonly()) {
296
+ <div class="md:col-span-2 -mt-2">
297
+ <small class="text-muted-color">{{
298
+ 'localization.key.readonly.edit.hint' | translate
299
+ }}</small>
300
+ </div>
301
+ }
302
+
303
+ <!-- Default Message -->
304
+ <div class="flex flex-col gap-2 md:col-span-2">
305
+ <label for="defaultMessage" class="font-medium"
306
+ >{{ 'localization.key.default.message' | translate }} *</label
307
+ >
308
+ <textarea
309
+ fTextarea
310
+ id="defaultMessage"
311
+ [formField]="formTree.defaultMessage"
312
+ [rows]="2"
313
+ [placeholder]="
314
+ 'localization.placeholder.default.message' | translate
315
+ "
316
+ ></textarea>
317
+ </div>
318
+
319
+ <!-- Description -->
320
+ <div class="flex flex-col gap-2 md:col-span-2">
321
+ <label for="description" class="font-medium">{{
322
+ 'localization.key.description' | translate
323
+ }}</label>
324
+ <input
325
+ fInputText
326
+ id="description"
327
+ [formField]="formTree.description"
328
+ [placeholder]="
329
+ 'localization.placeholder.key.description' | translate
330
+ "
331
+ />
332
+ </div>
333
+
334
+ <!-- Variables -->
335
+ <div class="flex flex-col gap-2 md:col-span-2">
336
+ <label for="variables" class="font-medium">{{
337
+ 'localization.key.variables' | translate
338
+ }}</label>
339
+ <input
340
+ fInputText
341
+ id="variables"
342
+ [formField]="formTree.variables"
343
+ [placeholder]="
344
+ 'localization.key.variables.placeholder' | translate
345
+ "
346
+ />
347
+ <small class="text-muted-color">{{
348
+ 'localization.key.variables.hint' | translate
349
+ }}</small>
350
+ </div>
351
+ </div>
352
+ </div>
353
+
354
+ <!-- Translations Section -->
355
+ <div class="p-4 surface-ground rounded-lg">
356
+ <h4 class="font-semibold mt-0 mb-4">
357
+ {{ 'localization.key.translations.section' | translate }}
358
+ </h4>
359
+ @if (isLoadingLanguages()) {
360
+ <div class="flex justify-center py-4">
361
+ <f-progressspinner strokeWidth="4" />
362
+ </div>
363
+ } @else if (languages().length === 0) {
364
+ <p class="text-muted-color text-center py-4">
365
+ {{ 'localization.key.no.languages' | translate }}
366
+ </p>
367
+ } @else {
368
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
369
+ @for (lang of languages(); track lang.id) {
370
+ <div class="flex flex-col gap-2">
371
+ <div class="flex justify-between items-center">
372
+ <label class="font-medium">
373
+ {{ lang.name }}
374
+ @if (lang.nativeName && lang.nativeName !== lang.name) {
375
+ <span class="text-muted-color"
376
+ >({{ lang.nativeName }})</span
377
+ >
378
+ }
379
+ </label>
380
+ <f-tag
381
+ [value]="lang.code"
382
+ severity="secondary"
383
+ class="uppercase"
384
+ />
385
+ </div>
386
+ <textarea
387
+ fTextarea
388
+ [ngModel]="getTranslationValue(lang.id)"
389
+ (ngModelChange)="setTranslationValue(lang.id, $event)"
390
+ [ngModelOptions]="{ standalone: true }"
391
+ [rows]="2"
392
+ [placeholder]="
393
+ formModel().defaultMessage ||
394
+ ('localization.key.enter.translation' | translate)
395
+ "
396
+ ></textarea>
397
+ </div>
398
+ }
399
+ </div>
400
+ }
401
+ </div>
402
+
403
+ <!-- Actions -->
404
+ <div class="flex justify-end gap-4 pt-4">
405
+ <button
406
+ fButton
407
+ type="button"
408
+ severity="secondary"
409
+ [outlined]="true"
410
+ (click)="onCancel()"
411
+ >
412
+ <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>
413
+ </button>
414
+ <button
415
+ fButton
416
+ type="submit"
417
+ [disabled]="!isFormValid() || isSaving()"
418
+ >
419
+ @if (isSaving()) {
420
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
421
+ }
422
+ <span pButtonLabel>{{
423
+ isEditMode()
424
+ ? ('shared.update' | translate)
425
+ : ('shared.create' | translate)
426
+ }}</span>
427
+ </button>
428
+ </div>
429
+ </form>
430
+ </f-card>
431
+ `,
432
+ isInline: true,
433
+ dependencies: [
434
+ {
435
+ kind: "component",
436
+ type: CardComponent,
437
+ selector: "f-card",
438
+ inputs: [
439
+ "header",
440
+ "subheader",
441
+ "styleClass",
442
+ "style"
443
+ ]
444
+ },
445
+ {
446
+ kind: "directive",
447
+ type: InputTextDirective,
448
+ selector: "[fInputText]",
449
+ inputs: [
450
+ "invalid",
451
+ "touched",
452
+ "variant",
453
+ "size",
454
+ "fluid"
455
+ ]
456
+ },
457
+ {
458
+ kind: "component",
459
+ type: SelectComponent,
460
+ selector: "f-select",
461
+ inputs: [
462
+ "options",
463
+ "optionLabel",
464
+ "optionValue",
465
+ "placeholder",
466
+ "disabled",
467
+ "filter",
468
+ "filterBy",
469
+ "filterMatchMode",
470
+ "filterLocale",
471
+ "filterPlaceholder",
472
+ "resetFilterOnHide",
473
+ "autofocusFilter",
474
+ "emptyMessage",
475
+ "emptyFilterMessage",
476
+ "showClear",
477
+ "loading",
478
+ "loadingIcon",
479
+ "dropdownIcon",
480
+ "clearIcon",
481
+ "inputId",
482
+ "invalid",
483
+ "touched",
484
+ "styleClass",
485
+ "style",
486
+ "appendTo",
487
+ "editable",
488
+ "readonly",
489
+ "autofocus",
490
+ "checkmark",
491
+ "group",
492
+ "optionGroupLabel",
493
+ "optionGroupChildren",
494
+ "virtualScroll",
495
+ "virtualScrollItemSize",
496
+ "lazy",
497
+ "selectedObject"
498
+ ],
499
+ outputs: [
500
+ "onFilter",
501
+ "onChange",
502
+ "onShow",
503
+ "onHide",
504
+ "onBlur",
505
+ "onFocus",
506
+ "onClear",
507
+ "onLazyLoad"
508
+ ]
509
+ },
510
+ {
511
+ kind: "directive",
512
+ type: TextareaDirective,
513
+ selector: "[fTextarea]",
514
+ inputs: [
515
+ "invalid",
516
+ "touched",
517
+ "variant",
518
+ "fluid",
519
+ "autoResize"
520
+ ],
521
+ outputs: ["onResize"]
522
+ },
523
+ {
524
+ kind: "component",
525
+ type: ProgressSpinnerComponent,
526
+ selector: "f-progressspinner",
527
+ inputs: [
528
+ "size",
529
+ "strokeWidth",
530
+ "animationDuration",
531
+ "styleClass",
532
+ "ariaLabel"
533
+ ]
534
+ },
535
+ {
536
+ kind: "component",
537
+ type: TagComponent,
538
+ selector: "f-tag",
539
+ inputs: [
540
+ "value",
541
+ "severity",
542
+ "icon",
543
+ "rounded",
544
+ "color",
545
+ "styleClass",
546
+ "style"
547
+ ]
548
+ },
549
+ {
550
+ kind: "directive",
551
+ type: FButtonDirective,
552
+ selector: "[fButton]",
553
+ inputs: [
554
+ "severity",
555
+ "text",
556
+ "outlined",
557
+ "link",
558
+ "variant",
559
+ "rounded",
560
+ "raised",
561
+ "fluid",
562
+ "disabled",
563
+ "loading",
564
+ "loadingIcon",
565
+ "size",
566
+ "label",
567
+ "icon",
568
+ "iconPos",
569
+ "ariaLabel",
570
+ "plain"
571
+ ]
572
+ },
573
+ {
574
+ kind: "component",
575
+ type: UiIconComponent,
576
+ selector: "ui-icon",
577
+ inputs: [
578
+ "name",
579
+ "size",
580
+ "spin",
581
+ "fill",
582
+ "severity"
583
+ ]
584
+ },
585
+ {
586
+ kind: "ngmodule",
587
+ type: FormsModule
588
+ },
589
+ {
590
+ kind: "directive",
591
+ type: i1.ɵNgNoValidate,
592
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
593
+ },
594
+ {
595
+ kind: "directive",
596
+ type: i1.DefaultValueAccessor,
597
+ selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
598
+ },
599
+ {
600
+ kind: "directive",
601
+ type: i1.NgControlStatus,
602
+ selector: "[formControlName],[ngModel],[formControl]"
603
+ },
604
+ {
605
+ kind: "directive",
606
+ type: i1.NgControlStatusGroup,
607
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
608
+ },
609
+ {
610
+ kind: "directive",
611
+ type: i1.NgModel,
612
+ selector: "[ngModel]:not([formControlName]):not([formControl])",
613
+ inputs: [
614
+ "name",
615
+ "disabled",
616
+ "ngModel",
617
+ "ngModelOptions"
618
+ ],
619
+ outputs: ["ngModelChange"],
620
+ exportAs: ["ngModel"]
621
+ },
622
+ {
623
+ kind: "directive",
624
+ type: i1.NgForm,
625
+ selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]",
626
+ inputs: ["ngFormOptions"],
627
+ outputs: ["ngSubmit"],
628
+ exportAs: ["ngForm"]
629
+ },
630
+ {
631
+ kind: "directive",
632
+ type: FormField,
633
+ selector: "[formField]",
634
+ inputs: ["formField"],
635
+ exportAs: ["formField"]
636
+ },
637
+ {
638
+ kind: "pipe",
639
+ type: TranslatePipe,
640
+ name: "translate"
641
+ }
642
+ ]
643
+ });
644
+ };
645
+ i0.ɵɵngDeclareClassMetadata({
646
+ minVersion: "12.0.0",
647
+ version: "22.2.1",
648
+ ngImport: i0,
649
+ type: TranslationKeyFormPage,
650
+ decorators: [{
651
+ type: Component,
652
+ args: [{
653
+ selector: "lib-translation-key-form-page",
654
+ imports: [
655
+ CardComponent,
656
+ InputTextDirective,
657
+ SelectComponent,
658
+ TextareaDirective,
659
+ ProgressSpinnerComponent,
660
+ TagComponent,
661
+ FButtonDirective,
662
+ UiIconComponent,
663
+ FormsModule,
664
+ FormField,
665
+ TranslatePipe
666
+ ],
667
+ providers: [MessageService],
668
+ template: `
669
+ <f-card>
670
+ <ng-template #header>
671
+ <div class="flex justify-between items-center p-4">
672
+ <h2 class="text-xl font-semibold m-0">
673
+ {{
674
+ isEditMode()
675
+ ? ('localization.key.edit' | translate)
676
+ : ('localization.key.new' | translate)
677
+ }}
678
+ </h2>
679
+ </div>
680
+ </ng-template>
681
+
682
+ <form (ngSubmit)="onSubmit()" class="flex flex-col gap-6">
683
+ <!-- Key Info Section -->
684
+ <div class="p-4 surface-ground rounded-lg">
685
+ <h4 class="font-semibold mt-0 mb-4">
686
+ {{ 'localization.key.about' | translate }}
687
+ </h4>
688
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
689
+ <!-- Key Name -->
690
+ <div class="flex flex-col gap-2">
691
+ <label for="key" class="font-medium"
692
+ >{{ 'localization.key.name' | translate }} *</label
693
+ >
694
+ <input
695
+ fInputText
696
+ id="key"
697
+ [formField]="formTree.key"
698
+ [placeholder]="'localization.placeholder.key.name' | translate"
699
+ />
700
+ </div>
701
+
702
+ <!-- Module -->
703
+ <div class="flex flex-col gap-2">
704
+ <label for="module" class="font-medium"
705
+ >{{ 'localization.key.module' | translate }} *</label
706
+ >
707
+ <f-select
708
+ id="module"
709
+ [ngModel]="formModel().module"
710
+ (ngModelChange)="updateModule($event)"
711
+ [ngModelOptions]="{ standalone: true }"
712
+ [options]="moduleOptions()"
713
+ optionLabel="label"
714
+ optionValue="value"
715
+ [editable]="true"
716
+ [disabled]="isReadonly()"
717
+ [placeholder]="'localization.key.select.module' | translate"
718
+ class="w-full"
719
+ />
720
+ </div>
721
+
722
+ @if (isReadonly()) {
723
+ <div class="md:col-span-2 -mt-2">
724
+ <small class="text-muted-color">{{
725
+ 'localization.key.readonly.edit.hint' | translate
726
+ }}</small>
727
+ </div>
728
+ }
729
+
730
+ <!-- Default Message -->
731
+ <div class="flex flex-col gap-2 md:col-span-2">
732
+ <label for="defaultMessage" class="font-medium"
733
+ >{{ 'localization.key.default.message' | translate }} *</label
734
+ >
735
+ <textarea
736
+ fTextarea
737
+ id="defaultMessage"
738
+ [formField]="formTree.defaultMessage"
739
+ [rows]="2"
740
+ [placeholder]="
741
+ 'localization.placeholder.default.message' | translate
742
+ "
743
+ ></textarea>
744
+ </div>
745
+
746
+ <!-- Description -->
747
+ <div class="flex flex-col gap-2 md:col-span-2">
748
+ <label for="description" class="font-medium">{{
749
+ 'localization.key.description' | translate
750
+ }}</label>
751
+ <input
752
+ fInputText
753
+ id="description"
754
+ [formField]="formTree.description"
755
+ [placeholder]="
756
+ 'localization.placeholder.key.description' | translate
757
+ "
758
+ />
759
+ </div>
760
+
761
+ <!-- Variables -->
762
+ <div class="flex flex-col gap-2 md:col-span-2">
763
+ <label for="variables" class="font-medium">{{
764
+ 'localization.key.variables' | translate
765
+ }}</label>
766
+ <input
767
+ fInputText
768
+ id="variables"
769
+ [formField]="formTree.variables"
770
+ [placeholder]="
771
+ 'localization.key.variables.placeholder' | translate
772
+ "
773
+ />
774
+ <small class="text-muted-color">{{
775
+ 'localization.key.variables.hint' | translate
776
+ }}</small>
777
+ </div>
778
+ </div>
779
+ </div>
780
+
781
+ <!-- Translations Section -->
782
+ <div class="p-4 surface-ground rounded-lg">
783
+ <h4 class="font-semibold mt-0 mb-4">
784
+ {{ 'localization.key.translations.section' | translate }}
785
+ </h4>
786
+ @if (isLoadingLanguages()) {
787
+ <div class="flex justify-center py-4">
788
+ <f-progressspinner strokeWidth="4" />
789
+ </div>
790
+ } @else if (languages().length === 0) {
791
+ <p class="text-muted-color text-center py-4">
792
+ {{ 'localization.key.no.languages' | translate }}
793
+ </p>
794
+ } @else {
795
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
796
+ @for (lang of languages(); track lang.id) {
797
+ <div class="flex flex-col gap-2">
798
+ <div class="flex justify-between items-center">
799
+ <label class="font-medium">
800
+ {{ lang.name }}
801
+ @if (lang.nativeName && lang.nativeName !== lang.name) {
802
+ <span class="text-muted-color"
803
+ >({{ lang.nativeName }})</span
804
+ >
805
+ }
806
+ </label>
807
+ <f-tag
808
+ [value]="lang.code"
809
+ severity="secondary"
810
+ class="uppercase"
811
+ />
812
+ </div>
813
+ <textarea
814
+ fTextarea
815
+ [ngModel]="getTranslationValue(lang.id)"
816
+ (ngModelChange)="setTranslationValue(lang.id, $event)"
817
+ [ngModelOptions]="{ standalone: true }"
818
+ [rows]="2"
819
+ [placeholder]="
820
+ formModel().defaultMessage ||
821
+ ('localization.key.enter.translation' | translate)
822
+ "
823
+ ></textarea>
824
+ </div>
825
+ }
826
+ </div>
827
+ }
828
+ </div>
829
+
830
+ <!-- Actions -->
831
+ <div class="flex justify-end gap-4 pt-4">
832
+ <button
833
+ fButton
834
+ type="button"
835
+ severity="secondary"
836
+ [outlined]="true"
837
+ (click)="onCancel()"
838
+ >
839
+ <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>
840
+ </button>
841
+ <button
842
+ fButton
843
+ type="submit"
844
+ [disabled]="!isFormValid() || isSaving()"
845
+ >
846
+ @if (isSaving()) {
847
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
848
+ }
849
+ <span pButtonLabel>{{
850
+ isEditMode()
851
+ ? ('shared.update' | translate)
852
+ : ('shared.create' | translate)
853
+ }}</span>
854
+ </button>
855
+ </div>
856
+ </form>
857
+ </f-card>
858
+ `
859
+ }]
860
+ }],
861
+ ctorParameters: () => []
862
+ });
863
+ export { TranslationKeyFormPage };
864
+
865
+ //# sourceMappingURL=flusys-ng-localization-translation-key-form.page-Dgn58Sh7.mjs.map