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>
This commit is contained in:
M 2026-02-15 20:39:58 +06:00 • committed by GitHub
parent 53316d73e5
commit acf7abe77a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
329 changed files with 4639 additions and 1783 deletions

1
i18n/af-ZA/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/ar-SA/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/ca-ES/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/cs-CZ/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/da-DK/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/de-DE/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/el-GR/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1330
i18n/en-US/equicord.json Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1
i18n/es-ES/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/fi-FI/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/fr-FR/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/he-IL/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/hu-HU/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/it-IT/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/ja-JP/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/ko-KR/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/nl-NL/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/no-NO/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/pl-PL/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/pt-PT/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/ro-RO/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/ru-RU/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/sr-SP/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/sv-SE/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/tr-TR/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/uk-UA/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/vi-VN/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

1
i18n/zh-CN/equicord.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -21,8 +21,8 @@ import "./AddonCard.css";
import { Badge } from "@components/Badge";
import { BaseText } from "@components/BaseText";
import { Switch } from "@components/Switch";
import { t } from "@utils/translation";
import { classNameFactory } from "@utils/css";
import { t } from "@utils/translation";
import { Tooltip, useRef } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react";

View file

@ -21,8 +21,8 @@ import "./AddonCard.css";
import { Badge } from "@components/Badge";
import { BaseText } from "@components/BaseText";
import { Switch } from "@components/settings";
import { t } from "@utils/translation";
import { classNameFactory } from "@utils/css";
import { t } from "@utils/translation";
import { useRef } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react";

View file

