valtech-components 2.0.290 → 2.0.292

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.
@@ -1024,13 +1024,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
1024
1024
  class LinkProcessorService {
1025
1025
  constructor(sanitizer) {
1026
1026
  this.sanitizer = sanitizer;
1027
- // Regex para detectar URLs completas (http/https) - permite caracteres válidos pero excluye puntuación al final
1028
- this.urlRegex = /(https?:\/\/[^\s]+?)(?=[.,;!?()\s]|$)/g;
1029
- // Regex para detectar rutas internas (empiezan con / pero no son URLs completas) - excluye puntuación al final
1030
- this.internalRouteRegex = /(\s|^)(\/[^\s]*?)(?=[.,;!?()\s]|$)/g;
1027
+ // Regex para detectar URLs completas (http/https) - captura toda la URL y luego limpiamos puntuación
1028
+ this.urlRegex = /(https?:\/\/[^\s]+)/g;
1029
+ // Regex para detectar rutas internas - captura toda la ruta y luego limpiamos puntuación
1030
+ this.internalRouteRegex = /(\s|^)(\/[^\s]*)/g;
1031
1031
  // Regex para detectar enlaces estilo Markdown [texto](url)
1032
1032
  this.markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
1033
1033
  }
1034
+ /**
1035
+ * Limpia la puntuación del final de una URL.
1036
+ * Mantiene caracteres válidos de URL pero remueve signos de puntuación comunes al final.
1037
+ * Preserva parámetros de consulta, fragmentos y caracteres válidos en URLs.
1038
+ */
1039
+ cleanUrlPunctuation(url) {
1040
+ // Caracteres que consideramos puntuación al final de oración, pero NO parte de URLs
1041
+ // No incluimos & o = que son parte de query params, ni # que es parte de fragmentos
1042
+ const trailingPunctuation = /[.,;!?)]+$/;
1043
+ // Casos especiales: si la URL termina con paréntesis pero no tiene paréntesis de apertura
1044
+ // probablemente el paréntesis no es parte de la URL
1045
+ const hasOpeningParen = url.includes('(');
1046
+ const endsWithClosingParen = url.endsWith(')');
1047
+ if (endsWithClosingParen && !hasOpeningParen) {
1048
+ // Remover el paréntesis de cierre si no hay uno de apertura
1049
+ url = url.replace(/\)$/, '');
1050
+ }
1051
+ return url.replace(trailingPunctuation, '');
1052
+ }
1034
1053
  /**
1035
1054
  * Procesa texto para convertir enlaces en elementos <a> clickeables.
1036
1055
  * Detecta automáticamente URLs externas, rutas internas y enlaces estilo Markdown.
@@ -1060,8 +1079,13 @@ class LinkProcessorService {
1060
1079
  let processedText = text;
1061
1080
  // 1. Procesar enlaces estilo Markdown [texto](url) primero
1062
1081
  if (processMarkdownLinks) {
1063
- this.markdownLinkRegex.lastIndex = 0; // Reset regex
1064
- processedText = processedText.replace(this.markdownLinkRegex, (match, linkText, url) => {
1082
+ const markdownMatches = Array.from(processedText.matchAll(this.markdownLinkRegex));
1083
+ // Procesar de atrás hacia adelante para mantener las posiciones
1084
+ for (let i = markdownMatches.length - 1; i >= 0; i--) {
1085
+ const match = markdownMatches[i];
1086
+ const [fullMatch, linkText, url] = match;
1087
+ const startIndex = match.index;
1088
+ const endIndex = startIndex + fullMatch.length;
1065
1089
  hasLinks = true;
1066
1090
  const isExternal = /^https?:\/\//.test(url);
1067
1091
  const target = (isExternal ? openExternalInNewTab : openInternalInNewTab)
@@ -1071,45 +1095,64 @@ class LinkProcessorService {
1071
1095
  : '';
1072
1096
  const typeClass = isExternal ? externalLinkClass : internalLinkClass;
1073
1097
  const classes = `${linkClass} ${typeClass}`.trim();
1074
- return `<a href="${url}"${target} class="${classes}">${linkText}</a>`;
1075
- });
1098
+ const linkHtml = `<a href="${url}"${target} class="${classes}">${linkText}</a>`;
1099
+ processedText =
1100
+ processedText.substring(0, startIndex) + linkHtml + processedText.substring(endIndex);
1101
+ }
1076
1102
  }
1077
- // 2. Procesar URLs externas directas (solo si no están ya en un enlace HTML)
1078
- this.urlRegex.lastIndex = 0; // Reset regex
1079
- processedText = processedText.replace(this.urlRegex, (fullMatch, url) => {
1103
+ // 2. Procesar URLs externas directas
1104
+ const urlMatches = Array.from(processedText.matchAll(this.urlRegex));
1105
+ // Procesar de atrás hacia adelante para mantener las posiciones
1106
+ for (let i = urlMatches.length - 1; i >= 0; i--) {
1107
+ const match = urlMatches[i];
1108
+ const [fullMatch, url] = match;
1109
+ const startIndex = match.index;
1110
+ const endIndex = startIndex + fullMatch.length;
1080
1111
  // Verificar que no esté ya dentro de un enlace HTML existente
1081
- const urlPosition = processedText.indexOf(fullMatch);
1082
- const textBefore = processedText.substring(0, urlPosition);
1083
- // Buscar la última apertura y cierre de enlace antes de esta posición
1112
+ const textBefore = processedText.substring(0, startIndex);
1084
1113
  const lastOpenTag = textBefore.lastIndexOf('<a ');
1085
1114
  const lastCloseTag = textBefore.lastIndexOf('</a>');
1086
1115
  // Si hay un tag <a abierto sin cerrar, no procesamos
1087
1116
  if (lastOpenTag > lastCloseTag) {
1088
- return fullMatch; // Mantener original
1117
+ continue;
1089
1118
  }
1119
+ // Limpiar puntuación del final de la URL
1120
+ const cleanUrl = this.cleanUrlPunctuation(url);
1121
+ const punctuationRemoved = url !== cleanUrl;
1122
+ const punctuation = punctuationRemoved ? url.substring(cleanUrl.length) : '';
1090
1123
  hasLinks = true;
1091
1124
  const target = openExternalInNewTab ? ' target="_blank" rel="noopener noreferrer"' : '';
1092
1125
  const classes = `${linkClass} ${externalLinkClass}`.trim();
1093
- return `<a href="${url}"${target} class="${classes}">${url}</a>`;
1094
- });
1095
- // 3. Procesar rutas internas (solo si no están ya en un enlace HTML)
1096
- this.internalRouteRegex.lastIndex = 0; // Reset regex
1097
- processedText = processedText.replace(this.internalRouteRegex, (match, prefix, route) => {
1126
+ const linkHtml = `<a href="${cleanUrl}"${target} class="${classes}">${cleanUrl}</a>`;
1127
+ // Reemplazar el URL original con el enlace + puntuación si existía
1128
+ const replacement = punctuationRemoved ? linkHtml + punctuation : linkHtml;
1129
+ processedText =
1130
+ processedText.substring(0, startIndex) + replacement + processedText.substring(endIndex);
1131
+ }
1132
+ // 3. Procesar rutas internas
1133
+ const internalMatches = Array.from(processedText.matchAll(this.internalRouteRegex));
1134
+ // Procesar de atrás hacia adelante para mantener las posiciones
1135
+ for (let i = internalMatches.length - 1; i >= 0; i--) {
1136
+ const match = internalMatches[i];
1137
+ const [fullMatch, prefix, route] = match;
1138
+ const startIndex = match.index;
1139
+ const endIndex = startIndex + fullMatch.length;
1098
1140
  // Verificar que no esté ya dentro de un enlace HTML existente
1099
- const matchPosition = processedText.indexOf(match);
1100
- const textBefore = processedText.substring(0, matchPosition);
1101
- // Buscar la última apertura y cierre de enlace antes de esta posición
1141
+ const textBefore = processedText.substring(0, startIndex);
1102
1142
  const lastOpenTag = textBefore.lastIndexOf('<a ');
1103
1143
  const lastCloseTag = textBefore.lastIndexOf('</a>');
1104
1144
  // Si hay un tag <a abierto sin cerrar, no procesamos
1105
1145
  if (lastOpenTag > lastCloseTag) {
1106
- return match; // Mantener original
1146
+ continue;
1107
1147
  }
1108
1148
  hasLinks = true;
1109
1149
  const target = openInternalInNewTab ? ' target="_blank"' : '';
1110
1150
  const classes = `${linkClass} ${internalLinkClass}`.trim();
1111
- return `${prefix}<a href="${route}"${target} class="${classes}">${route}</a>`;
1112
- });
1151
+ const linkHtml = `<a href="${route}"${target} class="${classes}">${route}</a>`;
1152
+ const replacement = `${prefix}${linkHtml}`;
1153
+ processedText =
1154
+ processedText.substring(0, startIndex) + replacement + processedText.substring(endIndex);
1155
+ }
1113
1156
  // Si hay enlaces, sanitizar el HTML
1114
1157
  if (hasLinks) {
1115
1158
  return this.sanitizer.bypassSecurityTrustHtml(processedText);
@@ -1500,11 +1543,20 @@ class TextContent {
1500
1543
  return this.text;
1501
1544
  }
1502
1545
  }
1503
- var LangOption;
1504
- (function (LangOption) {
1505
- LangOption["ES"] = "es";
1506
- LangOption["EN"] = "en";
1507
- })(LangOption || (LangOption = {}));
1546
+ /**
1547
+ * Common language constants for convenience.
1548
+ * Users can still use any language code string directly.
1549
+ */
1550
+ const LANGUAGES = {
1551
+ ES: 'es',
1552
+ EN: 'en',
1553
+ FR: 'fr',
1554
+ DE: 'de',
1555
+ PT: 'pt',
1556
+ IT: 'it',
1557
+ ZH: 'zh',
1558
+ JA: 'ja',
1559
+ };
1508
1560
 
