diff --git a/README-zh.md b/README-zh.md
index abeba7b..0b05689 100644
--- a/README-zh.md
+++ b/README-zh.md
@@ -86,7 +86,15 @@ MAX_FILE_SIZE=500000000
## 星标历史
-[](https://www.star-history.com/#franklioxygen/MyTube&type=date&legend=bottom-right)
+## Star History
+
+
+
+
+
+
+
+
## 免责声明
diff --git a/README.md b/README.md
index ccfe567..0c2afd9 100644
--- a/README.md
+++ b/README.md
@@ -86,7 +86,13 @@ For detailed instructions on how to deploy MyTube using Docker, please refer to
## Star History
-[](https://www.star-history.com/#franklioxygen/MyTube&type=date&legend=bottom-right)
+
+
+
+
+
+
+
## Disclaimer
diff --git a/backend/src/controllers/settingsController.ts b/backend/src/controllers/settingsController.ts
index 73fc36a..2e8db21 100644
--- a/backend/src/controllers/settingsController.ts
+++ b/backend/src/controllers/settingsController.ts
@@ -29,6 +29,7 @@ interface Settings {
ytDlpConfig?: string;
showYoutubeSearch?: boolean;
proxyOnlyYoutube?: boolean;
+ moveSubtitlesToVideoFolder?: boolean;
}
const defaultSettings: Settings = {
@@ -189,6 +190,16 @@ export const updateSettings = async (req: Request, res: Response) => {
storageService.saveSettings(newSettings);
+ // Check for moveSubtitlesToVideoFolder change
+ if (newSettings.moveSubtitlesToVideoFolder !== existingSettings.moveSubtitlesToVideoFolder) {
+ if (newSettings.moveSubtitlesToVideoFolder !== undefined) {
+ // Run asynchronously
+ const { moveAllSubtitles } = await import("../services/subtitleService");
+ moveAllSubtitles(newSettings.moveSubtitlesToVideoFolder)
+ .catch(err => console.error("Error moving subtitles in background:", err));
+ }
+ }
+
// Apply settings immediately where possible
downloadManager.setMaxConcurrentDownloads(
newSettings.maxConcurrentDownloads
diff --git a/backend/src/services/storageService.ts b/backend/src/services/storageService.ts
index 2429ee2..2336f9a 100644
--- a/backend/src/services/storageService.ts
+++ b/backend/src/services/storageService.ts
@@ -2,22 +2,22 @@ import { and, desc, eq, lt } from "drizzle-orm";
import fs from "fs-extra";
import path from "path";
import {
- DATA_DIR,
- IMAGES_DIR,
- STATUS_DATA_PATH,
- SUBTITLES_DIR,
- UPLOADS_DIR,
- VIDEOS_DIR,
+ DATA_DIR,
+ IMAGES_DIR,
+ STATUS_DATA_PATH,
+ SUBTITLES_DIR,
+ UPLOADS_DIR,
+ VIDEOS_DIR,
} from "../config/paths";
import { db, sqlite } from "../db";
import {
- collections,
- collectionVideos,
- downloadHistory,
- downloads,
- settings,
- videoDownloads,
- videos,
+ collections,
+ collectionVideos,
+ downloadHistory,
+ downloads,
+ settings,
+ videoDownloads,
+ videos,
} from "../db/schema";
import { formatVideoFilename } from "../utils/helpers";
@@ -1506,6 +1506,61 @@ export function addVideoToCollection(
}
}
+ // Handle subtitles
+ if (video.subtitles && video.subtitles.length > 0) {
+ const newSubtitles = [...video.subtitles];
+ let subtitlesUpdated = false;
+
+ newSubtitles.forEach((sub, index) => {
+ let currentSubPath = sub.path;
+ // Determine existing absolute path
+ let absoluteSourcePath = "";
+ if (sub.path.startsWith("/videos/")) {
+ absoluteSourcePath = path.join(VIDEOS_DIR, sub.path.replace("/videos/", ""));
+ } else if (sub.path.startsWith("/subtitles/")) {
+ absoluteSourcePath = path.join(path.dirname(SUBTITLES_DIR), sub.path); // SUBTITLES_DIR is uploads/subtitles
+ }
+
+ // If we can't determine source path easily from DB, try to find it
+ if (!fs.existsSync(absoluteSourcePath)) {
+ // Fallback: try finding in root or collection folders
+ // But simpler to rely on path stored in DB if valid
+ }
+
+ let targetSubDir = "";
+ let newWebPath = "";
+
+ // Logic:
+ // If it's currently in VIDEOS_DIR (starts with /videos/), it should stay with video -> move to new video folder
+ // If it's currently in SUBTITLES_DIR (starts with /subtitles/), it should move to new mirror folder in SUBTITLES_DIR
+
+ if (sub.path.startsWith("/videos/")) {
+ targetSubDir = path.join(VIDEOS_DIR, collectionName);
+ newWebPath = `/videos/${collectionName}/${path.basename(sub.path)}`;
+ } else if (sub.path.startsWith("/subtitles/")) {
+ targetSubDir = path.join(SUBTITLES_DIR, collectionName);
+ newWebPath = `/subtitles/${collectionName}/${path.basename(sub.path)}`;
+ }
+
+ if (absoluteSourcePath && targetSubDir && newWebPath) {
+ const targetSubPath = path.join(targetSubDir, path.basename(sub.path));
+ if (fs.existsSync(absoluteSourcePath) && absoluteSourcePath !== targetSubPath) {
+ moveFile(absoluteSourcePath, targetSubPath);
+ newSubtitles[index] = {
+ ...sub,
+ path: newWebPath
+ };
+ subtitlesUpdated = true;
+ }
+ }
+ });
+
+ if (subtitlesUpdated) {
+ updates.subtitles = newSubtitles;
+ updated = true;
+ }
+ }
+
if (updated) {
updateVideo(videoId, updates);
}
@@ -1577,6 +1632,65 @@ export function removeVideoFromCollection(
}
}
+ // Handle subtitles
+ if (video.subtitles && video.subtitles.length > 0) {
+ const newSubtitles = [...video.subtitles];
+ let subtitlesUpdated = false;
+
+ newSubtitles.forEach((sub, index) => {
+ let absoluteSourcePath = "";
+ // Construct absolute source path based on DB path
+ if (sub.path.startsWith("/videos/")) {
+ absoluteSourcePath = path.join(VIDEOS_DIR, sub.path.replace("/videos/", ""));
+ } else if (sub.path.startsWith("/subtitles/")) {
+ // sub.path is like /subtitles/Collection/file.vtt
+ // SUBTITLES_DIR is uploads/subtitles
+ absoluteSourcePath = path.join(UPLOADS_DIR, sub.path.replace(/^\//, "")); // path.join(headers...) -> uploads/subtitles/...
+ }
+
+ let targetSubDir = "";
+ let newWebPath = "";
+
+ if (sub.path.startsWith("/videos/")) {
+ targetSubDir = targetVideoDir; // Calculated above (root or other collection)
+ newWebPath = `${videoPathPrefix}/${path.basename(sub.path)}`;
+ } else if (sub.path.startsWith("/subtitles/")) {
+ // Should move to root subtitles or other collection subtitles
+ if (otherCollection) {
+ const otherName = otherCollection.name || otherCollection.title;
+ if (otherName) {
+ targetSubDir = path.join(SUBTITLES_DIR, otherName);
+ newWebPath = `/subtitles/${otherName}/${path.basename(sub.path)}`;
+ }
+ } else {
+ // Move to root subtitles dir
+ targetSubDir = SUBTITLES_DIR;
+ newWebPath = `/subtitles/${path.basename(sub.path)}`;
+ }
+ }
+
+ if (absoluteSourcePath && targetSubDir && newWebPath) {
+ const targetSubPath = path.join(targetSubDir, path.basename(sub.path));
+
+ // Ensure correct paths for move
+ // Need to handle potential double slashes or construction issues if any
+ if (fs.existsSync(absoluteSourcePath) && absoluteSourcePath !== targetSubPath) {
+ moveFile(absoluteSourcePath, targetSubPath);
+ newSubtitles[index] = {
+ ...sub,
+ path: newWebPath
+ };
+ subtitlesUpdated = true;
+ }
+ }
+ });
+
+ if (subtitlesUpdated) {
+ updates.subtitles = newSubtitles;
+ updated = true;
+ }
+ }
+
if (updated) {
updateVideo(videoId, updates);
}
@@ -1625,6 +1739,52 @@ export function deleteCollectionWithFiles(collectionId: string): boolean {
}
}
+ // Handle subtitles
+ if (video.subtitles && video.subtitles.length > 0) {
+ const newSubtitles = [...video.subtitles];
+ let subtitlesUpdated = false;
+
+ newSubtitles.forEach((sub, index) => {
+ let absoluteSourcePath = "";
+ // Construct absolute source path based on DB path
+ if (sub.path.startsWith("/videos/")) {
+ absoluteSourcePath = path.join(VIDEOS_DIR, sub.path.replace("/videos/", ""));
+ } else if (sub.path.startsWith("/subtitles/")) {
+ absoluteSourcePath = path.join(UPLOADS_DIR, sub.path.replace(/^\//, ""));
+ }
+
+ let targetSubDir = "";
+ let newWebPath = "";
+
+ if (sub.path.startsWith("/videos/")) {
+ targetSubDir = VIDEOS_DIR;
+ newWebPath = `/videos/${path.basename(sub.path)}`;
+ } else if (sub.path.startsWith("/subtitles/")) {
+ // Move to root subtitles dir
+ targetSubDir = SUBTITLES_DIR;
+ newWebPath = `/subtitles/${path.basename(sub.path)}`;
+ }
+
+ if (absoluteSourcePath && targetSubDir && newWebPath) {
+ const targetSubPath = path.join(targetSubDir, path.basename(sub.path));
+
+ if (fs.existsSync(absoluteSourcePath) && absoluteSourcePath !== targetSubPath) {
+ moveFile(absoluteSourcePath, targetSubPath);
+ newSubtitles[index] = {
+ ...sub,
+ path: newWebPath
+ };
+ subtitlesUpdated = true;
+ }
+ }
+ });
+
+ if (subtitlesUpdated) {
+ updates.subtitles = newSubtitles;
+ updated = true;
+ }
+ }
+
if (updated) {
updateVideo(videoId, updates);
}
diff --git a/backend/src/services/subtitleService.ts b/backend/src/services/subtitleService.ts
new file mode 100644
index 0000000..f1a862a
--- /dev/null
+++ b/backend/src/services/subtitleService.ts
@@ -0,0 +1,147 @@
+
+import fs from "fs-extra";
+import path from "path";
+import { SUBTITLES_DIR, VIDEOS_DIR } from "../config/paths";
+import * as storageService from "./storageService";
+
+export const moveAllSubtitles = async (toVideoFolder: boolean) => {
+ console.log(`Starting to move all subtitles. Target: ${toVideoFolder ? 'Video Folders' : 'Central Subtitles Folder'}`);
+ const allVideos = storageService.getVideos();
+ let movedCount = 0;
+ let errorCount = 0;
+
+ for (const video of allVideos) {
+ if (!video.subtitles || video.subtitles.length === 0) continue;
+
+ const newSubtitles = [];
+ let videoChanged = false;
+
+ // Determine where the video file is located
+ let videoDir = VIDEOS_DIR;
+ let relativeVideoDir = ""; // Relative to VIDEOS_DIR
+
+ if (video.videoFilename) {
+ // We need to find the actual location of the video file to know its folder
+ // storageService.findVideoFile is private, but we can replicate the logic or use the path stored in DB if reliable.
+ // However, video.videoPath usually starts with /videos/...
+ // Let's rely on finding the file to be sure.
+
+ // Heuristic: check if it's in a collection folder
+ // We can iterate collections, or check the file system.
+ // Let's look at the videoPath property if available, it usually reflects the web path
+ // e.g. /videos/MyCollection/video.mp4 or /videos/video.mp4
+ if (video.videoPath) {
+ const cleanPath = video.videoPath.replace(/^\/videos\//, '');
+ const dirName = path.dirname(cleanPath);
+ if (dirName && dirName !== '.') {
+ videoDir = path.join(VIDEOS_DIR, dirName);
+ relativeVideoDir = dirName;
+ }
+ } else {
+ // Fallback: check collections
+ const collections = storageService.getCollections();
+ for (const col of collections) {
+ if (col.videos.includes(video.id)) {
+ const colName = col.name || col.title;
+ if (colName) {
+ videoDir = path.join(VIDEOS_DIR, colName);
+ relativeVideoDir = colName;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ for (const sub of video.subtitles) {
+ try {
+ // Determine current absolute path
+ // sub.path is like /subtitles/filename.vtt (if central) or /videos/Folder/filename.vtt (if local)
+ // BUT we should rely on where the file ACTUALLY is right now.
+ // We can try to resolve it based on the stored path.
+
+ let currentAbsPath = "";
+ if (sub.path.startsWith("/videos/")) {
+ currentAbsPath = path.join(VIDEOS_DIR, sub.path.replace(/^\/videos\//, ""));
+ } else if (sub.path.startsWith("/subtitles/")) {
+ currentAbsPath = path.join(SUBTITLES_DIR, sub.path.replace(/^\/subtitles\//, ""));
+ } else {
+ // Fallback to filename search in both locations
+ const centralPath = path.join(SUBTITLES_DIR, sub.filename);
+ if (fs.existsSync(centralPath)) {
+ currentAbsPath = centralPath;
+ } else {
+ const localPath = path.join(videoDir, sub.filename);
+ if (fs.existsSync(localPath)) {
+ currentAbsPath = localPath;
+ }
+ }
+ }
+
+ if (!fs.existsSync(currentAbsPath)) {
+ console.warn(`Subtitle file not found: ${sub.path} or ${currentAbsPath}`);
+ newSubtitles.push(sub); // Keep the record even if file missing? Or maybe better to keep it to avoid data loss.
+ continue;
+ }
+
+ let targetAbsPath = "";
+ let newWebPath = "";
+
+ if (toVideoFolder) {
+ // Move TO video folder
+ targetAbsPath = path.join(videoDir, sub.filename);
+ if (relativeVideoDir) {
+ newWebPath = `/videos/${relativeVideoDir}/${sub.filename}`;
+ } else {
+ newWebPath = `/videos/${sub.filename}`;
+ }
+ } else {
+ // Move TO central subtitles folder
+ // Mirror the folder structure
+ if (relativeVideoDir) {
+ const targetDir = path.join(SUBTITLES_DIR, relativeVideoDir);
+ fs.ensureDirSync(targetDir);
+ targetAbsPath = path.join(targetDir, sub.filename);
+ newWebPath = `/subtitles/${relativeVideoDir}/${sub.filename}`;
+ } else {
+ targetAbsPath = path.join(SUBTITLES_DIR, sub.filename);
+ newWebPath = `/subtitles/${sub.filename}`;
+ }
+ }
+
+ if (currentAbsPath !== targetAbsPath) {
+ fs.moveSync(currentAbsPath, targetAbsPath, { overwrite: true });
+ newSubtitles.push({
+ ...sub,
+ path: newWebPath
+ });
+ videoChanged = true;
+ movedCount++;
+ } else {
+ // Already in the right place, but ensure path is correct
+ if (sub.path !== newWebPath) {
+ newSubtitles.push({
+ ...sub,
+ path: newWebPath
+ });
+ videoChanged = true;
+ } else {
+ newSubtitles.push(sub);
+ }
+ }
+
+ } catch (err) {
+ console.error(`Failed to move subtitle ${sub.filename}:`, err);
+ newSubtitles.push(sub); // Keep original on error
+ errorCount++;
+ }
+ }
+
+ if (videoChanged) {
+ storageService.updateVideo(video.id, { subtitles: newSubtitles });
+ }
+ }
+
+ console.log(`Finished moving subtitles. Moved: ${movedCount}, Errors: ${errorCount}`);
+ return { movedCount, errorCount };
+};
diff --git a/frontend/src/components/Settings/DatabaseSettings.tsx b/frontend/src/components/Settings/DatabaseSettings.tsx
index 2821757..1f01163 100644
--- a/frontend/src/components/Settings/DatabaseSettings.tsx
+++ b/frontend/src/components/Settings/DatabaseSettings.tsx
@@ -1,4 +1,4 @@
-import { Box, Button, Typography } from '@mui/material';
+import { Box, Button, FormControlLabel, Switch, Typography } from '@mui/material';
import React from 'react';
import { useLanguage } from '../../contexts/LanguageContext';
@@ -7,9 +7,18 @@ interface DatabaseSettingsProps {
onDeleteLegacy: () => void;
onFormatFilenames: () => void;
isSaving: boolean;
+ moveSubtitlesToVideoFolder: boolean;
+ onMoveSubtitlesToVideoFolderChange: (checked: boolean) => void;
}
-const DatabaseSettings: React.FC = ({ onMigrate, onDeleteLegacy, onFormatFilenames, isSaving }) => {
+const DatabaseSettings: React.FC = ({
+ onMigrate,
+ onDeleteLegacy,
+ onFormatFilenames,
+ isSaving,
+ moveSubtitlesToVideoFolder,
+ onMoveSubtitlesToVideoFolderChange
+}) => {
const { t } = useLanguage();
return (
@@ -56,6 +65,23 @@ const DatabaseSettings: React.FC = ({ onMigrate, onDelete
{t('deleteLegacyDataButton')}
+
+
+ {t('moveSubtitlesToVideoFolder')}
+ onMoveSubtitlesToVideoFolderChange(e.target.checked)}
+ disabled={isSaving}
+ />
+ }
+ label={moveSubtitlesToVideoFolder ? t('moveSubtitlesToVideoFolderOn') : t('moveSubtitlesToVideoFolderOff')}
+ />
+
+ {t('moveSubtitlesToVideoFolderDescription')}
+
+
);
};
diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx
index 4fe8790..36782c1 100644
--- a/frontend/src/pages/SettingsPage.tsx
+++ b/frontend/src/pages/SettingsPage.tsx
@@ -53,7 +53,8 @@ const SettingsPage: React.FC = () => {
itemsPerPage: 12,
ytDlpConfig: '',
showYoutubeSearch: true,
- proxyOnlyYoutube: false
+ proxyOnlyYoutube: false,
+ moveSubtitlesToVideoFolder: false
});
const [message, setMessage] = useState<{ text: string; type: 'success' | 'error' | 'warning' | 'info' } | null>(null);
const debouncedSettings = useDebounce(settings, 1000);
@@ -405,6 +406,8 @@ const SettingsPage: React.FC = () => {
onDeleteLegacy={() => setShowDeleteLegacyModal(true)}
onFormatFilenames={() => setShowFormatConfirmModal(true)}
isSaving={isSaving}
+ moveSubtitlesToVideoFolder={settings.moveSubtitlesToVideoFolder || false}
+ onMoveSubtitlesToVideoFolderChange={(checked) => handleChange('moveSubtitlesToVideoFolder', checked)}
/>
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 7915e35..00cbe1e 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -74,4 +74,5 @@ export interface Settings {
ytDlpConfig?: string;
showYoutubeSearch?: boolean;
proxyOnlyYoutube?: boolean;
+ moveSubtitlesToVideoFolder?: boolean;
}
diff --git a/frontend/src/utils/locales/ar.ts b/frontend/src/utils/locales/ar.ts
index 79e7911..46b0fc2 100644
--- a/frontend/src/utils/locales/ar.ts
+++ b/frontend/src/utils/locales/ar.ts
@@ -251,6 +251,12 @@ export const ar = {
collectionContains: "تحتوي هذه المجموعة على",
deleteCollectionOnly: "حذف المجموعة فقط",
+ proxyOnlyApplyToYoutube: 'الوكيل ينطبق فقط على يوتيوب',
+ moveSubtitlesToVideoFolder: 'موقع الترجمة',
+ moveSubtitlesToVideoFolderOn: 'مع الفيديو',
+ moveSubtitlesToVideoFolderOff: 'في مجلد الترجمات المعزول',
+ moveSubtitlesToVideoFolderDescription: 'عند التمكن، سيتم نقل ملفات الترجمة إلى نفس المجلد الموجود به ملف الفيديو. عند التعطيل، سيتم نقلها إلى مجلد الترجمات المعزول.',
+
// Snackbar Messages
videoDownloading: "جاري تنزيل الفيديو",
downloadStartedSuccessfully: "بدأ التنزيل بنجاح",
diff --git a/frontend/src/utils/locales/de.ts b/frontend/src/utils/locales/de.ts
index f3b6eb0..bae06e6 100644
--- a/frontend/src/utils/locales/de.ts
+++ b/frontend/src/utils/locales/de.ts
@@ -426,4 +426,9 @@ export const de = {
hide: "Ausblenden",
reset: "Zurücksetzen",
more: "Mehr",
+ proxyOnlyApplyToYoutube: 'Proxy nur auf Youtube anwenden',
+ moveSubtitlesToVideoFolder: 'Untertitel-Speicherort',
+ moveSubtitlesToVideoFolderOn: 'Zusammen mit Video',
+ moveSubtitlesToVideoFolderOff: 'Im isolierten Untertitel-Ordner',
+ moveSubtitlesToVideoFolderDescription: 'Wenn aktiviert, werden Untertiteldateien in denselben Ordner wie die Videodatei verschoben. Wenn deaktiviert, werden sie in den isolierten Untertitelordner verschoben.',
};
diff --git a/frontend/src/utils/locales/en.ts b/frontend/src/utils/locales/en.ts
index d84a091..395b37c 100644
--- a/frontend/src/utils/locales/en.ts
+++ b/frontend/src/utils/locales/en.ts
@@ -455,4 +455,8 @@ export const en = {
reset: "Reset",
more: "More",
proxyOnlyApplyToYoutube: 'Proxy only apply to Youtube',
+ moveSubtitlesToVideoFolder: 'Subtitles Location',
+ moveSubtitlesToVideoFolderOn: 'With video together',
+ moveSubtitlesToVideoFolderOff: 'In isolated subtitle folder',
+ moveSubtitlesToVideoFolderDescription: 'When enabled, subtitle files will be moved to the same folder as the video file. When disabled, they will be moved to the isolated subtitle folder.',
};
diff --git a/frontend/src/utils/locales/es.ts b/frontend/src/utils/locales/es.ts
index ab88342..81f55b3 100644
--- a/frontend/src/utils/locales/es.ts
+++ b/frontend/src/utils/locales/es.ts
@@ -22,6 +22,11 @@ export const es = {
searchResultsFor: "Resultados de búsqueda para",
fromYourLibrary: "De tu Biblioteca",
noMatchingVideos: "No hay videos coincidentes en tu biblioteca.",
+ proxyOnlyApplyToYoutube: 'Proxy solo se aplica a Youtube',
+ moveSubtitlesToVideoFolder: 'Ubicación de subtítulos',
+ moveSubtitlesToVideoFolderOn: 'Junto al video',
+ moveSubtitlesToVideoFolderOff: 'En carpeta de subtítulos aislada',
+ moveSubtitlesToVideoFolderDescription: 'Cuando está habilitado, los archivos de subtítulos se moverán a la misma carpeta que el archivo de video. Cuando está deshabilitado, se moverán a la carpeta de subtítulos aislada.',
fromYouTube: "De YouTube",
loadingYouTubeResults: "Cargando resultados de YouTube...",
noYouTubeResults: "No se encontraron resultados de YouTube",
diff --git a/frontend/src/utils/locales/fr.ts b/frontend/src/utils/locales/fr.ts
index 213d102..72fb7df 100644
--- a/frontend/src/utils/locales/fr.ts
+++ b/frontend/src/utils/locales/fr.ts
@@ -464,4 +464,9 @@ export const fr = {
hide: "Masquer",
reset: "Réinitialiser",
more: "Plus",
+ proxyOnlyApplyToYoutube: 'Proxy s\'applique uniquement à Youtube',
+ moveSubtitlesToVideoFolder: 'Emplacement des sous-titres',
+ moveSubtitlesToVideoFolderOn: 'Avec la vidéo',
+ moveSubtitlesToVideoFolderOff: 'Dans le dossier de sous-titres isolé',
+ moveSubtitlesToVideoFolderDescription: 'Si activé, les fichiers de sous-titres seront déplacés dans le même dossier que le fichier vidéo. Si désactivé, ils seront déplacés vers le dossier de sous-titres isolé.',
};
diff --git a/frontend/src/utils/locales/ja.ts b/frontend/src/utils/locales/ja.ts
index 089ff9c..e5384a8 100644
--- a/frontend/src/utils/locales/ja.ts
+++ b/frontend/src/utils/locales/ja.ts
@@ -453,5 +453,9 @@ export const ja = {
hide: "隠す",
reset: "リセット",
more: "もっと見る",
- proxyOnlyApplyToYoutube: 'Proxy only apply to Youtube',
+ proxyOnlyApplyToYoutube: 'プロキシはYoutubeにのみ適用されます',
+ moveSubtitlesToVideoFolder: '字幕の場所',
+ moveSubtitlesToVideoFolderOn: '動画と同じ場所',
+ moveSubtitlesToVideoFolderOff: '独立した字幕フォルダー',
+ moveSubtitlesToVideoFolderDescription: '有効にすると、字幕ファイルは動画ファイルと同じフォルダーに移動されます。無効にすると、独立した字幕フォルダーに移動されます。',
};
diff --git a/frontend/src/utils/locales/ko.ts b/frontend/src/utils/locales/ko.ts
index bad66c3..8e2fbde 100644
--- a/frontend/src/utils/locales/ko.ts
+++ b/frontend/src/utils/locales/ko.ts
@@ -447,5 +447,9 @@ export const ko = {
hide: "숨기기",
reset: "초기화",
more: "더 보기",
- proxyOnlyApplyToYoutube: 'Proxy only apply to Youtube',
+ proxyOnlyApplyToYoutube: '프록시는 Youtube에만 적용됩니다',
+ moveSubtitlesToVideoFolder: '자막 위치',
+ moveSubtitlesToVideoFolderOn: '동영상과 함께',
+ moveSubtitlesToVideoFolderOff: '격리된 자막 폴더',
+ moveSubtitlesToVideoFolderDescription: '활성화하면 자막 파일이 동영상 파일과 같은 폴더로 이동됩니다. 비활성화하면 격리된 자막 폴더로 이동됩니다.',
};
diff --git a/frontend/src/utils/locales/pt.ts b/frontend/src/utils/locales/pt.ts
index 3ecda17..e29c1b8 100644
--- a/frontend/src/utils/locales/pt.ts
+++ b/frontend/src/utils/locales/pt.ts
@@ -458,4 +458,9 @@ export const pt = {
hide: "Ocultar",
reset: "Redefinir",
more: "Mais",
+ proxyOnlyApplyToYoutube: 'Proxy aplica-se apenas ao Youtube',
+ moveSubtitlesToVideoFolder: 'Localização das legendas',
+ moveSubtitlesToVideoFolderOn: 'Junto com o vídeo',
+ moveSubtitlesToVideoFolderOff: 'Na pasta de legendas isolada',
+ moveSubtitlesToVideoFolderDescription: 'Quando ativado, os arquivos de legenda serão movidos para a mesma pasta do arquivo de vídeo. Quando desativado, eles serão movidos para a pasta de legendas isolada.',
};
diff --git a/frontend/src/utils/locales/ru.ts b/frontend/src/utils/locales/ru.ts
index 96c035f..6546d20 100644
--- a/frontend/src/utils/locales/ru.ts
+++ b/frontend/src/utils/locales/ru.ts
@@ -25,7 +25,11 @@ export const ru = {
searchResultsFor: "Результаты поиска для",
fromYourLibrary: "Из вашей библиотеки",
noMatchingVideos: "В вашей библиотеке нет подходящих видео.",
- fromYouTube: "С YouTube",
+ proxyOnlyApplyToYoutube: 'Прокси применяется только к Youtube',
+ moveSubtitlesToVideoFolder: 'Расположение субтитров',
+ moveSubtitlesToVideoFolderOn: 'Вместе с видео',
+ moveSubtitlesToVideoFolderOff: 'В изолированной папке субтитров',
+ moveSubtitlesToVideoFolderDescription: 'Если включено, файлы субтитров будут перемещены в ту же папку, что и видеофайл. Если отключено, они будут перемещены в изолированную папку субтитров.',
loadingYouTubeResults: "Загрузка результатов YouTube...",
noYouTubeResults: "Результаты YouTube не найдены",
noVideosYet: "Видео пока нет. Отправьте URL видео, чтобы скачать первое!",
diff --git a/frontend/src/utils/locales/zh.ts b/frontend/src/utils/locales/zh.ts
index decdba1..0d692af 100644
--- a/frontend/src/utils/locales/zh.ts
+++ b/frontend/src/utils/locales/zh.ts
@@ -443,5 +443,9 @@ export const zh = {
hide: "隐藏",
reset: "重置",
more: "更多",
- proxyOnlyApplyToYoutube: '代理仅应用于 Youtube',
+ proxyOnlyApplyToYoutube: '代理仅应用于Youtube',
+ moveSubtitlesToVideoFolder: '字幕位置',
+ moveSubtitlesToVideoFolderOn: '与视频在同一文件夹',
+ moveSubtitlesToVideoFolderOff: '在独立字幕文件夹',
+ moveSubtitlesToVideoFolderDescription: '启用后,字幕文件将被移动到与视频文件相同的文件夹中。禁用后,它们将被移动到独立字幕文件夹。',
};