turbogui-angular 20.6.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.
@@ -1988,7 +1988,7 @@ class RouterBaseService {
1988
1988
  * { path: '', component: HomePageComponent,
1989
1989
  * data: { titleKey: 'HOME', titleBundle: 'turbodepot/user-interface'} },
1990
1990
  *
1991
- * @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.
1992
1992
  * @param prefix A text to be added before the computed title.
1993
1993
  * @param sufix A text to be added after the computed title.
1994
1994
  */
@@ -2066,42 +2066,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
2066
2066
  */
2067
2067
  /**
2068
2068
  * Fully featured translation manager to be used with any application that requires text internationalization.
2069
- * It is defined as an abstract class so it must be extended in our application. This way we can
2070
- * 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.
2071
2089
  */
2072
- class LocalesBaseService {
2090
+ class TranslationManager {
2073
2091
  constructor() {
2074
2092
  /**
2075
- * 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()
2076
2108
  */
2077
- this._isInitialized = false;
2109
+ this.isLoadingNonAvaliableLocalesAllowed = false;
2078
2110
  /**
2079
- * @see getLocales()
2111
+ * @see setTranslationPriority()
2080
2112
  */
2081
- this._locales = [];
2113
+ this._translationPriority = [];
2082
2114
  /**
2083
- * @see getLanguages()
2115
+ * @see getLoadedLocales()
2084
2116
  */
2085
- this._languages = [];
2117
+ this._loadedLocales = [];
2086
2118
  /**
2087
- * 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" } } } ... }
2088
2121
  */
2089
- this._loadedTranslations = {};
2090
- /**
2091
- * Stores a memory cache to improve performance when outputing translations
2092
- */
2093
- this._keyValuesCache = {};
2122
+ this._loadedRawTranslationData = {};
2094
2123
  /**
2095
2124
  * @see setWildCardsFormat()
2096
2125
  */
2097
2126
  this._wildCardsFormat = '{N}';
2098
2127
  /**
2099
- * @see setMissingKeyFormat()
2128
+ * @see setMissingKeyBehaviour()
2100
2129
  */
2101
- this._missingKeyFormat = '$exception';
2130
+ this._missingKeyBehaviour = '$exception';
2131
+ /**
2132
+ * Stores a memory cache to improve performance when outputing translations
2133
+ */
2134
+ this._keyValuesCache = {};
2102
2135
  /**
2103
2136
  * Stores a hash value that is used to improve the performance for translation t() methods.
2104
- * This is computed based on _wildCardsFormat plus _missingKeyFormat plus the current primary locale
2137
+ *
2138
+ * This is computed based on _wildCardsFormat + _missingKeyBehaviour + _translationPriority
2139
+ *
2105
2140
  * Methods that change these values will recalculate the hash string, so when calling translation methods, the
2106
2141
  * performance will be as fast as possible.
2107
2142
  */
@@ -2119,7 +2154,7 @@ class LocalesBaseService {
2119
2154
  *
2120
2155
  * We usually set this before initializing the class translation data
2121
2156
  *
2122
- * 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.
2123
2158
  *
2124
2159
  * @param value The wildcards format we want to set
2125
2160
  *
@@ -2129,7 +2164,7 @@ class LocalesBaseService {
2129
2164
  if (!value.includes('N')) {
2130
2165
  throw new Error("N is mandatory to replace wildcards");
2131
2166
  }
2132
- this._cacheHashBaseString = value + this._missingKeyFormat + ((this._locales.length > 0) ? this._locales[0] : '');
2167
+ this._cacheHashBaseString = value + this._missingKeyBehaviour + this._translationPriority.join('');
2133
2168
  this._wildCardsFormat = value;
2134
2169
  return value;
2135
2170
  }
@@ -2137,159 +2172,232 @@ class LocalesBaseService {
2137
2172
  * Defines the behaviour for t(), tStartCase(), etc... methods when a key is not found on
2138
2173
  * a bundle or the bundle does not exist
2139
2174
  *
2140
- * 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)
2141
2176
  *
2142
- * 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
2143
2178
  *
2144
- * If missingKeyFormat contains a string with one of the following predefined wildcards:<br>
2145
- * - $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>
2146
2181
  * - $exception (default value) will throw an exception with the problem cause description.
2147
2182
  *
2148
2183
  * @param value The missing key format we want to set
2149
2184
  *
2150
2185
  * @returns The value that's been set
2151
2186
  */
2152
- setMissingKeyFormat(value) {
2153
- this._cacheHashBaseString = this._wildCardsFormat + value + ((this._locales.length > 0) ? this._locales[0] : '');
2154
- this._missingKeyFormat = value;
2187
+ setMissingKeyBehaviour(value) {
2188
+ this._cacheHashBaseString = this._wildCardsFormat + value + this._translationPriority.join('');
2189
+ this._missingKeyBehaviour = value;
2155
2190
  return value;
2156
2191
  }
2157
2192
  /**
2158
- * @see setMissingKeyFormat()
2193
+ * @see setMissingKeyBehaviour()
2159
2194
  */
2160
- getMissingKeyFormat() {
2161
- return this._missingKeyFormat;
2195
+ getMissingKeyBehaviour() {
2196
+ return this._missingKeyBehaviour;
2162
2197
  }
2163
2198
  /**
2164
2199
  * Adds translations to the class by loading and parsing bundles from the provided JSON object.
2165
- * 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
2166
2201
  * any provided key to any of the specified locales.
2167
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
+ *
2168
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.
2169
- * 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.
2170
2208
  *
2171
2209
  * @param json A JSON object containing the translation data. The structure must be as follows:
2172
2210
  * { library_name: { bundle_name: { locale_code: { key1: "translation1", key2: "translation2" } } } ... }
2173
2211
  *
2174
- * @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
2175
2216
  */
2176
2217
  loadLocalesFromJson(locales, json) {
2177
- this._isInitialized = false;
2218
+ // Cache must be cleared when loading new translations to ensure all keys are re-evaluated.
2219
+ this._keyValuesCache = {};
2178
2220
  // Validate received locales are correct
2179
2221
  for (const locale of locales) {
2180
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
+ }
2181
2227
  }
2182
2228
  // Validate the translations object follows the right structure
2183
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.
2184
2244
  for (const library in json) {
2245
+ if (!this._loadedRawTranslationData[library]) {
2246
+ this._loadedRawTranslationData[library] = {};
2247
+ }
2185
2248
  for (const bundle in json[library]) {
2249
+ if (!this._loadedRawTranslationData[library][bundle]) {
2250
+ this._loadedRawTranslationData[library][bundle] = {};
2251
+ }
2186
2252
  for (const locale in json[library][bundle]) {
2187
- this._validateLocaleString(locale);
2188
- 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]);
2189
2258
  }
2190
2259
  }
2191
2260
  }
2192
- if (!isTranslationsValid) {
2193
- 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
+ }
2194
2268
  }
2195
- this._loadedTranslations = json;
2196
- this._locales = locales;
2197
- this._languages = locales.map((l) => l.substring(0, 2));
2198
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
2199
- this._isInitialized = true;
2200
- return true;
2201
2269
  }
2202
2270
  /**
2203
- * Initializes the translation system by loading and parsing bundle files from the specified translations path.
2204
- * After the promise finishes, the class will contain all the translation data and will be ready to translate any
2205
- * provided key.
2271
+ * Checks if the specified locale is currently loaded into memory on this service.
2206
2272
  *
2207
- * @param locales An array of locale codes (['en_US', 'es_ES', 'fr_FR', ...]) to load from the url response.
2208
- * The order of this array will determine the translation priority
2209
- * @param url - Url where the translations are found. The response must be a Json with the expected structure:
2210
- * { library_name: { bundle_name: { locale_code: { key1: "translation1", key2: "translation2" } } } ... }
2273
+ * @param locale A locale to check. For example 'en_US'
2211
2274
  *
2212
- * @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.
2213
2276
  */
2214
- loadLocalesFromUrl(locales, url) {
2215
- this._isInitialized = false;
2216
- return new Promise((resolve, reject) => {
2217
- fetch(url).then(response => {
2218
- if (!response.ok) {
2219
- throw new Error(`HTTP error! status: ${response.status}`);
2220
- }
2221
- return response.json();
2222
- }).then(data => {
2223
- this.loadLocalesFromJson(locales, data);
2224
- resolve(undefined);
2225
- }).catch(error => {
2226
- reject(new Error(`ERROR LOADING LOCALES FROM: ${url}\n` + error));
2227
- });
2228
- });
2277
+ isLocaleLoaded(locale) {
2278
+ this._validateLocaleString(locale);
2279
+ return this._loadedLocales.includes(locale);
2229
2280
  }
2230
2281
  /**
2231
- * 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.
2232
2287
  */
2233
- isInitialized() {
2234
- return this._isInitialized;
2288
+ isLanguageLoaded(language) {
2289
+ this._validateLanguageString(language);
2290
+ return this.getLoadedLanguages().includes(language);
2235
2291
  }
2236
2292
  /**
2237
- * Aux method to verify that this class is correctly initialized with translation data
2293
+ * @see setAvailableLocales()
2238
2294
  */
2239
- _validateInitialized() {
2240
- if (!this._isInitialized) {
2241
- throw new Error('Translation service not initialized');
2242
- }
2295
+ getAvailableLocales() {
2296
+ return this._availableLocales;
2243
2297
  }
2244
2298
  /**
2245
- * 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.
2246
2302
  *
2247
- * @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.
2248
2305
  *
2249
- * @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
2250
2310
  */
2251
- isLocaleLoaded(locale) {
2252
- this._validateLocaleString(locale);
2253
- return this._locales.includes(locale);
2311
+ setAvailableLocales(locales) {
2312
+ for (const locale of locales) {
2313
+ this._validateLocaleString(locale);
2314
+ }
2315
+ this._availableLocales = locales;
2254
2316
  }
2255
2317
  /**
2256
- * 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
2257
2320
  *
2258
- * @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()
2259
2324
  */
2260
- _validateLocaleString(locale) {
2261
- if (!/^[a-z]{2}_[A-Z]{2}$/.test(locale)) {
2262
- throw new Error('locale must be a valid xx_XX value');
2263
- }
2325
+ getLoadedLocales() {
2326
+ return this._loadedLocales;
2264
2327
  }
2265
2328
  /**
2266
- * 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
2267
2331
  *
2268
- * @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)
2269
2333
  *
2270
- * @return True if the language is currently loaded on the class, false if not.
2334
+ * @see getLoadedLocales()
2271
2335
  */
2272
- isLanguageLoaded(language) {
2273
- this._validateLanguageString(language);
2274
- return this._languages.includes(language);
2336
+ getLoadedLanguages() {
2337
+ return this._loadedLocales.map((l) => l.substring(0, 2));
2275
2338
  }
2276
2339
  /**
2277
- * 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.
2278
2343
  *
2279
- * @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
2280
2357
  */
2281
- _validateLanguageString(language) {
2282
- if (!/^[a-z]{2}$/.test(language)) {
2283
- 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');
2284
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;
2285
2387
  }
2286
2388
  /**
2287
- * 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
2288
2396
  *
2289
2397
  * @param string key The key we want to read from the specified resource bundle
2290
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
2291
2399
  * @param array replaceWildcards A list of values that will replace wildcards that may be found on the translated text. Each wildcard
2292
- * 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
2293
2401
  * property to know more about how to setup wildcards on your translations.
2294
2402
  *
2295
2403
  * @see setWildCardsFormat()
@@ -2297,20 +2405,25 @@ class LocalesBaseService {
2297
2405
  * @return The translated text
2298
2406
  */
2299
2407
  t(key, bundlePath, replaceWildcards = []) {
2300
- this._validateInitialized();
2301
2408
  // Create a cache key to improve performance when requesting the same key translation several times
2302
2409
  const cacheKey = `${this._cacheHashBaseString}${key}${bundlePath}${replaceWildcards.join('')}`;
2303
2410
  if (!this._keyValuesCache[cacheKey]) {
2411
+ const [library, bundle] = bundlePath.split('/');
2304
2412
  this._forceNonEmptyString(key, '', 'key must be non empty string');
2305
2413
  this._forceNonEmptyString(bundlePath, '', 'bundlePath must be non empty string');
2306
- const [library, bundle] = bundlePath.split('/');
2307
2414
  this._forceNonEmptyString(library, '', 'no library specified on bundlePath');
2308
2415
  this._forceNonEmptyString(bundle, '', 'no bundle specified on bundlePath');
2416
+ if (this._translationPriority.length === 0) {
2417
+ throw new Error(`No translation priority specified`);
2418
+ }
2309
2419
  const replacementsCount = replaceWildcards.length;
2310
- // Loop all the locales to find the first one with a value for the specified key
2311
- for (const locale of this._locales) {
2312
- if (this._loadedTranslations[library]?.[bundle]?.[locale]?.[key]) {
2313
- 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];
2314
2427
  // Replace all wildcards on the text with the specified replacements if any
2315
2428
  for (let i = 0; i < replacementsCount; i++) {
2316
2429
  result = this._replace(result, this._replace(this._wildCardsFormat, 'N', i.toString()), replaceWildcards[i]);
@@ -2320,10 +2433,10 @@ class LocalesBaseService {
2320
2433
  }
2321
2434
  }
2322
2435
  // Check if an exception needs to be thrown if the specified key is not found on this bundle
2323
- if (this._missingKeyFormat.includes('$exception')) {
2436
+ if (this._missingKeyBehaviour.includes('$exception')) {
2324
2437
  throw new Error(`Translation key <${key}> not found on <${bundlePath}>`);
2325
2438
  }
2326
- this._keyValuesCache[cacheKey] = this._replace(this._missingKeyFormat, '$key', key);
2439
+ this._keyValuesCache[cacheKey] = this._replace(this._missingKeyBehaviour, '$key', key);
2327
2440
  }
2328
2441
  return this._keyValuesCache[cacheKey];
2329
2442
  }
@@ -2372,162 +2485,24 @@ class LocalesBaseService {
2372
2485
  return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
2373
2486
  }
2374
2487
  /**
2375
- * A list of strings containing the locales that are used by this class to translate the given keys, sorted by preference.
2376
- * Each string is formatted as a standard locale code with language and country joined by an underscore, like: en_US, fr_FR
2377
- *
2378
- * When a key and bundle are requested for translation, the class will check on the first language of this
2379
- * list for a translated text. If missing, the next one will be used, and so. This list is constructed after initialize
2380
- * methods is called.
2381
- *
2382
- * @example: After loading the following list of locales ['en_US', 'es_ES', 'fr_FR'] if we call t('HELLO', 'lib1/greetings')
2383
- * the localization manager will try to locate the en_US value for the HELLO tag on the greetings bundle for the library lib1.
2384
- * 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
2385
- * value is found or no more locales are defined.
2386
- */
2387
- getLocales() {
2388
- return this._locales;
2389
- }
2390
- /**
2391
- * A list of strings containing the languages that are used by this class to translate the given keys, sorted by preference.
2392
- * Each string is formatted as a 2 digit language code, like: en, fr
2393
- *
2394
- * This list is the same as the locales() one, but containing only the language part of each locale (the first two digits)
2395
- *
2396
- * @see getLocales()
2397
- */
2398
- getLanguages() {
2399
- return this._languages;
2400
- }
2401
- /**
2402
- * Get the first locale from the list of loaded locales, which is the currently used to search for translated texts.
2403
- *
2404
- * @returns The locale that is defined as the primary one. For example: en_US, es_ES, ..
2405
- */
2406
- getPrimaryLocale() {
2407
- this._validateInitialized();
2408
- return this._locales[0];
2409
- }
2410
- /**
2411
- * Get the first language from the list of loaded locales, which is the currently used to search for translated texts.
2412
- *
2413
- * @returns The 2 digit language code that is defined as the primary one. For example: en, es, ..
2414
- */
2415
- getPrimaryLanguage() {
2416
- this._validateInitialized();
2417
- return this._languages[0];
2418
- }
2419
- /**
2420
- * 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).
2421
- *
2422
- * This will be the first locale to use when trying to get a translation.
2423
- *
2424
- * @param locale A currently loaded locale that will be moved to the first position of the loaded locales list. If the specified locale
2425
- * is not currently loaded, an exception will happen.
2426
- *
2427
- * @returns void
2428
- */
2429
- setPrimaryLocale(locale) {
2430
- this._validateInitialized();
2431
- if (!this.isLocaleLoaded(locale)) {
2432
- throw new Error(locale + ' not loaded');
2433
- }
2434
- let result = [locale];
2435
- for (let l of this._locales) {
2436
- if (l !== locale) {
2437
- result.push(l);
2438
- }
2439
- }
2440
- this._locales = result;
2441
- this._languages = this._locales.map((l) => l.substring(0, 2));
2442
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
2443
- }
2444
- /**
2445
- * Moves the specified locales to the beginning of the locales list. This also alters the translation priority by setting the first
2446
- * provided locale as the most prioritary, the second as the next one and so.
2447
- *
2448
- * This method basically works exactly the same way as setPrimaryLocale but letting us add many locales at once.
2449
- *
2450
- * @see setPrimaryLocale()
2451
- *
2452
- * @param locales A list of locales to be moved to the beginning of the translation priority. First locales item will be the prefered
2453
- * 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
2454
- * specified locales is not currently loaded, an exception will happen.
2455
- *
2456
- * @returns void
2457
- */
2458
- setPrimaryLocales(locales) {
2459
- if (!Array.isArray(locales) ||
2460
- (new Set(locales).size !== locales.length) ||
2461
- locales.length === 0) {
2462
- throw new Error('locales must be non empty string array with no duplicate elements');
2463
- }
2464
- for (let i = locales.length - 1; i >= 0; i--) {
2465
- this.setPrimaryLocale(locales[i]);
2466
- }
2467
- }
2468
- /**
2469
- * 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).
2470
- *
2471
- * This will be the first language to use when trying to get a translation.
2472
- *
2473
- * @param language A 2 digit language code that matches with any of the currently loaded locales, which will
2474
- * be moved to the first position of the loaded locales list. If the specified language does not match with
2475
- * a locale that is currently loaded, an exception will happen.
2476
- *
2477
- * @returns void
2478
- */
2479
- setPrimaryLanguage(language) {
2480
- for (let locale of this._locales) {
2481
- if (locale.substring(0, 2) === language) {
2482
- this.setPrimaryLocale(locale);
2483
- return;
2484
- }
2485
- }
2486
- throw new Error(language + ' not loaded');
2487
- }
2488
- /**
2489
- * Moves the locales that match the specified languages to the beginning of the locales list.
2490
- * Works the same as setPrimaryLocales() but with a list of the 2 digit language codes that match the respective locales.
2491
- *
2492
- * @see setPrimaryLocale()
2493
- * @see setPrimaryLanguage()
2494
- *
2495
- * @param languages A list of 2 digit language codes to be moved to the beginning of the translation priority. If any of the
2496
- * 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
2497
2489
  *
2498
- * @returns void
2490
+ * @param string $locale A locale string
2499
2491
  */
2500
- setPrimaryLanguages(languages) {
2501
- if (!Array.isArray(languages) ||
2502
- (new Set(languages).size !== languages.length) ||
2503
- languages.length === 0) {
2504
- throw new Error('languages must be non empty string array with no duplicate elements');
2505
- }
2506
- for (let i = languages.length - 1; i >= 0; i--) {
2507
- 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');
2508
2495
  }
2509
2496
  }
2510
2497
  /**
2511
- * Change the loaded locales translation preference order. The same locales that are currently loaded must be passed
2512
- * but with a different order to change the translation priority.
2513
- *
2514
- * @param locales A list with the new locales translation priority
2498
+ * Aux method to validate that a language string is correctly formatted
2515
2499
  *
2516
- * @returns void
2500
+ * @param language A 2 digit language string
2517
2501
  */
2518
- setLocalesOrder(locales) {
2519
- if (locales.length !== this._locales.length) {
2520
- throw new Error('locales must contain all the currently loaded locales');
2521
- }
2522
- this._validateInitialized();
2523
- for (let locale of locales) {
2524
- if (!this.isLocaleLoaded(locale)) {
2525
- throw new Error(locale + ' not loaded');
2526
- }
2502
+ _validateLanguageString(language) {
2503
+ if (!/^[a-z]{2}$/.test(language)) {
2504
+ throw new Error('language must be a valid 2 digit value');
2527
2505
  }
2528
- this._locales = locales;
2529
- this._languages = this._locales.map((l) => l.substring(0, 2));
2530
- this._cacheHashBaseString = this._wildCardsFormat + this._missingKeyFormat + this._locales[0];
2531
2506
  }
2532
2507
  /**
2533
2508
  * This is an aux method to implement the TurboCommons StringUtils replace method.
@@ -2540,6 +2515,7 @@ class LocalesBaseService {
2540
2515
  /**
2541
2516
  * This is an aux method to implement the TurboCommons StringUtils isEmpty method.
2542
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
2543
2519
  */
2544
2520
  _isEmpty(string) {
2545
2521
  let isString = (typeof string === 'string' || string instanceof String);
@@ -2563,7 +2539,108 @@ class LocalesBaseService {
2563
2539
  throw new Error(valueName + ' ' + errorMessage);
2564
2540
  }
2565
2541
  }
2566
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2542
+ }
2543
+
2544
+ /**
2545
+ * TurboGUI is A library that helps with the most common and generic UI elements and functionalities
2546
+ *
2547
+ * Website : -> http://www.turbogui.org
2548
+ * License : -> Licensed under the Apache License, Version 2.0. You may not use this file except in compliance with the License.
2549
+ * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
2550
+ * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
2551
+ */
2552
+ /**
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.
2558
+ */
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()');
2585
+ }
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
+ }
2606
+ }
2607
+ // Return the locales found or the first allowed locale as a fallback
2608
+ return (foundLocales.length > 0) ? foundLocales : [this._availableLocales[0]];
2609
+ }
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 }); }
2567
2644
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, providedIn: 'root' }); }
2568
2645
  }
2569
2646
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: LocalesBaseService, decorators: [{
@@ -2756,11 +2833,11 @@ class DialogSingleSelectionListComponent extends DialogBaseComponent {
2756
2833
  }
2757
2834
  }
2758
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 }); }
2759
- 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]" }] }); }
2760
2837
  }
2761
2838
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleSelectionListComponent, decorators: [{
2762
2839
  type: Component,
2763
- 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"] }]
2764
2841
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: BrowserService }, { type: undefined, decorators: [{
2765
2842
  type: Inject,
2766
2843
  args: [MAT_DIALOG_DATA]