turbogui-angular 20.5.0 → 20.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -711,6 +711,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
711
711
  args: [MAT_DIALOG_DATA]
712
712
  }] }] });
713
713
 
714
+ /**
715
+ * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
716
+ *
717
+ * Website : -> http://www.turbogui.org
718
+ * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
719
+ * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
720
+ * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
721
+ */
722
+ /**
723
+ * A dialog component with a single option button, to be used with dialog service
724
+ */
725
+ class DialogSingleOptionComponent extends DialogBaseComponent {
726
+ static { this.DIALOG_CLASS_NAME = 'DialogSingleOptionComponent'; }
727
+ constructor(elementRef, dialogRef, data) {
728
+ super(elementRef, dialogRef);
729
+ this.elementRef = elementRef;
730
+ this.dialogRef = dialogRef;
731
+ this.data = data;
732
+ if (data.texts.length < 1) {
733
+ throw new Error('DialogSingleOptionComponent expects 2 texts: The title and optionally a description');
734
+ }
735
+ if (data.options.length !== 1) {
736
+ throw new Error('DialogSingleOptionComponent expects only one option');
737
+ }
738
+ }
739
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleOptionComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
740
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogSingleOptionComponent, isStandalone: true, selector: "tg-dialog-single-option", providers: [], usesInheritance: true, ngImport: i0, template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<p *ngIf=\"data.texts.length > 1\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n (click)=\"closeDialog(0)\">\r\n \r\n {{data.options[0]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-bottom:25px}p{margin-bottom:52px}button{float:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }] }); }
741
+ }
742
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleOptionComponent, decorators: [{
743
+ type: Component,
744
+ args: [{ selector: 'tg-dialog-single-option', imports: [CommonModule, MatButtonModule], providers: [], template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<p *ngIf=\"data.texts.length > 1\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n (click)=\"closeDialog(0)\">\r\n \r\n {{data.options[0]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-bottom:25px}p{margin-bottom:52px}button{float:right}\n"] }]
745
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: undefined, decorators: [{
746
+ type: Inject,
747
+ args: [MAT_DIALOG_DATA]
748
+ }] }] });
749
+
714
750
  /**
715
751
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
716
752
  *
@@ -1062,8 +1098,8 @@ class DialogService extends SingletoneStrictClass {
1062
1098
  id: properties.id ?? undefined,
1063
1099
  data: properties.url,
1064
1100
  texts: properties.title ? [properties.title] : undefined,
1065
- width: properties.width ?? "98vw",
1066
- maxWidth: properties.maxWidth ?? "1000px",
1101
+ width: properties.width ?? "85vw",
1102
+ maxWidth: properties.maxWidth ?? "1200px",
1067
1103
  height: properties.height ?? "98vh",
1068
1104
  maxHeight: properties.maxHeight ?? "3000px",
1069
1105
  modal: properties.modal ?? false
@@ -1095,8 +1131,8 @@ class DialogService extends SingletoneStrictClass {
1095
1131
  id: properties.id ?? undefined,
1096
1132
  data: { blob: properties.blob, mimeType: 'application/pdf' },
1097
1133
  texts: properties.title ? [properties.title] : undefined,
1098
- width: properties.width ?? "98vw",
1099
- maxWidth: properties.maxWidth ?? "1000px",
1134
+ width: properties.width ?? "85vw",
1135
+ maxWidth: properties.maxWidth ?? "1200px",
1100
1136
  height: properties.height ?? "98vh",
1101
1137
  maxHeight: properties.maxHeight ?? "3000px",
1102
1138
  modal: properties.modal ?? false
@@ -1150,7 +1186,8 @@ class DialogService extends SingletoneStrictClass {
1150
1186
  * - height: see addDialog() docs
1151
1187
  * - maxHeight: see addDialog() docs
1152
1188
  * - modal: see addDialog() docs
1153
- * - dialogErrorComponentClass: A custom component class to use instead of the default DialogErrorComponent. This custom component must extend DialogErrorComponent
1189
+ * - dialogClass: A custom component class to use instead of the default DialogErrorComponent.
1190
+ * This custom class must extend DialogErrorComponent
1154
1191
  *
1155
1192
  * @returns A Promise that resolves once the user selects the button with the option caption
1156
1193
  */
