Allow Chrome page translator to translate messages in rooms (#11113)

* support message translation in chat

* Update src/HtmlUtils.tsx

* update snapshots

* Convert TextualBody-test to use snapshots

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Co-authored-by: Richard van der Hoff <richard@matrix.org>
This commit is contained in:
Łukasz Polowczyk 2024-07-25 18:58:33 +02:00 committed by GitHub
parent 3c370c6cbc
commit c1420ba126
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 136 additions and 46 deletions

View file

@ -423,6 +423,8 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
"mx_EventTile_body": true, "mx_EventTile_body": true,
"mx_EventTile_bigEmoji": emojiBody, "mx_EventTile_bigEmoji": emojiBody,
"markdown-body": isHtmlMessage && !emojiBody, "markdown-body": isHtmlMessage && !emojiBody,
// Override the global `notranslate` class set by the top-level `matrixchat` div.
"translate": true,
}); });
let emojiBodyElements: JSX.Element[] | undefined; let emojiBodyElements: JSX.Element[] | undefined;

View file

@ -3,7 +3,7 @@
exports[`bodyToHtml does not mistake characters in text presentation mode for emoji 1`] = ` exports[`bodyToHtml does not mistake characters in text presentation mode for emoji 1`] = `
<DocumentFragment> <DocumentFragment>
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
↔ ❗︎ ↔ ❗︎
@ -22,7 +22,7 @@ exports[`bodyToHtml feature_latex_maths should render inline katex 1`] = `"hello
exports[`bodyToHtml generates big emoji for emoji made of multiple characters 1`] = ` exports[`bodyToHtml generates big emoji for emoji made of multiple characters 1`] = `
<DocumentFragment> <DocumentFragment>
<span <span
class="mx_EventTile_body mx_EventTile_bigEmoji" class="mx_EventTile_body mx_EventTile_bigEmoji translate"
dir="auto" dir="auto"
> >
<span <span
@ -52,7 +52,7 @@ exports[`bodyToHtml generates big emoji for emoji made of multiple characters 1`
exports[`bodyToHtml should generate big emoji for an emoji-only reply to a message 1`] = ` exports[`bodyToHtml should generate big emoji for an emoji-only reply to a message 1`] = `
<DocumentFragment> <DocumentFragment>
<span <span
class="mx_EventTile_body mx_EventTile_bigEmoji" class="mx_EventTile_body mx_EventTile_bigEmoji translate"
dir="auto" dir="auto"
> >
<span <span

View file

@ -77,7 +77,7 @@ exports[`<MessageEditHistory /> should match the snapshot 1`] = `
class="mx_EventTile_content" class="mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
My Great Massage My Great Massage
@ -291,7 +291,7 @@ exports[`<MessageEditHistory /> should support events with 1`] = `
class="mx_EventTile_content" class="mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
My Great Missage My Great Missage

View file

@ -135,7 +135,7 @@ describe("<TextualBody />", () => {
const { container } = getComponent({ mxEvent: ev }); const { container } = getComponent({ mxEvent: ev });
expect(container).toHaveTextContent("* sender winks"); expect(container).toHaveTextContent("* sender winks");
const content = container.querySelector(".mx_EventTile_body"); const content = container.querySelector(".mx_EventTile_body");
expect(content).toContainHTML('<span class="mx_EventTile_body" dir="auto">winks</span>'); expect(content).toMatchSnapshot();
}); });
it("renders m.notice correctly", () => { it("renders m.notice correctly", () => {
@ -155,7 +155,7 @@ describe("<TextualBody />", () => {
const { container } = getComponent({ mxEvent: ev }); const { container } = getComponent({ mxEvent: ev });
expect(container).toHaveTextContent(ev.getContent().body); expect(container).toHaveTextContent(ev.getContent().body);
const content = container.querySelector(".mx_EventTile_body"); const content = container.querySelector(".mx_EventTile_body");
expect(content).toContainHTML(`<span class="mx_EventTile_body" dir="auto">${ev.getContent().body}</span>`); expect(content).toMatchSnapshot();
}); });
describe("renders plain-text m.text correctly", () => { describe("renders plain-text m.text correctly", () => {
@ -168,7 +168,7 @@ describe("<TextualBody />", () => {
const { container } = getComponent({ mxEvent: ev }); const { container } = getComponent({ mxEvent: ev });
expect(container).toHaveTextContent(ev.getContent().body); expect(container).toHaveTextContent(ev.getContent().body);
const content = container.querySelector(".mx_EventTile_body"); const content = container.querySelector(".mx_EventTile_body");
expect(content).toContainHTML(`<span class="mx_EventTile_body" dir="auto">${ev.getContent().body}</span>`); expect(content).toMatchSnapshot();
}); });
// If pills were rendered within a Portal/same shadow DOM then it'd be easier to test // If pills were rendered within a Portal/same shadow DOM then it'd be easier to test
@ -177,11 +177,7 @@ describe("<TextualBody />", () => {
const { container } = getComponent({ mxEvent: ev }); const { container } = getComponent({ mxEvent: ev });
expect(container).toHaveTextContent(ev.getContent().body); expect(container).toHaveTextContent(ev.getContent().body);
const content = container.querySelector(".mx_EventTile_body"); const content = container.querySelector(".mx_EventTile_body");
expect(content).toContainHTML( expect(content).toMatchSnapshot();
'<span class="mx_EventTile_body" dir="auto">' +
'Visit <a href="https://matrix.org/" class="linkified" target="_blank" rel="noreferrer noopener">' +
"https://matrix.org/</a></span>",
);
}); });
it("should not pillify MXIDs", () => { it("should not pillify MXIDs", () => {
@ -266,11 +262,7 @@ describe("<TextualBody />", () => {
const { container } = getComponent({ mxEvent: ev }, matrixClient); const { container } = getComponent({ mxEvent: ev }, matrixClient);
expect(container).toHaveTextContent("foo baz bar del u"); expect(container).toHaveTextContent("foo baz bar del u");
const content = container.querySelector(".mx_EventTile_body"); const content = container.querySelector(".mx_EventTile_body");
expect(content).toContainHTML( expect(content).toMatchSnapshot();
'<span class="mx_EventTile_body markdown-body" dir="auto">' +
ev.getContent().formatted_body +
"</span>",
);
}); });
it("spoilers get injected properly into the DOM", () => { it("spoilers get injected properly into the DOM", () => {
@ -281,14 +273,7 @@ describe("<TextualBody />", () => {
const { container } = getComponent({ mxEvent: ev }, matrixClient); const { container } = getComponent({ mxEvent: ev }, matrixClient);
expect(container).toHaveTextContent("Hey (movie) the movie was awesome"); expect(container).toHaveTextContent("Hey (movie) the movie was awesome");
const content = container.querySelector(".mx_EventTile_body"); const content = container.querySelector(".mx_EventTile_body");
expect(content).toContainHTML( expect(content).toMatchSnapshot();
'<span class="mx_EventTile_body markdown-body" dir="auto">' +
"Hey <span>" +
'<button class="mx_EventTile_spoiler">' +
'<span class="mx_EventTile_spoiler_reason">(movie)</span>&nbsp;' +
'<span class="mx_EventTile_spoiler_content"><span>the movie was awesome</span></span>' +
"</span></button></span>",
);
}); });
it("linkification is not applied to code blocks", () => { it("linkification is not applied to code blocks", () => {
@ -366,7 +351,7 @@ describe("<TextualBody />", () => {
expect(content).toMatchSnapshot(); expect(content).toMatchSnapshot();
}); });
it("renders formatted body without html corretly", () => { it("renders formatted body without html correctly", () => {
const ev = mkEvent({ const ev = mkEvent({
type: "m.room.message", type: "m.room.message",
room: "room_id", room: "room_id",
@ -383,9 +368,7 @@ describe("<TextualBody />", () => {
const { container } = getComponent({ mxEvent: ev }, matrixClient); const { container } = getComponent({ mxEvent: ev }, matrixClient);
const content = container.querySelector(".mx_EventTile_body"); const content = container.querySelector(".mx_EventTile_body");
expect(content).toContainHTML( expect(content).toMatchSnapshot();
'<span class="mx_EventTile_body" dir="auto">' + "escaped *markdown*" + "</span>",
);
}); });
}); });

View file

@ -1,8 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<TextualBody /> renders formatted m.text correctly italics, bold, underline and strikethrough render as expected 1`] = `
<span
class="mx_EventTile_body markdown-body translate"
dir="auto"
>
foo
<em>
baz
</em>
<strong>
bar
</strong>
<del>
del
</del>
<u>
u
</u>
</span>
`;
exports[`<TextualBody /> renders formatted m.text correctly linkification is not applied to code blocks 1`] = ` exports[`<TextualBody /> renders formatted m.text correctly linkification is not applied to code blocks 1`] = `
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body translate"
dir="auto" dir="auto"
> >
<p> <p>
@ -43,7 +67,7 @@ exports[`<TextualBody /> renders formatted m.text correctly linkification is not
exports[`<TextualBody /> renders formatted m.text correctly pills appear for an MXID permalink 1`] = ` exports[`<TextualBody /> renders formatted m.text correctly pills appear for an MXID permalink 1`] = `
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body translate"
dir="auto" dir="auto"
> >
Chat with Chat with
@ -90,7 +114,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills appear for eve
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body translate"
dir="auto" dir="auto"
> >
See this message See this message
@ -139,7 +163,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills appear for roo
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body translate"
dir="auto" dir="auto"
> >
A A
@ -189,7 +213,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body translate"
dir="auto" dir="auto"
> >
An An
@ -207,7 +231,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
exports[`<TextualBody /> renders formatted m.text correctly pills do not appear in code blocks 1`] = ` exports[`<TextualBody /> renders formatted m.text correctly pills do not appear in code blocks 1`] = `
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body translate"
dir="auto" dir="auto"
> >
<p> <p>
@ -247,7 +271,7 @@ exports[`<TextualBody /> renders formatted m.text correctly pills do not appear
exports[`<TextualBody /> renders formatted m.text correctly pills get injected correctly into the DOM 1`] = ` exports[`<TextualBody /> renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `
<span <span
class="mx_EventTile_body markdown-body" class="mx_EventTile_body markdown-body translate"
dir="auto" dir="auto"
> >
Hey Hey
@ -288,13 +312,85 @@ exports[`<TextualBody /> renders formatted m.text correctly pills get injected c
</span> </span>
`; `;
exports[`<TextualBody /> renders formatted m.text correctly renders formatted body without html correctly 1`] = `
<span
class="mx_EventTile_body translate"
dir="auto"
>
escaped *markdown*
</span>
`;
exports[`<TextualBody /> renders formatted m.text correctly spoilers get injected properly into the DOM 1`] = `
<span
class="mx_EventTile_body markdown-body translate"
dir="auto"
>
Hey
<span>
<button
class="mx_EventTile_spoiler"
>
<span
class="mx_EventTile_spoiler_reason"
>
(movie)
</span>
 
<span
class="mx_EventTile_spoiler_content"
>
<span>
the movie was awesome
</span>
</span>
</button>
</span>
</span>
`;
exports[`<TextualBody /> renders m.emote correctly 1`] = `
<span
class="mx_EventTile_body translate"
dir="auto"
>
winks
</span>
`;
exports[`<TextualBody /> renders m.notice correctly 1`] = `
<span
class="mx_EventTile_body translate"
dir="auto"
>
this is a notice, probably from a bot
</span>
`;
exports[`<TextualBody /> renders plain-text m.text correctly linkification get applied correctly into the DOM 1`] = `
<span
class="mx_EventTile_body translate"
dir="auto"
>
Visit
<a
class="linkified"
href="https://matrix.org/"
rel="noreferrer noopener"
target="_blank"
>
https://matrix.org/
</a>
</span>
`;
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to a message in the same room with the label »Message from Member« 1`] = `"Visit <span><bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room1:example.com/%event_id%"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_mcap2_50" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message from Member</span></a></bdi></span>"`; exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to a message in the same room with the label »Message from Member« 1`] = `"Visit <span><bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room1:example.com/%event_id%"><span aria-label="Profile picture" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/image.png" referrerpolicy="no-referrer" class="_image_mcap2_50" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message from Member</span></a></bdi></span>"`;
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an event in another room with the label »Message in Room 2« 1`] = `"Visit <span><bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room2:example.com/%event_id%"><span aria-label="Avatar" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/room.png" referrerpolicy="no-referrer" class="_image_mcap2_50" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message in Room 2</span></a></bdi></span>"`; exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an event in another room with the label »Message in Room 2« 1`] = `"Visit <span><bdi><a class="mx_Pill mx_EventPill" href="https://matrix.to/#/!room2:example.com/%event_id%"><span aria-label="Avatar" aria-hidden="true" data-testid="avatar-img" data-type="round" data-color="2" class="_avatar_mcap2_17 mx_BaseAvatar" style="--cpd-avatar-size: 16px;"><img loading="lazy" alt="" src="mxc://avatar.url/room.png" referrerpolicy="no-referrer" class="_image_mcap2_50" data-type="round" width="16px" height="16px"></span><span class="mx_Pill_text">Message in Room 2</span></a></bdi></span>"`;
exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an unknown message in the same room with the label »Message« 1`] = ` exports[`<TextualBody /> renders plain-text m.text correctly should pillify a permalink to an unknown message in the same room with the label »Message« 1`] = `
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
Visit Visit
@ -317,3 +413,12 @@ exports[`<TextualBody /> renders plain-text m.text correctly should pillify a pe
</span> </span>
</span> </span>
`; `;
exports[`<TextualBody /> renders plain-text m.text correctly simple message renders as expected 1`] = `
<span
class="mx_EventTile_body translate"
dir="auto"
>
this is a plaintext message
</span>
`;

View file

@ -27,7 +27,7 @@ exports[`<PinnedEventTile /> should render pinned event 1`] = `
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
First pinned message First pinned message

View file

@ -102,7 +102,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
Hey you. You're the best! Hey you. You're the best!
@ -218,7 +218,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
Hey you. You're the best! Hey you. You're the best!
@ -334,7 +334,7 @@ exports[`<LayoutSwitcher /> should render 1`] = `
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
Hey you. You're the best! Hey you. You're the best!

View file

@ -245,7 +245,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
Hey you. You're the best! Hey you. You're the best!
@ -361,7 +361,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
Hey you. You're the best! Hey you. You're the best!
@ -477,7 +477,7 @@ exports[`AppearanceUserSettingsTab should render 1`] = `
class="mx_MTextBody mx_EventTile_content" class="mx_MTextBody mx_EventTile_content"
> >
<span <span
class="mx_EventTile_body" class="mx_EventTile_body translate"
dir="auto" dir="auto"
> >
Hey you. You're the best! Hey you. You're the best!

File diff suppressed because one or more lines are too long