mirror of
https://github.com/Equicord/Equicord.git
synced 2025-01-31 03:33:36 -05:00
Update Translate+ (#13)
* Updated Translate+ I came up with a way to keep Shavian * Updated Translate+ Removed a setting because its not needed, I made my API public :) * Update languages.ts * Update index.tsx * Translator+ v2 * Added types * Update languages.ts
This commit is contained in:
parent
839af9d486
commit
80a9307139
12 changed files with 260 additions and 467 deletions
|
@ -18,19 +18,16 @@
|
|||
|
||||
import "./style.css";
|
||||
|
||||
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, Menu } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { translate } from "./utils/misc";
|
||||
import { TranslateChatBarIcon, TranslateIcon } from "./utils/TranslateIcon";
|
||||
import { handleTranslate, TranslationAccessory } from "./utils/TranslationAccessory";
|
||||
import { Accessory, handleTranslate } from "./utils/accessory";
|
||||
import { Icon } from "./utils/icon";
|
||||
|
||||
const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) => {
|
||||
if (!message.content) return;
|
||||
|
@ -40,61 +37,37 @@ const messageCtxPatch: NavContextMenuPatchCallback = (children, { message }) =>
|
|||
|
||||
group.splice(group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, (
|
||||
<Menu.MenuItem
|
||||
id="vc-trans"
|
||||
id="ec-trans"
|
||||
label="Translate"
|
||||
icon={TranslateIcon}
|
||||
action={async () => {
|
||||
const trans = await translate("received", message.content);
|
||||
handleTranslate(message.id, trans);
|
||||
}}
|
||||
icon={Icon}
|
||||
action={() => handleTranslate(message)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "Translate+",
|
||||
description: "Translate messages with Google Translate and Toki Pona AI",
|
||||
description: "Vencord's translate plugin but with support for artistic languages!",
|
||||
dependencies: ["MessageAccessoriesAPI"],
|
||||
authors: [Devs.Ven, EquicordDevs.Prince527],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI", "ChatInputButtonAPI"],
|
||||
settings,
|
||||
contextMenus: {
|
||||
"message": messageCtxPatch
|
||||
},
|
||||
// not used, just here in case some other plugin wants it or w/e
|
||||
translate,
|
||||
|
||||
start() {
|
||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||
addAccessory("ec-translation", props => <Accessory message={props.message} />);
|
||||
|
||||
addChatBarButton("vc-translate", TranslateChatBarIcon);
|
||||
|
||||
addButton("vc-translate", message => {
|
||||
if (!message.content) return null;
|
||||
|
||||
return {
|
||||
label: "Translate",
|
||||
icon: TranslateIcon,
|
||||
message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: async () => {
|
||||
const trans = await translate("received", message.content);
|
||||
handleTranslate(message.id, trans);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
this.preSend = addPreSendListener(async (_, message) => {
|
||||
if (!settings.store.autoTranslate) return;
|
||||
if (!message.content) return;
|
||||
|
||||
message.content = (await translate("sent", message.content)).text;
|
||||
});
|
||||
addButton("ec-translate", message => ({
|
||||
label: "Translate",
|
||||
icon: Icon,
|
||||
message: message,
|
||||
channel: ChannelStore.getChannel(message.channel_id),
|
||||
onClick: () => handleTranslate(message),
|
||||
}));
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeChatBarButton("vc-translate");
|
||||
removeButton("vc-translate");
|
||||
removeAccessory("vc-translation");
|
||||
},
|
||||
removeButton("ec-translate");
|
||||
removeAccessory("ec-translation");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -31,9 +31,9 @@ copy(Object.fromEntries(
|
|||
))
|
||||
*/
|
||||
|
||||
export type Language = keyof typeof Languages;
|
||||
export type languages = keyof typeof languages;
|
||||
|
||||
export const Languages = {
|
||||
export const languages = {
|
||||
"auto": "Detect language",
|
||||
"af": "Afrikaans",
|
||||
"sq": "Albanian",
|
||||
|
@ -68,6 +68,7 @@ export const Languages = {
|
|||
"ee": "Ewe",
|
||||
"tl": "Filipino",
|
||||
"tp": "Toki Pona",
|
||||
"sh": "Shavian",
|
||||
"fi": "Finnish",
|
||||
"fr": "French",
|
||||
"fy": "Frisian",
|
||||
|
|
19
src/equicordplugins/translatePlus/misc/types.ts
Normal file
19
src/equicordplugins/translatePlus/misc/types.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
|
||||
export const cl = classNameFactory("eq-trans-");
|
||||
|
||||
export interface Translation {
|
||||
text: string;
|
||||
src: string;
|
||||
}
|
||||
|
||||
export type IconProps = {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
|
@ -1,69 +1,35 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { OptionType } from "@utils/types";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
receivedInput: {
|
||||
target: {
|
||||
type: OptionType.STRING,
|
||||
description: "Input language for received messages",
|
||||
default: "auto",
|
||||
hidden: true
|
||||
},
|
||||
receivedOutput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Output language for received messages",
|
||||
description: "Target language",
|
||||
default: "en",
|
||||
hidden: true
|
||||
restartNeeded: true
|
||||
},
|
||||
sentInput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Input language for sent messages",
|
||||
default: "auto",
|
||||
hidden: true
|
||||
},
|
||||
sentOutput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Output language for sent messages",
|
||||
default: "en",
|
||||
hidden: true
|
||||
},
|
||||
autoTranslate: {
|
||||
toki: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically translate your messages before sending. You can also shift/right click the translate button to toggle this",
|
||||
default: false
|
||||
description: "Enable Toki Pona",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
showChatBarButton: {
|
||||
sitelen: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show translate button in chat bar",
|
||||
default: true
|
||||
description: "Enable Sitelen Pona",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
tokiPonaAPI: {
|
||||
type: OptionType.STRING,
|
||||
description: "An API to translate messages to Toki Pona",
|
||||
default: "https://aiapi.serversmp.xyz/toki"
|
||||
},
|
||||
tokiPonaAuth: {
|
||||
type: OptionType.STRING,
|
||||
description: "An API key to use with the Toki Pona API",
|
||||
default: ""
|
||||
shavian: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Enable Shavian",
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
}
|
||||
}).withPrivateSettings<{
|
||||
showAutoTranslateAlert: boolean;
|
||||
}>();
|
||||
});
|
||||
|
|
|
@ -1,41 +1,20 @@
|
|||
.vc-trans-modal-content {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.vc-trans-modal-header {
|
||||
justify-content: space-between;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.vc-trans-modal-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vc-trans-accessory {
|
||||
.eq-trans-accessory {
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.5em;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.vc-trans-accessory svg {
|
||||
.eq-trans-accessory svg {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.vc-trans-dismiss {
|
||||
.eq-trans-dismiss {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
color: var(--text-link);
|
||||
}
|
||||
|
||||
.vc-trans-dismiss:is(:hover, :focus) {
|
||||
.eq-trans-dismiss:is(:hover, :focus) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.vc-trans-auto-translate {
|
||||
color: var(--green-360);
|
||||
}
|
||||
|
||||
.vc-trans-chat-button {
|
||||
scale: 1.085;
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ChatBarButton } from "@api/ChatButtons";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { Alerts, Forms } from "@webpack/common";
|
||||
|
||||
import { settings } from "../settings";
|
||||
import { cl } from "./misc";
|
||||
import { TranslateModal } from "./TranslateModal";
|
||||
|
||||
export function TranslateIcon({ height = 24, width = 24, className }: { height?: number; width?: number; className?: string; }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 96 960 960"
|
||||
height={height}
|
||||
width={width}
|
||||
className={classes(cl("icon"), className)}
|
||||
>
|
||||
<path fill="currentColor" d="m475 976 181-480h82l186 480h-87l-41-126H604l-47 126h-82Zm151-196h142l-70-194h-2l-70 194Zm-466 76-55-55 204-204q-38-44-67.5-88.5T190 416h87q17 33 37.5 62.5T361 539q45-47 75-97.5T487 336H40v-80h280v-80h80v80h280v80H567q-22 69-58.5 135.5T419 598l98 99-30 81-127-122-200 200Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export const TranslateChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
||||
const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]);
|
||||
|
||||
if (!isMainChat || !showChatBarButton) return null;
|
||||
|
||||
const toggle = () => {
|
||||
const newState = !autoTranslate;
|
||||
settings.store.autoTranslate = newState;
|
||||
if (newState && settings.store.showAutoTranslateAlert !== false)
|
||||
Alerts.show({
|
||||
title: "Vencord Auto-Translate Enabled",
|
||||
body: <>
|
||||
<Forms.FormText>
|
||||
You just enabled auto translate (by right clicking the Translate icon). Any message you send will automatically be translated before being sent.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText className={Margins.top16}>
|
||||
If this was an accident, disable it again, or it will change your message content before sending.
|
||||
</Forms.FormText>
|
||||
</>,
|
||||
cancelText: "Disable Auto-Translate",
|
||||
confirmText: "Got it",
|
||||
secondaryConfirmText: "Don't show again",
|
||||
onConfirmSecondary: () => settings.store.showAutoTranslateAlert = false,
|
||||
onCancel: () => settings.store.autoTranslate = false
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ChatBarButton
|
||||
tooltip="Open Translate Modal"
|
||||
onClick={e => {
|
||||
if (e.shiftKey) return toggle();
|
||||
|
||||
openModal(props => (
|
||||
<TranslateModal rootProps={props} />
|
||||
));
|
||||
}}
|
||||
onContextMenu={() => toggle()}
|
||||
buttonProps={{
|
||||
"aria-haspopup": "dialog"
|
||||
}}
|
||||
>
|
||||
<TranslateIcon className={cl({ "auto-translate": autoTranslate, "chat-button": true })} />
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Margins } from "@utils/margins";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
||||
import { Forms, SearchableSelect, Switch, useMemo } from "@webpack/common";
|
||||
|
||||
import { Languages } from "../misc/languages";
|
||||
import { settings } from "../settings";
|
||||
import { cl } from "./misc";
|
||||
|
||||
const LanguageSettingKeys = ["receivedInput", "receivedOutput", "sentInput", "sentOutput"] as const;
|
||||
|
||||
function LanguageSelect({ settingsKey, includeAuto }: { settingsKey: typeof LanguageSettingKeys[number]; includeAuto: boolean; }) {
|
||||
const currentValue = settings.use([settingsKey])[settingsKey];
|
||||
|
||||
const options = useMemo(
|
||||
() => {
|
||||
const options = Object.entries(Languages).map(([value, label]) => ({ value, label }));
|
||||
if (!includeAuto)
|
||||
options.shift();
|
||||
|
||||
return options;
|
||||
}, []
|
||||
);
|
||||
|
||||
return (
|
||||
<section className={Margins.bottom16}>
|
||||
<Forms.FormTitle tag="h3">
|
||||
{settings.def[settingsKey].description}
|
||||
</Forms.FormTitle>
|
||||
|
||||
<SearchableSelect
|
||||
options={options}
|
||||
value={options.find(o => o.value === currentValue)}
|
||||
placeholder={"Select a language"}
|
||||
maxVisibleItems={5}
|
||||
closeOnSelect={true}
|
||||
onChange={v => settings.store[settingsKey] = v}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function AutoTranslateToggle() {
|
||||
const value = settings.use(["autoTranslate"]).autoTranslate;
|
||||
|
||||
return (
|
||||
<Switch
|
||||
value={value}
|
||||
onChange={v => settings.store.autoTranslate = v}
|
||||
note={settings.def.autoTranslate.description}
|
||||
hideBorder
|
||||
>
|
||||
Auto Translate
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function TranslateModal({ rootProps }: { rootProps: ModalProps; }) {
|
||||
return (
|
||||
<ModalRoot {...rootProps}>
|
||||
<ModalHeader className={cl("modal-header")}>
|
||||
<Forms.FormTitle tag="h2">
|
||||
Translate
|
||||
</Forms.FormTitle>
|
||||
<ModalCloseButton onClick={rootProps.onClose} />
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent className={cl("modal-content")}>
|
||||
{LanguageSettingKeys.map(s => (
|
||||
<LanguageSelect
|
||||
key={s}
|
||||
settingsKey={s}
|
||||
includeAuto={s.endsWith("Input")}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Forms.FormDivider className={Margins.bottom16} />
|
||||
|
||||
<AutoTranslateToggle />
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Parser, useEffect, useState } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
import { Languages } from "../misc/languages";
|
||||
import { cl, TranslationValue } from "./misc";
|
||||
import { TranslateIcon } from "./TranslateIcon";
|
||||
|
||||
const TranslationSetters = new Map<string, (v: TranslationValue) => void>();
|
||||
|
||||
export function handleTranslate(messageId: string, data: TranslationValue) {
|
||||
TranslationSetters.get(messageId)!(data);
|
||||
}
|
||||
|
||||
function Dismiss({ onDismiss }: { onDismiss: () => void; }) {
|
||||
return (
|
||||
<button
|
||||
onClick={onDismiss}
|
||||
className={cl("dismiss")}
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function TranslationAccessory({ message }: { message: Message; }) {
|
||||
const [translation, setTranslation] = useState<TranslationValue>();
|
||||
|
||||
useEffect(() => {
|
||||
// Ignore MessageLinkEmbeds messages
|
||||
if ((message as any).vencordEmbeddedBy) return;
|
||||
|
||||
TranslationSetters.set(message.id, setTranslation);
|
||||
|
||||
return () => void TranslationSetters.delete(message.id);
|
||||
}, []);
|
||||
|
||||
if (!translation) return null;
|
||||
|
||||
return (
|
||||
<span className={cl("accessory")}>
|
||||
<TranslateIcon width={16} height={16} />
|
||||
{Parser.parse(translation.text)}
|
||||
{" "}
|
||||
(translated from {Languages[translation.src] ?? translation.src} - <Dismiss onDismiss={() => setTranslation(undefined)} />)
|
||||
</span>
|
||||
);
|
||||
}
|
42
src/equicordplugins/translatePlus/utils/accessory.tsx
Normal file
42
src/equicordplugins/translatePlus/utils/accessory.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Parser, useEffect, useState } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
import { languages } from "../misc/languages";
|
||||
import { cl, Translation } from "../misc/types";
|
||||
import { Icon } from "./icon";
|
||||
import { translate } from "./translator";
|
||||
|
||||
const setters = new Map();
|
||||
|
||||
export function Accessory({ message }: { message: Message; }) {
|
||||
const [translation, setTranslation] = useState<Translation | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if ((message as any).vencordEmbeddedBy) return;
|
||||
|
||||
setters.set(message.id, setTranslation);
|
||||
|
||||
return () => void setters.delete(message.id);
|
||||
}, []);
|
||||
|
||||
if (!translation) return null;
|
||||
|
||||
return (
|
||||
<span className={cl("accessory")}>
|
||||
<Icon width={16} height={16} />
|
||||
{Parser.parse(translation.text)}
|
||||
{" "}
|
||||
(translated from {languages[translation.src] ?? translation.src} - <button onClick={() => setTranslation(undefined)} className={cl("dismiss")}>Dismiss</button>)
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export async function handleTranslate(message: Message) {
|
||||
setters.get(message.id)!(await translate(message.content));
|
||||
}
|
17
src/equicordplugins/translatePlus/utils/icon.tsx
Normal file
17
src/equicordplugins/translatePlus/utils/icon.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { cl, IconProps } from "../misc/types";
|
||||
|
||||
export function Icon({ width = 24, height = 24 }: IconProps) {
|
||||
return (
|
||||
<svg viewBox="0 96 960 960" height={width} width={height} className={cl("icon")}>
|
||||
<path fill="currentColor" d="m475 976 181-480h82l186 480h-87l-41-126H604l-47 126h-82Zm151-196h142l-70-194h-2l-70 194Zm-466 76-55-55 204-204q-38-44-67.5-88.5T190 416h87q17 33 37.5 62.5T361 539q45-47 75-97.5T487 336H40v-80h280v-80h80v80h280v80H567q-22 69-58.5 135.5T419 598l98 99-30 81-127-122-200 200Z" />
|
||||
<path fill="currentColor" d="m 830.17456,136.43701 c -11.54729,0 -20.84473,8.71252 -20.84473,19.53369 v 66.21826 h -66.21826 c -10.82122,0 -19.53369,9.29373 -19.53369,20.84107 0,11.54734 8.71247,20.84473 19.53369,20.84473 h 66.21826 v 66.21826 c 0,10.8212 9.29742,19.53369 20.84473,19.53369 11.54731,0 20.84106,-8.71249 20.84106,-19.53369 v -66.21826 h 66.21827 c 10.82124,0 19.53369,-9.29737 19.53369,-20.84473 0,-11.54736 -8.71245,-20.84107 -19.53369,-20.84107 H 851.01562 V 155.9707 c 0,-10.82117 -9.29377,-19.53369 -20.84106,-19.53369 z" />
|
||||
<rect fill="currentColor" width="0.42110577" height="2.1055288" x="848.52814" y="112.42313" ry="0.2105529" />
|
||||
</svg>
|
||||
);
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
|
||||
import { settings } from "../settings";
|
||||
|
||||
export const cl = classNameFactory("vc-trans-");
|
||||
|
||||
interface TranslationData {
|
||||
src: string;
|
||||
sentences: {
|
||||
// 🏳️⚧️
|
||||
trans: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface TranslationValue {
|
||||
src: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const dictonary = /\b(?:leko|weka|pan|lete|linja|lipu|suli|nimi|akesi|misikeke|selo|ike|sijelo|sona|lili|pimeja|ante|jo|loje|telo|walo|kijetesantakalu|kasi|waso|wile|utala|lukin|sina|lape|ma|pilin|jasima|la|olin|pipi|meso|lawa|pi|pakala|oko|tan|ken|jaki|unpa|esun|seme|sitelen|len|kule|soko|open|ala|tenpo|lon|sinpin|pini|kokosila|mama|musi|monsi|mewika|taso|ona|mun|kiwen|tomo|mute|mi|nena|palisa|meli|laso|wawa|ale|kipisi|kulupu|ilo|lupa|nanpa|en|mu|jelo|kili|tonsi|moku|ni|kama|pu|poki|monsuta|sin|lasina|poka|soweli|sewi|elena|epiku|moli|pona|lanpan|alasa|anu|kute|uta|luka|suno|sama|awen|namako|suwi|noka|seli|mije|sike|jan|pali|tawa|inli|nasa|mani|wan|insa|nijon|nasin|kalama|ijo|toki|anpa|kala|kepeken|ko|kon|pana|tu|supa|kin|usawi|yupekosi)\b/gm;
|
||||
|
||||
function isTokiPona(string) {
|
||||
const words = string.split(/\s+/);
|
||||
|
||||
const matches = string.match(dictonary) || [];
|
||||
|
||||
const percentage = (matches.length / words.length) * 100;
|
||||
|
||||
return percentage >= 50;
|
||||
}
|
||||
|
||||
export async function translate(kind: "received" | "sent", text: string): Promise<TranslationValue> {
|
||||
let output;
|
||||
|
||||
if (isTokiPona(text)) {
|
||||
const [api, auth] = [settings.store.tokiPonaAPI, settings.store.tokiPonaAuth];
|
||||
|
||||
const translate = await (await fetch(api, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Basic ${auth}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: text,
|
||||
src: "tl",
|
||||
target: "en"
|
||||
})
|
||||
})).json();
|
||||
|
||||
output = {
|
||||
src: "tp",
|
||||
text: translate.translation[0]
|
||||
};
|
||||
} else {
|
||||
const [sourceLang, targetLang] = [settings.store[kind + "Input"], settings.store[kind + "Output"]];
|
||||
|
||||
const translate = await (await fetch(`https://translate.googleapis.com/translate_a/single?${new URLSearchParams({ client: "gtx", sl: sourceLang, tl: targetLang, dt: "t", dj: "1", source: "input", q: text })}`)).json();
|
||||
|
||||
output = {
|
||||
src: translate.src,
|
||||
text: translate.sentences.map(s => s.trans).filter(Boolean).join("")
|
||||
};
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
135
src/equicordplugins/translatePlus/utils/translator.ts
Normal file
135
src/equicordplugins/translatePlus/utils/translator.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { settings } from "../settings";
|
||||
|
||||
function isTokiPona(text: string) {
|
||||
const dictionary = /\b(?:leko|weka|pan|lete|linja|lipu|suli|nimi|akesi|misikeke|selo|ike|sijelo|sona|lili|pimeja|ante|jo|loje|telo|walo|kijetesantakalu|kasi|waso|wile|utala|lukin|sina|lape|ma|pilin|jasima|la|olin|pipi|meso|lawa|pi|pakala|oko|tan|ken|jaki|unpa|esun|seme|sitelen|len|kule|soko|open|ala|tenpo|lon|sinpin|pini|kokosila|mama|musi|monsi|mewika|taso|ona|mun|kiwen|tomo|mute|mi|nena|palisa|meli|laso|wawa|ale|kipisi|kulupu|ilo|lupa|nanpa|en|mu|jelo|kili|tonsi|moku|ni|kama|pu|poki|monsuta|sin|lasina|poka|soweli|sewi|elena|epiku|moli|pona|lanpan|alasa|anu|kute|uta|luka|suno|sama|awen|namako|suwi|noka|seli|mije|sike|jan|pali|tawa|inli|nasa|mani|wan|insa|nijon|nasin|kalama|ijo|toki|anpa|kala|kepeken|ko|kon|pana|tu|supa|kin|usawi|yupekosi)\b/gm;
|
||||
|
||||
return (text.match(dictionary) || []).length >= text.split(/\s+/).length * 0.5;
|
||||
}
|
||||
|
||||
function isSitelen(text: string) {
|
||||
const dictionary = /(?:||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||)/gm;
|
||||
|
||||
return dictionary.test(text);
|
||||
}
|
||||
|
||||
function isShavian(text: string) {
|
||||
const shavianRegex = /[\u{10450}-\u{1047F}]+/u;
|
||||
|
||||
return shavianRegex.test(text);
|
||||
}
|
||||
|
||||
async function translateShavian(message: string) {
|
||||
const dictionary = await (await fetch("https://forkprince.github.io/TranslatePlus/shavian.json")).json();
|
||||
|
||||
const punctuationMap = {
|
||||
'"': "\"",
|
||||
"«": "\"",
|
||||
"»": "\"",
|
||||
",": ",",
|
||||
"!": "!",
|
||||
"?": "?",
|
||||
".": ".",
|
||||
"(": "(",
|
||||
")": ")",
|
||||
"/": "/",
|
||||
";": ";",
|
||||
":": ":"
|
||||
};
|
||||
|
||||
let translated = "";
|
||||
const words = message.split(/\s+/);
|
||||
|
||||
for (let word of words) {
|
||||
let punctuationBefore = "", punctuationAfter = "";
|
||||
|
||||
if (word[0] in punctuationMap) {
|
||||
punctuationBefore = punctuationMap[word[0]];
|
||||
word = word.slice(1);
|
||||
}
|
||||
|
||||
if (word[word.length - 1] in punctuationMap) {
|
||||
punctuationAfter = punctuationMap[word[word.length - 1]];
|
||||
word = word.slice(0, -1);
|
||||
}
|
||||
|
||||
translated += punctuationBefore;
|
||||
|
||||
if (word in dictionary) translated += dictionary[word];
|
||||
else translated += word;
|
||||
|
||||
translated += punctuationAfter + " ";
|
||||
}
|
||||
|
||||
return translated.trim();
|
||||
}
|
||||
|
||||
async function translateSitelen(message: string) {
|
||||
message = Array.from(message).join(" ");
|
||||
|
||||
const dictionary = await (await fetch("https://forkprince.github.io/TranslatePlus/sitelen-pona.json")).json();
|
||||
|
||||
const sorted = Object.keys(dictionary).sort((a, b) => b.length - a.length);
|
||||
|
||||
const pattern = new RegExp(`(${sorted.join("|")})`, "g");
|
||||
|
||||
const translate = message.replace(pattern, match => dictionary[match]);
|
||||
|
||||
return translate;
|
||||
}
|
||||
|
||||
async function google(target: string, text: string) {
|
||||
const translate = await (await fetch(`https://translate.googleapis.com/translate_a/single?${new URLSearchParams({ client: "gtx", sl: "auto", tl: target, dt: "t", dj: "1", source: "input", q: text })}`)).json();
|
||||
|
||||
return {
|
||||
src: translate.src,
|
||||
text: translate.sentences.map(s => s.trans).filter(Boolean).join("")
|
||||
};
|
||||
}
|
||||
|
||||
export async function translate(text: string): Promise<any> {
|
||||
const { target, toki, sitelen, shavian } = settings.store;
|
||||
|
||||
const output = { src: "", text: "" };
|
||||
|
||||
if ((isTokiPona(text) || isSitelen(text)) && (toki || sitelen)) {
|
||||
if (isSitelen(text) && sitelen) text = await translateSitelen(text);
|
||||
|
||||
console.log(text);
|
||||
|
||||
const translate = await (await fetch("https://aiapi.serversmp.xyz/toki", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: text,
|
||||
src: "tl",
|
||||
target: "en"
|
||||
})
|
||||
})).json();
|
||||
|
||||
console.log(translate);
|
||||
|
||||
output.src = "tp";
|
||||
output.text = target === "en" ? translate.translation[0] : (await google(target, translate.translation[0])).text;
|
||||
} else if (isShavian(text) && shavian) {
|
||||
const translate = await translateShavian(text);
|
||||
|
||||
output.src = "sh";
|
||||
output.text = target === "en" ? translate : (await google(target, translate)).text;
|
||||
} else {
|
||||
const translate = await google(target, text);
|
||||
|
||||
output.src = translate.src;
|
||||
output.text = translate.text;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
Loading…
Reference in a new issue