@salesforce/ui-bundle-template-feature-angular-language-switcher 12.5.4

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.
Files changed (253) hide show
  1. package/LICENSE.txt +82 -0
  2. package/README.md +193 -0
  3. package/dist/.forceignore +15 -0
  4. package/dist/.husky/pre-commit +4 -0
  5. package/dist/.prettierignore +11 -0
  6. package/dist/.prettierrc +17 -0
  7. package/dist/CHANGELOG.md +5238 -0
  8. package/dist/README.md +77 -0
  9. package/dist/config/project-scratch-def.json +13 -0
  10. package/dist/eslint.config.js +7 -0
  11. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/.forceignore +15 -0
  12. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/.graphqlrc.yml +2 -0
  13. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/.postcssrc.json +5 -0
  14. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/.prettierignore +8 -0
  15. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/.prettierrc +12 -0
  16. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/CHANGELOG.md +4 -0
  17. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/README.md +85 -0
  18. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/angular.json +98 -0
  19. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/codegen.yml +95 -0
  20. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/components.json +6 -0
  21. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/e2e/app.spec.ts +15 -0
  22. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/esbuild/api-version.mjs +17 -0
  23. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/esbuild/graphql-codegen.mjs +13 -0
  24. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/eslint-plugin-angular-graphql.js +148 -0
  25. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/eslint.config.js +76 -0
  26. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/feature-angular-language-switcher.uibundle-meta.xml +7 -0
  27. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/middleware/html.mjs +3 -0
  28. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/middleware/proxy.mjs +12 -0
  29. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/package.json +69 -0
  30. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/playwright.config.ts +25 -0
  31. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/public/favicon.ico +0 -0
  32. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/scripts/get-graphql-schema.mjs +69 -0
  33. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/scripts/rewrite-e2e-assets.mjs +30 -0
  34. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/api/data-client.service.spec.ts +184 -0
  35. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/api/data-client.service.ts +74 -0
  36. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/app.config.ts +20 -0
  37. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/app.html +1 -0
  38. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/app.routes.ts +26 -0
  39. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/app.spec.ts +18 -0
  40. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/app.ts +15 -0
  41. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/layout/app-layout/app-layout.html +51 -0
  42. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/layout/app-layout/app-layout.spec.ts +63 -0
  43. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/layout/app-layout/app-layout.ts +27 -0
  44. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/alert/alert.html +27 -0
  45. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/alert/alert.ts +41 -0
  46. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/button/button.html +27 -0
  47. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/button/button.ts +66 -0
  48. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/card/card.ts +100 -0
  49. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/collapsible/collapsible.html +38 -0
  50. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/collapsible/collapsible.ts +21 -0
  51. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/date-picker/date-picker.html +20 -0
  52. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/date-picker/date-picker.ts +49 -0
  53. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/date-range-picker/date-range-picker.html +20 -0
  54. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/date-range-picker/date-range-picker.ts +69 -0
  55. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/dialog/dialog.html +20 -0
  56. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/dialog/dialog.ts +40 -0
  57. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/dropdown-menu/dropdown-menu.html +20 -0
  58. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/dropdown-menu/dropdown-menu.ts +100 -0
  59. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/field/field.html +15 -0
  60. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/field/field.ts +28 -0
  61. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/field-appearance.ts +8 -0
  62. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/field-size.ts +8 -0
  63. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/icon/icon.ts +68 -0
  64. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/input/input.html +34 -0
  65. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/input/input.ts +79 -0
  66. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/label/label.ts +21 -0
  67. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/paginator/paginator.html +74 -0
  68. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/paginator/paginator.ts +93 -0
  69. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/paginator/types.ts +13 -0
  70. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/popover/popover.html +13 -0
  71. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/popover/popover.ts +37 -0
  72. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/select/select.html +36 -0
  73. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/select/select.ts +63 -0
  74. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/separator/separator.ts +31 -0
  75. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/skeleton/skeleton.ts +20 -0
  76. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/components/ui/spinner/spinner.ts +28 -0
  77. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/features/language-switcher/__examples__/html-middleware-site-example.ts +41 -0
  78. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/features/language-switcher/__examples__/language-switcher-example.ts +41 -0
  79. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/features/language-switcher/build-language-url.ts +158 -0
  80. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/features/language-switcher/index.ts +28 -0
  81. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/features/language-switcher/language-switcher.html +22 -0
  82. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/features/language-switcher/language-switcher.ts +110 -0
  83. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/features/language-switcher/languages.ts +46 -0
  84. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/pages/home/home.html +6 -0
  85. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/pages/home/home.spec.ts +23 -0
  86. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/pages/home/home.ts +13 -0
  87. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/pages/not-found/not-found.html +12 -0
  88. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/pages/not-found/not-found.spec.ts +33 -0
  89. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/pages/not-found/not-found.ts +15 -0
  90. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/alert/hlm-alert-action.ts +14 -0
  91. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/alert/hlm-alert-description.ts +17 -0
  92. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/alert/hlm-alert-title.ts +17 -0
  93. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/alert/hlm-alert.ts +35 -0
  94. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/alert/index.ts +16 -0
  95. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/button/hlm-button.token.ts +22 -0
  96. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/button/hlm-button.ts +72 -0
  97. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/button/index.ts +6 -0
  98. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/calendar/hlm-calendar-multi.ts +195 -0
  99. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/calendar/hlm-calendar-range.ts +194 -0
  100. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/calendar/hlm-calendar.ts +189 -0
  101. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/calendar/hlm-month-year-calendar.ts +119 -0
  102. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/calendar/index.ts +16 -0
  103. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card-action.ts +12 -0
  104. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card-avatar.ts +14 -0
  105. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card-content.ts +12 -0
  106. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card-description.ts +12 -0
  107. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card-footer.ts +12 -0
  108. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card-header.ts +15 -0
  109. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card-title.ts +12 -0
  110. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card.token.ts +19 -0
  111. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/hlm-card.ts +22 -0
  112. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/card/index.ts +28 -0
  113. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/collapsible/hlm-collapsible-content.ts +14 -0
  114. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/collapsible/hlm-collapsible-trigger.ts +9 -0
  115. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/collapsible/hlm-collapsible.ts +15 -0
  116. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/collapsible/index.ts +13 -0
  117. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-multi-input.ts +104 -0
  118. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-picker-anchor.ts +23 -0
  119. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-picker-input.ts +118 -0
  120. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-picker-multi.token.ts +109 -0
  121. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-picker-multi.ts +220 -0
  122. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-picker-trigger.ts +104 -0
  123. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-picker.token.ts +69 -0
  124. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-picker.ts +203 -0
  125. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-range-input.ts +120 -0
  126. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-range-picker.token.ts +86 -0
  127. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-date-range-picker.ts +252 -0
  128. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-month-year-input.ts +105 -0
  129. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-month-year-picker.token.ts +89 -0
  130. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/hlm-month-year-picker.ts +196 -0
  131. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/date-picker/index.ts +38 -0
  132. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-close.ts +9 -0
  133. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-content.ts +74 -0
  134. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-description.ts +17 -0
  135. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-footer.ts +15 -0
  136. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-header.ts +12 -0
  137. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-overlay.ts +27 -0
  138. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-portal.ts +8 -0
  139. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-title.ts +14 -0
  140. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog-trigger.ts +14 -0
  141. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog.service.ts +46 -0
  142. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/hlm-dialog.ts +24 -0
  143. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dialog/index.ts +35 -0
  144. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-checkbox-indicator.ts +21 -0
  145. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-checkbox.ts +54 -0
  146. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-focus-on-hover.ts +35 -0
  147. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-group.ts +14 -0
  148. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-item-sub-indicator.ts +19 -0
  149. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-item.ts +42 -0
  150. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-label.ts +20 -0
  151. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-radio-indicator.ts +21 -0
  152. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-radio.ts +49 -0
  153. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-separator.ts +12 -0
  154. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-shortcut.ts +15 -0
  155. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-sub-trigger.ts +52 -0
  156. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-sub.ts +53 -0
  157. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-token.ts +26 -0
  158. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu-trigger.ts +46 -0
  159. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/hlm-dropdown-menu.ts +57 -0
  160. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/dropdown-menu/index.ts +49 -0
  161. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-content.ts +12 -0
  162. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-description.ts +53 -0
  163. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-error.ts +100 -0
  164. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-group.ts +15 -0
  165. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-label.ts +17 -0
  166. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-legend.ts +19 -0
  167. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-separator.ts +24 -0
  168. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-set.ts +15 -0
  169. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field-title.ts +15 -0
  170. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/hlm-field.ts +47 -0
  171. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/field/index.ts +34 -0
  172. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/input/hlm-input.ts +21 -0
  173. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/input/index.ts +5 -0
  174. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/input-group/hlm-input-group-addon.ts +40 -0
  175. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/input-group/hlm-input-group-button.ts +47 -0
  176. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/input-group/hlm-input-group-input.ts +17 -0
  177. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/input-group/hlm-input-group-text.ts +14 -0
  178. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/input-group/hlm-input-group.ts +18 -0
  179. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/input-group/index.ts +19 -0
  180. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/label/hlm-label.ts +17 -0
  181. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/label/index.ts +5 -0
  182. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/label/label.html +11 -0
  183. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-numbered-pagination-query-params.ts +191 -0
  184. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-numbered-pagination.ts +290 -0
  185. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-pagination-content.ts +12 -0
  186. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-pagination-ellipsis.ts +27 -0
  187. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-pagination-item.ts +7 -0
  188. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-pagination-link.ts +51 -0
  189. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-pagination-next.ts +65 -0
  190. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-pagination-previous.ts +65 -0
  191. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/hlm-pagination.ts +19 -0
  192. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/pagination/index.ts +31 -0
  193. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/popover/hlm-popover-content.ts +25 -0
  194. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/popover/hlm-popover-description.ts +12 -0
  195. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/popover/hlm-popover-header.ts +12 -0
  196. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/popover/hlm-popover-portal.ts +8 -0
  197. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/popover/hlm-popover-title.ts +12 -0
  198. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/popover/hlm-popover-trigger.ts +14 -0
  199. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/popover/hlm-popover.ts +23 -0
  200. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/popover/index.ts +25 -0
  201. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-content.ts +44 -0
  202. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-group.ts +14 -0
  203. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-item.ts +36 -0
  204. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-label.ts +14 -0
  205. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-multiple.ts +37 -0
  206. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-placeholder.ts +17 -0
  207. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-portal.ts +8 -0
  208. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-scroll-down.ts +22 -0
  209. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-scroll-up.ts +22 -0
  210. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-separator.ts +14 -0
  211. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-trigger.ts +59 -0
  212. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-value-template.ts +5 -0
  213. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-value.ts +18 -0
  214. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-values-content.ts +9 -0
  215. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select-values.ts +5 -0
  216. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/hlm-select.ts +37 -0
  217. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/select/index.ts +52 -0
  218. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/separator/hlm-separator.ts +19 -0
  219. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/separator/index.ts +5 -0
  220. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/skeleton/hlm-skeleton.ts +14 -0
  221. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/skeleton/index.ts +5 -0
  222. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/spinner/hlm-spinner.ts +26 -0
  223. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/spinner/index.ts +5 -0
  224. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/utils/hlm.ts +313 -0
  225. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/utils/index.ts +2 -0
  226. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/shared/utils/provide-spartan-hlm.ts +34 -0
  227. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/app/utils/async-data.ts +99 -0
  228. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/index.html +13 -0
  229. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/main.ts +5 -0
  230. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/styles.css +139 -0
  231. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/src/types/sf-globals.d.ts +23 -0
  232. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/tsconfig.app.json +11 -0
  233. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/tsconfig.json +36 -0
  234. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/tsconfig.spec.json +10 -0
  235. package/dist/force-app/main/default/uiBundles/feature-angular-language-switcher/ui-bundle.json +7 -0
  236. package/dist/jest.config.js +6 -0
  237. package/dist/package-lock.json +12554 -0
  238. package/dist/package.json +46 -0
  239. package/dist/scripts/apex/hello.apex +10 -0
  240. package/dist/scripts/gitignore-templates.json +4 -0
  241. package/dist/scripts/graphql-search.sh +191 -0
  242. package/dist/scripts/org-setup-config-schema.mjs +132 -0
  243. package/dist/scripts/org-setup-dev.mjs +114 -0
  244. package/dist/scripts/org-setup-url.mjs +108 -0
  245. package/dist/scripts/org-setup-utils.mjs +569 -0
  246. package/dist/scripts/org-setup-xml.mjs +278 -0
  247. package/dist/scripts/org-setup.config.json +5 -0
  248. package/dist/scripts/org-setup.mjs +2537 -0
  249. package/dist/scripts/sf-project-setup.mjs +103 -0
  250. package/dist/scripts/soql/account.soql +6 -0
  251. package/dist/scripts/validate-org-setup-config.mjs +38 -0
  252. package/dist/sfdx-project.json +12 -0
  253. package/package.json +46 -0
