{children}
diff --git a/frontend/apps/remark42/app/components/profile/profile.tsx b/frontend/apps/remark42/app/components/profile/profile.tsx
index ae15b5ed..ce189d8f 100644
--- a/frontend/apps/remark42/app/components/profile/profile.tsx
+++ b/frontend/apps/remark42/app/components/profile/profile.tsx
@@ -1,8 +1,8 @@
import clsx from 'clsx';
import { h, Fragment } from 'preact';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
-import { useIntl, FormattedMessage } from 'react-intl';
+import { useIntl, FormattedMessage } from 'common/intl';
import { getUserComments } from 'common/api';
import { parseQuery } from 'utils/parse-query';
import { requestDeletion } from 'utils/email';
diff --git a/frontend/apps/remark42/app/components/root/root.tsx b/frontend/apps/remark42/app/components/root/root.tsx
index ab29254b..9b25fab8 100644
--- a/frontend/apps/remark42/app/components/root/root.tsx
+++ b/frontend/apps/remark42/app/components/root/root.tsx
@@ -1,8 +1,8 @@
import { h, Component, Fragment } from 'preact';
import { useSelector } from 'store/context';
-import { IntlShape, useIntl, FormattedMessage, defineMessages } from 'react-intl';
import clsx from 'clsx';
+import { IntlShape, useIntl, FormattedMessage, defineMessages } from 'common/intl';
import 'styles/global.css';
import type { StoreState } from 'store';
import { COMMENT_NODE_CLASSNAME_PREFIX, MAX_SHOWN_ROOT_COMMENTS, THEMES, IS_MOBILE } from 'common/constants';
diff --git a/frontend/apps/remark42/app/components/settings/settings.tsx b/frontend/apps/remark42/app/components/settings/settings.tsx
index 908ca0cc..0a4ec113 100644
--- a/frontend/apps/remark42/app/components/settings/settings.tsx
+++ b/frontend/apps/remark42/app/components/settings/settings.tsx
@@ -4,7 +4,7 @@ import clsx from 'clsx';
import { User, BlockedUser, Theme, BlockTTL } from 'common/types';
import { getHandleClickProps } from 'common/accessibility';
import { StoreState } from 'store';
-import { defineMessages, IntlShape, FormattedMessage, useIntl } from 'react-intl';
+import { defineMessages, IntlShape, FormattedMessage, useIntl } from 'common/intl';
import { useTheme } from 'hooks/useTheme';
import styles from './settings.module.css';
diff --git a/frontend/apps/remark42/app/components/sort-picker.spec.tsx b/frontend/apps/remark42/app/components/sort-picker.spec.tsx
index 944afd5d..8367a4ed 100644
--- a/frontend/apps/remark42/app/components/sort-picker.spec.tsx
+++ b/frontend/apps/remark42/app/components/sort-picker.spec.tsx
@@ -37,10 +37,7 @@ describe('
', () => {
expect(select).toBeInTheDocument();
- // @testing-library/preact rewrites change to input for every element once it sees a
- // compat vnode, so fireEvent.change never reaches a select handler; dispatch directly
- select.value = nextOption;
- fireEvent(select, new Event('change', { bubbles: true }));
+ fireEvent.change(select, { target: { value: nextOption } });
await waitFor(() => expect(updateSorting).toHaveBeenCalledWith(nextOption));
diff --git a/frontend/apps/remark42/app/components/sort-picker.tsx b/frontend/apps/remark42/app/components/sort-picker.tsx
index bd099425..2cc71d8d 100644
--- a/frontend/apps/remark42/app/components/sort-picker.tsx
+++ b/frontend/apps/remark42/app/components/sort-picker.tsx
@@ -1,7 +1,7 @@
import { h } from 'preact';
-import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { useMemo } from 'preact/hooks';
+import { FormattedMessage, defineMessages, useIntl } from 'common/intl';
import { StoreState, useAppDispatch, useAppSelector } from 'store';
import { Select } from 'components/select';
import { updateSorting } from 'store/comments/actions';
diff --git a/frontend/apps/remark42/app/components/spinner/spinner.tsx b/frontend/apps/remark42/app/components/spinner/spinner.tsx
index 66edc300..87fc96b2 100644
--- a/frontend/apps/remark42/app/components/spinner/spinner.tsx
+++ b/frontend/apps/remark42/app/components/spinner/spinner.tsx
@@ -1,8 +1,8 @@
import clsx from 'clsx';
import { h } from 'preact';
import { messages } from 'components/auth/auth.messages';
-import { useIntl } from 'react-intl';
+import { useIntl } from 'common/intl';
import styles from './spinner.module.css';
type Props = {
diff --git a/frontend/apps/remark42/app/components/telegram/telegram-link.tsx b/frontend/apps/remark42/app/components/telegram/telegram-link.tsx
index 48bb1c96..2668b92f 100644
--- a/frontend/apps/remark42/app/components/telegram/telegram-link.tsx
+++ b/frontend/apps/remark42/app/components/telegram/telegram-link.tsx
@@ -1,10 +1,10 @@
import clsx from 'clsx';
import { h, Fragment, FunctionComponent } from 'preact';
-import { useIntl } from 'react-intl';
import { messages } from './telegram.messages';
import { BASE_URL, API_BASE } from 'common/constants.config';
import { Button } from 'components/button';
+import { useIntl } from 'common/intl';
import styles from './telegram-link.module.css';
export type TelegramLinkProps = {
diff --git a/frontend/apps/remark42/app/components/telegram/telegram.messages.ts b/frontend/apps/remark42/app/components/telegram/telegram.messages.ts
index 7134f323..33ad9e03 100644
--- a/frontend/apps/remark42/app/components/telegram/telegram.messages.ts
+++ b/frontend/apps/remark42/app/components/telegram/telegram.messages.ts
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl';
+import { defineMessages } from 'common/intl';
export const messages = defineMessages
({
telegramMessage1: {
diff --git a/frontend/apps/remark42/app/components/textarea-autosize.spec.tsx b/frontend/apps/remark42/app/components/textarea-autosize.spec.tsx
new file mode 100644
index 00000000..d9cda05e
--- /dev/null
+++ b/frontend/apps/remark42/app/components/textarea-autosize.spec.tsx
@@ -0,0 +1,21 @@
+import { createRef } from 'preact';
+import { render } from '@testing-library/preact';
+
+import { TextareaAutosize } from './textarea-autosize';
+
+describe('', () => {
+ it('points the given ref at the textarea', () => {
+ const ref = createRef();
+
+ render();
+
+ expect(ref.current).toBeInstanceOf(HTMLTextAreaElement);
+ expect(ref.current?.value).toBe('hello');
+ });
+
+ it('works without a ref', () => {
+ const { container } = render();
+
+ expect(container.querySelector('textarea')?.value).toBe('hi');
+ });
+});
diff --git a/frontend/apps/remark42/app/components/textarea-autosize.tsx b/frontend/apps/remark42/app/components/textarea-autosize.tsx
index 1a1cb0d4..133b6898 100644
--- a/frontend/apps/remark42/app/components/textarea-autosize.tsx
+++ b/frontend/apps/remark42/app/components/textarea-autosize.tsx
@@ -1,20 +1,20 @@
-import { h, JSX, type TextareaHTMLAttributes } from 'preact';
-import { forwardRef } from 'preact/compat';
-import { useEffect, useImperativeHandle, useRef } from 'preact/hooks';
+import { h, JSX, type RefObject, type TextareaHTMLAttributes } from 'preact';
+import { useEffect, useRef } from 'preact/hooks';
function autoResize(textarea: HTMLTextAreaElement) {
textarea.style.height = '';
textarea.style.height = `${textarea.scrollHeight}px`;
}
-type Props = Omit, 'onInput'> & {
+type Props = Omit, 'onInput' | 'ref'> & {
onInput?(evt: JSX.TargetedEvent): void;
+ /** Taken as a plain prop rather than through forwardRef, which lives only in preact/compat. */
+ textareaRef?: RefObject;
};
-export const TextareaAutosize = forwardRef(({ onInput, value, ...props }, externalRef) => {
- const ref = useRef(null);
-
- useImperativeHandle(externalRef, () => ref.current as HTMLTextAreaElement, []);
+export function TextareaAutosize({ onInput, value, textareaRef, ...props }: Props) {
+ const localRef = useRef(null);
+ const ref = textareaRef ?? localRef;
const handleInput: JSX.GenericEventHandler = (evt) => {
if (!ref.current) {
@@ -32,7 +32,7 @@ export const TextareaAutosize = forwardRef(({ onInpu
if (ref.current) {
autoResize(ref.current);
}
- }, [value]);
+ }, [value, ref]);
return ;
-});
+}
diff --git a/frontend/apps/remark42/app/components/thread/thread.tsx b/frontend/apps/remark42/app/components/thread/thread.tsx
index 89c856f3..791b0ae4 100644
--- a/frontend/apps/remark42/app/components/thread/thread.tsx
+++ b/frontend/apps/remark42/app/components/thread/thread.tsx
@@ -2,8 +2,8 @@ import { h, FunctionComponent, type AriaRole } from 'preact';
import { shallowEqual } from 'store/context';
import { useCallback } from 'preact/hooks';
import clsx from 'clsx';
-import { useIntl } from 'react-intl';
+import { useIntl } from 'common/intl';
import { Comment as CommentInterface } from 'common/types';
import { getHandleClickProps } from 'common/accessibility';
import { StoreState, useAppDispatch, useAppSelector } from 'store';
diff --git a/frontend/apps/remark42/app/hooks/useAction.ts b/frontend/apps/remark42/app/hooks/useAction.ts
index 64165aba..445e36b4 100644
--- a/frontend/apps/remark42/app/hooks/useAction.ts
+++ b/frontend/apps/remark42/app/hooks/useAction.ts
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
-import { useMemo } from 'preact/compat';
+import { useMemo } from 'preact/hooks';
import { useDispatch } from 'store/context';
import { BoundActionCreator, BoundActionCreators } from 'utils/actionBinder';
diff --git a/frontend/apps/remark42/app/hooks/useSessionState.spec.ts b/frontend/apps/remark42/app/hooks/useSessionState.spec.ts
index f5ea2fb6..a84dbd2d 100644
--- a/frontend/apps/remark42/app/hooks/useSessionState.spec.ts
+++ b/frontend/apps/remark42/app/hooks/useSessionState.spec.ts
@@ -1,49 +1,63 @@
-import { renderHook } from '@testing-library/preact-hooks';
+import { act, renderHook } from '@testing-library/preact';
import { useSessionStorage } from './useSessionState';
describe('useSessionStorage', () => {
+ // the tests write under the same key, so without this they depend on each other's order
+ beforeEach(() => {
+ sessionStorage.clear();
+ });
+
it('should return a value and a setter', () => {
const { result } = renderHook(() => useSessionStorage('test', 0));
- expect(result.current!).toHaveLength(2);
- expect(result.current![0]).toBe(0);
- expect(result.current![1]).toBeInstanceOf(Function);
+ expect(result.current).toHaveLength(2);
+ expect(result.current[0]).toBe(0);
+ expect(result.current[1]).toBeInstanceOf(Function);
+ });
+
+ it('should store the value it is given', () => {
+ const { result } = renderHook(() => useSessionStorage('test', 0));
+
+ act(() => result.current[1](5));
+
+ expect(result.current[0]).toBe(5);
+ expect(sessionStorage.getItem('test')).toBe('5');
});
it('should return the initial value', () => {
const { result } = renderHook(() => useSessionStorage('test', 0));
- expect(result.current![0]).toBe(0);
+ expect(result.current[0]).toBe(0);
});
it('should return the stored value', () => {
sessionStorage.setItem('test', JSON.stringify(1));
const { result } = renderHook(() => useSessionStorage('test', 0));
- expect(result.current![0]).toBe(1);
+ expect(result.current[0]).toBe(1);
});
it('should return the stored value if it is falsy', () => {
sessionStorage.setItem('test', JSON.stringify(false));
const { result } = renderHook(() => useSessionStorage('test', 0));
- expect(result.current![0]).toBe(false);
+ expect(result.current[0]).toBe(false);
});
it('should return the initial value if the stored value is not valid JSON', () => {
sessionStorage.setItem('test', 'not valid JSON');
const { result } = renderHook(() => useSessionStorage('test', 0));
- expect(result.current![0]).toBe(0);
+ expect(result.current[0]).toBe(0);
});
it('should return null if the stored value is null', () => {
// @ts-ignore
sessionStorage.setItem('test', null);
const { result } = renderHook(() => useSessionStorage('test', 0));
- expect(result.current![0]).toBe(null);
+ expect(result.current[0]).toBe(null);
});
it('should return the initial value if the stored value is undefined', () => {
// @ts-ignore
sessionStorage.setItem('test', undefined);
const { result } = renderHook(() => useSessionStorage('test', 0));
- expect(result.current![0]).toBe(0);
+ expect(result.current[0]).toBe(0);
});
});
diff --git a/frontend/apps/remark42/app/last-comments.tsx b/frontend/apps/remark42/app/last-comments.tsx
index 2f5acb3b..a76a79fb 100644
--- a/frontend/apps/remark42/app/last-comments.tsx
+++ b/frontend/apps/remark42/app/last-comments.tsx
@@ -1,6 +1,6 @@
import { h, render } from 'preact';
-import { IntlProvider } from 'react-intl';
+import { IntlProvider } from 'common/intl';
import { getLastComments } from 'common/api.getLastComments';
import { BASE_URL } from 'common/constants.config';
import { loadLocale } from 'utils/loadLocale';
diff --git a/frontend/apps/remark42/app/locales/mk.json b/frontend/apps/remark42/app/locales/mk.json
index 969f69da..3d08232f 100644
--- a/frontend/apps/remark42/app/locales/mk.json
+++ b/frontend/apps/remark42/app/locales/mk.json
@@ -119,7 +119,7 @@
"profile.request-to-delete-data": "Барање за бришење на моите податоци",
"retry": "Обиди се повторно",
"root.pinned-comments": "Пинувани коментари",
- "root.powered-by": "Овозможено од Диосфера",
+ "root.powered-by": "Овозможено од Remark42",
"root.show-more": "Прикажи повеќе",
"settings.block": "блокирај",
"settings.block-time": "до {day} во {time}",
diff --git a/frontend/apps/remark42/app/locales/th.json b/frontend/apps/remark42/app/locales/th.json
index 64d8e029..27720420 100644
--- a/frontend/apps/remark42/app/locales/th.json
+++ b/frontend/apps/remark42/app/locales/th.json
@@ -119,7 +119,7 @@
"profile.request-to-delete-data": "ขอให้ลบข้อมูลของฉัน",
"retry": "ลองอีกครั้ง",
"root.pinned-comments": "ความคิดเห็นที่ปักหมุด",
- "root.powered-by": "ระบบแสดงความคิดเห็นโดย Remark42Remark42",
"root.show-more": "แสดงเพิ่มเติม",
"settings.block": "บล็อก",
"settings.block-time": "จนถึงวันที่ {day} เวลา {time}",
diff --git a/frontend/apps/remark42/app/remark.tsx b/frontend/apps/remark42/app/remark.tsx
index 5df11131..3d4128d2 100644
--- a/frontend/apps/remark42/app/remark.tsx
+++ b/frontend/apps/remark42/app/remark.tsx
@@ -1,8 +1,8 @@
import { h, render } from 'preact';
import { bindActionCreators } from 'redux';
import { Provider } from 'store/context';
-import { IntlProvider } from 'react-intl';
+import { IntlProvider } from 'common/intl';
import { loadLocale } from 'utils/loadLocale';
import { parseMessage } from 'utils/post-message';
import { ConnectedRoot } from 'components/root';
diff --git a/frontend/apps/remark42/app/tests/utils.tsx b/frontend/apps/remark42/app/tests/utils.tsx
index fba4ea2e..2983c56d 100644
--- a/frontend/apps/remark42/app/tests/utils.tsx
+++ b/frontend/apps/remark42/app/tests/utils.tsx
@@ -1,8 +1,8 @@
import { h, ComponentChild } from 'preact';
-import { IntlProvider } from 'react-intl';
import { render as originalRender } from '@testing-library/preact';
import { Provider } from 'store/context';
+import { IntlProvider } from 'common/intl';
import en from 'locales/en.json';
import { mockStore } from '__stubs__/store';
import { StoreState } from 'store';
diff --git a/frontend/apps/remark42/app/typings/enzyme.d.ts b/frontend/apps/remark42/app/typings/enzyme.d.ts
deleted file mode 100644
index bb5f4397..00000000
--- a/frontend/apps/remark42/app/typings/enzyme.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/frontend/apps/remark42/app/utils/errorUtils.ts b/frontend/apps/remark42/app/utils/errorUtils.ts
index db68a846..5de1f7a8 100644
--- a/frontend/apps/remark42/app/utils/errorUtils.ts
+++ b/frontend/apps/remark42/app/utils/errorUtils.ts
@@ -1,4 +1,4 @@
-import { IntlShape, defineMessages } from 'react-intl';
+import { IntlShape, defineMessages } from 'common/intl';
import { ApiError } from '../common/types';
export const errorMessages = defineMessages({
diff --git a/frontend/apps/remark42/app/utils/loadLocale.ts b/frontend/apps/remark42/app/utils/loadLocale.ts
index cec4b1f6..73067552 100644
--- a/frontend/apps/remark42/app/utils/loadLocale.ts
+++ b/frontend/apps/remark42/app/utils/loadLocale.ts
@@ -1,4 +1,4 @@
-/** this is generated file by "npm run translation:generate" **/
+/** this is generated file by "pnpm translation:generate" **/
// it is ok that is empty. Default messages from code will be used.
const enMessages = {};
diff --git a/frontend/apps/remark42/jest.config.ts b/frontend/apps/remark42/jest.config.ts
index 4d9f28a9..98ca8a5f 100644
--- a/frontend/apps/remark42/jest.config.ts
+++ b/frontend/apps/remark42/jest.config.ts
@@ -29,7 +29,6 @@ const config: Config = {
'\\.css': 'identity-obj-proxy',
'\\.svg': '/app/__stubs__/svg.tsx',
},
- setupFiles: ['/jest.setup.ts'],
setupFilesAfterEnv: [
'/app/__mocks__/fetch.ts',
'/app/__mocks__/localstorage.ts',
diff --git a/frontend/apps/remark42/jest.setup.ts b/frontend/apps/remark42/jest.setup.ts
deleted file mode 100644
index 22c994ee..00000000
--- a/frontend/apps/remark42/jest.setup.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { configure } from 'enzyme';
-import PreactAdapter from 'enzyme-adapter-preact-pure';
-
-configure({ adapter: new PreactAdapter() });
diff --git a/frontend/apps/remark42/package.json b/frontend/apps/remark42/package.json
index fa2cd597..22d56e55 100644
--- a/frontend/apps/remark42/package.json
+++ b/frontend/apps/remark42/package.json
@@ -17,7 +17,7 @@
"size-check": "cross-env NODE_ENV=production npm run build && size-limit",
"type-check": "tsc -p tsconfig.json --noEmit",
"translation-check": "run-s translation:extract translation:check",
- "translation:extract": "formatjs extract --out-file=./extracted-messages/messages.json \"**/*.{ts,tsx}\" --ignore=\"**/*.d.ts\"",
+ "translation:extract": "formatjs extract --out-file=./extracted-messages/messages.json \"**/*.{ts,tsx}\" --ignore=\"**/*.d.ts\" --ignore=\"**/*.{test,spec}.{ts,tsx}\" --ignore=\"app/tests/**\" --ignore=\"app/__mocks__/**\" --ignore=\"app/__stubs__/**\"",
"translation:generate": "node ./tasks/generateDictionary.js",
"translation:check": "node ./tasks/checkTranslation.js"
},
@@ -32,13 +32,9 @@
"@ungap/custom-elements": "^1.3.0",
"clsx": "^2.1.1",
"core-js": "^3.49.0",
- "intersection-observer": "^0.12.2",
"lodash-es": "^4.18.1",
"node-emoji": "^1.11.0",
"preact": "10.29.8",
- "react": "npm:@preact/compat@^18.3.2",
- "react-dom": "npm:@preact/compat@^18.3.2",
- "react-intl": "6.0.5",
"redux": "^4.2.0",
"redux-thunk": "^2.4.1"
},
@@ -58,8 +54,6 @@
"@swc/jest": "^0.2.21",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/preact": "3.2.4",
- "@testing-library/preact-hooks": "^1.1.0",
- "@types/enzyme": "^3.10.19",
"@types/eslint": "^8.4.5",
"@types/jest": "^28.1.4",
"@types/lodash-es": "^4.17.12",
@@ -79,8 +73,6 @@
"css-minimizer-webpack-plugin": "^4.0.0",
"cssnano": "^5.1.12",
"dotenv": "^17.4.2",
- "enzyme": "^3.11.0",
- "enzyme-adapter-preact-pure": "^4.1.0",
"eslint": "^8.18.0",
"eslint-config-preact": "^1.3.0",
"eslint-config-prettier": "^8.5.0",
diff --git a/frontend/apps/remark42/tasks/checkTranslation.js b/frontend/apps/remark42/tasks/checkTranslation.js
index 675ba221..56ee0673 100644
--- a/frontend/apps/remark42/tasks/checkTranslation.js
+++ b/frontend/apps/remark42/tasks/checkTranslation.js
@@ -11,12 +11,107 @@ locales.forEach((locale) => {
keysFromDict.forEach((key) => {
if (!keys.includes(key)) {
errors.push(
- `"${key}" key not found in "${locale}" locale dict. Please run "npm run translation:generate" and commit changes.`
+ `"${key}" key not found in "${locale}" locale dict. Please run "pnpm translation:generate" and commit changes.`
);
}
return null;
});
});
+// the loop above catches a locale key absent from the source. this catches the
+// other direction, which nothing else can see: a key extracted from somewhere it should not
+// be, such as a test fixture, is absent from en.json but would be written into all 24
+// catalogues by the next translation:generate, and translators would be asked to translate it
+const en = require(getLocalePath({ locale: 'en' }));
+keys.forEach((key) => {
+ if (!Object.keys(en).includes(key)) {
+ errors.push(
+ `"${key}" was extracted from the source but is missing from the "en" locale dict. ` +
+ `If it is a new message, run "translation:generate" and commit the catalogues. ` +
+ `If it comes from a test or a fixture, exclude that file from "translation:extract".`
+ );
+ }
+});
+
+// a translation's markup and placeholders have to match the contract of the English string
+// it translates. neither the extractor nor the key comparison above looks at a value, so
+// without this a broken tag or an invented placeholder ships and the widget silently renders
+// that message in English instead of the translation
+const PLACEHOLDER = /\{\s*(\w+)\s*\}/g;
+
+function placeholdersIn(message) {
+ return new Set(Array.from(message.matchAll(PLACEHOLDER), (match) => match[1]));
+}
+
+function tagsIn(message) {
+ return new Set(Array.from(message.matchAll(/<(\w+)>/g), (match) => match[1]));
+}
+
+// mirrors app/common/intl.tsx: a self-closing tag is text, and only `` or `<` opening a
+// tag name is markup that has to resolve. testing for any angle bracket instead would
+// reject ordinary text such as "under < 10"
+const SELF_CLOSING = /<[a-zA-Z][\w-]*\s*\/>/g;
+const TAG_START = /<\/|<[a-zA-Z]/;
+
+/**
+ * True when anything tag-shaped survives once the pairs the English string uses are taken
+ * out. The pair contents are checked too, since stripping a pair removes whatever was
+ * nested inside it, and the widget rejects a nested tag.
+ */
+function hasBrokenMarkup(message, tags) {
+ const chunks = [];
+ const withoutPairs = tags.reduce(
+ (text, tag) =>
+ text.replace(new RegExp(`<${tag}>([\\s\\S]*?)${tag}>`, 'g'), (_match, chunk) => {
+ chunks.push(chunk);
+
+ return '';
+ }),
+ message
+ );
+
+ return (
+ TAG_START.test(withoutPairs.replace(SELF_CLOSING, '')) ||
+ chunks.some((chunk) => TAG_START.test(chunk.replace(SELF_CLOSING, '')))
+ );
+}
+
+locales.forEach((locale) => {
+ if (locale === 'en') {
+ return;
+ }
+
+ const dict = require(getLocalePath({ locale }));
+
+ Object.keys(dict).forEach((key) => {
+ const source = en[key];
+ const value = dict[key];
+
+ if (typeof source !== 'string' || typeof value !== 'string') {
+ return;
+ }
+
+ const tags = Array.from(tagsIn(source));
+
+ if (hasBrokenMarkup(value, tags)) {
+ errors.push(
+ `"${key}" in "${locale}" has markup the widget cannot resolve: ${JSON.stringify(value)}. ` +
+ `Tags must be well-formed pairs of the same names the English string uses ` +
+ `(${tags.length ? tags.map((tag) => `<${tag}>`).join(', ') : 'none'}), with no attributes. ` +
+ `A translation may leave a tag out entirely. As written, this message renders in English.`
+ );
+ }
+
+ const unknown = Array.from(placeholdersIn(value)).filter((name) => !placeholdersIn(source).has(name));
+
+ if (unknown.length) {
+ errors.push(
+ `"${key}" in "${locale}" uses ${unknown.map((name) => `{${name}}`).join(', ')}, which the ` +
+ `English string does not provide. As written, this message renders in English.`
+ );
+ }
+ });
+});
+
if (errors.length) {
// eslint-disable-next-line no-console
console.error(errors.join(`\n`));
diff --git a/frontend/apps/remark42/tasks/localeLoadTemplate.js b/frontend/apps/remark42/tasks/localeLoadTemplate.js
index fda9cb9d..5410bd60 100644
--- a/frontend/apps/remark42/tasks/localeLoadTemplate.js
+++ b/frontend/apps/remark42/tasks/localeLoadTemplate.js
@@ -1,5 +1,5 @@
function renderLoadLocale(locales) {
- return `/** this is generated file by "npm run translation:generate" **/
+ return `/** this is generated file by "pnpm translation:generate" **/
// it is ok that is empty. Default messages from code will be used.
const enMessages = {};
diff --git a/frontend/apps/remark42/tsconfig.json b/frontend/apps/remark42/tsconfig.json
index 51ac5906..907f0185 100644
--- a/frontend/apps/remark42/tsconfig.json
+++ b/frontend/apps/remark42/tsconfig.json
@@ -16,8 +16,6 @@
"skipLibCheck": true,
"sourceMap": true,
"paths": {
- "react": ["../node_modules/preact/compat"],
- "react-dom": ["../node_modules/preact/compat"],
"preact": ["../node_modules/preact"],
"preact/*": ["../node_modules/preact/*"]
}
diff --git a/frontend/apps/remark42/webpack.config.js b/frontend/apps/remark42/webpack.config.js
index b47e0eda..2e785e88 100644
--- a/frontend/apps/remark42/webpack.config.js
+++ b/frontend/apps/remark42/webpack.config.js
@@ -55,9 +55,6 @@ function getLocalIdent(loaderContext, _, localName, options) {
const exclude = [
'@github/markdown-toolbar-element',
'@github/text-expander-element',
- 'react-intl',
- 'intl-messageformat',
- 'intl-messageformat-parser',
].map((m) => path.resolve(__dirname, 'node_modules', m));
const htmlMinifyOptions = {
diff --git a/frontend/package.json b/frontend/package.json
index 6f3acd63..c69b915d 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -85,8 +85,7 @@
"undici@>=7.0.0 <8.0.0": ">=7.29.0 <8.0.0",
"body-parser@<1.20.6": ">=1.20.6 <2.0.0",
"preact": "10.29.8",
- "@types/minimatch": "5.1.2",
- "cheerio": "1.0.0-rc.12"
+ "@types/minimatch": "5.1.2"
}
}
}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 5b825974..8e0d8a01 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -71,7 +71,6 @@ overrides:
body-parser@<1.20.6: '>=1.20.6 <2.0.0'
preact: 10.29.8
'@types/minimatch': 5.1.2
- cheerio: 1.0.0-rc.12
importers:
@@ -101,9 +100,6 @@ importers:
core-js:
specifier: ^3.49.0
version: 3.49.0
- intersection-observer:
- specifier: ^0.12.2
- version: 0.12.2
lodash-es:
specifier: ^4.18.1
version: 4.18.1
@@ -113,15 +109,6 @@ importers:
preact:
specifier: 10.29.8
version: 10.29.8
- react:
- specifier: npm:@preact/compat@^18.3.2
- version: '@preact/compat@18.3.2(preact@10.29.8)'
- react-dom:
- specifier: npm:@preact/compat@^18.3.2
- version: '@preact/compat@18.3.2(preact@10.29.8)'
- react-intl:
- specifier: 6.0.5
- version: 6.0.5(@preact/compat@18.3.2(preact@10.29.8))(typescript@5.9.3)
redux:
specifier: ^4.2.0
version: 4.2.1
@@ -174,12 +161,6 @@ importers:
'@testing-library/preact':
specifier: 3.2.4
version: 3.2.4(preact@10.29.8)
- '@testing-library/preact-hooks':
- specifier: ^1.1.0
- version: 1.1.0(@testing-library/preact@3.2.4(preact@10.29.8))(preact@10.29.8)
- '@types/enzyme':
- specifier: ^3.10.19
- version: 3.10.19
'@types/eslint':
specifier: ^8.4.5
version: 8.56.12
@@ -237,12 +218,6 @@ importers:
dotenv:
specifier: ^17.4.2
version: 17.4.2
- enzyme:
- specifier: ^3.11.0
- version: 3.11.0
- enzyme-adapter-preact-pure:
- specifier: ^4.1.0
- version: 4.1.0(enzyme@3.11.0)(preact@10.29.8)
eslint:
specifier: ^8.18.0
version: 8.57.1
@@ -1238,35 +1213,6 @@ packages:
'@vue/compiler-sfc':
optional: true
- '@formatjs/ecma402-abstract@1.11.8':
- resolution: {integrity: sha512-fgLqyWlwmTEuqV/TSLEL/t9JOmHNLFvCdgzXB0jc2w+WOItPCOJ1T0eyN6fQBQKRPfSqqNlu+kWj7ijcOVTVVQ==}
-
- '@formatjs/fast-memoize@1.2.4':
- resolution: {integrity: sha512-9ARYoLR8AEzXvj2nYrOVHY/h1dDMDWGTnKDLXSISF1uoPakSmfcZuSqjiqZX2wRkEUimPxdwTu/agyozBtZRHA==}
-
- '@formatjs/icu-messageformat-parser@2.1.4':
- resolution: {integrity: sha512-3PqMvKWV1oyok0BuiXUAHIaotdhdTJw6OICqCZbfUgKT+ZRwRWO4IlCgvXJeCITaKS5p+PY0XXKjf/vUyIpWjQ==}
-
- '@formatjs/icu-skeleton-parser@1.3.10':
- resolution: {integrity: sha512-kXJmtLDqFF5aLTf8IxdJXnhrIX1Qb4Qp3a9jqRecGDYfzOa9hMhi9U0nKyhrJJ4cXxBzptcgb+LWkyeHL6nlBQ==}
-
- '@formatjs/intl-displaynames@6.0.3':
- resolution: {integrity: sha512-Mxh6W1VOlmiEvO/QPBrBQHlXrIn5VxjJWyyEI0V7ZHNGl0ee8AjSlq7vIJG8GodRJqGUuutF6N3OB/6qFv0YWg==}
-
- '@formatjs/intl-listformat@7.0.3':
- resolution: {integrity: sha512-ampNLRGZl/08epHa3i5sRmcHGLneC6JrknexbbgnexYFNSmJ6AbL/dCzgrQzw2Efl+5AZK7UbNFxcDYY3RePvw==}
-
- '@formatjs/intl-localematcher@0.2.28':
- resolution: {integrity: sha512-FLsc6Gifs1np/8HnCn/7Q+lHMmenrD5fuDhRT82yj0gi9O19kfaFwjQUw1gZsyILuRyT93GuzdifHj7TKRhBcw==}
-
- '@formatjs/intl@2.3.1':
- resolution: {integrity: sha512-f06qZ/ukpeN24gc01qFjh3P+r3FU/ikY4yG+fDJu6dPNvpUQzDy98lYogA1dr6ig2UtrnoEk3xncyFPL1e9cZw==}
- peerDependencies:
- typescript: ^4.5
- peerDependenciesMeta:
- typescript:
- optional: true
-
'@github/combobox-nav@2.3.1':
resolution: {integrity: sha512-gwxPzLw8XKecy1nP63i9lOBritS3bWmxl02UX6G0TwMQZbMem1BCS1tEZgYd3mkrkiDrUMWaX+DbFCuDFo3K+A==}
@@ -1604,11 +1550,6 @@ packages:
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
- '@preact/compat@18.3.2':
- resolution: {integrity: sha512-5vSl55K5yLMvocT7PBKxDOHGgYPjMrKQqqr6roSNjIXcJOtSgDDMjpiCAF3s7klRdmGrN75b/Przmjw8gmlg/w==}
- peerDependencies:
- preact: 10.29.8
-
'@prefresh/babel-plugin@0.4.4':
resolution: {integrity: sha512-/EvgIFMDL+nd20WNvMO0JQnzIl1EJPgmSaSYrZUww7A+aSdKsi37aL07TljrZR1cBMuzFxcr4xvqsUQLFJEukw==}
@@ -1758,12 +1699,6 @@ packages:
resolution: {integrity: sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==}
engines: {node: '>=8', npm: '>=6', yarn: '>=1'}
- '@testing-library/preact-hooks@1.1.0':
- resolution: {integrity: sha512-+JIor+NsOHkK3oIrwMDGKGHXTN0JJi462dBJlj4FNbGaDPTlctE6eu2ranWQirh7/FJMkWfzQCP+tk7jmY8ZrQ==}
- peerDependencies:
- '@testing-library/preact': ^2.0.0
- preact: 10.29.8
-
'@testing-library/preact@3.2.4':
resolution: {integrity: sha512-F+kJ243LP6VmEK1M809unzTE/ijg+bsMNuiRN0JEDIJBELKKDNhdgC/WrUSZ7klwJvtlO3wQZ9ix+jhObG07Fg==}
engines: {node: '>= 12'}
@@ -1807,18 +1742,12 @@ packages:
'@types/bonjour@3.5.13':
resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==}
- '@types/cheerio@0.22.35':
- resolution: {integrity: sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==}
-
'@types/connect-history-api-fallback@1.5.4':
resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==}
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
- '@types/enzyme@3.10.19':
- resolution: {integrity: sha512-kIfCo6/DdpgCHgmrLgPTugjzbZ46BUK8S2IP0kYo8+62LD2l1k8mSVsc+zQYNTdjDRoh2E9Spxu6F1NnEiW38Q==}
-
'@types/eslint@8.56.12':
resolution: {integrity: sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==}
@@ -1843,11 +1772,6 @@ packages:
'@types/graceful-fs@4.1.9':
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
- '@types/hoist-non-react-statics@3.3.7':
- resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==}
- peerDependencies:
- '@types/react': '*'
-
'@types/html-minifier-terser@6.1.0':
resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==}
@@ -1911,30 +1835,18 @@ packages:
'@types/prettier@2.7.3':
resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==}
- '@types/prop-types@15.7.15':
- resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
-
'@types/qs@6.15.1':
resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
'@types/range-parser@1.2.7':
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
- '@types/react@16.14.70':
- resolution: {integrity: sha512-DM5Q7rSx9G6QYcVvMgxvEurL5P06OxcDNUXrLxlpBzG4ccUewcBCmsztYbxJBobzO8RIwwmjoaD5OsKqdHDuYQ==}
-
- '@types/react@18.3.31':
- resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==}
-
'@types/redux-mock-store@1.5.0':
resolution: {integrity: sha512-jcscBazm6j05Hs6xYCca6psTUBbFT2wqMxT7wZEHAYFxHB/I8jYk7d5msrHUlDiSL02HdTqTmkK2oIV8i3C8DA==}
'@types/retry@0.12.2':
resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
- '@types/scheduler@0.16.8':
- resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
-
'@types/semver@7.7.1':
resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
@@ -2333,10 +2245,6 @@ packages:
resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==}
engines: {node: '>=0.10.0'}
- array.prototype.filter@1.0.4:
- resolution: {integrity: sha512-r+mCJ7zXgXElgR4IRC+fkvNCeoaavWBs6EdCso5Tbcf+iEMKzBU/His60lt34WEZ9vlb8wDkZvQGcVI5GwkfoQ==}
- engines: {node: '>= 0.4'}
-
array.prototype.findlast@1.2.5:
resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
engines: {node: '>= 0.4'}
@@ -2605,13 +2513,6 @@ packages:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
engines: {node: '>=10'}
- cheerio-select@2.1.0:
- resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
-
- cheerio@1.0.0-rc.12:
- resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
- engines: {node: '>= 6'}
-
chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
@@ -2927,9 +2828,6 @@ packages:
resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==}
engines: {node: '>=8'}
- csstype@3.2.3:
- resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
-
damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
@@ -3070,9 +2968,6 @@ packages:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
- discontinuous-range@1.0.0:
- resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==}
-
dns-packet@5.6.1:
resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==}
engines: {node: '>=6'}
@@ -3174,27 +3069,11 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
- entities@6.0.1:
- resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
- engines: {node: '>=0.12'}
-
envinfo@7.21.0:
resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==}
engines: {node: '>=4'}
hasBin: true
- enzyme-adapter-preact-pure@4.1.0:
- resolution: {integrity: sha512-SifzvBGf1qUEs0FfCAOiKgDppb1wg/R+rmX8D7jFVpQ/Q2L3/xuyc1V575zoi8QAhIBDUB/8QUWvI4KZc50trw==}
- peerDependencies:
- enzyme: ^3.11.0
- preact: 10.29.8
-
- enzyme-shallow-equal@1.0.7:
- resolution: {integrity: sha512-/um0GFqUXnpM9SvKtje+9Tjoz3f1fpBC3eXRFrNs8kpYn69JljciYP7KZTqM/YQbUY9KUjvKB4jo/q+L6WGGvg==}
-
- enzyme@3.11.0:
- resolution: {integrity: sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==}
-
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
@@ -3206,9 +3085,6 @@ packages:
resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
engines: {node: '>= 0.4'}
- es-array-method-boxes-properly@1.0.0:
- resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==}
-
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@@ -3748,10 +3624,6 @@ packages:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
- has@1.0.4:
- resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==}
- engines: {node: '>= 0.4.0'}
-
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
@@ -3760,9 +3632,6 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
- hoist-non-react-statics@3.3.2:
- resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
-
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
@@ -3773,10 +3642,6 @@ packages:
hpack.js@2.1.6:
resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==}
- html-element-map@1.4.0:
- resolution: {integrity: sha512-jiTQtpaVnCcT1KDghMcmvbB5Q1AAWyBsGNuJZiHOWwN5GIVZGKqCWj9ddOFxLLz8ELYL2dwv2TaeS4dMdc/Pkw==}
- engines: {node: '>= 0.4'}
-
html-encoding-sniffer@3.0.0:
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
engines: {node: '>=12'}
@@ -3925,13 +3790,6 @@ packages:
resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==}
engines: {node: '>= 0.10'}
- intersection-observer@0.12.2:
- resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
- deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.
-
- intl-messageformat@10.1.1:
- resolution: {integrity: sha512-FeJne2oooYW6shLPbrqyjRX6hTELVrQ90Dn88z7NomLk/xZBCLxLPAkgaYaTQJBRBV78nZ933d8APHHkTQrD9Q==}
-
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
@@ -4104,9 +3962,6 @@ packages:
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
engines: {node: '>= 0.4'}
- is-subset@0.1.1:
- resolution: {integrity: sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==}
-
is-symbol@1.1.1:
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
engines: {node: '>= 0.4'}
@@ -4472,16 +4327,6 @@ packages:
lodash.debounce@4.0.8:
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
- lodash.escape@4.0.1:
- resolution: {integrity: sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==}
-
- lodash.flattendeep@4.4.0:
- resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==}
-
- lodash.isequal@4.5.0:
- resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
- deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
-
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
@@ -4703,9 +4548,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
- moo@0.5.3:
- resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==}
-
mrmime@2.0.1:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'}
@@ -4742,10 +4584,6 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- nearley@2.20.1:
- resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==}
- hasBin: true
-
negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
@@ -4947,15 +4785,9 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
- parse5-htmlparser2-tree-adapter@7.1.0:
- resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
-
parse5@6.0.1:
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
- parse5@7.3.0:
- resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
-
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
@@ -5000,9 +4832,6 @@ packages:
resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==}
engines: {node: '>=18'}
- performance-now@2.1.0:
- resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
-
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -5534,16 +5363,6 @@ packages:
resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==}
engines: {node: '>=8'}
- raf@3.4.1:
- resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
-
- railroad-diagrams@1.0.0:
- resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==}
-
- randexp@0.4.6:
- resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==}
- engines: {node: '>=0.12'}
-
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
@@ -5556,15 +5375,6 @@ packages:
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
engines: {node: '>= 0.8'}
- react-intl@6.0.5:
- resolution: {integrity: sha512-nDZ3BosuE8WdovcGxsrjj1aIgJZklSL5aORs5oah+5tLQTzUdOEstzJEYQPM+sxl1dkDOu7RCuw0z9oI9ENf9g==}
- peerDependencies:
- react: ^16.6.0 || 17 || 18
- typescript: ^4.5
- peerDependenciesMeta:
- typescript:
- optional: true
-
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -5692,10 +5502,6 @@ packages:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- ret@0.1.15:
- resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
- engines: {node: '>=0.12'}
-
retry@0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
engines: {node: '>= 4'}
@@ -5717,9 +5523,6 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
- rst-selector-parser@2.2.3:
- resolution: {integrity: sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==}
-
run-applescript@7.1.0:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
@@ -6219,9 +6022,6 @@ packages:
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
- tslib@2.4.0:
- resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
-
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -7632,54 +7432,6 @@ snapshots:
'@formatjs/cli@5.1.13': {}
- '@formatjs/ecma402-abstract@1.11.8':
- dependencies:
- '@formatjs/intl-localematcher': 0.2.28
- tslib: 2.4.0
-
- '@formatjs/fast-memoize@1.2.4':
- dependencies:
- tslib: 2.4.0
-
- '@formatjs/icu-messageformat-parser@2.1.4':
- dependencies:
- '@formatjs/ecma402-abstract': 1.11.8
- '@formatjs/icu-skeleton-parser': 1.3.10
- tslib: 2.4.0
-
- '@formatjs/icu-skeleton-parser@1.3.10':
- dependencies:
- '@formatjs/ecma402-abstract': 1.11.8
- tslib: 2.4.0
-
- '@formatjs/intl-displaynames@6.0.3':
- dependencies:
- '@formatjs/ecma402-abstract': 1.11.8
- '@formatjs/intl-localematcher': 0.2.28
- tslib: 2.4.0
-
- '@formatjs/intl-listformat@7.0.3':
- dependencies:
- '@formatjs/ecma402-abstract': 1.11.8
- '@formatjs/intl-localematcher': 0.2.28
- tslib: 2.4.0
-
- '@formatjs/intl-localematcher@0.2.28':
- dependencies:
- tslib: 2.4.0
-
- '@formatjs/intl@2.3.1(typescript@5.9.3)':
- dependencies:
- '@formatjs/ecma402-abstract': 1.11.8
- '@formatjs/fast-memoize': 1.2.4
- '@formatjs/icu-messageformat-parser': 2.1.4
- '@formatjs/intl-displaynames': 6.0.3
- '@formatjs/intl-listformat': 7.0.3
- intl-messageformat: 10.1.1
- tslib: 2.4.0
- optionalDependencies:
- typescript: 5.9.3
-
'@github/combobox-nav@2.3.1': {}
'@github/markdown-toolbar-element@2.2.3': {}
@@ -8192,10 +7944,6 @@ snapshots:
'@polka/url@1.0.0-next.29': {}
- '@preact/compat@18.3.2(preact@10.29.8)':
- dependencies:
- preact: 10.29.8
-
'@prefresh/babel-plugin@0.4.4': {}
'@prefresh/core@1.5.10(preact@10.29.8)':
@@ -8324,11 +8072,6 @@ snapshots:
lodash: 4.18.1
redent: 3.0.0
- '@testing-library/preact-hooks@1.1.0(@testing-library/preact@3.2.4(preact@10.29.8))(preact@10.29.8)':
- dependencies:
- '@testing-library/preact': 3.2.4(preact@10.29.8)
- preact: 10.29.8
-
'@testing-library/preact@3.2.4(preact@10.29.8)':
dependencies:
'@testing-library/dom': 8.20.1
@@ -8376,10 +8119,6 @@ snapshots:
dependencies:
'@types/node': 26.0.1
- '@types/cheerio@0.22.35':
- dependencies:
- '@types/node': 18.19.130
-
'@types/connect-history-api-fallback@1.5.4':
dependencies:
'@types/express-serve-static-core': 5.1.3
@@ -8389,11 +8128,6 @@ snapshots:
dependencies:
'@types/node': 26.0.1
- '@types/enzyme@3.10.19':
- dependencies:
- '@types/cheerio': 0.22.35
- '@types/react': 16.14.70
-
'@types/eslint@8.56.12':
dependencies:
'@types/estree': 1.0.9
@@ -8437,11 +8171,6 @@ snapshots:
dependencies:
'@types/node': 18.19.130
- '@types/hoist-non-react-statics@3.3.7(@types/react@18.3.31)':
- dependencies:
- '@types/react': 18.3.31
- hoist-non-react-statics: 3.3.2
-
'@types/html-minifier-terser@6.1.0': {}
'@types/http-errors@2.0.5': {}
@@ -8501,31 +8230,16 @@ snapshots:
'@types/prettier@2.7.3': {}
- '@types/prop-types@15.7.15': {}
-
'@types/qs@6.15.1': {}
'@types/range-parser@1.2.7': {}
- '@types/react@16.14.70':
- dependencies:
- '@types/prop-types': 15.7.15
- '@types/scheduler': 0.16.8
- csstype: 3.2.3
-
- '@types/react@18.3.31':
- dependencies:
- '@types/prop-types': 15.7.15
- csstype: 3.2.3
-
'@types/redux-mock-store@1.5.0':
dependencies:
redux: 4.2.1
'@types/retry@0.12.2': {}
- '@types/scheduler@0.16.8': {}
-
'@types/semver@7.7.1': {}
'@types/send@0.17.6':
@@ -8997,15 +8711,6 @@ snapshots:
array-uniq@1.0.3: {}
- array.prototype.filter@1.0.4:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-array-method-boxes-properly: 1.0.0
- es-object-atoms: 1.1.2
- is-string: 1.1.1
-
array.prototype.findlast@1.2.5:
dependencies:
call-bind: 1.0.9
@@ -9364,25 +9069,6 @@ snapshots:
char-regex@1.0.2: {}
- cheerio-select@2.1.0:
- dependencies:
- boolbase: 1.0.0
- css-select: 5.2.2
- css-what: 6.2.2
- domelementtype: 2.3.0
- domhandler: 5.0.3
- domutils: 3.2.2
-
- cheerio@1.0.0-rc.12:
- dependencies:
- cheerio-select: 2.1.0
- dom-serializer: 2.0.0
- domhandler: 5.0.3
- domutils: 3.2.2
- htmlparser2: 8.0.2
- parse5: 7.3.0
- parse5-htmlparser2-tree-adapter: 7.1.0
-
chokidar@3.6.0:
dependencies:
anymatch: 3.1.3
@@ -9698,8 +9384,6 @@ snapshots:
dependencies:
cssom: 0.3.8
- csstype@3.2.3: {}
-
damerau-levenshtein@1.0.8: {}
data-urls@3.0.2:
@@ -9831,8 +9515,6 @@ snapshots:
dependencies:
path-type: 4.0.0
- discontinuous-range@1.0.0: {}
-
dns-packet@5.6.1:
dependencies:
'@leichtgewicht/ip-codec': 2.0.5
@@ -9931,45 +9613,8 @@ snapshots:
entities@4.5.0: {}
- entities@6.0.1: {}
-
envinfo@7.21.0: {}
- enzyme-adapter-preact-pure@4.1.0(enzyme@3.11.0)(preact@10.29.8):
- dependencies:
- enzyme: 3.11.0
- preact: 10.29.8
-
- enzyme-shallow-equal@1.0.7:
- dependencies:
- hasown: 2.0.4
- object-is: 1.1.6
-
- enzyme@3.11.0:
- dependencies:
- array.prototype.flat: 1.3.3
- cheerio: 1.0.0-rc.12
- enzyme-shallow-equal: 1.0.7
- function.prototype.name: 1.2.0
- has: 1.0.4
- html-element-map: 1.4.0
- is-boolean-object: 1.2.2
- is-callable: 1.2.7
- is-number-object: 1.1.1
- is-regex: 1.2.1
- is-string: 1.1.1
- is-subset: 0.1.1
- lodash.escape: 4.0.1
- lodash.isequal: 4.5.0
- object-inspect: 1.13.4
- object-is: 1.1.6
- object.assign: 4.1.7
- object.entries: 1.1.9
- object.values: 1.2.1
- raf: 3.4.1
- rst-selector-parser: 2.2.3
- string.prototype.trim: 1.2.11
-
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
@@ -10038,8 +9683,6 @@ snapshots:
unbox-primitive: 1.1.0
which-typed-array: 1.1.22
- es-array-method-boxes-properly@1.0.0: {}
-
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@@ -10795,18 +10438,12 @@ snapshots:
dependencies:
has-symbols: 1.1.0
- has@1.0.4: {}
-
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
he@1.2.0: {}
- hoist-non-react-statics@3.3.2:
- dependencies:
- react-is: 16.13.1
-
hosted-git-info@2.8.9: {}
hosted-git-info@4.1.0:
@@ -10820,11 +10457,6 @@ snapshots:
readable-stream: 2.3.8
wbuf: 1.7.3
- html-element-map@1.4.0:
- dependencies:
- array.prototype.filter: 1.0.4
- es-errors: 1.3.0
-
html-encoding-sniffer@3.0.0:
dependencies:
whatwg-encoding: 2.0.0
@@ -10977,15 +10609,6 @@ snapshots:
interpret@2.2.0: {}
- intersection-observer@0.12.2: {}
-
- intl-messageformat@10.1.1:
- dependencies:
- '@formatjs/ecma402-abstract': 1.11.8
- '@formatjs/fast-memoize': 1.2.4
- '@formatjs/icu-messageformat-parser': 2.1.4
- tslib: 2.4.0
-
ipaddr.js@1.9.1: {}
ipaddr.js@2.4.0: {}
@@ -11134,8 +10757,6 @@ snapshots:
call-bound: 1.0.4
has-tostringtag: 1.0.2
- is-subset@0.1.1: {}
-
is-symbol@1.1.1:
dependencies:
call-bound: 1.0.4
@@ -11731,12 +11352,6 @@ snapshots:
lodash.debounce@4.0.8: {}
- lodash.escape@4.0.1: {}
-
- lodash.flattendeep@4.4.0: {}
-
- lodash.isequal@4.5.0: {}
-
lodash.isplainobject@4.0.6: {}
lodash.memoize@4.1.2: {}
@@ -11914,8 +11529,6 @@ snapshots:
mkdirp@1.0.4: {}
- moo@0.5.3: {}
-
mrmime@2.0.1: {}
ms@2.0.0: {}
@@ -11941,13 +11554,6 @@ snapshots:
natural-compare@1.4.0: {}
- nearley@2.20.1:
- dependencies:
- commander: 2.20.3
- moo: 0.5.3
- railroad-diagrams: 1.0.0
- randexp: 0.4.6
-
negotiator@0.6.3: {}
negotiator@0.6.4: {}
@@ -12173,17 +11779,8 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
- parse5-htmlparser2-tree-adapter@7.1.0:
- dependencies:
- domhandler: 5.0.3
- parse5: 7.3.0
-
parse5@6.0.1: {}
- parse5@7.3.0:
- dependencies:
- entities: 6.0.1
-
parseurl@1.3.3: {}
pascal-case@3.1.2:
@@ -12213,8 +11810,6 @@ snapshots:
path-type@6.0.0: {}
- performance-now@2.1.0: {}
-
picocolors@1.1.1: {}
picomatch@2.3.2: {}
@@ -12727,17 +12322,6 @@ snapshots:
quick-lru@4.0.1: {}
- raf@3.4.1:
- dependencies:
- performance-now: 2.1.0
-
- railroad-diagrams@1.0.0: {}
-
- randexp@0.4.6:
- dependencies:
- discontinuous-range: 1.0.0
- ret: 0.1.15
-
range-parser@1.2.1: {}
range-parser@1.3.0: {}
@@ -12749,22 +12333,6 @@ snapshots:
iconv-lite: 0.4.24
unpipe: 1.0.0
- react-intl@6.0.5(@preact/compat@18.3.2(preact@10.29.8))(typescript@5.9.3):
- dependencies:
- '@formatjs/ecma402-abstract': 1.11.8
- '@formatjs/icu-messageformat-parser': 2.1.4
- '@formatjs/intl': 2.3.1(typescript@5.9.3)
- '@formatjs/intl-displaynames': 6.0.3
- '@formatjs/intl-listformat': 7.0.3
- '@types/hoist-non-react-statics': 3.3.7(@types/react@18.3.31)
- '@types/react': 18.3.31
- hoist-non-react-statics: 3.3.2
- intl-messageformat: 10.1.1
- react: '@preact/compat@18.3.2(preact@10.29.8)'
- tslib: 2.4.0
- optionalDependencies:
- typescript: 5.9.3
-
react-is@16.13.1: {}
react-is@17.0.2: {}
@@ -12920,8 +12488,6 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
- ret@0.1.15: {}
-
retry@0.13.1: {}
reusify@1.1.0: {}
@@ -12936,11 +12502,6 @@ snapshots:
dependencies:
glob: 7.2.3
- rst-selector-parser@2.2.3:
- dependencies:
- lodash.flattendeep: 4.4.0
- nearley: 2.20.1
-
run-applescript@7.1.0: {}
run-parallel@1.2.0:
@@ -13581,8 +13142,6 @@ snapshots:
tslib@1.14.1: {}
- tslib@2.4.0: {}
-
tslib@2.8.1: {}
tsutils@3.21.0(typescript@5.9.3):
diff --git a/site/src/docs/contributing/frontend/index.md b/site/src/docs/contributing/frontend/index.md
index 989cb30f..5c9901ad 100644
--- a/site/src/docs/contributing/frontend/index.md
+++ b/site/src/docs/contributing/frontend/index.md
@@ -8,7 +8,8 @@ Frontend for Remark42 is built with [Preact](https://preactjs.com) and [Redux](h
::: note 💡
We highly recommend checking out Preact [documentation](https://preactjs.com/guide/v10/getting-started).
-TLDR: Preact replicates React API and compatible with its libraries.
+React libraries are not usable here: the store bindings and the i18n helpers are small local
+modules under `app/`.
:::
In order to inject Remark42 widgets into websites we use `iframe` and `postMessage` for communication between a site and the widget.
@@ -111,10 +112,10 @@ Run `pnpm build` inside `./frontend`, and result files will be saved in `./front
## Testing
- Project uses [Jest](https://jestjs.io) as test framework
-- [Testing Library](https://testing-library.com) is used as UI test utilities (there are still tests with Enzyme, but we are in process of migration)
+- [Testing Library](https://testing-library.com) is used for UI tests
- Jest checks files that match regex `\.(test|spec)\.ts(x?)$`, i.e., `comment.test.tsx`, `comment.spec.ts`
- Tests are running on push attempt
-- Example tests can be found in `./app/components/auth/auth.spec.ts`, `./app/store/user/reducers.test.ts`
+- Example tests can be found in `./app/components/auth/auth.spec.tsx`, `./app/store/user/reducers.test.ts`
## Notes
diff --git a/site/src/docs/contributing/translations/index.md b/site/src/docs/contributing/translations/index.md
index 08966dfe..6c36e64f 100644
--- a/site/src/docs/contributing/translations/index.md
+++ b/site/src/docs/contributing/translations/index.md
@@ -16,6 +16,23 @@ directory with `.json` extension and content like following:
}
```
+::: note 🚨
+Translations support `{name}` placeholders and paired tags such as `text`. ICU plural,
+select and typed-argument syntax is not supported: a message using one either falls back to
+English or shows the raw syntax on the page, depending on the message. Apostrophe quoting is not
+supported either, so `''` stays as two apostrophes.
+
+You may drop a tag from the English string if your language reads better without the link. If
+you keep it, it has to stay paired and keep the same name, and every placeholder has to be one
+the English string already uses. A tag that is broken, stray, nested or unknown, or a placeholder
+the widget does not supply, makes that whole message fall back to English.
+
+CI checks every value's tags and placeholders against the English string it translates, and
+renders the two messages that carry a link. It cannot tell that an ICU form is unsupported,
+since that is ordinary text to it, so open your translation in the interface before sending
+it.
+:::
+
### Add a new translation
We truly appreciate people spending time contributing their translations to remark42. Please go through the steps
@@ -31,9 +48,9 @@ below to have your translation available to all remark42 users and included in t
```
1. Add a new locale with a [two-letter code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) of the language you want to do the translation into to list in [frontend/apps/remark42/tasks/supportedLocales.json](https://github.com/umputun/remark42/blob/master/frontend/apps/remark42/tasks/supportedLocales.json)
-1. Run `npm install` in the `frontend` folder
-1. Run `npm run translation:extract` in the `frontend` folder
-1. Run `npm run translation:generate` in the `frontend` folder
+1. Run `pnpm i` in the `frontend` folder
+1. Run `pnpm translation:extract` in the `frontend/apps/remark42` folder
+1. Run `pnpm translation:generate` in the `frontend/apps/remark42` folder
1. Translate all values in the newly created JSON file in
[frontend/apps/remark42/app/locales/](https://github.com/umputun/remark42/tree/master/frontend/apps/remark42/app/locales)
1. Commit all changes above in your fork