1509
1561
  /**
1510
1562
  * LangService - Reactive language and content management service.
@@ -1512,6 +1564,9 @@ var LangOption;
1512
1564
  * This service provides reactive content management with Observable-based language switching.
1513
1565
  * Components can subscribe to content changes and automatically update when the language changes.
1514
1566
  *
1567
+ * The service automatically detects available languages from the content configuration
1568
+ * and provides intelligent fallbacks with console warnings for missing translations.
1569
+ *
1515
1570
  * @example Basic usage:
1516
1571
  * ```typescript
1517
1572
  * constructor(private langService: LangService) {}
@@ -1531,12 +1586,120 @@ var LangOption;
1531
1586
  */
1532
1587
  class LangService {
1533
1588
  constructor(config) {
1534
- this.default = LangOption.ES;
1535
- console.log('injected config: ', config);
1589
+ this.availableLanguages = [];
1590
+ this.warnedMissingLanguages = new Set();
1591
+ console.log('LangService: Injected config:', config);
1536
1592
  this.content = config.content;
1537
1593
  this.config = config;
1594
+ // Detect available languages from content
1595
+ this.detectAvailableLanguages();
1596
+ // Set default language (prefer Spanish, then English, then first available)
1597
+ this.defaultLang = this.determineDefaultLanguage();
1598
+ // Initialize with stored language or default
1538
1599
  const current = LocalStorageService.get(LANG);
1539
- this.selectedLang = new BehaviorSubject(current || this.default);
1600
+ const initialLang = this.validateLanguage(current) || this.defaultLang;
1601
+ this.selectedLang = new BehaviorSubject(initialLang);
1602
+ console.log('LangService: Initialized with languages:', {
1603
+ available: this.availableLanguages,
1604
+ default: this.defaultLang,
1605
+ current: initialLang,
1606
+ });
1607
+ }
1608
+ /**
1609
+ * Detect available languages from the content configuration.
1610
+ * Scans all component content to find which languages are actually configured.
1611
+ */
1612
+ detectAvailableLanguages() {
1613
+ const languageSet = new Set();
1614
+ Object.values(this.content).forEach(componentContent => {
1615
+ if (componentContent?.Content) {
1616
+ Object.keys(componentContent.Content).forEach(lang => {
1617
+ languageSet.add(lang);
1618
+ });
1619
+ }
1620
+ });
1621
+ this.availableLanguages = Array.from(languageSet).sort();
1622
+ if (this.availableLanguages.length === 0) {
1623
+ console.warn('LangService: No languages detected in content configuration!');
1624
+ this.availableLanguages = [LANGUAGES.ES]; // Fallback
1625
+ }
1626
+ }
1627
+ /**
1628
+ * Determine the best default language based on available content.
1629
+ */
1630
+ determineDefaultLanguage() {
1631
+ // Preference order: Spanish, English, then first available
1632
+ const preferredOrder = [LANGUAGES.ES, LANGUAGES.EN];
1633
+ for (const preferred of preferredOrder) {
1634
+ if (this.availableLanguages.includes(preferred)) {
1635
+ return preferred;
1636
+ }
1637
+ }
1638
+ return this.availableLanguages[0];
1639
+ }
1640
+ /**
1641
+ * Validate if a language is available in the content.
1642
+ */
1643
+ validateLanguage(lang) {
1644
+ if (!lang)
1645
+ return null;
1646
+ return this.availableLanguages.includes(lang) ? lang : null;
1647
+ }
1648
+ /**
1649
+ * Get the best available language for a component and key.
1650
+ * Provides intelligent fallback with warnings.
1651
+ */
1652
+ getBestAvailableContent(className, key, requestedLang) {
1653
+ const componentContent = this.content[className];
1654
+ if (!componentContent) {
1655
+ return {
1656
+ content: undefined,
1657
+ actualLang: requestedLang,
1658
+ shouldWarn: false,
1659
+ };
1660
+ }
1661
+ // Try requested language first
1662
+ const requestedContent = componentContent.Content[requestedLang];
1663
+ if (requestedContent?.[key]) {
1664
+ return {
1665
+ content: requestedContent[key],
1666
+ actualLang: requestedLang,
1667
+ shouldWarn: false,
1668
+ };
1669
+ }
1670
+ // Language not available, try fallbacks
1671
+ const warningKey = `${className}.${key}.${requestedLang}`;
1672
+ const shouldWarn = !this.warnedMissingLanguages.has(warningKey);
1673
+ if (shouldWarn) {
1674
+ this.warnedMissingLanguages.add(warningKey);
1675
+ }
1676
+ // Try default language
1677
+ if (requestedLang !== this.defaultLang) {
1678
+ const defaultContent = componentContent.Content[this.defaultLang];
1679
+ if (defaultContent?.[key]) {
1680
+ return {
1681
+ content: defaultContent[key],
1682
+ actualLang: this.defaultLang,
1683
+ shouldWarn,
1684
+ };
1685
+ }
1686
+ }
1687
+ // Try first available language
1688
+ for (const availableLang of this.availableLanguages) {
1689
+ const availableContent = componentContent.Content[availableLang];
1690
+ if (availableContent?.[key]) {
1691
+ return {
1692
+ content: availableContent[key],
1693
+ actualLang: availableLang,
1694
+ shouldWarn,
1695
+ };
1696
+ }
1697
+ }
1698
+ return {
1699
+ content: undefined,
1700
+ actualLang: requestedLang,
1701
+ shouldWarn,
1702
+ };
1540
1703
  }
1541
1704
  /**
1542
1705
  * Observable that emits the current language whenever it changes.
@@ -1551,13 +1714,32 @@ class LangService {
1551
1714
  get currentLang() {
1552
1715
  return this.selectedLang.value;
1553
1716
  }
1717
+ /**
1718
+ * Get array of available languages detected from content.
1719
+ */
1720
+ get availableLangs() {
1721
+ return [...this.availableLanguages];
1722
+ }
1723
+ /**
1724
+ * Get the default language.
1725
+ */
1726
+ get defaultLanguage() {
1727
+ return this.defaultLang;
1728
+ }
1554
1729
  /**
1555
1730
  * Set the current language and persist it to localStorage.
1556
1731
  * This will trigger updates in all reactive content subscriptions.
1557
1732
  *
1733
+ * Validates that the language is available and warns if not.
1734
+ *
1558
1735
  * @param lang - The language to set
1559
1736
  */
1560
1737
  setLang(lang) {
1738
+ if (!this.availableLanguages.includes(lang)) {
1739
+ console.warn(`LangService: Language "${lang}" is not available. Available languages:`, this.availableLanguages);
1740
+ console.warn(`LangService: Falling back to default language "${this.defaultLang}"`);
1741
+ lang = this.defaultLang;
1742
+ }
1561
1743
  this.selectedLang.next(lang);
1562
1744
  LocalStorageService.set(LANG, lang);
1563
1745
  }
@@ -1567,10 +1749,12 @@ class LangService {
1567
1749
  * @deprecated Use getText() or getContent() for better type safety
1568
1750
  */
1569
1751
  Text(className) {
1570
- return this.content[className].Content[this.selectedLang.value];
1752
+ const componentContent = this.content[className];
1753
+ return componentContent?.Content[this.selectedLang.value] || {};
1571
1754
  }
1572
1755
  /**
1573
1756
  * Get a single content string synchronously for the current language.
1757
+ * Provides intelligent fallback with warnings for missing translations.
1574
1758
  *
1575
1759
  * @param className - The component class name
1576
1760
  * @param key - The text key
@@ -1578,12 +1762,16 @@ class LangService {
1578
1762
  * @returns The text string or fallback
1579
1763
  */
1580
1764
  getText(className, key, fallback) {
1581
- const classContent = this.content[className]?.Content[this.selectedLang.value];
1582
- return classContent?.[key] || fallback || `[${className}.${key}]`;
1765
+ const result = this.getBestAvailableContent(className, key, this.selectedLang.value);
1766
+ if (result.shouldWarn && result.actualLang !== this.selectedLang.value) {
1767
+ console.warn(`LangService: Content "${className}.${key}" not available in "${this.selectedLang.value}".`, `Using "${result.actualLang}" instead. Available languages:`, this.availableLanguages);
1768
+ }
1769
+ return result.content || fallback || `[${className}.${key}]`;
1583
1770
  }
1584
1771
  /**
1585
1772
  * Get a reactive Observable for a specific text key that updates when language changes.
1586
1773
  * This is the recommended method for components that need reactive content.
1774
+ * Provides intelligent fallback with warnings for missing translations.
1587
1775
  *
1588
1776
  * @param className - The component class name
1589
1777
  * @param key - The text key
@@ -1592,12 +1780,16 @@ class LangService {
1592
1780
  */
1593
1781
  getContent(className, key, fallback) {
1594
1782
  return this.currentLang$.pipe(map(lang => {
1595
- const classContent = this.content[className]?.Content[lang];
1596
- return classContent?.[key] || fallback || `[${className}.${key}]`;
1783
+ const result = this.getBestAvailableContent(className, key, lang);
1784
+ if (result.shouldWarn && result.actualLang !== lang) {
1785
+ console.warn(`LangService: Content "${className}.${key}" not available in "${lang}".`, `Using "${result.actualLang}" instead. Available languages:`, this.availableLanguages);
1786
+ }
1787
+ return result.content || fallback || `[${className}.${key}]`;
1597
1788
  }), distinctUntilChanged());
1598
1789
  }
1599
1790
  /**
1600
1791
  * Get reactive content for multiple keys at once.
1792
+ * Provides intelligent fallback with warnings for missing translations.
1601
1793
  *
1602
1794
  * @param className - The component class name
1603
1795
  * @param keys - Array of text keys to retrieve
@@ -1605,16 +1797,19 @@ class LangService {
1605
1797
  */
1606
1798
  getMultipleContent(className, keys) {
1607
1799
  return this.currentLang$.pipe(map(lang => {
1608
- const classContent = this.content[className]?.Content[lang] || {};
1609
1800
  const result = {};
1610
1801
  keys.forEach(key => {
1611
- result[key] = classContent[key] || `[${className}.${key}]`;
1802
+ const contentResult = this.getBestAvailableContent(className, key, lang);
1803
+ if (contentResult.shouldWarn && contentResult.actualLang !== lang) {
1804
+ console.warn(`LangService: Content "${className}.${key}" not available in "${lang}".`, `Using "${contentResult.actualLang}" instead.`);
1805
+ }
1806
+ result[key] = contentResult.content || `[${className}.${key}]`;
1612
1807
  });
1613
1808
  return result;
1614
1809
  }), distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)));
1615
1810
  }
