@flusys/ng-localization 1.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2556 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Injectable, inject, ApplicationRef, signal, computed, effect, DestroyRef, Component, provideAppInitializer } from '@angular/core';
3
+ import { ApiResourceService, PlatformService, TranslateService, AngularModule, TranslatePipe, LANGUAGE_PERMISSIONS, HasPermissionDirective, PermissionValidatorService, TRANSLATION_KEY_PERMISSIONS, IconComponent, resolveTranslationModule, SHARED_MESSAGES } from '@flusys/ng-shared';
4
+ import { HttpClient } from '@angular/common/http';
5
+ import { APP_CONFIG, LOCALIZATION_API_SERVICE, LOCALIZATION_STATE_SERVICE } from '@flusys/ng-core';
6
+ import { firstValueFrom, from, finalize, forkJoin } from 'rxjs';
7
+ import { DOCUMENT } from '@angular/common';
8
+ import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
9
+ import * as i1 from '@flusys/ng-ui';
10
+ import { UiModule, MenuComponent, MessageService, ConfirmationService } from '@flusys/ng-ui';
11
+ import { LAYOUT_LANGUAGE_SELECTOR, LAYOUT_LANGUAGE_SELECTOR_PANEL } from '@flusys/ng-layout';
12
+ import * as i2 from '@angular/forms';
13
+ import { FormsModule } from '@angular/forms';
14
+ import { form, required, maxLength, FormField } from '@angular/forms/signals';
15
+ import { Router, ActivatedRoute, RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
16
+
17
+ const LOCALIZATION_CONFIG = new InjectionToken('LOCALIZATION_CONFIG');
18
+ /** Get localization config with sensible defaults applied */
19
+ function getLocalizationConfig(overrides) {
20
+ const defaults = {
21
+ defaultLanguageCode: 'en',
22
+ loadStrategy: 'all',
23
+ initialModules: [],
24
+ enableLayoutSelector: false,
25
+ };
26
+ return {
27
+ ...defaults,
28
+ ...overrides,
29
+ defaultLanguageCode: overrides?.defaultLanguageCode || defaults.defaultLanguageCode,
30
+ };
31
+ }
32
+
33
+ const STORAGE_KEYS = {
34
+ CURRENT_LANGUAGE: 'flusys.language',
35
+ };
36
+
37
+ const LOCALIZATION_MESSAGES = {
38
+ // Language CRUD (API keys)
39
+ 'language.create.success': 'Language created successfully',
40
+ 'language.create.many.success': '{{count}} languages created successfully',
41
+ 'language.get.success': 'Language retrieved successfully',
42
+ 'language.get.by.ids.success': 'Languages retrieved successfully by Ids',
43
+ 'language.get.by.filter.success': 'Languages retrieved successfully by filter',
44
+ 'language.get.all.success': 'Languages retrieved successfully',
45
+ 'language.update.success': 'Language updated successfully',
46
+ 'language.update.many.success': '{{count}} languages updated successfully',
47
+ 'language.delete.success': 'Language deleted successfully',
48
+ 'language.restore.success': 'Language restored successfully',
49
+ 'language.active.success': 'Active languages retrieved successfully',
50
+ 'language.set.default.success': 'Default language updated successfully',
51
+ 'language.get.default.success': 'Default language retrieved successfully',
52
+ // Translation key CRUD (API keys)
53
+ 'translation.key.create.success': 'Translation key created successfully',
54
+ 'translation.key.create.many.success': '{{count}} translation keys created successfully',
55
+ 'translation.key.get.success': 'Translation key retrieved successfully',
56
+ 'translation.key.get.by.ids.success': 'Translation keys retrieved successfully by Ids',
57
+ 'translation.key.get.by.filter.success': 'Translation keys retrieved successfully by filter',
58
+ 'translation.key.get.all.success': 'Translation keys retrieved successfully',
59
+ 'translation.key.update.success': 'Translation key updated successfully',
60
+ 'translation.key.update.many.success': '{{count}} translation keys updated successfully',
61
+ 'translation.key.delete.success': 'Translation key deleted successfully',
62
+ 'translation.key.restore.success': 'Translation key restored successfully',
63
+ 'translation.key.modules.success': 'Modules retrieved successfully',
64
+ 'translation.key.readonly.delete.forbidden': 'Cannot delete readonly translation keys: {{keys}}',
65
+ 'translation.key.readonly.update.forbidden': 'Cannot modify key or module of system translation keys',
66
+ 'translation.key.duplicate.key.in.module': 'Translation key "{{key}}" already exists in module "{{module}}"',
67
+ // Translation CRUD (API keys)
68
+ 'translation.create.success': 'Translation created successfully',
69
+ 'translation.create.many.success': '{{count}} translations created successfully',
70
+ 'translation.get.success': 'Translation retrieved successfully',
71
+ 'translation.get.by.ids.success': 'Translations retrieved successfully by Ids',
72
+ 'translation.get.by.filter.success': 'Translations retrieved successfully by filter',
73
+ 'translation.get.all.success': 'Translations retrieved successfully',
74
+ 'translation.update.success': 'Translation updated successfully',
75
+ 'translation.update.many.success': '{{count}} translations updated successfully',
76
+ 'translation.delete.success': 'Translation deleted successfully',
77
+ 'translation.restore.success': 'Translation restored successfully',
78
+ 'translation.by.language.success': 'Translations retrieved successfully',
79
+ // pages/localization-container/localization-container.page.ts
80
+ 'localization.title': 'Localization',
81
+ 'localization.subtitle': 'Manage languages and translations',
82
+ 'localization.tabs.languages': 'Languages',
83
+ 'localization.tabs.keys': 'Keys',
84
+ // pages/language-list/language-list.page.ts
85
+ 'localization.language.title': 'Languages',
86
+ 'localization.language.subtitle': 'Manage available languages for your application',
87
+ 'localization.language.new': 'Add Language',
88
+ 'localization.language.code': 'Language Code',
89
+ 'localization.language.name': 'Language Name',
90
+ 'localization.language.native.name': 'Native Name',
91
+ 'localization.language.direction': 'Text Direction',
92
+ 'localization.language.rtl': 'RTL',
93
+ 'localization.language.ltr': 'LTR',
94
+ 'localization.language.default': 'Default',
95
+ 'localization.language.set.default': 'Set as Default',
96
+ 'localization.language.empty': 'No languages configured. Add a language to get started.',
97
+ 'localization.language.confirm.set.default': 'Set "{{name}}" as the default language?',
98
+ 'localization.language.delete.title': 'Delete Language',
99
+ // pages/language-form/language-form.page.ts
100
+ 'localization.language.edit': 'Edit Language',
101
+ 'localization.placeholder.code': 'en',
102
+ 'localization.language.iso.code.hint': 'ISO 639-1 language code (e.g., en, ar, fr)',
103
+ 'localization.placeholder.name': 'English',
104
+ 'localization.placeholder.native.name': 'English',
105
+ 'localization.language.native.name.hint': 'Name in the native language (e.g., العربية for Arabic)',
106
+ 'localization.language.display.order': 'Display Order',
107
+ 'localization.language.direction.ltr': 'Left to Right (LTR)',
108
+ 'localization.language.direction.rtl': 'Right to Left (RTL)',
109
+ 'localization.validation.code.required': 'Language code is required',
110
+ 'localization.validation.code.max.length': 'Language code must be at most 10 characters',
111
+ 'localization.validation.name.required': 'Language name is required',
112
+ 'localization.validation.name.max.length': 'Language name must be at most 100 characters',
113
+ // pages/translation-key-list/translation-key-list.page.ts
114
+ 'localization.key.title': 'Translation Keys',
115
+ 'localization.key.subtitle': 'Manage translation keys for your application',
116
+ 'localization.key.new': 'Add Key',
117
+ 'localization.key.filter.module': 'Filter by module',
118
+ 'localization.key.module': 'Module',
119
+ 'localization.key.name': 'Key Name',
120
+ 'localization.key.default.message': 'Default Message',
121
+ 'localization.key.translations': 'Translations',
122
+ 'localization.key.empty': 'No translation keys found',
123
+ 'localization.key.delete.title': 'Delete Translation Key',
124
+ // pages/translation-key-form/translation-key-form.page.ts
125
+ 'localization.key.edit': 'Edit Key',
126
+ 'localization.key.about': 'Key Information',
127
+ 'localization.placeholder.key.name': 'button.submit',
128
+ 'localization.key.select.module': 'Select or enter module',
129
+ 'localization.placeholder.default.message': 'Enter default value (English)',
130
+ 'localization.key.description': 'Description',
131
+ 'localization.placeholder.key.description': 'Optional description for translators',
132
+ 'localization.key.variables': 'Variables',
133
+ 'localization.key.variables.placeholder': 'e.g., name, count, date',
134
+ 'localization.key.variables.hint': 'Enter variable names used in this translation',
135
+ 'localization.key.translations.section': 'Translations',
136
+ 'localization.key.no.languages': 'No active languages configured',
137
+ 'localization.key.enter.translation': 'Enter translation',
138
+ 'localization.validation.key.required': 'Translation key is required',
139
+ 'localization.validation.default.message.required': 'Default message is required',
140
+ };
141
+
142
+ class LanguageApiService extends ApiResourceService {
143
+ constructor() {
144
+ super('language', 'localization');
145
+ }
146
+ setDefault(id) {
147
+ return this.http.post(`${this.baseUrl}/set-default`, { id });
148
+ }
149
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
150
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageApiService, providedIn: 'root' });
151
+ }
152
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageApiService, decorators: [{
153
+ type: Injectable,
154
+ args: [{ providedIn: 'root' }]
155
+ }], ctorParameters: () => [] });
156
+
157
+ class LocalizationApiService {
158
+ http = inject(HttpClient);
159
+ appConfig = inject(APP_CONFIG);
160
+ get baseUrl() {
161
+ return `${this.appConfig.apiBaseUrl}/localization`;
162
+ }
163
+ getActiveLanguages() {
164
+ return this.http.post(`${this.baseUrl}/language/get-active`, {});
165
+ }
166
+ getTranslationsByLanguage(languageCode, modules) {
167
+ return this.http.post(`${this.baseUrl}/translation/get-by-language`, { languageCode, ...(modules?.length ? { modules } : {}) });
168
+ }
169
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
170
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationApiService, providedIn: 'root' });
171
+ }
172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationApiService, decorators: [{
173
+ type: Injectable,
174
+ args: [{ providedIn: 'root' }]
175
+ }] });
176
+
177
+ class LocalizationInitializerService {
178
+ apiService = inject(LOCALIZATION_API_SERVICE, {
179
+ optional: false,
180
+ });
181
+ stateService = inject(LOCALIZATION_STATE_SERVICE, {
182
+ optional: false,
183
+ });
184
+ async initialize(initialFallbackMessages) {
185
+ try {
186
+ this.stateService.setLoading(true);
187
+ if (initialFallbackMessages) {
188
+ this.stateService.updateFallbackMessages(initialFallbackMessages);
189
+ }
190
+ const languagesResponse = await firstValueFrom(this.apiService.getActiveLanguages());
191
+ if (languagesResponse.success && languagesResponse.data) {
192
+ this.stateService.setAvailableLanguages(languagesResponse.data);
193
+ const savedCode = this.stateService.getSavedLanguageCode();
194
+ let selectedLanguage = languagesResponse.data.find((l) => l.code === savedCode);
195
+ if (!selectedLanguage) {
196
+ selectedLanguage = languagesResponse.data.find((l) => l.isDefault);
197
+ }
198
+ if (!selectedLanguage && languagesResponse.data.length > 0) {
199
+ selectedLanguage = languagesResponse.data[0];
200
+ }
201
+ if (selectedLanguage) {
202
+ this.stateService.setCurrentLanguage(selectedLanguage);
203
+ }
204
+ }
205
+ }
206
+ catch (error) {
207
+ // Silently fail - use default language
208
+ }
209
+ finally {
210
+ this.stateService.setLoading(false);
211
+ }
212
+ }
213
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationInitializerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
214
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationInitializerService, providedIn: 'root' });
215
+ }
216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationInitializerService, decorators: [{
217
+ type: Injectable,
218
+ args: [{ providedIn: 'root' }]
219
+ }] });
220
+
221
+ class LocalizationStateService {
222
+ platformService = inject(PlatformService);
223
+ document = inject(DOCUMENT);
224
+ appRef = inject(ApplicationRef);
225
+ _loadedModules = new Set();
226
+ _currentLanguage = signal(null, /* @ts-ignore */
227
+ ...(ngDevMode ? [{ debugName: "_currentLanguage" }] : /* istanbul ignore next */ []));
228
+ _availableLanguages = signal([], /* @ts-ignore */
229
+ ...(ngDevMode ? [{ debugName: "_availableLanguages" }] : /* istanbul ignore next */ []));
230
+ _translations = signal({}, /* @ts-ignore */
231
+ ...(ngDevMode ? [{ debugName: "_translations" }] : /* istanbul ignore next */ []));
232
+ _fallbackMessages = signal({}, /* @ts-ignore */
233
+ ...(ngDevMode ? [{ debugName: "_fallbackMessages" }] : /* istanbul ignore next */ []));
234
+ _isLoading = signal(false, /* @ts-ignore */
235
+ ...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
236
+ availableLanguages = this._availableLanguages.asReadonly();
237
+ translations = this._translations.asReadonly();
238
+ fallbackMessages = this._fallbackMessages.asReadonly();
239
+ isLoading = this._isLoading.asReadonly();
240
+ currentLanguageCode = computed(() => this._currentLanguage()?.code ?? 'en', /* @ts-ignore */
241
+ ...(ngDevMode ? [{ debugName: "currentLanguageCode" }] : /* istanbul ignore next */ []));
242
+ constructor() {
243
+ effect(() => {
244
+ const language = this._currentLanguage();
245
+ if (language && !this.platformService.isServer) {
246
+ this.document.documentElement.dir = language.direction ?? 'ltr';
247
+ this.document.documentElement.lang = language.code;
248
+ localStorage.setItem(STORAGE_KEYS.CURRENT_LANGUAGE, language.code);
249
+ }
250
+ });
251
+ effect(() => {
252
+ this._translations();
253
+ setTimeout(() => this.appRef.tick(), 0);
254
+ });
255
+ }
256
+ setCurrentLanguage(language) {
257
+ this._currentLanguage.set(language);
258
+ }
259
+ setAvailableLanguages(languages) {
260
+ this._availableLanguages.set(languages);
261
+ }
262
+ setTranslations(translations) {
263
+ this._translations.set(translations);
264
+ }
265
+ mergeTranslations(translations) {
266
+ this._translations.update((current) => ({ ...current, ...translations }));
267
+ }
268
+ setLoading(loading) {
269
+ this._isLoading.set(loading);
270
+ }
271
+ getSavedLanguageCode() {
272
+ if (this.platformService.isServer)
273
+ return null;
274
+ return localStorage.getItem(STORAGE_KEYS.CURRENT_LANGUAGE);
275
+ }
276
+ isModuleLoaded(module) {
277
+ return this._loadedModules.has(module);
278
+ }
279
+ markModuleLoaded(module) {
280
+ this._loadedModules.add(module);
281
+ }
282
+ getLoadedModules() {
283
+ return Array.from(this._loadedModules);
284
+ }
285
+ clearLoadedModules() {
286
+ this._loadedModules.clear();
287
+ }
288
+ updateFallbackMessages(messages) {
289
+ this._fallbackMessages.update((current) => ({ ...current, ...messages }));
290
+ }
291
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
292
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationStateService, providedIn: 'root' });
293
+ }
294
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationStateService, decorators: [{
295
+ type: Injectable,
296
+ args: [{ providedIn: 'root' }]
297
+ }], ctorParameters: () => [] });
298
+
299
+ class TranslationApiService extends ApiResourceService {
300
+ constructor() {
301
+ super('translation', 'localization');
302
+ }
303
+ getByKeyId(translationKeyId) {
304
+ return this.http.post(`${this.baseUrl}/get-all`, {
305
+ pagination: { currentPage: 0, pageSize: 1000 },
306
+ filter: { translationKeyId },
307
+ sort: {},
308
+ });
309
+ }
310
+ getCounts() {
311
+ return this.http.post(`${this.baseUrl}/get-counts`, {});
312
+ }
313
+ bulkUpdate(translations) {
314
+ return this.http.post(`${this.baseUrl}/update-many-values`, { translations });
315
+ }
316
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
317
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationApiService, providedIn: 'root' });
318
+ }
319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationApiService, decorators: [{
320
+ type: Injectable,
321
+ args: [{ providedIn: 'root' }]
322
+ }], ctorParameters: () => [] });
323
+
324
+ class TranslationKeyApiService extends ApiResourceService {
325
+ constructor() {
326
+ super('translation-key', 'localization');
327
+ }
328
+ getModules() {
329
+ return this.http.post(`${this.baseUrl}/get-modules`, {});
330
+ }
331
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationKeyApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
332
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationKeyApiService, providedIn: 'root' });
333
+ }
334
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationKeyApiService, decorators: [{
335
+ type: Injectable,
336
+ args: [{ providedIn: 'root' }]
337
+ }], ctorParameters: () => [] });
338
+
339
+ class TranslationLoaderService {
340
+ DEFAULT_INITIAL_MODULES = ['shared', 'layout'];
341
+ config = inject(LOCALIZATION_CONFIG, { optional: true });
342
+ apiService = inject(LOCALIZATION_API_SERVICE, {
343
+ optional: false,
344
+ });
345
+ stateService = inject(LOCALIZATION_STATE_SERVICE, {
346
+ optional: false,
347
+ });
348
+ async loadTranslations(languageCode, clearModules = false) {
349
+ const strategy = this.config?.loadStrategy ?? 'all';
350
+ // Save currently loaded modules before clearing (for language switch)
351
+ const previouslyLoadedModules = clearModules
352
+ ? this.stateService.getLoadedModules()
353
+ : [];
354
+ if (clearModules) {
355
+ this.stateService.clearLoadedModules();
356
+ }
357
+ this.stateService.setLoading(true);
358
+ try {
359
+ if (strategy === 'all') {
360
+ await this.loadAndSetTranslations(languageCode);
361
+ }
362
+ else {
363
+ const modules = this.config?.initialModules ?? this.DEFAULT_INITIAL_MODULES;
364
+ await this.loadAndSetTranslations(languageCode, modules);
365
+ // Reload all previously loaded modules for the new language
366
+ if (previouslyLoadedModules.length > 0) {
367
+ await this.reloadModules(languageCode, previouslyLoadedModules);
368
+ }
369
+ }
370
+ }
371
+ finally {
372
+ this.stateService.setLoading(false);
373
+ }
374
+ }
375
+ async loadAndSetTranslations(languageCode, modules) {
376
+ const response = await firstValueFrom(this.apiService.getTranslationsByLanguage(languageCode, modules));
377
+ if (response.success && response.data) {
378
+ this.stateService.setTranslations(response.data);
379
+ if (modules) {
380
+ modules.forEach((m) => this.stateService.markModuleLoaded(m));
381
+ }
382
+ }
383
+ }
384
+ async reloadModules(languageCode, modules) {
385
+ const initialModules = this.config?.initialModules ?? this.DEFAULT_INITIAL_MODULES;
386
+ const additionalModules = modules.filter((m) => !initialModules.includes(m));
387
+ if (additionalModules.length === 0)
388
+ return;
389
+ const response = await firstValueFrom(this.apiService.getTranslationsByLanguage(languageCode, additionalModules));
390
+ if (response.success && response.data) {
391
+ this.stateService.mergeTranslations(response.data);
392
+ additionalModules.forEach((m) => this.stateService.markModuleLoaded(m));
393
+ }
394
+ }
395
+ loadTranslationsAsync(languageCode, clearModules = false) {
396
+ return from(this.loadTranslations(languageCode, clearModules));
397
+ }
398
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
399
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationLoaderService, providedIn: 'root' });
400
+ }
401
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationLoaderService, decorators: [{
402
+ type: Injectable,
403
+ args: [{ providedIn: 'root' }]
404
+ }] });
405
+
406
+ class LanguageSelectorComponent {
407
+ localizationState = inject(LocalizationStateService);
408
+ translationLoader = inject(TranslationLoaderService);
409
+ destroyRef = inject(DestroyRef);
410
+ languages = this.localizationState.availableLanguages;
411
+ selectedLanguageCode = this.localizationState.currentLanguageCode;
412
+ selectedLanguage = computed(() => this.languages().find((l) => l.code === this.selectedLanguageCode()) ??
413
+ null, /* @ts-ignore */
414
+ ...(ngDevMode ? [{ debugName: "selectedLanguage" }] : /* istanbul ignore next */ []));
415
+ menuItems = computed(() => this.languages().map((lang) => ({
416
+ label: lang.nativeName || lang.name,
417
+ badge: lang.code.toUpperCase(),
418
+ selected: lang.code === this.selectedLanguageCode(),
419
+ command: () => this.onLanguageSelect(lang.code),
420
+ })), /* @ts-ignore */
421
+ ...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
422
+ onLanguageSelect(code) {
423
+ const language = this.languages().find((l) => l.code === code);
424
+ if (language) {
425
+ this.localizationState.setCurrentLanguage(language);
426
+ this.translationLoader
427
+ .loadTranslationsAsync(code, true)
428
+ .pipe(takeUntilDestroyed(this.destroyRef))
429
+ .subscribe();
430
+ }
431
+ }
432
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
433
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: LanguageSelectorComponent, isStandalone: true, selector: "lib-language-selector", host: { classAttribute: "relative" }, ngImport: i0, template: `
434
+ <button
435
+ type="button"
436
+ class="layout-topbar-action"
437
+ [class.layout-topbar-action-highlight]="menu.visible()"
438
+ (click)="menu.toggle($event)"
439
+ >
440
+ <ui-icon name="globe"></ui-icon>
441
+ <span class="font-semibold">{{
442
+ selectedLanguage()?.nativeName ||
443
+ selectedLanguage()?.name ||
444
+ selectedLanguageCode()
445
+ }}</span>
446
+ </button>
447
+ <f-menu
448
+ #menu
449
+ [model]="menuItems()"
450
+ [popup]="true"
451
+ class="absolute left-[0]"
452
+ [style]="{ 'inset-inline-end': '0' }"
453
+ />
454
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: i1.MenuComponent, selector: "f-menu", inputs: ["model", "popup", "appendTo", "styleClass", "style"], outputs: ["onShow", "onHide"] }] });
455
+ }
456
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageSelectorComponent, decorators: [{
457
+ type: Component,
458
+ args: [{
459
+ selector: 'lib-language-selector',
460
+ imports: [UiModule],
461
+ host: { class: 'relative' },
462
+ template: `
463
+ <button
464
+ type="button"
465
+ class="layout-topbar-action"
466
+ [class.layout-topbar-action-highlight]="menu.visible()"
467
+ (click)="menu.toggle($event)"
468
+ >
469
+ <ui-icon name="globe"></ui-icon>
470
+ <span class="font-semibold">{{
471
+ selectedLanguage()?.nativeName ||
472
+ selectedLanguage()?.name ||
473
+ selectedLanguageCode()
474
+ }}</span>
475
+ </button>
476
+ <f-menu
477
+ #menu
478
+ [model]="menuItems()"
479
+ [popup]="true"
480
+ class="absolute left-[0]"
481
+ [style]="{ 'inset-inline-end': '0' }"
482
+ />
483
+ `,
484
+ }]
485
+ }] });
486
+
487
+ class LanguageSelectorPanelComponent {
488
+ localizationState = inject(LocalizationStateService);
489
+ translationLoader = inject(TranslationLoaderService);
490
+ destroyRef = inject(DestroyRef);
491
+ languages = this.localizationState.availableLanguages;
492
+ _selectedLanguageCode = signal(this.localizationState.currentLanguageCode(), /* @ts-ignore */
493
+ ...(ngDevMode ? [{ debugName: "_selectedLanguageCode" }] : /* istanbul ignore next */ []));
494
+ selectedLanguageCode = this._selectedLanguageCode.asReadonly();
495
+ menuItems = computed(() => this.languages().map((lang) => ({
496
+ label: lang.nativeName || lang.name,
497
+ badge: lang.code.toUpperCase(),
498
+ selected: lang.code === this.selectedLanguageCode(),
499
+ command: () => this.onLanguageSelect(lang.code),
500
+ })), /* @ts-ignore */
501
+ ...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
502
+ constructor() {
503
+ effect(() => {
504
+ const currentCode = this.localizationState.currentLanguageCode();
505
+ if (currentCode !== this._selectedLanguageCode()) {
506
+ this._selectedLanguageCode.set(currentCode);
507
+ }
508
+ });
509
+ }
510
+ onLanguageSelect(code) {
511
+ const language = this.languages().find((l) => l.code === code);
512
+ if (language) {
513
+ this._selectedLanguageCode.set(code);
514
+ this.localizationState.setCurrentLanguage(language);
515
+ this.translationLoader
516
+ .loadTranslationsAsync(code, true)
517
+ .pipe(takeUntilDestroyed(this.destroyRef))
518
+ .subscribe();
519
+ }
520
+ }
521
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageSelectorPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
522
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.6", type: LanguageSelectorPanelComponent, isStandalone: true, selector: "lib-language-selector-panel", host: { styleAttribute: "background-color: var(--surface-overlay); inset-inline-end: 0;", classAttribute: "hidden absolute z-50 p-3 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] min-w-[180px]" }, ngImport: i0, template: `<f-menu [model]="menuItems()" />`, isInline: true, dependencies: [{ kind: "component", type: MenuComponent, selector: "f-menu", inputs: ["model", "popup", "appendTo", "styleClass", "style"], outputs: ["onShow", "onHide"] }] });
523
+ }
524
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageSelectorPanelComponent, decorators: [{
525
+ type: Component,
526
+ args: [{
527
+ selector: 'lib-language-selector-panel',
528
+ imports: [MenuComponent],
529
+ host: {
530
+ class: 'hidden absolute z-50 p-3 border border-surface rounded-border origin-top shadow-[0px_3px_5px_rgba(0,0,0,0.02),0px_0px_2px_rgba(0,0,0,0.05),0px_1px_4px_rgba(0,0,0,0.08)] min-w-[180px]',
531
+ style: 'background-color: var(--surface-overlay); inset-inline-end: 0;',
532
+ },
533
+ template: `<f-menu [model]="menuItems()" />`,
534
+ }]
535
+ }], ctorParameters: () => [] });
536
+
537
+ function provideLocalization(config, initialFallbackMessages) {
538
+ const providers = [
539
+ // 1. Value/Existing Providers
540
+ { provide: LOCALIZATION_CONFIG, useValue: config },
541
+ {
542
+ provide: LOCALIZATION_STATE_SERVICE,
543
+ useExisting: LocalizationStateService,
544
+ },
545
+ { provide: LOCALIZATION_API_SERVICE, useExisting: LocalizationApiService },
546
+ // 2. The Service itself
547
+ LocalizationInitializerService,
548
+ // 3. The modern Initializer
549
+ provideAppInitializer(() => {
550
+ const initializer = inject(LocalizationInitializerService);
551
+ return initializer.initialize(initialFallbackMessages);
552
+ }),
553
+ ];
554
+ if (config.enableLayoutSelector) {
555
+ providers.push({
556
+ provide: LAYOUT_LANGUAGE_SELECTOR,
557
+ useValue: LanguageSelectorComponent,
558
+ }, {
559
+ provide: LAYOUT_LANGUAGE_SELECTOR_PANEL,
560
+ useValue: LanguageSelectorPanelComponent,
561
+ });
562
+ }
563
+ return providers;
564
+ }
565
+
566
+ class LanguageFormPage {
567
+ router = inject(Router);
568
+ route = inject(ActivatedRoute);
569
+ languageService = inject(LanguageApiService);
570
+ messageService = inject(MessageService);
571
+ translateAdapter = inject(TranslateService, {
572
+ optional: true,
573
+ });
574
+ destroyRef = inject(DestroyRef);
575
+ routeParams = toSignal(this.route.paramMap);
576
+ loadTriggered = false;
577
+ isSaving = signal(false, /* @ts-ignore */
578
+ ...(ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []));
579
+ existingLanguage = signal(null, /* @ts-ignore */
580
+ ...(ngDevMode ? [{ debugName: "existingLanguage" }] : /* istanbul ignore next */ []));
581
+ isEditMode = computed(() => !!this.existingLanguage(), /* @ts-ignore */
582
+ ...(ngDevMode ? [{ debugName: "isEditMode" }] : /* istanbul ignore next */ []));
583
+ translate(key, vars) {
584
+ return this.translateAdapter?.translate(key, vars) ?? key;
585
+ }
586
+ directionOptions = computed(() => [
587
+ {
588
+ label: this.translate('localization.language.direction.ltr'),
589
+ value: 'ltr',
590
+ },
591
+ {
592
+ label: this.translate('localization.language.direction.rtl'),
593
+ value: 'rtl',
594
+ },
595
+ ], /* @ts-ignore */
596
+ ...(ngDevMode ? [{ debugName: "directionOptions" }] : /* istanbul ignore next */ []));
597
+ formModel = signal({
598
+ code: '',
599
+ name: '',
600
+ nativeName: '',
601
+ direction: 'ltr',
602
+ serial: '',
603
+ isActive: true,
604
+ isDefault: false,
605
+ }, /* @ts-ignore */
606
+ ...(ngDevMode ? [{ debugName: "formModel" }] : /* istanbul ignore next */ []));
607
+ formTree = form(this.formModel, (f) => {
608
+ required(f.code, {
609
+ message: this.translate('localization.validation.code.required'),
610
+ });
611
+ maxLength(f.code, 10, {
612
+ message: this.translate('localization.validation.code.max.length'),
613
+ });
614
+ required(f.name, {
615
+ message: this.translate('localization.validation.name.required'),
616
+ });
617
+ maxLength(f.name, 100, {
618
+ message: this.translate('localization.validation.name.max.length'),
619
+ });
620
+ });
621
+ isFormValid = computed(() => {
622
+ const model = this.formModel();
623
+ return model.code.trim().length > 0 && model.name.trim().length > 0;
624
+ }, /* @ts-ignore */
625
+ ...(ngDevMode ? [{ debugName: "isFormValid" }] : /* istanbul ignore next */ []));
626
+ constructor() {
627
+ effect(() => {
628
+ const params = this.routeParams();
629
+ if (!params || this.loadTriggered)
630
+ return;
631
+ this.loadTriggered = true;
632
+ const id = params.get('id');
633
+ if (id && id !== 'new') {
634
+ this.loadLanguage(id);
635
+ }
636
+ });
637
+ }
638
+ updateDirection(value) {
639
+ this.formModel.update((m) => ({ ...m, direction: value }));
640
+ }
641
+ loadLanguage(id) {
642
+ this.isSaving.set(true);
643
+ from(this.languageService.findById(id))
644
+ .pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isSaving.set(false)))
645
+ .subscribe({
646
+ next: (response) => {
647
+ if (response.success && response.data) {
648
+ this.existingLanguage.set(response.data);
649
+ this.formModel.set(this.toFormModel(response.data));
650
+ }
651
+ },
652
+ error: () => this.router.navigate(['/localization/languages']),
653
+ });
654
+ }
655
+ toFormModel(language) {
656
+ return {
657
+ code: language.code,
658
+ name: language.name,
659
+ nativeName: language.nativeName ?? '',
660
+ direction: language.direction ?? 'ltr',
661
+ serial: language.serial?.toString() ?? '',
662
+ isActive: language.isActive,
663
+ isDefault: language.isDefault,
664
+ };
665
+ }
666
+ onSubmit() {
667
+ if (!this.isFormValid())
668
+ return;
669
+ this.isSaving.set(true);
670
+ const model = this.formModel();
671
+ const dto = {
672
+ code: model.code,
673
+ name: model.name,
674
+ nativeName: model.nativeName || undefined,
675
+ direction: model.direction,
676
+ serial: model.serial ? parseInt(model.serial, 10) : undefined,
677
+ isActive: model.isActive,
678
+ isDefault: model.isDefault,
679
+ };
680
+ const operation$ = this.isEditMode()
681
+ ? from(this.languageService.update({
682
+ id: this.existingLanguage().id,
683
+ ...dto,
684
+ }))
685
+ : from(this.languageService.insert(dto));
686
+ operation$
687
+ .pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isSaving.set(false)))
688
+ .subscribe({
689
+ next: (response) => {
690
+ if (response.success) {
691
+ this.messageService.add({
692
+ severity: 'success',
693
+ summary: this.translate('shared.success'),
694
+ detail: response.messageKey
695
+ ? this.translate(response.messageKey, response.messageVariables)
696
+ : (response.message ?? ''),
697
+ });
698
+ this.router.navigate(['/localization/languages']);
699
+ }
700
+ },
701
+ });
702
+ }
703
+ onCancel() {
704
+ this.router.navigate(['/localization/languages']);
705
+ }
706
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageFormPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
707
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: LanguageFormPage, isStandalone: true, selector: "lib-language-form-page", providers: [MessageService], ngImport: i0, template: `
708
+ <f-card>
709
+ <ng-template #header>
710
+ <div class="flex justify-between items-center p-4">
711
+ <h2 class="text-xl font-semibold m-0">
712
+ {{
713
+ isEditMode()
714
+ ? ('localization.language.edit' | translate)
715
+ : ('localization.language.new' | translate)
716
+ }}
717
+ </h2>
718
+ </div>
719
+ </ng-template>
720
+
721
+ <form
722
+ (ngSubmit)="onSubmit()"
723
+ class="grid grid-cols-1 md:grid-cols-2 gap-4"
724
+ >
725
+ <!-- Language Code -->
726
+ <div class="flex flex-col gap-2">
727
+ <label for="code" class="font-medium"
728
+ >{{ 'localization.language.code' | translate }} *</label
729
+ >
730
+ <input
731
+ fInputText
732
+ id="code"
733
+ [formField]="formTree.code"
734
+ [placeholder]="'localization.placeholder.code' | translate"
735
+ />
736
+ <small class="text-muted-color">{{
737
+ 'localization.language.iso.code.hint' | translate
738
+ }}</small>
739
+ </div>
740
+
741
+ <!-- Language Name -->
742
+ <div class="flex flex-col gap-2">
743
+ <label for="name" class="font-medium"
744
+ >{{ 'localization.language.name' | translate }} *</label
745
+ >
746
+ <input
747
+ fInputText
748
+ id="name"
749
+ [formField]="formTree.name"
750
+ [placeholder]="'localization.placeholder.name' | translate"
751
+ />
752
+ </div>
753
+
754
+ <!-- Native Name -->
755
+ <div class="flex flex-col gap-2">
756
+ <label for="nativeName" class="font-medium">{{
757
+ 'localization.language.native.name' | translate
758
+ }}</label>
759
+ <input
760
+ fInputText
761
+ id="nativeName"
762
+ [formField]="formTree.nativeName"
763
+ [placeholder]="'localization.placeholder.native.name' | translate"
764
+ />
765
+ <small class="text-muted-color">{{
766
+ 'localization.language.native.name.hint' | translate
767
+ }}</small>
768
+ </div>
769
+
770
+ <!-- Text Direction -->
771
+ <div class="flex flex-col gap-2">
772
+ <label for="direction" class="font-medium"
773
+ >{{ 'localization.language.direction' | translate }} *</label
774
+ >
775
+ <f-select
776
+ id="direction"
777
+ [ngModel]="formModel().direction"
778
+ (ngModelChange)="updateDirection($event)"
779
+ [ngModelOptions]="{ standalone: true }"
780
+ [options]="directionOptions()"
781
+ optionLabel="label"
782
+ optionValue="value"
783
+ class="w-full"
784
+ />
785
+ </div>
786
+
787
+ <!-- Display Order -->
788
+ <div class="flex flex-col gap-2">
789
+ <label for="serial" class="font-medium">{{
790
+ 'localization.language.display.order' | translate
791
+ }}</label>
792
+ <input
793
+ fInputText
794
+ id="serial"
795
+ type="number"
796
+ [formField]="formTree.serial"
797
+ [placeholder]="'shared.display.order.placeholder' | translate"
798
+ />
799
+ </div>
800
+
801
+ <!-- Empty cell for alignment -->
802
+ <div class="hidden md:block"></div>
803
+
804
+ <!-- Checkboxes -->
805
+ <div class="flex items-center gap-6 md:col-span-2 pt-4">
806
+ <f-checkbox [formField]="formTree.isActive" [label]="'shared.active' | translate" />
807
+ <f-checkbox [formField]="formTree.isDefault" [label]="'localization.language.default' | translate" />
808
+ </div>
809
+
810
+ <!-- Actions -->
811
+ <div class="flex justify-end gap-4 md:col-span-2 pt-4">
812
+ <button
813
+ fButton
814
+ severity="secondary"
815
+ [outlined]="true"
816
+ (click)="onCancel()"
817
+ >
818
+ <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>
819
+ </button>
820
+ <button
821
+ fButton
822
+ type="submit"
823
+ [disabled]="!isFormValid() || isSaving()"
824
+ >
825
+ @if (isSaving()) {
826
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
827
+ }
828
+ <span pButtonLabel>{{
829
+ isEditMode()
830
+ ? ('shared.update' | translate)
831
+ : ('shared.create' | translate)
832
+ }}</span>
833
+ </button>
834
+ </div>
835
+ </form>
836
+ </f-card>
837
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "directive", type: i1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "component", type: i1.CardComponent, selector: "f-card", inputs: ["header", "subheader", "styleClass", "style"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
838
+ }
839
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageFormPage, decorators: [{
840
+ type: Component,
841
+ args: [{
842
+ selector: 'lib-language-form-page',
843
+ imports: [UiModule, AngularModule, FormsModule, FormField, TranslatePipe],
844
+ providers: [MessageService],
845
+ template: `
846
+ <f-card>
847
+ <ng-template #header>
848
+ <div class="flex justify-between items-center p-4">
849
+ <h2 class="text-xl font-semibold m-0">
850
+ {{
851
+ isEditMode()
852
+ ? ('localization.language.edit' | translate)
853
+ : ('localization.language.new' | translate)
854
+ }}
855
+ </h2>
856
+ </div>
857
+ </ng-template>
858
+
859
+ <form
860
+ (ngSubmit)="onSubmit()"
861
+ class="grid grid-cols-1 md:grid-cols-2 gap-4"
862
+ >
863
+ <!-- Language Code -->
864
+ <div class="flex flex-col gap-2">
865
+ <label for="code" class="font-medium"
866
+ >{{ 'localization.language.code' | translate }} *</label
867
+ >
868
+ <input
869
+ fInputText
870
+ id="code"
871
+ [formField]="formTree.code"
872
+ [placeholder]="'localization.placeholder.code' | translate"
873
+ />
874
+ <small class="text-muted-color">{{
875
+ 'localization.language.iso.code.hint' | translate
876
+ }}</small>
877
+ </div>
878
+
879
+ <!-- Language Name -->
880
+ <div class="flex flex-col gap-2">
881
+ <label for="name" class="font-medium"
882
+ >{{ 'localization.language.name' | translate }} *</label
883
+ >
884
+ <input
885
+ fInputText
886
+ id="name"
887
+ [formField]="formTree.name"
888
+ [placeholder]="'localization.placeholder.name' | translate"
889
+ />
890
+ </div>
891
+
892
+ <!-- Native Name -->
893
+ <div class="flex flex-col gap-2">
894
+ <label for="nativeName" class="font-medium">{{
895
+ 'localization.language.native.name' | translate
896
+ }}</label>
897
+ <input
898
+ fInputText
899
+ id="nativeName"
900
+ [formField]="formTree.nativeName"
901
+ [placeholder]="'localization.placeholder.native.name' | translate"
902
+ />
903
+ <small class="text-muted-color">{{
904
+ 'localization.language.native.name.hint' | translate
905
+ }}</small>
906
+ </div>
907
+
908
+ <!-- Text Direction -->
909
+ <div class="flex flex-col gap-2">
910
+ <label for="direction" class="font-medium"
911
+ >{{ 'localization.language.direction' | translate }} *</label
912
+ >
913
+ <f-select
914
+ id="direction"
915
+ [ngModel]="formModel().direction"
916
+ (ngModelChange)="updateDirection($event)"
917
+ [ngModelOptions]="{ standalone: true }"
918
+ [options]="directionOptions()"
919
+ optionLabel="label"
920
+ optionValue="value"
921
+ class="w-full"
922
+ />
923
+ </div>
924
+
925
+ <!-- Display Order -->
926
+ <div class="flex flex-col gap-2">
927
+ <label for="serial" class="font-medium">{{
928
+ 'localization.language.display.order' | translate
929
+ }}</label>
930
+ <input
931
+ fInputText
932
+ id="serial"
933
+ type="number"
934
+ [formField]="formTree.serial"
935
+ [placeholder]="'shared.display.order.placeholder' | translate"
936
+ />
937
+ </div>
938
+
939
+ <!-- Empty cell for alignment -->
940
+ <div class="hidden md:block"></div>
941
+
942
+ <!-- Checkboxes -->
943
+ <div class="flex items-center gap-6 md:col-span-2 pt-4">
944
+ <f-checkbox [formField]="formTree.isActive" [label]="'shared.active' | translate" />
945
+ <f-checkbox [formField]="formTree.isDefault" [label]="'localization.language.default' | translate" />
946
+ </div>
947
+
948
+ <!-- Actions -->
949
+ <div class="flex justify-end gap-4 md:col-span-2 pt-4">
950
+ <button
951
+ fButton
952
+ severity="secondary"
953
+ [outlined]="true"
954
+ (click)="onCancel()"
955
+ >
956
+ <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>
957
+ </button>
958
+ <button
959
+ fButton
960
+ type="submit"
961
+ [disabled]="!isFormValid() || isSaving()"
962
+ >
963
+ @if (isSaving()) {
964
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
965
+ }
966
+ <span pButtonLabel>{{
967
+ isEditMode()
968
+ ? ('shared.update' | translate)
969
+ : ('shared.create' | translate)
970
+ }}</span>
971
+ </button>
972
+ </div>
973
+ </form>
974
+ </f-card>
975
+ `,
976
+ }]
977
+ }], ctorParameters: () => [] });
978
+
979
+ var languageForm_page = /*#__PURE__*/Object.freeze({
980
+ __proto__: null,
981
+ LanguageFormPage: LanguageFormPage
982
+ });
983
+
984
+ class LanguageListPage {
985
+ LANGUAGE_PERMISSIONS = LANGUAGE_PERMISSIONS;
986
+ router = inject(Router);
987
+ languageService = inject(LanguageApiService);
988
+ confirmationService = inject(ConfirmationService);
989
+ messageService = inject(MessageService);
990
+ translateService = inject(TranslateService);
991
+ destroyRef = inject(DestroyRef);
992
+ ngOnInit() {
993
+ this.languageService.fetchList('', {
994
+ pagination: { currentPage: 0, pageSize: 100 },
995
+ filter: {},
996
+ sort: { serial: 'ASC', name: 'ASC' },
997
+ });
998
+ }
999
+ onCreate() {
1000
+ this.router.navigate(['/localization/languages/new']);
1001
+ }
1002
+ onEdit(id) {
1003
+ this.router.navigate(['/localization/languages', id]);
1004
+ }
1005
+ onSetDefault(lang) {
1006
+ this.confirmationService.confirm({
1007
+ message: this.translateService.translate('localization.language.confirm.set.default', { name: lang.name }),
1008
+ header: this.translateService.translate('localization.language.set.default'),
1009
+ icon: 'star',
1010
+ accept: () => {
1011
+ this.languageService
1012
+ .setDefault(lang.id)
1013
+ .pipe(takeUntilDestroyed(this.destroyRef))
1014
+ .subscribe({
1015
+ next: (response) => {
1016
+ if (response.success) {
1017
+ this.messageService.add({
1018
+ severity: 'success',
1019
+ summary: this.translateService.translate('shared.success'),
1020
+ detail: response.messageKey
1021
+ ? this.translateService.translate(response.messageKey, response.messageVariables)
1022
+ : (response.message ?? ''),
1023
+ });
1024
+ this.languageService.reload();
1025
+ }
1026
+ },
1027
+ });
1028
+ },
1029
+ });
1030
+ }
1031
+ onDelete(lang) {
1032
+ this.confirmationService.confirm({
1033
+ message: this.translateService.translate('shared.confirm.delete'),
1034
+ header: this.translateService.translate('localization.language.delete.title'),
1035
+ icon: 'exclamation-triangle',
1036
+ acceptButtonStyleClass: 'p-button-danger',
1037
+ accept: () => {
1038
+ from(this.languageService.delete({ id: lang.id, type: 'delete' }))
1039
+ .pipe(takeUntilDestroyed(this.destroyRef))
1040
+ .subscribe({
1041
+ next: (response) => {
1042
+ if (response.success) {
1043
+ this.messageService.add({
1044
+ severity: 'success',
1045
+ summary: this.translateService.translate('shared.success'),
1046
+ detail: response.messageKey
1047
+ ? this.translateService.translate(response.messageKey, response.messageVariables)
1048
+ : (response.message ?? ''),
1049
+ });
1050
+ this.languageService.reload();
1051
+ }
1052
+ },
1053
+ });
1054
+ },
1055
+ });
1056
+ }
1057
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageListPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
1058
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: LanguageListPage, isStandalone: true, selector: "lib-language-list-page", providers: [ConfirmationService, MessageService], ngImport: i0, template: `
1059
+ <div class="card">
1060
+ <div
1061
+ class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4"
1062
+ >
1063
+ <div>
1064
+ <h3 class="text-lg sm:text-xl font-semibold m-0">
1065
+ {{ 'localization.language.title' | translate }}
1066
+ </h3>
1067
+ <p class="text-sm text-muted-color mt-1">
1068
+ {{ 'localization.language.subtitle' | translate }}
1069
+ </p>
1070
+ </div>
1071
+ <button
1072
+ fButton
1073
+ *hasPermission="LANGUAGE_PERMISSIONS.CREATE"
1074
+ (click)="onCreate()"
1075
+ class="w-full sm:w-auto"
1076
+ >
1077
+ <ui-icon pButtonIcon name="plus"></ui-icon>
1078
+ <span pButtonLabel>{{ 'localization.language.new' | translate }}</span>
1079
+ </button>
1080
+ </div>
1081
+
1082
+ <div class="overflow-x-auto -mx-4 sm:mx-0">
1083
+ <f-table
1084
+ [value]="languageService.data()"
1085
+ [loading]="languageService.isLoading()"
1086
+ [tableStyle]="{ 'min-width': '40rem' }"
1087
+ >
1088
+ <ng-template #header>
1089
+ <tr>
1090
+ <th>{{ 'localization.language.code' | translate }}</th>
1091
+ <th>{{ 'localization.language.name' | translate }}</th>
1092
+ <th>{{ 'localization.language.native.name' | translate }}</th>
1093
+ <th>{{ 'localization.language.direction' | translate }}</th>
1094
+ <th>{{ 'shared.status' | translate }}</th>
1095
+ <th class="w-[150px]">{{ 'shared.actions' | translate }}</th>
1096
+ </tr>
1097
+ </ng-template>
1098
+
1099
+ <ng-template #body let-lang>
1100
+ <tr>
1101
+ <td>
1102
+ <span class="uppercase font-semibold">{{ lang.code }}</span>
1103
+ </td>
1104
+ <td>{{ lang.name }}</td>
1105
+ <td>{{ lang.nativeName || ('shared.na' | translate) }}</td>
1106
+ <td>
1107
+ <f-tag
1108
+ [value]="
1109
+ lang.direction === 'rtl'
1110
+ ? ('localization.language.rtl' | translate)
1111
+ : ('localization.language.ltr' | translate)
1112
+ "
1113
+ [severity]="lang.direction === 'rtl' ? 'warn' : 'info'"
1114
+ />
1115
+ </td>
1116
+ <td>
1117
+ <div class="flex gap-1 items-center">
1118
+ <f-tag
1119
+ [value]="
1120
+ lang.isActive
1121
+ ? ('shared.active' | translate)
1122
+ : ('shared.inactive' | translate)
1123
+ "
1124
+ [severity]="lang.isActive ? 'success' : 'secondary'"
1125
+ />
1126
+ @if (lang.isDefault) {
1127
+ <f-tag
1128
+ [value]="'localization.language.default' | translate"
1129
+ severity="contrast"
1130
+ />
1131
+ }
1132
+ </div>
1133
+ </td>
1134
+ <td>
1135
+ <div class="flex gap-1">
1136
+ @if (!lang.isDefault) {
1137
+ <button
1138
+ fButton
1139
+ *hasPermission="LANGUAGE_PERMISSIONS.UPDATE"
1140
+ [text]="true"
1141
+ size="small"
1142
+ [fTooltip]="
1143
+ 'localization.language.set.default' | translate
1144
+ "
1145
+ (click)="onSetDefault(lang)"
1146
+ >
1147
+ <ui-icon pButtonIcon name="star"></ui-icon>
1148
+ </button>
1149
+ }
1150
+ <button
1151
+ fButton
1152
+ *hasPermission="LANGUAGE_PERMISSIONS.UPDATE"
1153
+ [text]="true"
1154
+ severity="secondary"
1155
+ size="small"
1156
+ [fTooltip]="'shared.edit' | translate"
1157
+ (click)="onEdit(lang.id)"
1158
+ >
1159
+ <ui-icon pButtonIcon name="pencil"></ui-icon>
1160
+ </button>
1161
+ <button
1162
+ fButton
1163
+ *hasPermission="LANGUAGE_PERMISSIONS.DELETE"
1164
+ [text]="true"
1165
+ severity="danger"
1166
+ size="small"
1167
+ [fTooltip]="'shared.delete' | translate"
1168
+ [disabled]="lang.isDefault"
1169
+ (click)="onDelete(lang)"
1170
+ >
1171
+ <ui-icon pButtonIcon name="trash"></ui-icon>
1172
+ </button>
1173
+ </div>
1174
+ </td>
1175
+ </tr>
1176
+ </ng-template>
1177
+
1178
+ <ng-template #emptymessage>
1179
+ <tr>
1180
+ <td colspan="6" class="text-center py-4 text-muted-color">
1181
+ {{ 'localization.language.empty' | translate }}
1182
+ </td>
1183
+ </tr>
1184
+ </ng-template>
1185
+ </f-table>
1186
+ </div>
1187
+ </div>
1188
+
1189
+ <f-confirmdialog />
1190
+ <f-toast />
1191
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.ConfirmDialog, selector: "f-confirmdialog", inputs: ["key", "icon", "acceptLabel", "rejectLabel", "acceptIcon", "rejectIcon", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "blockScroll", "defaultFocus", "styleClass", "style"] }, { kind: "component", type: i1.ToastComponent, selector: "f-toast", inputs: ["key", "position"], outputs: ["onClose"] }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "component", type: i1.TableComponent, selector: "f-table", inputs: ["value", "lazy", "loading", "rows", "totalRecords", "rowHover", "dataKey", "scrollable", "scrollHeight", "paginator", "first", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "tableStyle", "styleClass", "globalFilterFields", "filterDelay", "responsiveLayout", "selectionMode", "sortMode", "rowExpandMode", "resizableColumns", "rowStyleClass", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "expandedRowKeys", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "expandedRowKeysChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onRowSelect", "onRowUnselect", "onSelectAllChange", "onRowExpand", "onRowCollapse", "onColResize", "onRowContextMenu"] }, { kind: "directive", type: i1.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1192
+ }
1193
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LanguageListPage, decorators: [{
1194
+ type: Component,
1195
+ args: [{
1196
+ selector: 'lib-language-list-page',
1197
+ imports: [AngularModule, UiModule, HasPermissionDirective, TranslatePipe],
1198
+ providers: [ConfirmationService, MessageService],
1199
+ template: `
1200
+ <div class="card">
1201
+ <div
1202
+ class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4"
1203
+ >
1204
+ <div>
1205
+ <h3 class="text-lg sm:text-xl font-semibold m-0">
1206
+ {{ 'localization.language.title' | translate }}
1207
+ </h3>
1208
+ <p class="text-sm text-muted-color mt-1">
1209
+ {{ 'localization.language.subtitle' | translate }}
1210
+ </p>
1211
+ </div>
1212
+ <button
1213
+ fButton
1214
+ *hasPermission="LANGUAGE_PERMISSIONS.CREATE"
1215
+ (click)="onCreate()"
1216
+ class="w-full sm:w-auto"
1217
+ >
1218
+ <ui-icon pButtonIcon name="plus"></ui-icon>
1219
+ <span pButtonLabel>{{ 'localization.language.new' | translate }}</span>
1220
+ </button>
1221
+ </div>
1222
+
1223
+ <div class="overflow-x-auto -mx-4 sm:mx-0">
1224
+ <f-table
1225
+ [value]="languageService.data()"
1226
+ [loading]="languageService.isLoading()"
1227
+ [tableStyle]="{ 'min-width': '40rem' }"
1228
+ >
1229
+ <ng-template #header>
1230
+ <tr>
1231
+ <th>{{ 'localization.language.code' | translate }}</th>
1232
+ <th>{{ 'localization.language.name' | translate }}</th>
1233
+ <th>{{ 'localization.language.native.name' | translate }}</th>
1234
+ <th>{{ 'localization.language.direction' | translate }}</th>
1235
+ <th>{{ 'shared.status' | translate }}</th>
1236
+ <th class="w-[150px]">{{ 'shared.actions' | translate }}</th>
1237
+ </tr>
1238
+ </ng-template>
1239
+
1240
+ <ng-template #body let-lang>
1241
+ <tr>
1242
+ <td>
1243
+ <span class="uppercase font-semibold">{{ lang.code }}</span>
1244
+ </td>
1245
+ <td>{{ lang.name }}</td>
1246
+ <td>{{ lang.nativeName || ('shared.na' | translate) }}</td>
1247
+ <td>
1248
+ <f-tag
1249
+ [value]="
1250
+ lang.direction === 'rtl'
1251
+ ? ('localization.language.rtl' | translate)
1252
+ : ('localization.language.ltr' | translate)
1253
+ "
1254
+ [severity]="lang.direction === 'rtl' ? 'warn' : 'info'"
1255
+ />
1256
+ </td>
1257
+ <td>
1258
+ <div class="flex gap-1 items-center">
1259
+ <f-tag
1260
+ [value]="
1261
+ lang.isActive
1262
+ ? ('shared.active' | translate)
1263
+ : ('shared.inactive' | translate)
1264
+ "
1265
+ [severity]="lang.isActive ? 'success' : 'secondary'"
1266
+ />
1267
+ @if (lang.isDefault) {
1268
+ <f-tag
1269
+ [value]="'localization.language.default' | translate"
1270
+ severity="contrast"
1271
+ />
1272
+ }
1273
+ </div>
1274
+ </td>
1275
+ <td>
1276
+ <div class="flex gap-1">
1277
+ @if (!lang.isDefault) {
1278
+ <button
1279
+ fButton
1280
+ *hasPermission="LANGUAGE_PERMISSIONS.UPDATE"
1281
+ [text]="true"
1282
+ size="small"
1283
+ [fTooltip]="
1284
+ 'localization.language.set.default' | translate
1285
+ "
1286
+ (click)="onSetDefault(lang)"
1287
+ >
1288
+ <ui-icon pButtonIcon name="star"></ui-icon>
1289
+ </button>
1290
+ }
1291
+ <button
1292
+ fButton
1293
+ *hasPermission="LANGUAGE_PERMISSIONS.UPDATE"
1294
+ [text]="true"
1295
+ severity="secondary"
1296
+ size="small"
1297
+ [fTooltip]="'shared.edit' | translate"
1298
+ (click)="onEdit(lang.id)"
1299
+ >
1300
+ <ui-icon pButtonIcon name="pencil"></ui-icon>
1301
+ </button>
1302
+ <button
1303
+ fButton
1304
+ *hasPermission="LANGUAGE_PERMISSIONS.DELETE"
1305
+ [text]="true"
1306
+ severity="danger"
1307
+ size="small"
1308
+ [fTooltip]="'shared.delete' | translate"
1309
+ [disabled]="lang.isDefault"
1310
+ (click)="onDelete(lang)"
1311
+ >
1312
+ <ui-icon pButtonIcon name="trash"></ui-icon>
1313
+ </button>
1314
+ </div>
1315
+ </td>
1316
+ </tr>
1317
+ </ng-template>
1318
+
1319
+ <ng-template #emptymessage>
1320
+ <tr>
1321
+ <td colspan="6" class="text-center py-4 text-muted-color">
1322
+ {{ 'localization.language.empty' | translate }}
1323
+ </td>
1324
+ </tr>
1325
+ </ng-template>
1326
+ </f-table>
1327
+ </div>
1328
+ </div>
1329
+
1330
+ <f-confirmdialog />
1331
+ <f-toast />
1332
+ `,
1333
+ }]
1334
+ }] });
1335
+
1336
+ var languageList_page = /*#__PURE__*/Object.freeze({
1337
+ __proto__: null,
1338
+ LanguageListPage: LanguageListPage
1339
+ });
1340
+
1341
+ class LocalizationContainerPage {
1342
+ permissionValidator = inject(PermissionValidatorService);
1343
+ allTabs = [
1344
+ {
1345
+ id: 'languages',
1346
+ labelKey: 'localization.tabs.languages',
1347
+ icon: 'globe',
1348
+ route: 'languages',
1349
+ permission: LANGUAGE_PERMISSIONS.READ,
1350
+ },
1351
+ {
1352
+ id: 'keys',
1353
+ labelKey: 'localization.tabs.keys',
1354
+ icon: 'key',
1355
+ route: 'keys',
1356
+ permission: TRANSLATION_KEY_PERMISSIONS.READ,
1357
+ },
1358
+ ];
1359
+ visibleTabs = computed(() => {
1360
+ // Explicitly track permissions signal for reactivity
1361
+ const permissions = this.permissionValidator.permissions();
1362
+ if (!permissions.length)
1363
+ return [];
1364
+ return this.allTabs.filter((tab) => this.permissionValidator.hasPermission(tab.permission));
1365
+ }, /* @ts-ignore */
1366
+ ...(ngDevMode ? [{ debugName: "visibleTabs" }] : /* istanbul ignore next */ []));
1367
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationContainerPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
1368
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: LocalizationContainerPage, isStandalone: true, selector: "lib-localization-container", ngImport: i0, template: `
1369
+ <div class="p-2 sm:p-4">
1370
+ <div class="mb-4">
1371
+ <h1 class="text-xl sm:text-2xl font-bold m-0">{{ 'localization.title' | translate }}</h1>
1372
+ <p class="text-muted-color mt-1 text-sm sm:text-base">{{ 'localization.subtitle' | translate }}</p>
1373
+ </div>
1374
+
1375
+ <div class="scrollbar-hide flex gap-1 mb-4 border-b border-surface overflow-x-auto flex-nowrap">
1376
+ @for (tab of visibleTabs(); track tab.id) {
1377
+ <a
1378
+ [routerLink]="tab.route"
1379
+ routerLinkActive="tab-active"
1380
+ class="flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer transition-colors text-muted-color no-underline border-b-2 border-transparent -mb-px hover:bg-surface-hover [&.tab-active]:text-primary [&.tab-active]:border-primary [&.tab-active]:bg-surface-ground whitespace-nowrap shrink-0"
1381
+ >
1382
+ <lib-icon [icon]="tab.icon"></lib-icon>
1383
+ <span>{{ tab.labelKey | translate }}</span>
1384
+ </a>
1385
+ }
1386
+ </div>
1387
+
1388
+ <router-outlet />
1389
+ </div>
1390
+ `, isInline: true, styles: [".scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}\n"], dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: IconComponent, selector: "lib-icon", inputs: ["icon", "iconType"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1391
+ }
1392
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LocalizationContainerPage, decorators: [{
1393
+ type: Component,
1394
+ args: [{ selector: 'lib-localization-container', imports: [RouterOutlet, RouterLink, RouterLinkActive, TranslatePipe, IconComponent], template: `
1395
+ <div class="p-2 sm:p-4">
1396
+ <div class="mb-4">
1397
+ <h1 class="text-xl sm:text-2xl font-bold m-0">{{ 'localization.title' | translate }}</h1>
1398
+ <p class="text-muted-color mt-1 text-sm sm:text-base">{{ 'localization.subtitle' | translate }}</p>
1399
+ </div>
1400
+
1401
+ <div class="scrollbar-hide flex gap-1 mb-4 border-b border-surface overflow-x-auto flex-nowrap">
1402
+ @for (tab of visibleTabs(); track tab.id) {
1403
+ <a
1404
+ [routerLink]="tab.route"
1405
+ routerLinkActive="tab-active"
1406
+ class="flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer transition-colors text-muted-color no-underline border-b-2 border-transparent -mb-px hover:bg-surface-hover [&.tab-active]:text-primary [&.tab-active]:border-primary [&.tab-active]:bg-surface-ground whitespace-nowrap shrink-0"
1407
+ >
1408
+ <lib-icon [icon]="tab.icon"></lib-icon>
1409
+ <span>{{ tab.labelKey | translate }}</span>
1410
+ </a>
1411
+ }
1412
+ </div>
1413
+
1414
+ <router-outlet />
1415
+ </div>
1416
+ `, styles: [".scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}\n"] }]
1417
+ }] });
1418
+
1419
+ var localizationContainer_page = /*#__PURE__*/Object.freeze({
1420
+ __proto__: null,
1421
+ LocalizationContainerPage: LocalizationContainerPage
1422
+ });
1423
+
1424
+ const LOCALIZATION_ROUTES = [
1425
+ {
1426
+ path: '',
1427
+ resolve: { translations: resolveTranslationModule({ modules: ['localization'], fallbackMessages: { ...LOCALIZATION_MESSAGES, ...SHARED_MESSAGES } }) },
1428
+ loadComponent: () => Promise.resolve().then(function () { return localizationContainer_page; }).then((m) => m.LocalizationContainerPage),
1429
+ children: [
1430
+ {
1431
+ path: 'languages',
1432
+ loadComponent: () => Promise.resolve().then(function () { return languageList_page; }).then((m) => m.LanguageListPage),
1433
+ },
1434
+ {
1435
+ path: 'languages/new',
1436
+ loadComponent: () => Promise.resolve().then(function () { return languageForm_page; }).then((m) => m.LanguageFormPage),
1437
+ },
1438
+ {
1439
+ path: 'languages/:id',
1440
+ loadComponent: () => Promise.resolve().then(function () { return languageForm_page; }).then((m) => m.LanguageFormPage),
1441
+ },
1442
+ {
1443
+ path: 'keys',
1444
+ loadComponent: () => Promise.resolve().then(function () { return translationKeyList_page; }).then((m) => m.TranslationKeyListPage),
1445
+ },
1446
+ {
1447
+ path: 'keys/new',
1448
+ loadComponent: () => Promise.resolve().then(function () { return translationKeyForm_page; }).then((m) => m.TranslationKeyFormPage),
1449
+ },
1450
+ {
1451
+ path: 'keys/:id',
1452
+ loadComponent: () => Promise.resolve().then(function () { return translationKeyForm_page; }).then((m) => m.TranslationKeyFormPage),
1453
+ },
1454
+ {
1455
+ path: '',
1456
+ redirectTo: 'languages',
1457
+ pathMatch: 'full',
1458
+ },
1459
+ ],
1460
+ },
1461
+ ];
1462
+
1463
+ class TranslationKeyFormPage {
1464
+ router = inject(Router);
1465
+ route = inject(ActivatedRoute);
1466
+ languageService = inject(LanguageApiService);
1467
+ translationKeyService = inject(TranslationKeyApiService);
1468
+ translationService = inject(TranslationApiService);
1469
+ messageService = inject(MessageService);
1470
+ translateAdapter = inject(TranslateService, {
1471
+ optional: true,
1472
+ });
1473
+ destroyRef = inject(DestroyRef);
1474
+ routeParams = toSignal(this.route.paramMap);
1475
+ loadTriggered = false;
1476
+ isSaving = signal(false, /* @ts-ignore */
1477
+ ...(ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []));
1478
+ isLoadingLanguages = signal(false, /* @ts-ignore */
1479
+ ...(ngDevMode ? [{ debugName: "isLoadingLanguages" }] : /* istanbul ignore next */ []));
1480
+ existingKey = signal(null, /* @ts-ignore */
1481
+ ...(ngDevMode ? [{ debugName: "existingKey" }] : /* istanbul ignore next */ []));
1482
+ isEditMode = computed(() => !!this.existingKey(), /* @ts-ignore */
1483
+ ...(ngDevMode ? [{ debugName: "isEditMode" }] : /* istanbul ignore next */ []));
1484
+ languages = signal([], /* @ts-ignore */
1485
+ ...(ngDevMode ? [{ debugName: "languages" }] : /* istanbul ignore next */ []));
1486
+ modules = signal([], /* @ts-ignore */
1487
+ ...(ngDevMode ? [{ debugName: "modules" }] : /* istanbul ignore next */ []));
1488
+ translations = signal([], /* @ts-ignore */
1489
+ ...(ngDevMode ? [{ debugName: "translations" }] : /* istanbul ignore next */ []));
1490
+ translate(key, vars) {
1491
+ return this.translateAdapter?.translate(key, vars) ?? key;
1492
+ }
1493
+ moduleOptions = computed(() => this.modules().map((m) => ({ label: m, value: m })), /* @ts-ignore */
1494
+ ...(ngDevMode ? [{ debugName: "moduleOptions" }] : /* istanbul ignore next */ []));
1495
+ formModel = signal({
1496
+ key: '',
1497
+ module: '',
1498
+ defaultMessage: '',
1499
+ description: '',
1500
+ variables: '',
1501
+ }, /* @ts-ignore */
1502
+ ...(ngDevMode ? [{ debugName: "formModel" }] : /* istanbul ignore next */ []));
1503
+ formTree = form(this.formModel, (f) => {
1504
+ required(f.key, {
1505
+ message: this.translate('localization.validation.key.required'),
1506
+ });
1507
+ required(f.defaultMessage, {
1508
+ message: this.translate('localization.validation.default.message.required'),
1509
+ });
1510
+ });
1511
+ isFormValid = computed(() => {
1512
+ const model = this.formModel();
1513
+ return (model.key.trim().length > 0 &&
1514
+ model.module.trim().length > 0 &&
1515
+ model.defaultMessage.trim().length > 0);
1516
+ }, /* @ts-ignore */
1517
+ ...(ngDevMode ? [{ debugName: "isFormValid" }] : /* istanbul ignore next */ []));
1518
+ constructor() {
1519
+ effect(() => {
1520
+ const params = this.routeParams();
1521
+ if (!params || this.loadTriggered)
1522
+ return;
1523
+ this.loadTriggered = true;
1524
+ const id = params.get('id');
1525
+ if (id && id !== 'new') {
1526
+ this.loadTranslationKey(id);
1527
+ }
1528
+ });
1529
+ }
1530
+ ngOnInit() {
1531
+ this.loadInitialData();
1532
+ }
1533
+ loadInitialData() {
1534
+ this.isLoadingLanguages.set(true);
1535
+ forkJoin({
1536
+ languages: from(this.languageService.getAll({
1537
+ pagination: { currentPage: 0, pageSize: 100 },
1538
+ filter: {},
1539
+ sort: { serial: 'ASC', name: 'ASC' },
1540
+ })),
1541
+ modules: this.translationKeyService.getModules(),
1542
+ })
1543
+ .pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isLoadingLanguages.set(false)))
1544
+ .subscribe({
1545
+ next: ({ languages, modules }) => {
1546
+ if (languages.success && languages.data) {
1547
+ const activeLanguages = languages.data.filter((l) => l.isActive);
1548
+ this.languages.set(activeLanguages);
1549
+ this.initializeTranslations(activeLanguages);
1550
+ }
1551
+ if (modules.success && modules.data) {
1552
+ this.modules.set(modules.data);
1553
+ }
1554
+ },
1555
+ });
1556
+ }
1557
+ initializeTranslations(languages) {
1558
+ const current = this.translations();
1559
+ if (current.length === 0) {
1560
+ this.translations.set(languages.map((lang) => ({ languageId: lang.id, value: '' })));
1561
+ }
1562
+ }
1563
+ updateModule(value) {
1564
+ this.formModel.update((m) => ({ ...m, module: value ?? '' }));
1565
+ }
1566
+ getTranslationValue(languageId) {
1567
+ return (this.translations().find((t) => t.languageId === languageId)?.value ?? '');
1568
+ }
1569
+ setTranslationValue(languageId, value) {
1570
+ this.translations.update((list) => {
1571
+ const existing = list.find((t) => t.languageId === languageId);
1572
+ if (existing) {
1573
+ return list.map((t) => t.languageId === languageId ? { ...t, value } : t);
1574
+ }
1575
+ return [...list, { languageId, value }];
1576
+ });
1577
+ }
1578
+ loadTranslationKey(id) {
1579
+ this.isSaving.set(true);
1580
+ from(this.translationKeyService.findById(id))
1581
+ .pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.isSaving.set(false)))
1582
+ .subscribe({
1583
+ next: (response) => {
1584
+ if (response.success && response.data) {
1585
+ this.existingKey.set(response.data);
1586
+ this.formModel.set(this.toFormModel(response.data));
1587
+ this.loadExistingTranslations(id);
1588
+ }
1589
+ },
1590
+ error: () => this.router.navigate(['/localization/keys']),
1591
+ });
1592
+ }
1593
+ loadExistingTranslations(keyId) {
1594
+ this.translationService
1595
+ .getByKeyId(keyId)
1596
+ .pipe(takeUntilDestroyed(this.destroyRef))
1597
+ .subscribe({
1598
+ next: (response) => {
1599
+ if (response.success && response.data) {
1600
+ this.translations.update((list) => {
1601
+ const updated = [...list];
1602
+ response.data.forEach((t) => {
1603
+ const existing = updated.find((tr) => tr.languageId === t.languageId);
1604
+ if (existing) {
1605
+ existing.value = t.value;
1606
+ existing.translationId = t.id;
1607
+ }
1608
+ else {
1609
+ updated.push({
1610
+ languageId: t.languageId,
1611
+ value: t.value,
1612
+ translationId: t.id,
1613
+ });
1614
+ }
1615
+ });
1616
+ return updated;
1617
+ });
1618
+ }
1619
+ },
1620
+ });
1621
+ }
1622
+ toFormModel(key) {
1623
+ return {
1624
+ key: key.key,
1625
+ module: key.module,
1626
+ defaultMessage: key.defaultMessage,
1627
+ description: key.description ?? '',
1628
+ variables: key.variables?.join(', ') ?? '',
1629
+ };
1630
+ }
1631
+ onSubmit() {
1632
+ if (!this.isFormValid())
1633
+ return;
1634
+ this.isSaving.set(true);
1635
+ const model = this.formModel();
1636
+ const variables = model.variables
1637
+ .split(',')
1638
+ .map((v) => v.trim())
1639
+ .filter((v) => v.length > 0);
1640
+ if (this.isEditMode()) {
1641
+ this.updateKey(model, variables);
1642
+ }
1643
+ else {
1644
+ this.createKey(model, variables);
1645
+ }
1646
+ }
1647
+ createKey(model, variables) {
1648
+ const createDto = {
1649
+ key: model.key,
1650
+ module: model.module,
1651
+ defaultMessage: model.defaultMessage,
1652
+ description: model.description || undefined,
1653
+ variables: variables.length > 0 ? variables : undefined,
1654
+ };
1655
+ from(this.translationKeyService.insert(createDto))
1656
+ .pipe(takeUntilDestroyed(this.destroyRef))
1657
+ .subscribe({
1658
+ next: (response) => {
1659
+ if (response.success && response.data) {
1660
+ this.saveTranslations(response.data.id, response.messageKey, response.messageVariables);
1661
+ }
1662
+ else {
1663
+ this.isSaving.set(false);
1664
+ }
1665
+ },
1666
+ error: () => this.isSaving.set(false),
1667
+ });
1668
+ }
1669
+ updateKey(model, variables) {
1670
+ const updateDto = {
1671
+ id: this.existingKey().id,
1672
+ key: model.key,
1673
+ module: model.module,
1674
+ defaultMessage: model.defaultMessage,
1675
+ description: model.description || undefined,
1676
+ variables: variables.length > 0 ? variables : undefined,
1677
+ };
1678
+ from(this.translationKeyService.update(updateDto))
1679
+ .pipe(takeUntilDestroyed(this.destroyRef))
1680
+ .subscribe({
1681
+ next: (response) => {
1682
+ if (response.success) {
1683
+ this.saveTranslations(this.existingKey().id, response.messageKey, response.messageVariables);
1684
+ }
1685
+ else {
1686
+ this.isSaving.set(false);
1687
+ }
1688
+ },
1689
+ error: () => this.isSaving.set(false),
1690
+ });
1691
+ }
1692
+ saveTranslations(keyId, messageKey, messageVariables) {
1693
+ const translationsToSave = this.translations()
1694
+ .filter((t) => t.value?.trim())
1695
+ .map((t) => ({
1696
+ id: t.translationId,
1697
+ languageId: t.languageId,
1698
+ translationKeyId: keyId,
1699
+ value: t.value,
1700
+ isVerified: false,
1701
+ }));
1702
+ if (translationsToSave.length === 0) {
1703
+ this.onSaveSuccess(messageKey, messageVariables);
1704
+ return;
1705
+ }
1706
+ this.translationService
1707
+ .bulkUpdate(translationsToSave)
1708
+ .pipe(takeUntilDestroyed(this.destroyRef))
1709
+ .subscribe({
1710
+ next: () => this.onSaveSuccess(messageKey, messageVariables),
1711
+ error: () => this.onSaveSuccess(messageKey, messageVariables),
1712
+ });
1713
+ }
1714
+ onSaveSuccess(messageKey, messageVariables) {
1715
+ this.messageService.add({
1716
+ severity: 'success',
1717
+ summary: this.translate('shared.success'),
1718
+ detail: messageKey ? this.translate(messageKey, messageVariables) : '',
1719
+ });
1720
+ this.isSaving.set(false);
1721
+ this.router.navigate(['/localization/keys']);
1722
+ }
1723
+ onCancel() {
1724
+ this.router.navigate(['/localization/keys']);
1725
+ }
1726
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationKeyFormPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
1727
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: TranslationKeyFormPage, isStandalone: true, selector: "lib-translation-key-form-page", providers: [MessageService], ngImport: i0, template: `
1728
+ <f-card>
1729
+ <ng-template #header>
1730
+ <div class="flex justify-between items-center p-4">
1731
+ <h2 class="text-xl font-semibold m-0">
1732
+ {{
1733
+ isEditMode()
1734
+ ? ('localization.key.edit' | translate)
1735
+ : ('localization.key.new' | translate)
1736
+ }}
1737
+ </h2>
1738
+ </div>
1739
+ </ng-template>
1740
+
1741
+ <form (ngSubmit)="onSubmit()" class="flex flex-col gap-6">
1742
+ <!-- Key Info Section -->
1743
+ <div class="p-4 surface-ground rounded-lg">
1744
+ <h4 class="font-semibold mt-0 mb-4">
1745
+ {{ 'localization.key.about' | translate }}
1746
+ </h4>
1747
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
1748
+ <!-- Key Name -->
1749
+ <div class="flex flex-col gap-2">
1750
+ <label for="key" class="font-medium"
1751
+ >{{ 'localization.key.name' | translate }} *</label
1752
+ >
1753
+ <input
1754
+ fInputText
1755
+ id="key"
1756
+ [formField]="formTree.key"
1757
+ [placeholder]="'localization.placeholder.key.name' | translate"
1758
+ />
1759
+ </div>
1760
+
1761
+ <!-- Module -->
1762
+ <div class="flex flex-col gap-2">
1763
+ <label for="module" class="font-medium"
1764
+ >{{ 'localization.key.module' | translate }} *</label
1765
+ >
1766
+ <f-select
1767
+ id="module"
1768
+ [ngModel]="formModel().module"
1769
+ (ngModelChange)="updateModule($event)"
1770
+ [ngModelOptions]="{ standalone: true }"
1771
+ [options]="moduleOptions()"
1772
+ optionLabel="label"
1773
+ optionValue="value"
1774
+ [editable]="true"
1775
+ [placeholder]="'localization.key.select.module' | translate"
1776
+ class="w-full"
1777
+ />
1778
+ </div>
1779
+
1780
+ <!-- Default Message -->
1781
+ <div class="flex flex-col gap-2 md:col-span-2">
1782
+ <label for="defaultMessage" class="font-medium"
1783
+ >{{ 'localization.key.default.message' | translate }} *</label
1784
+ >
1785
+ <textarea
1786
+ fTextarea
1787
+ id="defaultMessage"
1788
+ [formField]="formTree.defaultMessage"
1789
+ [rows]="2"
1790
+ [placeholder]="
1791
+ 'localization.placeholder.default.message' | translate
1792
+ "
1793
+ ></textarea>
1794
+ </div>
1795
+
1796
+ <!-- Description -->
1797
+ <div class="flex flex-col gap-2 md:col-span-2">
1798
+ <label for="description" class="font-medium">{{
1799
+ 'localization.key.description' | translate
1800
+ }}</label>
1801
+ <input
1802
+ fInputText
1803
+ id="description"
1804
+ [formField]="formTree.description"
1805
+ [placeholder]="
1806
+ 'localization.placeholder.key.description' | translate
1807
+ "
1808
+ />
1809
+ </div>
1810
+
1811
+ <!-- Variables -->
1812
+ <div class="flex flex-col gap-2 md:col-span-2">
1813
+ <label for="variables" class="font-medium">{{
1814
+ 'localization.key.variables' | translate
1815
+ }}</label>
1816
+ <input
1817
+ fInputText
1818
+ id="variables"
1819
+ [formField]="formTree.variables"
1820
+ [placeholder]="
1821
+ 'localization.key.variables.placeholder' | translate
1822
+ "
1823
+ />
1824
+ <small class="text-muted-color">{{
1825
+ 'localization.key.variables.hint' | translate
1826
+ }}</small>
1827
+ </div>
1828
+ </div>
1829
+ </div>
1830
+
1831
+ <!-- Translations Section -->
1832
+ <div class="p-4 surface-ground rounded-lg">
1833
+ <h4 class="font-semibold mt-0 mb-4">
1834
+ {{ 'localization.key.translations.section' | translate }}
1835
+ </h4>
1836
+ @if (isLoadingLanguages()) {
1837
+ <div class="flex justify-center py-4">
1838
+ <f-progressspinner strokeWidth="4" />
1839
+ </div>
1840
+ } @else if (languages().length === 0) {
1841
+ <p class="text-muted-color text-center py-4">
1842
+ {{ 'localization.key.no.languages' | translate }}
1843
+ </p>
1844
+ } @else {
1845
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
1846
+ @for (lang of languages(); track lang.id) {
1847
+ <div class="flex flex-col gap-2">
1848
+ <div class="flex justify-between items-center">
1849
+ <label class="font-medium">
1850
+ {{ lang.name }}
1851
+ @if (lang.nativeName && lang.nativeName !== lang.name) {
1852
+ <span class="text-muted-color"
1853
+ >({{ lang.nativeName }})</span
1854
+ >
1855
+ }
1856
+ </label>
1857
+ <f-tag
1858
+ [value]="lang.code"
1859
+ severity="secondary"
1860
+ class="uppercase"
1861
+ />
1862
+ </div>
1863
+ <textarea
1864
+ fTextarea
1865
+ [ngModel]="getTranslationValue(lang.id)"
1866
+ (ngModelChange)="setTranslationValue(lang.id, $event)"
1867
+ [ngModelOptions]="{ standalone: true }"
1868
+ [rows]="2"
1869
+ [placeholder]="
1870
+ formModel().defaultMessage ||
1871
+ ('localization.key.enter.translation' | translate)
1872
+ "
1873
+ ></textarea>
1874
+ </div>
1875
+ }
1876
+ </div>
1877
+ }
1878
+ </div>
1879
+
1880
+ <!-- Actions -->
1881
+ <div class="flex justify-end gap-4 pt-4">
1882
+ <button
1883
+ fButton
1884
+ severity="secondary"
1885
+ [outlined]="true"
1886
+ (click)="onCancel()"
1887
+ >
1888
+ <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>
1889
+ </button>
1890
+ <button
1891
+ fButton
1892
+ type="submit"
1893
+ [disabled]="!isFormValid() || isSaving()"
1894
+ >
1895
+ @if (isSaving()) {
1896
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
1897
+ }
1898
+ <span pButtonLabel>{{
1899
+ isEditMode()
1900
+ ? ('shared.update' | translate)
1901
+ : ('shared.create' | translate)
1902
+ }}</span>
1903
+ </button>
1904
+ </div>
1905
+ </form>
1906
+ </f-card>
1907
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1.ProgressSpinnerComponent, selector: "f-progressspinner", inputs: ["size", "strokeWidth", "animationDuration", "styleClass", "ariaLabel"] }, { kind: "component", type: i1.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "directive", type: i1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "directive", type: i1.TextareaDirective, selector: "[fTextarea]", inputs: ["invalid", "touched", "variant", "fluid", "autoResize"], outputs: ["onResize"] }, { kind: "component", type: i1.CardComponent, selector: "f-card", inputs: ["header", "subheader", "styleClass", "style"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, 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]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1908
+ }
1909
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationKeyFormPage, decorators: [{
1910
+ type: Component,
1911
+ args: [{
1912
+ selector: 'lib-translation-key-form-page',
1913
+ imports: [UiModule, AngularModule, FormsModule, FormField, TranslatePipe],
1914
+ providers: [MessageService],
1915
+ template: `
1916
+ <f-card>
1917
+ <ng-template #header>
1918
+ <div class="flex justify-between items-center p-4">
1919
+ <h2 class="text-xl font-semibold m-0">
1920
+ {{
1921
+ isEditMode()
1922
+ ? ('localization.key.edit' | translate)
1923
+ : ('localization.key.new' | translate)
1924
+ }}
1925
+ </h2>
1926
+ </div>
1927
+ </ng-template>
1928
+
1929
+ <form (ngSubmit)="onSubmit()" class="flex flex-col gap-6">
1930
+ <!-- Key Info Section -->
1931
+ <div class="p-4 surface-ground rounded-lg">
1932
+ <h4 class="font-semibold mt-0 mb-4">
1933
+ {{ 'localization.key.about' | translate }}
1934
+ </h4>
1935
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
1936
+ <!-- Key Name -->
1937
+ <div class="flex flex-col gap-2">
1938
+ <label for="key" class="font-medium"
1939
+ >{{ 'localization.key.name' | translate }} *</label
1940
+ >
1941
+ <input
1942
+ fInputText
1943
+ id="key"
1944
+ [formField]="formTree.key"
1945
+ [placeholder]="'localization.placeholder.key.name' | translate"
1946
+ />
1947
+ </div>
1948
+
1949
+ <!-- Module -->
1950
+ <div class="flex flex-col gap-2">
1951
+ <label for="module" class="font-medium"
1952
+ >{{ 'localization.key.module' | translate }} *</label
1953
+ >
1954
+ <f-select
1955
+ id="module"
1956
+ [ngModel]="formModel().module"
1957
+ (ngModelChange)="updateModule($event)"
1958
+ [ngModelOptions]="{ standalone: true }"
1959
+ [options]="moduleOptions()"
1960
+ optionLabel="label"
1961
+ optionValue="value"
1962
+ [editable]="true"
1963
+ [placeholder]="'localization.key.select.module' | translate"
1964
+ class="w-full"
1965
+ />
1966
+ </div>
1967
+
1968
+ <!-- Default Message -->
1969
+ <div class="flex flex-col gap-2 md:col-span-2">
1970
+ <label for="defaultMessage" class="font-medium"
1971
+ >{{ 'localization.key.default.message' | translate }} *</label
1972
+ >
1973
+ <textarea
1974
+ fTextarea
1975
+ id="defaultMessage"
1976
+ [formField]="formTree.defaultMessage"
1977
+ [rows]="2"
1978
+ [placeholder]="
1979
+ 'localization.placeholder.default.message' | translate
1980
+ "
1981
+ ></textarea>
1982
+ </div>
1983
+
1984
+ <!-- Description -->
1985
+ <div class="flex flex-col gap-2 md:col-span-2">
1986
+ <label for="description" class="font-medium">{{
1987
+ 'localization.key.description' | translate
1988
+ }}</label>
1989
+ <input
1990
+ fInputText
1991
+ id="description"
1992
+ [formField]="formTree.description"
1993
+ [placeholder]="
1994
+ 'localization.placeholder.key.description' | translate
1995
+ "
1996
+ />
1997
+ </div>
1998
+
1999
+ <!-- Variables -->
2000
+ <div class="flex flex-col gap-2 md:col-span-2">
2001
+ <label for="variables" class="font-medium">{{
2002
+ 'localization.key.variables' | translate
2003
+ }}</label>
2004
+ <input
2005
+ fInputText
2006
+ id="variables"
2007
+ [formField]="formTree.variables"
2008
+ [placeholder]="
2009
+ 'localization.key.variables.placeholder' | translate
2010
+ "
2011
+ />
2012
+ <small class="text-muted-color">{{
2013
+ 'localization.key.variables.hint' | translate
2014
+ }}</small>
2015
+ </div>
2016
+ </div>
2017
+ </div>
2018
+
2019
+ <!-- Translations Section -->
2020
+ <div class="p-4 surface-ground rounded-lg">
2021
+ <h4 class="font-semibold mt-0 mb-4">
2022
+ {{ 'localization.key.translations.section' | translate }}
2023
+ </h4>
2024
+ @if (isLoadingLanguages()) {
2025
+ <div class="flex justify-center py-4">
2026
+ <f-progressspinner strokeWidth="4" />
2027
+ </div>
2028
+ } @else if (languages().length === 0) {
2029
+ <p class="text-muted-color text-center py-4">
2030
+ {{ 'localization.key.no.languages' | translate }}
2031
+ </p>
2032
+ } @else {
2033
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2034
+ @for (lang of languages(); track lang.id) {
2035
+ <div class="flex flex-col gap-2">
2036
+ <div class="flex justify-between items-center">
2037
+ <label class="font-medium">
2038
+ {{ lang.name }}
2039
+ @if (lang.nativeName && lang.nativeName !== lang.name) {
2040
+ <span class="text-muted-color"
2041
+ >({{ lang.nativeName }})</span
2042
+ >
2043
+ }
2044
+ </label>
2045
+ <f-tag
2046
+ [value]="lang.code"
2047
+ severity="secondary"
2048
+ class="uppercase"
2049
+ />
2050
+ </div>
2051
+ <textarea
2052
+ fTextarea
2053
+ [ngModel]="getTranslationValue(lang.id)"
2054
+ (ngModelChange)="setTranslationValue(lang.id, $event)"
2055
+ [ngModelOptions]="{ standalone: true }"
2056
+ [rows]="2"
2057
+ [placeholder]="
2058
+ formModel().defaultMessage ||
2059
+ ('localization.key.enter.translation' | translate)
2060
+ "
2061
+ ></textarea>
2062
+ </div>
2063
+ }
2064
+ </div>
2065
+ }
2066
+ </div>
2067
+
2068
+ <!-- Actions -->
2069
+ <div class="flex justify-end gap-4 pt-4">
2070
+ <button
2071
+ fButton
2072
+ severity="secondary"
2073
+ [outlined]="true"
2074
+ (click)="onCancel()"
2075
+ >
2076
+ <span pButtonLabel>{{ 'shared.cancel' | translate }}</span>
2077
+ </button>
2078
+ <button
2079
+ fButton
2080
+ type="submit"
2081
+ [disabled]="!isFormValid() || isSaving()"
2082
+ >
2083
+ @if (isSaving()) {
2084
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
2085
+ }
2086
+ <span pButtonLabel>{{
2087
+ isEditMode()
2088
+ ? ('shared.update' | translate)
2089
+ : ('shared.create' | translate)
2090
+ }}</span>
2091
+ </button>
2092
+ </div>
2093
+ </form>
2094
+ </f-card>
2095
+ `,
2096
+ }]
2097
+ }], ctorParameters: () => [] });
2098
+
2099
+ var translationKeyForm_page = /*#__PURE__*/Object.freeze({
2100
+ __proto__: null,
2101
+ TranslationKeyFormPage: TranslationKeyFormPage
2102
+ });
2103
+
2104
+ class TranslationKeyListPage {
2105
+ TRANSLATION_KEY_PERMISSIONS = TRANSLATION_KEY_PERMISSIONS;
2106
+ router = inject(Router);
2107
+ languageService = inject(LanguageApiService);
2108
+ translationKeyService = inject(TranslationKeyApiService);
2109
+ translationService = inject(TranslationApiService);
2110
+ confirmationService = inject(ConfirmationService);
2111
+ messageService = inject(MessageService);
2112
+ translateService = inject(TranslateService);
2113
+ destroyRef = inject(DestroyRef);
2114
+ isLoading = signal(false, /* @ts-ignore */
2115
+ ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
2116
+ languages = signal([], /* @ts-ignore */
2117
+ ...(ngDevMode ? [{ debugName: "languages" }] : /* istanbul ignore next */ []));
2118
+ modules = signal([], /* @ts-ignore */
2119
+ ...(ngDevMode ? [{ debugName: "modules" }] : /* istanbul ignore next */ []));
2120
+ translationKeys = signal([], /* @ts-ignore */
2121
+ ...(ngDevMode ? [{ debugName: "translationKeys" }] : /* istanbul ignore next */ []));
2122
+ translationCounts = signal(new Map(), /* @ts-ignore */
2123
+ ...(ngDevMode ? [{ debugName: "translationCounts" }] : /* istanbul ignore next */ []));
2124
+ selectedModule = signal(null, /* @ts-ignore */
2125
+ ...(ngDevMode ? [{ debugName: "selectedModule" }] : /* istanbul ignore next */ []));
2126
+ searchTerm = signal('', /* @ts-ignore */
2127
+ ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
2128
+ moduleOptions = computed(() => [
2129
+ { label: this.translateService.translate('shared.all'), value: null },
2130
+ ...this.modules().map((m) => ({ label: m, value: m })),
2131
+ ], /* @ts-ignore */
2132
+ ...(ngDevMode ? [{ debugName: "moduleOptions" }] : /* istanbul ignore next */ []));
2133
+ filteredKeys = computed(() => {
2134
+ let keys = this.translationKeys();
2135
+ const module = this.selectedModule();
2136
+ const search = this.searchTerm().toLowerCase().trim();
2137
+ if (module) {
2138
+ keys = keys.filter((k) => k.module === module);
2139
+ }
2140
+ if (search) {
2141
+ keys = keys.filter((k) => k.key.toLowerCase().includes(search) ||
2142
+ k.defaultMessage.toLowerCase().includes(search) ||
2143
+ k.module.toLowerCase().includes(search));
2144
+ }
2145
+ return keys;
2146
+ }, /* @ts-ignore */
2147
+ ...(ngDevMode ? [{ debugName: "filteredKeys" }] : /* istanbul ignore next */ []));
2148
+ ngOnInit() {
2149
+ this.loadData();
2150
+ }
2151
+ loadData() {
2152
+ this.isLoading.set(true);
2153
+ forkJoin({
2154
+ languages: from(this.languageService.getAll({
2155
+ pagination: { currentPage: 0, pageSize: 100 },
2156
+ filter: {},
2157
+ sort: { serial: 'ASC', name: 'ASC' },
2158
+ })),
2159
+ modules: this.translationKeyService.getModules(),
2160
+ keys: from(this.translationKeyService.getAll({
2161
+ pagination: { currentPage: 0, pageSize: 1000 },
2162
+ filter: {},
2163
+ sort: { module: 'ASC', key: 'ASC' },
2164
+ })),
2165
+ })
2166
+ .pipe(takeUntilDestroyed(this.destroyRef))
2167
+ .subscribe({
2168
+ next: ({ languages, modules, keys }) => {
2169
+ if (languages.success && languages.data) {
2170
+ this.languages.set(languages.data.filter((l) => l.isActive));
2171
+ }
2172
+ if (modules.success && modules.data) {
2173
+ this.modules.set(modules.data);
2174
+ }
2175
+ if (keys.success && keys.data) {
2176
+ this.translationKeys.set(keys.data);
2177
+ this.loadTranslationCounts();
2178
+ }
2179
+ this.isLoading.set(false);
2180
+ },
2181
+ error: () => {
2182
+ this.isLoading.set(false);
2183
+ },
2184
+ });
2185
+ }
2186
+ loadTranslationCounts() {
2187
+ this.translationService
2188
+ .getCounts()
2189
+ .pipe(takeUntilDestroyed(this.destroyRef))
2190
+ .subscribe({
2191
+ next: (response) => {
2192
+ if (response.success && response.data) {
2193
+ this.translationCounts.set(new Map(Object.entries(response.data)));
2194
+ }
2195
+ },
2196
+ });
2197
+ }
2198
+ getTranslationCount(keyId) {
2199
+ return this.translationCounts().get(keyId) ?? 0;
2200
+ }
2201
+ onModuleChange(value) {
2202
+ this.selectedModule.set(value);
2203
+ }
2204
+ onCreate() {
2205
+ this.router.navigate(['/localization/keys/new']);
2206
+ }
2207
+ onEdit(key) {
2208
+ this.router.navigate(['/localization/keys', key.id]);
2209
+ }
2210
+ onDelete(key) {
2211
+ this.confirmationService.confirm({
2212
+ message: this.translateService.translate('shared.confirm.delete'),
2213
+ header: this.translateService.translate('localization.key.delete.title'),
2214
+ icon: 'exclamation-triangle',
2215
+ acceptButtonStyleClass: 'p-button-danger',
2216
+ accept: () => {
2217
+ from(this.translationKeyService.delete({ id: key.id, type: 'delete' }))
2218
+ .pipe(takeUntilDestroyed(this.destroyRef))
2219
+ .subscribe({
2220
+ next: (response) => {
2221
+ if (response.success) {
2222
+ this.messageService.add({
2223
+ severity: 'success',
2224
+ summary: this.translateService.translate('shared.success'),
2225
+ detail: response.messageKey
2226
+ ? this.translateService.translate(response.messageKey, response.messageVariables)
2227
+ : (response.message ?? ''),
2228
+ });
2229
+ this.loadData();
2230
+ }
2231
+ },
2232
+ });
2233
+ },
2234
+ });
2235
+ }
2236
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationKeyListPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
2237
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: TranslationKeyListPage, isStandalone: true, selector: "lib-translation-key-list-page", ngImport: i0, template: `
2238
+ <div class="card">
2239
+ <div
2240
+ class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4"
2241
+ >
2242
+ <div>
2243
+ <h3 class="text-lg sm:text-xl font-semibold m-0">
2244
+ {{ 'localization.key.title' | translate }}
2245
+ </h3>
2246
+ <p class="text-sm text-muted-color mt-1">
2247
+ {{ 'localization.key.subtitle' | translate }}
2248
+ </p>
2249
+ </div>
2250
+ <div class="flex gap-2">
2251
+ <button
2252
+ fButton
2253
+ *hasPermission="TRANSLATION_KEY_PERMISSIONS.CREATE"
2254
+ (click)="onCreate()"
2255
+ >
2256
+ <ui-icon pButtonIcon name="plus"></ui-icon>
2257
+ <span pButtonLabel>{{ 'localization.key.new' | translate }}</span>
2258
+ </button>
2259
+ </div>
2260
+ </div>
2261
+
2262
+ <div class="flex flex-col md:flex-row gap-4 mb-4">
2263
+ <div class="flex-1">
2264
+ <f-select
2265
+ [options]="moduleOptions()"
2266
+ [ngModel]="selectedModule()"
2267
+ (ngModelChange)="onModuleChange($event)"
2268
+ [ngModelOptions]="{ standalone: true }"
2269
+ optionLabel="label"
2270
+ optionValue="value"
2271
+ [placeholder]="'localization.key.filter.module' | translate"
2272
+ [showClear]="true"
2273
+ class="w-full"
2274
+ />
2275
+ </div>
2276
+ <div class="flex-1">
2277
+ <f-iconfield class="w-full">
2278
+ <f-inputicon><ui-icon name="search" /></f-inputicon>
2279
+ <input
2280
+ fInputText
2281
+ [ngModel]="searchTerm()"
2282
+ (ngModelChange)="searchTerm.set($event)"
2283
+ [ngModelOptions]="{ standalone: true }"
2284
+ [placeholder]="'shared.search' | translate"
2285
+ class="w-full"
2286
+ />
2287
+ </f-iconfield>
2288
+ </div>
2289
+ </div>
2290
+
2291
+ <!-- Table -->
2292
+ <f-table
2293
+ [value]="filteredKeys()"
2294
+ [loading]="isLoading()"
2295
+ [paginator]="true"
2296
+ [rows]="15"
2297
+ [rowsPerPageOptions]="[10, 15, 25, 50]"
2298
+ >
2299
+ <ng-template #header>
2300
+ <tr>
2301
+ <th style="width: 15%">
2302
+ {{ 'localization.key.module' | translate }}
2303
+ </th>
2304
+ <th style="width: 25%">
2305
+ {{ 'localization.key.name' | translate }}
2306
+ </th>
2307
+ <th style="width: 35%">
2308
+ {{ 'localization.key.default.message' | translate }}
2309
+ </th>
2310
+ <th style="width: 10%">
2311
+ {{ 'localization.key.translations' | translate }}
2312
+ </th>
2313
+ <th style="width: 15%">{{ 'shared.actions' | translate }}</th>
2314
+ </tr>
2315
+ </ng-template>
2316
+ <ng-template #body let-key>
2317
+ @let translationCount = getTranslationCount(key.id);
2318
+ @let languageCount = languages().length;
2319
+ <tr>
2320
+ <td>
2321
+ <f-tag [value]="key.module" severity="secondary" />
2322
+ </td>
2323
+ <td>
2324
+ <div class="font-medium">{{ key.key }}</div>
2325
+ @if (key.description) {
2326
+ <small class="text-muted-color">{{ key.description }}</small>
2327
+ }
2328
+ </td>
2329
+ <td>{{ key.defaultMessage }}</td>
2330
+ <td class="text-center">
2331
+ <span
2332
+ class="text-sm"
2333
+ [class.text-green-500]="translationCount === languageCount"
2334
+ [class.text-orange-500]="
2335
+ translationCount < languageCount && translationCount > 0
2336
+ "
2337
+ [class.text-muted-color]="translationCount === 0"
2338
+ >
2339
+ {{ translationCount }}/{{ languageCount }}
2340
+ </span>
2341
+ </td>
2342
+ <td>
2343
+ <div class="flex gap-1">
2344
+ <button
2345
+ fButton
2346
+ *hasPermission="TRANSLATION_KEY_PERMISSIONS.UPDATE"
2347
+ [text]="true"
2348
+ severity="secondary"
2349
+ size="small"
2350
+ [fTooltip]="'shared.edit' | translate"
2351
+ (click)="onEdit(key)"
2352
+ >
2353
+ <ui-icon pButtonIcon name="pencil"></ui-icon>
2354
+ </button>
2355
+ <button
2356
+ fButton
2357
+ *hasPermission="TRANSLATION_KEY_PERMISSIONS.DELETE"
2358
+ [text]="true"
2359
+ severity="danger"
2360
+ size="small"
2361
+ [fTooltip]="'shared.delete' | translate"
2362
+ (click)="onDelete(key)"
2363
+ >
2364
+ <ui-icon pButtonIcon name="trash"></ui-icon>
2365
+ </button>
2366
+ </div>
2367
+ </td>
2368
+ </tr>
2369
+ </ng-template>
2370
+ <ng-template #emptymessage>
2371
+ <tr>
2372
+ <td colspan="5" class="text-center py-4 text-muted-color">
2373
+ {{ 'localization.key.empty' | translate }}
2374
+ </td>
2375
+ </tr>
2376
+ </ng-template>
2377
+ </f-table>
2378
+ </div>
2379
+
2380
+ <f-confirmdialog />
2381
+ <f-toast />
2382
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i2.DefaultValueAccessor, 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]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.ConfirmDialog, selector: "f-confirmdialog", inputs: ["key", "icon", "acceptLabel", "rejectLabel", "acceptIcon", "rejectIcon", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "blockScroll", "defaultFocus", "styleClass", "style"] }, { kind: "component", type: i1.ToastComponent, selector: "f-toast", inputs: ["key", "position"], outputs: ["onClose"] }, { kind: "directive", type: i1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1.TagComponent, selector: "f-tag", inputs: ["value", "severity", "icon", "rounded", "color", "styleClass", "style"] }, { kind: "component", type: i1.IconFieldComponent, selector: "f-iconfield", inputs: ["iconPosition", "styleClass"] }, { kind: "component", type: i1.InputIconComponent, selector: "f-inputicon", inputs: ["icon", "position", "styleClass", "style"] }, { kind: "directive", type: i1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "component", type: i1.TableComponent, selector: "f-table", inputs: ["value", "lazy", "loading", "rows", "totalRecords", "rowHover", "dataKey", "scrollable", "scrollHeight", "paginator", "first", "rowsPerPageOptions", "showCurrentPageReport", "currentPageReportTemplate", "tableStyle", "styleClass", "globalFilterFields", "filterDelay", "responsiveLayout", "selectionMode", "sortMode", "rowExpandMode", "resizableColumns", "rowStyleClass", "virtualScroll", "virtualScrollItemSize", "sortField", "sortOrder", "multiSortMeta", "filters", "selection", "expandedRowKeys", "contextMenuSelection"], outputs: ["sortFieldChange", "sortOrderChange", "multiSortMetaChange", "filtersChange", "selectionChange", "expandedRowKeysChange", "contextMenuSelectionChange", "onLazyLoad", "onSort", "onFilter", "onRowSelect", "onRowUnselect", "onSelectAllChange", "onRowExpand", "onRowCollapse", "onColResize", "onRowContextMenu"] }, { kind: "directive", type: i1.TooltipDirective, selector: "[fTooltip]", inputs: ["fTooltip", "tooltipPosition", "positionStyle", "tooltipStyleClass", "disabled", "tooltipEvent", "showDelay", "hideDelay", "life", "autoHide", "positionTop", "positionLeft", "escape"] }, { kind: "component", type: i1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2383
+ }
2384
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: TranslationKeyListPage, decorators: [{
2385
+ type: Component,
2386
+ args: [{
2387
+ selector: 'lib-translation-key-list-page',
2388
+ imports: [
2389
+ AngularModule,
2390
+ UiModule,
2391
+ FormsModule,
2392
+ HasPermissionDirective,
2393
+ TranslatePipe,
2394
+ ],
2395
+ template: `
2396
+ <div class="card">
2397
+ <div
2398
+ class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4"
2399
+ >
2400
+ <div>
2401
+ <h3 class="text-lg sm:text-xl font-semibold m-0">
2402
+ {{ 'localization.key.title' | translate }}
2403
+ </h3>
2404
+ <p class="text-sm text-muted-color mt-1">
2405
+ {{ 'localization.key.subtitle' | translate }}
2406
+ </p>
2407
+ </div>
2408
+ <div class="flex gap-2">
2409
+ <button
2410
+ fButton
2411
+ *hasPermission="TRANSLATION_KEY_PERMISSIONS.CREATE"
2412
+ (click)="onCreate()"
2413
+ >
2414
+ <ui-icon pButtonIcon name="plus"></ui-icon>
2415
+ <span pButtonLabel>{{ 'localization.key.new' | translate }}</span>
2416
+ </button>
2417
+ </div>
2418
+ </div>
2419
+
2420
+ <div class="flex flex-col md:flex-row gap-4 mb-4">
2421
+ <div class="flex-1">
2422
+ <f-select
2423
+ [options]="moduleOptions()"
2424
+ [ngModel]="selectedModule()"
2425
+ (ngModelChange)="onModuleChange($event)"
2426
+ [ngModelOptions]="{ standalone: true }"
2427
+ optionLabel="label"
2428
+ optionValue="value"
2429
+ [placeholder]="'localization.key.filter.module' | translate"
2430
+ [showClear]="true"
2431
+ class="w-full"
2432
+ />
2433
+ </div>
2434
+ <div class="flex-1">
2435
+ <f-iconfield class="w-full">
2436
+ <f-inputicon><ui-icon name="search" /></f-inputicon>
2437
+ <input
2438
+ fInputText
2439
+ [ngModel]="searchTerm()"
2440
+ (ngModelChange)="searchTerm.set($event)"
2441
+ [ngModelOptions]="{ standalone: true }"
2442
+ [placeholder]="'shared.search' | translate"
2443
+ class="w-full"
2444
+ />
2445
+ </f-iconfield>
2446
+ </div>
2447
+ </div>
2448
+
2449
+ <!-- Table -->
2450
+ <f-table
2451
+ [value]="filteredKeys()"
2452
+ [loading]="isLoading()"
2453
+ [paginator]="true"
2454
+ [rows]="15"
2455
+ [rowsPerPageOptions]="[10, 15, 25, 50]"
2456
+ >
2457
+ <ng-template #header>
2458
+ <tr>
2459
+ <th style="width: 15%">
2460
+ {{ 'localization.key.module' | translate }}
2461
+ </th>
2462
+ <th style="width: 25%">
2463
+ {{ 'localization.key.name' | translate }}
2464
+ </th>
2465
+ <th style="width: 35%">
2466
+ {{ 'localization.key.default.message' | translate }}
2467
+ </th>
2468
+ <th style="width: 10%">
2469
+ {{ 'localization.key.translations' | translate }}
2470
+ </th>
2471
+ <th style="width: 15%">{{ 'shared.actions' | translate }}</th>
2472
+ </tr>
2473
+ </ng-template>
2474
+ <ng-template #body let-key>
2475
+ @let translationCount = getTranslationCount(key.id);
2476
+ @let languageCount = languages().length;
2477
+ <tr>
2478
+ <td>
2479
+ <f-tag [value]="key.module" severity="secondary" />
2480
+ </td>
2481
+ <td>
2482
+ <div class="font-medium">{{ key.key }}</div>
2483
+ @if (key.description) {
2484
+ <small class="text-muted-color">{{ key.description }}</small>
2485
+ }
2486
+ </td>
2487
+ <td>{{ key.defaultMessage }}</td>
2488
+ <td class="text-center">
2489
+ <span
2490
+ class="text-sm"
2491
+ [class.text-green-500]="translationCount === languageCount"
2492
+ [class.text-orange-500]="
2493
+ translationCount < languageCount && translationCount > 0
2494
+ "
2495
+ [class.text-muted-color]="translationCount === 0"
2496
+ >
2497
+ {{ translationCount }}/{{ languageCount }}
2498
+ </span>
2499
+ </td>
2500
+ <td>
2501
+ <div class="flex gap-1">
2502
+ <button
2503
+ fButton
2504
+ *hasPermission="TRANSLATION_KEY_PERMISSIONS.UPDATE"
2505
+ [text]="true"
2506
+ severity="secondary"
2507
+ size="small"
2508
+ [fTooltip]="'shared.edit' | translate"
2509
+ (click)="onEdit(key)"
2510
+ >
2511
+ <ui-icon pButtonIcon name="pencil"></ui-icon>
2512
+ </button>
2513
+ <button
2514
+ fButton
2515
+ *hasPermission="TRANSLATION_KEY_PERMISSIONS.DELETE"
2516
+ [text]="true"
2517
+ severity="danger"
2518
+ size="small"
2519
+ [fTooltip]="'shared.delete' | translate"
2520
+ (click)="onDelete(key)"
2521
+ >
2522
+ <ui-icon pButtonIcon name="trash"></ui-icon>
2523
+ </button>
2524
+ </div>
2525
+ </td>
2526
+ </tr>
2527
+ </ng-template>
2528
+ <ng-template #emptymessage>
2529
+ <tr>
2530
+ <td colspan="5" class="text-center py-4 text-muted-color">
2531
+ {{ 'localization.key.empty' | translate }}
2532
+ </td>
2533
+ </tr>
2534
+ </ng-template>
2535
+ </f-table>
2536
+ </div>
2537
+
2538
+ <f-confirmdialog />
2539
+ <f-toast />
2540
+ `,
2541
+ }]
2542
+ }] });
2543
+
2544
+ var translationKeyList_page = /*#__PURE__*/Object.freeze({
2545
+ __proto__: null,
2546
+ TranslationKeyListPage: TranslationKeyListPage
2547
+ });
2548
+
2549
+ // Interfaces
2550
+
2551
+ /**
2552
+ * Generated bundle index. Do not edit.
2553
+ */
2554
+
2555
+ export { LOCALIZATION_CONFIG, LOCALIZATION_MESSAGES, LOCALIZATION_ROUTES, LanguageApiService, LanguageFormPage, LanguageListPage, LanguageSelectorComponent, LocalizationApiService, LocalizationContainerPage, LocalizationInitializerService, LocalizationStateService, STORAGE_KEYS, TranslationApiService, TranslationKeyApiService, TranslationKeyFormPage, TranslationKeyListPage, TranslationLoaderService, getLocalizationConfig, provideLocalization };
2556
+ //# sourceMappingURL=flusys-ng-localization.mjs.map