New Plugin: Translate (#1089)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
This commit is contained in:
parent
195f1a032f
commit
cb385d1b28
@ -62,7 +62,7 @@
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"arrow-parens": ["error", "as-needed"],
|
||||
"eol-last": ["error", "always"],
|
||||
"func-call-spacing": ["error", "never"],
|
||||
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||
"no-multi-spaces": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-whitespace-before-property": "error",
|
||||
|
@ -129,6 +129,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||
} else {
|
||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||
if (setting.hidden) return null;
|
||||
|
||||
function onChange(newValue: any) {
|
||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ function ChatBarIcon() {
|
||||
onMouseLeave={onMouseLeave}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
onClick={() => buildEncModal()}
|
||||
style={{ padding: "0 4px" }}
|
||||
style={{ padding: "0 2px", scale: "0.9" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg
|
||||
|
@ -43,7 +43,7 @@
|
||||
}
|
||||
|
||||
.vc-st-button {
|
||||
padding: 0 8px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.vc-st-button svg {
|
||||
|
@ -72,7 +72,7 @@ function SilentMessageToggle(chatBoxProps: {
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
style={{ padding: "0 8px" }}
|
||||
style={{ padding: "0 6px" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg
|
||||
|
@ -57,7 +57,7 @@ function SilentTypingToggle(chatBoxProps: {
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
style={{ padding: "0 8px" }}
|
||||
style={{ padding: "0 6px" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
||||
|
70
src/plugins/translate/TranslateIcon.tsx
Normal file
70
src/plugins/translate/TranslateIcon.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 { classes } from "@utils/misc";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { TranslateModal } from "./TranslateModal";
|
||||
import { cl } from "./utils";
|
||||
|
||||
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 function TranslateChatBarIcon() {
|
||||
const { autoTranslate } = settings.use(["autoTranslate"]);
|
||||
|
||||
return (
|
||||
<Tooltip text="Open Translate Modal">
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div style={{ display: "flex" }}>
|
||||
<Button
|
||||
aria-haspopup="dialog"
|
||||
aria-label=""
|
||||
size=""
|
||||
look={ButtonLooks.BLANK}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
innerClassName={ButtonWrapperClasses.button}
|
||||
onClick={() =>
|
||||
openModal(props => (
|
||||
<TranslateModal rootProps={props} />
|
||||
))
|
||||
}
|
||||
style={{ padding: "0 4px" }}
|
||||
>
|
||||
<div className={ButtonWrapperClasses.buttonWrapper}>
|
||||
<TranslateIcon className={cl({ "auto-translate": autoTranslate })} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
101
src/plugins/translate/TranslateModal.tsx
Normal file
101
src/plugins/translate/TranslateModal.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 "./languages";
|
||||
import { settings } from "./settings";
|
||||
import { cl } from "./utils";
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
62
src/plugins/translate/TranslationAccessory.tsx
Normal file
62
src/plugins/translate/TranslationAccessory.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 "./languages";
|
||||
import { TranslateIcon } from "./TranslateIcon";
|
||||
import { cl, TranslationValue } from "./utils";
|
||||
|
||||
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(() => {
|
||||
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>
|
||||
);
|
||||
}
|
86
src/plugins/translate/index.tsx
Normal file
86
src/plugins/translate/index.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 "./styles.css";
|
||||
|
||||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { TranslateChatBarIcon, TranslateIcon } from "./TranslateIcon";
|
||||
import { handleTranslate, TranslationAccessory } from "./TranslationAccessory";
|
||||
import { translate } from "./utils";
|
||||
|
||||
export default definePlugin({
|
||||
name: "Translate",
|
||||
description: "Translate messages with Google Translate",
|
||||
authors: [Devs.Ven],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessagePopoverAPI", "MessageEventsAPI"],
|
||||
settings,
|
||||
// not used, just here in case some other plugin wants it or w/e
|
||||
translate,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".activeCommandOption",
|
||||
replacement: {
|
||||
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&;try{$2||$1.push($self.chatBarIcon())}catch{}",
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
start() {
|
||||
addAccessory("vc-translation", props => <TranslationAccessory message={props.message} />);
|
||||
|
||||
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;
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
removePreSendListener(this.preSend);
|
||||
removeButton("vc-translate");
|
||||
removeAccessory("vc-translation");
|
||||
},
|
||||
|
||||
chatBarIcon: ErrorBoundary.wrap(TranslateChatBarIcon, { noop: true }),
|
||||
});
|
172
src/plugins/translate/languages.ts
Normal file
172
src/plugins/translate/languages.ts
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
To generate:
|
||||
- Visit https://translate.google.com/?sl=auto&tl=en&op=translate
|
||||
- Open Language dropdown
|
||||
- Open Devtools and use the element picker to pick the root of the language picker
|
||||
- Right click on the element in devtools and click "Store as global variable"
|
||||
|
||||
copy(Object.fromEntries(
|
||||
Array.from(
|
||||
temp1.querySelectorAll("[data-language-code]"),
|
||||
e => [e.dataset.languageCode, e.children[1].textContent]
|
||||
).sort((a, b) => a[1] === "Detect language" ? -1 : b[1] === "Detect language" ? 1 : a[1].localeCompare(b[1]))
|
||||
))
|
||||
*/
|
||||
|
||||
export type Language = keyof typeof Languages;
|
||||
|
||||
export const Languages = {
|
||||
"auto": "Detect language",
|
||||
"af": "Afrikaans",
|
||||
"sq": "Albanian",
|
||||
"am": "Amharic",
|
||||
"ar": "Arabic",
|
||||
"hy": "Armenian",
|
||||
"as": "Assamese",
|
||||
"ay": "Aymara",
|
||||
"az": "Azerbaijani",
|
||||
"bm": "Bambara",
|
||||
"eu": "Basque",
|
||||
"be": "Belarusian",
|
||||
"bn": "Bengali",
|
||||
"bho": "Bhojpuri",
|
||||
"bs": "Bosnian",
|
||||
"bg": "Bulgarian",
|
||||
"ca": "Catalan",
|
||||
"ceb": "Cebuano",
|
||||
"ny": "Chichewa",
|
||||
"zh-CN": "Chinese (Simplified)",
|
||||
"zh-TW": "Chinese (Traditional)",
|
||||
"co": "Corsican",
|
||||
"hr": "Croatian",
|
||||
"cs": "Czech",
|
||||
"da": "Danish",
|
||||
"dv": "Dhivehi",
|
||||
"doi": "Dogri",
|
||||
"nl": "Dutch",
|
||||
"en": "English",
|
||||
"eo": "Esperanto",
|
||||
"et": "Estonian",
|
||||
"ee": "Ewe",
|
||||
"tl": "Filipino",
|
||||
"fi": "Finnish",
|
||||
"fr": "French",
|
||||
"fy": "Frisian",
|
||||
"gl": "Galician",
|
||||
"ka": "Georgian",
|
||||
"de": "German",
|
||||
"el": "Greek",
|
||||
"gn": "Guarani",
|
||||
"gu": "Gujarati",
|
||||
"ht": "Haitian Creole",
|
||||
"ha": "Hausa",
|
||||
"haw": "Hawaiian",
|
||||
"iw": "Hebrew",
|
||||
"hi": "Hindi",
|
||||
"hmn": "Hmong",
|
||||
"hu": "Hungarian",
|
||||
"is": "Icelandic",
|
||||
"ig": "Igbo",
|
||||
"ilo": "Ilocano",
|
||||
"id": "Indonesian",
|
||||
"ga": "Irish",
|
||||
"it": "Italian",
|
||||
"ja": "Japanese",
|
||||
"jw": "Javanese",
|
||||
"kn": "Kannada",
|
||||
"kk": "Kazakh",
|
||||
"km": "Khmer",
|
||||
"rw": "Kinyarwanda",
|
||||
"gom": "Konkani",
|
||||
"ko": "Korean",
|
||||
"kri": "Krio",
|
||||
"ku": "Kurdish (Kurmanji)",
|
||||
"ckb": "Kurdish (Sorani)",
|
||||
"ky": "Kyrgyz",
|
||||
"lo": "Lao",
|
||||
"la": "Latin",
|
||||
"lv": "Latvian",
|
||||
"ln": "Lingala",
|
||||
"lt": "Lithuanian",
|
||||
"lg": "Luganda",
|
||||
"lb": "Luxembourgish",
|
||||
"mk": "Macedonian",
|
||||
"mai": "Maithili",
|
||||
"mg": "Malagasy",
|
||||
"ms": "Malay",
|
||||
"ml": "Malayalam",
|
||||
"mt": "Maltese",
|
||||
"mi": "Maori",
|
||||
"mr": "Marathi",
|
||||
"mni-Mtei": "Meiteilon (Manipuri)",
|
||||
"lus": "Mizo",
|
||||
"mn": "Mongolian",
|
||||
"my": "Myanmar (Burmese)",
|
||||
"ne": "Nepali",
|
||||
"no": "Norwegian",
|
||||
"or": "Odia (Oriya)",
|
||||
"om": "Oromo",
|
||||
"ps": "Pashto",
|
||||
"fa": "Persian",
|
||||
"pl": "Polish",
|
||||
"pt": "Portuguese",
|
||||
"pa": "Punjabi",
|
||||
"qu": "Quechua",
|
||||
"ro": "Romanian",
|
||||
"ru": "Russian",
|
||||
"sm": "Samoan",
|
||||
"sa": "Sanskrit",
|
||||
"gd": "Scots Gaelic",
|
||||
"nso": "Sepedi",
|
||||
"sr": "Serbian",
|
||||
"st": "Sesotho",
|
||||
"sn": "Shona",
|
||||
"sd": "Sindhi",
|
||||
"si": "Sinhala",
|
||||
"sk": "Slovak",
|
||||
"sl": "Slovenian",
|
||||
"so": "Somali",
|
||||
"es": "Spanish",
|
||||
"su": "Sundanese",
|
||||
"sw": "Swahili",
|
||||
"sv": "Swedish",
|
||||
"tg": "Tajik",
|
||||
"ta": "Tamil",
|
||||
"tt": "Tatar",
|
||||
"te": "Telugu",
|
||||
"th": "Thai",
|
||||
"ti": "Tigrinya",
|
||||
"ts": "Tsonga",
|
||||
"tr": "Turkish",
|
||||
"tk": "Turkmen",
|
||||
"ak": "Twi",
|
||||
"uk": "Ukrainian",
|
||||
"ur": "Urdu",
|
||||
"ug": "Uyghur",
|
||||
"uz": "Uzbek",
|
||||
"vi": "Vietnamese",
|
||||
"cy": "Welsh",
|
||||
"xh": "Xhosa",
|
||||
"yi": "Yiddish",
|
||||
"yo": "Yoruba",
|
||||
"zu": "Zulu"
|
||||
} as const;
|
52
src/plugins/translate/settings.ts
Normal file
52
src/plugins/translate/settings.ts
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 { definePluginSettings } from "@api/Settings";
|
||||
import { OptionType } from "@utils/types";
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
receivedInput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Input language for received messages",
|
||||
default: "auto",
|
||||
hidden: true
|
||||
},
|
||||
receivedOutput: {
|
||||
type: OptionType.STRING,
|
||||
description: "Output language for received messages",
|
||||
default: "en",
|
||||
hidden: 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: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Automatically translate your messages before sending",
|
||||
default: false
|
||||
}
|
||||
});
|
37
src/plugins/translate/styles.css
Normal file
37
src/plugins/translate/styles.css
Normal file
@ -0,0 +1,37 @@
|
||||
.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 {
|
||||
color: var(--text-muted);
|
||||
margin-top: 0.5em;
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.vc-trans-accessory svg {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.vc-trans-dismiss {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
color: var(--text-link);
|
||||
}
|
||||
|
||||
.vc-trans-dismiss:is(:hover, :focus) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.vc-trans-auto-translate {
|
||||
color: var(--green-360);
|
||||
}
|
75
src/plugins/translate/utils.ts
Normal file
75
src/plugins/translate/utils.ts
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
export async function translate(kind: "received" | "sent", text: string): Promise<TranslationValue> {
|
||||
const sourceLang = settings.store[kind + "Input"];
|
||||
const targetLang = settings.store[kind + "Output"];
|
||||
|
||||
const url = "https://translate.googleapis.com/translate_a/single?" + new URLSearchParams({
|
||||
// see https://stackoverflow.com/a/29537590 for more params
|
||||
// holy shidd nvidia
|
||||
client: "gtx",
|
||||
// source language
|
||||
sl: sourceLang,
|
||||
// target language
|
||||
tl: targetLang,
|
||||
// what to return, t = translation probably
|
||||
dt: "t",
|
||||
// Send json object response instead of weird array
|
||||
dj: "1",
|
||||
source: "input",
|
||||
// query, duh
|
||||
q: text
|
||||
});
|
||||
|
||||
const res = await fetch(url);
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
`Failed to translate "${text}" (${sourceLang} -> ${targetLang}`
|
||||
+ `\n${res.status} ${res.statusText}`
|
||||
);
|
||||
|
||||
const { src, sentences }: TranslationData = await res.json();
|
||||
|
||||
return {
|
||||
src,
|
||||
text: sentences.
|
||||
map(s => s?.trans).
|
||||
filter(Boolean).
|
||||
join("")
|
||||
};
|
||||
}
|
@ -147,8 +147,15 @@ export interface PluginSettingCommon {
|
||||
description: string;
|
||||
placeholder?: string;
|
||||
onChange?(newValue: any): void;
|
||||
/**
|
||||
* Whether changing this setting requires a restart
|
||||
*/
|
||||
restartNeeded?: boolean;
|
||||
componentProps?: Record<string, any>;
|
||||
/**
|
||||
* Hide this setting from the settings UI
|
||||
*/
|
||||
hidden?: boolean;
|
||||
/**
|
||||
* Set this if the setting only works on Browser or Desktop, not both
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user