1616
1811
  /**
1617
- * Check if a content key exists for a component.
1812
+ * Check if a content key exists for a component in any available language.
1618
1813
  *
1619
1814
  * @param className - The component class name
1620
1815
  * @param key - The text key
@@ -1626,6 +1821,49 @@ class LangService {
1626
1821
  return false;
1627
1822
  return Object.values(classContent.Content).some(langContent => langContent && typeof langContent[key] === 'string');
1628
1823
  }
1824
+ /**
1825
+ * Check if a content key exists for a component in a specific language.
1826
+ *
1827
+ * @param className - The component class name
1828
+ * @param key - The text key
1829
+ * @param lang - The language to check (defaults to current language)
1830
+ * @returns True if the key exists in the specified language
1831
+ */
1832
+ hasContentInLanguage(className, key, lang) {
1833
+ const targetLang = lang || this.currentLang;
1834
+ const classContent = this.content[className]?.Content[targetLang];
1835
+ return classContent && typeof classContent[key] === 'string';
1836
+ }
1837
+ /**
1838
+ * Get available languages for a specific component.
1839
+ *
1840
+ * @param className - The component class name
1841
+ * @returns Array of language codes available for the component
1842
+ */
1843
+ getAvailableLanguagesForComponent(className) {
1844
+ const classContent = this.content[className];
1845
+ if (!classContent)
1846
+ return [];
1847
+ return Object.keys(classContent.Content).filter(lang => classContent.Content[lang] && Object.keys(classContent.Content[lang]).length > 0);
1848
+ }
1849
+ /**
1850
+ * Get missing content keys for a component in a specific language.
1851
+ * Useful for identifying incomplete translations.
1852
+ *
1853
+ * @param className - The component class name
1854
+ * @param lang - The language to check
1855
+ * @param referenceLang - The reference language to compare against (defaults to default language)
1856
+ * @returns Array of missing keys
1857
+ */
1858
+ getMissingContentKeys(className, lang, referenceLang) {
1859
+ const refLang = referenceLang || this.defaultLang;
1860
+ const classContent = this.content[className];
1861
+ if (!classContent)
1862
+ return [];
1863
+ const referenceContent = classContent.Content[refLang] || {};
1864
+ const targetContent = classContent.Content[lang] || {};
1865
+ return Object.keys(referenceContent).filter(key => !targetContent[key] || typeof targetContent[key] !== 'string');
1866
+ }
1629
1867
  // Legacy getters/setters for backward compatibility
