feat: add support i18n (#1468)

This commit is contained in:
JellyBrick
2023-12-01 01:30:46 +09:00
committed by GitHub
parent 7f71c36dc0
commit 7401cf69ad
65 changed files with 1226 additions and 303 deletions

View File

@ -9,6 +9,8 @@ import {
import injectCliqzPreload from './injectors/inject-cliqz-preload';
import { inject, isInjected } from './injectors/inject';
import { t } from '@/i18n';
import type { BrowserWindow } from 'electron';
interface AdblockerConfig {
@ -41,8 +43,8 @@ interface AdblockerConfig {
}
export default createPlugin({
name: 'Adblocker',
description: 'Block all ads and tracking out of the box',
name: t('plugins.adblocker.name'),
description: t('plugins.adblocker.description'),
restartNeeded: false,
config: {
enabled: true,
@ -56,7 +58,7 @@ export default createPlugin({
return [
{
label: 'Blocker',
label: t('plugins.adblocker.menu.blocker'),
submenu: Object.values(blockers).map((blocker) => ({
label: blocker,
type: 'radio',

View File

@ -3,13 +3,13 @@ import { FastAverageColor } from 'fast-average-color';
import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import type { VideoDataChanged } from '@/types/video-data-changed';
export default createPlugin({
name: 'Album Color Theme',
description:
'Applies a dynamic theme and visual effects based on the album color palette',
name: t('plugins.album-color-theme.name'),
description: t('plugins.album-color-theme.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -1,6 +1,7 @@
import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
export type AmbientModePluginConfig = {
enabled: boolean;
@ -24,9 +25,8 @@ const defaultConfig: AmbientModePluginConfig = {
};
export default createPlugin({
name: 'Ambient Mode',
description:
'Applies a lighting effect by casting gentle colors from the video, into your screens background.',
name: t('plugins.ambient-mode.name'),
description: t('plugins.ambient-mode.description'),
restartNeeded: false,
config: defaultConfig,
stylesheets: [style],
@ -42,9 +42,11 @@ export default createPlugin({
return [
{
label: 'Smoothness transition',
label: t('plugins.ambient-mode.menu.smoothness-transition.label'),
submenu: interpolationTimeList.map((interpolationTime) => ({
label: `During ${interpolationTime / 1000}s`,
label: t('plugins.ambient-mode.menu.smoothness-transition.submenu.during', {
interpolationTime: interpolationTime / 1000,
}),
type: 'radio',
checked: config.interpolationTime === interpolationTime,
click() {
@ -53,9 +55,9 @@ export default createPlugin({
})),
},
{
label: 'Quality',
label: t('plugins.ambient-mode.menu.quality.label'),
submenu: qualityList.map((quality) => ({
label: `${quality} pixels`,
label: t('plugins.ambient-mode.menu.quality.submenu.pixels', { quality }),
type: 'radio',
checked: config.quality === quality,
click() {
@ -64,9 +66,9 @@ export default createPlugin({
})),
},
{
label: 'Size',
label: t('plugins.ambient-mode.menu.size.label'),
submenu: sizeList.map((size) => ({
label: `${size}%`,
label: t('plugins.ambient-mode.menu.size.submenu.percent', { size }),
type: 'radio',
checked: config.size === size,
click() {
@ -75,9 +77,9 @@ export default createPlugin({
})),
},
{
label: 'Buffer',
label: t('plugins.ambient-mode.menu.buffer.label'),
submenu: bufferList.map((buffer) => ({
label: `${buffer}`,
label: t('plugins.ambient-mode.menu.buffer.submenu.buffer', { buffer }),
type: 'radio',
checked: config.buffer === buffer,
click() {
@ -86,9 +88,9 @@ export default createPlugin({
})),
},
{
label: 'Opacity',
label: t('plugins.ambient-mode.menu.opacity.label'),
submenu: opacityList.map((opacity) => ({
label: `${opacity * 100}%`,
label: t('plugins.ambient-mode.menu.opacity.submenu.percent', { opacity: opacity * 100 }),
type: 'radio',
checked: config.opacity === opacity,
click() {
@ -97,9 +99,9 @@ export default createPlugin({
})),
},
{
label: 'Blur amount',
label: t('plugins.ambient-mode.menu.blur-amount.label'),
submenu: blurAmountList.map((blur) => ({
label: `${blur} pixels`,
label: t('plugins.ambient-mode.menu.blur-amount.submenu.pixels', { blurAmount: blur }),
type: 'radio',
checked: config.blur === blur,
click() {
@ -108,7 +110,7 @@ export default createPlugin({
})),
},
{
label: 'Using fullscreen',
label: t('plugins.ambient-mode.menu.use-fullscreen.label'),
type: 'checkbox',
checked: config.fullscreen,
click(item) {

View File

@ -1,9 +1,9 @@
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
export default createPlugin({
name: 'Audio Compressor',
description:
'Apply compression to audio (lowers the volume of the loudest parts of the signal and raises the volume of the softest parts)',
name: t('plugins.audio-compressor.name'),
description: t('plugins.audio-compressor.description'),
renderer() {
document.addEventListener(

View File

@ -1,9 +1,10 @@
import { createPlugin } from '@/utils';
import style from './style.css?inline';
import { t } from '@/i18n';
export default createPlugin({
name: 'Blur Navigation Bar',
description: 'makes navigation bar transparent and blurry',
name: t('plugins.blur-nav-bar.name'),
description: t('plugins.blur-nav-bar.description'),
restartNeeded: true,
stylesheets: [style],
renderer() {},

View File

@ -1,8 +1,9 @@
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
export default createPlugin({
name: 'Bypass Age Restrictions',
description: "bypass YouTube's age verification",
name: t('plugins.bypass-age-restrictions.name'),
description: t('plugins.bypass-age-restrictions.description'),
restartNeeded: true,
// See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#userscript

View File

@ -2,6 +2,7 @@ import prompt from 'custom-electron-prompt';
import promptOptions from '@/providers/prompt-options';
import { createBackend } from '@/utils';
import { t } from '@/i18n';
export default createBackend({
start({ ipc: { handle }, window }) {
@ -10,8 +11,10 @@ export default createBackend({
async (captionLabels: Record<string, string>, currentIndex: string) =>
await prompt(
{
title: 'Choose Caption',
label: `Current Caption: ${captionLabels[currentIndex] || 'None'}`,
title: t('plugins.captions-selector.prompt.selector.title'),
label: t('plugins.captions-selector.prompt.selector.label', {
language: captionLabels[currentIndex] || t('plugins.captions-selector.prompt.selector.none'),
}),
type: 'select',
value: currentIndex,
selectOptions: captionLabels,

View File

@ -3,6 +3,7 @@ import { YoutubePlayer } from '@/types/youtube-player';
import backend from './back';
import renderer, { CaptionsSelectorConfig, LanguageOptions } from './renderer';
import { t } from '@/i18n';
export default createPlugin<
unknown,
@ -18,8 +19,8 @@ export default createPlugin<
},
CaptionsSelectorConfig
>({
name: 'Captions Selector',
description: 'Caption selector for YouTube Music audio tracks',
name: t('plugins.captions-selector.name'),
description: t('plugins.captions-selector.description'),
config: {
enabled: false,
disableCaptions: false,
@ -31,7 +32,7 @@ export default createPlugin<
const config = await getConfig();
return [
{
label: 'Automatically select last used caption',
label: t('plugins.captions-selector.menu.autoload'),
type: 'checkbox',
checked: config.autoload as boolean,
click(item) {
@ -39,7 +40,7 @@ export default createPlugin<
},
},
{
label: 'No captions by default',
label: t('plugins.captions-selector.menu.disable-captions'),
type: 'checkbox',
checked: config.disableCaptions as boolean,
click(item) {

View File

@ -1,4 +1,5 @@
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
export default createPlugin<
unknown,
@ -8,8 +9,8 @@ export default createPlugin<
isCompactSidebarDisabled: () => boolean;
}
>({
name: 'Compact Sidebar',
description: 'Always set the sidebar in compact mode',
name: t('plugins.compact-sidebar.name'),
description: t('plugins.compact-sidebar.description'),
restartNeeded: false,
config: {
enabled: false,

View File

@ -11,6 +11,7 @@ import { createPlugin } from '@/utils';
import { VolumeFader } from './fader';
import type { RendererContext } from '@/types/contexts';
import { t } from '@/i18n';
export type CrossfadePluginConfig = {
enabled: boolean;
@ -29,8 +30,8 @@ export default createPlugin<
},
CrossfadePluginConfig
>({
name: 'Crossfade [beta]',
description: 'Crossfade between songs',
name: t('plugins.crossfade.name'),
description: t('plugins.crossfade.description'),
restartNeeded: true,
config: {
enabled: false,
@ -67,11 +68,11 @@ export default createPlugin<
): Promise<Omit<CrossfadePluginConfig, 'enabled'> | undefined> => {
const res = await prompt(
{
title: 'Crossfade Options',
title: t('plugins.crossfade.prompt.options'),
type: 'multiInput',
multiInputOptions: [
{
label: 'Fade in duration (ms)',
label: t('plugins.crossfade.prompt.options.multi-input.fade-in-duration'),
value: options.fadeInDuration,
inputAttrs: {
type: 'number',
@ -81,7 +82,7 @@ export default createPlugin<
},
},
{
label: 'Fade out duration (ms)',
label: t('plugins.crossfade.prompt.options.multi-input.fade-out-duration'),
value: options.fadeOutDuration,
inputAttrs: {
type: 'number',
@ -91,7 +92,7 @@ export default createPlugin<
},
},
{
label: 'Crossfade x seconds before end',
label: t('plugins.crossfade.prompt.options.multi-input.seconds-before-end'),
value: options.secondsBeforeEnd,
inputAttrs: {
type: 'number',
@ -100,8 +101,11 @@ export default createPlugin<
},
},
{
label: 'Fade scaling',
selectOptions: { linear: 'Linear', logarithmic: 'Logarithmic' },
label: t('plugins.crossfade.prompt.options.multi-input.fade-scaling.label'),
selectOptions: {
linear: t('plugins.crossfade.prompt.options.multi-input.fade-scaling.linear'),
logarithmic: t('plugins.crossfade.prompt.options.multi-input.fade-scaling.logarithmic'),
},
value: options.fadeScaling,
},
],
@ -135,7 +139,7 @@ export default createPlugin<
return [
{
label: 'Advanced',
label: t('plugins.crossfade.menu.advanced'),
async click() {
const newOptions = await promptCrossfadeValues(
window,

View File

@ -1,5 +1,7 @@
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
import type { VideoDataChanged } from '@/types/video-data-changed';
import type { YoutubePlayer } from '@/types/youtube-player';
@ -19,8 +21,8 @@ export default createPlugin<
},
DisableAutoPlayPluginConfig
>({
name: 'Disable Autoplay',
description: 'Makes every song start in "paused" mode',
name: t('plugins.disable-autoplay.name'),
description: t('plugins.disable-autoplay.description'),
restartNeeded: false,
config: {
enabled: false,
@ -31,7 +33,7 @@ export default createPlugin<
return [
{
label: 'Applies only on startup',
label: t('plugins.disable-autoplay.menu.apply-once'),
type: 'checkbox',
checked: config.applyOnce,
async click() {

View File

@ -1,6 +1,7 @@
import { createPlugin } from '@/utils';
import { backend } from './main';
import { onMenu } from './menu';
import { t } from '@/i18n';
export type DiscordPluginConfig = {
enabled: boolean;
@ -35,8 +36,8 @@ export type DiscordPluginConfig = {
};
export default createPlugin({
name: 'Discord Rich Presence',
description: 'Show your friends what you listen to with Rich Presence',
name: t('plugins.discord.name'),
description: t('plugins.discord.description'),
restartNeeded: false,
config: {
enabled: false,

View File

@ -6,7 +6,9 @@ import { SetActivity } from '@xhayper/discord-rpc/dist/structures/ClientUser';
import registerCallback, { type SongInfo } from '@/providers/song-info';
import { createBackend } from '@/utils';
import { createBackend, LoggerPrefix } from '@/utils';
import { t } from '@/i18n';
import type { DiscordPluginConfig } from './index';
@ -38,7 +40,10 @@ const resetInfo = () => {
info.ready = false;
clearTimeout(clearActivity);
if (dev()) {
console.log('discord disconnected');
console.log(
LoggerPrefix,
t('plugins.discord.backend.disconnected')
);
}
for (const cb of refreshCallbacks) {
@ -68,7 +73,10 @@ let window: Electron.BrowserWindow;
export const connect = (showError = false) => {
if (info.rpc.isConnected) {
if (dev()) {
console.log('Attempted to connect with active connection');
console.log(
LoggerPrefix,
t('plugins.discord.backend.already-connected')
);
}
return;
@ -206,7 +214,10 @@ export const backend = createBackend<
info.rpc.on('connected', () => {
if (dev()) {
console.log('discord connected');
console.log(
LoggerPrefix,
t('plugins.discord.backend.connected')
);
}
for (const cb of refreshCallbacks) {

View File

@ -6,6 +6,8 @@ import { singleton } from '@/providers/decorators';
import promptOptions from '@/providers/prompt-options';
import { setMenuOptions } from '@/config/plugins';
import { t } from '@/i18n';
import type { MenuContext } from '@/types/contexts';
import type { DiscordPluginConfig } from './index';
@ -31,7 +33,7 @@ export const onMenu = async ({
click: () => connect(),
},
{
label: 'Auto reconnect',
label: t('plugins.discord.menu.auto-reconnect'),
type: 'checkbox',
checked: config.autoReconnect,
click(item: Electron.MenuItem) {
@ -41,11 +43,11 @@ export const onMenu = async ({
},
},
{
label: 'Clear activity',
label: t('plugins.discord.menu.clear-activity'),
click: clear,
},
{
label: 'Clear activity after timeout',
label: t('plugins.discord.menu.clear-activity-after-timeout'),
type: 'checkbox',
checked: config.activityTimeoutEnabled,
click(item: Electron.MenuItem) {
@ -55,7 +57,7 @@ export const onMenu = async ({
},
},
{
label: 'Play on YouTube Music',
label: t('plugins.discord.menu.play-on-youtube-music'),
type: 'checkbox',
checked: config.playOnYouTubeMusic,
click(item: Electron.MenuItem) {
@ -65,7 +67,7 @@ export const onMenu = async ({
},
},
{
label: 'Hide GitHub link Button',
label: t('plugins.discord.menu.hide-github-button'),
type: 'checkbox',
checked: config.hideGitHubButton,
click(item: Electron.MenuItem) {
@ -75,7 +77,7 @@ export const onMenu = async ({
},
},
{
label: 'Hide duration left',
label: t('plugins.discord.menu.hide-duration-left'),
type: 'checkbox',
checked: config.hideDurationLeft,
click(item: Electron.MenuItem) {
@ -85,7 +87,7 @@ export const onMenu = async ({
},
},
{
label: 'Set inactivity timeout',
label: t('plugins.discord.menu.set-inactivity-timeout'),
click: () => setInactivityTimeout(window, config),
},
];
@ -97,8 +99,8 @@ async function setInactivityTimeout(
) {
const output = await prompt(
{
title: 'Set Inactivity Timeout',
label: 'Enter inactivity timeout in seconds:',
title: t('plugins.discord.prompt.set-inactivity-timeout.title'),
label: t('plugins.discord.prompt.set-inactivity-timeout.label'),
value: String(Math.round((options.activityTimeoutTime ?? 0) / 1e3)),
type: 'counter',
counterOptions: { minimum: 0, multiFire: true },

View File

@ -5,6 +5,7 @@ import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { onConfigChange, onMainLoad } from './main';
import { onPlayerApiReady, onRendererLoad } from './renderer';
import { t } from '@/i18n';
export type DownloaderPluginConfig = {
enabled: boolean;
@ -25,8 +26,8 @@ export const defaultConfig: DownloaderPluginConfig = {
};
export default createPlugin({
name: 'Downloader',
description: 'Downloads MP3 / source audio directly from the interface',
name: t('plugins.downloader.name'),
description: t('plugins.downloader.description'),
restartNeeded: true,
config: defaultConfig,
stylesheets: [style],

View File

@ -34,6 +34,8 @@ import { cleanupName, getImage, SongInfo } from '@/providers/song-info';
import { getNetFetchAsFetch } from '@/plugins/utils/main';
import { cache } from '@/providers/decorators';
import { t } from '@/i18n';
import { YoutubeFormatList, type Preset, DefaultPresetList } from '../types';
import type { DownloaderPluginConfig } from '../index';
@ -74,9 +76,9 @@ const sendError = (error: Error, source?: string) => {
console.trace(error);
dialog.showMessageBox(win, {
type: 'info',
buttons: ['OK'],
title: 'Error in download!',
message: 'Argh! Apologies, download failed…',
buttons: [t('plugins.downloader.backend.dialog.error.buttons.ok')],
title: t('plugins.downloader.backend.dialog.error.title'),
message: t('plugins.downloader.backend.dialog.error.message'),
detail: message,
});
};
@ -179,20 +181,27 @@ async function downloadSongUnsafe(
}
};
sendFeedback('Downloading...', 2);
sendFeedback(
t('plugins.downloader.backend.feedback.downloading'),
2,
);
let id: string | null;
if (isId) {
id = idOrUrl;
} else {
id = getVideoId(idOrUrl);
if (typeof id !== 'string') throw new Error('Video not found');
if (typeof id !== 'string') throw new Error(
t('plugins.downloader.backend.feedback.video-id-not-found'),
);
}
let info: TrackInfo | VideoInfo = await yt.music.getInfo(id);
if (!info) {
throw new Error('Video not found');
throw new Error(
t('plugins.downloader.backend.feedback.video-id-not-found'),
);
}
const metadata = getMetadata(info);
@ -277,7 +286,11 @@ async function downloadSongUnsafe(
const stream = await info.download(downloadOptions);
console.info(
`Downloading ${metadata.artist} - ${metadata.title} [${metadata.videoId}]`,
t('plugins.downloader.backend.feedback.download-info', {
artist: metadata.artist,
title: metadata.title,
videoId: metadata.videoId,
}),
);
const iterableStream = Utils.streamToIterable(stream);
@ -312,7 +325,9 @@ async function downloadSongUnsafe(
}
sendFeedback(null, -1);
console.info(`Done: "${filePath}"`);
console.info(t('plugins.downloader.backend.feedback.done', {
filePath,
}));
}
async function iterableStreamToTargetFile(
@ -331,13 +346,21 @@ async function iterableStreamToTargetFile(
chunks.push(chunk);
const ratio = downloaded / contentLength;
const progress = Math.floor(ratio * 100);
sendFeedback(`Download: ${progress}%`, ratio);
sendFeedback(
t('plugins.downloader.backend.feedback.downloading-progress', {
percent: progress,
}),
ratio,
);
// 15% for download, 85% for conversion
// This is a very rough estimate, trying to make the progress bar look nice
increasePlaylistProgress(ratio * 0.15);
}
sendFeedback('Loading…', 2); // Indefinite progress bar after download
sendFeedback(
t('plugins.downloader.backend.feedback.loading'),
2,
); // Indefinite progress bar after download
const buffer = Buffer.concat(chunks);
const safeVideoName = randomBytes(32).toString('hex');
@ -348,13 +371,18 @@ async function iterableStreamToTargetFile(
await ffmpeg.load();
}
sendFeedback('Preparing file');
sendFeedback(t('plugins.downloader.backend.feedback.preparing-file'));
ffmpeg.FS('writeFile', safeVideoName, buffer);
sendFeedback('Converting');
sendFeedback(t('plugins.downloader.backend.feedback.converting'));
ffmpeg.setProgress(({ ratio }) => {
sendFeedback(`Converting: ${Math.floor(ratio * 100)}%`, ratio);
sendFeedback(
t('plugins.downloader.backend.feedback.conversion-progress', {
percent: Math.floor(ratio * 100),
}),
ratio,
);
increasePlaylistProgress(0.15 + (ratio * 0.85));
});
@ -371,7 +399,9 @@ async function iterableStreamToTargetFile(
ffmpeg.FS('unlink', safeVideoName);
}
sendFeedback('Saving…');
sendFeedback(
t('plugins.downloader.backend.feedback.saving'),
);
try {
return ffmpeg.FS('readFile', safeVideoNameWithExtension);
@ -397,7 +427,9 @@ async function writeID3(
sendFeedback: (str: string, value?: number) => void,
) {
try {
sendFeedback('Writing ID3 tags...');
sendFeedback(
t('plugins.downloader.backend.feedback.writing-id3'),
);
const tags: NodeID3.Tags = {};
// Create the metadata tags
@ -452,14 +484,22 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
getPlaylistID(givenUrl) || getPlaylistID(new URL(playingUrl));
if (!playlistId) {
sendError(new Error('No playlist ID found'));
sendError(new Error(
t('plugins.downloader.backend.feedback.playlist-id-not-found'),
));
return;
}
const sendFeedback = (message?: unknown) => sendFeedback_(win, message);
console.log(`trying to get playlist ID: '${playlistId}'`);
sendFeedback('Getting playlist info…');
console.log(
t('plugins.downloader.backend.feedback.trying-to-get-playlist-id', {
playlistId,
}),
);
sendFeedback(
t('plugins.downloader.backend.feedback.getting-playlist-info'),
);
let playlist: Playlist;
const items: YTNodes.MusicResponsiveListItem[] = [];
try {
@ -470,16 +510,18 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
} catch (error: unknown) {
sendError(
Error(
`Error getting playlist info: make sure it isn't a private or "Mixed for you" playlist\n\n${String(
error,
)}`,
t('plugins.downloader.backend.feedback.playlist-is-mix-or-private', {
error: String(error),
}),
),
);
return;
}
if (!playlist || !playlist.items || playlist.items.length === 0) {
sendError(new Error('Playlist is empty'));
sendError(new Error(
t('plugins.downloader.backend.feedback.playlist-is-empty'),
));
}
const normalPlaylistTitle = playlist.header?.title?.text;
@ -500,7 +542,9 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
}
if (items.length === 1) {
sendFeedback('Playlist has only one item, downloading it directly');
sendFeedback(
t('plugins.downloader.backend.feedback.playlist-has-only-one-song'),
);
await downloadSongFromId(items.at(0)!.id!);
return;
}
@ -514,7 +558,11 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
const playlistFolder = join(folder, safePlaylistTitle);
if (existsSync(playlistFolder)) {
if (!config.skipExisting) {
sendError(new Error(`The folder ${playlistFolder} already exists`));
sendError(new Error(
t('plugins.downloader.backend.feedback.folder-already-exists', {
playlistFolder,
})
));
return;
}
} else {
@ -523,15 +571,23 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
dialog.showMessageBox(win, {
type: 'info',
buttons: ['OK'],
title: 'Started Download',
message: `Downloading Playlist "${playlistTitle}"`,
detail: `(${items.length} songs)`,
buttons: [t('plugins.downloader.backend.dialog.start-download-playlist.buttons.ok')],
title: t('plugins.downloader.backend.dialog.start-download-playlist.title'),
message: t('plugins.downloader.backend.dialog.start-download-playlist.message', {
playlistTitle,
}),
detail: t('plugins.downloader.backend.dialog.start-download-playlist.detail', {
playlistSize: items.length,
}),
});
if (is.dev()) {
console.log(
`Downloading playlist "${playlistTitle}" - ${items.length} songs (${playlistId})`,
t('plugins.downloader.backend.feedback.downloading-playlist', {
playlistTitle,
playlistSize: items.length,
playlistId,
}),
);
}
@ -551,7 +607,12 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
try {
for (const song of items) {
sendFeedback(`Downloading ${counter}/${items.length}...`);
sendFeedback(
t('plugins.downloader.backend.feedback.downloading-counter', {
current: counter,
total: items.length,
})
);
const trackId = isAlbum ? counter : undefined;
await downloadSongFromId(
song.id!,
@ -561,9 +622,11 @@ export async function downloadPlaylist(givenUrl?: string | URL) {
).catch((error) =>
sendError(
new Error(
`Error downloading "${
song.author!.name
} - ${song.title!}":\n ${error}`,
t('plugins.downloader.backend.feedback.error-while-downloading', {
author: song.author!.name,
title: song.title!,
error: String(error),
}),
),
),
);

View File

@ -8,6 +8,7 @@ import type { MenuContext } from '@/types/contexts';
import type { MenuTemplate } from '@/menu';
import type { DownloaderPluginConfig } from './index';
import { t } from '@/i18n';
export const onMenu = async ({
getConfig,
@ -21,7 +22,7 @@ export const onMenu = async ({
click: () => downloadPlaylist(),
},
{
label: 'Choose download folder',
label: t('plugins.downloader.menu.choose-download-folder'),
click() {
const result = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory'],
@ -33,7 +34,7 @@ export const onMenu = async ({
},
},
{
label: 'Presets',
label: t('plugins.downloader.menu.presets'),
submenu: Object.keys(DefaultPresetList).map((preset) => ({
label: preset,
type: 'radio',
@ -44,7 +45,7 @@ export const onMenu = async ({
})),
},
{
label: 'Skip existing files',
label: t('plugins.downloader.menu.skip-existing'),
type: 'checkbox',
checked: config.skipExisting,
click(item) {

View File

@ -4,11 +4,14 @@ import defaultConfig from '@/config/defaults';
import { getSongMenu } from '@/providers/dom-elements';
import { getSongInfo } from '@/providers/song-info-front';
import { LoggerPrefix } from '@/utils';
import { ElementFromHtml } from '../utils/renderer';
import type { RendererContext } from '@/types/contexts';
import type { DownloaderPluginConfig } from './index';
import { t } from '@/i18n';
let menu: Element | null = null;
let progress: Element | null = null;
@ -75,7 +78,10 @@ export const onRendererLoad = ({
if (progress) {
progress.innerHTML = feedback || 'Download';
} else {
console.warn('Cannot update progress');
console.warn(
LoggerPrefix,
t('plugins.downloader.renderer.can-not-update-progress'),
);
}
});
};

View File

@ -39,7 +39,7 @@
class="text style-scope ytmusic-menu-navigation-item-renderer"
id="ytmcustom-download"
>
Download
<ytmd-trans key="plugins.downloader.templates.button"></ytmd-trans>
</div>
</a>
</div>

View File

@ -1,9 +1,9 @@
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
export default createPlugin({
name: 'Exponential Volume',
description:
"Makes the volume slider exponential so it's easier to select lower volumes.",
name: t('plugins.exponential-volume.name'),
description: t('plugins.exponential-volume.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -3,14 +3,15 @@ import { createPlugin } from '@/utils';
import { onMainLoad } from './main';
import { onMenu } from './menu';
import { onPlayerApiReady, onRendererLoad } from './renderer';
import { t } from '@/i18n';
export interface InAppMenuConfig {
enabled: boolean;
hideDOMWindowControls: boolean;
}
export default createPlugin({
name: 'In-App Menu',
description: 'gives menu-bars a fancy, dark or album-color look',
name: t('plugins.in-app-menu.name'),
description: t('plugins.in-app-menu.description'),
restartNeeded: true,
config: {
enabled:

View File

@ -1,5 +1,7 @@
import is from 'electron-is';
import { t } from '@/i18n';
import type { InAppMenuConfig } from './index';
import type { MenuContext } from '@/types/contexts';
import type { MenuTemplate } from '@/menu';
@ -13,7 +15,7 @@ export const onMenu = async ({
if (is.linux()) {
return [
{
label: 'Hide DOM Window Controls',
label: t('plugins.in-app-menu.hide-dom-window-controls'),
type: 'checkbox',
checked: config.hideDOMWindowControls,
click(item) {

View File

@ -99,7 +99,7 @@ export const createPanel = (
children.push(...children);
}
panel.appendChild(menu);
return panel.appendChild(menu);
});
/* methods */

View File

@ -1,6 +1,7 @@
import { createPlugin } from '@/utils';
import registerCallback from '@/providers/song-info';
import { addScrobble, getAndSetSessionKey, setNowPlaying } from './main';
import { t } from '@/i18n';
export interface LastFmPluginConfig {
enabled: boolean;
@ -33,8 +34,8 @@ export interface LastFmPluginConfig {
}
export default createPlugin({
name: 'Last.fm',
description: 'Add scrobbling support for Last.fm',
name: t('plugins.last-fm.name'),
description: t('plugins.last-fm.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -2,6 +2,7 @@ import { net } from 'electron';
import { createPlugin } from '@/utils';
import registerCallback from '@/providers/song-info';
import { t } from '@/i18n';
type LumiaData = {
origin: string;
@ -23,8 +24,8 @@ type LumiaData = {
};
export default createPlugin({
name: 'Lumia Stream [beta]',
description: 'Adds Lumia Stream support',
name: t('plugins.lumiastream.name'),
description: t('plugins.lumiastream.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -2,6 +2,7 @@ import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { onConfigChange, onMainLoad } from './main';
import { onRendererLoad } from './renderer';
import { t } from '@/i18n';
export type LyricsGeniusPluginConfig = {
enabled: boolean;
@ -9,8 +10,8 @@ export type LyricsGeniusPluginConfig = {
};
export default createPlugin({
name: 'Lyrics Genius',
description: 'Adds lyrics support for most songs',
name: t('plugins.lyrics-genius.name'),
description: t('plugins.lyrics-genius.description'),
restartNeeded: true,
config: {
enabled: false,
@ -22,7 +23,7 @@ export default createPlugin({
return [
{
label: 'Romanized Lyrics',
label: t('plugins.lyrics-genius.menu.romanized-lyrics'),
type: 'checkbox',
checked: config.romanizedLyrics,
click(item) {

View File

@ -1,6 +1,9 @@
import { LoggerPrefix } from '@/utils';
import type { SongInfo } from '@/providers/song-info';
import type { RendererContext } from '@/types/contexts';
import type { LyricsGeniusPluginConfig } from '@/plugins/lyrics-genius/index';
import { t } from '@/i18n';
export const onRendererLoad = ({
ipc: { invoke, on },
@ -55,7 +58,10 @@ export const onRendererLoad = ({
}
if (window.electronIs.dev()) {
console.log('Fetched lyrics from Genius');
console.log(
LoggerPrefix,
t('plugins.lyric-genius.renderer.fetched-lyrics'),
);
}
const tryToInjectLyric = (callback?: () => void) => {

View File

@ -2,13 +2,14 @@ import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { ElementFromHtml } from '@/plugins/utils/renderer';
import { t } from '@/i18n';
import forwardHTML from './templates/forward.html?raw';
import backHTML from './templates/back.html?raw';
export default createPlugin({
name: 'Navigation',
description:
'Next/Back navigation arrows directly integrated in the interface, like in your favorite browser',
name: t('plugins.navigation.name'),
description: t('plugins.navigation.description'),
restartNeeded: true,
config: {
enabled: true,

View File

@ -1,9 +1,10 @@
import style from './style.css?inline';
import { createPlugin } from '@/utils';
import { t } from '@/i18n';
export default createPlugin({
name: 'Remove Google Login',
description: 'Remove Google login buttons and links from the interface',
name: t('plugins.no-google-login.name'),
description: t('plugins.no-google-login.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -2,6 +2,7 @@ import { createPlugin } from '@/utils';
import { onConfigChange, onMainLoad } from './main';
import { onMenu } from './menu';
import { t } from '@/i18n';
export interface NotificationsPluginConfig {
enabled: boolean;
@ -35,9 +36,8 @@ export const defaultConfig: NotificationsPluginConfig = {
};
export default createPlugin({
name: 'Notifications',
description:
'Display a notification when a song starts playing (interactive notifications are available on windows)',
name: t('plugins.notifications.name'),
description: t('plugins.notifications.description'),
restartNeeded: true,
config: defaultConfig,
menu: onMenu,

View File

@ -3,6 +3,8 @@ import { MenuItem } from 'electron';
import { snakeToCamel, ToastStyles, urgencyLevels } from './utils';
import { t } from '@/i18n';
import type { NotificationsPluginConfig } from './index';
import type { MenuTemplate } from '@/menu';
@ -34,7 +36,7 @@ export const onMenu = async ({
if (is.linux()) {
return [
{
label: 'Notification Priority',
label: t('plugins.notifications.menu.priority'),
submenu: urgencyLevels.map((level) => ({
label: level.name,
type: 'radio',
@ -46,7 +48,7 @@ export const onMenu = async ({
} else if (is.windows()) {
return [
{
label: 'Interactive Notifications',
label: t('plugins.notifications.menu.interactive'),
type: 'checkbox',
checked: config.interactive,
// Doesn't update until restart
@ -54,24 +56,24 @@ export const onMenu = async ({
},
{
// Submenu with settings for interactive notifications (name shouldn't be too long)
label: 'Interactive Settings',
label: t('plugins.notifications.menu.interactive-settings.label'),
submenu: [
{
label: 'Open/Close on tray click',
label: t('plugins.notifications.menu.interactive-settings.submenu.tray-controls'),
type: 'checkbox',
checked: config.trayControls,
click: (item: MenuItem) =>
setConfig({ trayControls: item.checked }),
},
{
label: 'Hide Button Text',
label: t('plugins.notifications.menu.interactive-settings.submenu.hide-button-text'),
type: 'checkbox',
checked: config.hideButtonText,
click: (item: MenuItem) =>
setConfig({ hideButtonText: item.checked }),
},
{
label: 'Refresh on Play/Pause',
label: t('plugins.notifications.menu.interactive-settings.submenu.refresh-on-play-pause'),
type: 'checkbox',
checked: config.refreshOnPlayPause,
click: (item: MenuItem) =>
@ -80,7 +82,7 @@ export const onMenu = async ({
],
},
{
label: 'Style',
label: t('plugins.notifications.menu.toast-style'),
submenu: getToastStyleMenuItems(config),
},
];
@ -92,7 +94,7 @@ export const onMenu = async ({
return [
...getMenu(),
{
label: 'Show notification on unpause',
label: t('plugins.notifications.menu.unpause-notification'),
type: 'checkbox',
checked: config.unpauseNotification,
click: (item) => setConfig({ unpauseNotification: item.checked }),

View File

@ -4,6 +4,7 @@ import { createPlugin } from '@/utils';
import { onConfigChange, onMainLoad } from './main';
import { onMenu } from './menu';
import { onPlayerApiReady, onRendererLoad } from './renderer';
import { t } from '@/i18n';
export type PictureInPicturePluginConfig = {
enabled: boolean;
@ -18,8 +19,8 @@ export type PictureInPicturePluginConfig = {
};
export default createPlugin({
name: 'Picture In Picture',
description: 'Allows to switch the app to picture-in-picture mode',
name: t('plugins.picture-in-picture.name'),
description: t('plugins.picture-in-picture.description'),
restartNeeded: true,
config: {
'enabled': false,

View File

@ -2,6 +2,8 @@ import prompt from 'custom-electron-prompt';
import promptOptions from '@/providers/prompt-options';
import { t } from '@/i18n';
import type { PictureInPicturePluginConfig } from './index';
import type { MenuContext } from '@/types/contexts';
@ -16,7 +18,7 @@ export const onMenu = async ({
return [
{
label: 'Always on top',
label: t('plugins.picture-in-picture.menu.always-on-top'),
type: 'checkbox',
checked: config.alwaysOnTop,
click(item) {
@ -25,7 +27,7 @@ export const onMenu = async ({
},
},
{
label: 'Save window position',
label: t('plugins.picture-in-picture.menu.save-window-position'),
type: 'checkbox',
checked: config.savePosition,
click(item) {
@ -33,7 +35,7 @@ export const onMenu = async ({
},
},
{
label: 'Save window size',
label: t('plugins.picture-in-picture.menu.save-window-size'),
type: 'checkbox',
checked: config.saveSize,
click(item) {
@ -41,19 +43,19 @@ export const onMenu = async ({
},
},
{
label: 'Hotkey',
label: t('plugins.picture-in-picture.menu.hotkey.label'),
type: 'checkbox',
checked: !!config.hotkey,
async click(item) {
const output = await prompt(
{
title: 'Picture in Picture Hotkey',
label: 'Choose a hotkey for toggling Picture in Picture',
title: t('plugins.picture-in-picture.menu.prompt.title'),
label: t('plugins.picture-in-picture.menu.prompt.label'),
type: 'keybind',
keybindOptions: [
{
value: 'hotkey',
label: 'Hotkey',
label: t('plugins.picture-in-picture.menu.prompt.keybind-options.hotkey'),
default: config.hotkey,
},
],
@ -74,7 +76,7 @@ export const onMenu = async ({
},
},
{
label: 'Use native PiP',
label: t('plugins.picture-in-picture.menu.use-native-pip'),
type: 'checkbox',
checked: config.useNativePiP,
click(item) {

View File

@ -44,7 +44,7 @@
class="text style-scope ytmusic-menu-navigation-item-renderer"
id="ytmcustom-pip"
>
Picture in picture
<ytmd-trans key="plugins.picture-in-picture.templates.button"></ytmd-trans>
</div>
</div>
</div>

View File

@ -1,10 +1,10 @@
import { createPlugin } from '@/utils';
import { onPlayerApiReady, onUnload } from './renderer';
import { t } from '@/i18n';
export default createPlugin({
name: 'Playback Speed',
description:
'Listen fast, listen slow! Adds a slider that controls song speed',
name: t('plugins.playback-speed.name'),
description: t('plugins.playback-speed.description'),
restartNeeded: false,
config: {
enabled: false,

View File

@ -83,7 +83,7 @@
class="text style-scope ytmusic-menu-navigation-item-renderer"
id="ytmcustom-playback-speed"
>
Speed (<span id="playback-speed-value">1</span>)
<ytmd-trans key="plugins.playback-speed.templates.button"></ytmd-trans> (<span id="playback-speed-value">1</span>)
</div>
</div>
</div>

View File

@ -7,32 +7,41 @@ import { createPlugin } from '@/utils';
import promptOptions from '@/providers/prompt-options';
import { overrideListener } from './override';
import { onConfigChange, onPlayerApiReady } from './renderer';
import { t } from '@/i18n';
export type PreciseVolumePluginConfig = {
enabled: boolean;
/**
* Percentage of volume to change
*/
steps: number;
/**
* Enable ArrowUp + ArrowDown local shortcuts
*/
arrowsShortcut: boolean;
globalShortcuts: {
volumeUp: string;
volumeDown: string;
};
/**
* Plugin save volume between session here
*/
savedVolume: number | undefined;
};
export default createPlugin({
name: 'Precise Volume',
description:
'Control the volume precisely using mousewheel/hotkeys, with a custom HUD and customizable volume steps',
name: t('plugins.precise-volume.name'),
description: t('plugins.precise-volume.description'),
restartNeeded: true,
config: {
enabled: false,
steps: 1, // Percentage of volume to change
arrowsShortcut: true, // Enable ArrowUp + ArrowDown local shortcuts
steps: 1,
arrowsShortcut: true,
globalShortcuts: {
volumeUp: '',
volumeDown: '',
},
savedVolume: undefined, // Plugin save volume between session here
savedVolume: undefined,
} as PreciseVolumePluginConfig,
stylesheets: [hudStyle],
menu: async ({ setConfig, getConfig, window }) => {
@ -66,8 +75,8 @@ export default createPlugin({
async function promptVolumeSteps(options: PreciseVolumePluginConfig) {
const output = await prompt(
{
title: 'Volume Steps',
label: 'Choose Volume Increase/Decrease Steps',
title: t('plugins.precise-volume.prompt.volume-steps.title'),
label: t('plugins.precise-volume.prompt.volume-steps.label'),
value: options.steps || 1,
type: 'counter',
counterOptions: { minimum: 0, maximum: 100, multiFire: true },
@ -89,17 +98,17 @@ export default createPlugin({
) {
const output = await prompt(
{
title: 'Global Volume Keybinds',
label: 'Choose Global Volume Keybinds:',
title: t('plugins.precise-volume.prompt.global-shortcuts.title'),
label: t('plugins.precise-volume.prompt.global-shortcuts.label'),
type: 'keybind',
keybindOptions: [
kb(
'Increase Volume',
t('plugins.precise-volume.prompt.global-shortcuts.keybind-options.increase'),
'volumeUp',
options.globalShortcuts?.volumeUp,
),
kb(
'Decrease Volume',
t('plugins.precise-volume.prompt.global-shortcuts.keybind-options.decrease'),
'volumeDown',
options.globalShortcuts?.volumeDown,
),
@ -132,7 +141,7 @@ export default createPlugin({
return [
{
label: 'Local Arrowkeys Controls',
label: t('plugins.precise-volume.menu.arrows-shortcuts'),
type: 'checkbox',
checked: Boolean(config.arrowsShortcut),
click(item) {
@ -140,7 +149,7 @@ export default createPlugin({
},
},
{
label: 'Global Hotkeys',
label: t('plugins.precise-volume.menu.global-shortcuts'),
type: 'checkbox',
checked: Boolean(
config.globalShortcuts?.volumeUp ??
@ -149,7 +158,7 @@ export default createPlugin({
click: (item) => promptGlobalShortcuts(config, item),
},
{
label: 'Set Custom Volume Steps',
label: t('plugins.precise-volume.menu.custom-volume-steps'),
click: () => promptVolumeSteps(config),
},
];

View File

@ -4,13 +4,13 @@ import QualitySettingsTemplate from './templates/qualitySettingsTemplate.html?ra
import { createPlugin } from '@/utils';
import { ElementFromHtml } from '@/plugins/utils/renderer';
import { t } from '@/i18n';
import type { YoutubePlayer } from '@/types/youtube-player';
export default createPlugin({
name: 'Video Quality Changer',
description:
'Allows changing the video quality with a button on the video overlay',
name: t('plugins.quality-changer.name'),
description: t('plugins.quality-changer.description'),
restartNeeded: false,
config: {
enabled: false,
@ -24,9 +24,11 @@ export default createPlugin({
type: 'question',
buttons: qualityLabels,
defaultId: currentIndex,
title: 'Choose Video Quality',
message: 'Choose Video Quality:',
detail: `Current Quality: ${qualityLabels[currentIndex]}`,
title: t('plugins.quality-changer.backend.dialog.title'),
message: t('plugins.quality-changer.backend.dialog.message'),
detail: t('plugins.quality-changer.backend.dialog.detail', {
quality: qualityLabels[currentIndex],
}),
cancelId: -1,
}),
);

View File

@ -1,6 +1,7 @@
import { createPlugin } from '@/utils';
import { onMainLoad } from './main';
import { onMenu } from './menu';
import { t } from '@/i18n';
export type ShortcutMappingType = {
previous: string;
@ -15,9 +16,8 @@ export type ShortcutsPluginConfig = {
};
export default createPlugin({
name: 'Shortcuts (& MPRIS)',
description:
'Allows setting global hotkeys for playback (play/pause/next/previous) + disable media osd by overriding media keys + enable Ctrl/CMD + F to search + enable linux mpris support for mediakeys + custom hotkeys for advanced users',
name: t('plugins.shortcuts.name'),
description: t('plugins.shortcuts.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -2,6 +2,8 @@ import prompt, { KeybindOptions } from 'custom-electron-prompt';
import promptOptions from '@/providers/prompt-options';
import { t } from '@/i18n';
import type { ShortcutsPluginConfig } from './index';
import type { BrowserWindow } from 'electron';
import type { MenuContext } from '@/types/contexts';
@ -29,14 +31,14 @@ export const onMenu = async ({
) {
const output = await prompt(
{
title: 'Global Keybinds',
label: 'Choose Global Keybinds for Songs Control:',
title: t('plugins.shortcuts.prompt.keybind.title'),
label: t('plugins.shortcuts.prompt.keybind.label'),
type: 'keybind',
keybindOptions: [
// If default=undefined then no default is used
kb('Previous', 'previous', config.global?.previous),
kb('Play / Pause', 'playPause', config.global?.playPause),
kb('Next', 'next', config.global?.next),
kb(t('plugins.shortcuts.prompt.keybind.keybind-options.previous'), 'previous', config.global?.previous),
kb(t('plugins.shortcuts.prompt.keybind.keybind-options.play-pause'), 'playPause', config.global?.playPause),
kb(t('plugins.shortcuts.prompt.keybind.keybind-options.next'), 'next', config.global?.next),
],
height: 270,
...promptOptions(),
@ -59,11 +61,11 @@ export const onMenu = async ({
return [
{
label: 'Set Global Song Controls',
label: t('plugins.shortcuts.menu.set-keybinds'),
click: () => promptKeybind(config, window),
},
{
label: 'Override MediaKeys',
label: t('plugins.shortcuts.menu.override-media-keys'),
type: 'checkbox',
checked: config.overrideMediaKeys,
click: (item) => setConfig({ overrideMediaKeys: item.checked }),

View File

@ -1,5 +1,6 @@
import { createPlugin } from '@/utils';
import { onRendererLoad, onRendererUnload } from './renderer';
import { t } from '@/i18n';
export type SkipSilencesPluginConfig = {
enabled: boolean;
@ -7,8 +8,8 @@ export type SkipSilencesPluginConfig = {
};
export default createPlugin({
name: 'Skip Silences',
description: 'Automatically skip silenced sections',
name: t('plugins.skip-silences.name'),
description: t('plugins.skip-silences.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -4,6 +4,8 @@ import { createPlugin } from '@/utils';
import { sortSegments } from './segments';
import { t } from '@/i18n';
import type { GetPlayerResponse } from '@/types/get-player-response';
import type { Segment, SkipSegment } from './types';
@ -23,9 +25,8 @@ export type SponsorBlockPluginConfig = {
let currentSegments: Segment[] = [];
export default createPlugin({
name: 'SponsorBlock',
description:
"Automatically Skips non-music parts like intro/outro or parts of music videos where the song isn't playing",
name: t('plugins.sponsorblock.name'),
description: t('plugins.sponsorblock.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -9,10 +9,11 @@ import { createPlugin } from '@/utils';
import getSongControls from '@/providers/song-controls';
import registerCallback, { type SongInfo } from '@/providers/song-info';
import { mediaIcons } from '@/types/media-icons';
import { t } from '@/i18n';
export default createPlugin({
name: 'Taskbar Media Control',
description: 'Control playback from your Windows taskbar',
name: t('plugins.taskbar-mediacontrol.name'),
description: t('plugins.taskbar-mediacontrol.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -3,10 +3,11 @@ import { type NativeImage, TouchBar } from 'electron';
import { createPlugin } from '@/utils';
import getSongControls from '@/providers/song-controls';
import registerCallback from '@/providers/song-info';
import { t } from '@/i18n';
export default createPlugin({
name: 'TouchBar',
description: 'Custom TouchBar layout for macOS',
name: t('plugins.touchbar.name'),
description: t('plugins.touchbar.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -4,6 +4,7 @@ import is from 'electron-is';
import { createPlugin } from '@/utils';
import registerCallback from '@/providers/song-info';
import { t } from '@/i18n';
interface Data {
album: string | null | undefined;
@ -18,8 +19,8 @@ interface Data {
}
export default createPlugin({
name: 'Tuna OBS',
description: "Integration with OBS's plugin Tuna",
name: t('plugins.tuna-obs.name'),
description: t('plugins.tuna-obs.description'),
restartNeeded: true,
config: {
enabled: false,

View File

@ -6,6 +6,8 @@ import { createPlugin } from '@/utils';
import { moveVolumeHud as preciseVolumeMoveVolumeHud } from '@/plugins/precise-volume/renderer';
import { ElementFromHtml } from '@/plugins/utils/renderer';
import { ThumbnailElement } from '@/types/get-player-response';
import { t } from '@/i18n';
import { MenuTemplate } from '@/menu';
export type VideoTogglePluginConfig = {
enabled: boolean;
@ -16,9 +18,8 @@ export type VideoTogglePluginConfig = {
};
export default createPlugin({
name: 'Video Toggle',
description:
'Adds a button to switch between Video/Song mode. can also optionally remove the whole video tab',
name: t('plugins.video-toggle.name'),
description: t('plugins.video-toggle.description'),
restartNeeded: true,
config: {
enabled: false,
@ -28,15 +29,15 @@ export default createPlugin({
align: 'left',
} as VideoTogglePluginConfig,
stylesheets: [buttonSwitcherStyle, forceHideStyle],
menu: async ({ getConfig, setConfig }) => {
menu: async ({ getConfig, setConfig }): Promise<MenuTemplate> => {
const config = await getConfig();
return [
{
label: 'Mode',
label: t('plugins.video-toggle.menu.mode.label'),
submenu: [
{
label: 'Custom toggle',
label: t('plugins.video-toggle.menu.mode.submenu.custom'),
type: 'radio',
checked: config.mode === 'custom',
click() {
@ -44,7 +45,7 @@ export default createPlugin({
},
},
{
label: 'Native toggle',
label: t('plugins.video-toggle.menu.mode.submenu.native'),
type: 'radio',
checked: config.mode === 'native',
click() {
@ -52,7 +53,7 @@ export default createPlugin({
},
},
{
label: 'Disabled',
label: t('plugins.video-toggle.menu.mode.submenu.disabled'),
type: 'radio',
checked: config.mode === 'disabled',
click() {
@ -62,10 +63,10 @@ export default createPlugin({
],
},
{
label: 'Alignment',
label: t('plugins.video-toggle.menu.align.label'),
submenu: [
{
label: 'Left',
label: t('plugins.video-toggle.menu.align.submenu.left'),
type: 'radio',
checked: config.align === 'left',
click() {
@ -73,7 +74,7 @@ export default createPlugin({
},
},
{
label: 'Middle',
label: t('plugins.video-toggle.menu.align.submenu.middle'),
type: 'radio',
checked: config.align === 'middle',
click() {
@ -81,7 +82,7 @@ export default createPlugin({
},
},
{
label: 'Right',
label: t('plugins.video-toggle.menu.align.submenu.right'),
type: 'radio',
checked: config.align === 'right',
click() {
@ -91,7 +92,7 @@ export default createPlugin({
],
},
{
label: 'Force Remove Video Tab',
label: t('plugins.video-toggle.menu.force-hide'),
type: 'checkbox',
checked: config.forceHide,
click(item) {

View File

@ -1,4 +1,8 @@
<div class="video-switch-button">
<input checked="true" class="video-switch-button-checkbox" type="checkbox" />
<label class="video-switch-button-label" for=""><span class="video-switch-button-label-span">Song</span></label>
<label class="video-switch-button-label" for="">
<span class="video-switch-button-label-span">
<ytmd-trans key="plugins.video-toggle.templates.button"></ytmd-trans>
</span>
</label>
</div>

View File

@ -6,6 +6,7 @@ import {
VudioVisualizer as vudio,
WaveVisualizer as wave,
} from './visualizers';
import { t } from '@/i18n';
type WaveColor = {
gradient: string[];
@ -57,8 +58,8 @@ export type VisualizerPluginConfig = {
};
export default createPlugin({
name: 'Visualizer',
description: 'Adds a visualizer to the player',
name: t('plugins.visualizer.name'),
description: t('plugins.visualizer.description'),
restartNeeded: true,
config: {
enabled: false,
@ -133,7 +134,7 @@ export default createPlugin({
return [
{
label: 'Type',
label: t('plugins.visualizer.menu.visualizer-type'),
submenu: visualizerTypes.map((visualizerType) => ({
label: visualizerType,
type: 'radio',