Equicord/src/equicordplugins/ghosted/index.tsx
M acf7abe77a
feat(i18n): Add translation keys and implement localization for 80+ p… (#772)
* feat(i18n): Add translation keys and implement localization for 80+ plugins

- Add comprehensive translation keys to i18n/en-US/vencord.json
- Update all plugins to use t() function from @utils/translation
- Replace hardcoded strings with translation function calls
- Support plural forms with _one/_other suffixes
- Use Translate component for strings with embedded JSX

Plugins translated include:
- Core: reviewDB, translate, voiceMessages, messageLogger
- UI: showHiddenChannels, customRPC, clientTheme, permissionsViewer
- Utility: betterSessions, betterSettings, memberCount, betterFolders
- Social: relationshipNotifier, messageLatency, whoReacted
- Media: imageZoom, viewIcons, viewRaw, decor, fakeNitro
- And 60+ more plugins

All changes pass TypeScript compilation.

* Update i18n/en-US/vencord.json

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update i18n/en-US/vencord.json

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update i18n/en-US/vencord.json

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update i18n/en-US/vencord.json

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update i18n/en-US/vencord.json

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update i18n/en-US/vencord.json

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* feat(i18n): Add equicord.json for equicordplugins localization

- Create i18n/en-US/equicord.json with messageLoggerEnhanced translation keys
- Create empty equicord.json files for all other locales (Crowdin will handle translations)
- Build system automatically picks up all JSON files in i18n/{locale}/

* Update i18n/en-US/vencord.json

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* feat(i18n): Add translations to 200 equicordplugins

- Translate all user-facing strings in equicordplugins to use t() function
- Add translation keys to i18n/en-US/equicord.json
- Plugin names remain static strings for build system compatibility
- Settings descriptions, tooltips, context menus, and UI strings now translatable

* FileUpload: add sharex file support (#771)

* FileUpload: add sharex file support
Quick note, the settings seem to split pacal case with spaces for some reason,
so `ShareX` renders as `Share X`. This is stupid and im upset and I dont think
theres anything I can do about it

* FileUpload: use Headers object to construct HTTP headers

* Update vencord.json

---------

Co-authored-by: Mishal <91066601+mishalhossin@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Key <keircn@proton.me>
Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
2026-02-15 09:39:58 -05:00

186 lines
5.7 KiB
TypeScript

/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import { findGroupChildrenByChildId } from "@api/ContextMenu";
import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { closeModal, openModal } from "@utils/modal";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Channel } from "@vencord/discord-types";
import { Menu, Tooltip, useEffect, useState } from "@webpack/common";
import { Boo, clearChannelFromGhost, getBooCount, getGhostedChannels, onBooCountChange } from "./Boo";
import { getChannelDisplayName, GhostedUsersModal } from "./GhostedUsersModal";
import { IconGhost } from "./IconGhost";
export const cl = classNameFactory("vc-boo-");
export const settings = definePluginSettings({
showIndicator: {
type: OptionType.BOOLEAN,
description: t("ghosted.settings.showIndicator"),
default: true,
restartNeeded: false
},
showDmIcons: {
type: OptionType.BOOLEAN,
description: t("ghosted.settings.showDmIcons"),
default: true,
restartNeeded: false
},
ignoreGroupDms: {
type: OptionType.BOOLEAN,
description: t("ghosted.settings.ignoreGroupDms"),
default: false
},
exemptedChannels: {
type: OptionType.STRING,
description: t("ghosted.settings.exemptedChannels"),
default: "",
restartNeeded: false
},
ignoreBots: {
type: OptionType.BOOLEAN,
description: t("ghosted.settings.ignoreBots"),
default: true,
restartNeeded: false
}
});
function BooIndicator() {
const [count, setCount] = useState(getBooCount());
const [showJumpscare, setShowJumpscare] = useState(false);
useEffect(() => {
const unsubscribe = onBooCountChange(newCount => {
setCount(newCount);
});
return () => {
unsubscribe();
};
}, []);
if (!settings.store.showIndicator && !showJumpscare) return null;
const handleClick = () => {
const ghostedChannels = getGhostedChannels();
const modalKey = openModal(modalProps => (
<ErrorBoundary>
<GhostedUsersModal
modalProps={modalProps}
ghostedChannels={ghostedChannels}
onClose={() => closeModal(modalKey)}
onClearGhost={clearChannelFromGhost}
/>
</ErrorBoundary>
));
};
const getTooltipText = () => {
const ghostedChannels = getGhostedChannels();
if (ghostedChannels.length === 0) {
return t("ghosted.ui.noGhostedUsers");
}
if (ghostedChannels.length <= 5) {
return ghostedChannels
.map(id => getChannelDisplayName(id))
.join(", ");
}
return t("ghosted.ui.ghostedUsers", { count: ghostedChannels.length });
};
return (
<>
{settings.store.showIndicator && getGhostedChannels().length && (
<div id={cl("container")}>
<Tooltip text={getTooltipText()} position="right">
{({ onMouseEnter, onMouseLeave }) => (
<div
id={cl("container")}
className={cl("clickable")}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={handleClick}
>
{count} <IconGhost fill="currentColor" />
</div>
)}
</Tooltip>
</div>
)}
</>
);
}
function makeContextItem(props) {
return <Menu.MenuItem
id="ec-ghosted-clear"
key="ec-ghosted-clear"
label={t("ghosted.ui.unghost")}
action={() => {
clearChannelFromGhost(props.channel.id);
}}
/>;
}
export default definePlugin({
name: "Ghosted",
description: t("ghosted.description"),
authors: [EquicordDevs.vei, Devs.sadan, EquicordDevs.justjxke, EquicordDevs.iamme],
settings,
dependencies: ["AudioPlayerAPI", "ServerListAPI"],
contextMenus: {
"gdm-context": (menuItems, props) => {
const group = findGroupChildrenByChildId("leave", menuItems, true);
group?.unshift(makeContextItem(props));
},
"user-context": (menuItems, props) => {
const group = findGroupChildrenByChildId("close-dm", menuItems);
group?.push(makeContextItem(props));
}
},
patches: [
{
find: "PrivateChannel.renderAvatar",
replacement: {
match: /\]:\i\|\|\i.{0,50}children:\[/,
replace: "$&$self.renderBoo(arguments[0]),"
}
},
],
renderBoo(props: { channel: Channel; }) {
return (
<ErrorBoundary noop>
<Boo {...props} />
</ErrorBoundary>
);
},
renderIndicator() {
return (
<ErrorBoundary noop>
<BooIndicator />
</ErrorBoundary>
);
},
start() {
addServerListElement(ServerListRenderPosition.Above, this.renderIndicator);
},
stop() {
removeServerListElement(ServerListRenderPosition.Above, this.renderIndicator);
},
});