1630
1868
  get Lang() {
1631
1869
  return this.currentLang;
@@ -6270,6 +6508,211 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
6270
6508
  type: Output
6271
6509
  }] } });
6272
6510
 
6511
+ /**
6512
+ * ComprehensiveLinkTestComponent - Componente de prueba exhaustiva para el procesamiento de enlaces.
6513
+ *
6514
+ * Este componente demuestra todos los casos edge y escenarios complejos de procesamiento de enlaces,
6515
+ * incluyendo puntuación, URLs complejas, y mezclas de formatos.
6516
+ *
6517
+ * @example Uso en template:
6518
+ * ```html
6519
+ * <val-comprehensive-link-test></val-comprehensive-link-test>
6520
+ * ```
6521
+ */
6522
+ class ComprehensiveLinkTestComponent {
6523
+ constructor() {
6524
+ this.punctuationProps = {
6525
+ content: 'Diferentes puntuaciones: https://angular.io, también https://github.com! ¿Conoces https://typescript.org? Final: https://rxjs.dev. Entre paréntesis (https://zone.js) y con comillas "https://ionic.io".',
6526
+ size: 'medium',
6527
+ color: 'dark',
6528
+ bold: false,
6529
+ processLinks: true,
6530
+ linkConfig: {
6531
+ openExternalInNewTab: true,
6532
+ linkClass: 'test-punctuation',
6533
+ externalLinkClass: 'test-external',
6534
+ },
6535
+ };
6536
+ this.complexUrlProps = {
6537
+ content: 'URLs complejas: https://api.github.com/repos/angular/angular/issues?state=open&sort=updated&per_page=50, búsqueda https://google.com/search?q=angular+ionic+components#results, y documentación https://angular.io/guide/getting-started#development-environment.',
6538
+ size: 'medium',
6539
+ color: 'dark',
6540
+ bold: false,
6541
+ processLinks: true,
6542
+ linkConfig: {
6543
+ openExternalInNewTab: true,
6544
+ linkClass: 'test-complex',
6545
+ externalLinkClass: 'test-external',
6546
+ },
6547
+ };
6548
+ this.parenthesesProps = {
6549
+ content: 'Paréntesis de contexto (ver https://docs.angular.io) vs URLs con paréntesis https://example.com/api/method(param) en el contenido. También funciona (https://ionic.io/docs).',
6550
+ size: 'medium',
6551
+ color: 'dark',
6552
+ bold: false,
6553
+ processLinks: true,
6554
+ linkConfig: {
6555
+ openExternalInNewTab: true,
6556
+ linkClass: 'test-parentheses',
6557
+ externalLinkClass: 'test-external',
6558
+ },
6559
+ };
6560
+ this.mixedFormatsProps = {
6561
+ content: 'Formatos mezclados: [Documentación oficial](https://angular.io/docs), enlace directo https://github.com/angular/angular, ruta interna /dashboard/settings, [guía de inicio](/getting-started), y API https://api.example.com/v1/users?active=true.',
6562
+ size: 'medium',
6563
+ color: 'dark',
6564
+ bold: false,
6565
+ processLinks: true,
6566
+ linkConfig: {
6567
+ openExternalInNewTab: true,
6568
+ openInternalInNewTab: false,
6569
+ processMarkdownLinks: true,
6570
+ linkClass: 'test-mixed',
6571
+ externalLinkClass: 'test-external',
6572
+ internalLinkClass: 'test-internal',
6573
+ },
6574
+ };
6575
+ this.edgeCasesProps = {
6576
+ content: 'Casos extremos: "https://quoted-url.com", múltiple puntuación https://example.com?!!, URL al final de oración https://final-url.org. También consecutivos: https://first.com y https://second.com.',
6577
+ size: 'medium',
6578
+ color: 'dark',
6579
+ bold: false,
6580
+ processLinks: true,
6581
+ linkConfig: {
6582
+ openExternalInNewTab: true,
6583
+ linkClass: 'test-edge',
6584
+ externalLinkClass: 'test-external',
6585
+ },
6586
+ };
6587
+ this.devUrlsProps = {
6588
+ content: 'URLs de desarrollo: http://localhost:4200/dashboard, servidor local https://127.0.0.1:8080/api/status, desarrollo http://dev.example.com:3000/debug?verbose=true, y túnel https://abc123.ngrok.io/webhook.',
6589
+ size: 'medium',
6590
+ color: 'dark',
6591
+ bold: false,
6592
+ processLinks: true,
6593
+ linkConfig: {
6594
+ openExternalInNewTab: false, // Para desarrollo, puede ser útil no abrir en nueva pestaña
6595
+ linkClass: 'test-dev',
6596
+ externalLinkClass: 'test-dev-external',
6597
+ },
6598
+ };
6599
+ }
6600
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ComprehensiveLinkTestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6601
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: ComprehensiveLinkTestComponent, isStandalone: true, selector: "val-comprehensive-link-test", ngImport: i0, template: `
6602
+ <div class="comprehensive-test">
6603
+ <h2>Prueba Exhaustiva de Procesamiento de Enlaces</h2>
6604
+
6605
+ <div class="test-section">
6606
+ <h3>✅ Puntuación Final - SOLUCIONADO</h3>
6607
+ <val-text [props]="punctuationProps"></val-text>
6608
+ <p class="note">
6609
+ <strong>Esperado:</strong> Los enlaces no incluyen puntuación final (.,;!?), pero la puntuación se preserva
6610
+ como texto después del enlace.
6611
+ </p>
6612
+ </div>
6613
+
6614
+ <div class="test-section">
6615
+ <h3>✅ URLs Complejas con Parámetros - SOLUCIONADO</h3>
6616
+ <val-text [props]="complexUrlProps"></val-text>
6617
+ <p class="note">
6618
+ <strong>Esperado:</strong> URLs con query params, fragmentos y rutas complejas se preservan completamente.
6619
+ </p>
6620
+ </div>
6621
+
6622
+ <div class="test-section">
6623
+ <h3>✅ Paréntesis Inteligentes - SOLUCIONADO</h3>
6624
+ <val-text [props]="parenthesesProps"></val-text>
6625
+ <p class="note">
6626
+ <strong>Esperado:</strong> Paréntesis de contexto (texto) vs paréntesis de URL se manejan correctamente.
6627
+ </p>
6628
+ </div>
6629
+
6630
+ <div class="test-section">
6631
+ <h3>✅ Mezcla de Formatos - SOLUCIONADO</h3>
6632
+ <val-text [props]="mixedFormatsProps"></val-text>
6633
+ <p class="note">
6634
+ <strong>Esperado:</strong> Enlaces Markdown, URLs directas y rutas internas coexisten sin conflictos.
6635
+ </p>
6636
+ </div>
6637
+
6638
+ <div class="test-section">
6639
+ <h3>✅ Casos Extremos - SOLUCIONADO</h3>
6640
+ <val-text [props]="edgeCasesProps"></val-text>
6641
+ <p class="note">
6642
+ <strong>Esperado:</strong> Comillas, múltiple puntuación, y URLs al final de oraciones se procesan
6643
+ correctamente.
6644
+ </p>
6645
+ </div>
6646
+
6647
+ <div class="test-section">
6648
+ <h3>✅ URLs de Desarrollo - SOLUCIONADO</h3>
6649
+ <val-text [props]="devUrlsProps"></val-text>
6650
+ <p class="note">
6651
+ <strong>Esperado:</strong> URLs de localhost, puertos, y rutas de desarrollo se detectan correctamente.
6652
+ </p>
6653
+ </div>
6654
+ </div>
6655
+ `, isInline: true, styles: [".comprehensive-test{padding:20px;max-width:1000px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.test-section{margin-bottom:32px;padding:20px;border:2px solid var(--ion-color-success, #2dd36f);border-radius:12px;background:var(--ion-color-success-tint, #42d77d) 10}h2{color:var(--ion-color-primary, #3880ff);margin-bottom:24px;text-align:center}h3{color:var(--ion-color-success, #2dd36f);margin-bottom:16px;font-size:18px;display:flex;align-items:center;gap:8px}.note{margin-top:12px;padding:12px;background:var(--ion-color-light, #f4f5f8);border-radius:8px;font-size:14px;color:var(--ion-color-medium, #92949c);border-left:4px solid var(--ion-color-primary, #3880ff)}.note strong{color:var(--ion-color-dark, #222428)}\n"], dependencies: [{ kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }] }); }
6656
+ }
6657
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ComprehensiveLinkTestComponent, decorators: [{
6658
+ type: Component,
6659
+ args: [{ selector: 'val-comprehensive-link-test', standalone: true, imports: [TextComponent], template: `
6660
+ <div class="comprehensive-test">
6661
+ <h2>Prueba Exhaustiva de Procesamiento de Enlaces</h2>
6662
+
6663
+ <div class="test-section">
6664
+ <h3>✅ Puntuación Final - SOLUCIONADO</h3>
6665
+ <val-text [props]="punctuationProps"></val-text>
6666
+ <p class="note">
6667
+ <strong>Esperado:</strong> Los enlaces no incluyen puntuación final (.,;!?), pero la puntuación se preserva
6668
+ como texto después del enlace.
6669
+ </p>
6670
+ </div>
6671
+
6672
+ <div class="test-section">
6673
+ <h3>✅ URLs Complejas con Parámetros - SOLUCIONADO</h3>
6674
+ <val-text [props]="complexUrlProps"></val-text>
6675
+ <p class="note">
6676
+ <strong>Esperado:</strong> URLs con query params, fragmentos y rutas complejas se preservan completamente.
6677
+ </p>
6678
+ </div>
6679
+
6680
+ <div class="test-section">
6681
+ <h3>✅ Paréntesis Inteligentes - SOLUCIONADO</h3>
6682
+ <val-text [props]="parenthesesProps"></val-text>
6683
+ <p class="note">
6684
+ <strong>Esperado:</strong> Paréntesis de contexto (texto) vs paréntesis de URL se manejan correctamente.
6685
+ </p>
6686
+ </div>
6687
+
6688
+ <div class="test-section">
6689
+ <h3>✅ Mezcla de Formatos - SOLUCIONADO</h3>
6690
+ <val-text [props]="mixedFormatsProps"></val-text>
6691
+ <p class="note">
6692
+ <strong>Esperado:</strong> Enlaces Markdown, URLs directas y rutas internas coexisten sin conflictos.
6693
+ </p>
6694
+ </div>
6695
+
6696
+ <div class="test-section">
6697
+ <h3>✅ Casos Extremos - SOLUCIONADO</h3>
6698
+ <val-text [props]="edgeCasesProps"></val-text>
6699
+ <p class="note">
6700
+ <strong>Esperado:</strong> Comillas, múltiple puntuación, y URLs al final de oraciones se procesan
6701
+ correctamente.
6702
+ </p>
6703
+ </div>
6704
+
6705
+ <div class="test-section">
6706
+ <h3>✅ URLs de Desarrollo - SOLUCIONADO</h3>
6707
+ <val-text [props]="devUrlsProps"></val-text>
6708
+ <p class="note">
6709
+ <strong>Esperado:</strong> URLs de localhost, puertos, y rutas de desarrollo se detectan correctamente.
6710
+ </p>
6711
+ </div>
6712
+ </div>
6713
+ `, styles: [".comprehensive-test{padding:20px;max-width:1000px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.test-section{margin-bottom:32px;padding:20px;border:2px solid var(--ion-color-success, #2dd36f);border-radius:12px;background:var(--ion-color-success-tint, #42d77d) 10}h2{color:var(--ion-color-primary, #3880ff);margin-bottom:24px;text-align:center}h3{color:var(--ion-color-success, #2dd36f);margin-bottom:16px;font-size:18px;display:flex;align-items:center;gap:8px}.note{margin-top:12px;padding:12px;background:var(--ion-color-light, #f4f5f8);border-radius:8px;font-size:14px;color:var(--ion-color-medium, #92949c);border-left:4px solid var(--ion-color-primary, #3880ff)}.note strong{color:var(--ion-color-dark, #222428)}\n"] }]
6714
+ }] });
6715
+
6273
6716
  class CustomContentDemoComponent {
6274
6717
  constructor() {
6275
6718
  this.content = inject(ContentService);
@@ -6316,7 +6759,7 @@ class CustomContentDemoComponent {
6316
6759
  this.diagnoseConfiguration();
6317
6760
  }
6318
6761
  switchLanguage() {
6319
- const newLang = this.currentLang === 'es' ? LangOption.EN : LangOption.ES;
6762
+ const newLang = this.currentLang === 'es' ? LANGUAGES.EN : LANGUAGES.ES;
6320
6763
  this.content.setLang(newLang);
6321
6764
  // Actualizar textos síncronos después del cambio
6322
6765
  setTimeout(() => {
@@ -6654,7 +7097,7 @@ class LinkProcessingExampleComponent {
6654
7097
  },
6655
7098
  };
6656
7099
  this.punctuationTestProps = {
6657
- content: 'URLs con puntuación: https://ionicframework.com/docs, revisa https://angular.io! También https://github.com/angular? Y finalmente https://typescript.org. ¡Todos deben funcionar correctamente!',
7100
+ content: 'URLs con puntuación final: https://ionicframework.com/docs, también https://angular.io! Pregunta sobre https://github.com/angular? Y punto final: https://typescript.org. Paréntesis (https://rxjs.dev) y comillas "https://zone.js". ¡Todos funcionan!',
6658
7101
  size: 'medium',
6659
7102
  color: 'dark',
6660
7103
  bold: false,
@@ -6665,6 +7108,18 @@ class LinkProcessingExampleComponent {
6665
7108
  externalLinkClass: 'external-punct',
6666
7109
  },
6667
7110
  };
7111
+ this.complexUrlsProps = {
7112
+ content: 'URLs complejas: https://example.com/path?param=value&other=123#section, búsqueda en https://google.com/search?q=angular+components, y API https://api.github.com/repos/owner/repo/issues?state=open. Todos con parámetros y fragmentos.',
7113
+ size: 'medium',
7114
+ color: 'dark',
7115
+ bold: false,
7116
+ processLinks: true,
7117
+ linkConfig: {
7118
+ openExternalInNewTab: true,
7119
+ linkClass: 'complex-url',
7120
+ externalLinkClass: 'complex-external',
7121
+ },
7122
+ };
6668
7123
  }
6669
7124
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LinkProcessingExampleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6670
7125
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: LinkProcessingExampleComponent, isStandalone: true, selector: "val-link-processing-example", ngImport: i0, template: `
@@ -6710,8 +7165,13 @@ class LinkProcessingExampleComponent {
6710
7165
  <h3>Corrección de puntuación en URLs:</h3>
6711
7166
  <val-text [props]="punctuationTestProps"></val-text>
6712
7167
  </div>
7168
+
7169
+ <div class="example-section">
7170
+ <h3>URLs complejas con parámetros y fragmentos:</h3>
7171
+ <val-text [props]="complexUrlsProps"></val-text>
7172
+ </div>
6713
7173
  </div>
6714
- `, isInline: true, styles: [".link-examples{padding:20px;max-width:800px}.example-section{margin-bottom:24px;padding:16px;border:1px solid var(--ion-color-light, #f4f5f8);border-radius:8px;background:var(--ion-color-light-tint, #f5f6f9)}h2{color:var(--ion-color-primary, #3880ff);margin-bottom:20px}h3{color:var(--ion-color-dark, #222428);margin-bottom:10px;font-size:16px}\n"], dependencies: [{ kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }] }); }
7174
+ `, isInline: true, styles: [".example-section{margin-bottom:24px;padding:16px;border:1px solid var(--ion-color-light, #f4f5f8);border-radius:8px;background:var(--ion-color-light-tint, #f5f6f9)}h2{color:var(--ion-color-primary, #3880ff);margin-bottom:20px}h3{color:var(--ion-color-dark, #222428);margin-bottom:10px;font-size:16px}\n"], dependencies: [{ kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }] }); }
6715
7175
  }
6716
7176
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LinkProcessingExampleComponent, decorators: [{
6717
7177
  type: Component,
@@ -6758,10 +7218,310 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
6758
7218
  <h3>Corrección de puntuación en URLs:</h3>
6759
7219
  <val-text [props]="punctuationTestProps"></val-text>
6760
7220
  </div>
7221
+
7222
+ <div class="example-section">
7223
+ <h3>URLs complejas con parámetros y fragmentos:</h3>
7224
+ <val-text [props]="complexUrlsProps"></val-text>
7225
+ </div>
6761
7226
  </div>
6762
- `, styles: [".link-examples{padding:20px;max-width:800px}.example-section{margin-bottom:24px;padding:16px;border:1px solid var(--ion-color-light, #f4f5f8);border-radius:8px;background:var(--ion-color-light-tint, #f5f6f9)}h2{color:var(--ion-color-primary, #3880ff);margin-bottom:20px}h3{color:var(--ion-color-dark, #222428);margin-bottom:10px;font-size:16px}\n"] }]
7227
+ `, styles: [".example-section{margin-bottom:24px;padding:16px;border:1px solid var(--ion-color-light, #f4f5f8);border-radius:8px;background:var(--ion-color-light-tint, #f5f6f9)}h2{color:var(--ion-color-primary, #3880ff);margin-bottom:20px}h3{color:var(--ion-color-dark, #222428);margin-bottom:10px;font-size:16px}\n"] }]
6763
7228
  }] });
6764
7229
 
7230
+ /**
7231
+ * MultiLanguageDemoComponent - Demuestra el sistema de idiomas flexible.
7232
+ *
7233
+ * Este componente muestra cómo el sistema maneja múltiples idiomas,
7234
+ * fallbacks automáticos y warnings por traducciones faltantes.
7235
+ */
7236
+ class MultiLanguageDemoComponent {
7237
+ constructor(content, langService) {
7238
+ this.content = content;
7239
+ this.langService = langService;
7240
+ this.availableLanguages = [];
7241
+ this.analysisResults = null;
7242
+ // Global content observables
7243
+ this.okButton$ = this.content.fromContent({ key: 'ok' });
7244
+ this.cancelButton$ = this.content.fromContent({ key: 'cancel' });
7245
+ this.saveButton$ = this.content.fromContent({ key: 'save' });
7246
+ this.deleteButton$ = this.content.fromContent({ key: 'delete' });
7247
+ // Content that might have missing translations
7248
+ this.nextButton$ = this.content.fromContent({ key: 'next', fallback: 'Next' });
7249
+ this.finishButton$ = this.content.fromContent({ key: 'finish', fallback: 'Finish' });
7250
+ this.searchPlaceholder$ = this.content.fromContent({ key: 'searchPlaceholder', fallback: 'Search...' });
7251
+ this.noDataMessage$ = this.content.fromContent({ key: 'noData', fallback: 'No data available' });
7252
+ }
7253
+ ngOnInit() {
7254
+ this.availableLanguages = this.langService.availableLangs;
7255
+ }
7256
+ switchLanguage(lang) {
7257
+ console.log(`Switching to language: ${lang}`);
7258
+ this.langService.setLang(lang);
7259
+ this.analysisResults = null; // Reset analysis when language changes
7260
+ }
7261
+ getLanguageName(lang) {
7262
+ const names = {
7263
+ [LANGUAGES.ES]: 'Español',
7264
+ [LANGUAGES.EN]: 'English',
7265
+ [LANGUAGES.FR]: 'Français',
7266
+ [LANGUAGES.DE]: 'Deutsch',
7267
+ pt: 'Português',
7268
+ };
7269
+ return names[lang] || lang.toUpperCase();
7270
+ }
7271
+ analyzeCurrentComponent() {
7272
+ const componentName = '_global'; // Analyzing global content for this demo
7273
+ const availableLanguages = this.langService.getAvailableLanguagesForComponent(componentName);
7274
+ const missingKeys = this.langService.getMissingContentKeys(componentName, this.langService.currentLang);
7275
+ this.analysisResults = {
7276
+ availableLanguages,
7277
+ missingKeys,
7278
+ };
7279
+ console.log('Component analysis results:', this.analysisResults);
7280
+ }
7281
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MultiLanguageDemoComponent, deps: [{ token: ContentService }, { token: LangService }], target: i0.ɵɵFactoryTarget.Component }); }
7282
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: MultiLanguageDemoComponent, isStandalone: true, selector: "val-multi-language-demo", ngImport: i0, template: `
7283
+ <div class="multi-lang-demo">
7284
+ <h2>Sistema de Idiomas Flexible</h2>
7285
+
7286
+ <div class="language-info">
7287
+ <h3>Información del Sistema:</h3>
7288
+ <p><strong>Idioma actual:</strong> {{ langService.currentLang }}</p>
7289
+ <p><strong>Idioma por defecto:</strong> {{ langService.defaultLanguage }}</p>
7290
+ <p><strong>Idiomas disponibles:</strong> {{ langService.availableLangs.join(', ') }}</p>
7291
+ </div>
7292
+
7293
+ <div class="language-switcher">
7294
+ <h3>Cambiar Idioma:</h3>
7295
+ <div class="button-group">
7296
+ <button
7297
+ *ngFor="let lang of availableLanguages"
7298
+ [class.active]="lang === langService.currentLang"
7299
+ (click)="switchLanguage(lang)"
7300
+ >
7301
+ {{ getLanguageName(lang) }}
7302
+ </button>
7303
+ <!-- Ejemplo de idioma no disponible -->
7304
+ <button (click)="switchLanguage('pt')">Português (no disponible)</button>
7305
+ </div>
7306
+ </div>
7307
+
7308
+ <div class="content-examples">
7309
+ <h3>Contenido Global:</h3>
7310
+ <div class="example-grid">
7311
+ <val-text
7312
+ [props]="{ content: okButton$ | async, size: 'medium', color: 'dark', bold: false, processLinks: false }"
7313
+ ></val-text>
7314
+ <val-text
7315
+ [props]="{
7316
+ content: cancelButton$ | async,
7317
+ size: 'medium',
7318
+ color: 'dark',
7319
+ bold: false,
7320
+ processLinks: false,
7321
+ }"
7322
+ ></val-text>
7323
+ <val-text
7324
+ [props]="{ content: saveButton$ | async, size: 'medium', color: 'dark', bold: false, processLinks: false }"
7325
+ ></val-text>
7326
+ <val-text
7327
+ [props]="{
7328
+ content: deleteButton$ | async,
7329
+ size: 'medium',
7330
+ color: 'dark',
7331
+ bold: false,
7332
+ processLinks: false,
7333
+ }"
7334
+ ></val-text>
7335
+ </div>
7336
+
7337
+ <h3>Contenido con Fallback (algunas traducciones faltantes):</h3>
7338
+ <div class="example-grid">
7339
+ <val-text
7340
+ [props]="{ content: nextButton$ | async, size: 'medium', color: 'dark', bold: false, processLinks: false }"
7341
+ ></val-text>
7342
+ <val-text
7343
+ [props]="{
7344
+ content: finishButton$ | async,
7345
+ size: 'medium',
7346
+ color: 'dark',
7347
+ bold: false,
7348
+ processLinks: false,
7349
+ }"
7350
+ ></val-text>
7351
+ <val-text
7352
+ [props]="{
7353
+ content: searchPlaceholder$ | async,
7354
+ size: 'medium',
7355
+ color: 'dark',
7356
+ bold: false,
7357
+ processLinks: false,
7358
+ }"
7359
+ ></val-text>
7360
+ <val-text
7361
+ [props]="{
7362
+ content: noDataMessage$ | async,
7363
+ size: 'medium',
7364
+ color: 'dark',
7365
+ bold: false,
7366
+ processLinks: false,
7367
+ }"
7368
+ ></val-text>
7369
+ </div>
7370
+ </div>
7371
+
7372
+ <div class="warning-info">
7373
+ <h3>Información de Warnings:</h3>
7374
+ <p>
7375
+ Abre la consola del navegador para ver los warnings cuando cambies a idiomas con traducciones incompletas
7376
+ (francés, alemán).
7377
+ </p>
7378
+ <p>El sistema automáticamente usará el idioma por defecto o el primer idioma disponible como fallback.</p>
7379
+ </div>
7380
+
7381
+ <div class="component-analysis">
7382
+ <h3>Análisis del Componente:</h3>
7383
+ <button (click)="analyzeCurrentComponent()">Analizar Contenido</button>
7384
+ <div *ngIf="analysisResults" class="analysis-results">
7385
+ <p>
7386
+ <strong>Idiomas disponibles para este componente:</strong>
7387
+ {{ analysisResults.availableLanguages.join(', ') }}
7388
+ </p>
7389
+ <div *ngIf="analysisResults.missingKeys.length > 0">
7390
+ <p>
7391
+ <strong>Claves faltantes en {{ langService.currentLang }}:</strong>
7392
+ </p>
7393
+ <ul>
7394
+ <li *ngFor="let key of analysisResults.missingKeys">{{ key }}</li>
7395
+ </ul>
7396
+ </div>
7397
+ </div>
7398
+ </div>
7399
+ </div>
7400
+ `, isInline: true, styles: [".multi-lang-demo{padding:20px;max-width:800px}.language-info,.content-examples,.warning-info,.component-analysis{margin:20px 0;padding:15px;border:1px solid var(--ion-color-light, #f4f5f8);border-radius:8px;background:var(--ion-color-light-tint, #f5f6f9)}.button-group{display:flex;gap:10px;flex-wrap:wrap}.button-group button{padding:8px 16px;border:1px solid var(--ion-color-primary, #3880ff);background:#fff;color:var(--ion-color-primary, #3880ff);border-radius:4px;cursor:pointer;transition:all .2s}.button-group button:hover{background:var(--ion-color-primary-tint, #4992ff);color:#fff}.button-group button.active{background:var(--ion-color-primary, #3880ff);color:#fff}.example-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;margin-top:10px}.analysis-results{margin-top:10px;padding:10px;background:#fff;border-radius:4px}h2{color:var(--ion-color-primary, #3880ff)}h3{color:var(--ion-color-dark, #222428);margin-bottom:10px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }] }); }
7401
+ }
7402
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MultiLanguageDemoComponent, decorators: [{
7403
+ type: Component,
7404
+ args: [{ selector: 'val-multi-language-demo', standalone: true, imports: [CommonModule, TextComponent, ButtonComponent], template: `
7405
+ <div class="multi-lang-demo">
7406
+ <h2>Sistema de Idiomas Flexible</h2>
7407
+
7408
+ <div class="language-info">
7409
+ <h3>Información del Sistema:</h3>
7410
+ <p><strong>Idioma actual:</strong> {{ langService.currentLang }}</p>
7411
+ <p><strong>Idioma por defecto:</strong> {{ langService.defaultLanguage }}</p>
7412
+ <p><strong>Idiomas disponibles:</strong> {{ langService.availableLangs.join(', ') }}</p>
7413
+ </div>
7414
+
7415
+ <div class="language-switcher">
7416
+ <h3>Cambiar Idioma:</h3>
7417
+ <div class="button-group">
7418
+ <button
7419
+ *ngFor="let lang of availableLanguages"
7420
+ [class.active]="lang === langService.currentLang"
7421
+ (click)="switchLanguage(lang)"
7422
+ >
7423
+ {{ getLanguageName(lang) }}
7424
+ </button>
7425
+ <!-- Ejemplo de idioma no disponible -->
7426
+ <button (click)="switchLanguage('pt')">Português (no disponible)</button>
7427
+ </div>
7428
+ </div>
7429
+
7430
+ <div class="content-examples">
7431
+ <h3>Contenido Global:</h3>
7432
+ <div class="example-grid">
7433
+ <val-text
7434
+ [props]="{ content: okButton$ | async, size: 'medium', color: 'dark', bold: false, processLinks: false }"
7435
+ ></val-text>
7436
+ <val-text
7437
+ [props]="{
7438
+ content: cancelButton$ | async,
7439
+ size: 'medium',
7440
+ color: 'dark',
7441
+ bold: false,
7442
+ processLinks: false,
7443
+ }"
7444
+ ></val-text>
7445
+ <val-text
7446
+ [props]="{ content: saveButton$ | async, size: 'medium', color: 'dark', bold: false, processLinks: false }"
7447
+ ></val-text>
7448
+ <val-text
7449
+ [props]="{
7450
+ content: deleteButton$ | async,
7451
+ size: 'medium',
7452
+ color: 'dark',
7453
+ bold: false,
7454
+ processLinks: false,
7455
+ }"
7456
+ ></val-text>
7457
+ </div>
7458
+
7459
+ <h3>Contenido con Fallback (algunas traducciones faltantes):</h3>
7460
+ <div class="example-grid">
7461
+ <val-text
7462
+ [props]="{ content: nextButton$ | async, size: 'medium', color: 'dark', bold: false, processLinks: false }"
7463
+ ></val-text>
7464
+ <val-text
7465
+ [props]="{
7466
+ content: finishButton$ | async,
7467
+ size: 'medium',
7468
+ color: 'dark',
7469
+ bold: false,
7470
+ processLinks: false,
7471
+ }"
7472
+ ></val-text>
7473
+ <val-text
7474
+ [props]="{
7475
+ content: searchPlaceholder$ | async,
7476
+ size: 'medium',
7477
+ color: 'dark',
7478
+ bold: false,
7479
+ processLinks: false,
7480
+ }"
7481
+ ></val-text>
7482
+ <val-text
7483
+ [props]="{
7484
+ content: noDataMessage$ | async,
7485
+ size: 'medium',
7486
+ color: 'dark',
7487
+ bold: false,
7488
+ processLinks: false,
7489
+ }"
7490
+ ></val-text>
7491
+ </div>
7492
+ </div>
7493
+
7494
+ <div class="warning-info">
7495
+ <h3>Información de Warnings:</h3>
7496
+ <p>
7497
+ Abre la consola del navegador para ver los warnings cuando cambies a idiomas con traducciones incompletas
7498
+ (francés, alemán).
7499
+ </p>
7500
+ <p>El sistema automáticamente usará el idioma por defecto o el primer idioma disponible como fallback.</p>
7501
+ </div>
7502
+
7503
+ <div class="component-analysis">
7504
+ <h3>Análisis del Componente:</h3>
7505
+ <button (click)="analyzeCurrentComponent()">Analizar Contenido</button>
7506
+ <div *ngIf="analysisResults" class="analysis-results">
7507
+ <p>
7508
+ <strong>Idiomas disponibles para este componente:</strong>
7509
+ {{ analysisResults.availableLanguages.join(', ') }}
7510
+ </p>
7511
+ <div *ngIf="analysisResults.missingKeys.length > 0">
7512
+ <p>
7513
+ <strong>Claves faltantes en {{ langService.currentLang }}:</strong>
7514
+ </p>
7515
+ <ul>
7516
+ <li *ngFor="let key of analysisResults.missingKeys">{{ key }}</li>
7517
+ </ul>
7518
+ </div>
7519
+ </div>
7520
+ </div>
7521
+ </div>
7522
+ `, styles: [".multi-lang-demo{padding:20px;max-width:800px}.language-info,.content-examples,.warning-info,.component-analysis{margin:20px 0;padding:15px;border:1px solid var(--ion-color-light, #f4f5f8);border-radius:8px;background:var(--ion-color-light-tint, #f5f6f9)}.button-group{display:flex;gap:10px;flex-wrap:wrap}.button-group button{padding:8px 16px;border:1px solid var(--ion-color-primary, #3880ff);background:#fff;color:var(--ion-color-primary, #3880ff);border-radius:4px;cursor:pointer;transition:all .2s}.button-group button:hover{background:var(--ion-color-primary-tint, #4992ff);color:#fff}.button-group button.active{background:var(--ion-color-primary, #3880ff);color:#fff}.example-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:10px;margin-top:10px}.analysis-results{margin-top:10px;padding:10px;background:#fff;border-radius:4px}h2{color:var(--ion-color-primary, #3880ff)}h3{color:var(--ion-color-dark, #222428);margin-bottom:10px}\n"] }]
7523
+ }], ctorParameters: () => [{ type: ContentService }, { type: LangService }] });
7524
+
6765
7525
  const text = {
6766
7526
  es: {
6767
7527
  spanish: 'Español',
@@ -6777,7 +7537,10 @@ var LangSettings = new TextContent(text);
6777
7537
  /**
6778
7538
  * Global content that can be used across all components.
6779
7539
  * These are common texts like buttons, actions, states, etc.
6780
- * Structure: {es: {key1: 'value1', key2: 'value2'}, en: {key1: 'value1', key2: 'value2'}}
7540
+ * Structure: {es: {key1: 'value1', key2: 'value2'}, en: {key1: 'value1', key2: 'value2'}, fr: {...}}
7541
+ *
7542
+ * Note: You can add any language code. The system will automatically detect available languages
7543
+ * and provide intelligent fallbacks with warnings for missing translations.
6781
7544
  */
6782
7545
  const globalContentData = {
6783
7546
  es: {
@@ -6848,6 +7611,34 @@ const globalContentData = {
6848
7611
  // Common placeholders
6849
7612
  searchPlaceholder: 'Search...',
6850
7613
  },
7614
+ fr: {
7615
+ // Common buttons - Example of partial translation (missing some keys intentionally)
7616
+ ok: 'OK',
7617
+ cancel: 'Annuler',
7618
+ save: 'Sauvegarder',
7619
+ delete: 'Supprimer',
7620
+ edit: 'Modifier',
7621
+ close: 'Fermer',
7622
+ back: 'Retour',
7623
+ // Common states and messages (intentionally incomplete to show fallback behavior)
7624
+ loading: 'Chargement...',
7625
+ error: 'Erreur',
7626
+ success: 'Succès',
7627
+ // Common confirmations
7628
+ areYouSure: 'Êtes-vous sûr?',
7629
+ },
7630
+ de: {
7631
+ // Common buttons - Another example of partial translation
7632
+ ok: 'OK',
7633
+ cancel: 'Abbrechen',
7634
+ save: 'Speichern',
7635
+ delete: 'Löschen',
7636
+ // Common states and messages
7637
+ loading: 'Laden...',
7638
+ error: 'Fehler',
7639
+ // Common confirmations
7640
+ areYouSure: 'Sind Sie sicher?',
7641
+ },
6851
7642
  };
6852
7643
  const GlobalContent = new TextContent(globalContentData);
6853
7644
  const content = {
@@ -6910,5 +7701,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
6910
7701
  * Generated bundle index. Do not edit.
6911
7702
  */
6912
7703
 
6913
- export { ActionType, AlertBoxComponent, AvatarComponent, BannerComponent, BaseDefault, BoxComponent, ButtonComponent, ButtonGroupComponent, CardComponent, CardSection, CardType, CheckInputComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CommentInputComponent, ComponentStates, ContentLoaderComponent, ContentService, CustomContentDemoComponent, DateInputComponent, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FileInputComponent, FooterComponent, FormComponent, FormFooterComponent, GlobalContent, HeaderComponent, HintComponent, HourInputComponent, HrefComponent, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InputType, ItemListComponent, LangOption, LangService, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessingExampleComponent, LinkProcessorService, LinksCakeComponent, LocalStorageService, MOTION, NavigationService, NoContentComponent, NotesBoxComponent, NumberInputComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PasswordInputComponent, PinInputComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProgressBarComponent, ProgressStatusComponent, PrompterComponent, RadioInputComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SelectSearchComponent, SimpleComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, TextComponent, TextContent, TextInputComponent, ThemeOption, ThemeService, TitleBlockComponent, TitleComponent, ToastService, ToolbarActionType, ToolbarComponent, ValtechConfigService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, content, createContentHelper, createTextProps, fromContent, fromContentWithInterpolation, fromMultipleContent, globalContentData, goToTop, interpolateContent, isAtEnd, maxLength, replaceSpecialChars, resolveColor, resolveInputDefaultValue };
7704
+ export { ActionType, AlertBoxComponent, AvatarComponent, BannerComponent, BaseDefault, BoxComponent, ButtonComponent, ButtonGroupComponent, CardComponent, CardSection, CardType, CheckInputComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CommentInputComponent, ComponentStates, ComprehensiveLinkTestComponent, ContentLoaderComponent, ContentService, CustomContentDemoComponent, DateInputComponent, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FileInputComponent, FooterComponent, FormComponent, FormFooterComponent, GlobalContent, HeaderComponent, HintComponent, HourInputComponent, HrefComponent, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InputType, ItemListComponent, LANGUAGES, LangService, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessingExampleComponent, LinkProcessorService, LinksCakeComponent, LocalStorageService, MOTION, MultiLanguageDemoComponent, NavigationService, NoContentComponent, NotesBoxComponent, NumberInputComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PasswordInputComponent, PinInputComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProgressBarComponent, ProgressStatusComponent, PrompterComponent, RadioInputComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SelectSearchComponent, SimpleComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, TextComponent, TextContent, TextInputComponent, ThemeOption, ThemeService, TitleBlockComponent, TitleComponent, ToastService, ToolbarActionType, ToolbarComponent, ValtechConfigService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, content, createContentHelper, createTextProps, fromContent, fromContentWithInterpolation, fromMultipleContent, globalContentData, goToTop, interpolateContent, isAtEnd, maxLength, replaceSpecialChars, resolveColor, resolveInputDefaultValue };
6914
7705
  //# sourceMappingURL=valtech-components.mjs.map