react-cheminfo 0.24.0 → 0.26.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 (432) hide show
  1. package/README.md +73 -2
  2. package/bin/check-messages.mjs +229 -0
  3. package/lib/about/ui/AboutBuild.d.ts.map +1 -1
  4. package/lib/about/ui/AboutBuild.js +15 -4
  5. package/lib/about/ui/AboutBuild.js.map +1 -1
  6. package/lib/about/ui/AboutCitations.d.ts.map +1 -1
  7. package/lib/about/ui/AboutCitations.js +3 -1
  8. package/lib/about/ui/AboutCitations.js.map +1 -1
  9. package/lib/about/ui/AboutPage.d.ts.map +1 -1
  10. package/lib/about/ui/AboutPage.js +9 -1
  11. package/lib/about/ui/AboutPage.js.map +1 -1
  12. package/lib/about/ui/AboutProvidedBy.d.ts.map +1 -1
  13. package/lib/about/ui/AboutProvidedBy.js +8 -3
  14. package/lib/about/ui/AboutProvidedBy.js.map +1 -1
  15. package/lib/capsule/ui/Capsule.d.ts +30 -0
  16. package/lib/capsule/ui/Capsule.d.ts.map +1 -0
  17. package/lib/capsule/ui/Capsule.js +12 -0
  18. package/lib/capsule/ui/Capsule.js.map +1 -0
  19. package/lib/capsule/ui/CapsuleFilter.d.ts +19 -3
  20. package/lib/capsule/ui/CapsuleFilter.d.ts.map +1 -1
  21. package/lib/capsule/ui/CapsuleFilter.js +14 -44
  22. package/lib/capsule/ui/CapsuleFilter.js.map +1 -1
  23. package/lib/capsule/ui/capsuleFilterCapsules.d.ts +15 -0
  24. package/lib/capsule/ui/capsuleFilterCapsules.d.ts.map +1 -0
  25. package/lib/capsule/ui/capsuleFilterCapsules.js +47 -0
  26. package/lib/capsule/ui/capsuleFilterCapsules.js.map +1 -0
  27. package/lib/chart/ui/ChartTrackingLayer.d.ts.map +1 -1
  28. package/lib/chart/ui/ChartTrackingLayer.js +4 -2
  29. package/lib/chart/ui/ChartTrackingLayer.js.map +1 -1
  30. package/lib/chrome/ui/SiteHeader.d.ts +1 -1
  31. package/lib/chrome/ui/SiteHeader.d.ts.map +1 -1
  32. package/lib/chrome/ui/SiteHeader.js +4 -2
  33. package/lib/chrome/ui/SiteHeader.js.map +1 -1
  34. package/lib/citation/ui/CitationMenu.d.ts +1 -1
  35. package/lib/citation/ui/CitationMenu.d.ts.map +1 -1
  36. package/lib/citation/ui/CitationMenu.js +7 -3
  37. package/lib/citation/ui/CitationMenu.js.map +1 -1
  38. package/lib/citation/ui/CiteButton.d.ts +1 -1
  39. package/lib/citation/ui/CiteButton.d.ts.map +1 -1
  40. package/lib/citation/ui/CiteButton.js +3 -1
  41. package/lib/citation/ui/CiteButton.js.map +1 -1
  42. package/lib/citation/ui/CopyFormatEntries.d.ts.map +1 -1
  43. package/lib/citation/ui/CopyFormatEntries.js +3 -1
  44. package/lib/citation/ui/CopyFormatEntries.js.map +1 -1
  45. package/lib/citation/ui/WorkEntry.d.ts.map +1 -1
  46. package/lib/citation/ui/WorkEntry.js +3 -1
  47. package/lib/citation/ui/WorkEntry.js.map +1 -1
  48. package/lib/citation/ui/entries.d.ts.map +1 -1
  49. package/lib/citation/ui/entries.js +8 -2
  50. package/lib/citation/ui/entries.js.map +1 -1
  51. package/lib/citation/ui/useCopyFeedback.d.ts.map +1 -1
  52. package/lib/citation/ui/useCopyFeedback.js +9 -4
  53. package/lib/citation/ui/useCopyFeedback.js.map +1 -1
  54. package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -1
  55. package/lib/clipboard/ui/ClickToCopy.js +12 -6
  56. package/lib/clipboard/ui/ClickToCopy.js.map +1 -1
  57. package/lib/clipboard/ui/CopyButton.d.ts +3 -3
  58. package/lib/clipboard/ui/CopyButton.d.ts.map +1 -1
  59. package/lib/clipboard/ui/CopyButton.js +7 -4
  60. package/lib/clipboard/ui/CopyButton.js.map +1 -1
  61. package/lib/color/ui/ColorScaleEditor.d.ts.map +1 -1
  62. package/lib/color/ui/ColorScaleEditor.js +15 -10
  63. package/lib/color/ui/ColorScaleEditor.js.map +1 -1
  64. package/lib/color/ui/ColorScaleEntry.d.ts.map +1 -1
  65. package/lib/color/ui/ColorScaleEntry.js +4 -2
  66. package/lib/color/ui/ColorScaleEntry.js.map +1 -1
  67. package/lib/color/ui/ColorScaleLegend.d.ts.map +1 -1
  68. package/lib/color/ui/ColorScaleLegend.js +7 -1
  69. package/lib/color/ui/ColorScaleLegend.js.map +1 -1
  70. package/lib/color/ui/ColorScaleSelect.d.ts.map +1 -1
  71. package/lib/color/ui/ColorScaleSelect.js +8 -4
  72. package/lib/color/ui/ColorScaleSelect.js.map +1 -1
  73. package/lib/confirm/ui/ConfirmButton.d.ts +2 -2
  74. package/lib/confirm/ui/ConfirmButton.d.ts.map +1 -1
  75. package/lib/confirm/ui/ConfirmButton.js +4 -2
  76. package/lib/confirm/ui/ConfirmButton.js.map +1 -1
  77. package/lib/core.d.ts +1 -0
  78. package/lib/core.d.ts.map +1 -1
  79. package/lib/core.js +1 -0
  80. package/lib/core.js.map +1 -1
  81. package/lib/credits/ui/CreditsList.d.ts.map +1 -1
  82. package/lib/credits/ui/CreditsList.js +3 -1
  83. package/lib/credits/ui/CreditsList.js.map +1 -1
  84. package/lib/delimited/ui/DelimitedTextDialog.d.ts.map +1 -1
  85. package/lib/delimited/ui/DelimitedTextDialog.js +5 -2
  86. package/lib/delimited/ui/DelimitedTextDialog.js.map +1 -1
  87. package/lib/delimited/ui/DelimitedTextPanel.d.ts.map +1 -1
  88. package/lib/delimited/ui/DelimitedTextPanel.js +8 -8
  89. package/lib/delimited/ui/DelimitedTextPanel.js.map +1 -1
  90. package/lib/download/ui/FigureDownload.d.ts +2 -2
  91. package/lib/download/ui/FigureDownload.d.ts.map +1 -1
  92. package/lib/download/ui/FigureDownload.js +4 -2
  93. package/lib/download/ui/FigureDownload.js.map +1 -1
  94. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  95. package/lib/download/ui/FigureDownloadPanel.js +24 -18
  96. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  97. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  98. package/lib/ecosystem/core/sites.js +4 -2
  99. package/lib/ecosystem/core/sites.js.map +1 -1
  100. package/lib/ecosystem/ui/EcosystemButton.d.ts +1 -1
  101. package/lib/ecosystem/ui/EcosystemButton.d.ts.map +1 -1
  102. package/lib/ecosystem/ui/EcosystemButton.js +4 -2
  103. package/lib/ecosystem/ui/EcosystemButton.js.map +1 -1
  104. package/lib/ecosystem/ui/EcosystemLinks.d.ts +1 -1
  105. package/lib/ecosystem/ui/EcosystemLinks.d.ts.map +1 -1
  106. package/lib/ecosystem/ui/EcosystemLinks.js +5 -2
  107. package/lib/ecosystem/ui/EcosystemLinks.js.map +1 -1
  108. package/lib/ecosystem/ui/EcosystemMenu.d.ts.map +1 -1
  109. package/lib/ecosystem/ui/EcosystemMenu.js +3 -1
  110. package/lib/ecosystem/ui/EcosystemMenu.js.map +1 -1
  111. package/lib/ecosystem/ui/SiteGroupSection.d.ts.map +1 -1
  112. package/lib/ecosystem/ui/SiteGroupSection.js +3 -1
  113. package/lib/ecosystem/ui/SiteGroupSection.js.map +1 -1
  114. package/lib/ecosystem/ui/SiteTile.d.ts.map +1 -1
  115. package/lib/ecosystem/ui/SiteTile.js +3 -1
  116. package/lib/ecosystem/ui/SiteTile.js.map +1 -1
  117. package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
  118. package/lib/ecosystem/ui/glyphs.js +6 -5
  119. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  120. package/lib/error/ui/ErrorFallback.d.ts +2 -2
  121. package/lib/error/ui/ErrorFallback.d.ts.map +1 -1
  122. package/lib/error/ui/ErrorFallback.js +4 -2
  123. package/lib/error/ui/ErrorFallback.js.map +1 -1
  124. package/lib/help/ui/HelpBody.d.ts.map +1 -1
  125. package/lib/help/ui/HelpBody.js +3 -1
  126. package/lib/help/ui/HelpBody.js.map +1 -1
  127. package/lib/help/ui/HelpIcon.d.ts +2 -1
  128. package/lib/help/ui/HelpIcon.d.ts.map +1 -1
  129. package/lib/help/ui/HelpIcon.js +4 -2
  130. package/lib/help/ui/HelpIcon.js.map +1 -1
  131. package/lib/help/ui/HelpToolbarButton.d.ts.map +1 -1
  132. package/lib/help/ui/HelpToolbarButton.js +3 -1
  133. package/lib/help/ui/HelpToolbarButton.js.map +1 -1
  134. package/lib/help/ui/helpName.d.ts +5 -1
  135. package/lib/help/ui/helpName.d.ts.map +1 -1
  136. package/lib/help/ui/helpName.js +4 -2
  137. package/lib/help/ui/helpName.js.map +1 -1
  138. package/lib/i18n/core/chromeCatalog.d.ts +21 -0
  139. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -0
  140. package/lib/i18n/core/chromeCatalog.js +29 -0
  141. package/lib/i18n/core/chromeCatalog.js.map +1 -0
  142. package/lib/i18n/core/index.d.ts +12 -0
  143. package/lib/i18n/core/index.d.ts.map +1 -0
  144. package/lib/i18n/core/index.js +6 -0
  145. package/lib/i18n/core/index.js.map +1 -0
  146. package/lib/i18n/core/languages.d.ts +32 -0
  147. package/lib/i18n/core/languages.d.ts.map +1 -0
  148. package/lib/i18n/core/languages.js +41 -0
  149. package/lib/i18n/core/languages.js.map +1 -0
  150. package/lib/i18n/core/messageCatalog.d.ts +113 -0
  151. package/lib/i18n/core/messageCatalog.d.ts.map +1 -0
  152. package/lib/i18n/core/messageCatalog.js +135 -0
  153. package/lib/i18n/core/messageCatalog.js.map +1 -0
  154. package/lib/i18n/core/messageValues.d.ts +3 -0
  155. package/lib/i18n/core/messageValues.d.ts.map +1 -0
  156. package/lib/i18n/core/messageValues.js +2 -0
  157. package/lib/i18n/core/messageValues.js.map +1 -0
  158. package/lib/i18n/core/session.d.ts +51 -0
  159. package/lib/i18n/core/session.d.ts.map +1 -0
  160. package/lib/i18n/core/session.js +64 -0
  161. package/lib/i18n/core/session.js.map +1 -0
  162. package/lib/i18n/core/translateSetup.d.ts +28 -0
  163. package/lib/i18n/core/translateSetup.d.ts.map +1 -0
  164. package/lib/i18n/core/translateSetup.js +30 -0
  165. package/lib/i18n/core/translateSetup.js.map +1 -0
  166. package/lib/i18n/ui/LanguageSelect.d.ts +37 -0
  167. package/lib/i18n/ui/LanguageSelect.d.ts.map +1 -0
  168. package/lib/i18n/ui/LanguageSelect.js +27 -0
  169. package/lib/i18n/ui/LanguageSelect.js.map +1 -0
  170. package/lib/i18n/ui/index.d.ts +6 -0
  171. package/lib/i18n/ui/index.d.ts.map +1 -0
  172. package/lib/i18n/ui/index.js +4 -0
  173. package/lib/i18n/ui/index.js.map +1 -0
  174. package/lib/i18n/ui/useLanguage.d.ts +23 -0
  175. package/lib/i18n/ui/useLanguage.d.ts.map +1 -0
  176. package/lib/i18n/ui/useLanguage.js +36 -0
  177. package/lib/i18n/ui/useLanguage.js.map +1 -0
  178. package/lib/i18n/ui/useT.d.ts +47 -0
  179. package/lib/i18n/ui/useT.d.ts.map +1 -0
  180. package/lib/i18n/ui/useT.js +51 -0
  181. package/lib/i18n/ui/useT.js.map +1 -0
  182. package/lib/locales/de.json +442 -0
  183. package/lib/locales/en.json +442 -0
  184. package/lib/locales/es.json +442 -0
  185. package/lib/locales/fr.json +442 -0
  186. package/lib/molecule3d/core/gestures.d.ts +10 -0
  187. package/lib/molecule3d/core/gestures.d.ts.map +1 -1
  188. package/lib/molecule3d/core/gestures.js +39 -7
  189. package/lib/molecule3d/core/gestures.js.map +1 -1
  190. package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
  191. package/lib/molecule3d/ui/Molecule3DExport.js +3 -1
  192. package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
  193. package/lib/molecule3d/ui/Molecule3DHelp.d.ts.map +1 -1
  194. package/lib/molecule3d/ui/Molecule3DHelp.js +7 -2
  195. package/lib/molecule3d/ui/Molecule3DHelp.js.map +1 -1
  196. package/lib/molecule3d/ui/Molecule3DOptions.d.ts.map +1 -1
  197. package/lib/molecule3d/ui/Molecule3DOptions.js +9 -9
  198. package/lib/molecule3d/ui/Molecule3DOptions.js.map +1 -1
  199. package/lib/molecule3d/ui/Molecule3DSurfaceColors.d.ts.map +1 -1
  200. package/lib/molecule3d/ui/Molecule3DSurfaceColors.js +8 -8
  201. package/lib/molecule3d/ui/Molecule3DSurfaceColors.js.map +1 -1
  202. package/lib/molecule3d/ui/Molecule3DToolbar.d.ts.map +1 -1
  203. package/lib/molecule3d/ui/Molecule3DToolbar.js +15 -10
  204. package/lib/molecule3d/ui/Molecule3DToolbar.js.map +1 -1
  205. package/lib/molecule3d/ui/MoleculeViewer3D.d.ts.map +1 -1
  206. package/lib/molecule3d/ui/MoleculeViewer3D.js +6 -3
  207. package/lib/molecule3d/ui/MoleculeViewer3D.js.map +1 -1
  208. package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts.map +1 -1
  209. package/lib/orbital/ui/AtomicOrbitalCanvas.js +3 -1
  210. package/lib/orbital/ui/AtomicOrbitalCanvas.js.map +1 -1
  211. package/lib/orbital/ui/AtomicOrbitalControls.d.ts.map +1 -1
  212. package/lib/orbital/ui/AtomicOrbitalControls.js +12 -15
  213. package/lib/orbital/ui/AtomicOrbitalControls.js.map +1 -1
  214. package/lib/orbital/ui/AtomicOrbitalViewer.d.ts.map +1 -1
  215. package/lib/orbital/ui/AtomicOrbitalViewer.js +6 -3
  216. package/lib/orbital/ui/AtomicOrbitalViewer.js.map +1 -1
  217. package/lib/orbital/ui/capability.d.ts +4 -0
  218. package/lib/orbital/ui/capability.d.ts.map +1 -1
  219. package/lib/orbital/ui/capability.js +4 -0
  220. package/lib/orbital/ui/capability.js.map +1 -1
  221. package/lib/overlay/ui/OverlayBar.d.ts +1 -1
  222. package/lib/overlay/ui/OverlayBar.d.ts.map +1 -1
  223. package/lib/overlay/ui/OverlayBar.js +6 -3
  224. package/lib/overlay/ui/OverlayBar.js.map +1 -1
  225. package/lib/overlay/ui/OverlayInfo.d.ts +1 -1
  226. package/lib/overlay/ui/OverlayInfo.d.ts.map +1 -1
  227. package/lib/overlay/ui/OverlayInfo.js +5 -2
  228. package/lib/overlay/ui/OverlayInfo.js.map +1 -1
  229. package/lib/overlay/ui/OverlayPanel.d.ts +9 -2
  230. package/lib/overlay/ui/OverlayPanel.d.ts.map +1 -1
  231. package/lib/overlay/ui/OverlayPanel.js +5 -11
  232. package/lib/overlay/ui/OverlayPanel.js.map +1 -1
  233. package/lib/overlay/ui/OverlayReadout.d.ts.map +1 -1
  234. package/lib/overlay/ui/OverlayReadout.js +3 -1
  235. package/lib/overlay/ui/OverlayReadout.js.map +1 -1
  236. package/lib/pedagogy/ui/ExerciseActions.d.ts +1 -1
  237. package/lib/pedagogy/ui/ExerciseActions.d.ts.map +1 -1
  238. package/lib/pedagogy/ui/ExerciseActions.js +9 -2
  239. package/lib/pedagogy/ui/ExerciseActions.js.map +1 -1
  240. package/lib/pedagogy/ui/ExerciseProgressHeader.d.ts +1 -1
  241. package/lib/pedagogy/ui/ExerciseProgressHeader.d.ts.map +1 -1
  242. package/lib/pedagogy/ui/ExerciseProgressHeader.js +9 -5
  243. package/lib/pedagogy/ui/ExerciseProgressHeader.js.map +1 -1
  244. package/lib/pedagogy/ui/GlossaryIndex.d.ts +1 -1
  245. package/lib/pedagogy/ui/GlossaryIndex.d.ts.map +1 -1
  246. package/lib/pedagogy/ui/GlossaryIndex.js +6 -8
  247. package/lib/pedagogy/ui/GlossaryIndex.js.map +1 -1
  248. package/lib/pedagogy/ui/HintLadder.d.ts +2 -2
  249. package/lib/pedagogy/ui/HintLadder.d.ts.map +1 -1
  250. package/lib/pedagogy/ui/HintLadder.js +8 -2
  251. package/lib/pedagogy/ui/HintLadder.js.map +1 -1
  252. package/lib/pedagogy/ui/SyntaxTooltip.d.ts +4 -4
  253. package/lib/pedagogy/ui/SyntaxTooltip.d.ts.map +1 -1
  254. package/lib/pedagogy/ui/SyntaxTooltip.js +4 -2
  255. package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
  256. package/lib/pedagogy/ui/TutorialStepStrip.d.ts.map +1 -1
  257. package/lib/pedagogy/ui/TutorialStepStrip.js +7 -2
  258. package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
  259. package/lib/periodic/ui/CategoryLegend.d.ts.map +1 -1
  260. package/lib/periodic/ui/CategoryLegend.js +3 -1
  261. package/lib/periodic/ui/CategoryLegend.js.map +1 -1
  262. package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
  263. package/lib/periodic/ui/ElementCell.js +7 -1
  264. package/lib/periodic/ui/ElementCell.js.map +1 -1
  265. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  266. package/lib/periodic/ui/PeriodicTable.js +3 -1
  267. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  268. package/lib/periodic/ui/PeriodicTableChrome.d.ts.map +1 -1
  269. package/lib/periodic/ui/PeriodicTableChrome.js +4 -2
  270. package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
  271. package/lib/share/ui/ShareButton.d.ts +2 -2
  272. package/lib/share/ui/ShareButton.d.ts.map +1 -1
  273. package/lib/share/ui/ShareButton.js +5 -2
  274. package/lib/share/ui/ShareButton.js.map +1 -1
  275. package/lib/share/ui/ShareConfigOptions.d.ts.map +1 -1
  276. package/lib/share/ui/ShareConfigOptions.js +4 -2
  277. package/lib/share/ui/ShareConfigOptions.js.map +1 -1
  278. package/lib/share/ui/ShareDialog.d.ts.map +1 -1
  279. package/lib/share/ui/ShareDialog.js +3 -1
  280. package/lib/share/ui/ShareDialog.js.map +1 -1
  281. package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
  282. package/lib/share/ui/ShareDialogContent.js +10 -1
  283. package/lib/share/ui/ShareDialogContent.js.map +1 -1
  284. package/lib/share/ui/SharePresetTabs.d.ts.map +1 -1
  285. package/lib/share/ui/SharePresetTabs.js +3 -1
  286. package/lib/share/ui/SharePresetTabs.js.map +1 -1
  287. package/lib/slides/ui/BackToSlides.d.ts.map +1 -1
  288. package/lib/slides/ui/BackToSlides.js +4 -2
  289. package/lib/slides/ui/BackToSlides.js.map +1 -1
  290. package/lib/slides/ui/EmbedSlide.d.ts.map +1 -1
  291. package/lib/slides/ui/EmbedSlide.js +3 -1
  292. package/lib/slides/ui/EmbedSlide.js.map +1 -1
  293. package/lib/slides/ui/Slide.d.ts.map +1 -1
  294. package/lib/slides/ui/Slide.js +3 -1
  295. package/lib/slides/ui/Slide.js.map +1 -1
  296. package/lib/slides/ui/SlideshowBar.d.ts.map +1 -1
  297. package/lib/slides/ui/SlideshowBar.js +5 -3
  298. package/lib/slides/ui/SlideshowBar.js.map +1 -1
  299. package/lib/slides/ui/TalkList.d.ts +1 -1
  300. package/lib/slides/ui/TalkList.d.ts.map +1 -1
  301. package/lib/slides/ui/TalkList.js +4 -2
  302. package/lib/slides/ui/TalkList.js.map +1 -1
  303. package/lib/structure/core/editorGuide.d.ts +14 -0
  304. package/lib/structure/core/editorGuide.d.ts.map +1 -1
  305. package/lib/structure/core/editorGuide.js +117 -19
  306. package/lib/structure/core/editorGuide.js.map +1 -1
  307. package/lib/structure/core/editorToolbar.d.ts +3 -1
  308. package/lib/structure/core/editorToolbar.d.ts.map +1 -1
  309. package/lib/structure/core/editorToolbar.js +44 -23
  310. package/lib/structure/core/editorToolbar.js.map +1 -1
  311. package/lib/structure/ui/EditorHelpButton.d.ts.map +1 -1
  312. package/lib/structure/ui/EditorHelpButton.js +3 -2
  313. package/lib/structure/ui/EditorHelpButton.js.map +1 -1
  314. package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
  315. package/lib/structure/ui/StructureEditor.js +3 -1
  316. package/lib/structure/ui/StructureEditor.js.map +1 -1
  317. package/lib/structure/ui/StructureEditorHelp.d.ts.map +1 -1
  318. package/lib/structure/ui/StructureEditorHelp.js +12 -3
  319. package/lib/structure/ui/StructureEditorHelp.js.map +1 -1
  320. package/lib/structure/ui/ToolbarTooltip.d.ts.map +1 -1
  321. package/lib/structure/ui/ToolbarTooltip.js +5 -3
  322. package/lib/structure/ui/ToolbarTooltip.js.map +1 -1
  323. package/lib/translate/core/index.d.ts +1 -1
  324. package/lib/translate/core/index.d.ts.map +1 -1
  325. package/lib/translate/core/start.d.ts +35 -2
  326. package/lib/translate/core/start.d.ts.map +1 -1
  327. package/lib/translate/core/start.js +23 -4
  328. package/lib/translate/core/start.js.map +1 -1
  329. package/lib/ui.d.ts +1 -0
  330. package/lib/ui.d.ts.map +1 -1
  331. package/lib/ui.js +1 -0
  332. package/lib/ui.js.map +1 -1
  333. package/package.json +4 -2
  334. package/src/about/ui/AboutBuild.tsx +23 -16
  335. package/src/about/ui/AboutCitations.tsx +3 -1
  336. package/src/about/ui/AboutPage.tsx +38 -12
  337. package/src/about/ui/AboutProvidedBy.tsx +13 -3
  338. package/src/capsule/ui/Capsule.tsx +48 -0
  339. package/src/capsule/ui/CapsuleFilter.tsx +38 -114
  340. package/src/capsule/ui/capsuleFilterCapsules.tsx +110 -0
  341. package/src/chart/ui/ChartTrackingLayer.tsx +4 -2
  342. package/src/chrome/ui/SiteHeader.tsx +5 -3
  343. package/src/citation/ui/CitationMenu.tsx +9 -5
  344. package/src/citation/ui/CiteButton.tsx +4 -2
  345. package/src/citation/ui/CopyFormatEntries.tsx +3 -1
  346. package/src/citation/ui/WorkEntry.tsx +5 -3
  347. package/src/citation/ui/entries.tsx +9 -2
  348. package/src/citation/ui/useCopyFeedback.ts +11 -4
  349. package/src/clipboard/ui/ClickToCopy.tsx +15 -5
  350. package/src/clipboard/ui/CopyButton.tsx +14 -10
  351. package/src/color/ui/ColorScaleEditor.tsx +17 -15
  352. package/src/color/ui/ColorScaleEntry.tsx +7 -3
  353. package/src/color/ui/ColorScaleLegend.tsx +7 -1
  354. package/src/color/ui/ColorScaleSelect.tsx +12 -6
  355. package/src/confirm/ui/ConfirmButton.tsx +9 -11
  356. package/src/core.ts +1 -0
  357. package/src/credits/ui/CreditsList.tsx +5 -1
  358. package/src/delimited/ui/DelimitedTextDialog.tsx +10 -10
  359. package/src/delimited/ui/DelimitedTextPanel.tsx +19 -12
  360. package/src/download/ui/FigureDownload.tsx +7 -5
  361. package/src/download/ui/FigureDownloadPanel.tsx +31 -25
  362. package/src/ecosystem/core/sites.ts +4 -2
  363. package/src/ecosystem/ui/EcosystemButton.tsx +5 -3
  364. package/src/ecosystem/ui/EcosystemLinks.tsx +10 -10
  365. package/src/ecosystem/ui/EcosystemMenu.tsx +3 -1
  366. package/src/ecosystem/ui/SiteGroupSection.tsx +6 -2
  367. package/src/ecosystem/ui/SiteTile.tsx +6 -2
  368. package/src/ecosystem/ui/glyphs.tsx +16 -22
  369. package/src/error/ui/ErrorFallback.tsx +13 -12
  370. package/src/help/ui/HelpBody.tsx +4 -2
  371. package/src/help/ui/HelpIcon.tsx +6 -3
  372. package/src/help/ui/HelpToolbarButton.tsx +4 -1
  373. package/src/help/ui/helpName.ts +10 -2
  374. package/src/i18n/core/chromeCatalog.ts +35 -0
  375. package/src/i18n/core/index.ts +26 -0
  376. package/src/i18n/core/languages.ts +46 -0
  377. package/src/i18n/core/messageCatalog.ts +202 -0
  378. package/src/i18n/core/messageValues.ts +2 -0
  379. package/src/i18n/core/session.ts +82 -0
  380. package/src/i18n/core/translateSetup.ts +47 -0
  381. package/src/i18n/ui/LanguageSelect.tsx +72 -0
  382. package/src/i18n/ui/index.ts +9 -0
  383. package/src/i18n/ui/useLanguage.ts +48 -0
  384. package/src/i18n/ui/useT.ts +90 -0
  385. package/src/locales/de.json +442 -0
  386. package/src/locales/en.json +442 -0
  387. package/src/locales/es.json +442 -0
  388. package/src/locales/fr.json +442 -0
  389. package/src/molecule3d/core/gestures.ts +44 -7
  390. package/src/molecule3d/ui/Molecule3DExport.tsx +3 -1
  391. package/src/molecule3d/ui/Molecule3DHelp.tsx +17 -6
  392. package/src/molecule3d/ui/Molecule3DOptions.tsx +13 -13
  393. package/src/molecule3d/ui/Molecule3DSurfaceColors.tsx +16 -13
  394. package/src/molecule3d/ui/Molecule3DToolbar.tsx +29 -18
  395. package/src/molecule3d/ui/MoleculeViewer3D.tsx +13 -8
  396. package/src/orbital/ui/AtomicOrbitalCanvas.tsx +3 -1
  397. package/src/orbital/ui/AtomicOrbitalControls.tsx +15 -20
  398. package/src/orbital/ui/AtomicOrbitalViewer.tsx +13 -13
  399. package/src/orbital/ui/capability.ts +10 -0
  400. package/src/overlay/ui/OverlayBar.tsx +7 -4
  401. package/src/overlay/ui/OverlayInfo.tsx +6 -3
  402. package/src/overlay/ui/OverlayPanel.tsx +10 -7
  403. package/src/overlay/ui/OverlayReadout.tsx +3 -1
  404. package/src/pedagogy/ui/ExerciseActions.tsx +16 -6
  405. package/src/pedagogy/ui/ExerciseProgressHeader.tsx +13 -11
  406. package/src/pedagogy/ui/GlossaryIndex.tsx +11 -12
  407. package/src/pedagogy/ui/HintLadder.tsx +15 -12
  408. package/src/pedagogy/ui/SyntaxTooltip.tsx +9 -7
  409. package/src/pedagogy/ui/TutorialStepStrip.tsx +8 -3
  410. package/src/periodic/ui/CategoryLegend.tsx +6 -1
  411. package/src/periodic/ui/ElementCell.tsx +7 -1
  412. package/src/periodic/ui/PeriodicTable.tsx +3 -1
  413. package/src/periodic/ui/PeriodicTableChrome.tsx +4 -2
  414. package/src/share/ui/ShareButton.tsx +8 -5
  415. package/src/share/ui/ShareConfigOptions.tsx +6 -7
  416. package/src/share/ui/ShareDialog.tsx +3 -1
  417. package/src/share/ui/ShareDialogContent.tsx +20 -10
  418. package/src/share/ui/SharePresetTabs.tsx +3 -1
  419. package/src/slides/ui/BackToSlides.tsx +4 -2
  420. package/src/slides/ui/EmbedSlide.tsx +3 -3
  421. package/src/slides/ui/Slide.tsx +3 -1
  422. package/src/slides/ui/SlideshowBar.tsx +12 -4
  423. package/src/slides/ui/TalkList.tsx +8 -10
  424. package/src/structure/core/editorGuide.ts +126 -19
  425. package/src/structure/core/editorToolbar.ts +52 -24
  426. package/src/structure/ui/EditorHelpButton.tsx +9 -4
  427. package/src/structure/ui/StructureEditor.tsx +7 -1
  428. package/src/structure/ui/StructureEditorHelp.tsx +34 -11
  429. package/src/structure/ui/ToolbarTooltip.tsx +8 -4
  430. package/src/translate/core/index.ts +1 -1
  431. package/src/translate/core/start.ts +65 -3
  432. package/src/ui.ts +1 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The text this library puts on the page.
