feat: add passkey feature

This commit is contained in:
Peifan Li
2026-01-02 23:42:56 -05:00
parent c9657bad51
commit 6fdfa90d01
21 changed files with 2062 additions and 322 deletions

View File

@@ -12,6 +12,7 @@
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.5",
"@mui/material": "^7.3.5",
"@simplewebauthn/browser": "^13.2.2",
"@tanstack/react-query": "^5.90.11",
"@tanstack/react-query-devtools": "^5.91.1",
"axios": "^1.13.2",
@@ -1918,6 +1919,12 @@
"win32"
]
},
"node_modules/@simplewebauthn/browser": {
"version": "13.2.2",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.2.2.tgz",
"integrity": "sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==",
"license": "MIT"
},
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",

View File

@@ -16,6 +16,7 @@
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.5",
"@mui/material": "^7.3.5",
"@simplewebauthn/browser": "^13.2.2",
"@tanstack/react-query": "^5.90.11",
"@tanstack/react-query-devtools": "^5.91.1",
"axios": "^1.13.2",

View File

@@ -1,7 +1,14 @@
import { Box, FormControlLabel, Switch, TextField } from '@mui/material';
import React from 'react';
import { Box, Button, FormControlLabel, Switch, TextField } from '@mui/material';
import { startRegistration } from '@simplewebauthn/browser';
import { useMutation, useQuery } from '@tanstack/react-query';
import axios from 'axios';
import React, { useState } from 'react';
import { useLanguage } from '../../contexts/LanguageContext';
import { Settings } from '../../types';
import AlertModal from '../AlertModal';
import ConfirmationModal from '../ConfirmationModal';
const API_URL = import.meta.env.VITE_API_URL;
interface SecuritySettingsProps {
settings: Settings;
@@ -10,6 +17,120 @@ interface SecuritySettingsProps {
const SecuritySettings: React.FC<SecuritySettingsProps> = ({ settings, onChange }) => {
const { t } = useLanguage();
const [showRemoveModal, setShowRemoveModal] = useState(false);
const [alertOpen, setAlertOpen] = useState(false);
const [alertTitle, setAlertTitle] = useState('');
const [alertMessage, setAlertMessage] = useState('');
const showAlert = (title: string, message: string) => {
setAlertTitle(title);
setAlertMessage(message);
setAlertOpen(true);
};
// Check if passkeys exist
const { data: passkeysData, refetch: refetchPasskeys } = useQuery({
queryKey: ['passkeys-exists'],
queryFn: async () => {
const response = await axios.get(`${API_URL}/settings/passkeys/exists`);
return response.data;
},
});
const passkeysExist = passkeysData?.exists || false;
// Create passkey mutation
const createPasskeyMutation = useMutation({
mutationFn: async () => {
// Step 1: Get registration options
const optionsResponse = await axios.post(`${API_URL}/settings/passkeys/register`, {
userName: 'MyTube User',
});
const { options, challenge } = optionsResponse.data;
// Step 2: Start registration with browser
const attestationResponse = await startRegistration(options);
// Step 3: Verify registration
const verifyResponse = await axios.post(`${API_URL}/settings/passkeys/register/verify`, {
body: attestationResponse,
challenge,
});
if (!verifyResponse.data.success) {
throw new Error('Passkey registration failed');
}
},
onSuccess: () => {
refetchPasskeys();
refetchPasskeys();
showAlert(t('success'), t('passkeyCreated') || 'Passkey created successfully');
},
onError: (error: any) => {
console.error('Error creating passkey:', error);
// Extract error message from axios response or error object
let errorMessage = t('passkeyCreationFailed') || 'Failed to create passkey. Please try again.';
if (error?.response?.data?.error) {
// Backend error message
errorMessage = error.response.data.error;
} else if (error?.response?.data?.message) {
errorMessage = error.response.data.message;
} else if (error?.message) {
errorMessage = error.message;
}
showAlert(t('error'), errorMessage);
},
});
// Remove passkeys mutation
const removePasskeysMutation = useMutation({
mutationFn: async () => {
await axios.delete(`${API_URL}/settings/passkeys`);
},
onSuccess: () => {
refetchPasskeys();
setShowRemoveModal(false);
showAlert(t('success'), t('passkeysRemoved') || 'All passkeys have been removed');
},
onError: (error: any) => {
console.error('Error removing passkeys:', error);
showAlert(t('error'), t('passkeysRemoveFailed') || 'Failed to remove passkeys. Please try again.');
},
});
const handleCreatePasskey = () => {
// Check if we're in a secure context (HTTPS or localhost)
// This is the most important check - WebAuthn requires secure context
if (!window.isSecureContext) {
const hostname = window.location.hostname;
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';
if (!isLocalhost) {
showAlert(t('error'), t('passkeyRequiresHttps') || 'WebAuthn requires HTTPS or localhost. Please access the application via HTTPS or use localhost instead of an IP address.');
return;
}
}
// Check if WebAuthn is supported
// Check multiple ways to detect WebAuthn support
const hasWebAuthn =
typeof window.PublicKeyCredential !== 'undefined' ||
(typeof navigator !== 'undefined' && 'credentials' in navigator && 'create' in navigator.credentials);
if (!hasWebAuthn) {
showAlert(t('error'), t('passkeyWebAuthnNotSupported') || 'WebAuthn is not supported in this browser. Please use a modern browser that supports WebAuthn.');
return;
}
createPasskeyMutation.mutate();
};
const handleRemovePasskeys = () => {
removePasskeysMutation.mutate();
};
return (
<Box>
@@ -37,8 +158,50 @@ const SecuritySettings: React.FC<SecuritySettingsProps> = ({ settings, onChange
: t('passwordSetHelper')
}
/>
<Box sx={{ mt: 3 }}>
<Box sx={{ mb: 2 }}>
<Button
variant="outlined"
onClick={handleCreatePasskey}
disabled={!settings.loginEnabled || createPasskeyMutation.isPending}
fullWidth
>
{createPasskeyMutation.isPending
? (t('creatingPasskey') || 'Creating...')
: (t('createPasskey') || 'Create Passkey')}
</Button>
</Box>
<Button
variant="outlined"
color="error"
onClick={() => setShowRemoveModal(true)}
disabled={!settings.loginEnabled || !passkeysExist || removePasskeysMutation.isPending}
fullWidth
>
{t('removePasskeys') || 'Remove All Passkeys'}
</Button>
</Box>
</Box>
)}
<ConfirmationModal
isOpen={showRemoveModal}
onClose={() => setShowRemoveModal(false)}
onConfirm={handleRemovePasskeys}
title={t('removePasskeysTitle') || 'Remove All Passkeys'}
message={t('removePasskeysMessage') || 'Are you sure you want to remove all passkeys? This action cannot be undone.'}
confirmText={t('remove') || 'Remove'}
cancelText={t('cancel') || 'Cancel'}
isDanger={true}
/>
<AlertModal
open={alertOpen}
onClose={() => setAlertOpen(false)}
title={alertTitle}
message={alertMessage}
/>
</Box>
);
};

View File

@@ -1,4 +1,5 @@
import { render, screen } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render as rtlRender, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import SecuritySettings from '../SecuritySettings';
@@ -8,6 +9,23 @@ vi.mock('../../../contexts/LanguageContext', () => ({
useLanguage: () => ({ t: (key: string) => key }),
}));
const createTestQueryClient = () => new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
const render = (ui: React.ReactElement) => {
const queryClient = createTestQueryClient();
return rtlRender(
<QueryClientProvider client={queryClient}>
{ui}
</QueryClientProvider>
);
};
describe('SecuritySettings', () => {
const mockOnChange = vi.fn();
const defaultSettings: any = {

View File

@@ -1,4 +1,4 @@
import { ErrorOutline, LockOutlined, Refresh, Visibility, VisibilityOff } from '@mui/icons-material';
import { ErrorOutline, Fingerprint, LockOutlined, Refresh, Visibility, VisibilityOff } from '@mui/icons-material';
import {
Alert,
Avatar,
@@ -7,16 +7,19 @@ import {
CircularProgress,
Container,
CssBaseline,
Divider,
IconButton,
InputAdornment,
TextField,
ThemeProvider,
Typography
} from '@mui/material';
import { startAuthentication } from '@simplewebauthn/browser';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import logo from '../assets/logo.svg';
import AlertModal from '../components/AlertModal';
import ConfirmationModal from '../components/ConfirmationModal';
import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext';
@@ -30,6 +33,9 @@ const LoginPage: React.FC = () => {
const [error, setError] = useState('');
const [waitTime, setWaitTime] = useState(0); // in milliseconds
const [showResetModal, setShowResetModal] = useState(false);
const [alertOpen, setAlertOpen] = useState(false);
const [alertTitle, setAlertTitle] = useState('');
const [alertMessage, setAlertMessage] = useState('');
const [websiteName, setWebsiteName] = useState('MyTube');
const { t } = useLanguage();
const { login } = useAuth();
@@ -68,6 +74,24 @@ const LoginPage: React.FC = () => {
retryDelay: 1000,
});
// Check if passkeys exist
const { data: passkeysData } = useQuery({
queryKey: ['passkeys-exists'],
queryFn: async () => {
try {
const response = await axios.get(`${API_URL}/settings/passkeys/exists`, { timeout: 5000 });
return response.data;
} catch (error) {
return { exists: false };
}
},
retry: 1,
retryDelay: 1000,
enabled: !isCheckingConnection && !isConnectionError,
});
const passkeysExist = passkeysData?.exists || false;
// Initialize wait time from server response
useEffect(() => {
if (statusData && statusData.waitTime) {
@@ -110,6 +134,12 @@ const LoginPage: React.FC = () => {
return `${days} day${days !== 1 ? 's' : ''}`;
};
const showAlert = (title: string, message: string) => {
setAlertTitle(title);
setAlertMessage(message);
setAlertOpen(true);
};
const loginMutation = useMutation({
mutationFn: async (password: string) => {
const response = await axios.post(`${API_URL}/settings/verify-password`, { password });
@@ -120,7 +150,7 @@ const LoginPage: React.FC = () => {
setWaitTime(0); // Reset wait time on success
login();
} else {
setError(t('incorrectPassword'));
showAlert(t('error'), t('incorrectPassword'));
}
},
onError: (err: any) => {
@@ -132,26 +162,22 @@ const LoginPage: React.FC = () => {
const waitTimeMs = responseData.waitTime || 0;
setWaitTime(waitTimeMs);
const formattedTime = formatWaitTime(waitTimeMs);
setError(
`${t('tooManyAttempts')} ${t('waitTimeMessage').replace('{time}', formattedTime)}`
);
showAlert(t('error'), `${t('tooManyAttempts')} ${t('waitTimeMessage').replace('{time}', formattedTime)}`);
} else if (err.response.status === 401) {
// Incorrect password - check if wait time is returned
const waitTimeMs = responseData.waitTime || 0;
if (waitTimeMs > 0) {
setWaitTime(waitTimeMs);
const formattedTime = formatWaitTime(waitTimeMs);
setError(
`${t('incorrectPassword')} ${t('waitTimeMessage').replace('{time}', formattedTime)}`
);
showAlert(t('error'), `${t('incorrectPassword')} ${t('waitTimeMessage').replace('{time}', formattedTime)}`);
} else {
setError(t('incorrectPassword'));
showAlert(t('error'), t('incorrectPassword'));
}
} else {
setError(t('loginFailed'));
showAlert(t('error'), t('loginFailed'));
}
} else {
setError(t('loginFailed'));
showAlert(t('error'), t('loginFailed'));
}
}
});
@@ -167,11 +193,58 @@ const LoginPage: React.FC = () => {
setWaitTime(0);
queryClient.invalidateQueries({ queryKey: ['healthCheck'] });
// Show success message
alert(t('resetPasswordSuccess'));
showAlert(t('success'), t('resetPasswordSuccess'));
},
onError: (err: any) => {
console.error('Reset password error:', err);
setError(t('loginFailed'));
showAlert(t('error'), t('loginFailed'));
}
});
// Passkey authentication mutation
const passkeyLoginMutation = useMutation({
mutationFn: async () => {
// Step 1: Get authentication options
const optionsResponse = await axios.post(`${API_URL}/settings/passkeys/authenticate`);
const { options, challenge } = optionsResponse.data;
// Step 2: Start authentication with browser
const assertionResponse = await startAuthentication(options);
// Step 3: Verify authentication
const verifyResponse = await axios.post(`${API_URL}/settings/passkeys/authenticate/verify`, {
body: assertionResponse,
challenge,
});
if (!verifyResponse.data.success) {
throw new Error('Passkey authentication failed');
}
return verifyResponse.data;
},
onSuccess: () => {
setError('');
setWaitTime(0);
login();
},
onError: (err: any) => {
console.error('Passkey login error:', err);
// Extract error message from axios response or error object
let errorMessage = t('passkeyLoginFailed') || 'Passkey authentication failed. Please try again.';
if (err?.response?.data?.error) {
// Backend error message (e.g., "No passkeys registered" or "No passkeys found for RP_ID")
errorMessage = err.response.data.error;
} else if (err?.response?.data?.message) {
errorMessage = err.response.data.message;
} else if (err?.message) {
errorMessage = err.message;
}
showAlert(t('error'), errorMessage);
}
});
@@ -188,6 +261,32 @@ const LoginPage: React.FC = () => {
resetPasswordMutation.mutate();
};
const handlePasskeyLogin = () => {
// Check if we're in a secure context (HTTPS or localhost)
// This is the most important check - WebAuthn requires secure context
if (!window.isSecureContext) {
const hostname = window.location.hostname;
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';
if (!isLocalhost) {
showAlert(t('error'), t('passkeyRequiresHttps') || 'WebAuthn requires HTTPS or localhost. Please access the application via HTTPS or use localhost instead of an IP address.');
return;
}
}
// Check if WebAuthn is supported
// Check multiple ways to detect WebAuthn support
const hasWebAuthn =
typeof window.PublicKeyCredential !== 'undefined' ||
(typeof navigator !== 'undefined' && 'credentials' in navigator && 'create' in navigator.credentials);
if (!hasWebAuthn) {
showAlert(t('error'), t('passkeyWebAuthnNotSupported') || 'WebAuthn is not supported in this browser. Please use a modern browser that supports WebAuthn.');
return;
}
passkeyLoginMutation.mutate();
};
return (
<ThemeProvider theme={theme}>
<CssBaseline />
@@ -290,6 +389,23 @@ const LoginPage: React.FC = () => {
>
{loginMutation.isPending ? (t('verifying') || 'Verifying...') : t('signIn')}
</Button>
{passkeysExist && (
<>
<Divider sx={{ my: 2 }}>OR</Divider>
<Button
fullWidth
variant="outlined"
startIcon={<Fingerprint />}
onClick={handlePasskeyLogin}
sx={{ mb: 2 }}
disabled={passkeyLoginMutation.isPending || waitTime > 0}
>
{passkeyLoginMutation.isPending
? (t('authenticating') || 'Authenticating...')
: (t('loginWithPasskey') || 'Login with Passkey')}
</Button>
</>
)}
<Button
fullWidth
variant="outlined"
@@ -327,6 +443,12 @@ const LoginPage: React.FC = () => {
cancelText={t('cancel')}
isDanger={true}
/>
<AlertModal
open={alertOpen}
onClose={() => setAlertOpen(false)}
title={alertTitle}
message={alertMessage}
/>
</ThemeProvider>
);
};

View File

@@ -129,8 +129,10 @@ export const ar = {
showYoutubeSearch: "عرض نتائج بحث YouTube",
visitorMode: "وضع الزائر (للقراءة فقط)",
visitorModeReadOnly: "وضع الزائر: للقراءة فقط",
visitorModeDescription: "وضع القراءة فقط. لن تكون مقاطع الفيديو المخفية مرئية للزوار.",
visitorModePasswordPrompt: "يرجى إدخال كلمة مرور الموقع لتغيير إعدادات وضع الزائر.",
visitorModeDescription:
"وضع القراءة فقط. لن تكون مقاطع الفيديو المخفية مرئية للزوار.",
visitorModePasswordPrompt:
"يرجى إدخال كلمة مرور الموقع لتغيير إعدادات وضع الزائر.",
cleanupTempFilesSuccess: "تم حذف {count} ملف (ملفات) مؤقت بنجاح.",
cleanupTempFilesFailed: "فشل تنظيف الملفات المؤقتة",
@@ -159,11 +161,13 @@ export const ar = {
apiUrlHelper: "مثال: https://your-alist-instance.com/api/fs/put",
token: "الرمز المميز (Token)",
publicUrl: "عنوان URL العام",
publicUrlHelper: "النطاق العام للوصول إلى الملفات (مثال: https://your-cloudflare-tunnel-domain.com). إذا تم تعيينه، سيتم استخدامه بدلاً من عنوان API للوصول إلى الملفات.",
publicUrlHelper:
"النطاق العام للوصول إلى الملفات (مثال: https://your-cloudflare-tunnel-domain.com). إذا تم تعيينه، سيتم استخدامه بدلاً من عنوان API للوصول إلى الملفات.",
uploadPath: "مسار التحميل",
cloudDrivePathHelper: "مسار الدليل في التخزين السحابي، مثال: /mytube-uploads",
scanPaths: "مسارات المسح",
scanPathsHelper: "مسار واحد في كل سطر. سيتم مسح مقاطع الفيديو من هذه المسارات. إذا كانت فارغة، سيتم استخدام مسار التحميل. مثال:\n/a/أفلام\n/b/وثائقيات",
scanPathsHelper:
"مسار واحد في كل سطر. سيتم مسح مقاطع الفيديو من هذه المسارات. إذا كانت فارغة، سيتم استخدام مسار التحميل. مثال:\n/a/أفلام\n/b/وثائقيات",
cloudDriveNote:
"بعد تفعيل هذه الميزة، سيتم تحميل مقاطع الفيديو التي تم تنزيلها حديثًا تلقائيًا إلى التخزين السحابي وسيتم حذف الملفات المحلية. سيتم تشغيل مقاطع الفيديو من التخزين السحابي عبر الوكيل.",
cloudScanAdded: "تمت الإضافة من السحابة",
@@ -171,7 +175,8 @@ export const ar = {
testConnection: "اختبار الاتصال",
sync: "مزامنة",
syncToCloud: "مزامنة ثنائية الاتجاه",
syncWarning: "ستقوم هذه العملية برفع مقاطع الفيديو المحلية إلى السحابة ومسح التخزين السحابي بحثًا عن ملفات جديدة. سيتم حذف الملفات المحلية بعد الرفع.",
syncWarning:
"ستقوم هذه العملية برفع مقاطع الفيديو المحلية إلى السحابة ومسح التخزين السحابي بحثًا عن ملفات جديدة. سيتم حذف الملفات المحلية بعد الرفع.",
syncing: "جاري المزامنة...",
syncCompleted: "اكتملت المزامنة",
syncFailed: "فشلت المزامنة",
@@ -188,9 +193,11 @@ export const ar = {
uploadingVideo: "جاري الرفع: {title}",
clearThumbnailCache: "مسح ذاكرة التخزين المؤقت للصور المصغرة",
clearing: "جاري المسح...",
clearThumbnailCacheSuccess: "تم مسح ذاكرة التخزين المؤقت للصور المصغرة بنجاح. سيتم إعادة إنشاء الصور المصغرة عند الوصول إليها في المرة القادمة.",
clearThumbnailCacheSuccess:
"تم مسح ذاكرة التخزين المؤقت للصور المصغرة بنجاح. سيتم إعادة إنشاء الصور المصغرة عند الوصول إليها في المرة القادمة.",
clearThumbnailCacheError: "فشل مسح ذاكرة التخزين المؤقت للصور المصغرة",
clearThumbnailCacheConfirmMessage: "سيؤدي هذا إلى مسح جميع الصور المصغرة المخزنة مؤقتًا محليًا لمقاطع الفيديو السحابية. سيتم إعادة إنشاء الصور المصغرة من التخزين السحابي عند الوصول إليها في المرة القادمة. هل تريد المتابعة؟",
clearThumbnailCacheConfirmMessage:
"سيؤدي هذا إلى مسح جميع الصور المصغرة المخزنة مؤقتًا محليًا لمقاطع الفيديو السحابية. سيتم إعادة إنشاء الصور المصغرة من التخزين السحابي عند الوصول إليها في المرة القادمة. هل تريد المتابعة؟",
// Manage
manageContent: "إدارة المحتوى",
@@ -280,7 +287,8 @@ export const ar = {
openInExternalPlayer: "فتح في مشغل خارجي",
playWith: "تشغيل بواسطة...",
deleteAllFilteredVideos: "حذف جميع الفيديوهات المصفاة",
confirmDeleteFilteredVideos: "هل أنت متأكد أنك تريد حذف {count} فيديو مصفى بواسطة العلامات المحددة؟",
confirmDeleteFilteredVideos:
"هل أنت متأكد أنك تريد حذف {count} فيديو مصفى بواسطة العلامات المحددة؟",
deleteFilteredVideosSuccess: "تم حذف {count} فيديو بنجاح.",
deletingVideos: "جاري حذف الفيديوهات...",
@@ -304,8 +312,24 @@ export const ar = {
"تم إعادة تعيين كلمة المرور. تحقق من سجلات الخادم للحصول على كلمة المرور الجديدة.",
waitTimeMessage: "يرجى الانتظار {time} قبل المحاولة مرة أخرى.",
tooManyAttempts: "محاولات فاشلة كثيرة جداً.",
// Passkeys
createPasskey: "إنشاء مفتاح مرور",
creatingPasskey: "جاري الإنشاء...",
passkeyCreated: "تم إنشاء مفتاح المرور بنجاح",
passkeyCreationFailed: "فشل إنشاء مفتاح المرور. يرجى المحاولة مرة أخرى.",
removePasskeys: "إزالة جميع مفاتيح المرور",
removePasskeysTitle: "إزالة جميع مفاتيح المرور",
removePasskeysMessage:
"هل أنت متأكد من أنك تريد إزالة جميع مفاتيح المرور؟ لا يمكن التراجع عن هذا الإجراء.",
passkeysRemoved: "تم إزالة جميع مفاتيح المرور",
passkeysRemoveFailed: "فشل إزالة مفاتيح المرور. يرجى المحاولة مرة أخرى.",
loginWithPasskey: "تسجيل الدخول بمفتاح المرور",
authenticating: "جاري المصادقة...",
passkeyLoginFailed: "فشلت مصادقة مفتاح المرور. يرجى المحاولة مرة أخرى.",
linkCopied: "تم نسخ الرابط إلى الحافظة",
copyFailed: "فشل نسخ الرابط",
passkeyRequiresHttps: "يتطلب WebAuthn استخدام HTTPS أو localhost. يرجى الدخول إلى التطبيق عبر HTTPS أو استخدام localhost بدلاً من عنوان IP.",
passkeyWebAuthnNotSupported: "WebAuthn غير مدعوم في هذا المتصفح. يرجى استخدام متصفح حديث يدعم WebAuthn.",
// Collection Page
loadingCollection: "جاري تحميل المجموعة...",
@@ -387,7 +411,8 @@ export const ar = {
authorOrPlaylist: "المؤلف / قائمة التشغيل",
playlistDetected: "تم اكتشاف قائمة تشغيل",
playlistHasVideos: "تحتوي قائمة التشغيل هذه على {count} فيديوهات.",
downloadPlaylistAndCreateCollection: "هل تريد تحميل فيديوهات قائمة التشغيل وإنشاء مجموعة لها؟",
downloadPlaylistAndCreateCollection:
"هل تريد تحميل فيديوهات قائمة التشغيل وإنشاء مجموعة لها؟",
collectionHasVideos: "تحتوي هذه المجموعة من Bilibili على {count} فيديوهات.",
seriesHasVideos: "تحتوي هذه السلسلة من Bilibili على {count} فيديوهات.",
videoHasParts: "يحتوي هذا الفيديو من Bilibili على {count} أجزاء.",
@@ -463,11 +488,13 @@ export const ar = {
confirmCancelTask: "هل أنت متأكد أنك تريد إلغاء مهمة التنزيل لـ {author}؟",
taskCancelled: "تم إلغاء المهمة بنجاح",
deleteTask: "حذف المهمة",
confirmDeleteTask: "هل أنت متأكد أنك تريد حذف سجل المهمة لـ {author}؟ لا يمكن التراجع عن هذا الإجراء.",
confirmDeleteTask:
"هل أنت متأكد أنك تريد حذف سجل المهمة لـ {author}؟ لا يمكن التراجع عن هذا الإجراء.",
taskDeleted: "تم حذف المهمة بنجاح",
clearFinishedTasks: "مسح المهام المنتهية",
tasksCleared: "تم مسح المهام المنتهية بنجاح",
confirmClearFinishedTasks: "هل أنت متأكد أنك تريد مسح جميع المهام المنتهية (المكتملة، الملغاة)؟ سيؤدي هذا إلى إزالتها من القائمة ولكن لن يحذف أي ملفات تم تنزيلها.",
confirmClearFinishedTasks:
"هل أنت متأكد أنك تريد مسح جميع المهام المنتهية (المكتملة، الملغاة)؟ سيؤدي هذا إلى إزالتها من القائمة ولكن لن يحذف أي ملفات تم تنزيلها.",
clear: "مسح",
// Instruction Page
instructionSection1Title: "1. التنزيل وإدارة المهام",
@@ -591,7 +618,8 @@ export const ar = {
lastBackupDate: "تاريخ آخر نسخة احتياطية",
noBackupAvailable: "لا توجد نسخة احتياطية متاحة",
deleteAuthor: "حذف المؤلف",
deleteAuthorConfirmation: "هل أنت متأكد أنك تريد حذف المؤلف {author}؟ سيؤدي هذا إلى حذف جميع مقاطع الفيديو المرتبطة بهذا المؤلف.",
deleteAuthorConfirmation:
"هل أنت متأكد أنك تريد حذف المؤلف {author}؟ سيؤدي هذا إلى حذف جميع مقاطع الفيديو المرتبطة بهذا المؤلف.",
authorDeletedSuccessfully: "تم حذف المؤلف بنجاح",
failedToDeleteAuthor: "فشل حذف المؤلف",
@@ -599,7 +627,8 @@ export const ar = {
cloudflaredTunnel: "نفق Cloudflare",
enableCloudflaredTunnel: "تمكين نفق Cloudflare",
cloudflaredToken: "رمز النفق (اختياري)",
cloudflaredTokenHelper: "الصق رمز النفق الخاص بك هنا، أو اتركه فارغًا لاستخدام نفق سريع عشوائي.",
cloudflaredTokenHelper:
"الصق رمز النفق الخاص بك هنا، أو اتركه فارغًا لاستخدام نفق سريع عشوائي.",
waitingForUrl: "في انتظار عنوان النفق السريع URL...",
running: "يعمل",
stopped: "متوقف",
@@ -607,8 +636,10 @@ export const ar = {
accountTag: "علامة الحساب",
copied: "تم النسخ!",
clickToCopy: "انقر للنسخ",
quickTunnelWarning: "تتغير عناوين URL للنفق السريع في كل مرة يتم فيها إعادة تشغيل النفق.",
managedInDashboard: "تتم إدارة اسم المضيف العام في لوحة تحكم Cloudflare Zero Trust الخاصة بك.",
quickTunnelWarning:
"تتغير عناوين URL للنفق السريع في كل مرة يتم فيها إعادة تشغيل النفق.",
managedInDashboard:
"تتم إدارة اسم المضيف العام في لوحة تحكم Cloudflare Zero Trust الخاصة بك.",
failedToDownloadVideo: "فشل تنزيل الفيديو. يرجى المحاولة مرة أخرى.",
failedToDownload: "فشل التنزيل. يرجى المحاولة مرة أخرى.",
playlistDownloadStarted: "بدأ تنزيل قائمة التشغيل",
@@ -617,26 +648,28 @@ export const ar = {
copyUrl: "نسخ الرابط",
new: "جديد",
// Task Hooks
taskHooks: 'خطافات المهام',
taskHooksDescription: 'نفذ أوامر shell مخصصة في نقاط محددة من دورة حياة المهمة. متغيرات البيئة المتاحة: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'تحذير: يتم تشغيل الأوامر بصلاحيات الخادم. استخدم بحذر.',
enterPasswordToUploadHook: 'الرجاء إدخال كلمة المرور لتحميل نص Hook هذا.',
riskCommandDetected: 'تم اكتشاف أمر خطر: {command}. تم رفض التحميل.',
hookTaskBeforeStart: 'قبل بدء المهمة',
hookTaskBeforeStartHelper: 'ينفذ قبل بدء التنزيل.',
hookTaskSuccess: 'نجاح المهمة',
hookTaskSuccessHelper: 'ينفذ بعد التنزيل الناجح، قبل الرفع السحابي/الحذف (ينتظر الاكتمال).',
hookTaskFail: 'فشل المهمة',
hookTaskFailHelper: 'ينفذ عند فشل المهمة.',
hookTaskCancel: 'إلغاء المهمة',
hookTaskCancelHelper: 'ينفذ عند إلغاء المهمة يدوياً.',
found: 'موجود',
notFound: 'غير معين',
deleteHook: 'حذف سكريبت الخطاف',
confirmDeleteHook: 'هل أنت متأكد أنك تريد حذف سكريبت الخطاف هذا؟',
uploadHook: 'رفع .sh',
taskHooks: "خطافات المهام",
taskHooksDescription:
"نفذ أوامر shell مخصصة في نقاط محددة من دورة حياة المهمة. متغيرات البيئة المتاحة: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.",
taskHooksWarning: "تحذير: يتم تشغيل الأوامر بصلاحيات الخادم. استخدم بحذر.",
enterPasswordToUploadHook: "الرجاء إدخال كلمة المرور لتحميل نص Hook هذا.",
riskCommandDetected: "تم اكتشاف أمر خطر: {command}. تم رفض التحميل.",
hookTaskBeforeStart: "قبل بدء المهمة",
hookTaskBeforeStartHelper: "ينفذ قبل بدء التنزيل.",
hookTaskSuccess: "نجاح المهمة",
hookTaskSuccessHelper:
"ينفذ بعد التنزيل الناجح، قبل الرفع السحابي/الحذف (ينتظر الاكتمال).",
hookTaskFail: "فشل المهمة",
hookTaskFailHelper: "ينفذ عند فشل المهمة.",
hookTaskCancel: "إلغاء المهمة",
hookTaskCancelHelper: "ينفذ عند إلغاء المهمة يدوياً.",
found: "موجود",
notFound: "غير معين",
deleteHook: "حذف سكريبت الخطاف",
confirmDeleteHook: "هل أنت متأكد أنك تريد حذف سكريبت الخطاف هذا؟",
uploadHook: "رفع .sh",
disclaimerTitle: "إخلاء المسؤولية",
disclaimerText: "1. الغرض والقيود\nهذا البرنامج (بما في ذلك الكود والوثائق) مخصص فقط للتعلم الشخصي والبحث والتبادل التقني. يُحظر تمامًا استخدام هذا البرنامج لأي أغراض تجارية أو لأي أنشطة غير قانونية تنتهك القوانين واللوائح المحلية.\n\n2. المسؤولية\nالمطور ليس على علم ولا يملك أي سيطرة على كيفية استخدام المستخدمين لهذا البرنامج. يتحمل المستخدم وحده أي مسؤوليات قانونية أو نزاعات أو أضرار تنشأ عن الاستخدام غير القانوني أو غير السليم لهذا البرنامج (بما في ذلك على سبيل المثال لا الحصر انتهاك حقوق الطبع والنشر). لا يتحمل المطور أي مسؤولية مباشرة أو غير مباشرة أو مشتركة.\n\n3. التعديلات والتوزيع\nهذا المشروع مفتوح المصدر. يجب على أي فرد أو منظمة تقوم بتعديل أو تفرع هذا الكود الالتزام بترخيص المصدر المفتوح. هام: إذا قام طرف ثالث بتعديل الكود لتجاوز أو إزالة آليات مصادقة/أمان المستخدم الأصلية وتوزيع مثل هذه الإصدارات، فإن المعدل/الموزع يتحمل المسؤولية الكاملة عن أي عواقب. ننصح بشدة بعدم تجاوز أو العبث بأي آليات للتحقق من الأمان.\n\n4. بيان غير ربحي\nهذا مشروع مفتوح المصدر مجاني تمامًا. لا يقبل المطور التبرعات ولم ينشر أي صفحات للتبرع. لا يسمح البرنامج نفسه بأي رسوم ولا يقدم أي خدمات مدفوعة. يرجى توخي الحذر والحذر من أي عمليات احتيال أو معلومات مضللة تدعي تحصيل رسوم نيابة عن هذا المشروع.",
disclaimerText:
"1. الغرض والقيود\nهذا البرنامج (بما في ذلك الكود والوثائق) مخصص فقط للتعلم الشخصي والبحث والتبادل التقني. يُحظر تمامًا استخدام هذا البرنامج لأي أغراض تجارية أو لأي أنشطة غير قانونية تنتهك القوانين واللوائح المحلية.\n\n2. المسؤولية\nالمطور ليس على علم ولا يملك أي سيطرة على كيفية استخدام المستخدمين لهذا البرنامج. يتحمل المستخدم وحده أي مسؤوليات قانونية أو نزاعات أو أضرار تنشأ عن الاستخدام غير القانوني أو غير السليم لهذا البرنامج (بما في ذلك على سبيل المثال لا الحصر انتهاك حقوق الطبع والنشر). لا يتحمل المطور أي مسؤولية مباشرة أو غير مباشرة أو مشتركة.\n\n3. التعديلات والتوزيع\nهذا المشروع مفتوح المصدر. يجب على أي فرد أو منظمة تقوم بتعديل أو تفرع هذا الكود الالتزام بترخيص المصدر المفتوح. هام: إذا قام طرف ثالث بتعديل الكود لتجاوز أو إزالة آليات مصادقة/أمان المستخدم الأصلية وتوزيع مثل هذه الإصدارات، فإن المعدل/الموزع يتحمل المسؤولية الكاملة عن أي عواقب. ننصح بشدة بعدم تجاوز أو العبث بأي آليات للتحقق من الأمان.\n\n4. بيان غير ربحي\nهذا مشروع مفتوح المصدر مجاني تمامًا. لا يقبل المطور التبرعات ولم ينشر أي صفحات للتبرع. لا يسمح البرنامج نفسه بأي رسوم ولا يقدم أي خدمات مدفوعة. يرجى توخي الحذر والحذر من أي عمليات احتيال أو معلومات مضللة تدعي تحصيل رسوم نيابة عن هذا المشروع.",
};

View File

@@ -45,7 +45,8 @@ export const de = {
websiteName: "Website-Name",
websiteNameHelper: "{current}/{max} Zeichen (Standard: {default})",
infiniteScroll: "Unendliches Scrollen",
infiniteScrollDisabled: "Deaktiviert, wenn unendliches Scrollen aktiviert ist",
infiniteScrollDisabled:
"Deaktiviert, wenn unendliches Scrollen aktiviert ist",
maxVideoColumns: "Maximale Videospalten (Startseite)",
videoColumns: "Videospalten (Startseite)",
columnsCount: "{count} Spalten",
@@ -92,7 +93,6 @@ export const de = {
migrationSuccess: "Migration abgeschlossen. Details in der Warnung anzeigen.",
migrationNoData: "Migration abgeschlossen, aber keine Daten gefunden.",
migrationFailed: "Migration fehlgeschlagen",
migrationWarnings: "WARNUNGEN",
migrationErrors: "FEHLER",
itemsMigrated: "Elemente migriert",
@@ -125,8 +125,10 @@ export const de = {
showYoutubeSearch: "YouTube-Suchergebnisse anzeigen",
visitorMode: "Besuchermodus (Nur-Lesen)",
visitorModeReadOnly: "Besuchermodus: Nur-Lesen",
visitorModeDescription: "Nur-Lese-Modus. Ausgeblendete Videos sind für Besucher nicht sichtbar.",
visitorModePasswordPrompt: "Bitte geben Sie das Website-Passwort ein, um die Besuchermodus-Einstellungen zu ändern.",
visitorModeDescription:
"Nur-Lese-Modus. Ausgeblendete Videos sind für Besucher nicht sichtbar.",
visitorModePasswordPrompt:
"Bitte geben Sie das Website-Passwort ein, um die Besuchermodus-Einstellungen zu ändern.",
cleanupTempFilesSuccess: "Erfolgreich {count} temporäre Datei(en) gelöscht.",
cleanupTempFilesFailed: "Fehler beim Bereinigen temporärer Dateien",
@@ -155,12 +157,14 @@ export const de = {
apiUrlHelper: "z.B. https://your-alist-instance.com/api/fs/put",
token: "Token",
publicUrl: "Öffentliche URL",
publicUrlHelper: "Öffentliche Domain für den Dateizugriff (z.B. https://your-cloudflare-tunnel-domain.com). Wenn gesetzt, wird diese anstelle der API-URL für den Dateizugriff verwendet.",
publicUrlHelper:
"Öffentliche Domain für den Dateizugriff (z.B. https://your-cloudflare-tunnel-domain.com). Wenn gesetzt, wird diese anstelle der API-URL für den Dateizugriff verwendet.",
uploadPath: "Upload-Pfad",
cloudDrivePathHelper:
"Verzeichnispfad im Cloud-Speicher, z.B. /mytube-uploads",
scanPaths: "Scan-Pfade",
scanPathsHelper: "Ein Pfad pro Zeile. Videos werden von diesen Pfaden gescannt. Wenn leer, wird der Upload-Pfad verwendet. Beispiel:\n/a/Filme\n/b/Dokumentationen",
scanPathsHelper:
"Ein Pfad pro Zeile. Videos werden von diesen Pfaden gescannt. Wenn leer, wird der Upload-Pfad verwendet. Beispiel:\n/a/Filme\n/b/Dokumentationen",
cloudDriveNote:
"Nach Aktivierung dieser Funktion werden neu heruntergeladene Videos automatisch in den Cloud-Speicher hochgeladen und lokale Dateien werden gelöscht. Videos werden über einen Proxy aus dem Cloud-Speicher abgespielt.",
cloudScanAdded: "Aus Cloud hinzugefügt",
@@ -168,26 +172,36 @@ export const de = {
testConnection: "Verbindung testen",
sync: "Synchronisieren",
syncToCloud: "Zwei-Wege-Synchronisierung",
syncWarning: "Dieser Vorgang lädt lokale Videos in die Cloud hoch und sucht im Cloud-Speicher nach neuen Dateien. Lokale Dateien werden nach dem Upload gelöscht.",
syncWarning:
"Dieser Vorgang lädt lokale Videos in die Cloud hoch und sucht im Cloud-Speicher nach neuen Dateien. Lokale Dateien werden nach dem Upload gelöscht.",
syncing: "Synchronisiere...",
syncCompleted: "Synchronisation abgeschlossen",
syncFailed: "Synchronisation fehlgeschlagen",
syncReport: "Gesamt: {total} | Hochgeladen: {uploaded} | Fehlgeschlagen: {failed}",
syncReport:
"Gesamt: {total} | Hochgeladen: {uploaded} | Fehlgeschlagen: {failed}",
syncErrors: "Fehler:",
fillApiUrlToken: "Bitte füllen Sie zuerst API-URL und Token aus",
connectionTestSuccess: "Verbindungstest erfolgreich! Einstellungen sind gültig.",
connectionFailedStatus: "Verbindung fehlgeschlagen: Server gab Status {status} zurück",
connectionFailedUrl: "Kann nicht mit Server verbinden. Bitte überprüfen Sie die API-URL.",
authFailed: "Authentifizierung fehlgeschlagen. Bitte überprüfen Sie Ihr Token.",
connectionTestSuccess:
"Verbindungstest erfolgreich! Einstellungen sind gültig.",
connectionFailedStatus:
"Verbindung fehlgeschlagen: Server gab Status {status} zurück",
connectionFailedUrl:
"Kann nicht mit Server verbinden. Bitte überprüfen Sie die API-URL.",
authFailed:
"Authentifizierung fehlgeschlagen. Bitte überprüfen Sie Ihr Token.",
connectionTestFailed: "Verbindungstest fehlgeschlagen: {error}",
syncFailedMessage: "Synchronisierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
foundVideosToSync: "{count} Videos mit lokalen Dateien zum Synchronisieren gefunden",
syncFailedMessage:
"Synchronisierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
foundVideosToSync:
"{count} Videos mit lokalen Dateien zum Synchronisieren gefunden",
uploadingVideo: "Lade hoch: {title}",
clearThumbnailCache: "Lokalen Thumbnail-Cache leeren",
clearing: "Leeren...",
clearThumbnailCacheSuccess: "Thumbnail-Cache erfolgreich geleert. Thumbnails werden beim nächsten Zugriff neu generiert.",
clearThumbnailCacheSuccess:
"Thumbnail-Cache erfolgreich geleert. Thumbnails werden beim nächsten Zugriff neu generiert.",
clearThumbnailCacheError: "Fehler beim Leeren des Thumbnail-Caches",
clearThumbnailCacheConfirmMessage: "Dies löscht alle lokal zwischengespeicherten Thumbnails für Cloud-Videos. Thumbnails werden beim nächsten Zugriff aus dem Cloud-Speicher neu generiert. Fortfahren?",
clearThumbnailCacheConfirmMessage:
"Dies löscht alle lokal zwischengespeicherten Thumbnails für Cloud-Videos. Thumbnails werden beim nächsten Zugriff aus dem Cloud-Speicher neu generiert. Fortfahren?",
manageContent: "Inhalte Verwalten",
videos: "Videos",
@@ -275,7 +289,8 @@ export const de = {
openInExternalPlayer: "In externem Player öffnen",
playWith: "Abspielen mit...",
deleteAllFilteredVideos: "Alle gefilterten Videos löschen",
confirmDeleteFilteredVideos: "Sind Sie sicher, dass Sie {count} Videos löschen möchten, die nach den ausgewählten Tags gefiltert wurden?",
confirmDeleteFilteredVideos:
"Sind Sie sicher, dass Sie {count} Videos löschen möchten, die nach den ausgewählten Tags gefiltert wurden?",
deleteFilteredVideosSuccess: "Erfolgreich {count} Videos gelöscht.",
deletingVideos: "Videos werden gelöscht...",
signIn: "Anmelden",
@@ -297,8 +312,27 @@ export const de = {
"Das Passwort wurde zurückgesetzt. Überprüfen Sie die Backend-Protokolle für das neue Passwort.",
waitTimeMessage: "Bitte warten Sie {time}, bevor Sie es erneut versuchen.",
tooManyAttempts: "Zu viele fehlgeschlagene Versuche.",
// Passkeys
createPasskey: "Passkey erstellen",
creatingPasskey: "Wird erstellt...",
passkeyCreated: "Passkey erfolgreich erstellt",
passkeyCreationFailed:
"Passkey konnte nicht erstellt werden. Bitte versuchen Sie es erneut.",
removePasskeys: "Alle Passkeys entfernen",
removePasskeysTitle: "Alle Passkeys entfernen",
removePasskeysMessage:
"Sind Sie sicher, dass Sie alle Passkeys entfernen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
passkeysRemoved: "Alle Passkeys wurden entfernt",
passkeysRemoveFailed:
"Passkeys konnten nicht entfernt werden. Bitte versuchen Sie es erneut.",
loginWithPasskey: "Mit Passkey anmelden",
authenticating: "Wird authentifiziert...",
passkeyLoginFailed:
"Passkey-Authentifizierung fehlgeschlagen. Bitte versuchen Sie es erneut.",
linkCopied: "Link in die Zwischenablage kopiert",
copyFailed: "Link konnte nicht kopiert werden",
passkeyRequiresHttps: "WebAuthn erfordert HTTPS oder localhost. Bitte greifen Sie über HTTPS auf die Anwendung zu oder verwenden Sie localhost anstelle einer IP-Adresse.",
passkeyWebAuthnNotSupported: "WebAuthn wird in diesem Browser nicht unterstützt. Bitte verwenden Sie einen modernen Browser, der WebAuthn unterstützt.",
loadingCollection: "Sammlung wird geladen...",
collectionNotFound: "Sammlung nicht gefunden",
noVideosInCollection: "Keine Videos in dieser Sammlung.",
@@ -308,7 +342,8 @@ export const de = {
unknownAuthor: "Unbekannt",
noVideosForAuthor: "Keine Videos für diesen Autor gefunden.",
deleteAuthor: "Autor löschen",
deleteAuthorConfirmation: "Sind Sie sicher, dass Sie den Autor {author} löschen möchten? Dies löscht alle Videos dieses Autors.",
deleteAuthorConfirmation:
"Sind Sie sicher, dass Sie den Autor {author} löschen möchten? Dies löscht alle Videos dieses Autors.",
authorDeletedSuccessfully: "Autor erfolgreich gelöscht",
failedToDeleteAuthor: "Fehler beim Löschen des Autors",
deleteCollectionTitle: "Sammlung Löschen",
@@ -351,7 +386,8 @@ export const de = {
authorOrPlaylist: "Autor / Wiedergabeliste",
playlistDetected: "Wiedergabeliste erkannt",
playlistHasVideos: "Diese Wiedergabeliste hat {count} Videos.",
downloadPlaylistAndCreateCollection: "Videos der Wiedergabeliste herunterladen und eine Sammlung dafür erstellen?",
downloadPlaylistAndCreateCollection:
"Videos der Wiedergabeliste herunterladen und eine Sammlung dafür erstellen?",
collectionHasVideos: "Diese Bilibili-Sammlung hat {count} Videos.",
seriesHasVideos: "Diese Bilibili-Serie hat {count} Videos.",
videoHasParts: "Dieses Bilibili-Video hat {count} Teile.",
@@ -423,7 +459,8 @@ export const de = {
subscriptionAlreadyExists: "Sie haben diesen Autor bereits abonniert.",
minutes: "Minuten",
never: "Nie",
downloadAllPreviousVideos: "Alle vorherigen Videos dieses Autors herunterladen",
downloadAllPreviousVideos:
"Alle vorherigen Videos dieses Autors herunterladen",
downloadAllPreviousWarning:
"Warnung: Dies lädt alle vorherigen Videos dieses Autors herunter. Dies kann erheblichen Speicherplatz verbrauchen und könnte Bot-Erkennungsmechanismen auslösen, die zu temporären oder dauerhaften Sperren der Plattform führen können. Verwenden Sie auf eigenes Risiko.",
continuousDownloadTasks: "Kontinuierliche Download-Aufgaben",
@@ -433,14 +470,17 @@ export const de = {
taskStatusCancelled: "Abgebrochen",
downloaded: "Heruntergeladen",
cancelTask: "Aufgabe abbrechen",
confirmCancelTask: "Sind Sie sicher, dass Sie die Download-Aufgabe für {author} abbrechen möchten?",
confirmCancelTask:
"Sind Sie sicher, dass Sie die Download-Aufgabe für {author} abbrechen möchten?",
taskCancelled: "Aufgabe erfolgreich abgebrochen",
deleteTask: "Aufgabe löschen",
confirmDeleteTask: "Sind Sie sicher, dass Sie den Aufgaben-Datensatz für {author} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
confirmDeleteTask:
"Sind Sie sicher, dass Sie den Aufgaben-Datensatz für {author} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
taskDeleted: "Aufgabe erfolgreich gelöscht",
clearFinishedTasks: "Beendete Aufgaben löschen",
tasksCleared: "Beendete Aufgaben erfolgreich gelöscht",
confirmClearFinishedTasks: "Sind Sie sicher, dass Sie alle beendeten Aufgaben (abgeschlossen, abgebrochen) löschen möchten? Dies entfernt sie aus der Liste, löscht aber keine heruntergeladenen Dateien.",
confirmClearFinishedTasks:
"Sind Sie sicher, dass Sie alle beendeten Aufgaben (abgeschlossen, abgebrochen) löschen möchten? Dies entfernt sie aus der Liste, löscht aber keine heruntergeladenen Dateien.",
clear: "Löschen",
// Instruction Page
instructionSection1Title: "1. Download & Aufgabenverwaltung",
@@ -580,7 +620,8 @@ export const de = {
cloudflaredTunnel: "Cloudflare Tunnel",
enableCloudflaredTunnel: "Cloudflare Tunnel aktivieren",
cloudflaredToken: "Tunnel-Token (Optional)",
cloudflaredTokenHelper: "Fügen Sie hier Ihr Tunnel-Token ein oder lassen Sie es leer, um einen zufälligen Quick Tunnel zu verwenden.",
cloudflaredTokenHelper:
"Fügen Sie hier Ihr Tunnel-Token ein oder lassen Sie es leer, um einen zufälligen Quick Tunnel zu verwenden.",
waitingForUrl: "Warte auf Quick Tunnel URL...",
running: "Läuft",
stopped: "Gestoppt",
@@ -588,38 +629,49 @@ export const de = {
accountTag: "Konto-Tag",
copied: "Kopiert!",
clickToCopy: "Zum Kopieren klicken",
quickTunnelWarning: "Quick Tunnel URLs ändern sich bei jedem Neustart des Tunnels.",
managedInDashboard: "Öffentlicher Hostname wird in Ihrem Cloudflare Zero Trust Dashboard verwaltet.",
failedToDownloadVideo: "Fehler beim Herunterladen des Videos. Bitte versuchen Sie es erneut.",
quickTunnelWarning:
"Quick Tunnel URLs ändern sich bei jedem Neustart des Tunnels.",
managedInDashboard:
"Öffentlicher Hostname wird in Ihrem Cloudflare Zero Trust Dashboard verwaltet.",
failedToDownloadVideo:
"Fehler beim Herunterladen des Videos. Bitte versuchen Sie es erneut.",
failedToDownload: "Fehler beim Herunterladen. Bitte versuchen Sie es erneut.",
playlistDownloadStarted: "Playlist-Download gestartet",
cleanupTempFilesConfirmMessage: "Dadurch werden alle .ytdl- und .part-Dateien im Upload-Verzeichnis dauerhaft gelöscht. Stellen Sie sicher, dass keine Downloads aktiv sind, bevor Sie fortfahren.",
cleanupTempFilesActiveDownloads: "Temporäre Dateien können nicht bereinigt werden, während Downloads aktiv sind. Bitte warten Sie, bis alle Downloads abgeschlossen sind, oder brechen Sie sie zuerst ab.",
cleanupTempFilesConfirmMessage:
"Dadurch werden alle .ytdl- und .part-Dateien im Upload-Verzeichnis dauerhaft gelöscht. Stellen Sie sicher, dass keine Downloads aktiv sind, bevor Sie fortfahren.",
cleanupTempFilesActiveDownloads:
"Temporäre Dateien können nicht bereinigt werden, während Downloads aktiv sind. Bitte warten Sie, bis alle Downloads abgeschlossen sind, oder brechen Sie sie zuerst ab.",
status: "Status",
videoDownloading: "Video wird heruntergeladen",
copyUrl: "URL kopieren",
new: "NEU",
// Task Hooks
taskHooks: 'Aufgaben-Hoks',
taskHooksDescription: 'Führen Sie benutzerdefinierte Shell-Befehle an bestimmten Punkten im Aufgabenlebenszyklus aus. Verfügbare Umgebungsvariablen: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Warnung: Befehle werden mit den Berechtigungen des Servers ausgeführt. Mit Vorsicht verwenden.',
enterPasswordToUploadHook: 'Bitte geben Sie Ihr Passwort ein, um dieses Hook-Skript hochzuladen.',
riskCommandDetected: 'Risikobefehl erkannt: {command}. Upload abgelehnt.',
hookTaskBeforeStart: 'Vor Aufgabenstart',
hookTaskBeforeStartHelper: 'Wird ausgeführt, bevor der Download beginnt.',
hookTaskSuccess: 'Aufgabe Erfolgreich',
hookTaskSuccessHelper: 'Wird nach erfolgreichem Download ausgeführt, vor Cloud-Upload/Löschung (wartet auf Abschluss).',
hookTaskFail: 'Aufgabe Fehlgeschlagen',
hookTaskFailHelper: 'Wird ausgeführt, wenn eine Aufgabe fehlschlägt.',
hookTaskCancel: 'Aufgabe Abgebrochen',
hookTaskCancelHelper: 'Wird ausgeführt, wenn eine Aufgabe manuell abgebrochen wird.',
found: 'Gefunden',
notFound: 'Nicht Gesetzt',
deleteHook: 'Hook-Skript Löschen',
confirmDeleteHook: 'Sind Sie sicher, dass Sie dieses Hook-Skript löschen möchten?',
uploadHook: 'Hochladen .sh',
taskHooks: "Aufgaben-Hoks",
taskHooksDescription:
"Führen Sie benutzerdefinierte Shell-Befehle an bestimmten Punkten im Aufgabenlebenszyklus aus. Verfügbare Umgebungsvariablen: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.",
taskHooksWarning:
"Warnung: Befehle werden mit den Berechtigungen des Servers ausgeführt. Mit Vorsicht verwenden.",
enterPasswordToUploadHook:
"Bitte geben Sie Ihr Passwort ein, um dieses Hook-Skript hochzuladen.",
riskCommandDetected: "Risikobefehl erkannt: {command}. Upload abgelehnt.",
hookTaskBeforeStart: "Vor Aufgabenstart",
hookTaskBeforeStartHelper: "Wird ausgeführt, bevor der Download beginnt.",
hookTaskSuccess: "Aufgabe Erfolgreich",
hookTaskSuccessHelper:
"Wird nach erfolgreichem Download ausgeführt, vor Cloud-Upload/Löschung (wartet auf Abschluss).",
hookTaskFail: "Aufgabe Fehlgeschlagen",
hookTaskFailHelper: "Wird ausgeführt, wenn eine Aufgabe fehlschlägt.",
hookTaskCancel: "Aufgabe Abgebrochen",
hookTaskCancelHelper:
"Wird ausgeführt, wenn eine Aufgabe manuell abgebrochen wird.",
found: "Gefunden",
notFound: "Nicht Gesetzt",
deleteHook: "Hook-Skript Löschen",
confirmDeleteHook:
"Sind Sie sicher, dass Sie dieses Hook-Skript löschen möchten?",
uploadHook: "Hochladen .sh",
disclaimerTitle: "Haftungsausschluss",
disclaimerText: "1. Zweck und Einschränkungen\nDiese Software (einschließlich Code und Dokumentation) ist ausschließlich für persönliches Lernen, Forschung und technischen Austausch bestimmt. Es ist strengstens untersagt, diese Software für kommerzielle Zwecke oder illegale Aktivitäten zu verwenden, die gegen lokale Gesetze und Vorschriften verstoßen.\n\n2. Haftung\nDer Entwickler hat keine Kontrolle darüber, wie Benutzer diese Software verwenden. Jegliche rechtliche Haftung, Streitigkeiten oder Schäden, die aus der illegalen oder unsachgemäßen Verwendung dieser Software entstehen (einschließlich, aber nicht beschränkt auf Urheberrechtsverletzungen), liegen allein beim Benutzer. Der Entwickler übernimmt keine direkte, indirekte oder gesamtschuldnerische Haftung.\n\n3. Änderungen und Verbreitung\nDieses Projekt ist Open Source. Jede Einzelperson oder Organisation, die diesen Code ändert oder forkt, muss die Open-Source-Lizenz einhalten. Wichtig: Wenn Dritte den Code ändern, um die ursprünglichen Benutzerauthentifizierungs-/Sicherheitsmechanismen zu umgehen oder zu entfernen, und solche Versionen verbreiten, trägt der Modifikator/Verteiler die volle Verantwortung für alle Konsequenzen. Wir raten dringend davon ab, Sicherheitsüberprüfungsmechanismen zu umgehen oder zu manipulieren.\n\n4. Gemeinnützige Erklärung\nDies ist ein komplett kostenloses Open-Source-Projekt. Der Entwickler akzeptiert keine Spenden und hat nie Spendenseiten veröffentlicht. Die Software selbst erlaubt keine Gebühren und bietet keine kostenpflichtigen Dienste an. Bitte seien Sie wachsam und hüten Sie sich vor Betrug oder irreführenden Informationen, die behaupten, Gebühren im Namen dieses Projekts zu erheben.",
disclaimerText:
"1. Zweck und Einschränkungen\nDiese Software (einschließlich Code und Dokumentation) ist ausschließlich für persönliches Lernen, Forschung und technischen Austausch bestimmt. Es ist strengstens untersagt, diese Software für kommerzielle Zwecke oder illegale Aktivitäten zu verwenden, die gegen lokale Gesetze und Vorschriften verstoßen.\n\n2. Haftung\nDer Entwickler hat keine Kontrolle darüber, wie Benutzer diese Software verwenden. Jegliche rechtliche Haftung, Streitigkeiten oder Schäden, die aus der illegalen oder unsachgemäßen Verwendung dieser Software entstehen (einschließlich, aber nicht beschränkt auf Urheberrechtsverletzungen), liegen allein beim Benutzer. Der Entwickler übernimmt keine direkte, indirekte oder gesamtschuldnerische Haftung.\n\n3. Änderungen und Verbreitung\nDieses Projekt ist Open Source. Jede Einzelperson oder Organisation, die diesen Code ändert oder forkt, muss die Open-Source-Lizenz einhalten. Wichtig: Wenn Dritte den Code ändern, um die ursprünglichen Benutzerauthentifizierungs-/Sicherheitsmechanismen zu umgehen oder zu entfernen, und solche Versionen verbreiten, trägt der Modifikator/Verteiler die volle Verantwortung für alle Konsequenzen. Wir raten dringend davon ab, Sicherheitsüberprüfungsmechanismen zu umgehen oder zu manipulieren.\n\n4. Gemeinnützige Erklärung\nDies ist ein komplett kostenloses Open-Source-Projekt. Der Entwickler akzeptiert keine Spenden und hat nie Spendenseiten veröffentlicht. Die Software selbst erlaubt keine Gebühren und bietet keine kostenpflichtigen Dienste an. Bitte seien Sie wachsam und hüten Sie sich vor Betrug oder irreführenden Informationen, die behaupten, Gebühren im Namen dieses Projekts zu erheben.",
};

View File

@@ -326,6 +326,21 @@ export const en = {
"Password has been reset. Check backend logs for the new password.",
waitTimeMessage: "Please wait {time} before trying again.",
tooManyAttempts: "Too many failed attempts.",
// Passkeys
createPasskey: "Create Passkey",
creatingPasskey: "Creating...",
passkeyCreated: "Passkey created successfully",
passkeyCreationFailed: "Failed to create passkey. Please try again.",
passkeyWebAuthnNotSupported: "WebAuthn is not supported in this browser. Please use a modern browser that supports WebAuthn.",
passkeyRequiresHttps: "WebAuthn requires HTTPS or localhost. Please access the application via HTTPS or use localhost instead of an IP address.",
removePasskeys: "Remove All Passkeys",
removePasskeysTitle: "Remove All Passkeys",
removePasskeysMessage: "Are you sure you want to remove all passkeys? This action cannot be undone.",
passkeysRemoved: "All passkeys have been removed",
passkeysRemoveFailed: "Failed to remove passkeys. Please try again.",
loginWithPasskey: "Login with Passkey",
authenticating: "Authenticating...",
passkeyLoginFailed: "Passkey authentication failed. Please try again.",
linkCopied: "Link copied to clipboard",
copyFailed: "Failed to copy link",
copyUrl: "Copy URL",

View File

@@ -56,7 +56,8 @@ export const es = {
websiteName: "Nombre del sitio web",
websiteNameHelper: "{current}/{max} caracteres (Predeterminado: {default})",
infiniteScroll: "Desplazamiento infinito",
infiniteScrollDisabled: "Desactivado cuando el desplazamiento infinito está habilitado",
infiniteScrollDisabled:
"Desactivado cuando el desplazamiento infinito está habilitado",
maxVideoColumns: "Columnas de video máximas (Página de inicio)",
videoColumns: "Columnas de video (Página de inicio)",
columnsCount: "{count} Columnas",
@@ -139,8 +140,10 @@ export const es = {
showYoutubeSearch: "Mostrar resultados de búsqueda de YouTube",
visitorMode: "Modo Visitante (Solo lectura)",
visitorModeReadOnly: "Modo visitante: Solo lectura",
visitorModeDescription: "Modo de solo lectura. Los videos ocultos no serán visibles para los visitantes.",
visitorModePasswordPrompt: "Por favor, introduzca la contraseña del sitio web para cambiar la configuración del modo visitante.",
visitorModeDescription:
"Modo de solo lectura. Los videos ocultos no serán visibles para los visitantes.",
visitorModePasswordPrompt:
"Por favor, introduzca la contraseña del sitio web para cambiar la configuración del modo visitante.",
cleanupTempFilesSuccess:
"Se eliminaron exitosamente {count} archivo(s) temporal(es).",
cleanupTempFilesFailed: "Error al limpiar archivos temporales",
@@ -170,11 +173,13 @@ export const es = {
apiUrlHelper: "ej. https://your-alist-instance.com/api/fs/put",
token: "Token",
publicUrl: "URL Público",
publicUrlHelper: "Dominio público para acceder a archivos (ej. https://your-cloudflare-tunnel-domain.com). Si se establece, se usará en lugar de la URL de la API para acceder a archivos.",
publicUrlHelper:
"Dominio público para acceder a archivos (ej. https://your-cloudflare-tunnel-domain.com). Si se establece, se usará en lugar de la URL de la API para acceder a archivos.",
uploadPath: "Ruta de carga",
cloudDrivePathHelper: "Ruta del directorio en la nube, ej. /mytube-uploads",
scanPaths: "Rutas de escaneo",
scanPathsHelper: "Una ruta por línea. Se escanearán videos de estas rutas. Si está vacío, se usará la ruta de carga. Ejemplo:\n/a/Peliculas\n/b/Documentales",
scanPathsHelper:
"Una ruta por línea. Se escanearán videos de estas rutas. Si está vacío, se usará la ruta de carga. Ejemplo:\n/a/Peliculas\n/b/Documentales",
cloudDriveNote:
"Después de habilitar esta función, los videos recién descargados se subirán automáticamente al almacenamiento en la nube y se eliminarán los archivos locales. Los videos se reproducirán desde el almacenamiento en la nube a través de un proxy.",
cloudScanAdded: "Añadido desde la nube",
@@ -182,26 +187,33 @@ export const es = {
testConnection: "Probar Conexión",
sync: "Sincronizar",
syncToCloud: "Sincronización bidireccional",
syncWarning: "Esta operación subirá videos locales a la nube y buscará nuevos archivos en el almacenamiento en la nube. Los archivos locales se eliminarán después de la carga.",
syncWarning:
"Esta operación subirá videos locales a la nube y buscará nuevos archivos en el almacenamiento en la nube. Los archivos locales se eliminarán después de la carga.",
syncing: "Sincronizando...",
syncCompleted: "Sincronización Completada",
syncFailed: "Sincronización Fallida",
syncReport: "Total: {total} | Cargados: {uploaded} | Fallidos: {failed}",
syncErrors: "Errores:",
fillApiUrlToken: "Por favor complete primero la URL de la API y el Token",
connectionTestSuccess: "¡Prueba de conexión exitosa! La configuración es válida.",
connectionFailedStatus: "Conexión fallida: El servidor devolvió el estado {status}",
connectionFailedUrl: "No se puede conectar al servidor. Por favor verifique la URL de la API.",
connectionTestSuccess:
"¡Prueba de conexión exitosa! La configuración es válida.",
connectionFailedStatus:
"Conexión fallida: El servidor devolvió el estado {status}",
connectionFailedUrl:
"No se puede conectar al servidor. Por favor verifique la URL de la API.",
authFailed: "Autentiación fallida. Por favor verifique su token.",
connectionTestFailed: "Prueba de conexión fallida: {error}",
syncFailedMessage: "Sincronización fallida. Por favor intente de nuevo.",
foundVideosToSync: "Se encontraron {count} videos con archivos locales para sincronizar",
foundVideosToSync:
"Se encontraron {count} videos con archivos locales para sincronizar",
uploadingVideo: "Subiendo: {title}",
clearThumbnailCache: "Borrar caché local de miniaturas",
clearing: "Borrando...",
clearThumbnailCacheSuccess: "Caché de miniaturas borrado con éxito. Las miniaturas se regenerarán la próxima vez que se acceda a ellas.",
clearThumbnailCacheSuccess:
"Caché de miniaturas borrado con éxito. Las miniaturas se regenerarán la próxima vez que se acceda a ellas.",
clearThumbnailCacheError: "Error al borrar el caché de miniaturas",
clearThumbnailCacheConfirmMessage: "Esto borrará todas las miniaturas almacenadas en caché localmente para videos en la nube. Las miniaturas se regenerarán desde el almacenamiento en la nube la próxima vez que se acceda a ellas. ¿Continuar?",
clearThumbnailCacheConfirmMessage:
"Esto borrará todas las miniaturas almacenadas en caché localmente para videos en la nube. Las miniaturas se regenerarán desde el almacenamiento en la nube la próxima vez que se acceda a ellas. ¿Continuar?",
manageContent: "Gestionar Contenido",
videos: "Videos",
@@ -300,7 +312,8 @@ export const es = {
openInExternalPlayer: "Abrir en reproductor externo",
playWith: "Reproducir con...",
deleteAllFilteredVideos: "Eliminar todos los videos filtrados",
confirmDeleteFilteredVideos: "¿Está seguro de que desea eliminar {count} videos filtrados por las etiquetas seleccionadas?",
confirmDeleteFilteredVideos:
"¿Está seguro de que desea eliminar {count} videos filtrados por las etiquetas seleccionadas?",
deleteFilteredVideosSuccess: "Se han eliminado {count} videos con éxito.",
deletingVideos: "Eliminando videos...",
signIn: "Iniciar Sesión",
@@ -322,8 +335,27 @@ export const es = {
"La contraseña ha sido restablecida. Consulte los registros del backend para obtener la nueva contraseña.",
waitTimeMessage: "Por favor espere {time} antes de intentar nuevamente.",
tooManyAttempts: "Demasiados intentos fallidos.",
// Passkeys
createPasskey: "Crear clave de acceso",
creatingPasskey: "Creando...",
passkeyCreated: "Clave de acceso creada exitosamente",
passkeyCreationFailed:
"Error al crear la clave de acceso. Por favor, inténtelo de nuevo.",
removePasskeys: "Eliminar todas las claves de acceso",
removePasskeysTitle: "Eliminar todas las claves de acceso",
removePasskeysMessage:
"¿Está seguro de que desea eliminar todas las claves de acceso? Esta acción no se puede deshacer.",
passkeysRemoved: "Todas las claves de acceso han sido eliminadas",
passkeysRemoveFailed:
"Error al eliminar las claves de acceso. Por favor, inténtelo de nuevo.",
loginWithPasskey: "Iniciar sesión con clave de acceso",
authenticating: "Autenticando...",
passkeyLoginFailed:
"Error en la autenticación con clave de acceso. Por favor, inténtelo de nuevo.",
linkCopied: "Enlace copiado al portapapeles",
copyFailed: "Error al copiar enlace",
passkeyRequiresHttps: "WebAuthn requiere HTTPS o localhost. Por favor, acceda a la aplicación a través de HTTPS o utilice localhost en lugar de una dirección IP.",
passkeyWebAuthnNotSupported: "WebAuthn no es compatible con este navegador. Por favor, utilice un navegador moderno que sea compatible con WebAuthn.",
// Collection Page: "Cargando colección...", collectionNotFound: "Colección no encontrada",
noVideosInCollection: "No hay videos en esta colección.",
@@ -333,7 +365,8 @@ export const es = {
unknownAuthor: "Desconocido",
noVideosForAuthor: "No se encontraron videos para este autor.",
deleteAuthor: "Eliminar Autor",
deleteAuthorConfirmation: "¿Está seguro de que desea eliminar al autor {author}? Esto eliminará todos los videos asociados con este autor.",
deleteAuthorConfirmation:
"¿Está seguro de que desea eliminar al autor {author}? Esto eliminará todos los videos asociados con este autor.",
authorDeletedSuccessfully: "Autor eliminado con éxito",
failedToDeleteAuthor: "Error al eliminar autor",
deleteCollectionTitle: "Eliminar Colección",
@@ -375,7 +408,8 @@ export const es = {
authorOrPlaylist: "Autor / Lista de reproducción",
playlistDetected: "Lista de reproducción detectada",
playlistHasVideos: "Esta lista de reproducción tiene {count} videos.",
downloadPlaylistAndCreateCollection: "¿Descargar videos de la lista de reproducción y crear una colección para ella?",
downloadPlaylistAndCreateCollection:
"¿Descargar videos de la lista de reproducción y crear una colección para ella?",
collectionHasVideos: "Esta colección de Bilibili tiene {count} videos.",
seriesHasVideos: "Esta serie de Bilibili tiene {count} videos.",
videoHasParts: "Este video de Bilibili tiene {count} partes.",
@@ -438,7 +472,8 @@ export const es = {
subscriptionAlreadyExists: "Ya estás suscrito a este autor.",
minutes: "minutos",
never: "Nunca",
downloadAllPreviousVideos: "Descargar todos los videos anteriores de este autor",
downloadAllPreviousVideos:
"Descargar todos los videos anteriores de este autor",
downloadAllPreviousWarning:
"Advertencia: Esto descargará todos los videos anteriores de este autor. Esto puede consumir un espacio de almacenamiento significativo y podría activar mecanismos de detección de bots que pueden resultar en prohibiciones temporales o permanentes de la plataforma. Úselo bajo su propio riesgo.",
continuousDownloadTasks: "Tareas de descarga continua",
@@ -448,14 +483,17 @@ export const es = {
taskStatusCancelled: "Cancelado",
downloaded: "Descargado",
cancelTask: "Cancelar tarea",
confirmCancelTask: "¿Estás seguro de que quieres cancelar la tarea de descarga para {author}?",
confirmCancelTask:
"¿Estás seguro de que quieres cancelar la tarea de descarga para {author}?",
taskCancelled: "Tarea cancelada exitosamente",
deleteTask: "Eliminar tarea",
confirmDeleteTask: "¿Estás seguro de que quieres eliminar el registro de tarea para {author}? Esta acción no se puede deshacer.",
confirmDeleteTask:
"¿Estás seguro de que quieres eliminar el registro de tarea para {author}? Esta acción no se puede deshacer.",
taskDeleted: "Tarea eliminada exitosamente",
clearFinishedTasks: "Borrar tareas finalizadas",
tasksCleared: "Tareas finalizadas borradas con éxito",
confirmClearFinishedTasks: "¿Está seguro de que desea borrar todas las tareas finalizadas (completadas, canceladas)? Esto las eliminará de la lista pero no borrará ningún archivo descargado.",
confirmClearFinishedTasks:
"¿Está seguro de que desea borrar todas las tareas finalizadas (completadas, canceladas)? Esto las eliminará de la lista pero no borrará ningún archivo descargado.",
clear: "Borrar",
// Instruction Page
instructionSection1Title: "1. Descarga y Gestión de Tareas",
@@ -586,7 +624,8 @@ export const es = {
cloudflaredTunnel: "Túnel Cloudflare",
enableCloudflaredTunnel: "Habilitar túnel Cloudflare",
cloudflaredToken: "Token del túnel (Opcional)",
cloudflaredTokenHelper: "Pegue su token de túnel aquí, o déjelo vacío para usar un Quick Tunnel aleatorio.",
cloudflaredTokenHelper:
"Pegue su token de túnel aquí, o déjelo vacío para usar un Quick Tunnel aleatorio.",
waitingForUrl: "Esperando URL de Quick Tunnel...",
running: "Ejecutando",
stopped: "Detenido",
@@ -594,8 +633,10 @@ export const es = {
accountTag: "Etiqueta de cuenta",
copied: "¡Copiado!",
clickToCopy: "Clic para copiar",
quickTunnelWarning: "Las URL de Quick Tunnel cambian cada vez que se reinicia el túnel.",
managedInDashboard: "El nombre de host público se gestiona en su panel de control de Cloudflare Zero Trust.",
quickTunnelWarning:
"Las URL de Quick Tunnel cambian cada vez que se reinicia el túnel.",
managedInDashboard:
"El nombre de host público se gestiona en su panel de control de Cloudflare Zero Trust.",
failedToDownloadVideo: "Error al descargar el video. Inténtalo de nuevo.",
failedToDownload: "Error al descargar. Inténtalo de nuevo.",
playlistDownloadStarted: "Descarga de lista de reproducción iniciada",
@@ -607,24 +648,28 @@ export const es = {
copyUrl: "Copiar URL",
new: "NUEVO",
// Task Hooks
taskHooks: 'Ganchos de Tarea',
taskHooksDescription: 'Ejecute comandos de shell personalizados en puntos específicos del ciclo de vida de la tarea. Variables de entorno disponibles: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Advertencia: Los comandos se ejecutan con los permisos del servidor. Úselo con precaución.',
hookTaskBeforeStart: 'Antes del Inicio de la Tarea',
hookTaskBeforeStartHelper: 'Se ejecuta antes de que comience la descarga.',
hookTaskSuccess: 'Tarea Exitosa',
hookTaskSuccessHelper: 'Se ejecuta después de una descarga exitosa, antes de la carga/eliminación en la nube (espera finalización).',
hookTaskFail: 'Tarea Fallida',
hookTaskFailHelper: 'Se ejecuta cuando falla una tarea.',
hookTaskCancel: 'Tarea Cancelada',
hookTaskCancelHelper: 'Se ejecuta cuando una tarea se cancela manualmente.',
found: 'Encontrado',
notFound: 'No Establecido',
deleteHook: 'Eliminar Script de Gancho',
confirmDeleteHook: '¿Está seguro de que desea eliminar este script de gancho?',
uploadHook: 'Subir .sh',
taskHooks: "Ganchos de Tarea",
taskHooksDescription:
"Ejecute comandos de shell personalizados en puntos específicos del ciclo de vida de la tarea. Variables de entorno disponibles: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.",
taskHooksWarning:
"Advertencia: Los comandos se ejecutan con los permisos del servidor. Úselo con precaución.",
hookTaskBeforeStart: "Antes del Inicio de la Tarea",
hookTaskBeforeStartHelper: "Se ejecuta antes de que comience la descarga.",
hookTaskSuccess: "Tarea Exitosa",
hookTaskSuccessHelper:
"Se ejecuta después de una descarga exitosa, antes de la carga/eliminación en la nube (espera finalización).",
hookTaskFail: "Tarea Fallida",
hookTaskFailHelper: "Se ejecuta cuando falla una tarea.",
hookTaskCancel: "Tarea Cancelada",
hookTaskCancelHelper: "Se ejecuta cuando una tarea se cancela manualmente.",
found: "Encontrado",
notFound: "No Establecido",
deleteHook: "Eliminar Script de Gancho",
confirmDeleteHook:
"¿Está seguro de que desea eliminar este script de gancho?",
uploadHook: "Subir .sh",
disclaimerTitle: "Descargo de responsabilidad",
disclaimerText: "1. Propósito y Restricciones\nEste software (incluyendo código y documentación) está destinado únicamente para aprendizaje personal, investigación e intercambio técnico. Está estrictamente prohibido utilizar este software para fines comerciales o actividades ilegales que violen las leyes y regulaciones locales.\n\n2. Responsabilidad\nEl desarrollador desconoce y no tiene control sobre cómo los usuarios utilizan este software. Cualquier responsabilidad legal, disputa o daño derivado del uso ilegal o indebido de este software (incluyendo, entre otros, la infracción de derechos de autor) recaerá únicamente en el usuario. El desarrollador no asume ninguna responsabilidad directa, indirecta o conjunta.\n\n3. Modificaciones y Distribución\nEste proyecto es de código abierto. Cualquier individuo u organización que modifique o bifurque este código debe cumplir con la licencia de código abierto. Importante: Si un tercero modifica el código para eludir o eliminar los mecanismos originales de autenticación/seguridad del usuario y distribuye dichas versiones, el modificador/distribuidor asume toda la responsabilidad por cualquier consecuencia. Desaconsejamos encarecidamente eludir o manipular cualquier mecanismo de verificación de seguridad.\n\n4. Declaración Sin Fines de Lucro\nEste es un proyecto de código abierto completamente gratuito. El desarrollador no acepta donaciones y nunca ha publicado páginas de donación. El software en sí no permite cargos y no ofrece servicios pagos. Por favor, esté atento y tenga cuidado con cualquier estafa o información engañosa que reclame cobrar tarifas en nombre de este proyecto.",
disclaimerText:
"1. Propósito y Restricciones\nEste software (incluyendo código y documentación) está destinado únicamente para aprendizaje personal, investigación e intercambio técnico. Está estrictamente prohibido utilizar este software para fines comerciales o actividades ilegales que violen las leyes y regulaciones locales.\n\n2. Responsabilidad\nEl desarrollador desconoce y no tiene control sobre cómo los usuarios utilizan este software. Cualquier responsabilidad legal, disputa o daño derivado del uso ilegal o indebido de este software (incluyendo, entre otros, la infracción de derechos de autor) recaerá únicamente en el usuario. El desarrollador no asume ninguna responsabilidad directa, indirecta o conjunta.\n\n3. Modificaciones y Distribución\nEste proyecto es de código abierto. Cualquier individuo u organización que modifique o bifurque este código debe cumplir con la licencia de código abierto. Importante: Si un tercero modifica el código para eludir o eliminar los mecanismos originales de autenticación/seguridad del usuario y distribuye dichas versiones, el modificador/distribuidor asume toda la responsabilidad por cualquier consecuencia. Desaconsejamos encarecidamente eludir o manipular cualquier mecanismo de verificación de seguridad.\n\n4. Declaración Sin Fines de Lucro\nEste es un proyecto de código abierto completamente gratuito. El desarrollador no acepta donaciones y nunca ha publicado páginas de donación. El software en sí no permite cargos y no ofrece servicios pagos. Por favor, esté atento y tenga cuidado con cualquier estafa o información engañosa que reclame cobrar tarifas en nombre de este proyecto.",
};

View File

@@ -208,9 +208,11 @@ export const fr = {
uploadingVideo: "Téléversement : {title}",
clearThumbnailCache: "Vider le cache des miniatures locales",
clearing: "Nettoyage...",
clearThumbnailCacheSuccess: "Cache des miniatures vidé avec succès. Les miniatures seront régénérées lors du prochain accès.",
clearThumbnailCacheSuccess:
"Cache des miniatures vidé avec succès. Les miniatures seront régénérées lors du prochain accès.",
clearThumbnailCacheError: "Échec du vidage du cache des miniatures",
clearThumbnailCacheConfirmMessage: "Cela effacera toutes les miniatures mises en cache localement pour les vidéos cloud. Les miniatures seront régénérées à partir du stockage cloud lors du prochain accès. Continuer ?",
clearThumbnailCacheConfirmMessage:
"Cela effacera toutes les miniatures mises en cache localement pour les vidéos cloud. Les miniatures seront régénérées à partir du stockage cloud lors du prochain accès. Continuer ?",
// Manage
manageContent: "Gérer le contenu",
@@ -333,8 +335,27 @@ export const fr = {
"Le mot de passe a été réinitialisé. Consultez les journaux du backend pour le nouveau mot de passe.",
waitTimeMessage: "Veuillez attendre {time} avant de réessayer.",
tooManyAttempts: "Trop de tentatives échouées.",
// Passkeys
createPasskey: "Créer une clé d'accès",
creatingPasskey: "Création en cours...",
passkeyCreated: "Clé d'accès créée avec succès",
passkeyCreationFailed:
"Échec de la création de la clé d'accès. Veuillez réessayer.",
removePasskeys: "Supprimer toutes les clés d'accès",
removePasskeysTitle: "Supprimer toutes les clés d'accès",
removePasskeysMessage:
"Êtes-vous sûr de vouloir supprimer toutes les clés d'accès ? Cette action ne peut pas être annulée.",
passkeysRemoved: "Toutes les clés d'accès ont été supprimées",
passkeysRemoveFailed:
"Échec de la suppression des clés d'accès. Veuillez réessayer.",
loginWithPasskey: "Se connecter avec une clé d'accès",
authenticating: "Authentification en cours...",
passkeyLoginFailed:
"Échec de l'authentification par clé d'accès. Veuillez réessayer.",
linkCopied: "Lien copié dans le presse-papiers",
copyFailed: "Échec de la copie du lien",
passkeyRequiresHttps: "WebAuthn nécessite HTTPS ou localhost. Veuillez accéder à l'application via HTTPS ou utiliser localhost au lieu d'une adresse IP.",
passkeyWebAuthnNotSupported: "WebAuthn n'est pas supporté par ce navigateur. Veuillez utiliser un navigateur moderne qui supporte WebAuthn.",
// Collection Page
loadingCollection: "Chargement de la collection...",
@@ -402,7 +423,8 @@ export const fr = {
authorOrPlaylist: "Auteur / Playlist",
playlistDetected: "Playlist détectée",
playlistHasVideos: "Cette playlist contient {count} vidéos.",
downloadPlaylistAndCreateCollection: "Télécharger les vidéos de la playlist et créer une collection pour celle-ci ?",
downloadPlaylistAndCreateCollection:
"Télécharger les vidéos de la playlist et créer une collection pour celle-ci ?",
collectionHasVideos: "Cette collection Bilibili contient {count} vidéos.",
seriesHasVideos: "Cette série Bilibili contient {count} vidéos.",
videoHasParts: "Cette vidéo Bilibili contient {count} parties.",
@@ -489,7 +511,8 @@ export const fr = {
taskDeleted: "Tâche supprimée avec succès",
clearFinishedTasks: "Effacer les tâches terminées",
tasksCleared: "Tâches terminées effacées avec succès",
confirmClearFinishedTasks: "Êtes-vous sûr de vouloir effacer toutes les tâches terminées (complétées, annulées) ? Cela les supprimera de la liste mais ne supprimera aucun fichier téléchargé.",
confirmClearFinishedTasks:
"Êtes-vous sûr de vouloir effacer toutes les tâches terminées (complétées, annulées) ? Cela les supprimera de la liste mais ne supprimera aucun fichier téléchargé.",
clear: "Effacer",
// Instruction Page
instructionSection1Title: "1. Téléchargement et Gestion des Tâches",
@@ -633,7 +656,8 @@ export const fr = {
cloudflaredTunnel: "Tunnel Cloudflare",
enableCloudflaredTunnel: "Activer le tunnel Cloudflare",
cloudflaredToken: "Jeton de tunnel (Optionnel)",
cloudflaredTokenHelper: "Collez votre jeton de tunnel ici, ou laissez vide pour utiliser un Quick Tunnel aléatoire.",
cloudflaredTokenHelper:
"Collez votre jeton de tunnel ici, ou laissez vide pour utiliser un Quick Tunnel aléatoire.",
waitingForUrl: "En attente de l'URL Quick Tunnel...",
running: "En cours",
stopped: "Arrêté",
@@ -641,34 +665,43 @@ export const fr = {
accountTag: "Tag de compte",
copied: "Copié !",
clickToCopy: "Cliquer pour copier",
quickTunnelWarning: "Les URL Quick Tunnel changent à chaque redémarrage du tunnel.",
managedInDashboard: "Le nom d'hôte public est géré dans votre tableau de bord Cloudflare Zero Trust.",
failedToDownloadVideo: "Échec du téléchargement de la vidéo. Veuillez réessayer.",
quickTunnelWarning:
"Les URL Quick Tunnel changent à chaque redémarrage du tunnel.",
managedInDashboard:
"Le nom d'hôte public est géré dans votre tableau de bord Cloudflare Zero Trust.",
failedToDownloadVideo:
"Échec du téléchargement de la vidéo. Veuillez réessayer.",
failedToDownload: "Échec du téléchargement. Veuillez réessayer.",
playlistDownloadStarted: "Téléchargement de la playlist commencé",
copyUrl: "Copier l'URL",
new: "NOUVEAU",
// Task Hooks
taskHooks: 'Crochets de Tâche',
taskHooksDescription: 'Exécutez des commandes shell personnalisées à des points spécifiques du cycle de vie de la tâche. Variables d\'environnement disponibles : MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Avertissement : Les commandes s\'exécutent avec les permissions du serveur. À utiliser avec prudence.',
enterPasswordToUploadHook: 'Veuillez entrer votre mot de passe pour télécharger ce script Hook.',
riskCommandDetected: 'Commande à risque détectée : {command}. Téléchargement rejeté.',
hookTaskBeforeStart: 'Avant le Début de la Tâche',
hookTaskBeforeStartHelper: 'S\'exécute avant le début du téléchargement.',
hookTaskSuccess: 'Tâche Réussie',
hookTaskSuccessHelper: 'S\'exécute après un téléchargement réussi, avant le téléchargement/suppression cloud (attend la fin).',
hookTaskFail: 'Tâche Échouée',
hookTaskFailHelper: 'S\'exécute lorsqu\'une tâche échoue.',
hookTaskCancel: 'Tâche Annulée',
hookTaskCancelHelper: 'S\'exécute lorsqu\'une tâche est annulée manuellement.',
found: 'Trouvé',
notFound: 'Non Défini',
deleteHook: 'Supprimer le Script de Crochet',
confirmDeleteHook: 'Êtes-vous sûr de vouloir supprimer ce script de crochet ?',
uploadHook: 'Téléverser .sh',
taskHooks: "Crochets de Tâche",
taskHooksDescription:
"Exécutez des commandes shell personnalisées à des points spécifiques du cycle de vie de la tâche. Variables d'environnement disponibles : MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.",
taskHooksWarning:
"Avertissement : Les commandes s'exécutent avec les permissions du serveur. À utiliser avec prudence.",
enterPasswordToUploadHook:
"Veuillez entrer votre mot de passe pour télécharger ce script Hook.",
riskCommandDetected:
"Commande à risque détectée : {command}. Téléchargement rejeté.",
hookTaskBeforeStart: "Avant le Début de la Tâche",
hookTaskBeforeStartHelper: "S'exécute avant le début du téléchargement.",
hookTaskSuccess: "Tâche Réussie",
hookTaskSuccessHelper:
"S'exécute après un téléchargement réussi, avant le téléchargement/suppression cloud (attend la fin).",
hookTaskFail: "Tâche Échouée",
hookTaskFailHelper: "S'exécute lorsqu'une tâche échoue.",
hookTaskCancel: "Tâche Annulée",
hookTaskCancelHelper: "S'exécute lorsqu'une tâche est annulée manuellement.",
found: "Trouvé",
notFound: "Non Défini",
deleteHook: "Supprimer le Script de Crochet",
confirmDeleteHook:
"Êtes-vous sûr de vouloir supprimer ce script de crochet ?",
uploadHook: "Téléverser .sh",
disclaimerTitle: "Avis de non-responsabilité",
disclaimerText: "1. Objectif et Restrictions\nCe logiciel (y compris le code et la documentation) est destiné uniquement à l'apprentissage personnel, à la recherche et à l'échange technique. Il est strictement interdit d'utiliser ce logiciel à des fins commerciales ou pour toute activité illégale violant les lois et réglementations locales.\n\n2. Responsabilité\nLe développeur n'a aucune connaissance et aucun contrôle sur la façon dont les utilisateurs utilisent ce logiciel. Toute responsabilité légale, litige ou dommage découlant de l'utilisation illégale ou inappropriée de ce logiciel (y compris, mais sans s'y limiter, la violation du droit d'auteur) sera à la charge exclusive de l'utilisateur. Le développeur n'assume aucune responsabilité directe, indirecte ou conjointe.\n\n3. Modifications et Distribution\nCe projet est open source. Tout individu ou organisation modifiant ou forkant ce code doit se conformer à la licence open source. Important : Si un tiers modifie le code pour contourner ou supprimer les mécanismes d'authentification/sécurité d'origine de l'utilisateur et distribue de telles versions, le modificateur/distributeur porte l'entière responsabilité de toutes les conséquences. Nous déconseillons fortement de contourner ou d'altérer tout mécanisme de vérification de sécurité.\n\n4. Déclaration à But Non Lucratif\nCeci est un projet open source entièrement gratuit. Le développeur n'accepte pas de dons et n'a jamais publié de pages de dons. Le logiciel lui-même ne permet aucun frais et n'offre aucun service payant. Veuillez être vigilant et vous méfier de toute arnaque ou information trompeuse prétendant percevoir des frais au nom de ce projet.",
disclaimerText:
"1. Objectif et Restrictions\nCe logiciel (y compris le code et la documentation) est destiné uniquement à l'apprentissage personnel, à la recherche et à l'échange technique. Il est strictement interdit d'utiliser ce logiciel à des fins commerciales ou pour toute activité illégale violant les lois et réglementations locales.\n\n2. Responsabilité\nLe développeur n'a aucune connaissance et aucun contrôle sur la façon dont les utilisateurs utilisent ce logiciel. Toute responsabilité légale, litige ou dommage découlant de l'utilisation illégale ou inappropriée de ce logiciel (y compris, mais sans s'y limiter, la violation du droit d'auteur) sera à la charge exclusive de l'utilisateur. Le développeur n'assume aucune responsabilité directe, indirecte ou conjointe.\n\n3. Modifications et Distribution\nCe projet est open source. Tout individu ou organisation modifiant ou forkant ce code doit se conformer à la licence open source. Important : Si un tiers modifie le code pour contourner ou supprimer les mécanismes d'authentification/sécurité d'origine de l'utilisateur et distribue de telles versions, le modificateur/distributeur porte l'entière responsabilité de toutes les conséquences. Nous déconseillons fortement de contourner ou d'altérer tout mécanisme de vérification de sécurité.\n\n4. Déclaration à But Non Lucratif\nCeci est un projet open source entièrement gratuit. Le développeur n'accepte pas de dons et n'a jamais publié de pages de dons. Le logiciel lui-même ne permet aucun frais et n'offre aucun service payant. Veuillez être vigilant et vous méfier de toute arnaque ou information trompeuse prétendant percevoir des frais au nom de ce projet.",
};

View File

@@ -134,8 +134,10 @@ export const ja = {
showYoutubeSearch: "YouTube検索結果を表示",
visitorMode: "ビジターモード(読み取り専用)",
visitorModeReadOnly: "ビジターモード:読み取り専用",
visitorModeDescription: "読み取り専用モード。非表示の動画は訪問者には表示されません。",
visitorModePasswordPrompt: "ビジターモードの設定を変更するには、ウェブサイトのパスワードを入力してください。",
visitorModeDescription:
"読み取り専用モード。非表示の動画は訪問者には表示されません。",
visitorModePasswordPrompt:
"ビジターモードの設定を変更するには、ウェブサイトのパスワードを入力してください。",
cleanupTempFilesSuccess: "{count}個の一時ファイルを正常に削除しました。",
cleanupTempFilesFailed: "一時ファイルのクリーンアップに失敗しました",
@@ -164,12 +166,14 @@ export const ja = {
apiUrlHelper: "例: https://your-alist-instance.com/api/fs/put",
token: "トークン",
publicUrl: "公開URL",
publicUrlHelper: "ファイルにアクセスするための公開ドメイン(例: https://your-cloudflare-tunnel-domain.com。設定されている場合、ファイルアクセスにはAPI URLの代わりにこれが使用されます。",
publicUrlHelper:
"ファイルにアクセスするための公開ドメイン(例: https://your-cloudflare-tunnel-domain.com。設定されている場合、ファイルアクセスにはAPI URLの代わりにこれが使用されます。",
uploadPath: "アップロードパス",
cloudDrivePathHelper:
"クラウドドライブ内のディレクトリパス、例: /mytube-uploads",
scanPaths: "スキャンパス",
scanPathsHelper: "1行に1つのパスを入力してください。これらのパスから動画がスキャンされます。空の場合はアップロードパスが使用されます。例\n/a/映画\n/b/ドキュメンタリー",
scanPathsHelper:
"1行に1つのパスを入力してください。これらのパスから動画がスキャンされます。空の場合はアップロードパスが使用されます。例\n/a/映画\n/b/ドキュメンタリー",
cloudDriveNote:
"この機能を有効にすると、新しくダウンロードされた動画は自動的にクラウドストレージにアップロードされ、ローカルファイルは削除されます。動画はプロキシ経由でクラウドストレージから再生されます。",
cloudScanAdded: "クラウドから追加",
@@ -177,7 +181,8 @@ export const ja = {
testConnection: "接続をテスト",
sync: "同期",
syncToCloud: "双方向同期",
syncWarning: "この操作はローカル動画をクラウドにアップロードし、クラウド上の新しいファイルをスキャンします。アップロード後、ローカルファイルは削除されます。",
syncWarning:
"この操作はローカル動画をクラウドにアップロードし、クラウド上の新しいファイルをスキャンします。アップロード後、ローカルファイルは削除されます。",
syncing: "同期中...",
syncCompleted: "同期完了",
syncFailed: "同期に失敗しました",
@@ -185,18 +190,22 @@ export const ja = {
syncErrors: "エラー:",
fillApiUrlToken: "API URLとトークンを入力してください",
connectionTestSuccess: "接続テスト成功!設定は有効です。",
connectionFailedStatus: "接続失敗:サーバーがステータス {status} を返しました",
connectionFailedStatus:
"接続失敗:サーバーがステータス {status} を返しました",
connectionFailedUrl: "サーバーに接続できません。API URLを確認してください。",
authFailed: "認証に失敗しました。トークンを確認してください。",
connectionTestFailed: "接続テスト失敗:{error}",
syncFailedMessage: "同期に失敗しました。もう一度お試しください。",
foundVideosToSync: "同期するローカルファイルを持つ動画が {count} 件見つかりました",
foundVideosToSync:
"同期するローカルファイルを持つ動画が {count} 件見つかりました",
uploadingVideo: "アップロード中: {title}",
clearThumbnailCache: "サムネイルのローカルキャッシュをクリア",
clearing: "クリア中...",
clearThumbnailCacheSuccess: "サムネイルキャッシュが正常にクリアされました。サムネイルは次回のアクセス時に再生成されます。",
clearThumbnailCacheSuccess:
"サムネイルキャッシュが正常にクリアされました。サムネイルは次回のアクセス時に再生成されます。",
clearThumbnailCacheError: "サムネイルキャッシュのクリアに失敗しました",
clearThumbnailCacheConfirmMessage: "クラウド動画用にローカルにキャッシュされたすべてのサムネイルをクリアします。サムネイルは次回のアクセス時にクラウドストレージから再生成されます。続行しますか?",
clearThumbnailCacheConfirmMessage:
"クラウド動画用にローカルにキャッシュされたすべてのサムネイルをクリアします。サムネイルは次回のアクセス時にクラウドストレージから再生成されます。続行しますか?",
// Manage
manageContent: "コンテンツの管理",
@@ -285,7 +294,8 @@ export const ja = {
openInExternalPlayer: "外部プレーヤーで開く",
playWith: "で再生...",
deleteAllFilteredVideos: "フィルタリングされた動画をすべて削除",
confirmDeleteFilteredVideos: "選択されたタグでフィルタリングされた {count} 本の動画を削除してもよろしいですか?",
confirmDeleteFilteredVideos:
"選択されたタグでフィルタリングされた {count} 本の動画を削除してもよろしいですか?",
deleteFilteredVideosSuccess: "{count} 本の動画を削除しました。",
deletingVideos: "動画を削除中...",
@@ -309,8 +319,26 @@ export const ja = {
"パスワードがリセットされました。新しいパスワードについては、バックエンドログを確認してください。",
waitTimeMessage: "再試行する前に {time} お待ちください。",
tooManyAttempts: "失敗した試行が多すぎます。",
// Passkeys
createPasskey: "パスキーを作成",
creatingPasskey: "作成中...",
passkeyCreated: "パスキーが正常に作成されました",
passkeyCreationFailed:
"パスキーの作成に失敗しました。もう一度お試しください。",
removePasskeys: "すべてのパスキーを削除",
removePasskeysTitle: "すべてのパスキーを削除",
removePasskeysMessage:
"すべてのパスキーを削除してもよろしいですか?この操作は元に戻せません。",
passkeysRemoved: "すべてのパスキーが削除されました",
passkeysRemoveFailed:
"パスキーの削除に失敗しました。もう一度お試しください。",
loginWithPasskey: "パスキーでログイン",
authenticating: "認証中...",
passkeyLoginFailed: "パスキー認証に失敗しました。もう一度お試しください。",
linkCopied: "リンクをクリップボードにコピーしました",
copyFailed: "リンクのコピーに失敗しました",
passkeyRequiresHttps: "WebAuthnにはHTTPSまたはlocalhostが必要です。HTTPS経由でアプリケーションにアクセスするか、IPアドレスの代わりにlocalhostを使用してください。",
passkeyWebAuthnNotSupported: "このブラウザはWebAuthnをサポートしていません。WebAuthnをサポートする最新のブラウザを使用してください。",
// Collection Page
loadingCollection: "コレクションを読み込み中...",
@@ -380,7 +408,8 @@ export const ja = {
authorOrPlaylist: "作者 / 再生リスト",
playlistDetected: "プレイリストが検出されました",
playlistHasVideos: "このプレイリストには{count}本の動画があります。",
downloadPlaylistAndCreateCollection: "プレイリストの動画をダウンロードして、コレクションを作成しますか?",
downloadPlaylistAndCreateCollection:
"プレイリストの動画をダウンロードして、コレクションを作成しますか?",
collectionHasVideos:
"このBilibiliコレクションには{count}個の動画があります。",
seriesHasVideos: "このBilibiliシリーズには{count}個の動画があります。",
@@ -454,14 +483,17 @@ export const ja = {
taskStatusCancelled: "キャンセル済み",
downloaded: "ダウンロード済み",
cancelTask: "タスクをキャンセル",
confirmCancelTask: "{author} のダウンロードタスクをキャンセルしてもよろしいですか?",
confirmCancelTask:
"{author} のダウンロードタスクをキャンセルしてもよろしいですか?",
taskCancelled: "タスクが正常にキャンセルされました",
deleteTask: "タスクを削除",
confirmDeleteTask: "{author} のタスクレコードを削除してもよろしいですか?この操作は元に戻せません。",
confirmDeleteTask:
"{author} のタスクレコードを削除してもよろしいですか?この操作は元に戻せません。",
taskDeleted: "タスクが正常に削除されました",
clearFinishedTasks: "完了したタスクをクリア",
tasksCleared: "完了したタスクを正常にクリアしました",
confirmClearFinishedTasks: "完了したタスク(完了、キャンセル済み)をすべてクリアしてもよろしいですか?これによりリストからは削除されますが、ダウンロードされたファイルは削除されません。",
confirmClearFinishedTasks:
"完了したタスク(完了、キャンセル済み)をすべてクリアしてもよろしいですか?これによりリストからは削除されますが、ダウンロードされたファイルは削除されません。",
clear: "クリア",
// Instruction Page
instructionSection1Title: "1. ダウンロードとタスク管理",
@@ -600,7 +632,8 @@ export const ja = {
lastBackupDate: "最後のバックアップ日時",
noBackupAvailable: "利用可能なバックアップがありません",
deleteAuthor: "著者を削除",
deleteAuthorConfirmation: "著者 {author} を削除してもよろしいですか?これにより、この著者に関連するすべての動画が削除されます。",
deleteAuthorConfirmation:
"著者 {author} を削除してもよろしいですか?これにより、この著者に関連するすべての動画が削除されます。",
authorDeletedSuccessfully: "著者が正常に削除されました",
failedToDeleteAuthor: "著者の削除に失敗しました",
@@ -608,7 +641,8 @@ export const ja = {
cloudflaredTunnel: "Cloudflare Tunnel",
enableCloudflaredTunnel: "Cloudflare Tunnelを有効にする",
cloudflaredToken: "トンネルトークン (オプション)",
cloudflaredTokenHelper: "ここにトンネルトークンを貼り付けるか、空のままにしてランダムなQuick Tunnelを使用します。",
cloudflaredTokenHelper:
"ここにトンネルトークンを貼り付けるか、空のままにしてランダムなQuick Tunnelを使用します。",
waitingForUrl: "Quick Tunnel URLを待機中...",
running: "実行中",
stopped: "停止",
@@ -616,9 +650,12 @@ export const ja = {
accountTag: "アカウントタグ",
copied: "コピーしました!",
clickToCopy: "クリックしてコピー",
quickTunnelWarning: "Quick TunnelのURLは、トンネルが再起動するたびに変更されます。",
managedInDashboard: "パブリックホスト名はCloudflare Zero Trustダッシュボードで管理されています。",
failedToDownloadVideo: "動画のダウンロードに失敗しました。もう一度お試しください。",
quickTunnelWarning:
"Quick TunnelのURLは、トンネルが再起動するたびに変更されます。",
managedInDashboard:
"パブリックホスト名はCloudflare Zero Trustダッシュボードで管理されています。",
failedToDownloadVideo:
"動画のダウンロードに失敗しました。もう一度お試しください。",
failedToDownload: "ダウンロードに失敗しました。もう一度お試しください。",
playlistDownloadStarted: "プレイリストのダウンロードが開始されました",
deleteFailed: "削除に失敗しました",
@@ -628,26 +665,31 @@ export const ja = {
copyUrl: "URLをコピー",
new: "新規",
// Task Hooks
taskHooks: 'タスクフック',
taskHooksDescription: 'タスクライフサイクルの特定のポイントでカスタムシェルコマンドを実行します。利用可能な環境変数: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH。',
taskHooksWarning: '警告: コマンドはサーバーの権限で実行されます。注意して使用してください。',
enterPasswordToUploadHook: 'このフック・スクリプトをアップロードするにはパスワードを入力してください。',
riskCommandDetected: '危険なコマンドが検出されました: {command}。アップロードは拒否されました。',
hookTaskBeforeStart: 'タスク開始前',
hookTaskBeforeStartHelper: 'ダウンロードが始まる前に実行されます。',
hookTaskSuccess: 'タスク成功',
hookTaskSuccessHelper: 'ダウンロード成功後、クラウドアップロード/削除の前に実行されます(完了を待ちます)。',
hookTaskFail: 'タスク失敗',
hookTaskFailHelper: 'タスクが失敗したときに実行されます。',
hookTaskCancel: 'タスクキャンセル',
hookTaskCancelHelper: 'タスクが手動でキャンセルされたときに実行されます。',
found: '見つかりました',
notFound: '未設定',
deleteHook: 'フックスクリプトを削除',
confirmDeleteHook: 'このフックスクリプトを削除してもよろしいですか?',
uploadHook: 'アップロード .sh',
taskHooks: "タスクフック",
taskHooksDescription:
"タスクライフサイクルの特定のポイントでカスタムシェルコマンドを実行します。利用可能な環境変数: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH。",
taskHooksWarning:
"警告: コマンドはサーバーの権限で実行されます。注意して使用してください。",
enterPasswordToUploadHook:
"このフック・スクリプトをアップロードするにはパスワードを入力してください。",
riskCommandDetected:
"危険なコマンドが検出されました: {command}。アップロードは拒否されました。",
hookTaskBeforeStart: "タスク開始前",
hookTaskBeforeStartHelper: "ダウンロードが始まる前に実行されます。",
hookTaskSuccess: "タスク成功",
hookTaskSuccessHelper:
"ダウンロード成功後、クラウドアップロード/削除の前に実行されます(完了を待ちます)。",
hookTaskFail: "タスク失敗",
hookTaskFailHelper: "タスクが失敗したときに実行されます。",
hookTaskCancel: "タスクキャンセル",
hookTaskCancelHelper: "タスクが手動でキャンセルされたときに実行されます。",
found: "見つかりました",
notFound: "未設定",
deleteHook: "フックスクリプトを削除",
confirmDeleteHook: "このフックスクリプトを削除してもよろしいですか?",
uploadHook: "アップロード .sh",
disclaimerTitle: "免責事項",
disclaimerText: "1. 目的と制限\nこのソフトウェアコードおよびドキュメントを含むは、個人の学習、研究、および技術交流のみを目的としています。このソフトウェアを商業目的で使用すること、または地域の法律や規制に違反する違法行為に使用することは固く禁じられています。\n\n2. 責任\n開発者は、ユーザーがこのソフトウェアをどのように使用するかについて認識しておらず、管理もしていません。このソフトウェアの違法または不適切な使用著作権侵害を含むがこれに限定されないから生じる法的責任、紛争、または損害は、ユーザーのみが負担するものとします。開発者は、直接的、間接的、または連帯責任を負いません。\n\n3. 修正と配布\nこのプロジェクトはオープンソースです。このコードを修正またはフォークする個人または組織は、オープンソースライセンスを遵守する必要があります。重要第三者が元のユーザー認証/セキュリティメカニズムを回避または削除するためにコードを修正し、そのようなバージョンを配布する場合、修正者/配布者はすべての結果に対して全責任を負います。セキュリティ検証メカニズムを回避または改ざんすることを強くお勧めしません。\n\n4. 非営利声明\nこれは完全に無料のオープンソースプロジェクトです。開発者は寄付を受け付けておらず、寄付ページを公開したこともありません。ソフトウェア自体は料金を許可しておらず、有料サービスも提供していません。このプロジェクトに代わって料金を徴収すると主張する詐欺や誤解を招く情報には十分ご注意ください。",
disclaimerText:
"1. 目的と制限\nこのソフトウェアコードおよびドキュメントを含むは、個人の学習、研究、および技術交流のみを目的としています。このソフトウェアを商業目的で使用すること、または地域の法律や規制に違反する違法行為に使用することは固く禁じられています。\n\n2. 責任\n開発者は、ユーザーがこのソフトウェアをどのように使用するかについて認識しておらず、管理もしていません。このソフトウェアの違法または不適切な使用著作権侵害を含むがこれに限定されないから生じる法的責任、紛争、または損害は、ユーザーのみが負担するものとします。開発者は、直接的、間接的、または連帯責任を負いません。\n\n3. 修正と配布\nこのプロジェクトはオープンソースです。このコードを修正またはフォークする個人または組織は、オープンソースライセンスを遵守する必要があります。重要第三者が元のユーザー認証/セキュリティメカニズムを回避または削除するためにコードを修正し、そのようなバージョンを配布する場合、修正者/配布者はすべての結果に対して全責任を負います。セキュリティ検証メカニズムを回避または改ざんすることを強くお勧めしません。\n\n4. 非営利声明\nこれは完全に無料のオープンソースプロジェクトです。開発者は寄付を受け付けておらず、寄付ページを公開したこともありません。ソフトウェア自体は料金を許可しておらず、有料サービスも提供していません。このプロジェクトに代わって料金を徴収すると主張する詐欺や誤解を招く情報には十分ご注意ください。",
};

View File

@@ -131,8 +131,10 @@ export const ko = {
showYoutubeSearch: "YouTube 검색 결과 표시",
visitorMode: "방문자 모드 (읽기 전용)",
visitorModeReadOnly: "방문자 모드: 읽기 전용",
visitorModeDescription: "읽기 전용 모드. 숨겨진 동영상은 방문자에게 표시되지 않습니다.",
visitorModePasswordPrompt: "방문자 모드 설정을 변경하려면 웹사이트 비밀번호를 입력하세요.",
visitorModeDescription:
"읽기 전용 모드. 숨겨진 동영상은 방문자에게 표시되지 않습니다.",
visitorModePasswordPrompt:
"방문자 모드 설정을 변경하려면 웹사이트 비밀번호를 입력하세요.",
cleanupTempFilesSuccess: "{count}개의 임시 파일을 성공적으로 삭제했습니다.",
cleanupTempFilesFailed: "임시 파일 정리 실패",
@@ -161,12 +163,14 @@ export const ko = {
apiUrlHelper: "예: https://your-alist-instance.com/api/fs/put",
token: "토큰",
publicUrl: "공개 URL",
publicUrlHelper: "파일 액세스를 위한 공개 도메인 (예: https://your-cloudflare-tunnel-domain.com). 설정된 경우 파일 액세스에 API URL 대신 이것이 사용됩니다.",
publicUrlHelper:
"파일 액세스를 위한 공개 도메인 (예: https://your-cloudflare-tunnel-domain.com). 설정된 경우 파일 액세스에 API URL 대신 이것이 사용됩니다.",
uploadPath: "업로드 경로",
cloudDrivePathHelper:
"클라우드 드라이브 내 디렉토리 경로, 예: /mytube-uploads",
scanPaths: "스캔 경로",
scanPathsHelper: "줄당 하나의 경로. 이 경로에서 동영상을 스캔합니다. 비어 있으면 업로드 경로를 사용합니다. 예:\n/a/영화\n/b/다큐멘터리",
scanPathsHelper:
"줄당 하나의 경로. 이 경로에서 동영상을 스캔합니다. 비어 있으면 업로드 경로를 사용합니다. 예:\n/a/영화\n/b/다큐멘터리",
cloudDriveNote:
"이 기능을 활성화한 후 새로 다운로드된 비디오는 자동으로 클라우드 스토리지에 업로드되고 로컬 파일은 삭제됩니다. 비디오는 프록시를 통해 클라우드 스토리지에서 재생됩니다.",
cloudScanAdded: "클라우드에서 추가됨",
@@ -174,7 +178,8 @@ export const ko = {
testConnection: "연결 테스트",
sync: "동기화",
syncToCloud: "양방향 동기화",
syncWarning: "이 작업은 로컬 동영상을 클라우드로 업로드하고 클라우드 저장소에서 새 파일을 검색합니다. 업로드 후 로컬 파일은 삭제됩니다。",
syncWarning:
"이 작업은 로컬 동영상을 클라우드로 업로드하고 클라우드 저장소에서 새 파일을 검색합니다. 업로드 후 로컬 파일은 삭제됩니다。",
syncing: "동기화 중...",
syncCompleted: "동기화 완료",
syncFailed: "동기화 실패",
@@ -191,9 +196,11 @@ export const ko = {
uploadingVideo: "업로드 중: {title}",
clearThumbnailCache: "썸네일 로컬 캐시 지우기",
clearing: "지우는 중...",
clearThumbnailCacheSuccess: "썸네일 캐시가 성공적으로 지워졌습니다. 썸네일은 다음에 액세스할 때 재생성됩니다.",
clearThumbnailCacheSuccess:
"썸네일 캐시가 성공적으로 지워졌습니다. 썸네일은 다음에 액세스할 때 재생성됩니다.",
clearThumbnailCacheError: "썸네일 캐시 지우기 실패",
clearThumbnailCacheConfirmMessage: "이 작업은 클라우드 비디오에 대해 로컬로 캐시된 모든 썸네일을 지웁니다. 썸네일은 다음에 액세스할 때 클라우드 저장소에서 재생성됩니다. 계속하시겠습니까?",
clearThumbnailCacheConfirmMessage:
"이 작업은 클라우드 비디오에 대해 로컬로 캐시된 모든 썸네일을 지웁니다. 썸네일은 다음에 액세스할 때 클라우드 저장소에서 재생성됩니다. 계속하시겠습니까?",
// Manage
manageContent: "콘텐츠 관리",
@@ -282,7 +289,8 @@ export const ko = {
openInExternalPlayer: "외부 플레이어에서 열기",
playWith: "다음으로 재생...",
deleteAllFilteredVideos: "필터링된 모든 동영상 삭제",
confirmDeleteFilteredVideos: "선택한 태그로 필터링된 {count}개의 동영상을 삭제하시겠습니까?",
confirmDeleteFilteredVideos:
"선택한 태그로 필터링된 {count}개의 동영상을 삭제하시겠습니까?",
deleteFilteredVideosSuccess: "{count}개의 동영상을 성공적으로 삭제했습니다.",
deletingVideos: "동영상 삭제 중...",
@@ -306,8 +314,24 @@ export const ko = {
"비밀번호가 재설정되었습니다. 새 비밀번호는 백엔드 로그를 확인하세요.",
waitTimeMessage: "다시 시도하기 전에 {time} 기다려 주세요.",
tooManyAttempts: "실패한 시도가 너무 많습니다.",
// Passkeys
createPasskey: "패스키 만들기",
creatingPasskey: "만드는 중...",
passkeyCreated: "패스키가 성공적으로 생성되었습니다",
passkeyCreationFailed: "패스키 생성에 실패했습니다. 다시 시도해 주세요.",
removePasskeys: "모든 패스키 제거",
removePasskeysTitle: "모든 패스키 제거",
removePasskeysMessage:
"모든 패스키를 제거하시겠습니까? 이 작업은 취소할 수 없습니다.",
passkeysRemoved: "모든 패스키가 제거되었습니다",
passkeysRemoveFailed: "패스키 제거에 실패했습니다. 다시 시도해 주세요.",
loginWithPasskey: "패스키로 로그인",
authenticating: "인증 중...",
passkeyLoginFailed: "패스키 인증에 실패했습니다. 다시 시도해 주세요.",
linkCopied: "링크가 클립보드에 복사되었습니다",
copyFailed: "링크 복사 실패",
passkeyRequiresHttps: "WebAuthn은 HTTPS 또는 localhost가 필요합니다. HTTPS를 통해 애플리케이션에 액세스하거나 IP 주소 대신 localhost를 사용하십시오.",
passkeyWebAuthnNotSupported: "이 브라우저는 WebAuthn을 지원하지 않습니다. WebAuthn을 지원하는 최신 브라우저를 사용하십시오.",
// Collection Page
loadingCollection: "컬렉션 로드 중...",
@@ -377,7 +401,8 @@ export const ko = {
authorOrPlaylist: "작성자 / 재생 목록",
playlistDetected: "재생 목록 감지됨",
playlistHasVideos: "이 재생 목록에는 {count}개의 동영상이 있습니다.",
downloadPlaylistAndCreateCollection: "재생 목록 동영상을 다운로드하고 컬렉션을 만드시겠습니까?",
downloadPlaylistAndCreateCollection:
"재생 목록 동영상을 다운로드하고 컬렉션을 만드시겠습니까?",
collectionHasVideos: "이 Bilibili 컬렉션에는 {count}개의 동영상이 있습니다.",
seriesHasVideos: "이 Bilibili 시리즈에는 {count}개의 동영상이 있습니다.",
videoHasParts: "이 Bilibili 동영상에는 {count}개의 파트가 있습니다.",
@@ -453,11 +478,13 @@ export const ko = {
confirmCancelTask: "{author}님의 다운로드 작업을 취소하시겠습니까?",
taskCancelled: "작업이 성공적으로 취소되었습니다",
deleteTask: "작업 삭제",
confirmDeleteTask: "{author}님의 작업 기록을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
confirmDeleteTask:
"{author}님의 작업 기록을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
taskDeleted: "작업이 성공적으로 삭제되었습니다",
clearFinishedTasks: "완료된 작업 지우기",
tasksCleared: "완료된 작업이 성공적으로 지워졌습니다",
confirmClearFinishedTasks: "완료된 모든 작업(완료됨, 취소됨)을 지우시겠습니까? 목록에서 제거되지만 다운로드된 파일은 삭제되지 않습니다.",
confirmClearFinishedTasks:
"완료된 모든 작업(완료됨, 취소됨)을 지우시겠습니까? 목록에서 제거되지만 다운로드된 파일은 삭제되지 않습니다.",
clear: "지우기",
// Instruction Page
instructionSection1Title: "1. 다운로드 및 작업 관리",
@@ -591,7 +618,8 @@ export const ko = {
lastBackupDate: "마지막 백업 날짜",
noBackupAvailable: "사용 가능한 백업 없음",
deleteAuthor: "작성자 삭제",
deleteAuthorConfirmation: "작성자 {author}님을 삭제하시겠습니까? 이 작성자와 관련된 모든 동영상이 삭제됩니다.",
deleteAuthorConfirmation:
"작성자 {author}님을 삭제하시겠습니까? 이 작성자와 관련된 모든 동영상이 삭제됩니다.",
authorDeletedSuccessfully: "작성자가 성공적으로 삭제되었습니다",
failedToDeleteAuthor: "작성자 삭제 실패",
@@ -599,7 +627,8 @@ export const ko = {
cloudflaredTunnel: "Cloudflare 터널",
enableCloudflaredTunnel: "Cloudflare 터널 활성화",
cloudflaredToken: "터널 토큰 (선택 사항)",
cloudflaredTokenHelper: "여기에 터널 토큰을 붙여넣거나, 임의의 Quick Tunnel을 사용하려면 비워 두세요.",
cloudflaredTokenHelper:
"여기에 터널 토큰을 붙여넣거나, 임의의 Quick Tunnel을 사용하려면 비워 두세요.",
waitingForUrl: "Quick Tunnel URL 대기 중...",
running: "실행 중",
stopped: "중지됨",
@@ -607,8 +636,10 @@ export const ko = {
accountTag: "계정 태그",
copied: "복사됨!",
clickToCopy: "클릭하여 복사",
quickTunnelWarning: "Quick Tunnel URL은 터널이 다시 시작될 때마다 변경됩니다.",
managedInDashboard: "공개 호스트 이름은 Cloudflare Zero Trust 대시보드에서 관리됩니다.",
quickTunnelWarning:
"Quick Tunnel URL은 터널이 다시 시작될 때마다 변경됩니다.",
managedInDashboard:
"공개 호스트 이름은 Cloudflare Zero Trust 대시보드에서 관리됩니다.",
failedToDownloadVideo: "동영상 다운로드에 실패했습니다. 다시 시도해 주세요.",
failedToDownload: "다운로드에 실패했습니다. 다시 시도해 주세요.",
playlistDownloadStarted: "재생 목록 다운로드가 시작되었습니다",
@@ -616,26 +647,30 @@ export const ko = {
copyUrl: "URL 복사",
new: "신규",
// Task Hooks
taskHooks: '태스크 훅',
taskHooksDescription: '태스크 수명 주기의 특정 지점에서 사용자 지정 셸 명령을 실행합니다. 사용 가능한 환경 변수: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: '경고: 명령은 서버 권한으로 실행니다. 주의해서 사용하십시오.',
enterPasswordToUploadHook: '이 훅 스크립트를 업로드하려면 비밀번호를 입력하십시오.',
riskCommandDetected: '위험한 명령 감지됨: {command}. 업로드 거부됨.',
hookTaskBeforeStart: '태스크 시작 전',
hookTaskBeforeStartHelper: '다운로드가 시작되기 전에 실행됩니다.',
hookTaskSuccess: '태스크 성공',
hookTaskSuccessHelper: '다운로드 성공 후, 클라우드 업로드/삭제 전에 실행됩니다 (완료 대기).',
hookTaskFail: '태스크 실패',
hookTaskFailHelper: '태스크가 실패할 때 실행됩니다.',
hookTaskCancel: '태스크 취소됨',
hookTaskCancelHelper: '태스크가 수동으로 취소될 때 실행됩니다.',
found: '찾음',
notFound: '설정되지 않음',
deleteHook: '훅 스크립트 삭제',
confirmDeleteHook: '이 훅 스크립트를 삭제하시겠습니까?',
uploadHook: '업로드 .sh',
taskHooks: "태스크 훅",
taskHooksDescription:
"태스크 수명 주기의 특정 지점에서 사용자 지정 셸 명령을 실행니다. 사용 가능한 환경 변수: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.",
taskHooksWarning:
"경고: 명령은 서버 권한으로 실행됩니다. 주의해서 사용하십시오.",
enterPasswordToUploadHook:
"이 훅 스크립트를 업로드하려면 비밀번호를 입력하십시오.",
riskCommandDetected: "위험한 명령 감지됨: {command}. 업로드 거부됨.",
hookTaskBeforeStart: "태스크 시작 전",
hookTaskBeforeStartHelper: "다운로드가 시작되기 전에 실행됩니다.",
hookTaskSuccess: "태스크 성공",
hookTaskSuccessHelper:
"다운로드 성공 후, 클라우드 업로드/삭제 전에 실행됩니다 (완료 대기).",
hookTaskFail: "태스크 실패",
hookTaskFailHelper: "태스크가 실패할 때 실행됩니다.",
hookTaskCancel: "태스크 취소됨",
hookTaskCancelHelper: "태스크가 수동으로 취소될 때 실행됩니다.",
found: "찾음",
notFound: "설정되지 않음",
deleteHook: "훅 스크립트 삭제",
confirmDeleteHook: "이 훅 스크립트를 삭제하시겠습니까?",
uploadHook: "업로드 .sh",
disclaimerTitle: "면책 조항",
disclaimerText: "1. 목적 및 제한\n이 소프트웨어(코드 및 문서 포함)는 개인적인 학습, 연구 및 기술 교류만을 목적으로 합니다. 이 소프트웨어를 상업적 목적으로 사용하거나 현지 법률 및 규정을 위반하는 불법 활동에 사용하는 것은 엄격히 금지됩니다.\n\n2. 책임\n개발자는 사용자가 이 소프트웨어를 어떻게 사용하는지 알지 못하며 통제할 수 없습니다. 이 소프트웨어의 불법적 또는 부적절한 사용(저작권 침해를 포함하되 이에 국한되지 않음)으로 인해 발생하는 모든 법적 책임, 분쟁 또는 손해는 전적으로 사용자가 부담해야 합니다. 개발자는 어떠한 직접적, 간접적 또는 공동 책임도 지지 않습니다.\n\n3. 수정 및 배포\n이 프로젝트는 오픈 소스입니다. 이 코드를 수정하거나 포크하는 개인이나 조직은 오픈 소스 라이선스를 준수해야 합니다. 중요: 제3자가 원래의 사용자 인증/보안 메커니즘을 우회하거나 제거하기 위해 코드를 수정하고 이러한 버전을 배포하는 경우, 수정자/배포자는 모든 결과에 대해 전적인 책임을 집니다. 보안 검증 메커니즘을 우회하거나 변조하는 것을 강력히 권장하지 않습니다.\n\n4. 비영리 성명\n이것은 완전히 무료인 오픈 소스 프로젝트입니다. 개발자는 기부를 받지 않으며 기부 페이지를 게시한 적이 없습니다. 소프트웨어 자체는 요금을 부과하지 않으며 유료 서비스를 제공하지 않습니다. 이 프로젝트를 대신하여 수수료를 징수한다고 주장하는 사기나 오해의 소지가 있는 정보에 주의하시기 바랍니다.",
disclaimerText:
"1. 목적 및 제한\n이 소프트웨어(코드 및 문서 포함)는 개인적인 학습, 연구 및 기술 교류만을 목적으로 합니다. 이 소프트웨어를 상업적 목적으로 사용하거나 현지 법률 및 규정을 위반하는 불법 활동에 사용하는 것은 엄격히 금지됩니다.\n\n2. 책임\n개발자는 사용자가 이 소프트웨어를 어떻게 사용하는지 알지 못하며 통제할 수 없습니다. 이 소프트웨어의 불법적 또는 부적절한 사용(저작권 침해를 포함하되 이에 국한되지 않음)으로 인해 발생하는 모든 법적 책임, 분쟁 또는 손해는 전적으로 사용자가 부담해야 합니다. 개발자는 어떠한 직접적, 간접적 또는 공동 책임도 지지 않습니다.\n\n3. 수정 및 배포\n이 프로젝트는 오픈 소스입니다. 이 코드를 수정하거나 포크하는 개인이나 조직은 오픈 소스 라이선스를 준수해야 합니다. 중요: 제3자가 원래의 사용자 인증/보안 메커니즘을 우회하거나 제거하기 위해 코드를 수정하고 이러한 버전을 배포하는 경우, 수정자/배포자는 모든 결과에 대해 전적인 책임을 집니다. 보안 검증 메커니즘을 우회하거나 변조하는 것을 강력히 권장하지 않습니다.\n\n4. 비영리 성명\n이것은 완전히 무료인 오픈 소스 프로젝트입니다. 개발자는 기부를 받지 않으며 기부 페이지를 게시한 적이 없습니다. 소프트웨어 자체는 요금을 부과하지 않으며 유료 서비스를 제공하지 않습니다. 이 프로젝트를 대신하여 수수료를 징수한다고 주장하는 사기나 오해의 소지가 있는 정보에 주의하시기 바랍니다.",
};

View File

@@ -134,8 +134,10 @@ export const pt = {
showYoutubeSearch: "Mostrar resultados de pesquisa do YouTube",
visitorMode: "Modo Visitante (Somente leitura)",
visitorModeReadOnly: "Modo visitante: Somente leitura",
visitorModeDescription: "Modo somente leitura. Vídeos ocultos não serão visíveis para visitantes.",
visitorModePasswordPrompt: "Por favor, digite a senha do site para alterar as configurações do modo visitante.",
visitorModeDescription:
"Modo somente leitura. Vídeos ocultos não serão visíveis para visitantes.",
visitorModePasswordPrompt:
"Por favor, digite a senha do site para alterar as configurações do modo visitante.",
cleanupTempFilesSuccess:
"{count} arquivo(s) temporário(s) excluído(s) com sucesso.",
cleanupTempFilesFailed: "Falha ao limpar arquivos temporários",
@@ -165,11 +167,13 @@ export const pt = {
apiUrlHelper: "ex. https://your-alist-instance.com/api/fs/put",
token: "Token",
publicUrl: "URL Público",
publicUrlHelper: "Domínio público para acessar arquivos (ex. https://your-cloudflare-tunnel-domain.com). Se definido, será usado em vez da URL da API para acessar arquivos.",
publicUrlHelper:
"Domínio público para acessar arquivos (ex. https://your-cloudflare-tunnel-domain.com). Se definido, será usado em vez da URL da API para acessar arquivos.",
uploadPath: "Caminho de upload",
cloudDrivePathHelper: "Caminho do diretório na nuvem, ex. /mytube-uploads",
scanPaths: "Caminhos de Varredura",
scanPathsHelper: "Um caminho por linha. Os vídeos serão verificados a partir desses caminhos. Se vazio, usará o caminho de upload. Exemplo:\n/a/Filmes\n/b/Documentários",
scanPathsHelper:
"Um caminho por linha. Os vídeos serão verificados a partir desses caminhos. Se vazio, usará o caminho de upload. Exemplo:\n/a/Filmes\n/b/Documentários",
cloudDriveNote:
"Após habilitar este recurso, os vídeos recém-baixados serão automaticamente enviados para o armazenamento em nuvem e os arquivos locais serão excluídos. Os vídeos serão reproduzidos do armazenamento em nuvem via proxy.",
cloudScanAdded: "Adicionado da nuvem",
@@ -177,26 +181,33 @@ export const pt = {
testConnection: "Testar Conexão",
sync: "Sincronizar",
syncToCloud: "Sincronização Bidirecional",
syncWarning: "Esta operação fará upload de vídeos locais para a nuvem e verificará se há novos arquivos no armazenamento em nuvem. Os arquivos locais serão excluídos após o upload.",
syncWarning:
"Esta operação fará upload de vídeos locais para a nuvem e verificará se há novos arquivos no armazenamento em nuvem. Os arquivos locais serão excluídos após o upload.",
syncing: "Sincronizando...",
syncCompleted: "Sincronização Concluída",
syncFailed: "Falha na Sincronização",
syncReport: "Total: {total} | Enviados: {uploaded} | Falhos: {failed}",
syncErrors: "Erros:",
fillApiUrlToken: "Por favor, preencha a URL da API e o Token primeiro",
connectionTestSuccess: "Teste de conexão bem-sucedido! As configurações são válidas.",
connectionFailedStatus: "Falha na conexão: O servidor retornou o status {status}",
connectionFailedUrl: "Não é possível conectar ao servidor. Verifique a URL da API.",
connectionTestSuccess:
"Teste de conexão bem-sucedido! As configurações são válidas.",
connectionFailedStatus:
"Falha na conexão: O servidor retornou o status {status}",
connectionFailedUrl:
"Não é possível conectar ao servidor. Verifique a URL da API.",
authFailed: "Falha na autenticação. Verifique seu token.",
connectionTestFailed: "Falha no teste de conexão: {error}",
syncFailedMessage: "Falha na sincronização. Tente novamente.",
foundVideosToSync: "Encontrados {count} vídeos com arquivos locais para sincronizar",
foundVideosToSync:
"Encontrados {count} vídeos com arquivos locais para sincronizar",
uploadingVideo: "Enviando: {title}",
clearThumbnailCache: "Limpar Cache Local de Miniaturas",
clearing: "Limpando...",
clearThumbnailCacheSuccess: "Cache de miniaturas limpo com sucesso. As miniaturas serão regeneradas na próxima vez que forem acessadas.",
clearThumbnailCacheSuccess:
"Cache de miniaturas limpo com sucesso. As miniaturas serão regeneradas na próxima vez que forem acessadas.",
clearThumbnailCacheError: "Falha ao limpar cache de miniaturas",
clearThumbnailCacheConfirmMessage: "Isso limpará todas as miniaturas armazenadas localmente para vídeos na nuvem. As miniaturas serão regeneradas do armazenamento em nuvem na próxima vez que forem acessadas. Continuar?",
clearThumbnailCacheConfirmMessage:
"Isso limpará todas as miniaturas armazenadas localmente para vídeos na nuvem. As miniaturas serão regeneradas do armazenamento em nuvem na próxima vez que forem acessadas. Continuar?",
// Manage
manageContent: "Gerenciar Conteúdo",
@@ -294,7 +305,8 @@ export const pt = {
openInExternalPlayer: "Abrir no player externo",
playWith: "Reproduzir com...",
deleteAllFilteredVideos: "Excluir todos os vídeos filtrados",
confirmDeleteFilteredVideos: "Tem certeza de que deseja excluir {count} vídeos filtrados pelas tags selecionadas?",
confirmDeleteFilteredVideos:
"Tem certeza de que deseja excluir {count} vídeos filtrados pelas tags selecionadas?",
deleteFilteredVideosSuccess: "{count} vídeos excluídos com sucesso.",
deletingVideos: "Excluindo vídeos...",
@@ -318,8 +330,27 @@ export const pt = {
"A senha foi redefinida. Verifique os logs do backend para a nova senha.",
waitTimeMessage: "Por favor, aguarde {time} antes de tentar novamente.",
tooManyAttempts: "Muitas tentativas falharam.",
// Passkeys
createPasskey: "Criar chave de acesso",
creatingPasskey: "Criando...",
passkeyCreated: "Chave de acesso criada com sucesso",
passkeyCreationFailed:
"Falha ao criar chave de acesso. Por favor, tente novamente.",
removePasskeys: "Remover todas as chaves de acesso",
removePasskeysTitle: "Remover todas as chaves de acesso",
removePasskeysMessage:
"Tem certeza de que deseja remover todas as chaves de acesso? Esta ação não pode ser desfeita.",
passkeysRemoved: "Todas as chaves de acesso foram removidas",
passkeysRemoveFailed:
"Falha ao remover chaves de acesso. Por favor, tente novamente.",
loginWithPasskey: "Entrar com chave de acesso",
authenticating: "Autenticando...",
passkeyLoginFailed:
"Falha na autenticação com chave de acesso. Por favor, tente novamente.",
linkCopied: "Link copiado para a área de transferência",
copyFailed: "Falha ao copiar link",
passkeyRequiresHttps: "WebAuthn requer HTTPS ou localhost. Por favor, acesse o aplicativo via HTTPS ou use localhost em vez de um endereço IP.",
passkeyWebAuthnNotSupported: "WebAuthn não é suportado neste navegador. Por favor, use um navegador moderno que suporte WebAuthn.",
// Collection Page
loadingCollection: "Carregando coleção...",
@@ -333,7 +364,8 @@ export const pt = {
unknownAuthor: "Desconhecido",
noVideosForAuthor: "Nenhum vídeo encontrado para este autor.",
deleteAuthor: "Excluir Autor",
deleteAuthorConfirmation: "Tem certeza de que deseja excluir o autor {author}? Isso excluirá todos os vídeos associados a este autor.",
deleteAuthorConfirmation:
"Tem certeza de que deseja excluir o autor {author}? Isso excluirá todos os vídeos associados a este autor.",
authorDeletedSuccessfully: "Autor excluído com sucesso",
failedToDeleteAuthor: "Falha ao excluir autor",
@@ -385,7 +417,8 @@ export const pt = {
authorOrPlaylist: "Autor / Lista de reprodução",
playlistDetected: "Lista de reprodução detectada",
playlistHasVideos: "Esta lista de reprodução tem {count} vídeos.",
downloadPlaylistAndCreateCollection: "Baixar vídeos da lista de reprodução e criar uma coleção para ela?",
downloadPlaylistAndCreateCollection:
"Baixar vídeos da lista de reprodução e criar uma coleção para ela?",
collectionHasVideos: "Esta coleção Bilibili tem {count} vídeos.",
previouslyDeletedVideo: "Vídeo Anteriormente Excluído",
previouslyDeleted: "Anteriormente excluído",
@@ -463,14 +496,17 @@ export const pt = {
taskStatusCancelled: "Cancelado",
downloaded: "Baixado",
cancelTask: "Cancelar tarefa",
confirmCancelTask: "Tem certeza de que deseja cancelar a tarefa de download para {author}?",
confirmCancelTask:
"Tem certeza de que deseja cancelar a tarefa de download para {author}?",
taskCancelled: "Tarefa cancelada com sucesso",
deleteTask: "Excluir tarefa",
confirmDeleteTask: "Tem certeza de que deseja excluir o registro da tarefa para {author}? Esta ação não pode ser desfeita.",
confirmDeleteTask:
"Tem certeza de que deseja excluir o registro da tarefa para {author}? Esta ação não pode ser desfeita.",
taskDeleted: "Tarefa excluída com sucesso",
clearFinishedTasks: "Limpar tarefas concluídas",
tasksCleared: "Tarefas concluídas limpas com sucesso",
confirmClearFinishedTasks: "Tem certeza de que deseja limpar todas as tarefas concluídas (concluídas, canceladas)? Isso as removerá da lista, mas não excluirá nenhum arquivo baixado.",
confirmClearFinishedTasks:
"Tem certeza de que deseja limpar todas as tarefas concluídas (concluídas, canceladas)? Isso as removerá da lista, mas não excluirá nenhum arquivo baixado.",
clear: "Limpar",
// Instruction Page
instructionSection1Title: "1. Download e Gerenciamento de Tarefas",
@@ -611,7 +647,8 @@ export const pt = {
cloudflaredTunnel: "Túnel Cloudflare",
enableCloudflaredTunnel: "Habilitar Túnel Cloudflare",
cloudflaredToken: "Token do Túnel (Opcional)",
cloudflaredTokenHelper: "Cole o token do túnel aqui, ou deixe em branco para usar um Túnel Rápido aleatório.",
cloudflaredTokenHelper:
"Cole o token do túnel aqui, ou deixe em branco para usar um Túnel Rápido aleatório.",
waitingForUrl: "Aguardando URL do Túnel Rápido...",
running: "Executando",
stopped: "Parado",
@@ -619,33 +656,40 @@ export const pt = {
accountTag: "Tag da Conta",
copied: "Copiado!",
clickToCopy: "Clique para copiar",
quickTunnelWarning: "URLs de Túnel Rápido mudam toda vez que o túnel é reiniciado.",
managedInDashboard: "O nome do host público é gerenciado no painel Cloudflare Zero Trust.",
quickTunnelWarning:
"URLs de Túnel Rápido mudam toda vez que o túnel é reiniciado.",
managedInDashboard:
"O nome do host público é gerenciado no painel Cloudflare Zero Trust.",
failedToDownloadVideo: "Falha ao baixar o vídeo. Por favor, tente novamente.",
failedToDownload: "Falha ao baixar. Por favor, tente novamente.",
playlistDownloadStarted: "Download da playlist iniciado",
copyUrl: "Copiar URL",
new: "NOVO",
// Task Hooks
taskHooks: 'Ganchos de Tarefa',
taskHooksDescription: 'Execute comandos shell personalizados em pontos específicos do ciclo de vida da tarefa. Variáveis de ambiente disponíveis: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Aviso: Os comandos são executados com as permissões do servidor. Use com cautela.',
enterPasswordToUploadHook: 'Por favor, digite sua senha para fazer upload deste script Hook.',
riskCommandDetected: 'Comando de risco detectado: {command}. Upload rejeitado.',
hookTaskBeforeStart: 'Antes do Início da Tarefa',
hookTaskBeforeStartHelper: 'Executa antes do download começar.',
hookTaskSuccess: 'Tarefa com Sucesso',
hookTaskSuccessHelper: 'Executa após download bem-sucedido, antes do upload/exclusão na nuvem (aguarda conclusão).',
hookTaskFail: 'Falha na Tarefa',
hookTaskFailHelper: 'Executa quando uma tarefa falha.',
hookTaskCancel: 'Tarefa Cancelada',
hookTaskCancelHelper: 'Executa quando uma tarefa é cancelada manualmente.',
found: 'Encontrado',
notFound: 'Não Definido',
deleteHook: 'Excluir Script de Gancho',
confirmDeleteHook: 'Tem certeza que deseja excluir este script de gancho?',
uploadHook: 'Enviar .sh',
taskHooks: "Ganchos de Tarefa",
taskHooksDescription:
"Execute comandos shell personalizados em pontos específicos do ciclo de vida da tarefa. Variáveis de ambiente disponíveis: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.",
taskHooksWarning:
"Aviso: Os comandos são executados com as permissões do servidor. Use com cautela.",
enterPasswordToUploadHook:
"Por favor, digite sua senha para fazer upload deste script Hook.",
riskCommandDetected:
"Comando de risco detectado: {command}. Upload rejeitado.",
hookTaskBeforeStart: "Antes do Início da Tarefa",
hookTaskBeforeStartHelper: "Executa antes do download começar.",
hookTaskSuccess: "Tarefa com Sucesso",
hookTaskSuccessHelper:
"Executa após download bem-sucedido, antes do upload/exclusão na nuvem (aguarda conclusão).",
hookTaskFail: "Falha na Tarefa",
hookTaskFailHelper: "Executa quando uma tarefa falha.",
hookTaskCancel: "Tarefa Cancelada",
hookTaskCancelHelper: "Executa quando uma tarefa é cancelada manualmente.",
found: "Encontrado",
notFound: "Não Definido",
deleteHook: "Excluir Script de Gancho",
confirmDeleteHook: "Tem certeza que deseja excluir este script de gancho?",
uploadHook: "Enviar .sh",
disclaimerTitle: "Isenção de responsabilidade",
disclaimerText: "1. Objetivo e Restrições\nEste software (incluindo código e documentação) destina-se exclusivamente a aprendizagem pessoal, pesquisa e intercâmbio técnico. É estritamente proibido usar este software para fins comerciais ou para quaisquer atividades ilegais que violem as leis e regulamentos locais.\n\n2. Responsabilidade\nO desenvolvedor desconhece e não tem controle sobre como os usuários utilizam este software. Quaisquer responsabilidades legais, disputas ou danos decorrentes do uso ilegal ou impróprio deste software (incluindo, mas não se limitando a violação de direitos autorais) serão de responsabilidade exclusiva do usuário. O desenvolvedor não assume nenhuma responsabilidade direta, indireta ou conjunta.\n\n3. Modificações e Distribuição\nEste projeto é de código aberto. Qualquer indivíduo ou organização que modifique ou faça fork deste código deve cumprir a licença de código aberto. Importante: Se um terceiro modificar o código para contornar ou remover os mecanismos originais de autenticação/segurança do usuário e distribuir tais versões, o modificador/distribuidor assume total responsabilidade por quaisquer consequências. Desaconselhamos fortemente contornar ou adulterar quaisquer mecanismos de verificação de segurança.\n\n4. Declaração Sem Fins Lucrativos\nEste é um projeto de código aberto totalmente gratuito. O desenvolvedor não aceita doações e nunca publicou páginas de doação. O software em si não permite cobranças e não oferece serviços pagos. Por favor, esteja vigilante e cuidado com quaisquer golpes ou informações enganosas que aleguem cobrar taxas em nome deste projeto.",
disclaimerText:
"1. Objetivo e Restrições\nEste software (incluindo código e documentação) destina-se exclusivamente a aprendizagem pessoal, pesquisa e intercâmbio técnico. É estritamente proibido usar este software para fins comerciais ou para quaisquer atividades ilegais que violem as leis e regulamentos locais.\n\n2. Responsabilidade\nO desenvolvedor desconhece e não tem controle sobre como os usuários utilizam este software. Quaisquer responsabilidades legais, disputas ou danos decorrentes do uso ilegal ou impróprio deste software (incluindo, mas não se limitando a violação de direitos autorais) serão de responsabilidade exclusiva do usuário. O desenvolvedor não assume nenhuma responsabilidade direta, indireta ou conjunta.\n\n3. Modificações e Distribuição\nEste projeto é de código aberto. Qualquer indivíduo ou organização que modifique ou faça fork deste código deve cumprir a licença de código aberto. Importante: Se um terceiro modificar o código para contornar ou remover os mecanismos originais de autenticação/segurança do usuário e distribuir tais versões, o modificador/distribuidor assume total responsabilidade por quaisquer consequências. Desaconselhamos fortemente contornar ou adulterar quaisquer mecanismos de verificação de segurança.\n\n4. Declaração Sem Fins Lucrativos\nEste é um projeto de código aberto totalmente gratuito. O desenvolvedor não aceita doações e nunca publicou páginas de doação. O software em si não permite cobranças e não oferece serviços pagos. Por favor, esteja vigilante e cuidado com quaisquer golpes ou informações enganosas que aleguem cobrar taxas em nome deste projeto.",
};

View File

@@ -143,8 +143,10 @@ export const ru = {
showYoutubeSearch: "Показать результаты поиска YouTube",
visitorMode: "Режим посетителя (Только чтение)",
visitorModeReadOnly: "Режим посетителя: Только чтение",
visitorModeDescription: "Режим только чтения. Скрытые видео не будут видны посетителям.",
visitorModePasswordPrompt: "Пожалуйста, введите пароль веб-сайта для изменения настроек режима посетителя.",
visitorModeDescription:
"Режим только чтения. Скрытые видео не будут видны посетителям.",
visitorModePasswordPrompt:
"Пожалуйста, введите пароль веб-сайта для изменения настроек режима посетителя.",
cleanupTempFilesSuccess: "Успешно удалено {count} временных файлов.",
cleanupTempFilesFailed: "Не удалось очистить временные файлы",
@@ -173,11 +175,13 @@ export const ru = {
apiUrlHelper: "напр. https://your-alist-instance.com/api/fs/put",
token: "Токен",
publicUrl: "Публичный URL",
publicUrlHelper: "Публичный домен для доступа к файлам (напр. https://your-cloudflare-tunnel-domain.com). Если установлен, будет использоваться вместо URL API для доступа к файлам.",
publicUrlHelper:
"Публичный домен для доступа к файлам (напр. https://your-cloudflare-tunnel-domain.com). Если установлен, будет использоваться вместо URL API для доступа к файлам.",
uploadPath: "Путь загрузки",
cloudDrivePathHelper: "Путь к каталогу в облаке, напр. /mytube-uploads",
scanPaths: "Пути сканирования",
scanPathsHelper: "Один путь в строке. Видео будут сканироваться из этих путей. Если пусто, будет использоваться путь загрузки. Пример:\n/a/Фильмы\n/b/Документальные",
scanPathsHelper:
"Один путь в строке. Видео будут сканироваться из этих путей. Если пусто, будет использоваться путь загрузки. Пример:\n/a/Фильмы\n/b/Документальные",
cloudDriveNote:
"После включения этой функции недавно загруженные видео будут автоматически загружены в облачное хранилище, а локальные файлы будут удалены. Видео будут воспроизводиться из облачного хранилища через прокси.",
cloudScanAdded: "Добавлено из облака",
@@ -185,7 +189,8 @@ export const ru = {
testConnection: "Тестировать соединение",
sync: "Синхронизировать",
syncToCloud: "Двусторонняя синхронизация",
syncWarning: "Эта операция загрузит локальные видео в облако и просканирует облачное хранилище на наличие новых файлов. Локальные файлы будут удалены после загрузки.",
syncWarning:
"Эта операция загрузит локальные видео в облако и просканирует облачное хранилище на наличие новых файлов. Локальные файлы будут удалены после загрузки.",
syncing: "Синхронизация...",
syncCompleted: "Синхронизация завершена",
syncFailed: "Ошибка синхронизации",
@@ -194,17 +199,21 @@ export const ru = {
fillApiUrlToken: "Пожалуйста, сначала заполните URL API и токен",
connectionTestSuccess: "Тест соединения прошел успешно! Настройки верны.",
connectionFailedStatus: "Ошибка соединения: Сервер вернул статус {status}",
connectionFailedUrl: "Невозможно подключиться к серверу. Пожалуйста, проверьте URL API.",
connectionFailedUrl:
"Невозможно подключиться к серверу. Пожалуйста, проверьте URL API.",
authFailed: "Ошибка аутентификации. Пожалуйста, проверьте ваш токен.",
connectionTestFailed: "Тест соединения не удался: {error}",
syncFailedMessage: "Ошибка синхронизации. Пожалуйста, попробуйте снова.",
foundVideosToSync: "Найдено {count} видео с локальными файлами для синхронизации",
foundVideosToSync:
"Найдено {count} видео с локальными файлами для синхронизации",
uploadingVideo: "Загрузка: {title}",
clearThumbnailCache: "Очистить локальный кэш миниатюр",
clearing: "Очистка...",
clearThumbnailCacheSuccess: "Кэш миниатюр успешно очищен. Миниатюры будут сгенерированы заново при следующем доступе.",
clearThumbnailCacheSuccess:
"Кэш миниатюр успешно очищен. Миниатюры будут сгенерированы заново при следующем доступе.",
clearThumbnailCacheError: "Не удалось очистить кэш миниатюр",
clearThumbnailCacheConfirmMessage: "Это удалит все локально кэшированные миниатюры для облачных видео. Миниатюры будут сгенерированы заново из облачного хранилища при следующем доступе. Продолжить?",
clearThumbnailCacheConfirmMessage:
"Это удалит все локально кэшированные миниатюры для облачных видео. Миниатюры будут сгенерированы заново из облачного хранилища при следующем доступе. Продолжить?",
// Manage
manageContent: "Управление контентом",
@@ -296,7 +305,8 @@ export const ru = {
openInExternalPlayer: "Открыть во внешнем плеере",
playWith: "Воспроизвести с помощью...",
deleteAllFilteredVideos: "Удалить все отфильтрованные видео",
confirmDeleteFilteredVideos: "Вы уверены, что хотите удалить {count} видео, отфильтрованных по выбранным тегам?",
confirmDeleteFilteredVideos:
"Вы уверены, что хотите удалить {count} видео, отфильтрованных по выбранным тегам?",
deleteFilteredVideosSuccess: "Успешно удалено {count} видео.",
deletingVideos: "Удаление видео...",
@@ -320,8 +330,27 @@ export const ru = {
"Пароль был сброшен. Проверьте логи бэкенда для нового пароля.",
waitTimeMessage: "Пожалуйста, подождите {time} перед повторной попыткой.",
tooManyAttempts: "Слишком много неудачных попыток.",
// Passkeys
createPasskey: "Создать ключ доступа",
creatingPasskey: "Создание...",
passkeyCreated: "Ключ доступа успешно создан",
passkeyCreationFailed:
"Не удалось создать ключ доступа. Пожалуйста, попробуйте снова.",
removePasskeys: "Удалить все ключи доступа",
removePasskeysTitle: "Удалить все ключи доступа",
removePasskeysMessage:
"Вы уверены, что хотите удалить все ключи доступа? Это действие нельзя отменить.",
passkeysRemoved: "Все ключи доступа удалены",
passkeysRemoveFailed:
"Не удалось удалить ключи доступа. Пожалуйста, попробуйте снова.",
loginWithPasskey: "Войти с помощью ключа доступа",
authenticating: "Аутентификация...",
passkeyLoginFailed:
"Ошибка аутентификации с помощью ключа доступа. Пожалуйста, попробуйте снова.",
linkCopied: "Ссылка скопирована в буфер обмена",
copyFailed: "Не удалось скопировать ссылку",
passkeyRequiresHttps: "WebAuthn требует HTTPS или localhost. Пожалуйста, войдите в приложение через HTTPS или используйте localhost вместо IP-адреса.",
passkeyWebAuthnNotSupported: "WebAuthn не поддерживается в этом браузере. Пожалуйста, используйте современный браузер с поддержкой WebAuthn.",
// Collection Page
loadingCollection: "Загрузка коллекции...",
@@ -343,7 +372,8 @@ export const ru = {
unknownAuthor: "Неизвестно",
noVideosForAuthor: "Видео этого автора не найдены.",
deleteAuthor: "Удалить автора",
deleteAuthorConfirmation: "Вы уверены, что хотите удалить автора {author}? Это удалит все видео, связанные с этим автором.",
deleteAuthorConfirmation:
"Вы уверены, что хотите удалить автора {author}? Это удалит все видео, связанные с этим автором.",
authorDeletedSuccessfully: "Автор успешно удален",
failedToDeleteAuthor: "Не удалось удалить автора",
@@ -395,7 +425,8 @@ export const ru = {
authorOrPlaylist: "Автор / Плейлист",
playlistDetected: "Обнаружен плейлист",
playlistHasVideos: "В этом плейлисте {count} видео.",
downloadPlaylistAndCreateCollection: "Скачать видео из плейлиста и создать для него коллекцию?",
downloadPlaylistAndCreateCollection:
"Скачать видео из плейлиста и создать для него коллекцию?",
collectionHasVideos: "В этой коллекции Bilibili {count} видео.",
seriesHasVideos: "В этой серии Bilibili {count} видео.",
videoHasParts: "В этом видео Bilibili {count} частей.",
@@ -468,14 +499,17 @@ export const ru = {
taskStatusCancelled: "Отменена",
downloaded: "Скачано",
cancelTask: "Отменить задачу",
confirmCancelTask: "Вы уверены, что хотите отменить задачу загрузки для {author}?",
confirmCancelTask:
"Вы уверены, что хотите отменить задачу загрузки для {author}?",
taskCancelled: "Задача успешно отменена",
deleteTask: "Удалить задачу",
confirmDeleteTask: "Вы уверены, что хотите удалить запись задачи для {author}? Это действие нельзя отменить.",
confirmDeleteTask:
"Вы уверены, что хотите удалить запись задачи для {author}? Это действие нельзя отменить.",
taskDeleted: "Задача успешно удалена",
clearFinishedTasks: "Очистить завершенные задачи",
tasksCleared: "Завершенные задачи успешно очищены",
confirmClearFinishedTasks: "Вы уверены, что хотите очистить все завершенные задачи (завершенные, отмененные)? Это удалит их из списка, но не удалит загруженные файлы.",
confirmClearFinishedTasks:
"Вы уверены, что хотите очистить все завершенные задачи (завершенные, отмененные)? Это удалит их из списка, но не удалит загруженные файлы.",
clear: "Очистить",
// Instruction Page
instructionSection1Title: "1. Загрузка и управление задачами",
@@ -605,7 +639,8 @@ export const ru = {
cloudflaredTunnel: "Cloudflare Tunnel",
enableCloudflaredTunnel: "Включить Cloudflare Tunnel",
cloudflaredToken: "Токен туннеля (Необязательно)",
cloudflaredTokenHelper: "Вставьте сюда токен туннеля или оставьте пустым, чтобы использовать случайный быстрый туннель.",
cloudflaredTokenHelper:
"Вставьте сюда токен туннеля или оставьте пустым, чтобы использовать случайный быстрый туннель.",
waitingForUrl: "Ожидание URL быстрого туннеля...",
running: "Запущен",
stopped: "Остановлен",
@@ -613,35 +648,43 @@ export const ru = {
accountTag: "Тег учетной записи",
copied: "Скопировано!",
clickToCopy: "Нажмите, чтобы скопировать",
quickTunnelWarning: "URL быстрых туннелей меняются при каждом перезапуске туннеля.",
managedInDashboard: "Публичное имя хоста управляется в панели управления Cloudflare Zero Trust.",
failedToDownloadVideo: "Не удалось скачать видео. Пожалуйста, попробуйте снова.",
quickTunnelWarning:
"URL быстрых туннелей меняются при каждом перезапуске туннеля.",
managedInDashboard:
"Публичное имя хоста управляется в панели управления Cloudflare Zero Trust.",
failedToDownloadVideo:
"Не удалось скачать видео. Пожалуйста, попробуйте снова.",
failedToDownload: "Не удалось скачать. Пожалуйста, попробуйте снова.",
playlistDownloadStarted: "Скачивание плейлиста началось",
fromYouTube: "С YouTube",
copyUrl: "Копировать URL",
new: "НОВЫЙ",
// Task Hooks
taskHooks: 'Хуки Задач',
taskHooksDescription: 'Выполняйте пользовательские shell-команды в определенные моменты жизненного цикла задачи. Доступные переменные окружения: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.',
taskHooksWarning: 'Предупреждение: Команды выполняются с правами сервера. Используйте с осторожностью.',
enterPasswordToUploadHook: 'Пожалуйста, введите пароль для загрузки этого Hook-скрипта.',
riskCommandDetected: 'Обнаружена опасная команда: {command}. Загрузка отклонена.',
hookTaskBeforeStart: 'Перед Началом Задачи',
hookTaskBeforeStartHelper: 'Выполняется перед началом загрузки.',
hookTaskSuccess: 'Успех Задачи',
hookTaskSuccessHelper: 'Выполняется после успешной загрузки, перед облачной загрузкой/удалением (ожидает завершения).',
hookTaskFail: 'Сбой Задачи',
hookTaskFailHelper: 'Выполняется при сбое задачи.',
hookTaskCancel: 'Задача Отменена',
hookTaskCancelHelper: 'Выполняется при ручной отмене задачи.',
found: 'Найдено',
notFound: 'Не Задано',
deleteHook: 'Удалить Скрипт Хука',
confirmDeleteHook: 'Вы уверены, что хотите удалить этот скрипт хука?',
uploadHook: 'Загрузить .sh',
taskHooks: "Хуки Задач",
taskHooksDescription:
"Выполняйте пользовательские shell-команды в определенные моменты жизненного цикла задачи. Доступные переменные окружения: MYTUBE_TASK_ID, MYTUBE_TASK_TITLE, MYTUBE_SOURCE_URL, MYTUBE_VIDEO_PATH.",
taskHooksWarning:
"Предупреждение: Команды выполняются с правами сервера. Используйте с осторожностью.",
enterPasswordToUploadHook:
"Пожалуйста, введите пароль для загрузки этого Hook-скрипта.",
riskCommandDetected:
"Обнаружена опасная команда: {command}. Загрузка отклонена.",
hookTaskBeforeStart: "Перед Началом Задачи",
hookTaskBeforeStartHelper: "Выполняется перед началом загрузки.",
hookTaskSuccess: "Успех Задачи",
hookTaskSuccessHelper:
"Выполняется после успешной загрузки, перед облачной загрузкой/удалением (ожидает завершения).",
hookTaskFail: "Сбой Задачи",
hookTaskFailHelper: "Выполняется при сбое задачи.",
hookTaskCancel: "Задача Отменена",
hookTaskCancelHelper: "Выполняется при ручной отмене задачи.",
found: "Найдено",
notFound: "Не Задано",
deleteHook: "Удалить Скрипт Хука",
confirmDeleteHook: "Вы уверены, что хотите удалить этот скрипт хука?",
uploadHook: "Загрузить .sh",
disclaimerTitle: "Отказ от ответственности",
disclaimerText: "1. Цель и Ограничения\nЭто программное обеспечение (включая код и документацию) предназначено исключительно для личного обучения, исследований и технического обмена. Строго запрещено использовать это программное обеспечение в коммерческих целях или для любой незаконной деятельности, нарушающей местные законы и правила.\n\n2. Ответственность\nРазработчик не знает и не контролирует, как пользователи используют это программное обеспечение. Любая юридическая ответственность, споры или ущерб, возникающие в результате незаконного или ненадлежащего использования этого программного обеспечения (включая, помимо прочего, нарушение авторских прав), возлагаются исключительно на пользователя. Разработчик не несет никакой прямой, косвенной или солидарной ответственности.\n\n3. Модификации и Распространение\nЭтот проект с открытым исходным кодом. Любое физическое лицо или организация, изменяющая или создающая форк этого кода, должна соблюдать лицензию с открытым исходным кодом. Важно: Если третья сторона изменяет код для обхода или удаления оригинальных механизмов аутентификации/безопасности пользователей и распространяет такие версии, модификатор/распространитель несет полную ответственность за любые последствия. Мы настоятельно не рекомендуем обходить или вмешиваться в любые механизмы проверки безопасности.\n\n4. Некоммерческое Заявление\nЭто полностью бесплатный проект с открытым исходным кодом. Разработчик не принимает пожертвования и никогда не публиковал страницы для пожертвований. Сама программа не предусматривает взимания платы и не предлагает платных услуг. Пожалуйста, будьте бдительны и остерегайтесь мошенничества или вводящей в заблуждение информации, утверждающей о сборе средств от имени этого проекта.",
disclaimerText:
"1. Цель и Ограничения\nЭто программное обеспечение (включая код и документацию) предназначено исключительно для личного обучения, исследований и технического обмена. Строго запрещено использовать это программное обеспечение в коммерческих целях или для любой незаконной деятельности, нарушающей местные законы и правила.\n\n2. Ответственность\nРазработчик не знает и не контролирует, как пользователи используют это программное обеспечение. Любая юридическая ответственность, споры или ущерб, возникающие в результате незаконного или ненадлежащего использования этого программного обеспечения (включая, помимо прочего, нарушение авторских прав), возлагаются исключительно на пользователя. Разработчик не несет никакой прямой, косвенной или солидарной ответственности.\n\n3. Модификации и Распространение\nЭтот проект с открытым исходным кодом. Любое физическое лицо или организация, изменяющая или создающая форк этого кода, должна соблюдать лицензию с открытым исходным кодом. Важно: Если третья сторона изменяет код для обхода или удаления оригинальных механизмов аутентификации/безопасности пользователей и распространяет такие версии, модификатор/распространитель несет полную ответственность за любые последствия. Мы настоятельно не рекомендуем обходить или вмешиваться в любые механизмы проверки безопасности.\n\n4. Некоммерческое Заявление\nЭто полностью бесплатный проект с открытым исходным кодом. Разработчик не принимает пожертвования и никогда не публиковал страницы для пожертвований. Сама программа не предусматривает взимания платы и не предлагает платных услуг. Пожалуйста, будьте бдительны и остерегайтесь мошенничества или вводящей в заблуждение информации, утверждающей о сборе средств от имени этого проекта.",
};

View File

@@ -302,6 +302,21 @@ export const zh = {
resetPasswordSuccess: "密码已重置。请查看后端日志以获取新密码。",
waitTimeMessage: "请等待 {time} 后再试。",
tooManyAttempts: "失败尝试次数过多。",
// Passkeys
createPasskey: "创建通行密钥",
creatingPasskey: "创建中...",
passkeyCreated: "通行密钥创建成功",
passkeyCreationFailed: "创建通行密钥失败,请重试。",
passkeyWebAuthnNotSupported: "此浏览器不支持 WebAuthn。请使用支持 WebAuthn 的现代浏览器。",
passkeyRequiresHttps: "WebAuthn 需要 HTTPS 或 localhost。请通过 HTTPS 访问应用程序,或使用 localhost 而不是 IP 地址。",
removePasskeys: "删除所有通行密钥",
removePasskeysTitle: "删除所有通行密钥",
removePasskeysMessage: "您确定要删除所有通行密钥吗?此操作无法撤销。",
passkeysRemoved: "所有通行密钥已删除",
passkeysRemoveFailed: "删除通行密钥失败,请重试。",
loginWithPasskey: "使用通行密钥登录",
authenticating: "验证中...",
passkeyLoginFailed: "通行密钥验证失败,请重试。",
linkCopied: "链接已复制到剪贴板",
copyFailed: "复制链接失败",