@@ -0,0 +1,36 @@
1
+ @if (label()) {
2
+ <label hlmLabel [for]="labelId" class="mb-1.5 block">{{ label() }}</label>
3
+ }
4
+ @if (!multiple()) {
5
+ <hlm-select
6
+ [value]="value()"
7
+ (valueChange)="onSingleChange($event)"
8
+ [disabled]="disabled()"
9
+ [itemToString]="itemToString"
10
+ >
11
+ <hlm-select-trigger [buttonId]="labelId" [size]="size()" [class]="triggerClasses()">
12
+ <hlm-select-value [placeholder]="placeholder()" />
13
+ </hlm-select-trigger>
14
+ <hlm-select-content *hlmSelectPortal>
15
+ @for (option of options(); track option.value) {
16
+ <hlm-select-item [value]="option.value">{{ option.label }}</hlm-select-item>
17
+ }
18
+ </hlm-select-content>
19
+ </hlm-select>
20
+ } @else {
21
+ <hlm-select-multiple
22
+ [value]="$any(values())"
23
+ (valueChange)="onMultiChange($event)"
24
+ [disabled]="disabled()"
25
+ [itemToString]="itemToString"
26
+ >
27
+ <hlm-select-trigger [buttonId]="labelId" [size]="size()" [class]="triggerClasses()">
28
+ <hlm-select-value [placeholder]="placeholder()" />
29
+ </hlm-select-trigger>
30
+ <hlm-select-content *hlmSelectPortal>
31
+ @for (option of options(); track option.value) {
32
+ <hlm-select-item [value]="option.value">{{ option.label }}</hlm-select-item>
33
+ }
34
+ </hlm-select-content>
35
+ </hlm-select-multiple>
36
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import {
7
+ ChangeDetectionStrategy,
8
+ Component,
9
+ booleanAttribute,
10
+ computed,
11
+ input,
12
+ model,
13
+ } from '@angular/core';
14
+ import { HlmSelectImports } from '@spartan-ng/styles/select';
15
+ import { HlmLabelImports } from '@spartan-ng/styles/label';
16
+ import type { AppFieldSize } from '../field-size';
17
+ import type { AppFieldAppearance } from '../field-appearance';
18
+
19
+ export interface AppSelectOption {
20
+ value: string;
21
+ label: string;
22
+ }
23
+
24
+ @Component({
25
+ selector: 'app-select',
26
+ changeDetection: ChangeDetectionStrategy.OnPush,
27
+ imports: [HlmSelectImports, HlmLabelImports],
28
+ host: { class: 'block min-w-0' },
29
+ templateUrl: './select.html',
30
+ })
31
+ export class SelectComponent {
32
+ readonly value = model<string>('');
33
+ readonly values = model<readonly string[]>([]);
34
+ readonly multiple = input(false, { transform: booleanAttribute });
35
+ readonly options = input<readonly AppSelectOption[]>([]);
36
+ readonly placeholder = input<string>('');
37
+ readonly label = input<string>('');
38
+ readonly disabled = input(false, { transform: booleanAttribute });
39
+ readonly appearance = input<AppFieldAppearance>('outline');
40
+ readonly size = input<AppFieldSize>('default');
41
+
42
+ private static _uid = 0;
43
+ protected readonly labelId = `app-select-${SelectComponent._uid++}`;
44
+
45
+ protected readonly itemToString = (value: string): string => {
46
+ return this.options().find((o) => o.value === value)?.label ?? value;
47
+ };
48
+
49
+ protected readonly triggerClasses = computed(() => {
50
+ if (this.appearance() === 'fill') return 'border-transparent bg-muted';
51
+ return '';
52
+ });
53
+
54
+ protected onSingleChange(event: string | null | undefined): void {
55
+ this.value.set(event ?? '');
56
+ }
57
+
58
+ protected onMultiChange(event: string[] | readonly string[] | null | undefined): void {
59
+ this.values.set(event ?? []);
60
+ }
61
+ }
62
+
63
+ export const SelectImports = [SelectComponent] as const;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { booleanAttribute, ChangeDetectionStrategy, Component, input } from '@angular/core';
7
+ import { BrnSeparator } from '@spartan-ng/brain/separator';
8
+ import { hlmSeparatorClass } from '@spartan-ng/styles/separator';
9
+ import { classes } from '@spartan-ng/styles/utils';
10
+
11
+ export type AppSeparatorOrientation = 'horizontal' | 'vertical';
12
+
13
+ @Component({
14
+ selector: 'app-separator',
15
+ changeDetection: ChangeDetectionStrategy.OnPush,
16
+ hostDirectives: [{ directive: BrnSeparator, inputs: ['orientation', 'decorative'] }],
17
+ host: {
18
+ 'data-slot': 'separator',
19
+ },
20
+ template: '',
21
+ })
22
+ export class SeparatorComponent {
23
+ readonly orientation = input<AppSeparatorOrientation>('horizontal');
24
+ readonly decorative = input(true, { transform: booleanAttribute });
25
+
26
+ constructor() {
27
+ classes(() => hlmSeparatorClass);
28
+ }
29
+ }
30
+
31
+ export const SeparatorImports = [SeparatorComponent] as const;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
7
+ import { HlmSkeleton } from '@spartan-ng/styles/skeleton';
8
+
9
+ @Component({
10
+ selector: 'app-skeleton',
11
+ changeDetection: ChangeDetectionStrategy.OnPush,
12
+ hostDirectives: [HlmSkeleton],
13
+ host: {
14
+ 'aria-hidden': 'true',
15
+ },
16
+ template: '<ng-content />',
17
+ })
18
+ export class SkeletonComponent {}
19
+
20
+ export const SkeletonImports = [SkeletonComponent] as const;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
7
+ import { HlmSpinnerImports } from '@spartan-ng/styles/spinner';
8
+ import { classes } from '@spartan-ng/styles/utils';
9
+
10
+ @Component({
11
+ selector: 'app-spinner',
12
+ imports: [HlmSpinnerImports],
13
+ changeDetection: ChangeDetectionStrategy.OnPush,
14
+ host: {
15
+ role: 'status',
16
+ '[attr.aria-label]': 'ariaLabel()',
17
+ },
18
+ template: `<hlm-spinner />`,
19
+ })
20
+ export class SpinnerComponent {
21
+ public readonly ariaLabel = input<string>('Loading', { alias: 'aria-label' });
22
+
23
+ constructor() {
24
+ classes(() => 'text-[length:--spacing(4)]');
25
+ }
26
+ }
27
+
28
+ export const SpinnerImports = [SpinnerComponent] as const;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /**
8
+ * Example: switching your app's `middleware/html.mjs` to the SITE entry of
9
+ * `@salesforce/angular-plugin-ui-bundle` so language switching works in local dev.
10
+ *
11
+ * This file is illustrative — it is copied into the app as an example, not used
12
+ * directly. Apply the change below to your real `middleware/html.mjs`.
13
+ *
14
+ * WHY: On a deployed Experience site the platform injects `SFDC_ENV.language`
15
+ * and folds the active language into `SFDC_ENV.basePath` (e.g. `/shop/fr`),
16
+ * recomputed from the URL on every load. The generic local Angular dev server
17
+ * does neither — `SFDC_ENV.language` is absent and `basePath` is always `/` — so
18
+ * the switcher can't route a non-default language locally (the reload 404s and
19
+ * the labels never flip). The SITE entry mirrors production for local dev only.
20
+ *
21
+ * HOW: change the import from `createHtmlMiddleware` to `createSiteHtmlMiddleware`
22
+ * (same package), and pass the feature's supported `LANGUAGES` (the single source
23
+ * of truth). The middleware needs the language CODES, so map the `{ code, label }`
24
+ * list to codes. Pass `defaultLanguage` explicitly so the middleware knows which
25
+ * language is served un-prefixed at `/`.
26
+ *
27
+ * This has NO effect on a deployed build; the middleware only runs in local dev.
28
+ */
29
+
30
+ // In your middleware/html.mjs, replace:
31
+ // import { createHtmlMiddleware } from "@salesforce/angular-plugin-ui-bundle";
32
+ // export default await createHtmlMiddleware();
33
+ //
34
+ // with:
35
+ import { createSiteHtmlMiddleware } from "@salesforce/angular-plugin-ui-bundle";
36
+ import { DEFAULT_LANGUAGE, LANGUAGES } from "../languages";
37
+
38
+ export default await createSiteHtmlMiddleware({
39
+ languages: LANGUAGES.map((l) => l.code),
40
+ defaultLanguage: DEFAULT_LANGUAGE,
41
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /**
8
+ * Example: mounting <app-language-switcher /> in an app layout/header.
9
+ *
10
+ * This file is illustrative — it is copied into the app as an example, not wired
11
+ * into routing. To adopt the switcher, add `LanguageSwitcherComponent` to your
12
+ * layout component's `imports` array and place `<app-language-switcher />` in the
13
+ * template wherever your app's chrome lives (typically the nav/header in
14
+ * `app-layout.ts`). It manages its own state from the URL + SFDC_ENV, so no
15
+ * inputs are required.
16
+ */
17
+
18
+ import { Component } from "@angular/core";
19
+ import { RouterOutlet } from "@angular/router";
20
+ import { LanguageSwitcherComponent } from "../index";
21
+
22
+ @Component({
23
+ selector: "app-layout-with-language-switcher",
24
+ standalone: true,
25
+ imports: [RouterOutlet, LanguageSwitcherComponent],
26
+ template: `
27
+ <nav class="bg-white border-b border-gray-200">
28
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
29
+ <div class="flex justify-between items-center h-16">
30
+ <span class="text-xl font-semibold text-gray-900">Angular App</span>
31
+ <div class="flex items-center gap-2">
32
+ <!-- Drop the switcher anywhere in your header/chrome. -->
33
+ <app-language-switcher />
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </nav>
38
+ <router-outlet />
39
+ `,
40
+ })
41
+ export class AppLayoutWithLanguageSwitcherComponent {}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /** Strip leading and trailing slashes from a path fragment. */
8
+ function trimSlashes(value: string): string {
9
+ return value.replace(/^\/+/, "").replace(/\/+$/, "");
10
+ }
11
+
12
+ /**
13
+ * Convert a Salesforce locale code to the form used in the URL path.
14
+ *
15
+ * Locale codes use underscores (e.g. `en_US`), but URL path segments use hyphens
16
+ * (e.g. `en-US`). This maps a code to its URL segment.
17
+ */
18
+ function codeToUrlSegment(code: string): string {
19
+ return code.replace(/_/g, "-");
20
+ }
21
+
22
+ /**
23
+ * Convert a hyphenated language token back to a Salesforce locale code — the
24
+ * inverse of {@link codeToUrlSegment} (e.g. `en-US` → `en_US`). Locale codes
25
+ * never contain hyphens, so this remapping is unambiguous. Used both for URL
26
+ * path segments and for the hyphenated `SFDC_ENV.language` value the platform
27
+ * injects (e.g. `"en-US"`), which share the same hyphen convention.
28
+ */
29
+ export function urlSegmentToCode(segment: string): string {
30
+ return segment.replace(/-/g, "_");
31
+ }
32
+
33
+ /**
34
+ * Recover the true, language-free site root from `basePath`.
35
+ *
36
+ * When a non-default language is active, the platform folds the language segment
37
+ * into `SFDC_ENV.basePath` itself — `/shop` becomes `/shop/fr` — rather than
38
+ * leaving `basePath` as the stable site root with the language living in the path
39
+ * after it. If we took `basePath` at face value, the language segment would be
40
+ * treated as part of the base and never seen as a leading language segment, so
41
+ * switching to the default (which should remove the segment) and switching
42
+ * between non-default languages (which should replace it) would both fail.
43
+ *
44
+ * Strip a single trailing segment when it is a known language (remapped from its
45
+ * URL form back to a locale code), yielding the language-free site root so the
46
+ * rest of this module can treat the leading path segment after it as the language.
47
+ */
48
+ function normalizeBasePath(basePath: string, knownCodes: ReadonlySet<string>): string {
49
+ const segments = trimSlashes(basePath).split("/").filter(Boolean);
50
+ const last = segments[segments.length - 1];
51
+ if (last && knownCodes.has(urlSegmentToCode(last))) {
52
+ segments.pop();
53
+ }
54
+ return segments.join("/");
55
+ }
56
+
57
+ /**
58
+ * Return the path segments that live AFTER `basePath` in `pathname`.
59
+ * Both are compared with leading/trailing slashes normalized.
60
+ */
61
+ function segmentsAfterBase(pathname: string, basePath: string): string[] {
62
+ const base = trimSlashes(basePath);
63
+ const trimmedPath = trimSlashes(pathname);
64
+
65
+ let rest = trimmedPath;
66
+ if (base && (trimmedPath === base || trimmedPath.startsWith(`${base}/`))) {
67
+ rest = trimmedPath.slice(base.length);
68
+ }
69
+ return trimSlashes(rest).split("/").filter(Boolean);
70
+ }
71
+
72
+ /**
73
+ * Read the language encoded in a URL: the first path segment after `basePath`,
74
+ * remapped from its URL form (hyphens) back to a locale code (underscores) and
75
+ * returned only when it is one of `knownCodes`. Returns `undefined` when the
76
+ * leading segment is absent or is not a known language code.
77
+ *
78
+ * @param currentUrl Absolute URL to inspect (e.g. `window.location.href`).
79
+ * @param basePath Site root path (e.g. `SFDC_ENV.basePath`); may be `""`.
80
+ * @param knownCodes Language codes considered valid leading segments.
81
+ * @returns The matched locale code (e.g. `en_US`), or `undefined`.
82
+ */
83
+ export function getLanguageFromUrl(
84
+ currentUrl: string,
85
+ basePath: string,
86
+ knownCodes: ReadonlySet<string>,
87
+ ): string | undefined {
88
+ const { pathname } = new URL(currentUrl);
89
+ const siteRoot = normalizeBasePath(basePath, knownCodes);
90
+ const [first] = segmentsAfterBase(pathname, siteRoot);
91
+ if (!first) {
92
+ return undefined;
93
+ }
94
+ const code = urlSegmentToCode(first);
95
+ return knownCodes.has(code) ? code : undefined;
96
+ }
97
+
98
+ /**
99
+ * Rewrite `currentUrl` so `language` is reflected in the first path segment after
100
+ * `basePath`. The rest of the path, the query string, and the hash are preserved.
101
+ *
102
+ * The leading segment after `basePath` is examined and remapped from its URL form
103
+ * (hyphens) back to a locale code before being compared against `knownCodes`:
104
+ *
105
+ * - **Default language** (`language === defaultLanguage`): the default is the
106
+ * implicit, un-prefixed language, so NO segment is written. An existing
107
+ * known-language leading segment is REMOVED (`/shop/fr/page` → `/shop/page`);
108
+ * if there is none, the path is left unchanged.
109
+ * - **Non-default language**: an existing known-language leading segment is
110
+ * REPLACED — so switching never stacks segments (`/en-US/fr/page`) — otherwise
111
+ * the language is INSERTED as a new first segment. The written segment uses the
112
+ * URL form of the code (underscores → hyphens, e.g. `en_US` → `en-US`).
113
+ *
114
+ * When a non-default language is active the platform folds that language into
115
+ * `basePath` itself (`/shop` → `/shop/fr`); a trailing known-language segment is
116
+ * stripped from `basePath` first (see {@link normalizeBasePath}) so the leading
117
+ * segment after the true site root is always what gets replaced/removed/inserted.
118
+ *
119
+ * `basePath` is matched with trailing slashes normalized, and the result never
120
+ * contains double slashes.
121
+ *
122
+ * @param currentUrl Absolute URL to rewrite (e.g. `window.location.href`).
123
+ * @param basePath Site root path (e.g. `SFDC_ENV.basePath`); may be `""`.
124
+ * @param language Locale code to reflect after `basePath` (e.g. `en_US`).
125
+ * @param knownCodes Language codes eligible for the leading-segment check.
126
+ * @param defaultLanguage Locale code that is served without a URL segment.
127
+ * @returns The rewritten absolute URL as a string.
128
+ */
129
+ export function buildLanguageUrl(
130
+ currentUrl: string,
131
+ basePath: string,
132
+ language: string,
133
+ knownCodes: ReadonlySet<string>,
134
+ defaultLanguage: string,
135
+ ): string {
136
+ const url = new URL(currentUrl);
137
+
138
+ const base = normalizeBasePath(basePath, knownCodes);
139
+ const segments = segmentsAfterBase(url.pathname, base);
140
+ const leadingIsLanguage = segments.length > 0 && knownCodes.has(urlSegmentToCode(segments[0]));
141
+
142
+ if (language === defaultLanguage) {
143
+ // The default language is served un-prefixed. Drop an existing language
144
+ // segment so switching back to default removes it, rather than writing one.
145
+ if (leadingIsLanguage) {
146
+ segments.shift();
147
+ }
148
+ } else if (leadingIsLanguage) {
149
+ segments[0] = codeToUrlSegment(language);
150
+ } else {
151
+ segments.unshift(codeToUrlSegment(language));
152
+ }
153
+
154
+ const rebuiltPath = [base, ...segments].filter(Boolean).join("/");
155
+ url.pathname = `/${rebuiltPath}`;
156
+
157
+ return url.toString();
158
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /**
8
+ * Public API for the Angular language switcher feature.
9
+ *
10
+ * Most apps only need:
11
+ * ```typescript
12
+ * import { LanguageSwitcherComponent } from ".../features/language-switcher";
13
+ * // Add to component imports: [LanguageSwitcherComponent]
14
+ * // Use in template: <app-language-switcher />
15
+ * ```
16
+ *
17
+ * Lower-level pieces are exported for custom UIs and testing:
18
+ * - {@link getCurrentLanguage} to read the active language
19
+ * - {@link buildLanguageUrl} to compute the rewritten URL yourself
20
+ * - {@link LANGUAGES} / {@link DEFAULT_LANGUAGE} for the supported set
21
+ */
22
+
23
+ export { LanguageSwitcherComponent, getCurrentLanguage } from "./language-switcher";
24
+
25
+ export { buildLanguageUrl, getLanguageFromUrl, urlSegmentToCode } from "./build-language-url";
26
+
27
+ export { LANGUAGES, DEFAULT_LANGUAGE, LANGUAGE_CODES } from "./languages";
28
+ export type { LanguageOption } from "./languages";
@@ -0,0 +1,22 @@
1
+ @if (hasMultipleLanguages) {
2
+ <app-dropdown-menu align="end" [aria-label]="accessibleLabel()" [triggerClass]="triggerClass">
3
+ <span trigger class="inline-flex items-center gap-1.5">
4
+ <app-icon name="globe" size="16" class="inline-flex" />
5
+ <span>{{ currentLanguageLabel }}</span>
6
+ <app-icon name="chevronDown" size="14" class="inline-flex opacity-50" />
7
+ </span>
8
+
9
+ <ng-template appDropdownMenuContent>
10
+ @for (language of languages; track language.code) {
11
+ <button appDropdownMenuItem type="button" (triggered)="switchLanguage(language.code)">
12
+ @if (language.code === currentLanguage) {
13
+ <app-icon name="check" size="14" class="mr-2" />
14
+ } @else {
15
+ <span class="mr-2 inline-block size-3.5"></span>
16
+ }
17
+ <span>{{ language.label }}</span>
18
+ </button>
19
+ }
20
+ </ng-template>
21
+ </app-dropdown-menu>
22
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /**
8
+ * Drop-in language switcher for B2X (Experience) Angular apps.
9
+ *
10
+ * Renders a dropdown menu of the site's supported {@link LANGUAGES} using the
11
+ * shared `app-dropdown-menu` component. Choosing a language rewrites the current
12
+ * URL so the chosen code becomes the first path segment after `basePath` (see
13
+ * {@link buildLanguageUrl}) and reloads the page via `window.location.assign`.
14
+ * A full reload lets the platform serve the correctly localized content — this
15
+ * component owns only the URL rewrite.
16
+ */
17
+
18
+ import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core";
19
+
20
+ import { DropdownMenuImports } from "../../components/ui/dropdown-menu/dropdown-menu";
21
+ import { IconComponent } from "../../components/ui/icon/icon";
22
+ import { buildLanguageUrl, getLanguageFromUrl, urlSegmentToCode } from "./build-language-url";
23
+ import { DEFAULT_LANGUAGE, LANGUAGE_CODES, LANGUAGES } from "./languages";
24
+
25
+ /**
26
+ * The subset of `SFDC_ENV` this component reads. We type it structurally here
27
+ * (rather than relying on the ambient `SfdcEnv`) so the feature type-checks in
28
+ * isolation: its ambient augmentation intentionally declares only `language?`,
29
+ * while `basePath` is provided by the platform types once composed into an app.
30
+ */
31
+ interface SfdcEnvSubset {
32
+ basePath?: string;
33
+ /** Page locale in hyphenated form, e.g. "en-US" (the platform's injected form). */
34
+ language?: string;
35
+ }
36
+
37
+ /** Read `SFDC_ENV` off the global scope without assuming it exists. */
38
+ function getSfdcEnv(): SfdcEnvSubset | undefined {
39
+ return (globalThis as { SFDC_ENV?: SfdcEnvSubset }).SFDC_ENV;
40
+ }
41
+
42
+ /**
43
+ * Resolve the active language, in precedence order:
44
+ *
45
+ * 1. `SFDC_ENV.language` — authoritative when the platform provides it. The
46
+ * platform injects this in hyphenated form (e.g. `"en-US"`), so it is
47
+ * remapped back to a locale code (`en_US`) before matching, mirroring how the
48
+ * URL segment is handled.
49
+ * 2. The leading URL path segment after `basePath`, when it is a supported
50
+ * language code — reflects the page the user is actually viewing (e.g. a
51
+ * direct hit on `/shop/fr/catalog`) even before the runtime populates
52
+ * `SFDC_ENV.language`.
53
+ * 3. {@link DEFAULT_LANGUAGE}.
54
+ *
55
+ * Only codes in {@link LANGUAGES} are honored; anything else falls through.
56
+ */
57
+ export function getCurrentLanguage(): string {
58
+ const envLanguage = getSfdcEnv()?.language;
59
+ if (envLanguage) {
60
+ const envCode = urlSegmentToCode(envLanguage);
61
+ if (LANGUAGE_CODES.has(envCode)) {
62
+ return envCode;
63
+ }
64
+ }
65
+
66
+ const basePath = getSfdcEnv()?.basePath ?? "";
67
+ const urlLanguage = getLanguageFromUrl(window.location.href, basePath, LANGUAGE_CODES);
68
+ return urlLanguage ?? DEFAULT_LANGUAGE;
69
+ }
70
+
71
+ @Component({
72
+ selector: "app-language-switcher",
73
+ standalone: true,
74
+ changeDetection: ChangeDetectionStrategy.OnPush,
75
+ imports: [...DropdownMenuImports, IconComponent],
76
+ templateUrl: "./language-switcher.html",
77
+ })
78
+ export class LanguageSwitcherComponent {
79
+ /** Accessible label prefix for the dropdown trigger. Defaults to "Language". */
80
+ readonly label = input<string>("Language");
81
+
82
+ readonly languages = LANGUAGES;
83
+ readonly currentLanguage = getCurrentLanguage();
84
+ readonly hasMultipleLanguages = LANGUAGES.length >= 2;
85
+
86
+ /** The label of the currently active language (for display in the trigger). */
87
+ readonly currentLanguageLabel =
88
+ LANGUAGES.find((l) => l.code === this.currentLanguage)?.label ?? "";
89
+
90
+ /** Accessible label that includes the visible current-language text (WCAG 2.5.3). */
91
+ readonly accessibleLabel = computed(() => `${this.label()}: ${this.currentLanguageLabel}`);
92
+
93
+ /** Classes applied to the dropdown-menu's trigger button. */
94
+ readonly triggerClass =
95
+ "inline-flex items-center gap-1.5 rounded-md border border-border bg-card px-3 py-1.5 text-sm text-foreground hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none";
96
+
97
+ switchLanguage(code: string): void {
98
+ if (code === this.currentLanguage) return;
99
+
100
+ const basePath = getSfdcEnv()?.basePath ?? "";
101
+ const nextUrl = buildLanguageUrl(
102
+ window.location.href,
103
+ basePath,
104
+ code,
105
+ LANGUAGE_CODES,
106
+ DEFAULT_LANGUAGE,
107
+ );
108
+ window.location.assign(nextUrl);
109
+ }
110
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Copyright (c) 2026, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+
7
+ /** A single language option offered by the switcher. */
8
+ export interface LanguageOption {
9
+ /** Salesforce locale code, e.g. "en_US". Used as the URL path segment. */
10
+ code: string;
11
+ /** Human-readable label shown in the dropdown, e.g. "English". */
12
+ label: string;
13
+ }
14
+
15
+ /**
16
+ * The languages this app offers in the switcher.
17
+ *
18
+ * IMPORTANT — keep this list in sync BY HAND with the site's language settings:
19
+ * digitalExperiences/site/<siteName>/sfdc_cms__languageSettings/content.json
20
+ *
21
+ * There is no runtime API to read `sfdc_cms__languageSettings` on a published
22
+ * site, so the supported languages are baked into the bundle at authoring time.
23
+ * When the site's content.json changes, update this array (and
24
+ * {@link DEFAULT_LANGUAGE}) to match.
25
+ *
26
+ * `code` values are Salesforce locale codes (underscores). They are converted to
27
+ * hyphenated form for use as the first URL path segment after `basePath` (e.g.
28
+ * `en_US` → `/en-US/...`, `fr` → `/fr/...`). Display order here is the order shown
29
+ * in the dropdown.
30
+ */
31
+ export const LANGUAGES: readonly LanguageOption[] = [
32
+ { code: "en_US", label: "English" },
33
+ { code: "fr", label: "Français" },
34
+ { code: "es", label: "Español" },
35
+ { code: "de", label: "Deutsch" },
36
+ { code: "ja", label: "日本語" },
37
+ ];
38
+
39
+ /**
40
+ * Language used when `SFDC_ENV.language` is missing or is not one of
41
+ * {@link LANGUAGES}. Should match the site's default language in content.json.
42
+ */
43
+ export const DEFAULT_LANGUAGE = "en_US";
44
+
45
+ /** Set of known language codes, for fast membership checks. */
46
+ export const LANGUAGE_CODES: ReadonlySet<string> = new Set(LANGUAGES.map((l) => l.code));
@@ -0,0 +1,6 @@
1
+ <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
2
+ <div class="text-center">
3
+ <h1 class="text-4xl font-bold text-gray-900 mb-4">Home</h1>
4
+ <p class="text-lg text-gray-600 mb-8">Welcome to your Angular application.</p>
5
+ </div>
6
+ </main>
@@ -0,0 +1,23 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { HomeComponent } from './home';
3
+
4
+ describe('HomeComponent', () => {
5
+ beforeEach(async () => {
6
+ await TestBed.configureTestingModule({
7
+ imports: [HomeComponent],
8
+ }).compileComponents();
9
+ });
10
+
11
+ it('should create the component', () => {
12
+ const fixture = TestBed.createComponent(HomeComponent);
13
+ const component = fixture.componentInstance;
14
+ expect(component).toBeTruthy();
15
+ });
16
+
17
+ it('should render main landmark', async () => {
18
+ const fixture = TestBed.createComponent(HomeComponent);
19
+ await fixture.whenStable();
20
+ const compiled = fixture.nativeElement as HTMLElement;
21
+ expect(compiled.querySelector('main')).toBeTruthy();
22
+ });
23
+ });