Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/plugins/translate/TranslationAccessory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ import { TranslateIcon } from "./TranslateIcon";
import { cl, TranslationValue } from "./utils";

const TranslationSetters = new Map<string, (v: TranslationValue) => void>();
export const translationCache = new Map<string, TranslationValue>();

export function handleTranslate(messageId: string, data: TranslationValue) {
TranslationSetters.get(messageId)!(data);
translationCache.set(messageId, data);
TranslationSetters.get(messageId)?.(data);
}

function Dismiss({ onDismiss }: { onDismiss: () => void; }) {
Expand All @@ -48,6 +50,9 @@ export function TranslationAccessory({ message }: { message: Message; }) {

TranslationSetters.set(message.id, setTranslation);

const cached = translationCache.get(message.id);
if (cached) setTranslation(cached);

return () => void TranslationSetters.delete(message.id);
}, []);

Expand All @@ -58,7 +63,10 @@ export function TranslationAccessory({ message }: { message: Message; }) {
<TranslateIcon width={16} height={16} className={cl("accessory-icon")} />
{Parser.parse(translation.text)}
<br />
(translated from {translation.sourceLanguage} - <Dismiss onDismiss={() => setTranslation(undefined)} />)
(translated from {translation.sourceLanguage} - <Dismiss onDismiss={() => {
translationCache.delete(message.id);
setTranslation(undefined);
}} />)
</span>
);
}
84 changes: 81 additions & 3 deletions src/plugins/translate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Message } from "@vencord/discord-types";
import { ChannelStore, Menu } from "@webpack/common";
import { ChannelStore, Menu, MessageStore, SelectedChannelStore, UserStore } from "@webpack/common";

import { settings } from "./settings";
import { setShouldShowTranslateEnabledTooltip, TranslateChatBarIcon, TranslateIcon } from "./TranslateIcon";
import { handleTranslate, TranslationAccessory } from "./TranslationAccessory";
import { handleTranslate, TranslationAccessory, translationCache } from "./TranslationAccessory";
import { translate } from "./utils";

const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }: { message: Message; }) => {
Expand Down Expand Up @@ -59,13 +59,61 @@ function getMessageContent(message: Message) {
|| message.embeds?.find(embed => embed.type === "auto_moderation_message")?.rawDescription || "";
}

const inFlightTranslations = new Map<string, string>();

function autoTranslateMessage(message: Message) {
if ((message as any).vencordEmbeddedBy) return;

const me = UserStore.getCurrentUser();
if (me && message.author?.id === me.id) return;

if (message.channel_id !== SelectedChannelStore.getChannelId()) return;
const content = getMessageContent(message);
if (!content) return;

if (inFlightTranslations.get(message.id) === content) return;

const cached = translationCache.get(message.id);
if (cached) {
handleTranslate(message.id, cached);
return;
}

inFlightTranslations.set(message.id, content);

translate("received", content)
.then(trans => {
if (inFlightTranslations.get(message.id) !== content) return;

handleTranslate(message.id, trans);
})
.finally(() => {
if (inFlightTranslations.get(message.id) === content)
inFlightTranslations.delete(message.id);
});
}

function queueRecentMessages(channelId: string) {
if (!channelId) return;
if (channelId !== SelectedChannelStore.getChannelId()) return;

const limit = settings.store.autoTranslateReceivedLimit ?? 0;
if (limit <= 0) return;

const messages = MessageStore.getMessages(channelId)?._array as Message[];
for (const message of messages.slice(-limit)) {
autoTranslateMessage(message);
}
}


let tooltipTimeout: any;

export default definePlugin({
name: "Translate",
description: "Translate messages with Google Translate, DeepL or Kagi.",
tags: ["Chat", "Utility"],
authors: [Devs.Ven, Devs.AshtonMemer, Devs.koish1],
authors: [Devs.Ven, Devs.AshtonMemer, Devs.koish1, Devs.RumBugen],
settings,
contextMenus: {
"message": messageCtxPatch
Expand Down Expand Up @@ -99,6 +147,36 @@ export default definePlugin({
}
},

flux: {
MESSAGE_CREATE({ message, optimistic }: { message: Message; optimistic: boolean; }) {
if (optimistic) return;
if (!settings.store.autoTranslateReceived) return;
if (message.channel_id !== SelectedChannelStore.getChannelId()) return;

autoTranslateMessage(message);
},
MESSAGE_UPDATE({ message }: { message: Message; }) {
if (!message?.id) return;
if (message.channel_id !== SelectedChannelStore.getChannelId()) return;
const limit = settings.store.autoTranslateReceivedLimit ?? 0;
if (!settings.store.autoTranslateReceived && limit <= 0) return;

translationCache.delete(message.id);
autoTranslateMessage(message);
},
async CHANNEL_SELECT({ channelId }: { channelId?: string; }) {
if (!channelId) return;
const limit = settings.store.autoTranslateReceivedLimit ?? 0;
if (limit <= 0) return;
if (!MessageStore.isReady(channelId)) {
await new Promise<void>(resolve => MessageStore.whenReady(channelId, resolve));
}
if (channelId !== SelectedChannelStore.getChannelId()) return;

queueRecentMessages(channelId);
}
},

async onBeforeMessageSend(_, message) {
if (!settings.store.autoTranslate) return;
if (!message.content) return;
Expand Down
10 changes: 10 additions & 0 deletions src/plugins/translate/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ export const settings = definePluginSettings({
description: "Automatically translate your messages before sending. You can also Shift+click or right-click the translate button to toggle this",
default: false
},
autoTranslateReceived: {
type: OptionType.BOOLEAN,
description: "Automatically translate new received messages in the currently open channel",
default: false
},
autoTranslateReceivedLimit: {
type: OptionType.NUMBER,
description: "How many recent messages to auto-translate when you open a channel (0 = off)",
default: 0
},
showAutoTranslateTooltip: {
type: OptionType.BOOLEAN,
description: "Show a tooltip on the chat bar button when a message is auto-translated",
Expand Down
4 changes: 4 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "prism",
id: 390884143749136386n,
},
RumBugen: {
name: "RumBugen",
id: 296659878985072642n,
},
} satisfies Record<string, Dev>);

// iife so #__PURE__ works correctly
Expand Down