diff --git a/package.json b/package.json index d943a3f1e3..0a005d1893 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "eslint-plugin-react": "7.33.2", "execa": "5.1.1", "fs-extra": "11.1.1", - "glob": "10.3.4", + "glob": "10.3.5", "gulp": "4.0.2", "husky": "3.1.0", "lerna": "3.22.1", diff --git a/packages/app-cli/package.json b/packages/app-cli/package.json index 55054aeb72..417eb94655 100644 --- a/packages/app-cli/package.json +++ b/packages/app-cli/package.json @@ -57,7 +57,7 @@ "proper-lockfile": "4.1.2", "read-chunk": "2.1.0", "server-destroy": "1.0.1", - "sharp": "0.32.5", + "sharp": "0.32.6", "sprintf-js": "1.1.3", "sqlite3": "5.1.6", "string-padding": "1.0.2", @@ -73,7 +73,7 @@ "@joplin/tools": "~2.13", "@types/fs-extra": "11.0.2", "@types/jest": "29.5.4", - "@types/node": "18.17.17", + "@types/node": "18.17.18", "@types/proper-lockfile": "^4.1.2", "gulp": "4.0.2", "jest": "29.6.4", diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.ts b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.ts index 2fa1112131..86002e2364 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.ts +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.ts @@ -2,17 +2,16 @@ import { ContextMenuEvent, ContextMenuParams } from 'electron'; import { useEffect, RefObject } from 'react'; import { _ } from '@joplin/lib/locale'; -import Setting from '@joplin/lib/models/Setting'; import { PluginStates } from '@joplin/lib/services/plugins/reducer'; import { MenuItemLocation } from '@joplin/lib/services/plugins/api/types'; import MenuUtils from '@joplin/lib/services/commands/MenuUtils'; import CommandService from '@joplin/lib/services/CommandService'; -import convertToScreenCoordinates from '../../../../utils/convertToScreenCoordinates'; import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService'; import { EditContextMenuFilterObject } from '@joplin/lib/services/plugins/api/JoplinWorkspace'; import type CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl'; import eventManager from '@joplin/lib/eventManager'; import bridge from '../../../../../services/bridge'; +import Setting from '@joplin/lib/models/Setting'; const Menu = bridge().Menu; const MenuItem = bridge().MenuItem; @@ -35,7 +34,7 @@ const useContextMenu = (props: ContextMenuProps) => { // It might be buggy, refer to the below issue // https://github.com/laurent22/joplin/pull/3974#issuecomment-718936703 useEffect(() => { - const isAncestorOfCodeMirrorEditor = (elem: HTMLElement) => { + const isAncestorOfCodeMirrorEditor = (elem: Element) => { for (; elem.parentElement; elem = elem.parentElement) { if (elem.classList.contains(props.editorClassName)) { return true; @@ -45,14 +44,9 @@ const useContextMenu = (props: ContextMenuProps) => { return false; }; - let lastInCodeMirrorContextMenuTimestamp = 0; - - // The browser's contextmenu event provides additional information about the - // target of the event, not provided by the Electron context-menu event. - const onBrowserContextMenu = (event: Event) => { - if (isAncestorOfCodeMirrorEditor(event.target as HTMLElement)) { - lastInCodeMirrorContextMenuTimestamp = Date.now(); - } + const convertFromScreenCoordinates = (zoomPercent: number, screenXY: number) => { + const zoomFraction = zoomPercent / 100; + return screenXY / zoomFraction; }; function pointerInsideEditor(params: ContextMenuParams) { @@ -64,13 +58,15 @@ const useContextMenu = (props: ContextMenuProps) => { // params.inputFieldType is "plainText". Thus, such a check would be inconsistent. if (!elements.length || !isEditable) return false; - const maximumMsSinceBrowserEvent = 100; - if (Date.now() - lastInCodeMirrorContextMenuTimestamp > maximumMsSinceBrowserEvent) { - return false; - } - - const rect = convertToScreenCoordinates(Setting.value('windowContentZoomFactor'), elements[0].getBoundingClientRect()); - return rect.x < x && rect.y < y && rect.right > x && rect.bottom > y; + // Checks whether the element the pointer clicked on is inside the editor. + // This logic will need to be changed if the editor is eventually wrapped + // in an iframe, as elementFromPoint will return the iframe container (and not + // a child of the editor). + const zoom = Setting.value('windowContentZoomFactor'); + const xScreen = convertFromScreenCoordinates(zoom, x); + const yScreen = convertFromScreenCoordinates(zoom, y); + const intersectingElement = document.elementFromPoint(xScreen, yScreen); + return intersectingElement && isAncestorOfCodeMirrorEditor(intersectingElement); } async function onContextMenu(event: ContextMenuEvent, params: ContextMenuParams) { @@ -160,11 +156,8 @@ const useContextMenu = (props: ContextMenuProps) => { // the listener that shows the default menu. bridge().window().webContents.prependListener('context-menu', onContextMenu); - window.addEventListener('contextmenu', onBrowserContextMenu); - return () => { bridge().window().webContents.off('context-menu', onContextMenu); - window.removeEventListener('contextmenu', onBrowserContextMenu); }; }, [ props.plugins, props.editorClassName, editorRef, diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index c195c2bad1..8da0a7c8b6 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -118,13 +118,13 @@ "@joplin/tools": "~2.13", "@testing-library/react-hooks": "8.0.1", "@types/jest": "29.5.4", - "@types/node": "18.17.17", - "@types/react": "18.2.21", + "@types/node": "18.17.18", + "@types/react": "18.2.22", "@types/react-redux": "7.1.26", "@types/styled-components": "5.1.27", "electron": "25.8.1", "electron-builder": "24.4.0", - "glob": "10.3.4", + "glob": "10.3.5", "gulp": "4.0.2", "jest": "29.6.4", "jest-environment-jsdom": "29.6.4", @@ -173,7 +173,7 @@ "react-datetime": "3.2.0", "react-dom": "18.2.0", "react-redux": "8.1.2", - "react-select": "5.7.4", + "react-select": "5.7.5", "react-toggle-button": "2.2.0", "react-tooltip": "4.5.1", "redux": "4.2.1", diff --git a/packages/app-mobile/package.json b/packages/app-mobile/package.json index 53b2b36de0..b6e7502dbf 100644 --- a/packages/app-mobile/package.json +++ b/packages/app-mobile/package.json @@ -58,20 +58,20 @@ "react-native-image-picker": "5.6.1", "react-native-localize": "3.0.2", "react-native-modal-datetime-picker": "17.1.0", - "react-native-paper": "5.10.4", + "react-native-paper": "5.10.6", "react-native-popup-menu": "0.16.1", "react-native-quick-actions": "0.3.13", "react-native-rsa-native": "2.0.5", "react-native-safe-area-context": "4.7.2", "react-native-securerandom": "1.0.1", - "react-native-share": "9.2.4", + "react-native-share": "9.4.0", "react-native-side-menu-updated": "1.3.2", "react-native-sqlite-storage": "6.0.1", "react-native-url-polyfill": "2.0.0", "react-native-vector-icons": "10.0.0", "react-native-version-info": "1.1.1", "react-native-vosk": "0.1.12", - "react-native-webview": "13.4.0", + "react-native-webview": "13.5.1", "react-native-zip-archive": "6.0.9", "react-redux": "8.1.2", "redux": "4.2.1", @@ -95,7 +95,7 @@ "@tsconfig/react-native": "2.0.2", "@types/fs-extra": "11.0.2", "@types/jest": "29.5.4", - "@types/react": "18.2.21", + "@types/react": "18.2.22", "@types/react-native": "0.70.6", "@types/react-redux": "7.1.26", "@types/tar-stream": "2.2.3", diff --git a/packages/editor/package.json b/packages/editor/package.json index 64135eda27..fbcdad6d22 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -18,7 +18,7 @@ "@joplin/lib": "~2.13", "@testing-library/react-hooks": "8.0.1", "@types/jest": "29.5.4", - "@types/react": "18.0.24", + "@types/react": "18.2.22", "@types/react-redux": "7.1.26", "@types/styled-components": "5.1.27", "jest": "29.6.3", diff --git a/packages/fork-htmlparser2/package.json b/packages/fork-htmlparser2/package.json index 61c09b3a1a..a443c9d15a 100644 --- a/packages/fork-htmlparser2/package.json +++ b/packages/fork-htmlparser2/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@types/jest": "29.5.4", - "@types/node": "18.17.17", + "@types/node": "18.17.18", "@typescript-eslint/eslint-plugin": "6.0.0", "@typescript-eslint/parser": "6.0.0", "coveralls": "3.1.1", diff --git a/packages/lib/locale.test.ts b/packages/lib/locale.test.ts index a8e39d4232..0627ef5ba5 100644 --- a/packages/lib/locale.test.ts +++ b/packages/lib/locale.test.ts @@ -1,4 +1,4 @@ -import { closestSupportedLocale } from './locale'; +import { closestSupportedLocale, parsePluralForm, setLocale, _n } from './locale'; describe('locale', () => { @@ -15,4 +15,80 @@ describe('locale', () => { } }); + it('should translate plurals - en_GB', () => { + setLocale('en_GB'); + expect(_n('Copy Shareable Link', 'Copy Shareable Links', 1)).toBe('Copy Shareable Link'); + expect(_n('Copy Shareable Link', 'Copy Shareable Links', 2)).toBe('Copy Shareable Links'); + }); + + it('should translate plurals - fr_FR', () => { + setLocale('fr_FR'); + expect(_n('Copy Shareable Link', 'Copy Shareable Links', 1)).toBe('Copier lien partageable'); + expect(_n('Copy Shareable Link', 'Copy Shareable Links', 2)).toBe('Copier liens partageables'); + }); + + it('should translate plurals - pl_PL', () => { + setLocale('pl_PL'); + // Not the best test since 5 is the same as 2, but it's all I could find + expect(_n('Copy Shareable Link', 'Copy Shareable Links', 1)).toBe('Kopiuj udostępnialny link'); + expect(_n('Copy Shareable Link', 'Copy Shareable Links', 2)).toBe('Kopiuj udostępnialne linki'); + expect(_n('Copy Shareable Link', 'Copy Shareable Links', 5)).toBe('Kopiuj udostępnialne linki'); + }); + + it('should parse the plural form', async () => { + const pluralForms = [ + 'nplurals=1; plural=0;', + 'nplurals=2; plural=(n != 0);', + 'nplurals=2; plural=(n != 1);', + 'nplurals=2; plural=(n > 1);', + 'nplurals=2; plural=(n%10!=1 || n%100==11);', + 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);', + 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);', + 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);', + 'nplurals=3; plural=(n==0 ? 0 : n==1 ? 1 : 2);', + 'nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);', + 'nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);', + 'nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;', + 'nplurals=3; plural=(n==1) ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;', + 'nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0);', + 'nplurals=4; plural=(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3);', + 'nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3;', + 'nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3;', + 'nplurals=4; plural=(n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3;', + 'nplurals=5; plural=n==1 ? 0 : n==2 ? 1 : (n>2 && n<7) ? 2 :(n>6 && n<11) ? 3 : 4;', + 'nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);', + ]; + + const pluralValues = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + [2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 1, 1, 1], + [2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 1, 1, 2, 2, 2], + [0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2], + [2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2], + [2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], + [2, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2], + [0, 1, 2, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3], + [3, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + [2, 0, 1, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], + [3, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], + [4, 0, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], + [0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], + ]; + + for (let index = 0; index < pluralForms.length; index++) { + const form = pluralForms[index]; + const pluralFn = parsePluralForm(form); + for (let i = 0; i < 128; ++i) { + expect(pluralValues[index][i]).toBe(pluralFn(i)); + } + } + }); + }); diff --git a/packages/lib/locale.ts b/packages/lib/locale.ts index d732cac5bb..d9477e523a 100644 --- a/packages/lib/locale.ts +++ b/packages/lib/locale.ts @@ -7,6 +7,7 @@ interface StringToStringMap { interface CodeToCountryMap { [key: string]: string[]; } +type ParsePluralFormFunction = (n: number)=> number; const codeToLanguageE_: StringToStringMap = {}; codeToLanguageE_['aa'] = 'Afar'; @@ -436,12 +437,51 @@ const codeToCountry_: CodeToCountryMap = { let supportedLocales_: any = null; let localeStats_: any = null; -const loadedLocales_: any = {}; +const loadedLocales_: Record> = {}; + +const pluralFunctions_: Record = {}; const defaultLocale_ = 'en_GB'; let currentLocale_ = defaultLocale_; +// Copied from https://github.com/eugeny-dementev/parse-gettext-plural-form +// along with the tests +export const parsePluralForm = (form: string): ParsePluralFormFunction => { + const pluralFormRegex = /^(\s*nplurals\s*=\s*[0-9]+\s*;\s*plural\s*=\s*(?:\s|[-?|&=!<>+*/%:;a-zA-Z0-9_()])+)$/m; + + if (!pluralFormRegex.test(form)) throw new Error(`Plural-Forms is invalid: ${form}`); + + if (!/;\s*$/.test(form)) { + form += ';'; + } + + const code = [ + 'var plural;', + 'var nplurals;', + form, + 'return (plural === true ? 1 : plural ? plural : 0);', + ].join('\n'); + + // eslint-disable-next-line no-new-func -- There's a regex to check the form but it's still slighlty unsafe, eventually we should automatically generate all the functions in advance in build-translations.ts + return (new Function('n', code)) as ParsePluralFormFunction; +}; + +const getPluralFunction = (lang: string) => { + if (!(lang in pluralFunctions_)) { + const locale = closestSupportedLocale(lang); + const stats = localeStats()[locale]; + + if (!stats.pluralForms) { + pluralFunctions_[lang] = null; + } else { + pluralFunctions_[lang] = parsePluralForm(stats.pluralForms); + } + } + + return pluralFunctions_[lang]; +}; + function defaultLocale() { return defaultLocale_; } @@ -589,18 +629,45 @@ function _(s: string, ...args: any[]): string { } function _n(singular: string, plural: string, n: number, ...args: any[]) { - if (n > 1) return _(plural, ...args); - return _(singular, ...args); + if (['en_GB', 'en_US'].includes(currentLocale_)) { + if (n > 1) return _(plural, ...args); + return _(singular, ...args); + } else { + const pluralFn = getPluralFunction(currentLocale_); + const stringIndex = pluralFn ? pluralFn(n) : 0; + const strings = localeStrings(currentLocale_); + const result = strings[singular]; + + let translatedString = ''; + if (result === undefined || !result.join('')) { + translatedString = singular; + } else { + translatedString = stringIndex < result.length ? result[stringIndex] : result[0]; + } + + try { + return sprintf(translatedString, ...args); + } catch (error) { + return `${translatedString} ${args.join(', ')} (Translation error: ${error.message})`; + } + } } const stringByLocale = (locale: string, s: string, ...args: any[]): string => { const strings = localeStrings(locale); - let result = strings[s]; - if (result === '' || result === undefined) result = s; + const result = strings[s]; + let translatedString = ''; + + if (result === undefined || !result.join('')) { + translatedString = s; + } else { + translatedString = result[0]; + } + try { - return sprintf(result, ...args); + return sprintf(translatedString, ...args); } catch (error) { - return `${result} ${args.join(', ')} (Translation error: ${error.message})`; + return `${translatedString} ${args.join(', ')} (Translation error: ${error.message})`; } }; diff --git a/packages/lib/package.json b/packages/lib/package.json index 3f68d139db..688d38988a 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -19,13 +19,13 @@ "@types/fs-extra": "11.0.2", "@types/jest": "29.5.4", "@types/js-yaml": "4.0.6", - "@types/node": "18.17.17", + "@types/node": "18.17.18", "@types/node-rsa": "1.1.1", - "@types/react": "18.2.21", + "@types/react": "18.2.22", "@types/uuid": "9.0.4", "clean-html": "1.5.0", "jest": "29.6.4", - "sharp": "0.32.5", + "sharp": "0.32.6", "typescript": "5.1.6" }, "dependencies": { @@ -52,7 +52,7 @@ "es6-promise-pool": "2.5.0", "fast-deep-equal": "3.1.3", "fast-xml-parser": "3.21.1", - "follow-redirects": "1.15.2", + "follow-redirects": "1.15.3", "form-data": "4.0.0", "fs-extra": "11.1.1", "hpagent": "1.2.0", diff --git a/packages/pdf-viewer/package.json b/packages/pdf-viewer/package.json index 02950d203d..a99e50605c 100644 --- a/packages/pdf-viewer/package.json +++ b/packages/pdf-viewer/package.json @@ -21,7 +21,7 @@ "devDependencies": { "@types/jest": "29.5.4", "@types/pdfjs-dist": "2.10.378", - "@types/react": "18.2.21", + "@types/react": "18.2.22", "@types/react-dom": "18.2.7", "@types/styled-components": "5.1.27", "babel-jest": "29.6.4", diff --git a/packages/plugin-repo-cli/package.json b/packages/plugin-repo-cli/package.json index 67566d1c25..38e27ac30e 100644 --- a/packages/plugin-repo-cli/package.json +++ b/packages/plugin-repo-cli/package.json @@ -30,7 +30,7 @@ "devDependencies": { "@types/fs-extra": "11.0.2", "@types/jest": "29.5.4", - "@types/node": "18.17.17", + "@types/node": "18.17.18", "jest": "29.6.4", "source-map-loader": "4.0.1", "typescript": "5.1.6", diff --git a/packages/renderer/package.json b/packages/renderer/package.json index ca55a66126..a41892a8dd 100644 --- a/packages/renderer/package.json +++ b/packages/renderer/package.json @@ -20,7 +20,7 @@ "license": "AGPL-3.0-or-later", "devDependencies": { "@types/jest": "29.5.4", - "@types/node": "18.17.17", + "@types/node": "18.17.18", "jest": "29.6.4", "jest-environment-jsdom": "29.6.4", "ts-jest": "29.1.1", diff --git a/packages/server/package.json b/packages/server/package.json index bd727e4e04..f5148cc643 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -32,7 +32,7 @@ "bulma": "0.9.4", "bulma-prefers-dark": "0.1.0-beta.1", "compare-versions": "6.1.0", - "dayjs": "1.11.9", + "dayjs": "1.11.10", "formidable": "3.5.1", "fs-extra": "11.1.1", "html-entities": "1.4.0", diff --git a/packages/tools/build-translation.ts b/packages/tools/build-translation.ts index 108d85798e..32191060a8 100644 --- a/packages/tools/build-translation.ts +++ b/packages/tools/build-translation.ts @@ -12,7 +12,7 @@ import { countryDisplayName, countryCodeOnly } from '@joplin/lib/locale'; import { readdirSync, writeFileSync } from 'fs'; import { readFile } from 'fs/promises'; import { copy, mkdirpSync, remove } from 'fs-extra'; -const { GettextExtractor, JsExtractors } = require('gettext-extractor'); +import { GettextExtractor, JsExtractors } from 'gettext-extractor'; const rootDir = `${__dirname}/../..`; const localesDir = `${__dirname}/locales`; @@ -20,7 +20,7 @@ const libDir = `${rootDir}/packages/lib`; function serializeTranslation(translation: string) { const output = parseTranslations(translation); - return JSON.stringify(output, Object.keys(output).sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : +1), ' '); + return JSON.stringify(output, Object.keys(output).sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : +1), '\t'); } function saveToFile(filePath: string, data: string) { @@ -31,6 +31,7 @@ async function buildLocale(inputFile: string, outputFile: string) { const r = await parsePoFile(inputFile); const translation = serializeTranslation(r); saveToFile(outputFile, translation); + return { headers: r.headers }; } async function createPotFile(potFilePath: string) { @@ -391,9 +392,10 @@ async function main() { const poFilePäth = `${localesDir}/${locale}.po`; const jsonFilePath = `${jsonLocalesDir}/${locale}.json`; if (locale !== defaultLocale) await mergePotToPo(potFilePath, poFilePäth); - await buildLocale(poFilePäth, jsonFilePath); + const { headers } = await buildLocale(poFilePäth, jsonFilePath); const stat = await translationStatus(defaultLocale === locale, poFilePäth); + stat.pluralForms = headers['Plural-Forms']; stat.locale = locale; stat.languageName = countryDisplayName(locale); stats.push(stat); diff --git a/packages/tools/package.json b/packages/tools/package.json index 8a3ecad1d3..090a98e44c 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -24,11 +24,11 @@ "@joplin/renderer": "~2.13", "@joplin/utils": "~2.13", "compare-versions": "6.1.0", - "dayjs": "1.11.9", + "dayjs": "1.11.10", "execa": "4.1.0", "fs-extra": "11.1.1", "gettext-parser": "7.0.1", - "glob": "10.3.4", + "glob": "10.3.5", "markdown-it": "13.0.1", "md5-file": "5.0.0", "moment": "2.29.4", @@ -36,7 +36,7 @@ "node-fetch": "2.6.7", "relative": "3.0.2", "request": "2.88.2", - "sharp": "0.32.5", + "sharp": "0.32.6", "source-map-support": "0.5.21", "uri-template": "2.0.0", "yargs": "17.7.2" @@ -48,7 +48,7 @@ "@types/jest": "29.5.4", "@types/markdown-it": "13.0.1", "@types/mustache": "4.2.2", - "@types/node": "18.17.17", + "@types/node": "18.17.18", "@types/node-fetch": "2.6.5", "@types/yargs": "17.0.24", "gettext-extractor": "3.8.0", diff --git a/packages/tools/utils/translation.ts b/packages/tools/utils/translation.ts index e448407693..1a649da034 100644 --- a/packages/tools/utils/translation.ts +++ b/packages/tools/utils/translation.ts @@ -8,9 +8,10 @@ export interface TranslationStatus { translatorName: string; percentDone: number; untranslatedCount: number; + pluralForms?: string; } -export type Translations = Record; +export type Translations = Record; export const removePoHeaderDate = async (filePath: string) => { let sedPrefix = 'sed -i'; @@ -67,14 +68,14 @@ export const parseTranslations = (gettextTranslations: any) => { if (!translations.hasOwnProperty(n)) continue; if (n === '') continue; const t = translations[n]; - let translated = ''; + let translated: string[] = []; if (t.comments && t.comments.flag && t.comments.flag.indexOf('fuzzy') >= 0) { // Don't include fuzzy translations } else { - translated = t['msgstr'][0]; + translated = t['msgstr']; } - if (translated) output[n] = translated; + if (translated.length) output[n] = translated; } } diff --git a/packages/tools/website/utils/applyTranslations.test.ts b/packages/tools/website/utils/applyTranslations.test.ts index 02f3a91a31..6edc7eff4f 100644 --- a/packages/tools/website/utils/applyTranslations.test.ts +++ b/packages/tools/website/utils/applyTranslations.test.ts @@ -7,7 +7,7 @@ describe('applyTranslations', () => { { html: '
Translate me
', translations: { - 'Translate me': 'Traduis moi', + 'Translate me': ['Traduis moi'], }, htmlTranslated: '
\n\nTraduis moi\n\n
', }, @@ -19,14 +19,14 @@ describe('applyTranslations', () => { { html: '

\nFree your notes\n

', translations: { - 'Free your notes': 'Libérez vos notes', + 'Free your notes': ['Libérez vos notes'], }, htmlTranslated: '

\nLibérez vos notes\n

', }, { html: '
Save web pages
as notes
', translations: { - 'Save web pages
as notes': 'Sauvegardez vos pages web
en notes', + 'Save web pages
as notes': ['Sauvegardez vos pages web
en notes'], }, htmlTranslated: '
\nSauvegardez vos pages web
en notes\n
', }, diff --git a/packages/tools/website/utils/applyTranslations.ts b/packages/tools/website/utils/applyTranslations.ts index 5a6b84a8c9..235177872a 100644 --- a/packages/tools/website/utils/applyTranslations.ts +++ b/packages/tools/website/utils/applyTranslations.ts @@ -1,5 +1,6 @@ import { unique } from '@joplin/lib/ArrayUtils'; import { attributesHtml, isSelfClosingTag } from '@joplin/renderer/htmlUtils'; +import { Translations } from '../../utils/translation'; const Entities = require('html-entities').AllHtmlEntities; const htmlentities = new Entities().encode; const htmlparser2 = require('@joplin/fork-htmlparser2'); @@ -15,7 +16,7 @@ const trimHtml = (content: string) => { .replace(/\t+$/, ''); }; -const findTranslation = (englishString: string, translations: Record): string => { +const findTranslation = (englishString: string, translations: Translations): string => { const stringsToTry = unique([ englishString, englishString.replace(//gi, '
'), @@ -26,7 +27,8 @@ const findTranslation = (englishString: string, translations: Record { .replace(/{{> /gi, '{{> '); // Don't break Mustache partials }; -export default (html: string, _languageCode: string, translations: Record) => { +export default (html: string, _languageCode: string, translations: Translations) => { const output: string[] = []; interface State { diff --git a/packages/tools/website/utils/processTranslations.ts b/packages/tools/website/utils/processTranslations.ts index 33c9ed010c..a3eca9981c 100644 --- a/packages/tools/website/utils/processTranslations.ts +++ b/packages/tools/website/utils/processTranslations.ts @@ -1,8 +1,9 @@ import { mkdirp, readFile, writeFile } from 'fs-extra'; import { dirname } from 'path'; +import { Translations } from '../../utils/translation'; import applyTranslations from './applyTranslations'; -export default async (englishFilePath: string, translatedFilePath: string, languageCode: string, translations: Record) => { +export default async (englishFilePath: string, translatedFilePath: string, languageCode: string, translations: Translations) => { let content = await readFile(englishFilePath, 'utf8'); content = content.replace('', ``); const translatedContent = await applyTranslations(content, languageCode, translations); diff --git a/packages/tools/website/utils/types.ts b/packages/tools/website/utils/types.ts index 3eb079ed05..a4123f5534 100644 --- a/packages/tools/website/utils/types.ts +++ b/packages/tools/website/utils/types.ts @@ -1,5 +1,6 @@ import { Plan, StripePublicConfig } from '@joplin/lib/utils/joplinCloud'; import { Sponsors } from '../../utils/loadSponsors'; +import { Translations } from '../../utils/translation'; import { OpenGraphTags } from './openGraph'; export enum Env { @@ -8,7 +9,7 @@ export enum Env { } export interface Locale { - htmlTranslations: Record; + htmlTranslations: Translations; lang: string; pathPrefix: string; } diff --git a/packages/utils/package.json b/packages/utils/package.json index 26bef76f29..0cb992aa73 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -29,7 +29,7 @@ "async-mutex": "0.4.0", "execa": "5.1.1", "fs-extra": "11.1.1", - "glob": "10.3.4", + "glob": "10.3.5", "html-entities": "1.4.0", "moment": "2.29.4", "node-fetch": "2.6.7", diff --git a/yarn.lock b/yarn.lock index dc5a5cd47d..70f45de319 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4664,8 +4664,8 @@ __metadata: "@testing-library/react-hooks": 8.0.1 "@types/jest": 29.5.4 "@types/mustache": 4.2.2 - "@types/node": 18.17.17 - "@types/react": 18.2.21 + "@types/node": 18.17.18 + "@types/react": 18.2.22 "@types/react-redux": 7.1.26 "@types/styled-components": 5.1.27 async-mutex: 0.4.0 @@ -4679,7 +4679,7 @@ __metadata: electron-window-state: 5.0.3 formatcoords: 1.1.3 fs-extra: 11.1.1 - glob: 10.3.4 + glob: 10.3.5 gulp: 4.0.2 highlight.js: 11.8.0 immer: 7.0.15 @@ -4701,7 +4701,7 @@ __metadata: react-datetime: 3.2.0 react-dom: 18.2.0 react-redux: 8.1.2 - react-select: 5.7.4 + react-select: 5.7.5 react-test-renderer: 18.2.0 react-toggle-button: 2.2.0 react-tooltip: 4.5.1 @@ -4753,7 +4753,7 @@ __metadata: "@tsconfig/react-native": 2.0.2 "@types/fs-extra": 11.0.2 "@types/jest": 29.5.4 - "@types/react": 18.2.21 + "@types/react": 18.2.22 "@types/react-native": 0.70.6 "@types/react-redux": 7.1.26 "@types/tar-stream": 2.2.3 @@ -4797,20 +4797,20 @@ __metadata: react-native-image-picker: 5.6.1 react-native-localize: 3.0.2 react-native-modal-datetime-picker: 17.1.0 - react-native-paper: 5.10.4 + react-native-paper: 5.10.6 react-native-popup-menu: 0.16.1 react-native-quick-actions: 0.3.13 react-native-rsa-native: 2.0.5 react-native-safe-area-context: 4.7.2 react-native-securerandom: 1.0.1 - react-native-share: 9.2.4 + react-native-share: 9.4.0 react-native-side-menu-updated: 1.3.2 react-native-sqlite-storage: 6.0.1 react-native-url-polyfill: 2.0.0 react-native-vector-icons: 10.0.0 react-native-version-info: 1.1.1 react-native-vosk: 0.1.12 - react-native-webview: 13.4.0 + react-native-webview: 13.5.1 react-native-zip-archive: 6.0.9 react-redux: 8.1.2 react-test-renderer: 18.2.0 @@ -4855,7 +4855,7 @@ __metadata: "@replit/codemirror-vim": 6.0.14 "@testing-library/react-hooks": 8.0.1 "@types/jest": 29.5.4 - "@types/react": 18.0.24 + "@types/react": 18.2.22 "@types/react-redux": 7.1.26 "@types/styled-components": 5.1.27 jest: 29.6.3 @@ -4870,7 +4870,7 @@ __metadata: resolution: "@joplin/fork-htmlparser2@workspace:packages/fork-htmlparser2" dependencies: "@types/jest": 29.5.4 - "@types/node": 18.17.17 + "@types/node": 18.17.18 "@typescript-eslint/eslint-plugin": 6.0.0 "@typescript-eslint/parser": 6.0.0 coveralls: 3.1.1 @@ -4936,9 +4936,9 @@ __metadata: "@types/jest": 29.5.4 "@types/js-yaml": 4.0.6 "@types/nanoid": 3.0.0 - "@types/node": 18.17.17 + "@types/node": 18.17.18 "@types/node-rsa": 1.1.1 - "@types/react": 18.2.21 + "@types/react": 18.2.22 "@types/uuid": 9.0.4 async-mutex: 0.4.0 base-64: 1.0.0 @@ -4953,7 +4953,7 @@ __metadata: es6-promise-pool: 2.5.0 fast-deep-equal: 3.1.3 fast-xml-parser: 3.21.1 - follow-redirects: 1.15.2 + follow-redirects: 1.15.3 form-data: 4.0.0 fs-extra: 11.1.1 hpagent: 1.2.0 @@ -4984,7 +4984,7 @@ __metadata: relative: 3.0.2 reselect: 4.1.8 server-destroy: 1.0.1 - sharp: 0.32.5 + sharp: 0.32.6 sprintf-js: 1.1.3 sqlite3: 5.1.6 string-padding: 1.0.2 @@ -5010,7 +5010,7 @@ __metadata: "@joplin/lib": ~2.13 "@types/jest": 29.5.4 "@types/pdfjs-dist": 2.10.378 - "@types/react": 18.2.21 + "@types/react": 18.2.22 "@types/react-dom": 18.2.7 "@types/styled-components": 5.1.27 async-mutex: 0.4.0 @@ -5040,7 +5040,7 @@ __metadata: "@joplin/utils": ~2.13 "@types/fs-extra": 11.0.2 "@types/jest": 29.5.4 - "@types/node": 18.17.17 + "@types/node": 18.17.18 fs-extra: 11.1.1 gh-release-assets: 2.0.1 jest: 29.6.4 @@ -5094,7 +5094,7 @@ __metadata: "@joplin/fork-uslug": ^1.0.11 "@joplin/utils": ~2.13 "@types/jest": 29.5.4 - "@types/node": 18.17.17 + "@types/node": 18.17.18 font-awesome-filetypes: 2.1.0 fs-extra: 11.1.1 highlight.js: 11.8.0 @@ -5151,7 +5151,7 @@ __metadata: bulma: 0.9.4 bulma-prefers-dark: 0.1.0-beta.1 compare-versions: 6.1.0 - dayjs: 1.11.9 + dayjs: 1.11.10 formidable: 3.5.1 fs-extra: 11.1.1 gulp: 4.0.2 @@ -5198,16 +5198,16 @@ __metadata: "@types/jest": 29.5.4 "@types/markdown-it": 13.0.1 "@types/mustache": 4.2.2 - "@types/node": 18.17.17 + "@types/node": 18.17.18 "@types/node-fetch": 2.6.5 "@types/yargs": 17.0.24 compare-versions: 6.1.0 - dayjs: 1.11.9 + dayjs: 1.11.10 execa: 4.1.0 fs-extra: 11.1.1 gettext-extractor: 3.8.0 gettext-parser: 7.0.1 - glob: 10.3.4 + glob: 10.3.5 gulp: 4.0.2 html-entities: 1.4.0 jest: 29.6.4 @@ -5220,7 +5220,7 @@ __metadata: request: 2.88.2 rss: 1.2.2 sass: 1.66.1 - sharp: 0.32.5 + sharp: 0.32.6 source-map-support: 0.5.21 sqlite3: 5.1.6 typescript: 5.1.6 @@ -5283,7 +5283,7 @@ __metadata: async-mutex: 0.4.0 execa: 5.1.1 fs-extra: 11.1.1 - glob: 10.3.4 + glob: 10.3.5 html-entities: 1.4.0 jest: 29.6.4 moment: 2.29.4 @@ -8231,10 +8231,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:18.17.17": - version: 18.17.17 - resolution: "@types/node@npm:18.17.17" - checksum: ff28f347c77723780836f9bb2ffa6db0cd72490bfd7604397c03db31db34f1f2899e82f0aaf3e825efeb09c15bd94d076ea9aca19a1407e1b56cb4603318936c +"@types/node@npm:18.17.18": + version: 18.17.18 + resolution: "@types/node@npm:18.17.18" + checksum: 59cbd906363d37017fe9ba0c08c1446e440d4d977459609c5f90b8fb7eb41f273ce8af30c5a5b5d599d7de934c1b3702bc9fc27caf8d2270e5cdb659c5232991 languageName: node linkType: hard @@ -8383,25 +8383,14 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:18.0.24": - version: 18.0.24 - resolution: "@types/react@npm:18.0.24" +"@types/react@npm:18.2.22": + version: 18.2.22 + resolution: "@types/react@npm:18.2.22" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 7d06125bac61e1c6661e5dfbeeeb56d5b6d1d4c743292faebaa6b0f30f8414c7af3cadf674923fd86e4ca14e82566ff9156cd40c56786be024600c31b97d6c03 - languageName: node - linkType: hard - -"@types/react@npm:18.2.21": - version: 18.2.21 - resolution: "@types/react@npm:18.2.21" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: ffed203bfe7aad772b8286f7953305c9181ac3a8f27d3f5400fbbc2a8e27ca8e5bbff818ee014f39ca0d19d2b3bb154e5bdbec7e232c6f80b59069375aa78349 + checksum: 44289523dabaadcd3fd85689abb98f9ebcc8492d7e978348d1c986138acef4801030b279e89a19e38a6319e294bcea77559e37e0c803e4bacf2b8ae3a56ba587 languageName: node linkType: hard @@ -14067,7 +14056,14 @@ __metadata: languageName: node linkType: hard -"dayjs@npm:1.11.9, dayjs@npm:^1.11.7": +"dayjs@npm:1.11.10": + version: 1.11.10 + resolution: "dayjs@npm:1.11.10" + checksum: a6b5a3813b8884f5cd557e2e6b7fa569f4c5d0c97aca9558e38534af4f2d60daafd3ff8c2000fed3435cfcec9e805bcebd99f90130c6d1c5ef524084ced588c4 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.7": version: 1.11.9 resolution: "dayjs@npm:1.11.9" checksum: a4844d83dc87f921348bb9b1b93af851c51e6f71fa259604809cfe1b49d1230e6b0212dab44d1cb01994c096ad3a77ea1cf18fa55154da6efcc9d3610526ac38 @@ -17415,13 +17411,13 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:1.15.2": - version: 1.15.2 - resolution: "follow-redirects@npm:1.15.2" +"follow-redirects@npm:1.15.3": + version: 1.15.3 + resolution: "follow-redirects@npm:1.15.3" peerDependenciesMeta: debug: optional: true - checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190 + checksum: 584da22ec5420c837bd096559ebfb8fe69d82512d5585004e36a3b4a6ef6d5905780e0c74508c7b72f907d1fa2b7bd339e613859e9c304d0dc96af2027fd0231 languageName: node linkType: hard @@ -18344,9 +18340,9 @@ __metadata: languageName: node linkType: hard -"glob@npm:10.3.4": - version: 10.3.4 - resolution: "glob@npm:10.3.4" +"glob@npm:10.3.5": + version: 10.3.5 + resolution: "glob@npm:10.3.5" dependencies: foreground-child: ^3.1.0 jackspeak: ^2.0.3 @@ -18355,7 +18351,7 @@ __metadata: path-scurry: ^1.10.1 bin: glob: dist/cjs/src/bin.js - checksum: 176b97c124414401cb51329a93d2ba112cef8814adbed10348481916b9521b677773eee2691cb6b24d66632d8c8bb8913533f5ac4bfb2d0ef5454a1856082361 + checksum: 564f4799cae48c0bcc841c88a20b539b5701c27ed5596f8623f588b3c523262d3fc20eb1ea89cab9c75b0912faf40ca5501fc835f982225d0d0599282b09e97a languageName: node linkType: hard @@ -22172,7 +22168,7 @@ __metadata: "@joplin/utils": ~2.13 "@types/fs-extra": 11.0.2 "@types/jest": 29.5.4 - "@types/node": 18.17.17 + "@types/node": 18.17.18 "@types/proper-lockfile": ^4.1.2 aws-sdk: 2.1340.0 chalk: 4.1.2 @@ -22189,7 +22185,7 @@ __metadata: proper-lockfile: 4.1.2 read-chunk: 2.1.0 server-destroy: 1.0.1 - sharp: 0.32.5 + sharp: 0.32.6 sprintf-js: 1.1.3 sqlite3: 5.1.6 string-padding: 1.0.2 @@ -29412,9 +29408,9 @@ __metadata: languageName: node linkType: hard -"react-native-paper@npm:5.10.4": - version: 5.10.4 - resolution: "react-native-paper@npm:5.10.4" +"react-native-paper@npm:5.10.6": + version: 5.10.6 + resolution: "react-native-paper@npm:5.10.6" dependencies: "@callstack/react-theme-provider": ^3.0.9 color: ^3.1.2 @@ -29424,7 +29420,7 @@ __metadata: react-native: "*" react-native-safe-area-context: "*" react-native-vector-icons: "*" - checksum: 28662d669b728601e7998be3a9488e3b8cf81b0a8cbe4cdc499bb88eac3de872ed5f05f94b14c905d56f3db3e9316eee696b317860e168cd0a52b90f38025cb2 + checksum: 6793a0ad54ad64894fc6b3d8a2f86c06a4cd553d38d1d22776f04cc9991e33cab83cc01c2ecd600356cd5f0ea7b847660ca1487432a09f772adadc33b0fa4edf languageName: node linkType: hard @@ -29470,10 +29466,10 @@ __metadata: languageName: node linkType: hard -"react-native-share@npm:9.2.4": - version: 9.2.4 - resolution: "react-native-share@npm:9.2.4" - checksum: 2d6062c5de2202afb2cf360c82283d7314033e3920980a1dc40c24e5d9208960f1225ee9a02727ff8174a290a95ede3120ce6084f5a60d0e40e138aa03683db7 +"react-native-share@npm:9.4.0": + version: 9.4.0 + resolution: "react-native-share@npm:9.4.0" + checksum: 0ca3afca7d352b0c107af326b63f4024fe307134eb1d922fd3e870900d3dded04aa4f3e2d581c313b4e100bc3ed83b85538a3fb05ae6de91c2797ee2c5f238b3 languageName: node linkType: hard @@ -29550,16 +29546,16 @@ __metadata: languageName: node linkType: hard -"react-native-webview@npm:13.4.0": - version: 13.4.0 - resolution: "react-native-webview@npm:13.4.0" +"react-native-webview@npm:13.5.1": + version: 13.5.1 + resolution: "react-native-webview@npm:13.5.1" dependencies: escape-string-regexp: 2.0.0 invariant: 2.2.4 peerDependencies: react: "*" react-native: "*" - checksum: fb2b6f936d4f29eeda44e8d1187833c9ffe5a16183600e7b160a9d30052e8eaa8d604182b5e778b99c60d6247160c86dc2ee878b2073f1f775efdec89a058b1d + checksum: f7536d0832c401d75c6f92bb997daeb7fe9de82471bf50bbc895421b82cd355da476dc393b186e9256d5759c345f5c97f2f8185fd079d3cb6fc174ca8ee70ba5 languageName: node linkType: hard @@ -29761,9 +29757,9 @@ __metadata: languageName: node linkType: hard -"react-select@npm:5.7.4": - version: 5.7.4 - resolution: "react-select@npm:5.7.4" +"react-select@npm:5.7.5": + version: 5.7.5 + resolution: "react-select@npm:5.7.5" dependencies: "@babel/runtime": ^7.12.0 "@emotion/cache": ^11.4.0 @@ -29777,7 +29773,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: ca72941ad1d2c578ec04c09ed3deb7e373f987e589f403fadedc6fcc3e29935b5425ec4d2628f0fe58c21319bcaf153c0d0172432e09fc6423da869d848de757 + checksum: 88f2d94c4a6778df525a9fb5d7acac1bf34821f6efcfdc5927ec608f5f933cf3f47e1c4e4fd3b92d7b2ba1d91e44595d45ac4e2fd7528ba420086008ac5a81cf languageName: node linkType: hard @@ -30971,7 +30967,7 @@ __metadata: eslint-plugin-react: 7.33.2 execa: 5.1.1 fs-extra: 11.1.1 - glob: 10.3.4 + glob: 10.3.5 gulp: 4.0.2 http-server: 14.1.1 husky: 3.1.0 @@ -31503,9 +31499,9 @@ __metadata: languageName: node linkType: hard -"sharp@npm:0.32.5": - version: 0.32.5 - resolution: "sharp@npm:0.32.5" +"sharp@npm:0.32.6": + version: 0.32.6 + resolution: "sharp@npm:0.32.6" dependencies: color: ^4.2.3 detect-libc: ^2.0.2 @@ -31516,7 +31512,7 @@ __metadata: simple-get: ^4.0.1 tar-fs: ^3.0.4 tunnel-agent: ^0.6.0 - checksum: 3cd6dc037c9ba126a30af90ac94043c4418bbb4228e15fd446638ff43fc9b14eabb553037988e484162c318f7baff21d896a5bef7dcc453f608e247d468f41e0 + checksum: 0cca1d16b1920800c0e22d27bc6305f4c67c9ebe44f67daceb30bf645ae39e7fb7dfbd7f5d6cd9f9eebfddd87ac3f7e2695f4eb906d19b7a775286238e6a29fc languageName: node linkType: hard