@ -35,19 +35,20 @@ export function resolveError(isValidResult: boolean | string) {
interface SettingsSectionProps extends PropsWithChildren {
name: string;
description: string;
description: string | (() => string);
error?: string | null;
inlineSetting?: boolean;
tag?: "label" | "div";
}
export function SettingsSection({ tag: Tag = "div", name, description, error, inlineSetting, children }: SettingsSectionProps) {
const resolvedDescription = typeof description === "function" ? description() : description;
return (
<Tag className={cl("section")}>
<div className={classes(cl("content"), inlineSetting && cl("inline"))}>
<div className={cl("label")}>
{name && <BaseText className={cl("title")} size="md" weight="medium">{wordsToTitle(wordsFromCamel(name))}</BaseText>}
{description && <BaseText className={cl("description")} size="sm">{description}</BaseText>}
{resolvedDescription && <BaseText className={cl("description")} size="sm">{resolvedDescription}</BaseText>}
</div>
{children}
</div>

View file

@ -10,6 +10,7 @@ import { HeadingSecondary } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { EquicordDevs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { ApplicationAssetUtils, FluxDispatcher, showToast } from "@webpack/common";
@ -51,15 +52,15 @@ interface MediaData {
const settings = definePluginSettings({
serverUrl: {
description: "AudioBookShelf server URL (e.g., https://abs.example.com)",
description: t("absRPC.settings.serverUrl"),
type: OptionType.STRING,
},
username: {
description: "AudioBookShelf username",
description: t("absRPC.settings.username"),
type: OptionType.STRING,
},
password: {
description: "AudioBookShelf password",
description: t("absRPC.settings.password"),
type: OptionType.STRING,
},
});
@ -85,16 +86,16 @@ function setActivity(activity: Activity | null) {
export default definePlugin({
name: "AudioBookShelfRichPresence",
description: "Rich presence for AudioBookShelf media server",
description: t("absRPC.description"),
authors: [EquicordDevs.vmohammad],
settingsAboutComponent: () => (
<>
<HeadingSecondary>How to connect to AudioBookShelf</HeadingSecondary>
<HeadingSecondary>{t("absRPC.about.title")}</HeadingSecondary>
<Paragraph>
Enter your AudioBookShelf server URL, username, and password to display your currently playing audiobooks as Discord Rich Presence.
{t("absRPC.about.description")}
<br /><br />
The plugin will automatically authenticate and fetch your listening progress.
{t("absRPC.about.automaticAuth")}
</Paragraph>
</>
),
@ -114,7 +115,7 @@ export default definePlugin({
async authenticate(): Promise<boolean> {
if (!settings.store.serverUrl || !settings.store.username || !settings.store.password) {
logger.warn("AudioBookShelf server URL, username, or password is not set in settings.");
showToast("AudioBookShelf RPC is not configured.", "failure", {
showToast(t("absRPC.toasts.notConfigured"), "failure", {
duration: 15000,
});
return false;

View file

@ -5,11 +5,12 @@
*/
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
export default definePlugin({
name: "AltKrispSwitch",
description: "Makes the Noise Suppression Popout switch between None and Krisp instead of Krisp and Standard",
description: t("altKrispSwitch.description"),
authors: [Devs.newwares],
patches: [
{

View file

@ -6,11 +6,12 @@
import { Devs } from "@utils/constants";
import { openUserProfile } from "@utils/discord";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
export default definePlugin({
name: "AlwaysExpandProfiles",
description: "Always expands profile popouts to the full modal",
description: t("alwaysExpandProfiles.description"),
authors: [Devs.thororen],
patches: [
{

View file

@ -6,50 +6,51 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
export const settings = definePluginSettings({
dms: {
type: OptionType.BOOLEAN,
default: true,
description: "Remove shops above DMs list",
description: t("anammox.settings.dms"),
restartNeeded: true,
},
quests: {
type: OptionType.BOOLEAN,
default: false,
description: "Remove quests above DMs list",
description: t("anammox.settings.quests"),
restartNeeded: true,
},
serverBoost: {
type: OptionType.BOOLEAN,
default: true,
description: "Remove server boost info above channel list",
description: t("anammox.settings.serverBoost"),
restartNeeded: true,
},
billing: {
type: OptionType.BOOLEAN,
default: true,
description: "Remove billing settings",
description: t("anammox.settings.billing"),
restartNeeded: true,
},
gift: {
type: OptionType.BOOLEAN,
default: true,
description: "Remove gift button",
description: t("anammox.settings.gift"),
restartNeeded: true,
},
emojiList: {
type: OptionType.BOOLEAN,
default: true,
description: "Remove unavailable categories from the emoji picker",
description: t("anammox.settings.emojiList"),
restartNeeded: true,
},
});
export default definePlugin({
name: "Anammox",
description: "Hides various features related to nitro and the store",
description: t("anammox.description"),
authors: [Devs.Kyuuhachi],
settings,

View file

@ -6,57 +6,58 @@
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { SelectedChannelStore, UserStore } from "@webpack/common";
const settings = definePluginSettings({
volume: {
type: OptionType.SLIDER,
description: "Volume of the animalese sound",
description: t("animalese.settings.volume"),
default: 0.5,
markers: [0, 0.1, 0.25, 0.5, 0.6, 0.75, 1],
},
speed: {
type: OptionType.SLIDER,
description: "Speed of the animalese sound",
description: t("animalese.settings.speed"),
default: 1,
markers: [0.5, 0.75, 1, 1.25, 1.5],
},
pitch: {
type: OptionType.SLIDER,
description: "Pitch multiplier",
description: t("animalese.settings.pitch"),
default: 1,
markers: [0.75, 0.8, 0.85, 1, 1.15, 1.25, 1.35, 1.5],
},
messageLengthLimit: {
type: OptionType.NUMBER,
description: "Maximum length of message to process",
description: t("animalese.settings.messageLengthLimit"),
default: 50,
},
processOwnMessages: {
type: OptionType.BOOLEAN,
description: "Enable to yap your own messages too",
description: t("animalese.settings.processOwnMessages"),
default: true,
},
soundQuality: {
type: OptionType.SELECT,
description: "Quality of sound to use",
description: t("animalese.settings.soundQuality"),
options: [
{
label: "High",
label: t("animalese.quality.high"),
value: "high",
default: true
},
{
label: "Medium",
label: t("animalese.quality.medium"),
value: "med"
},
{
label: "Low",
label: t("animalese.quality.low"),
value: "low"
},
{
label: "Lowest",
label: t("animalese.quality.lowest"),
value: "low"
}
]
@ -192,7 +193,7 @@ async function playSound(buffer: AudioBuffer, volume: number) {
export default definePlugin({
name: "Animalese",
description: "Plays animal crossing animalese for every message sent (they yap a lot)",
description: t("animalese.description"),
authors: [EquicordDevs.ryanamay, EquicordDevs.Mocha],
settings,

View file

@ -6,20 +6,21 @@
import { addMessagePreSendListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { ChannelStore, GuildMemberStore, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
export default definePlugin({
name: "AtSomeone",
authors: [Devs.Joona],
description: "Mention someone randomly",
description: t("atSomeone.description"),
patches: [
{
find: ".LAUNCHABLE_APPLICATIONS;",
replacement: [
{
match: /&(\i)\(\)\((\i),\i\(\)\.test\)&&(\i)\.push\(\i\(\)\)/g,
replace: "$&,$1()($2,/someone/.test)&&$3.push({text:'@someone',description:'Mention someone randomly'})"
replace: "$&,$1()($2,/someone/.test)&&$3.push({text:'@someone',description:$self.getSomeoneDescription()})"
},
],
},
@ -31,6 +32,9 @@ export default definePlugin({
}
}
],
getSomeoneDescription() {
return t("atSomeone.contextMenu.someone");
},
start() {
this.preSend = addMessagePreSendListener((_, msg) => {
msg.content = msg.content.replace(/@someone/g, () => `<@${randomUser()}>`);

View file

@ -7,6 +7,7 @@
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { ChannelStore, DraftType, SelectedChannelStore, UploadHandler } from "@webpack/common";
import { zipSync } from "fflate";
@ -16,7 +17,7 @@ const logger = new Logger("AutoZipper");
const settings = definePluginSettings({
extensions: {
type: OptionType.STRING,
description: "Comma-separated list of file extensions to auto-zip (e.g., .psd,.blend,.exe,.dmg)",
description: t("autoZipper.settings.extensions"),
default: ".psd,.blend,.exe,.dmg,.app,.apk,.iso",
onChange: () => {
extensionsToZip.clear();
@ -208,7 +209,7 @@ function handlePaste(event: ClipboardEvent) {
export default definePlugin({
name: "AutoZipper",
description: "Automatically zips specified file types and folders before uploading to Discord",
description: t("autoZipper.description"),
authors: [EquicordDevs.SSnowly],
settings,

View file

@ -10,6 +10,7 @@ import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import usrbg from "@plugins/usrbg";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { UserProfileStore } from "@webpack/common";
@ -28,12 +29,12 @@ interface Nameplate {
const settings = definePluginSettings({
animate: {
description: "Animate banners",
description: t("bannersEverywhere.settings.animate"),
type: OptionType.BOOLEAN,
default: false
},
preferNameplate: {
description: "prefer nameplate over banner",
description: t("bannersEverywhere.settings.preferNameplate"),
type: OptionType.BOOLEAN,
default: false
},
@ -43,7 +44,7 @@ const DATASTORE_KEY = "bannersEverywhere";
export default definePlugin({
name: "BannersEverywhere",
description: "Displays banners in the member list ",
description: t("bannersEverywhere.description"),
authors: [Devs.ImLvna, Devs.AutumnVN],
settings,
patches: [

View file

@ -25,6 +25,7 @@ import { Heading } from "@components/Heading";
import { EquicordDevs } from "@utils/constants";
import { copyWithToast } from "@utils/discord";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Button, ChannelStore } from "@webpack/common";
@ -68,12 +69,12 @@ function openDecodedBase64Modal(decodedContent) {
<ErrorBoundary>
<ModalRoot {...props} size={ModalSize.LARGE}>
<ModalHeader>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>Decoded Base64 Content</BaseText>
<BaseText size="lg" weight="semibold" style={{ flexGrow: 1 }}>{t("baseDecoder.ui.modalTitle")}</BaseText>
<ModalCloseButton onClick={() => closeModal(key)} />
</ModalHeader>
<ModalContent>
<div style={{ padding: "16px 0" }}>
<Heading>Decoded Content</Heading>
<Heading>{t("baseDecoder.ui.decodedContent")}</Heading>
{decodedContent.map((content, index) => (
<CodeBlock key={index} content={content} lang="" />
))}
@ -82,8 +83,8 @@ function openDecodedBase64Modal(decodedContent) {
<ModalFooter>
<Flex gap={10}>
{decodedContent.map((content, index) => (
<Button key={index} onClick={() => copyWithToast(content, "Decoded content copied to clipboard!")}>
Copy Decoded Content {index + 1}
<Button key={index} onClick={() => copyWithToast(content, t("baseDecoder.ui.copiedToast"))}>
{t("baseDecoder.ui.copyDecoded")} {index + 1}
</Button>
))}
</Flex>
@ -95,18 +96,18 @@ function openDecodedBase64Modal(decodedContent) {
const settings = definePluginSettings({
clickMethod: {
description: "Change the button to decode base64 content of any message.",
description: t("baseDecoder.settings.clickMethod"),
type: OptionType.SELECT,
options: [
{ label: "Left Click to decode the base64 content.", value: "Left", default: true },
{ label: "Right click to decode the base64 content.", value: "Right" }
{ label: t("baseDecoder.settings.clickMethodOptions.left"), value: "Left", default: true },
{ label: t("baseDecoder.settings.clickMethodOptions.right"), value: "Right" }
]
}
});
export default definePlugin({
name: "DecodeBase64",
description: "Decode base64 content of any message and copy the decoded content.",
description: t("baseDecoder.description"),
authors: [EquicordDevs.ThePirateStoner],
settings,
messagePopoverButton: {
@ -137,8 +138,8 @@ export default definePlugin({
};
const label = settings.store.clickMethod === "Right"
? "Copy Decoded (Left Click) / Decode Base64 (Right Click)"
: "Decode Base64 (Left Click) / Copy Decoded (Right Click)";
? t("baseDecoder.ui.tooltipLeft")
: t("baseDecoder.ui.tooltipRight");
return {
label,

View file

@ -8,6 +8,7 @@ import "./styles.css";
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { patchActivityList } from "./patch-helpers/activityList";
@ -18,7 +19,7 @@ migratePluginSettings("BetterActivities", "MemberListActivities");
export default definePlugin({
name: "BetterActivities",
description: "Shows activity icons in the member list and allows showing all activities",
description: t("betterActivities.description"),
authors: [Devs.D3SOX, Devs.Arjix, Devs.AutumnVN, Devs.thororen],
tags: ["activity"],
settings,

View file

@ -5,38 +5,39 @@
*/
import { definePluginSettings } from "@api/Settings";
import { t } from "@utils/translation";
import { OptionType } from "@utils/types";
import { React } from "@webpack/common";
export const settings = definePluginSettings({
memberList: {
type: OptionType.BOOLEAN,
description: "Show activity icons in the member list",
description: t("betterActivities.settings.memberList"),
default: true,
restartNeeded: true,
},
iconSize: {
type: OptionType.SLIDER,
description: "Size of the activity icons",
description: t("betterActivities.settings.iconSize"),
markers: [10, 15, 20],
default: 15,
stickToMarkers: false,
},
specialFirst: {
type: OptionType.BOOLEAN,
description: "Show special activities first (Currently Spotify and Twitch)",
description: t("betterActivities.settings.specialFirst"),
default: true,
restartNeeded: false,
},
renderGifs: {
type: OptionType.BOOLEAN,
description: "Allow rendering GIFs",
description: t("betterActivities.settings.renderGifs"),
default: true,
restartNeeded: false,
},
removeGameActivityStatus: {
type: OptionType.BOOLEAN,
description: "Remove game activity icon and status",
description: t("betterActivities.settings.removeGameActivityStatus"),
default: false,
restartNeeded: true,
},
@ -55,26 +56,26 @@ export const settings = definePluginSettings({
},
userPopout: {
type: OptionType.BOOLEAN,
description: "Show all activities in the profile popout/sidebar",
description: t("betterActivities.settings.userPopout"),
default: true,
restartNeeded: true,
},
hideTooltip: {
type: OptionType.BOOLEAN,
description: "Hides activities in various places",
description: t("betterActivities.settings.hideTooltip"),
default: true,
},
allActivitiesStyle: {
type: OptionType.SELECT,
description: "Style for showing all activities",
description: t("betterActivities.settings.allActivitiesStyle"),
options: [
{
default: true,
label: "Carousel",
label: t("betterActivities.options.carousel"),
value: "carousel",
},
{
label: "List",
label: t("betterActivities.options.list"),
value: "list",
},
]

View file

@ -8,6 +8,7 @@ import "./style.css";
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { showToast, Toasts } from "@webpack/common";
@ -245,7 +246,7 @@ function handleColorChange(value, settingKey, defaultValue) {
if (rgb.match(rgbPattern)) {
settings.store[settingKey] = rgb;
} else {
showToast(`Invalid color format for ${settingKey}, make sure it's in the format 'R, G, B' or '#RRGGBB'`, Toasts.Type.FAILURE);
showToast(t("betterAudioPlayer.toasts.invalidColor", { settingKey }), Toasts.Type.FAILURE);
settings.store[settingKey] = defaultValue;
}
} else {
@ -256,39 +257,39 @@ function handleColorChange(value, settingKey, defaultValue) {
const settings = definePluginSettings({
oscilloscope: {
type: OptionType.BOOLEAN,
description: "Enable oscilloscope visualizer",
description: t("betterAudioPlayer.settings.oscilloscope"),
default: true,
},
spectrograph: {
type: OptionType.BOOLEAN,
description: "Enable spectrograph visualizer",
description: t("betterAudioPlayer.settings.spectrograph"),
default: true,
},
oscilloscopeSolidColor: {
type: OptionType.BOOLEAN,
description: "Use solid color for oscilloscope",
description: t("betterAudioPlayer.settings.oscilloscopeSolidColor"),
default: false,
},
oscilloscopeColor: {
type: OptionType.STRING,
description: "Color for oscilloscope",
description: t("betterAudioPlayer.settings.oscilloscopeColor"),
default: "255, 255, 255",
onChange: value => handleColorChange(value, "oscilloscopeColor", "255, 255, 255"),
},
spectrographSolidColor: {
type: OptionType.BOOLEAN,
description: "Use solid color for spectrograph",
description: t("betterAudioPlayer.settings.spectrographSolidColor"),
default: false,
},
spectrographColor: {
type: OptionType.STRING,
description: "Color for spectrograph",
description: t("betterAudioPlayer.settings.spectrographColor"),
default: "33, 150, 243",
onChange: value => handleColorChange(value, "spectrographColor", "33, 150, 243"),
},
forceMoveBelow: {
type: OptionType.BOOLEAN,
description: "Force the visualizer below the audio player",
description: t("betterAudioPlayer.settings.forceMoveBelow"),
default: true,
},
});
@ -297,7 +298,7 @@ let observer: MutationObserver | null = null;
export default definePlugin({
name: "BetterAudioPlayer",
description: "Adds a spectrograph and oscilloscope visualizer to audio attachment players",
description: t("betterAudioPlayer.description"),
authors: [EquicordDevs.creations],
settings,
start() {

View file

@ -11,6 +11,7 @@ import { Heading } from "@components/Heading";
import { DeleteIcon, PlusIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Button, TextInput } from "@webpack/common";
@ -21,7 +22,7 @@ function ReasonsComponent() {
return (
<section>
<Heading>Reasons</Heading>
<Heading>{t("betterBanReasons.ui.reasons")}</Heading>
{reasons.map((r, i) => (
<div
key={i}
@ -33,7 +34,7 @@ function ReasonsComponent() {
reasons[i] = v;
settings.store.reasons = reasons;
}}
placeholder="Reason"
placeholder={t("betterBanReasons.ui.reasonPlaceholder")}
/>
<Button
className={cl("remove-button")}
@ -51,7 +52,7 @@ function ReasonsComponent() {
))}
<div className={cl("reason-wrapper")}>
<Button onClick={() => settings.store.reasons.push("")} className={cl("add-button")} size={Button.Sizes.LARGE} color={Button.Colors.TRANSPARENT}>
<PlusIcon /> Add another reason
<PlusIcon /> {t("betterBanReasons.ui.addAnotherReason")}
</Button>
</div>
</section>
@ -60,20 +61,20 @@ function ReasonsComponent() {
const settings = definePluginSettings({
reasons: {
description: "Your custom reasons",
description: t("betterBanReasons.settings.reasons"),
type: OptionType.COMPONENT,
default: [] as string[],
component: ReasonsComponent,
},
isTextInputDefault: {
type: OptionType.BOOLEAN,
description: 'Shows a text input instead of a select menu by default. (Equivalent to clicking the "Other" option)'
description: t("betterBanReasons.settings.isTextInputDefault")
}
});
export default definePlugin({
name: "BetterBanReasons",
description: "Create custom reasons to use in the Discord ban modal, and/or show a text input by default instead of the options.",
description: t("betterBanReasons.description"),
authors: [Devs.Inbestigator],
patches: [
{

View file

@ -8,6 +8,7 @@ import "./styles.css";
import { EquicordDevs } from "@utils/constants";
import { getIntlMessage, openUserProfile } from "@utils/discord";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Button, React, RelationshipStore, TextInput, UserStore } from "@webpack/common";
@ -16,7 +17,7 @@ let updateFunc = (v: any) => { };
export default definePlugin({
name: "BetterBlockedUsers",
description: "Allows you to search in blocked users list and makes names selectable in settings.",
description: t("betterBlockedUsers.description"),
authors: [EquicordDevs.TheArmagan],
patches: [
{
@ -50,7 +51,7 @@ export default definePlugin({
}, []);
return <TextInput
placeholder="Search users..."
placeholder={t("betterBlockedUsers.ui.searchPlaceholder")}
style={{ width: "200px" }}
onInput={e => {
const search = (e.target as HTMLInputElement).value.toLowerCase().trim();

View file

@ -7,17 +7,18 @@
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { FluxDispatcher, UserStore } from "@webpack/common";
const settings = definePluginSettings({
autoFillArguments: {
description: "Automatically fill command with all arguements instead of just required ones",
description: t("betterCommands.settings.autoFillArguments"),
type: OptionType.BOOLEAN,
default: true,
},
allowNewlinesInCommands: {
description: "Allow newlines in command inputs (CTRL + Shift + Enter)",
description: t("betterCommands.settings.allowNewlinesInCommands"),
type: OptionType.BOOLEAN,
default: true,
}
@ -32,7 +33,7 @@ function fetchIndex(target: object) {
export default definePlugin({
name: "BetterCommands",
description: "Enhances the command system with miscellaneous improvements.",
description: t("betterCommands.description"),
authors: [Devs.thororen],
settings,
patches: [
@ -70,11 +71,11 @@ export default definePlugin({
commands: [
{
name: "refresh",
description: "Refreshes Discord application commands locally",
description: t("betterCommands.commands.refresh.description"),
options: [
{
name: "user",
description: "specific user to try and refresh",
description: t("betterCommands.commands.refresh.userOption"),
type: ApplicationCommandOptionType.USER,
}
],
@ -86,7 +87,7 @@ export default definePlugin({
const user = findOption(opts, "user") as string;
sendBotMessage(ctx.channel.id, {
content: "Refreshing application commands...",
content: t("betterCommands.commands.refresh.refreshing"),
});
fetchIndex({ type: "channel", channelId });
@ -103,12 +104,12 @@ export default definePlugin({
}
sendBotMessage(ctx.channel.id, {
content: "Commands refreshed successfully!",
content: t("betterCommands.commands.refresh.success"),
});
} catch (err) {
console.error("[Refresh Command] Error refreshing commands:", err);
sendBotMessage(ctx.channel.id, {
content: "Failed to refresh commands. Check the console for details.",
content: t("betterCommands.commands.refresh.failed"),
});
}
},

View file

@ -10,6 +10,7 @@ import { InfoIcon } from "@components/Icons";
import { Devs, EquicordDevs } from "@utils/constants";
import { openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc";
import { t } from "@utils/translation";
import definePlugin, { StartAt } from "@utils/types";
import { Guild } from "@vencord/discord-types";
import { findByPropsLazy, findCssClassesLazy } from "@webpack";
@ -33,7 +34,7 @@ function lurk(id: string) {
export default definePlugin({
name: "BetterInvites",
description: "See invites expiration date, view inviter profile and preview servers before joining by clicking the name",
description: t("betterInvites.description"),
authors: [EquicordDevs.iamme, Devs.thororen],
patches: [
{
@ -66,7 +67,9 @@ export default definePlugin({
const timestamp = <>{Parser.parse(`<t:${Math.round(new Date(expires_at).getTime() / 1000)}:R>`)}</>;
const tooltipText = (
<>
This invite will {expires_at ? <>expire {timestamp}</> : <>not expire</>}
{t("betterInvites.ui.expireTooltip", {
expiry: expires_at ? <>{t("betterInvites.ui.expireWill")} {timestamp}</> : <>{t("betterInvites.ui.notExpire")}</>
})}
</>
);
@ -98,7 +101,7 @@ export default definePlugin({
: "/assets/1f0bfc0865d324c2587920a7d80c609b.png?size=128"}
/>
<div className="vc-bi-header-text">
{inviter.global_name || inviter.username} invited you to {guildName}
{t("betterInvites.ui.invitedYouTo", { inviter: inviter.global_name || inviter.username, guild: guildName })}
</div>
</div>
);

View file

@ -5,13 +5,14 @@
*/
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { MessageStore } from "@webpack/common";
export default definePlugin({
name: "BetterPlusReacts",
authors: [Devs.Joona],
description: "The amount of plus before :emoji: is the message to add it to",
description: t("betterPlusReacts.description"),
patches: [
{
find: ".SLASH_COMMAND_USED,",

View file

@ -6,42 +6,43 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
const settings = definePluginSettings({
removeNameplate: {
type: OptionType.BOOLEAN,
description: "Remove nameplate styling.",
description: t("betterUserArea.settings.removeNameplate"),
default: true,
restartNeeded: true
},
removeAudioMenus: {
type: OptionType.BOOLEAN,
description: "Remove menus next to mute and deafen buttons.",
description: t("betterUserArea.settings.removeAudioMenus"),
default: true,
restartNeeded: true
},
alwaysShowUsername: {
type: OptionType.BOOLEAN,
description: "Always show username instead of status.",
description: t("betterUserArea.settings.alwaysShowUsername"),
default: true,
restartNeeded: true
},
removeButtonTooltips: {
type: OptionType.BOOLEAN,
description: "Remove button tooltips.",
description: t("betterUserArea.settings.removeButtonTooltips"),
default: false,
restartNeeded: true
},
removeAvatarDecoration: {
type: OptionType.BOOLEAN,
description: "Remove avatar decoration.",
description: t("betterUserArea.settings.removeAvatarDecoration"),
default: true,
restartNeeded: true
},
removeUsernameStyles: {
type: OptionType.BOOLEAN,
description: "Remove username colors and effects.",
description: t("betterUserArea.settings.removeUsernameStyles"),
default: true,
restartNeeded: true
}
@ -49,7 +50,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "BetterUserArea",
description: "Customize and make the user area more clean.",
description: t("betterUserArea.description"),
authors: [Devs.prism],
settings,
patches: [

View file

@ -6,6 +6,7 @@
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types";
@ -14,24 +15,24 @@ let blockedKeywords: Array<RegExp>;
const settings = definePluginSettings({
blockedWords: {
type: OptionType.STRING,
description: "Comma-seperated list of words to block",
description: t("blockKeywords.settings.blockedWords"),
default: "",
restartNeeded: true
},
useRegex: {
type: OptionType.BOOLEAN,
description: "Use each value as a regular expression when checking message content (advanced)",
description: t("blockKeywords.settings.useRegex"),
default: false,
restartNeeded: true
},
caseSensitive: {
type: OptionType.BOOLEAN,
description: "Whether to use a case sensitive search or not",
description: t("blockKeywords.settings.caseSensitive"),
default: false,
restartNeeded: true
},
ignoreBlockedMessages: {
description: "Completely ignores (recent) new messages bar",
description: t("blockKeywords.settings.ignoreBlockedMessages"),
type: OptionType.BOOLEAN,
default: true,
restartNeeded: true,
@ -69,7 +70,7 @@ export function containsBlockedKeywords(message: Message) {
export default definePlugin({
name: "BlockKeywords",
description: "Blocks messages containing specific user-defined keywords, as if the user sending them was blocked.",
description: t("blockKeywords.description"),
authors: [EquicordDevs.catcraft],
patches: [
{

View file

@ -5,11 +5,12 @@
*/
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
export default definePlugin({
name: "BlockKrisp",
description: "Prevent Krisp from loading",
description: t("blockKrisp.description"),
authors: [Devs.D3SOX],
patches: [
// Block loading modules on Desktop

View file

@ -5,11 +5,12 @@
*/
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
export default definePlugin({
name: "BypassPinPrompt",
description: "Bypass the pin prompt when using the pin functions",
description: t("bypassPinPrompt.description"),
authors: [Devs.thororen],
patches: [
...[

View file

@ -11,6 +11,7 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import type { Message } from "@vencord/discord-types";
import { ChannelActionCreators, ChannelStore, Menu, MessageStore, NavigationRouter, PresenceStore, UserStore, WindowStore } from "@webpack/common";
@ -79,7 +80,7 @@ function ContextCallback(name: "guild" | "user" | "channel"): NavContextMenuPatc
<Menu.MenuGroup>
<Menu.MenuItem
id={`status-${name}-bypass`}
label={`${enabled ? "Remove" : "Add"} Status Bypass`}
label={enabled ? t("bypassStatus.contextMenu.removeStatusBypass") : t("bypassStatus.contextMenu.addStatusBypass")}
icon={() => Icon(enabled)}
action={() => {
let bypasses: string[] = settings.store[`${name}s`].split(", ");
@ -96,58 +97,58 @@ function ContextCallback(name: "guild" | "user" | "channel"): NavContextMenuPatc
const settings = definePluginSettings({
guilds: {
type: OptionType.STRING,
description: "Guilds to let bypass (notified when pinged anywhere in guild)",
description: t("bypassStatus.settings.guilds"),
default: "",
placeholder: "Separate with commas",
placeholder: t("bypassStatus.placeholder.separateWithCommas"),
onChange: value => settings.store.guilds = processIds(value)
},
channels: {
type: OptionType.STRING,
description: "Channels to let bypass (notified when pinged in that channel)",
description: t("bypassStatus.settings.channels"),
default: "",
placeholder: "Separate with commas",
placeholder: t("bypassStatus.placeholder.separateWithCommas"),
onChange: value => settings.store.channels = processIds(value)
},
users: {
type: OptionType.STRING,
description: "Users to let bypass (notified for all messages sent in DMs)",
description: t("bypassStatus.settings.users"),
default: "",
placeholder: "Separate with commas",
placeholder: t("bypassStatus.placeholder.separateWithCommas"),
onChange: value => settings.store.users = processIds(value)
},
allowOutsideOfDms: {
type: OptionType.BOOLEAN,
description: "Allow selected users to bypass status outside of DMs too (acts like a channel/guild bypass, but it's for all messages sent by the selected users)"
description: t("bypassStatus.settings.allowOutsideOfDms")
},
notificationSound: {
type: OptionType.BOOLEAN,
description: "Whether the notification sound should be played",
description: t("bypassStatus.settings.notificationSound"),
default: true,
},
respectSilentPings: {
type: OptionType.BOOLEAN,
description: "Respect silent pings (@silent / suppress notifications)",
description: t("bypassStatus.settings.respectSilentPings"),
default: true
},
statusToUse: {
type: OptionType.SELECT,
description: "Status to use for whitelist",
description: t("bypassStatus.settings.statusToUse"),
options: [
{
label: "Online",
label: t("bypassStatus.statusOptions.online"),
value: "online",
},
{
label: "Idle",
label: t("bypassStatus.statusOptions.idle"),
value: "idle",
},
{
label: "Do Not Disturb",
label: t("bypassStatus.statusOptions.dnd"),
value: "dnd",
default: true
},
{
label: "Invisible",
label: t("bypassStatus.statusOptions.invisible"),
value: "invisible",
}
]
@ -156,7 +157,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "BypassStatus",
description: "Still get notifications from specific sources when in do not disturb mode. Right-click on users/channels/guilds to set them to bypass do not disturb mode.",
description: t("bypassStatus.description"),
authors: [Devs.Inbestigator],
dependencies: ["AudioPlayerAPI"],
flux: {

View file

@ -7,6 +7,7 @@
import "./style.css";
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Channel } from "@vencord/discord-types";
import { GuildStore, React, SelectedGuildStore } from "@webpack/common";
@ -31,7 +32,7 @@ function renderBadge(id: number, title: string) {
export default definePlugin({
name: "ChannelBadges",
description: "Adds badges to channels based on their type",
description: t("channelBadges.description"),
authors: [EquicordDevs.creations, Devs.thororen],
settings,
patches: [
@ -64,9 +65,9 @@ export default definePlugin({
const guild = selectedGuildId ? GuildStore.getGuild(selectedGuildId) : null;
const badgeConditions = [
{ id: 6101, condition: isPrivate, title: "This channel is locked." },
{ id: 6100, condition: isNSFW, title: "This channel is marked as NSFW." },
{ id: 6102, condition: guild?.rulesChannelId === channel.id, title: "This channel is the server rules channel." },
{ id: 6101, condition: isPrivate, title: t("channelBadges.tooltips.lockedChannel") },
{ id: 6100, condition: isNSFW, title: t("channelBadges.tooltips.nsfwChannel") },
{ id: 6102, condition: guild?.rulesChannelId === channel.id, title: t("channelBadges.tooltips.rulesChannel") },
];
let badges: JSX.Element[] = [];

View file

@ -5,270 +5,271 @@
*/
import { definePluginSettings } from "@api/Settings";
import { t } from "@utils/translation";
import { OptionType } from "@utils/types";
export const settings = definePluginSettings({
oneBadgePerChannel: {
type: OptionType.BOOLEAN,
default: false,
description: "Show only one badge per channel",
description: t("channelBadges.settings.oneBadgePerChannel"),
restartNeeded: true,
},
showTextBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Text badge",
description: t("channelBadges.settings.showTextBadge"),
restartNeeded: true,
},
showVoiceBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Voice badge",
description: t("channelBadges.settings.showVoiceBadge"),
restartNeeded: true,
},
showCategoryBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Category badge",
description: t("channelBadges.settings.showCategoryBadge"),
restartNeeded: true,
},
showDirectoryBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Directory badge",
description: t("channelBadges.settings.showDirectoryBadge"),
restartNeeded: true,
},
showAnnouncementThreadBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Announcement Thread badge",
description: t("channelBadges.settings.showAnnouncementThreadBadge"),
restartNeeded: true,
},
showPublicThreadBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Public Thread badge",
description: t("channelBadges.settings.showPublicThreadBadge"),
restartNeeded: true,
},
showPrivateThreadBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Private Thread badge",
description: t("channelBadges.settings.showPrivateThreadBadge"),
restartNeeded: true,
},
showStageBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Stage badge",
description: t("channelBadges.settings.showStageBadge"),
restartNeeded: true,
},
showAnnouncementBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Announcement badge",
description: t("channelBadges.settings.showAnnouncementBadge"),
restartNeeded: true,
},
showForumBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Forum badge",
description: t("channelBadges.settings.showForumBadge"),
restartNeeded: true,
},
showMediaBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Media badge",
description: t("channelBadges.settings.showMediaBadge"),
restartNeeded: true,
},
showNSFWBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show NSFW badge",
description: t("channelBadges.settings.showNSFWBadge"),
restartNeeded: true,
},
showLockedBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Locked badge",
description: t("channelBadges.settings.showLockedBadge"),
restartNeeded: true,
},
showRulesBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Rules badge",
description: t("channelBadges.settings.showRulesBadge"),
restartNeeded: true,
},
showUnknownBadge: {
type: OptionType.BOOLEAN,
default: true,
description: "Show Unknown badge",
description: t("channelBadges.settings.showUnknownBadge"),
restartNeeded: true,
},
textBadgeLabel: {
type: OptionType.STRING,
default: "Text",
description: "Text badge label",
description: t("channelBadges.settings.textBadgeLabel"),
restartNeeded: true,
},
voiceBadgeLabel: {
type: OptionType.STRING,
default: "Voice",
description: "Voice badge label",
description: t("channelBadges.settings.voiceBadgeLabel"),
restartNeeded: true,
},
categoryBadgeLabel: {
type: OptionType.STRING,
default: "Category",
description: "Category badge label",
description: t("channelBadges.settings.categoryBadgeLabel"),
restartNeeded: true,
},
announcementBadgeLabel: {
type: OptionType.STRING,
default: "News",
description: "Announcement badge label",
description: t("channelBadges.settings.announcementBadgeLabel"),
restartNeeded: true,
},
announcementThreadBadgeLabel: {
type: OptionType.STRING,
default: "News Thread",
description: "Announcement Thread badge label",
description: t("channelBadges.settings.announcementThreadBadgeLabel"),
restartNeeded: true,
},
publicThreadBadgeLabel: {
type: OptionType.STRING,
default: "Thread",
description: "Public Thread badge label",
description: t("channelBadges.settings.publicThreadBadgeLabel"),
restartNeeded: true,
},
privateThreadBadgeLabel: {
type: OptionType.STRING,
default: "Private Thread",
description: "Private Thread badge label",
description: t("channelBadges.settings.privateThreadBadgeLabel"),
restartNeeded: true,
},
stageBadgeLabel: {
type: OptionType.STRING,
default: "Stage",
description: "Stage badge label",
description: t("channelBadges.settings.stageBadgeLabel"),
restartNeeded: true,
},
directoryBadgeLabel: {
type: OptionType.STRING,
default: "Directory",
description: "Directory badge label",
description: t("channelBadges.settings.directoryBadgeLabel"),
restartNeeded: true,
},
forumBadgeLabel: {
type: OptionType.STRING,
default: "Forum",
description: "Forum badge label",
description: t("channelBadges.settings.forumBadgeLabel"),
restartNeeded: true,
},
mediaBadgeLabel: {
type: OptionType.STRING,
default: "Media",
description: "Media badge label",
description: t("channelBadges.settings.mediaBadgeLabel"),
restartNeeded: true,
},
nsfwBadgeLabel: {
type: OptionType.STRING,
default: "NSFW",
description: "NSFW badge label",
description: t("channelBadges.settings.nsfwBadgeLabel"),
restartNeeded: true,
},
lockedBadgeLabel: {
type: OptionType.STRING,
default: "Locked",
description: "Locked badge label",
description: t("channelBadges.settings.lockedBadgeLabel"),
restartNeeded: true,
},
rulesBadgeLabel: {
type: OptionType.STRING,
default: "Rules",
description: "Rules badge label",
description: t("channelBadges.settings.rulesBadgeLabel"),
restartNeeded: true,
},
unknownBadgeLabel: {
type: OptionType.STRING,
default: "Unknown",
description: "Unknown badge label",
description: t("channelBadges.settings.unknownBadgeLabel"),
restartNeeded: true,
},
textBadgeColor: {
type: OptionType.STRING,
description: "Text badge color",
description: t("channelBadges.settings.textBadgeColor"),
restartNeeded: true,
},
voiceBadgeColor: {
type: OptionType.STRING,
description: "Voice badge color",
description: t("channelBadges.settings.voiceBadgeColor"),
restartNeeded: true,
},
categoryBadgeColor: {
type: OptionType.STRING,
description: "Category badge color",
description: t("channelBadges.settings.categoryBadgeColor"),
restartNeeded: true,
},
announcementBadgeColor: {
type: OptionType.STRING,
description: "Announcement badge color",
description: t("channelBadges.settings.announcementBadgeColor"),
restartNeeded: true,
},
announcementThreadBadgeColor: {
type: OptionType.STRING,
description: "Announcement Thread badge color",
description: t("channelBadges.settings.announcementThreadBadgeColor"),
restartNeeded: true,
},
publicThreadBadgeColor: {
type: OptionType.STRING,
description: "Public Thread badge color",
description: t("channelBadges.settings.publicThreadBadgeColor"),
restartNeeded: true,
},
privateThreadBadgeColor: {
type: OptionType.STRING,
description: "Private Thread badge color",
description: t("channelBadges.settings.privateThreadBadgeColor"),
restartNeeded: true,
},
stageBadgeColor: {
type: OptionType.STRING,
description: "Stage badge color",
description: t("channelBadges.settings.stageBadgeColor"),
restartNeeded: true,
},
directoryBadgeColor: {
type: OptionType.STRING,
description: "Directory badge color",
description: t("channelBadges.settings.directoryBadgeColor"),
restartNeeded: true,
},
forumBadgeColor: {
type: OptionType.STRING,
description: "Forum badge color",
description: t("channelBadges.settings.forumBadgeColor"),
restartNeeded: true,
},
mediaBadgeColor: {
type: OptionType.STRING,
description: "Media badge color",
description: t("channelBadges.settings.mediaBadgeColor"),
restartNeeded: true,
},
nsfwBadgeColor: {
type: OptionType.STRING,
description: "NSFW badge color",
description: t("channelBadges.settings.nsfwBadgeColor"),
restartNeeded: true,
},
lockedBadgeColor: {
type: OptionType.STRING,
description: "Locked badge color",
description: t("channelBadges.settings.lockedBadgeColor"),
restartNeeded: true,
},
rulesBadgeColor: {
type: OptionType.STRING,
description: "Rules badge color",
description: t("channelBadges.settings.rulesBadgeColor"),
restartNeeded: true,
},
unknownBadgeColor: {
type: OptionType.STRING,
description: "Unknown badge color",
description: t("channelBadges.settings.unknownBadgeColor"),
restartNeeded: true,
},
});

View file

@ -9,6 +9,7 @@ import "./style.css";
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Channel, Message } from "@vencord/discord-types";
import { ChannelStore, Menu } from "@webpack/common";
@ -23,7 +24,7 @@ const contextMenuPatch: NavContextMenuPatchCallback = (children, props: { channe
const menuItem = (
<Menu.MenuItem
label="Open in New Tab"
label={t("channelTabs.contextMenu.openInNewTab")}
id="open-link-in-tab"
action={() => createTab({
guildId: channel.guild_id || "@me", // Normalize for DMs/Group Chats
@ -46,7 +47,7 @@ const contextMenuPatch: NavContextMenuPatchCallback = (children, props: { channe
export default definePlugin({
name: "ChannelTabs",
description: "Group your commonly visited channels in tabs, like a browser",
description: t("channelTabs.description"),
authors: [Devs.TheSun, Devs.TheKodeToad, EquicordDevs.keifufu, Devs.Nickyux, EquicordDevs.DiabeloDEV, EquicordDevs.justjxke],
dependencies: ["ContextMenuAPI"],
contextMenus: {

View file

@ -8,20 +8,21 @@ import "./style.css";
import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { UserStore } from "@webpack/common";
const settings = definePluginSettings({
colorEffects: {
type: OptionType.BOOLEAN,
description: "Turn on or off color effects for getting close to the character limit",
description: t("characterCounter.settings.colorEffects"),
default: true,
},
});
export default definePlugin({
name: "CharacterCounter",
description: "Adds a character counter to the chat input",
description: t("characterCounter.description"),
authors: [EquicordDevs.creations, EquicordDevs.Panniku, Devs.thororen],
settings,
patches: [

View file

@ -5,13 +5,14 @@
*/
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Channel } from "@vencord/discord-types";
export default definePlugin({
name: "CleanChannelName",
authors: [Devs.AutumnVN],
description: "remove all emoji and decor shit from channel names",
description: t("cleanChannelName.description"),
patches: [
{
find: "loadAllGuildAndPrivateChannelsFromDisk(){",

View file

@ -5,11 +5,12 @@
*/
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
export default definePlugin({
name: "CleanerChannelGroups",
description: "Hides all channels in collapsed categories, even if they have unread messages.",
description: t("cleanerChannelGroups.description"),
authors: [EquicordDevs.justjxke],
patches: [
{

View file

@ -7,6 +7,7 @@
import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph";
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { GuildMember } from "@vencord/discord-types";
import { ChannelStore, GuildMemberStore, GuildRoleStore, React, RelationshipStore, UserStore } from "@webpack/common";
@ -14,52 +15,52 @@ import { ChannelStore, GuildMemberStore, GuildRoleStore, React, RelationshipStor
const settings = definePluginSettings({
hideVc: {
type: OptionType.BOOLEAN,
description: "Hide voice channels containing blocked users.",
description: t("clientSideBlock.settings.hideVc"),
default: false,
restartNeeded: true
},
usersToBlock: {
type: OptionType.STRING,
description: "User IDs seperated by a comma and a space",
description: t("clientSideBlock.settings.usersToBlock"),
restartNeeded: true,
default: ""
},
hideBlockedUsers: {
type: OptionType.BOOLEAN,
description: "Should blocked users should also be hidden everywhere",
description: t("clientSideBlock.settings.hideBlockedUsers"),
default: true,
restartNeeded: true
},
hideBlockedMessages: {
type: OptionType.BOOLEAN,
description: "Should messages from blocked users should be hidden fully (same as the old noblockedmessages plugin)",
description: t("clientSideBlock.settings.hideBlockedMessages"),
default: true,
restartNeeded: true
},
hideEmptyRoles: {
type: OptionType.BOOLEAN,
description: "Should role headers be hidden if all of their members are blocked",
description: t("clientSideBlock.settings.hideEmptyRoles"),
restartNeeded: true,
default: true
},
blockedReplyDisplay: {
type: OptionType.SELECT,
description: "What should display instead of the message when someone replies to someone you have hidden",
description: t("clientSideBlock.settings.blockedReplyDisplay"),
restartNeeded: true,
options: [
{ value: "displayText", label: "Display text saying a hidden message was replied to", default: true },
{ value: "hideReply", label: "Literally nothing" }
{ value: "displayText", label: t("clientSideBlock.settings.blockedReplyDisplayOptions.displayText"), default: true },
{ value: "hideReply", label: t("clientSideBlock.settings.blockedReplyDisplayOptions.hideReply") }
]
},
guildBlackList: {
type: OptionType.STRING,
description: "Guild ids to disable functionality in",
description: t("clientSideBlock.settings.guildBlackList"),
restartNeeded: true,
default: ""
},
guildWhiteList: {
type: OptionType.STRING,
description: "Guild ids to enable functionality in",
description: t("clientSideBlock.settings.guildWhiteList"),
restartNeeded: true,
default: ""
}
@ -100,7 +101,7 @@ function hiddenReplyComponent() {
case "displayText":
return <Paragraph style={{ marginTop: "0px", marginBottom: "0px" }}>
<i>
↓ Replying to blocked message
↓ {t("clientSideBlock.reply.replyingToBlocked")}
</i>
</Paragraph>;
case "hideReply":
@ -141,7 +142,7 @@ function activeNowView(cards) {
export default definePlugin({
name: "ClientSideBlock",
description: "Allows you to locally hide almost all content from any user",
description: t("clientSideBlock.description"),
tags: ["blocked", "block", "hide", "hidden", "noblockedmessages"],
authors: [Devs.Samwich, EquicordDevs.KamiRu],
settings,

View file

@ -8,6 +8,7 @@ import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Button } from "@components/Button";
import { Devs, EquicordDevs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Activity } from "@vencord/discord-types";
import { PresenceStore, SettingsRouter, UserStore } from "@webpack/common";
@ -18,34 +19,34 @@ const extraFramerates = [45, 90, 120, 144, 165, 240];
const settings = definePluginSettings({
richPresenceTagging: {
type: OptionType.SELECT,
description: "When should clips be tagged with the current Rich Presence?",
description: t("clipsEnhancements.settings.richPresenceTagging"),
options: [
{ label: "Always", value: "always" },
{ label: "Only when beginning or end of activity name matches", value: "whenMatched", default: true },
{ label: "Never", value: "never" },
{ label: t("clipsEnhancements.settings.richPresenceTaggingOptions.always"), value: "always" },
{ label: t("clipsEnhancements.settings.richPresenceTaggingOptions.whenMatched"), value: "whenMatched", default: true },
{ label: t("clipsEnhancements.settings.richPresenceTaggingOptions.never"), value: "never" },
]
},
enableScreenshotKeybind: {
type: OptionType.BOOLEAN,
description: "Enable the screenshot keybind feature",
description: t("clipsEnhancements.settings.enableScreenshotKeybind"),
default: true,
restartNeeded: true
},
enableVoiceOnlyClips: {
type: OptionType.BOOLEAN,
description: "Enable voice-only clips (audio without video)",
description: t("clipsEnhancements.settings.enableVoiceOnlyClips"),
default: true,
restartNeeded: true
},
enableAdvancedSignals: {
type: OptionType.BOOLEAN,
description: "Enable advanced clip signals (auto-clip triggers)",
description: t("clipsEnhancements.settings.enableAdvancedSignals"),
default: true,
restartNeeded: true
},
ignorePlatformRestriction: {
type: OptionType.BOOLEAN,
description: "Allow Platform Restricted Clipping (may cause save errors)",
description: t("clipsEnhancements.settings.ignorePlatformRestriction"),
default: true,
restartNeeded: true
},
@ -60,7 +61,7 @@ const settings = definePluginSettings({
SettingsRouter.openUserSettings("clips_panel");
}}
>
Change FPS and duration options in Clips settings!
{t("clipsEnhancements.settings.clipsLink")}
</Button >
</>
);
@ -71,7 +72,7 @@ const settings = definePluginSettings({
migratePluginSettings("ClipsEnhancements", "TimelessClips");
export default definePlugin({
name: "ClipsEnhancements",
description: "Add more Clip FPS and duration options, custom clip length, RPC tagging and more",
description: t("clipsEnhancements.description"),
authors: [Devs.niko, Devs.Joona, EquicordDevs.keircn],
settings,
patches: [

View file

@ -8,6 +8,7 @@ import { definePluginSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import { EquicordDevs, IS_MAC } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { useEffect, useState } from "@webpack/common";
@ -92,9 +93,9 @@ function KeybindRecorder() {
return (
<div className="vc-command-palette-keybind-input">
<div className="vc-command-palette-keybind-info">
<BaseText size="md" weight="semibold">Command Palette Hotkey</BaseText>
<BaseText size="md" weight="semibold">{t("commandPalette.ui.hotkeyTitle")}</BaseText>
<BaseText size="sm" weight="normal" style={{ color: "var(--text-muted)" }}>
Hotkey used to open the command palette
{t("commandPalette.ui.hotkeyDescription")}
</BaseText>
{error && (
<BaseText size="xs" weight="normal" className="vc-command-palette-keybind-conflict">
@ -111,14 +112,14 @@ function KeybindRecorder() {
>
{isListening ? (
<BaseText size="sm" weight="normal" style={{ color: "var(--white)", opacity: 0.8 }}>
Press any key...
{t("commandPalette.ui.pressAnyKey")}
</BaseText>
) : (
formatKeybind(currentKeybind)
)}
</Button>
<Button size="small" variant="secondary" onClick={handleReset}>
Reset
{t("commandPalette.ui.reset")}
</Button>
</div>
</div>
@ -127,31 +128,31 @@ function KeybindRecorder() {
export const settings = definePluginSettings({
hotkey: {
description: "Hotkey used to open the command palette",
description: t("commandPalette.settings.hotkey"),
type: OptionType.COMPONENT,
default: DEFAULT_KEYS,
component: KeybindRecorder
},
visualStyle: {
description: "Palette appearance",
description: t("commandPalette.settings.visualStyle"),
type: OptionType.SELECT,
options: [
{ label: "Classic", value: "classic", default: true },
{ label: "Polished", value: "polished" }
{ label: t("commandPalette.settings.visualStyleOptions.classic"), value: "classic", default: true },
{ label: t("commandPalette.settings.visualStyleOptions.polished"), value: "polished" }
]
},
showTags: {
description: "Display tag chips for commands",
description: t("commandPalette.settings.showTags"),
type: OptionType.BOOLEAN,
default: true
},
enableTagFilter: {
description: "Show the tag filter bar",
description: t("commandPalette.settings.enableTagFilter"),
type: OptionType.BOOLEAN,
default: true
},
customCommands: {
description: "Manage custom command palette entries",
description: t("commandPalette.settings.customCommands"),
type: OptionType.COMPONENT,
component: CommandPaletteSettingsPanel
}
@ -223,7 +224,7 @@ function hotkeyUsesModifiers() {
export default definePlugin({
name: "CommandPalette",
description: "Quickly run actions through a searchable command palette",
description: t("commandPalette.description"),
authors: [EquicordDevs.justjxke, EquicordDevs.Ethan],
settings,
patches: [

View file

@ -9,6 +9,7 @@ import "./styles.css";
import SettingsPlugin from "@plugins/_core/settings";
import { Devs } from "@utils/constants";
import { removeFromArray } from "@utils/misc";
import { t } from "@utils/translation";
import definePlugin, { StartAt } from "@utils/types";
import { SettingsRouter } from "@webpack/common";
@ -40,7 +41,7 @@ function ComponentsIcon(props: React.SVGProps<SVGSVGElement>) {
export default definePlugin({
name: "Components",
description: "Adds a new tab to settings to browse Discord components.",
description: t("components.description"),
authors: [Devs.prism],
dependencies: ["Settings"],
startAt: StartAt.WebpackReady,

View file

@ -14,6 +14,7 @@ import { DeleteIcon } from "@components/Icons";
import { EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { useForceUpdater } from "@utils/react";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Button, TextInput, useState } from "@webpack/common";
@ -93,7 +94,7 @@ function FlaggedInput({ index, forceUpdate }) {
return (<Flex flexDirection="row">
<div style={{ flexGrow: 1 }}>
<TextInput
placeholder="Word"
placeholder={t("contentWarning.ui.placeholder")}
spellCheck={false}
value={value}
onChange={updateValue}
@ -130,7 +131,7 @@ function FlaggedWords() {
return (
<>
<HeadingTertiary>Flagged Words</HeadingTertiary>
<HeadingTertiary>{t("contentWarning.ui.flaggedWords")}</HeadingTertiary>
{inputs}
</>
);
@ -143,7 +144,7 @@ const settings = definePluginSettings({
},
onClick: {
type: OptionType.BOOLEAN,
description: "Only show trigger content on click instead of hover",
description: t("contentWarning.settings.onClick"),
default: false,
}
});
@ -151,7 +152,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "ContentWarning",
authors: [EquicordDevs.camila314],
description: "Allows you to specify certain trigger words that will be blurred by default. Hovering/Clicking on the blurred content will reveal it.",
description: t("contentWarning.description"),
settings,
patches: [
{

View file

@ -7,6 +7,7 @@
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { copyToClipboard } from "@utils/clipboard";
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Menu, Toasts, UserProfileStore } from "@webpack/common";
@ -34,7 +35,7 @@ function copyProfileColors(userId) {
if (!colors) {
Toasts.show({
type: Toasts.Type.FAILURE,
message: "No profile colors found!",
message: t("copyProfileColors.toast.noColorsFound"),
id: Toasts.genId()
});
return;
@ -49,14 +50,14 @@ function copyProfileColors(userId) {
copyToClipboard(formattedColors);
Toasts.show({
type: Toasts.Type.SUCCESS,
message: "Profile colors copied to clipboard!",
message: t("copyProfileColors.toast.copied"),
id: Toasts.genId()
});
} catch (e) {
console.error("Failed to copy to clipboard:", e);
Toasts.show({
type: Toasts.Type.FAILURE,
message: "Error copying profile colors!",
message: t("copyProfileColors.toast.error"),
id: Toasts.genId()
});
}
@ -81,7 +82,7 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }) =
<Menu.MenuItem
id="CopyProfileColors"
icon={ColorIcon}
label="Copy Profile Colors"
label={t("copyProfileColors.contextMenu.copyProfileColors")}
action={() => copyProfileColors(user.id)}
/>
);
@ -89,7 +90,7 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }) =
export default definePlugin({
name: "CopyProfileColors",
description: "A plugin to copy people's profile gradient colors to clipboard.",
description: t("copyProfileColors.description"),
authors: [EquicordDevs.Crxa, EquicordDevs.Cortex],
contextMenus: {
"user-context": userContextMenuPatch,

View file

@ -6,6 +6,7 @@
import { copyToClipboard } from "@utils/clipboard";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { User } from "@vencord/discord-types";
import { findByCodeLazy } from "@webpack";
@ -22,7 +23,7 @@ const getMetadataFromApi: (activity: any, userId: string) => Promise<any> = find
export default definePlugin({
name: "CopyStatusUrls",
description: "Copy the users status url when you right-click it",
description: t("copyStatusUrls.description"),
authors: [Devs.sadan],
patches: [
@ -45,7 +46,7 @@ export default definePlugin({
copyToClipboard(button_urls[index]);
Toasts.show({
id: Toasts.genId(),
message: "Copied URL",
message: t("copyStatusUrls.toast.copied"),
type: Toasts.Type.SUCCESS,
options: {
position: Toasts.Position.TOP
@ -55,7 +56,7 @@ export default definePlugin({
console.error(e);
Toasts.show({
id: Toasts.genId(),
message: "Error copying URL, check console for more info",
message: t("copyStatusUrls.toast.error"),
type: Toasts.Type.FAILURE,
options: {
position: Toasts.Position.TOP

View file

@ -7,6 +7,7 @@
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { copyToClipboard } from "@utils/clipboard";
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import type { Channel, User } from "@vencord/discord-types";
import { Menu } from "@webpack/common";
@ -37,7 +38,7 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: U
children.push(
<Menu.MenuItem
id="vc-copy-user-mention"
label="Copy User Mention"
label={t("copyUserMention.contextMenu.copyUserMention")}
action={() => copyToClipboard(`<@${user.id}>`)}
icon={MentionIcon}
/>
@ -47,7 +48,7 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: U
export default definePlugin({
name: "CopyUserMention",
authors: [EquicordDevs.Cortex, Devs.castdrian],
description: "Adds a button to copy user's mention on the user context menu, works best with ValidUser.",
description: t("copyUserMention.description"),
contextMenus: {
"user-context": UserContextMenuPatch
}

View file

@ -12,6 +12,7 @@ import { Divider } from "@components/Divider";
import { Heading } from "@components/Heading";
import { Devs, EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { ColorPicker } from "@webpack/common";
@ -54,7 +55,7 @@ function OnekoColorSettings() {
<div>
<div className={cl("color-modal")}>
<div>
<Heading className="form-subtitle">Fur Color</Heading>
<Heading className="form-subtitle">{t("cursorBuddy.ui.furColor")}</Heading>
<div className={cl("color")}>
<ColorPicker
color={parseHexToNumber(furColor) ?? 16777215}
@ -65,7 +66,7 @@ function OnekoColorSettings() {
</div>
<div>
<Heading className="form-subtitle">Outline Color</Heading>
<Heading className="form-subtitle">{t("cursorBuddy.ui.outlineColor")}</Heading>
<div className={cl("color")}>
<ColorPicker
color={parseHexToNumber(outlineColor) ?? 0}
@ -81,30 +82,30 @@ function OnekoColorSettings() {
const settings = definePluginSettings({
buddy: {
description: "Pick a cursor buddy",
description: t("cursorBuddy.settings.buddy"),
type: OptionType.SELECT,
options: [
{
label: "Oneko",
label: t("cursorBuddy.settings.buddyOptions.oneko"),
value: "oneko",
default: true
},
{
label: "Fatass Horse",
label: t("cursorBuddy.settings.buddyOptions.fathorse"),
value: "fathorse"
}
],
onChange: load,
},
speed: {
description: "Speed of your buddy",
description: t("cursorBuddy.settings.speed"),
type: OptionType.NUMBER,
default: 10,
isValid: (value: number) => value >= 0 || "Speed must be bigger than 0",
onChange: load,
},
fps: {
description: "Framerate of your buddy",
description: t("cursorBuddy.settings.fps"),
type: OptionType.NUMBER,
default: 24,
isValid: (value: number) => value > 0 || "Framerate must be bigger than 0",
@ -125,14 +126,14 @@ const settings = definePluginSettings({
component: OnekoColorSettings,
},
furColor: {
description: "Fur hex color for Oneko",
description: t("cursorBuddy.settings.furColor"),
type: OptionType.STRING,
default: "#FFFFFF",
onChange: load,
hidden: true,
},
outlineColor: {
description: "Outline hex color for Oneko",
description: t("cursorBuddy.settings.outlineColor"),
type: OptionType.STRING,
default: "#000000",
onChange: load,
@ -149,26 +150,26 @@ const settings = definePluginSettings({
),
},
size: {
description: "Size of the fatass horse",
description: t("cursorBuddy.settings.size"),
type: OptionType.NUMBER,
default: 120,
isValid: (value: number) => value > 0 || "Size must be bigger than 0",
onChange: load
},
fade: {
description: "If the horse should fade when the cursor is near",
description: t("cursorBuddy.settings.fade"),
type: OptionType.BOOLEAN,
default: true,
onChange: load
},
freeroam: {
description: "If the horse should roam freely when idle",
description: t("cursorBuddy.settings.freeroam"),
type: OptionType.BOOLEAN,
default: true,
onChange: load
},
shake: {
description: "If the horse should shake the window when it's walking",
description: t("cursorBuddy.settings.shake"),
type: OptionType.BOOLEAN,
default: false,
onChange: load,
@ -192,7 +193,7 @@ const settings = definePluginSettings({
disabled() { return this.store.buddy !== "fathorse"; },
},
shake: {
disabled() { return this.store.buddy !== "fathorse"; },
disabled() { return this.store.buddy !== "fathorse"; }
}
});
@ -234,7 +235,7 @@ function load() {
migratePluginSettings("CursorBuddy", "Oneko", "oneko");
export default definePlugin({
name: "CursorBuddy",
description: "Adds a sprite that follows your cursor.",
description: t("cursorBuddy.description"),
authors: [Devs.Ven, Devs.adryd, EquicordDevs.nexpid, EquicordDevs.ZcraftElite],
tags: ["Oneko", "FatassHorse", "Pet"],
settings,

View file

@ -5,6 +5,7 @@
*/
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { makeContextItem } from "./components";
@ -13,7 +14,7 @@ import { folderProp, int2rgba } from "./util";
export default definePlugin({
name: "CustomFolderIcons",
description: "Customize folder icons with any png",
description: t("customFolderIcons.description"),
authors: [EquicordDevs.sadan],
settings,
patches: [

View file

@ -12,6 +12,7 @@ import { definePluginSettings } from "@api/Settings";
import { Heading } from "@components/Heading";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { t } from "@utils/translation";
import definePlugin, { OptionType, StartAt } from "@utils/types";
import { Button, React, showToast, TextInput } from "@webpack/common";
@ -245,7 +246,7 @@ const settings = definePluginSettings({
});
dataUriCache.clear();
setResetTrigger(prev => prev + 1);
showToast("All overrides reset successfully!");
showToast(t("customSounds.toast.resetSuccess"));
};
const triggerFileUpload = () => {
@ -277,10 +278,10 @@ const settings = definePluginSettings({
}
setResetTrigger(prev => prev + 1);
showToast("Settings imported successfully!");
showToast(t("customSounds.toast.importSuccess"));
} catch (error) {
console.error("Error importing settings:", error);
showToast("Error importing settings. Check console for details.");
showToast(t("customSounds.toast.importError"));
}
};
@ -314,7 +315,7 @@ const settings = definePluginSettings({
a.click();
URL.revokeObjectURL(url);
showToast(`Exported ${overrides.length} settings (audio files not included)`);
showToast(t("customSounds.toast.exportSuccess", { count: overrides.length }));
};
const filteredSoundTypes = allSoundTypes.filter(type =>
@ -325,10 +326,10 @@ const settings = definePluginSettings({
return (
<div>
<div className="vc-custom-sounds-buttons">
<Button color={Button.Colors.BRAND} onClick={triggerFileUpload}>Import</Button>
<Button color={Button.Colors.PRIMARY} onClick={downloadSettings}>Export</Button>
<Button color={Button.Colors.RED} onClick={resetOverrides}>Reset All</Button>
<Button color={Button.Colors.WHITE} onClick={debugCustomSounds}>Debug</Button>
<Button color={Button.Colors.BRAND} onClick={triggerFileUpload}>{t("customSounds.settings.import")}</Button>
<Button color={Button.Colors.PRIMARY} onClick={downloadSettings}>{t("customSounds.settings.export")}</Button>
<Button color={Button.Colors.RED} onClick={resetOverrides}>{t("customSounds.settings.resetAll")}</Button>
<Button color={Button.Colors.WHITE} onClick={debugCustomSounds}>{t("customSounds.settings.debug")}</Button>
<input
ref={fileInputRef}
type="file"
@ -339,11 +340,11 @@ const settings = definePluginSettings({
</div>
<div className={cl("search")}>
<Heading>Search Sounds</Heading>
<Heading>{t("customSounds.settings.searchSounds")}</Heading>
<TextInput
value={searchQuery}
onChange={e => setSearchQuery(e)}
placeholder="Search by name or ID"
placeholder={t("customSounds.settings.searchPlaceholder")}
/>
</div>
@ -365,7 +366,7 @@ const settings = definePluginSettings({
await ensureDataURICached(currentOverride.selectedFileId);
} catch (error) {
console.error(`[CustomSounds] Failed to cache data URI for ${type.id}:`, error);
showToast("Error loading custom sound file");
showToast(t("customSounds.toast.loadError"));
}
}
@ -392,7 +393,7 @@ export function findOverride(id: string): SoundOverride | null {
export default definePlugin({
name: "CustomSounds",
description: "Customize Discord's sounds.",
description: t("customSounds.description"),
authors: [Devs.ScattrdBlade, Devs.TheKodeToad],
settings,
startAt: StartAt.Init,

View file

@ -14,6 +14,7 @@ import { Paragraph } from "@components/Paragraph";
import { Devs, EquicordDevs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { useForceUpdater } from "@utils/react";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { moment, TextInput, useEffect, useState } from "@webpack/common";
@ -74,7 +75,7 @@ const TimeRow = (props: TimeRowProps) => {
const settings = definePluginSettings({
formats: {
type: OptionType.COMPONENT,
description: "Customize the timestamp formats",
description: t("customTimestamps.settings.formats"),
component: componentProps => {
const [settingsState, setSettingsState] = useState(useSettings().plugins?.CustomTimestamps?.formats ?? {});
@ -92,9 +93,9 @@ const settings = definePluginSettings({
{key === "sameDayFormat" && (
<div className={Margins.bottom20}>
<Divider style={{ marginBottom: "10px" }} />
<Heading tag="h1">Calendar formats</Heading>
<Heading tag="h1">{t("customTimestamps.settings.calendarFormats")}</Heading>
<Paragraph>
How to format the [calendar] value if used in the above timestamps.
{t("customTimestamps.settings.calendarFormatsDescription")}
</Paragraph>
</div>
)}
@ -124,19 +125,18 @@ const settings = definePluginSettings({
export default definePlugin({
name: "CustomTimestamps",
description: "Custom timestamps on messages and tooltips",
description: t("customTimestamps.description"),
authors: [Devs.Rini, EquicordDevs.nvhhr, EquicordDevs.Suffocate, Devs.Obsidian],
settings,
settingsAboutComponent: () => (
<div className={"vc-cmt-info-card"}>
<HeadingPrimary>How to use:</HeadingPrimary>
<HeadingPrimary>{t("customTimestamps.about.title")}</HeadingPrimary>
<Paragraph>
<Link href="https://momentjs.com/docs/#/displaying/format/">Moment.js formatting documentation</Link>
<div className={Margins.top8}>
Additionally you can use these in your inputs:<br />
<b>[calendar]</b> enables dynamic date formatting such
as &quot;Today&quot; or &quot;Yesterday&quot;.<br />
<b>[relative]</b> gives you times such as &quot;4 hours ago&quot;.<br />
{t("customTimestamps.about.additional")}<br />
<b>[calendar]</b> {t("customTimestamps.about.calendar")}<br />
<b>[relative]</b> {t("customTimestamps.about.relative")}<br />
</div>
</Paragraph>
</div>

View file

@ -11,6 +11,7 @@ import { get } from "@api/DataStore";
import { definePluginSettings, Settings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { openModal } from "@utils/modal";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Channel, User } from "@vencord/discord-types";
import { extractAndLoadChunksLazy } from "@webpack";
@ -46,7 +47,7 @@ const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: {
const setCustomColorItem = (
<Menu.MenuItem
label="Set Color"
label={t("customUserColors.contextMenu.setColor")}
id="set-color"
icon={ColorIcon}
action={async () => {
@ -64,7 +65,7 @@ const channelContextMenuPatch: NavContextMenuPatchCallback = (children, { channe
const setCustomColorItem = (
<Menu.MenuItem
label="Set Color"
label={t("customUserColors.contextMenu.setColor")}
id="set-color"
icon={ColorIcon}
action={async () => {
@ -87,19 +88,19 @@ export function getCustomColorString(id: string | undefined, withHash?: boolean)
const settings = definePluginSettings({
dmList: {
type: OptionType.BOOLEAN,
description: "Users with custom colors defined will have their name in the dm list colored",
description: t("customUserColors.settings.dmList"),
default: true,
},
colorInServers: {
type: OptionType.BOOLEAN,
description: "If name colors should be changed within servers",
description: t("customUserColors.settings.colorInServers"),
default: true,
}
});
export default definePlugin({
name: "CustomUserColors",
description: "Lets you add a custom color to any user, anywhere! Highly recommend to use with typingTweaks and roleColorEverywhere",
description: t("customUserColors.description"),
authors: [EquicordDevs.mochienya],
contextMenus: {
"user-context": userContextMenuPatch,

View file

@ -5,11 +5,12 @@
*/
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
export default definePlugin({
name: "DisableCameras",
description: "Disables cameras in a call by default",
description: t("disableCameras.description"),
authors: [Devs.Joona],
patches: [
{

View file

@ -7,13 +7,14 @@
import "./styles.css";
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { makeDevBanner, settings } from "./components";
export default definePlugin({
name: "DiscordDevBanner",
description: "Enables the Discord developer banner, in which displays the build-ID",
description: t("discordDevBanner.description"),
authors: [EquicordDevs.KrystalSkull, Devs.thororen],
settings,
patches: [

View file

@ -1,26 +1,15 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2024 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) 2026 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import { EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { classes } from "@utils/misc";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { findByPropsLazy, findCssClassesLazy } from "@webpack";
import { useDrag, useDrop, useLayoutEffect, useRef, UserSettingsActionCreators } from "@webpack/common";
@ -44,7 +33,7 @@ type EmojiDescriptor = {
export default definePlugin({
name: "DragFavoriteEmotes",
authors: [EquicordDevs.PWall],
description: "Adds the ability to change the order of your favourite emotes",
description: t("dragFavoriteEmotes.description"),
patches: [
{
find: "#{intl::EMOJI_FAVORITE_TOOLTIP}",

View file

@ -10,6 +10,7 @@ import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { getGuildAcronym, insertTextIntoChatInputBox } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import type { Channel } from "@vencord/discord-types";
import { ChannelType } from "@vencord/discord-types/enums";
@ -74,61 +75,61 @@ function shouldIgnoreDrop(key: string): boolean {
const settings = definePluginSettings({
userOutput: {
type: OptionType.SELECT,
description: "User drop output.",
description: t("dragify.settings.userOutput"),
options: [
{ label: "Mention user", value: "mention", default: true },
{ label: "User ID", value: "id" },
{ label: t("dragify.settings.userOutputOptions.mention"), value: "mention", default: true },
{ label: t("dragify.settings.userOutputOptions.id"), value: "id" },
],
},
channelOutput: {
type: OptionType.SELECT,
description: "Channel drop output.",
description: t("dragify.settings.channelOutput"),
options: [
{ label: "#channel mention", value: "mention", default: true },
{ label: "Channel link", value: "link" },
{ label: t("dragify.settings.channelOutputOptions.mention"), value: "mention", default: true },
{ label: t("dragify.settings.channelOutputOptions.link"), value: "link" },
],
},
inviteExpireAfter: {
type: OptionType.SELECT,
description: "Invite expiration.",
description: t("dragify.settings.inviteExpireAfter"),
options: [
{ label: "30 minutes", value: 1800 },
{ label: "1 hour", value: 3600 },
{ label: "6 hours", value: 21600 },
{ label: "12 hours", value: 43200 },
{ label: "1 day", value: 86400 },
{ label: "7 days", value: 604800 },
{ label: "Never", value: 0, default: true },
{ label: t("dragify.settings.inviteExpireAfterOptions.30min"), value: 1800 },
{ label: t("dragify.settings.inviteExpireAfterOptions.1hour"), value: 3600 },
{ label: t("dragify.settings.inviteExpireAfterOptions.6hours"), value: 21600 },
{ label: t("dragify.settings.inviteExpireAfterOptions.12hours"), value: 43200 },
{ label: t("dragify.settings.inviteExpireAfterOptions.1day"), value: 86400 },
{ label: t("dragify.settings.inviteExpireAfterOptions.7days"), value: 604800 },
{ label: t("dragify.settings.inviteExpireAfterOptions.never"), value: 0, default: true },
],
},
inviteMaxUses: {
type: OptionType.SELECT,
description: "Invite max uses.",
description: t("dragify.settings.inviteMaxUses"),
options: [
{ label: "No limit", value: 0, default: true },
{ label: "1 use", value: 1 },
{ label: "5 uses", value: 5 },
{ label: "10 uses", value: 10 },
{ label: "25 uses", value: 25 },
{ label: "50 uses", value: 50 },
{ label: "100 uses", value: 100 },
{ label: t("dragify.settings.inviteMaxUsesOptions.noLimit"), value: 0, default: true },
{ label: t("dragify.settings.inviteMaxUsesOptions.1use"), value: 1 },
{ label: t("dragify.settings.inviteMaxUsesOptions.5uses"), value: 5 },
{ label: t("dragify.settings.inviteMaxUsesOptions.10uses"), value: 10 },
{ label: t("dragify.settings.inviteMaxUsesOptions.25uses"), value: 25 },
{ label: t("dragify.settings.inviteMaxUsesOptions.50uses"), value: 50 },
{ label: t("dragify.settings.inviteMaxUsesOptions.100uses"), value: 100 },
],
},
inviteTemporaryMembership: {
type: OptionType.BOOLEAN,
default: false,
description: "Grant temporary membership.",
description: t("dragify.settings.inviteTemporaryMembership"),
},
reuseExistingInvites: {
type: OptionType.BOOLEAN,
default: false,
description: "Reuse existing invite instead of creating a new one.",
description: t("dragify.settings.reuseExistingInvites"),
},
allowChatBodyDrop: {
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true,
description: "Allow dropping into the main chat body to insert text.",
description: t("dragify.settings.allowChatBodyDrop"),
},
});
@ -159,7 +160,7 @@ const inviteCache = new Map<string, { code: string; expiresAt: number | null; ma
export default definePlugin({
name: "Dragify",
description: "Drop users, channels, or servers into chat to insert mentions or invites.",
description: t("dragify.description"),
authors: [EquicordDevs.justjxke],
settings,
@ -340,7 +341,7 @@ export default definePlugin({
clearDragState();
} catch (error) {
logger.error("Failed handling drop", error);
showToast("Dragify failed to handle drop.", Toasts.Type.FAILURE);
showToast(t("dragify.toast.dropFailed"), Toasts.Type.FAILURE);
}
},
@ -385,11 +386,11 @@ export default definePlugin({
const inviteChannelId = inviteChannel?.id ?? fallbackChannelId;
if (!inviteChannelId) {
showToast("No channel available for invites.", Toasts.Type.FAILURE);
showToast(t("dragify.toast.noChannelForInvites"), Toasts.Type.FAILURE);
return null;
}
if (inviteChannel && inviteChannel.guild_id !== guildId) {
showToast("No channel available for invites.", Toasts.Type.FAILURE);
showToast(t("dragify.toast.noChannelForInvites"), Toasts.Type.FAILURE);
return null;
}
@ -413,11 +414,11 @@ export default definePlugin({
maxUses: maxUses === 0 ? null : maxUses,
uses: 0,
});
showToast("Invite created.", Toasts.Type.SUCCESS);
showToast(t("dragify.toast.inviteCreated"), Toasts.Type.SUCCESS);
return `https://discord.gg/${code}`;
} catch (error) {
logger.error("Failed to create invite", error);
showToast("Unable to create invite.", Toasts.Type.FAILURE); // uh oh!
showToast(t("dragify.toast.inviteFailed"), Toasts.Type.FAILURE); // uh oh!
return null;
}
},

View file

@ -11,6 +11,7 @@ import { Button } from "@components/Button";
import { Devs, IS_MAC } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { copyWithToast } from "@utils/discord";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { useEffect, useState } from "@webpack/common";
@ -79,14 +80,14 @@ function KeybindRecorder() {
className={cl("keybind-button", isListening ? "listening" : "")}
onClick={() => setIsListening(true)}
>
{isListening ? "Recording..." : <KeybindShortcut shortcut={currentKeybind.join("+")} />}
{isListening ? t("elementHighlighter.ui.recording") : <KeybindShortcut shortcut={currentKeybind.join("+")} />}
</button>
<Button
size="small"
variant="secondary"
onClick={() => { settings.store.keybind = DEFAULT_KEYBIND; }}
>
Reset
{t("elementHighlighter.ui.reset")}
</Button>
</div>
);
@ -94,48 +95,48 @@ function KeybindRecorder() {
const settings = definePluginSettings({
keybind: {
description: "Toggle Highlighter",
description: t("elementHighlighter.settings.keybind"),
type: OptionType.COMPONENT,
default: DEFAULT_KEYBIND,
component: KeybindRecorder
},
showClasses: {
description: "Display the element's CSS class names in the tooltip",
description: t("elementHighlighter.settings.showClasses"),
type: OptionType.BOOLEAN,
default: false
},
showId: {
description: "Display the element's ID attribute in the tooltip",
description: t("elementHighlighter.settings.showId"),
type: OptionType.BOOLEAN,
default: false
},
showFont: {
description: "Display the computed font family and font size",
description: t("elementHighlighter.settings.showFont"),
type: OptionType.BOOLEAN,
default: false
},
showPadding: {
description: "Display the element's padding values",
description: t("elementHighlighter.settings.showPadding"),
type: OptionType.BOOLEAN,
default: false
},
showMargin: {
description: "Display the element's margin values",
description: t("elementHighlighter.settings.showMargin"),
type: OptionType.BOOLEAN,
default: false
},
showBorderRadius: {
description: "Display the element's border-radius values",
description: t("elementHighlighter.settings.showBorderRadius"),
type: OptionType.BOOLEAN,
default: false
},
showPosition: {
description: "Display the element's CSS position type and z-index",
description: t("elementHighlighter.settings.showPosition"),
type: OptionType.BOOLEAN,
default: false
},
showDisplay: {
description: "Display the element's display type along with flex or grid properties",
description: t("elementHighlighter.settings.showDisplay"),
type: OptionType.BOOLEAN,
default: false
}
@ -354,7 +355,7 @@ function onClick(e: MouseEvent) {
if (!el || el === overlay || el === tooltip) return;
const colorVar = getColorVar(el);
if (colorVar) copyWithToast(colorVar, "Copied color to clipboard!");
if (colorVar) copyWithToast(colorVar, t("elementHighlighter.ui.copiedColor"));
}
function onKeyDown(e: KeyboardEvent) {
@ -448,7 +449,7 @@ function onToggle(e: KeyboardEvent) {
export default definePlugin({
name: "ElementHighlighter",
description: "Highlight and inspect elements easily.",
description: t("elementHighlighter.description"),
authors: [Devs.prism],
settings,

View file

@ -11,6 +11,7 @@ import { definePluginSettings } from "@api/Settings";
import customRPC from "@plugins/customRPC";
import { Devs, EquicordDevs, GUILD_ID, SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_IDS, VC_SUPPORT_CHANNEL_IDS } from "@utils/constants";
import { isAnyPluginDev } from "@utils/misc";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { StandingState } from "@vencord/discord-types/enums";
import { findByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
@ -28,11 +29,11 @@ const WarningIcon = findExportedComponentLazy("WarningIcon");
const ShieldIcon = findExportedComponentLazy("ShieldIcon");
const StandingConfig: Record<number, { label: string; hoverColor: string; Icon: ComponentType<any>; }> = {
[StandingState.ALL_GOOD]: { label: "All good!", hoverColor: "var(--status-positive)", Icon: ShieldIcon },
[StandingState.LIMITED]: { label: "Limited", hoverColor: "var(--status-warning)", Icon: WarningIcon },
[StandingState.VERY_LIMITED]: { label: "Very limited", hoverColor: "var(--orange-345)", Icon: WarningIcon },
[StandingState.AT_RISK]: { label: "At risk", hoverColor: "var(--status-danger)", Icon: WarningIcon },
[StandingState.SUSPENDED]: { label: "Suspended", hoverColor: "var(--interactive-muted)", Icon: WarningIcon },
[StandingState.ALL_GOOD]: { label: t("equicordHelper.standing.allGood"), hoverColor: "var(--status-positive)", Icon: ShieldIcon },
[StandingState.LIMITED]: { label: t("equicordHelper.standing.limited"), hoverColor: "var(--status-warning)", Icon: WarningIcon },
[StandingState.VERY_LIMITED]: { label: t("equicordHelper.standing.veryLimited"), hoverColor: "var(--orange-345)", Icon: WarningIcon },
[StandingState.AT_RISK]: { label: t("equicordHelper.standing.atRisk"), hoverColor: "var(--status-danger)", Icon: WarningIcon },
[StandingState.SUSPENDED]: { label: t("equicordHelper.standing.suspended"), hoverColor: "var(--interactive-muted)", Icon: WarningIcon },
};
function StandingButton() {
@ -61,42 +62,42 @@ function StandingButton() {
const settings = definePluginSettings({
noMirroredCamera: {
type: OptionType.BOOLEAN,
description: "Prevents the camera from being mirrored on your screen",
description: t("equicordHelper.settings.noMirroredCamera"),
restartNeeded: true,
default: false,
},
removeActivitySection: {
type: OptionType.BOOLEAN,
description: "Removes the activity section above member list",
description: t("equicordHelper.settings.removeActivitySection"),
restartNeeded: true,
default: false,
},
showYourOwnActivityButtons: {
type: OptionType.BOOLEAN,
description: "Discord hides your own activity buttons for some reason",
description: t("equicordHelper.settings.showYourOwnActivityButtons"),
restartNeeded: true,
default: false,
},
noDefaultHangStatus: {
type: OptionType.BOOLEAN,
description: "Disable the default hang status when joining voice channels",
description: t("equicordHelper.settings.noDefaultHangStatus"),
restartNeeded: true,
default: false,
},
refreshSlashCommands: {
type: OptionType.BOOLEAN,
description: "Refreshes Slash Commands to show newly added commands without restarting your client.",
description: t("equicordHelper.settings.refreshSlashCommands"),
default: false,
},
forceRoleIcon: {
type: OptionType.BOOLEAN,
description: "Forces role icons to display next to messages in compact mode",
description: t("equicordHelper.settings.forceRoleIcon"),
restartNeeded: true,
default: false
},
accountStandingButton: {
type: OptionType.BOOLEAN,
description: "Show an account standing button in the header bar",
description: t("equicordHelper.settings.accountStandingButton"),
default: false,
restartNeeded: true,
}
@ -104,7 +105,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "EquicordHelper",
description: "Used to provide support, fix discord caused crashes, and other misc features.",
description: t("equicordHelper.description"),
authors: [Devs.thororen, EquicordDevs.nyx, EquicordDevs.Naibuu, EquicordDevs.keircn, EquicordDevs.SerStars, EquicordDevs.mart],
required: true,
settings,
@ -218,13 +219,13 @@ export default definePlugin({
if (!selfId || isAnyPluginDev(selfId)) return;
if (VC_SUPPORT_CHANNEL_IDS.includes(channelId) && !clicked) {
return Alerts.show({
title: "Vencord Support Channel Warning",
body: "Before asking for help. Check updates and if this issue is actually caused by Equicord!",
confirmText: "Equicord Support",
title: t("equicordHelper.alerts.supportWarningTitle"),
body: t("equicordHelper.alerts.supportWarningBody"),
confirmText: t("equicordHelper.alerts.equicordSupport"),
onConfirm() {
NavigationRouter.transitionTo(`/channels/${GUILD_ID}/${SUPPORT_CHANNEL_ID}`);
},
cancelText: "Okay continue",
cancelText: t("equicordHelper.alerts.okayContinue"),
onCancel() {
clicked = true;
},
@ -235,17 +236,17 @@ export default definePlugin({
commands: [
{
name: "refresh-commands",
description: "Refresh Slash Commands",
description: t("equicordHelper.commands.refreshCommands"),
inputType: ApplicationCommandInputType.BUILT_IN,
predicate: () => settings.store.refreshSlashCommands,
execute: async (opts, ctx) => {
try {
ApplicationCommandIndexStore.indices = {};
sendBotMessage(ctx.channel.id, { content: "Slash Commands refreshed successfully." });
sendBotMessage(ctx.channel.id, { content: t("equicordHelper.commands.refreshSuccess") });
}
catch (e) {
console.error("[refreshSlashCommands] Failed to refresh commands:", e);
sendBotMessage(ctx.channel.id, { content: "Failed to refresh commands. Check console for details." });
sendBotMessage(ctx.channel.id, { content: t("equicordHelper.commands.refreshFailed") });
}
}
}

View file

@ -21,6 +21,7 @@ import "./styles.css";
import { HeaderBarButton } from "@api/HeaderBar";
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Popout, useRef, useState } from "@webpack/common";
@ -30,7 +31,7 @@ export const settings = definePluginSettings({
showPluginMenu: {
type: OptionType.BOOLEAN,
default: true,
description: "Show the plugins menu in the toolbox",
description: t("equicordToolbox.settings.showPluginMenu"),
}
});
@ -64,7 +65,7 @@ function VencordPopoutButton() {
ref={buttonRef}
className="vc-toolbox-btn"
onClick={() => setShow(v => !v)}
tooltip={isShown ? null : "Equicord Toolbox"}
tooltip={isShown ? null : t("equicordToolbox.ui.tooltip")}
icon={Icon}
selected={isShown}
/>
@ -76,7 +77,7 @@ function VencordPopoutButton() {
migratePluginSettings("EquicordToolbox", "VencordToolbox");
export default definePlugin({
name: "EquicordToolbox",
description: "Adds a button next to the inbox button in the channel header that houses Equicord quick actions",
description: t("equicordToolbox.description"),
authors: [Devs.Ven, Devs.AutumnVN],
dependencies: ["HeaderBarAPI"],

View file

@ -10,6 +10,7 @@ import { Settings, useSettings } from "@api/Settings";
import { openPluginModal, openSettingsTabModal, PluginsTab, ThemesTab } from "@components/settings";
import { useAwaiter } from "@utils/react";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { t } from "@utils/translation";
import { OptionType, Plugin } from "@utils/types";
import { Menu, showToast, useMemo, useState } from "@webpack/common";
import type { ReactNode } from "react";
@ -27,7 +28,7 @@ function buildPluginMenu() {
return (
<Menu.MenuItem
id="plugins"
label="Plugins"
label={t("equicordToolbox.ui.plugins")}
action={() => openSettingsTabModal(PluginsTab)}
>
{pluginEntries}
@ -103,7 +104,7 @@ export function buildPluginMenuEntries(includeEmpty = false) {
checked={s[key]}
action={() => {
s[key] = !s[key];
if (option.restartNeeded) showToast("Restart to apply the change");
if (option.restartNeeded) showToast(t("equicordToolbox.ui.restartToApply"));
}}
/>
);
@ -120,7 +121,7 @@ export function buildPluginMenuEntries(includeEmpty = false) {
checked={s[key] === opt.value}
action={() => {
s[key] = opt.value;
if (option.restartNeeded) showToast("Restart to apply the change");
if (option.restartNeeded) showToast(t("equicordToolbox.ui.restartToApply"));
}}
/>
))}
@ -188,7 +189,7 @@ export function buildThemeMenu() {
return (
<Menu.MenuItem
id="themes"
label="Themes"
label={t("equicordToolbox.ui.themes")}
action={() => openSettingsTabModal(ThemesTab)}
>
{buildThemeMenuEntries()}
@ -205,19 +206,19 @@ export function buildThemeMenuEntries() {
<Menu.MenuCheckboxItem
id="toggle-quickcss"
checked={useQuickCss}
label={"Enable QuickCSS"}
label={t("equicordToolbox.ui.enableQuickCss")}
action={() => {
Settings.useQuickCss = !useQuickCss;
}}
/>
<Menu.MenuItem
id="edit-quickcss"
label="Edit QuickCSS"
label={t("equicordToolbox.ui.editQuickCss")}
action={() => VencordNative.quickCss.openEditor()}
/>
<Menu.MenuItem
id="manage-themes"
label="Manage Themes"
label={t("equicordToolbox.ui.manageThemes")}
action={() => openSettingsTabModal(ThemesTab)}
/>
{!!themes?.length && (
@ -304,7 +305,7 @@ export function renderPopout(onClose: () => void) {
>
<Menu.MenuItem
id="notifications"
label="Open Notification Log"
label={t("equicordToolbox.ui.openNotificationLog")}
action={openNotificationLogModal}
/>

View file

@ -7,6 +7,7 @@
import { AudioPlayerInterface, createAudioPlayer } from "@api/AudioPlayer";
import { definePluginSettings } from "@api/Settings";
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
let clickCount = 0;
@ -25,14 +26,14 @@ function play() {
const settings = definePluginSettings({
amount: {
type: OptionType.NUMBER,
description: "amount of clicks to trigger crossant",
description: t("equissant.settings.amount"),
default: 10,
}
});
export default definePlugin({
name: "Equissant",
description: "Crossant every specified amount of clicks :trolley:",
description: t("equissant.description"),
authors: [EquicordDevs.SomeAspy, Devs.thororen],
dependencies: ["AudioPlayerAPI"],
settings,

View file

@ -12,6 +12,7 @@ import { definePluginSettings } from "@api/Settings";
import { Button } from "@components/Button";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { ChannelActions, Constants, GuildStore, IconUtils, MediaEngineStore, Menu, RestAPI, SearchableSelect, SelectedChannelStore, TextInput, Toasts } from "@webpack/common";
@ -31,7 +32,7 @@ function GuildSelector() {
<SearchableSelect
options={options}
value={options.find(o => o.value === soundGuildId)}
placeholder="Select a server..."
placeholder={t("exitSounds.ui.selectServer")}
maxVisibleItems={6}
closeOnSelect={true}
onChange={v => settings.store.soundGuildId = v}
@ -58,7 +59,7 @@ function SoundIdInput() {
<TextInput
value={soundId}
onChange={v => settings.store.soundId = v}
placeholder="Enter sound ID..."
placeholder={t("exitSounds.ui.enterSoundId")}
/>
</div>
<Button
@ -74,12 +75,12 @@ function SoundIdInput() {
const settings = definePluginSettings({
soundGuildId: {
type: OptionType.COMPONENT,
description: "Select the server containing the sound.",
description: t("exitSounds.settings.soundGuildId"),
component: GuildSelector
},
soundId: {
type: OptionType.COMPONENT,
description: "Enter the ID of the sound you want to play.",
description: t("exitSounds.settings.soundId"),
component: SoundIdInput
}
});
@ -89,7 +90,7 @@ const SoundButtonContext: NavContextMenuPatchCallback = (children, { sound }: {
<Menu.MenuGroup>
<Menu.MenuItem
id="set-global-exit-sound"
label="Set as global exit sound"
label={t("exitSounds.ui.setGlobalExitSound")}
action={() => {
settings.store.soundGuildId = sound.guildId;
settings.store.soundId = sound.soundId;
@ -103,7 +104,7 @@ let original: typeof ChannelActions.selectVoiceChannel;
export default definePlugin({
name: "ExitSounds",
description: "Play soundboard sounds when you disconnect from voice.",
description: t("exitSounds.description"),
authors: [Devs.prism],
dependencies: ["AudioPlayerAPI"],
settings,
@ -130,7 +131,7 @@ export default definePlugin({
await new Promise(r => setTimeout(r, 500));
} catch {
Toasts.show({
message: "Oops! Something went wrong.",
message: t("exitSounds.toasts.somethingWentWrong"),
id: Toasts.genId(),
type: Toasts.Type.FAILURE
});

View file

@ -12,6 +12,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { copyToClipboard } from "@utils/clipboard";
import { EquicordDevs } from "@utils/constants";
import { showItemInFolder } from "@utils/native";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { saveFile } from "@utils/web";
import { Message } from "@vencord/discord-types";
@ -22,12 +23,12 @@ import { ContactsList } from "./types";
const settings = definePluginSettings({
openFileAfterExport: {
type: OptionType.BOOLEAN,
description: "Open the exported file in the default file handler after export",
description: t("exportMessages.settings.openFileAfterExport"),
default: true
},
exportContacts: {
type: OptionType.BOOLEAN,
description: "Export a list of friends to your clipboard. Adds a new button to the menu bar for the friends tab.",
description: t("exportMessages.settings.exportContacts"),
default: false
}
});
@ -81,14 +82,14 @@ async function exportMessage(message: Message) {
}
showNotification({
title: "Export Messages",
body: `Message exported successfully as ${filename}`,
title: t("exportMessages.notifications.title"),
body: t("exportMessages.notifications.exportSuccess", { filename }),
icon: "📄"
});
} catch (error) {
showNotification({
title: "Export Messages",
body: "Failed to export message",
title: t("exportMessages.notifications.title"),
body: t("exportMessages.notifications.exportFailed"),
icon: "❌"
});
}
@ -102,7 +103,7 @@ const messageContextMenuPatch = (children: Array<React.ReactElement<any> | null>
children.push(
<Menu.MenuItem
id="export-message"
label="Export Message"
label={t("exportMessages.ui.exportMessage")}
icon={() => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z" />
@ -128,7 +129,7 @@ function getUsernames(contacts: ContactsList[], type: number): string[] {
export default definePlugin({
name: "ExportMessages",
description: "Allows you to export any message to a file",
description: t("exportMessages.description"),
authors: [EquicordDevs.veygax, EquicordDevs.dat_insanity],
settings,
contextMenus: {
@ -165,14 +166,14 @@ export default definePlugin({
},
addExportButton() {
return <ErrorBoundary noop key=".2">
<button className="export-contacts-button" onClick={() => { this.copyContactToClipboard(); console.log("clicked"); }}>Export</button>
<button className="export-contacts-button" onClick={() => { this.copyContactToClipboard(); console.log("clicked"); }}>{t("exportMessages.ui.export")}</button>
</ErrorBoundary>;
},
copyContactToClipboard() {
if (this.contactList) {
copyToClipboard(JSON.stringify(this.contactList));
Toasts.show({
message: "Contacts copied to clipboard successfully.",
message: t("exportMessages.ui.contactsCopied"),
type: Toasts.Type.SUCCESS,
id: Toasts.genId(),
options: {
@ -182,11 +183,8 @@ export default definePlugin({
});
return;
}
// reason why you need to click the all tab is because the data is extracted during
// the request itself when you fetch all your friends. this is done to avoid sending a
// manual request to discord, which may raise suspicion and might even get you terminated.
Toasts.show({
message: "Contact list is undefined. Click on the \"All\" tab before exporting.",
message: t("exportMessages.ui.contactListUndefined"),
type: Toasts.Type.FAILURE,
id: Toasts.genId(),
options: {

View file

@ -6,6 +6,7 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Constants, PermissionsBits, PermissionStore, React, RestAPI, useCallback, useEffect, useState } from "@webpack/common";
@ -49,23 +50,23 @@ function showIcon() {
// TY ToggleVideoBind
const settings = definePluginSettings({
keyBind: {
description: "The key to toggle trash when pressed.",
description: t("fastDeleteChannels.settings.keyBind"),
type: OptionType.STRING,
default: "KeyZ",
isValid: (value: string) => validKeycodes.includes(value),
},
reqCtrl: {
description: "Require control to be held.",
description: t("fastDeleteChannels.settings.reqCtrl"),
type: OptionType.BOOLEAN,
default: true,
},
reqShift: {
description: "Require shift to be held.",
description: t("fastDeleteChannels.settings.reqShift"),
type: OptionType.BOOLEAN,
default: true,
},
reqAlt: {
description: "Require alt to be held.",
description: t("fastDeleteChannels.settings.reqAlt"),
type: OptionType.BOOLEAN,
default: false,
},
@ -73,7 +74,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "FastDeleteChannels",
description: "Adds a trash icon to delete channels",
description: t("fastDeleteChannels.description"),
authors: [Devs.thororen],
settings,
patches: [

View file

@ -5,6 +5,7 @@
*/
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Embed } from "@vencord/discord-types";
import { findComponentByCodeLazy, findCssClassesLazy, proxyLazyWebpack } from "@webpack";
@ -44,7 +45,7 @@ const EmbedContext = proxyLazyWebpack(() => React.createContext<null | Embed>(nu
export default definePlugin({
name: "FavouriteAnything",
description: "Favourite any image",
description: t("favouriteAnything.description"),
authors: [Devs.nin0dev, EquicordDevs.davri],
patches: [
{

View file

@ -6,6 +6,7 @@
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Menu } from "@webpack/common";
@ -31,7 +32,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
group.push(
<Menu.MenuItem
label={`Upload to ${serviceName}`}
label={t("fileUpload.ui.uploadTo", { serviceName })}
key="file-upload"
id="file-upload"
action={() => uploadFile(url)}
@ -56,7 +57,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =>
children.push(
<Menu.MenuGroup id="file-upload-group">
<Menu.MenuItem
label={`Upload to ${serviceName}`}
label={t("fileUpload.ui.uploadTo", { serviceName })}
key="file-upload"
id="file-upload"
action={() => uploadFile(url)}
@ -67,7 +68,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =>
export default definePlugin({
name: "FileUpload",
description: "Upload images and videos to file hosting services like Zipline and Nest",
description: t("fileUpload.description"),
authors: [EquicordDevs.creations, EquicordDevs.keircn],
settings,
contextMenus: {

View file

@ -19,6 +19,7 @@
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack";
@ -68,19 +69,19 @@ function findReplies(message: Message) {
const settings = definePluginSettings({
includePings: {
type: OptionType.BOOLEAN,
description: "Will also search for messages that @ the author directly",
description: t("findReply.settings.includePings"),
default: false,
restartNeeded: false
},
includeAuthor: {
type: OptionType.BOOLEAN,
description: "Will also search for messages that reply to the author in general, not just that exact message",
description: t("findReply.settings.includeAuthor"),
default: false,
restartNeeded: false
},
hideButtonIfNoReply: {
type: OptionType.BOOLEAN,
description: "Hides the button if there are no replies to the message",
description: t("findReply.settings.hideButtonIfNoReply"),
default: true,
restartNeeded: true
}
@ -88,7 +89,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "FindReply",
description: "Jumps to the earliest reply to a message in a channel (lets you follow past conversations more easily).",
description: t("findReply.description"),
authors: [Devs.newwares],
settings,
messagePopoverButton: {
@ -98,7 +99,7 @@ export default definePlugin({
const replies = findReplies(message);
if (settings.store.hideButtonIfNoReply && !replies.length) return null;
return {
label: "Jump to Reply",
label: t("findReply.ui.jumpToReply"),
icon: FindReplyIcon,
message,
channel: ChannelStore.getChannel(message.channel_id),
@ -115,14 +116,14 @@ export default definePlugin({
if (replies.length > 1) {
Toasts.show({
id: Toasts.genId(),
message: "Use the bottom panel to navigate between replies.",
message: t("findReply.toasts.useBottomPanel"),
type: Toasts.Type.MESSAGE
});
const container = document.querySelector("[class*=channelBottomBarArea_]");
if (!container) {
Toasts.show({
id: Toasts.genId(),
message: "Couldn't find the container element.",
message: t("findReply.toasts.containerNotFound"),
type: Toasts.Type.FAILURE
});
return;
@ -139,7 +140,7 @@ export default definePlugin({
} else {
Toasts.show({
id: Toasts.genId(),
message: "Couldn't find a reply.",
message: t("findReply.toasts.noReplyFound"),
type: Toasts.Type.FAILURE
});
}

View file

@ -7,6 +7,7 @@
import { isPluginEnabled } from "@api/PluginManager";
import anonymiseFileNames, { tarExtMatcher } from "@plugins/anonymiseFileNames";
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { ReporterTestable } from "@utils/types";
import { CloudUpload } from "@vencord/discord-types";
@ -28,7 +29,7 @@ export const reverseExtensionMap = Object.entries(extensionMap).reduce((acc, [ta
export default definePlugin({
name: "FixFileExtensions",
authors: [Devs.thororen],
description: "Fixes file extensions by renaming them to a compatible supported format if possible",
description: t("fixFileExtensions.description"),
reporterTestable: ReporterTestable.None,
patches: [
// Taken from AnonymiseFileNames

View file

@ -8,6 +8,7 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { Notice } from "@components/Notice";
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Channel, User, VoiceState } from "@vencord/discord-types";
import { findByPropsLazy, findStoreLazy } from "@webpack";
@ -34,12 +35,12 @@ const settings = definePluginSettings({
onlyWhenInVoice: {
type: OptionType.BOOLEAN,
default: true,
description: "Only follow the user when you are in a voice channel"
description: t("followVoiceUser.settings.onlyWhenInVoice")
},
leaveWhenUserLeaves: {
type: OptionType.BOOLEAN,
default: false,
description: "Leave the voice channel when the user leaves. (That can cause you to sometimes enter infinite leave/join loop)"
description: t("followVoiceUser.settings.leaveWhenUserLeaves")
}
});
@ -52,7 +53,7 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { channel,
<Menu.MenuSeparator />,
<Menu.MenuCheckboxItem
id="fvu-follow-user"
label="Follow User"
label={t("followVoiceUser.ui.followUser")}
checked={checked}
action={() => {
if (followedUserInfo?.userId === user.id) {
@ -73,12 +74,12 @@ const UserContextMenuPatch: NavContextMenuPatchCallback = (children, { channel,
export default definePlugin({
name: "FollowVoiceUser",
description: "Follow a friend in voice chat.",
description: t("followVoiceUser.description"),
authors: [EquicordDevs.TheArmagan],
settings,
settingsAboutComponent: () => (
<Notice.Info>
This Plugin is used to follow a Friend/Friends into voice chat(s).
{t("followVoiceUser.info")}
</Notice.Info>
),
flux: {

View file

@ -14,6 +14,7 @@ import { debounce } from "@shared/debounce";
import { EquicordDevs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { React, TextInput } from "@webpack/common";
@ -134,13 +135,13 @@ function GoogleFontSearch({ onSelect }: { onSelect: (font: GoogleFontMetadata) =
return (
<section>
<HeadingSecondary>Search Google Fonts</HeadingSecondary>
<Paragraph>Click on any font to apply it.</Paragraph>
<HeadingSecondary>{t("fontLoader.ui.searchGoogleFonts")}</HeadingSecondary>
<Paragraph>{t("fontLoader.ui.clickToApply")}</Paragraph>
<TextInput
value={query}
onChange={e => handleSearch(e)}
placeholder="Search fonts..."
placeholder={t("fontLoader.ui.searchFonts")}
disabled={loading}
className={Margins.bottom16}
/>
@ -159,7 +160,7 @@ function GoogleFontSearch({ onSelect }: { onSelect: (font: GoogleFontMetadata) =
</div>
{font.authors?.length && (
<Paragraph className={Margins.top8} style={{ opacity: 0.7 }}>
by {font.authors.join(", ")}
{t("fontLoader.ui.by", { authors: font.authors.join(", ") })}
</Paragraph>
)}
</Card>
@ -192,14 +193,14 @@ const settings = definePluginSettings({
},
applyOnCodeBlocks: {
type: OptionType.BOOLEAN,
description: "Apply the font to code blocks",
description: t("fontLoader.settings.applyOnCodeBlocks"),
default: false
}
});
export default definePlugin({
name: "FontLoader",
description: "Loads any font from Google Fonts",
description: t("fontLoader.description"),
authors: [EquicordDevs.vmohammad],
settings,

View file

@ -7,24 +7,25 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { sendMessage } from "@utils/discord";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Channel, Message } from "@vencord/discord-types";
// Taken From Signature :)
const settings = definePluginSettings({
forwardPreface: {
description: "What should forwarded from be prefaced with",
description: t("forwardAnywhere.settings.forwardPreface"),
type: OptionType.SELECT,
options: [
{ label: ">", value: ">", default: true },
{ label: "-#", value: "-#" }
{ label: t("forwardAnywhere.options.greaterThan"), value: ">", default: true },
{ label: t("forwardAnywhere.options.hash"), value: "-#" }
]
}
});
export default definePlugin({
name: "ForwardAnywhere",
description: "If a forward fails send it as a normal message also allows nsfw forwards",
description: t("forwardAnywhere.description"),
authors: [Devs.thororen],
settings,
patches: [

View file

@ -5,6 +5,7 @@
*/
import { Devs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { ChannelStore, UserSettingsActionCreators } from "@webpack/common";
@ -36,7 +37,7 @@ function generateSearchResults(query) {
export default definePlugin({
name: "FrequentQuickSwitcher",
description: "Rewrites and filters the quick switcher results to be your most frequent channels",
description: t("frequentQuickSwitcher.description"),
authors: [Devs.Samwich],
generateSearchResults: generateSearchResults,
patches: [

View file

@ -19,6 +19,7 @@
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
import { EquicordDevs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { User } from "@vencord/discord-types";
import { findStore } from "@webpack";
@ -51,17 +52,17 @@ interface UserPosition {
export default definePlugin({
name: "FriendCloud",
description: "Adds a /friendcloud command to visualize the users you most interact with",
description: t("friendCloud.description"),
authors: [EquicordDevs.Fafa],
commands: [
{
inputType: ApplicationCommandInputType.BUILT_IN,
name: "friendcloud",
description: "Display user you most interact with in a cloud",
description: t("friendCloud.commands.friendcloud"),
options: [
{
name: "count",
description: "Number of users to display",
description: t("friendCloud.commands.count"),
type: ApplicationCommandOptionType.NUMBER,
required: false
}
@ -69,14 +70,14 @@ export default definePlugin({
execute: async (opts, cmdCtx) => {
const count = findOption(opts, "count", 25);
if (!count) return sendBotMessage(cmdCtx.channel.id, { content: "The count must be 1 or higher!" });
if (!count) return sendBotMessage(cmdCtx.channel.id, { content: t("friendCloud.errors.countMustBeHigher") });
try {
const affinities: AffinitiesV2[] = findStore("UserAffinitiesV2Store").getUserAffinities();
if (!affinities?.length) {
return sendBotMessage(cmdCtx.channel.id, {
content: "No affinities found. Check your [privacy settings](<https://support.discord.com/hc/en-us/articles/21864805694999-Data-Used-to-Improve-Discord>)."
content: t("friendCloud.errors.noAffinities")
});
}
@ -91,7 +92,7 @@ export default definePlugin({
if (!users.length) {
return sendBotMessage(cmdCtx.channel.id, {
content: "No valid users found in affinities. Check your [privacy settings](<https://support.discord.com/hc/en-us/articles/21864805694999-Data-Used-to-Improve-Discord>)."
content: t("friendCloud.errors.noValidUsers")
});
}
@ -153,7 +154,7 @@ export default definePlugin({
if (loadedImages === totalImages) {
canvas.toBlob(blob => {
if (!blob) {
sendBotMessage(cmdCtx.channel.id, { content: "Couldn't generate the image :c" });
sendBotMessage(cmdCtx.channel.id, { content: t("friendCloud.errors.couldntGenerate") });
return;
}
const file = new File([blob], "affinities-cloud.png", { type: "image/png" });

View file

@ -13,6 +13,7 @@ import { BaseText } from "@components/BaseText";
import { Divider } from "@components/Divider";
import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/react";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Button, ChannelStore, Menu, RelationshipStore, TextInput, useEffect, UserStore, useState } from "@webpack/common";
@ -99,12 +100,12 @@ function TagConfigCard(props) {
return (
<>
<BaseText size="md" tag="h5">Name</BaseText>
<BaseText size="md" tag="h5">{t("friendTags.ui.name")}</BaseText>
<TextInput value={tagName} onChange={setTagName}></TextInput>
<BaseText size="md" tag="h5">Users (Seperated by comma)</BaseText>
<BaseText size="md" tag="h5">{t("friendTags.ui.usersSeperated")}</BaseText>
<TextInput value={userIds} onChange={setUserIDs}></TextInput>
<div className={"vc-friend-tags-user-header-container"}>
<BaseText>User List (Click A User To Remove)</BaseText>
<BaseText>{t("friendTags.ui.userList")}</BaseText>
<div className={"vc-friend-tags-user-header-btns"}>
{
userIds.split(", ").map(user => {
@ -128,7 +129,7 @@ function TagConfigCard(props) {
}}
color={Button.Colors.RED}
>
Remove
{t("friendTags.ui.remove")}
</Button>
</>
);
@ -156,7 +157,7 @@ function TagConfigurationComponent() {
});
SetData();
update();
}}>Add</Button>
}}>{t("friendTags.ui.add")}</Button>
</>
);
}
@ -187,14 +188,14 @@ const userPatch: NavContextMenuPatchCallback = (children, { user }) => {
const buttonElement =
<Menu.MenuItem
id="vc-tag-group"
label="Tag"
label={t("friendTags.ui.tag")}
>
{SavedData.map(tag => {
const isTagged = SavedData.filter(e => e.tagName === tag.tagName)[0].userIds.includes(user.id);
return (
<Menu.MenuItem
label={`${isTagged ? "Remove from" : "Add to"} ${tag.tagName}`}
label={isTagged ? t("friendTags.ui.removeFrom", { tagName: tag.tagName }) : t("friendTags.ui.addTo", { tagName: tag.tagName })}
key={`vc-tag-${tag.tagName}`}
id={`vc-tag-${tag.tagName}`}
action={() => { UserToTagID(user.id, tag.tagName, isTagged); }}
@ -208,7 +209,7 @@ const userPatch: NavContextMenuPatchCallback = (children, { user }) => {
export default definePlugin({
name: "FriendTags",
description: "Allows you to filter by custom tags in the quick switcher by starting a search with &",
description: t("friendTags.description"),
authors: [Devs.Samwich],
settings,
contextMenus: {

View file

@ -14,6 +14,7 @@ import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
import { ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { Forms, RelationshipStore } from "@webpack/common";
@ -40,44 +41,44 @@ function daysSince(dateString: string): number {
const ranks: rankInfo[] =
[
{
title: "Sprout",
description: "Your friendship is just starting",
title: t("friendshipRanks.ranks.sprout.title"),
description: t("friendshipRanks.ranks.sprout.description"),
requirement: 0,
iconSrc: "https://equicord.org/assets/plugins/friendshipRanks/sprout.png"
},
{
title: "Blooming",
description: "Your friendship is getting there! (1 Month)",
title: t("friendshipRanks.ranks.blooming.title"),
description: t("friendshipRanks.ranks.blooming.description"),
requirement: 30,
iconSrc: "https://equicord.org/assets/plugins/friendshipRanks/blooming.png"
},
{
title: "Burning",
description: "Your friendship has reached terminal velocity (3 Months)",
title: t("friendshipRanks.ranks.burning.title"),
description: t("friendshipRanks.ranks.burning.description"),
requirement: 90,
iconSrc: "https://equicord.org/assets/plugins/friendshipRanks/burning.png"
},
{
title: "Fighter",
description: "Your friendship is strong (6 Months)",
title: t("friendshipRanks.ranks.fighter.title"),
description: t("friendshipRanks.ranks.fighter.description"),
requirement: 182.5,
iconSrc: "https://equicord.org/assets/plugins/friendshipRanks/fighter.png"
},
{
title: "Star",
description: "Your friendship has been going on for a WHILE (1 Year)",
title: t("friendshipRanks.ranks.star.title"),
description: t("friendshipRanks.ranks.star.description"),
requirement: 365,
iconSrc: "https://equicord.org/assets/plugins/friendshipRanks/star.png"
},
{
title: "Royal",
description: "Your friendship has gone through thick and thin- a whole 2 years!",
title: t("friendshipRanks.ranks.royal.title"),
description: t("friendshipRanks.ranks.royal.description"),
requirement: 730,
iconSrc: "https://equicord.org/assets/plugins/friendshipRanks/royal.png"
},
{
title: "Besties",
description: "How do you even manage this??? (5 Years)",
title: t("friendshipRanks.ranks.besties.title"),
description: t("friendshipRanks.ranks.besties.description"),
requirement: 1826.25,
iconSrc: "https://equicord.org/assets/plugins/friendshipRanks/besties.png"
}
@ -142,7 +143,7 @@ function getBadgesToApply() {
export default definePlugin({
name: "FriendshipRanks",
description: "Adds badges showcasing how long you have been friends with a user for",
description: t("friendshipRanks.description"),
authors: [Devs.Samwich],
start() {
getBadgesToApply().forEach(b => Badges.addProfileBadge(b));

View file

@ -6,6 +6,7 @@
import { disableStyle, enableStyle } from "@api/Styles";
import { EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin from "@utils/types";
import { ChannelRTCStore, IconUtils, UserStore, VoiceStateStore } from "@webpack/common";
@ -13,7 +14,7 @@ import style from "./style.css?managed";
export default definePlugin({
name: "FullVCPFP",
description: "Makes avatars take up the entire vc tile",
description: t("fullVcPfp.description"),
authors: [EquicordDevs.mochienya],
patches: [
{

View file

@ -7,6 +7,7 @@
import { definePluginSettings } from "@api/Settings";
import { Paragraph } from "@components/Paragraph";
import { Devs, EquicordDevs } from "@utils/constants";
import { t } from "@utils/translation";
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
import { Activity } from "@vencord/discord-types";
import { ActivityFlags, ActivityType } from "@vencord/discord-types/enums";
@ -31,7 +32,7 @@ function getImageAsset(data: string) {
const settings = definePluginSettings({
refreshInterval: {
type: OptionType.SLIDER,
description: "The interval between activity refreshes (seconds)",
description: t("gensokyoRadioRPC.settings.refreshInterval"),
markers: [1, 2, 2.5, 3, 5, 10, 15],
default: 15,
restartNeeded: true,
@ -42,14 +43,14 @@ let updateInterval: NodeJS.Timeout | undefined;
export default definePlugin({
name: "GensokyoRadioRPC",
description: "Discord rich presence for Gensokyo Radio!",
description: t("gensokyoRadioRPC.description"),
authors: [Devs.RyanCaoDev, EquicordDevs.Prince527],
reporterTestable: ReporterTestable.None,
settingsAboutComponent() {
return (
<Paragraph>
Discord rich presence for Gensokyo Radio!
{t("gensokyoRadioRPC.info")}
</Paragraph>
);
},

View file

@ -13,6 +13,7 @@ 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";
@ -26,30 +27,30 @@ export const cl = classNameFactory("vc-boo-");
export const settings = definePluginSettings({
showIndicator: {
type: OptionType.BOOLEAN,
description: "Show the ghost counter at the top of the server list",
description: t("ghosted.settings.showIndicator"),
default: true,
restartNeeded: false
},
showDmIcons: {
type: OptionType.BOOLEAN,
description: "Show ghost icons next to individual DMs",
description: t("ghosted.settings.showDmIcons"),
default: true,
restartNeeded: false
},
ignoreGroupDms: {
type: OptionType.BOOLEAN,
description: "Exclude all group dms from ghosting",
description: t("ghosted.settings.ignoreGroupDms"),
default: false
},
exemptedChannels: {
type: OptionType.STRING,
description: "Comma-separated list of channel IDs to exempt from ghosting (right-click a DM channel to copy its ID)",
description: t("ghosted.settings.exemptedChannels"),
default: "",
restartNeeded: false
},
ignoreBots: {
type: OptionType.BOOLEAN,
description: "Ignore DMs from bots",
description: t("ghosted.settings.ignoreBots"),
default: true,
restartNeeded: false
}
@ -88,14 +89,14 @@ function BooIndicator() {
const getTooltipText = () => {
const ghostedChannels = getGhostedChannels();
if (ghostedChannels.length === 0) {
return "No Ghosted Users";
return t("ghosted.ui.noGhostedUsers");
}
if (ghostedChannels.length <= 5) {
return ghostedChannels
.map(id => getChannelDisplayName(id))
.join(", ");
}
return `${ghostedChannels.length} Ghosted Users`;
return t("ghosted.ui.ghostedUsers", { count: ghostedChannels.length });
};
return (
@ -125,7 +126,7 @@ function makeContextItem(props) {
return <Menu.MenuItem
id="ec-ghosted-clear"
key="ec-ghosted-clear"
label="unghost"
label={t("ghosted.ui.unghost")}
action={() => {
clearChannelFromGhost(props.channel.id);
}}
@ -134,7 +135,7 @@ function makeContextItem(props) {
export default definePlugin({
name: "Ghosted",
description: "A cute ghost will appear if you don't answer their DMs",
description: t("ghosted.description"),
authors: [EquicordDevs.vei, Devs.sadan, EquicordDevs.justjxke, EquicordDevs.iamme],
settings,
dependencies: ["AudioPlayerAPI", "ServerListAPI"],

View file

@ -16,6 +16,7 @@ import { Paragraph } from "@components/Paragraph";
import { copyToClipboard } from "@utils/clipboard";
import { Devs, EquicordDevs } from "@utils/constants";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { t } from "@utils/translation";
import definePlugin, { OptionType } from "@utils/types";
import { Alerts, Button, ContextMenuApi, FluxDispatcher, Menu, React, showToast, TextInput, Toasts, useCallback, useState } from "@webpack/common";
@ -48,12 +49,12 @@ const addCollectionContextMenuPatch: NavContextMenuPatchCallback = (children, pr
if (settings.store.showCopyImageLink) {
group.push(
<Menu.MenuItem
label="Copy Image Link"
label={t("gifCollections.ui.copyImageLink")}
key="copy-image-link"
id="copy-image-link"
action={() => {
copyToClipboard(gif.url);
showToast("Image link copied to clipboard", Toasts.Type.SUCCESS);
showToast(t("gifCollections.ui.imageLinkCopied"), Toasts.Type.SUCCESS);
}}
/>
);
@ -61,7 +62,7 @@ const addCollectionContextMenuPatch: NavContextMenuPatchCallback = (children, pr
group.push(
<Menu.MenuItem
label="Add To Collection"
label={t("gifCollections.ui.addToCollection")}
key="add-to-collection"
id="add-to-collection"
>
@ -79,7 +80,7 @@ const addCollectionContextMenuPatch: NavContextMenuPatchCallback = (children, pr
<Menu.MenuItem
key="create-collection"
id="create-collection"
label="Create Collection"
label={t("gifCollections.ui.createCollection")}
action={() => {
openModal(modalProps => (
<CreateCollectionModal onClose={modalProps.onClose} gif={gif} modalProps={modalProps} />
@ -93,7 +94,7 @@ const addCollectionContextMenuPatch: NavContextMenuPatchCallback = (children, pr
export const settings = definePluginSettings({
itemPrefix: {
description: "The prefix for gif items",
description: t("gifCollections.settings.itemPrefix"),
type: OptionType.STRING,
default: "gc-item:",
onChange: value => {
@ -106,7 +107,7 @@ export const settings = definePluginSettings({
restartNeeded: true
},
collectionPrefix: {
description: "The prefix for collections",
description: t("gifCollections.settings.collectionPrefix"),
type: OptionType.STRING,
default: "gc:",
onChange: value => {
@ -119,28 +120,28 @@ export const settings = definePluginSettings({
restartNeeded: true
},
onlyShowCollections: {
description: "Only show collections",
description: t("gifCollections.settings.onlyShowCollections"),
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true
},
stopWarnings: {
description: "Stop deletion warnings",
description: t("gifCollections.settings.stopWarnings"),
type: OptionType.BOOLEAN,
default: false,
},
showCopyImageLink: {
description: "Show 'Copy Image Link' option in context menus",
description: t("gifCollections.settings.showCopyImageLink"),
type: OptionType.BOOLEAN,
default: false,
},
preventDuplicates: {
description: "Prevent adding the same GIF to a collection multiple times",
description: t("gifCollections.settings.preventDuplicates"),
type: OptionType.BOOLEAN,
default: false,
},
defaultEmptyCollectionImage: {
description: "The image / gif that will be shown when a collection has no images / gifs",
description: t("gifCollections.settings.defaultEmptyCollectionImage"),
type: OptionType.STRING,
default: "https://c.tenor.com/YEG33HsLEaIAAAAC/parksandrec-oops.gif"
},
@ -175,14 +176,14 @@ export const settings = definePluginSettings({
return (
<div className="collections-sort-container">
<Heading className="collections-sort-title">Sort Collections</Heading>
<Heading className="collections-sort-title">{t("gifCollections.ui.sortCollections")}</Heading>
<Divider className="collections-sort-divider" />
<Paragraph className="collections-sort-description">
Choose a sorting criteria for your collections
{t("gifCollections.ui.sortCollectionsDescription")}
</Paragraph>
<Divider className="collections-sort-divider" />
<div className="collections-sort-section">
<Paragraph className="collections-sort-section-title">Sort By</Paragraph>
<Paragraph className="collections-sort-section-title">{t("gifCollections.ui.sortBy")}</Paragraph>
<div className="collections-sort-option">
<label className="collections-sort-label">
<input
@ -193,7 +194,7 @@ export const settings = definePluginSettings({
onChange={() => handleSortTypeChange(SortingOptions.NAME)}
className="collections-sort-input"
/>
Name
{t("gifCollections.ui.name")}
</label>
</div>
<div className="collections-sort-option">
@ -206,7 +207,7 @@ export const settings = definePluginSettings({
onChange={() => handleSortTypeChange(SortingOptions.CREATION_DATE)}
className="collections-sort-input"
/>
Creation Date
{t("gifCollections.ui.creationDate")}
</label>
</div>
<div className="collections-sort-option">
@ -219,13 +220,13 @@ export const settings = definePluginSettings({
onChange={() => handleSortTypeChange(SortingOptions.MODIFIED_DATE)}
className="collections-sort-input"
/>
Modified Date
{t("gifCollections.ui.modifiedDate")}
</label>
</div>
</div>
<Divider className="collections-sort-divider" />
<div className="collections-sort-section">
<Paragraph className="collections-sort-section-title">Order</Paragraph>
<Paragraph className="collections-sort-section-title">{t("gifCollections.ui.order")}</Paragraph>
<div className="collections-sort-option">
<label className="collections-sort-label">
<input
@ -236,7 +237,7 @@ export const settings = definePluginSettings({
onChange={() => handleSortOrderChange("asc")}
className="collections-sort-input"
/>
Ascending
{t("gifCollections.ui.ascending")}
</label>
</div>
<div className="collections-sort-option">
@ -249,7 +250,7 @@ export const settings = definePluginSettings({
onChange={() => handleSortOrderChange("desc")}
className="collections-sort-input"
/>
Descending
{t("gifCollections.ui.descending")}
</label>
</div>
</div>
@ -259,52 +260,52 @@ export const settings = definePluginSettings({
},
importGifs: {
type: OptionType.COMPONENT,
description: "Import Collections",
description: t("gifCollections.ui.importCollections"),
component: () =>
<Button onClick={async () =>
(await getCollections()).length ? Alerts.show({
title: "Are you sure?",
body: "Importing collections will overwrite your current collections.",
confirmText: "Import",
title: t("gifCollections.ui.areYouSure"),
body: t("gifCollections.ui.importOverwrite"),
confirmText: t("gifCollections.ui.import"),
confirmColor: Button.Colors.RED,
cancelText: "Nevermind",
cancelText: t("gifCollections.ui.nevermind"),
onConfirm: async () => uploadGifCollections()
}) : uploadGifCollections()}>
Import Collections
{t("gifCollections.ui.importCollections")}
</Button>,
},
exportGifs: {
type: OptionType.COMPONENT,
description: "Export Collections",
description: t("gifCollections.ui.exportCollections"),
component: () =>
<Button onClick={downloadCollections}>
Export Collections
{t("gifCollections.ui.exportCollections")}
</Button>
},
resetCollections: {
type: OptionType.COMPONENT,
description: "Reset Collections",
description: t("gifCollections.ui.resetCollections"),
component: () =>
<Button onClick={() =>
Alerts.show({
title: "Are you sure?",
body: "Resetting collections will remove all your collections.",
confirmText: "Reset",
title: t("gifCollections.ui.areYouSure"),
body: t("gifCollections.ui.resetRemove"),
confirmText: t("gifCollections.ui.reset"),
confirmColor: Button.Colors.RED,
cancelText: "Nevermind",
cancelText: t("gifCollections.ui.nevermind"),
onConfirm: async () => {
await DataStore.set(DATA_COLLECTION_NAME, []);
refreshCacheCollection();
}
})}>
Reset Collections
{t("gifCollections.ui.resetCollections")}
</Button>
}
});
export default definePlugin({
name: "GifCollections",
description: "Allows you to create collections of gifs",
description: t("gifCollections.description"),
authors: [Devs.Aria, EquicordDevs.creations],
patches: [
{
@ -448,7 +449,7 @@ const RemoveItemContextMenu = ({ type, nameOrId, instance }) => (
<Menu.MenuItem
key="collection-information"
id="collection-information"
label="Collection Information"
label={t("gifCollections.ui.collectionInformation")}
action={() => {
const collection = cache_collections.find(c => c.name === nameOrId);
if (!collection) return;
@ -460,30 +461,30 @@ const RemoveItemContextMenu = ({ type, nameOrId, instance }) => (
className="custom-modal"
>
<ModalHeader separator={false} className="custom-modal-header">
<Paragraph className="custom-modal-title">Collection Information</Paragraph>
<Paragraph className="custom-modal-title">{t("gifCollections.ui.collectionInformation")}</Paragraph>
</ModalHeader>
<ModalContent className="custom-modal-content">
<section>
<Flex className="collection-info">
<Heading className="collection-info-title">Name</Heading>
<Heading className="collection-info-title">{t("gifCollections.ui.name")}</Heading>
<Paragraph className="collection-info-text">{collection.name.replace(/.+?:/, "")}</Paragraph>
</Flex>
<Flex className="collection-info">
<Heading className="collection-info-title">Gifs</Heading>
<Heading className="collection-info-title">{t("gifCollections.ui.gifs")}</Heading>
<Paragraph className="collection-info-text">{collection.gifs.length}</Paragraph>
</Flex>
<Flex className="collection-info">
<Heading className="collection-info-title">Created At</Heading>
<Paragraph className="collection-info-text">{collection.createdAt ? new Date(collection.createdAt).toLocaleString() : "Unknown"}</Paragraph>
<Heading className="collection-info-title">{t("gifCollections.ui.createdAt")}</Heading>
<Paragraph className="collection-info-text">{collection.createdAt ? new Date(collection.createdAt).toLocaleString() : t("gifCollections.ui.unknown")}</Paragraph>
</Flex>
<Flex className="collection-info">
<Heading className="collection-info-title">Last Updated</Heading>
<Paragraph className="collection-info-text">{collection.lastUpdated ? new Date(collection.lastUpdated).toLocaleString() : "Unknown"}</Paragraph>
<Heading className="collection-info-title">{t("gifCollections.ui.lastUpdated")}</Heading>
<Paragraph className="collection-info-text">{collection.lastUpdated ? new Date(collection.lastUpdated).toLocaleString() : t("gifCollections.ui.unknown")}</Paragraph>
</Flex>
</section>
</ModalContent>
<ModalFooter className="custom-modal-footer">
<Button onClick={modalProps.onClose} className="custom-modal-button">Close</Button>
<Button onClick={modalProps.onClose} className="custom-modal-button">{t("gifCollections.ui.close")}</Button>
</ModalFooter>
</ModalRoot>
));
@ -493,7 +494,7 @@ const RemoveItemContextMenu = ({ type, nameOrId, instance }) => (
<Menu.MenuItem
key="rename-collection"
id="rename-collection"
label="Rename"
label={t("gifCollections.ui.rename")}
action={() => openModal(modalProps => (
<RenameCollectionModal
onClose={modalProps.onClose}

Some files were not shown because too many files have changed in this diff Show more