react-cheminfo 0.25.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.
- package/README.md +73 -2
- package/bin/check-messages.mjs +229 -0
- package/lib/about/ui/AboutBuild.d.ts.map +1 -1
- package/lib/about/ui/AboutBuild.js +15 -4
- package/lib/about/ui/AboutBuild.js.map +1 -1
- package/lib/about/ui/AboutCitations.d.ts.map +1 -1
- package/lib/about/ui/AboutCitations.js +3 -1
- package/lib/about/ui/AboutCitations.js.map +1 -1
- package/lib/about/ui/AboutPage.d.ts.map +1 -1
- package/lib/about/ui/AboutPage.js +9 -1
- package/lib/about/ui/AboutPage.js.map +1 -1
- package/lib/about/ui/AboutProvidedBy.d.ts.map +1 -1
- package/lib/about/ui/AboutProvidedBy.js +8 -3
- package/lib/about/ui/AboutProvidedBy.js.map +1 -1
- package/lib/capsule/ui/Capsule.d.ts +30 -0
- package/lib/capsule/ui/Capsule.d.ts.map +1 -0
- package/lib/capsule/ui/Capsule.js +12 -0
- package/lib/capsule/ui/Capsule.js.map +1 -0
- package/lib/capsule/ui/CapsuleFilter.d.ts +19 -3
- package/lib/capsule/ui/CapsuleFilter.d.ts.map +1 -1
- package/lib/capsule/ui/CapsuleFilter.js +14 -44
- package/lib/capsule/ui/CapsuleFilter.js.map +1 -1
- package/lib/capsule/ui/capsuleFilterCapsules.d.ts +15 -0
- package/lib/capsule/ui/capsuleFilterCapsules.d.ts.map +1 -0
- package/lib/capsule/ui/capsuleFilterCapsules.js +47 -0
- package/lib/capsule/ui/capsuleFilterCapsules.js.map +1 -0
- package/lib/chart/ui/ChartTrackingLayer.d.ts.map +1 -1
- package/lib/chart/ui/ChartTrackingLayer.js +4 -2
- package/lib/chart/ui/ChartTrackingLayer.js.map +1 -1
- package/lib/chrome/ui/SiteHeader.d.ts +1 -1
- package/lib/chrome/ui/SiteHeader.d.ts.map +1 -1
- package/lib/chrome/ui/SiteHeader.js +4 -2
- package/lib/chrome/ui/SiteHeader.js.map +1 -1
- package/lib/citation/ui/CitationMenu.d.ts +1 -1
- package/lib/citation/ui/CitationMenu.d.ts.map +1 -1
- package/lib/citation/ui/CitationMenu.js +7 -3
- package/lib/citation/ui/CitationMenu.js.map +1 -1
- package/lib/citation/ui/CiteButton.d.ts +1 -1
- package/lib/citation/ui/CiteButton.d.ts.map +1 -1
- package/lib/citation/ui/CiteButton.js +3 -1
- package/lib/citation/ui/CiteButton.js.map +1 -1
- package/lib/citation/ui/CopyFormatEntries.d.ts.map +1 -1
- package/lib/citation/ui/CopyFormatEntries.js +3 -1
- package/lib/citation/ui/CopyFormatEntries.js.map +1 -1
- package/lib/citation/ui/WorkEntry.d.ts.map +1 -1
- package/lib/citation/ui/WorkEntry.js +3 -1
- package/lib/citation/ui/WorkEntry.js.map +1 -1
- package/lib/citation/ui/entries.d.ts.map +1 -1
- package/lib/citation/ui/entries.js +8 -2
- package/lib/citation/ui/entries.js.map +1 -1
- package/lib/citation/ui/useCopyFeedback.d.ts.map +1 -1
- package/lib/citation/ui/useCopyFeedback.js +9 -4
- package/lib/citation/ui/useCopyFeedback.js.map +1 -1
- package/lib/clipboard/ui/ClickToCopy.d.ts.map +1 -1
- package/lib/clipboard/ui/ClickToCopy.js +12 -6
- package/lib/clipboard/ui/ClickToCopy.js.map +1 -1
- package/lib/clipboard/ui/CopyButton.d.ts +3 -3
- package/lib/clipboard/ui/CopyButton.d.ts.map +1 -1
- package/lib/clipboard/ui/CopyButton.js +7 -4
- package/lib/clipboard/ui/CopyButton.js.map +1 -1
- package/lib/color/ui/ColorScaleEditor.d.ts.map +1 -1
- package/lib/color/ui/ColorScaleEditor.js +15 -10
- package/lib/color/ui/ColorScaleEditor.js.map +1 -1
- package/lib/color/ui/ColorScaleEntry.d.ts.map +1 -1
- package/lib/color/ui/ColorScaleEntry.js +4 -2
- package/lib/color/ui/ColorScaleEntry.js.map +1 -1
- package/lib/color/ui/ColorScaleLegend.d.ts.map +1 -1
- package/lib/color/ui/ColorScaleLegend.js +7 -1
- package/lib/color/ui/ColorScaleLegend.js.map +1 -1
- package/lib/color/ui/ColorScaleSelect.d.ts.map +1 -1
- package/lib/color/ui/ColorScaleSelect.js +8 -4
- package/lib/color/ui/ColorScaleSelect.js.map +1 -1
- package/lib/confirm/ui/ConfirmButton.d.ts +2 -2
- package/lib/confirm/ui/ConfirmButton.d.ts.map +1 -1
- package/lib/confirm/ui/ConfirmButton.js +4 -2
- package/lib/confirm/ui/ConfirmButton.js.map +1 -1
- package/lib/core.d.ts +1 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +1 -0
- package/lib/core.js.map +1 -1
- package/lib/credits/ui/CreditsList.d.ts.map +1 -1
- package/lib/credits/ui/CreditsList.js +3 -1
- package/lib/credits/ui/CreditsList.js.map +1 -1
- package/lib/delimited/ui/DelimitedTextDialog.d.ts.map +1 -1
- package/lib/delimited/ui/DelimitedTextDialog.js +5 -2
- package/lib/delimited/ui/DelimitedTextDialog.js.map +1 -1
- package/lib/delimited/ui/DelimitedTextPanel.d.ts.map +1 -1
- package/lib/delimited/ui/DelimitedTextPanel.js +8 -8
- package/lib/delimited/ui/DelimitedTextPanel.js.map +1 -1
- package/lib/download/ui/FigureDownload.d.ts +2 -2
- package/lib/download/ui/FigureDownload.d.ts.map +1 -1
- package/lib/download/ui/FigureDownload.js +4 -2
- package/lib/download/ui/FigureDownload.js.map +1 -1
- package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
- package/lib/download/ui/FigureDownloadPanel.js +24 -18
- package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemButton.d.ts +1 -1
- package/lib/ecosystem/ui/EcosystemButton.d.ts.map +1 -1
- package/lib/ecosystem/ui/EcosystemButton.js +4 -2
- package/lib/ecosystem/ui/EcosystemButton.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemLinks.d.ts +1 -1
- package/lib/ecosystem/ui/EcosystemLinks.d.ts.map +1 -1
- package/lib/ecosystem/ui/EcosystemLinks.js +5 -2
- package/lib/ecosystem/ui/EcosystemLinks.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemMenu.d.ts.map +1 -1
- package/lib/ecosystem/ui/EcosystemMenu.js +3 -1
- package/lib/ecosystem/ui/EcosystemMenu.js.map +1 -1
- package/lib/ecosystem/ui/SiteGroupSection.d.ts.map +1 -1
- package/lib/ecosystem/ui/SiteGroupSection.js +3 -1
- package/lib/ecosystem/ui/SiteGroupSection.js.map +1 -1
- package/lib/ecosystem/ui/SiteTile.d.ts.map +1 -1
- package/lib/ecosystem/ui/SiteTile.js +3 -1
- package/lib/ecosystem/ui/SiteTile.js.map +1 -1
- package/lib/error/ui/ErrorFallback.d.ts +2 -2
- package/lib/error/ui/ErrorFallback.d.ts.map +1 -1
- package/lib/error/ui/ErrorFallback.js +4 -2
- package/lib/error/ui/ErrorFallback.js.map +1 -1
- package/lib/help/ui/HelpBody.d.ts.map +1 -1
- package/lib/help/ui/HelpBody.js +3 -1
- package/lib/help/ui/HelpBody.js.map +1 -1
- package/lib/help/ui/HelpIcon.d.ts +2 -1
- package/lib/help/ui/HelpIcon.d.ts.map +1 -1
- package/lib/help/ui/HelpIcon.js +4 -2
- package/lib/help/ui/HelpIcon.js.map +1 -1
- package/lib/help/ui/HelpToolbarButton.d.ts.map +1 -1
- package/lib/help/ui/HelpToolbarButton.js +3 -1
- package/lib/help/ui/HelpToolbarButton.js.map +1 -1
- package/lib/help/ui/helpName.d.ts +5 -1
- package/lib/help/ui/helpName.d.ts.map +1 -1
- package/lib/help/ui/helpName.js +4 -2
- package/lib/help/ui/helpName.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +21 -0
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -0
- package/lib/i18n/core/chromeCatalog.js +29 -0
- package/lib/i18n/core/chromeCatalog.js.map +1 -0
- package/lib/i18n/core/index.d.ts +12 -0
- package/lib/i18n/core/index.d.ts.map +1 -0
- package/lib/i18n/core/index.js +6 -0
- package/lib/i18n/core/index.js.map +1 -0
- package/lib/i18n/core/languages.d.ts +32 -0
- package/lib/i18n/core/languages.d.ts.map +1 -0
- package/lib/i18n/core/languages.js +41 -0
- package/lib/i18n/core/languages.js.map +1 -0
- package/lib/i18n/core/messageCatalog.d.ts +113 -0
- package/lib/i18n/core/messageCatalog.d.ts.map +1 -0
- package/lib/i18n/core/messageCatalog.js +135 -0
- package/lib/i18n/core/messageCatalog.js.map +1 -0
- package/lib/i18n/core/messageValues.d.ts +3 -0
- package/lib/i18n/core/messageValues.d.ts.map +1 -0
- package/lib/i18n/core/messageValues.js +2 -0
- package/lib/i18n/core/messageValues.js.map +1 -0
- package/lib/i18n/core/session.d.ts +51 -0
- package/lib/i18n/core/session.d.ts.map +1 -0
- package/lib/i18n/core/session.js +64 -0
- package/lib/i18n/core/session.js.map +1 -0
- package/lib/i18n/core/translateSetup.d.ts +28 -0
- package/lib/i18n/core/translateSetup.d.ts.map +1 -0
- package/lib/i18n/core/translateSetup.js +30 -0
- package/lib/i18n/core/translateSetup.js.map +1 -0
- package/lib/i18n/ui/LanguageSelect.d.ts +37 -0
- package/lib/i18n/ui/LanguageSelect.d.ts.map +1 -0
- package/lib/i18n/ui/LanguageSelect.js +27 -0
- package/lib/i18n/ui/LanguageSelect.js.map +1 -0
- package/lib/i18n/ui/index.d.ts +6 -0
- package/lib/i18n/ui/index.d.ts.map +1 -0
- package/lib/i18n/ui/index.js +4 -0
- package/lib/i18n/ui/index.js.map +1 -0
- package/lib/i18n/ui/useLanguage.d.ts +23 -0
- package/lib/i18n/ui/useLanguage.d.ts.map +1 -0
- package/lib/i18n/ui/useLanguage.js +36 -0
- package/lib/i18n/ui/useLanguage.js.map +1 -0
- package/lib/i18n/ui/useT.d.ts +47 -0
- package/lib/i18n/ui/useT.d.ts.map +1 -0
- package/lib/i18n/ui/useT.js +51 -0
- package/lib/i18n/ui/useT.js.map +1 -0
- package/lib/locales/de.json +442 -0
- package/lib/locales/en.json +442 -0
- package/lib/locales/es.json +442 -0
- package/lib/locales/fr.json +442 -0
- package/lib/molecule3d/core/gestures.d.ts +10 -0
- package/lib/molecule3d/core/gestures.d.ts.map +1 -1
- package/lib/molecule3d/core/gestures.js +39 -7
- package/lib/molecule3d/core/gestures.js.map +1 -1
- package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DExport.js +3 -1
- package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
- package/lib/molecule3d/ui/Molecule3DHelp.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DHelp.js +7 -2
- package/lib/molecule3d/ui/Molecule3DHelp.js.map +1 -1
- package/lib/molecule3d/ui/Molecule3DOptions.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DOptions.js +9 -9
- package/lib/molecule3d/ui/Molecule3DOptions.js.map +1 -1
- package/lib/molecule3d/ui/Molecule3DSurfaceColors.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DSurfaceColors.js +8 -8
- package/lib/molecule3d/ui/Molecule3DSurfaceColors.js.map +1 -1
- package/lib/molecule3d/ui/Molecule3DToolbar.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DToolbar.js +15 -10
- package/lib/molecule3d/ui/Molecule3DToolbar.js.map +1 -1
- package/lib/molecule3d/ui/MoleculeViewer3D.d.ts.map +1 -1
- package/lib/molecule3d/ui/MoleculeViewer3D.js +6 -3
- package/lib/molecule3d/ui/MoleculeViewer3D.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.js +3 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalControls.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalControls.js +12 -15
- package/lib/orbital/ui/AtomicOrbitalControls.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.js +6 -3
- package/lib/orbital/ui/AtomicOrbitalViewer.js.map +1 -1
- package/lib/orbital/ui/capability.d.ts +4 -0
- package/lib/orbital/ui/capability.d.ts.map +1 -1
- package/lib/orbital/ui/capability.js +4 -0
- package/lib/orbital/ui/capability.js.map +1 -1
- package/lib/overlay/ui/OverlayBar.d.ts +1 -1
- package/lib/overlay/ui/OverlayBar.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayBar.js +6 -3
- package/lib/overlay/ui/OverlayBar.js.map +1 -1
- package/lib/overlay/ui/OverlayInfo.d.ts +1 -1
- package/lib/overlay/ui/OverlayInfo.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayInfo.js +5 -2
- package/lib/overlay/ui/OverlayInfo.js.map +1 -1
- package/lib/overlay/ui/OverlayPanel.d.ts +9 -2
- package/lib/overlay/ui/OverlayPanel.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayPanel.js +5 -11
- package/lib/overlay/ui/OverlayPanel.js.map +1 -1
- package/lib/overlay/ui/OverlayReadout.d.ts.map +1 -1
- package/lib/overlay/ui/OverlayReadout.js +3 -1
- package/lib/overlay/ui/OverlayReadout.js.map +1 -1
- package/lib/pedagogy/ui/ExerciseActions.d.ts +1 -1
- package/lib/pedagogy/ui/ExerciseActions.d.ts.map +1 -1
- package/lib/pedagogy/ui/ExerciseActions.js +9 -2
- package/lib/pedagogy/ui/ExerciseActions.js.map +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.d.ts +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.d.ts.map +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.js +9 -5
- package/lib/pedagogy/ui/ExerciseProgressHeader.js.map +1 -1
- package/lib/pedagogy/ui/GlossaryIndex.d.ts +1 -1
- package/lib/pedagogy/ui/GlossaryIndex.d.ts.map +1 -1
- package/lib/pedagogy/ui/GlossaryIndex.js +6 -8
- package/lib/pedagogy/ui/GlossaryIndex.js.map +1 -1
- package/lib/pedagogy/ui/HintLadder.d.ts +2 -2
- package/lib/pedagogy/ui/HintLadder.d.ts.map +1 -1
- package/lib/pedagogy/ui/HintLadder.js +8 -2
- package/lib/pedagogy/ui/HintLadder.js.map +1 -1
- package/lib/pedagogy/ui/SyntaxTooltip.d.ts +4 -4
- package/lib/pedagogy/ui/SyntaxTooltip.d.ts.map +1 -1
- package/lib/pedagogy/ui/SyntaxTooltip.js +4 -2
- package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.d.ts.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.js +7 -2
- package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
- package/lib/periodic/ui/CategoryLegend.d.ts.map +1 -1
- package/lib/periodic/ui/CategoryLegend.js +3 -1
- package/lib/periodic/ui/CategoryLegend.js.map +1 -1
- package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
- package/lib/periodic/ui/ElementCell.js +7 -1
- package/lib/periodic/ui/ElementCell.js.map +1 -1
- package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
- package/lib/periodic/ui/PeriodicTable.js +3 -1
- package/lib/periodic/ui/PeriodicTable.js.map +1 -1
- package/lib/periodic/ui/PeriodicTableChrome.d.ts.map +1 -1
- package/lib/periodic/ui/PeriodicTableChrome.js +4 -2
- package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
- package/lib/share/ui/ShareButton.d.ts +2 -2
- package/lib/share/ui/ShareButton.d.ts.map +1 -1
- package/lib/share/ui/ShareButton.js +5 -2
- package/lib/share/ui/ShareButton.js.map +1 -1
- package/lib/share/ui/ShareConfigOptions.d.ts.map +1 -1
- package/lib/share/ui/ShareConfigOptions.js +4 -2
- package/lib/share/ui/ShareConfigOptions.js.map +1 -1
- package/lib/share/ui/ShareDialog.d.ts.map +1 -1
- package/lib/share/ui/ShareDialog.js +3 -1
- package/lib/share/ui/ShareDialog.js.map +1 -1
- package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
- package/lib/share/ui/ShareDialogContent.js +10 -1
- package/lib/share/ui/ShareDialogContent.js.map +1 -1
- package/lib/share/ui/SharePresetTabs.d.ts.map +1 -1
- package/lib/share/ui/SharePresetTabs.js +3 -1
- package/lib/share/ui/SharePresetTabs.js.map +1 -1
- package/lib/slides/ui/BackToSlides.d.ts.map +1 -1
- package/lib/slides/ui/BackToSlides.js +4 -2
- package/lib/slides/ui/BackToSlides.js.map +1 -1
- package/lib/slides/ui/EmbedSlide.d.ts.map +1 -1
- package/lib/slides/ui/EmbedSlide.js +3 -1
- package/lib/slides/ui/EmbedSlide.js.map +1 -1
- package/lib/slides/ui/Slide.d.ts.map +1 -1
- package/lib/slides/ui/Slide.js +3 -1
- package/lib/slides/ui/Slide.js.map +1 -1
- package/lib/slides/ui/SlideshowBar.d.ts.map +1 -1
- package/lib/slides/ui/SlideshowBar.js +5 -3
- package/lib/slides/ui/SlideshowBar.js.map +1 -1
- package/lib/slides/ui/TalkList.d.ts +1 -1
- package/lib/slides/ui/TalkList.d.ts.map +1 -1
- package/lib/slides/ui/TalkList.js +4 -2
- package/lib/slides/ui/TalkList.js.map +1 -1
- package/lib/structure/core/editorGuide.d.ts +14 -0
- package/lib/structure/core/editorGuide.d.ts.map +1 -1
- package/lib/structure/core/editorGuide.js +117 -19
- package/lib/structure/core/editorGuide.js.map +1 -1
- package/lib/structure/core/editorToolbar.d.ts +3 -1
- package/lib/structure/core/editorToolbar.d.ts.map +1 -1
- package/lib/structure/core/editorToolbar.js +44 -23
- package/lib/structure/core/editorToolbar.js.map +1 -1
- package/lib/structure/ui/EditorHelpButton.d.ts.map +1 -1
- package/lib/structure/ui/EditorHelpButton.js +3 -2
- package/lib/structure/ui/EditorHelpButton.js.map +1 -1
- package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
- package/lib/structure/ui/StructureEditor.js +3 -1
- package/lib/structure/ui/StructureEditor.js.map +1 -1
- package/lib/structure/ui/StructureEditorHelp.d.ts.map +1 -1
- package/lib/structure/ui/StructureEditorHelp.js +12 -3
- package/lib/structure/ui/StructureEditorHelp.js.map +1 -1
- package/lib/structure/ui/ToolbarTooltip.d.ts.map +1 -1
- package/lib/structure/ui/ToolbarTooltip.js +5 -3
- package/lib/structure/ui/ToolbarTooltip.js.map +1 -1
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/package.json +4 -2
- package/src/about/ui/AboutBuild.tsx +23 -16
- package/src/about/ui/AboutCitations.tsx +3 -1
- package/src/about/ui/AboutPage.tsx +38 -12
- package/src/about/ui/AboutProvidedBy.tsx +13 -3
- package/src/capsule/ui/Capsule.tsx +48 -0
- package/src/capsule/ui/CapsuleFilter.tsx +38 -114
- package/src/capsule/ui/capsuleFilterCapsules.tsx +110 -0
- package/src/chart/ui/ChartTrackingLayer.tsx +4 -2
- package/src/chrome/ui/SiteHeader.tsx +5 -3
- package/src/citation/ui/CitationMenu.tsx +9 -5
- package/src/citation/ui/CiteButton.tsx +4 -2
- package/src/citation/ui/CopyFormatEntries.tsx +3 -1
- package/src/citation/ui/WorkEntry.tsx +5 -3
- package/src/citation/ui/entries.tsx +9 -2
- package/src/citation/ui/useCopyFeedback.ts +11 -4
- package/src/clipboard/ui/ClickToCopy.tsx +15 -5
- package/src/clipboard/ui/CopyButton.tsx +14 -10
- package/src/color/ui/ColorScaleEditor.tsx +17 -15
- package/src/color/ui/ColorScaleEntry.tsx +7 -3
- package/src/color/ui/ColorScaleLegend.tsx +7 -1
- package/src/color/ui/ColorScaleSelect.tsx +12 -6
- package/src/confirm/ui/ConfirmButton.tsx +9 -11
- package/src/core.ts +1 -0
- package/src/credits/ui/CreditsList.tsx +5 -1
- package/src/delimited/ui/DelimitedTextDialog.tsx +10 -10
- package/src/delimited/ui/DelimitedTextPanel.tsx +19 -12
- package/src/download/ui/FigureDownload.tsx +7 -5
- package/src/download/ui/FigureDownloadPanel.tsx +31 -25
- package/src/ecosystem/ui/EcosystemButton.tsx +5 -3
- package/src/ecosystem/ui/EcosystemLinks.tsx +10 -10
- package/src/ecosystem/ui/EcosystemMenu.tsx +3 -1
- package/src/ecosystem/ui/SiteGroupSection.tsx +6 -2
- package/src/ecosystem/ui/SiteTile.tsx +6 -2
- package/src/error/ui/ErrorFallback.tsx +13 -12
- package/src/help/ui/HelpBody.tsx +4 -2
- package/src/help/ui/HelpIcon.tsx +6 -3
- package/src/help/ui/HelpToolbarButton.tsx +4 -1
- package/src/help/ui/helpName.ts +10 -2
- package/src/i18n/core/chromeCatalog.ts +35 -0
- package/src/i18n/core/index.ts +26 -0
- package/src/i18n/core/languages.ts +46 -0
- package/src/i18n/core/messageCatalog.ts +202 -0
- package/src/i18n/core/messageValues.ts +2 -0
- package/src/i18n/core/session.ts +82 -0
- package/src/i18n/core/translateSetup.ts +47 -0
- package/src/i18n/ui/LanguageSelect.tsx +72 -0
- package/src/i18n/ui/index.ts +9 -0
- package/src/i18n/ui/useLanguage.ts +48 -0
- package/src/i18n/ui/useT.ts +90 -0
- package/src/locales/de.json +442 -0
- package/src/locales/en.json +442 -0
- package/src/locales/es.json +442 -0
- package/src/locales/fr.json +442 -0
- package/src/molecule3d/core/gestures.ts +44 -7
- package/src/molecule3d/ui/Molecule3DExport.tsx +3 -1
- package/src/molecule3d/ui/Molecule3DHelp.tsx +17 -6
- package/src/molecule3d/ui/Molecule3DOptions.tsx +13 -13
- package/src/molecule3d/ui/Molecule3DSurfaceColors.tsx +16 -13
- package/src/molecule3d/ui/Molecule3DToolbar.tsx +29 -18
- package/src/molecule3d/ui/MoleculeViewer3D.tsx +13 -8
- package/src/orbital/ui/AtomicOrbitalCanvas.tsx +3 -1
- package/src/orbital/ui/AtomicOrbitalControls.tsx +15 -20
- package/src/orbital/ui/AtomicOrbitalViewer.tsx +13 -13
- package/src/orbital/ui/capability.ts +10 -0
- package/src/overlay/ui/OverlayBar.tsx +7 -4
- package/src/overlay/ui/OverlayInfo.tsx +6 -3
- package/src/overlay/ui/OverlayPanel.tsx +10 -7
- package/src/overlay/ui/OverlayReadout.tsx +3 -1
- package/src/pedagogy/ui/ExerciseActions.tsx +16 -6
- package/src/pedagogy/ui/ExerciseProgressHeader.tsx +13 -11
- package/src/pedagogy/ui/GlossaryIndex.tsx +11 -12
- package/src/pedagogy/ui/HintLadder.tsx +15 -12
- package/src/pedagogy/ui/SyntaxTooltip.tsx +9 -7
- package/src/pedagogy/ui/TutorialStepStrip.tsx +8 -3
- package/src/periodic/ui/CategoryLegend.tsx +6 -1
- package/src/periodic/ui/ElementCell.tsx +7 -1
- package/src/periodic/ui/PeriodicTable.tsx +3 -1
- package/src/periodic/ui/PeriodicTableChrome.tsx +4 -2
- package/src/share/ui/ShareButton.tsx +8 -5
- package/src/share/ui/ShareConfigOptions.tsx +6 -7
- package/src/share/ui/ShareDialog.tsx +3 -1
- package/src/share/ui/ShareDialogContent.tsx +20 -10
- package/src/share/ui/SharePresetTabs.tsx +3 -1
- package/src/slides/ui/BackToSlides.tsx +4 -2
- package/src/slides/ui/EmbedSlide.tsx +3 -3
- package/src/slides/ui/Slide.tsx +3 -1
- package/src/slides/ui/SlideshowBar.tsx +12 -4
- package/src/slides/ui/TalkList.tsx +8 -10
- package/src/structure/core/editorGuide.ts +126 -19
- package/src/structure/core/editorToolbar.ts +52 -24
- package/src/structure/ui/EditorHelpButton.tsx +9 -4
- package/src/structure/ui/StructureEditor.tsx +7 -1
- package/src/structure/ui/StructureEditorHelp.tsx +34 -11
- package/src/structure/ui/ToolbarTooltip.tsx +8 -4
- package/src/ui.ts +1 -0
|
@@ -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,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
|
+
}
|