Merge branch 'dev' of github.com:laurent22/joplin into dev

pull/9052/head
Laurent Cozic 2023-10-11 13:05:30 +03:00
commit 3ac25104c3
23 changed files with 283 additions and 144 deletions

View File

@ -79,7 +79,7 @@
"eslint-plugin-react": "7.33.2", "eslint-plugin-react": "7.33.2",
"execa": "5.1.1", "execa": "5.1.1",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"glob": "10.3.4", "glob": "10.3.5",
"gulp": "4.0.2", "gulp": "4.0.2",
"husky": "3.1.0", "husky": "3.1.0",
"lerna": "3.22.1", "lerna": "3.22.1",

View File

@ -57,7 +57,7 @@
"proper-lockfile": "4.1.2", "proper-lockfile": "4.1.2",
"read-chunk": "2.1.0", "read-chunk": "2.1.0",
"server-destroy": "1.0.1", "server-destroy": "1.0.1",
"sharp": "0.32.5", "sharp": "0.32.6",
"sprintf-js": "1.1.3", "sprintf-js": "1.1.3",
"sqlite3": "5.1.6", "sqlite3": "5.1.6",
"string-padding": "1.0.2", "string-padding": "1.0.2",
@ -73,7 +73,7 @@
"@joplin/tools": "~2.13", "@joplin/tools": "~2.13",
"@types/fs-extra": "11.0.2", "@types/fs-extra": "11.0.2",
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/node": "18.17.17", "@types/node": "18.17.18",
"@types/proper-lockfile": "^4.1.2", "@types/proper-lockfile": "^4.1.2",
"gulp": "4.0.2", "gulp": "4.0.2",
"jest": "29.6.4", "jest": "29.6.4",

View File

