waybetter 0.1.1 → 0.7.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 +139 -6
- package/bin/waybetter.mjs +43 -0
- package/build/runtime.mjs +105 -0
- package/cli/buildenv.mjs +130 -0
- package/cli/dev.mjs +97 -0
- package/cli/i18n.mjs +382 -0
- package/cli/manifests.mjs +383 -0
- package/cli/release.mjs +437 -0
- package/cli/releasecheck.mjs +15 -0
- package/cli/remote.mjs +260 -0
- package/cli/store.mjs +378 -0
- package/cli/strings.mjs +123 -0
- package/dist/runtime/column.d.ts +93 -0
- package/dist/runtime/column.js +451 -0
- package/dist/runtime/column.js.map +1 -0
- package/dist/runtime/commandReport.d.ts +19 -0
- package/dist/runtime/commandReport.js +47 -0
- package/dist/runtime/commandReport.js.map +1 -0
- package/dist/runtime/commands.d.ts +72 -0
- package/dist/runtime/commands.js +214 -0
- package/dist/runtime/commands.js.map +1 -0
- package/dist/runtime/devErrors.d.ts +16 -0
- package/dist/runtime/devErrors.js +30 -0
- package/dist/runtime/devErrors.js.map +1 -0
- package/dist/runtime/domHub.d.ts +21 -0
- package/dist/runtime/domHub.js +139 -4
- package/dist/runtime/domHub.js.map +1 -1
- package/dist/runtime/featureModules.d.ts +157 -0
- package/dist/runtime/featureModules.js +484 -0
- package/dist/runtime/featureModules.js.map +1 -0
- package/dist/runtime/featureTable.d.ts +144 -0
- package/dist/runtime/featureTable.js +223 -0
- package/dist/runtime/featureTable.js.map +1 -0
- package/dist/runtime/fonts.d.ts +22 -0
- package/dist/runtime/fonts.js +72 -0
- package/dist/runtime/fonts.js.map +1 -0
- package/dist/runtime/i18n.d.ts +138 -0
- package/dist/runtime/i18n.js +614 -0
- package/dist/runtime/i18n.js.map +1 -0
- package/dist/runtime/i18nCheck.d.ts +30 -0
- package/dist/runtime/i18nCheck.js +224 -0
- package/dist/runtime/i18nCheck.js.map +1 -0
- package/dist/runtime/pageLang.d.ts +8 -0
- package/dist/runtime/pageLang.js +64 -0
- package/dist/runtime/pageLang.js.map +1 -0
- package/dist/runtime/pageStrings.d.ts +35 -0
- package/dist/runtime/pageStrings.js +76 -0
- package/dist/runtime/pageStrings.js.map +1 -0
- package/dist/runtime/panelButton.d.ts +33 -0
- package/dist/runtime/panelButton.js +83 -0
- package/dist/runtime/panelButton.js.map +1 -0
- package/dist/runtime/permissions.d.ts +47 -0
- package/dist/runtime/permissions.js +141 -0
- package/dist/runtime/permissions.js.map +1 -0
- package/dist/runtime/pluginApi.d.ts +51 -2
- package/dist/runtime/pluginApi.js +137 -22
- package/dist/runtime/pluginApi.js.map +1 -1
- package/dist/runtime/pluginI18n.d.ts +29 -0
- package/dist/runtime/pluginI18n.js +119 -0
- package/dist/runtime/pluginI18n.js.map +1 -0
- package/dist/runtime/settingsForm.d.ts +66 -0
- package/dist/runtime/settingsForm.js +114 -0
- package/dist/runtime/settingsForm.js.map +1 -0
- package/dist/runtime/site.d.ts +5 -0
- package/dist/runtime/site.js +1 -1
- package/dist/runtime/site.js.map +1 -1
- package/dist/runtime/siteRuntime.d.ts +144 -0
- package/dist/runtime/siteRuntime.js +175 -0
- package/dist/runtime/siteRuntime.js.map +1 -0
- package/dist/runtime/stringPackHook.d.ts +84 -0
- package/dist/runtime/stringPackHook.js +220 -0
- package/dist/runtime/stringPackHook.js.map +1 -0
- package/dist/runtime/stringPackTranslate.d.ts +61 -0
- package/dist/runtime/stringPackTranslate.js +267 -0
- package/dist/runtime/stringPackTranslate.js.map +1 -0
- package/dist/runtime/stringPacks.d.ts +248 -0
- package/dist/runtime/stringPacks.js +610 -0
- package/dist/runtime/stringPacks.js.map +1 -0
- package/dist/runtime/styles.d.ts +15 -0
- package/dist/runtime/styles.js +81 -0
- package/dist/runtime/styles.js.map +1 -0
- package/dist/runtime/textNet.d.ts +15 -0
- package/dist/runtime/textNet.js +131 -0
- package/dist/runtime/textNet.js.map +1 -0
- package/dist/runtime/themeApi.d.ts +121 -0
- package/dist/runtime/themeApi.js +220 -0
- package/dist/runtime/themeApi.js.map +1 -0
- package/dist/runtime/themeBar.d.ts +28 -0
- package/dist/runtime/themeBar.js +82 -0
- package/dist/runtime/themeBar.js.map +1 -0
- package/dist/runtime/themeChoice.d.ts +52 -0
- package/dist/runtime/themeChoice.js +69 -0
- package/dist/runtime/themeChoice.js.map +1 -0
- package/dist/runtime/themeComponents.d.ts +435 -0
- package/dist/runtime/themeComponents.js +476 -0
- package/dist/runtime/themeComponents.js.map +1 -0
- package/dist/runtime/themeDecor.d.ts +73 -0
- package/dist/runtime/themeDecor.js +133 -0
- package/dist/runtime/themeDecor.js.map +1 -0
- package/dist/runtime/themeDerive.d.ts +69 -0
- package/dist/runtime/themeDerive.js +117 -0
- package/dist/runtime/themeDerive.js.map +1 -0
- package/dist/runtime/themeDoctor.d.ts +52 -0
- package/dist/runtime/themeDoctor.js +112 -0
- package/dist/runtime/themeDoctor.js.map +1 -0
- package/dist/runtime/themeGuards.d.ts +60 -0
- package/dist/runtime/themeGuards.js +235 -0
- package/dist/runtime/themeGuards.js.map +1 -0
- package/dist/runtime/themeIcons.d.ts +130 -0
- package/dist/runtime/themeIcons.js +271 -0
- package/dist/runtime/themeIcons.js.map +1 -0
- package/dist/runtime/themeInk.d.ts +37 -0
- package/dist/runtime/themeInk.js +111 -0
- package/dist/runtime/themeInk.js.map +1 -0
- package/dist/runtime/themeInvariants.d.ts +39 -0
- package/dist/runtime/themeInvariants.js +273 -0
- package/dist/runtime/themeInvariants.js.map +1 -0
- package/dist/runtime/themeRuntime.d.ts +114 -0
- package/dist/runtime/themeRuntime.js +570 -0
- package/dist/runtime/themeRuntime.js.map +1 -0
- package/dist/runtime/themeSchema.d.ts +10 -0
- package/dist/runtime/themeSchema.js +150 -0
- package/dist/runtime/themeSchema.js.map +1 -0
- package/dist/runtime/themeSpec.d.ts +263 -0
- package/dist/runtime/themeSpec.js +435 -0
- package/dist/runtime/themeSpec.js.map +1 -0
- package/dist/runtime/themeTokens.d.ts +12 -0
- package/dist/runtime/themeTokens.js +14 -6
- package/dist/runtime/themeTokens.js.map +1 -1
- package/dist/runtime/themeVariants.d.ts +30 -0
- package/dist/runtime/themeVariants.js +53 -0
- package/dist/runtime/themeVariants.js.map +1 -0
- package/dist/runtime/ui.d.ts +192 -0
- package/dist/runtime/ui.js +926 -0
- package/dist/runtime/ui.js.map +1 -0
- package/dist/shell/InstallDialog.d.ts +40 -0
- package/dist/shell/InstallDialog.js +176 -0
- package/dist/shell/InstallDialog.js.map +1 -0
- package/dist/shell/StoreView.d.ts +78 -0
- package/dist/shell/StoreView.js +108 -0
- package/dist/shell/StoreView.js.map +1 -0
- package/dist/shell/addressModel.js.map +1 -1
- package/dist/shell/chrome.js.map +1 -1
- package/dist/shell/deckModel.d.ts +117 -0
- package/dist/shell/deckModel.js +267 -0
- package/dist/shell/deckModel.js.map +1 -0
- package/dist/shell/frame/AccountSwitcher.d.ts +42 -0
- package/dist/shell/frame/AccountSwitcher.js +243 -0
- package/dist/shell/frame/AccountSwitcher.js.map +1 -0
- package/dist/shell/frame/AddColumnDialog.d.ts +8 -0
- package/dist/shell/frame/AddColumnDialog.js +81 -0
- package/dist/shell/frame/AddColumnDialog.js.map +1 -0
- package/dist/shell/frame/AppMenu.d.ts +44 -0
- package/dist/shell/frame/AppMenu.js +721 -0
- package/dist/shell/frame/AppMenu.js.map +1 -0
- package/dist/shell/frame/CommandPalette.d.ts +7 -0
- package/dist/shell/frame/CommandPalette.js +118 -0
- package/dist/shell/frame/CommandPalette.js.map +1 -0
- package/dist/shell/frame/DeckView.d.ts +13 -0
- package/dist/shell/frame/DeckView.js +287 -0
- package/dist/shell/frame/DeckView.js.map +1 -0
- package/dist/shell/frame/FindBar.d.ts +10 -0
- package/dist/shell/frame/FindBar.js +108 -0
- package/dist/shell/frame/FindBar.js.map +1 -0
- package/dist/shell/frame/MediaPanel.d.ts +8 -0
- package/dist/shell/frame/MediaPanel.js +76 -0
- package/dist/shell/frame/MediaPanel.js.map +1 -0
- package/dist/shell/frame/NewTabPage.d.ts +9 -0
- package/dist/shell/frame/NewTabPage.js +71 -0
- package/dist/shell/frame/NewTabPage.js.map +1 -0
- package/dist/shell/frame/PaneTheme.d.ts +16 -0
- package/dist/shell/frame/PaneTheme.js +67 -0
- package/dist/shell/frame/PaneTheme.js.map +1 -0
- package/dist/shell/frame/ReportDialog.d.ts +3 -0
- package/dist/shell/frame/ReportDialog.js +95 -0
- package/dist/shell/frame/ReportDialog.js.map +1 -0
- package/dist/shell/frame/Shell.d.ts +9 -0
- package/dist/shell/frame/Shell.js +1139 -0
- package/dist/shell/frame/Shell.js.map +1 -0
- package/dist/shell/frame/Titlebar.d.ts +17 -0
- package/dist/shell/frame/Titlebar.js +79 -0
- package/dist/shell/frame/Titlebar.js.map +1 -0
- package/dist/shell/frame/TitlebarExtras.d.ts +4 -0
- package/dist/shell/frame/TitlebarExtras.js +202 -0
- package/dist/shell/frame/TitlebarExtras.js.map +1 -0
- package/dist/shell/frame/WebPanel.d.ts +4 -0
- package/dist/shell/frame/WebPanel.js +185 -0
- package/dist/shell/frame/WebPanel.js.map +1 -0
- package/dist/shell/frame/accountModel.d.ts +49 -0
- package/dist/shell/frame/accountModel.js +67 -0
- package/dist/shell/frame/accountModel.js.map +1 -0
- package/dist/shell/frame/accountSwitcher.css +91 -0
- package/dist/shell/frame/appI18n.d.ts +44 -0
- package/dist/shell/frame/appI18n.js +64 -0
- package/dist/shell/frame/appI18n.js.map +1 -0
- package/dist/shell/frame/appMenu.css +127 -0
- package/dist/shell/frame/backupModel.d.ts +38 -0
- package/dist/shell/frame/backupModel.js +192 -0
- package/dist/shell/frame/backupModel.js.map +1 -0
- package/dist/shell/frame/boot.d.ts +8 -0
- package/dist/shell/frame/boot.js +37 -0
- package/dist/shell/frame/boot.js.map +1 -0
- package/dist/shell/frame/brand.d.ts +1 -0
- package/dist/shell/frame/brand.js +4 -0
- package/dist/shell/frame/brand.js.map +1 -0
- package/dist/shell/frame/bridge.d.ts +35 -0
- package/dist/shell/frame/bridge.js +70 -0
- package/dist/shell/frame/bridge.js.map +1 -0
- package/dist/shell/frame/chromeTheme.d.ts +47 -0
- package/dist/shell/frame/chromeTheme.js +118 -0
- package/dist/shell/frame/chromeTheme.js.map +1 -0
- package/dist/shell/frame/deck.css +209 -0
- package/dist/shell/frame/deckStore.d.ts +89 -0
- package/dist/shell/frame/deckStore.js +239 -0
- package/dist/shell/frame/deckStore.js.map +1 -0
- package/dist/shell/frame/devHost.d.ts +35 -0
- package/dist/shell/frame/devHost.js +80 -0
- package/dist/shell/frame/devHost.js.map +1 -0
- package/dist/shell/frame/devModel.d.ts +7 -0
- package/dist/shell/frame/devModel.js +17 -0
- package/dist/shell/frame/devModel.js.map +1 -0
- package/dist/shell/frame/diag.d.ts +7 -0
- package/dist/shell/frame/diag.js +39 -0
- package/dist/shell/frame/diag.js.map +1 -0
- package/dist/shell/frame/edges.css +159 -0
- package/dist/shell/frame/findBar.css +14 -0
- package/dist/shell/frame/frameMessages.d.ts +248 -0
- package/dist/shell/frame/frameMessages.js +252 -0
- package/dist/shell/frame/frameMessages.js.map +1 -0
- package/dist/shell/frame/hooks.d.ts +39 -0
- package/dist/shell/frame/hooks.js +97 -0
- package/dist/shell/frame/hooks.js.map +1 -0
- package/dist/shell/frame/i18n.d.ts +27 -0
- package/dist/shell/frame/i18n.js +66 -0
- package/dist/shell/frame/i18n.js.map +1 -0
- package/dist/shell/frame/iconPack.d.ts +11 -0
- package/dist/shell/frame/iconPack.js +45 -0
- package/dist/shell/frame/iconPack.js.map +1 -0
- package/dist/shell/frame/icons.d.ts +71 -0
- package/dist/shell/frame/icons.js +75 -0
- package/dist/shell/frame/icons.js.map +1 -0
- package/dist/shell/frame/index.d.ts +34 -0
- package/dist/shell/frame/index.js +43 -0
- package/dist/shell/frame/index.js.map +1 -0
- package/dist/shell/frame/language.d.ts +58 -0
- package/dist/shell/frame/language.js +165 -0
- package/dist/shell/frame/language.js.map +1 -0
- package/dist/shell/frame/mediaModel.d.ts +47 -0
- package/dist/shell/frame/mediaModel.js +67 -0
- package/dist/shell/frame/mediaModel.js.map +1 -0
- package/dist/shell/frame/menuMessages.en.json +96 -0
- package/dist/shell/frame/menuModel.d.ts +319 -0
- package/dist/shell/frame/menuModel.js +301 -0
- package/dist/shell/frame/menuModel.js.map +1 -0
- package/dist/shell/frame/nativeMenu.d.ts +5 -0
- package/dist/shell/frame/nativeMenu.js +178 -0
- package/dist/shell/frame/nativeMenu.js.map +1 -0
- package/dist/shell/frame/nativeMenuModel.d.ts +102 -0
- package/dist/shell/frame/nativeMenuModel.js +186 -0
- package/dist/shell/frame/nativeMenuModel.js.map +1 -0
- package/dist/shell/frame/paletteModel.d.ts +139 -0
- package/dist/shell/frame/paletteModel.js +114 -0
- package/dist/shell/frame/paletteModel.js.map +1 -0
- package/dist/shell/frame/paneTheme.css +9 -0
- package/dist/shell/frame/pluginCommandStore.d.ts +23 -0
- package/dist/shell/frame/pluginCommandStore.js +70 -0
- package/dist/shell/frame/pluginCommandStore.js.map +1 -0
- package/dist/shell/frame/reportModel.d.ts +52 -0
- package/dist/shell/frame/reportModel.js +105 -0
- package/dist/shell/frame/reportModel.js.map +1 -0
- package/dist/shell/frame/safeStore.d.ts +7 -0
- package/dist/shell/frame/safeStore.js +40 -0
- package/dist/shell/frame/safeStore.js.map +1 -0
- package/dist/shell/frame/searchInput.d.ts +2 -0
- package/dist/shell/frame/searchInput.js +11 -0
- package/dist/shell/frame/searchInput.js.map +1 -0
- package/dist/shell/frame/shell.css +634 -0
- package/dist/shell/frame/site.d.ts +206 -0
- package/dist/shell/frame/site.js +22 -0
- package/dist/shell/frame/site.js.map +1 -0
- package/dist/shell/frame/state.d.ts +101 -0
- package/dist/shell/frame/state.js +286 -0
- package/dist/shell/frame/state.js.map +1 -0
- package/dist/shell/frame/syncGate.d.ts +10 -0
- package/dist/shell/frame/syncGate.js +36 -0
- package/dist/shell/frame/syncGate.js.map +1 -0
- package/dist/shell/frame/syncHost.d.ts +35 -0
- package/dist/shell/frame/syncHost.js +85 -0
- package/dist/shell/frame/syncHost.js.map +1 -0
- package/dist/shell/frame/tabStill.d.ts +2 -0
- package/dist/shell/frame/tabStill.js +62 -0
- package/dist/shell/frame/tabStill.js.map +1 -0
- package/dist/shell/frame/themeScopes.d.ts +29 -0
- package/dist/shell/frame/themeScopes.js +61 -0
- package/dist/shell/frame/themeScopes.js.map +1 -0
- package/dist/shell/frame/themeStore.d.ts +99 -0
- package/dist/shell/frame/themeStore.js +267 -0
- package/dist/shell/frame/themeStore.js.map +1 -0
- package/dist/shell/frame/windowBinding.d.ts +2 -0
- package/dist/shell/frame/windowBinding.js +7 -0
- package/dist/shell/frame/windowBinding.js.map +1 -0
- package/dist/shell/frame/workspaceModel.d.ts +82 -0
- package/dist/shell/frame/workspaceModel.js +250 -0
- package/dist/shell/frame/workspaceModel.js.map +1 -0
- package/dist/shell/frame/workspaces.css +84 -0
- package/dist/shell/frame/workspaces.d.ts +22 -0
- package/dist/shell/frame/workspaces.js +125 -0
- package/dist/shell/frame/workspaces.js.map +1 -0
- package/dist/shell/frame/zoomStore.d.ts +10 -0
- package/dist/shell/frame/zoomStore.js +65 -0
- package/dist/shell/frame/zoomStore.js.map +1 -0
- package/dist/shell/installController.d.ts +137 -0
- package/dist/shell/installController.js +172 -0
- package/dist/shell/installController.js.map +1 -0
- package/dist/shell/installerMessages.d.ts +30 -0
- package/dist/shell/installerMessages.js +36 -0
- package/dist/shell/installerMessages.js.map +1 -0
- package/dist/shell/linkDrop.js.map +1 -1
- package/dist/shell/messages.d.ts +16 -0
- package/dist/shell/messages.js +27 -0
- package/dist/shell/messages.js.map +1 -0
- package/dist/shell/palette.js.map +1 -1
- package/dist/shell/paneModel.d.ts +22 -0
- package/dist/shell/paneModel.js +24 -1
- package/dist/shell/paneModel.js.map +1 -1
- package/dist/shell/panel/Appearance.d.ts +8 -0
- package/dist/shell/panel/Appearance.js +265 -0
- package/dist/shell/panel/Appearance.js.map +1 -0
- package/dist/shell/panel/BackupPanel.d.ts +5 -0
- package/dist/shell/panel/BackupPanel.js +198 -0
- package/dist/shell/panel/BackupPanel.js.map +1 -0
- package/dist/shell/panel/DeveloperPanel.d.ts +4 -0
- package/dist/shell/panel/DeveloperPanel.js +51 -0
- package/dist/shell/panel/DeveloperPanel.js.map +1 -0
- package/dist/shell/panel/FeatureToggles.d.ts +13 -0
- package/dist/shell/panel/FeatureToggles.js +42 -0
- package/dist/shell/panel/FeatureToggles.js.map +1 -0
- package/dist/shell/panel/Home.d.ts +3 -0
- package/dist/shell/panel/Home.js +23 -0
- package/dist/shell/panel/Home.js.map +1 -0
- package/dist/shell/panel/IconPackInstall.d.ts +15 -0
- package/dist/shell/panel/IconPackInstall.js +41 -0
- package/dist/shell/panel/IconPackInstall.js.map +1 -0
- package/dist/shell/panel/LanguageSettings.d.ts +1 -0
- package/dist/shell/panel/LanguageSettings.js +54 -0
- package/dist/shell/panel/LanguageSettings.js.map +1 -0
- package/dist/shell/panel/MakePanel.d.ts +3 -0
- package/dist/shell/panel/MakePanel.js +237 -0
- package/dist/shell/panel/MakePanel.js.map +1 -0
- package/dist/shell/panel/MarkdownDoc.d.ts +6 -0
- package/dist/shell/panel/MarkdownDoc.js +35 -0
- package/dist/shell/panel/MarkdownDoc.js.map +1 -0
- package/dist/shell/panel/Modes.d.ts +40 -0
- package/dist/shell/panel/Modes.js +270 -0
- package/dist/shell/panel/Modes.js.map +1 -0
- package/dist/shell/panel/NavIcon.d.ts +4 -0
- package/dist/shell/panel/NavIcon.js +6 -0
- package/dist/shell/panel/NavIcon.js.map +1 -0
- package/dist/shell/panel/Panel.d.ts +7 -0
- package/dist/shell/panel/Panel.js +139 -0
- package/dist/shell/panel/Panel.js.map +1 -0
- package/dist/shell/panel/PluginFeatures.d.ts +24 -0
- package/dist/shell/panel/PluginFeatures.js +165 -0
- package/dist/shell/panel/PluginFeatures.js.map +1 -0
- package/dist/shell/panel/Plugins.d.ts +33 -0
- package/dist/shell/panel/Plugins.js +112 -0
- package/dist/shell/panel/Plugins.js.map +1 -0
- package/dist/shell/panel/PowerSettings.d.ts +4 -0
- package/dist/shell/panel/PowerSettings.js +52 -0
- package/dist/shell/panel/PowerSettings.js.map +1 -0
- package/dist/shell/panel/Settings.d.ts +23 -0
- package/dist/shell/panel/Settings.js +117 -0
- package/dist/shell/panel/Settings.js.map +1 -0
- package/dist/shell/panel/ShareSetup.d.ts +8 -0
- package/dist/shell/panel/ShareSetup.js +130 -0
- package/dist/shell/panel/ShareSetup.js.map +1 -0
- package/dist/shell/panel/StoreSection.d.ts +7 -0
- package/dist/shell/panel/StoreSection.js +78 -0
- package/dist/shell/panel/StoreSection.js.map +1 -0
- package/dist/shell/panel/StringPacks.d.ts +20 -0
- package/dist/shell/panel/StringPacks.js +131 -0
- package/dist/shell/panel/StringPacks.js.map +1 -0
- package/dist/shell/panel/ThemeBuilder.d.ts +5 -0
- package/dist/shell/panel/ThemeBuilder.js +205 -0
- package/dist/shell/panel/ThemeBuilder.js.map +1 -0
- package/dist/shell/panel/backup.css +34 -0
- package/dist/shell/panel/builder.css +240 -0
- package/dist/shell/panel/builderModel.d.ts +55 -0
- package/dist/shell/panel/builderModel.js +95 -0
- package/dist/shell/panel/builderModel.js.map +1 -0
- package/dist/shell/panel/compare.d.ts +10 -0
- package/dist/shell/panel/compare.js +51 -0
- package/dist/shell/panel/compare.js.map +1 -0
- package/dist/shell/panel/controls.d.ts +135 -0
- package/dist/shell/panel/controls.js +213 -0
- package/dist/shell/panel/controls.js.map +1 -0
- package/dist/shell/panel/featureModel.d.ts +69 -0
- package/dist/shell/panel/featureModel.js +79 -0
- package/dist/shell/panel/featureModel.js.map +1 -0
- package/dist/shell/panel/fonts/FontBrowser.d.ts +10 -0
- package/dist/shell/panel/fonts/FontBrowser.js +189 -0
- package/dist/shell/panel/fonts/FontBrowser.js.map +1 -0
- package/dist/shell/panel/fonts/FontRoles.d.ts +20 -0
- package/dist/shell/panel/fonts/FontRoles.js +77 -0
- package/dist/shell/panel/fonts/FontRoles.js.map +1 -0
- package/dist/shell/panel/fonts/catalog.d.ts +37 -0
- package/dist/shell/panel/fonts/catalog.js +68 -0
- package/dist/shell/panel/fonts/catalog.js.map +1 -0
- package/dist/shell/panel/fonts/googleFontsCatalog.json +1 -0
- package/dist/shell/panel/index.d.ts +20 -0
- package/dist/shell/panel/index.js +34 -0
- package/dist/shell/panel/index.js.map +1 -0
- package/dist/shell/panel/languageModel.d.ts +5 -0
- package/dist/shell/panel/languageModel.js +7 -0
- package/dist/shell/panel/languageModel.js.map +1 -0
- package/dist/shell/panel/make.css +73 -0
- package/dist/shell/panel/makeModel.d.ts +32 -0
- package/dist/shell/panel/makeModel.js +84 -0
- package/dist/shell/panel/makeModel.js.map +1 -0
- package/dist/shell/panel/markdown.d.ts +46 -0
- package/dist/shell/panel/markdown.js +100 -0
- package/dist/shell/panel/markdown.js.map +1 -0
- package/dist/shell/panel/nav.d.ts +12 -0
- package/dist/shell/panel/nav.js +50 -0
- package/dist/shell/panel/nav.js.map +1 -0
- package/dist/shell/panel/panel.css +401 -0
- package/dist/shell/panel/panelMessages.d.ts +803 -0
- package/dist/shell/panel/panelMessages.js +779 -0
- package/dist/shell/panel/panelMessages.js.map +1 -0
- package/dist/shell/panel/plugins.css +59 -0
- package/dist/shell/panel/setupModel.d.ts +87 -0
- package/dist/shell/panel/setupModel.js +152 -0
- package/dist/shell/panel/setupModel.js.map +1 -0
- package/dist/shell/panel/site.d.ts +259 -0
- package/dist/shell/panel/site.js +25 -0
- package/dist/shell/panel/site.js.map +1 -0
- package/dist/shell/panel/strings.css +7 -0
- package/dist/shell/panel/themes.d.ts +10 -0
- package/dist/shell/panel/themes.js +8 -0
- package/dist/shell/panel/themes.js.map +1 -0
- package/dist/shell/profileModel.d.ts +3 -2
- package/dist/shell/profileModel.js +7 -5
- package/dist/shell/profileModel.js.map +1 -1
- package/dist/shell/store.css +250 -0
- package/dist/shell/storeMessages.d.ts +193 -0
- package/dist/shell/storeMessages.js +196 -0
- package/dist/shell/storeMessages.js.map +1 -0
- package/dist/shell/storeModel.d.ts +205 -0
- package/dist/shell/storeModel.js +146 -0
- package/dist/shell/storeModel.js.map +1 -0
- package/dist/shell/stringPacksModel.d.ts +44 -0
- package/dist/shell/stringPacksModel.js +99 -0
- package/dist/shell/stringPacksModel.js.map +1 -0
- package/dist/shell/updateModel.d.ts +2 -1
- package/dist/shell/updateModel.js +5 -4
- package/dist/shell/updateModel.js.map +1 -1
- package/dist/shell/windowControls.d.ts +4 -0
- package/dist/shell/windowControls.js +5 -0
- package/dist/shell/windowControls.js.map +1 -1
- package/i18n-config.schema.json +29 -0
- package/i18n-meta.schema.json +26 -0
- package/i18n.schema.json +42 -0
- package/icons.schema.json +655 -0
- package/languages.schema.json +23 -0
- package/locales/ar-x-fm.json +11 -0
- package/locales/ar.json +1388 -0
- package/locales/bg.json +1387 -0
- package/locales/bn.json +1387 -0
- package/locales/ca.json +1387 -0
- package/locales/cs.json +1387 -0
- package/locales/da.json +1387 -0
- package/locales/de.json +1387 -0
- package/locales/el.json +1387 -0
- package/locales/en-GB.json +27 -0
- package/locales/en.json +1386 -0
- package/locales/en.meta.json +51 -0
- package/locales/es.json +1387 -0
- package/locales/eu.json +1387 -0
- package/locales/fa.json +1388 -0
- package/locales/fi.json +1387 -0
- package/locales/fil.json +1387 -0
- package/locales/fr.json +1387 -0
- package/locales/ga.json +1387 -0
- package/locales/gl.json +1387 -0
- package/locales/gu.json +1387 -0
- package/locales/ha.json +1387 -0
- package/locales/he.json +1388 -0
- package/locales/hi.json +1387 -0
- package/locales/hr.json +1387 -0
- package/locales/hu.json +1387 -0
- package/locales/id.json +1387 -0
- package/locales/ig.json +1387 -0
- package/locales/it.json +1387 -0
- package/locales/ja.json +1387 -0
- package/locales/kn.json +1387 -0
- package/locales/ko.json +1387 -0
- package/locales/mr.json +1387 -0
- package/locales/ms.json +1387 -0
- package/locales/nb.json +1387 -0
- package/locales/nl.json +1387 -0
- package/locales/pl.json +1387 -0
- package/locales/pt.json +1387 -0
- package/locales/ro.json +1387 -0
- package/locales/ru.json +1387 -0
- package/locales/sk.json +1387 -0
- package/locales/sr.json +1387 -0
- package/locales/sv.json +1387 -0
- package/locales/ta.json +1387 -0
- package/locales/th.json +1387 -0
- package/locales/tr.json +1387 -0
- package/locales/uk.json +1387 -0
- package/locales/ur.json +1388 -0
- package/locales/vi.json +1387 -0
- package/locales/yo.json +1387 -0
- package/locales/zh-Hans.json +1387 -0
- package/locales/zh-Hant.json +1387 -0
- package/package.json +138 -5
- package/store-catalog.schema.json +78 -0
- package/theme.schema.json +4668 -0
- package/windows/installer.nsi +946 -0
- package/windows/languages/Basque.nsh +28 -0
- package/windows/languages/Catalan.nsh +28 -0
- package/windows/languages/Croatian.nsh +28 -0
- package/windows/languages/Czech.nsh +28 -0
- package/windows/languages/Danish.nsh +28 -0
- package/windows/languages/Finnish.nsh +28 -0
- package/windows/languages/Galician.nsh +28 -0
- package/windows/languages/Greek.nsh +28 -0
- package/windows/languages/Hungarian.nsh +28 -0
- package/windows/languages/Indonesian.nsh +28 -0
- package/windows/languages/Irish.nsh +28 -0
- package/windows/languages/Malay.nsh +28 -0
- package/windows/languages/Polish.nsh +28 -0
- package/windows/languages/Romanian.nsh +28 -0
- package/windows/languages/Serbian.nsh +28 -0
- package/windows/languages/Slovak.nsh +28 -0
- package/windows/languages/Thai.nsh +28 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
// String packs: data-only packs that replace the wrapped site's own UI strings (published as
|
|
2
|
+
// `waybetter/runtime/stringPacks`). A pack adds a language the site does not offer, fixes its
|
|
3
|
+
// translation or rewords it. This file is the data side: the pack and inventory formats, the
|
|
4
|
+
// validator, the ordered application engine, coverage and the drift report. It touches no DOM and
|
|
5
|
+
// runs the same in the page, in the shell and in Node (`waybetter strings check`).
|
|
6
|
+
//
|
|
7
|
+
// The rules (they are the owner's, and every function here keeps them):
|
|
8
|
+
// - Idempotent. The result is a pure function of (the site's own values, the ordered pack list):
|
|
9
|
+
// applying twice equals applying once, and a later pack wins over an earlier one per key.
|
|
10
|
+
// - The site's own language is the fallback. A key no pack changes keeps exactly the site's value
|
|
11
|
+
// (the same object, not a copy).
|
|
12
|
+
// - An inventory of the site's known keys. A pack may change only inventory keys, and only keys
|
|
13
|
+
// whose kind can be replaced by data; unknown keys are reported, never applied.
|
|
14
|
+
// - Packs are data: plain text in an ICU MessageFormat subset ({param}, plural, select). Never
|
|
15
|
+
// HTML, never code. A message is interpreted by closures here; nothing is evaluated.
|
|
16
|
+
//
|
|
17
|
+
// One site can have several catalogs (each with its own key space, inventory and hook adapter,
|
|
18
|
+
// see stringPackHook.ts); a pack names the one it is for.
|
|
19
|
+
import { parseMessage } from "./i18n.js";
|
|
20
|
+
export const STRING_PACK_LIMITS = Object.freeze({ strings: 40000, value: 2000, name: 80, authors: 16 });
|
|
21
|
+
export const MAIN_GROUP = 'main';
|
|
22
|
+
const TAG = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
|
|
23
|
+
const IDENT = /^[a-z][a-z0-9-]{0,31}$/;
|
|
24
|
+
const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f--]/;
|
|
25
|
+
const REWRITE_FROM = /^[\p{L}\p{N}]{1,40}$/u;
|
|
26
|
+
const REWRITE_TO = /^[\p{L}\p{N}][\p{L}\p{N}'’-]{0,39}$/u;
|
|
27
|
+
const MAX_REWRITES = 64;
|
|
28
|
+
const CATEGORIES = new Set(['zero', 'one', 'two', 'few', 'many', 'other']);
|
|
29
|
+
/** Whether a pack can replace a value of this kind (a `rich` one only with known `slots`: pass the shape). */
|
|
30
|
+
export function translatable(kind) {
|
|
31
|
+
const { kind: k, slots } = typeof kind === 'string' ? { kind, slots: undefined } : kind;
|
|
32
|
+
return k === 'text' || k === 'message' || (k === 'rich' && slots !== undefined);
|
|
33
|
+
}
|
|
34
|
+
/** Whether `language` (the site's) satisfies a pack's `when`. */
|
|
35
|
+
export function conditionHolds(when, language) {
|
|
36
|
+
if (!when?.languages)
|
|
37
|
+
return true;
|
|
38
|
+
if (!language)
|
|
39
|
+
return false;
|
|
40
|
+
const have = language.toLowerCase();
|
|
41
|
+
return when.languages.some(tag => { const want = tag.toLowerCase(); return have === want || have.startsWith(`${want}-`); });
|
|
42
|
+
}
|
|
43
|
+
/** 53-bit hash of a text as 11 base-36 characters. Same result in every runtime. */
|
|
44
|
+
export function hashText(text) {
|
|
45
|
+
let h1 = 0xdeadbeef;
|
|
46
|
+
let h2 = 0x41c6ce57;
|
|
47
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
48
|
+
const ch = text.charCodeAt(i);
|
|
49
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
50
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
51
|
+
}
|
|
52
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
53
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
54
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).padStart(11, '0');
|
|
55
|
+
}
|
|
56
|
+
function compact(site, catalog, shapes) {
|
|
57
|
+
const text = [];
|
|
58
|
+
const closed = [];
|
|
59
|
+
const message = {};
|
|
60
|
+
const rich = {};
|
|
61
|
+
for (const [id, entry] of shapes) {
|
|
62
|
+
if (entry.kind === 'rich' && entry.slots !== undefined && !entry.tags?.length)
|
|
63
|
+
rich[id] = entry.slots;
|
|
64
|
+
else if (entry.kind === 'text' && !entry.tags?.length)
|
|
65
|
+
text.push(id);
|
|
66
|
+
else if (entry.kind === 'message' && !entry.tags?.length) {
|
|
67
|
+
message[id] = (entry.params ?? []).map(name => `${name}${entry.plural?.includes(name) ? '*' : ''}${entry.select?.includes(name) ? '?' : ''}`).join(' ');
|
|
68
|
+
}
|
|
69
|
+
else
|
|
70
|
+
closed.push(id);
|
|
71
|
+
}
|
|
72
|
+
return { site, catalog, text: text.join(' '), message, closed: closed.join(' '), ...(Object.keys(rich).length ? { rich } : {}) };
|
|
73
|
+
}
|
|
74
|
+
export function packInventory(inventory) {
|
|
75
|
+
return compact(inventory.site, inventory.catalog, Object.entries(inventory.entries));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The part of an inventory that the given keys need: what travels to a page together with the
|
|
79
|
+
* packs it should apply (a page never needs the rest). Keys the inventory does not have are left
|
|
80
|
+
* out, so they stay unknown there too.
|
|
81
|
+
*/
|
|
82
|
+
export function sliceInventory(inventory, ids) {
|
|
83
|
+
const lookup = inventoryLookup(inventory);
|
|
84
|
+
const shapes = [];
|
|
85
|
+
for (const id of new Set(ids)) {
|
|
86
|
+
const shape = lookup(id);
|
|
87
|
+
if (shape)
|
|
88
|
+
shapes.push([id, shape]);
|
|
89
|
+
}
|
|
90
|
+
return { ...compact(inventory.site, inventory.catalog, shapes), partial: true };
|
|
91
|
+
}
|
|
92
|
+
/** Whether untrusted data has the shape of a page inventory (ids and parameter names only). */
|
|
93
|
+
export function isPageInventory(input) {
|
|
94
|
+
if (!isRecord(input))
|
|
95
|
+
return false;
|
|
96
|
+
const ids = (value) => typeof value === 'string' && value.length <= 2_000_000 && /^[\w$. -]*$/.test(value);
|
|
97
|
+
return typeof input.site === 'string' && IDENT.test(input.site) && typeof input.catalog === 'string' && IDENT.test(input.catalog)
|
|
98
|
+
&& ids(input.text) && ids(input.closed) && isRecord(input.message)
|
|
99
|
+
&& Object.entries(input.message).every(([id, names]) => /^[\w$.-]+$/.test(id) && typeof names === 'string' && /^[\w$*? ]*$/.test(names))
|
|
100
|
+
&& (input.rich === undefined || (isRecord(input.rich) && Object.entries(input.rich).every(([id, n]) => /^[\w$.-]+$/.test(id) && Number.isInteger(n) && n >= 0 && n <= 32)))
|
|
101
|
+
&& (input.partial === undefined || typeof input.partial === 'boolean');
|
|
102
|
+
}
|
|
103
|
+
const TEXT_SHAPE = Object.freeze({ kind: 'text' });
|
|
104
|
+
const CLOSED_SHAPE = Object.freeze({ kind: 'rich' });
|
|
105
|
+
/** A lookup over either form. The page form is decoded on first use. */
|
|
106
|
+
export function inventoryLookup(inventory) {
|
|
107
|
+
if ('entries' in inventory) {
|
|
108
|
+
const entries = inventory.entries;
|
|
109
|
+
return id => (Object.prototype.hasOwnProperty.call(entries, id) ? entries[id] : undefined);
|
|
110
|
+
}
|
|
111
|
+
let table = null;
|
|
112
|
+
const decode = () => {
|
|
113
|
+
const map = new Map();
|
|
114
|
+
if (inventory.text)
|
|
115
|
+
for (const id of inventory.text.split(' '))
|
|
116
|
+
map.set(id, TEXT_SHAPE);
|
|
117
|
+
if (inventory.closed)
|
|
118
|
+
for (const id of inventory.closed.split(' '))
|
|
119
|
+
map.set(id, CLOSED_SHAPE);
|
|
120
|
+
if (inventory.rich)
|
|
121
|
+
for (const [id, slots] of Object.entries(inventory.rich))
|
|
122
|
+
map.set(id, { kind: 'rich', slots });
|
|
123
|
+
for (const [id, spec] of Object.entries(inventory.message)) {
|
|
124
|
+
const names = spec ? spec.split(' ') : [];
|
|
125
|
+
map.set(id, {
|
|
126
|
+
kind: 'message',
|
|
127
|
+
params: names.map(name => name.replace(/[*?]+$/, '')),
|
|
128
|
+
plural: names.filter(name => name.includes('*')).map(name => name.replace(/[*?]+$/, '')),
|
|
129
|
+
select: names.filter(name => name.includes('?')).map(name => name.replace(/[*?]+$/, '')),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return map;
|
|
133
|
+
};
|
|
134
|
+
return id => (table ??= decode()).get(id);
|
|
135
|
+
}
|
|
136
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
137
|
+
/** Checks the document envelope. Errors mean the pack is not applied at all. */
|
|
138
|
+
export function checkPackShape(input) {
|
|
139
|
+
const issues = [];
|
|
140
|
+
const error = (message) => { issues.push({ level: 'error', message }); };
|
|
141
|
+
if (!isRecord(input))
|
|
142
|
+
return [{ level: 'error', message: 'a string pack is a JSON object' }];
|
|
143
|
+
if (input.format !== 1)
|
|
144
|
+
error('"format" must be 1');
|
|
145
|
+
for (const field of ['site', 'catalog']) {
|
|
146
|
+
if (typeof input[field] !== 'string' || !IDENT.test(input[field]))
|
|
147
|
+
error(`"${field}" must be a short lowercase id`);
|
|
148
|
+
}
|
|
149
|
+
if (typeof input.language !== 'string' || !TAG.test(input.language))
|
|
150
|
+
error('"language" must be a language tag (en, pt-BR, en-x-pirate)');
|
|
151
|
+
if (input.base !== undefined && (typeof input.base !== 'string' || !TAG.test(input.base)))
|
|
152
|
+
error('"base" must be a language tag');
|
|
153
|
+
if (typeof input.name !== 'string' || !input.name.trim() || input.name.length > STRING_PACK_LIMITS.name || CONTROL.test(input.name) || /[<>]/.test(input.name)) {
|
|
154
|
+
error(`"name" must be 1 to ${STRING_PACK_LIMITS.name} characters of plain text`);
|
|
155
|
+
}
|
|
156
|
+
if (!Array.isArray(input.authors) || input.authors.length > STRING_PACK_LIMITS.authors
|
|
157
|
+
|| input.authors.some(author => typeof author !== 'string' || !author.trim() || author.length > STRING_PACK_LIMITS.name || CONTROL.test(author) || /[<>]/.test(author))) {
|
|
158
|
+
error(`"authors" must be a list of at most ${STRING_PACK_LIMITS.authors} plain-text names`);
|
|
159
|
+
}
|
|
160
|
+
if (input.when !== undefined) {
|
|
161
|
+
const when = input.when;
|
|
162
|
+
if (!isRecord(when) || Object.keys(when).some(key => key !== 'languages'))
|
|
163
|
+
error('"when" is an object with "languages"');
|
|
164
|
+
else if (when.languages !== undefined && (!Array.isArray(when.languages) || !when.languages.length || when.languages.length > 16
|
|
165
|
+
|| when.languages.some(tag => typeof tag !== 'string' || !TAG.test(tag))))
|
|
166
|
+
error('"when.languages" must be a list of 1 to 16 language tags');
|
|
167
|
+
}
|
|
168
|
+
if (input.rewrite !== undefined) {
|
|
169
|
+
const rewrite = input.rewrite;
|
|
170
|
+
const words = isRecord(rewrite) ? rewrite.words : undefined;
|
|
171
|
+
if (!isRecord(rewrite) || Object.keys(rewrite).some(key => key !== 'words') || !isRecord(words) || !Object.keys(words).length || Object.keys(words).length > MAX_REWRITES
|
|
172
|
+
|| Object.entries(words).some(([from, to]) => !REWRITE_FROM.test(from) || typeof to !== 'string' || !REWRITE_TO.test(to))) {
|
|
173
|
+
error(`"rewrite" is { "words": { "Word": "Replacement" } } with 1 to ${MAX_REWRITES} plain words`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const field of ['version', 'description']) {
|
|
177
|
+
const value = input[field];
|
|
178
|
+
if (value !== undefined && (typeof value !== 'string' || value.length > 400 || CONTROL.test(value) || /[<>]/.test(value)))
|
|
179
|
+
error(`"${field}" must be plain text`);
|
|
180
|
+
}
|
|
181
|
+
if (!isRecord(input.strings))
|
|
182
|
+
error('"strings" must be an object of key id -> text');
|
|
183
|
+
else if (Object.keys(input.strings).length > STRING_PACK_LIMITS.strings)
|
|
184
|
+
error(`"strings" has more than ${STRING_PACK_LIMITS.strings} entries`);
|
|
185
|
+
const known = new Set(['$schema', 'format', 'site', 'catalog', 'language', 'base', 'when', 'rewrite', 'name', 'authors', 'version', 'description', 'strings']);
|
|
186
|
+
for (const key of Object.keys(input))
|
|
187
|
+
if (!known.has(key))
|
|
188
|
+
issues.push({ level: 'warning', message: `unknown field "${key}" is ignored` });
|
|
189
|
+
return issues;
|
|
190
|
+
}
|
|
191
|
+
const TAG_TOKEN = /<\/?([A-Za-z][A-Za-z0-9-]*)>/g;
|
|
192
|
+
/** Tag rule: only the original's tag slots, each opened and closed; any other `<` or `>` is refused. */
|
|
193
|
+
function checkTags(value, tags) {
|
|
194
|
+
if (!value.includes('<') && !value.includes('>')) {
|
|
195
|
+
return tags.length ? `the original's tags are missing: ${tags.map(tag => `<${tag}>`).join(' ')}` : null;
|
|
196
|
+
}
|
|
197
|
+
if (!tags.length)
|
|
198
|
+
return 'markup is not allowed: a pack is text, < and > cannot be used';
|
|
199
|
+
const open = [];
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
for (const match of value.matchAll(TAG_TOKEN)) {
|
|
202
|
+
const name = match[1];
|
|
203
|
+
if (!tags.includes(name))
|
|
204
|
+
return `<${name}> is not a tag of the original`;
|
|
205
|
+
if (match[0].startsWith('</')) {
|
|
206
|
+
if (open.pop() !== name)
|
|
207
|
+
return `</${name}> does not close an open tag`;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
open.push(name);
|
|
211
|
+
seen.add(name);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (open.length)
|
|
215
|
+
return `<${open[open.length - 1]}> is not closed`;
|
|
216
|
+
if (/[<>]/.test(value.replace(TAG_TOKEN, '')))
|
|
217
|
+
return '< and > may only be used for the original\'s tags';
|
|
218
|
+
const missing = tags.filter(tag => !seen.has(tag));
|
|
219
|
+
return missing.length ? `the original's tags are missing: ${missing.map(tag => `<${tag}>`).join(' ')}` : null;
|
|
220
|
+
}
|
|
221
|
+
/** Walks a parsed message against the key's shape; returns the first problem. */
|
|
222
|
+
function checkParts(parts, shape) {
|
|
223
|
+
const params = shape.params ?? [];
|
|
224
|
+
for (const part of parts) {
|
|
225
|
+
if (typeof part === 'string' || part.kind === 'pound')
|
|
226
|
+
continue;
|
|
227
|
+
if (!params.includes(part.name)) {
|
|
228
|
+
return params.length ? `{${part.name}} is not a parameter of the original (it has: ${params.join(', ')})` : `{${part.name}}: the original takes no parameters`;
|
|
229
|
+
}
|
|
230
|
+
if (part.kind === 'arg') {
|
|
231
|
+
if (part.format)
|
|
232
|
+
return `{${part.name}, ${part.format}} is not supported: only {name}, plural and select`;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (part.kind === 'plural') {
|
|
236
|
+
if (part.ordinal)
|
|
237
|
+
return `{${part.name}, selectordinal} is not supported: only {name}, plural and select`;
|
|
238
|
+
if (part.offset)
|
|
239
|
+
return `{${part.name}, plural, offset:…} is not supported`;
|
|
240
|
+
if (!shape.plural?.includes(part.name))
|
|
241
|
+
return `{${part.name}} is not a plural of the original`;
|
|
242
|
+
for (const selector of Object.keys(part.options)) {
|
|
243
|
+
if (!CATEGORIES.has(selector) && !/^=\d+$/.test(selector))
|
|
244
|
+
return `"${selector}" is not a plural form (zero, one, two, few, many, other, =N)`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (!shape.select?.includes(part.name)) {
|
|
248
|
+
return `{${part.name}} is not a select of the original`;
|
|
249
|
+
}
|
|
250
|
+
for (const option of Object.values(part.options)) {
|
|
251
|
+
const nested = checkParts(option, shape);
|
|
252
|
+
if (nested)
|
|
253
|
+
return nested;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Validates one pack value against the key's inventory entry. Returns the parsed message, or the
|
|
260
|
+
* reason it is refused (the original then stays).
|
|
261
|
+
*/
|
|
262
|
+
export function validateString(shape, value) {
|
|
263
|
+
if (!translatable(shape))
|
|
264
|
+
return { reason: `not translatable yet (${shape.kind})` };
|
|
265
|
+
if (typeof value !== 'string')
|
|
266
|
+
return { reason: 'the value must be text' };
|
|
267
|
+
if (!value)
|
|
268
|
+
return { reason: 'the value is empty' };
|
|
269
|
+
if (value.length > STRING_PACK_LIMITS.value)
|
|
270
|
+
return { reason: `longer than ${STRING_PACK_LIMITS.value} characters` };
|
|
271
|
+
if (CONTROL.test(value))
|
|
272
|
+
return { reason: 'control or bidi-override characters are not allowed' };
|
|
273
|
+
const tags = checkTags(value, shape.tags ?? []);
|
|
274
|
+
if (tags)
|
|
275
|
+
return { reason: tags };
|
|
276
|
+
let parts;
|
|
277
|
+
try {
|
|
278
|
+
parts = parseMessage(value);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
return { reason: `invalid message syntax: ${error instanceof Error ? error.message : String(error)}` };
|
|
282
|
+
}
|
|
283
|
+
if (shape.kind === 'rich') {
|
|
284
|
+
// Element slots: `{e0}` … `{e<slots-1>}`, each exactly once (the last may be left out); no plural, select or other parameter.
|
|
285
|
+
const slots = Array.from({ length: shape.slots ?? 0 }, (_, index) => `e${index}`);
|
|
286
|
+
const bad = checkParts(parts, { kind: 'message', params: slots });
|
|
287
|
+
if (bad)
|
|
288
|
+
return { reason: bad };
|
|
289
|
+
const used = parts.flatMap(part => (typeof part !== 'string' && part.kind === 'arg' ? [part.name] : []));
|
|
290
|
+
for (const name of slots) {
|
|
291
|
+
const count = used.filter(item => item === name).length;
|
|
292
|
+
// The element after the last piece is not always there: that one slot may be left out.
|
|
293
|
+
if (count === 0 && name === slots[slots.length - 1] && slots.length > 0)
|
|
294
|
+
continue;
|
|
295
|
+
if (count !== 1)
|
|
296
|
+
return { reason: count ? `{${name}} (the original's element) must appear once, not ${count} times` : `the original's element {${name}} is missing` };
|
|
297
|
+
}
|
|
298
|
+
return { parts };
|
|
299
|
+
}
|
|
300
|
+
const problem = checkParts(parts, shape.kind === 'message' ? shape : { kind: 'text' });
|
|
301
|
+
return problem ? { reason: problem } : { parts };
|
|
302
|
+
}
|
|
303
|
+
/** A message without arguments as its literal text (quotes resolved). */
|
|
304
|
+
function literal(parts) {
|
|
305
|
+
return parts.map(part => (typeof part === 'string' ? part : '')).join('');
|
|
306
|
+
}
|
|
307
|
+
/** The word-for-word rewriting of a validated `rewrite` rule. */
|
|
308
|
+
function compileRewrite(rule) {
|
|
309
|
+
const words = new Map(Object.entries(rule.words));
|
|
310
|
+
// Words are letters and digits only (checked), so they need no escaping.
|
|
311
|
+
const alternatives = [...words.keys()].sort((a, b) => b.length - a.length).join('|');
|
|
312
|
+
const pattern = new RegExp(String.raw `(?<![\p{L}\p{N}_])(?:${alternatives})(?![\p{L}\p{N}_])`, 'gu');
|
|
313
|
+
return text => text.replace(pattern, word => words.get(word) ?? word);
|
|
314
|
+
}
|
|
315
|
+
/** A validated rich value as pieces and element order. */
|
|
316
|
+
function richTemplate(parts) {
|
|
317
|
+
const pieces = [];
|
|
318
|
+
const order = [];
|
|
319
|
+
let current = '';
|
|
320
|
+
for (const part of parts) {
|
|
321
|
+
if (typeof part === 'string')
|
|
322
|
+
current += part;
|
|
323
|
+
else if (part.kind === 'arg') {
|
|
324
|
+
pieces.push(current);
|
|
325
|
+
order.push(Number(part.name.slice(1)));
|
|
326
|
+
current = '';
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
pieces.push(current);
|
|
330
|
+
return Object.freeze({ pieces: Object.freeze(pieces), order: Object.freeze(order) });
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Validates a pack against an inventory. Throws on a malformed envelope or a pack for another
|
|
334
|
+
* site/catalog; every other problem is per key and lands in `report`.
|
|
335
|
+
*/
|
|
336
|
+
export function compilePack(input, inventory) {
|
|
337
|
+
const shapeErrors = checkPackShape(input).filter(issue => issue.level === 'error');
|
|
338
|
+
if (shapeErrors.length)
|
|
339
|
+
throw new Error(`invalid string pack: ${shapeErrors.map(issue => issue.message).join('; ')}`);
|
|
340
|
+
const pack = input;
|
|
341
|
+
if (pack.site !== inventory.site || pack.catalog !== inventory.catalog) {
|
|
342
|
+
throw new Error(`the pack is for ${pack.site}/${pack.catalog}, the inventory is ${inventory.site}/${inventory.catalog}`);
|
|
343
|
+
}
|
|
344
|
+
const lookup = inventoryLookup(inventory);
|
|
345
|
+
const values = new Map();
|
|
346
|
+
const report = { total: 0, valid: 0, unknown: [], rejected: [] };
|
|
347
|
+
for (const [id, value] of Object.entries(pack.strings)) {
|
|
348
|
+
report.total += 1;
|
|
349
|
+
const shape = lookup(id);
|
|
350
|
+
if (!shape) {
|
|
351
|
+
report.unknown.push(id);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const result = validateString(shape, value);
|
|
355
|
+
if ('reason' in result) {
|
|
356
|
+
report.rejected.push({ id, reason: result.reason });
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
values.set(id, shape.kind === 'text' ? { text: literal(result.parts) } : shape.kind === 'rich' ? { rich: richTemplate(result.parts) } : { parts: result.parts });
|
|
360
|
+
report.valid += 1;
|
|
361
|
+
}
|
|
362
|
+
return { name: pack.name, language: pack.language, base: pack.base, when: pack.when, rewrite: pack.rewrite ? compileRewrite(pack.rewrite) : undefined, site: pack.site, catalog: pack.catalog, values, report };
|
|
363
|
+
}
|
|
364
|
+
// -------------------------------------------------------------------------------------------
|
|
365
|
+
// Interpreting a message (closures over the parsed parts; nothing is evaluated)
|
|
366
|
+
const rulesCache = new Map();
|
|
367
|
+
/** Plural rules of the first language the runtime knows; `null` = every number is `other`. */
|
|
368
|
+
function rulesFor(languages) {
|
|
369
|
+
const key = languages.join('|');
|
|
370
|
+
let rules = rulesCache.get(key);
|
|
371
|
+
if (rules !== undefined)
|
|
372
|
+
return rules;
|
|
373
|
+
rules = null;
|
|
374
|
+
for (const language of languages) {
|
|
375
|
+
if (!language)
|
|
376
|
+
continue;
|
|
377
|
+
const tag = language.split(/-x-/i)[0];
|
|
378
|
+
try {
|
|
379
|
+
if (Intl.PluralRules.supportedLocalesOf([tag]).length) {
|
|
380
|
+
rules = new Intl.PluralRules(tag);
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
// Not a tag this runtime accepts: try the next.
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
rulesCache.set(key, rules);
|
|
389
|
+
return rules;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Renders a validated message. A parameter is inserted the way string concatenation would
|
|
393
|
+
* (`String(value)`, nothing for a missing one); `#` is the plural's own value.
|
|
394
|
+
*/
|
|
395
|
+
export function renderMessage(parts, params, languages, pound = undefined) {
|
|
396
|
+
let out = '';
|
|
397
|
+
for (const part of parts) {
|
|
398
|
+
if (typeof part === 'string') {
|
|
399
|
+
out += part;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
// The parser only yields `#` inside a plural, so there is always a value it stands for.
|
|
403
|
+
if (part.kind === 'pound') {
|
|
404
|
+
out += pound === undefined || pound === null ? '' : String(pound);
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const value = params[part.name];
|
|
408
|
+
if (part.kind === 'arg') {
|
|
409
|
+
out += value === undefined || value === null ? '' : String(value);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (part.kind === 'select') {
|
|
413
|
+
out += renderMessage(part.options[String(value)] ?? part.options.other, params, languages, pound);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const n = typeof value === 'number' ? value : Number(value);
|
|
417
|
+
const option = Number.isFinite(n)
|
|
418
|
+
? part.options[`=${n}`] ?? part.options[rulesFor(languages)?.select(n) ?? 'other'] ?? part.options.other
|
|
419
|
+
: part.options.other;
|
|
420
|
+
out += renderMessage(option, params, languages, value);
|
|
421
|
+
}
|
|
422
|
+
return out;
|
|
423
|
+
}
|
|
424
|
+
/** The site's (or a pack's) value of one key with `rewrite` applied; a message keeps its parameters' values as they are. */
|
|
425
|
+
function rewriteValue(shape, value, rewrite) {
|
|
426
|
+
if (typeof value === 'string')
|
|
427
|
+
return rewrite(value);
|
|
428
|
+
if (Array.isArray(value)) {
|
|
429
|
+
const mapped = value.map(piece => (typeof piece === 'string' ? rewrite(piece) : piece));
|
|
430
|
+
return mapped.some((piece, index) => piece !== value[index]) ? mapped : value;
|
|
431
|
+
}
|
|
432
|
+
if (typeof value === 'function') {
|
|
433
|
+
const message = value;
|
|
434
|
+
const plural = new Set(shape.plural ?? []);
|
|
435
|
+
return function (params) {
|
|
436
|
+
const input = isRecord(params) ? params : {};
|
|
437
|
+
// The parameters go through as private markers, so only the site's own words are reworded.
|
|
438
|
+
const forward = { ...input };
|
|
439
|
+
const held = [];
|
|
440
|
+
for (const name of shape.params ?? []) {
|
|
441
|
+
if (plural.has(name) || !(name in input))
|
|
442
|
+
continue;
|
|
443
|
+
const marker = `\uE000${held.length}\uE001`;
|
|
444
|
+
held.push([marker, input[name]]);
|
|
445
|
+
forward[name] = marker;
|
|
446
|
+
}
|
|
447
|
+
try {
|
|
448
|
+
const out = message.call(this, forward);
|
|
449
|
+
if (typeof out !== 'string')
|
|
450
|
+
return message.call(this, params);
|
|
451
|
+
let text = rewrite(out);
|
|
452
|
+
for (const [marker, original] of held)
|
|
453
|
+
text = text.split(marker).join(String(original));
|
|
454
|
+
return text;
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return message.call(this, params);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (typeof value === 'object' && value !== null && 'pieces' in value) {
|
|
462
|
+
const template = value;
|
|
463
|
+
return Object.freeze({ pieces: Object.freeze(template.pieces.map(rewrite)), order: template.order });
|
|
464
|
+
}
|
|
465
|
+
return value;
|
|
466
|
+
}
|
|
467
|
+
const MAX_LISTED = 200;
|
|
468
|
+
/** `packs` in application order: a later pack wins over an earlier one for the same key. */
|
|
469
|
+
export function createStringEngine(inventory, packs) {
|
|
470
|
+
const lookup = typeof inventory === 'function' ? inventory : inventoryLookup(inventory);
|
|
471
|
+
// A slice only knows the packs' keys: what it lacks is not a new key of the site.
|
|
472
|
+
const whole = typeof inventory === 'function' || !('partial' in inventory && inventory.partial);
|
|
473
|
+
const originals = new Map();
|
|
474
|
+
const applied = new Map();
|
|
475
|
+
const produced = new WeakMap();
|
|
476
|
+
const seen = new Set();
|
|
477
|
+
const unlisted = new Set();
|
|
478
|
+
const mismatched = new Set();
|
|
479
|
+
let siteLanguage;
|
|
480
|
+
const winner = (id) => {
|
|
481
|
+
for (let index = packs.length - 1; index >= 0; index -= 1) {
|
|
482
|
+
if (!conditionHolds(packs[index].when, siteLanguage))
|
|
483
|
+
continue;
|
|
484
|
+
const value = packs[index].values.get(id);
|
|
485
|
+
if (value)
|
|
486
|
+
return { pack: packs[index], value, index };
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
};
|
|
490
|
+
const resolve = (id, incoming) => {
|
|
491
|
+
let original = incoming;
|
|
492
|
+
// A value of ours coming back in (a second application): work from the site's own value again.
|
|
493
|
+
if ((typeof incoming === 'function' || (typeof incoming === 'object' && incoming !== null)) && produced.has(incoming))
|
|
494
|
+
original = produced.get(incoming);
|
|
495
|
+
else if (typeof incoming === 'string' && applied.has(id) && applied.get(id) === incoming)
|
|
496
|
+
original = originals.get(id);
|
|
497
|
+
seen.add(id);
|
|
498
|
+
const found = winner(id);
|
|
499
|
+
// Word rewriting of the packs after the one whose text won (all of them when none did).
|
|
500
|
+
const rewrites = packs.flatMap((pack, index) => (index > (found?.index ?? -1) && pack.rewrite && conditionHolds(pack.when, siteLanguage) ? [pack.rewrite] : []));
|
|
501
|
+
if (!found && !rewrites.length) {
|
|
502
|
+
if (whole && unlisted.size < MAX_LISTED && !lookup(id))
|
|
503
|
+
unlisted.add(id);
|
|
504
|
+
return original;
|
|
505
|
+
}
|
|
506
|
+
const shape = lookup(id);
|
|
507
|
+
if (!shape || shape.kind === 'format' || (found && !translatable(shape)))
|
|
508
|
+
return original;
|
|
509
|
+
if (shape.kind === 'text' ? typeof original !== 'string' : shape.kind === 'rich' ? !Array.isArray(original) : typeof original !== 'function') {
|
|
510
|
+
if (mismatched.size < MAX_LISTED)
|
|
511
|
+
mismatched.add(id);
|
|
512
|
+
return original;
|
|
513
|
+
}
|
|
514
|
+
if (originals.has(id) && originals.get(id) === original)
|
|
515
|
+
return applied.get(id);
|
|
516
|
+
let next = original;
|
|
517
|
+
if (found) {
|
|
518
|
+
if ('text' in found.value) {
|
|
519
|
+
next = found.value.text;
|
|
520
|
+
}
|
|
521
|
+
else if ('rich' in found.value) {
|
|
522
|
+
next = found.value.rich;
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
const parts = found.value.parts;
|
|
526
|
+
const languages = [found.pack.language, found.pack.base];
|
|
527
|
+
next = (params) => renderMessage(parts, isRecord(params) ? params : {}, languages);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
for (const rewrite of rewrites)
|
|
531
|
+
next = rewriteValue(shape, next, rewrite);
|
|
532
|
+
if (next === original)
|
|
533
|
+
return original;
|
|
534
|
+
if (typeof next === 'function' || typeof next === 'object')
|
|
535
|
+
produced.set(next, original);
|
|
536
|
+
originals.set(id, original);
|
|
537
|
+
applied.set(id, next);
|
|
538
|
+
return next;
|
|
539
|
+
};
|
|
540
|
+
return {
|
|
541
|
+
resolve,
|
|
542
|
+
setLanguage(language) { siteLanguage = language; },
|
|
543
|
+
originals: () => originals,
|
|
544
|
+
stats: () => ({ seen: seen.size, applied: applied.size, unlisted: [...unlisted], mismatched: [...mismatched] }),
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* The whole catalog at once: a new object with the packs applied; `catalog` itself is not
|
|
549
|
+
* changed. `applyPacks(applyPacks(c, …), …)` with one engine equals `applyPacks(c, …)`.
|
|
550
|
+
*/
|
|
551
|
+
export function applyPacks(catalog, engine) {
|
|
552
|
+
const out = {};
|
|
553
|
+
for (const id of Object.keys(catalog))
|
|
554
|
+
out[id] = engine.resolve(id, catalog[id]);
|
|
555
|
+
return out;
|
|
556
|
+
}
|
|
557
|
+
/** The inverse: every value a pack changed is the site's own again; `catalog` is not changed. */
|
|
558
|
+
export function restoreCatalog(catalog, engine) {
|
|
559
|
+
const originals = engine.originals();
|
|
560
|
+
const out = {};
|
|
561
|
+
for (const id of Object.keys(catalog))
|
|
562
|
+
out[id] = originals.has(id) ? originals.get(id) : catalog[id];
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
/** Coverage of a compiled pack per group of the inventory, plus `all`. */
|
|
566
|
+
export function coverage(inventory, pack) {
|
|
567
|
+
const out = { all: { translatable: 0, translated: 0, closed: 0 } };
|
|
568
|
+
for (const [id, entry] of Object.entries(inventory.entries)) {
|
|
569
|
+
const group = (out[entry.group ?? MAIN_GROUP] ??= { translatable: 0, translated: 0, closed: 0 });
|
|
570
|
+
for (const bucket of [group, out.all]) {
|
|
571
|
+
if (!translatable(entry)) {
|
|
572
|
+
bucket.closed += 1;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
bucket.translatable += 1;
|
|
576
|
+
if (pack.values.has(id))
|
|
577
|
+
bucket.translated += 1;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return out;
|
|
581
|
+
}
|
|
582
|
+
const sameList = (a = [], b = []) => a.length === b.length && [...a].sort().join('\n') === [...b].sort().join('\n');
|
|
583
|
+
/**
|
|
584
|
+
* Compares an inventory with what was observed now. `loaded` names the groups whose base catalog
|
|
585
|
+
* was actually read: a key can only be called vanished when its group was (a lazily loaded group
|
|
586
|
+
* that never arrived says nothing about its keys).
|
|
587
|
+
*/
|
|
588
|
+
export function driftReport(inventory, observed, loaded) {
|
|
589
|
+
const report = { added: [], reworded: [], reshaped: [], vanished: [] };
|
|
590
|
+
for (const [id, now] of Object.entries(observed)) {
|
|
591
|
+
const before = Object.prototype.hasOwnProperty.call(inventory.entries, id) ? inventory.entries[id] : undefined;
|
|
592
|
+
if (!before) {
|
|
593
|
+
report.added.push(id);
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (before.kind !== now.kind || before.slots !== now.slots || !sameList(before.params, now.params) || !sameList(before.plural, now.plural) || !sameList(before.select, now.select) || !sameList(before.tags, now.tags)) {
|
|
597
|
+
report.reshaped.push(id);
|
|
598
|
+
}
|
|
599
|
+
else if (before.hash !== now.hash) {
|
|
600
|
+
report.reworded.push(id);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const groups = new Set(loaded);
|
|
604
|
+
for (const [id, entry] of Object.entries(inventory.entries)) {
|
|
605
|
+
if (!Object.prototype.hasOwnProperty.call(observed, id) && groups.has(entry.group ?? MAIN_GROUP))
|
|
606
|
+
report.vanished.push(id);
|
|
607
|
+
}
|
|
608
|
+
return report;
|
|
609
|
+
}
|
|
610
|
+
//# sourceMappingURL=stringPacks.js.map
|