* 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>
166 lines
6.3 KiB
TypeScript
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;
|
|
},
|
|
});
|