@ -2,17 +2,16 @@
import { ContextMenuEvent, ContextMenuParams } from 'electron'; import { ContextMenuEvent, ContextMenuParams } from 'electron';
import { useEffect, RefObject } from 'react'; import { useEffect, RefObject } from 'react';
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import Setting from '@joplin/lib/models/Setting';
import { PluginStates } from '@joplin/lib/services/plugins/reducer'; import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import { MenuItemLocation } from '@joplin/lib/services/plugins/api/types'; import { MenuItemLocation } from '@joplin/lib/services/plugins/api/types';
import MenuUtils from '@joplin/lib/services/commands/MenuUtils'; import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
import CommandService from '@joplin/lib/services/CommandService'; import CommandService from '@joplin/lib/services/CommandService';
import convertToScreenCoordinates from '../../../../utils/convertToScreenCoordinates';
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService'; import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
import { EditContextMenuFilterObject } from '@joplin/lib/services/plugins/api/JoplinWorkspace'; import { EditContextMenuFilterObject } from '@joplin/lib/services/plugins/api/JoplinWorkspace';
import type CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl'; import type CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
import eventManager from '@joplin/lib/eventManager'; import eventManager from '@joplin/lib/eventManager';
import bridge from '../../../../../services/bridge'; import bridge from '../../../../../services/bridge';
import Setting from '@joplin/lib/models/Setting';
const Menu = bridge().Menu; const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem; const MenuItem = bridge().MenuItem;
@ -35,7 +34,7 @@ const useContextMenu = (props: ContextMenuProps) => {
// It might be buggy, refer to the below issue // It might be buggy, refer to the below issue
// https://github.com/laurent22/joplin/pull/3974#issuecomment-718936703 // https://github.com/laurent22/joplin/pull/3974#issuecomment-718936703
useEffect(() => { useEffect(() => {
const isAncestorOfCodeMirrorEditor = (elem: HTMLElement) => { const isAncestorOfCodeMirrorEditor = (elem: Element) => {
for (; elem.parentElement; elem = elem.parentElement) { for (; elem.parentElement; elem = elem.parentElement) {
if (elem.classList.contains(props.editorClassName)) { if (elem.classList.contains(props.editorClassName)) {
return true; return true;
@ -45,14 +44,9 @@ const useContextMenu = (props: ContextMenuProps) => {
return false; return false;
}; };
let lastInCodeMirrorContextMenuTimestamp = 0; const convertFromScreenCoordinates = (zoomPercent: number, screenXY: number) => {
const zoomFraction = zoomPercent / 100;
// The browser's contextmenu event provides additional information about the return screenXY / zoomFraction;
// 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();
}
}; };
function pointerInsideEditor(params: ContextMenuParams) { function pointerInsideEditor(params: ContextMenuParams) {
@ -64,13 +58,15 @@ const useContextMenu = (props: ContextMenuProps) => {
// params.inputFieldType is "plainText". Thus, such a check would be inconsistent. // params.inputFieldType is "plainText". Thus, such a check would be inconsistent.
if (!elements.length || !isEditable) return false; if (!elements.length || !isEditable) return false;
const maximumMsSinceBrowserEvent = 100; // Checks whether the element the pointer clicked on is inside the editor.
if (Date.now() - lastInCodeMirrorContextMenuTimestamp > maximumMsSinceBrowserEvent) { // This logic will need to be changed if the editor is eventually wrapped
return false; // in an iframe, as elementFromPoint will return the iframe container (and not
} // a child of the editor).
const zoom = Setting.value('windowContentZoomFactor');
const rect = convertToScreenCoordinates(Setting.value('windowContentZoomFactor'), elements[0].getBoundingClientRect()); const xScreen = convertFromScreenCoordinates(zoom, x);
return rect.x < x && rect.y < y && rect.right > x && rect.bottom > y; const yScreen = convertFromScreenCoordinates(zoom, y);
const intersectingElement = document.elementFromPoint(xScreen, yScreen);
return intersectingElement && isAncestorOfCodeMirrorEditor(intersectingElement);
} }
async function onContextMenu(event: ContextMenuEvent, params: ContextMenuParams) { async function onContextMenu(event: ContextMenuEvent, params: ContextMenuParams) {
@ -160,11 +156,8 @@ const useContextMenu = (props: ContextMenuProps) => {
// the listener that shows the default menu. // the listener that shows the default menu.
bridge().window().webContents.prependListener('context-menu', onContextMenu); bridge().window().webContents.prependListener('context-menu', onContextMenu);
window.addEventListener('contextmenu', onBrowserContextMenu);
return () => { return () => {
bridge().window().webContents.off('context-menu', onContextMenu); bridge().window().webContents.off('context-menu', onContextMenu);
window.removeEventListener('contextmenu', onBrowserContextMenu);
}; };
}, [ }, [
props.plugins, props.editorClassName, editorRef, props.plugins, props.editorClassName, editorRef,

View File

@ -118,13 +118,13 @@
"@joplin/tools": "~2.13", "@joplin/tools": "~2.13",
"@testing-library/react-hooks": "8.0.1", "@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/node": "18.17.17", "@types/node": "18.17.18",
"@types/react": "18.2.21", "@types/react": "18.2.22",
"@types/react-redux": "7.1.26", "@types/react-redux": "7.1.26",
"@types/styled-components": "5.1.27", "@types/styled-components": "5.1.27",
"electron": "25.8.1", "electron": "25.8.1",
"electron-builder": "24.4.0", "electron-builder": "24.4.0",
"glob": "10.3.4", "glob": "10.3.5",
"gulp": "4.0.2", "gulp": "4.0.2",
"jest": "29.6.4", "jest": "29.6.4",
"jest-environment-jsdom": "29.6.4", "jest-environment-jsdom": "29.6.4",
@ -173,7 +173,7 @@
"react-datetime": "3.2.0", "react-datetime": "3.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-redux": "8.1.2", "react-redux": "8.1.2",
"react-select": "5.7.4", "react-select": "5.7.5",
"react-toggle-button": "2.2.0", "react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1", "react-tooltip": "4.5.1",
"redux": "4.2.1", "redux": "4.2.1",

View File

@ -58,20 +58,20 @@
"react-native-image-picker": "5.6.1", "react-native-image-picker": "5.6.1",
"react-native-localize": "3.0.2", "react-native-localize": "3.0.2",
"react-native-modal-datetime-picker": "17.1.0", "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-popup-menu": "0.16.1",
"react-native-quick-actions": "0.3.13", "react-native-quick-actions": "0.3.13",
"react-native-rsa-native": "2.0.5", "react-native-rsa-native": "2.0.5",
"react-native-safe-area-context": "4.7.2", "react-native-safe-area-context": "4.7.2",
"react-native-securerandom": "1.0.1", "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-side-menu-updated": "1.3.2",
"react-native-sqlite-storage": "6.0.1", "react-native-sqlite-storage": "6.0.1",
"react-native-url-polyfill": "2.0.0", "react-native-url-polyfill": "2.0.0",
"react-native-vector-icons": "10.0.0", "react-native-vector-icons": "10.0.0",
"react-native-version-info": "1.1.1", "react-native-version-info": "1.1.1",
"react-native-vosk": "0.1.12", "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-native-zip-archive": "6.0.9",
"react-redux": "8.1.2", "react-redux": "8.1.2",
"redux": "4.2.1", "redux": "4.2.1",
@ -95,7 +95,7 @@
"@tsconfig/react-native": "2.0.2", "@tsconfig/react-native": "2.0.2",
"@types/fs-extra": "11.0.2", "@types/fs-extra": "11.0.2",
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/react": "18.2.21", "@types/react": "18.2.22",
"@types/react-native": "0.70.6", "@types/react-native": "0.70.6",
"@types/react-redux": "7.1.26", "@types/react-redux": "7.1.26",
"@types/tar-stream": "2.2.3", "@types/tar-stream": "2.2.3",

View File

@ -18,7 +18,7 @@
"@joplin/lib": "~2.13", "@joplin/lib": "~2.13",
"@testing-library/react-hooks": "8.0.1", "@testing-library/react-hooks": "8.0.1",
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/react": "18.0.24", "@types/react": "18.2.22",
"@types/react-redux": "7.1.26", "@types/react-redux": "7.1.26",
"@types/styled-components": "5.1.27", "@types/styled-components": "5.1.27",
"jest": "29.6.3", "jest": "29.6.3",

View File

@ -46,7 +46,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/node": "18.17.17", "@types/node": "18.17.18",
"@typescript-eslint/eslint-plugin": "6.0.0", "@typescript-eslint/eslint-plugin": "6.0.0",
"@typescript-eslint/parser": "6.0.0", "@typescript-eslint/parser": "6.0.0",
"coveralls": "3.1.1", "coveralls": "3.1.1",

View File

@ -1,4 +1,4 @@
import { closestSupportedLocale } from './locale'; import { closestSupportedLocale, parsePluralForm, setLocale, _n } from './locale';
describe('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));
}
}
});
}); });

View File

@ -7,6 +7,7 @@ interface StringToStringMap {
interface CodeToCountryMap { interface CodeToCountryMap {
[key: string]: string[]; [key: string]: string[];
} }
type ParsePluralFormFunction = (n: number)=> number;
const codeToLanguageE_: StringToStringMap = {}; const codeToLanguageE_: StringToStringMap = {};
codeToLanguageE_['aa'] = 'Afar'; codeToLanguageE_['aa'] = 'Afar';
@ -436,12 +437,51 @@ const codeToCountry_: CodeToCountryMap = {
let supportedLocales_: any = null; let supportedLocales_: any = null;
let localeStats_: any = null; let localeStats_: any = null;
const loadedLocales_: any = {}; const loadedLocales_: Record<string, Record<string, string[]>> = {};
const pluralFunctions_: Record<string, ParsePluralFormFunction> = {};
const defaultLocale_ = 'en_GB'; const defaultLocale_ = 'en_GB';
let currentLocale_ = defaultLocale_; 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() { function defaultLocale() {
return defaultLocale_; return defaultLocale_;
} }
@ -589,18 +629,45 @@ function _(s: string, ...args: any[]): string {
} }
function _n(singular: string, plural: string, n: number, ...args: any[]) { function _n(singular: string, plural: string, n: number, ...args: any[]) {
if (n > 1) return _(plural, ...args); if (['en_GB', 'en_US'].includes(currentLocale_)) {
return _(singular, ...args); 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 stringByLocale = (locale: string, s: string, ...args: any[]): string => {
const strings = localeStrings(locale); const strings = localeStrings(locale);
let result = strings[s]; const result = strings[s];
if (result === '' || result === undefined) result = s; let translatedString = '';
if (result === undefined || !result.join('')) {
translatedString = s;
} else {
translatedString = result[0];
}
try { try {
return sprintf(result, ...args); return sprintf(translatedString, ...args);
} catch (error) { } catch (error) {
return `${result} ${args.join(', ')} (Translation error: ${error.message})`; return `${translatedString} ${args.join(', ')} (Translation error: ${error.message})`;
} }
}; };

View File

@ -19,13 +19,13 @@
"@types/fs-extra": "11.0.2", "@types/fs-extra": "11.0.2",
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/js-yaml": "4.0.6", "@types/js-yaml": "4.0.6",
"@types/node": "18.17.17", "@types/node": "18.17.18",
"@types/node-rsa": "1.1.1", "@types/node-rsa": "1.1.1",
"@types/react": "18.2.21", "@types/react": "18.2.22",
"@types/uuid": "9.0.4", "@types/uuid": "9.0.4",
"clean-html": "1.5.0", "clean-html": "1.5.0",
"jest": "29.6.4", "jest": "29.6.4",
"sharp": "0.32.5", "sharp": "0.32.6",
"typescript": "5.1.6" "typescript": "5.1.6"
}, },
"dependencies": { "dependencies": {
@ -52,7 +52,7 @@
"es6-promise-pool": "2.5.0", "es6-promise-pool": "2.5.0",
"fast-deep-equal": "3.1.3", "fast-deep-equal": "3.1.3",
"fast-xml-parser": "3.21.1", "fast-xml-parser": "3.21.1",
"follow-redirects": "1.15.2", "follow-redirects": "1.15.3",
"form-data": "4.0.0", "form-data": "4.0.0",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"hpagent": "1.2.0", "hpagent": "1.2.0",

View File

@ -21,7 +21,7 @@
"devDependencies": { "devDependencies": {
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/pdfjs-dist": "2.10.378", "@types/pdfjs-dist": "2.10.378",
"@types/react": "18.2.21", "@types/react": "18.2.22",
"@types/react-dom": "18.2.7", "@types/react-dom": "18.2.7",
"@types/styled-components": "5.1.27", "@types/styled-components": "5.1.27",
"babel-jest": "29.6.4", "babel-jest": "29.6.4",

View File

@ -30,7 +30,7 @@
"devDependencies": { "devDependencies": {
"@types/fs-extra": "11.0.2", "@types/fs-extra": "11.0.2",
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/node": "18.17.17", "@types/node": "18.17.18",
"jest": "29.6.4", "jest": "29.6.4",
"source-map-loader": "4.0.1", "source-map-loader": "4.0.1",
"typescript": "5.1.6", "typescript": "5.1.6",

View File

@ -20,7 +20,7 @@
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"devDependencies": { "devDependencies": {
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/node": "18.17.17", "@types/node": "18.17.18",
"jest": "29.6.4", "jest": "29.6.4",
"jest-environment-jsdom": "29.6.4", "jest-environment-jsdom": "29.6.4",
"ts-jest": "29.1.1", "ts-jest": "29.1.1",

View File

@ -32,7 +32,7 @@
"bulma": "0.9.4", "bulma": "0.9.4",
"bulma-prefers-dark": "0.1.0-beta.1", "bulma-prefers-dark": "0.1.0-beta.1",
"compare-versions": "6.1.0", "compare-versions": "6.1.0",
"dayjs": "1.11.9", "dayjs": "1.11.10",
"formidable": "3.5.1", "formidable": "3.5.1",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"html-entities": "1.4.0", "html-entities": "1.4.0",

View File

@ -12,7 +12,7 @@ import { countryDisplayName, countryCodeOnly } from '@joplin/lib/locale';
import { readdirSync, writeFileSync } from 'fs'; import { readdirSync, writeFileSync } from 'fs';
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import { copy, mkdirpSync, remove } from 'fs-extra'; import { copy, mkdirpSync, remove } from 'fs-extra';
const { GettextExtractor, JsExtractors } = require('gettext-extractor'); import { GettextExtractor, JsExtractors } from 'gettext-extractor';
const rootDir = `${__dirname}/../..`; const rootDir = `${__dirname}/../..`;
const localesDir = `${__dirname}/locales`; const localesDir = `${__dirname}/locales`;
@ -20,7 +20,7 @@ const libDir = `${rootDir}/packages/lib`;
function serializeTranslation(translation: string) { function serializeTranslation(translation: string) {
const output = parseTranslations(translation); 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) { function saveToFile(filePath: string, data: string) {
@ -31,6 +31,7 @@ async function buildLocale(inputFile: string, outputFile: string) {
const r = await parsePoFile(inputFile); const r = await parsePoFile(inputFile);
const translation = serializeTranslation(r); const translation = serializeTranslation(r);
saveToFile(outputFile, translation); saveToFile(outputFile, translation);
return { headers: r.headers };
} }
async function createPotFile(potFilePath: string) { async function createPotFile(potFilePath: string) {
@ -391,9 +392,10 @@ async function main() {
const poFilePäth = `${localesDir}/${locale}.po`; const poFilePäth = `${localesDir}/${locale}.po`;
const jsonFilePath = `${jsonLocalesDir}/${locale}.json`; const jsonFilePath = `${jsonLocalesDir}/${locale}.json`;
if (locale !== defaultLocale) await mergePotToPo(potFilePath, poFilePäth); 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); const stat = await translationStatus(defaultLocale === locale, poFilePäth);
stat.pluralForms = headers['Plural-Forms'];
stat.locale = locale; stat.locale = locale;
stat.languageName = countryDisplayName(locale); stat.languageName = countryDisplayName(locale);
stats.push(stat); stats.push(stat);

View File

@ -24,11 +24,11 @@
"@joplin/renderer": "~2.13", "@joplin/renderer": "~2.13",
"@joplin/utils": "~2.13", "@joplin/utils": "~2.13",
"compare-versions": "6.1.0", "compare-versions": "6.1.0",
"dayjs": "1.11.9", "dayjs": "1.11.10",
"execa": "4.1.0", "execa": "4.1.0",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"gettext-parser": "7.0.1", "gettext-parser": "7.0.1",
"glob": "10.3.4", "glob": "10.3.5",
"markdown-it": "13.0.1", "markdown-it": "13.0.1",
"md5-file": "5.0.0", "md5-file": "5.0.0",
"moment": "2.29.4", "moment": "2.29.4",
@ -36,7 +36,7 @@
"node-fetch": "2.6.7", "node-fetch": "2.6.7",
"relative": "3.0.2", "relative": "3.0.2",
"request": "2.88.2", "request": "2.88.2",
"sharp": "0.32.5", "sharp": "0.32.6",
"source-map-support": "0.5.21", "source-map-support": "0.5.21",
"uri-template": "2.0.0", "uri-template": "2.0.0",
"yargs": "17.7.2" "yargs": "17.7.2"
@ -48,7 +48,7 @@
"@types/jest": "29.5.4", "@types/jest": "29.5.4",
"@types/markdown-it": "13.0.1", "@types/markdown-it": "13.0.1",
"@types/mustache": "4.2.2", "@types/mustache": "4.2.2",
"@types/node": "18.17.17", "@types/node": "18.17.18",
"@types/node-fetch": "2.6.5", "@types/node-fetch": "2.6.5",
"@types/yargs": "17.0.24", "@types/yargs": "17.0.24",
"gettext-extractor": "3.8.0", "gettext-extractor": "3.8.0",

View File

@ -8,9 +8,10 @@ export interface TranslationStatus {
translatorName: string; translatorName: string;
percentDone: number; percentDone: number;
untranslatedCount: number; untranslatedCount: number;
pluralForms?: string;
} }
export type Translations = Record<string, string>; export type Translations = Record<string, string[]>;
export const removePoHeaderDate = async (filePath: string) => { export const removePoHeaderDate = async (filePath: string) => {
let sedPrefix = 'sed -i'; let sedPrefix = 'sed -i';
@ -67,14 +68,14 @@ export const parseTranslations = (gettextTranslations: any) => {
if (!translations.hasOwnProperty(n)) continue; if (!translations.hasOwnProperty(n)) continue;
if (n === '') continue; if (n === '') continue;
const t = translations[n]; const t = translations[n];
let translated = ''; let translated: string[] = [];
if (t.comments && t.comments.flag && t.comments.flag.indexOf('fuzzy') >= 0) { if (t.comments && t.comments.flag && t.comments.flag.indexOf('fuzzy') >= 0) {
// Don't include fuzzy translations // Don't include fuzzy translations
} else { } else {
translated = t['msgstr'][0]; translated = t['msgstr'];
} }
if (translated) output[n] = translated; if (translated.length) output[n] = translated;
} }
} }

View File

@ -7,7 +7,7 @@ describe('applyTranslations', () => {
{ {
html: '<div><span translate>Translate me</span></div>', html: '<div><span translate>Translate me</span></div>',
translations: { translations: {
'Translate me': 'Traduis moi', 'Translate me': ['Traduis moi'],
}, },
htmlTranslated: '<div>\n<span translate>\nTraduis moi\n</span>\n</div>', htmlTranslated: '<div>\n<span translate>\nTraduis moi\n</span>\n</div>',
}, },
@ -19,14 +19,14 @@ describe('applyTranslations', () => {
{ {
html: '<h1 translate class="text-center">\nFree your <span class="frame-bg frame-bg-blue">notes</span>\n</h1>', html: '<h1 translate class="text-center">\nFree your <span class="frame-bg frame-bg-blue">notes</span>\n</h1>',
translations: { translations: {
'Free your <span class="frame-bg frame-bg-blue">notes</span>': 'Libérez vos <span class="frame-bg frame-bg-blue">notes</span>', 'Free your <span class="frame-bg frame-bg-blue">notes</span>': ['Libérez vos <span class="frame-bg frame-bg-blue">notes</span>'],
}, },
htmlTranslated: '<h1 translate class="text-center">\nLibérez vos <span class="frame-bg frame-bg-blue">notes</span>\n</h1>', htmlTranslated: '<h1 translate class="text-center">\nLibérez vos <span class="frame-bg frame-bg-blue">notes</span>\n</h1>',
}, },
{ {
html: '<div translate>Save <span class="frame-bg frame-bg-blue">web pages</span> <br />as notes</div>', html: '<div translate>Save <span class="frame-bg frame-bg-blue">web pages</span> <br />as notes</div>',
translations: { translations: {
'Save <span class="frame-bg frame-bg-blue">web pages</span> <br>as notes': 'Sauvegardez vos <span class="frame-bg frame-bg-blue">pages web</span> <br>en notes', 'Save <span class="frame-bg frame-bg-blue">web pages</span> <br>as notes': ['Sauvegardez vos <span class="frame-bg frame-bg-blue">pages web</span> <br>en notes'],
}, },
htmlTranslated: '<div translate>\nSauvegardez vos <span class="frame-bg frame-bg-blue">pages web</span> <br>en notes\n</div>', htmlTranslated: '<div translate>\nSauvegardez vos <span class="frame-bg frame-bg-blue">pages web</span> <br>en notes\n</div>',
}, },

View File

@ -1,5 +1,6 @@
import { unique } from '@joplin/lib/ArrayUtils'; import { unique } from '@joplin/lib/ArrayUtils';
import { attributesHtml, isSelfClosingTag } from '@joplin/renderer/htmlUtils'; import { attributesHtml, isSelfClosingTag } from '@joplin/renderer/htmlUtils';
import { Translations } from '../../utils/translation';
const Entities = require('html-entities').AllHtmlEntities; const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = new Entities().encode; const htmlentities = new Entities().encode;
const htmlparser2 = require('@joplin/fork-htmlparser2'); const htmlparser2 = require('@joplin/fork-htmlparser2');
@ -15,7 +16,7 @@ const trimHtml = (content: string) => {
.replace(/\t+$/, ''); .replace(/\t+$/, '');
}; };
const findTranslation = (englishString: string, translations: Record<string, string>): string => { const findTranslation = (englishString: string, translations: Translations): string => {
const stringsToTry = unique([ const stringsToTry = unique([
englishString, englishString,
englishString.replace(/<br\/>/gi, '<br>'), englishString.replace(/<br\/>/gi, '<br>'),
@ -26,7 +27,8 @@ const findTranslation = (englishString: string, translations: Record<string, str
]) as string[]; ]) as string[];
for (const stringToTry of stringsToTry) { for (const stringToTry of stringsToTry) {
if (translations[stringToTry]) return translations[stringToTry]; // Note that we don't currently support plural forms for the website
if (translations[stringToTry] && translations[stringToTry].length) return translations[stringToTry][0];
} }
return englishString; return englishString;
@ -38,7 +40,7 @@ const encodeHtml = (decodedText: string): string => {
.replace(/{{&gt; /gi, '{{> '); // Don't break Mustache partials .replace(/{{&gt; /gi, '{{> '); // Don't break Mustache partials
}; };
export default (html: string, _languageCode: string, translations: Record<string, string>) => { export default (html: string, _languageCode: string, translations: Translations) => {
const output: string[] = []; const output: string[] = [];
interface State { interface State {

View File

@ -1,8 +1,9 @@
import { mkdirp, readFile, writeFile } from 'fs-extra'; import { mkdirp, readFile, writeFile } from 'fs-extra';
import { dirname } from 'path'; import { dirname } from 'path';
import { Translations } from '../../utils/translation';
import applyTranslations from './applyTranslations'; import applyTranslations from './applyTranslations';
export default async (englishFilePath: string, translatedFilePath: string, languageCode: string, translations: Record<string, string>) => { export default async (englishFilePath: string, translatedFilePath: string, languageCode: string, translations: Translations) => {
let content = await readFile(englishFilePath, 'utf8'); let content = await readFile(englishFilePath, 'utf8');
content = content.replace('<html lang="en-gb">', `<html lang="${languageCode}">`); content = content.replace('<html lang="en-gb">', `<html lang="${languageCode}">`);
const translatedContent = await applyTranslations(content, languageCode, translations); const translatedContent = await applyTranslations(content, languageCode, translations);

View File

@ -1,5 +1,6 @@
import { Plan, StripePublicConfig } from '@joplin/lib/utils/joplinCloud'; import { Plan, StripePublicConfig } from '@joplin/lib/utils/joplinCloud';
import { Sponsors } from '../../utils/loadSponsors'; import { Sponsors } from '../../utils/loadSponsors';
import { Translations } from '../../utils/translation';
import { OpenGraphTags } from './openGraph'; import { OpenGraphTags } from './openGraph';
export enum Env { export enum Env {
@ -8,7 +9,7 @@ export enum Env {
} }
export interface Locale { export interface Locale {
htmlTranslations: Record<string, string>; htmlTranslations: Translations;
lang: string; lang: string;
pathPrefix: string; pathPrefix: string;
} }

View File

@ -29,7 +29,7 @@
"async-mutex": "0.4.0", "async-mutex": "0.4.0",
"execa": "5.1.1", "execa": "5.1.1",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"glob": "10.3.4", "glob": "10.3.5",
"html-entities": "1.4.0", "html-entities": "1.4.0",
"moment": "2.29.4", "moment": "2.29.4",
"node-fetch": "2.6.7", "node-fetch": "2.6.7",

144
yarn.lock
View File

@ -4664,8 +4664,8 @@ __metadata:
"@testing-library/react-hooks": 8.0.1 "@testing-library/react-hooks": 8.0.1
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/mustache": 4.2.2 "@types/mustache": 4.2.2
"@types/node": 18.17.17 "@types/node": 18.17.18
"@types/react": 18.2.21 "@types/react": 18.2.22
"@types/react-redux": 7.1.26 "@types/react-redux": 7.1.26
"@types/styled-components": 5.1.27 "@types/styled-components": 5.1.27
async-mutex: 0.4.0 async-mutex: 0.4.0
@ -4679,7 +4679,7 @@ __metadata:
electron-window-state: 5.0.3 electron-window-state: 5.0.3
formatcoords: 1.1.3 formatcoords: 1.1.3
fs-extra: 11.1.1 fs-extra: 11.1.1
glob: 10.3.4 glob: 10.3.5
gulp: 4.0.2 gulp: 4.0.2
highlight.js: 11.8.0 highlight.js: 11.8.0
immer: 7.0.15 immer: 7.0.15
@ -4701,7 +4701,7 @@ __metadata:
react-datetime: 3.2.0 react-datetime: 3.2.0
react-dom: 18.2.0 react-dom: 18.2.0
react-redux: 8.1.2 react-redux: 8.1.2
react-select: 5.7.4 react-select: 5.7.5
react-test-renderer: 18.2.0 react-test-renderer: 18.2.0
react-toggle-button: 2.2.0 react-toggle-button: 2.2.0
react-tooltip: 4.5.1 react-tooltip: 4.5.1
@ -4753,7 +4753,7 @@ __metadata:
"@tsconfig/react-native": 2.0.2 "@tsconfig/react-native": 2.0.2
"@types/fs-extra": 11.0.2 "@types/fs-extra": 11.0.2
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/react": 18.2.21 "@types/react": 18.2.22
"@types/react-native": 0.70.6 "@types/react-native": 0.70.6
"@types/react-redux": 7.1.26 "@types/react-redux": 7.1.26
"@types/tar-stream": 2.2.3 "@types/tar-stream": 2.2.3
@ -4797,20 +4797,20 @@ __metadata:
react-native-image-picker: 5.6.1 react-native-image-picker: 5.6.1
react-native-localize: 3.0.2 react-native-localize: 3.0.2
react-native-modal-datetime-picker: 17.1.0 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-popup-menu: 0.16.1
react-native-quick-actions: 0.3.13 react-native-quick-actions: 0.3.13
react-native-rsa-native: 2.0.5 react-native-rsa-native: 2.0.5
react-native-safe-area-context: 4.7.2 react-native-safe-area-context: 4.7.2
react-native-securerandom: 1.0.1 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-side-menu-updated: 1.3.2
react-native-sqlite-storage: 6.0.1 react-native-sqlite-storage: 6.0.1
react-native-url-polyfill: 2.0.0 react-native-url-polyfill: 2.0.0
react-native-vector-icons: 10.0.0 react-native-vector-icons: 10.0.0
react-native-version-info: 1.1.1 react-native-version-info: 1.1.1
react-native-vosk: 0.1.12 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-native-zip-archive: 6.0.9
react-redux: 8.1.2 react-redux: 8.1.2
react-test-renderer: 18.2.0 react-test-renderer: 18.2.0
@ -4855,7 +4855,7 @@ __metadata:
"@replit/codemirror-vim": 6.0.14 "@replit/codemirror-vim": 6.0.14
"@testing-library/react-hooks": 8.0.1 "@testing-library/react-hooks": 8.0.1
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/react": 18.0.24 "@types/react": 18.2.22
"@types/react-redux": 7.1.26 "@types/react-redux": 7.1.26
"@types/styled-components": 5.1.27 "@types/styled-components": 5.1.27
jest: 29.6.3 jest: 29.6.3
@ -4870,7 +4870,7 @@ __metadata:
resolution: "@joplin/fork-htmlparser2@workspace:packages/fork-htmlparser2" resolution: "@joplin/fork-htmlparser2@workspace:packages/fork-htmlparser2"
dependencies: dependencies:
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/node": 18.17.17 "@types/node": 18.17.18
"@typescript-eslint/eslint-plugin": 6.0.0 "@typescript-eslint/eslint-plugin": 6.0.0
"@typescript-eslint/parser": 6.0.0 "@typescript-eslint/parser": 6.0.0
coveralls: 3.1.1 coveralls: 3.1.1
@ -4936,9 +4936,9 @@ __metadata:
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/js-yaml": 4.0.6 "@types/js-yaml": 4.0.6
"@types/nanoid": 3.0.0 "@types/nanoid": 3.0.0
"@types/node": 18.17.17 "@types/node": 18.17.18
"@types/node-rsa": 1.1.1 "@types/node-rsa": 1.1.1
"@types/react": 18.2.21 "@types/react": 18.2.22
"@types/uuid": 9.0.4 "@types/uuid": 9.0.4
async-mutex: 0.4.0 async-mutex: 0.4.0
base-64: 1.0.0 base-64: 1.0.0
@ -4953,7 +4953,7 @@ __metadata:
es6-promise-pool: 2.5.0 es6-promise-pool: 2.5.0
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
fast-xml-parser: 3.21.1 fast-xml-parser: 3.21.1
follow-redirects: 1.15.2 follow-redirects: 1.15.3
form-data: 4.0.0 form-data: 4.0.0
fs-extra: 11.1.1 fs-extra: 11.1.1
hpagent: 1.2.0 hpagent: 1.2.0
@ -4984,7 +4984,7 @@ __metadata:
relative: 3.0.2 relative: 3.0.2
reselect: 4.1.8 reselect: 4.1.8
server-destroy: 1.0.1 server-destroy: 1.0.1
sharp: 0.32.5 sharp: 0.32.6
sprintf-js: 1.1.3 sprintf-js: 1.1.3
sqlite3: 5.1.6 sqlite3: 5.1.6
string-padding: 1.0.2 string-padding: 1.0.2
@ -5010,7 +5010,7 @@ __metadata:
"@joplin/lib": ~2.13 "@joplin/lib": ~2.13
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/pdfjs-dist": 2.10.378 "@types/pdfjs-dist": 2.10.378
"@types/react": 18.2.21 "@types/react": 18.2.22
"@types/react-dom": 18.2.7 "@types/react-dom": 18.2.7
"@types/styled-components": 5.1.27 "@types/styled-components": 5.1.27
async-mutex: 0.4.0 async-mutex: 0.4.0
@ -5040,7 +5040,7 @@ __metadata:
"@joplin/utils": ~2.13 "@joplin/utils": ~2.13
"@types/fs-extra": 11.0.2 "@types/fs-extra": 11.0.2
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/node": 18.17.17 "@types/node": 18.17.18
fs-extra: 11.1.1 fs-extra: 11.1.1
gh-release-assets: 2.0.1 gh-release-assets: 2.0.1
jest: 29.6.4 jest: 29.6.4
@ -5094,7 +5094,7 @@ __metadata:
"@joplin/fork-uslug": ^1.0.11 "@joplin/fork-uslug": ^1.0.11
"@joplin/utils": ~2.13 "@joplin/utils": ~2.13
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/node": 18.17.17 "@types/node": 18.17.18
font-awesome-filetypes: 2.1.0 font-awesome-filetypes: 2.1.0
fs-extra: 11.1.1 fs-extra: 11.1.1
highlight.js: 11.8.0 highlight.js: 11.8.0
@ -5151,7 +5151,7 @@ __metadata:
bulma: 0.9.4 bulma: 0.9.4
bulma-prefers-dark: 0.1.0-beta.1 bulma-prefers-dark: 0.1.0-beta.1
compare-versions: 6.1.0 compare-versions: 6.1.0
dayjs: 1.11.9 dayjs: 1.11.10
formidable: 3.5.1 formidable: 3.5.1
fs-extra: 11.1.1 fs-extra: 11.1.1
gulp: 4.0.2 gulp: 4.0.2
@ -5198,16 +5198,16 @@ __metadata:
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/markdown-it": 13.0.1 "@types/markdown-it": 13.0.1
"@types/mustache": 4.2.2 "@types/mustache": 4.2.2
"@types/node": 18.17.17 "@types/node": 18.17.18
"@types/node-fetch": 2.6.5 "@types/node-fetch": 2.6.5
"@types/yargs": 17.0.24 "@types/yargs": 17.0.24
compare-versions: 6.1.0 compare-versions: 6.1.0
dayjs: 1.11.9 dayjs: 1.11.10
execa: 4.1.0 execa: 4.1.0
fs-extra: 11.1.1 fs-extra: 11.1.1
gettext-extractor: 3.8.0 gettext-extractor: 3.8.0
gettext-parser: 7.0.1 gettext-parser: 7.0.1
glob: 10.3.4 glob: 10.3.5
gulp: 4.0.2 gulp: 4.0.2
html-entities: 1.4.0 html-entities: 1.4.0
jest: 29.6.4 jest: 29.6.4
@ -5220,7 +5220,7 @@ __metadata:
request: 2.88.2 request: 2.88.2
rss: 1.2.2 rss: 1.2.2
sass: 1.66.1 sass: 1.66.1
sharp: 0.32.5 sharp: 0.32.6
source-map-support: 0.5.21 source-map-support: 0.5.21
sqlite3: 5.1.6 sqlite3: 5.1.6
typescript: 5.1.6 typescript: 5.1.6
@ -5283,7 +5283,7 @@ __metadata:
async-mutex: 0.4.0 async-mutex: 0.4.0
execa: 5.1.1 execa: 5.1.1
fs-extra: 11.1.1 fs-extra: 11.1.1
glob: 10.3.4 glob: 10.3.5
html-entities: 1.4.0 html-entities: 1.4.0
jest: 29.6.4 jest: 29.6.4
moment: 2.29.4 moment: 2.29.4
@ -8231,10 +8231,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/node@npm:18.17.17": "@types/node@npm:18.17.18":
version: 18.17.17 version: 18.17.18
resolution: "@types/node@npm:18.17.17" resolution: "@types/node@npm:18.17.18"
checksum: ff28f347c77723780836f9bb2ffa6db0cd72490bfd7604397c03db31db34f1f2899e82f0aaf3e825efeb09c15bd94d076ea9aca19a1407e1b56cb4603318936c checksum: 59cbd906363d37017fe9ba0c08c1446e440d4d977459609c5f90b8fb7eb41f273ce8af30c5a5b5d599d7de934c1b3702bc9fc27caf8d2270e5cdb659c5232991
languageName: node languageName: node
linkType: hard linkType: hard
@ -8383,25 +8383,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/react@npm:18.0.24": "@types/react@npm:18.2.22":
version: 18.0.24 version: 18.2.22
resolution: "@types/react@npm:18.0.24" resolution: "@types/react@npm:18.2.22"
dependencies: dependencies:
"@types/prop-types": "*" "@types/prop-types": "*"
"@types/scheduler": "*" "@types/scheduler": "*"
csstype: ^3.0.2 csstype: ^3.0.2
checksum: 7d06125bac61e1c6661e5dfbeeeb56d5b6d1d4c743292faebaa6b0f30f8414c7af3cadf674923fd86e4ca14e82566ff9156cd40c56786be024600c31b97d6c03 checksum: 44289523dabaadcd3fd85689abb98f9ebcc8492d7e978348d1c986138acef4801030b279e89a19e38a6319e294bcea77559e37e0c803e4bacf2b8ae3a56ba587
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
languageName: node languageName: node
linkType: hard linkType: hard
@ -14067,7 +14056,14 @@ __metadata:
languageName: node languageName: node
linkType: hard 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 version: 1.11.9
resolution: "dayjs@npm:1.11.9" resolution: "dayjs@npm:1.11.9"
checksum: a4844d83dc87f921348bb9b1b93af851c51e6f71fa259604809cfe1b49d1230e6b0212dab44d1cb01994c096ad3a77ea1cf18fa55154da6efcc9d3610526ac38 checksum: a4844d83dc87f921348bb9b1b93af851c51e6f71fa259604809cfe1b49d1230e6b0212dab44d1cb01994c096ad3a77ea1cf18fa55154da6efcc9d3610526ac38
@ -17415,13 +17411,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"follow-redirects@npm:1.15.2": "follow-redirects@npm:1.15.3":
version: 1.15.2 version: 1.15.3
resolution: "follow-redirects@npm:1.15.2" resolution: "follow-redirects@npm:1.15.3"
peerDependenciesMeta: peerDependenciesMeta:
debug: debug:
optional: true optional: true
checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190 checksum: 584da22ec5420c837bd096559ebfb8fe69d82512d5585004e36a3b4a6ef6d5905780e0c74508c7b72f907d1fa2b7bd339e613859e9c304d0dc96af2027fd0231
languageName: node languageName: node
linkType: hard linkType: hard
@ -18344,9 +18340,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"glob@npm:10.3.4": "glob@npm:10.3.5":
version: 10.3.4 version: 10.3.5
resolution: "glob@npm:10.3.4" resolution: "glob@npm:10.3.5"
dependencies: dependencies:
foreground-child: ^3.1.0 foreground-child: ^3.1.0
jackspeak: ^2.0.3 jackspeak: ^2.0.3
@ -18355,7 +18351,7 @@ __metadata:
path-scurry: ^1.10.1 path-scurry: ^1.10.1
bin: bin:
glob: dist/cjs/src/bin.js glob: dist/cjs/src/bin.js
checksum: 176b97c124414401cb51329a93d2ba112cef8814adbed10348481916b9521b677773eee2691cb6b24d66632d8c8bb8913533f5ac4bfb2d0ef5454a1856082361 checksum: 564f4799cae48c0bcc841c88a20b539b5701c27ed5596f8623f588b3c523262d3fc20eb1ea89cab9c75b0912faf40ca5501fc835f982225d0d0599282b09e97a
languageName: node languageName: node
linkType: hard linkType: hard
@ -22172,7 +22168,7 @@ __metadata:
"@joplin/utils": ~2.13 "@joplin/utils": ~2.13
"@types/fs-extra": 11.0.2 "@types/fs-extra": 11.0.2
"@types/jest": 29.5.4 "@types/jest": 29.5.4
"@types/node": 18.17.17 "@types/node": 18.17.18
"@types/proper-lockfile": ^4.1.2 "@types/proper-lockfile": ^4.1.2
aws-sdk: 2.1340.0 aws-sdk: 2.1340.0
chalk: 4.1.2 chalk: 4.1.2
@ -22189,7 +22185,7 @@ __metadata:
proper-lockfile: 4.1.2 proper-lockfile: 4.1.2
read-chunk: 2.1.0 read-chunk: 2.1.0
server-destroy: 1.0.1 server-destroy: 1.0.1
sharp: 0.32.5 sharp: 0.32.6
sprintf-js: 1.1.3 sprintf-js: 1.1.3
sqlite3: 5.1.6 sqlite3: 5.1.6
string-padding: 1.0.2 string-padding: 1.0.2
@ -29412,9 +29408,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-native-paper@npm:5.10.4": "react-native-paper@npm:5.10.6":
version: 5.10.4 version: 5.10.6
resolution: "react-native-paper@npm:5.10.4" resolution: "react-native-paper@npm:5.10.6"
dependencies: dependencies:
"@callstack/react-theme-provider": ^3.0.9 "@callstack/react-theme-provider": ^3.0.9
color: ^3.1.2 color: ^3.1.2
@ -29424,7 +29420,7 @@ __metadata:
react-native: "*" react-native: "*"
react-native-safe-area-context: "*" react-native-safe-area-context: "*"
react-native-vector-icons: "*" react-native-vector-icons: "*"
checksum: 28662d669b728601e7998be3a9488e3b8cf81b0a8cbe4cdc499bb88eac3de872ed5f05f94b14c905d56f3db3e9316eee696b317860e168cd0a52b90f38025cb2 checksum: 6793a0ad54ad64894fc6b3d8a2f86c06a4cd553d38d1d22776f04cc9991e33cab83cc01c2ecd600356cd5f0ea7b847660ca1487432a09f772adadc33b0fa4edf
languageName: node languageName: node
linkType: hard linkType: hard
@ -29470,10 +29466,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-native-share@npm:9.2.4": "react-native-share@npm:9.4.0":
version: 9.2.4 version: 9.4.0
resolution: "react-native-share@npm:9.2.4" resolution: "react-native-share@npm:9.4.0"
checksum: 2d6062c5de2202afb2cf360c82283d7314033e3920980a1dc40c24e5d9208960f1225ee9a02727ff8174a290a95ede3120ce6084f5a60d0e40e138aa03683db7 checksum: 0ca3afca7d352b0c107af326b63f4024fe307134eb1d922fd3e870900d3dded04aa4f3e2d581c313b4e100bc3ed83b85538a3fb05ae6de91c2797ee2c5f238b3
languageName: node languageName: node
linkType: hard linkType: hard
@ -29550,16 +29546,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-native-webview@npm:13.4.0": "react-native-webview@npm:13.5.1":
version: 13.4.0 version: 13.5.1
resolution: "react-native-webview@npm:13.4.0" resolution: "react-native-webview@npm:13.5.1"
dependencies: dependencies:
escape-string-regexp: 2.0.0 escape-string-regexp: 2.0.0
invariant: 2.2.4 invariant: 2.2.4
peerDependencies: peerDependencies:
react: "*" react: "*"
react-native: "*" react-native: "*"
checksum: fb2b6f936d4f29eeda44e8d1187833c9ffe5a16183600e7b160a9d30052e8eaa8d604182b5e778b99c60d6247160c86dc2ee878b2073f1f775efdec89a058b1d checksum: f7536d0832c401d75c6f92bb997daeb7fe9de82471bf50bbc895421b82cd355da476dc393b186e9256d5759c345f5c97f2f8185fd079d3cb6fc174ca8ee70ba5
languageName: node languageName: node
linkType: hard linkType: hard
@ -29761,9 +29757,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"react-select@npm:5.7.4": "react-select@npm:5.7.5":
version: 5.7.4 version: 5.7.5
resolution: "react-select@npm:5.7.4" resolution: "react-select@npm:5.7.5"
dependencies: dependencies:
"@babel/runtime": ^7.12.0 "@babel/runtime": ^7.12.0
"@emotion/cache": ^11.4.0 "@emotion/cache": ^11.4.0
@ -29777,7 +29773,7 @@ __metadata:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^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 languageName: node
linkType: hard linkType: hard
@ -30971,7 +30967,7 @@ __metadata:
eslint-plugin-react: 7.33.2 eslint-plugin-react: 7.33.2
execa: 5.1.1 execa: 5.1.1
fs-extra: 11.1.1 fs-extra: 11.1.1
glob: 10.3.4 glob: 10.3.5
gulp: 4.0.2 gulp: 4.0.2
http-server: 14.1.1 http-server: 14.1.1
husky: 3.1.0 husky: 3.1.0
@ -31503,9 +31499,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"sharp@npm:0.32.5": "sharp@npm:0.32.6":
version: 0.32.5 version: 0.32.6
resolution: "sharp@npm:0.32.5" resolution: "sharp@npm:0.32.6"
dependencies: dependencies:
color: ^4.2.3 color: ^4.2.3
detect-libc: ^2.0.2 detect-libc: ^2.0.2
@ -31516,7 +31512,7 @@ __metadata:
simple-get: ^4.0.1 simple-get: ^4.0.1
tar-fs: ^3.0.4 tar-fs: ^3.0.4
tunnel-agent: ^0.6.0 tunnel-agent: ^0.6.0
checksum: 3cd6dc037c9ba126a30af90ac94043c4418bbb4228e15fd446638ff43fc9b14eabb553037988e484162c318f7baff21d896a5bef7dcc453f608e247d468f41e0 checksum: 0cca1d16b1920800c0e22d27bc6305f4c67c9ebe44f67daceb30bf645ae39e7fb7dfbd7f5d6cd9f9eebfddd87ac3f7e2695f4eb906d19b7a775286238e6a29fc
languageName: node languageName: node
linkType: hard linkType: hard