From 80a93071394468117018cc0abd31c3245c420f4a Mon Sep 17 00:00:00 2001 From: Prince527 Date: Thu, 18 Jul 2024 19:26:01 -0300 Subject: [PATCH] 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 --- src/equicordplugins/translatePlus/index.tsx | 63 +++----- .../translatePlus/misc/languages.ts | 5 +- .../translatePlus/misc/types.ts | 19 +++ src/equicordplugins/translatePlus/settings.ts | 76 +++------- src/equicordplugins/translatePlus/style.css | 31 +--- .../translatePlus/utils/TranslateIcon.tsx | 87 ----------- .../translatePlus/utils/TranslateModal.tsx | 101 ------------- .../utils/TranslationAccessory.tsx | 65 --------- .../translatePlus/utils/accessory.tsx | 42 ++++++ .../translatePlus/utils/icon.tsx | 17 +++ .../translatePlus/utils/misc.ts | 86 ----------- .../translatePlus/utils/translator.ts | 135 ++++++++++++++++++ 12 files changed, 260 insertions(+), 467 deletions(-) create mode 100644 src/equicordplugins/translatePlus/misc/types.ts delete mode 100644 src/equicordplugins/translatePlus/utils/TranslateIcon.tsx delete mode 100644 src/equicordplugins/translatePlus/utils/TranslateModal.tsx delete mode 100644 src/equicordplugins/translatePlus/utils/TranslationAccessory.tsx create mode 100644 src/equicordplugins/translatePlus/utils/accessory.tsx create mode 100644 src/equicordplugins/translatePlus/utils/icon.tsx delete mode 100644 src/equicordplugins/translatePlus/utils/misc.ts create mode 100644 src/equicordplugins/translatePlus/utils/translator.ts diff --git a/src/equicordplugins/translatePlus/index.tsx b/src/equicordplugins/translatePlus/index.tsx index c7a73165..3eb882d5 100644 --- a/src/equicordplugins/translatePlus/index.tsx +++ b/src/equicordplugins/translatePlus/index.tsx @@ -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, ( { - 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 => ); + addAccessory("ec-translation", props => ); - 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"); + } }); diff --git a/src/equicordplugins/translatePlus/misc/languages.ts b/src/equicordplugins/translatePlus/misc/languages.ts index 75454f59..20b0448d 100644 --- a/src/equicordplugins/translatePlus/misc/languages.ts +++ b/src/equicordplugins/translatePlus/misc/languages.ts @@ -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", diff --git a/src/equicordplugins/translatePlus/misc/types.ts b/src/equicordplugins/translatePlus/misc/types.ts new file mode 100644 index 00000000..dde38179 --- /dev/null +++ b/src/equicordplugins/translatePlus/misc/types.ts @@ -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; +}; diff --git a/src/equicordplugins/translatePlus/settings.ts b/src/equicordplugins/translatePlus/settings.ts index 17e386f3..3fd2f030 100644 --- a/src/equicordplugins/translatePlus/settings.ts +++ b/src/equicordplugins/translatePlus/settings.ts @@ -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 . -*/ + * 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; -}>(); +}); diff --git a/src/equicordplugins/translatePlus/style.css b/src/equicordplugins/translatePlus/style.css index e9085b4e..e81dad64 100644 --- a/src/equicordplugins/translatePlus/style.css +++ b/src/equicordplugins/translatePlus/style.css @@ -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; -} +} \ No newline at end of file diff --git a/src/equicordplugins/translatePlus/utils/TranslateIcon.tsx b/src/equicordplugins/translatePlus/utils/TranslateIcon.tsx deleted file mode 100644 index e8171ccb..00000000 --- a/src/equicordplugins/translatePlus/utils/TranslateIcon.tsx +++ /dev/null @@ -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 . -*/ - -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 ( - - - - ); -} - -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: <> - - You just enabled auto translate (by right clicking the Translate icon). Any message you send will automatically be translated before being sent. - - - If this was an accident, disable it again, or it will change your message content before sending. - - , - cancelText: "Disable Auto-Translate", - confirmText: "Got it", - secondaryConfirmText: "Don't show again", - onConfirmSecondary: () => settings.store.showAutoTranslateAlert = false, - onCancel: () => settings.store.autoTranslate = false - }); - }; - - return ( - { - if (e.shiftKey) return toggle(); - - openModal(props => ( - - )); - }} - onContextMenu={() => toggle()} - buttonProps={{ - "aria-haspopup": "dialog" - }} - > - - - ); -}; diff --git a/src/equicordplugins/translatePlus/utils/TranslateModal.tsx b/src/equicordplugins/translatePlus/utils/TranslateModal.tsx deleted file mode 100644 index 04cd1d7b..00000000 --- a/src/equicordplugins/translatePlus/utils/TranslateModal.tsx +++ /dev/null @@ -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 . -*/ - -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 ( -
- - {settings.def[settingsKey].description} - - - o.value === currentValue)} - placeholder={"Select a language"} - maxVisibleItems={5} - closeOnSelect={true} - onChange={v => settings.store[settingsKey] = v} - /> -
- ); -} - -function AutoTranslateToggle() { - const value = settings.use(["autoTranslate"]).autoTranslate; - - return ( - settings.store.autoTranslate = v} - note={settings.def.autoTranslate.description} - hideBorder - > - Auto Translate - - ); -} - - -export function TranslateModal({ rootProps }: { rootProps: ModalProps; }) { - return ( - - - - Translate - - - - - - {LanguageSettingKeys.map(s => ( - - ))} - - - - - - - ); -} diff --git a/src/equicordplugins/translatePlus/utils/TranslationAccessory.tsx b/src/equicordplugins/translatePlus/utils/TranslationAccessory.tsx deleted file mode 100644 index 94778bc7..00000000 --- a/src/equicordplugins/translatePlus/utils/TranslationAccessory.tsx +++ /dev/null @@ -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 . -*/ - -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 void>(); - -export function handleTranslate(messageId: string, data: TranslationValue) { - TranslationSetters.get(messageId)!(data); -} - -function Dismiss({ onDismiss }: { onDismiss: () => void; }) { - return ( - - ); -} - -export function TranslationAccessory({ message }: { message: Message; }) { - const [translation, setTranslation] = useState(); - - 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 ( - - - {Parser.parse(translation.text)} - {" "} - (translated from {Languages[translation.src] ?? translation.src} - setTranslation(undefined)} />) - - ); -} diff --git a/src/equicordplugins/translatePlus/utils/accessory.tsx b/src/equicordplugins/translatePlus/utils/accessory.tsx new file mode 100644 index 00000000..7d473b19 --- /dev/null +++ b/src/equicordplugins/translatePlus/utils/accessory.tsx @@ -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(undefined); + + useEffect(() => { + if ((message as any).vencordEmbeddedBy) return; + + setters.set(message.id, setTranslation); + + return () => void setters.delete(message.id); + }, []); + + if (!translation) return null; + + return ( + + + {Parser.parse(translation.text)} + {" "} + (translated from {languages[translation.src] ?? translation.src} - ) + + ); +} + +export async function handleTranslate(message: Message) { + setters.get(message.id)!(await translate(message.content)); +} diff --git a/src/equicordplugins/translatePlus/utils/icon.tsx b/src/equicordplugins/translatePlus/utils/icon.tsx new file mode 100644 index 00000000..6c644e0b --- /dev/null +++ b/src/equicordplugins/translatePlus/utils/icon.tsx @@ -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 ( + + + + + + ); +} diff --git a/src/equicordplugins/translatePlus/utils/misc.ts b/src/equicordplugins/translatePlus/utils/misc.ts deleted file mode 100644 index bdb984b6..00000000 --- a/src/equicordplugins/translatePlus/utils/misc.ts +++ /dev/null @@ -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 . -*/ - -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 { - 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; -} diff --git a/src/equicordplugins/translatePlus/utils/translator.ts b/src/equicordplugins/translatePlus/utils/translator.ts new file mode 100644 index 00000000..4598909e --- /dev/null +++ b/src/equicordplugins/translatePlus/utils/translator.ts @@ -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 { + 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; +}