3
+ *
4
+ * Every site of the family shows the same header, footer, Tools menu, Cite
5
+ * menu, share dialog and About page, so those words are translated once here
6
+ * rather than re-typed into eighteen catalogs of their own: one merged pull
7
+ * request writes every site in a new language.
8
+ */
9
+
10
+ import en from '../../locales/en.json' with { type: 'json' };
11
+
12
+ import { MessageCatalog } from './messageCatalog.ts';
13
+
14
+ /** The name the page knows the chrome's own catalog by. */
15
+ export const CHROME_CATALOG_ID = 'react-cheminfo';
16
+
17
+ /** Every key the chrome declares. */
18
+ export type ChromeKey = keyof typeof en;
19
+
20
+ /**
21
+ * The chrome's messages. English is part of the page because it is what a
22
+ * missing message falls back to; the other three arrive as a chunk of their
23
+ * own, so a site read only in English downloads none of them.
24
+ */
25
+ export const CHROME_CATALOG = new MessageCatalog<ChromeKey>({
26
+ id: CHROME_CATALOG_ID,
27
+ repository: 'cheminfo/react-cheminfo',
28
+ directory: 'src/locales',
29
+ source: en,
30
+ translations: {
31
+ fr: () => import('../../locales/fr.json', { with: { type: 'json' } }),
32
+ de: () => import('../../locales/de.json', { with: { type: 'json' } }),
33
+ es: () => import('../../locales/es.json', { with: { type: 'json' } }),
34
+ },
35
+ });
@@ -0,0 +1,26 @@
1
+ export type { ChromeKey } from './chromeCatalog.ts';
2
+ export { CHROME_CATALOG, CHROME_CATALOG_ID } from './chromeCatalog.ts';
3
+ export type { Language } from './languages.ts';
4
+ export {
5
+ DEFAULT_LANGUAGE,
6
+ LANGUAGES,
7
+ LANGUAGE_LABELS,
8
+ isLanguage,
9
+ } from './languages.ts';
10
+ export type {
11
+ LooseKey,
12
+ MessageCatalogOptions,
13
+ MessagesLoader,
14
+ TranslateOptions,
15
+ } from './messageCatalog.ts';
16
+ export { MessageCatalog } from './messageCatalog.ts';
17
+ export type { MessageValues } from './messageValues.ts';
18
+ export type { MessageSession } from './session.ts';
19
+ export {
20
+ messageSession,
21
+ messagesVersion,
22
+ setMessageSession,
23
+ subscribeToMessages,
24
+ } from './session.ts';
25
+ export type { LoadedCatalogs } from './translateSetup.ts';
26
+ export { loadCatalogs } from './translateSetup.ts';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The languages the family speaks.
3
+ *
4
+ * Four, decided together rather than site by site: a visitor who switches to
5
+ * German on one tool and follows a link to the next must land in German there
6
+ * too, and a list that differs between two sites makes that impossible.
7
+ */
8
+
9
+ /** Every language a site of the family is written in. */
10
+ export const LANGUAGES = ['en', 'fr', 'de', 'es'] as const;
11
+
12
+ /** One of {@link LANGUAGES}. */
13
+ export type Language = (typeof LANGUAGES)[number];
14
+
15
+ /**
16
+ * The language the catalogs are written in first, and the one every site opens
17
+ * in: an address naming it carries nothing, and a message a translation does
18
+ * not hold falls back to it.
19
+ */
20
+ export const DEFAULT_LANGUAGE: Language = 'en';
21
+
22
+ /**
23
+ * What a language switch writes behind each entry.
24
+ *
25
+ * A language names itself, in its own words: `Deutsch`, never `German`. A
26
+ * reader looking for their own language recognises it written their way, and
27
+ * would have to know English to find it written the other.
28
+ */
29
+ export const LANGUAGE_LABELS: Record<Language, string> = {
30
+ en: 'English',
31
+ fr: 'Français',
32
+ de: 'Deutsch',
33
+ es: 'Español',
34
+ };
35
+
36
+ /**
37
+ * Whether a string is one of the languages the family speaks.
38
+ * @param value - Candidate tag, typically read off an address.
39
+ * @returns True when a catalog can be written in it.
40
+ */
41
+ export function isLanguage(value: string): value is Language {
42
+ for (const language of LANGUAGES) {
43
+ if (language === value) return true;
44
+ }
45
+ return false;
46
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * One directory of messages, and the reading of it.
3
+ *
4
+ * A catalog is `en.json` — the source of truth — with one file per language
5
+ * beside it. English is part of the page, because it is what every missing
6
+ * message falls back to; the others are fetched when a visitor asks for them,
7
+ * so a site nobody reads in German never downloads the German.
8
+ *
9
+ * Only the plain `{name}` argument of ICU MessageFormat is filled here. A
10
+ * plural or a select needs the formatter itself, which is 40 kB an ordinary
11
+ * visit must not pay for — `check-messages` refuses one in a catalog, and the
12
+ * page is written with a key per branch instead.
13
+ */
14
+
15
+ import type { CatalogSource, Messages } from '../../translate/core/catalog.ts';
16
+ import { ownMessage } from '../../translate/core/catalog.ts';
17
+
18
+ import type { Language } from './languages.ts';
19
+ import { DEFAULT_LANGUAGE } from './languages.ts';
20
+ import type { MessageValues } from './messageValues.ts';
21
+ import { messageSession, notifyMessages } from './session.ts';
22
+
23
+ /** A language's messages, fetched only once a visitor asks for that language. */
24
+ export type MessagesLoader = () => Promise<{ default: Messages }>;
25
+
26
+ /**
27
+ * A key the catalog declares, or any other string.
28
+ *
29
+ * Many keys are built from the data — `site.surge.tagline`, `element.Fe.name`
30
+ * — so a row added before the catalogs have caught up must read as itself
31
+ * rather than fail to compile.
32
+ */
33
+ export type LooseKey<TKey extends string> =
34
+ TKey | (string & Record<never, never>);
35
+
36
+ /** How a key is resolved when no catalog carries it, and with what. */
37
+ export interface TranslateOptions {
38
+ /**
39
+ * What to answer when neither the language nor English carries the key.
40
+ * @default the key itself
41
+ */
42
+ fallback?: string;
43
+ /**
44
+ * What each `{placeholder}` of the message stands for.
45
+ * @default undefined
46
+ */
47
+ values?: MessageValues;
48
+ }
49
+
50
+ /** How a catalog is declared. */
51
+ export interface MessageCatalogOptions<TKey extends string> {
52
+ /**
53
+ * The name the page knows the catalog by, unique on the page, e.g.
54
+ * `react-cheminfo` or `surge.cheminfo.org`.
55
+ */
56
+ id: string;
57
+ /** The GitHub repository the files live in, written `owner/repo`. */
58
+ repository: string;
59
+ /**
60
+ * The directory holding the locale files, relative to the repository root,
61
+ * e.g. `src/locales`.
62
+ */
63
+ directory: string;
64
+ /** The English messages, which are the keys the catalog declares. */
65
+ source: Record<TKey, string>;
66
+ /**
67
+ * The other languages: the messages themselves, or the import that fetches
68
+ * them. A language left out simply reads as English.
69
+ * @default {}
70
+ */
71
+ translations?: Partial<Record<Language, Messages | MessagesLoader>>;
72
+ }
73
+
74
+ /**
75
+ * The messages of one repository, read in whichever language the page is
76
+ * written in.
77
+ */
78
+ export class MessageCatalog<TKey extends string = string> {
79
+ /** The name the page knows this catalog by. */
80
+ public readonly id: string;
81
+ /** What a translator's session and the overlay need to place an edit. */
82
+ public readonly source: CatalogSource;
83
+
84
+ readonly #loaded = new Map<Language, Messages>();
85
+ readonly #loaders = new Map<Language, MessagesLoader>();
86
+ readonly #pending = new Map<Language, Promise<Messages>>();
87
+
88
+ /**
89
+ * Declare a catalog.
90
+ * @param options - See {@link MessageCatalogOptions}.
91
+ */
92
+ public constructor(options: MessageCatalogOptions<TKey>) {
93
+ const { id, repository, directory, source, translations = {} } = options;
94
+ this.id = id;
95
+ this.source = { id, repository, directory, messages: source };
96
+ this.#loaded.set(DEFAULT_LANGUAGE, source);
97
+ for (const [language, messages] of Object.entries(translations)) {
98
+ if (messages === undefined) continue;
99
+ if (typeof messages === 'function') {
100
+ this.#loaders.set(language as Language, messages);
101
+ } else {
102
+ this.#loaded.set(language as Language, messages);
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * The languages this catalog carries, whether or not they are in memory.
109
+ * @returns The languages, English first.
110
+ */
111
+ public get languages(): Language[] {
112
+ return [...new Set([...this.#loaded.keys(), ...this.#loaders.keys()])];
113
+ }
114
+
115
+ /**
116
+ * The text a key carries in one language.
117
+ * @param key - Key to read.
118
+ * @param language - Language to read it in.
119
+ * @param options - See {@link TranslateOptions}.
120
+ * @returns The text.
121
+ */
122
+ public translate(
123
+ key: LooseKey<TKey>,
124
+ language: Language,
125
+ options: TranslateOptions = {},
126
+ ): string {
127
+ const { fallback = key, values } = options;
128
+ const english = this.source.messages;
129
+ const session = messageSession();
130
+ if (session !== null) {
131
+ // A key the catalog does not declare is not the translator's to edit:
132
+ // marking it would offer them a message that lives nowhere.
133
+ if (ownMessage(english, key) === undefined) return fill(fallback, values);
134
+ return session.format(this.id, key, values);
135
+ }
136
+ const translated = this.#loaded.get(language);
137
+ const message =
138
+ (translated === undefined ? undefined : ownMessage(translated, key)) ??
139
+ ownMessage(english, key) ??
140
+ fallback;
141
+ return fill(message, values);
142
+ }
143
+
144
+ /**
145
+ * The messages of a language, once they are in memory.
146
+ * @param language - Language asked for.
147
+ * @returns The messages, or an empty catalog while they are still coming.
148
+ */
149
+ public messages(language: Language): Messages {
150
+ return this.#loaded.get(language) ?? {};
151
+ }
152
+
153
+ /**
154
+ * Fetch a language's messages, if they are not in memory already.
155
+ * @param language - Language to read next.
156
+ * @returns Its messages.
157
+ */
158
+ public async load(language: Language): Promise<Messages> {
159
+ const loaded = this.#loaded.get(language);
160
+ if (loaded !== undefined) return loaded;
161
+ const pending = this.#pending.get(language);
162
+ if (pending !== undefined) return pending;
163
+ const loader = this.#loaders.get(language);
164
+ if (loader === undefined) return {};
165
+ const fetching = loader().then(
166
+ (module) => {
167
+ this.#loaded.set(language, module.default);
168
+ this.#pending.delete(language);
169
+ notifyMessages();
170
+ return module.default;
171
+ },
172
+ () => {
173
+ // A language that cannot be fetched is a page in English, never a
174
+ // page of raw keys: forget the attempt so a later render retries.
175
+ this.#pending.delete(language);
176
+ return {};
177
+ },
178
+ );
179
+ this.#pending.set(language, fetching);
180
+ return fetching;
181
+ }
182
+
183
+ /**
184
+ * Start fetching a language without waiting for it. Safe to call on every
185
+ * render: a language already in memory, or already on its way, costs
186
+ * nothing.
187
+ * @param language - Language the page is about to be written in.
188
+ */
189
+ public prefetch(language: Language): void {
190
+ if (this.#loaded.has(language) || this.#pending.has(language)) return;
191
+ void this.load(language);
192
+ }
193
+ }
194
+
195
+ const PLACEHOLDER = /\{(?<name>\w+)\}/g;
196
+
197
+ function fill(message: string, values: MessageValues | undefined): string {
198
+ if (values === undefined) return message;
199
+ return message.replaceAll(PLACEHOLDER, (match, name: string) =>
200
+ Object.hasOwn(values, name) ? String(values[name]) : match,
201
+ );
202
+ }
@@ -0,0 +1,2 @@
1
+ /** What a message's `{placeholder}`s stand for. */
2
+ export type MessageValues = Record<string, string | number>;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Where the page formats its messages from, and who is told when that changes.
3
+ *
4
+ * Ordinarily a message is read straight out of a catalog. While a translator
5
+ * has the page open, every message goes through their session instead — marked
6
+ * invisibly so the overlay can find it in the DOM, and answered with whatever
7
+ * they have typed rather than with what was published. Both the session and a
8
+ * language arriving late are changes the page has to redraw for, so they share
9
+ * one version counter and one set of listeners.
10
+ */
11
+
12
+ import type { MessageValues } from './messageValues.ts';
13
+
14
+ /**
15
+ * What a page in translate mode formats through — satisfied by
16
+ * `TranslateSession` from `react-cheminfo/translate`, which is never imported
17
+ * here: an ordinary visit must not load the ICU formatter behind it.
18
+ */
19
+ export interface MessageSession {
20
+ /** The message of a catalog, formatted and marked. */
21
+ format: (catalogId: string, key: string, values?: MessageValues) => string;
22
+ /** Be told whenever an edit is typed; returns the function that stops it. */
23
+ subscribe?: (listener: () => void) => () => void;
24
+ }
25
+
26
+ let session: MessageSession | null = null;
27
+ let stopListening: (() => void) | null = null;
28
+ let version = 0;
29
+ const listeners = new Set<() => void>();
30
+
31
+ /**
32
+ * Format every message through a translator's session from now on.
33
+ *
34
+ * While one is set it is the session's locale the page is written in, not the
35
+ * visitor's preference, and every message carries the marker the overlay finds
36
+ * it by.
37
+ * @param active - The session, or `null` to go back to the catalogs.
38
+ */
39
+ export function setMessageSession(active: MessageSession | null): void {
40
+ stopListening?.();
41
+ stopListening = null;
42
+ session = active;
43
+ if (active?.subscribe !== undefined) {
44
+ stopListening = active.subscribe(notifyMessages);
45
+ }
46
+ notifyMessages();
47
+ }
48
+
49
+ /**
50
+ * The session the page formats through, if a translator has it open.
51
+ * @returns The session, or `null`.
52
+ */
53
+ export function messageSession(): MessageSession | null {
54
+ return session;
55
+ }
56
+
57
+ /**
58
+ * How many times the text of the page has changed — a session set or dropped,
59
+ * an edit typed, a language finished loading.
60
+ * @returns The count, which only ever grows.
61
+ */
62
+ export function messagesVersion(): number {
63
+ return version;
64
+ }
65
+
66
+ /**
67
+ * Be told whenever the text of the page changes.
68
+ * @param listener - Called after each change.
69
+ * @returns The function that stops it.
70
+ */
71
+ export function subscribeToMessages(listener: () => void): () => void {
72
+ listeners.add(listener);
73
+ return () => {
74
+ listeners.delete(listener);
75
+ };
76
+ }
77
+
78
+ /** Say that the text of the page has changed. Never part of the public API. */
79
+ export function notifyMessages(): void {
80
+ version++;
81
+ for (const listener of listeners) listener();
82
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Handing a page's catalogs to a translator.
3
+ *
4
+ * `startTranslating` wants the catalogs and, beside them, whatever of the
5
+ * target language is already published — that is what tells the overlay which
6
+ * messages are still missing. The published messages of a language are exactly
7
+ * what a catalog fetches on demand, so they are gathered here rather than
8
+ * re-imported by every site.
9
+ */
10
+
11
+ import type { CatalogSource, Messages } from '../../translate/core/catalog.ts';
12
+
13
+ import { isLanguage } from './languages.ts';
14
+ import type { MessageCatalog } from './messageCatalog.ts';
15
+
16
+ /** What `startTranslating` is handed. */
17
+ export interface LoadedCatalogs {
18
+ /** The catalogs, as the session registers them. */
19
+ catalogs: CatalogSource[];
20
+ /** What is already published in the locale, by catalog id. */
21
+ translations: Record<string, Messages>;
22
+ }
23
+
24
+ /**
25
+ * Fetch what each catalog already publishes in a locale.
26
+ * @param catalogs - The catalogs the page renders from.
27
+ * @param locale - The locale being translated into, which may well be one the
28
+ * family does not speak yet — then nothing is published and every message is
29
+ * offered as missing.
30
+ * @returns See {@link LoadedCatalogs}.
31
+ */
32
+ export async function loadCatalogs(
33
+ catalogs: ReadonlyArray<MessageCatalog<string>>,
34
+ locale: string,
35
+ ): Promise<LoadedCatalogs> {
36
+ const language = isLanguage(locale) ? locale : undefined;
37
+ const translations: Record<string, Messages> = {};
38
+ if (language !== undefined) {
39
+ const loaded = await Promise.all(
40
+ catalogs.map(async (catalog) => catalog.load(language)),
41
+ );
42
+ for (const [index, catalog] of catalogs.entries()) {
43
+ translations[catalog.id] = loaded[index] ?? {};
44
+ }
45
+ }
46
+ return { catalogs: catalogs.map((catalog) => catalog.source), translations };
47
+ }
@@ -0,0 +1,72 @@
1
+ import type { ReactElement } from 'react';
2
+
3
+ import type { HeaderToggleOption } from '../../chrome/ui/HeaderToggle.tsx';
4
+ import { HeaderToggle } from '../../chrome/ui/HeaderToggle.tsx';
5
+ import type { Language } from '../core/languages.ts';
6
+ import { LANGUAGES, LANGUAGE_LABELS } from '../core/languages.ts';
7
+
8
+ import { useChromeT } from './useT.ts';
9
+
10
+ /** Props of {@link LanguageSelect}. */
11
+ export interface LanguageSelectProps {
12
+ /** The language the site is written in. */
13
+ value: Language;
14
+ /** Called with the language picked. */
15
+ onChange: (language: Language) => void;
16
+ /**
17
+ * The languages on offer, for a site that is not written in all four yet.
18
+ * @default LANGUAGES
19
+ */
20
+ languages?: readonly Language[];
21
+ /**
22
+ * Whether the bar has run out of room, so the row collapses to the language
23
+ * in force and a click advances to the next.
24
+ * @default false
25
+ */
26
+ compact?: boolean;
27
+ /**
28
+ * `data-testid` of the row; each button carries it suffixed by its tag.
29
+ * @default 'language-select'
30
+ */
31
+ testId?: string;
32
+ }
33
+
34
+ /**
35
+ * The language switch of the site header: the tags themselves, the one in
36
+ * force pressed.
37
+ *
38
+ * A language names itself — `FR` in the bar, `Français` behind it — because a
39
+ * reader looking for their own language would have to know English to find it
40
+ * written any other way.
41
+ * @param props - See {@link LanguageSelectProps}.
42
+ * @returns The row.
43
+ */
44
+ export function LanguageSelect(props: LanguageSelectProps): ReactElement {
45
+ const {
46
+ value,
47
+ onChange,
48
+ languages = LANGUAGES,
49
+ compact = false,
50
+ testId = 'language-select',
51
+ } = props;
52
+ const t = useChromeT();
53
+
54
+ return (
55
+ <HeaderToggle
56
+ label={t('language.label')}
57
+ options={languages.map(toOption)}
58
+ value={value}
59
+ onChange={onChange}
60
+ compact={compact}
61
+ testId={testId}
62
+ />
63
+ );
64
+ }
65
+
66
+ function toOption(language: Language): HeaderToggleOption<Language> {
67
+ return {
68
+ value: language,
69
+ label: language.toUpperCase(),
70
+ title: LANGUAGE_LABELS[language],
71
+ };
72
+ }
@@ -0,0 +1,9 @@
1
+ export type { LanguageSelectProps } from './LanguageSelect.tsx';
2
+ export { LanguageSelect } from './LanguageSelect.tsx';
3
+ export {
4
+ useIsTranslating,
5
+ useLanguage,
6
+ useMessagesVersion,
7
+ } from './useLanguage.ts';
8
+ export type { Translate } from './useT.ts';
9
+ export { translateMessage, useChromeT, useT } from './useT.ts';
@@ -0,0 +1,48 @@
1
+ import { useSyncExternalStore } from 'react';
2
+
3
+ import { useSiteLanguage } from '../../language/ui/siteLanguageContext.ts';
4
+ import type { Language } from '../core/languages.ts';
5
+ import { DEFAULT_LANGUAGE, isLanguage } from '../core/languages.ts';
6
+ import {
7
+ messageSession,
8
+ messagesVersion,
9
+ subscribeToMessages,
10
+ } from '../core/session.ts';
11
+
12
+ /**
13
+ * The language the page is written in.
14
+ *
15
+ * It is what the site declared with `SiteLanguage`, or what the address names
16
+ * when the site declared nothing — the same value every link out of the page
17
+ * carries, so the chrome is never written in a language the links deny.
18
+ * @returns The language.
19
+ */
20
+ export function useLanguage(): Language {
21
+ const declared = useSiteLanguage();
22
+ return declared !== undefined && isLanguage(declared)
23
+ ? declared
24
+ : DEFAULT_LANGUAGE;
25
+ }
26
+
27
+ /**
28
+ * Redraw whenever the text of the page changes — a language finished loading,
29
+ * a translator typed an edit, a session was set or dropped.
30
+ * @returns The version the page was drawn at.
31
+ */
32
+ export function useMessagesVersion(): number {
33
+ return useSyncExternalStore(
34
+ subscribeToMessages,
35
+ messagesVersion,
36
+ messagesVersion,
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Whether a translator has the page open, in which case the language switch
42
+ * says nothing about what is on show.
43
+ * @returns True in translate mode.
44
+ */
45
+ export function useIsTranslating(): boolean {
46
+ useMessagesVersion();
47
+ return messageSession() !== null;
48
+ }
@@ -0,0 +1,90 @@
1
+ import type { ChromeKey } from '../core/chromeCatalog.ts';
2
+ import { CHROME_CATALOG } from '../core/chromeCatalog.ts';
3
+ import type {
4
+ Language,
5
+ LooseKey,
6
+ MessageValues,
7
+ TranslateOptions,
8
+ } from '../core/index.ts';
9
+ import type { MessageCatalog } from '../core/messageCatalog.ts';
10
+
11
+ import { useLanguage, useMessagesVersion } from './useLanguage.ts';
12
+
13
+ /**
14
+ * What a component reads its text with.
15
+ *
16
+ * `t(key)` is a key the catalog declares, checked at compile time.
17
+ * `t.or(key, fallback)` is a key built from the data — a site's id, an
18
+ * element's symbol — which the catalog may not have caught up with yet; it
19
+ * reads as the fallback rather than as a raw key.
20
+ */
21
+ export type Translate<TKey extends string> = ((
22
+ key: LooseKey<TKey>,
23
+ values?: MessageValues,
24
+ ) => string) & {
25
+ or: (key: string, fallback: string, values?: MessageValues) => string;
26
+ };
27
+
28
+ /**
29
+ * The formatter of a catalog's text, in the language the page is written in.
30
+ *
31
+ * It is meant to be called while rendering, never kept in a module constant:
32
+ * the language and a translator's edits both change under the page, and only a
33
+ * render picks them up.
34
+ * @param catalog - The catalog to read, typed by its English keys.
35
+ * @returns See {@link Translate}.
36
+ */
37
+ export function useT<TKey extends string>(
38
+ catalog: MessageCatalog<TKey>,
39
+ ): Translate<TKey> {
40
+ const language = useLanguage();
41
+ useMessagesVersion();
42
+ catalog.prefetch(language);
43
+ return translator(catalog, language);
44
+ }
45
+
46
+ // Built outside the hook, because the `or` form is a property of the function
47
+ // itself and a render may not write to a value it has just read.
48
+ function translator<TKey extends string>(
49
+ catalog: MessageCatalog<TKey>,
50
+ language: Language,
51
+ ): Translate<TKey> {
52
+ const translate = (key: LooseKey<TKey>, values?: MessageValues) =>
53
+ catalog.translate(key, language, { values });
54
+ translate.or = (key: string, fallback: string, values?: MessageValues) =>
55
+ catalog.translate(key, language, { fallback, values });
56
+ return translate;
57
+ }
58
+
59
+ /**
60
+ * The formatter of this library's own text — the header, the Tools menu, the
61
+ * share dialog, the About page.
62
+ *
63
+ * A site reads it for a word it shows beside the chrome's own, so `Share` and
64
+ * `Cite` are written the same way everywhere.
65
+ * @returns `t(key, values)`, typed by the chrome's English catalog.
66
+ */
67
+ export function useChromeT(): Translate<ChromeKey> {
68
+ return useT(CHROME_CATALOG);
69
+ }
70
+
71
+ /**
72
+ * One message of a catalog, outside a component.
73
+ *
74
+ * Only for the rare place a hook cannot go — a label computed in a module
75
+ * helper, a message thrown from a worker. A component uses {@link useT}, which
76
+ * redraws when the language does.
77
+ * @param catalog - The catalog to read.
78
+ * @param key - Key to read.
79
+ * @param language - Language to read it in.
80
+ * @param options - See `TranslateOptions`.
81
+ * @returns The text.
82
+ */
83
+ export function translateMessage<TKey extends string>(
84
+ catalog: MessageCatalog<TKey>,
85
+ key: LooseKey<TKey>,
86
+ language: Language,
87
+ options?: TranslateOptions,
88
+ ): string {
89
+ return catalog.translate(key, language, options);
90
+ }