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

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

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

All changes pass TypeScript compilation.

* Update i18n/en-US/vencord.json

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

* Update i18n/en-US/vencord.json

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

* Update i18n/en-US/vencord.json

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

* Update i18n/en-US/vencord.json

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

* Update i18n/en-US/vencord.json

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

* Update i18n/en-US/vencord.json

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

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

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

* Update i18n/en-US/vencord.json

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

* feat(i18n): Add translations to 200 equicordplugins

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

* FileUpload: add sharex file support (#771)

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

* FileUpload: use Headers object to construct HTTP headers

* Update vencord.json

---------

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

166 lines
6.3 KiB
TypeScript

/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
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";
const extraTimeslots = [3, 4, 5, 6, 7, 10, 15, 20, 25, 30];
const extraFramerates = [45, 90, 120, 144, 165, 240];
const settings = definePluginSettings({
richPresenceTagging: {
type: OptionType.SELECT,
description: t("clipsEnhancements.settings.richPresenceTagging"),
options: [
{ 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: t("clipsEnhancements.settings.enableScreenshotKeybind"),
default: true,
restartNeeded: true
},
enableVoiceOnlyClips: {
type: OptionType.BOOLEAN,
description: t("clipsEnhancements.settings.enableVoiceOnlyClips"),
default: true,
restartNeeded: true
},
enableAdvancedSignals: {
type: OptionType.BOOLEAN,
description: t("clipsEnhancements.settings.enableAdvancedSignals"),
default: true,
restartNeeded: true
},
ignorePlatformRestriction: {
type: OptionType.BOOLEAN,
description: t("clipsEnhancements.settings.ignorePlatformRestriction"),
default: true,
restartNeeded: true
},
clipsLink: {
type: OptionType.COMPONENT,
description: "",
component: () => {
return (
<>
<Button
onClick={() => {
SettingsRouter.openUserSettings("clips_panel");
}}
>
{t("clipsEnhancements.settings.clipsLink")}
</Button >
</>
);
}
},
});
migratePluginSettings("ClipsEnhancements", "TimelessClips");
export default definePlugin({
name: "ClipsEnhancements",
description: t("clipsEnhancements.description"),
authors: [Devs.niko, Devs.Joona, EquicordDevs.keircn],
settings,
patches: [
{
find: "#{intl::CLIPS_UNKNOWN_SOURCE}",
replacement: {
match: /(applicationName:)(.{0,50})(,applicationId:)(\i)/,
replace: "$1$2$3$self.getApplicationId($2)??$4"
}
},
{
find: ".CLIPS_FRAME_RATE,{",
replacement: {
match: /\[\{.{0,25}\i.\i.FPS_15.{0,500}\}\]/,
replace: "$self.patchFramerates($&)"
}
},
{
find: ".CLIPS_LENGTH,{",
replacement: {
match: /\[\{.{0,25}\i.\i.SECONDS_30.{0,500}\}\]/,
replace: "$self.patchTimeslots($&)"
}
},
// enables clips
{
find: "2022-11_clips_experiment",
replacement: {
match: /defaultConfig:\{enableClips:!\d,ignorePlatformRestriction:!\d,showClipsHeaderEntrypoint:!\d,enableScreenshotKeybind:!\d,enableVoiceOnlyClips:!\d,enableAdvancedSignals:!\d\}/,
replace: "defaultConfig:{enableClips:!0,ignorePlatformRestriction:$self.settings.store.ignorePlatformRestriction,showClipsHeaderEntrypoint:!0,enableScreenshotKeybind:$self.settings.store.enableScreenshotKeybind,enableVoiceOnlyClips:$self.settings.store.enableVoiceOnlyClips,enableAdvancedSignals:$self.settings.store.enableAdvancedSignals}"
}
},
{
find: "2023-10_viewer_clipping",
replacement: {
match: /defaultConfig:\{enableViewerClipping:!\d,ignoreSenderPreference:!\d\}/,
replace: "defaultConfig:{enableViewerClipping:!0,ignoreSenderPreference:!0}"
}
}
],
patchTimeslots(timeslots: { id: string; value: number; label: string; }[]) {
const newTimeslots = [...timeslots];
extraTimeslots.forEach(timeslot => newTimeslots.push({
id: `${timeslot}min`,
value: timeslot * 60000,
label: getIntlMessage("CLIPS_LENGTH_MINUTES", {
count: timeslot
})
}));
return newTimeslots.sort((a, b) => a.value - b.value);
},
patchFramerates(framerates: { id: string; value: number; label: string; }[]) {
const newFramerates = [...framerates];
// Lower framerates than 15FPS have adverse affects on compression, 3 minute clips at 10FPS skyrocket the filesize to 200mb!!
extraFramerates.forEach(framerate => newFramerates.push({
id: `${framerate}fps`,
value: framerate,
label: getIntlMessage("SCREENSHARE_FPS_ABBREVIATED", {
fps: framerate
})
}));
return newFramerates.sort((a, b) => a.value - b.value);
},
getApplicationId(activityName: string) {
if (settings.store.richPresenceTagging === "never") return null;
const activities: Activity[] = PresenceStore.getActivities(UserStore.getCurrentUser().id);
const validActivities = activities.filter(activity => activity.type === 0 && activity.application_id !== null);
const splitName = activityName.split(" ");
// Try to match activity by it's start and end
const matchedActivities = validActivities.filter(activity => activity.name.endsWith(splitName.at(-1)!) || activity.name.startsWith(splitName.at(0)!));
if (matchedActivities.length > 0) {
return matchedActivities[0].application_id;
}
if (settings.store.richPresenceTagging !== "whenMatched") {
return validActivities[0]?.application_id ?? null;
}
return null;
},
});