file upload update
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
||||||
import TestSection from '@/app/ui/marking-page/test-section';
|
|
||||||
import FilesTable from '@/app/ui/dashboard/files-table';
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
import {useTranslations} from 'next-intl';
|
import PageTitle from '@/app/ui/page-title';
|
||||||
|
|
||||||
|
const ALLOWED_EXTENSIONS: string[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
|
||||||
|
const FILE_TYPE = "VIDEO"
|
||||||
|
const MAX_SIZE = 1048576000;
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const t = useTranslations('Global');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="page-title">
|
<PageTitle title="audio-protection" />
|
||||||
<h1>{t('audio-protection')}</h1>
|
|
||||||
</div>
|
|
||||||
<ProtectionSummary />
|
<ProtectionSummary />
|
||||||
{/* <TestSection /> */}
|
{/* <TestSection /> */}
|
||||||
<FilesTable />
|
<FilesTable />
|
||||||
|
|||||||
@@ -2,21 +2,22 @@ import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
|||||||
import PageTitle from '@/app/ui/page-title';
|
import PageTitle from '@/app/ui/page-title';
|
||||||
import UploadSectionFile from '@/app/components/upload-section-file';
|
import UploadSectionFile from '@/app/components/upload-section-file';
|
||||||
import FilesTable from '@/app/ui/dashboard/files-table';
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
import { FileExtension } from '@/app/actions/definitions';
|
|
||||||
import { getAllowedFilesExtensions } from '@/app/actions/file-upload';
|
import { getAllowedFilesExtensions } from '@/app/actions/file-upload';
|
||||||
|
|
||||||
const ALLOWED_EXTENSIONS: FileExtension[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
|
const ALLOWED_EXTENSIONS: string[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
|
||||||
const FILE_TYPE = "IMAGE"
|
const FILE_TYPE = "IMAGE"
|
||||||
|
const MAX_SIZE = 1048576000;
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
/* const allowedFilesExtensions = await getAllowedFilesExtensions(FILE_TYPE);
|
/* const allowedFilesExtensions = await getAllowedFilesExtensions(FILE_TYPE);
|
||||||
|
console.log(allowedFilesExtensions);
|
||||||
console.log(allowedFilesExtensions); */
|
console.log(allowedFilesExtensions); */
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<PageTitle title="image-protection" />
|
<PageTitle title="image-protection" />
|
||||||
<ProtectionSummary />
|
<ProtectionSummary />
|
||||||
<UploadSectionFile fileType={FILE_TYPE} allowedExtensions={ALLOWED_EXTENSIONS} />
|
<UploadSectionFile fileType={FILE_TYPE} allowedExtensions={ALLOWED_EXTENSIONS} maxFileSize={MAX_SIZE} />
|
||||||
{/* <TestSection /> */}
|
{/* <TestSection /> */}
|
||||||
<FilesTable />
|
<FilesTable />
|
||||||
{/* <ProtectedFilesTable /> */}
|
{/* <ProtectedFilesTable /> */}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
||||||
import TestSection from '@/app/ui/marking-page/test-section';
|
|
||||||
import UploadSectionVideo from '@/app/ui/marking-page/upload-section-video';
|
import UploadSectionVideo from '@/app/ui/marking-page/upload-section-video';
|
||||||
import FilesTable from '@/app/ui/dashboard/files-table';
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
import {useTranslations} from 'next-intl';
|
import PageTitle from '@/app/ui/page-title';
|
||||||
|
import UploadSectionFile from '@/app/components/upload-section-file';
|
||||||
|
|
||||||
|
const ALLOWED_EXTENSIONS: string[] = ['mp4', 'avi', 'webm', 'mkv', 'flv', 'wmv', 'mov'];
|
||||||
|
const FILE_TYPE = "VIDEO"
|
||||||
|
const MAX_SIZE = 1048576000;
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const t = useTranslations('Global');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="page-title">
|
<PageTitle title="video-protection" />
|
||||||
<h1>{t('video-protection')}</h1>
|
|
||||||
</div>
|
|
||||||
<ProtectionSummary />
|
<ProtectionSummary />
|
||||||
<UploadSectionVideo />
|
<UploadSectionFile fileType={FILE_TYPE} allowedExtensions={ALLOWED_EXTENSIONS} maxFileSize={MAX_SIZE} />
|
||||||
{/* <TestSection /> */}
|
{/* <TestSection /> */}
|
||||||
<FilesTable />
|
<FilesTable />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,8 +78,6 @@ export const loginFormSchema = z
|
|||||||
|
|
||||||
export const localDevelopmentUrl = 'http://localhost';
|
export const localDevelopmentUrl = 'http://localhost';
|
||||||
|
|
||||||
export type FileExtension = 'jpg' | 'jpeg' | 'png' | 'gif' | 'bmp';
|
|
||||||
|
|
||||||
export const testUserData = {
|
export const testUserData = {
|
||||||
fullName: 'test',
|
fullName: 'test',
|
||||||
email: 'test@mail.com',
|
email: 'test@mail.com',
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import React, { useRef, useState, useCallback, ChangeEvent, DragEvent } from 're
|
|||||||
import { IconShieldAdd } from '@/app/ui/icons/icons';
|
import { IconShieldAdd } from '@/app/ui/icons/icons';
|
||||||
import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/file-upload';
|
import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/file-upload';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { FileExtension } from '@/app/actions/definitions';
|
|
||||||
|
|
||||||
interface SelectedFile {
|
interface SelectedFile {
|
||||||
file: File;
|
file: File;
|
||||||
@@ -27,40 +26,79 @@ interface FileUploadInitResponse {
|
|||||||
|
|
||||||
interface UploadSectionFile {
|
interface UploadSectionFile {
|
||||||
fileType: string
|
fileType: string
|
||||||
allowedExtensions: FileExtension[]
|
allowedExtensions: string[]
|
||||||
|
maxFileSize: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/* type SupportedFormat = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/bmp'; */
|
export default function UploadSectionFile({ fileType, allowedExtensions, maxFileSize }: UploadSectionFile) {
|
||||||
|
|
||||||
export default function UploadSectionFile({ fileType, allowedExtensions }: UploadSectionFile) {
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||||
const [selectedFile, setSelectedFile] = useState<SelectedFile | null>(null);
|
const [selectedFile, setSelectedFile] = useState<SelectedFile | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [uploadId, setUploadId] = useState<string | null>(null);
|
const [uploadId, setUploadId] = useState<string | null>(null);
|
||||||
const [isFileUploaded, setIsFileUploaded] = useState<boolean>(false);
|
const [isFileUploaded, setIsFileUploaded] = useState<boolean>(false);
|
||||||
|
const [uploadProgress, setUploadProgress] = useState<number>(0);
|
||||||
|
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
console.log('fileType');
|
let SUPPORTED_FORMATS: string[] = [];
|
||||||
console.log(allowedExtensions);
|
|
||||||
let SUPPORTED_FORMATS: string[] = []
|
|
||||||
|
|
||||||
if (fileType === "IMAGE") {
|
switch (fileType) {
|
||||||
|
case 'IMAGE':
|
||||||
SUPPORTED_FORMATS = [
|
SUPPORTED_FORMATS = [
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
|
'image/jpg',
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/gif',
|
'image/gif',
|
||||||
'image/bmp'
|
'image/bmp',
|
||||||
|
'image/webp',
|
||||||
|
'image/svg+xml',
|
||||||
|
'image/tiff',
|
||||||
|
'image/x-icon'
|
||||||
];
|
];
|
||||||
|
break;
|
||||||
|
case 'VIDEO':
|
||||||
|
SUPPORTED_FORMATS = [
|
||||||
|
'video/mp4',
|
||||||
|
'video/mpeg',
|
||||||
|
'video/ogg',
|
||||||
|
'video/webm',
|
||||||
|
'video/quicktime',
|
||||||
|
'video/x-msvideo',
|
||||||
|
'video/x-matroska',
|
||||||
|
'video/x-flv',
|
||||||
|
'video/3gpp',
|
||||||
|
'video/3gpp2'
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case 'AUDIO':
|
||||||
|
SUPPORTED_FORMATS = [
|
||||||
|
'audio/mpeg',
|
||||||
|
'audio/wav',
|
||||||
|
'audio/wave',
|
||||||
|
'audio/x-wav',
|
||||||
|
'audio/x-pn-wav',
|
||||||
|
'audio/ogg',
|
||||||
|
'audio/webm',
|
||||||
|
'audio/aac',
|
||||||
|
'audio/mp4',
|
||||||
|
'audio/x-m4a',
|
||||||
|
'audio/flac',
|
||||||
|
'audio/x-flac',
|
||||||
|
'audio/opus'
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALLOWED_EXTENSIONS: FileExtension[] = allowedExtensions;
|
const ALLOWED_EXTENSIONS: string[] = allowedExtensions;
|
||||||
|
|
||||||
|
|
||||||
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
|
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
|
||||||
if (!SUPPORTED_FORMATS.includes(file.type as string)) {
|
if (!SUPPORTED_FORMATS.includes(file.type as string)) {
|
||||||
const extension = file.name.split('.').pop()?.toLowerCase();
|
const extension = file.name.split('.').pop()?.toLowerCase();
|
||||||
if (!extension || !ALLOWED_EXTENSIONS.includes(extension as FileExtension)) {
|
if (!extension || !ALLOWED_EXTENSIONS.includes(extension as string)) {
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
errorMessage: 'Неподдерживаемый формат файла.'
|
errorMessage: 'Неподдерживаемый формат файла.'
|
||||||
@@ -68,11 +106,12 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_SIZE = 100 * 1024 * 1024;
|
const MAX_SIZE = maxFileSize;
|
||||||
|
|
||||||
if (file.size > MAX_SIZE) {
|
if (file.size > MAX_SIZE) {
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
errorMessage: 'Файл слишком большой. Максимальный размер: 100MB'
|
errorMessage: 'Файл слишком большой. Максимальный размер: 1000MB'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,24 +146,29 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
|
|||||||
setError(null);
|
setError(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setUploadProgress(0);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const validation = validateFile(file);
|
const validation = validateFile(file);
|
||||||
|
|
||||||
if (!validation.isValid) {
|
if (!validation.isValid) {
|
||||||
setError(validation.errorMessage || 'Неизвестная ошибка валидации');
|
setError(validation.errorMessage || 'Неизвестная ошибка валидации');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dimensions = await getImageDimensions(file);
|
let dimensions = undefined;
|
||||||
|
|
||||||
|
if (fileType === "IMAGE") {
|
||||||
|
dimensions = await getImageDimensions(file) as { width: number, height: number };
|
||||||
if (dimensions.width < 150 || dimensions.height < 150 ||
|
if (dimensions.width < 150 || dimensions.height < 150 ||
|
||||||
dimensions.width > 8000 || dimensions.height > 8000) {
|
dimensions.width > 10000 || dimensions.height > 10000) {
|
||||||
setError(`Размер изображения: ${dimensions.width}x${dimensions.height}. Допустимый диапазон: 150x150 - 4000x4000 пикселей.`);
|
setError(`Размер изображения: ${dimensions.width}x${dimensions.height}. Допустимый диапазон: 150x150 - 10000x10000 пикселей.`);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const objectUrl = URL.createObjectURL(file);
|
const objectUrl = URL.createObjectURL(file);
|
||||||
|
|
||||||
@@ -133,14 +177,15 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
|
|||||||
preview: objectUrl,
|
preview: objectUrl,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
size: `${(file.size / 1024 / 1024).toFixed(2)} MB`,
|
size: `${(file.size / 1024 / 1024).toFixed(2)} MB`,
|
||||||
dimensions
|
...(dimensions && { dimensions })
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Файл обработан:', {
|
console.log('Файл обработан:', {
|
||||||
|
file: file,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
dimensions
|
...(dimensions && { dimensions })
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -192,13 +237,21 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
|
|||||||
setIsFileUploaded(false);
|
setIsFileUploaded(false);
|
||||||
setSelectedFile(null);
|
setSelectedFile(null);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setUploadProgress(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlerFileUpload = async (file: SelectedFile): Promise<any> => {
|
const handlerFileUpload = async (file: SelectedFile): Promise<void> => {
|
||||||
|
const extension = file.name.split('.').pop();
|
||||||
|
|
||||||
|
if (!extension) {
|
||||||
|
setError(t('file-has-no-extension'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const initMessageBody = {
|
const initMessageBody = {
|
||||||
file_name: file.name,
|
file_name: file.name,
|
||||||
file_type: fileType,
|
file_type: fileType,
|
||||||
extension: file.name.split('.')[1],
|
extension: extension,
|
||||||
file_size: file.file.size
|
file_size: file.file.size
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -239,26 +292,26 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
|
|||||||
|
|
||||||
start = end;
|
start = end;
|
||||||
chunkIndex++;
|
chunkIndex++;
|
||||||
|
setUploadProgress(
|
||||||
|
() => {
|
||||||
|
return Math.floor(chunkIndex / totalChunks * 100);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError('Chunk uploaded error ' + error);
|
setError('Chunk uploaded error ' + error);
|
||||||
console.error('Chunk upload error:', error);
|
console.error('Chunk upload error:', error);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log(response);
|
||||||
|
setError('file uploaded error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (selectedFile) {
|
|
||||||
URL.revokeObjectURL(selectedFile.preview);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [selectedFile]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="upload-section">
|
<div className="upload-section">
|
||||||
<h3>
|
<h3>
|
||||||
{t('download-for-protection', { fileType: t(fileType.toLocaleLowerCase()) })}
|
{t('upload-for-protection', { fileType: t(fileType.toLocaleLowerCase()) })}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -349,14 +402,27 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{fileType === 'IMAGE' && (
|
||||||
<img
|
<img
|
||||||
src={selectedFile.preview}
|
src={selectedFile.preview}
|
||||||
alt="Предпросмотр загруженного изображения"
|
alt="Предпросмотр загруженного изображения"
|
||||||
className="uploaded-image"
|
className="uploaded-image"
|
||||||
onError={() => setError('Не удалось загрузить превью изображения')}
|
onError={() => setError('Не удалось загрузить превью изображения')}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="mt-4 flex gap-3 justify-center">
|
{fileType === 'VIDEO' && (
|
||||||
|
<video
|
||||||
|
src={selectedFile.preview}
|
||||||
|
className="uploaded-image"
|
||||||
|
controls
|
||||||
|
muted
|
||||||
|
onError={() => setError('Не удалось загрузить превью видео')}
|
||||||
|
onLoadedData={() => console.log('Видео загружено')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-4 flex gap-3 justify-center relative">
|
||||||
<button
|
<button
|
||||||
className="btn btn-cancel"
|
className="btn btn-cancel"
|
||||||
onClick={handleClearFile}
|
onClick={handleClearFile}
|
||||||
@@ -365,11 +431,12 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
|
|||||||
{isFileUploaded ? t('close') : t('cancel')}
|
{isFileUploaded ? t('close') : t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-confirm"
|
className={`btn btn-confirm ${error ? 'disabled' : ''}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handlerFileUpload(selectedFile);
|
handlerFileUpload(selectedFile);
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
|
disabled={error ? true : false}
|
||||||
>
|
>
|
||||||
{t('upload-file')}
|
{t('upload-file')}
|
||||||
</button>
|
</button>
|
||||||
@@ -379,15 +446,20 @@ export default function UploadSectionFile({ fileType, allowedExtensions }: Uploa
|
|||||||
onClick={
|
onClick={
|
||||||
async () => {
|
async () => {
|
||||||
if (uploadId) {
|
if (uploadId) {
|
||||||
console.log('checkChunkStatus')
|
await checkChunkStatus(uploadId);
|
||||||
const response = await checkChunkStatus(uploadId);
|
|
||||||
console.log(response);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
dev-test-button
|
dev-test-button
|
||||||
</button>
|
</button>
|
||||||
|
{uploadProgress !== 0 && (
|
||||||
|
<div
|
||||||
|
className={`absolute top-5 right-5 font-medium ${isFileUploaded ? "text-green-600" : ""}`}
|
||||||
|
>
|
||||||
|
{uploadProgress}%
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -517,6 +517,14 @@
|
|||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
transform: translateY(0px);
|
transform: translateY(0px);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-cancel {
|
.btn-cancel {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
/* removed */
|
||||||
|
|
||||||
import { useState, useRef } from 'react';
|
import { useState, useRef } from 'react';
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"checks": "Checks",
|
"checks": "Checks",
|
||||||
"check": "Checks",
|
"check": "Checks",
|
||||||
"videos": "Videos",
|
"videos": "Videos",
|
||||||
"video": "vides",
|
"video": "videos",
|
||||||
"audios": "Audio",
|
"audios": "Audio",
|
||||||
"audio": "audio",
|
"audio": "audio",
|
||||||
"images": "Images",
|
"images": "Images",
|
||||||
@@ -126,8 +126,9 @@
|
|||||||
"or": "or",
|
"or": "or",
|
||||||
"drag-the-file-here": "Drag the file here",
|
"drag-the-file-here": "Drag the file here",
|
||||||
"select-files-to-protect": "Select files to protect",
|
"select-files-to-protect": "Select files to protect",
|
||||||
"download-for-protection": "Download {fileType} for protection",
|
"upload-for-protection": "Upload {fileType} for protection",
|
||||||
"image": "image"
|
"image": "image",
|
||||||
|
"file-has-no-extension": "File has no extension"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "and",
|
"and": "and",
|
||||||
|
|||||||
@@ -126,8 +126,9 @@
|
|||||||
"or": "или",
|
"or": "или",
|
||||||
"drag-the-file-here": "Перетащите файл сюда",
|
"drag-the-file-here": "Перетащите файл сюда",
|
||||||
"select-files-to-protect": "Выбрать файлы для защиты",
|
"select-files-to-protect": "Выбрать файлы для защиты",
|
||||||
"download-for-protection": "Загрузить {fileType} для защиты",
|
"upload-for-protection": "Загрузить {fileType} для защиты",
|
||||||
"image": "изображений"
|
"image": "изображений",
|
||||||
|
"file-has-no-extension": "Файл не имеет расширения"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "и",
|
"and": "и",
|
||||||
|
|||||||
Reference in New Issue
Block a user