ooxml-ui 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) hide show
  1. package/custom-elements.json +139 -2
  2. package/dist/{chunk-LAVVSCRJ.js → chunk-CHFMXUPK.js} +195 -12
  3. package/dist/chunk-GSWCAZQ3.js +3533 -0
  4. package/dist/{chunk-C3MXKNNK.js → chunk-JVNIZBG7.js} +1462 -781
  5. package/dist/{chunk-C5F7454A.js → chunk-QUG6IRQQ.js} +2 -2
  6. package/dist/{chunk-DSQBXLMO.js → chunk-UIVYZERK.js} +1 -1
  7. package/dist/controls.d.ts +1 -1
  8. package/dist/controls.d.ts.map +1 -1
  9. package/dist/controls.js +1 -1
  10. package/dist/docx.js +3 -3
  11. package/dist/index.js +5 -5
  12. package/dist/pptx/{chart-3d-view-scene-CFUVOILD.cjs → chart-3d-view-scene-3HDRDXX6.cjs} +38 -38
  13. package/dist/pptx/{chart-3d-view-scene-BEAJCQIS.mjs → chart-3d-view-scene-NMFMG76N.mjs} +2 -2
  14. package/dist/pptx/{chunk-5UGJC25J.cjs → chunk-3DGTZWI5.cjs} +1 -1
  15. package/dist/pptx/{chunk-4NBVWV6I.cjs → chunk-A2X5BP7J.cjs} +581 -0
  16. package/dist/pptx/{chunk-2JTHIDMD.cjs → chunk-CEH63AZB.cjs} +1172 -85
  17. package/dist/pptx/{chunk-DWUC6LZK.mjs → chunk-GCHJRKXZ.mjs} +10 -30
  18. package/dist/pptx/{chunk-VXKFXPCU.mjs → chunk-HL53SH7C.mjs} +1169 -86
  19. package/dist/pptx/{chunk-5DUBDWOU.cjs → chunk-KTHBMVMQ.cjs} +10436 -6294
  20. package/dist/pptx/{chunk-HULZNSHL.cjs → chunk-L2LK7TEC.cjs} +19 -40
  21. package/dist/pptx/{chunk-GUAP5VUN.mjs → chunk-RACO4FQT.mjs} +9643 -5546
  22. package/dist/pptx/{chunk-P3YE5LT2.mjs → chunk-XOMYHWQ3.mjs} +1 -1
  23. package/dist/pptx/{chunk-OJXPN2WX.mjs → chunk-ZNXKAINY.mjs} +581 -0
  24. package/dist/pptx/dom/element-styles.d.ts.map +1 -1
  25. package/dist/pptx/dom/elements/chart-editable.d.ts +11 -0
  26. package/dist/pptx/dom/elements/chart-editable.d.ts.map +1 -1
  27. package/dist/pptx/dom/elements/chart.d.ts.map +1 -1
  28. package/dist/pptx/dom/index.cjs +278 -265
  29. package/dist/pptx/dom/index.mjs +37 -25
  30. package/dist/pptx/export/html2canvas-clone.cjs +2 -2
  31. package/dist/pptx/export/html2canvas-clone.mjs +1 -1
  32. package/dist/pptx/i18n/index.cjs +6 -6
  33. package/dist/pptx/i18n/index.mjs +1 -1
  34. package/dist/pptx/i18n/translations-en-chart-format.d.ts +3 -0
  35. package/dist/pptx/i18n/translations-en-chart-format.d.ts.map +1 -0
  36. package/dist/pptx/i18n/translations-en-compare.d.ts +3 -0
  37. package/dist/pptx/i18n/translations-en-compare.d.ts.map +1 -0
  38. package/dist/pptx/i18n/translations-en-design-galleries.d.ts +3 -0
  39. package/dist/pptx/i18n/translations-en-design-galleries.d.ts.map +1 -0
  40. package/dist/pptx/i18n/translations-en-galleries-smartart-layouts.d.ts +6 -0
  41. package/dist/pptx/i18n/translations-en-galleries-smartart-layouts.d.ts.map +1 -0
  42. package/dist/pptx/i18n/translations-en-shape-gallery.d.ts +7 -0
  43. package/dist/pptx/i18n/translations-en-shape-gallery.d.ts.map +1 -0
  44. package/dist/pptx/i18n/translations-en.d.ts.map +1 -1
  45. package/dist/pptx/index.cjs +3338 -3146
  46. package/dist/pptx/index.mjs +6 -6
  47. package/dist/pptx/render/chart-axis-primitives.d.ts.map +1 -1
  48. package/dist/pptx/render/chart-canvas-drag.d.ts.map +1 -1
  49. package/dist/pptx/render/chart-cartesian-bars.d.ts.map +1 -1
  50. package/dist/pptx/render/chart-category-axis.d.ts.map +1 -1
  51. package/dist/pptx/render/chart-combo-stock.cjs +3 -3
  52. package/dist/pptx/render/chart-combo-stock.mjs +1 -1
  53. package/dist/pptx/render/chart-date-axis.d.ts.map +1 -1
  54. package/dist/pptx/render/chart-format/chart-area-outer-effects.d.ts +20 -0
  55. package/dist/pptx/render/chart-format/chart-area-outer-effects.d.ts.map +1 -0
  56. package/dist/pptx/render/chart-format/chart-format-3d-svg.d.ts +23 -0
  57. package/dist/pptx/render/chart-format/chart-format-3d-svg.d.ts.map +1 -0
  58. package/dist/pptx/render/chart-format/chart-format-context-menu.d.ts +11 -0
  59. package/dist/pptx/render/chart-format/chart-format-context-menu.d.ts.map +1 -0
  60. package/dist/pptx/render/chart-format/chart-format-effects-svg.d.ts +87 -0
  61. package/dist/pptx/render/chart-format/chart-format-effects-svg.d.ts.map +1 -0
  62. package/dist/pptx/render/chart-format/chart-format-fixtures.test-util.d.ts +10 -0
  63. package/dist/pptx/render/chart-format/chart-format-fixtures.test-util.d.ts.map +1 -0
  64. package/dist/pptx/render/chart-format/chart-format-geometry.d.ts +25 -0
  65. package/dist/pptx/render/chart-format/chart-format-geometry.d.ts.map +1 -0
  66. package/dist/pptx/render/chart-format/chart-format-hit-test.d.ts +42 -0
  67. package/dist/pptx/render/chart-format/chart-format-hit-test.d.ts.map +1 -0
  68. package/dist/pptx/render/chart-format/chart-format-paint-svg.d.ts +38 -0
  69. package/dist/pptx/render/chart-format/chart-format-paint-svg.d.ts.map +1 -0
  70. package/dist/pptx/render/chart-format/chart-format-pane-3d.d.ts +13 -0
  71. package/dist/pptx/render/chart-format/chart-format-pane-3d.d.ts.map +1 -0
  72. package/dist/pptx/render/chart-format/chart-format-pane-axis.d.ts +12 -0
  73. package/dist/pptx/render/chart-format/chart-format-pane-axis.d.ts.map +1 -0
  74. package/dist/pptx/render/chart-format/chart-format-pane-common.d.ts +14 -0
  75. package/dist/pptx/render/chart-format/chart-format-pane-common.d.ts.map +1 -0
  76. package/dist/pptx/render/chart-format/chart-format-pane-edit-extra.d.ts +28 -0
  77. package/dist/pptx/render/chart-format/chart-format-pane-edit-extra.d.ts.map +1 -0
  78. package/dist/pptx/render/chart-format/chart-format-pane-edit.d.ts +18 -0
  79. package/dist/pptx/render/chart-format/chart-format-pane-edit.d.ts.map +1 -0
  80. package/dist/pptx/render/chart-format/chart-format-pane-effects.d.ts +31 -0
  81. package/dist/pptx/render/chart-format/chart-format-pane-effects.d.ts.map +1 -0
  82. package/dist/pptx/render/chart-format/chart-format-pane-fill-kinds.d.ts +15 -0
  83. package/dist/pptx/render/chart-format/chart-format-pane-fill-kinds.d.ts.map +1 -0
  84. package/dist/pptx/render/chart-format/chart-format-pane-fill-line.d.ts +12 -0
  85. package/dist/pptx/render/chart-format/chart-format-pane-fill-line.d.ts.map +1 -0
  86. package/dist/pptx/render/chart-format/chart-format-pane-model.d.ts +17 -0
  87. package/dist/pptx/render/chart-format/chart-format-pane-model.d.ts.map +1 -0
  88. package/dist/pptx/render/chart-format/chart-format-pane-options.d.ts +13 -0
  89. package/dist/pptx/render/chart-format/chart-format-pane-options.d.ts.map +1 -0
  90. package/dist/pptx/render/chart-format/chart-format-pane-text.d.ts +14 -0
  91. package/dist/pptx/render/chart-format/chart-format-pane-text.d.ts.map +1 -0
  92. package/dist/pptx/render/chart-format/chart-format-pane-types.d.ts +96 -0
  93. package/dist/pptx/render/chart-format/chart-format-pane-types.d.ts.map +1 -0
  94. package/dist/pptx/render/chart-format/chart-format-render-labels.d.ts +26 -0
  95. package/dist/pptx/render/chart-format/chart-format-render-labels.d.ts.map +1 -0
  96. package/dist/pptx/render/chart-format/chart-format-render-lines.d.ts +36 -0
  97. package/dist/pptx/render/chart-format/chart-format-render-lines.d.ts.map +1 -0
  98. package/dist/pptx/render/chart-format/chart-format-render-series.d.ts +30 -0
  99. package/dist/pptx/render/chart-format/chart-format-render-series.d.ts.map +1 -0
  100. package/dist/pptx/render/chart-format/chart-format-render-text.d.ts +28 -0
  101. package/dist/pptx/render/chart-format/chart-format-render-text.d.ts.map +1 -0
  102. package/dist/pptx/render/chart-format/chart-format-render.d.ts +26 -0
  103. package/dist/pptx/render/chart-format/chart-format-render.d.ts.map +1 -0
  104. package/dist/pptx/render/chart-format/chart-format-targets.d.ts +36 -0
  105. package/dist/pptx/render/chart-format/chart-format-targets.d.ts.map +1 -0
  106. package/dist/pptx/render/chart-format/chart-selection-strip.d.ts +39 -0
  107. package/dist/pptx/render/chart-format/chart-selection-strip.d.ts.map +1 -0
  108. package/dist/pptx/render/chart-format/index.d.ts +16 -0
  109. package/dist/pptx/render/chart-format/index.d.ts.map +1 -0
  110. package/dist/pptx/render/chart-legend-layout.d.ts +3 -1
  111. package/dist/pptx/render/chart-legend-layout.d.ts.map +1 -1
  112. package/dist/pptx/render/chart-overlays.cjs +7 -7
  113. package/dist/pptx/render/chart-overlays.mjs +1 -1
  114. package/dist/pptx/render/chart-surface-treemap.cjs +4 -4
  115. package/dist/pptx/render/chart-surface-treemap.mjs +1 -1
  116. package/dist/pptx/render/chart-svg-primitives.d.ts +6 -0
  117. package/dist/pptx/render/chart-svg-primitives.d.ts.map +1 -1
  118. package/dist/pptx/render/chart-view-model-build.d.ts.map +1 -1
  119. package/dist/pptx/render/chart-view-model-dom.d.ts +17 -2
  120. package/dist/pptx/render/chart-view-model-dom.d.ts.map +1 -1
  121. package/dist/pptx/render/chart-view-model-types.d.ts +3 -0
  122. package/dist/pptx/render/chart-view-model-types.d.ts.map +1 -1
  123. package/dist/pptx/render/chart-view-model.cjs +44 -44
  124. package/dist/pptx/render/chart-view-model.mjs +1 -1
  125. package/dist/pptx/render/chart-waterfall-map.cjs +6 -6
  126. package/dist/pptx/render/chart-waterfall-map.mjs +1 -1
  127. package/dist/pptx/render/context-menu-commands.d.ts +6 -1
  128. package/dist/pptx/render/context-menu-commands.d.ts.map +1 -1
  129. package/dist/pptx/render/customization/customization-catalog.d.ts +1 -1
  130. package/dist/pptx/render/customization/customization-catalog.d.ts.map +1 -1
  131. package/dist/pptx/render/customization/customization-descriptions.d.ts.map +1 -1
  132. package/dist/pptx/render/customization/ribbon-control-catalog-core.d.ts +11 -1
  133. package/dist/pptx/render/customization/ribbon-control-catalog-core.d.ts.map +1 -1
  134. package/dist/pptx/render/customization/ribbon-control-catalog-tail.d.ts +10 -0
  135. package/dist/pptx/render/customization/ribbon-control-catalog-tail.d.ts.map +1 -1
  136. package/dist/pptx/render/customization/ribbon-control-catalog.d.ts +21 -1
  137. package/dist/pptx/render/customization/ribbon-control-catalog.d.ts.map +1 -1
  138. package/dist/pptx/render/deck-style.d.ts +126 -0
  139. package/dist/pptx/render/deck-style.d.ts.map +1 -0
  140. package/dist/pptx/render/editor-chrome/metrics.d.ts +7 -0
  141. package/dist/pptx/render/editor-chrome/metrics.d.ts.map +1 -1
  142. package/dist/pptx/render/frame-effect-style.d.ts +22 -0
  143. package/dist/pptx/render/frame-effect-style.d.ts.map +1 -0
  144. package/dist/pptx/render/index.d.ts +6 -0
  145. package/dist/pptx/render/index.d.ts.map +1 -1
  146. package/dist/pptx/render/ribbon-design-commands.d.ts.map +1 -1
  147. package/dist/pptx/render/ribbon-galleries/gallery-module.d.ts +2 -1
  148. package/dist/pptx/render/ribbon-galleries/gallery-module.d.ts.map +1 -1
  149. package/dist/pptx/render/ribbon-galleries/gallery-placements.d.ts +1 -1
  150. package/dist/pptx/render/ribbon-galleries/gallery-placements.d.ts.map +1 -1
  151. package/dist/pptx/render/ribbon-galleries/gallery-registry.d.ts +1 -1
  152. package/dist/pptx/render/ribbon-galleries/gallery-registry.d.ts.map +1 -1
  153. package/dist/pptx/render/ribbon-galleries/gallery-types.d.ts +52 -3
  154. package/dist/pptx/render/ribbon-galleries/gallery-types.d.ts.map +1 -1
  155. package/dist/pptx/render/ribbon-galleries/gallery-view.d.ts.map +1 -1
  156. package/dist/pptx/render/ribbon-galleries/smartart-layouts-gallery.d.ts +5 -0
  157. package/dist/pptx/render/ribbon-galleries/smartart-layouts-gallery.d.ts.map +1 -1
  158. package/dist/pptx/render/ribbon-galleries/theme-backgrounds-gallery.d.ts +7 -0
  159. package/dist/pptx/render/ribbon-galleries/theme-backgrounds-gallery.d.ts.map +1 -0
  160. package/dist/pptx/render/ribbon-galleries/theme-colors-gallery.d.ts.map +1 -1
  161. package/dist/pptx/render/ribbon-galleries/theme-effects-gallery.d.ts +6 -0
  162. package/dist/pptx/render/ribbon-galleries/theme-effects-gallery.d.ts.map +1 -0
  163. package/dist/pptx/render/ribbon-galleries/theme-fonts-gallery.d.ts.map +1 -1
  164. package/dist/pptx/render/ribbon-galleries/theme-variants-gallery.d.ts +32 -0
  165. package/dist/pptx/render/ribbon-galleries/theme-variants-gallery.d.ts.map +1 -0
  166. package/dist/pptx/render/ribbon-galleries/themes-gallery.d.ts +17 -0
  167. package/dist/pptx/render/ribbon-galleries/themes-gallery.d.ts.map +1 -0
  168. package/dist/pptx/render/ribbon-home-families-objects.d.ts.map +1 -1
  169. package/dist/pptx/render/ribbon-home-families.d.ts.map +1 -1
  170. package/dist/pptx/render/ribbon-home-menus.d.ts +1 -1
  171. package/dist/pptx/render/ribbon-home-menus.d.ts.map +1 -1
  172. package/dist/pptx/render/ribbon-home-spec.d.ts +9 -1
  173. package/dist/pptx/render/ribbon-home-spec.d.ts.map +1 -1
  174. package/dist/pptx/render/ribbon-home-state-objects.d.ts.map +1 -1
  175. package/dist/pptx/render/ribbon-icons/chart-format-icons.d.ts +4 -0
  176. package/dist/pptx/render/ribbon-icons/chart-format-icons.d.ts.map +1 -0
  177. package/dist/pptx/render/ribbon-icons.d.ts.map +1 -1
  178. package/dist/pptx/render/ribbon-insert-layout.d.ts.map +1 -1
  179. package/dist/pptx/render/ribbon-insert-state.d.ts +6 -0
  180. package/dist/pptx/render/ribbon-insert-state.d.ts.map +1 -1
  181. package/dist/pptx/render/shape-gallery-home.d.ts +6 -0
  182. package/dist/pptx/render/shape-gallery-home.d.ts.map +1 -0
  183. package/dist/pptx/render/shape-gallery.d.ts +51 -0
  184. package/dist/pptx/render/shape-gallery.d.ts.map +1 -0
  185. package/dist/pptx/render/slide-compare-text.d.ts +13 -0
  186. package/dist/pptx/render/slide-compare-text.d.ts.map +1 -0
  187. package/dist/pptx/render/slide-compare.d.ts +10 -0
  188. package/dist/pptx/render/slide-compare.d.ts.map +1 -1
  189. package/dist/pptx/render/table-style-presets.d.ts +2 -0
  190. package/dist/pptx/render/table-style-presets.d.ts.map +1 -1
  191. package/dist/pptx/render/text-3d-presets.d.ts +4 -0
  192. package/dist/pptx/render/text-3d-presets.d.ts.map +1 -1
  193. package/dist/pptx/render/text-warp-presets.d.ts +2 -0
  194. package/dist/pptx/render/text-warp-presets.d.ts.map +1 -1
  195. package/dist/pptx/render/toolbar-actions.d.ts +1 -1
  196. package/dist/pptx/render/toolbar-actions.d.ts.map +1 -1
  197. package/dist/pptx/render/visual-effects.d.ts.map +1 -1
  198. package/dist/pptx/theme/index.cjs +16 -16
  199. package/dist/pptx/theme/index.mjs +1 -1
  200. package/dist/pptx/web-components/chart-format-pane-fields.d.ts +17 -0
  201. package/dist/pptx/web-components/chart-format-pane-fields.d.ts.map +1 -0
  202. package/dist/pptx/web-components/chart-format-pane-styles.d.ts +3 -0
  203. package/dist/pptx/web-components/chart-format-pane-styles.d.ts.map +1 -0
  204. package/dist/pptx/web-components/chart-format-pane.d.ts +34 -0
  205. package/dist/pptx/web-components/chart-format-pane.d.ts.map +1 -0
  206. package/dist/pptx/web-components/index.d.ts +3 -1
  207. package/dist/pptx/web-components/index.d.ts.map +1 -1
  208. package/dist/pptx/web-components/mobile-insert-styles.d.ts +2 -0
  209. package/dist/pptx/web-components/mobile-insert-styles.d.ts.map +1 -0
  210. package/dist/pptx/web-components/mobile-insert.d.ts +21 -0
  211. package/dist/pptx/web-components/mobile-insert.d.ts.map +1 -0
  212. package/dist/pptx/web-components/ribbon-gallery.d.ts +11 -0
  213. package/dist/pptx/web-components/ribbon-gallery.d.ts.map +1 -1
  214. package/dist/pptx/web-components/ribbon-home-controls.d.ts.map +1 -1
  215. package/dist/pptx/web-components/ribbon-home-layout.d.ts +6 -0
  216. package/dist/pptx/web-components/ribbon-home-layout.d.ts.map +1 -1
  217. package/dist/pptx/web-components/ribbon-home-menu.d.ts +1 -1
  218. package/dist/pptx/web-components/ribbon-home-menu.d.ts.map +1 -1
  219. package/dist/pptx/web-components/ribbon-home-styles.d.ts +1 -1
  220. package/dist/pptx/web-components/ribbon-home-styles.d.ts.map +1 -1
  221. package/dist/pptx/web-components/ribbon-home.d.ts +2 -0
  222. package/dist/pptx/web-components/ribbon-home.d.ts.map +1 -1
  223. package/dist/pptx/web-components/ribbon-icons.d.ts.map +1 -1
  224. package/dist/pptx/web-components/ribbon-insert-styles.d.ts.map +1 -1
  225. package/dist/pptx/web-components/ribbon-insert-table-picker.d.ts +2 -0
  226. package/dist/pptx/web-components/ribbon-insert-table-picker.d.ts.map +1 -1
  227. package/dist/pptx/web-components/ribbon-insert-view.d.ts.map +1 -1
  228. package/dist/pptx/web-components/ribbon-overflow.d.ts.map +1 -1
  229. package/dist/pptx/web-components/theme-editor-dock.d.ts +12 -1
  230. package/dist/pptx/web-components/theme-editor-dock.d.ts.map +1 -1
  231. package/dist/{presentation-preview-HVKJ3KJN.js → presentation-preview-HMEBDYEL.js} +1 -1
  232. package/dist/ribbon/gallery.d.ts +50 -0
  233. package/dist/ribbon/gallery.d.ts.map +1 -1
  234. package/dist/ribbon/ribbon-group.d.ts +1 -1
  235. package/dist/ribbon/ribbon-group.d.ts.map +1 -1
  236. package/dist/teams/activity-feed.d.ts +1 -1
  237. package/dist/teams/app/add-tab-dialog.d.ts +11 -10
  238. package/dist/teams/app/bind.d.ts +2 -0
  239. package/dist/teams/app/channel-tab.d.ts +3 -2
  240. package/dist/teams/app/chat-app.d.ts +2 -1
  241. package/dist/teams/app/content-preview.d.ts +3 -2
  242. package/dist/teams/app/create-channel-dialog.d.ts +3 -2
  243. package/dist/teams/app/draft-list.d.ts +2 -1
  244. package/dist/teams/app/file-actions.d.ts +3 -2
  245. package/dist/teams/app/files-panel.d.ts +3 -2
  246. package/dist/teams/app/followed-threads.d.ts +2 -1
  247. package/dist/teams/app/markdown-preview.d.ts +2 -1
  248. package/dist/teams/app/meeting-dialog.d.ts +11 -3
  249. package/dist/teams/app/message-transfers.d.ts +2 -1
  250. package/dist/teams/app/notify-delivery.d.ts +3 -2
  251. package/dist/teams/app/pinned-bar.d.ts +3 -2
  252. package/dist/teams/app/presentation-preview.d.ts +3 -2
  253. package/dist/teams/app/profile-menu.d.ts +12 -2
  254. package/dist/teams/app/repeat-text.d.ts +26 -0
  255. package/dist/teams/app/settings-density.d.ts +2 -1
  256. package/dist/teams/app/settings-notifications.d.ts +2 -1
  257. package/dist/teams/app/settings-search.d.ts +6 -4
  258. package/dist/teams/app/settings-theme.d.ts +2 -1
  259. package/dist/teams/app/sign-in-flow.d.ts +49 -0
  260. package/dist/teams/app/sign-in-strings.d.ts +4 -0
  261. package/dist/teams/app/teams-app.d.ts +12 -2
  262. package/dist/teams/app/teams-settings.d.ts +3 -2
  263. package/dist/teams/app/teams-sign-in.d.ts +57 -0
  264. package/dist/teams/app/thread-pane.d.ts +2 -1
  265. package/dist/teams/app/workbook-actions.d.ts +2 -1
  266. package/dist/teams/app-rail.d.ts +1 -0
  267. package/dist/teams/base.d.ts +25 -5
  268. package/dist/teams/calendar-view.d.ts +1 -0
  269. package/dist/teams/call-controls.d.ts +1 -0
  270. package/dist/teams/call-grid.d.ts +1 -0
  271. package/dist/teams/chat-list.d.ts +1 -0
  272. package/dist/teams/i18n/core-text.d.ts +3 -0
  273. package/dist/teams/i18n/describe.d.ts +4 -0
  274. package/dist/teams/i18n/lit-element.d.ts +16 -0
  275. package/dist/teams/i18n/locales/de/calendar.d.ts +2 -0
  276. package/dist/teams/i18n/locales/de/chat.d.ts +2 -0
  277. package/dist/teams/i18n/locales/de/core.d.ts +2 -0
  278. package/dist/teams/i18n/locales/de/files.d.ts +2 -0
  279. package/dist/teams/i18n/locales/de/settings.d.ts +2 -0
  280. package/dist/teams/i18n/locales/de/shell.d.ts +2 -0
  281. package/dist/teams/i18n/locales/de.d.ts +617 -0
  282. package/dist/teams/i18n/locales/en/calendar.d.ts +78 -0
  283. package/dist/teams/i18n/locales/en/chat.d.ts +109 -0
  284. package/dist/teams/i18n/locales/en/core.d.ts +34 -0
  285. package/dist/teams/i18n/locales/en/files.d.ts +128 -0
  286. package/dist/teams/i18n/locales/en/settings.d.ts +131 -0
  287. package/dist/teams/i18n/locales/en/shell.d.ts +146 -0
  288. package/dist/teams/i18n/locales/en.d.ts +617 -0
  289. package/dist/teams/i18n/locales/es/calendar.d.ts +2 -0
  290. package/dist/teams/i18n/locales/es/chat.d.ts +2 -0
  291. package/dist/teams/i18n/locales/es/core.d.ts +2 -0
  292. package/dist/teams/i18n/locales/es/files.d.ts +2 -0
  293. package/dist/teams/i18n/locales/es/settings.d.ts +2 -0
  294. package/dist/teams/i18n/locales/es/shell.d.ts +2 -0
  295. package/dist/teams/i18n/locales/es.d.ts +617 -0
  296. package/dist/teams/i18n/locales/fr/calendar.d.ts +2 -0
  297. package/dist/teams/i18n/locales/fr/chat.d.ts +2 -0
  298. package/dist/teams/i18n/locales/fr/core.d.ts +2 -0
  299. package/dist/teams/i18n/locales/fr/files.d.ts +2 -0
  300. package/dist/teams/i18n/locales/fr/settings.d.ts +2 -0
  301. package/dist/teams/i18n/locales/fr/shell.d.ts +2 -0
  302. package/dist/teams/i18n/locales/fr.d.ts +617 -0
  303. package/dist/teams/i18n/locales/zh-CN/calendar.d.ts +2 -0
  304. package/dist/teams/i18n/locales/zh-CN/chat.d.ts +2 -0
  305. package/dist/teams/i18n/locales/zh-CN/core.d.ts +2 -0
  306. package/dist/teams/i18n/locales/zh-CN/files.d.ts +2 -0
  307. package/dist/teams/i18n/locales/zh-CN/settings.d.ts +2 -0
  308. package/dist/teams/i18n/locales/zh-CN/shell.d.ts +2 -0
  309. package/dist/teams/i18n/locales/zh-CN.d.ts +617 -0
  310. package/dist/teams/i18n/runtime.d.ts +15 -0
  311. package/dist/teams/i18n/translator.d.ts +28 -0
  312. package/dist/teams.js +3 -3
  313. package/dist/visio.js +31 -4
  314. package/dist/xlsx/dialogs/help-dialogs.d.ts +1 -1
  315. package/dist/xlsx/dialogs/pivot-field-settings.d.ts +3 -3
  316. package/dist/xlsx/dialogs/pivot-page-filter.d.ts +1 -1
  317. package/dist/xlsx.js +68 -37
  318. package/package.json +2 -2
  319. package/dist/chunk-VRVFLYGI.js +0 -236
  320. /package/dist/pptx/{chunk-47FYJVMH.cjs → chunk-BDXQX6RA.cjs} +0 -0
  321. /package/dist/pptx/{chunk-XRWEURSV.mjs → chunk-ZAOD6O2D.mjs} +0 -0
@@ -1,10 +1,11 @@
1
+ import { TeamsLitElement, LOCALE_HOST, teamsI18n, inheritLocale, pushLocale } from './chunk-GSWCAZQ3.js';
1
2
  import { THEME_CSS, installOfficeUiTheme } from './chunk-7E3HU7RL.js';
2
- import { defineMenuButton, defineMenuItem, registerControls, defineSwitch } from './chunk-LAVVSCRJ.js';
3
+ import { defineMenuButton, defineMenuItem, registerControls, defineSwitch } from './chunk-CHFMXUPK.js';
3
4
  import { registerIcon, getIcon } from './chunk-M6GF5ED2.js';
4
5
  import { initialsOf, definePresence } from './chunk-KNC2UEUR.js';
5
6
  import { definer, OfficeElement, controlStyles } from './chunk-CWBSXK7P.js';
6
- import { nothing, html, LitElement, unsafeCSS, svg } from 'lit';
7
- import { filterActivity, ACTIVITY_FILTERS, describeActivity, localZone, startOfDay, occurrencesBetween, DAY_MS as DAY_MS$1, dayKey, zonedToUtc, viewDays, zonedParts, shiftAnchor, layoutDay, mentionQueryAt, filterMentionTargets, insertMention, MAX_ATTACHMENTS, mentionsUser, mentionSegments, detectContentKind, contentUrl, readContent, sortFiles, filterFiles, parseServerConfig, STATUS_DURATIONS, STATUS_DURATION_LABELS, PRESENCE_LABELS, MAX_STATUS_MESSAGE_CHARS, isPresenceStatus, PRESENCE_STATUSES, isStatusDuration, IDLE_MINUTE_CHOICES, sanitizeChannelName, MAX_CHANNEL_NAME_CHARS, MAX_CHANNEL_TOPIC_CHARS, MAX_CHAT_MEMBERS, occurrenceAt, MAX_RECURRENCE_COUNT, MINUTE_MS, MAX_MEETING_TITLE, MAX_MEETING_DESCRIPTION, markdownBlocks, createTeamsClient, sanitizeAttachment, markdownInline } from 'ooxml-core/teams';
7
+ import { nothing, html, unsafeCSS, svg } from 'lit';
8
+ import { filterActivity, ACTIVITY_FILTERS, localZone, startOfDay, occurrencesBetween, DAY_MS as DAY_MS$1, dayKey, zonedToUtc, viewDays, zonedParts, shiftAnchor, layoutDay, mentionQueryAt, filterMentionTargets, insertMention, MAX_ATTACHMENTS, mentionsUser, mentionSegments, detectContentKind, contentUrl, readContent, sortFiles, filterFiles, parseServerConfig, STATUS_DURATIONS, MAX_STATUS_MESSAGE_CHARS, isPresenceStatus, PRESENCE_STATUSES, isStatusDuration, IDLE_MINUTE_CHOICES, sanitizeChannelName, MAX_CHANNEL_NAME_CHARS, MAX_CHANNEL_TOPIC_CHARS, MAX_CHAT_MEMBERS, occurrenceAt, MAX_RECURRENCE_COUNT, MINUTE_MS, MAX_MEETING_TITLE, MAX_MEETING_DESCRIPTION, markdownBlocks, repeatChoices, serverOrigin, probeTeamsAccounts, requestTeamsSession, signOutTeamsSession, checkTeamsSession, createTeamsClient, sanitizeAttachment, markdownInline, TeamsSignInError } from 'ooxml-core/teams';
8
9
  import { repeat } from 'lit/directives/repeat.js';
9
10
  import { live } from 'lit/directives/live.js';
10
11
  import { keyed } from 'lit/directives/keyed.js';
@@ -19,6 +20,37 @@ var withStyles = (css) => {
19
20
  };
20
21
  var TeamsElement = class extends OfficeElement {
21
22
  static syncUpdates = false;
23
+ static properties = { locale: { type: String } };
24
+ constructor() {
25
+ super();
26
+ this.locale = "";
27
+ }
28
+ get [LOCALE_HOST]() {
29
+ return true;
30
+ }
31
+ get i18n() {
32
+ return teamsI18n(this.locale);
33
+ }
34
+ t(key, params) {
35
+ return this.i18n.t(key, params);
36
+ }
37
+ tn(key, count, params) {
38
+ return this.i18n.tn(key, count, params);
39
+ }
40
+ /** The accessible name of the host element itself, rewritten with every update. */
41
+ hostLabel() {
42
+ return void 0;
43
+ }
44
+ connectedCallback() {
45
+ super.connectedCallback();
46
+ inheritLocale(this);
47
+ }
48
+ update(changed) {
49
+ super.update(changed);
50
+ const label = this.hostLabel();
51
+ if (label !== void 0) this.setAttribute("aria-label", label);
52
+ if (changed.has("locale")) pushLocale(this);
53
+ }
22
54
  };
23
55
  function colorFor(id) {
24
56
  let hue = 0;
@@ -26,32 +58,68 @@ function colorFor(id) {
26
58
  return `hsl(${hue} 45% 42%)`;
27
59
  }
28
60
  var DAY_MS = 864e5;
29
- function formatTime(ts) {
30
- return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
61
+ function formatTime(ts, locale) {
62
+ return new Date(ts).toLocaleTimeString(teamsI18n(locale).intl, {
63
+ hour: "2-digit",
64
+ minute: "2-digit"
65
+ });
31
66
  }
32
- function dayLabel(ts, now = Date.now()) {
67
+ function dayKind(ts, now = Date.now()) {
33
68
  const start = (t) => new Date(t).setHours(0, 0, 0, 0);
34
69
  const diff = Math.round((start(now) - start(ts)) / DAY_MS);
35
- if (diff === 0) return "Today";
36
- if (diff === 1) return "Yesterday";
37
- return new Date(ts).toLocaleDateString([], { weekday: "long", month: "long", day: "numeric" });
70
+ return diff === 0 ? "today" : diff === 1 ? "yesterday" : "other";
38
71
  }
39
- function formatSize(bytes) {
72
+ function dayLabel(ts, now = Date.now(), locale) {
73
+ const i18n = teamsI18n(locale);
74
+ const kind = dayKind(ts, now);
75
+ if (kind === "today") return i18n.t("time.today");
76
+ if (kind === "yesterday") return i18n.t("time.yesterday");
77
+ return new Date(ts).toLocaleDateString(i18n.intl, {
78
+ weekday: "long",
79
+ month: "long",
80
+ day: "numeric"
81
+ });
82
+ }
83
+ function formatSize(bytes, locale) {
40
84
  if (bytes === void 0) return "";
41
- if (bytes < 1024) return `${bytes} B`;
42
- if (bytes < 1048576) return `${Math.round(bytes / 1024)} KB`;
43
- return `${(bytes / 1048576).toFixed(1)} MB`;
85
+ const i18n = teamsI18n(locale);
86
+ const number = (n, digits = 0) => new Intl.NumberFormat(i18n.intl, {
87
+ maximumFractionDigits: digits,
88
+ minimumFractionDigits: digits
89
+ }).format(n);
90
+ if (bytes < 1024) return i18n.t("size.bytes", { size: number(bytes) });
91
+ if (bytes < 1048576) return i18n.t("size.kb", { size: number(Math.round(bytes / 1024)) });
92
+ return i18n.t("size.mb", { size: number(bytes / 1048576, 1) });
44
93
  }
45
- var timeOnly = (ts) => new Date(ts).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
46
- function formatWhen(start, end) {
47
- const day = new Date(start).toLocaleDateString([], {
94
+ var timeOnly = (ts, intl) => new Date(ts).toLocaleTimeString(intl, { hour: "numeric", minute: "2-digit" });
95
+ function formatWhen(start, end, locale) {
96
+ const intl = teamsI18n(locale).intl;
97
+ const day = new Date(start).toLocaleDateString(intl, {
48
98
  weekday: "short",
49
99
  month: "short",
50
100
  day: "numeric"
51
101
  });
52
102
  const sameDay = new Date(start).toDateString() === new Date(end).toDateString();
53
- const tail = sameDay ? timeOnly(end) : `${new Date(end).toLocaleDateString([], { month: "short", day: "numeric" })}, ${timeOnly(end)}`;
54
- return `${day}, ${timeOnly(start)} - ${tail}`;
103
+ const tail = sameDay ? timeOnly(end, intl) : `${new Date(end).toLocaleDateString(intl, { month: "short", day: "numeric" })}, ${timeOnly(end, intl)}`;
104
+ return `${day}, ${timeOnly(start, intl)} - ${tail}`;
105
+ }
106
+
107
+ // src/teams/i18n/describe.ts
108
+ function describeActivityIn(item, i18n) {
109
+ const where = item.chat ? item.channelName : `# ${item.channelName}`;
110
+ const actor = item.actorName;
111
+ switch (item.kind) {
112
+ case "mention":
113
+ return i18n.t("activity.mention", { actor, where });
114
+ case "reply":
115
+ return i18n.t("activity.reply", { actor, where });
116
+ case "reaction":
117
+ return item.emoji ? i18n.t("activity.reactionEmoji", { actor, emoji: item.emoji }) : i18n.t("activity.reaction", { actor });
118
+ case "pin":
119
+ return i18n.t("activity.pin", { actor, where });
120
+ case "meeting":
121
+ return i18n.t("activity.meeting", { title: item.channelName });
122
+ }
55
123
  }
56
124
  var GLYPHS = {
57
125
  back: "M12.5 4 6.5 10l6 6",
@@ -90,12 +158,12 @@ function icon(name) {
90
158
  var activity_feed_default = ":host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n}\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--office-space-2);\n padding: var(--office-space-3) var(--office-space-3) var(--office-space-1);\n}\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n font-weight: var(--office-font-weight-bold);\n}\n.read-all {\n padding: var(--office-space-1) var(--office-space-2);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n color: var(--office-teams-brand);\n font-size: var(--office-font-size-sm);\n}\n.read-all:hover:not(:disabled) {\n background: var(--office-selected);\n}\n.read-all:disabled {\n color: var(--office-teams-text-subtle);\n cursor: default;\n}\n.filters {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-1);\n padding: var(--office-space-1) var(--office-space-3) var(--office-space-2);\n}\n.filters button {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-1);\n padding: var(--office-space-1) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-full);\n background: transparent;\n font-size: var(--office-font-size-sm);\n}\n.filters button:hover {\n background: var(--office-selected);\n}\n.filters button[aria-pressed='true'] {\n border-color: var(--office-teams-brand);\n background: var(--office-teams-brand-subtle);\n color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\n.count {\n min-width: 1.4em;\n padding: 0 var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: var(--office-font-size-3xs);\n line-height: 1.6;\n text-align: center;\n}\nul {\n flex: 1;\n min-height: 0;\n margin: 0;\n padding: 0 var(--office-space-2) var(--office-space-2);\n overflow-y: auto;\n list-style: none;\n}\nli {\n position: relative;\n}\n.item {\n display: flex;\n align-items: flex-start;\n gap: var(--office-space-3);\n width: 100%;\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n color: inherit;\n text-align: start;\n}\n.item:hover {\n background: var(--office-selected);\n}\n.item[aria-current='true'] {\n background: var(--office-teams-brand-subtle);\n}\n.face {\n position: relative;\n flex: none;\n}\n.kind {\n position: absolute;\n inset-block-end: calc(-1 * var(--office-space-1));\n inset-inline-end: calc(-1 * var(--office-space-1));\n display: grid;\n place-items: center;\n width: 1.5em;\n height: 1.5em;\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\n.kind svg {\n width: 0.9em;\n height: 0.9em;\n}\n.text {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n}\n.line {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\nli[data-unread='true'] .line {\n font-weight: var(--office-font-weight-bold);\n}\n.preview {\n overflow: hidden;\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.when {\n display: flex;\n flex: none;\n flex-direction: column;\n align-items: flex-end;\n gap: var(--office-space-1);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.dot {\n width: 0.7em;\n height: 0.7em;\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n}\n.toggle {\n position: absolute;\n inset-block-end: var(--office-space-1);\n inset-inline-end: var(--office-space-2);\n display: none;\n place-items: center;\n width: 2em;\n height: 2em;\n border: 0;\n border-radius: var(--office-radius);\n background: var(--office-popover);\n color: var(--office-teams-text-subtle);\n box-shadow: var(--office-shadow-teams);\n}\nli:hover .toggle,\nli:focus-within .toggle {\n display: grid;\n}\n.empty {\n margin: 0;\n padding: var(--office-space-4);\n color: var(--office-teams-text-subtle);\n text-align: center;\n}\n@media (pointer: coarse) {\n .filters button,\n .read-all {\n min-height: var(--office-touch-target, 44px);\n }\n .toggle {\n display: grid;\n width: var(--office-touch-target, 44px);\n height: var(--office-touch-target, 44px);\n }\n}\n";
91
159
 
92
160
  // src/teams/activity-feed.ts
93
- var FILTER_LABEL = {
94
- all: "All",
95
- unread: "Unread",
96
- mentions: "Mentions",
97
- replies: "Replies",
98
- reactions: "Reactions"
161
+ var FILTER_KEY = {
162
+ all: "activity.filter.all",
163
+ unread: "activity.filter.unread",
164
+ mentions: "activity.filter.mentions",
165
+ replies: "activity.filter.replies",
166
+ reactions: "activity.filter.reactions"
99
167
  };
100
168
  var KIND_ICON = {
101
169
  mention: "at",
@@ -104,18 +172,19 @@ var KIND_ICON = {
104
172
  pin: "pin",
105
173
  meeting: "calendar"
106
174
  };
107
- var EMPTY = {
108
- all: "No activity yet. Mentions, replies, reactions and pinned messages show up here.",
109
- unread: "You are all caught up.",
110
- mentions: "Nobody has mentioned you yet.",
111
- replies: "No replies to your messages or followed threads yet.",
112
- reactions: "No reactions to your messages yet."
175
+ var EMPTY_KEY = {
176
+ all: "activity.empty.all",
177
+ unread: "activity.empty.unread",
178
+ mentions: "activity.empty.mentions",
179
+ replies: "activity.empty.replies",
180
+ reactions: "activity.empty.reactions"
113
181
  };
114
- function activityTime(ts, now = Date.now()) {
115
- const day = dayLabel(ts, now);
116
- if (day === "Today") return formatTime(ts);
117
- if (day === "Yesterday") return day;
118
- return new Date(ts).toLocaleDateString([], { month: "short", day: "numeric" });
182
+ function activityTime(ts, now = Date.now(), locale) {
183
+ const kind = dayKind(ts, now);
184
+ if (kind === "today") return formatTime(ts, locale);
185
+ const i18n = teamsI18n(locale);
186
+ if (kind === "yesterday") return i18n.t("time.yesterday");
187
+ return new Date(ts).toLocaleDateString(i18n.intl, { month: "short", day: "numeric" });
119
188
  }
120
189
  var OfficeUiActivityFeed = class extends TeamsElement {
121
190
  static styles = withStyles(activity_feed_default);
@@ -138,17 +207,17 @@ var OfficeUiActivityFeed = class extends TeamsElement {
138
207
  const unread = this.unread;
139
208
  return html`
140
209
  <header>
141
- <h2>Activity</h2>
210
+ <h2>${this.t("activity.title")}</h2>
142
211
  <button
143
212
  type="button"
144
213
  class="read-all"
145
214
  ?disabled=${unread === 0}
146
215
  @click=${() => this.fire("office-activity-read-all", {})}
147
216
  >
148
- Mark all as read
217
+ ${this.t("activity.markAllRead")}
149
218
  </button>
150
219
  </header>
151
- <div class="filters" role="group" aria-label="Filter activity">
220
+ <div class="filters" role="group" aria-label=${this.t("activity.filterLabel")}>
152
221
  ${ACTIVITY_FILTERS.map(
153
222
  (value) => html`<button
154
223
  type="button"
@@ -158,23 +227,24 @@ var OfficeUiActivityFeed = class extends TeamsElement {
158
227
  this.fire("office-activity-filter", { filter: value });
159
228
  }}
160
229
  >
161
- ${FILTER_LABEL[value]}${value === "unread" && unread ? html` <span class="count">${unread}</span>` : nothing}
230
+ ${this.t(FILTER_KEY[value])}${value === "unread" && unread ? html` <span class="count">${unread}</span>` : nothing}
162
231
  </button>`
163
232
  )}
164
233
  </div>
165
- ${shown.length ? html`<ul aria-label="Activity feed">
234
+ ${shown.length ? html`<ul aria-label=${this.t("activity.feedLabel")}>
166
235
  ${shown.map((item) => this.row(item))}
167
- </ul>` : html`<p class="empty" role="status">${EMPTY[this.filter]}</p>`}
236
+ </ul>` : html`<p class="empty" role="status">${this.t(EMPTY_KEY[this.filter])}</p>`}
168
237
  `;
169
238
  }
170
239
  row(item) {
171
- const line = describeActivity(item);
240
+ const line = describeActivityIn(item, this.i18n);
241
+ const toggle = this.t(item.read ? "activity.markUnread" : "activity.markRead");
172
242
  return html`<li data-unread=${String(!item.read)} data-kind=${item.kind}>
173
243
  <button
174
244
  type="button"
175
245
  class="item"
176
246
  aria-current=${this.selectedId === item.id ? "true" : "false"}
177
- aria-label=${`${item.read ? "" : "Unread. "}${line}. ${item.preview}`}
247
+ aria-label=${`${item.read ? "" : `${this.t("activity.unreadPrefix")} `}${line}. ${item.preview}`}
178
248
  @click=${() => this.fire("office-activity-open", { id: item.id })}
179
249
  >
180
250
  <span class="face">
@@ -190,15 +260,17 @@ var OfficeUiActivityFeed = class extends TeamsElement {
190
260
  <span class="preview">${item.preview}</span>
191
261
  </span>
192
262
  <span class="when">
193
- <time datetime=${new Date(item.ts).toISOString()}>${activityTime(item.ts)}</time>
263
+ <time datetime=${new Date(item.ts).toISOString()}
264
+ >${activityTime(item.ts, Date.now(), this.locale)}</time
265
+ >
194
266
  ${item.read ? nothing : html`<span class="dot" aria-hidden="true"></span>`}
195
267
  </span>
196
268
  </button>
197
269
  <button
198
270
  type="button"
199
271
  class="toggle"
200
- title=${item.read ? "Mark as unread" : "Mark as read"}
201
- aria-label=${item.read ? "Mark as unread" : "Mark as read"}
272
+ title=${toggle}
273
+ aria-label=${toggle}
202
274
  @click=${() => this.fire("office-activity-read", { id: item.id, read: !item.read })}
203
275
  >
204
276
  ${icon(item.read ? "bell" : "check")}
@@ -231,7 +303,9 @@ var OfficeUiAppRail = class extends TeamsElement {
231
303
  connectedCallback() {
232
304
  super.connectedCallback();
233
305
  this.setAttribute("role", "navigation");
234
- this.setAttribute("aria-label", "App bar");
306
+ }
307
+ hostLabel() {
308
+ return this.t("rail.appBar");
235
309
  }
236
310
  updated(changed) {
237
311
  const dialog = this.renderRoot.querySelector("dialog");
@@ -264,7 +338,7 @@ var OfficeUiAppRail = class extends TeamsElement {
264
338
  <button
265
339
  type="button"
266
340
  class="more"
267
- aria-label=${badge ? `More, ${badge} unread` : "More"}
341
+ aria-label=${badge ? this.t("rail.moreUnread", { count: badge }) : this.t("rail.more")}
268
342
  aria-haspopup="dialog"
269
343
  aria-expanded=${String(this.moreOpen)}
270
344
  aria-current=${inMore ? "page" : "false"}
@@ -274,13 +348,13 @@ var OfficeUiAppRail = class extends TeamsElement {
274
348
  ${icon("more")}
275
349
  ${badge ? html`<span class="badge">${total(badge)}</span>` : nothing}
276
350
  </span>
277
- <span class="short" aria-hidden="true">More</span>
351
+ <span class="short" aria-hidden="true">${this.t("rail.more")}</span>
278
352
  </button>
279
353
  </li>` : nothing}
280
354
  </ul>
281
355
  ${secondary.length ? html`<dialog
282
356
  class="sheet"
283
- aria-label="More apps"
357
+ aria-label=${this.t("rail.moreApps")}
284
358
  @cancel=${(e) => {
285
359
  e.preventDefault();
286
360
  this.closeMore();
@@ -292,7 +366,9 @@ var OfficeUiAppRail = class extends TeamsElement {
292
366
  <ul>
293
367
  ${secondary.map((item) => html`<li>${this.button(item, true)}</li>`)}
294
368
  </ul>
295
- <button type="button" class="close" @click=${() => this.closeMore()}>Close</button>
369
+ <button type="button" class="close" @click=${() => this.closeMore()}>
370
+ ${this.t("common.close")}
371
+ </button>
296
372
  </dialog>` : nothing}
297
373
  `;
298
374
  }
@@ -319,13 +395,13 @@ var defineAppRail = definer("office-ui-app-rail", () => OfficeUiAppRail);
319
395
  var avatar_default = ":host {\n --_size: 2.3em;\n position: relative;\n display: inline-block;\n width: var(--_size);\n height: var(--_size);\n flex: none;\n}\n:host([size='sm']) {\n --_size: 1.7em;\n}\n:host([size='lg']) {\n --_size: 3em;\n}\n:host([size='xl']) {\n --_size: 6em;\n}\n\n.face {\n display: grid;\n place-items: center;\n width: 100%;\n height: 100%;\n border-radius: var(--office-radius-full);\n color: var(--office-call-foreground);\n font-size: calc(var(--_size) * 0.38);\n font-weight: var(--office-font-weight-bold);\n user-select: none;\n}\n\n.presence {\n position: absolute;\n inset-inline-end: 0;\n inset-block-end: 0;\n width: 32%;\n height: 32%;\n min-width: 0.5em;\n min-height: 0.5em;\n border-radius: var(--office-radius-full);\n border: var(--office-border-width-thick) solid var(--office-background);\n background: var(--office-presence-offline);\n}\n.presence[data-presence='available'] {\n background: var(--office-presence-available);\n}\n.presence[data-presence='busy'] {\n background: var(--office-presence-busy);\n}\n.presence[data-presence='away'] {\n background: var(--office-presence-away);\n}\n.presence[data-presence='dnd'] {\n background: var(--office-presence-busy);\n}\n.presence[data-presence='brb'] {\n background: var(--office-presence-away);\n}\n/* Do not disturb carries a white bar; Away and Be right back share the clock hands. */\n.presence[data-presence='dnd']::after,\n.presence[data-presence='away']::after,\n.presence[data-presence='brb']::after {\n content: '';\n position: absolute;\n inset: 0;\n margin: auto;\n background: var(--office-background);\n}\n.presence[data-presence='dnd']::after {\n width: 60%;\n height: 16%;\n border-radius: var(--office-radius-full);\n}\n.presence[data-presence='away']::after,\n.presence[data-presence='brb']::after {\n width: 14%;\n height: 50%;\n inset-block: 10% auto;\n border-radius: var(--office-radius-full);\n}\n/* Appear offline is a hollow grey ring. */\n.presence[data-presence='offline'] {\n background: var(--office-background);\n border-color: var(--office-presence-offline);\n box-shadow: inset 0 0 0 var(--office-border-width-thick) var(--office-presence-offline);\n}\n";
320
396
 
321
397
  // src/teams/avatar.ts
322
- var PRESENCE_LABEL = {
323
- available: "Available",
324
- busy: "Busy",
325
- dnd: "Do not disturb",
326
- brb: "Be right back",
327
- away: "Away",
328
- offline: "Offline"
398
+ var PRESENCE_KEY = {
399
+ available: "presence.available",
400
+ busy: "presence.busy",
401
+ dnd: "presence.dnd",
402
+ brb: "presence.brb",
403
+ away: "presence.away",
404
+ offline: "presence.offline"
329
405
  };
330
406
  var OfficeUiAvatar = class extends TeamsElement {
331
407
  static styles = withStyles(avatar_default);
@@ -345,9 +421,9 @@ var OfficeUiAvatar = class extends TeamsElement {
345
421
  this.presence = "";
346
422
  }
347
423
  willUpdate(changed) {
348
- if (changed.has("name") || changed.has("presence")) {
424
+ if (changed.has("name") || changed.has("presence") || changed.has("locale")) {
349
425
  this.setAttribute("role", "img");
350
- const status = this.presence ? `, ${PRESENCE_LABEL[this.presence]}` : "";
426
+ const status = this.presence ? `, ${this.t(PRESENCE_KEY[this.presence])}` : "";
351
427
  this.setAttribute("aria-label", `${this.name}${status}`);
352
428
  }
353
429
  }
@@ -358,7 +434,7 @@ var OfficeUiAvatar = class extends TeamsElement {
358
434
  ${this.presence ? html`<span
359
435
  class="presence"
360
436
  data-presence=${this.presence}
361
- title=${PRESENCE_LABEL[this.presence]}
437
+ title=${this.t(PRESENCE_KEY[this.presence])}
362
438
  ></span>` : ""}
363
439
  `;
364
440
  }
@@ -404,9 +480,11 @@ var OfficeUiCallControls = class extends TeamsElement {
404
480
  connectedCallback() {
405
481
  super.connectedCallback();
406
482
  this.setAttribute("role", "toolbar");
407
- this.setAttribute("aria-label", "Meeting controls");
408
483
  this.timer = setInterval(() => this.now = Date.now(), 1e3);
409
484
  }
485
+ hostLabel() {
486
+ return this.t("call.controls");
487
+ }
410
488
  disconnectedCallback() {
411
489
  clearInterval(this.timer);
412
490
  super.disconnectedCallback();
@@ -437,19 +515,19 @@ var OfficeUiCallControls = class extends TeamsElement {
437
515
  >${this.startedAt ? html`<time>${formatElapsed(this.now - this.startedAt)}</time>` : nothing}
438
516
  </div>
439
517
  <div class="actions">
440
- ${this.button("chat", "Chat", "chat", { on: this.panel === "chat" })}
441
- ${this.button("people", "People", "users", { on: this.panel === "people" })}
442
- ${this.button("hand", this.hand ? "Lower" : "Raise", "hand", { on: this.hand })}
443
- ${this.button("camera", this.camera ? "Camera off" : "Camera on", this.camera ? "video" : "videoOff", { off: !this.camera })}
444
- ${this.button("mic", this.mic ? "Mute" : "Unmute", this.mic ? "mic" : "micOff", { off: !this.mic })}
445
- ${this.button("screen", this.screen ? "Stop sharing" : "Share", "screenShare", { on: this.screen, disabled: this.shareUnavailable })}
518
+ ${this.button("chat", this.t("call.chat"), "chat", { on: this.panel === "chat" })}
519
+ ${this.button("people", this.t("call.people"), "users", { on: this.panel === "people" })}
520
+ ${this.button("hand", this.t(this.hand ? "call.lower" : "call.raise"), "hand", { on: this.hand })}
521
+ ${this.button("camera", this.t(this.camera ? "call.cameraOff" : "call.cameraOn"), this.camera ? "video" : "videoOff", { off: !this.camera })}
522
+ ${this.button("mic", this.t(this.mic ? "call.mute" : "call.unmute"), this.mic ? "mic" : "micOff", { off: !this.mic })}
523
+ ${this.button("screen", this.t(this.screen ? "call.stopSharing" : "call.share"), "screenShare", { on: this.screen, disabled: this.shareUnavailable })}
446
524
  <button
447
525
  type="button"
448
526
  class="leave"
449
- aria-label="Leave call"
527
+ aria-label=${this.t("call.leaveCall")}
450
528
  @click=${() => this.fire("office-call-action", { action: "leave" })}
451
529
  >
452
- ${icon("phoneOff")}<span class="text">Leave</span>
530
+ ${icon("phoneOff")}<span class="text">${this.t("call.leave")}</span>
453
531
  </button>
454
532
  </div>
455
533
  `;
@@ -487,7 +565,7 @@ var OfficeUiCalendarAgenda = class extends TeamsElement {
487
565
  const key = dayKey(row.o.start, this.zone);
488
566
  let group = groups.get(key);
489
567
  if (!group) {
490
- const label = new Intl.DateTimeFormat([], {
568
+ const label = new Intl.DateTimeFormat(this.i18n.intl, {
491
569
  weekday: "long",
492
570
  month: "short",
493
571
  day: "numeric",
@@ -495,25 +573,28 @@ var OfficeUiCalendarAgenda = class extends TeamsElement {
495
573
  }).format(row.o.start);
496
574
  groups.set(
497
575
  key,
498
- group = { label: key === dayKey(now, this.zone) ? `Today, ${label}` : label, rows: [] }
576
+ group = {
577
+ label: key === dayKey(now, this.zone) ? this.t("agenda.todayDate", { date: label }) : label,
578
+ rows: []
579
+ }
499
580
  );
500
581
  }
501
582
  group.rows.push(row);
502
583
  }
503
584
  return html`
504
585
  <header>
505
- <h2>Calendar</h2>
586
+ <h2>${this.t("calendar.title")}</h2>
506
587
  <button
507
588
  class="new"
508
589
  type="button"
509
- title="New meeting"
510
- aria-label="New meeting"
590
+ title=${this.t("calendar.newMeeting")}
591
+ aria-label=${this.t("calendar.newMeeting")}
511
592
  @click=${() => this.fire("office-agenda-new", {})}
512
593
  >
513
594
  ${icon("plus")}
514
595
  </button>
515
596
  </header>
516
- ${groups.size === 0 ? html`<p class="empty">No upcoming meetings. Schedule one with the plus button.</p>` : [...groups.values()].map(
597
+ ${groups.size === 0 ? html`<p class="empty">${this.t("agenda.empty")}</p>` : [...groups.values()].map(
517
598
  (g) => html`<section>
518
599
  <h3>${g.label}</h3>
519
600
  <ul>
@@ -537,20 +618,20 @@ var OfficeUiCalendarAgenda = class extends TeamsElement {
537
618
  >
538
619
  <span class="time">${time}</span>
539
620
  <span class="title">${title}</span>
540
- <span class="who">${m.mine ? "You organized" : m.organizerName}</span>
621
+ <span class="who">${m.mine ? this.t("agenda.youOrganized") : m.organizerName}</span>
541
622
  </button>
542
623
  ${joinable ? html`<button
543
624
  type="button"
544
625
  class="join"
545
- aria-label=${`Join ${title}`}
626
+ aria-label=${this.t("agenda.join", { title })}
546
627
  @click=${() => this.fire("office-agenda-join", { meetingId: m.id })}
547
628
  >
548
- ${m.live ? "Join now" : "Join"}
629
+ ${this.t(m.live ? "call.joinNow" : "call.join")}
549
630
  </button>` : ""}
550
631
  </li>`;
551
632
  }
552
633
  clock(ms) {
553
- return new Date(ms).toLocaleTimeString([], {
634
+ return new Date(ms).toLocaleTimeString(this.i18n.intl, {
554
635
  hour: "numeric",
555
636
  minute: "2-digit",
556
637
  timeZone: this.zone
@@ -567,9 +648,9 @@ var calendar_view_default = ":host {\n --_hour: 4em;\n display: flex;\n flex-dir
567
648
 
568
649
  // src/teams/calendar-view.ts
569
650
  var MODES = [
570
- { id: "workweek", label: "Work week" },
571
- { id: "week", label: "Week" },
572
- { id: "day", label: "Day" }
651
+ { id: "workweek", label: "calendar.workWeek" },
652
+ { id: "week", label: "calendar.week" },
653
+ { id: "day", label: "calendar.day" }
573
654
  ];
574
655
  var SLOT_MINUTES = 30;
575
656
  var FIRST_VISIBLE_HOUR = 8;
@@ -601,7 +682,6 @@ var OfficeUiCalendarView = class extends TeamsElement {
601
682
  connectedCallback() {
602
683
  super.connectedCallback();
603
684
  this.setAttribute("role", "region");
604
- this.setAttribute("aria-label", "Calendar");
605
685
  if (!this.anchor) this.anchor = this.clock();
606
686
  if (globalThis.matchMedia?.("(max-width: 40em)").matches && this.mode === "workweek")
607
687
  this.mode = "day";
@@ -611,6 +691,9 @@ var OfficeUiCalendarView = class extends TeamsElement {
611
691
  this.resize.observe(this);
612
692
  }
613
693
  }
694
+ hostLabel() {
695
+ return this.t("calendar.title");
696
+ }
614
697
  disconnectedCallback() {
615
698
  clearInterval(this.timer);
616
699
  this.resize?.disconnect();
@@ -636,10 +719,11 @@ var OfficeUiCalendarView = class extends TeamsElement {
636
719
  range(days) {
637
720
  const first = days[0];
638
721
  const last = days[days.length - 1];
639
- const fmt = (opts, ms) => new Intl.DateTimeFormat([], { ...opts, timeZone: this.zone }).format(ms);
722
+ const intl = this.i18n.intl;
723
+ const fmt = (opts, ms) => new Intl.DateTimeFormat(intl, { ...opts, timeZone: this.zone }).format(ms);
640
724
  if (days.length === 1)
641
725
  return fmt({ weekday: "long", month: "long", day: "numeric", year: "numeric" }, first.start);
642
- return new Intl.DateTimeFormat([], {
726
+ return new Intl.DateTimeFormat(intl, {
643
727
  month: "long",
644
728
  day: "numeric",
645
729
  year: "numeric",
@@ -666,12 +750,12 @@ var OfficeUiCalendarView = class extends TeamsElement {
666
750
  <div class="bar">
667
751
  <h2 class="range" aria-live="polite">${this.range(days)}</h2>
668
752
  <button type="button" class="today" @click=${() => this.anchor = this.clock()}>
669
- Today
753
+ ${this.t("time.today")}
670
754
  </button>
671
755
  <button
672
756
  type="button"
673
757
  class="step"
674
- aria-label=${this.mode === "day" ? "Previous day" : "Previous week"}
758
+ aria-label=${this.t(this.mode === "day" ? "calendar.previousDay" : "calendar.previousWeek")}
675
759
  @click=${() => this.anchor = shiftAnchor(this.anchor || now, this.mode, -1, this.zone)}
676
760
  >
677
761
  ${icon("back")}
@@ -679,19 +763,19 @@ var OfficeUiCalendarView = class extends TeamsElement {
679
763
  <button
680
764
  type="button"
681
765
  class="step next"
682
- aria-label=${this.mode === "day" ? "Next day" : "Next week"}
766
+ aria-label=${this.t(this.mode === "day" ? "calendar.nextDay" : "calendar.nextWeek")}
683
767
  @click=${() => this.anchor = shiftAnchor(this.anchor || now, this.mode, 1, this.zone)}
684
768
  >
685
769
  ${icon("back")}
686
770
  </button>
687
- <div class="modes" role="group" aria-label="Calendar view">
771
+ <div class="modes" role="group" aria-label=${this.t("calendar.viewLabel")}>
688
772
  ${MODES.map(
689
773
  (m) => html`<button
690
774
  type="button"
691
775
  aria-pressed=${String(this.mode === m.id)}
692
776
  @click=${() => this.setMode(m.id)}
693
777
  >
694
- ${m.label}
778
+ ${this.t(m.label)}
695
779
  </button>`
696
780
  )}
697
781
  </div>
@@ -700,7 +784,7 @@ var OfficeUiCalendarView = class extends TeamsElement {
700
784
  class="new"
701
785
  @click=${() => this.fire("office-calendar-new", { start: 0 })}
702
786
  >
703
- ${icon("plus")} New meeting
787
+ ${icon("plus")} ${this.t("calendar.newMeeting")}
704
788
  </button>
705
789
  </div>
706
790
  <div class="scroller">
@@ -717,15 +801,16 @@ var OfficeUiCalendarView = class extends TeamsElement {
717
801
  }
718
802
  hourLabel(hour) {
719
803
  if (hour === 0) return "";
720
- return new Date(Date.UTC(2e3, 0, 1, hour)).toLocaleTimeString([], {
804
+ return new Date(Date.UTC(2e3, 0, 1, hour)).toLocaleTimeString(this.i18n.intl, {
721
805
  hour: "numeric",
722
806
  timeZone: "UTC"
723
807
  });
724
808
  }
725
809
  dayHead(day, today) {
726
- const name = new Intl.DateTimeFormat([], { weekday: "short", timeZone: this.zone }).format(
727
- day.start
728
- );
810
+ const name = new Intl.DateTimeFormat(this.i18n.intl, {
811
+ weekday: "short",
812
+ timeZone: this.zone
813
+ }).format(day.start);
729
814
  return html`<div class="head" data-today=${String(today)}>
730
815
  <span class="dow">${name}</span>
731
816
  <span class="dom">${day.day}</span>
@@ -741,7 +826,7 @@ var OfficeUiCalendarView = class extends TeamsElement {
741
826
  this.zone
742
827
  );
743
828
  const byKey = new Map(occurrences.map((x) => [x.o.key, x.m]));
744
- const label = new Intl.DateTimeFormat([], {
829
+ const label = new Intl.DateTimeFormat(this.i18n.intl, {
745
830
  weekday: "long",
746
831
  month: "long",
747
832
  day: "numeric",
@@ -788,7 +873,7 @@ var OfficeUiCalendarView = class extends TeamsElement {
788
873
  </div>`;
789
874
  }
790
875
  clockText(ms) {
791
- return new Date(ms).toLocaleTimeString([], {
876
+ return new Date(ms).toLocaleTimeString(this.i18n.intl, {
792
877
  hour: "numeric",
793
878
  minute: "2-digit",
794
879
  timeZone: this.zone
@@ -812,11 +897,13 @@ var OfficeUiCallGrid = class extends TeamsElement {
812
897
  connectedCallback() {
813
898
  super.connectedCallback();
814
899
  this.setAttribute("role", "list");
815
- this.setAttribute("aria-label", "Participants");
900
+ }
901
+ hostLabel() {
902
+ return this.t("call.participants");
816
903
  }
817
904
  render() {
818
905
  const people = this.participants;
819
- if (people.length === 0) return html`<p class="empty">Nobody is in the call</p>`;
906
+ if (people.length === 0) return html`<p class="empty">${this.t("call.nobody")}</p>`;
820
907
  const sharer = people.find((p) => p.screen);
821
908
  const spotlight = sharer !== void 0 && people.length > 1;
822
909
  const ordered = sharer ? [sharer, ...people.filter((p) => p !== sharer)] : people;
@@ -851,11 +938,11 @@ var OfficeUiCallGrid = class extends TeamsElement {
851
938
  ${showAvatar ? html`<office-ui-avatar size="xl" name=${p.name} seed=${p.id}></office-ui-avatar>` : nothing}
852
939
  <div class="name">
853
940
  ${p.audio === false ? html`<span class="muted">${icon("micOff")}</span>` : nothing}
854
- <span>${p.self ? `${p.name} (you)` : p.name}</span>
941
+ <span>${p.self ? this.t("common.nameYou", { name: p.name }) : p.name}</span>
855
942
  </div>
856
- ${p.hand ? html`<span class="hand" role="img" aria-label="Hand raised">✋</span>` : nothing}
943
+ ${p.hand ? html`<span class="hand" role="img" aria-label=${this.t("call.handRaised")}>✋</span>` : nothing}
857
944
  ${!p.self && p.connection && p.connection !== "connected" ? html`<span class="status"
858
- >${p.connection === "failed" ? "Connection failed" : "Connecting\u2026"}</span
945
+ >${this.t(p.connection === "failed" ? "call.connectionFailed" : "call.connecting")}</span
859
946
  >` : nothing}
860
947
  </div>
861
948
  `;
@@ -877,7 +964,7 @@ var OfficeUiChannelList = class extends TeamsElement {
877
964
  };
878
965
  constructor() {
879
966
  super();
880
- this.heading = "Channels";
967
+ this.heading = "";
881
968
  this.channels = [];
882
969
  this.selectedId = "";
883
970
  this.collapsed = false;
@@ -892,13 +979,13 @@ var OfficeUiChannelList = class extends TeamsElement {
892
979
  @click=${() => this.collapsed = !this.collapsed}
893
980
  >
894
981
  <span class="chevron" data-open=${String(!this.collapsed)}>${icon("chevronRight")}</span>
895
- <span class="name">${this.heading}</span>
982
+ <span class="name">${this.heading || this.t("channels.heading")}</span>
896
983
  </button>
897
984
  <button
898
985
  class="add"
899
986
  type="button"
900
- title="Add channel"
901
- aria-label="Add channel"
987
+ title=${this.t("channels.add")}
988
+ aria-label=${this.t("channels.add")}
902
989
  @click=${() => this.fire("office-channel-create", {})}
903
990
  >
904
991
  ${icon("plus")}
@@ -917,7 +1004,9 @@ var OfficeUiChannelList = class extends TeamsElement {
917
1004
  >
918
1005
  ${icon(c.kind === "direct" ? "message" : "hash")}
919
1006
  <span class="title">${c.name}</span>
920
- ${c.live ? html`<span class="live" title="Call in progress">${icon("video")}</span>` : ""}
1007
+ ${c.live ? html`<span class="live" title=${this.t("call.inProgress")}
1008
+ >${icon("video")}</span
1009
+ >` : ""}
921
1010
  ${c.unread ? html`<span class="count">${c.unread > 99 ? "99+" : c.unread}</span>` : ""}
922
1011
  </button>
923
1012
  </li>
@@ -978,7 +1067,7 @@ var OfficeUiChatComposer = class extends TeamsElement {
978
1067
  this.replyingTo = null;
979
1068
  this.editing = false;
980
1069
  this.disabled = false;
981
- this.placeholder = "Type a message";
1070
+ this.placeholder = "";
982
1071
  this.value = "";
983
1072
  this.files = [];
984
1073
  this.missingFiles = [];
@@ -1111,7 +1200,7 @@ var OfficeUiChatComposer = class extends TeamsElement {
1111
1200
  typingLine() {
1112
1201
  const n = this.typing.slice(0, 3);
1113
1202
  if (n.length === 0) return "";
1114
- return n.length === 1 ? `${n[0]} is typing\u2026` : `${n.join(", ")} are typing\u2026`;
1203
+ return n.length === 1 ? this.t("composer.typingOne", { name: n[0] }) : this.t("composer.typingMany", { names: n.join(", ") });
1115
1204
  }
1116
1205
  mentionList() {
1117
1206
  const options = this.pickerOptions();
@@ -1120,7 +1209,7 @@ var OfficeUiChatComposer = class extends TeamsElement {
1120
1209
  id="mention-list"
1121
1210
  class="mentions"
1122
1211
  role="listbox"
1123
- aria-label="People to mention"
1212
+ aria-label=${this.t("composer.mentionPeople")}
1124
1213
  @mousedown=${(event) => event.preventDefault()}
1125
1214
  >
1126
1215
  ${options.map(
@@ -1136,13 +1225,13 @@ var OfficeUiChatComposer = class extends TeamsElement {
1136
1225
  seed=${target.id}
1137
1226
  ></office-ui-avatar>` : html`<span class="everyone" aria-hidden="true">@</span>`}
1138
1227
  <span class="name">${target.name}</span>
1139
- ${target.kind === "user" ? nothing : html`<span class="hint">${target.kind === "channel" ? "Notify everyone in this channel" : "Notify the whole team"}</span>`}
1228
+ ${target.kind === "user" ? nothing : html`<span class="hint">${this.t(target.kind === "channel" ? "composer.notifyChannel" : "composer.notifyTeam")}</span>`}
1140
1229
  </li>`
1141
1230
  )}
1142
1231
  </ul>`;
1143
1232
  }
1144
1233
  render() {
1145
- const banner = this.editing ? "Editing message" : this.replyingTo !== null ? `Replying to ${this.replyingTo}` : "";
1234
+ const banner = this.editing ? this.t("composer.editing") : this.replyingTo !== null ? this.t("composer.replyingTo", { name: this.replyingTo }) : "";
1146
1235
  return html`
1147
1236
  <div class="typing" aria-live="polite">${this.typingLine()}</div>
1148
1237
  <div
@@ -1162,19 +1251,19 @@ var OfficeUiChatComposer = class extends TeamsElement {
1162
1251
  }}
1163
1252
  >
1164
1253
  ${this.missingFiles.length ? html`<div role="status">
1165
- Reattach or discard these draft attachments before sending:
1254
+ ${this.t("composer.reattach")}
1166
1255
  <ul>
1167
1256
  ${this.missingFiles.map(
1168
1257
  (name) => html`<li>
1169
1258
  ${name}<button
1170
1259
  type="button"
1171
- aria-label=${`Discard missing ${name}`}
1260
+ aria-label=${this.t("composer.discardMissing", { name })}
1172
1261
  @click=${() => {
1173
1262
  this.missingFiles = this.missingFiles.filter((item) => item !== name);
1174
1263
  this.saveDraft();
1175
1264
  }}
1176
1265
  >
1177
- Discard
1266
+ ${this.t("composer.discard")}
1178
1267
  </button>
1179
1268
  </li>`
1180
1269
  )}
@@ -1184,7 +1273,7 @@ var OfficeUiChatComposer = class extends TeamsElement {
1184
1273
  <span>${banner}</span>
1185
1274
  <button
1186
1275
  type="button"
1187
- aria-label="Cancel"
1276
+ aria-label=${this.t("common.cancel")}
1188
1277
  @click=${() => this.fire("office-chat-cancel", {})}
1189
1278
  >
1190
1279
  ${icon("close")}
@@ -1193,12 +1282,12 @@ var OfficeUiChatComposer = class extends TeamsElement {
1193
1282
  ${this.mentionList()}
1194
1283
  <textarea
1195
1284
  rows="1"
1196
- aria-label="Message"
1285
+ aria-label=${this.t("composer.message")}
1197
1286
  aria-haspopup="listbox"
1198
1287
  aria-controls=${this.pickerOptions().length ? "mention-list" : nothing}
1199
1288
  aria-activedescendant=${this.pickerOptions().length ? `mention-option-${this.pickerIndex}` : nothing}
1200
1289
  .value=${live(this.value)}
1201
- placeholder=${this.placeholder}
1290
+ placeholder=${this.placeholder || this.t("composer.placeholder")}
1202
1291
  ?disabled=${this.disabled}
1203
1292
  @input=${this.onInput}
1204
1293
  @paste=${(event) => {
@@ -1219,7 +1308,7 @@ var OfficeUiChatComposer = class extends TeamsElement {
1219
1308
  (f, i) => html`<li>
1220
1309
  <button
1221
1310
  type="button"
1222
- aria-label=${`Remove ${f.name}`}
1311
+ aria-label=${this.t("people.remove", { name: f.name })}
1223
1312
  @click=${() => {
1224
1313
  this.files = this.files.filter((_, j) => j !== i);
1225
1314
  this.focusMessage();
@@ -1234,8 +1323,8 @@ var OfficeUiChatComposer = class extends TeamsElement {
1234
1323
  <div class="bar">
1235
1324
  <button
1236
1325
  type="button"
1237
- title="Attach files"
1238
- aria-label="Attach files"
1326
+ title=${this.t("composer.attach")}
1327
+ aria-label=${this.t("composer.attach")}
1239
1328
  ?disabled=${this.disabled}
1240
1329
  @click=${() => this.renderRoot.querySelector("input[type=file]")?.click()}
1241
1330
  >
@@ -1244,8 +1333,8 @@ var OfficeUiChatComposer = class extends TeamsElement {
1244
1333
  <div class="emoji">
1245
1334
  <button
1246
1335
  type="button"
1247
- title="Emoji"
1248
- aria-label="Emoji"
1336
+ title=${this.t("composer.emoji")}
1337
+ aria-label=${this.t("composer.emoji")}
1249
1338
  aria-expanded=${String(this.emojiOpen)}
1250
1339
  ?disabled=${this.disabled}
1251
1340
  @click=${() => this.emojiOpen = !this.emojiOpen}
@@ -1279,8 +1368,8 @@ var OfficeUiChatComposer = class extends TeamsElement {
1279
1368
  <button
1280
1369
  type="button"
1281
1370
  class="send"
1282
- title="Send"
1283
- aria-label="Send message"
1371
+ title=${this.t("composer.send")}
1372
+ aria-label=${this.t("composer.sendMessage")}
1284
1373
  ?disabled=${!this.canSend}
1285
1374
  @click=${this.submit}
1286
1375
  >
@@ -1331,16 +1420,19 @@ var OfficeUiChatList = class extends TeamsElement {
1331
1420
  super.connectedCallback();
1332
1421
  this.setAttribute("role", "log");
1333
1422
  this.setAttribute("aria-live", "polite");
1334
- this.setAttribute("aria-label", "Messages");
1335
1423
  this.addEventListener("scroll", () => {
1336
1424
  this.stickToBottom = this.scrollHeight - this.scrollTop - this.clientHeight < 64;
1337
1425
  });
1338
1426
  }
1427
+ hostLabel() {
1428
+ return this.t("chat.messages");
1429
+ }
1339
1430
  updated() {
1340
1431
  if (this.stickToBottom) this.scrollTop = this.scrollHeight;
1341
1432
  }
1342
1433
  render() {
1343
- if (this.messages.length === 0) return html`<p class="empty">No messages yet. Say hello.</p>`;
1434
+ if (this.messages.length === 0) return html`<p class="empty">${this.t("chat.empty")}</p>`;
1435
+ const now = Date.now();
1344
1436
  const byId = new Map(this.messages.map((m) => [m.id, m]));
1345
1437
  const rows = [];
1346
1438
  let previous;
@@ -1350,9 +1442,9 @@ var OfficeUiChatList = class extends TeamsElement {
1350
1442
  previous = void 0;
1351
1443
  continue;
1352
1444
  }
1353
- if (!previous || dayLabel(previous.ts) !== dayLabel(m.ts))
1354
- rows.push(html`<div class="day"><span>${dayLabel(m.ts)}</span></div>`);
1355
- const grouped = previous !== void 0 && previous.authorId === m.authorId && m.ts - previous.ts < GROUP_MS && !m.replyTo && dayLabel(previous.ts) === dayLabel(m.ts);
1445
+ if (!previous || dayLabel(previous.ts, now, this.locale) !== dayLabel(m.ts, now, this.locale))
1446
+ rows.push(html`<div class="day"><span>${dayLabel(m.ts, now, this.locale)}</span></div>`);
1447
+ const grouped = previous !== void 0 && previous.authorId === m.authorId && m.ts - previous.ts < GROUP_MS && !m.replyTo && dayLabel(previous.ts, now, this.locale) === dayLabel(m.ts, now, this.locale);
1356
1448
  rows.push(this.message(m, grouped, byId.get(m.replyTo ?? "")));
1357
1449
  previous = m;
1358
1450
  }
@@ -1377,18 +1469,18 @@ var OfficeUiChatList = class extends TeamsElement {
1377
1469
  <div class="body">
1378
1470
  ${grouped ? nothing : html`<header>
1379
1471
  <span class="author">${m.authorName}</span>
1380
- <time>${formatTime(m.ts)}</time>
1472
+ <time>${formatTime(m.ts, this.locale)}</time>
1381
1473
  </header>`}
1382
1474
  ${this.pinMark(m)}
1383
1475
  <div class="bubble">
1384
1476
  ${parent ? html`<blockquote>
1385
1477
  <strong>${parent.authorName}</strong
1386
- >${parent.deleted ? "This message was deleted" : parent.text.slice(0, 120)}
1478
+ >${parent.deleted ? this.t("chat.deleted") : parent.text.slice(0, 120)}
1387
1479
  </blockquote>` : nothing}
1388
- ${m.deleted ? html`<span class="deleted">This message was deleted</span>` : html`<span class="text">${this.body(m)}</span>${m.editedAt ? html` <span class="edited">(edited)</span>` : nothing}`}
1480
+ ${m.deleted ? html`<span class="deleted">${this.t("chat.deleted")}</span>` : html`<span class="text">${this.body(m)}</span>${m.editedAt ? html` <span class="edited">${this.t("chat.edited")}</span>` : nothing}`}
1389
1481
  ${!m.deleted && m.meetingId ? this.meetingCard(m.meetingId) : nothing}
1390
1482
  ${!m.deleted && m.attachments?.length ? this.files(m.attachments) : nothing}
1391
- ${!m.deleted && m.tabId ? html`<button class="thread-link" type="button" @click=${() => this.fire("office-chat-open-tab", { tabId: m.tabId })}>Open tab</button>` : nothing}
1483
+ ${!m.deleted && m.tabId ? html`<button class="thread-link" type="button" @click=${() => this.fire("office-chat-open-tab", { tabId: m.tabId })}>${this.t("chat.openTab")}</button>` : nothing}
1392
1484
  </div>
1393
1485
  ${!m.deleted && reactions.length ? html`<div class="reactions">
1394
1486
  ${reactions.map(
@@ -1406,7 +1498,7 @@ var OfficeUiChatList = class extends TeamsElement {
1406
1498
  )}
1407
1499
  </div>` : nothing}
1408
1500
  ${m.deleted ? nothing : this.toolbar(m, own)}
1409
- ${this.replyCounts[m.id] === void 0 ? nothing : html`<button type="button" class="thread-link" @click=${() => this.fire("office-chat-thread", { messageId: m.id })}>${this.replyCounts[m.id]} ${this.replyCounts[m.id] === 1 ? "reply" : "replies"}</button>`}
1501
+ ${this.replyCounts[m.id] === void 0 ? nothing : html`<button type="button" class="thread-link" @click=${() => this.fire("office-chat-thread", { messageId: m.id })}>${this.tn("chat.replies", this.replyCounts[m.id])}</button>`}
1410
1502
  </div>
1411
1503
  </article>
1412
1504
  `;
@@ -1427,38 +1519,38 @@ var OfficeUiChatList = class extends TeamsElement {
1427
1519
  return html`<div class="meeting-card" data-gone="true">
1428
1520
  <span class="meeting-icon">${icon("calendar")}</span>
1429
1521
  <span class="meeting-meta">
1430
- <span class="meeting-title">This meeting was canceled or is not in your calendar</span>
1522
+ <span class="meeting-title">${this.t("chat.meetingGone")}</span>
1431
1523
  </span>
1432
1524
  </div>`;
1433
1525
  return html`<div class="meeting-card">
1434
1526
  <span class="meeting-icon">${icon("calendar")}</span>
1435
1527
  <span class="meeting-meta">
1436
1528
  <span class="meeting-title">${card.title}</span>
1437
- <span class="meeting-time">${formatWhen(card.start, card.end)}</span>
1529
+ <span class="meeting-time">${formatWhen(card.start, card.end, this.locale)}</span>
1438
1530
  </span>
1439
1531
  <button
1440
1532
  type="button"
1441
1533
  class="meeting-open"
1442
1534
  @click=${() => this.fire("office-chat-meeting", { meetingId, action: "open" })}
1443
1535
  >
1444
- Details
1536
+ ${this.t("chat.meetingDetails")}
1445
1537
  </button>
1446
1538
  <button
1447
1539
  type="button"
1448
1540
  class="meeting-join"
1449
1541
  @click=${() => this.fire("office-chat-meeting", { meetingId, action: "join" })}
1450
1542
  >
1451
- ${card.live ? "Join now" : "Join"}
1543
+ ${this.t(card.live ? "call.joinNow" : "call.join")}
1452
1544
  </button>
1453
1545
  </div>`;
1454
1546
  }
1455
1547
  toolbar(m, own) {
1456
1548
  return html`
1457
- <div class="toolbar" role="toolbar" aria-label="Message actions">
1549
+ <div class="toolbar" role="toolbar" aria-label=${this.t("chat.messageActions")}>
1458
1550
  ${QUICK_REACTIONS.slice(0, 4).map(
1459
1551
  (emoji) => html`<button
1460
1552
  type="button"
1461
- aria-label=${`React ${emoji}`}
1553
+ aria-label=${this.t("chat.react", { emoji })}
1462
1554
  @click=${() => this.fire("office-chat-react", { messageId: m.id, emoji })}
1463
1555
  >
1464
1556
  ${emoji}
@@ -1466,8 +1558,9 @@ var OfficeUiChatList = class extends TeamsElement {
1466
1558
  )}
1467
1559
  <button
1468
1560
  type="button"
1469
- title="Reply"
1470
- aria-label="Reply"
1561
+ data-action="reply"
1562
+ title=${this.t("chat.reply")}
1563
+ aria-label=${this.t("chat.reply")}
1471
1564
  @click=${() => this.fire("office-chat-reply", { messageId: m.id })}
1472
1565
  >
1473
1566
  ${icon("reply")}
@@ -1475,16 +1568,16 @@ var OfficeUiChatList = class extends TeamsElement {
1475
1568
  ${this.pinButton(m)}
1476
1569
  ${own ? html`<button
1477
1570
  type="button"
1478
- title="Edit"
1479
- aria-label="Edit"
1571
+ title=${this.t("common.edit")}
1572
+ aria-label=${this.t("common.edit")}
1480
1573
  @click=${() => this.fire("office-chat-edit", { messageId: m.id })}
1481
1574
  >
1482
1575
  ${icon("pencil")}
1483
1576
  </button>
1484
1577
  <button
1485
1578
  type="button"
1486
- title="Delete"
1487
- aria-label="Delete"
1579
+ title=${this.t("chat.delete")}
1580
+ aria-label=${this.t("chat.delete")}
1488
1581
  @click=${() => this.fire("office-chat-delete", { messageId: m.id })}
1489
1582
  >
1490
1583
  ${icon("trash")}
@@ -1493,11 +1586,11 @@ var OfficeUiChatList = class extends TeamsElement {
1493
1586
  `;
1494
1587
  }
1495
1588
  pinMark(m) {
1496
- return !m.deleted && this.pinnedIds.includes(m.id) ? html`<span class="pin-mark">${icon("pin")} Pinned</span>` : nothing;
1589
+ return !m.deleted && this.pinnedIds.includes(m.id) ? html`<span class="pin-mark">${icon("pin")} ${this.t("chat.pinned")}</span>` : nothing;
1497
1590
  }
1498
1591
  pinButton(m) {
1499
1592
  const pinned = this.pinnedIds.includes(m.id);
1500
- const label = pinned ? "Unpin" : "Pin";
1593
+ const label = this.t(pinned ? "chat.unpin" : "chat.pin");
1501
1594
  return html`<button
1502
1595
  type="button"
1503
1596
  title=${label}
@@ -1515,13 +1608,15 @@ var OfficeUiChatList = class extends TeamsElement {
1515
1608
  <button
1516
1609
  type="button"
1517
1610
  class="file"
1518
- aria-label=${`Open ${a.name}`}
1611
+ aria-label=${this.t("chat.openFile", { name: a.name })}
1519
1612
  @click=${() => this.fire("office-chat-open-file", { attachment: a })}
1520
1613
  >
1521
1614
  <span class="badge" data-kind=${a.kind}>${KIND_LETTER[a.kind]}</span>
1522
1615
  <span class="meta">
1523
1616
  <span class="file-name">${a.name}</span>
1524
- <span class="file-size">${a.kind.toUpperCase()} ${formatSize(a.size)}</span>
1617
+ <span class="file-size"
1618
+ >${a.kind.toUpperCase()} ${formatSize(a.size, this.locale)}</span
1619
+ >
1525
1620
  </span>
1526
1621
  </button>
1527
1622
  `
@@ -1535,9 +1630,9 @@ var defineChatList = definer("office-ui-chat-list", () => OfficeUiChatList);
1535
1630
  var chat_sidebar_default = ":host {\n display: block;\n overflow: auto;\n}\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-3) var(--office-space-3) var(--office-space-2) var(--office-space-4);\n}\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\n.new {\n display: grid;\n place-items: center;\n width: 2.2em;\n height: 2.2em;\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n color: var(--office-teams-brand);\n}\n.new:hover {\n background: var(--office-selected);\n}\nul {\n margin: 0;\n padding: 0 var(--office-space-2);\n list-style: none;\n}\n.empty {\n margin: var(--office-space-4);\n color: var(--office-teams-text-subtle);\n}\n.row {\n position: relative;\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n width: 100%;\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n color: var(--office-teams-text-subtle);\n text-align: start;\n}\n.row:hover {\n background: var(--office-selected);\n}\n.row[aria-current='true'] {\n background: var(--office-teams-brand-subtle);\n color: var(--office-foreground);\n}\n.row[aria-current='true']::before {\n content: '';\n position: absolute;\n inset-inline-start: 0;\n inset-block: 20%;\n width: var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n}\n.group {\n display: grid;\n place-items: center;\n flex: none;\n width: 2.3em;\n height: 2.3em;\n border-radius: var(--office-radius-full);\n background: var(--office-teams-bubble);\n color: var(--office-teams-brand);\n}\n.text {\n display: grid;\n flex: 1;\n min-width: 0;\n}\n.line {\n display: flex;\n align-items: baseline;\n gap: var(--office-space-2);\n}\n.title {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n color: var(--office-foreground);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\ntime {\n font-size: var(--office-font-size-xs);\n}\n.preview {\n overflow: hidden;\n font-size: var(--office-font-size-sm);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.row[data-unread='true'] .title,\n.row[data-unread='true'] .preview {\n color: var(--office-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.live {\n display: grid;\n color: var(--office-call-live);\n}\n.count {\n min-width: 1.6em;\n padding: 0 var(--office-space-1);\n border-radius: var(--office-radius-full);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: var(--office-font-size-xs);\n line-height: 1.6;\n text-align: center;\n}\n@media (pointer: coarse) {\n .new,\n .row {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n";
1536
1631
 
1537
1632
  // src/teams/chat-sidebar.ts
1538
- function stamp(ts) {
1633
+ function stamp(ts, locale) {
1539
1634
  if (!ts) return "";
1540
- return dayLabel(ts) === "Today" ? formatTime(ts) : new Date(ts).toLocaleDateString([], { month: "short", day: "numeric" });
1635
+ return dayKind(ts) === "today" ? formatTime(ts, locale) : new Date(ts).toLocaleDateString(teamsI18n(locale).intl, { month: "short", day: "numeric" });
1541
1636
  }
1542
1637
  var OfficeUiChatSidebar = class extends TeamsElement {
1543
1638
  static styles = withStyles(chat_sidebar_default);
@@ -1553,25 +1648,28 @@ var OfficeUiChatSidebar = class extends TeamsElement {
1553
1648
  render() {
1554
1649
  return html`
1555
1650
  <header>
1556
- <h2>Chat</h2>
1651
+ <h2>${this.t("rail.chat")}</h2>
1557
1652
  <button
1558
1653
  class="new"
1559
1654
  type="button"
1560
- title="New chat"
1561
- aria-label="New chat"
1655
+ title=${this.t("chat.new")}
1656
+ aria-label=${this.t("chat.new")}
1562
1657
  @click=${() => this.fire("office-chat-new", {})}
1563
1658
  >
1564
1659
  ${icon("plus")}
1565
1660
  </button>
1566
1661
  </header>
1567
- ${this.chats.length === 0 ? html`<p class="empty">No chats yet. Start one with the plus button.</p>` : html`<ul>
1662
+ ${this.chats.length === 0 ? html`<p class="empty">${this.t("chat.none")}</p>` : html`<ul>
1568
1663
  ${this.chats.map((c) => this.row(c))}
1569
1664
  </ul>`}
1570
1665
  `;
1571
1666
  }
1572
1667
  row(c) {
1573
1668
  const other = c.kind === "direct" ? c.members.find((m) => !m.self) : void 0;
1574
- const preview = c.lastMessage ? c.lastMessage.system ? c.lastMessage.text : c.lastMessage.mine ? `You: ${c.lastMessage.text}` : c.kind === "direct" ? c.lastMessage.text : `${c.lastMessage.authorName}: ${c.lastMessage.text}` : "No messages yet";
1669
+ const preview = c.lastMessage ? c.lastMessage.system ? c.lastMessage.text : c.lastMessage.mine ? this.t("chat.previewYou", { text: c.lastMessage.text }) : c.kind === "direct" ? c.lastMessage.text : this.t("chat.previewAuthor", {
1670
+ author: c.lastMessage.authorName,
1671
+ text: c.lastMessage.text
1672
+ }) : this.t("chat.noMessagesPreview");
1575
1673
  return html`
1576
1674
  <li>
1577
1675
  <button
@@ -1591,12 +1689,14 @@ var OfficeUiChatSidebar = class extends TeamsElement {
1591
1689
  <span class="text">
1592
1690
  <span class="line">
1593
1691
  <span class="title">${c.title}</span>
1594
- <time>${stamp(c.ts)}</time>
1692
+ <time>${stamp(c.ts, this.locale)}</time>
1595
1693
  </span>
1596
1694
  <span class="preview">${preview}</span>
1597
1695
  </span>
1598
- ${c.live ? html`<span class="live" title="Call in progress">${icon("video")}</span>` : nothing}
1599
- ${c.unread ? html`<span class="count" aria-label=${`${c.unread} unread`}
1696
+ ${c.live ? html`<span class="live" title=${this.t("call.inProgress")}>${icon("video")}</span>` : nothing}
1697
+ ${c.unread ? html`<span
1698
+ class="count"
1699
+ aria-label=${this.t("chat.unreadCount", { count: c.unread })}
1600
1700
  >${c.unread > 99 ? "99+" : c.unread}</span
1601
1701
  >` : nothing}
1602
1702
  </button>
@@ -1624,7 +1724,7 @@ var OfficeUiPeoplePicker = class extends TeamsElement {
1624
1724
  this.contacts = [];
1625
1725
  this.selected = [];
1626
1726
  this.exclude = [];
1627
- this.label = "People";
1727
+ this.label = "";
1628
1728
  this.query = "";
1629
1729
  }
1630
1730
  toggle(id) {
@@ -1637,14 +1737,15 @@ var OfficeUiPeoplePicker = class extends TeamsElement {
1637
1737
  (c) => !this.exclude.includes(c.id) && (!q || c.name.toLocaleLowerCase().includes(q))
1638
1738
  );
1639
1739
  const chosen = this.contacts.filter((c) => this.selected.includes(c.id));
1740
+ const label = this.label || this.t("people.label");
1640
1741
  return html`
1641
- ${chosen.length ? html`<ul class="chips" aria-label="Selected people">
1742
+ ${chosen.length ? html`<ul class="chips" aria-label=${this.t("people.selected")}>
1642
1743
  ${chosen.map(
1643
1744
  (c) => html`<li>
1644
1745
  <button
1645
1746
  type="button"
1646
1747
  class="chip"
1647
- aria-label=${`Remove ${c.name}`}
1748
+ aria-label=${this.t("people.remove", { name: c.name })}
1648
1749
  @click=${() => this.toggle(c.id)}
1649
1750
  >
1650
1751
  ${c.name} <span aria-hidden="true">×</span>
@@ -1654,14 +1755,14 @@ var OfficeUiPeoplePicker = class extends TeamsElement {
1654
1755
  </ul>` : nothing}
1655
1756
  <input
1656
1757
  type="search"
1657
- placeholder="Search people"
1658
- aria-label=${`Search ${this.label.toLocaleLowerCase()}`}
1758
+ placeholder=${this.t("people.search")}
1759
+ aria-label=${this.t("people.search")}
1659
1760
  .value=${this.query}
1660
1761
  @input=${(e) => this.query = e.target.value}
1661
1762
  />
1662
1763
  ${offered.length === 0 ? html`<p class="none">
1663
- ${this.contacts.length === 0 ? "Nobody else has joined this workspace yet." : "No one matches that search."}
1664
- </p>` : html`<ul class="list" role="group" aria-label=${this.label}>
1764
+ ${this.contacts.length === 0 ? this.t("people.nobodyElse") : this.t("people.noMatch")}
1765
+ </p>` : html`<ul class="list" role="group" aria-label=${label}>
1665
1766
  ${offered.map(
1666
1767
  (c) => html`<li>
1667
1768
  <label>
@@ -1736,20 +1837,20 @@ var OfficeUiPrejoin = class extends TeamsElement {
1736
1837
  .muted=${true}
1737
1838
  .srcObject=${this.preview}
1738
1839
  ></video>` : html`<office-ui-avatar size="xl" name=${this.name}></office-ui-avatar>
1739
- <p>${this.video ? "Starting camera\u2026" : "Your camera is turned off"}</p>`}
1840
+ <p>${this.t(this.video ? "prejoin.startingCamera" : "prejoin.cameraOff")}</p>`}
1740
1841
  </div>
1741
1842
  <div class="panel">
1742
- <h2>Choose your audio and video settings</h2>
1843
+ <h2>${this.t("prejoin.title")}</h2>
1743
1844
  <p class="for">${this.heading}</p>
1744
- ${this.toggle("Camera", this.video ? "video" : "videoOff", this.video, "video")}
1745
- ${this.toggle("Microphone", this.audio ? "mic" : "micOff", this.audio, "audio")}
1845
+ ${this.toggle(this.t("prejoin.camera"), this.video ? "video" : "videoOff", this.video, "video")}
1846
+ ${this.toggle(this.t("prejoin.microphone"), this.audio ? "mic" : "micOff", this.audio, "audio")}
1746
1847
  <div class="actions">
1747
1848
  <button
1748
1849
  type="button"
1749
1850
  class="cancel"
1750
1851
  @click=${() => this.fire("office-prejoin-cancel", {})}
1751
1852
  >
1752
- Cancel
1853
+ ${this.t("common.cancel")}
1753
1854
  </button>
1754
1855
  <button
1755
1856
  type="button"
@@ -1757,7 +1858,7 @@ var OfficeUiPrejoin = class extends TeamsElement {
1757
1858
  ?disabled=${this.busy}
1758
1859
  @click=${() => this.fire("office-prejoin-join", {})}
1759
1860
  >
1760
- ${this.busy ? "Joining\u2026" : "Join now"}
1861
+ ${this.t(this.busy ? "prejoin.joining" : "call.joinNow")}
1761
1862
  </button>
1762
1863
  </div>
1763
1864
  ${nothing}
@@ -1767,6 +1868,71 @@ var OfficeUiPrejoin = class extends TeamsElement {
1767
1868
  }
1768
1869
  };
1769
1870
  var definePrejoin = definer("office-ui-prejoin", () => OfficeUiPrejoin);
1871
+
1872
+ // src/teams/i18n/core-text.ts
1873
+ var exact = (text) => new RegExp(`^${text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}$`, "u");
1874
+ var RULES = [
1875
+ [exact("That person is not available to chat with"), "core.personUnavailable"],
1876
+ [exact("Pick at least one person"), "core.pickPerson"],
1877
+ [exact("The chat could not be created"), "core.chatNotCreated"],
1878
+ [
1879
+ exact("The meeting needs a title and an end after its start (24 hours at most)"),
1880
+ "core.meetingInvalid"
1881
+ ],
1882
+ [exact("Only the organizer can change a meeting"), "core.organizerChange"],
1883
+ [exact("Only the organizer can cancel a meeting"), "core.organizerCancel"],
1884
+ [
1885
+ exact("Only the organizer can cancel an occurrence of a meeting"),
1886
+ "core.organizerCancelOccurrence"
1887
+ ],
1888
+ [
1889
+ exact("Only the organizer can change an occurrence of a meeting"),
1890
+ "core.organizerChangeOccurrence"
1891
+ ],
1892
+ [exact("Camera unavailable"), "core.cameraUnavailable"],
1893
+ [
1894
+ exact("Reattach or discard the missing draft attachments before sending"),
1895
+ "core.reattachDrafts"
1896
+ ],
1897
+ [exact("The edit was not applied; the draft was retained"), "core.editNotApplied"],
1898
+ [exact("Message send canceled. The message was saved in Drafts."), "core.sendCanceled"],
1899
+ [
1900
+ /^Message was not sent: (.*)\. Saved in Drafts\.$/su,
1901
+ "core.sendFailed",
1902
+ (m) => ({ reason: m[1] ?? "" })
1903
+ ],
1904
+ [exact("This message cannot be pinned"), "core.cannotPin"],
1905
+ [exact("Could not open the file"), "core.openFailed"],
1906
+ [exact("This meeting is not available"), "core.meetingUnavailable"],
1907
+ [exact("The channel is no longer available"), "core.channelGone"],
1908
+ [
1909
+ exact("The channel is no longer available; the uploaded files were not shared"),
1910
+ "core.channelGoneUploaded"
1911
+ ],
1912
+ [exact("Configure file storage before sharing files"), "core.configureStorage"],
1913
+ [exact("Configure file storage before sharing attachments"), "core.configureStorage"],
1914
+ [exact("The files could not be shared"), "core.filesNotShared"],
1915
+ [/^Choose between 1 and (\d+) files$/u, "core.chooseFiles", (m) => ({ max: m[1] ?? "" })],
1916
+ [exact("Enter a workbook name"), "core.workbookName"],
1917
+ [/^Upload failed \((\d+)\)$/u, "core.uploadFailed", (m) => ({ status: m[1] ?? "" })],
1918
+ [exact("Server returned an invalid file URL"), "core.invalidFileUrl"],
1919
+ [exact("This file cannot be saved to the channel"), "core.cannotSaveFile"],
1920
+ [exact("The file could not be uploaded"), "core.fileNotUploaded"],
1921
+ [/^Could not load content \((\d+)\)$/u, "core.loadFailed", (m) => ({ status: m[1] ?? "" })],
1922
+ [exact("Content streaming is unavailable"), "core.streamingUnavailable"],
1923
+ [exact("Content is too large to preview"), "core.tooLarge"],
1924
+ [exact("The file is no longer available"), "core.fileGone"],
1925
+ [/^Call is full \((\d+) participants\)$/u, "core.callFull", (m) => ({ max: m[1] ?? "" })],
1926
+ [/^Link failed \((\d+)\)$/u, "core.linkFailed", (m) => ({ status: m[1] ?? "" })]
1927
+ ];
1928
+ function localizeCoreText(text, i18n) {
1929
+ if (i18n.locale === "en") return text;
1930
+ for (const [pattern, key, params] of RULES) {
1931
+ const match = pattern.exec(text);
1932
+ if (match) return i18n.t(key, params?.(match));
1933
+ }
1934
+ return text;
1935
+ }
1770
1936
  var TeamsController = class {
1771
1937
  constructor(host, onNotice, onNotification = () => void 0) {
1772
1938
  this.host = host;
@@ -1903,15 +2069,16 @@ function pickFile(root, accept = OPEN_ACCEPT) {
1903
2069
  input.click();
1904
2070
  });
1905
2071
  }
1906
- function workbookActions(state, actions) {
2072
+ function workbookActions(state, actions, i18n = teamsI18n()) {
2073
+ const { t } = i18n;
1907
2074
  return html`<button type="button" ?disabled=${state.saving} @click=${actions.toggle}>
1908
- ${state.editing ? "View workbook" : "Edit workbook"}
2075
+ ${t(state.editing ? "workbook.view" : "workbook.edit")}
1909
2076
  </button>
1910
2077
  <button type="button" ?disabled=${state.saving} @click=${actions.download}>
1911
- Download workbook copy
2078
+ ${t("workbook.download")}
1912
2079
  </button>
1913
- ${state.canShare ? html`<button type="button" ?disabled=${state.saving || !state.editing} @click=${actions.share}>${state.saving ? "Saving copy..." : "Save copy to channel"}</button>` : nothing}
1914
- ${state.saving ? html`<button type="button" @click=${actions.cancel}>Cancel workbook save</button>` : nothing}`;
2080
+ ${state.canShare ? html`<button type="button" ?disabled=${state.saving || !state.editing} @click=${actions.share}>${t(state.saving ? "workbook.saving" : "workbook.saveToChannel")}</button>` : nothing}
2081
+ ${state.saving ? html`<button type="button" @click=${actions.cancel}>${t("workbook.cancelSave")}</button>` : nothing}`;
1915
2082
  }
1916
2083
  function inline(text, base) {
1917
2084
  return markdownInline(text, base).map((token) => {
@@ -1925,7 +2092,7 @@ function inline(text, base) {
1925
2092
  return token.text;
1926
2093
  });
1927
2094
  }
1928
- function markdownPreview(source, base) {
2095
+ function markdownPreview(source, base, i18n = teamsI18n()) {
1929
2096
  return markdownBlocks(source).map((block) => {
1930
2097
  if (block.kind === "code") return html`<pre><code>${block.text}</code></pre>`;
1931
2098
  if (block.kind === "table" && block.table) {
@@ -1934,7 +2101,7 @@ function markdownPreview(source, base) {
1934
2101
  class="markdown-table"
1935
2102
  tabindex="0"
1936
2103
  role="region"
1937
- aria-label="Markdown table"
2104
+ aria-label=${i18n.t("preview.markdownTable")}
1938
2105
  >
1939
2106
  <table>
1940
2107
  <thead>
@@ -1967,7 +2134,7 @@ function markdownPreview(source, base) {
1967
2134
  }
1968
2135
 
1969
2136
  // src/teams/app/content-preview.ts
1970
- var TeamsContentPreview = class extends LitElement {
2137
+ var TeamsContentPreview = class extends TeamsLitElement {
1971
2138
  static styles = unsafeCSS(content_preview_default);
1972
2139
  static properties = {
1973
2140
  detail: { attribute: false },
@@ -2029,14 +2196,14 @@ var TeamsContentPreview = class extends LitElement {
2029
2196
  /** Called by the workspace before replacing this pane. */
2030
2197
  canLeave() {
2031
2198
  if (this.saving) {
2032
- this.saveError = "Wait for the workbook copy to finish saving or cancel the save.";
2199
+ this.saveError = this.t("preview.waitSave");
2033
2200
  return false;
2034
2201
  }
2035
2202
  if (!this.commitPendingEdit()) {
2036
- this.saveError = "Finish or cancel the current cell edit before leaving.";
2203
+ this.saveError = this.t("preview.finishEditLeave");
2037
2204
  return false;
2038
2205
  }
2039
- return !this.dirty || globalThis.confirm?.("Discard unsaved workbook edits?") === true;
2206
+ return !this.dirty || globalThis.confirm?.(this.t("preview.discardEdits")) === true;
2040
2207
  }
2041
2208
  commitPendingEdit() {
2042
2209
  const viewer = this.shadowRoot?.querySelector(
@@ -2064,15 +2231,15 @@ var TeamsContentPreview = class extends LitElement {
2064
2231
  const kind = detectContentKind(detail.attachment.name, detail.attachment.mime);
2065
2232
  try {
2066
2233
  const url = contentUrl(detail.url, this.ownerDocument.baseURI);
2067
- if (!url) throw new Error("This content has no valid web URL");
2234
+ if (!url) throw new Error(this.t("preview.noUrl"));
2068
2235
  const embed = this.embeds[detail.attachment.kind];
2069
2236
  if (embed || kind === "website") {
2070
2237
  this.frame = contentUrl(embed ? embed(detail) : url, this.ownerDocument.baseURI);
2071
- if (!this.frame) throw new Error("The viewer returned an invalid embed URL");
2238
+ if (!this.frame) throw new Error(this.t("preview.badEmbed"));
2072
2239
  this.status = "ready";
2073
2240
  return;
2074
2241
  }
2075
- if (kind === "other") throw new Error("No embedded viewer is configured for this file type");
2242
+ if (kind === "other") throw new Error(this.t("preview.noViewer"));
2076
2243
  const bytes = await readContent(
2077
2244
  url,
2078
2245
  request.signal,
@@ -2086,26 +2253,26 @@ var TeamsContentPreview = class extends LitElement {
2086
2253
  if (kind === "xlsx") (await import('./xlsx.js')).defineXlsxEditor();
2087
2254
  if (kind === "vsdx") (await import('./visio.js')).registerVisioViewer();
2088
2255
  if (kind === "pptx")
2089
- (await import('./presentation-preview-HVKJ3KJN.js')).defineTeamsPresentationPreview();
2256
+ (await import('./presentation-preview-HMEBDYEL.js')).defineTeamsPresentationPreview();
2090
2257
  if (request.signal.aborted) return;
2091
2258
  this.native = kind;
2092
2259
  await this.updateComplete;
2093
2260
  if (request.signal.aborted) return;
2094
2261
  const viewer = this.shadowRoot?.querySelector("docx-editor, xlsx-editor, visio-viewer, teams-presentation-preview");
2095
- if (!viewer) throw new Error("The viewer could not be mounted");
2262
+ if (!viewer) throw new Error(this.t("preview.notMounted"));
2096
2263
  await viewer.load(bytes, detail.attachment.name);
2097
2264
  }
2098
2265
  if (!request.signal.aborted) this.status = "ready";
2099
2266
  } catch (error) {
2100
2267
  if (request.signal.aborted) return;
2101
2268
  this.native = null;
2102
- this.error = error instanceof Error ? error.message : "Could not preview content";
2269
+ this.error = error instanceof Error ? localizeCoreText(error.message, this.i18n) : this.t("preview.failed");
2103
2270
  this.status = "error";
2104
2271
  }
2105
2272
  }
2106
2273
  toggleWorkbook() {
2107
2274
  if (!this.commitPendingEdit()) {
2108
- this.saveError = "Finish or cancel the current cell edit before switching modes.";
2275
+ this.saveError = this.t("preview.finishEditSwitch");
2109
2276
  return;
2110
2277
  }
2111
2278
  this.saveError = "";
@@ -2115,7 +2282,7 @@ var TeamsContentPreview = class extends LitElement {
2115
2282
  if (this.saving || !download && !this.saveCopy || this.native !== "xlsx" || !this.detail)
2116
2283
  return;
2117
2284
  if (!this.commitPendingEdit()) {
2118
- this.saveError = "Finish or cancel the current cell edit before saving.";
2285
+ this.saveError = this.t("preview.finishEditSave");
2119
2286
  return;
2120
2287
  }
2121
2288
  const viewer = this.shadowRoot?.querySelector("xlsx-editor");
@@ -2150,7 +2317,7 @@ var TeamsContentPreview = class extends LitElement {
2150
2317
  this.saved = true;
2151
2318
  } catch (error) {
2152
2319
  if (!request?.signal.aborted && !controller.signal.aborted)
2153
- this.saveError = error instanceof Error ? error.message : "Could not save the workbook copy";
2320
+ this.saveError = error instanceof Error ? localizeCoreText(error.message, this.i18n) : this.t("preview.saveFailed");
2154
2321
  } finally {
2155
2322
  if (this.saveAbort === controller) {
2156
2323
  this.saveAbort = void 0;
@@ -2162,14 +2329,14 @@ var TeamsContentPreview = class extends LitElement {
2162
2329
  this.saveAbort?.abort();
2163
2330
  this.saveAbort = void 0;
2164
2331
  this.saving = false;
2165
- this.saveError = "Workbook save canceled. Your edits remain local.";
2332
+ this.saveError = this.t("preview.saveCanceled");
2166
2333
  }
2167
2334
  render() {
2168
2335
  const detail = this.detail;
2169
2336
  if (!detail) return nothing;
2170
2337
  const kind = detectContentKind(detail.attachment.name, detail.attachment.mime);
2171
2338
  const url = contentUrl(detail.url, this.ownerDocument.baseURI);
2172
- return html`<section aria-label="Content preview">
2339
+ return html`<section aria-label=${this.t("preview.title")}>
2173
2340
  <header>
2174
2341
  <h2>${detail.attachment.name}</h2>
2175
2342
  ${this.native === "xlsx" && this.status === "ready" ? workbookActions(
@@ -2179,24 +2346,27 @@ var TeamsContentPreview = class extends LitElement {
2179
2346
  share: () => void this.saveWorkbookCopy(),
2180
2347
  download: () => void this.saveWorkbookCopy(true),
2181
2348
  cancel: () => this.cancelWorkbookSave()
2182
- }
2349
+ },
2350
+ this.i18n
2183
2351
  ) : nothing}
2184
- ${url ? html`<a href=${url} target="_blank" rel="noopener noreferrer">Open externally</a>` : nothing}
2352
+ ${url ? html`<a href=${url} target="_blank" rel="noopener noreferrer">${this.t("preview.openExternally")}</a>` : nothing}
2185
2353
  <button
2186
2354
  type="button"
2187
2355
  @click=${() => this.dispatchEvent(new CustomEvent("teams-preview-close", { bubbles: true, composed: true }))}
2188
2356
  >
2189
- Close preview
2357
+ ${this.t("preview.close")}
2190
2358
  </button>
2191
2359
  </header>
2192
- ${this.native === "xlsx" ? html`<p class="hint">Edits stay local. Download a copy${this.saveCopy ? " or save a copy to the channel" : ""} to keep them. Other people keep their own copy open.</p>` : nothing}
2193
- ${this.saved ? html`<p role="status">Workbook copy shared in the channel.</p>` : nothing}
2194
- ${this.downloaded ? html`<p role="status">Workbook download started. Your changes remain local.</p>` : nothing}
2360
+ ${this.native === "xlsx" ? html`<p class="hint">${this.t(this.saveCopy ? "preview.editsLocalShare" : "preview.editsLocal")}</p>` : nothing}
2361
+ ${this.saved ? html`<p role="status">${this.t("preview.copyShared")}</p>` : nothing}
2362
+ ${this.downloaded ? html`<p role="status">${this.t("preview.downloadStarted")}</p>` : nothing}
2195
2363
  ${this.saveError ? html`<p role="alert">${this.saveError}</p>` : nothing}
2196
- ${this.status === "loading" ? html`<p role="status">Loading preview...</p>` : nothing}
2364
+ ${this.status === "loading" ? html`<p role="status">${this.t("preview.loading")}</p>` : nothing}
2197
2365
  ${this.status === "error" ? html`<p role="alert">${this.error}</p>
2198
- <button type="button" @click=${() => void this.load()}>Retry</button>` : nothing}
2199
- ${this.frame ? html`<p class="hint">If this site blocks embedding, use Open externally.</p>
2366
+ <button type="button" @click=${() => void this.load()}>
2367
+ ${this.t("common.retry")}
2368
+ </button>` : nothing}
2369
+ ${this.frame ? html`<p class="hint">${this.t("preview.blocksEmbedding")}</p>
2200
2370
  <iframe
2201
2371
  title=${detail.attachment.name}
2202
2372
  src=${this.frame}
@@ -2205,8 +2375,13 @@ var TeamsContentPreview = class extends LitElement {
2205
2375
  ></iframe>` : nothing}
2206
2376
  ${keyed(
2207
2377
  detail,
2208
- this.native === "docx" ? html`<docx-editor .readOnly=${true} .fileName=${detail.attachment.name}></docx-editor>` : this.native === "xlsx" ? html`<xlsx-editor
2378
+ this.native === "docx" ? html`<docx-editor
2379
+ .readOnly=${true}
2380
+ .locale=${this.i18n.locale}
2381
+ .fileName=${detail.attachment.name}
2382
+ ></docx-editor>` : this.native === "xlsx" ? html`<xlsx-editor
2209
2383
  .readOnly=${!this.editing}
2384
+ .locale=${this.i18n.locale}
2210
2385
  .fileName=${detail.attachment.name}
2211
2386
  @workbook-change=${() => {
2212
2387
  this.revision++;
@@ -2220,7 +2395,7 @@ var TeamsContentPreview = class extends LitElement {
2220
2395
  @file-command=${(e) => {
2221
2396
  if (["save", "saveAs", "export", "exportCsv"].includes(e.detail.command) && !this.commitPendingEdit()) {
2222
2397
  e.preventDefault();
2223
- this.saveError = "Finish or cancel the current cell edit before saving.";
2398
+ this.saveError = this.t("preview.finishEditSave");
2224
2399
  return;
2225
2400
  }
2226
2401
  if (e.detail.command === "save") {
@@ -2229,10 +2404,10 @@ var TeamsContentPreview = class extends LitElement {
2229
2404
  }
2230
2405
  if (e.detail.command === "new" || e.detail.command === "open") e.preventDefault();
2231
2406
  }}
2232
- ></xlsx-editor>` : this.native === "vsdx" ? html`<visio-viewer .showToolbar=${false}></visio-viewer>` : this.native === "pptx" ? html`<teams-presentation-preview></teams-presentation-preview>` : nothing
2407
+ ></xlsx-editor>` : this.native === "vsdx" ? html`<visio-viewer .showToolbar=${false} locale=${this.i18n.locale}></visio-viewer>` : this.native === "pptx" ? html`<teams-presentation-preview></teams-presentation-preview>` : nothing
2233
2408
  )}
2234
2409
  ${this.status === "ready" && !this.frame && !this.native ? html`<article>
2235
- ${kind === "markdown" ? markdownPreview(this.text, url) : html`<pre>${this.text}</pre>`}
2410
+ ${kind === "markdown" ? markdownPreview(this.text, url, this.i18n) : html`<pre>${this.text}</pre>`}
2236
2411
  </article>` : nothing}
2237
2412
  </section>`;
2238
2413
  }
@@ -2246,7 +2421,7 @@ function defineTeamsContentPreview() {
2246
2421
  var channel_tab_default = ":host {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n}\n";
2247
2422
 
2248
2423
  // src/teams/app/channel-tab.ts
2249
- var TeamsChannelTab = class extends LitElement {
2424
+ var TeamsChannelTab = class extends TeamsLitElement {
2250
2425
  static styles = unsafeCSS(channel_tab_default);
2251
2426
  static properties = {
2252
2427
  tab: { attribute: false },
@@ -2298,12 +2473,12 @@ var TeamsChannelTab = class extends LitElement {
2298
2473
  try {
2299
2474
  const url = tab.content.type === "file" ? await client.fileUrl(attachment) : attachment.url;
2300
2475
  if (generation !== this.generation || !this.isConnected) return;
2301
- if (!url) throw new Error("Could not resolve the tab content");
2476
+ if (!url) throw new Error(this.t("tab.unresolved"));
2302
2477
  this.detail = { attachment, url, channelId: tab.channelId };
2303
2478
  if (tab.content.type === "file") this.openInHost();
2304
2479
  } catch (error) {
2305
2480
  if (generation === this.generation)
2306
- this.error = error instanceof Error ? error.message : "Could not open the tab";
2481
+ this.error = error instanceof Error ? localizeCoreText(error.message, this.i18n) : this.t("tab.openFailed");
2307
2482
  }
2308
2483
  }
2309
2484
  saveCopy() {
@@ -2326,7 +2501,9 @@ var TeamsChannelTab = class extends LitElement {
2326
2501
  render() {
2327
2502
  if (this.hostOpened)
2328
2503
  return html`<p>${this.detail?.attachment.name}</p>
2329
- <button type="button" @click=${() => this.openInHost()}>Open document</button>`;
2504
+ <button type="button" @click=${() => this.openInHost()}>
2505
+ ${this.t("tab.openDocument")}
2506
+ </button>`;
2330
2507
  return html`${this.error ? html`<p role="alert">${this.error}</p>
2331
2508
  <button
2332
2509
  type="button"
@@ -2335,12 +2512,12 @@ var TeamsChannelTab = class extends LitElement {
2335
2512
  void this.resolve();
2336
2513
  }}
2337
2514
  >
2338
- Retry
2515
+ ${this.t("common.retry")}
2339
2516
  </button>` : this.detail ? html`<teams-content-preview
2340
2517
  .detail=${this.detail}
2341
2518
  .embeds=${this.embeds}
2342
2519
  .saveCopy=${this.saveCopy()}
2343
- ></teams-content-preview>` : html`<p role="status">Opening tab...</p>`}`;
2520
+ ></teams-content-preview>` : html`<p role="status">${this.t("tab.opening")}</p>`}`;
2344
2521
  }
2345
2522
  };
2346
2523
  function defineTeamsChannelTab() {
@@ -2373,7 +2550,7 @@ function toggleAnchoredPopover(panel, trigger) {
2373
2550
 
2374
2551
  // src/teams/app/file-actions.ts
2375
2552
  var fileKey = (file) => JSON.stringify([file?.channelId, file?.messageId, file?.url, file?.name]);
2376
- var TeamsFileActions = class extends LitElement {
2553
+ var TeamsFileActions = class extends TeamsLitElement {
2377
2554
  static styles = unsafeCSS(file_actions_default);
2378
2555
  static properties = {
2379
2556
  file: { attribute: false },
@@ -2442,7 +2619,7 @@ var TeamsFileActions = class extends LitElement {
2442
2619
  try {
2443
2620
  const url = contentUrl(await client.fileUrl(file), this.ownerDocument.baseURI);
2444
2621
  if (!this.isConnected || abort.signal.aborted) return;
2445
- if (!url) throw new Error("The file is no longer available");
2622
+ if (!url) throw new Error(this.t("core.fileGone"));
2446
2623
  if (action === "download") {
2447
2624
  const bytes = await readContent(url, abort.signal, 33554432);
2448
2625
  if (this.isConnected && !abort.signal.aborted)
@@ -2462,7 +2639,7 @@ var TeamsFileActions = class extends LitElement {
2462
2639
  }
2463
2640
  } catch (error) {
2464
2641
  if (!abort.signal.aborted)
2465
- this.error = error instanceof Error ? error.message : "Could not access this file";
2642
+ this.error = error instanceof Error ? localizeCoreText(error.message, this.i18n) : this.t("files.accessFailed");
2466
2643
  } finally {
2467
2644
  if (this.abort === abort) {
2468
2645
  this.abort = void 0;
@@ -2473,7 +2650,7 @@ var TeamsFileActions = class extends LitElement {
2473
2650
  render() {
2474
2651
  return html`<button
2475
2652
  type="button"
2476
- aria-label=${`More actions for ${this.file?.name ?? "file"}`}
2653
+ aria-label=${this.t("files.moreActions", { name: this.file?.name ?? this.t("files.fileFallback") })}
2477
2654
  ?disabled=${!this.file?.url}
2478
2655
  aria-controls="actions"
2479
2656
  aria-expanded=${String(this.menuOpen)}
@@ -2484,23 +2661,29 @@ var TeamsFileActions = class extends LitElement {
2484
2661
  <div
2485
2662
  id="actions"
2486
2663
  popover="auto"
2487
- aria-label="File actions"
2664
+ aria-label=${this.t("files.actions")}
2488
2665
  @toggle=${() => this.menuOpen = this.renderRoot.querySelector("[popover]").matches(":popover-open")}
2489
2666
  >
2490
- <button type="button" @click=${() => this.open("open")}>Open in OpenTeams</button>
2491
- <button type="button" @click=${() => this.open("browser")}>Open in browser</button>
2667
+ <button type="button" @click=${() => this.open("open")}>
2668
+ ${this.t("files.openInTeams")}
2669
+ </button>
2670
+ <button type="button" @click=${() => this.open("browser")}>
2671
+ ${this.t("files.openInBrowser")}
2672
+ </button>
2492
2673
  <button type="button" ?disabled=${this.busy} @click=${() => void this.transfer("download")}>
2493
- Download
2674
+ ${this.t("files.download")}
2494
2675
  </button>
2495
2676
  <button type="button" ?disabled=${this.busy} @click=${() => void this.transfer("copy")}>
2496
- Copy link
2677
+ ${this.t("files.copyLink")}
2497
2678
  </button>
2498
- <button type="button" @click=${() => this.open("pin")}>Pin as tab</button>
2499
- ${this.busy ? html`<p role="status">Preparing file…</p>
2500
- <button type="button" @click=${() => this.abort?.abort()}>Cancel</button>` : nothing}
2679
+ <button type="button" @click=${() => this.open("pin")}>${this.t("files.pinAsTab")}</button>
2680
+ ${this.busy ? html`<p role="status">${this.t("files.preparingFile")}</p>
2681
+ <button type="button" @click=${() => this.abort?.abort()}>
2682
+ ${this.t("common.cancel")}
2683
+ </button>` : nothing}
2501
2684
  ${this.error ? html`<p role="alert">${this.error}</p>` : nothing}
2502
- ${this.copied ? html`<p role="status">Link copied</p>` : nothing}
2503
- ${this.copyUrl ? html`<label>File link<input readonly .value=${this.copyUrl} @focus=${(event) => event.target.select()} /></label>` : nothing}
2685
+ ${this.copied ? html`<p role="status">${this.t("files.linkCopied")}</p>` : nothing}
2686
+ ${this.copyUrl ? html`<label>${this.t("files.fileLink")}<input readonly .value=${this.copyUrl} @focus=${(event) => event.target.select()} /></label>` : nothing}
2504
2687
  </div>`;
2505
2688
  }
2506
2689
  };
@@ -2510,7 +2693,7 @@ function defineTeamsFileActions() {
2510
2693
  }
2511
2694
 
2512
2695
  // src/teams/app/files-panel.ts
2513
- var TeamsFilesPanel = class extends LitElement {
2696
+ var TeamsFilesPanel = class extends TeamsLitElement {
2514
2697
  static styles = unsafeCSS(files_panel_default);
2515
2698
  static properties = {
2516
2699
  client: { attribute: false },
@@ -2601,7 +2784,7 @@ var TeamsFilesPanel = class extends LitElement {
2601
2784
  if (this.client === client && this.active === controller && this.isConnected) {
2602
2785
  this.canceled = controller.signal.aborted;
2603
2786
  if (!this.canceled)
2604
- this.error = error instanceof Error ? error.message : "Could not share files";
2787
+ this.error = error instanceof Error ? localizeCoreText(error.message, this.i18n) : this.t("files.shareFailed");
2605
2788
  }
2606
2789
  } finally {
2607
2790
  if (this.active === controller) {
@@ -2654,10 +2837,10 @@ var TeamsFilesPanel = class extends LitElement {
2654
2837
  this.sortDirection
2655
2838
  );
2656
2839
  const disabled = this.busy || !this.canUpload || !this.channelId;
2657
- return html`<div class="toolbar" role="toolbar" aria-label="File actions">
2840
+ return html`<div class="toolbar" role="toolbar" aria-label=${this.t("files.actions")}>
2658
2841
  <office-ui-menu-button
2659
2842
  class="new-command"
2660
- label="+ New"
2843
+ label=${this.t("files.new")}
2661
2844
  .disabled=${disabled}
2662
2845
  @office-command=${async (event) => {
2663
2846
  if (event.detail.command !== "new-workbook" || disabled) return;
@@ -2667,7 +2850,7 @@ var TeamsFilesPanel = class extends LitElement {
2667
2850
  }}
2668
2851
  >
2669
2852
  <office-ui-menu-item
2670
- label="New Excel workbook"
2853
+ label=${this.t("files.newWorkbook")}
2671
2854
  command="new-workbook"
2672
2855
  ></office-ui-menu-item>
2673
2856
  </office-ui-menu-button>
@@ -2676,12 +2859,12 @@ var TeamsFilesPanel = class extends LitElement {
2676
2859
  ?disabled=${disabled}
2677
2860
  @click=${() => this.renderRoot.querySelector('input[type="file"]')?.click()}
2678
2861
  >
2679
- ${icon("upload")} Upload
2862
+ ${icon("upload")} ${this.t("files.upload")}
2680
2863
  </button>
2681
2864
  <input
2682
2865
  type="file"
2683
2866
  class="file-picker"
2684
- aria-label="Upload files"
2867
+ aria-label=${this.t("files.uploadFiles")}
2685
2868
  multiple
2686
2869
  ?disabled=${disabled}
2687
2870
  @change=${this.upload}
@@ -2689,55 +2872,67 @@ var TeamsFilesPanel = class extends LitElement {
2689
2872
  <span class="toolbar-space"></span>
2690
2873
  <input
2691
2874
  type="search"
2692
- aria-label="Search files"
2693
- placeholder="Search files"
2875
+ aria-label=${this.t("files.search")}
2876
+ placeholder=${this.t("files.search")}
2694
2877
  .value=${this.query}
2695
2878
  @input=${(e) => this.query = e.target.value}
2696
2879
  />
2697
2880
  </div>
2698
2881
  <p class="hint">
2699
- ${this.canUpload ? `New files are shared in # ${this.channelName}.` : "Configure file storage to upload files or create workbooks."}
2882
+ ${this.canUpload ? this.t("files.sharedIn", { channel: this.channelName }) : this.t("files.configureStorage")}
2700
2883
  </p>
2701
- ${this.creating ? html`<form aria-label="Create Excel workbook" @submit=${this.create}>
2884
+ ${this.creating ? html`<form aria-label=${this.t("files.createWorkbook")} @submit=${this.create}>
2702
2885
  <input
2703
2886
  name="name"
2704
- aria-label="Workbook name"
2705
- placeholder="Workbook name"
2887
+ aria-label=${this.t("files.workbookName")}
2888
+ placeholder=${this.t("files.workbookName")}
2706
2889
  maxlength="75"
2707
2890
  required
2708
2891
  ?disabled=${this.busy}
2709
2892
  />
2710
- <button type="submit" ?disabled=${disabled}>Create workbook</button>
2893
+ <button type="submit" ?disabled=${disabled}>
2894
+ ${this.t("files.createWorkbookButton")}
2895
+ </button>
2711
2896
  <button type="button" ?disabled=${this.busy} @click=${() => this.creating = false}>
2712
- Cancel
2897
+ ${this.t("common.cancel")}
2713
2898
  </button>
2714
2899
  </form>` : nothing}
2715
2900
  ${this.busy ? html`<div class="transfer">
2716
2901
  <p role="status">
2717
- Sharing files in # ${this.destination}:
2718
- ${this.transfer?.phase === "preparing" ? "Preparing" : "Uploading"}
2719
- ${this.transfer?.fileName ?? ""} (${this.transfer?.completed ?? 0} of
2720
- ${this.transfer?.total ?? 0} uploaded)
2902
+ ${this.t("files.sharing", {
2903
+ channel: this.destination,
2904
+ phase: this.t(
2905
+ this.transfer?.phase === "preparing" ? "files.preparing" : "files.uploading"
2906
+ ),
2907
+ file: this.transfer?.fileName ?? "",
2908
+ completed: this.transfer?.completed ?? 0,
2909
+ total: this.transfer?.total ?? 0
2910
+ })}
2721
2911
  </p>
2722
- ${this.transfer ? html`<progress aria-label="Files uploaded" max=${this.transfer.total} value=${this.transfer.completed}></progress>` : nothing}
2723
- <button type="button" @click=${() => this.active?.abort()}>Cancel sharing</button>
2912
+ ${this.transfer ? html`<progress aria-label=${this.t("files.uploaded")} max=${this.transfer.total} value=${this.transfer.completed}></progress>` : nothing}
2913
+ <button type="button" @click=${() => this.active?.abort()}>
2914
+ ${this.t("files.cancelSharing")}
2915
+ </button>
2724
2916
  </div>` : nothing}
2725
- ${this.error || this.canceled ? html`${this.canceled ? html`<p role="status">Sharing in # ${this.destination} canceled.</p>` : html`<p role="alert">Sharing in # ${this.destination} failed: ${this.error}</p>`}
2917
+ ${this.error || this.canceled ? html`${this.canceled ? html`<p role="status">${this.t("files.canceled", { channel: this.destination })}</p>` : html`<p role="alert">${this.t("files.failed", { channel: this.destination, error: this.error })}</p>`}
2726
2918
  <button
2727
2919
  type="button"
2728
2920
  ?disabled=${this.busy}
2729
2921
  @click=${() => this.retry && void this.run(this.retry)}
2730
2922
  >
2731
- Retry sharing files
2923
+ ${this.t("files.retry")}
2732
2924
  </button>` : nothing}
2733
2925
  <div class="file-list">
2734
- <table aria-label="Shared files">
2926
+ <table aria-label=${this.t("files.shared")}>
2735
2927
  <thead>
2736
2928
  <tr>
2737
- ${this.sortHeading("name", "Name")} ${this.sortHeading("ts", "Shared on")}
2738
- ${this.sortHeading("author", "Shared by")}
2739
- <th scope="col">Location</th>
2740
- <th scope="col"><span class="visually-hidden">Actions</span></th>
2929
+ ${this.sortHeading("name", this.t("files.name"))}
2930
+ ${this.sortHeading("ts", this.t("files.sharedOn"))}
2931
+ ${this.sortHeading("author", this.t("files.sharedBy"))}
2932
+ <th scope="col">${this.t("files.location")}</th>
2933
+ <th scope="col">
2934
+ <span class="visually-hidden">${this.t("files.actionsColumn")}</span>
2935
+ </th>
2741
2936
  </tr>
2742
2937
  </thead>
2743
2938
  <tbody>
@@ -2751,7 +2946,7 @@ var TeamsFilesPanel = class extends LitElement {
2751
2946
  <button
2752
2947
  class="file-open"
2753
2948
  type="button"
2754
- aria-label=${`Open ${file.name}`}
2949
+ aria-label=${this.t("chat.openFile", { name: file.name })}
2755
2950
  title=${file.name}
2756
2951
  ?disabled=${!file.url}
2757
2952
  @click=${() => this.emit("open", file)}
@@ -2760,7 +2955,9 @@ var TeamsFilesPanel = class extends LitElement {
2760
2955
  </button>
2761
2956
  </div>
2762
2957
  </td>
2763
- <td><time>${new Date(file.ts).toLocaleDateString()}</time></td>
2958
+ <td>
2959
+ <time>${new Date(file.ts).toLocaleDateString(this.i18n.intl)}</time>
2960
+ </td>
2764
2961
  <td>${file.author}</td>
2765
2962
  <td>
2766
2963
  ${file.channelName ? `# ${file.channelName}` : `# ${this.channelName}`}
@@ -2774,7 +2971,7 @@ var TeamsFilesPanel = class extends LitElement {
2774
2971
  </tr>`
2775
2972
  ) : html`<tr>
2776
2973
  <td colspan="5" class="none">
2777
- ${this.query.trim() ? "No matching files." : "No files shared yet."}
2974
+ ${this.t(this.query.trim() ? "files.noMatch" : "files.none")}
2778
2975
  </td>
2779
2976
  </tr>`}
2780
2977
  </tbody>
@@ -2792,8 +2989,13 @@ var teams_settings_default = ":host {\n font: var(--office-font-size) var(--offi
2792
2989
 
2793
2990
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/settings-previews.css?raw
2794
2991
  var settings_previews_default = ".theme-options,\n.density-options {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-3);\n}\n.theme-options button {\n display: grid;\n gap: var(--office-space-3);\n padding: var(--office-space-2);\n text-align: start;\n min-width: 9em;\n}\n.theme-options button[aria-pressed='true'],\n.density-choice:has(input:checked) {\n border-color: var(--office-teams-brand);\n box-shadow: inset 0 0 0 var(--office-border-width) var(--office-teams-brand);\n}\n.theme-swatch {\n --preview-paper: var(--office-call-foreground);\n --preview-ink: var(--office-call-background);\n display: grid;\n grid-template-columns: 0.7em 2.4em 1fr;\n grid-template-rows: 0.7em 1fr;\n width: 9em;\n height: 5.5em;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius);\n overflow: hidden;\n background: var(--preview-paper);\n}\n.theme-swatch[data-theme='dark'] {\n --preview-paper: var(--office-call-background);\n --preview-ink: var(--office-call-foreground);\n}\n.theme-swatch[data-theme='system'] {\n background: linear-gradient(\n 90deg,\n var(--office-call-foreground) 50%,\n var(--office-call-background) 50%\n );\n}\n.preview-toolbar {\n grid-column: 1 / -1;\n background: var(--office-teams-brand);\n}\n.preview-rail,\n.preview-sidebar {\n background: color-mix(in srgb, var(--preview-ink) 8%, var(--preview-paper));\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.preview-sidebar,\n.preview-conversation {\n display: flex;\n flex-direction: column;\n gap: var(--office-space-2);\n padding: var(--office-space-2);\n}\n.preview-sidebar i,\n.preview-conversation i {\n height: 0.4em;\n border-radius: var(--office-radius);\n background: color-mix(in srgb, var(--preview-ink) 25%, var(--preview-paper));\n}\n.preview-conversation i {\n height: 0.8em;\n width: 80%;\n}\n.preview-conversation i:nth-child(2) {\n align-self: flex-end;\n background: var(--office-teams-brand-subtle);\n}\n.density-options {\n margin: var(--office-space-5) 0 0;\n padding: var(--office-space-5) 0 0;\n border: 0;\n border-top: var(--office-border-width) solid var(--office-teams-divider);\n}\n.density-options legend {\n float: inline-start;\n width: 100%;\n font-weight: var(--office-font-weight-bold);\n margin-bottom: var(--office-space-3);\n}\n.density-choice {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: var(--office-space-3);\n width: 13em;\n padding: var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n cursor: pointer;\n font-weight: normal;\n}\n.density-choice input {\n position: absolute;\n inset-inline-end: var(--office-space-2);\n inset-block-end: var(--office-space-2);\n accent-color: var(--office-teams-brand);\n}\n.density-caption {\n display: grid;\n gap: var(--office-space-1);\n padding-inline-end: var(--office-space-5);\n}\n.density-preview {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: var(--office-space-3);\n height: 7em;\n padding: var(--office-space-3);\n border-radius: var(--office-radius);\n background: var(--office-teams-panel);\n}\n.preview-message {\n display: flex;\n gap: var(--office-space-2);\n align-items: flex-start;\n}\n.preview-message > i {\n width: 1.1em;\n height: 1.1em;\n border-radius: 50%;\n background: var(--office-teams-brand-subtle);\n border: var(--office-border-width) solid var(--office-teams-brand);\n}\n.preview-message > span {\n display: grid;\n gap: var(--office-space-1);\n flex: 1;\n}\n.preview-message b {\n height: 0.3em;\n width: 75%;\n background: var(--office-teams-text-subtle);\n border-radius: var(--office-radius);\n}\n.preview-message b + b {\n width: 100%;\n opacity: 0.4;\n}\n.density-preview[data-density='compact'] {\n gap: var(--office-space-1);\n}\n.density-preview[data-density='compact'] .preview-message > i {\n width: 0.7em;\n height: 0.7em;\n}\n.density-preview[data-density='compact'] .preview-message > span {\n display: flex;\n align-items: center;\n}\n@media (forced-colors: active) {\n .theme-options button[aria-pressed='true'],\n .density-choice:has(input:checked) {\n outline: var(--office-focus-width) solid Highlight;\n }\n}\n";
2795
- function themeChoices(current, change) {
2796
- return html`<div class="theme-options" role="group" aria-label="Theme">
2992
+ var THEME_KEY = {
2993
+ system: "settings.themeSystem",
2994
+ light: "settings.themeLight",
2995
+ dark: "settings.themeDark"
2996
+ };
2997
+ function themeChoices(current, change, i18n = teamsI18n()) {
2998
+ return html`<div class="theme-options" role="group" aria-label=${i18n.t("settings.theme")}>
2797
2999
  ${["system", "light", "dark"].map(
2798
3000
  (theme) => html`<button
2799
3001
  type="button"
@@ -2806,9 +3008,7 @@ function themeChoices(current, change) {
2806
3008
  <span class="preview-sidebar"><i></i><i></i><i></i></span>
2807
3009
  <span class="preview-conversation"><i></i><i></i><i></i></span>
2808
3010
  </span>
2809
- <span
2810
- >${theme === "system" ? "Follow system" : theme === "light" ? "Light" : "Dark"}</span
2811
- >
3011
+ <span>${i18n.t(THEME_KEY[theme])}</span>
2812
3012
  </button>`
2813
3013
  )}
2814
3014
  </div>`;
@@ -2828,9 +3028,10 @@ function parseIceLines(text) {
2828
3028
  var iceToText = (config) => config.iceServers.map(
2829
3029
  (server) => [[server.urls].flat().join(","), server.username, server.credential].filter(Boolean).join(" ")
2830
3030
  ).join("\n");
2831
- function densityChoices(density, change) {
3031
+ function densityChoices(density, change, i18n = teamsI18n()) {
3032
+ const { t } = i18n;
2832
3033
  return html`<fieldset class="density-options">
2833
- <legend>Chat density</legend>
3034
+ <legend>${t("settings.density")}</legend>
2834
3035
  ${["comfy", "compact"].map(
2835
3036
  (value) => html`<label class="density-choice">
2836
3037
  <input
@@ -2848,9 +3049,9 @@ function densityChoices(density, change) {
2848
3049
  )}
2849
3050
  </span>
2850
3051
  <span class="density-caption"
2851
- ><strong>${value === "comfy" ? "Comfy" : "Compact"}</strong>
3052
+ ><strong>${t(value === "comfy" ? "settings.comfy" : "settings.compact")}</strong>
2852
3053
  <small
2853
- >${value === "comfy" ? "More space between messages" : "More messages in less space"}</small
3054
+ >${t(value === "comfy" ? "settings.comfyText" : "settings.compactText")}</small
2854
3055
  ></span
2855
3056
  >
2856
3057
  </label>`
@@ -2858,20 +3059,21 @@ function densityChoices(density, change) {
2858
3059
  </fieldset>`;
2859
3060
  }
2860
3061
  var LEVELS = [
2861
- ["all", "All activity", "Mentions, replies, reactions and pinned messages"],
2862
- ["mentions", "Mentions only", "Only when someone mentions you"],
2863
- ["off", "Off", "Activity still collects in the Activity feed"]
3062
+ ["all", "settings.levelAll", "settings.levelAllText"],
3063
+ ["mentions", "settings.levelMentions", "settings.levelMentionsText"],
3064
+ ["off", "settings.levelOff", "settings.levelOffText"]
2864
3065
  ];
2865
- var PERMISSION_TEXT = {
2866
- granted: "Desktop notifications are on for this browser. They appear when this tab is in the background.",
2867
- default: "Desktop notifications appear when this tab is in the background. Your browser asks first.",
2868
- denied: "Desktop notifications are blocked for this site. Allow them in your browser settings to turn them on.",
2869
- unsupported: "This browser does not support desktop notifications."
3066
+ var PERMISSION_KEY = {
3067
+ granted: "settings.permissionGranted",
3068
+ default: "settings.permissionDefault",
3069
+ denied: "settings.permissionDenied",
3070
+ unsupported: "settings.permissionUnsupported"
2870
3071
  };
2871
3072
  var desktopPermission = () => globalThis.Notification?.permission ?? "unsupported";
2872
- function notificationChoices(settings, permission, handlers) {
3073
+ function notificationChoices(settings, permission, handlers, i18n = teamsI18n()) {
3074
+ const { t } = i18n;
2873
3075
  return html`<fieldset class="notification-level">
2874
- <legend>Notify me about</legend>
3076
+ <legend>${t("settings.notifyAbout")}</legend>
2875
3077
  ${LEVELS.map(
2876
3078
  ([level, label, description]) => html`<label class="notification-choice">
2877
3079
  <input
@@ -2881,102 +3083,86 @@ function notificationChoices(settings, permission, handlers) {
2881
3083
  .checked=${settings.level === level}
2882
3084
  @change=${() => handlers.change({ level })}
2883
3085
  />
2884
- <span><strong>${label}</strong><small>${description}</small></span>
3086
+ <span><strong>${t(label)}</strong><small>${t(description)}</small></span>
2885
3087
  </label>`
2886
3088
  )}
2887
3089
  </fieldset>
2888
- <h3>Sound</h3>
3090
+ <h3>${t("settings.sound")}</h3>
2889
3091
  <label class="preference"
2890
- ><span>Play a sound for new notifications</span
3092
+ ><span>${t("settings.soundText")}</span
2891
3093
  ><office-ui-switch
2892
- aria-label="Notification sound"
3094
+ aria-label=${t("settings.soundLabel")}
2893
3095
  .checked=${settings.sound}
2894
3096
  @change=${(event) => handlers.change({ sound: event.target.checked })}
2895
3097
  ></office-ui-switch
2896
3098
  ></label>
2897
- <h3>Desktop notifications</h3>
2898
- <p class="description" role="status">${PERMISSION_TEXT[permission]}</p>
3099
+ <h3>${t("settings.desktop")}</h3>
3100
+ <p class="description" role="status">${t(PERMISSION_KEY[permission])}</p>
2899
3101
  ${permission === "default" ? html`<button type="button" class="primary" @click=${handlers.requestPermission}>
2900
- Enable desktop notifications
3102
+ ${t("settings.enableDesktop")}
2901
3103
  </button>` : ""}
2902
- <p class="description">
2903
- Do not disturb silences every notification. Email and mobile push notifications are not
2904
- available.
2905
- </p>`;
3104
+ <p class="description">${t("settings.dndNote")}</p>`;
2906
3105
  }
2907
3106
  var SETTINGS_CATEGORIES = [
2908
- ["general", "General"],
2909
- ["appearance", "Appearance and accessibility"],
2910
- ["notifications", "Notifications and activity"],
2911
- ["files", "Files and links"],
2912
- ["connection", "Connection"]
3107
+ ["general", "settings.general"],
3108
+ ["appearance", "settings.appearance"],
3109
+ ["notifications", "settings.notifications"],
3110
+ ["files", "settings.files"],
3111
+ ["connection", "settings.connection"]
2913
3112
  ];
2914
3113
  var TOPICS = [
2915
- { category: "general", title: "Profile", description: "Your name and workspace on this device" },
2916
- {
2917
- category: "appearance",
2918
- title: "Theme",
2919
- description: "Follow system, light or dark appearance"
2920
- },
2921
- {
2922
- category: "appearance",
2923
- title: "Chat density",
2924
- description: "Comfy or compact message spacing"
2925
- },
2926
- {
2927
- category: "appearance",
2928
- title: "Show app names",
2929
- description: "Display labels beneath app bar icons"
2930
- },
3114
+ { category: "general", title: "settings.profile", description: "settings.topic.profile" },
3115
+ { category: "appearance", title: "settings.theme", description: "settings.topic.theme" },
3116
+ { category: "appearance", title: "settings.density", description: "settings.topic.density" },
3117
+ { category: "appearance", title: "settings.appNames", description: "settings.appNamesText" },
2931
3118
  {
2932
3119
  category: "notifications",
2933
- title: "Activity notifications",
2934
- description: "Notify for all activity, mentions only or nothing"
3120
+ title: "settings.activityNotifications",
3121
+ description: "settings.topic.activityNotifications"
2935
3122
  },
3123
+ { category: "notifications", title: "settings.sound", description: "settings.soundText" },
2936
3124
  {
2937
3125
  category: "notifications",
2938
- title: "Sound",
2939
- description: "Play a sound for new notifications"
3126
+ title: "settings.desktop",
3127
+ description: "settings.topic.desktop"
2940
3128
  },
2941
3129
  {
2942
3130
  category: "notifications",
2943
- title: "Desktop notifications",
2944
- description: "Browser notifications while this tab is in the background, and Do not disturb"
2945
- },
2946
- {
2947
- category: "notifications",
2948
- title: "Followed threads",
2949
- description: "Threads I start and threads I reply to"
2950
- },
2951
- {
2952
- category: "files",
2953
- title: "File open preference",
2954
- description: "Open Word, PowerPoint and Excel in OpenTeams or browser"
3131
+ title: "settings.followed",
3132
+ description: "settings.topic.followed"
2955
3133
  },
3134
+ { category: "files", title: "settings.fileOpen", description: "settings.topic.fileOpen" },
2956
3135
  {
2957
3136
  category: "connection",
2958
- title: "Connection",
2959
- description: "Workspace server, sync URL, signaling URL, access token and ICE servers"
3137
+ title: "settings.connection",
3138
+ description: "settings.topic.connection"
2960
3139
  }
2961
3140
  ];
2962
- function settingsSearch(query, select) {
2963
- const words = query.trim().toLocaleLowerCase().split(/\s+/u);
2964
- const matches = TOPICS.filter((topic) => {
2965
- const category = SETTINGS_CATEGORIES.find(([id]) => id === topic.category)[1];
2966
- const text = `${category} ${topic.title} ${topic.description}`.toLocaleLowerCase();
3141
+ var categoryLabel = (category, i18n) => i18n.t(SETTINGS_CATEGORIES.find(([id]) => id === category)[1]);
3142
+ function settingsSearch(query, select, i18n = teamsI18n()) {
3143
+ const { t } = i18n;
3144
+ const words = query.trim().toLocaleLowerCase(i18n.locale).split(/\s+/u);
3145
+ const topics = TOPICS.map((topic) => ({
3146
+ category: topic.category,
3147
+ title: t(topic.title),
3148
+ description: t(topic.description)
3149
+ }));
3150
+ const matches = topics.filter((topic) => {
3151
+ const text = `${categoryLabel(topic.category, i18n)} ${topic.title} ${topic.description}`.toLocaleLowerCase(
3152
+ i18n.locale
3153
+ );
2967
3154
  return words.every((word) => text.includes(word));
2968
3155
  });
2969
- return html`<section class="settings-results" aria-label="Settings search results">
2970
- <h2>Search results</h2>
3156
+ return html`<section class="settings-results" aria-label=${t("settings.resultsLabel")}>
3157
+ <h2>${t("settings.results")}</h2>
2971
3158
  <p role="status">
2972
- ${matches.length ? `${matches.length} ${matches.length === 1 ? "setting" : "settings"} found` : "No matching settings. Try another keyword."}
3159
+ ${matches.length ? i18n.tn("settings.found", matches.length) : t("settings.noMatch")}
2973
3160
  </p>
2974
3161
  <ul>
2975
3162
  ${matches.map(
2976
3163
  (topic) => html`<li>
2977
3164
  <button type="button" @click=${() => select(topic)}>
2978
- <strong>${topic.title}</strong
2979
- ><span>${SETTINGS_CATEGORIES.find(([id]) => id === topic.category)[1]}</span
3165
+ <strong>${topic.title}</strong><span>${categoryLabel(topic.category, i18n)}</span
2980
3166
  ><small>${topic.description}</small>
2981
3167
  </button>
2982
3168
  </li>`
@@ -2986,7 +3172,7 @@ function settingsSearch(query, select) {
2986
3172
  }
2987
3173
 
2988
3174
  // src/teams/app/teams-settings.ts
2989
- var TeamsSettings = class extends LitElement {
3175
+ var TeamsSettings = class extends TeamsLitElement {
2990
3176
  static styles = [unsafeCSS(teams_settings_default), unsafeCSS(settings_previews_default)];
2991
3177
  static properties = {
2992
3178
  config: { attribute: false },
@@ -3079,7 +3265,7 @@ var TeamsSettings = class extends LitElement {
3079
3265
  const c = this.config;
3080
3266
  return html`
3081
3267
  <dialog
3082
- aria-label="Settings"
3268
+ aria-label=${this.t("settings.title")}
3083
3269
  @close=${() => {
3084
3270
  if (this.open) this.close();
3085
3271
  }}
@@ -3095,21 +3281,26 @@ var TeamsSettings = class extends LitElement {
3095
3281
  }}
3096
3282
  >
3097
3283
  <header>
3098
- <h1>Settings</h1>
3284
+ <h1>${this.t("settings.title")}</h1>
3099
3285
  <input
3100
3286
  type="search"
3101
- aria-label="Search settings"
3102
- placeholder="Search settings"
3287
+ aria-label=${this.t("settings.search")}
3288
+ placeholder=${this.t("settings.search")}
3103
3289
  .value=${this.query}
3104
3290
  @input=${(event) => this.query = event.target.value}
3105
3291
  />
3106
- <button type="button" class="close" aria-label="Close settings" @click=${this.close}>
3292
+ <button
3293
+ type="button"
3294
+ class="close"
3295
+ aria-label=${this.t("settings.close")}
3296
+ @click=${this.close}
3297
+ >
3107
3298
  ×
3108
3299
  </button>
3109
3300
  </header>
3110
3301
  <div class="settings-layout">
3111
3302
  <nav
3112
- aria-label="Settings categories"
3303
+ aria-label=${this.t("settings.categories")}
3113
3304
  role="tablist"
3114
3305
  aria-orientation="vertical"
3115
3306
  @keydown=${(event) => {
@@ -3136,40 +3327,45 @@ var TeamsSettings = class extends LitElement {
3136
3327
  }}
3137
3328
  >
3138
3329
  ${icon({ general: "settings", appearance: "formatPainter", notifications: "message", files: "folder", connection: "screenShare" }[id])}<span
3139
- >${label}</span
3330
+ >${this.t(label)}</span
3140
3331
  >
3141
3332
  </button>`
3142
3333
  )}
3143
3334
  </nav>
3144
3335
  <div class="settings-content">
3145
- ${this.query.trim() ? settingsSearch(this.query, async (topic) => {
3146
- this.category = topic.category;
3147
- this.query = "";
3148
- await this.updateComplete;
3149
- if (!this.open || this.query.trim() || this.category !== topic.category) return;
3150
- const panel = this.renderRoot.querySelector(
3151
- `#panel-${topic.category}`
3152
- );
3153
- const heading = [...panel.querySelectorAll("h2, h3, legend")].find(
3154
- (element) => element.textContent?.trim() === topic.title
3155
- ) ?? panel;
3156
- heading.tabIndex = -1;
3157
- heading.focus();
3158
- }) : nothing}
3336
+ ${this.query.trim() ? settingsSearch(
3337
+ this.query,
3338
+ async (topic) => {
3339
+ this.category = topic.category;
3340
+ this.query = "";
3341
+ await this.updateComplete;
3342
+ if (!this.open || this.query.trim() || this.category !== topic.category)
3343
+ return;
3344
+ const panel = this.renderRoot.querySelector(
3345
+ `#panel-${topic.category}`
3346
+ );
3347
+ const heading = [...panel.querySelectorAll("h2, h3, legend")].find(
3348
+ (element) => element.textContent?.trim() === topic.title
3349
+ ) ?? panel;
3350
+ heading.tabIndex = -1;
3351
+ heading.focus();
3352
+ },
3353
+ this.i18n
3354
+ ) : nothing}
3159
3355
  <section
3160
3356
  id="panel-general"
3161
3357
  role="tabpanel"
3162
3358
  aria-labelledby="settings-general"
3163
3359
  ?hidden=${!!this.query.trim() || this.category !== "general"}
3164
3360
  >
3165
- <h2>General</h2>
3166
- <h3>Profile</h3>
3167
- <p>${this.userName || "OpenTeams user"}</p>
3168
- <h3>Your workspace</h3>
3169
- <p>${c.mode === "local" ? "Local workspace" : "Connected workspace"}</p>
3170
- <p class="description">
3171
- Preferences are saved for this user and workspace on this device.
3361
+ <h2>${this.t("settings.general")}</h2>
3362
+ <h3>${this.t("settings.profile")}</h3>
3363
+ <p>${this.userName || this.t("settings.defaultUser")}</p>
3364
+ <h3>${this.t("settings.workspace")}</h3>
3365
+ <p>
3366
+ ${this.t(c.mode === "local" ? "profile.localWorkspace" : "settings.connectedWorkspace")}
3172
3367
  </p>
3368
+ <p class="description">${this.t("settings.savedNote")}</p>
3173
3369
  </section>
3174
3370
  <section
3175
3371
  id="panel-appearance"
@@ -3177,23 +3373,20 @@ var TeamsSettings = class extends LitElement {
3177
3373
  aria-labelledby="settings-appearance"
3178
3374
  ?hidden=${!!this.query.trim() || this.category !== "appearance"}
3179
3375
  >
3180
- <h2>Appearance and accessibility</h2>
3181
- <h3>Theme</h3>
3182
- ${themeChoices(this.theme, (theme) => this.dispatchEvent(new CustomEvent("teams-settings-theme", { detail: { theme }, bubbles: true, composed: true })))}
3183
- ${densityChoices(this.chatDensity, (density) => this.dispatchEvent(new CustomEvent("teams-settings-density", { detail: { density }, bubbles: true, composed: true })))}
3184
- <h3>Show app names</h3>
3376
+ <h2>${this.t("settings.appearance")}</h2>
3377
+ <h3>${this.t("settings.theme")}</h3>
3378
+ ${themeChoices(this.theme, (theme) => this.dispatchEvent(new CustomEvent("teams-settings-theme", { detail: { theme }, bubbles: true, composed: true })), this.i18n)}
3379
+ ${densityChoices(this.chatDensity, (density) => this.dispatchEvent(new CustomEvent("teams-settings-density", { detail: { density }, bubbles: true, composed: true })), this.i18n)}
3380
+ <h3>${this.t("settings.appNames")}</h3>
3185
3381
  <label class="preference"
3186
- ><span>Display labels beneath app bar icons</span
3382
+ ><span>${this.t("settings.appNamesText")}</span
3187
3383
  ><office-ui-switch
3188
- aria-label="Show app names"
3384
+ aria-label=${this.t("settings.appNames")}
3189
3385
  .checked=${this.showAppNames}
3190
3386
  @change=${(event) => this.dispatchEvent(new CustomEvent("teams-settings-app-names", { detail: { show: event.target.checked }, bubbles: true, composed: true }))}
3191
3387
  ></office-ui-switch
3192
3388
  ></label>
3193
- <p class="description">
3194
- Changes apply immediately to this OpenTeams workspace. Keyboard focus and system
3195
- high-contrast preferences remain available.
3196
- </p>
3389
+ <p class="description">${this.t("settings.appearanceNote")}</p>
3197
3390
  </section>
3198
3391
  <section
3199
3392
  id="panel-notifications"
@@ -3201,23 +3394,28 @@ var TeamsSettings = class extends LitElement {
3201
3394
  aria-labelledby="settings-notifications"
3202
3395
  ?hidden=${!!this.query.trim() || this.category !== "notifications"}
3203
3396
  >
3204
- <h2>Notifications and activity</h2>
3205
- <h3>Activity notifications</h3>
3206
- ${notificationChoices(this.notificationSettings, desktopPermission(), {
3207
- change: (detail) => this.dispatchEvent(
3208
- new CustomEvent("teams-settings-notifications", {
3209
- detail,
3210
- bubbles: true,
3211
- composed: true
3212
- })
3213
- ),
3214
- requestPermission: () => void globalThis.Notification?.requestPermission().then(
3215
- () => this.requestUpdate()
3216
- )
3217
- })}
3218
- <h3>Followed threads</h3>
3219
- <p class="description">Choose which conversations appear in Followed threads.</p>
3220
- ${["started", "replied"].map((key) => html`<label class="preference"><span>${key === "started" ? "Threads I start" : "Threads I reply to"}</span><office-ui-switch aria-label=${key === "started" ? "Threads I start" : "Threads I reply to"} .checked=${this.followSettings[key]} @change=${(event) => this.dispatchEvent(new CustomEvent("teams-settings-follow", { detail: { [key]: event.target.checked }, bubbles: true, composed: true }))}></office-ui-switch></label>`)}
3397
+ <h2>${this.t("settings.notifications")}</h2>
3398
+ <h3>${this.t("settings.activityNotifications")}</h3>
3399
+ ${notificationChoices(
3400
+ this.notificationSettings,
3401
+ desktopPermission(),
3402
+ {
3403
+ change: (detail) => this.dispatchEvent(
3404
+ new CustomEvent("teams-settings-notifications", {
3405
+ detail,
3406
+ bubbles: true,
3407
+ composed: true
3408
+ })
3409
+ ),
3410
+ requestPermission: () => void globalThis.Notification?.requestPermission().then(
3411
+ () => this.requestUpdate()
3412
+ )
3413
+ },
3414
+ this.i18n
3415
+ )}
3416
+ <h3>${this.t("settings.followed")}</h3>
3417
+ <p class="description">${this.t("settings.followedNote")}</p>
3418
+ ${["started", "replied"].map((key) => html`<label class="preference"><span>${this.t(key === "started" ? "followed.started" : "followed.replied")}</span><office-ui-switch aria-label=${this.t(key === "started" ? "followed.started" : "followed.replied")} .checked=${this.followSettings[key]} @change=${(event) => this.dispatchEvent(new CustomEvent("teams-settings-follow", { detail: { [key]: event.target.checked }, bubbles: true, composed: true }))}></office-ui-switch></label>`)}
3221
3419
  </section>
3222
3420
  <section
3223
3421
  id="panel-files"
@@ -3225,24 +3423,20 @@ var TeamsSettings = class extends LitElement {
3225
3423
  aria-labelledby="settings-files"
3226
3424
  ?hidden=${!!this.query.trim() || this.category !== "files"}
3227
3425
  >
3228
- <h2>Files and links</h2>
3229
- <h3>File open preference</h3>
3426
+ <h2>${this.t("settings.files")}</h2>
3427
+ <h3>${this.t("settings.fileOpen")}</h3>
3230
3428
  <label
3231
- >Always open Word, PowerPoint and Excel files in:
3429
+ >${this.t("settings.fileOpenLabel")}
3232
3430
  <select
3233
- aria-label="Office file open preference"
3431
+ aria-label=${this.t("settings.fileOpenAria")}
3234
3432
  .value=${this.fileOpenPreference}
3235
3433
  @change=${(event) => this.dispatchEvent(new CustomEvent("teams-settings-file-open", { detail: { preference: event.target.value }, bubbles: true, composed: true }))}
3236
3434
  >
3237
3435
  <option value="teams">OpenTeams</option>
3238
- <option value="browser">Browser</option>
3436
+ <option value="browser">${this.t("settings.browser")}</option>
3239
3437
  </select></label
3240
3438
  >
3241
- <p class="description">
3242
- Browser opens the built-in viewer in a separate tab. Hosts can provide their own
3243
- viewers. Desktop-app defaults and download-location preferences are not available
3244
- yet.
3245
- </p>
3439
+ <p class="description">${this.t("settings.fileOpenNote")}</p>
3246
3440
  </section>
3247
3441
  <form
3248
3442
  id="panel-connection"
@@ -3251,19 +3445,21 @@ var TeamsSettings = class extends LitElement {
3251
3445
  ?hidden=${!!this.query.trim() || this.category !== "connection"}
3252
3446
  @submit=${this.apply}
3253
3447
  >
3254
- <h2>Connection</h2>
3255
- <p class="description">Configure your workspace server and calling service.</p>
3448
+ <h2>${this.t("settings.connection")}</h2>
3449
+ <p class="description">${this.t("settings.connectionNote")}</p>
3256
3450
  <label>
3257
- <span>Mode</span>
3451
+ <span>${this.t("settings.mode")}</span>
3258
3452
  <select id="mode" .value=${c.mode}>
3259
3453
  <option value="local" ?selected=${c.mode === "local"}>
3260
- Local: tabs of this browser only (no server)
3454
+ ${this.t("settings.modeLocal")}
3455
+ </option>
3456
+ <option value="server" ?selected=${c.mode === "server"}>
3457
+ ${this.t("settings.modeServer")}
3261
3458
  </option>
3262
- <option value="server" ?selected=${c.mode === "server"}>Your server</option>
3263
3459
  </select>
3264
3460
  </label>
3265
3461
  <label>
3266
- <span>Sync URL</span>
3462
+ <span>${this.t("settings.syncUrl")}</span>
3267
3463
  <input
3268
3464
  id="sync"
3269
3465
  .value=${c.syncUrl ?? ""}
@@ -3271,10 +3467,10 @@ var TeamsSettings = class extends LitElement {
3271
3467
  spellcheck="false"
3272
3468
  autocomplete="off"
3273
3469
  />
3274
- <small>y-websocket compatible endpoint; the workspace id is appended.</small>
3470
+ <small>${this.t("settings.syncHint")}</small>
3275
3471
  </label>
3276
3472
  <label>
3277
- <span>Signaling URL</span>
3473
+ <span>${this.t("settings.signalUrl")}</span>
3278
3474
  <input
3279
3475
  id="signal"
3280
3476
  .value=${c.signalingUrl ?? ""}
@@ -3282,35 +3478,35 @@ var TeamsSettings = class extends LitElement {
3282
3478
  spellcheck="false"
3283
3479
  autocomplete="off"
3284
3480
  />
3285
- <small>WebRTC signaling relay; the call room is appended.</small>
3481
+ <small>${this.t("settings.signalHint")}</small>
3286
3482
  </label>
3287
3483
  <label>
3288
- <span>Access token</span>
3484
+ <span>${this.t("settings.token")}</span>
3289
3485
  <input
3290
3486
  id="token"
3291
3487
  type="password"
3292
3488
  .value=${c.token ?? ""}
3293
- placeholder="shared secret"
3489
+ placeholder=${this.t("settings.tokenPlaceholder")}
3294
3490
  autocomplete="off"
3295
3491
  />
3296
- <small>Sent as ?token= to both sockets. Stored in this browser.</small>
3492
+ <small>${this.t("settings.tokenHint")}</small>
3297
3493
  </label>
3298
3494
  <label>
3299
- <span>ICE servers</span>
3495
+ <span>${this.t("settings.iceServers")}</span>
3300
3496
  <textarea
3301
3497
  id="ice"
3302
3498
  spellcheck="false"
3303
3499
  .value=${iceToText(c)}
3304
3500
  placeholder="stun:stun.example.org:3478&#10;turn:turn.example.org:3478 user password"
3305
3501
  ></textarea>
3306
- <small>One per line: urls [username credential]. TURN needs credentials.</small>
3502
+ <small>${this.t("settings.iceHint")}</small>
3307
3503
  </label>
3308
3504
  <label>
3309
- <span>ICE transport</span>
3505
+ <span>${this.t("settings.iceTransport")}</span>
3310
3506
  <select id="policy" .value=${c.iceTransportPolicy ?? "all"}>
3311
- <option value="all">All routes (direct when possible)</option>
3507
+ <option value="all">${this.t("settings.iceAll")}</option>
3312
3508
  <option value="relay" ?selected=${c.iceTransportPolicy === "relay"}>
3313
- Relay only (TURN required, hides IP addresses)
3509
+ ${this.t("settings.iceRelay")}
3314
3510
  </option>
3315
3511
  </select>
3316
3512
  </label>
@@ -3318,8 +3514,8 @@ var TeamsSettings = class extends LitElement {
3318
3514
  ${this.issues.map((i) => html`<li>${i}</li>`)}
3319
3515
  </ul>` : nothing}
3320
3516
  <div class="actions">
3321
- <button type="button" @click=${this.close}>Cancel</button>
3322
- <button type="submit" class="primary">Apply and reconnect</button>
3517
+ <button type="button" @click=${this.close}>${this.t("common.cancel")}</button>
3518
+ <button type="submit" class="primary">${this.t("settings.apply")}</button>
3323
3519
  </div>
3324
3520
  </form>
3325
3521
  </div>
@@ -3373,19 +3569,20 @@ var saveConfig = (config) => safeStorage.setItem("teams:config", JSON.stringify(
3373
3569
 
3374
3570
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/teams-app.css?raw
3375
3571
  var teams_app_default = "/* <teams-app>: the shell. Colours, spacing and radii come from the ooxml-ui tokens, so a host\n retheme or dark mode reaches the whole app. The elements inside bring their own styles. */\n:host {\n display: block;\n height: 100%;\n min-height: 28em;\n font: var(--office-font-size) var(--office-font);\n color: var(--office-foreground);\n background: var(--office-background);\n}\n:host([data-office-theme='dark']) {\n --office-background: var(--office-teams-panel);\n --office-surface: var(--office-teams-bubble);\n --office-popover: var(--office-teams-panel);\n}\n:host([data-chat-density='compact']) {\n --office-teams-message-gap: var(--office-space-1);\n --office-teams-message-padding: var(--office-space-1) var(--office-space-2);\n}\n@media (prefers-color-scheme: dark) {\n :host(:not([data-office-theme='light'])) {\n --office-background: var(--office-teams-panel);\n --office-surface: var(--office-teams-bubble);\n --office-popover: var(--office-teams-panel);\n }\n}\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\nbutton {\n font: inherit;\n color: inherit;\n cursor: pointer;\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n\n.shell {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n height: 100%;\n}\n\n/* ---- top bar ---------------------------------------------------------------------------- */\n.topbar {\n display: grid;\n grid-template-columns: 16em minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--office-space-4);\n padding: var(--office-space-2) var(--office-space-4);\n background: var(--office-teams-rail);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n}\n.brand {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n font-size: var(--office-font-size-lg);\n font-weight: var(--office-font-weight-bold);\n}\n.logo {\n display: inline-grid;\n place-items: center;\n width: 1.8em;\n height: 1.8em;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-size: 0.85em;\n}\n.search {\n position: relative;\n justify-self: center;\n width: min(34em, 100%);\n}\n.search input {\n width: 100%;\n padding: var(--office-space-2) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-full);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n}\n.results {\n position: absolute;\n inset-inline: 0;\n inset-block-start: calc(100% + var(--office-space-1));\n z-index: 5;\n max-height: 22em;\n margin: 0;\n padding: var(--office-space-1);\n overflow: auto;\n list-style: none;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n box-shadow: var(--office-shadow-teams);\n}\n.results button {\n display: grid;\n width: 100%;\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n text-align: start;\n}\n.results button:hover {\n background: var(--office-selected);\n}\n.results .in,\n.results .none {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n}\n.results .none {\n padding: var(--office-space-3);\n}\n.snippet {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.me {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n}\n.me office-ui-menu-button::part(caret) {\n display: none;\n}\n.channel-head office-ui-menu-button::part(caret) {\n display: none;\n}\n.channel-head svg {\n width: 1em;\n height: 1em;\n fill: none;\n stroke: currentColor;\n stroke-width: var(--office-icon-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n/* ---- body ------------------------------------------------------------------------------- */\n.body {\n display: grid;\n grid-template-columns: auto 19em minmax(0, 1fr);\n min-height: 0;\n grid-template-rows: minmax(0, 1fr);\n}\n.body > office-ui-app-rail {\n grid-column: 1;\n grid-row: 1;\n}\n.body > .side {\n grid-column: 2;\n grid-row: 1;\n}\n.body > .main {\n grid-column: 3;\n grid-row: 1;\n}\n.body > teams-settings {\n grid-column: 2 / -1;\n grid-row: 1;\n min-width: 0;\n min-height: 0;\n z-index: 1;\n}\n.body > teams-settings:not([open]) {\n display: none;\n}\n.body > [inert] {\n visibility: hidden;\n}\n.side {\n min-height: 0;\n overflow: auto;\n background: var(--office-teams-panel);\n border-inline-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.side h2 {\n margin: 0;\n padding: var(--office-space-3) var(--office-space-4);\n font-size: var(--office-font-size-lg);\n}\n.main {\n display: flex;\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n}\n.popout-view {\n display: flex;\n height: 100%;\n min-height: 0;\n}\n.popout-view teams-content-preview {\n flex: 1;\n}\n.main office-ui-chat-list,\n.main office-ui-prejoin {\n flex: 1;\n}\n\nul.plain {\n margin: 0;\n padding: 0 var(--office-space-3);\n list-style: none;\n}\nul.plain li {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n padding: var(--office-space-2) var(--office-space-2);\n}\nul.plain li > :first-child:not(office-ui-avatar) {\n flex: 1;\n}\n.hint {\n padding: 0 var(--office-space-4);\n color: var(--office-teams-text-subtle);\n}\n.join {\n padding: var(--office-space-1) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n}\n.join.live {\n border-color: transparent;\n background: var(--office-call-live);\n color: var(--office-call-foreground);\n}\n\n/* ---- conversation ----------------------------------------------------------------------- */\n.channel-head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-4);\n padding: var(--office-space-2) var(--office-space-5);\n border-bottom: var(--office-border-width) solid var(--office-teams-divider);\n}\n.channel-head > button:not(.meet),\n.thread-head button {\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n}\n.channel-head > button:hover:not(:disabled),\n.thread-head button:hover {\n background: var(--office-selected);\n}\n.channel-head > button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.channel-head h1 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n white-space: nowrap;\n}\n.hash {\n color: var(--office-teams-text-subtle);\n}\n.tabs {\n display: flex;\n gap: var(--office-space-1);\n}\n.tabs button {\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-bottom: var(--office-border-width-thick) solid transparent;\n background: transparent;\n}\n.tabs button[aria-selected='true'] {\n border-bottom-color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\n.topic {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n color: var(--office-teams-text-subtle);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.people {\n display: flex;\n align-items: center;\n gap: var(--office-space-0);\n}\n.more {\n margin-inline-start: var(--office-space-1);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n}\n.meet {\n padding: var(--office-space-2) var(--office-space-4);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.meet:hover:not(:disabled) {\n background: var(--office-teams-brand-hover);\n}\n.meet:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.callbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--office-space-2) var(--office-space-5);\n background: var(--office-call-live);\n color: var(--office-call-foreground);\n}\n.callbar button {\n padding: 0 var(--office-space-3);\n border: var(--office-border-width) solid currentColor;\n border-radius: var(--office-radius);\n background: transparent;\n}\n\n/* ---- files ------------------------------------------------------------------------------ */\n/* ---- meeting ---------------------------------------------------------------------------- */\n.meeting {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n height: 100%;\n background: var(--office-call-background);\n}\n.stage {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n min-height: 0;\n}\n.panel {\n display: flex;\n flex-direction: column;\n width: 22em;\n min-height: 0;\n background: var(--office-background);\n border-inline-start: var(--office-border-width) solid var(--office-teams-divider);\n}\n.panel office-ui-chat-list {\n flex: 1;\n padding-inline: var(--office-space-3);\n}\n.panel office-ui-chat-composer {\n padding-inline: var(--office-space-3);\n}\n.people-panel h2 {\n margin: 0;\n padding: var(--office-space-3) var(--office-space-4);\n font-size: var(--office-font-size-lg);\n}\n\n/* ---- welcome and toast ------------------------------------------------------------------ */\n.welcome {\n display: grid;\n place-items: center;\n height: 100%;\n background: var(--office-teams-panel);\n}\n.welcome form {\n display: grid;\n justify-items: center;\n gap: var(--office-space-3);\n width: min(24em, 90%);\n padding: var(--office-space-6);\n border-radius: var(--office-radius-lg);\n background: var(--office-background);\n box-shadow: var(--office-shadow-teams);\n}\n.welcome h1 {\n margin: 0;\n font-size: var(--office-font-size-xl);\n}\n.welcome p {\n margin: 0;\n color: var(--office-teams-text-subtle);\n}\n.welcome input {\n width: 100%;\n padding: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n color: inherit;\n font: inherit;\n}\n.welcome button {\n width: 100%;\n padding: var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font-weight: var(--office-font-weight-bold);\n}\n.toast {\n position: fixed;\n inset-block-end: var(--office-space-4);\n inset-inline-start: 50%;\n z-index: 10;\n transform: translateX(-50%);\n padding: var(--office-space-2) var(--office-space-4);\n border-radius: var(--office-radius-md);\n background: var(--office-call-background);\n color: var(--office-call-foreground);\n}\n\n.conversation-grid {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n flex: 1;\n min-height: 0;\n}\n.conversation-grid[data-thread='true'] {\n grid-template-columns: minmax(0, 1fr) minmax(18em, 0.8fr);\n}\n.conversation-main,\n.thread-pane {\n display: flex;\n flex-direction: column;\n min-height: 0;\n min-width: 0;\n}\n.tab-conversation teams-channel-tab {\n flex: 1;\n min-height: 0;\n}\n.thread-pane {\n border-inline-start: var(--office-border-width) solid var(--office-teams-divider);\n}\n.thread-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--office-space-3);\n padding: var(--office-space-3);\n}\n.thread-head h2 {\n margin: 0;\n font-size: 1em;\n}\n.followed-threads {\n overflow: auto;\n padding: var(--office-space-4);\n}\n.message-transfer {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-3);\n background: var(--office-teams-panel);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.message-transfer span {\n flex: 1;\n overflow-wrap: anywhere;\n}\n.message-transfer button {\n font: inherit;\n color: inherit;\n background: var(--office-background);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n padding: var(--office-space-2) var(--office-space-3);\n cursor: pointer;\n}\n.followed-threads ul {\n list-style: none;\n padding: 0;\n}\n.followed-threads fieldset {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-4);\n padding: var(--office-space-3);\n margin-block: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n}\n.followed-threads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--office-space-3);\n padding-block: var(--office-space-3);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\n.followed-threads button {\n font: inherit;\n color: inherit;\n background: var(--office-background);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n padding: var(--office-space-2) var(--office-space-3);\n cursor: pointer;\n}\n.followed-threads button:hover {\n background: var(--office-selected);\n}\n.followed-threads li button:first-child {\n display: grid;\n gap: var(--office-space-2);\n flex: 1;\n text-align: start;\n min-width: 0;\n overflow-wrap: anywhere;\n border-color: transparent;\n}\n.followed-threads li[data-unread='true'] button:first-child span {\n font-weight: var(--office-font-weight-bold);\n}\n@media (max-width: 56em) {\n .conversation-grid[data-thread='true'] {\n grid-template-columns: minmax(0, 1fr);\n }\n .conversation-grid[data-thread='true'] .conversation-main {\n display: none;\n }\n}\n.tabs {\n overflow-x: auto;\n min-width: 0;\n flex: 1 1 16em;\n}\n\n.channel-head > .back {\n display: none;\n}\n\n/* ---- compact shell ---------------------------------------------------------------------- */\n/* Below 56em the app rail is a bottom bar, the channel list is its own full-screen view and the\n conversation (or any other content) fills the width with a back button to the list. */\n@media (max-width: 56em) {\n .brand {\n display: none;\n }\n .topbar {\n grid-template-columns: minmax(0, 1fr) auto;\n gap: var(--office-space-2);\n }\n .search {\n width: 100%;\n }\n .channel-head {\n gap: var(--office-space-2);\n padding-inline: var(--office-space-3);\n }\n .channel-head > .back {\n display: inline-flex;\n border-color: transparent;\n }\n .channel-head .topic,\n .channel-head .people {\n display: none;\n }\n .channel-head .tabs {\n flex-basis: 100%;\n order: 1;\n }\n .body {\n grid-template-columns: minmax(0, 1fr);\n grid-template-rows: minmax(0, 1fr) auto;\n }\n .body > office-ui-app-rail {\n grid-column: 1;\n grid-row: 2;\n }\n .body > .side,\n .body > .main,\n .body > teams-settings {\n grid-column: 1;\n grid-row: 1;\n }\n .side {\n border-inline-end: 0;\n }\n .shell[data-mobile-view='list'] .main,\n .shell[data-mobile-view='content'] .side {\n display: none;\n }\n}\n/* Phone landscape has little height: keep the chrome to one thin row each. */\n@media (max-width: 56em) and (max-height: 30em) {\n .topbar {\n padding-block: var(--office-space-1);\n }\n .channel-head {\n flex-wrap: nowrap;\n padding-block: var(--office-space-1);\n }\n .channel-head .tabs {\n flex: 1 1 0;\n order: 0;\n }\n .channel-head h1 {\n font-size: var(--office-font-size);\n }\n}\n/* Touch: every control in the shell reaches the minimum hit target. */\n@media (pointer: coarse) {\n .channel-head > button,\n .meet,\n .tabs button,\n .join,\n .thread-head button,\n .callbar button {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n\n/* An application host can own the single suite-level account control. */\n:host([host-profile]) teams-profile-menu {\n display: none;\n}\n\n/* ---- activity ---------------------------------------------------------------------------- */\n.side > office-ui-activity-feed {\n height: 100%;\n}\n/* Toasts for new activity stack at the top right, under the header, so they never cover the\n compose box; on phones they are full-width banners below the header. */\n.activity-toasts {\n position: fixed;\n inset-block-start: 8em;\n inset-inline-end: var(--office-space-4);\n z-index: 10;\n display: flex;\n flex-direction: column;\n gap: var(--office-space-2);\n width: min(28em, calc(100% - var(--office-space-5)));\n pointer-events: none;\n}\n.activity-toast {\n display: flex;\n align-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-2) var(--office-space-3) var(--office-space-2) var(--office-space-4);\n border-radius: var(--office-radius-md);\n background: var(--office-call-background);\n color: var(--office-call-foreground);\n box-shadow: var(--office-shadow-teams);\n pointer-events: auto;\n}\n@media (max-width: 40em) {\n .activity-toasts {\n inset-block-start: 4.6em;\n inset-inline: var(--office-space-2);\n width: auto;\n }\n}\n.activity-toast .toast-text {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-width: 0;\n}\n.activity-toast .toast-text > * {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.activity-toast .toast-preview {\n opacity: 0.8;\n font-size: var(--office-font-size-sm);\n}\n.activity-toast button {\n flex: none;\n padding: var(--office-space-1) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius);\n background: transparent;\n color: inherit;\n font-weight: var(--office-font-weight-bold);\n}\n.activity-toast button:hover {\n background: var(--office-selected);\n}\n.activity-toast button svg {\n width: 1em;\n height: 1em;\n fill: none;\n stroke: currentColor;\n stroke-width: var(--office-icon-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n@media (pointer: coarse) {\n .activity-toast button {\n min-width: var(--office-touch-target, 44px);\n min-height: var(--office-touch-target, 44px);\n }\n}\n\n/* ---- chat and calendar ------------------------------------------------------------------ */\n.calendar-back svg,\n.show-calendar svg,\n.group-mark svg,\n.chat-head .chat-people svg {\n width: 1.25em;\n height: 1.25em;\n flex: none;\n fill: none;\n stroke: currentColor;\n stroke-width: var(--office-icon-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n.chat-head office-ui-avatar {\n flex: none;\n}\n.group-mark {\n display: grid;\n flex: none;\n place-items: center;\n width: 2.3em;\n height: 2.3em;\n border-radius: var(--office-radius-full);\n background: var(--office-teams-bubble);\n color: var(--office-teams-brand);\n}\n.chat-head .chat-people {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-2);\n}\n.empty-main {\n display: grid;\n flex: 1;\n place-content: center;\n justify-items: center;\n gap: var(--office-space-3);\n padding: var(--office-space-6);\n color: var(--office-teams-text-subtle);\n text-align: center;\n}\n.empty-main button {\n padding: var(--office-space-2) var(--office-space-4);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font: inherit;\n}\n.main > office-ui-calendar-view {\n flex: 1;\n min-height: 0;\n}\n.calendar-back {\n display: none;\n}\n@media (max-width: 56em) {\n .calendar-back {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n padding: var(--office-space-2) var(--office-space-3);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n }\n .calendar-back button {\n display: inline-flex;\n align-items: center;\n gap: var(--office-space-2);\n min-height: var(--office-touch-target, 44px);\n padding: var(--office-space-2) var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: transparent;\n }\n .chat-head .chat-people {\n min-width: var(--office-touch-target, 44px);\n }\n}\n.show-calendar {\n display: none;\n}\n@media (max-width: 56em) {\n .show-calendar {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--office-space-2);\n width: calc(100% - 2 * var(--office-space-4));\n min-height: var(--office-touch-target, 44px);\n margin: var(--office-space-3) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n }\n}\n";
3376
- function threadPane(state, client, openFile, close, highlightId = "") {
3572
+ function threadPane(state, client, openFile, close, highlightId = "", i18n = teamsI18n()) {
3377
3573
  const thread = state.thread;
3378
3574
  if (!thread) return nothing;
3379
- return html`<aside class="thread-pane" aria-label="Thread">
3575
+ const { t } = i18n;
3576
+ return html`<aside class="thread-pane" aria-label=${t("thread.title")}>
3380
3577
  <header class="thread-head">
3381
- <h2 tabindex="-1" data-thread-heading>Thread</h2>
3578
+ <h2 tabindex="-1" data-thread-heading>${t("thread.title")}</h2>
3382
3579
  <button
3383
3580
  type="button"
3384
3581
  @click=${() => client.followThread(state.selectedChannelId, thread.root.id, !state.threadFollowed)}
3385
3582
  >
3386
- ${state.threadFollowed ? "Unfollow thread" : "Follow thread"}
3583
+ ${t(state.threadFollowed ? "thread.unfollow" : "thread.follow")}
3387
3584
  </button>
3388
- <button type="button" @click=${close}>Close thread</button>
3585
+ <button type="button" @click=${close}>${t("thread.close")}</button>
3389
3586
  </header>
3390
3587
  <office-ui-chat-list
3391
3588
  .messages=${[thread.root, ...thread.replies]}
@@ -3397,7 +3594,7 @@ function threadPane(state, client, openFile, close, highlightId = "") {
3397
3594
  @office-chat-react=${(e) => client.toggleReaction(e.detail.messageId, e.detail.emoji)}
3398
3595
  @office-chat-reply=${(e) => client.startReply(e.detail.messageId)}
3399
3596
  @office-chat-edit=${(e) => client.startEdit(e.detail.messageId)}
3400
- @office-chat-delete=${(e) => globalThis.confirm?.("Delete this message?") !== false && client.deleteMessage(e.detail.messageId)}
3597
+ @office-chat-delete=${(e) => globalThis.confirm?.(t("chat.confirmDelete")) !== false && client.deleteMessage(e.detail.messageId)}
3401
3598
  @office-chat-open-file=${(e) => openFile(e.detail.attachment)}
3402
3599
  ></office-ui-chat-list>
3403
3600
  <office-ui-chat-composer
@@ -3409,7 +3606,7 @@ function threadPane(state, client, openFile, close, highlightId = "") {
3409
3606
  .missingFiles=${state.draft.missingFiles}
3410
3607
  .mentionTargets=${state.mentionTargets}
3411
3608
  @office-chat-draft=${(e) => client.setDraft(e.detail)}
3412
- placeholder="Reply in thread"
3609
+ placeholder=${t("thread.replyPlaceholder")}
3413
3610
  @office-chat-typing=${() => client.notifyTyping()}
3414
3611
  @office-chat-cancel=${() => client.cancelCompose()}
3415
3612
  @office-chat-send=${(e) => {
@@ -3420,23 +3617,21 @@ function threadPane(state, client, openFile, close, highlightId = "") {
3420
3617
  ></office-ui-chat-composer>
3421
3618
  </aside>`;
3422
3619
  }
3423
- function followedThreads(state, client, open, unreadOnly, filter) {
3620
+ function followedThreads(state, client, open, unreadOnly, filter, i18n = teamsI18n()) {
3621
+ const { t, tn } = i18n;
3424
3622
  const threads = state.followedThreads.filter((thread) => !unreadOnly || thread.unread > 0);
3425
- return html`<section class="followed-threads" aria-label="Followed threads">
3426
- <h1>Followed threads</h1>
3427
- <p>
3428
- Threads you follow on this device. Their replies appear in Activity and notify you according
3429
- to your notification settings.
3430
- </p>
3623
+ return html`<section class="followed-threads" aria-label=${t("followed.title")}>
3624
+ <h1>${t("followed.title")}</h1>
3625
+ <p>${t("followed.intro")}</p>
3431
3626
  <fieldset>
3432
- <legend>Automatically follow</legend>
3627
+ <legend>${t("followed.autoFollow")}</legend>
3433
3628
  <label
3434
3629
  ><input
3435
3630
  type="checkbox"
3436
3631
  .checked=${state.threadFollowSettings.started}
3437
3632
  @change=${(event) => client.setThreadFollowSettings({ started: event.target.checked })}
3438
3633
  />
3439
- Threads I start</label
3634
+ ${t("followed.started")}</label
3440
3635
  >
3441
3636
  <label
3442
3637
  ><input
@@ -3444,7 +3639,7 @@ function followedThreads(state, client, open, unreadOnly, filter) {
3444
3639
  .checked=${state.threadFollowSettings.replied}
3445
3640
  @change=${(event) => client.setThreadFollowSettings({ replied: event.target.checked })}
3446
3641
  />
3447
- Threads I reply to</label
3642
+ ${t("followed.replied")}</label
3448
3643
  >
3449
3644
  </fieldset>
3450
3645
  <label
@@ -3453,7 +3648,7 @@ function followedThreads(state, client, open, unreadOnly, filter) {
3453
3648
  .checked=${unreadOnly}
3454
3649
  @change=${(event) => filter(event.target.checked)}
3455
3650
  />
3456
- Unread only</label
3651
+ ${t("followed.unreadOnly")}</label
3457
3652
  >
3458
3653
  <ul>
3459
3654
  ${threads.length ? threads.map(
@@ -3461,64 +3656,72 @@ function followedThreads(state, client, open, unreadOnly, filter) {
3461
3656
  <button
3462
3657
  type="button"
3463
3658
  @click=${() => open(thread)}
3464
- aria-label=${`Open thread in ${thread.channelName}: ${thread.root.deleted ? "Deleted message" : thread.root.text}`}
3659
+ aria-label=${t("followed.open", {
3660
+ channel: thread.channelName,
3661
+ text: thread.root.deleted ? t("followed.deletedMessage") : thread.root.text
3662
+ })}
3465
3663
  >
3466
3664
  <strong># ${thread.channelName}</strong
3467
3665
  ><span
3468
- >${thread.root.deleted ? "This message was deleted" : thread.root.text || "Shared file"}</span
3666
+ >${thread.root.deleted ? t("chat.deleted") : thread.root.text || t("followed.sharedFile")}</span
3469
3667
  >
3470
3668
  <small
3471
- >${thread.replyCount} ${thread.replyCount === 1 ? "reply" : "replies"} ·
3472
- ${new Date(thread.updatedAt).toLocaleString()}</small
3669
+ >${tn("chat.replies", thread.replyCount)} ·
3670
+ ${new Date(thread.updatedAt).toLocaleString(i18n.intl)}</small
3473
3671
  ></button
3474
- ><span>${thread.unread > 0 ? `${thread.unread} unread` : "Read"}</span
3672
+ ><span
3673
+ >${thread.unread > 0 ? t("followed.unreadCount", { count: thread.unread }) : t("followed.read")}</span
3475
3674
  ><button
3476
3675
  type="button"
3477
3676
  @click=${() => client.markThreadRead(thread.channelId, thread.root.id, thread.unread > 0)}
3478
3677
  >
3479
- ${thread.unread > 0 ? "Mark as read" : "Mark as unread"}</button
3678
+ ${t(thread.unread > 0 ? "activity.markRead" : "activity.markUnread")}</button
3480
3679
  ><button
3481
3680
  type="button"
3482
3681
  @click=${() => client.followThread(thread.channelId, thread.root.id, false)}
3483
3682
  >
3484
- Unfollow thread
3683
+ ${t("thread.unfollow")}
3485
3684
  </button>
3486
3685
  </li>`
3487
- ) : html`<li>
3488
- ${unreadOnly ? "No unread followed threads." : "No followed threads yet. Open a thread and choose Follow thread."}
3489
- </li>`}
3686
+ ) : html`<li>${t(unreadOnly ? "followed.noneUnread" : "followed.none")}</li>`}
3490
3687
  </ul>
3491
3688
  </section>`;
3492
3689
  }
3493
- function draftList(state, client, open) {
3494
- return html`<section class="followed-threads" aria-label="Drafts">
3495
- <h1>Drafts</h1>
3496
- <p>Unsent messages on this device. After reloading, draft attachments must be reattached.</p>
3690
+ function draftList(state, client, open, i18n = teamsI18n()) {
3691
+ const { t, tn } = i18n;
3692
+ return html`<section class="followed-threads" aria-label=${t("drafts.title")}>
3693
+ <h1>${t("drafts.title")}</h1>
3694
+ <p>${t("drafts.intro")}</p>
3497
3695
  <ul>
3498
3696
  ${state.drafts.length ? state.drafts.map((draft) => {
3499
3697
  const channel = state.channels.find((item) => item.id === draft.context.channelId);
3698
+ const body = draft.text || t("drafts.attachments");
3500
3699
  return html`<li>
3501
3700
  <button
3502
3701
  type="button"
3503
- aria-label=${`Resume draft in ${channel.name}: ${draft.text.slice(0, 80) || "Attachments"}`}
3702
+ aria-label=${t("drafts.resume", {
3703
+ channel: channel.name,
3704
+ text: draft.text.slice(0, 80) || t("drafts.attachments")
3705
+ })}
3504
3706
  @click=${() => open(draft.context)}
3505
3707
  >
3506
- <strong># ${channel.name}</strong><span>${draft.text || "Attachments"}</span
3708
+ <strong># ${channel.name}</strong><span>${body}</span
3507
3709
  ><small
3508
- >${draft.context.editId ? "Edit" : draft.context.threadId ? "Thread reply" : "New post"}
3509
- · ${draft.files.length + draft.missingFiles.length} attachments</small
3710
+ >${draft.context.editId ? t("drafts.edit") : draft.context.threadId ? t("drafts.threadReply") : t("drafts.newPost")}
3711
+ ·
3712
+ ${tn("drafts.attachmentCount", draft.files.length + draft.missingFiles.length)}</small
3510
3713
  ></button
3511
3714
  ><button
3512
3715
  type="button"
3513
3716
  @click=${() => {
3514
- if (globalThis.confirm?.("Discard this unsent draft?") !== false)
3717
+ if (globalThis.confirm?.(t("drafts.confirmDiscard")) !== false)
3515
3718
  client.discardDraft(draft.context);
3516
3719
  }}
3517
3720
  >
3518
- Discard draft
3721
+ ${t("drafts.discard")}
3519
3722
  </button>
3520
3723
  </li>`;
3521
- }) : html`<li>No unsent drafts.</li>`}
3724
+ }) : html`<li>${t("drafts.none")}</li>`}
3522
3725
  </ul>
3523
3726
  </section>`;
3524
3727
  }
@@ -3545,43 +3748,76 @@ function filePopoutDetail(pageUrl) {
3545
3748
  return null;
3546
3749
  }
3547
3750
  }
3548
- function messageTransfers(state, client) {
3751
+ function messageTransfers(state, client, i18n = teamsI18n()) {
3752
+ const { t } = i18n;
3549
3753
  return state.messageTransfers.filter((transfer) => transfer.context.channelId === state.selectedChannelId).map(
3550
3754
  (transfer) => html`<div class="message-transfer" role="status">
3551
3755
  <span
3552
- >${`${transfer.context.threadId || transfer.context.replyTo ? "Sending thread reply" : "Sending channel post"}: ${transfer.progress.fileName}. ${transfer.progress.completed} of ${transfer.progress.total} files uploaded`}</span
3756
+ >${t("transfer.status", {
3757
+ kind: t(
3758
+ transfer.context.threadId || transfer.context.replyTo ? "transfer.threadReply" : "transfer.channelPost"
3759
+ ),
3760
+ file: transfer.progress.fileName,
3761
+ completed: transfer.progress.completed,
3762
+ total: transfer.progress.total
3763
+ })}</span
3553
3764
  ><progress
3554
- aria-label="Message file progress"
3765
+ aria-label=${t("transfer.progress")}
3555
3766
  max=${transfer.progress.total}
3556
3767
  value=${transfer.progress.completed}
3557
3768
  ></progress
3558
3769
  ><button type="button" @click=${() => client.cancelSend(transfer.id)}>
3559
- Cancel message send
3770
+ ${t("transfer.cancel")}
3560
3771
  </button>
3561
3772
  </div>`
3562
3773
  );
3563
3774
  }
3564
3775
 
3565
3776
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/profile-menu.css?raw
3566
- var profile_menu_default = ":host {\n display: inline-flex;\n font: inherit;\n color: inherit;\n}\n.profile {\n display: inline-flex;\n align-items: center;\n padding: var(--office-space-1);\n border: 0;\n border-radius: var(--office-radius-full);\n background: transparent;\n cursor: pointer;\n}\n.profile:hover {\n background: var(--office-selected);\n}\n[popover] {\n min-width: 20em;\n max-width: 90vw;\n max-height: min(80vh, 40em);\n overflow: auto;\n padding: var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n color: var(--office-foreground);\n box-shadow: var(--office-shadow-teams);\n}\n.identity {\n display: flex;\n gap: var(--office-space-3);\n align-items: center;\n margin-bottom: var(--office-space-4);\n}\n.identity strong {\n overflow-wrap: anywhere;\n}\nlabel {\n display: grid;\n gap: var(--office-space-2);\n margin-bottom: var(--office-space-3);\n}\n.who {\n display: grid;\n min-width: 0;\n}\n.status-line,\n.message,\n.hint {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n overflow-wrap: anywhere;\n}\n.hint:empty {\n display: none;\n}\n.actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-2);\n margin-bottom: var(--office-space-3);\n}\n.actions button {\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n.actions button:hover:not(:disabled) {\n background: var(--office-selected);\n}\n.actions button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n@media (pointer: coarse) {\n .actions button,\n select,\n input {\n min-height: var(--office-touch-target, 44px);\n }\n}\ninput,\nselect {\n font: inherit;\n padding: var(--office-space-2);\n color: inherit;\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n}\nselect:disabled {\n opacity: 0.6;\n}\n.connection {\n color: var(--office-teams-text-subtle);\n margin-bottom: 0;\n font-size: var(--office-font-size-sm);\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n";
3777
+ var profile_menu_default = ":host {\n display: inline-flex;\n font: inherit;\n color: inherit;\n}\n.profile {\n display: inline-flex;\n align-items: center;\n padding: var(--office-space-1);\n border: 0;\n border-radius: var(--office-radius-full);\n background: transparent;\n cursor: pointer;\n}\n.profile:hover {\n background: var(--office-selected);\n}\n[popover] {\n min-width: 20em;\n max-width: 90vw;\n max-height: min(80vh, 40em);\n overflow: auto;\n padding: var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-popover);\n color: var(--office-foreground);\n box-shadow: var(--office-shadow-teams);\n}\n.identity {\n display: flex;\n gap: var(--office-space-3);\n align-items: center;\n margin-bottom: var(--office-space-4);\n}\n.identity strong {\n overflow-wrap: anywhere;\n}\nlabel {\n display: grid;\n gap: var(--office-space-2);\n margin-bottom: var(--office-space-3);\n}\n.who {\n display: grid;\n min-width: 0;\n}\n.status-line,\n.message,\n.hint {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n overflow-wrap: anywhere;\n}\n.hint:empty {\n display: none;\n}\n.actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--office-space-2);\n margin-bottom: var(--office-space-3);\n}\n.actions button,\n.account button {\n padding: var(--office-space-2) var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n.actions button:hover:not(:disabled),\n.account button:hover {\n background: var(--office-selected);\n}\n.actions button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n@media (pointer: coarse) {\n .actions button,\n .account button,\n select,\n input {\n min-height: var(--office-touch-target, 44px);\n }\n}\n.account {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--office-space-2);\n margin-bottom: var(--office-space-3);\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-sm);\n overflow-wrap: anywhere;\n}\ninput,\nselect {\n font: inherit;\n padding: var(--office-space-2);\n color: inherit;\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n}\nselect:disabled {\n opacity: 0.6;\n}\n.connection {\n color: var(--office-teams-text-subtle);\n margin-bottom: 0;\n font-size: var(--office-font-size-sm);\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n";
3567
3778
 
3568
3779
  // src/teams/app/profile-menu.ts
3569
- var idleLabel = (minutes) => minutes === 0 ? "Never" : `${minutes} minutes`;
3570
- var untilLabel = (at) => {
3780
+ var STATUS_KEY = {
3781
+ available: "status.available",
3782
+ busy: "status.busy",
3783
+ dnd: "status.dnd",
3784
+ brb: "status.brb",
3785
+ away: "status.away",
3786
+ offline: "status.offline"
3787
+ };
3788
+ var CONNECTION_KEY = {
3789
+ connecting: "connection.connecting",
3790
+ connected: "connection.connected",
3791
+ disconnected: "connection.disconnected",
3792
+ error: "connection.error"
3793
+ };
3794
+ var DURATION_KEY = {
3795
+ never: "duration.never",
3796
+ "30m": "duration.30m",
3797
+ "1h": "duration.1h",
3798
+ "2h": "duration.2h",
3799
+ today: "duration.today",
3800
+ week: "duration.week"
3801
+ };
3802
+ function untilLabel(at, i18n = teamsI18n()) {
3571
3803
  if (at === null) return "";
3804
+ const { t, intl } = i18n;
3572
3805
  const d = new Date(at);
3573
3806
  const today = (/* @__PURE__ */ new Date()).toDateString();
3574
3807
  if (d.getHours() === 0 && d.getMinutes() === 0) {
3575
3808
  const lastDay = new Date(at - 1);
3576
- return lastDay.toDateString() === today ? "Until end of today" : `Until end of ${lastDay.toLocaleDateString([], { weekday: "long" })}`;
3809
+ return lastDay.toDateString() === today ? t("profile.untilEndToday") : t("profile.untilEndDay", {
3810
+ day: lastDay.toLocaleDateString(intl, { weekday: "long" })
3811
+ });
3577
3812
  }
3578
- const time = d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
3579
- return d.toDateString() === today ? `Until ${time}` : `Until ${d.toLocaleDateString([], { weekday: "long" })} ${time}`;
3580
- };
3581
- var TeamsProfileMenu = class extends LitElement {
3813
+ const time = d.toLocaleTimeString(intl, { hour: "numeric", minute: "2-digit" });
3814
+ return d.toDateString() === today ? t("profile.until", { time }) : t("profile.untilDayTime", { day: d.toLocaleDateString(intl, { weekday: "long" }), time });
3815
+ }
3816
+ var TeamsProfileMenu = class extends TeamsLitElement {
3582
3817
  static styles = unsafeCSS(profile_menu_default);
3583
3818
  static properties = {
3584
3819
  state: { attribute: false },
3820
+ account: { type: String },
3585
3821
  open: { state: true },
3586
3822
  draft: { state: true },
3587
3823
  resetAfter: { state: true },
@@ -3590,6 +3826,7 @@ var TeamsProfileMenu = class extends LitElement {
3590
3826
  constructor() {
3591
3827
  super();
3592
3828
  this.state = null;
3829
+ this.account = "";
3593
3830
  this.open = false;
3594
3831
  this.draft = null;
3595
3832
  this.resetAfter = "never";
@@ -3618,10 +3855,13 @@ var TeamsProfileMenu = class extends LitElement {
3618
3855
  durationOptions(selected) {
3619
3856
  return STATUS_DURATIONS.map(
3620
3857
  (value) => html`<option value=${value} ?selected=${selected === value}>
3621
- ${STATUS_DURATION_LABELS[value]}
3858
+ ${this.t(DURATION_KEY[value])}
3622
3859
  </option>`
3623
3860
  );
3624
3861
  }
3862
+ idleLabel(minutes) {
3863
+ return minutes === 0 ? this.t("duration.never") : this.tn("profile.idleMinutes", minutes);
3864
+ }
3625
3865
  render() {
3626
3866
  const state = this.state;
3627
3867
  if (!state) return nothing;
@@ -3629,7 +3869,7 @@ var TeamsProfileMenu = class extends LitElement {
3629
3869
  return html`<button
3630
3870
  type="button"
3631
3871
  class="profile"
3632
- aria-label=${`Profile for ${state.user.name}`}
3872
+ aria-label=${this.t("profile.for", { name: state.user.name })}
3633
3873
  aria-expanded=${String(this.open)}
3634
3874
  aria-haspopup="dialog"
3635
3875
  aria-controls="profile"
@@ -3645,7 +3885,7 @@ var TeamsProfileMenu = class extends LitElement {
3645
3885
  id="profile"
3646
3886
  popover="auto"
3647
3887
  role="dialog"
3648
- aria-label="Your profile"
3888
+ aria-label=${this.t("profile.yours")}
3649
3889
  @keydown=${(event) => {
3650
3890
  if (event.key !== "Escape") return;
3651
3891
  event.preventDefault();
@@ -3669,13 +3909,13 @@ var TeamsProfileMenu = class extends LitElement {
3669
3909
  <div class="who">
3670
3910
  <strong>${state.user.name}</strong>
3671
3911
  <span class="status-line"
3672
- >${PRESENCE_LABELS[state.availability]}${state.presence.idle && state.presence.status === "available" ? " (idle)" : ""}</span
3912
+ >${this.t(STATUS_KEY[state.availability])}${state.presence.idle && state.presence.status === "available" ? ` ${this.t("profile.idle")}` : ""}</span
3673
3913
  >
3674
3914
  ${presence.message ? html`<span class="message">${presence.message}</span>` : nothing}
3675
3915
  </div>
3676
3916
  </div>
3677
3917
  <label
3678
- >Availability<select
3918
+ >${this.t("profile.availability")}<select
3679
3919
  .value=${presence.status}
3680
3920
  @change=${(event) => {
3681
3921
  const availability = event.target.value;
@@ -3683,11 +3923,11 @@ var TeamsProfileMenu = class extends LitElement {
3683
3923
  this.fire("teams-profile-status", { availability, duration: this.resetAfter });
3684
3924
  }}
3685
3925
  >
3686
- ${PRESENCE_STATUSES.map((value) => html`<option value=${value} ?selected=${presence.status === value}>${PRESENCE_LABELS[value]}</option>`)}
3926
+ ${PRESENCE_STATUSES.map((value) => html`<option value=${value} ?selected=${presence.status === value}>${this.t(STATUS_KEY[value])}</option>`)}
3687
3927
  </select></label
3688
3928
  >
3689
3929
  <label
3690
- >Reset status after<select
3930
+ >${this.t("profile.resetAfter")}<select
3691
3931
  name="status-duration"
3692
3932
  @change=${(event) => {
3693
3933
  const duration = event.target.value;
@@ -3698,14 +3938,14 @@ var TeamsProfileMenu = class extends LitElement {
3698
3938
  >
3699
3939
  ${this.durationOptions(this.resetAfter)}
3700
3940
  </select>
3701
- <span class="hint">${untilLabel(presence.statusExpiresAt)}</span></label
3941
+ <span class="hint">${untilLabel(presence.statusExpiresAt, this.i18n)}</span></label
3702
3942
  >
3703
3943
  <label
3704
- >Status message<input
3944
+ >${this.t("profile.statusMessage")}<input
3705
3945
  type="text"
3706
3946
  name="message"
3707
3947
  maxlength=${MAX_STATUS_MESSAGE_CHARS}
3708
- placeholder="What's your status message?"
3948
+ placeholder=${this.t("profile.messagePlaceholder")}
3709
3949
  .value=${this.draft ?? presence.message}
3710
3950
  @input=${(event) => this.draft = event.target.value}
3711
3951
  @keydown=${(event) => {
@@ -3714,10 +3954,10 @@ var TeamsProfileMenu = class extends LitElement {
3714
3954
  this.saveMessage();
3715
3955
  }}
3716
3956
  />
3717
- <span class="hint">${untilLabel(presence.messageExpiresAt)}</span></label
3957
+ <span class="hint">${untilLabel(presence.messageExpiresAt, this.i18n)}</span></label
3718
3958
  >
3719
3959
  <label
3720
- >Clear status message after<select
3960
+ >${this.t("profile.clearAfter")}<select
3721
3961
  name="message-duration"
3722
3962
  @change=${(event) => {
3723
3963
  const duration = event.target.value;
@@ -3728,7 +3968,7 @@ var TeamsProfileMenu = class extends LitElement {
3728
3968
  </select></label
3729
3969
  >
3730
3970
  <div class="actions">
3731
- <button type="button" @click=${this.saveMessage}>Save message</button>
3971
+ <button type="button" @click=${this.saveMessage}>${this.t("profile.saveMessage")}</button>
3732
3972
  <button
3733
3973
  type="button"
3734
3974
  ?disabled=${!presence.message && !this.draft}
@@ -3737,11 +3977,11 @@ var TeamsProfileMenu = class extends LitElement {
3737
3977
  this.saveMessage();
3738
3978
  }}
3739
3979
  >
3740
- Clear message
3980
+ ${this.t("profile.clearMessage")}
3741
3981
  </button>
3742
3982
  </div>
3743
3983
  <label
3744
- >Show me as away after<select
3984
+ >${this.t("profile.awayAfter")}<select
3745
3985
  name="idle"
3746
3986
  @change=${(event) => {
3747
3987
  const minutes = Number(event.target.value);
@@ -3749,11 +3989,22 @@ var TeamsProfileMenu = class extends LitElement {
3749
3989
  this.fire("teams-profile-idle", { minutes });
3750
3990
  }}
3751
3991
  >
3752
- ${IDLE_MINUTE_CHOICES.map((minutes) => html`<option value=${minutes} ?selected=${presence.idleMinutes === minutes}>${idleLabel(minutes)}</option>`)}
3992
+ ${IDLE_MINUTE_CHOICES.map((minutes) => html`<option value=${minutes} ?selected=${presence.idleMinutes === minutes}>${this.idleLabel(minutes)}</option>`)}
3753
3993
  </select></label
3754
3994
  >
3995
+ ${this.account ? html`<div class="account">
3996
+ <span>${this.t("signIn.signedInAs", { id: this.account })}</span>
3997
+ <button
3998
+ type="button"
3999
+ name="sign-out"
4000
+ @click=${() => this.fire("teams-sign-out", {})}
4001
+ >
4002
+ ${this.t("signIn.signOut")}
4003
+ </button>
4004
+ </div>` : nothing}
3755
4005
  <p class="connection">
3756
- ${state.mode === "local" ? "Local workspace" : "Workspace server"}: ${state.status}
4006
+ ${this.t(state.mode === "local" ? "profile.localWorkspace" : "profile.workspaceServer")}:
4007
+ ${this.t(CONNECTION_KEY[state.status])}
3757
4008
  </p>
3758
4009
  </div>`;
3759
4010
  }
@@ -3763,17 +4014,302 @@ function defineTeamsProfileMenu() {
3763
4014
  customElements.define("teams-profile-menu", TeamsProfileMenu);
3764
4015
  }
3765
4016
 
4017
+ // src/teams/app/sign-in-strings.ts
4018
+ function signInErrorText(reason, retryAfterSec, i18n = teamsI18n()) {
4019
+ switch (reason) {
4020
+ case "credentials":
4021
+ return i18n.t("signIn.error.credentials");
4022
+ case "rate-limited":
4023
+ return retryAfterSec ? i18n.t("signIn.error.rateLimited", { seconds: retryAfterSec }) : i18n.t("signIn.error.rateLimitedMinute");
4024
+ case "network":
4025
+ return i18n.t("signIn.error.network");
4026
+ default:
4027
+ return i18n.t("signIn.error.server");
4028
+ }
4029
+ }
4030
+
4031
+ // src/teams/app/sign-in-flow.ts
4032
+ var PROBE_AFTER_MS = 4e3;
4033
+ var PROBE_AGAIN_MS = 15e3;
4034
+ var MAX_TIMER_MS = 2 ** 31 - 1;
4035
+ var SignInFlow = class {
4036
+ constructor(host) {
4037
+ this.host = host;
4038
+ }
4039
+ host;
4040
+ phase = "open";
4041
+ session = null;
4042
+ /** Kept across a re-prompt so the person only retypes the password. */
4043
+ userId = "";
4044
+ notice = "";
4045
+ error = "";
4046
+ busy = false;
4047
+ retryAfterSec = 0;
4048
+ accounts = /* @__PURE__ */ new Map();
4049
+ probing = "";
4050
+ expiryTimer;
4051
+ checkTimer;
4052
+ checking = false;
4053
+ generation = 0;
4054
+ i18n() {
4055
+ return this.host.i18n?.() ?? teamsI18n();
4056
+ }
4057
+ key(config) {
4058
+ return `${serverOrigin(config) ?? ""}|${config.token ?? ""}`;
4059
+ }
4060
+ /**
4061
+ * Whether the client must wait: true while the server is being asked and while the sign-in
4062
+ * screen is showing. Call it from every (re)start with the settings-derived config.
4063
+ */
4064
+ gate(config) {
4065
+ if (config.mode !== "server" || config.sessionToken || !serverOrigin(config)) {
4066
+ this.phase = "open";
4067
+ return false;
4068
+ }
4069
+ if (this.session) {
4070
+ this.phase = "open";
4071
+ return false;
4072
+ }
4073
+ const key = this.key(config);
4074
+ const known = this.accounts.get(key);
4075
+ if (known === false) {
4076
+ this.phase = "open";
4077
+ return false;
4078
+ }
4079
+ if (known === true) {
4080
+ this.phase = "required";
4081
+ return true;
4082
+ }
4083
+ this.phase = "checking";
4084
+ if (this.probing !== key) {
4085
+ this.probing = key;
4086
+ const generation = this.generation;
4087
+ void probeTeamsAccounts(config).then((accounts) => {
4088
+ if (generation !== this.generation || this.probing !== key) return;
4089
+ this.probing = "";
4090
+ if (accounts !== null) this.accounts.set(key, accounts);
4091
+ this.phase = accounts ? "required" : "open";
4092
+ this.host.changed();
4093
+ });
4094
+ }
4095
+ return true;
4096
+ }
4097
+ /** The config to start the client with: the session, when there is one, rides along. */
4098
+ apply(config) {
4099
+ return this.session && !config.sessionToken ? { ...config, sessionToken: this.session.token } : config;
4100
+ }
4101
+ async submit(config, userId, credential) {
4102
+ if (this.busy) return;
4103
+ const id = userId.trim();
4104
+ this.userId = id;
4105
+ this.error = "";
4106
+ this.retryAfterSec = 0;
4107
+ this.busy = true;
4108
+ this.host.changed();
4109
+ try {
4110
+ const session = await requestTeamsSession(config, { userId: id, credential });
4111
+ this.session = session;
4112
+ this.userId = session.userId;
4113
+ this.notice = "";
4114
+ this.phase = "open";
4115
+ this.armExpiry(session);
4116
+ this.host.emit("teams-signed-in", { userId: session.userId, expiresAt: session.expiresAt });
4117
+ } catch (error) {
4118
+ const failure = error instanceof TeamsSignInError ? error : null;
4119
+ this.error = signInErrorText(
4120
+ failure?.reason ?? "server",
4121
+ failure?.retryAfterSec,
4122
+ this.i18n()
4123
+ );
4124
+ this.retryAfterSec = failure?.retryAfterSec ?? 0;
4125
+ } finally {
4126
+ this.busy = false;
4127
+ this.host.changed();
4128
+ }
4129
+ }
4130
+ /** The session no longer works: back to the sign-in screen, keeping the user id. */
4131
+ expire(message) {
4132
+ if (!this.session) return;
4133
+ this.drop();
4134
+ this.notice = message;
4135
+ this.host.emit("teams-session-ended", { userId: this.userId });
4136
+ this.host.changed();
4137
+ }
4138
+ async signOut(config) {
4139
+ const session = this.session;
4140
+ if (!session) return;
4141
+ this.drop();
4142
+ this.userId = "";
4143
+ this.notice = this.i18n().t("signIn.signedOut");
4144
+ this.host.emit("teams-signed-out", { userId: session.userId });
4145
+ this.host.changed();
4146
+ await signOutTeamsSession(config, session.token);
4147
+ }
4148
+ /** Feed the connection status after every render; a session that stays unusable is checked. */
4149
+ watchConnection(connected, config) {
4150
+ if (!this.session) return;
4151
+ if (connected) {
4152
+ clearTimeout(this.checkTimer);
4153
+ this.checkTimer = void 0;
4154
+ return;
4155
+ }
4156
+ if (this.checkTimer !== void 0 || this.checking) return;
4157
+ this.scheduleCheck(config, PROBE_AFTER_MS);
4158
+ }
4159
+ scheduleCheck(config, delay) {
4160
+ const session = this.session;
4161
+ if (!session) return;
4162
+ this.checkTimer = setTimeout(() => {
4163
+ this.checkTimer = void 0;
4164
+ this.checking = true;
4165
+ void checkTeamsSession(config, session.token).then((result) => {
4166
+ this.checking = false;
4167
+ if (this.session !== session) return;
4168
+ if (result === "rejected") this.expire(this.i18n().t("signIn.ended"));
4169
+ else this.scheduleCheck(config, PROBE_AGAIN_MS);
4170
+ });
4171
+ }, delay);
4172
+ }
4173
+ armExpiry(session) {
4174
+ clearTimeout(this.expiryTimer);
4175
+ const wait = Math.min(Math.max(session.expiresAt - Date.now(), 0), MAX_TIMER_MS);
4176
+ this.expiryTimer = setTimeout(() => {
4177
+ if (this.session === session) this.expire(this.i18n().t("signIn.expired"));
4178
+ }, wait);
4179
+ }
4180
+ drop() {
4181
+ this.session = null;
4182
+ this.error = "";
4183
+ this.retryAfterSec = 0;
4184
+ clearTimeout(this.expiryTimer);
4185
+ clearTimeout(this.checkTimer);
4186
+ this.checkTimer = void 0;
4187
+ this.phase = "required";
4188
+ }
4189
+ /** Forget everything (the element is leaving the page). */
4190
+ dispose() {
4191
+ this.generation++;
4192
+ this.probing = "";
4193
+ clearTimeout(this.expiryTimer);
4194
+ clearTimeout(this.checkTimer);
4195
+ this.checkTimer = void 0;
4196
+ }
4197
+ };
4198
+
4199
+ // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/teams-sign-in.css?raw
4200
+ var teams_sign_in_default = ":host {\n display: grid;\n place-items: center;\n height: 100%;\n background: var(--office-teams-panel);\n color: var(--office-foreground);\n font: inherit;\n}\n.card {\n display: grid;\n justify-items: center;\n gap: var(--office-space-3);\n width: min(24em, 90%);\n padding: var(--office-space-6);\n border-radius: var(--office-radius-lg);\n background: var(--office-background);\n box-shadow: var(--office-shadow-teams);\n}\n.logo {\n display: inline-grid;\n place-items: center;\n width: 2.4em;\n height: 2.4em;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n}\nh1 {\n margin: 0;\n font-size: var(--office-font-size-xl);\n}\np {\n margin: 0;\n text-align: center;\n overflow-wrap: anywhere;\n}\n.muted {\n color: var(--office-teams-text-subtle);\n}\nlabel {\n display: grid;\n gap: var(--office-space-2);\n width: 100%;\n}\ninput {\n width: 100%;\n box-sizing: border-box;\n padding: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius);\n background: var(--office-field-background);\n color: inherit;\n font: inherit;\n}\ninput:focus-visible,\nbutton:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n.error {\n color: var(--office-danger);\n}\nbutton {\n width: 100%;\n padding: var(--office-space-3);\n border: 0;\n border-radius: var(--office-radius-md);\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n font: inherit;\n font-weight: var(--office-font-weight-bold);\n cursor: pointer;\n}\nbutton:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n";
4201
+
4202
+ // src/teams/app/teams-sign-in.ts
4203
+ var TeamsSignIn = class extends TeamsLitElement {
4204
+ static styles = unsafeCSS(teams_sign_in_default);
4205
+ static properties = {
4206
+ userId: { type: String },
4207
+ notice: { type: String },
4208
+ error: { type: String },
4209
+ retryAfter: { type: Number },
4210
+ busy: { type: Boolean },
4211
+ checking: { type: Boolean },
4212
+ wait: { state: true }
4213
+ };
4214
+ timer;
4215
+ constructor() {
4216
+ super();
4217
+ this.userId = "";
4218
+ this.notice = "";
4219
+ this.error = "";
4220
+ this.retryAfter = 0;
4221
+ this.busy = false;
4222
+ this.checking = false;
4223
+ this.wait = 0;
4224
+ }
4225
+ willUpdate(changed) {
4226
+ if (!changed.has("retryAfter") && !changed.has("error")) return;
4227
+ clearInterval(this.timer);
4228
+ this.wait = this.error ? this.retryAfter : 0;
4229
+ if (this.wait <= 0) return;
4230
+ this.timer = setInterval(() => {
4231
+ this.wait = Math.max(0, this.wait - 1);
4232
+ if (this.wait === 0) clearInterval(this.timer);
4233
+ }, 1e3);
4234
+ }
4235
+ updated(changed) {
4236
+ if (changed.has("busy") && !this.busy && this.error)
4237
+ this.renderRoot.querySelector('input[name="credential"]')?.focus();
4238
+ }
4239
+ disconnectedCallback() {
4240
+ clearInterval(this.timer);
4241
+ super.disconnectedCallback();
4242
+ }
4243
+ submit(event) {
4244
+ event.preventDefault();
4245
+ if (this.busy || this.wait > 0) return;
4246
+ const form = event.target;
4247
+ const data = new FormData(form);
4248
+ const userId = String(data.get("user") ?? "").trim();
4249
+ const credential = String(data.get("credential") ?? "");
4250
+ form.elements.namedItem("credential").value = "";
4251
+ if (!userId || !credential) return;
4252
+ this.dispatchEvent(
4253
+ new CustomEvent("teams-sign-in-submit", {
4254
+ detail: { userId, credential },
4255
+ bubbles: true,
4256
+ composed: true
4257
+ })
4258
+ );
4259
+ }
4260
+ render() {
4261
+ if (this.checking)
4262
+ return html`<div class="card" role="status" aria-live="polite">
4263
+ <span class="logo" aria-hidden="true">T</span>
4264
+ <p class="muted">${this.t("signIn.checking")}</p>
4265
+ </div>`;
4266
+ const locked = this.busy || this.wait > 0;
4267
+ const error = this.error && this.wait > 0 && this.retryAfter > 0 ? this.t("signIn.error.rateLimited", { seconds: this.wait }) : this.error;
4268
+ return html`<form class="card" @submit=${this.submit} aria-busy=${String(this.busy)}>
4269
+ <span class="logo" aria-hidden="true">T</span>
4270
+ <h1>${this.t("signIn.title")}</h1>
4271
+ <p class="muted">${this.notice || this.t("signIn.intro")}</p>
4272
+ <label
4273
+ >${this.t("signIn.userLabel")}<input
4274
+ name="user"
4275
+ autocomplete="username"
4276
+ autocapitalize="none"
4277
+ spellcheck="false"
4278
+ maxlength="64"
4279
+ required
4280
+ .value=${this.userId}
4281
+ ?disabled=${this.busy}
4282
+ /></label>
4283
+ <label
4284
+ >${this.t("signIn.credentialLabel")}<input
4285
+ name="credential"
4286
+ type="password"
4287
+ autocomplete="current-password"
4288
+ required
4289
+ ?disabled=${this.busy}
4290
+ /></label>
4291
+ ${error ? html`<p class="error" role="alert">${error}</p>` : nothing}
4292
+ <button type="submit" ?disabled=${locked}>
4293
+ ${this.t(this.busy ? "signIn.submitting" : "signIn.submit")}
4294
+ </button>
4295
+ </form>`;
4296
+ }
4297
+ };
4298
+ function defineTeamsSignIn() {
4299
+ if (!customElements.get("teams-sign-in")) customElements.define("teams-sign-in", TeamsSignIn);
4300
+ }
4301
+
3766
4302
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/pinned-bar.css?raw
3767
4303
  var pinned_bar_default = ":host {\n display: block;\n flex: none;\n font: inherit;\n color: inherit;\n}\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\nsection {\n background: var(--office-teams-panel);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\nbutton {\n font: inherit;\n color: inherit;\n cursor: pointer;\n border: 0;\n background: transparent;\n}\n.summary {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n padding: var(--office-space-1) var(--office-space-3);\n}\n.latest {\n display: flex;\n flex: 1;\n align-items: center;\n gap: var(--office-space-2);\n min-width: 0;\n min-height: 2.2em;\n padding: var(--office-space-1) var(--office-space-2);\n border-radius: var(--office-radius);\n text-align: start;\n}\n.latest:hover,\n.count:hover,\n.jump:hover,\n.unpin:hover {\n background: var(--office-selected);\n}\n.mark {\n display: inline-grid;\n flex: none;\n color: var(--office-teams-brand);\n}\nsvg {\n width: 1.1em;\n height: 1.1em;\n fill: none;\n stroke: currentColor;\n stroke-width: var(--office-icon-stroke);\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n.text {\n min-width: 0;\n overflow: hidden;\n color: var(--office-teams-text-subtle);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.count {\n flex: none;\n min-height: 2.2em;\n padding: var(--office-space-1) var(--office-space-3);\n border-radius: var(--office-radius);\n color: var(--office-teams-brand);\n font-weight: var(--office-font-weight-bold);\n}\nul {\n display: grid;\n gap: var(--office-space-1);\n max-height: 14em;\n margin: 0;\n padding: 0 var(--office-space-3) var(--office-space-2);\n overflow: auto;\n list-style: none;\n}\nli {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n}\n.jump {\n display: grid;\n flex: 1;\n gap: var(--office-space-0);\n min-width: 0;\n padding: var(--office-space-2) var(--office-space-3);\n border-radius: var(--office-radius-md);\n text-align: start;\n}\n.by {\n color: var(--office-teams-text-subtle);\n font-size: var(--office-font-size-xs);\n}\n.unpin {\n flex: none;\n margin-inline-end: var(--office-space-2);\n padding: var(--office-space-1) var(--office-space-3);\n border-radius: var(--office-radius);\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n@media (pointer: coarse) {\n .latest,\n .count,\n .unpin {\n min-height: var(--office-touch-target, 44px);\n }\n .unpin {\n min-width: var(--office-touch-target, 44px);\n }\n}\n";
3768
4304
 
3769
4305
  // src/teams/app/pinned-bar.ts
3770
4306
  var SNIPPET = 140;
3771
- var snippet = (m) => {
4307
+ var snippet = (m, attachment) => {
3772
4308
  const text = m.text.replace(/\s+/gu, " ").trim();
3773
4309
  if (text) return text.length > SNIPPET ? `${text.slice(0, SNIPPET)}...` : text;
3774
- return m.attachments[0]?.name ?? "Attachment";
4310
+ return m.attachments[0]?.name ?? attachment;
3775
4311
  };
3776
- var TeamsPinnedBar = class extends LitElement {
4312
+ var TeamsPinnedBar = class extends TeamsLitElement {
3777
4313
  static styles = unsafeCSS(pinned_bar_default);
3778
4314
  static properties = { pinned: { attribute: false }, expanded: { state: true } };
3779
4315
  constructor() {
@@ -3790,7 +4326,7 @@ var TeamsPinnedBar = class extends LitElement {
3790
4326
  const [latest] = this.pinned;
3791
4327
  if (!latest) return nothing;
3792
4328
  return html`
3793
- <section aria-label="Pinned messages">
4329
+ <section aria-label=${this.t("pinned.messages")}>
3794
4330
  <div class="summary">
3795
4331
  <button
3796
4332
  type="button"
@@ -3799,7 +4335,7 @@ var TeamsPinnedBar = class extends LitElement {
3799
4335
  >
3800
4336
  <span class="mark">${icon("pin")}</span>
3801
4337
  <strong>${latest.message.authorName}</strong>
3802
- <span class="text">${snippet(latest.message)}</span>
4338
+ <span class="text">${snippet(latest.message, this.t("pinned.attachment"))}</span>
3803
4339
  </button>
3804
4340
  <button
3805
4341
  type="button"
@@ -3808,7 +4344,7 @@ var TeamsPinnedBar = class extends LitElement {
3808
4344
  aria-controls="pins"
3809
4345
  @click=${() => this.expanded = !this.expanded}
3810
4346
  >
3811
- ${this.pinned.length} pinned
4347
+ ${this.t("pinned.count", { count: this.pinned.length })}
3812
4348
  </button>
3813
4349
  </div>
3814
4350
  ${this.expanded ? html`<ul id="pins">
@@ -3820,17 +4356,19 @@ var TeamsPinnedBar = class extends LitElement {
3820
4356
  @click=${() => this.emit("teams-pinned-open", pin.message.id)}
3821
4357
  >
3822
4358
  <strong>${pin.message.authorName}</strong>
3823
- <span class="text">${snippet(pin.message)}</span>
3824
- <span class="by">Pinned by ${pin.pinnedByName}</span>
4359
+ <span class="text"
4360
+ >${snippet(pin.message, this.t("pinned.attachment"))}</span
4361
+ >
4362
+ <span class="by">${this.t("pinned.by", { name: pin.pinnedByName })}</span>
3825
4363
  </button>
3826
4364
  <button
3827
4365
  type="button"
3828
4366
  class="unpin"
3829
- aria-label=${`Unpin message from ${pin.message.authorName}`}
3830
- title="Unpin"
4367
+ aria-label=${this.t("pinned.unpinFrom", { name: pin.message.authorName })}
4368
+ title=${this.t("chat.unpin")}
3831
4369
  @click=${() => this.emit("teams-pinned-unpin", pin.message.id)}
3832
4370
  >
3833
- Unpin
4371
+ ${this.t("chat.unpin")}
3834
4372
  </button>
3835
4373
  </li>`
3836
4374
  )}
@@ -3859,14 +4397,16 @@ function watchActivity(target, onActivity, throttleMs = 1e3, now = Date.now) {
3859
4397
  for (const name of ACTIVITY_EVENTS) target.removeEventListener(name, handler);
3860
4398
  };
3861
4399
  }
3862
- function deliverNotification(item, settings, env, actions) {
4400
+
4401
+ // src/teams/app/notify-delivery.ts
4402
+ function deliverNotification(item, settings, env, actions, i18n = teamsI18n()) {
3863
4403
  let result = "none";
3864
4404
  if (!env.hidden()) {
3865
4405
  actions.toast(item);
3866
4406
  result = "toast";
3867
4407
  } else if (env.permission() === "granted") {
3868
4408
  env.show(
3869
- describeActivity(item),
4409
+ describeActivityIn(item, i18n),
3870
4410
  { body: item.preview, tag: item.id },
3871
4411
  () => actions.open(item)
3872
4412
  );
@@ -3919,17 +4459,17 @@ var add_tab_dialog_default = ":host {\n font: var(--office-font-size) var(--offi
3919
4459
 
3920
4460
  // src/teams/app/add-tab-dialog.ts
3921
4461
  var APPS = [
3922
- { id: "docx", label: "Word", kind: "docx", letter: "W" },
3923
- { id: "xlsx", label: "Excel", kind: "xlsx", letter: "X" },
3924
- { id: "pptx", label: "PowerPoint", kind: "pptx", letter: "P" },
3925
- { id: "vsdx", label: "Visio", kind: "vsdx", letter: "V" },
3926
- { id: "markdown", label: "Markdown", kind: "markdown", letter: "M" },
3927
- { id: "site", label: "Static site", kind: "website", letter: "H" },
3928
- { id: "text", label: "Text", kind: "text", letter: "T" },
3929
- { id: "website", label: "Website", kind: null, letter: "\u2197" }
4462
+ { id: "docx", label: "app.word", kind: "docx", letter: "W" },
4463
+ { id: "xlsx", label: "app.excel", kind: "xlsx", letter: "X" },
4464
+ { id: "pptx", label: "app.powerpoint", kind: "pptx", letter: "P" },
4465
+ { id: "vsdx", label: "app.visio", kind: "vsdx", letter: "V" },
4466
+ { id: "markdown", label: "app.markdown", kind: "markdown", letter: "M" },
4467
+ { id: "site", label: "app.site", kind: "website", letter: "H" },
4468
+ { id: "text", label: "app.text", kind: "text", letter: "T" },
4469
+ { id: "website", label: "app.website", kind: null, letter: "\u2197" }
3930
4470
  ];
3931
4471
  var fileKey2 = (file) => JSON.stringify([file.messageId, file.name, file.url]);
3932
- var TeamsAddTabDialog = class extends LitElement {
4472
+ var TeamsAddTabDialog = class extends TeamsLitElement {
3933
4473
  static styles = unsafeCSS(add_tab_dialog_default);
3934
4474
  static properties = {
3935
4475
  open: { type: Boolean },
@@ -4002,8 +4542,7 @@ var TeamsAddTabDialog = class extends LitElement {
4002
4542
  return app?.kind ? this.eligible(app.kind).find((file) => fileKey2(file) === this.selectedKey) : void 0;
4003
4543
  }
4004
4544
  availableLabel(kind) {
4005
- const count = this.eligible(kind).length;
4006
- return `${count} shared ${count === 1 ? "file" : "files"}`;
4545
+ return this.tn("addTab.sharedFiles", this.eligible(kind).length);
4007
4546
  }
4008
4547
  choose(app) {
4009
4548
  this.app = app;
@@ -4017,55 +4556,59 @@ var TeamsAddTabDialog = class extends LitElement {
4017
4556
  event.preventDefault();
4018
4557
  this.error = "";
4019
4558
  if (!this.current || !this.owner || !this.add) {
4020
- this.error = "The channel changed. Close this dialog and try again.";
4559
+ this.error = this.t("addTab.channelChanged");
4021
4560
  return;
4022
4561
  }
4023
4562
  if (this.app === "website" && !contentUrl(this.url, this.ownerDocument.baseURI)) {
4024
- this.error = "Enter a valid http or https website URL.";
4563
+ this.error = this.t("addTab.invalidUrl");
4025
4564
  return;
4026
4565
  }
4027
4566
  const content = this.app === "website" ? { type: "website", url: this.url } : this.selected ? { type: "file", attachment: this.selected } : null;
4028
4567
  if (!content) {
4029
- this.error = "Choose a file shared in this channel.";
4568
+ this.error = this.t("addTab.chooseShared");
4030
4569
  return;
4031
4570
  }
4032
4571
  const result = this.add(this.name, content, this.context, this.owner, this.postToChannel);
4033
- if (!result) this.error = "Enter a tab name and valid content.";
4572
+ if (!result) this.error = this.t("addTab.invalid");
4034
4573
  }
4035
4574
  render() {
4036
4575
  const app = APPS.find((entry) => entry.id === this.app);
4037
4576
  const files = app?.kind ? filterFiles(this.eligible(app.kind), this.query) : [];
4038
- return html`<dialog aria-label="Add a tab" @cancel=${this.close} @close=${this.close}>
4577
+ return html`<dialog
4578
+ aria-label=${this.t("addTab.title")}
4579
+ @cancel=${this.close}
4580
+ @close=${this.close}
4581
+ >
4039
4582
  <header>
4040
- <h1>Add a tab</h1>
4041
- <button type="button" aria-label="Close add tab" @click=${this.close}>×</button>
4583
+ <h1>${this.t("addTab.title")}</h1>
4584
+ <button type="button" aria-label=${this.t("addTab.close")} @click=${this.close}>×</button>
4042
4585
  </header>
4043
4586
  ${!app ? html`<section>
4044
- <h2>Choose an app</h2>
4045
- <p>Pin content to # ${this.contextName} so everyone in the channel can find it.</p>
4587
+ <h2>${this.t("addTab.chooseApp")}</h2>
4588
+ <p>${this.t("addTab.pinTo", { channel: this.contextName })}</p>
4046
4589
  <div class="apps">
4047
4590
  ${APPS.map(
4048
4591
  (entry, index) => html`<button
4049
4592
  type="button"
4050
4593
  class="app"
4051
- aria-label=${`Choose ${entry.label}`}
4594
+ aria-label=${this.t("addTab.choose", { app: this.t(entry.label) })}
4052
4595
  ?data-first-app=${index === 0}
4053
4596
  ?autofocus=${index === 0}
4054
4597
  @click=${() => this.choose(entry.id)}
4055
4598
  >
4056
4599
  <span class="badge" data-kind=${entry.kind ?? "other"}>${entry.letter}</span
4057
- ><strong>${entry.label}</strong>
4600
+ ><strong>${this.t(entry.label)}</strong>
4058
4601
  <small
4059
- >${entry.kind ? this.availableLabel(entry.kind) : "Add a web address"}</small
4602
+ >${entry.kind ? this.availableLabel(entry.kind) : this.t("addTab.addAddress")}</small
4060
4603
  >
4061
4604
  </button>`
4062
4605
  )}
4063
4606
  </div>
4064
- <p class="hint">File tabs use files already shared in this channel.</p>
4607
+ <p class="hint">${this.t("addTab.fileHint")}</p>
4065
4608
  </section>` : html`<form @submit=${this.submit}>
4066
4609
  <div class="app-heading">
4067
4610
  <span class="badge" data-kind=${app.kind ?? "other"}>${app.letter}</span>
4068
- <h2>${app.label}</h2>
4611
+ <h2>${this.t(app.label)}</h2>
4069
4612
  <button
4070
4613
  type="button"
4071
4614
  @click=${() => {
@@ -4073,11 +4616,11 @@ var TeamsAddTabDialog = class extends LitElement {
4073
4616
  this.error = "";
4074
4617
  }}
4075
4618
  >
4076
- Back
4619
+ ${this.t("common.back")}
4077
4620
  </button>
4078
4621
  </div>
4079
4622
  <label
4080
- >Tab name<input
4623
+ >${this.t("addTab.name")}<input
4081
4624
  data-configure
4082
4625
  name="name"
4083
4626
  maxlength="80"
@@ -4089,7 +4632,7 @@ var TeamsAddTabDialog = class extends LitElement {
4089
4632
  }}
4090
4633
  /></label>
4091
4634
  ${!app.kind ? html`<label
4092
- >Tab website URL<input
4635
+ >${this.t("addTab.websiteUrl")}<input
4093
4636
  type="url"
4094
4637
  required
4095
4638
  maxlength="2048"
@@ -4100,17 +4643,14 @@ var TeamsAddTabDialog = class extends LitElement {
4100
4643
  this.error = "";
4101
4644
  }}
4102
4645
  /></label>
4103
- <p class="hint">
4104
- Some websites block embedding. Their link remains available from the
4105
- preview.
4106
- </p>` : html`<label
4107
- >Search shared files<input
4646
+ <p class="hint">${this.t("addTab.embedHint")}</p>` : html`<label
4647
+ >${this.t("addTab.searchFiles")}<input
4108
4648
  type="search"
4109
4649
  .value=${this.query}
4110
4650
  @input=${(event) => this.query = event.target.value}
4111
4651
  /></label>
4112
4652
  <fieldset class="files">
4113
- <legend>Choose a file</legend>
4653
+ <legend>${this.t("addTab.chooseFile")}</legend>
4114
4654
  ${files.map(
4115
4655
  (file) => html`<label class="file"
4116
4656
  ><input
@@ -4125,30 +4665,32 @@ var TeamsAddTabDialog = class extends LitElement {
4125
4665
  }}
4126
4666
  /><span
4127
4667
  ><strong>${file.name}</strong
4128
- ><small>Shared by ${file.author}</small></span
4668
+ ><small
4669
+ >${this.t("addTab.sharedBy", { author: file.author })}</small
4670
+ ></span
4129
4671
  ></label
4130
4672
  >`
4131
4673
  )}
4132
- ${files.length ? nothing : html`<p class="hint">${this.query ? "No matching files." : `No ${app.label} files are shared in this channel. Upload a file from Shared first.`}</p>`}
4674
+ ${files.length ? nothing : html`<p class="hint">${this.query ? this.t("files.noMatch") : this.t("addTab.noFiles", { app: this.t(app.label) })}</p>`}
4133
4675
  </fieldset>
4134
- ${this.selected ? html`<p class="hint">Selected: ${this.selected.name}</p>` : this.selectedKey ? html`<p role="alert">This file is no longer shared in this channel.</p>` : nothing}`}
4135
- ${!this.current ? html`<p role="alert">The channel changed. Close this dialog and try again.</p>` : nothing}
4676
+ ${this.selected ? html`<p class="hint">${this.t("addTab.selected", { name: this.selected.name })}</p>` : this.selectedKey ? html`<p role="alert">${this.t("addTab.noLongerShared")}</p>` : nothing}`}
4677
+ ${!this.current ? html`<p role="alert">${this.t("addTab.channelChanged")}</p>` : nothing}
4136
4678
  ${this.error ? html`<p role="alert">${this.error}</p>` : nothing}
4137
4679
  <label class="post-choice"
4138
4680
  ><input
4139
4681
  type="checkbox"
4140
4682
  .checked=${this.postToChannel}
4141
4683
  @change=${(event) => this.postToChannel = event.target.checked}
4142
- />Post to the channel about this tab</label
4684
+ />${this.t("addTab.postToChannel")}</label
4143
4685
  >
4144
4686
  <footer>
4145
- <button type="button" @click=${this.close}>Cancel</button
4687
+ <button type="button" @click=${this.close}>${this.t("common.cancel")}</button
4146
4688
  ><button
4147
4689
  type="submit"
4148
4690
  class="primary"
4149
4691
  ?disabled=${!this.current || !this.name.trim() || (app.kind ? !this.selected : !this.url.trim())}
4150
4692
  >
4151
- Save
4693
+ ${this.t("common.save")}
4152
4694
  </button>
4153
4695
  </footer>
4154
4696
  </form>`}
@@ -4164,7 +4706,7 @@ function defineTeamsAddTabDialog() {
4164
4706
  var create_channel_dialog_default = ":host {\n font: var(--office-font-size) var(--office-font);\n color: var(--office-foreground);\n}\ndialog {\n box-sizing: border-box;\n width: min(40em, 94vw);\n max-height: 90dvh;\n padding: 0;\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-lg);\n background: var(--office-popover);\n color: inherit;\n box-shadow: var(--office-shadow-teams);\n}\ndialog::backdrop {\n background: var(--office-overlay);\n}\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--office-space-3);\n padding: var(--office-space-4) var(--office-space-5);\n border-block-end: var(--office-border-width) solid var(--office-teams-divider);\n}\nh2 {\n margin: 0;\n font-size: var(--office-font-size-lg);\n}\nform {\n display: grid;\n gap: var(--office-space-3);\n padding: var(--office-space-5);\n}\nlabel {\n display: grid;\n gap: var(--office-space-2);\n font-weight: var(--office-font-weight-bold);\n}\ninput,\ntextarea {\n min-width: 0;\n width: 100%;\n box-sizing: border-box;\n padding: var(--office-space-3);\n font: inherit;\n font-weight: normal;\n color: inherit;\n background: var(--office-field-background);\n border: var(--office-border-width) solid var(--office-field-border);\n border-radius: var(--office-radius-md);\n}\ntextarea {\n min-height: 6em;\n resize: vertical;\n}\np,\nsmall {\n margin: 0;\n color: var(--office-teams-text-subtle);\n line-height: 1.5;\n}\n.issue {\n color: var(--office-danger);\n}\nfooter {\n display: flex;\n justify-content: flex-end;\n gap: var(--office-space-2);\n}\nbutton {\n padding: var(--office-space-2) var(--office-space-4);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n background: var(--office-background);\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\nbutton:hover {\n background: var(--office-selected);\n}\nbutton.primary {\n background: var(--office-teams-brand);\n color: var(--office-teams-brand-foreground);\n border-color: transparent;\n}\nbutton.primary:hover {\n background: var(--office-teams-brand-hover);\n}\n:focus-visible {\n outline: var(--office-focus-width) solid var(--office-ring);\n outline-offset: var(--office-focus-offset);\n}\n";
4165
4707
 
4166
4708
  // src/teams/app/create-channel-dialog.ts
4167
- var TeamsCreateChannelDialog = class extends LitElement {
4709
+ var TeamsCreateChannelDialog = class extends TeamsLitElement {
4168
4710
  static styles = unsafeCSS(create_channel_dialog_default);
4169
4711
  static properties = {
4170
4712
  open: { type: Boolean, reflect: true },
@@ -4202,12 +4744,11 @@ var TeamsCreateChannelDialog = class extends LitElement {
4202
4744
  event.preventDefault();
4203
4745
  const name = sanitizeChannelName(this.name);
4204
4746
  if (!name) {
4205
- this.issue = "Enter a channel name.";
4747
+ this.issue = this.t("channel.enterName");
4206
4748
  this.renderRoot.querySelector("input")?.focus();
4207
4749
  return;
4208
4750
  }
4209
- if (!this.create?.(name, this.description))
4210
- this.issue = "The channel was not created. Your details are kept here.";
4751
+ if (!this.create?.(name, this.description)) this.issue = this.t("channel.notCreated");
4211
4752
  }
4212
4753
  render() {
4213
4754
  return html`<dialog
@@ -4218,12 +4759,14 @@ var TeamsCreateChannelDialog = class extends LitElement {
4218
4759
  }}
4219
4760
  >
4220
4761
  <header>
4221
- <h2 id="title">Create a channel</h2>
4222
- <button type="button" aria-label="Close create channel" @click=${this.close}>×</button>
4762
+ <h2 id="title">${this.t("channel.create")}</h2>
4763
+ <button type="button" aria-label=${this.t("channel.closeCreate")} @click=${this.close}>
4764
+ ×
4765
+ </button>
4223
4766
  </header>
4224
4767
  <form @submit=${this.submit}>
4225
4768
  <label
4226
- >Channel name<input
4769
+ >${this.t("channel.name")}<input
4227
4770
  autofocus
4228
4771
  maxlength=${MAX_CHANNEL_NAME_CHARS}
4229
4772
  .value=${this.name}
@@ -4231,29 +4774,28 @@ var TeamsCreateChannelDialog = class extends LitElement {
4231
4774
  this.name = event.target.value;
4232
4775
  this.issue = "";
4233
4776
  }}
4234
- placeholder="Give your channel a name"
4777
+ placeholder=${this.t("channel.namePlaceholder")}
4235
4778
  aria-describedby="channel-name-help"
4236
4779
  /></label>
4237
- <small id="channel-name-help">${MAX_CHANNEL_NAME_CHARS} characters maximum.</small>
4780
+ <small id="channel-name-help"
4781
+ >${this.t("channel.maxChars", { count: MAX_CHANNEL_NAME_CHARS })}</small
4782
+ >
4238
4783
  <label
4239
- >Description (optional)<textarea
4784
+ >${this.t("channel.description")}<textarea
4240
4785
  maxlength=${MAX_CHANNEL_TOPIC_CHARS}
4241
4786
  .value=${this.description}
4242
4787
  @input=${(event) => this.description = event.target.value}
4243
- placeholder="Help others understand what this channel is for"
4788
+ placeholder=${this.t("channel.descriptionPlaceholder")}
4244
4789
  ></textarea>
4245
4790
  </label>
4246
4791
  <div>
4247
- <strong>Standard channel</strong>
4248
- <p>
4249
- Available to everyone in this workspace. Private and shared channels are not available
4250
- yet.
4251
- </p>
4792
+ <strong>${this.t("channel.standard")}</strong>
4793
+ <p>${this.t("channel.standardNote")}</p>
4252
4794
  </div>
4253
4795
  ${this.issue ? html`<p class="issue" role="alert">${this.issue}</p>` : nothing}
4254
4796
  <footer>
4255
- <button type="button" @click=${this.close}>Cancel</button
4256
- ><button type="submit" class="primary">Create</button>
4797
+ <button type="button" @click=${this.close}>${this.t("common.cancel")}</button
4798
+ ><button type="submit" class="primary">${this.t("common.create")}</button>
4257
4799
  </footer>
4258
4800
  </form>
4259
4801
  </dialog>`;
@@ -4288,7 +4830,7 @@ var TeamsDialog = class extends TeamsElement {
4288
4830
  this.fire("teams-dialog-close", {});
4289
4831
  }
4290
4832
  /** The dialog frame with a title and a close button around `body`. */
4291
- frame(title, body, closeLabel = `Close ${title.toLowerCase()}`) {
4833
+ frame(title, body, closeLabel = this.t("common.close")) {
4292
4834
  return html`<dialog
4293
4835
  aria-labelledby="title"
4294
4836
  @cancel=${(event) => {
@@ -4337,21 +4879,21 @@ var TeamsNewChatDialog = class extends TeamsDialog {
4337
4879
  submit(event) {
4338
4880
  event.preventDefault();
4339
4881
  if (this.selected.length === 0) {
4340
- this.issue = "Pick at least one person.";
4882
+ this.issue = this.t("newChat.pickOne");
4341
4883
  return;
4342
4884
  }
4343
4885
  if (!this.create?.(this.selected, this.selected.length > 1 ? this.name : ""))
4344
- this.issue = "The chat could not be started.";
4886
+ this.issue = this.t("newChat.failed");
4345
4887
  }
4346
4888
  render() {
4347
4889
  const group = this.selected.length > 1;
4348
4890
  return this.frame(
4349
- "New chat",
4891
+ this.t("chat.new"),
4350
4892
  html`<form @submit=${this.submit}>
4351
4893
  <div class="field">
4352
- To
4894
+ ${this.t("newChat.to")}
4353
4895
  <office-ui-people-picker
4354
- label="People to chat with"
4896
+ label=${this.t("newChat.peopleLabel")}
4355
4897
  .contacts=${this.contacts}
4356
4898
  .selected=${this.selected}
4357
4899
  @office-people-change=${(e) => {
@@ -4361,23 +4903,22 @@ var TeamsNewChatDialog = class extends TeamsDialog {
4361
4903
  ></office-ui-people-picker>
4362
4904
  </div>
4363
4905
  ${group ? html`<label
4364
- >Group name (optional)<input
4906
+ >${this.t("chatPeople.groupNameOptional")}<input
4365
4907
  maxlength=${MAX_CHANNEL_NAME_CHARS}
4366
4908
  .value=${this.name}
4367
- placeholder="Name this group chat"
4909
+ placeholder=${this.t("chatPeople.groupPlaceholder")}
4368
4910
  @input=${(e) => this.name = e.target.value}
4369
4911
  /></label>` : nothing}
4370
- <small
4371
- >${group ? "Everyone you pick joins one group chat. Only its members can read it." : "Pick one person for a 1:1 chat, or several for a group chat."}</small
4372
- >
4912
+ <small>${group ? this.t("newChat.hintGroup") : this.t("newChat.hintOne")}</small>
4373
4913
  ${this.issue ? html`<p class="issue" role="alert">${this.issue}</p>` : nothing}
4374
4914
  <footer>
4375
- <button type="button" @click=${this.requestClose}>Cancel</button>
4915
+ <button type="button" @click=${this.requestClose}>${this.t("common.cancel")}</button>
4376
4916
  <button type="submit" class="primary" ?disabled=${this.selected.length === 0}>
4377
- ${group ? "Start group chat" : "Start chat"}
4917
+ ${this.t(group ? "newChat.startGroup" : "newChat.start")}
4378
4918
  </button>
4379
4919
  </footer>
4380
- </form>`
4920
+ </form>`,
4921
+ this.t("dialog.closeNewChat")
4381
4922
  );
4382
4923
  }
4383
4924
  };
@@ -4414,10 +4955,10 @@ var TeamsChatPeopleDialog = class extends TeamsDialog {
4414
4955
  }
4415
4956
  render() {
4416
4957
  const chat = this.chat;
4417
- if (!chat) return this.frame("People", html``);
4958
+ if (!chat) return this.frame(this.t("chat.people"), html``, this.t("dialog.closePeople"));
4418
4959
  const group = chat.kind === "group";
4419
4960
  return this.frame(
4420
- "People",
4961
+ this.t("chat.people"),
4421
4962
  html`<div class="body">
4422
4963
  ${group ? html`<form
4423
4964
  class="rename"
@@ -4427,15 +4968,15 @@ var TeamsChatPeopleDialog = class extends TeamsDialog {
4427
4968
  }}
4428
4969
  >
4429
4970
  <label
4430
- >Group name<input
4971
+ >${this.t("chatPeople.groupName")}<input
4431
4972
  maxlength=${MAX_CHANNEL_NAME_CHARS}
4432
4973
  .value=${this.name}
4433
- placeholder="Name this group chat"
4974
+ placeholder=${this.t("chatPeople.groupPlaceholder")}
4434
4975
  @input=${(e) => this.name = e.target.value}
4435
4976
  /></label>
4436
- <button type="submit">Rename</button>
4977
+ <button type="submit">${this.t("chatPeople.rename")}</button>
4437
4978
  </form>` : nothing}
4438
- <h3>In this chat (${chat.members.length})</h3>
4979
+ <h3>${this.t("chatPeople.inChat", { count: chat.members.length })}</h3>
4439
4980
  <ul class="members">
4440
4981
  ${chat.members.map(
4441
4982
  (m) => html`<li>
@@ -4445,20 +4986,22 @@ var TeamsChatPeopleDialog = class extends TeamsDialog {
4445
4986
  seed=${m.id}
4446
4987
  presence=${m.availability}
4447
4988
  ></office-ui-avatar>
4448
- <span class="name">${m.name}${m.self ? " (you)" : ""}</span>
4989
+ <span class="name"
4990
+ >${m.self ? this.t("common.nameYou", { name: m.name }) : m.name}</span
4991
+ >
4449
4992
  ${group && !m.self && chat.members.length > 1 ? html`<button
4450
4993
  type="button"
4451
- aria-label=${`Remove ${m.name}`}
4994
+ aria-label=${this.t("people.remove", { name: m.name })}
4452
4995
  @click=${() => this.fire("teams-chat-remove", { id: chat.id, userId: m.id })}
4453
4996
  >
4454
- Remove
4997
+ ${this.t("chatPeople.remove")}
4455
4998
  </button>` : nothing}
4456
4999
  </li>`
4457
5000
  )}
4458
5001
  </ul>
4459
- ${group ? html`<h3>Add people</h3>
5002
+ ${group ? html`<h3>${this.t("chatPeople.addPeople")}</h3>
4460
5003
  <office-ui-people-picker
4461
- label="People to add"
5004
+ label=${this.t("chatPeople.peopleToAdd")}
4462
5005
  .contacts=${this.contacts}
4463
5006
  .exclude=${chat.members.map((m) => m.id)}
4464
5007
  .selected=${this.adding}
@@ -4474,22 +5017,21 @@ var TeamsChatPeopleDialog = class extends TeamsDialog {
4474
5017
  this.adding = [];
4475
5018
  }}
4476
5019
  >
4477
- Add to chat
5020
+ ${this.t("chatPeople.addToChat")}
4478
5021
  </button>
4479
- </footer>` : html`<p>
4480
- People cannot be added to or removed from a 1:1 chat. Start a group chat instead.
4481
- </p>`}
5022
+ </footer>` : html`<p>${this.t("chatPeople.directNote")}</p>`}
4482
5023
  <footer>
4483
5024
  ${chat.canLeave ? html`<button
4484
5025
  type="button"
4485
5026
  class="danger"
4486
5027
  @click=${() => this.fire("teams-chat-leave", { id: chat.id })}
4487
5028
  >
4488
- Leave chat
5029
+ ${this.t("chatPeople.leave")}
4489
5030
  </button>` : nothing}
4490
- <button type="button" @click=${this.requestClose}>Done</button>
5031
+ <button type="button" @click=${this.requestClose}>${this.t("common.done")}</button>
4491
5032
  </footer>
4492
- </div>`
5033
+ </div>`,
5034
+ this.t("dialog.closePeople")
4493
5035
  );
4494
5036
  }
4495
5037
  };
@@ -4500,10 +5042,61 @@ function defineTeamsChatPeopleDialog(registry = globalThis.customElements) {
4500
5042
 
4501
5043
  // raw-css:/home/runner/work/ooxml/ooxml/src/ui/src/teams/app/meeting-dialog.css?raw
4502
5044
  var meeting_dialog_default = ".times {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--office-space-3);\n}\noffice-ui-people-picker {\n font-weight: normal;\n}\n@media (max-width: 40em) {\n .times {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n";
5045
+ var monthDay = (month, day, i18n) => new Intl.DateTimeFormat(i18n.locale, { month: "long", day: "numeric", timeZone: "UTC" }).format(
5046
+ Date.UTC(2e3, month - 1, day)
5047
+ );
5048
+ var weekdayName = (weekday, i18n) => new Intl.DateTimeFormat(i18n.locale, { weekday: "long", timeZone: "UTC" }).format(
5049
+ Date.UTC(2e3, 0, 2 + weekday % 7)
5050
+ );
5051
+ var ORDINAL_KEYS = {
5052
+ 1: "repeat.monthlyOrdinal.1",
5053
+ 2: "repeat.monthlyOrdinal.2",
5054
+ 3: "repeat.monthlyOrdinal.3",
5055
+ 4: "repeat.monthlyOrdinal.4",
5056
+ [-1]: "repeat.monthlyOrdinal.last"
5057
+ };
5058
+ var monthlyWeekday = (ordinal, weekday, i18n) => i18n.t(ORDINAL_KEYS[ordinal] ?? "repeat.monthlyOrdinal.1", {
5059
+ weekday: weekdayName(weekday, i18n)
5060
+ });
5061
+ var NO_REPEAT = { repeat: "none", monthlyBy: "date", ordinal: 1 };
5062
+ var repeatValue = (r) => r.repeat === "monthly" ? r.monthlyBy === "weekday" ? `monthly-${r.ordinal}` : "monthly-date" : r.repeat;
5063
+ function parseRepeatValue(value) {
5064
+ if (value === "monthly-date") return { repeat: "monthly", monthlyBy: "date", ordinal: 1 };
5065
+ const m = /^monthly-(-?\d)$/u.exec(value);
5066
+ if (m) return { repeat: "monthly", monthlyBy: "weekday", ordinal: Number(m[1]) };
5067
+ if (value === "daily" || value === "weekly" || value === "yearly")
5068
+ return { repeat: value, monthlyBy: "date", ordinal: 1 };
5069
+ return NO_REPEAT;
5070
+ }
5071
+ function repeatOptions(start, zone, i18n = teamsI18n()) {
5072
+ const { month, day, weekday, ordinals } = repeatChoices(start, zone);
5073
+ return [
5074
+ { value: "none", label: i18n.t("repeat.none") },
5075
+ { value: "daily", label: i18n.t("repeat.daily") },
5076
+ { value: "weekly", label: i18n.t("repeat.weekly") },
5077
+ { value: "monthly-date", label: i18n.t("repeat.monthlyDay", { day }) },
5078
+ ...ordinals.map((o) => ({
5079
+ value: `monthly-${o}`,
5080
+ label: monthlyWeekday(o, weekday, i18n)
5081
+ })),
5082
+ { value: "yearly", label: i18n.t("repeat.yearlyOn", { date: monthDay(month, day, i18n) }) }
5083
+ ];
5084
+ }
5085
+ var inSentence = (label, i18n) => label.charAt(0).toLocaleLowerCase(i18n.locale) + label.slice(1);
5086
+ function repeatText(m, i18n = teamsI18n()) {
5087
+ if (m.repeat === "none") return "";
5088
+ const { month, day, weekday } = repeatChoices(m.start, m.zone);
5089
+ const rule = m.repeat === "daily" ? i18n.t("repeat.daily") : m.repeat === "weekly" ? i18n.t("repeat.weekly") : m.repeat === "yearly" ? i18n.t("repeat.yearlyOn", { date: monthDay(month, day, i18n) }) : m.monthlyBy === "weekday" && m.ordinal !== void 0 ? monthlyWeekday(m.ordinal, weekday, i18n) : i18n.t("repeat.monthlyDay", { day });
5090
+ return i18n.t("repeat.sentence", { rule: inSentence(rule, i18n) });
5091
+ }
4503
5092
 
4504
5093
  // src/teams/app/meeting-dialog.ts
4505
5094
  var SLOT = 30 * MINUTE_MS;
4506
- var NOT_SAVED = "The meeting was not saved. Your details are kept here.";
5095
+ var repeatOption = (o, current) => html`<option value=${o.value} ?selected=${o.value === current}>${o.label}</option>`;
5096
+ function repeatFields(r) {
5097
+ if (r.repeat !== "monthly") return { repeat: r.repeat };
5098
+ return r.monthlyBy === "weekday" ? { repeat: "monthly", monthlyBy: "weekday", ordinal: r.ordinal } : { repeat: "monthly", monthlyBy: "date" };
5099
+ }
4507
5100
  function toLocalInput(ms) {
4508
5101
  const d = new Date(ms);
4509
5102
  const p = (n) => String(n).padStart(2, "0");
@@ -4523,7 +5116,7 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4523
5116
  description: { state: true },
4524
5117
  startText: { state: true },
4525
5118
  endText: { state: true },
4526
- repeat: { state: true },
5119
+ repeatChoice: { state: true },
4527
5120
  endMode: { state: true },
4528
5121
  untilText: { state: true },
4529
5122
  countText: { state: true },
@@ -4543,7 +5136,7 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4543
5136
  this.description = "";
4544
5137
  this.startText = "";
4545
5138
  this.endText = "";
4546
- this.repeat = "none";
5139
+ this.repeatChoice = NO_REPEAT;
4547
5140
  this.endMode = "never";
4548
5141
  this.untilText = "";
4549
5142
  this.countText = "10";
@@ -4560,7 +5153,7 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4560
5153
  this.description = one?.description ?? m?.description ?? "";
4561
5154
  this.startText = toLocalInput(begin);
4562
5155
  this.endText = toLocalInput(one?.end ?? m?.end ?? begin + SLOT);
4563
- this.repeat = m?.repeat ?? "none";
5156
+ this.repeatChoice = m ? { repeat: m.repeat, monthlyBy: m.monthlyBy ?? "date", ordinal: m.ordinal ?? 1 } : NO_REPEAT;
4564
5157
  this.endMode = m?.count !== void 0 ? "count" : m?.until !== void 0 ? "date" : "never";
4565
5158
  this.countText = String(m?.count ?? 10);
4566
5159
  this.untilText = toLocalInput(m?.until ?? begin + 28 * 864e5).slice(0, 10);
@@ -4577,6 +5170,20 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4577
5170
  this.endText = toLocalInput(end + (after - before));
4578
5171
  this.issue = "";
4579
5172
  }
5173
+ /** The zone the series is defined in: the organizer's for an existing meeting, else this device's. */
5174
+ zone() {
5175
+ return this.meeting?.zone ?? localZone();
5176
+ }
5177
+ /**
5178
+ * The repeat to store. A weekday ordinal that no longer fits the start date (the start moved)
5179
+ * becomes the first one that does.
5180
+ */
5181
+ repeatToStore(start) {
5182
+ const choice = this.repeatChoice;
5183
+ if (choice.repeat !== "monthly" || choice.monthlyBy !== "weekday") return choice;
5184
+ const fits = repeatOptions(start, this.zone(), this.i18n).map((o) => o.value);
5185
+ return fits.includes(repeatValue(choice)) ? choice : parseRepeatValue(fits.find((v) => v.startsWith("monthly-") && v !== "monthly-date") ?? "");
5186
+ }
4580
5187
  validCount() {
4581
5188
  const n = Number(this.countText);
4582
5189
  return Number.isInteger(n) && n >= 1 && n <= MAX_RECURRENCE_COUNT;
@@ -4590,50 +5197,54 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4590
5197
  }
4591
5198
  /** The end of the repeat to store: a count, the end of a day, or none (null clears an old end). */
4592
5199
  recurrenceEnd() {
4593
- if (this.repeat === "none" || this.endMode === "never") return { count: null, until: null };
5200
+ if (this.repeatChoice.repeat === "none" || this.endMode === "never")
5201
+ return { count: null, until: null };
4594
5202
  return this.endMode === "count" ? { count: Number(this.countText), until: null } : { count: null, until: this.untilTime() };
4595
5203
  }
4596
5204
  submit(event) {
4597
5205
  event.preventDefault();
4598
5206
  const start = new Date(this.startText).getTime();
4599
5207
  const end = new Date(this.endText).getTime();
4600
- if (!this.meetingTitle.trim()) this.issue = "Add a title.";
4601
- else if (!Number.isFinite(start) || !Number.isFinite(end)) this.issue = "Pick a start and end.";
4602
- else if (end <= start) this.issue = "The meeting must end after it starts.";
4603
- else if (end - start > 24 * 60 * MINUTE_MS) this.issue = "A meeting can last at most 24 hours.";
5208
+ if (!this.meetingTitle.trim()) this.issue = this.t("meeting.addTitle");
5209
+ else if (!Number.isFinite(start) || !Number.isFinite(end))
5210
+ this.issue = this.t("meeting.pickStartEnd");
5211
+ else if (end <= start) this.issue = this.t("meeting.endAfterStart");
5212
+ else if (end - start > 24 * 60 * MINUTE_MS) this.issue = this.t("meeting.maxDuration");
4604
5213
  else if (this.occurrence) {
4605
5214
  const one = { title: this.meetingTitle, description: this.description, start, end };
4606
- if (!this.save?.(one, this.meeting?.id, this.occurrence)) this.issue = NOT_SAVED;
4607
- } else if (this.repeat !== "none" && this.endMode === "count" && !this.validCount())
4608
- this.issue = `Enter a number of events from 1 to ${MAX_RECURRENCE_COUNT}.`;
4609
- else if (this.repeat !== "none" && this.endMode === "date" && !this.validUntil(start))
4610
- this.issue = "The series must end on or after its first event.";
5215
+ if (!this.save?.(one, this.meeting?.id, this.occurrence))
5216
+ this.issue = this.t("meeting.notSaved");
5217
+ } else if (this.repeatChoice.repeat !== "none" && this.endMode === "count" && !this.validCount())
5218
+ this.issue = this.t("meeting.countRange", { max: MAX_RECURRENCE_COUNT });
5219
+ else if (this.repeatChoice.repeat !== "none" && this.endMode === "date" && !this.validUntil(start))
5220
+ this.issue = this.t("meeting.untilBeforeStart");
4611
5221
  else if (!this.save?.(
4612
5222
  {
4613
5223
  title: this.meetingTitle,
4614
5224
  description: this.description,
4615
5225
  start,
4616
5226
  end,
4617
- repeat: this.repeat,
5227
+ ...repeatFields(this.repeatToStore(start)),
4618
5228
  ...this.recurrenceEnd(),
4619
5229
  attendeeIds: this.channelId ? [] : this.attendees,
4620
5230
  ...this.channelId ? { channelId: this.channelId } : {}
4621
5231
  },
4622
5232
  this.meeting?.id
4623
5233
  ))
4624
- this.issue = NOT_SAVED;
5234
+ this.issue = this.t("meeting.notSaved");
4625
5235
  }
4626
5236
  render() {
4627
5237
  const editing = this.meeting !== null;
4628
5238
  const single = this.occurrence !== 0;
5239
+ const current = repeatValue(this.repeatToStore(new Date(this.startText).getTime()));
4629
5240
  return this.frame(
4630
- single ? "Edit this event" : editing ? "Edit meeting" : "New meeting",
5241
+ this.t(single ? "meeting.editEvent" : editing ? "meeting.edit" : "calendar.newMeeting"),
4631
5242
  html`<form @submit=${this.submit}>
4632
5243
  <label
4633
- >Title<input
5244
+ >${this.t("meeting.title")}<input
4634
5245
  maxlength=${MAX_MEETING_TITLE}
4635
5246
  .value=${this.meetingTitle}
4636
- placeholder="Add a title"
5247
+ placeholder=${this.t("meeting.titlePlaceholder")}
4637
5248
  @input=${(e) => {
4638
5249
  this.meetingTitle = e.target.value;
4639
5250
  this.issue = "";
@@ -4641,13 +5252,13 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4641
5252
  /></label>
4642
5253
  <div class="times">
4643
5254
  <label
4644
- >Start<input
5255
+ >${this.t("meeting.start")}<input
4645
5256
  type="datetime-local"
4646
5257
  .value=${this.startText}
4647
5258
  @change=${(e) => this.moveStart(e.target.value)}
4648
5259
  /></label>
4649
5260
  <label
4650
- >End<input
5261
+ >${this.t("meeting.end")}<input
4651
5262
  type="datetime-local"
4652
5263
  .value=${this.endText}
4653
5264
  @change=${(e) => {
@@ -4656,29 +5267,30 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4656
5267
  }}
4657
5268
  /></label>
4658
5269
  </div>
4659
- ${single ? html`<small>Only this event changes. The rest of the series stays as it is.</small>` : html`
5270
+ ${single ? html`<small>${this.t("meeting.onlyThisEvent")}</small>` : html`
4660
5271
  <label
4661
- >Repeat<select
4662
- .value=${this.repeat}
4663
- @change=${(e) => this.repeat = e.target.value}
5272
+ >${this.t("meeting.repeat")}<select
5273
+ @change=${(e) => this.repeatChoice = parseRepeatValue(e.target.value)}
4664
5274
  >
4665
- <option value="none">Does not repeat</option>
4666
- <option value="daily">Every day</option>
4667
- <option value="weekly">Every week</option>
5275
+ ${repeatOptions(
5276
+ new Date(this.startText).getTime() || Date.now(),
5277
+ this.zone(),
5278
+ this.i18n
5279
+ ).map((o) => repeatOption(o, current))}
4668
5280
  </select></label
4669
5281
  >
4670
- ${this.repeat === "none" ? nothing : html`<label
4671
- >Ends<select
5282
+ ${this.repeatChoice.repeat === "none" ? nothing : html`<label
5283
+ >${this.t("meeting.ends")}<select
4672
5284
  .value=${this.endMode}
4673
5285
  @change=${(e) => this.endMode = e.target.value}
4674
5286
  >
4675
- <option value="never">Never</option>
4676
- <option value="date">On a date</option>
4677
- <option value="count">After a number of events</option>
5287
+ <option value="never">${this.t("meeting.endsNever")}</option>
5288
+ <option value="date">${this.t("meeting.endsDate")}</option>
5289
+ <option value="count">${this.t("meeting.endsCount")}</option>
4678
5290
  </select></label
4679
5291
  >
4680
5292
  ${this.endMode === "date" ? html`<label
4681
- >End date<input
5293
+ >${this.t("meeting.endDate")}<input
4682
5294
  type="date"
4683
5295
  .value=${this.untilText}
4684
5296
  @change=${(e) => {
@@ -4687,7 +5299,7 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4687
5299
  }}
4688
5300
  /></label>` : nothing}
4689
5301
  ${this.endMode === "count" ? html`<label
4690
- >Number of events<input
5302
+ >${this.t("meeting.numberOfEvents")}<input
4691
5303
  type="number"
4692
5304
  min="1"
4693
5305
  max=${MAX_RECURRENCE_COUNT}
@@ -4698,22 +5310,19 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4698
5310
  }}
4699
5311
  /></label>` : nothing}`}
4700
5312
  <label
4701
- >Channel (optional)<select
5313
+ >${this.t("meeting.channelOptional")}<select
4702
5314
  ?disabled=${editing}
4703
5315
  .value=${this.channelId}
4704
5316
  @change=${(e) => this.channelId = e.target.value}
4705
5317
  >
4706
- <option value="">No channel: invite people</option>
5318
+ <option value="">${this.t("meeting.noChannel")}</option>
4707
5319
  ${this.channels.map((c) => html`<option value=${c.id}># ${c.name}</option>`)}
4708
5320
  </select></label
4709
5321
  >
4710
- ${this.channelId ? html`<small
4711
- >A channel meeting posts a meeting card in the channel and shows in
4712
- everyone's calendar.</small
4713
- >` : html`<div class="field">
4714
- Attendees
5322
+ ${this.channelId ? html`<small>${this.t("meeting.channelNote")}</small>` : html`<div class="field">
5323
+ ${this.t("meeting.attendees")}
4715
5324
  <office-ui-people-picker
4716
- label="Attendees"
5325
+ label=${this.t("meeting.attendees")}
4717
5326
  .contacts=${this.contacts}
4718
5327
  .selected=${this.attendees}
4719
5328
  @office-people-change=${(e) => this.attendees = e.detail.selected}
@@ -4721,22 +5330,25 @@ var TeamsMeetingDialog = class extends TeamsDialog {
4721
5330
  </div>`}
4722
5331
  `}
4723
5332
  <label
4724
- >Details (optional)<textarea
5333
+ >${this.t("meeting.detailsOptional")}<textarea
4725
5334
  maxlength=${MAX_MEETING_DESCRIPTION}
4726
5335
  .value=${this.description}
4727
- placeholder="Add an agenda or notes"
5336
+ placeholder=${this.t("meeting.detailsPlaceholder")}
4728
5337
  @input=${(e) => this.description = e.target.value}
4729
5338
  ></textarea>
4730
5339
  </label>
4731
- <small
4732
- >Times are shown in your time zone. The organizer's zone keeps repeats on schedule.</small
4733
- >
5340
+ <small>${this.t("meeting.zoneNote")}</small>
4734
5341
  ${this.issue ? html`<p class="issue" role="alert">${this.issue}</p>` : nothing}
4735
5342
  <footer>
4736
- <button type="button" @click=${this.requestClose}>Cancel</button>
4737
- <button type="submit" class="primary">${editing ? "Save" : "Send"}</button>
5343
+ <button type="button" @click=${this.requestClose}>${this.t("common.cancel")}</button>
5344
+ <button type="submit" class="primary">
5345
+ ${this.t(editing ? "common.save" : "meeting.send")}
5346
+ </button>
4738
5347
  </footer>
4739
- </form>`
5348
+ </form>`,
5349
+ this.t(
5350
+ single ? "dialog.closeEditEvent" : editing ? "dialog.closeEditMeeting" : "dialog.closeNewMeeting"
5351
+ )
4740
5352
  );
4741
5353
  }
4742
5354
  };
@@ -4749,7 +5361,6 @@ function defineTeamsMeetingDialog(registry = globalThis.customElements) {
4749
5361
  var meeting_details_default = ".when {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n color: var(--office-foreground);\n font-weight: var(--office-font-weight-bold);\n}\nh3 {\n margin: 0 0 var(--office-space-1);\n font-size: var(--office-font-size);\n}\n.people ul {\n margin: 0;\n padding-inline-start: var(--office-space-5);\n}\n.description {\n color: var(--office-foreground);\n white-space: pre-wrap;\n}\n.scope {\n display: grid;\n gap: var(--office-space-3);\n}\n.scope fieldset {\n display: grid;\n gap: var(--office-space-2);\n margin: 0;\n padding: var(--office-space-3);\n border: var(--office-border-width) solid var(--office-teams-divider);\n border-radius: var(--office-radius-md);\n}\n.scope label {\n display: flex;\n align-items: center;\n gap: var(--office-space-2);\n}\nfooter[hidden] {\n display: none;\n}\n";
4750
5362
 
4751
5363
  // src/teams/app/meeting-details.ts
4752
- var REPEAT_TEXT = { none: "", daily: "Repeats every day", weekly: "Repeats every week" };
4753
5364
  var TeamsMeetingDetails = class extends TeamsDialog {
4754
5365
  static styles = dialogStyles(meeting_details_default);
4755
5366
  static properties = {
@@ -4799,17 +5410,17 @@ var TeamsMeetingDetails = class extends TeamsDialog {
4799
5410
  this.pending = null;
4800
5411
  }
4801
5412
  endText(m) {
4802
- const date = (ms) => new Intl.DateTimeFormat([], {
5413
+ const date = (ms) => new Intl.DateTimeFormat(this.i18n.intl, {
4803
5414
  dateStyle: "medium",
4804
5415
  ...this.zone ? { timeZone: this.zone } : {}
4805
5416
  }).format(ms);
4806
- if (m.count !== void 0) return m.count === 1 ? ", 1 time" : `, ${m.count} times`;
4807
- return m.until !== void 0 ? `, until ${date(m.until)}` : "";
5417
+ if (m.count !== void 0) return this.tn("details.times", m.count);
5418
+ return m.until !== void 0 ? this.t("details.until", { date: date(m.until) }) : "";
4808
5419
  }
4809
5420
  scopeChoice(action) {
4810
5421
  const options = [
4811
- ["occurrence", action === "edit" ? "This event" : "This event only"],
4812
- ["series", action === "edit" ? "All events in the series" : "All events in the series"]
5422
+ ["occurrence", this.t(action === "edit" ? "details.thisEvent" : "details.thisEventOnly")],
5423
+ ["series", this.t("details.allEvents")]
4813
5424
  ];
4814
5425
  return html`<form
4815
5426
  class="scope"
@@ -4820,7 +5431,7 @@ var TeamsMeetingDetails = class extends TeamsDialog {
4820
5431
  >
4821
5432
  <fieldset>
4822
5433
  <legend>
4823
- ${action === "edit" ? "Edit recurring meeting" : "Cancel recurring meeting"}
5434
+ ${this.t(action === "edit" ? "details.editRecurring" : "details.cancelRecurring")}
4824
5435
  </legend>
4825
5436
  ${options.map(
4826
5437
  ([value, label]) => html`<label
@@ -4836,16 +5447,19 @@ var TeamsMeetingDetails = class extends TeamsDialog {
4836
5447
  )}
4837
5448
  </fieldset>
4838
5449
  <footer>
4839
- <button type="button" @click=${() => this.pending = null}>Back</button>
5450
+ <button type="button" @click=${() => this.pending = null}>
5451
+ ${this.t("common.back")}
5452
+ </button>
4840
5453
  <button type="submit" class=${action === "cancel" ? "danger" : "primary"}>
4841
- ${action === "edit" ? "Continue" : "Cancel meeting"}
5454
+ ${this.t(action === "edit" ? "details.continue" : "details.cancelMeeting")}
4842
5455
  </button>
4843
5456
  </footer>
4844
5457
  </form>`;
4845
5458
  }
4846
5459
  render() {
4847
5460
  const m = this.meeting;
4848
- if (!m) return this.frame("Meeting", html``);
5461
+ if (!m)
5462
+ return this.frame(this.t("details.meeting"), html``, this.t("dialog.closeMeetingDetails"));
4849
5463
  const occurrence = occurrenceAt(m, this.occurrenceStart || m.start);
4850
5464
  const start = occurrence?.start ?? (this.occurrenceStart || m.start);
4851
5465
  const end = occurrence?.end ?? start + (m.end - m.start);
@@ -4854,17 +5468,18 @@ var TeamsMeetingDetails = class extends TeamsDialog {
4854
5468
  return this.frame(
4855
5469
  occurrence?.title ?? m.title,
4856
5470
  html`<div class="body">
4857
- <p class="when">${icon("calendar")} ${formatWhen(start, end)}</p>
4858
- ${m.repeat !== "none" ? html`<p>${REPEAT_TEXT[m.repeat]}${this.endText(m)}</p>` : nothing}
4859
- ${m.zone !== this.zone ? html`<p>Scheduled in the organizer's time zone, ${m.zone.replaceAll("_", " ")}.</p>` : nothing}
4860
- <p>Organized by ${m.mine ? "you" : m.organizerName}</p>
5471
+ <p class="when">${icon("calendar")} ${formatWhen(start, end, this.locale)}</p>
5472
+ ${m.repeat !== "none" ? html`<p>${repeatText(m, this.i18n)}${this.endText(m)}</p>` : nothing}
5473
+ ${m.zone !== this.zone ? html`<p>${this.t("details.organizerZone", { zone: m.zone.replaceAll("_", " ") })}</p>` : nothing}
5474
+ <p>
5475
+ ${m.mine ? this.t("details.organizedByYou") : this.t("details.organizedBy", { name: m.organizerName })}
5476
+ </p>
4861
5477
  ${m.channelId ? html`<p>
4862
- Channel meeting in # ${this.channelName || "a channel"}. Everyone in the channel can
4863
- join.
5478
+ ${this.t("details.channelMeeting", { channel: this.channelName || this.t("details.aChannel") })}
4864
5479
  </p>` : html`<div class="people">
4865
- <h3>Attendees (${m.attendees.length + 1})</h3>
5480
+ <h3>${this.t("details.attendees", { count: m.attendees.length + 1 })}</h3>
4866
5481
  <ul>
4867
- <li>${m.organizerName} (organizer)</li>
5482
+ <li>${this.t("details.organizerItem", { name: m.organizerName })}</li>
4868
5483
  ${m.attendees.map((a) => html`<li>${a.name}</li>`)}
4869
5484
  </ul>
4870
5485
  </div>`}
@@ -4872,19 +5487,23 @@ var TeamsMeetingDetails = class extends TeamsDialog {
4872
5487
  ${this.pending ? this.scopeChoice(this.pending) : nothing}
4873
5488
  <footer ?hidden=${this.pending !== null}>
4874
5489
  <button type="button" class="primary" @click=${() => this.emit("teams-meeting-join")}>
4875
- ${m.live ? "Join now" : "Join"}
5490
+ ${this.t(m.live ? "call.joinNow" : "call.join")}
4876
5491
  </button>
4877
5492
  ${hasChat ? html`<button type="button" @click=${() => this.emit("teams-meeting-chat")}>
4878
- Meeting chat
5493
+ ${this.t("details.meetingChat")}
4879
5494
  </button>` : nothing}
4880
- <button type="button" @click=${() => this.emit("teams-meeting-ics")}>Export .ics</button>
4881
- ${m.mine ? html`<button type="button" @click=${() => this.change("edit")}>Edit</button>
5495
+ <button type="button" @click=${() => this.emit("teams-meeting-ics")}>
5496
+ ${this.t("details.exportIcs")}
5497
+ </button>
5498
+ ${m.mine ? html`<button type="button" @click=${() => this.change("edit")}>
5499
+ ${this.t("common.edit")}
5500
+ </button>
4882
5501
  <button type="button" class="danger" @click=${() => this.change("cancel")}>
4883
- Cancel meeting
5502
+ ${this.t("details.cancelMeeting")}
4884
5503
  </button>` : nothing}
4885
5504
  </footer>
4886
5505
  </div>`,
4887
- "Close meeting details"
5506
+ this.t("dialog.closeMeetingDetails")
4888
5507
  );
4889
5508
  }
4890
5509
  };
@@ -4892,15 +5511,25 @@ function defineTeamsMeetingDetails(registry = globalThis.customElements) {
4892
5511
  if (registry && !registry.get("teams-meeting-details"))
4893
5512
  registry.define("teams-meeting-details", TeamsMeetingDetails);
4894
5513
  }
4895
- function chatHeader(chat, on) {
5514
+ var PRESENCE_KEY2 = {
5515
+ available: "presence.available",
5516
+ busy: "presence.busy",
5517
+ dnd: "presence.dnd",
5518
+ brb: "presence.brb",
5519
+ away: "presence.away",
5520
+ offline: "presence.offline"
5521
+ };
5522
+ function chatHeader(chat, on, i18n = teamsI18n()) {
5523
+ const { t } = i18n;
4896
5524
  const other = chat.kind === "direct" ? chat.members.find((m) => !m.self) : void 0;
4897
5525
  const online = chat.members.filter((m) => !m.self && m.availability !== "offline").length;
5526
+ const topic = chat.kind === "direct" ? other ? t(PRESENCE_KEY2[other.availability]) : "" : online ? t("chat.peopleOnline", { count: chat.members.length, online }) : i18n.tn("chat.peopleCount", chat.members.length);
4898
5527
  return html`<header class="channel-head chat-head">
4899
5528
  <button
4900
5529
  type="button"
4901
5530
  class="back"
4902
- aria-label="Back to chats"
4903
- title="Back to chats"
5531
+ aria-label=${t("chat.backToChats")}
5532
+ title=${t("chat.backToChats")}
4904
5533
  @click=${on.back}
4905
5534
  >
4906
5535
  ${icon("back")}
@@ -4913,10 +5542,8 @@ function chatHeader(chat, on) {
4913
5542
  >${icon(chat.meetingId ? "calendar" : "users")}</span
4914
5543
  >`}
4915
5544
  <h1>${chat.title}</h1>
4916
- <span class="topic"
4917
- >${chat.kind === "direct" ? other?.availability ?? "" : `${chat.members.length} people${online ? `, ${online} online` : ""}`}</span
4918
- >
4919
- <div class="people" aria-label="People in this chat">
5545
+ <span class="topic">${topic}</span>
5546
+ <div class="people" aria-label=${t("chat.peopleInChat")}>
4920
5547
  ${chat.members.slice(0, 5).map(
4921
5548
  (m) => html`<office-ui-avatar
4922
5549
  size="sm"
@@ -4927,9 +5554,11 @@ function chatHeader(chat, on) {
4927
5554
  )}
4928
5555
  ${chat.members.length > 5 ? html`<span class="more">+${chat.members.length - 5}</span>` : nothing}
4929
5556
  </div>
4930
- <button type="button" class="chat-people" @click=${on.people}>${icon("users")} People</button>
5557
+ <button type="button" class="chat-people" @click=${on.people}>
5558
+ ${icon("users")} ${t("chat.people")}
5559
+ </button>
4931
5560
  <button type="button" class="meet" @click=${on.call}>
4932
- ${chat.live ? "Join call" : "Call"}
5561
+ ${t(chat.live ? "call.joinCall" : "chat.call")}
4933
5562
  </button>
4934
5563
  </header>`;
4935
5564
  }
@@ -4968,22 +5597,22 @@ var LOCAL_THEME = THEME_CSS.replaceAll(
4968
5597
  ':host([data-office-theme="dark"])'
4969
5598
  ).replaceAll(':root:not([data-office-theme="light"])', ':host([data-office-theme="system"])').replaceAll(":root", ":host([data-office-theme])");
4970
5599
  var RAIL = [
4971
- { id: "activity", label: "Activity", icon: "bell" },
4972
- { id: "chat", label: "Chat", icon: "message" },
4973
- { id: "teams", label: "Teams", icon: "users" },
4974
- { id: "calendar", label: "Calendar", icon: "calendar" },
4975
- { id: "calls", label: "Calls", icon: "phone", secondary: true },
4976
- { id: "files", label: "Files", icon: "folder", secondary: true },
5600
+ { id: "activity", label: "rail.activity", icon: "bell" },
5601
+ { id: "chat", label: "rail.chat", icon: "message" },
5602
+ { id: "teams", label: "rail.teams", icon: "users" },
5603
+ { id: "calendar", label: "rail.calendar", icon: "calendar" },
5604
+ { id: "calls", label: "rail.calls", icon: "phone", secondary: true },
5605
+ { id: "files", label: "rail.files", icon: "folder", secondary: true },
4977
5606
  {
4978
5607
  id: "followed",
4979
- label: "Followed threads",
4980
- shortLabel: "Threads",
5608
+ label: "rail.followed",
5609
+ shortLabel: "rail.threads",
4981
5610
  icon: "chat",
4982
5611
  secondary: true
4983
5612
  },
4984
- { id: "drafts", label: "Drafts", icon: "chat", secondary: true }
5613
+ { id: "drafts", label: "rail.drafts", icon: "chat", secondary: true }
4985
5614
  ];
4986
- var TeamsApp = class extends LitElement {
5615
+ var TeamsApp = class extends TeamsLitElement {
4987
5616
  static styles = [unsafeCSS(LOCAL_THEME), unsafeCSS(teams_app_default)];
4988
5617
  static properties = {
4989
5618
  workspaceId: { type: String, attribute: "workspace-id" },
@@ -5016,11 +5645,19 @@ var TeamsApp = class extends LitElement {
5016
5645
  theme: { state: true },
5017
5646
  fileOpenPreference: { state: true },
5018
5647
  chatDensity: { state: true },
5019
- showAppNames: { state: true }
5648
+ showAppNames: { state: true },
5649
+ authRevision: { state: true }
5020
5650
  };
5651
+ /** Sign-in for servers that require accounts; a server without them never gates the app. */
5652
+ signIn = new SignInFlow({
5653
+ changed: () => this.authRevision++,
5654
+ emit: (name, detail) => this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true })),
5655
+ i18n: () => this.i18n
5656
+ });
5657
+ authKey = "";
5021
5658
  teams = new TeamsController(
5022
5659
  this,
5023
- (text) => this.notify(text),
5660
+ (text) => this.notify(localizeCoreText(text, this.i18n)),
5024
5661
  (item) => this.announce(item)
5025
5662
  );
5026
5663
  toastTimer;
@@ -5072,6 +5709,7 @@ var TeamsApp = class extends LitElement {
5072
5709
  this.fileOpenPreference = "teams";
5073
5710
  this.chatDensity = "comfy";
5074
5711
  this.showAppNames = true;
5712
+ this.authRevision = 0;
5075
5713
  }
5076
5714
  /** The core client behind this element, for hosts that want the raw actions. */
5077
5715
  get client() {
@@ -5094,8 +5732,10 @@ var TeamsApp = class extends LitElement {
5094
5732
  defineTeamsChatPeopleDialog();
5095
5733
  defineTeamsMeetingDialog();
5096
5734
  defineTeamsMeetingDetails();
5735
+ defineTeamsSignIn();
5097
5736
  this.stopActivity = watchActivity(this.ownerDocument, () => this.teams.client?.noteActivity());
5098
5737
  super.connectedCallback();
5738
+ if (this.signIn.phase === "checking") this.authRevision++;
5099
5739
  }
5100
5740
  // ---- lifecycle --------------------------------------------------------------------------
5101
5741
  resolveConfig() {
@@ -5110,14 +5750,13 @@ var TeamsApp = class extends LitElement {
5110
5750
  }
5111
5751
  willUpdate(changed) {
5112
5752
  if (this.popoutRequested) return;
5113
- const restart = [
5114
- "workspaceId",
5115
- "userName",
5116
- "userId",
5117
- "serverConfig",
5118
- "config",
5119
- "identity"
5120
- ].some((k) => changed.has(k));
5753
+ const base = this.resolveConfig();
5754
+ const waiting = this.signIn.gate(base);
5755
+ const authKey = `${waiting}|${this.signIn.session?.token ?? ""}`;
5756
+ const restart = ["workspaceId", "userName", "userId", "serverConfig", "config", "identity"].some(
5757
+ (k) => changed.has(k)
5758
+ ) || changed.has("authRevision") && authKey !== this.authKey;
5759
+ this.authKey = authKey;
5121
5760
  if (!restart && this.teams.client) return;
5122
5761
  this.preview = null;
5123
5762
  this.tab = "posts";
@@ -5127,8 +5766,14 @@ var TeamsApp = class extends LitElement {
5127
5766
  this.meetingForm = this.meetingDetails = null;
5128
5767
  this.settingsOpen = false;
5129
5768
  this.openRequest++;
5130
- const named = this.userName ? { id: this.userId || loadIdentity()?.id || crypto.randomUUID(), name: this.userName } : this.identity ?? loadIdentity();
5131
- if (this.userName && !this.userId) saveIdentity(named);
5769
+ if (waiting) return this.teams.stop();
5770
+ const account = this.signIn.session;
5771
+ const remembered = this.identity ?? loadIdentity();
5772
+ const named = account ? {
5773
+ id: account.userId,
5774
+ name: this.userName || (remembered?.id === account.userId ? remembered.name : account.userId)
5775
+ } : this.userName ? { id: this.userId || loadIdentity()?.id || crypto.randomUUID(), name: this.userName } : remembered;
5776
+ if (this.userName && !this.userId && !account) saveIdentity(named);
5132
5777
  if (!named) return this.teams.stop();
5133
5778
  const workspaceId = /^[\w-]{1,100}$/u.test(this.workspaceId) ? this.workspaceId : "demo";
5134
5779
  this.themeStorageKey = `teams:theme:${encodeURIComponent(workspaceId)}:${encodeURIComponent(named.id)}`;
@@ -5142,7 +5787,7 @@ var TeamsApp = class extends LitElement {
5142
5787
  this.teams.start({
5143
5788
  workspaceId,
5144
5789
  user: named,
5145
- config: this.resolveConfig(),
5790
+ config: this.signIn.apply(base),
5146
5791
  storage: safeStorage,
5147
5792
  ...this.uploadFile ? { uploadFile: this.uploadFile } : {}
5148
5793
  });
@@ -5153,6 +5798,7 @@ var TeamsApp = class extends LitElement {
5153
5798
  updated() {
5154
5799
  this.setAttribute("data-chat-density", this.chatDensity);
5155
5800
  const state = this.teams.state;
5801
+ this.signIn.watchConnection(state?.status === "connected", this.resolveConfig());
5156
5802
  if (state?.channels.some((c) => c.id === state.selectedChannelId))
5157
5803
  this.lastChannelId = state.selectedChannelId;
5158
5804
  if (state?.thread && document.visibilityState === "visible" && this.renderRoot.querySelector(".thread-pane"))
@@ -5223,12 +5869,12 @@ var TeamsApp = class extends LitElement {
5223
5869
  return;
5224
5870
  }
5225
5871
  popout?.close();
5226
- this.notify("Allow popups to open this file in a browser tab");
5872
+ this.notify(this.t("app.allowPopups"));
5227
5873
  return;
5228
5874
  }
5229
5875
  popout?.close();
5230
5876
  if (url) this.preview = detail;
5231
- else this.notify("This file was shared by name only");
5877
+ else this.notify(this.t("app.nameOnly"));
5232
5878
  }
5233
5879
  /** Preview a host-provided website or file without changing shared conversation state. */
5234
5880
  previewContent(detail) {
@@ -5258,6 +5904,7 @@ var TeamsApp = class extends LitElement {
5258
5904
  clearTimeout(this.highlightTimer);
5259
5905
  this.stopActivity?.();
5260
5906
  this.stopActivity = void 0;
5907
+ this.signIn.dispose();
5261
5908
  super.disconnectedCallback();
5262
5909
  }
5263
5910
  createChannel() {
@@ -5325,7 +5972,7 @@ var TeamsApp = class extends LitElement {
5325
5972
  this.settingsOpen = false;
5326
5973
  await this.updateComplete;
5327
5974
  if (!this.isConnected || this.settingsOpen) return;
5328
- this.renderRoot.querySelector('office-ui-menu-button[label="Settings and more"]')?.shadowRoot?.querySelector("button")?.focus();
5975
+ this.renderRoot.querySelector("office-ui-menu-button.settings-menu")?.shadowRoot?.querySelector("button")?.focus();
5329
5976
  }
5330
5977
  applyTheme() {
5331
5978
  if (this.theme === "system") this.removeAttribute("data-office-theme");
@@ -5344,7 +5991,8 @@ var TeamsApp = class extends LitElement {
5344
5991
  .detail=${this.popout}
5345
5992
  @teams-preview-close=${() => globalThis.close()}
5346
5993
  ></teams-content-preview>
5347
- </div>` : html`<p role="alert">The file preview link is invalid.</p>`;
5994
+ </div>` : html`<p role="alert">${this.t("app.invalidPreview")}</p>`;
5995
+ if (this.signIn.phase !== "open") return this.signInScreen();
5348
5996
  const s = this.teams.state;
5349
5997
  if (!s) return this.welcome();
5350
5998
  return html`
@@ -5354,14 +6002,14 @@ var TeamsApp = class extends LitElement {
5354
6002
  @office-chat-open-tab=${(event) => {
5355
6003
  if (this.teams.state?.tabs.some((entry) => entry.id === event.detail.tabId))
5356
6004
  this.selectTab(event.detail.tabId);
5357
- else this.notify("This tab is no longer available in this channel");
6005
+ else this.notify(this.t("app.tabGone"));
5358
6006
  }}
5359
6007
  >
5360
6008
  ${this.topbar(s)}
5361
6009
  <div class="body">
5362
6010
  <office-ui-app-rail
5363
6011
  .hideLabels=${!this.showAppNames}
5364
- .items=${RAIL.map((i) => ({ ...i, ...i.id === "calls" && s.channels.some((c) => c.live) ? { badge: s.channels.filter((c) => c.live).length } : {}, ...i.id === "activity" && s.activityUnread ? { badge: s.activityUnread } : {}, ...i.id === "chat" && s.chats.some((c) => c.unread) ? { badge: s.chats.reduce((n, c) => n + c.unread, 0) } : {} }))}
6012
+ .items=${RAIL.map((i) => ({ ...i, label: this.t(i.label), ..."shortLabel" in i ? { shortLabel: this.t(i.shortLabel) } : {}, ...i.id === "calls" && s.channels.some((c) => c.live) ? { badge: s.channels.filter((c) => c.live).length } : {}, ...i.id === "activity" && s.activityUnread ? { badge: s.activityUnread } : {}, ...i.id === "chat" && s.chats.some((c) => c.unread) ? { badge: s.chats.reduce((n, c) => n + c.unread, 0) } : {} }))}
5365
6013
  selected=${this.rail}
5366
6014
  @office-rail-select=${(e) => {
5367
6015
  if (!this.closePreview()) return;
@@ -5465,6 +6113,18 @@ var TeamsApp = class extends LitElement {
5465
6113
  </div>
5466
6114
  `;
5467
6115
  }
6116
+ signInScreen() {
6117
+ const flow = this.signIn;
6118
+ return html`<teams-sign-in
6119
+ .userId=${flow.userId}
6120
+ .notice=${flow.notice}
6121
+ .error=${flow.error}
6122
+ .retryAfter=${flow.retryAfterSec}
6123
+ ?busy=${flow.busy}
6124
+ ?checking=${flow.phase === "checking"}
6125
+ @teams-sign-in-submit=${(event) => void flow.submit(this.resolveConfig(), event.detail.userId, event.detail.credential)}
6126
+ ></teams-sign-in>`;
6127
+ }
5468
6128
  welcome() {
5469
6129
  return html`
5470
6130
  <div class="welcome">
@@ -5479,17 +6139,17 @@ var TeamsApp = class extends LitElement {
5479
6139
  }}
5480
6140
  >
5481
6141
  <span class="logo">T</span>
5482
- <h1>Welcome to Teams</h1>
5483
- <p>Pick the name your colleagues will see.</p>
6142
+ <h1>${this.t("welcome.title")}</h1>
6143
+ <p>${this.t("welcome.pickName")}</p>
5484
6144
  <input
5485
6145
  name="name"
5486
- placeholder="Your name"
5487
- aria-label="Your name"
6146
+ placeholder=${this.t("welcome.yourName")}
6147
+ aria-label=${this.t("welcome.yourName")}
5488
6148
  maxlength="40"
5489
6149
  autocomplete="name"
5490
6150
  required
5491
6151
  />
5492
- <button type="submit">Continue</button>
6152
+ <button type="submit">${this.t("welcome.continue")}</button>
5493
6153
  </form>
5494
6154
  </div>
5495
6155
  `;
@@ -5497,17 +6157,17 @@ var TeamsApp = class extends LitElement {
5497
6157
  topbar(s) {
5498
6158
  return html`
5499
6159
  <header class="topbar">
5500
- <div class="brand"><span class="logo">T</span><span>Teams</span></div>
6160
+ <div class="brand"><span class="logo">T</span><span>${this.t("rail.teams")}</span></div>
5501
6161
  <div class="search">
5502
6162
  <input
5503
6163
  type="search"
5504
- placeholder="Search messages"
5505
- aria-label="Search messages"
6164
+ placeholder=${this.t("search.messages")}
6165
+ aria-label=${this.t("search.messages")}
5506
6166
  .value=${s.searchQuery}
5507
6167
  @input=${(e) => this.teams.client?.search(e.target.value)}
5508
6168
  />
5509
- ${s.searchQuery ? html`<ul class="results" role="listbox" aria-label="Search results">
5510
- ${s.searchResults.length === 0 ? html`<li class="none">No results</li>` : s.searchResults.map(
6169
+ ${s.searchQuery ? html`<ul class="results" role="listbox" aria-label=${this.t("search.results")}>
6170
+ ${s.searchResults.length === 0 ? html`<li class="none">${this.t("search.none")}</li>` : s.searchResults.map(
5511
6171
  (h) => html`<li>
5512
6172
  <button
5513
6173
  type="button"
@@ -5520,8 +6180,7 @@ var TeamsApp = class extends LitElement {
5520
6180
  >
5521
6181
  <strong>${h.message.authorName}</strong>
5522
6182
  <span class="in"
5523
- >in
5524
- ${s.chats.some((c) => c.id === h.channelId) ? "" : "# "}${h.channelName}</span
6183
+ >${this.t("search.in", { where: `${s.chats.some((c) => c.id === h.channelId) ? "" : "# "}${h.channelName}` })}</span
5525
6184
  >
5526
6185
  <span class="snippet">${h.message.text.slice(0, 100)}</span>
5527
6186
  </button>
@@ -5531,17 +6190,23 @@ var TeamsApp = class extends LitElement {
5531
6190
  </div>
5532
6191
  <div class="me">
5533
6192
  <office-ui-menu-button
5534
- label="Settings and more"
6193
+ class="settings-menu"
6194
+ label=${this.t("app.settingsAndMore")}
5535
6195
  icon="more"
5536
6196
  icon-only
5537
6197
  @office-command=${(event) => {
5538
6198
  if (event.detail.command === "teams-settings") this.askSettings();
5539
6199
  }}
5540
6200
  >
5541
- <office-ui-menu-item label="Settings" command="teams-settings"></office-ui-menu-item>
6201
+ <office-ui-menu-item
6202
+ label=${this.t("settings.title")}
6203
+ command="teams-settings"
6204
+ ></office-ui-menu-item>
5542
6205
  </office-ui-menu-button>
5543
6206
  <teams-profile-menu
5544
6207
  .state=${s}
6208
+ .account=${this.signIn.session?.userId ?? ""}
6209
+ @teams-sign-out=${() => void this.signIn.signOut(this.resolveConfig())}
5545
6210
  @teams-profile-status=${(event) => {
5546
6211
  const { availability, duration } = event.detail;
5547
6212
  if (isPresenceStatus(availability))
@@ -5568,7 +6233,7 @@ var TeamsApp = class extends LitElement {
5568
6233
  activityToastView(item) {
5569
6234
  return html`<div class="activity-toast" role="status" data-kind=${item.kind}>
5570
6235
  <span class="toast-text"
5571
- ><strong>${describeActivity(item)}</strong
6236
+ ><strong>${describeActivityIn(item, this.i18n)}</strong
5572
6237
  ><span class="toast-preview">${item.preview}</span></span
5573
6238
  >
5574
6239
  <button
@@ -5579,9 +6244,13 @@ var TeamsApp = class extends LitElement {
5579
6244
  else void this.openActivity(item.id);
5580
6245
  }}
5581
6246
  >
5582
- View
6247
+ ${this.t("app.view")}
5583
6248
  </button>
5584
- <button type="button" aria-label="Dismiss" @click=${() => this.dismissToast(item.id)}>
6249
+ <button
6250
+ type="button"
6251
+ aria-label=${this.t("app.dismiss")}
6252
+ @click=${() => this.dismissToast(item.id)}
6253
+ >
5585
6254
  ${icon("close")}
5586
6255
  </button>
5587
6256
  </div>`;
@@ -5607,7 +6276,7 @@ var TeamsApp = class extends LitElement {
5607
6276
  class="show-calendar"
5608
6277
  @click=${() => this.mobileView = "conversation"}
5609
6278
  >
5610
- ${icon("calendar")} Open calendar
6279
+ ${icon("calendar")} ${this.t("app.openCalendar")}
5611
6280
  </button>`;
5612
6281
  if (this.rail === "activity")
5613
6282
  return html`<office-ui-activity-feed
@@ -5621,7 +6290,7 @@ var TeamsApp = class extends LitElement {
5621
6290
  if (this.rail === "calls") {
5622
6291
  const live2 = s.channels.filter((c) => c.live);
5623
6292
  return html`
5624
- <h2>Calls</h2>
6293
+ <h2>${this.t("rail.calls")}</h2>
5625
6294
  <ul class="plain">
5626
6295
  ${s.channels.map(
5627
6296
  (c) => html`<li>
@@ -5631,17 +6300,16 @@ var TeamsApp = class extends LitElement {
5631
6300
  class=${c.live ? "join live" : "join"}
5632
6301
  @click=${() => this.startMeeting(c.id)}
5633
6302
  >
5634
- ${c.live ? "Join" : "Meet now"}
6303
+ ${this.t(c.live ? "call.join" : "call.meetNow")}
5635
6304
  </button>
5636
6305
  </li>`
5637
6306
  )}
5638
6307
  </ul>
5639
- ${live2.length === 0 ? html`<p class="hint">Nobody is in a call right now.</p>` : nothing}
6308
+ ${live2.length === 0 ? html`<p class="hint">${this.t("app.nobodyInCall")}</p>` : nothing}
5640
6309
  `;
5641
6310
  }
5642
6311
  return html`
5643
6312
  <office-ui-channel-list
5644
- heading="Channels"
5645
6313
  selected-id=${s.selectedChannelId}
5646
6314
  .channels=${s.channels}
5647
6315
  @office-channel-select=${(e) => this.selectChannel(e.detail.id)}
@@ -5651,7 +6319,12 @@ var TeamsApp = class extends LitElement {
5651
6319
  }
5652
6320
  main(s) {
5653
6321
  if (this.rail === "drafts" && this.teams.client)
5654
- return draftList(s, this.teams.client, (context) => void this.resumeDraft(context));
6322
+ return draftList(
6323
+ s,
6324
+ this.teams.client,
6325
+ (context) => void this.resumeDraft(context),
6326
+ this.i18n
6327
+ );
5655
6328
  if (this.preview)
5656
6329
  return html`<teams-content-preview
5657
6330
  .detail=${this.preview}
@@ -5670,7 +6343,8 @@ var TeamsApp = class extends LitElement {
5670
6343
  this.followedUnreadOnly,
5671
6344
  (value) => {
5672
6345
  this.followedUnreadOnly = value;
5673
- }
6346
+ },
6347
+ this.i18n
5674
6348
  );
5675
6349
  if (s.call && this.meeting) return this.meetingView(s);
5676
6350
  if (this.rail === "calendar") return this.calendarMain(s);
@@ -5678,7 +6352,7 @@ var TeamsApp = class extends LitElement {
5678
6352
  return this.chatEmpty();
5679
6353
  return html`
5680
6354
  ${s.call && !this.meeting ? html`<div class="callbar">
5681
- You are in a call in ${this.callHeading(s.call)}
6355
+ ${this.t("app.inCall", { where: this.callHeading(s.call) })}
5682
6356
  <button
5683
6357
  type="button"
5684
6358
  @click=${() => {
@@ -5687,7 +6361,7 @@ var TeamsApp = class extends LitElement {
5687
6361
  } else if (this.selectChannel(s.call.channelId)) this.meeting = true;
5688
6362
  }}
5689
6363
  >
5690
- Return to call
6364
+ ${this.t("app.returnToCall")}
5691
6365
  </button>
5692
6366
  </div>` : nothing}
5693
6367
  ${this.conversation(s)}
@@ -5702,21 +6376,25 @@ var TeamsApp = class extends LitElement {
5702
6376
  if (sharedTab) this.retainedTab = sharedTab;
5703
6377
  const activeTab = sharedTab ?? (this.retainedTab?.id === this.tab && this.retainedTab.channelId === s.selectedChannelId ? this.retainedTab : void 0);
5704
6378
  return html`
5705
- ${compact ? nothing : chat ? chatHeader(chat, {
5706
- back: () => this.mobileView = "list",
5707
- call: () => this.startMeeting(),
5708
- people: () => this.chatPeople = true
5709
- }) : html`<header class="channel-head">
6379
+ ${compact ? nothing : chat ? chatHeader(
6380
+ chat,
6381
+ {
6382
+ back: () => this.mobileView = "list",
6383
+ call: () => this.startMeeting(),
6384
+ people: () => this.chatPeople = true
6385
+ },
6386
+ this.i18n
6387
+ ) : html`<header class="channel-head">
5710
6388
  <button
5711
6389
  type="button"
5712
6390
  class="back"
5713
- aria-label="Back to channels"
5714
- title="Back to channels"
6391
+ aria-label=${this.t("app.backToChannels")}
6392
+ title=${this.t("app.backToChannels")}
5715
6393
  @click=${() => this.mobileView = "list"}
5716
6394
  >
5717
6395
  ${icon("back")}
5718
6396
  </button>
5719
- <h1><span class="hash">#</span> ${channel?.name ?? "No channel yet"}</h1>
6397
+ <h1><span class="hash">#</span> ${channel?.name ?? this.t("app.noChannel")}</h1>
5720
6398
  <nav class="tabs" role="tablist">
5721
6399
  ${["posts", "files"].map(
5722
6400
  (t) => html`<button
@@ -5725,15 +6403,15 @@ var TeamsApp = class extends LitElement {
5725
6403
  aria-selected=${String(this.tab === t)}
5726
6404
  @click=${() => this.selectTab(t)}
5727
6405
  >
5728
- ${t === "posts" ? "Posts" : "Shared"}
6406
+ ${this.t(t === "posts" ? "app.posts" : "app.shared")}
5729
6407
  </button>`
5730
6408
  )}
5731
6409
  ${s.tabs.map((t) => html`<button type="button" role="tab" aria-selected=${String(this.tab === t.id)} @click=${() => this.selectTab(t.id)}>${t.name}</button>`)}
5732
6410
  </nav>
5733
6411
  <button
5734
6412
  type="button"
5735
- aria-label="Add tab"
5736
- title="Add tab"
6413
+ aria-label=${this.t("app.addTab")}
6414
+ title=${this.t("app.addTab")}
5737
6415
  ?disabled=${!channel}
5738
6416
  @click=${() => this.addingTab = !this.addingTab}
5739
6417
  >
@@ -5742,8 +6420,8 @@ var TeamsApp = class extends LitElement {
5742
6420
  ${sharedTab ? html`<button
5743
6421
  type="button"
5744
6422
  data-tab-conversation
5745
- aria-label="Show tab conversation"
5746
- title="Show tab conversation"
6423
+ aria-label=${this.t("app.tabConversation")}
6424
+ title=${this.t("app.tabConversation")}
5747
6425
  aria-expanded=${String(s.thread?.root.tabId === sharedTab.id)}
5748
6426
  @click=${async () => {
5749
6427
  if (s.thread?.root.tabId === sharedTab.id) {
@@ -5751,7 +6429,7 @@ var TeamsApp = class extends LitElement {
5751
6429
  return;
5752
6430
  }
5753
6431
  if (!c?.openTabConversation(sharedTab.id)) {
5754
- this.notify("Could not open this tab conversation");
6432
+ this.notify(this.t("app.tabConversationFailed"));
5755
6433
  return;
5756
6434
  }
5757
6435
  await Promise.resolve();
@@ -5762,17 +6440,17 @@ var TeamsApp = class extends LitElement {
5762
6440
  ${icon("chat")}
5763
6441
  </button>` : nothing}
5764
6442
  ${sharedTab?.createdBy === s.user.id ? html`<office-ui-menu-button
5765
- label="Tab options"
6443
+ label=${this.t("app.tabOptions")}
5766
6444
  icon="more"
5767
6445
  icon-only
5768
6446
  @office-command=${(event) => {
5769
6447
  if (event.detail.command === "rename-tab") {
5770
- const name = globalThis.prompt?.("Tab name", sharedTab.name)?.trim();
6448
+ const name = globalThis.prompt?.(this.t("app.tabNamePrompt"), sharedTab.name)?.trim();
5771
6449
  if (name) c?.renameTab(sharedTab.id, name);
5772
6450
  } else if (event.detail.command === "remove-tab") {
5773
6451
  if (!this.canLeaveContent()) return;
5774
6452
  if (globalThis.confirm?.(
5775
- `Remove ${sharedTab.name} from this channel?`
6453
+ this.t("app.confirmRemoveTab", { name: sharedTab.name })
5776
6454
  ) === true && c?.removeTab(sharedTab.id)) {
5777
6455
  this.openRequest++;
5778
6456
  this.preview = null;
@@ -5781,16 +6459,16 @@ var TeamsApp = class extends LitElement {
5781
6459
  }
5782
6460
  }}
5783
6461
  ><office-ui-menu-item
5784
- label="Rename tab"
6462
+ label=${this.t("app.renameTab")}
5785
6463
  command="rename-tab"
5786
6464
  ></office-ui-menu-item
5787
6465
  ><office-ui-menu-item
5788
- label="Remove tab"
6466
+ label=${this.t("app.removeTab")}
5789
6467
  command="remove-tab"
5790
6468
  ></office-ui-menu-item
5791
6469
  ></office-ui-menu-button>` : nothing}
5792
6470
  <span class="topic">${channel?.topic ?? ""}</span>
5793
- <div class="people" aria-label="People online">
6471
+ <div class="people" aria-label=${this.t("app.peopleOnline")}>
5794
6472
  ${s.people.slice(0, 5).map((p) => html`<office-ui-avatar size="sm" name=${p.name} seed=${p.id} color=${p.color} presence=${p.availability}></office-ui-avatar>`)}
5795
6473
  ${s.people.length > 5 ? html`<span class="more">+${s.people.length - 5}</span>` : nothing}
5796
6474
  </div>
@@ -5800,11 +6478,11 @@ var TeamsApp = class extends LitElement {
5800
6478
  ?disabled=${!channel}
5801
6479
  @click=${() => this.startMeeting()}
5802
6480
  >
5803
- ${s.channels.find((x) => x.id === s.selectedChannelId)?.live ? "Join call" : "Meet now"}
6481
+ ${this.t(s.channels.find((x) => x.id === s.selectedChannelId)?.live ? "call.joinCall" : "call.meetNow")}
5804
6482
  </button>
5805
6483
  </header>`}
5806
- ${c ? messageTransfers(s, c) : nothing}
5807
- ${activeTab && !sharedTab && !compact ? html`<p role="status">This tab was removed from the channel. Your open copy remains here until you close it.</p>` : nothing}
6484
+ ${c ? messageTransfers(s, c, this.i18n) : nothing}
6485
+ ${activeTab && !sharedTab && !compact ? html`<p role="status">${this.t("app.tabRemoved")}</p>` : nothing}
5808
6486
  ${activeTab && !compact ? html`<div
5809
6487
  class="conversation-grid tab-conversation"
5810
6488
  data-thread=${String(s.thread?.root.tabId === activeTab.id)}
@@ -5823,7 +6501,8 @@ var TeamsApp = class extends LitElement {
5823
6501
  c,
5824
6502
  (attachment) => void this.openFile(attachment),
5825
6503
  () => void this.closeThread(),
5826
- this.highlightId
6504
+ this.highlightId,
6505
+ this.i18n
5827
6506
  ) : nothing}
5828
6507
  </div>` : this.tab === "files" && !compact ? this.fileList(s.files, s) : html`<div class="conversation-grid" data-thread=${String(!!s.thread && !compact)}>
5829
6508
  <div class="conversation-main">
@@ -5846,7 +6525,7 @@ var TeamsApp = class extends LitElement {
5846
6525
  @office-chat-thread=${(e) => void this.openThread(e.detail.messageId)}
5847
6526
  @office-chat-reply=${(e) => compact ? c?.startReply(e.detail.messageId) : void this.openThread(e.detail.messageId, "reply")}
5848
6527
  @office-chat-edit=${(e) => compact ? c?.startEdit(e.detail.messageId) : void this.openThread(e.detail.messageId, "edit")}
5849
- @office-chat-delete=${(e) => globalThis.confirm?.("Delete this message?") !== false && c?.deleteMessage(e.detail.messageId)}
6528
+ @office-chat-delete=${(e) => globalThis.confirm?.(this.t("chat.confirmDelete")) !== false && c?.deleteMessage(e.detail.messageId)}
5850
6529
  @office-chat-open-file=${(e) => this.openFile(e.detail.attachment)}
5851
6530
  ></office-ui-chat-list>
5852
6531
  ${s.thread && !compact ? nothing : html`<office-ui-chat-composer
@@ -5859,7 +6538,7 @@ var TeamsApp = class extends LitElement {
5859
6538
  .mentionTargets=${s.mentionTargets}
5860
6539
  @office-chat-draft=${(e) => c?.setDraft(e.detail)}
5861
6540
  ?disabled=${!channel}
5862
- placeholder=${chat ? `Message ${chat.title}` : channel ? `Message # ${channel.name}` : "Create a channel first"}
6541
+ placeholder=${chat ? this.t("app.messageTo", { name: chat.title }) : channel ? this.t("app.messageTo", { name: `# ${channel.name}` }) : this.t("app.createChannelFirst")}
5863
6542
  @office-chat-typing=${() => c?.notifyTyping()}
5864
6543
  @office-chat-cancel=${() => c?.cancelCompose()}
5865
6544
  @office-chat-send=${(e) => void c?.send(e.detail)}
@@ -5871,7 +6550,8 @@ var TeamsApp = class extends LitElement {
5871
6550
  c,
5872
6551
  (attachment) => void this.openFile(attachment),
5873
6552
  () => void this.closeThread(),
5874
- this.highlightId
6553
+ this.highlightId,
6554
+ this.i18n
5875
6555
  ) : nothing}
5876
6556
  </div>`}
5877
6557
  `;
@@ -5941,9 +6621,9 @@ var TeamsApp = class extends LitElement {
5941
6621
  }
5942
6622
  chatEmpty() {
5943
6623
  return html`<div class="empty-main">
5944
- <h1>Chat</h1>
5945
- <p>Pick a chat from the list, or start a new one.</p>
5946
- <button type="button" @click=${() => this.newChat = true}>New chat</button>
6624
+ <h1>${this.t("rail.chat")}</h1>
6625
+ <p>${this.t("app.pickChat")}</p>
6626
+ <button type="button" @click=${() => this.newChat = true}>${this.t("chat.new")}</button>
5947
6627
  </div>`;
5948
6628
  }
5949
6629
  commitChat(ids, name) {
@@ -5976,8 +6656,7 @@ var TeamsApp = class extends LitElement {
5976
6656
  @teams-chat-add=${(e) => c?.addToChat(e.detail.id, e.detail.userIds)}
5977
6657
  @teams-chat-remove=${(e) => c?.removeFromChat(e.detail.id, e.detail.userId)}
5978
6658
  @teams-chat-leave=${(e) => {
5979
- if (globalThis.confirm?.("Leave this chat? You will stop receiving its messages.") === false)
5980
- return;
6659
+ if (globalThis.confirm?.(this.t("chatPeople.confirmLeave")) === false) return;
5981
6660
  if (c?.leaveChat(e.detail.id)) {
5982
6661
  this.chatPeople = false;
5983
6662
  this.mobileView = "list";
@@ -5992,7 +6671,7 @@ var TeamsApp = class extends LitElement {
5992
6671
  return html`
5993
6672
  <div class="calendar-back">
5994
6673
  <button type="button" @click=${() => this.mobileView = "list"}>
5995
- ${icon("back")} Upcoming meetings
6674
+ ${icon("back")} ${this.t("app.upcoming")}
5996
6675
  </button>
5997
6676
  </div>
5998
6677
  <office-ui-calendar-view
@@ -6075,14 +6754,14 @@ var TeamsApp = class extends LitElement {
6075
6754
  if (c?.cancelOccurrence(meetingId, recurrenceId)) this.meetingDetails = null;
6076
6755
  return;
6077
6756
  }
6078
- const ask = details?.repeat === "none" ? "Cancel this meeting for everyone?" : "Cancel every event in this series?";
6757
+ const ask = details?.repeat === "none" ? this.t("details.confirmCancel") : this.t("details.confirmCancelSeries");
6079
6758
  if (globalThis.confirm?.(ask) === false) return;
6080
6759
  if (c?.cancelMeeting(meetingId)) this.meetingDetails = null;
6081
6760
  }}
6082
6761
  @teams-meeting-ics=${(e) => {
6083
6762
  const file = c?.meetingIcs(e.detail.meetingId);
6084
6763
  if (file && !downloadText(file.name, file.text))
6085
- this.notify("Could not start the download");
6764
+ this.notify(this.t("app.downloadFailed"));
6086
6765
  }}
6087
6766
  @teams-dialog-close=${() => this.meetingDetails = null}
6088
6767
  ></teams-meeting-details>
@@ -6112,7 +6791,7 @@ var TeamsApp = class extends LitElement {
6112
6791
  const message = Array.from(
6113
6792
  list?.shadowRoot?.querySelectorAll("[data-message-id]") ?? []
6114
6793
  ).find((element) => element.dataset.messageId === root);
6115
- message?.querySelector('.thread-link, button[aria-label="Reply"]')?.focus();
6794
+ message?.querySelector('.thread-link, [data-action="reply"]')?.focus();
6116
6795
  }
6117
6796
  pinFile(file) {
6118
6797
  if (!this.selectChannel(file.channelId ?? this.teams.state?.selectedChannelId ?? "")) return;
@@ -6122,7 +6801,7 @@ var TeamsApp = class extends LitElement {
6122
6801
  { postToChannel: true }
6123
6802
  );
6124
6803
  if (tab) this.selectTab(tab.id);
6125
- else this.notify("This file needs an uploaded URL before it can be pinned");
6804
+ else this.notify(this.t("app.pinNeedsUrl"));
6126
6805
  }
6127
6806
  saveCopyFor(detail, state) {
6128
6807
  const client = this.teams.client;
@@ -6146,8 +6825,8 @@ var TeamsApp = class extends LitElement {
6146
6825
  }
6147
6826
  allFiles(s) {
6148
6827
  return html`<header class="channel-head">
6149
- <h1>Files</h1>
6150
- <span class="topic">Shared across your channels</span>
6828
+ <h1>${this.t("rail.files")}</h1>
6829
+ <span class="topic">${this.t("app.sharedAcross")}</span>
6151
6830
  </header>
6152
6831
  ${this.fileList(s.allFiles, s)}`;
6153
6832
  }
@@ -6195,7 +6874,7 @@ var TeamsApp = class extends LitElement {
6195
6874
  }))}
6196
6875
  ></office-ui-call-grid>
6197
6876
  ${this.panel === "chat" ? html`<aside class="panel chat">${this.conversation(s, true)}</aside>` : this.panel === "people" ? html`<aside class="panel people-panel">
6198
- <h2>People (${call.participants.length})</h2>
6877
+ <h2>${this.t("app.peopleCount", { count: call.participants.length })}</h2>
6199
6878
  <ul class="plain">
6200
6879
  ${call.participants.map(
6201
6880
  (p) => html`<li>
@@ -6204,8 +6883,10 @@ var TeamsApp = class extends LitElement {
6204
6883
  name=${p.name}
6205
6884
  seed=${p.id}
6206
6885
  ></office-ui-avatar>
6207
- <span>${p.name}${p.self ? " (you)" : ""}</span>
6208
- ${p.state.hand ? html`<span title="Hand raised">✋</span>` : nothing}
6886
+ <span
6887
+ >${p.self ? this.t("common.nameYou", { name: p.name }) : p.name}</span
6888
+ >
6889
+ ${p.state.hand ? html`<span title=${this.t("call.handRaised")}>✋</span>` : nothing}
6209
6890
  </li>`
6210
6891
  )}
6211
6892
  </ul>
@@ -6220,7 +6901,7 @@ function defineTeamsApp(registry = globalThis.customElements) {
6220
6901
  }
6221
6902
 
6222
6903
  // src/teams/app/bind.ts
6223
- var SCALARS = ["workspaceId", "userName", "userId"];
6904
+ var SCALARS = ["workspaceId", "userName", "userId", "locale"];
6224
6905
  function applyTeamsProps(el, props, prev = {}) {
6225
6906
  for (const key of SCALARS) {
6226
6907
  if (props[key] === prev[key]) continue;
@@ -6283,4 +6964,4 @@ function registerTeams() {
6283
6964
  defineCalendarAgenda();
6284
6965
  }
6285
6966
 
6286
- export { EMOJI, OfficeUiActivityFeed, OfficeUiAppRail, OfficeUiAvatar, OfficeUiCalendarAgenda, OfficeUiCalendarView, OfficeUiCallControls, OfficeUiCallGrid, OfficeUiChannelList, OfficeUiChatComposer, OfficeUiChatList, OfficeUiChatSidebar, OfficeUiPeoplePicker, OfficeUiPrejoin, QUICK_REACTIONS, TEAMS_TAGS, TeamsApp, TeamsChannelTab, TeamsContentPreview, TeamsController, TeamsElement, TeamsFilesPanel, TeamsSettings, XLSX_EDITOR_EVENTS, activityTime, announceFileCommand, applyTeamsProps, blobFor, colorFor, createTeams, dayLabel, defineActivityFeed, defineAppRail, defineAvatar, defineCalendarAgenda, defineCalendarView, defineCallControls, defineCallGrid, defineChannelList, defineChatComposer, defineChatList, defineChatSidebar, definePeoplePicker, definePrejoin, defineTeamsApp, defineTeamsChannelTab, defineTeamsContentPreview, defineTeamsFilesPanel, defineTeamsSettings, downloadBytes, emit, emitEvent, formatElapsed, formatSize, formatTime, formatWhen, icon, listenTeamsEvents, loadConfig, loadIdentity, parseIceLines, pickFile, registerTeams, safeStorage, saveConfig, saveExtension, saveIdentity, withExtension, withStyles };
6967
+ export { EMOJI, OfficeUiActivityFeed, OfficeUiAppRail, OfficeUiAvatar, OfficeUiCalendarAgenda, OfficeUiCalendarView, OfficeUiCallControls, OfficeUiCallGrid, OfficeUiChannelList, OfficeUiChatComposer, OfficeUiChatList, OfficeUiChatSidebar, OfficeUiPeoplePicker, OfficeUiPrejoin, QUICK_REACTIONS, TEAMS_TAGS, TeamsApp, TeamsChannelTab, TeamsContentPreview, TeamsController, TeamsElement, TeamsFilesPanel, TeamsSettings, XLSX_EDITOR_EVENTS, activityTime, announceFileCommand, applyTeamsProps, blobFor, colorFor, createTeams, dayKind, dayLabel, defineActivityFeed, defineAppRail, defineAvatar, defineCalendarAgenda, defineCalendarView, defineCallControls, defineCallGrid, defineChannelList, defineChatComposer, defineChatList, defineChatSidebar, definePeoplePicker, definePrejoin, defineTeamsApp, defineTeamsChannelTab, defineTeamsContentPreview, defineTeamsFilesPanel, defineTeamsSettings, downloadBytes, emit, emitEvent, formatElapsed, formatSize, formatTime, formatWhen, icon, listenTeamsEvents, loadConfig, loadIdentity, parseIceLines, pickFile, registerTeams, safeStorage, saveConfig, saveExtension, saveIdentity, withExtension, withStyles };