@@ -1160,7 +1197,7 @@ class DialogService extends SingletoneStrictClass {
1160
1197
  if (properties.description && properties.description !== undefined) {
1161
1198
  texts.push(properties.description);
1162
1199
  }
1163
- await this.addDialog(properties.dialogErrorComponentClass ?? DialogErrorComponent, {
1200
+ await this.addDialog(properties.dialogClass ?? DialogErrorComponent, {
1164
1201
  id: properties.id ?? undefined,
1165
1202
  width: properties.width ?? "70%",
1166
1203
  maxWidth: properties.maxWidth ?? "500px",
@@ -1173,6 +1210,30 @@ class DialogService extends SingletoneStrictClass {
1173
1210
  }
1174
1211
  return null;
1175
1212
  }
1213
+ /**
1214
+ * Show a dialog with a message and a single option button to close it.
1215
+ *
1216
+ * This method is a shortcut for addDialog() method using DialogSingleOptionComponent as the dialog component class
1217
+ *
1218
+ * @param properties An object containing the different visual and textual options that this dialog allows:
1219
+ * - title (mandatory): The dialog title
1220
+ * - option (mandatory): The text to place on the single option button
1221
+ * - description: An optional description text to show below the title
1222
+ * - id: see addDialog() docs
1223
+ * - width: see addDialog() docs
1224
+ * - maxWidth: see addDialog() docs
1225
+ * - height: see addDialog() docs
1226
+ * - maxHeight: see addDialog() docs
1227
+ * - modal: see addDialog() docs
1228
+ * - dialogClass: A custom component class to use instead of the default DialogSingleOptionComponent.
1229
+ * This custom class must extend DialogSingleOptionComponent
1230
+ *
1231
+ * @returns A Promise that resolves once the user selects the button with the option caption
1232
+ */
1233
+ async addMessageDialog(properties) {
1234
+ properties.dialogClass ??= DialogSingleOptionComponent;
1235
+ return this.addErrorDialog(properties);
1236
+ }
1176
1237
  /**
1177
1238
  * Force the removal of all the dialogs that are currently visible.
1178
1239
  *
@@ -1927,7 +1988,7 @@ class RouterBaseService {
1927
1988
  * { path: '', component: HomePageComponent,
1928
1989
  * data: { titleKey: 'HOME', titleBundle: 'turbodepot/user-interface'} },
1929
1990
  *
1930
- * @param localesService An instance of the LocalesService to be used for translations.
1991
+ * @param localesService An instance of the already initialized LocalesService to be used for translations.
1931
1992
  * @param prefix A text to be added before the computed title.
1932
1993
  * @param sufix A text to be added after the computed title.
1933
1994
  */
@@ -2005,42 +2066,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
2005
2066
  */
2006
2067
  /**
2007
2068
  * Fully featured translation manager to be used with any application that requires text internationalization.
2008
- * It is defined as an abstract class so it must be extended in our application. This way we can
2009
- * write custom methods to extend the functionality of this class if needed.
2069
+ *
2070
+ * It is designed to load translation bundles from several sources, and then provide methods to translate
2071
+ * any key on any bundle to the desired locale.
2072
+ *
2073
+ * - Provides methods to format the translated text to several case formats like start case, all upper case,
2074
+ * all lower case, first upper rest lower, etc...
2075
+ *
2076
+ * - Provides a way to define wildcards on the translated texts that can be replaced at runtime with custom values.
2077
+ *
2078
+ * The class is designed to be as fast as possible when requesting translations, so it includes an internal memory cache
2079
+ * that will store the results of previous translations and return them directly when the same key is requested again.
2080
+ *
2081
+ * It also includes several configuration options to define how missing keys are handled, what locales are available,
2082
+ * what locales are loaded into memory, what locales are used for translation priority, etc...
2083
+ *
2084
+ * The class does not include any method to load translation bundles from external sources like files or APIs. This is
2085
+ * done on purpose to keep the class as generic as possible and avoid any dependency with external libraries or frameworks.
2086
+ *
2087
+ * It is recommended to use this class as a singleton service in your application, so you can easily access it from
2088
+ * any component or service that requires translation functionalities.
2010
2089
  */
2011
- class LocalesBaseService {
2090
+ class TranslationManager {
2012
2091
  constructor() {
2013
2092
  /**
2014
- * if the class has been correctly initialized and translations have been correctly loaded
2093
+ * This flag defines if the class will allow loading locales that are not defined on the availableLocales list.
2094
+ *
2095
+ * By default this is set to false, so only locales that are defined on the availableLocales list can be loaded.
2096
+ *
2097
+ * If set to true, any locale can be loaded, even if it is not defined on the availableLocales list. In this case,
2098
+ * the locale will be added at the end of the availableLocales list when loaded.
2099
+ *
2100
+ * It is recommended to keep this set to false and always define the availableLocales list before loading any locale,
2101
+ * so we can avoid loading locales that are not supported by our application.
2102
+ *
2103
+ * Notice that this flag does not affect the translation priority list, which must always contain locales that are
2104
+ * defined on the availableLocales list.
2105
+ *
2106
+ * @see setAvailableLocales()
2107
+ * @see setTranslationPriority()
2015
2108
  */
2016
- this._isInitialized = false;
2109
+ this.isLoadingNonAvaliableLocalesAllowed = false;
2017
2110
  /**
2018
- * @see getLocales()
2111
+ * @see setTranslationPriority()
2019
2112
  */
2020
- this._locales = [];
2113
+ this._translationPriority = [];
2021
2114
  /**
2022
- * @see getLanguages()
2115
+ * @see getLoadedLocales()
2023
2116
  */
2024
- this._languages = [];
2117
+ this._loadedLocales = [];
2025
2118
  /**
2026
- * Stores all the loaded localization data by library name, bundle name, key and locales
2119
+ * Stores all the raw localization data that is loaded into memory on this service for all locales.
2120
+ * It is organized like: { library_name: { bundle_name: { locale_code: { key1: "translation1", key2: "translation2" } } } ... }
2027
2121
  */
2028
- this._loadedTranslations = {};
2029
- /**
2030
- * Stores a memory cache to improve performance when outputing translations
2031
- */
2032
- this._keyValuesCache = {};
2122
+ this._loadedRawTranslationData = {};
2033
2123
  /**
2034
2124
  * @see setWildCardsFormat()
2035
2125
  */
2036
2126
  this._wildCardsFormat = '{N}';
2037
2127
  /**
2038
- * @see setMissingKeyFormat()
2128
+ * @see setMissingKeyBehaviour()
2039
2129
  */
2040
- this._missingKeyFormat = '$exception';
2130
+ this._missingKeyBehaviour = '$exception';
2131
+ /**
2132
+ * Stores a memory cache to improve performance when outputing translations
2133
+ */
2134
+ this._keyValuesCache = {};
2041
2135
  /**
2042
2136
  * Stores a hash value that is used to improve the performance for translation t() methods.
2043
- * This is computed based on _wildCardsFormat plus _missingKeyFormat plus the current primary locale
2137
+ *
2138
+ * This is computed based on _wildCardsFormat + _missingKeyBehaviour + _translationPriority
2139
+ *
2044
2140
  * Methods that change these values will recalculate the hash string, so when calling translation methods, the
2045
2141
  * performance will be as fast as possible.
2046
2142
  */
@@ -2058,7 +2154,7 @@ class LocalesBaseService {
2058
2154
  *
2059
2155
  * We usually set this before initializing the class translation data
2060
2156
  *
2061
- * Notice that N is mandayory on the wildcards format and the first index value is 0.
2157
+ * Notice that N is mandatory on the wildcards format and the first index value is 0.
2062
2158
  *
2063
2159
  * @param value The wildcards format we want to set
2064
2160
  *
@@ -2068,7 +2164,7 @@ class LocalesBaseService {
2068
2164
  if (!value.includes('N')) {
2069
2165
  throw new Error("N is mandatory to replace wildcards");
2070
2166
  }
2071
- this._cacheHashBaseString = value + this._missingKeyFormat + ((this._locales.length > 0) ? this._locales[0] : '');
2167
+ this._cacheHashBaseString = value + this._missingKeyBehaviour + this._translationPriority.join('');
2072
2168
  this._wildCardsFormat = value;
2073
2169
  return value;
2074
2170
  }
@@ -2076,159 +2172,232 @@ class LocalesBaseService {
2076
2172
  * Defines the behaviour for t(), tStartCase(), etc... methods when a key is not found on
2077
2173
  * a bundle or the bundle does not exist
2078
2174
  *
2079
- * If missingKeyFormat is an empty string, all missing keys will return an empty value (not recommended)
2175
+ * If missingKeyBehaviour is an empty string, all missing keys will return an empty value (not recommended)
2080
2176
  *
2081
- * If missingKeyFormat contains a string, that string will be always returned for missing keys
2177
+ * If missingKeyBehaviour contains a string, that string will be always returned for missing keys
2082
2178
  *
2083
- * If missingKeyFormat contains a string with one of the following predefined wildcards:<br>
2084
- * - $key will be replaced with key name. Example: get("NAME") will output [NAME] if key is not found and missingKeyFormat = '[$key]'<br>
2179
+ * If missingKeyBehaviour contains a string with one of the following predefined wildcards:<br>
2180
+ * - $key will be replaced with key name. Example: get("NAME") will output [NAME] if key is not found and missingKeyBehaviour = '[$key]'<br>
2085
2181
  * - $exception (default value) will throw an exception with the problem cause description.
2086
2182
  *
2087
2183
  * @param value The missing key format we want to set
2088
2184
  *
2089
2185
  * @returns The value that's been set
2090
2186
  */
2091
- setMissingKeyFormat(value) {
2092
- this._cacheHashBaseString = this._wildCardsFormat + value + ((this._locales.length > 0) ? this._locales[0] : '');
2093
- this._missingKeyFormat = value;
2187
+ setMissingKeyBehaviour(value) {
2188
+ this._cacheHashBaseString = this._wildCardsFormat + value + this._translationPriority.join('');
2189
+ this._missingKeyBehaviour = value;
2094
2190
  return value;
2095
2191
  }
2096
2192
  /**
2097
- * @see setMissingKeyFormat()
2193
+ * @see setMissingKeyBehaviour()
2098
2194
  */
2099
- getMissingKeyFormat() {
2100
- return this._missingKeyFormat;
2195
+ getMissingKeyBehaviour() {
2196
+ return this._missingKeyBehaviour;
2101
2197
  }
2102
2198
  /**
2103
2199
  * Adds translations to the class by loading and parsing bundles from the provided JSON object.
2104
- * After the method finishes, the class will contain all the translation data and will be ready to translate
2200
+ * After the method finishes, the class will contain in memory all the translation data and will be ready to translate
2105
2201
  * any provided key to any of the specified locales.
2106
2202
  *
2203
+ * This method can be called multiple times. New translation data will be merged with any existing data.
2204
+ * If a translation key already exists for a specific library, bundle, and locale, its value will be overwritten by the new data.
2205
+ *
2107
2206
  * @param locales An array of locale codes (e.g., ['en_US', 'es_ES', 'fr_FR']) to load from the json data into this class.
2108
- * The order of this array will determine the priority when looking for translations.
2207
+ * The order of this array is not important for translation priority.
2109
2208
  *
2110
2209
  * @param json A JSON object containing the translation data. The structure must be as follows:
2111
2210
  * { library_name: { bundle_name: { locale_code: { key1: "translation1", key2: "translation2" } } } ... }
2112
2211
  *
2113
- * @return True if the translations get correctly loaded. Any unsuccessful initialization will throw an exception
2212
+ * @throws Error If any of the provided locales is not correctly formatted or not defined on the availableLocales
2213
+ * list or json data does not follow the expected structure.
2214
+ *
2215
+ * @return void
2114
2216
  */
2115
2217
  loadLocalesFromJson(locales, json) {
2116
- this._isInitialized = false;
2218
+ // Cache must be cleared when loading new translations to ensure all keys are re-evaluated.
2219
+ this._keyValuesCache = {};
2117
2220
  // Validate received locales are correct
2118
2221
  for (const locale of locales) {
2119
2222
  this._validateLocaleString(locale);
2223
+ if (!this.isLoadingNonAvaliableLocalesAllowed &&
2224
+ (!this._availableLocales || !this._availableLocales.includes(locale))) {
2225
+ throw new Error(locale + ' not defined as available for translation');
2226
+ }
2120
2227
  }
2121
2228
  // Validate the translations object follows the right structure
2122
2229
  let isTranslationsValid = false;
2230
+ if (json && typeof json === 'object' && Object.keys(json).length > 0) {
2231
+ for (const library in json) {
2232
+ for (const bundle in json[library]) {
2233
+ for (const locale in json[library][bundle]) {
2234
+ this._validateLocaleString(locale);
2235
+ isTranslationsValid = true;
2236
+ }
2237
+ }
2238
+ }
2239
+ }
2240
+ if (!isTranslationsValid) {
2241
+ throw new Error('translations must be a non empty object with the structure: { library: { bundle: { xx_XX: { key: translation } } } }');
2242
+ }
2243
+ // Deep merge the new translation data with the existing data.
2123
2244
  for (const library in json) {
2245
+ if (!this._loadedRawTranslationData[library]) {
2246
+ this._loadedRawTranslationData[library] = {};
2247
+ }
2124
2248
  for (const bundle in json[library]) {
2249
+ if (!this._loadedRawTranslationData[library][bundle]) {
2250
+ this._loadedRawTranslationData[library][bundle] = {};
2251
+ }
2125
2252
  for (const locale in json[library][bundle]) {
2126
- this._validateLocaleString(locale);
2127
- isTranslationsValid = true;
2253
+ if (!this._loadedRawTranslationData[library][bundle][locale]) {
2254
+ this._loadedRawTranslationData[library][bundle][locale] = {};
2255
+ }
2256
+ // Merge the new keys for the locale, overwriting any existing ones.
2257
+ Object.assign(this._loadedRawTranslationData[library][bundle][locale], json[library][bundle][locale]);
2128
2258
  }
2129
2259
  }
2130
2260
  }
2131
- if (!isTranslationsValid) {
2132
- throw new Error('translations must be a non empty object with the structure: { library: { bundle: { xx_XX: { key: translation } } } }');
2261
+ // Update the list of loaded locales, ensuring no duplicates.
2262
+ this._loadedLocales = Array.from(new Set([...this._loadedLocales, ...locales]));
2263
+ // Add loaded locales to the end of the availableLocales list if they are not already there
2264
+ for (const locale of locales) {
2265
+ if (!this._availableLocales.includes(locale)) {
2266
+ this._availableLocales.push(locale);
2267
+ }
2133
2268
  }
2134
- this._loadedTranslations = json;
2135
- this._locales = locales;
2136
- this._languages = locales.map((l) => l.substring(0, 2));
2137
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
2138
- this._isInitialized = true;
2139
- return true;
2140
2269
  }
2141
2270
  /**
2142
- * Initializes the translation system by loading and parsing bundle files from the specified translations path.
2143
- * After the promise finishes, the class will contain all the translation data and will be ready to translate any
2144
- * provided key.
2271
+ * Checks if the specified locale is currently loaded into memory on this service.
2145
2272
  *
2146
- * @param locales An array of locale codes (['en_US', 'es_ES', 'fr_FR', ...]) to load from the url response.
2147
- * The order of this array will determine the translation priority
2148
- * @param url - Url where the translations are found. The response must be a Json with the expected structure:
2149
- * { library_name: { bundle_name: { locale_code: { key1: "translation1", key2: "translation2" } } } ... }
2273
+ * @param locale A locale to check. For example 'en_US'
2150
2274
  *
2151
- * @return A promise that will resolve if the translations get correctly loaded, or reject with an error if load fails
2275
+ * @return True if the locale is currently loaded on the class, false if not.
2152
2276
  */
2153
- loadLocalesFromUrl(locales, url) {
2154
- this._isInitialized = false;
2155
- return new Promise((resolve, reject) => {
2156
- fetch(url).then(response => {
2157
- if (!response.ok) {
2158
- throw new Error(`HTTP error! status: ${response.status}`);
2159
- }
2160
- return response.json();
2161
- }).then(data => {
2162
- this.loadLocalesFromJson(locales, data);
2163
- resolve(undefined);
2164
- }).catch(error => {
2165
- reject(new Error(`ERROR LOADING LOCALES FROM: ${url}\n` + error));
2166
- });
2167
- });
2277
+ isLocaleLoaded(locale) {
2278
+ this._validateLocaleString(locale);
2279
+ return this._loadedLocales.includes(locale);
2168
2280
  }
2169
2281
  /**
2170
- * Check if the class has been correctly initialized and translations have been correctly loaded
2282
+ * Checks if the specified 2 digit language is currently loaded for the currently defined bundles and paths.
2283
+ *
2284
+ * @param language A language to check. For example 'en'
2285
+ *
2286
+ * @return True if the language is currently loaded on the class, false if not.
2171
2287
  */
2172
- isInitialized() {
2173
- return this._isInitialized;
2288
+ isLanguageLoaded(language) {
2289
+ this._validateLanguageString(language);
2290
+ return this.getLoadedLanguages().includes(language);
2174
2291
  }
2175
2292
  /**
2176
- * Aux method to verify that this class is correctly initialized with translation data
2293
+ * @see setAvailableLocales()
2177
2294
  */
2178
- _validateInitialized() {
2179
- if (!this._isInitialized) {
2180
- throw new Error('Translation service not initialized');
2181
- }
2295
+ getAvailableLocales() {
2296
+ return this._availableLocales;
2182
2297
  }
2183
2298
  /**
2184
- * Checks if the specified locale is currently loaded for the currently defined bundles and paths.
2299
+ * Define the list of locales that are allowed to be loaded by this class (sorted by preference!).
2300
+ * Each string must be formatted as a standard locale code with language and country joined by an
2301
+ * underscore, like: en_US, fr_FR. The order of this list does matter, so it is better to sort by priority.
2185
2302
  *
2186
- * @param locale A locale to check. For example 'en_US'
2303
+ * IMPORTANT: This method does not load any locale by itself, it just defines what locales are available for translation.
2304
+ * You also need to load the translation data for the locales to translate texts.
2187
2305
  *
2188
- * @return True if the locale is currently loaded on the class, false if not.
2306
+ * Before performing any translation, available locales and translation priority must be set, and also translation
2307
+ * data must be loaded for all the locales that are defined on the translation priority list.
2308
+ *
2309
+ * @param locales A list of locales to be set as available for loading
2189
2310
  */
2190
- isLocaleLoaded(locale) {
2191
- this._validateLocaleString(locale);
2192
- return this._locales.includes(locale);
2311
+ setAvailableLocales(locales) {
2312
+ for (const locale of locales) {
2313
+ this._validateLocaleString(locale);
2314
+ }
2315
+ this._availableLocales = locales;
2193
2316
  }
2194
2317
  /**
2195
- * Aux method to validate that a locale string is correctly formatted
2318
+ * A list containing the locales that have been loaded into memory and ready to be used for translation.
2319
+ * Each string is formatted as a standard locale code with language and country joined by an underscore, like: en_US, fr_FR
2196
2320
  *
2197
- * @param string $locale A locale string
2321
+ * The order on this list does not matter for translation priority, as that is defined by the translationPriority list.
2322
+ *
2323
+ * @see getLoadedLanguages()
2198
2324
  */
2199
- _validateLocaleString(locale) {
2200
- if (!/^[a-z]{2}_[A-Z]{2}$/.test(locale)) {
2201
- throw new Error('locale must be a valid xx_XX value');
2202
- }
2325
+ getLoadedLocales() {
2326
+ return this._loadedLocales;
2203
2327
  }
2204
2328
  /**
2205
- * Checks if the specified 2 digit language is currently loaded for the currently defined bundles and paths.
2329
+ * A list of strings containing the languages have been loaded into memory and ready to be used for translation.
2330
+ * Each string is formatted as a 2 digit language code, like: en, fr
2206
2331
  *
2207
- * @param language A language to check. For example 'en'
2332
+ * This list is the same as the locales() one, but containing only the language part of each locale (the first two digits)
2208
2333
  *
2209
- * @return True if the language is currently loaded on the class, false if not.
2334
+ * @see getLoadedLocales()
2210
2335
  */
2211
- isLanguageLoaded(language) {
2212
- this._validateLanguageString(language);
2213
- return this._languages.includes(language);
2336
+ getLoadedLanguages() {
2337
+ return this._loadedLocales.map((l) => l.substring(0, 2));
2214
2338
  }
2215
2339
  /**
2216
- * Aux method to validate that a language string is correctly formatted
2340
+ * Define the list of locales that will be used for translation priority.
2341
+ * The first locale on the list will be the primary to be used when trying to get a translation, rest will be used
2342
+ * as fallback in the same order as they are defined on this list.
2217
2343
  *
2218
- * @param language A 2 digit language string
2344
+ * - The list must contain at least one locale and no duplicate elements.
2345
+ * - All locales provided here must exist on the availableLocales list, otherwise an exception will happen.
2346
+ * - When requesting a translation all the locales on this list should be loaded into memory or an exception may happen.
2347
+ * - IMPORTANT: Translation behaviour depends heavily on the missingKeyBehaviour property value.
2348
+ *
2349
+ * @example: After loading the following list of locales ['en_US', 'es_ES', 'fr_FR'] if we call t('HELLO', 'lib1/greetings')
2350
+ * the localization manager will try to locate the en_US value for the HELLO tag on the greetings bundle for the library lib1.
2351
+ * If the tag is not found for the specified locale and bundle, the same search will be performed for the es_ES locale, and so, till a
2352
+ * value is found or no more locales are defined.
2353
+ *
2354
+ * @param localesOrLanguages:string A list of locales (xx_XX) or languages (xx) to be used for translation priority.
2355
+ *
2356
+ * @returns The list of locales that has been set for translation priority
2219
2357
  */
2220
- _validateLanguageString(language) {
2221
- if (!/^[a-z]{2}$/.test(language)) {
2222
- throw new Error('language must be a valid 2 digit value');
2358
+ setTranslationPriority(localesOrLanguages) {
2359
+ // Validate received list is correct
2360
+ if (!Array.isArray(localesOrLanguages) ||
2361
+ (new Set(localesOrLanguages).size !== localesOrLanguages.length) ||
2362
+ localesOrLanguages.length === 0) {
2363
+ throw new Error('locales must be non empty string array with no duplicate elements');
2223
2364
  }
2365
+ const resolvedLocales = [];
2366
+ // Validate all requested locales are available for translation
2367
+ for (const item of localesOrLanguages) {
2368
+ let resolvedLocale = '';
2369
+ if (/^[a-z]{2}$/.test(item)) {
2370
+ resolvedLocale = this._availableLocales.find(l => l.startsWith(item + '_')) ?? '';
2371
+ if (resolvedLocale === '') {
2372
+ throw new Error(`Language ${item} could not be resolved to any available locale.`);
2373
+ }
2374
+ }
2375
+ else {
2376
+ this._validateLocaleString(item);
2377
+ resolvedLocale = item;
2378
+ }
2379
+ if (!this._availableLocales.includes(resolvedLocale)) {
2380
+ throw new Error(resolvedLocale + ' not defined as available for translation');
2381
+ }
2382
+ resolvedLocales.push(resolvedLocale);
2383
+ }
2384
+ this._translationPriority = resolvedLocales;
2385
+ this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyBehaviour + this._translationPriority.join('');
2386
+ return this._translationPriority;
2224
2387
  }
2225
2388
  /**
2226
- * Get the translation to the current primary locale for the given key, library and bundle
2389
+ * @see setTranslationPriority()
2390
+ */
2391
+ getTranslationPriority() {
2392
+ return this._translationPriority;
2393
+ }
2394
+ /**
2395
+ * Get a translation based on the current locales translation priority for the given key, library and bundle
2227
2396
  *
2228
2397
  * @param string key The key we want to read from the specified resource bundle
2229
2398
  * @param string bundlePath A string with the format 'library_name/bundle_name' that is used to locate the bundle were the key to translate is found
2230
2399
  * @param array replaceWildcards A list of values that will replace wildcards that may be found on the translated text. Each wildcard
2231
- * will be replaced with the element whose index on replaceWildcards matches it. Check the documentation for this.wildCardsFormat
2400
+ * will be replaced with the element whose index on replaceWildcards matches it. Check the documentation for setWildCardsFormat
2232
2401
  * property to know more about how to setup wildcards on your translations.
2233
2402
  *
2234
2403
  * @see setWildCardsFormat()
@@ -2236,20 +2405,25 @@ class LocalesBaseService {
2236
2405
  * @return The translated text
2237
2406
  */
2238
2407
  t(key, bundlePath, replaceWildcards = []) {
2239
- this._validateInitialized();
2240
2408
  // Create a cache key to improve performance when requesting the same key translation several times
2241
2409
  const cacheKey = `${this._cacheHashBaseString}${key}${bundlePath}${replaceWildcards.join('')}`;
2242
2410
  if (!this._keyValuesCache[cacheKey]) {
2411
+ const [library, bundle] = bundlePath.split('/');
2243
2412
  this._forceNonEmptyString(key, '', 'key must be non empty string');
2244
2413
  this._forceNonEmptyString(bundlePath, '', 'bundlePath must be non empty string');
2245
- const [library, bundle] = bundlePath.split('/');
2246
2414
  this._forceNonEmptyString(library, '', 'no library specified on bundlePath');
2247
2415
  this._forceNonEmptyString(bundle, '', 'no bundle specified on bundlePath');
2416
+ if (this._translationPriority.length === 0) {
2417
+ throw new Error(`No translation priority specified`);
2418
+ }
2248
2419
  const replacementsCount = replaceWildcards.length;
2249
- // Loop all the locales to find the first one with a value for the specified key
2250
- for (const locale of this._locales) {
2251
- if (this._loadedTranslations[library]?.[bundle]?.[locale]?.[key]) {
2252
- let result = this._loadedTranslations[library][bundle][locale][key];
2420
+ // Loop all the translation priority locales to find the first one with a value for the specified key
2421
+ for (const locale of this._translationPriority) {
2422
+ if (!this.isLocaleLoaded(locale)) {
2423
+ throw new Error(`Locale ${locale} must be loaded before requesting translations`);
2424
+ }
2425
+ if (this._loadedRawTranslationData[library]?.[bundle]?.[locale]?.[key]) {
2426
+ let result = this._loadedRawTranslationData[library][bundle][locale][key];
2253
2427
  // Replace all wildcards on the text with the specified replacements if any
2254
2428
  for (let i = 0; i < replacementsCount; i++) {
2255
2429
  result = this._replace(result, this._replace(this._wildCardsFormat, 'N', i.toString()), replaceWildcards[i]);
@@ -2259,10 +2433,10 @@ class LocalesBaseService {
2259
2433
  }
2260
2434
  }
2261
2435
  // Check if an exception needs to be thrown if the specified key is not found on this bundle
2262
- if (this._missingKeyFormat.includes('$exception')) {
2436
+ if (this._missingKeyBehaviour.includes('$exception')) {
2263
2437
  throw new Error(`Translation key <${key}> not found on <${bundlePath}>`);
2264
2438
  }
2265
- this._keyValuesCache[cacheKey] = this._replace(this._missingKeyFormat, '$key', key);
2439
+ this._keyValuesCache[cacheKey] = this._replace(this._missingKeyBehaviour, '$key', key);
2266
2440
  }
2267
2441
  return this._keyValuesCache[cacheKey];
2268
2442
  }
@@ -2311,162 +2485,24 @@ class LocalesBaseService {
2311
2485
  return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
2312
2486
  }
2313
2487
  /**
2314
- * A list of strings containing the locales that are used by this class to translate the given keys, sorted by preference.
2315
- * Each string is formatted as a standard locale code with language and country joined by an underscore, like: en_US, fr_FR
2316
- *
2317
- * When a key and bundle are requested for translation, the class will check on the first language of this
2318
- * list for a translated text. If missing, the next one will be used, and so. This list is constructed after initialize
2319
- * methods is called.
2320
- *
2321
- * @example: After loading the following list of locales ['en_US', 'es_ES', 'fr_FR'] if we call t('HELLO', 'lib1/greetings')
2322
- * the localization manager will try to locate the en_US value for the HELLO tag on the greetings bundle for the library lib1.
2323
- * If the tag is not found for the specified locale and bundle, the same search will be performed for the es_ES locale, and so, till a
2324
- * value is found or no more locales are defined.
2325
- */
2326
- getLocales() {
2327
- return this._locales;
2328
- }
2329
- /**
2330
- * A list of strings containing the languages that are used by this class to translate the given keys, sorted by preference.
2331
- * Each string is formatted as a 2 digit language code, like: en, fr
2332
- *
2333
- * This list is the same as the locales() one, but containing only the language part of each locale (the first two digits)
2334
- *
2335
- * @see getLocales()
2336
- */
2337
- getLanguages() {
2338
- return this._languages;
2339
- }
2340
- /**
2341
- * Get the first locale from the list of loaded locales, which is the currently used to search for translated texts.
2342
- *
2343
- * @returns The locale that is defined as the primary one. For example: en_US, es_ES, ..
2344
- */
2345
- getPrimaryLocale() {
2346
- this._validateInitialized();
2347
- return this._locales[0];
2348
- }
2349
- /**
2350
- * Get the first language from the list of loaded locales, which is the currently used to search for translated texts.
2351
- *
2352
- * @returns The 2 digit language code that is defined as the primary one. For example: en, es, ..
2353
- */
2354
- getPrimaryLanguage() {
2355
- this._validateInitialized();
2356
- return this._languages[0];
2357
- }
2358
- /**
2359
- * Define the locale that will be placed at the front of the currently loaded locales list (moving all the others one position to the right).
2360
- *
2361
- * This will be the first locale to use when trying to get a translation.
2362
- *
2363
- * @param locale A currently loaded locale that will be moved to the first position of the loaded locales list. If the specified locale
2364
- * is not currently loaded, an exception will happen.
2365
- *
2366
- * @returns void
2367
- */
2368
- setPrimaryLocale(locale) {
2369
- this._validateInitialized();
2370
- if (!this.isLocaleLoaded(locale)) {
2371
- throw new Error(locale + ' not loaded');
2372
- }
2373
- let result = [locale];
2374
- for (let l of this._locales) {
2375
- if (l !== locale) {
2376
- result.push(l);
2377
- }
2378
- }
2379
- this._locales = result;
2380
- this._languages = this._locales.map((l) => l.substring(0, 2));
2381
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
2382
- }
2383
- /**
2384
- * Moves the specified locales to the beginning of the locales list. This also alters the translation priority by setting the first
2385
- * provided locale as the most prioritary, the second as the next one and so.
2386
- *
2387
- * This method basically works exactly the same way as setPrimaryLocale but letting us add many locales at once.
2388
- *
2389
- * @see setPrimaryLocale()
2390
- *
2391
- * @param locales A list of locales to be moved to the beginning of the translation priority. First locales item will be the prefered
2392
- * locale for translation, second will be the next one in case some key is not translated for the first one and so. If any of the
2393
- * specified locales is not currently loaded, an exception will happen.
2394
- *
2395
- * @returns void
2396
- */
2397
- setPrimaryLocales(locales) {
2398
- if (!Array.isArray(locales) ||
2399
- (new Set(locales).size !== locales.length) ||
2400
- locales.length === 0) {
2401
- throw new Error('locales must be non empty string array with no duplicate elements');
2402
- }
2403
- for (let i = locales.length - 1; i >= 0; i--) {
2404
- this.setPrimaryLocale(locales[i]);
2405
- }
2406
- }
2407
- /**
2408
- * Define the 2 digit language that will be placed at the front of the currently loaded locales list (moving all the others one position to the right).
2409
- *
2410
- * This will be the first language to use when trying to get a translation.
2411
- *
2412
- * @param language A 2 digit language code that matches with any of the currently loaded locales, which will
2413
- * be moved to the first position of the loaded locales list. If the specified language does not match with
2414
- * a locale that is currently loaded, an exception will happen.
2415
- *
2416
- * @returns void
2417
- */
2418
- setPrimaryLanguage(language) {
2419
- for (let locale of this._locales) {
2420
- if (locale.substring(0, 2) === language) {
2421
- this.setPrimaryLocale(locale);
2422
- return;
2423
- }
2424
- }
2425
- throw new Error(language + ' not loaded');
2426
- }
2427
- /**
2428
- * Moves the locales that match the specified languages to the beginning of the locales list.
2429
- * Works the same as setPrimaryLocales() but with a list of the 2 digit language codes that match the respective locales.
2430
- *
2431
- * @see setPrimaryLocale()
2432
- * @see setPrimaryLanguage()
2433
- *
2434
- * @param languages A list of 2 digit language codes to be moved to the beginning of the translation priority. If any of the
2435
- * specified languages does not match with a locale that is currently loaded, an exception will happen.
2488
+ * Aux method to validate that a locale string is correctly formatted
2436
2489
  *
2437
- * @returns void
2490
+ * @param string $locale A locale string
2438
2491
  */
2439
- setPrimaryLanguages(languages) {
2440
- if (!Array.isArray(languages) ||
2441
- (new Set(languages).size !== languages.length) ||
2442
- languages.length === 0) {
2443
- throw new Error('languages must be non empty string array with no duplicate elements');
2444
- }
2445
- for (let i = languages.length - 1; i >= 0; i--) {
2446
- this.setPrimaryLanguage(languages[i]);
2492
+ _validateLocaleString(locale) {
2493
+ if (!/^[a-z]{2}_[A-Z]{2}$/.test(locale)) {
2494
+ throw new Error('locale must be a valid xx_XX value');
2447
2495
  }
2448
2496
  }
2449
2497
  /**
2450
- * Change the loaded locales translation preference order. The same locales that are currently loaded must be passed
2451
- * but with a different order to change the translation priority.
2452
- *
2453
- * @param locales A list with the new locales translation priority
2498
+ * Aux method to validate that a language string is correctly formatted
2454
2499
  *
2455
- * @returns void
2500
+ * @param language A 2 digit language string
2456
2501
  */
2457
- setLocalesOrder(locales) {
2458
- if (locales.length !== this._locales.length) {
2459
- throw new Error('locales must contain all the currently loaded locales');
2460
- }
2461
- this._validateInitialized();
2462
- for (let locale of locales) {
2463
- if (!this.isLocaleLoaded(locale)) {
2464
- throw new Error(locale + ' not loaded');
2465
- }
2502
+ _validateLanguageString(language) {
2503
+ if (!/^[a-z]{2}$/.test(language)) {
2504
+ throw new Error('language must be a valid 2 digit value');
2466
2505
  }
2467
- this._locales = locales;
2468
- this._languages = this._locales.map((l) => l.substring(0, 2));
2469
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
2470
2506
  }
2471
2507
  /**
2472
2508
  * This is an aux method to implement the TurboCommons StringUtils replace method.
@@ -2479,6 +2515,7 @@ class LocalesBaseService {
2479
2515
  /**
2480
2516
  * This is an aux method to implement the TurboCommons StringUtils isEmpty method.
2481
2517
  * It is exactly the same as the one on the library, but we implement it here to avoid having a dependency with TurboCommons
2518
+ * TODO - If we move this class to TurboCommons, we can remove this method
2482
2519
  */
2483
2520
  _isEmpty(string) {
2484
2521
  let isString = (typeof string === 'string' || string instanceof String);
@@ -2502,15 +2539,7 @@ class LocalesBaseService {
2502
2539
  throw new Error(valueName + ' ' + errorMessage);
2503
2540
  }
2504
2541
  }
2505
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2506
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, providedIn: 'root' }); }
2507
2542
  }
2508
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, decorators: [{
2509
- type: Injectable,
2510
- args: [{
2511
- providedIn: 'root',
2512
- }]
2513
- }] });
2514
2543
 
2515
2544
  /**
2516
2545
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -2521,32 +2550,105 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
2521
2550
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
2522
2551
  */
2523
2552
  /**
2524
- * A dialog component with a single option button, to be used with dialog service
2553
+ * Fully featured translation service to be used with any application that requires text internationalization.
2554
+ * It is defined as an abstract service class so it must be extended in our application. This way we can
2555
+ * write custom methods to extend the functionality of this class if needed.
2556
+ *
2557
+ * @see TranslationBaseManager for more information about the methods and properties available.
2525
2558
  */
2526
- class DialogSingleOptionComponent extends DialogBaseComponent {
2527
- static { this.DIALOG_CLASS_NAME = 'DialogSingleOptionComponent'; }
2528
- constructor(elementRef, dialogRef, data) {
2529
- super(elementRef, dialogRef);
2530
- this.elementRef = elementRef;
2531
- this.dialogRef = dialogRef;
2532
- this.data = data;
2533
- if (data.texts.length < 1) {
2534
- throw new Error('DialogSingleOptionComponent expects 2 texts: The title and optionally a description');
2559
+ class LocalesBaseService extends TranslationManager {
2560
+ constructor() {
2561
+ super(...arguments);
2562
+ /**
2563
+ * Key used to store the user preferred locales in local storage
2564
+ */
2565
+ this._storageKey = 'turbogui-user-translation-priority';
2566
+ }
2567
+ /**
2568
+ * Try to detect the list of locales (sorted by preference) that are defined by the user
2569
+ * at the browser setup and are also available for translation at this class.
2570
+ *
2571
+ * First we will check if there is a stored local storage value that was defined on a previous session.
2572
+ * If found, all the locales stored there that are also available on this class will be returned.
2573
+ *
2574
+ * If no locales could be found from previous sessions, the method will check the browser default language and the
2575
+ * browser languages list. All matching languages that are also listed on availableLocales will be returned, sorted
2576
+ * by the browser preference.
2577
+ *
2578
+ * Finally, if nothing is found, the first locale from the class availableLocales will be returned.
2579
+ *
2580
+ * @returns A list of locales that can be used as a translation priority list
2581
+ */
2582
+ getAvailableLocalesFromBrowser() {
2583
+ if (this._availableLocales.length === 0) {
2584
+ throw new Error('You must define availableLocales before calling detectBrowserLocales()');
2535
2585
  }
2536
- if (data.options.length !== 1) {
2537
- throw new Error('DialogSingleOptionComponent expects only one option');
2586
+ let foundLocales = [];
2587
+ // Check for previously stored locales in local storage
2588
+ const storedLocales = localStorage.getItem(this._storageKey);
2589
+ if (storedLocales) {
2590
+ for (const locale of storedLocales.split(',')) {
2591
+ if (this._availableLocales.includes(locale)) {
2592
+ foundLocales.push(locale);
2593
+ }
2594
+ }
2595
+ }
2596
+ // Check for languages on the current browser setup
2597
+ if (foundLocales.length === 0) {
2598
+ const browserLanguages = navigator.languages || [navigator.language];
2599
+ for (const lang of browserLanguages) {
2600
+ const langCode = lang.split(/[-_]/)[0].toLowerCase();
2601
+ const allowedLocale = this._availableLocales.find(locale => locale.startsWith(langCode + '_'));
2602
+ if (allowedLocale) {
2603
+ foundLocales.push(allowedLocale);
2604
+ }
2605
+ }
2538
2606
  }
2607
+ // Return the locales found or the first allowed locale as a fallback
2608
+ return (foundLocales.length > 0) ? foundLocales : [this._availableLocales[0]];
2539
2609
  }
2540
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleOptionComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
2541
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogSingleOptionComponent, isStandalone: true, selector: "tg-dialog-single-option", providers: [], usesInheritance: true, ngImport: i0, template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<p *ngIf=\"data.texts.length > 1\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n (click)=\"closeDialog(0)\">\r\n \r\n {{data.options[0]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-bottom:25px}p{margin-bottom:52px}button{float:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }] }); }
2610
+ /**
2611
+ * Initializes the translation system by loading and parsing bundle files from the specified translations path.
2612
+ * After the promise finishes, the class will contain all the translation data and will be ready to translate any
2613
+ * provided key.
2614
+ *
2615
+ * @param locales An array of locale codes (['en_US', 'es_ES', 'fr_FR', ...]) to load from the url response.
2616
+ * The order of this array is not important for translation priority.
2617
+ *
2618
+ * @param url - Url where the translations are found. The response must be a Json with the expected structure:
2619
+ * { library_name: { bundle_name: { locale_code: { key1: "translation1", key2: "translation2" } } } ... }
2620
+ *
2621
+ * @return A promise that will resolve if the translations get correctly loaded, or reject with an error if load fails
2622
+ */
2623
+ async loadLocalesFromUrl(locales, url) {
2624
+ try {
2625
+ const response = await fetch(url);
2626
+ if (!response.ok) {
2627
+ throw new Error(`HTTP error! status: ${response.status}`);
2628
+ }
2629
+ const data = await response.json();
2630
+ this.loadLocalesFromJson(locales, data);
2631
+ }
2632
+ catch (error) {
2633
+ throw new Error(`ERROR LOADING LOCALES FROM: ${url}\n${error}`);
2634
+ }
2635
+ }
2636
+ setTranslationPriority(localesOrLanguages) {
2637
+ const locales = super.setTranslationPriority(localesOrLanguages);
2638
+ // We override this method to store the user preferred locales in local storage
2639
+ // for future sessions
2640
+ localStorage.setItem(this._storageKey, locales.join(','));
2641
+ return locales;
2642
+ }
2643
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
2644
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, providedIn: 'root' }); }
2542
2645
  }
2543
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleOptionComponent, decorators: [{
2544
- type: Component,
2545
- args: [{ selector: 'tg-dialog-single-option', imports: [CommonModule, MatButtonModule], providers: [], template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<p *ngIf=\"data.texts.length > 1\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n (click)=\"closeDialog(0)\">\r\n \r\n {{data.options[0]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-bottom:25px}p{margin-bottom:52px}button{float:right}\n"] }]
2546
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: undefined, decorators: [{
2547
- type: Inject,
2548
- args: [MAT_DIALOG_DATA]
2549
- }] }] });
2646
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, decorators: [{
2647
+ type: Injectable,
2648
+ args: [{
2649
+ providedIn: 'root',
2650
+ }]
2651
+ }] });
2550
2652
 
2551
2653
  /**
2552
2654
  * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
@@ -2731,11 +2833,11 @@ class DialogSingleSelectionListComponent extends DialogBaseComponent {
2731
2833
  }
2732
2834
  }
2733
2835
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleSelectionListComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }, { token: BrowserService }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
2734
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogSingleSelectionListComponent, isStandalone: true, selector: "tg-dialog-single-selection-list", providers: [], usesInheritance: true, ngImport: i0, template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<!-- Here goes the dialog subtitle. Leave it blank if you don't need it -->\r\n<p *ngIf=\"data.texts.length > 1 &amp;&amp; !stringUtils.isEmpty(data.texts[1])\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<mat-form-field *ngIf=\"data.texts.length > 2 &amp;&amp; !stringUtils.isEmpty(data.texts[2])\"\r\n class=\"searchItemInputContainer\">\r\n \r\n <mat-label>{{data.texts[2]}}</mat-label>\r\n <input matInput autoFocusOnDisplay\r\n (keyup.enter)=\"onIntroKeyPress()\"\r\n (input)=\"onSearchChange($event.target)\">\r\n \r\n</mat-form-field>\r\n\r\n<!-- here goes the list of elements that will be shown to the user -->\r\n<div class=\"listItemsContainer\"\r\n [style.max-height]=\"getListItemsContainerMaxheight()\">\r\n\r\n <div class=\"listItemContainer\"\r\n [style.background-color]=\"selectedItemIndex === i ? '#90d1ffad' : (i % 2 === 0 ? 'initial' : '#00000009')\"\r\n *ngFor=\"let item of filteredOptions; let i = index; trackBy: trackByFn\"\r\n (click)=\"data.texts.length < 4 ? closeDialog(i) : selectedItemIndex = i\">\r\n \r\n <p *ngIf=\"item !== ''\">\r\n {{item}}\r\n </p>\r\n \r\n </div>\r\n\r\n</div>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n [disabled]=\"selectedItemIndex < 0\"\r\n (click)=\"closeDialog(selectedItemIndex)\"\r\n *ngIf=\"data.texts.length > 3\">\r\n\r\n {{data.texts[3]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-top:0;margin-bottom:10px}p{margin-top:0;margin-bottom:5px}.searchItemInputContainer{width:100%;margin-top:0;margin-bottom:0}.listItemsContainer{overflow-y:auto;border:1px solid rgba(0,0,0,.2);border-radius:4px;margin-bottom:5px}.listItemContainer p{line-height:36px;width:calc(100% - 12px);margin:0 0 0 6px}button{float:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: TurboGuiAngularModule }, { kind: "directive", type: AutoFocusOnDisplayDirective, selector: "[autoFocusOnDisplay]" }] }); }
2836
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogSingleSelectionListComponent, isStandalone: true, selector: "tg-dialog-single-selection-list", providers: [], usesInheritance: true, ngImport: i0, template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<!-- Here goes the dialog subtitle. Leave it blank if you don't need it -->\r\n<p *ngIf=\"data.texts.length > 1 &amp;&amp; !stringUtils.isEmpty(data.texts[1])\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<mat-form-field *ngIf=\"data.texts.length > 2 &amp;&amp; !stringUtils.isEmpty(data.texts[2])\"\r\n class=\"searchItemInputContainer\">\r\n \r\n <mat-label>{{data.texts[2]}}</mat-label>\r\n <input matInput autoFocusOnDisplay\r\n (keyup.enter)=\"onIntroKeyPress()\"\r\n (input)=\"onSearchChange($event.target)\">\r\n \r\n</mat-form-field>\r\n\r\n<!-- here goes the list of elements that will be shown to the user -->\r\n<div class=\"listItemsContainer\"\r\n [style.max-height]=\"getListItemsContainerMaxheight()\">\r\n\r\n <div class=\"listItemContainer\"\r\n [style.background-color]=\"selectedItemIndex === i ? '#90d1ffad' : (i % 2 === 0 ? 'initial' : '#00000009')\"\r\n *ngFor=\"let item of filteredOptions; let i = index; trackBy: trackByFn\"\r\n (click)=\"data.texts.length < 4 ? closeDialog(i) : selectedItemIndex = i\">\r\n \r\n <p *ngIf=\"item !== ''\">\r\n {{item}}\r\n </p>\r\n \r\n </div>\r\n\r\n</div>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n [disabled]=\"selectedItemIndex < 0\"\r\n (click)=\"closeDialog(selectedItemIndex)\"\r\n *ngIf=\"data.texts.length > 3\">\r\n\r\n {{data.texts[3]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-top:0;margin-bottom:10px}p{margin-top:0;margin-bottom:5px}.searchItemInputContainer{width:100%;margin-top:0;margin-bottom:0}.listItemsContainer{overflow-y:auto;border:1px solid rgba(0,0,0,.2);border-radius:4px;margin-bottom:5px}.listItemContainer p{line-height:36px;width:calc(100% - 12px);margin:0 0 0 6px;cursor:pointer}button{float:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: TurboGuiAngularModule }, { kind: "directive", type: AutoFocusOnDisplayDirective, selector: "[autoFocusOnDisplay]" }] }); }
2735
2837
  }
2736
2838
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleSelectionListComponent, decorators: [{
2737
2839
  type: Component,
2738
- args: [{ selector: 'tg-dialog-single-selection-list', imports: [CommonModule, MatInputModule, MatFormFieldModule, TurboGuiAngularModule], providers: [], template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<!-- Here goes the dialog subtitle. Leave it blank if you don't need it -->\r\n<p *ngIf=\"data.texts.length > 1 &amp;&amp; !stringUtils.isEmpty(data.texts[1])\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<mat-form-field *ngIf=\"data.texts.length > 2 &amp;&amp; !stringUtils.isEmpty(data.texts[2])\"\r\n class=\"searchItemInputContainer\">\r\n \r\n <mat-label>{{data.texts[2]}}</mat-label>\r\n <input matInput autoFocusOnDisplay\r\n (keyup.enter)=\"onIntroKeyPress()\"\r\n (input)=\"onSearchChange($event.target)\">\r\n \r\n</mat-form-field>\r\n\r\n<!-- here goes the list of elements that will be shown to the user -->\r\n<div class=\"listItemsContainer\"\r\n [style.max-height]=\"getListItemsContainerMaxheight()\">\r\n\r\n <div class=\"listItemContainer\"\r\n [style.background-color]=\"selectedItemIndex === i ? '#90d1ffad' : (i % 2 === 0 ? 'initial' : '#00000009')\"\r\n *ngFor=\"let item of filteredOptions; let i = index; trackBy: trackByFn\"\r\n (click)=\"data.texts.length < 4 ? closeDialog(i) : selectedItemIndex = i\">\r\n \r\n <p *ngIf=\"item !== ''\">\r\n {{item}}\r\n </p>\r\n \r\n </div>\r\n\r\n</div>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n [disabled]=\"selectedItemIndex < 0\"\r\n (click)=\"closeDialog(selectedItemIndex)\"\r\n *ngIf=\"data.texts.length > 3\">\r\n\r\n {{data.texts[3]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-top:0;margin-bottom:10px}p{margin-top:0;margin-bottom:5px}.searchItemInputContainer{width:100%;margin-top:0;margin-bottom:0}.listItemsContainer{overflow-y:auto;border:1px solid rgba(0,0,0,.2);border-radius:4px;margin-bottom:5px}.listItemContainer p{line-height:36px;width:calc(100% - 12px);margin:0 0 0 6px}button{float:right}\n"] }]
2840
+ args: [{ selector: 'tg-dialog-single-selection-list', imports: [CommonModule, MatInputModule, MatFormFieldModule, TurboGuiAngularModule], providers: [], template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<!-- Here goes the dialog subtitle. Leave it blank if you don't need it -->\r\n<p *ngIf=\"data.texts.length > 1 &amp;&amp; !stringUtils.isEmpty(data.texts[1])\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<mat-form-field *ngIf=\"data.texts.length > 2 &amp;&amp; !stringUtils.isEmpty(data.texts[2])\"\r\n class=\"searchItemInputContainer\">\r\n \r\n <mat-label>{{data.texts[2]}}</mat-label>\r\n <input matInput autoFocusOnDisplay\r\n (keyup.enter)=\"onIntroKeyPress()\"\r\n (input)=\"onSearchChange($event.target)\">\r\n \r\n</mat-form-field>\r\n\r\n<!-- here goes the list of elements that will be shown to the user -->\r\n<div class=\"listItemsContainer\"\r\n [style.max-height]=\"getListItemsContainerMaxheight()\">\r\n\r\n <div class=\"listItemContainer\"\r\n [style.background-color]=\"selectedItemIndex === i ? '#90d1ffad' : (i % 2 === 0 ? 'initial' : '#00000009')\"\r\n *ngFor=\"let item of filteredOptions; let i = index; trackBy: trackByFn\"\r\n (click)=\"data.texts.length < 4 ? closeDialog(i) : selectedItemIndex = i\">\r\n \r\n <p *ngIf=\"item !== ''\">\r\n {{item}}\r\n </p>\r\n \r\n </div>\r\n\r\n</div>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n [disabled]=\"selectedItemIndex < 0\"\r\n (click)=\"closeDialog(selectedItemIndex)\"\r\n *ngIf=\"data.texts.length > 3\">\r\n\r\n {{data.texts[3]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-top:0;margin-bottom:10px}p{margin-top:0;margin-bottom:5px}.searchItemInputContainer{width:100%;margin-top:0;margin-bottom:0}.listItemsContainer{overflow-y:auto;border:1px solid rgba(0,0,0,.2);border-radius:4px;margin-bottom:5px}.listItemContainer p{line-height:36px;width:calc(100% - 12px);margin:0 0 0 6px;cursor:pointer}button{float:right}\n"] }]
2739
2841
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: BrowserService }, { type: undefined, decorators: [{
2740
2842
  type: Inject,
2741
2843
  args: [MAT_DIALOG_DATA]