Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b49ec93c54 | ||
|
|
e6584322fa | ||
|
|
1138cd02b0 | ||
|
|
1ba8d0cbd1 | ||
|
|
98380e2d48 | ||
|
|
e377ed10e1 | ||
|
|
acc35f4717 | ||
|
|
33742a043d | ||
|
|
5cee9cbbb9 | ||
|
|
e0fbbeca3c | ||
|
|
bbfc9d6f14 |
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,3 +1,31 @@
|
||||
## [0.20.1](https://github.com/stonith404/pingvin-share/compare/v0.20.0...v0.20.1) (2023-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* share information text color in light mode ([1138cd0](https://github.com/stonith404/pingvin-share/commit/1138cd02b0b6ac1d71c4dbc2808110c672237190))
|
||||
|
||||
## [0.20.0](https://github.com/stonith404/pingvin-share/compare/v0.19.2...v0.20.0) (2023-11-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* ability to add and delete files of existing share ([#306](https://github.com/stonith404/pingvin-share/issues/306)) ([98380e2](https://github.com/stonith404/pingvin-share/commit/98380e2d48cc8ffa831d9b69cf5c0e8a40e28862))
|
||||
|
||||
## [0.19.2](https://github.com/stonith404/pingvin-share/compare/v0.19.1...v0.19.2) (2023-11-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* ability to limit the max expiration of a share ([bbfc9d6](https://github.com/stonith404/pingvin-share/commit/bbfc9d6f147eea404f011c3af9d7dc7655c3d21d))
|
||||
* change totp issuer to display logo in 2FAS app ([e0fbbec](https://github.com/stonith404/pingvin-share/commit/e0fbbeca3c1a858838b20aeead52694772b7d871))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* jwt secret changes on application restart ([33742a0](https://github.com/stonith404/pingvin-share/commit/33742a043d6549783984ae7e8a3c30f0fe3917de))
|
||||
* wrong validation of setting max share expiration to `0` ([acc35f4](https://github.com/stonith404/pingvin-share/commit/acc35f47178e230f50ce54d6f1ad5370caa3382d))
|
||||
|
||||
## [0.19.1](https://github.com/stonith404/pingvin-share/compare/v0.19.0...v0.19.1) (2023-10-22)
|
||||
|
||||
|
||||
|
||||
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.19.1",
|
||||
"version": "0.20.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.19.1",
|
||||
"version": "0.20.1",
|
||||
"dependencies": {
|
||||
"@nestjs/cache-manager": "^2.1.0",
|
||||
"@nestjs/common": "^10.1.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.19.1",
|
||||
"version": "0.20.1",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"dev": "cross-env NODE_ENV=development nest start --watch",
|
||||
|
||||
@@ -5,7 +5,7 @@ const configVariables: ConfigVariables = {
|
||||
internal: {
|
||||
jwtSecret: {
|
||||
type: "string",
|
||||
defaultValue: crypto.randomBytes(256).toString("base64"),
|
||||
value: crypto.randomBytes(256).toString("base64"),
|
||||
locked: true,
|
||||
},
|
||||
},
|
||||
@@ -37,6 +37,11 @@ const configVariables: ConfigVariables = {
|
||||
defaultValue: "false",
|
||||
secret: false,
|
||||
},
|
||||
maxExpiration: {
|
||||
type: "number",
|
||||
defaultValue: "0",
|
||||
secret: false,
|
||||
},
|
||||
maxSize: {
|
||||
type: "number",
|
||||
defaultValue: "1000000000",
|
||||
|
||||
@@ -8,7 +8,6 @@ import { User } from "@prisma/client";
|
||||
import * as argon from "argon2";
|
||||
import { authenticator, totp } from "otplib";
|
||||
import * as qrcode from "qrcode-svg";
|
||||
import { ConfigService } from "src/config/config.service";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { AuthSignInTotpDTO } from "./dto/authSignInTotp.dto";
|
||||
@@ -18,7 +17,6 @@ export class AuthTotpService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private authService: AuthService,
|
||||
private config: ConfigService,
|
||||
) {}
|
||||
|
||||
async signInTotp(dto: AuthSignInTotpDTO) {
|
||||
@@ -78,12 +76,11 @@ export class AuthTotpService {
|
||||
throw new BadRequestException("TOTP is already enabled");
|
||||
}
|
||||
|
||||
// TODO: Maybe make the issuer configurable with env vars?
|
||||
const secret = authenticator.generateSecret();
|
||||
|
||||
const otpURL = totp.keyuri(
|
||||
user.username || user.email,
|
||||
this.config.get("general.appName"),
|
||||
"pingvin-share",
|
||||
secret,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
@@ -81,4 +82,14 @@ export class FileController {
|
||||
|
||||
return new StreamableFile(file.file);
|
||||
}
|
||||
|
||||
@Delete(":fileId")
|
||||
@SkipThrottle()
|
||||
@UseGuards(ShareOwnerGuard)
|
||||
async remove(
|
||||
@Param("fileId") fileId: string,
|
||||
@Param("shareId") shareId: string,
|
||||
) {
|
||||
await this.fileService.remove(shareId, fileId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,18 @@ export class FileService {
|
||||
};
|
||||
}
|
||||
|
||||
async remove(shareId: string, fileId: string) {
|
||||
const fileMetaData = await this.prisma.file.findUnique({
|
||||
where: { id: fileId },
|
||||
});
|
||||
|
||||
if (!fileMetaData) throw new NotFoundException("File not found");
|
||||
|
||||
fs.unlinkSync(`${SHARE_DIRECTORY}/${shareId}/${fileId}`);
|
||||
|
||||
await this.prisma.file.delete({ where: { id: fileId } });
|
||||
}
|
||||
|
||||
async deleteAllFiles(shareId: string) {
|
||||
await fs.promises.rm(`${SHARE_DIRECTORY}/${shareId}`, {
|
||||
recursive: true,
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as moment from "moment";
|
||||
import { ConfigService } from "src/config/config.service";
|
||||
import { FileService } from "src/file/file.service";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { parseRelativeDateToAbsolute } from "src/utils/date.util";
|
||||
import { CreateReverseShareDTO } from "./dto/createReverseShare.dto";
|
||||
|
||||
@Injectable()
|
||||
@@ -24,6 +25,17 @@ export class ReverseShareService {
|
||||
)
|
||||
.toDate();
|
||||
|
||||
const parsedExpiration = parseRelativeDateToAbsolute(data.shareExpiration);
|
||||
if (
|
||||
this.config.get("share.maxExpiration") !== 0 &&
|
||||
parsedExpiration >
|
||||
moment().add(this.config.get("share.maxExpiration"), "hours").toDate()
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"Expiration date exceeds maximum expiration date",
|
||||
);
|
||||
}
|
||||
|
||||
const globalMaxShareSize = this.config.get("share.maxSize");
|
||||
|
||||
if (globalMaxShareSize < data.maxShareSize)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
@@ -7,12 +6,21 @@ import {
|
||||
import { User } from "@prisma/client";
|
||||
import { Request } from "express";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { JwtGuard } from "../../auth/guard/jwt.guard";
|
||||
import { ConfigService } from "src/config/config.service";
|
||||
|
||||
@Injectable()
|
||||
export class ShareOwnerGuard implements CanActivate {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
export class ShareOwnerGuard extends JwtGuard {
|
||||
constructor(
|
||||
configService: ConfigService,
|
||||
private prisma: PrismaService,
|
||||
) {
|
||||
super(configService);
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext) {
|
||||
if (!(await super.canActivate(context))) return false;
|
||||
|
||||
const request: Request = context.switchToHttp().getRequest();
|
||||
const shareId = Object.prototype.hasOwnProperty.call(
|
||||
request.params,
|
||||
|
||||
@@ -43,6 +43,12 @@ export class ShareController {
|
||||
return new ShareDTO().from(await this.shareService.get(id));
|
||||
}
|
||||
|
||||
@Get(":id/from-owner")
|
||||
@UseGuards(ShareOwnerGuard)
|
||||
async getFromOwner(@Param("id") id: string) {
|
||||
return new ShareDTO().from(await this.shareService.get(id));
|
||||
}
|
||||
|
||||
@Get(":id/metaData")
|
||||
@UseGuards(ShareSecurityGuard)
|
||||
async getMetaData(@Param("id") id: string) {
|
||||
@@ -62,12 +68,6 @@ export class ShareController {
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@UseGuards(JwtGuard, ShareOwnerGuard)
|
||||
async remove(@Param("id") id: string) {
|
||||
await this.shareService.remove(id);
|
||||
}
|
||||
|
||||
@Post(":id/complete")
|
||||
@HttpCode(202)
|
||||
@UseGuards(CreateShareGuard, ShareOwnerGuard)
|
||||
@@ -78,6 +78,18 @@ export class ShareController {
|
||||
);
|
||||
}
|
||||
|
||||
@Delete(":id/complete")
|
||||
@UseGuards(ShareOwnerGuard)
|
||||
async revertComplete(@Param("id") id: string) {
|
||||
return new ShareDTO().from(await this.shareService.revertComplete(id));
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@UseGuards(ShareOwnerGuard)
|
||||
async remove(@Param("id") id: string) {
|
||||
await this.shareService.remove(id);
|
||||
}
|
||||
|
||||
@Throttle(10, 60)
|
||||
@Get("isShareIdAvailable/:id")
|
||||
async isShareIdAvailable(@Param("id") id: string) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { EmailService } from "src/email/email.service";
|
||||
import { FileService } from "src/file/file.service";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { ReverseShareService } from "src/reverseShare/reverseShare.service";
|
||||
import { parseRelativeDateToAbsolute } from "src/utils/date.util";
|
||||
import { SHARE_DIRECTORY } from "../constants";
|
||||
import { CreateShareDTO } from "./dto/createShare.dto";
|
||||
|
||||
@@ -51,19 +52,19 @@ export class ShareService {
|
||||
if (reverseShare) {
|
||||
expirationDate = reverseShare.shareExpiration;
|
||||
} else {
|
||||
// We have to add an exception for "never" (since moment won't like that)
|
||||
if (share.expiration !== "never") {
|
||||
expirationDate = moment()
|
||||
.add(
|
||||
share.expiration.split("-")[0],
|
||||
share.expiration.split(
|
||||
"-",
|
||||
)[1] as moment.unitOfTime.DurationConstructor,
|
||||
)
|
||||
.toDate();
|
||||
} else {
|
||||
expirationDate = moment(0).toDate();
|
||||
const parsedExpiration = parseRelativeDateToAbsolute(share.expiration);
|
||||
|
||||
if (
|
||||
this.config.get("share.maxExpiration") !== 0 &&
|
||||
parsedExpiration >
|
||||
moment().add(this.config.get("share.maxExpiration"), "hours").toDate()
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"Expiration date exceeds maximum expiration date",
|
||||
);
|
||||
}
|
||||
|
||||
expirationDate = parsedExpiration;
|
||||
}
|
||||
|
||||
fs.mkdirSync(`${SHARE_DIRECTORY}/${share.id}`, {
|
||||
@@ -181,6 +182,13 @@ export class ShareService {
|
||||
});
|
||||
}
|
||||
|
||||
async revertComplete(id: string) {
|
||||
return this.prisma.share.update({
|
||||
where: { id },
|
||||
data: { uploadLocked: false, isZipReady: false },
|
||||
});
|
||||
}
|
||||
|
||||
async getSharesByUser(userId: string) {
|
||||
const shares = await this.prisma.share.findMany({
|
||||
where: {
|
||||
|
||||
12
backend/src/utils/date.util.ts
Normal file
12
backend/src/utils/date.util.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as moment from "moment";
|
||||
|
||||
export function parseRelativeDateToAbsolute(relativeDate: string) {
|
||||
if (relativeDate == "never") return moment(0).toDate();
|
||||
|
||||
return moment()
|
||||
.add(
|
||||
relativeDate.split("-")[0],
|
||||
relativeDate.split("-")[1] as moment.unitOfTime.DurationConstructor,
|
||||
)
|
||||
.toDate();
|
||||
}
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.19.1",
|
||||
"version": "0.20.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.19.1",
|
||||
"version": "0.20.1",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.19.1",
|
||||
"version": "0.20.1",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
|
||||
@@ -12,7 +12,7 @@ const showShareInformationsModal = (
|
||||
modals: ModalsContextProps,
|
||||
share: MyShare,
|
||||
appUrl: string,
|
||||
maxShareSize: number,
|
||||
maxShareSize: number
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
const link = `${appUrl}/s/${share.id}`;
|
||||
@@ -36,28 +36,28 @@ const showShareInformationsModal = (
|
||||
|
||||
children: (
|
||||
<Stack align="stretch" spacing="md">
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.id" />:{" "}
|
||||
</b>
|
||||
{share.id}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.description" />:{" "}
|
||||
</b>
|
||||
{share.description || "No description"}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.createdAt" />:{" "}
|
||||
</b>
|
||||
{formattedCreatedAt}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.expiresAt" />:{" "}
|
||||
</b>
|
||||
@@ -66,7 +66,7 @@ const showShareInformationsModal = (
|
||||
<Divider />
|
||||
<CopyTextField link={link} />
|
||||
<Divider />
|
||||
<Text size="sm" color="lightgray">
|
||||
<Text size="sm">
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.size" />:{" "}
|
||||
</b>
|
||||
@@ -76,7 +76,7 @@ const showShareInformationsModal = (
|
||||
|
||||
<Flex align="center" justify="center">
|
||||
{shareSize / maxShareSize < 0.1 && (
|
||||
<Text size="xs" color="lightgray" style={{ marginRight: "4px" }}>
|
||||
<Text size="xs" style={{ marginRight: "4px" }}>
|
||||
{formattedShareSize}
|
||||
</Text>
|
||||
)}
|
||||
@@ -87,7 +87,7 @@ const showShareInformationsModal = (
|
||||
size="xl"
|
||||
radius="xl"
|
||||
/>
|
||||
<Text size="xs" color="lightgray" style={{ marginLeft: "4px" }}>
|
||||
<Text size="xs" style={{ marginLeft: "4px" }}>
|
||||
{formattedMaxShareSize}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { useForm } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import moment from "moment";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useTranslate, {
|
||||
translateOutsideContext,
|
||||
@@ -25,6 +26,7 @@ import showCompletedReverseShareModal from "./showCompletedReverseShareModal";
|
||||
const showCreateReverseShareModal = (
|
||||
modals: ModalsContextProps,
|
||||
showSendEmailNotificationOption: boolean,
|
||||
maxExpirationInHours: number,
|
||||
getReverseShares: () => void,
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
@@ -34,6 +36,7 @@ const showCreateReverseShareModal = (
|
||||
<Body
|
||||
showSendEmailNotificationOption={showSendEmailNotificationOption}
|
||||
getReverseShares={getReverseShares}
|
||||
maxExpirationInHours={maxExpirationInHours}
|
||||
/>
|
||||
),
|
||||
});
|
||||
@@ -42,9 +45,11 @@ const showCreateReverseShareModal = (
|
||||
const Body = ({
|
||||
getReverseShares,
|
||||
showSendEmailNotificationOption,
|
||||
maxExpirationInHours,
|
||||
}: {
|
||||
getReverseShares: () => void;
|
||||
showSendEmailNotificationOption: boolean;
|
||||
maxExpirationInHours: number;
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
@@ -58,27 +63,48 @@ const Body = ({
|
||||
expiration_unit: "-days",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = form.onSubmit(async (values) => {
|
||||
const expirationDate = moment().add(
|
||||
form.values.expiration_num,
|
||||
form.values.expiration_unit.replace(
|
||||
"-",
|
||||
"",
|
||||
) as moment.unitOfTime.DurationConstructor,
|
||||
);
|
||||
if (
|
||||
maxExpirationInHours != 0 &&
|
||||
expirationDate.isAfter(moment().add(maxExpirationInHours, "hours"))
|
||||
) {
|
||||
form.setFieldError(
|
||||
"expiration_num",
|
||||
t("upload.modal.expires.error.too-long", {
|
||||
max: moment.duration(maxExpirationInHours, "hours").humanize(),
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
shareService
|
||||
.createReverseShare(
|
||||
values.expiration_num + values.expiration_unit,
|
||||
values.maxShareSize,
|
||||
values.maxUseCount,
|
||||
values.sendEmailNotification,
|
||||
)
|
||||
.then(({ link }) => {
|
||||
modals.closeAll();
|
||||
showCompletedReverseShareModal(modals, link, getReverseShares);
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
});
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
shareService
|
||||
.createReverseShare(
|
||||
values.expiration_num + values.expiration_unit,
|
||||
values.maxShareSize,
|
||||
values.maxUseCount,
|
||||
values.sendEmailNotification,
|
||||
)
|
||||
.then(({ link }) => {
|
||||
modals.closeAll();
|
||||
showCompletedReverseShareModal(modals, link, getReverseShares);
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
})}
|
||||
>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Stack align="stretch">
|
||||
<div>
|
||||
<Grid align={form.errors.link ? "center" : "flex-end"}>
|
||||
<Grid align={form.errors.expiration_num ? "center" : "flex-end"}>
|
||||
<Col xs={6}>
|
||||
<NumberInput
|
||||
min={1}
|
||||
|
||||
@@ -33,10 +33,12 @@ const useStyles = createStyles((theme) => ({
|
||||
}));
|
||||
|
||||
const Dropzone = ({
|
||||
title,
|
||||
isUploading,
|
||||
maxShareSize,
|
||||
showCreateUploadModalCallback,
|
||||
}: {
|
||||
title?: string;
|
||||
isUploading: boolean;
|
||||
maxShareSize: number;
|
||||
showCreateUploadModalCallback: (files: FileUpload[]) => void;
|
||||
@@ -78,7 +80,7 @@ const Dropzone = ({
|
||||
<TbCloudUpload size={50} />
|
||||
</Group>
|
||||
<Text align="center" weight={700} size="lg" mt="xl">
|
||||
<FormattedMessage id="upload.dropzone.title" />
|
||||
{title || <FormattedMessage id="upload.dropzone.title" />}
|
||||
</Text>
|
||||
<Text align="center" size="sm" mt="xs" color="dimmed">
|
||||
<FormattedMessage
|
||||
|
||||
238
frontend/src/components/upload/EditableUpload.tsx
Normal file
238
frontend/src/components/upload/EditableUpload.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import { Button, Group } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { cleanNotifications } from "@mantine/notifications";
|
||||
import { AxiosError } from "axios";
|
||||
import pLimit from "p-limit";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Dropzone from "../../components/upload/Dropzone";
|
||||
import FileList from "../../components/upload/FileList";
|
||||
import showCompletedUploadModal from "../../components/upload/modals/showCompletedUploadModal";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import shareService from "../../services/share.service";
|
||||
import { FileListItem, FileMetaData, FileUpload } from "../../types/File.type";
|
||||
import toast from "../../utils/toast.util";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const promiseLimit = pLimit(3);
|
||||
const chunkSize = 10 * 1024 * 1024; // 10MB
|
||||
let errorToastShown = false;
|
||||
|
||||
const EditableUpload = ({
|
||||
maxShareSize,
|
||||
shareId,
|
||||
files: savedFiles = [],
|
||||
}: {
|
||||
maxShareSize?: number;
|
||||
isReverseShare?: boolean;
|
||||
shareId: string;
|
||||
files?: FileMetaData[];
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
const router = useRouter();
|
||||
const config = useConfig();
|
||||
|
||||
const [existingFiles, setExistingFiles] =
|
||||
useState<Array<FileMetaData & { deleted?: boolean }>>(savedFiles);
|
||||
const [uploadingFiles, setUploadingFiles] = useState<FileUpload[]>([]);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
const existingAndUploadedFiles: FileListItem[] = useMemo(
|
||||
() => [...uploadingFiles, ...existingFiles],
|
||||
[existingFiles, uploadingFiles],
|
||||
);
|
||||
const dirty = useMemo(() => {
|
||||
return (
|
||||
existingFiles.some((file) => !!file.deleted) || !!uploadingFiles.length
|
||||
);
|
||||
}, [existingFiles, uploadingFiles]);
|
||||
|
||||
const setFiles = (files: FileListItem[]) => {
|
||||
const _uploadFiles = files.filter(
|
||||
(file) => "uploadingProgress" in file,
|
||||
) as FileUpload[];
|
||||
const _existingFiles = files.filter(
|
||||
(file) => !("uploadingProgress" in file),
|
||||
) as FileMetaData[];
|
||||
|
||||
setUploadingFiles(_uploadFiles);
|
||||
setExistingFiles(_existingFiles);
|
||||
};
|
||||
|
||||
maxShareSize ??= parseInt(config.get("share.maxSize"));
|
||||
|
||||
const uploadFiles = async (files: FileUpload[]) => {
|
||||
const fileUploadPromises = files.map(async (file, fileIndex) =>
|
||||
// Limit the number of concurrent uploads to 3
|
||||
promiseLimit(async () => {
|
||||
let fileId: string;
|
||||
|
||||
const setFileProgress = (progress: number) => {
|
||||
setUploadingFiles((files) =>
|
||||
files.map((file, callbackIndex) => {
|
||||
if (fileIndex == callbackIndex) {
|
||||
file.uploadingProgress = progress;
|
||||
}
|
||||
return file;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
setFileProgress(1);
|
||||
|
||||
let chunks = Math.ceil(file.size / chunkSize);
|
||||
|
||||
// If the file is 0 bytes, we still need to upload 1 chunk
|
||||
if (chunks == 0) chunks++;
|
||||
|
||||
for (let chunkIndex = 0; chunkIndex < chunks; chunkIndex++) {
|
||||
const from = chunkIndex * chunkSize;
|
||||
const to = from + chunkSize;
|
||||
const blob = file.slice(from, to);
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (event) =>
|
||||
await shareService
|
||||
.uploadFile(
|
||||
shareId,
|
||||
event,
|
||||
{
|
||||
id: fileId,
|
||||
name: file.name,
|
||||
},
|
||||
chunkIndex,
|
||||
chunks,
|
||||
)
|
||||
.then((response) => {
|
||||
fileId = response.id;
|
||||
resolve(response);
|
||||
})
|
||||
.catch(reject);
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
|
||||
setFileProgress(((chunkIndex + 1) / chunks) * 100);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof AxiosError &&
|
||||
e.response?.data.error == "unexpected_chunk_index"
|
||||
) {
|
||||
// Retry with the expected chunk index
|
||||
chunkIndex = e.response!.data!.expectedChunkIndex - 1;
|
||||
continue;
|
||||
} else {
|
||||
setFileProgress(-1);
|
||||
// Retry after 5 seconds
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
chunkIndex = -1;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.all(fileUploadPromises);
|
||||
};
|
||||
|
||||
const removeFiles = async () => {
|
||||
const removedFiles = existingFiles.filter((file) => !!file.deleted);
|
||||
|
||||
if (removedFiles.length > 0) {
|
||||
await Promise.all(
|
||||
removedFiles.map(async (file) => {
|
||||
await shareService.removeFile(shareId, file.id);
|
||||
}),
|
||||
);
|
||||
|
||||
setExistingFiles(existingFiles.filter((file) => !file.deleted));
|
||||
}
|
||||
};
|
||||
|
||||
const revertComplete = async () => {
|
||||
await shareService.revertComplete(shareId).then();
|
||||
};
|
||||
|
||||
const completeShare = async () => {
|
||||
return await shareService.completeShare(shareId);
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
setIsUploading(true);
|
||||
|
||||
try {
|
||||
await revertComplete();
|
||||
await uploadFiles(uploadingFiles);
|
||||
|
||||
const hasFailed = uploadingFiles.some(
|
||||
(file) => file.uploadingProgress == -1,
|
||||
);
|
||||
|
||||
if (!hasFailed) {
|
||||
await removeFiles();
|
||||
}
|
||||
|
||||
await completeShare();
|
||||
|
||||
if (!hasFailed) {
|
||||
toast.success(t("share.edit.notify.save-success"));
|
||||
router.back();
|
||||
}
|
||||
} catch {
|
||||
toast.error(t("share.edit.notify.generic-error"));
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const appendFiles = (appendingFiles: FileUpload[]) => {
|
||||
setUploadingFiles([...appendingFiles, ...uploadingFiles]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Check if there are any files that failed to upload
|
||||
const fileErrorCount = uploadingFiles.filter(
|
||||
(file) => file.uploadingProgress == -1,
|
||||
).length;
|
||||
|
||||
if (fileErrorCount > 0) {
|
||||
if (!errorToastShown) {
|
||||
toast.error(
|
||||
t("upload.notify.count-failed", { count: fileErrorCount }),
|
||||
{
|
||||
withCloseButton: false,
|
||||
autoClose: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
errorToastShown = true;
|
||||
} else {
|
||||
cleanNotifications();
|
||||
errorToastShown = false;
|
||||
}
|
||||
}, [uploadingFiles]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group position="right" mb={20}>
|
||||
<Button loading={isUploading} disabled={!dirty} onClick={() => save()}>
|
||||
<FormattedMessage id="common.button.save" />
|
||||
</Button>
|
||||
</Group>
|
||||
<Dropzone
|
||||
title={t("share.edit.append-upload")}
|
||||
maxShareSize={maxShareSize}
|
||||
showCreateUploadModalCallback={appendFiles}
|
||||
isUploading={isUploading}
|
||||
/>
|
||||
{existingAndUploadedFiles.length > 0 && (
|
||||
<FileList files={existingAndUploadedFiles} setFiles={setFiles} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default EditableUpload;
|
||||
@@ -1,41 +1,106 @@
|
||||
import { ActionIcon, Table } from "@mantine/core";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { TbTrash } from "react-icons/tb";
|
||||
import { FileUpload } from "../../types/File.type";
|
||||
import { GrUndo } from "react-icons/gr";
|
||||
import { FileListItem } from "../../types/File.type";
|
||||
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
||||
import UploadProgressIndicator from "./UploadProgressIndicator";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const FileList = ({
|
||||
const FileListRow = ({
|
||||
file,
|
||||
onRemove,
|
||||
onRestore,
|
||||
}: {
|
||||
file: FileListItem;
|
||||
onRemove?: () => void;
|
||||
onRestore?: () => void;
|
||||
}) => {
|
||||
{
|
||||
const uploadable = "uploadingProgress" in file;
|
||||
const uploading = uploadable && file.uploadingProgress !== 0;
|
||||
const removable = uploadable
|
||||
? file.uploadingProgress === 0
|
||||
: onRemove && !file.deleted;
|
||||
const restorable = onRestore && !uploadable && !!file.deleted; // maybe undefined, force boolean
|
||||
const deleted = !uploadable && !!file.deleted;
|
||||
|
||||
return (
|
||||
<tr
|
||||
style={{
|
||||
color: deleted ? "rgba(120, 120, 120, 0.5)" : "inherit",
|
||||
textDecoration: deleted ? "line-through" : "none",
|
||||
}}
|
||||
>
|
||||
<td>{file.name}</td>
|
||||
<td>{byteToHumanSizeString(+file.size)}</td>
|
||||
<td>
|
||||
{removable && (
|
||||
<ActionIcon
|
||||
color="red"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={onRemove}
|
||||
>
|
||||
<TbTrash />
|
||||
</ActionIcon>
|
||||
)}
|
||||
{uploading && (
|
||||
<UploadProgressIndicator progress={file.uploadingProgress} />
|
||||
)}
|
||||
{restorable && (
|
||||
<ActionIcon
|
||||
color="primary"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={onRestore}
|
||||
>
|
||||
<GrUndo />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const FileList = <T extends FileListItem = FileListItem>({
|
||||
files,
|
||||
setFiles,
|
||||
}: {
|
||||
files: FileUpload[];
|
||||
setFiles: Dispatch<SetStateAction<FileUpload[]>>;
|
||||
files: T[];
|
||||
setFiles: (files: T[]) => void;
|
||||
}) => {
|
||||
const remove = (index: number) => {
|
||||
files.splice(index, 1);
|
||||
const file = files[index];
|
||||
|
||||
if ("uploadingProgress" in file) {
|
||||
files.splice(index, 1);
|
||||
} else {
|
||||
files[index] = { ...file, deleted: true };
|
||||
}
|
||||
|
||||
setFiles([...files]);
|
||||
};
|
||||
|
||||
const restore = (index: number) => {
|
||||
const file = files[index];
|
||||
|
||||
if ("uploadingProgress" in file) {
|
||||
return;
|
||||
} else {
|
||||
files[index] = { ...file, deleted: false };
|
||||
}
|
||||
|
||||
setFiles([...files]);
|
||||
};
|
||||
|
||||
const rows = files.map((file, i) => (
|
||||
<tr key={i}>
|
||||
<td>{file.name}</td>
|
||||
<td>{byteToHumanSizeString(file.size)}</td>
|
||||
<td>
|
||||
{file.uploadingProgress == 0 ? (
|
||||
<ActionIcon
|
||||
color="red"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={() => remove(i)}
|
||||
>
|
||||
<TbTrash />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<UploadProgressIndicator progress={file.uploadingProgress} />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<FileListRow
|
||||
key={i}
|
||||
file={file}
|
||||
onRemove={() => remove(i)}
|
||||
onRestore={() => restore(i)}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import moment from "moment";
|
||||
import { useState } from "react";
|
||||
import { TbAlertCircle } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
@@ -38,6 +39,7 @@ const showCreateUploadModal = (
|
||||
appUrl: string;
|
||||
allowUnauthenticatedShares: boolean;
|
||||
enableEmailRecepients: boolean;
|
||||
maxExpirationInHours: number;
|
||||
},
|
||||
files: FileUpload[],
|
||||
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void,
|
||||
@@ -69,6 +71,7 @@ const CreateUploadModalBody = ({
|
||||
appUrl: string;
|
||||
allowUnauthenticatedShares: boolean;
|
||||
enableEmailRecepients: boolean;
|
||||
maxExpirationInHours: number;
|
||||
};
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
@@ -92,6 +95,7 @@ const CreateUploadModalBody = ({
|
||||
password: yup.string().min(3).max(30),
|
||||
maxViews: yup.number().min(1),
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
link: generatedLink,
|
||||
@@ -105,6 +109,56 @@ const CreateUploadModalBody = ({
|
||||
},
|
||||
validate: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const onSubmit = form.onSubmit(async (values) => {
|
||||
if (!(await shareService.isShareIdAvailable(values.link))) {
|
||||
form.setFieldError("link", t("upload.modal.link.error.taken"));
|
||||
} else {
|
||||
const expirationString = form.values.never_expires
|
||||
? "never"
|
||||
: form.values.expiration_num + form.values.expiration_unit;
|
||||
|
||||
const expirationDate = moment().add(
|
||||
form.values.expiration_num,
|
||||
form.values.expiration_unit.replace(
|
||||
"-",
|
||||
"",
|
||||
) as moment.unitOfTime.DurationConstructor,
|
||||
);
|
||||
if (
|
||||
options.maxExpirationInHours != 0 &&
|
||||
expirationDate.isAfter(
|
||||
moment().add(options.maxExpirationInHours, "hours"),
|
||||
)
|
||||
) {
|
||||
form.setFieldError(
|
||||
"expiration_num",
|
||||
t("upload.modal.expires.error.too-long", {
|
||||
max: moment
|
||||
.duration(options.maxExpirationInHours, "hours")
|
||||
.humanize(),
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
uploadCallback(
|
||||
{
|
||||
id: values.link,
|
||||
expiration: expirationString,
|
||||
recipients: values.recipients,
|
||||
description: values.description,
|
||||
security: {
|
||||
password: values.password,
|
||||
maxViews: values.maxViews,
|
||||
},
|
||||
},
|
||||
files,
|
||||
);
|
||||
modals.closeAll();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{showNotSignedInAlert && !options.isUserSignedIn && (
|
||||
@@ -118,33 +172,9 @@ const CreateUploadModalBody = ({
|
||||
<FormattedMessage id="upload.modal.not-signed-in-description" />
|
||||
</Alert>
|
||||
)}
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
if (!(await shareService.isShareIdAvailable(values.link))) {
|
||||
form.setFieldError("link", t("upload.modal.link.error.taken"));
|
||||
} else {
|
||||
const expiration = form.values.never_expires
|
||||
? "never"
|
||||
: form.values.expiration_num + form.values.expiration_unit;
|
||||
uploadCallback(
|
||||
{
|
||||
id: values.link,
|
||||
expiration: expiration,
|
||||
recipients: values.recipients,
|
||||
description: values.description,
|
||||
security: {
|
||||
password: values.password,
|
||||
maxViews: values.maxViews,
|
||||
},
|
||||
},
|
||||
files,
|
||||
);
|
||||
modals.closeAll();
|
||||
}
|
||||
})}
|
||||
>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Stack align="stretch">
|
||||
<Group align="end">
|
||||
<Group align={form.errors.link ? "center" : "flex-end"}>
|
||||
<TextInput
|
||||
style={{ flex: "1" }}
|
||||
variant="filled"
|
||||
@@ -179,7 +209,7 @@ const CreateUploadModalBody = ({
|
||||
</Text>
|
||||
{!options.isReverseShare && (
|
||||
<>
|
||||
<Grid align={form.errors.link ? "center" : "flex-end"}>
|
||||
<Grid align={form.errors.expiration_num ? "center" : "flex-end"}>
|
||||
<Col xs={6}>
|
||||
<NumberInput
|
||||
min={1}
|
||||
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
"totp.button.signIn": "Log ind",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Glemt din adgangskode?",
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "Du vil ikke være i stand til at slette din deling manuelt og se antallet af besøgende.",
|
||||
"upload.modal.expires.never": "aldrig",
|
||||
"upload.modal.expires.never-long": "Udløber aldrig",
|
||||
"upload.modal.expires.error.too-long": "Udløbsdatoen overskrider den maksimalt tilladte udløbsdato på {max}.",
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.expires.label": "Udløb",
|
||||
"upload.modal.expires.minute-singular": "Minut",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Forhåndsvisning ikke understøttet",
|
||||
"share.modal.file-preview.error.not-supported.description": "En forhåndsvisning for thise filtype er ikke understøttet. Download venligst filen for at se den.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Konfiguration",
|
||||
"admin.config.category.general": "Generelt",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Om alle skal kunne oprette en bruger",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Tillad uautoriserede delinger",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Whether unauthenticated users can create shares",
|
||||
"admin.config.share.max-expiration": "Maks. udløb",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "Maks. størrelse",
|
||||
"admin.config.share.max-size.description": "Maksimal filstørrelse i bytes",
|
||||
"admin.config.share.zip-compression-level": "Zip compression level",
|
||||
|
||||
@@ -115,7 +115,7 @@ export default {
|
||||
"account.shares.title.empty": "Es ist so leer hier 👀",
|
||||
"account.shares.description.empty": "Du hast keine Freigaben erstellt.",
|
||||
"account.shares.button.create": "Erstelle eine",
|
||||
"account.shares.info.title": "Teile deine Information",
|
||||
"account.shares.info.title": "Freigabe Informationen",
|
||||
"account.shares.table.id": "ID",
|
||||
"account.shares.table.name": "Name",
|
||||
"account.shares.table.description": "Beschreibung",
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "Du wirst deine Freigabe nicht löschen können oder die Besucheranzahl sehen.",
|
||||
"upload.modal.expires.never": "niemals",
|
||||
"upload.modal.expires.never-long": "Läuft nicht ab",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.expires.label": "Gültig bis",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Vorschau wird nicht unterstützt",
|
||||
"share.modal.file-preview.error.not-supported.description": "Eine Vorschau für diesen Dateityp wird nicht unterstützt. Bitte lade die Datei herunter, um sie anzuzeigen.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Einstellungen",
|
||||
"admin.config.category.general": "Allgemein",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Gibt an, ob eine Registrierung erlaubt ist",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Nicht authentifizierte Freigaben erlauben",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Gibt an, ob nicht authentifizierte Benutzer Freigaben erstellen können",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "Maximale Größe",
|
||||
"admin.config.share.max-size.description": "Maximale Größe einer Freigabe in Bytes",
|
||||
"admin.config.share.zip-compression-level": "Zip Komprimierungsstufe",
|
||||
|
||||
@@ -288,6 +288,7 @@ export default {
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
@@ -357,6 +358,13 @@ export default {
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
@@ -413,6 +421,9 @@ export default {
|
||||
"Allow unauthenticated shares",
|
||||
"admin.config.share.allow-unauthenticated-shares.description":
|
||||
"Whether unauthenticated users can create shares",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description":
|
||||
"Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
"admin.config.share.zip-compression-level": "Zip compression level",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "No podrás eliminar tus compartidos manualmente ni ver el número de visitas.",
|
||||
"upload.modal.expires.never": "nunca",
|
||||
"upload.modal.expires.never-long": "Nunca Expira",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.link.label": "Enlace",
|
||||
"upload.modal.expires.label": "Expiración",
|
||||
"upload.modal.expires.minute-singular": "Minuto",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Vista previa no disponible",
|
||||
"share.modal.file-preview.error.not-supported.description": "La vista previa para este tipo de archivo no está disponible. Por favor descargue el archivo para verlo.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuración",
|
||||
"admin.config.category.general": "General",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Si se permite el registro",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Permitir compartir sin iniciar sesión",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Si los usuarios que no han iniciado sesión pueden compartir",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "Tamaño máximo",
|
||||
"admin.config.share.max-size.description": "Tamaño máximo de los archivos, en bytes",
|
||||
"admin.config.share.zip-compression-level": "Nivel de compresión del Zip",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "Et voi poistaa jakoasi manuaalisesti ja tarkastella kävijöiden määrää.",
|
||||
"upload.modal.expires.never": "ei koskaan",
|
||||
"upload.modal.expires.never-long": "Ei vanhene koskaan",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.link.label": "Linkki",
|
||||
"upload.modal.expires.label": "Vanhentuminen",
|
||||
"upload.modal.expires.minute-singular": "Minuutti",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Esikatselua ei tuettu",
|
||||
"share.modal.file-preview.error.not-supported.description": "Esikatselua thise tiedostotyypille ei tueta. Ole hyvä ja lataa tiedosto nähdäksesi sen.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Asetukset",
|
||||
"admin.config.category.general": "Yleiset",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Onko rekisteröinti sallittu",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Salli anonyymit jaot",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Voiko tunnistamattomat käyttäjät luoda jakoja",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "Maksimikoko",
|
||||
"admin.config.share.max-size.description": "Jaon enimmäiskoko tavuissa (bytes)",
|
||||
"admin.config.share.zip-compression-level": "Zip puristustaso",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Envoyer",
|
||||
"navbar.upload": "Téléverser",
|
||||
"navbar.signin": "Se connecter",
|
||||
"navbar.home": "Accueil",
|
||||
"navbar.signup": "S'inscrire",
|
||||
"navbar.signup": "S’inscrire",
|
||||
"navbar.links.shares": "Mes partages",
|
||||
"navbar.links.reverse": "Partages inversés",
|
||||
"navbar.avatar.account": "Mon compte",
|
||||
@@ -18,22 +18,22 @@ export default {
|
||||
"home.bullet.b.name": "Confidentialité",
|
||||
"home.bullet.b.description": "Vos fichiers sont vos fichiers et ne devraient jamais être mis entre les mains de tiers.",
|
||||
"home.bullet.c.name": "Aucune rébarbative limite de taille",
|
||||
"home.bullet.c.description": "Téléchargez des fichiers volumineux que vous le souhaitez. Seul votre disque dur est la limite.",
|
||||
"home.bullet.c.description": "Téléchargez des fichiers aussi volumineux que vous le souhaitez. Seul votre disque dur est la limite.",
|
||||
"home.button.start": "Commencer",
|
||||
"home.button.source": "Code source",
|
||||
// END /
|
||||
// /auth/signin
|
||||
"signin.title": "Content de vous revoir",
|
||||
"signin.description": "Pas encore de compte ?",
|
||||
"signin.button.signup": "S'inscrire",
|
||||
"signin.input.email-or-username": "Courriel ou pseudo",
|
||||
"signin.input.email-or-username.placeholder": "Votre courriel ou pseudo",
|
||||
"signin.button.signup": "S’inscrire",
|
||||
"signin.input.email-or-username": "Courriel ou surnom",
|
||||
"signin.input.email-or-username.placeholder": "Votre courriel ou surnom",
|
||||
"signin.input.password": "Mot de passe",
|
||||
"signin.input.password.placeholder": "Votre mot de passe",
|
||||
"signin.button.submit": "Se connecter",
|
||||
"signIn.notify.totp-required.title": "Une authentification à deux facteurs est requise",
|
||||
"signIn.notify.totp-required.description": "Veuillez entrer votre code d'authentification à deux facteurs",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.or": "OU",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
@@ -44,20 +44,20 @@ export default {
|
||||
"signup.title": "Créer un compte",
|
||||
"signup.description": "Vous avez déjà un compte ?",
|
||||
"signup.button.signin": "Se connecter",
|
||||
"signup.input.username": "Pseudo",
|
||||
"signup.input.username.placeholder": "Votre pseudo",
|
||||
"signup.input.username": "Surnom",
|
||||
"signup.input.username.placeholder": "Votre surnom",
|
||||
"signup.input.email": "Adresse email",
|
||||
"signup.input.email.placeholder": "Votre adresse email",
|
||||
"signup.input.email.placeholder": "Votre courriel",
|
||||
"signup.button.submit": "Commençons",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
"totp.title": "Authentification TOTP",
|
||||
"totp.button.signIn": "Se connecter",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Mot de passe oublié ?",
|
||||
"resetPassword.description": "Saisissez votre email pour réinitialiser votre mot de passe.",
|
||||
"resetPassword.notify.success": "Un email a été envoyé avec un lien pour réinitialiser votre mot de passe.",
|
||||
"resetPassword.description": "Saisissez votre courriel pour réinitialiser votre mot de passe.",
|
||||
"resetPassword.notify.success": "Un courriel a été envoyé avec un lien pour réinitialiser votre mot de passe.",
|
||||
"resetPassword.button.back": "Retour à la page de connexion",
|
||||
"resetPassword.text.resetPassword": "Réinitialiser le mot de passe",
|
||||
"resetPassword.text.enterNewPassword": "Saisissez votre nouveau mot de passe",
|
||||
@@ -66,30 +66,30 @@ export default {
|
||||
// /account
|
||||
"account.title": "Mon compte",
|
||||
"account.card.info.title": "Détails du compte",
|
||||
"account.card.info.username": "Pseudo",
|
||||
"account.card.info.username": "Surnom",
|
||||
"account.card.info.email": "Adresse email",
|
||||
"account.notify.info.success": "Compte mis à jour avec succès",
|
||||
"account.card.password.title": "Mot de passe",
|
||||
"account.card.password.old": "Ancien mot de passe",
|
||||
"account.card.password.new": "Nouveau mot de passe",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.card.password.noPasswordSet": "Vous n’avez pas de mot de passe défini. Si vous voulez vous connecter avec un e-mail et un mot de passe, vous devez définir un mot de passe.",
|
||||
"account.notify.password.success": "Le mot de passe a été modifié avec succès",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.title": "Identifiant social",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.oauth.link": "Associer",
|
||||
"account.card.oauth.unlink": "Dissocier",
|
||||
"account.card.oauth.unlinked": "Dissocié",
|
||||
"account.modal.unlink.title": "Dissocier le compte",
|
||||
"account.modal.unlink.description": "Dissocier vos comptes de réseaux sociaux peut vous faire perdre votre compte si jamais vous ne vous souvenez pas de votre nom d’utilisateur et de votre mot de passe.",
|
||||
"account.notify.oauth.unlinked.success": "Dissocié avec succès",
|
||||
"account.card.security.title": "Sécurité",
|
||||
"account.card.security.totp.enable.description": "Entrez votre mot de passe actuel pour commencer à activer TOTP",
|
||||
"account.card.security.totp.disable.description": "Entrez votre mot de passe pour désactiver TOTP",
|
||||
"account.card.security.totp.button.start": "Commencer",
|
||||
"account.card.security.totp.enable.description": "Entrez votre mot de passe actuel pour activer TOTP",
|
||||
"account.card.security.totp.disable.description": "Entrez votre mot de passe actuel pour désactiver TOTP",
|
||||
"account.card.security.totp.button.start": "Démarrer",
|
||||
"account.modal.totp.title": "Activer TOTP",
|
||||
"account.modal.totp.step1": "Étape 1 : Ajouter votre authentificateur",
|
||||
"account.modal.totp.step2": "Étape 2 : Valider votre code",
|
||||
@@ -97,8 +97,8 @@ export default {
|
||||
"account.modal.totp.code": "Code",
|
||||
"account.modal.totp.clickToCopy": "Cliquez pour copier",
|
||||
"account.modal.totp.verify": "Vérifier",
|
||||
"account.notify.totp.disable": "TOTP désactivé",
|
||||
"account.notify.totp.enable": "TOTP activé",
|
||||
"account.notify.totp.disable": "TOTP désactivé avec succès",
|
||||
"account.notify.totp.enable": "TOTP activé avec succès",
|
||||
"account.card.language.title": "Langue",
|
||||
"account.card.language.description": "Le projet est traduit par la communauté. Certaines traductions peuvent être incomplètes.",
|
||||
"account.card.color.title": "Thème de couleurs",
|
||||
@@ -131,8 +131,8 @@ export default {
|
||||
// /account/reverseShares
|
||||
"account.reverseShares.title": "Partages inversés",
|
||||
"account.reverseShares.description": "Un partage inversé vous permet de générer une URL unique qui permet à des utilisateurs externes de créer un partage.",
|
||||
"account.reverseShares.title.empty": "Il n'y a rien ici 👀",
|
||||
"account.reverseShares.description.empty": "Vous n'avez aucun partage inversé.",
|
||||
"account.reverseShares.title.empty": "C’est plutôt vide 👀",
|
||||
"account.reverseShares.description.empty": "Vous n’avez aucun partage inversé.",
|
||||
// showCreateReverseShareModal.tsx
|
||||
"account.reverseShares.modal.title": "Créer un partage inversé",
|
||||
"account.reverseShares.modal.expiration.label": "Expiration",
|
||||
@@ -149,8 +149,8 @@ export default {
|
||||
"account.reverseShares.modal.expiration.year-singular": "An",
|
||||
"account.reverseShares.modal.expiration.year-plural": "Ans",
|
||||
"account.reverseShares.modal.max-size.label": "Taille maximale du partage",
|
||||
"account.reverseShares.modal.send-email": "Envoyer un email de notification",
|
||||
"account.reverseShares.modal.send-email.description": "Envoyer une notification par email lorsqu'un partage est créé depuis ce partage inversé.",
|
||||
"account.reverseShares.modal.send-email": "Envoyer un courriel de notification",
|
||||
"account.reverseShares.modal.send-email.description": "Envoyer une notification par courriel lorsqu'un partage est créé depuis ce partage inversé.",
|
||||
"account.reverseShares.modal.max-use.label": "Nombre d'utilisation max",
|
||||
"account.reverseShares.modal.max-use.description": "Le nombre maximal de fois que cette URL peut être utilisée pour créer un partage.",
|
||||
"account.reverseShare.never-expires": "Ce partage inversé n'expirera jamais.",
|
||||
@@ -161,10 +161,10 @@ export default {
|
||||
"account.reverseShares.table.shares": "Partages",
|
||||
"account.reverseShares.table.remaining": "Utilisations restantes",
|
||||
"account.reverseShares.table.max-size": "Taille maximale du partage",
|
||||
"account.reverseShares.table.expires": "Expire dans",
|
||||
"account.reverseShares.table.expires": "Expire le",
|
||||
"account.reverseShares.modal.reverse-share-link": "Lien du partage inversé",
|
||||
"account.reverseShares.modal.delete.title": "Supprimer le partage inversé",
|
||||
"account.reverseShares.modal.delete.description": "Voulez-vous vraiment supprimer ce partage inversé ? Si vous le faites, les partages qu'il contient seront également supprimés.",
|
||||
"account.reverseShares.modal.delete.description": "Voulez-vous vraiment supprimer ce partage inversé ? Si vous le faites, les partages qu’il contient seront également supprimés.",
|
||||
// END /account/reverseShares
|
||||
// /admin
|
||||
"admin.title": "Administration",
|
||||
@@ -174,11 +174,11 @@ export default {
|
||||
// END /admin
|
||||
// /admin/users
|
||||
"admin.users.title": "Gestion des utilisateurs",
|
||||
"admin.users.table.username": "Pseudo",
|
||||
"admin.users.table.email": "Adresse email",
|
||||
"admin.users.table.username": "Surnom",
|
||||
"admin.users.table.email": "Courriel",
|
||||
"admin.users.table.admin": "Admin",
|
||||
"admin.users.edit.update.title": "Modifier l'utilisateur {username}",
|
||||
"admin.users.edit.update.admin-privileges": "Privilèges admin",
|
||||
"admin.users.edit.update.admin-privileges": "Privilèges d’admin",
|
||||
"admin.users.edit.update.change-password.title": "Changer le mot de passe",
|
||||
"admin.users.edit.update.change-password.field": "Nouveau mot de passe",
|
||||
"admin.users.edit.update.change-password.button": "Enregistrer le nouveau mot de passe",
|
||||
@@ -188,17 +188,17 @@ export default {
|
||||
// showCreateUserModal.tsx
|
||||
"admin.users.modal.create.title": "Créer un utilisateur",
|
||||
"admin.users.modal.create.username": "Surnom",
|
||||
"admin.users.modal.create.email": "Email",
|
||||
"admin.users.modal.create.email": "Courriel",
|
||||
"admin.users.modal.create.password": "Mot de passe",
|
||||
"admin.users.modal.create.manual-password": "Définir le mot de passe manuellement",
|
||||
"admin.users.modal.create.manual-password.description": "S'il n'est pas coché, l'utilisateur recevra un email avec un lien pour définir son mot de passe.",
|
||||
"admin.users.modal.create.admin": "Privilèges admin",
|
||||
"admin.users.modal.create.admin.description": "Si coché, l'utilisateur pourra accéder au panneau d'administration.",
|
||||
"admin.users.modal.create.manual-password.description": "S’il n'est pas coché, l’utilisateur recevra un email avec un lien pour définir son mot de passe.",
|
||||
"admin.users.modal.create.admin": "Privilèges d’admin",
|
||||
"admin.users.modal.create.admin.description": "Si coché, l’utilisateur pourra accéder au panneau d'administration.",
|
||||
// END /admin/users
|
||||
// /upload
|
||||
"upload.title": "Envoyer",
|
||||
"upload.notify.generic-error": "Une erreur est survenue durant le traitement de votre partage.",
|
||||
"upload.notify.count-failed": "{count} fichier(s) n'a(ont) pas pu être envoyé(s). Veuillez réessayer.",
|
||||
"upload.notify.count-failed": "{count} fichier(s) n’a(ont) pas pu être envoyé(s). Veuillez réessayer.",
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Téléverser des fichiers",
|
||||
"upload.dropzone.description": "Glissez-déposez des fichiers ici pour commencer votre partage. Ils ne peuvent avoir une taille supérieur à {maxSize} au total.",
|
||||
@@ -213,7 +213,8 @@ export default {
|
||||
"upload.modal.not-signed-in": "Vous n'êtes pas connecté",
|
||||
"upload.modal.not-signed-in-description": "Vous ne pourrez pas supprimer votre partage manuellement et afficher le nombre de visiteurs.",
|
||||
"upload.modal.expires.never": "jamais",
|
||||
"upload.modal.expires.never-long": "N'expire jamais",
|
||||
"upload.modal.expires.never-long": "N’expire jamais",
|
||||
"upload.modal.expires.error.too-long": "L’expiration dépasse la date d'expiration du {max}.",
|
||||
"upload.modal.link.label": "Lien",
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
@@ -232,25 +233,25 @@ export default {
|
||||
"upload.modal.accordion.description.placeholder": "Note pour les destinataires de ce partage",
|
||||
"upload.modal.accordion.email.title": "Adresse courriel des destinataires",
|
||||
"upload.modal.accordion.email.placeholder": "Saisir les destinataires de ce partage",
|
||||
"upload.modal.accordion.email.invalid-email": "Adresse email invalide",
|
||||
"upload.modal.accordion.email.invalid-email": "Courriel invalide",
|
||||
"upload.modal.accordion.security.title": "Options de sécurité",
|
||||
"upload.modal.accordion.security.password.label": "Protection par mot de passe",
|
||||
"upload.modal.accordion.security.password.placeholder": "Aucun mot de passe",
|
||||
"upload.modal.accordion.security.max-views.label": "Nombre de vues maximum",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "Aucune limite",
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "Ce partage n'expirera jamais.",
|
||||
"upload.modal.completed.never-expires": "Ce partage n’expirera jamais.",
|
||||
"upload.modal.completed.expires-on": "Ce partage expirera le {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Partage prêt",
|
||||
// END /upload
|
||||
// /share/[id]
|
||||
"share.title": "Partage {shareId}",
|
||||
"share.description": "Regardez ce que j'ai partagé !",
|
||||
"share.description": "Regardez ce que j’ai partagé !",
|
||||
"share.error.visitor-limit-exceeded.title": "Limite de visiteurs dépassée",
|
||||
"share.error.visitor-limit-exceeded.description": "La limite de visiteurs de ce partage a été dépassée.",
|
||||
"share.error.removed.title": "Partage supprimé",
|
||||
"share.error.not-found.title": "Partage introuvable",
|
||||
"share.error.not-found.description": "Le partage que vous cherchez n'existe pas.",
|
||||
"share.error.not-found.description": "Le partage que vous cherchez n’existe pas.",
|
||||
"share.modal.password.title": "Mot de passe requis",
|
||||
"share.modal.password.description": "Pour accéder à ce partage, veuillez entrer le mot de passe du partage.",
|
||||
"share.modal.password": "Mot de passe",
|
||||
@@ -263,113 +264,121 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Aperçu non supporté",
|
||||
"share.modal.file-preview.error.not-supported.description": "Un aperçu pour ce type de fichier n'est pas pris en charge. Veuillez télécharger le fichier pour le voir.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Paramètres",
|
||||
"admin.config.category.general": "Général",
|
||||
"admin.config.category.share": "Partage",
|
||||
"admin.config.category.email": "Courriel",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "Nom de l'appli",
|
||||
"admin.config.general.app-name.description": "Le nom de l'application",
|
||||
"admin.config.category.oauth": "Identifiant social",
|
||||
"admin.config.general.app-name": "Nom de l’appli",
|
||||
"admin.config.general.app-name.description": "Le nom de l’application",
|
||||
"admin.config.general.app-url": "URL de l’appli",
|
||||
"admin.config.general.app-url.description": "Depuis quel URL le partage Pingvin est disponible",
|
||||
"admin.config.general.show-home-page": "Afficher la page d'accueil",
|
||||
"admin.config.general.show-home-page.description": "Afficher ou non la page d'accueil",
|
||||
"admin.config.general.show-home-page": "Afficher la page d’accueil",
|
||||
"admin.config.general.show-home-page.description": "Afficher ou non la page d’accueil",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description": "Changez de logo en envoyant une nouvelle image. L'image doit être au format PNG et doit avoir un ratio 1:1.",
|
||||
"admin.config.general.logo.description": "Changez de logo en envoyant une nouvelle image. L’image doit être au format PNG et doit avoir un ratio 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Sélectionner une image",
|
||||
"admin.config.email.enable-share-email-recipients": "Autoriser le partage par courriel",
|
||||
"admin.config.email.enable-share-email-recipients.description": "Permet d'envoyer le lien du partage par courriel. N'activez cette option que si vous avez activé SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Sujet d'un partage",
|
||||
"admin.config.email.share-recipients-subject.description": "Intitulé du courriel envoyé aux destinataires d'un partage.",
|
||||
"admin.config.email.share-recipients-message": "Message d'un partage",
|
||||
"admin.config.email.share-recipients-message.description": "Contenu du courriel qui est envoyé aux destinataires du partage. Variables possibles :\n• {creator} : Le pseudo de l'auteur du partage\n• {shareUrl} : L'URL du partage\n• {desc} : La description du partage\n• {expires} : La date d'expiration du partage\nLes variables seront remplacées par leur valeur réelle.",
|
||||
"admin.config.email.reverse-share-subject": "Sujet d'un partage inversé",
|
||||
"admin.config.email.reverse-share-subject.description": "Intitulé du courriel envoyé lorsque quelqu'un a partagé des fichiers depuis votre partage inversé.",
|
||||
"admin.config.email.reverse-share-message": "Message du partage inversé",
|
||||
"admin.config.email.reverse-share-message.description": "Contenu du courriel envoyé lorsque quelqu'un partage des fichiers depuis votre partage inversé. {shareUrl} sera remplacé par le nom du créateur et l'URL de partage.",
|
||||
"admin.config.email.reset-password-subject": "Sujet d'une réinitialisation du mot de passe",
|
||||
"admin.config.email.reset-password-subject.description": "Intitulé du courriel envoyé lorsqu'un utilisateur demande une réinitialisation de son mot de passe.",
|
||||
"admin.config.email.share-recipients-subject": "Sujet d’un partage",
|
||||
"admin.config.email.share-recipients-subject.description": "Intitulé du courriel envoyé aux destinataires d’un partage.",
|
||||
"admin.config.email.share-recipients-message": "Message d’un partage",
|
||||
"admin.config.email.share-recipients-message.description": "Contenu du courriel qui est envoyé aux destinataires du partage. Variables possibles :\n• {creator} : Le pseudo de l’auteur du partage\n• {shareUrl} : L’URL du partage\n• {desc} : La description du partage\n• {expires} : La date d'expiration du partage\nLes variables seront remplacées par leur valeur réelle.",
|
||||
"admin.config.email.reverse-share-subject": "Sujet d’un partage inversé",
|
||||
"admin.config.email.reverse-share-subject.description": "Intitulé du courriel envoyé lorsque quelqu’un a partagé des fichiers depuis votre partage inversé.",
|
||||
"admin.config.email.reverse-share-message": "Message d’un partage inversé",
|
||||
"admin.config.email.reverse-share-message.description": "Contenu du courriel envoyé lorsque quelqu’un partage des fichiers depuis votre partage inversé. {shareUrl} sera remplacé par le nom du créateur et l’URL de partage.",
|
||||
"admin.config.email.reset-password-subject": "Sujet d’une réinitialisation du mot de passe",
|
||||
"admin.config.email.reset-password-subject.description": "Intitulé du courriel envoyé lorsqu’un utilisateur demande une réinitialisation de son mot de passe.",
|
||||
"admin.config.email.reset-password-message": "Message de réinitialisation du mot de passe",
|
||||
"admin.config.email.reset-password-message.description": "Contenu du courriel envoyé lorsqu'un utilisateur demande à réinitialiser son mot de passe. {url} sera remplacé par l'URL de réinitialisation du mot de passe.",
|
||||
"admin.config.email.invite-subject": "Sujet d'une invitation",
|
||||
"admin.config.email.invite-subject.description": "Intitulé du courriel envoyé lorsqu'un administrateur invite un utilisateur.",
|
||||
"admin.config.email.invite-message": "Message de l'invitation",
|
||||
"admin.config.email.invite-message.description": "Contenu du courriel envoyé lorsqu'un administrateur invite un utilisateur. {url} sera remplacé par l'URL d'invitation et {password} par le mot de passe.",
|
||||
"admin.config.email.reset-password-message.description": "Contenu du courriel envoyé lorsqu’un utilisateur demande à réinitialiser son mot de passe. {url} sera remplacé par l’URL de réinitialisation du mot de passe.",
|
||||
"admin.config.email.invite-subject": "Sujet d’une invitation",
|
||||
"admin.config.email.invite-subject.description": "Intitulé du courriel envoyé lorsqu’un administrateur invite un utilisateur.",
|
||||
"admin.config.email.invite-message": "Message d’une invitation",
|
||||
"admin.config.email.invite-message.description": "Contenu du courriel envoyé lorsqu’un administrateur invite un utilisateur. {url} sera remplacé par l’URL d'invitation et {password} par le mot de passe.",
|
||||
"admin.config.share.allow-registration": "Autoriser les inscriptions",
|
||||
"admin.config.share.allow-registration.description": "Permet aux visiteurs de créer un compte.",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Autoriser les partages anonymes",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Autorise des utilisateurs non authentifiés à créer des partages",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Permet aux visiteurs de créer des partages",
|
||||
"admin.config.share.max-expiration": "Échéance",
|
||||
"admin.config.share.max-expiration.description": "Échéance du partage en heures. Indiquez 0 pour qu’il n’expire jamais.",
|
||||
"admin.config.share.max-size": "Taille max",
|
||||
"admin.config.share.max-size.description": "Taille maximale du partage en octets",
|
||||
"admin.config.share.zip-compression-level": "Niveau de compression",
|
||||
"admin.config.share.zip-compression-level.description": "Ajustez le niveau pour trouver l'équilibre entre la taille du fichier et la vitesse de compression. Les valeurs valides vont de 0 à 9, 0 étant sans compression et 9 étant la compression maximale. ",
|
||||
"admin.config.smtp.enabled": "Activer",
|
||||
"admin.config.smtp.enabled.description": "Active SMTP. Activez ceci uniquement si vous avez saisi l'hôte, le port, le courriel, l'utilisateur et le mot de passe de votre serveur SMTP.",
|
||||
"admin.config.smtp.enabled.description": "Active SMTP. Activez ceci uniquement si vous avez saisi l’hôte, le port, le courriel, l’utilisateur et son mot de passe, de votre serveur SMTP.",
|
||||
"admin.config.smtp.host": "Hôte",
|
||||
"admin.config.smtp.host.description": "Nom du serveur SMTP",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port du serveur SMTP",
|
||||
"admin.config.smtp.email": "Adresse email",
|
||||
"admin.config.smtp.email.description": "Adresse à partir de laquelle les emails sont envoyés",
|
||||
"admin.config.smtp.username": "Nom d'utilisateur",
|
||||
"admin.config.smtp.username.description": "Nom d'utilisateur du serveur SMTP",
|
||||
"admin.config.smtp.email": "Courriel",
|
||||
"admin.config.smtp.email.description": "Adresse à partir de laquelle les courriels sont envoyés",
|
||||
"admin.config.smtp.username": "Nom d’utilisateur",
|
||||
"admin.config.smtp.username.description": "Nom d’utilisateur du serveur SMTP",
|
||||
"admin.config.smtp.password": "Mot de passe",
|
||||
"admin.config.smtp.password.description": "Mot de passe du serveur SMTP",
|
||||
"admin.config.smtp.button.test": "Envoyer un email de test",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.smtp.button.test": "Envoyer un courriel de test",
|
||||
"admin.config.oauth.allow-registration": "Autoriser l’inscription",
|
||||
"admin.config.oauth.allow-registration.description": "Permettre aux utilisateurs de s’inscrire via leur identifiant social",
|
||||
"admin.config.oauth.ignore-totp": "Ignorer TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Ignorer le TOTP lorsque l’utilisateur utilise un identifiant social.",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-enabled.description": "Permettre la connexion via GitHub.",
|
||||
"admin.config.oauth.github-client-id": "ID client de GitHub",
|
||||
"admin.config.oauth.github-client-id.description": "L’ID du client de l’application OAuth GitHub",
|
||||
"admin.config.oauth.github-client-secret": "Secret du client GitHub",
|
||||
"admin.config.oauth.github-client-secret.description": "Le secret du client de l’application OAuth GitHub",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.google-enabled.description": "Permettre la connexion via Google.",
|
||||
"admin.config.oauth.google-client-id": "ID du client Google",
|
||||
"admin.config.oauth.google-client-id.description": "L’ID du client de l’application OAuth Google",
|
||||
"admin.config.oauth.google-client-secret": "Secret client de Google",
|
||||
"admin.config.oauth.google-client-secret.description": "Le secret du client de l’application OAuth Google",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Permettre la connexion via Microsoft.",
|
||||
"admin.config.oauth.microsoft-tenant": "Locataire Microsoft",
|
||||
"admin.config.oauth.microsoft-tenant.description": "ID locataire de l'application OAuth Microsoft\nCommun : les utilisateurs ayant au choix un compte personnel ou d’entreprise issue de Microsoft Entra.\nOrganisations : limité aux utilisateurs ayant un compte d’entreprise ou d’université issue de Microsoft Entra.\nPersonnel : limité aux utilisateurs ayant un compte personnel\nDomanial : limité aux utilisateurs d'un domaine Microsoft Entra spécifié ou d’un ID locataire (au format GUID), qu’ils soient membres d’un registre d’entreprise ou d’université ou bien enregistrés en tant qu’invités avec un compte personnel.",
|
||||
"admin.config.oauth.microsoft-client-id": "ID du client Microsoft",
|
||||
"admin.config.oauth.microsoft-client-id.description": "L’ID du client de l’application Microsoft OAuth",
|
||||
"admin.config.oauth.microsoft-client-secret": "Secret du client Microsoft",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Le secret du client de l’application Microsoft OAuth",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-enabled.description": "Permettre la connexion via Discord.",
|
||||
"admin.config.oauth.discord-client-id": "ID du client Discord",
|
||||
"admin.config.oauth.discord-client-id.description": "L’ID du client de l’application OAuth Discord",
|
||||
"admin.config.oauth.discord-client-secret": "Secret du client Discord",
|
||||
"admin.config.oauth.discord-client-secret.description": "Le secret du client de l’application OAuth Discord",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-enabled.description": "Permettre la connexion via OpenID.",
|
||||
"admin.config.oauth.oidc-discovery-uri": "URI de découverte OpenID",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "URI de découverte de l’application OpenID OAuth",
|
||||
"admin.config.oauth.oidc-client-id": "ID du client OpenID",
|
||||
"admin.config.oauth.oidc-client-id.description": "L’ID du client de l’application OAuth OpenID",
|
||||
"admin.config.oauth.oidc-client-secret": "Secret du client OpenID",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Le secret du client de l’application OAuth OpenID",
|
||||
// 404
|
||||
"404.description": "Désolé, mais cette page n’existe pas.",
|
||||
"404.button.home": "Retour à l'accueil",
|
||||
"404.button.home": "Retour à l’accueil",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.title": "Erreur",
|
||||
"error.description": "Oups !",
|
||||
"error.button.back": "Retour",
|
||||
"error.msg.default": "Quelque chose a mal tourné.",
|
||||
"error.msg.access_denied": "Vous avez annulé le processus d’authentification, veuillez réessayer.",
|
||||
"error.msg.expired_token": "Le processus d’authentification a pris trop de temps, veuillez réessayer.",
|
||||
"error.msg.no_user": "L’utilisateur associé au compte {0} n’existe pas.",
|
||||
"error.msg.no_email": "Impossible d’obtenir le courriel du compte {0}.",
|
||||
"error.msg.already_linked": "Le compte {0} est déjà associé à un autre compte.",
|
||||
"error.msg.not_linked": "Le compte {0} n’est pas encore associé à compte.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "共有の手動削除と訪問者カウンターは表示できません。",
|
||||
"upload.modal.expires.never": "永久",
|
||||
"upload.modal.expires.never-long": "期限切れにさせない",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.link.label": "リンク",
|
||||
"upload.modal.expires.label": "有効期限",
|
||||
"upload.modal.expires.minute-singular": "分間",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "プレビューに対応していません",
|
||||
"share.modal.file-preview.error.not-supported.description": "これらのファイルのプレビューには対応していません。ファイルをダウンロードして、直接確認してください。",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "設定",
|
||||
"admin.config.category.general": "一般",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "登録を許可するかどうかを選択してください。",
|
||||
"admin.config.share.allow-unauthenticated-shares": "ログインしていない状態での共有を許可する",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "ログインしていないユーザーに共有の作成を許可するかどうかを選択してください。",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "最大ファイルサイズ",
|
||||
"admin.config.share.max-size.description": "最大ファイルサイズ(byte単位)",
|
||||
"admin.config.share.zip-compression-level": "Zip圧縮レベル",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "You will be unable to delete your share manually and view the visitor count.",
|
||||
"upload.modal.expires.never": "nooit",
|
||||
"upload.modal.expires.never-long": "Verloopt nooit",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.link.label": "Koppeling",
|
||||
"upload.modal.expires.label": "Vervaldatum",
|
||||
"upload.modal.expires.minute-singular": "Minuut",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Voorbeeld niet ondersteund",
|
||||
"share.modal.file-preview.error.not-supported.description": "Een voorbeeld voor dit bestandstype wordt niet ondersteund. Download het bestand om het te bekijken.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuratie",
|
||||
"admin.config.category.general": "Algemeen",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Of registratie is toegestaan",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Allow unauthenticated shares",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Whether unauthenticated users can create shares",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "Max grootte",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
"admin.config.share.zip-compression-level": "Zip compressie niveau",
|
||||
|
||||
@@ -32,8 +32,8 @@ export default {
|
||||
"signin.input.password.placeholder": "Twoje hasło",
|
||||
"signin.button.submit": "Zaloguj się",
|
||||
"signIn.notify.totp-required.title": "Wymagane jest uwierzytelnianie dwuetapowe",
|
||||
"signIn.notify.totp-required.description": "Podaj kod logowania dwuetapowego",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.notify.totp-required.description": "Wprowadź kod uwierzytelniania dwuetapowego",
|
||||
"signIn.oauth.or": "LUB",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
@@ -51,8 +51,8 @@ export default {
|
||||
"signup.button.submit": "Zaczynajmy",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
"totp.title": "Uwierzytelnianie TOTP",
|
||||
"totp.button.signIn": "Zaloguj się",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Nie pamiętasz hasła?",
|
||||
@@ -72,20 +72,20 @@ export default {
|
||||
"account.card.password.title": "Hasło",
|
||||
"account.card.password.old": "Dotychczasowe hasło",
|
||||
"account.card.password.new": "Nowe hasło",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.card.password.noPasswordSet": "Nie masz ustawionego hasła. Jeśli chcesz zalogować się za pomocą adresu e-mail i hasła, musisz ustawić hasło.",
|
||||
"account.notify.password.success": "Hasło zostało pomyślnie zmienione",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.title": "Logowanie za pomocą konta społecznościowego",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.oauth.link": "Połącz",
|
||||
"account.card.oauth.unlink": "Odłącz",
|
||||
"account.card.oauth.unlinked": "Rozłączono",
|
||||
"account.modal.unlink.title": "Odłącz konto",
|
||||
"account.modal.unlink.description": "Odłączenie kont społecznościowych, jeśli nie pamiętasz nazwy użytkownika i hasła, może spowodować utratę konta.",
|
||||
"account.notify.oauth.unlinked.success": "Odłączono pomyślnie",
|
||||
"account.card.security.title": "Zabezpieczenia",
|
||||
"account.card.security.totp.enable.description": "Wprowadź aktualne hasło, aby móc włączyć TOTP",
|
||||
"account.card.security.totp.disable.description": "Wprowadź aktualne hasło, aby wyłączyć TOTP",
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "Nie możesz ręcznie usunąć swojego udziału ani wyświetlić licznika odwiedzających.",
|
||||
"upload.modal.expires.never": "nigdy",
|
||||
"upload.modal.expires.never-long": "Nigdy nie wygasa",
|
||||
"upload.modal.expires.error.too-long": "Termin ważności przekracza maksymalną datę wygaśnięcia {max}.",
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.expires.label": "Wygasanie",
|
||||
"upload.modal.expires.minute-singular": "Minuta",
|
||||
@@ -263,13 +264,19 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Podgląd nie jest obsługiwany",
|
||||
"share.modal.file-preview.error.not-supported.description": "Podgląd dla tego typu pliku nie jest obsługiwany. Pobierz plik, aby go zobaczyć.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edytuj {shareId}",
|
||||
"share.edit.append-upload": "Dołącz plik",
|
||||
"share.edit.notify.generic-error": "W trakcie zakańczania tworzenia udziału wystąpił błąd.",
|
||||
"share.edit.notify.save-success": "Udział zaktualizowany pomyślnie",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Konfiguracja",
|
||||
"admin.config.category.general": "Ogólne",
|
||||
"admin.config.category.share": "Udostępnij",
|
||||
"admin.config.category.email": "Adres e-mail",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.category.oauth": "Logowanie za pomocą konta społecznościowego",
|
||||
"admin.config.general.app-name": "Nazwa aplikacji",
|
||||
"admin.config.general.app-name.description": "Nazwa aplikacji",
|
||||
"admin.config.general.app-url": "Adres URL aplikacji",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Czy dozwolona jest rejestracja",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Zezwalaj na nieuwierzytelnione udostępnianie",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Czy nieautoryzowani użytkownicy mogą tworzyć udostępnienia",
|
||||
"admin.config.share.max-expiration": "Maksymalny okres ważności",
|
||||
"admin.config.share.max-expiration.description": "Maksymalny okres ważności udziału w godzinach. Ustaw na 0, aby zezwolić na nieograniczony okres ważności.",
|
||||
"admin.config.share.max-size": "Rozmiar maksymalny",
|
||||
"admin.config.share.max-size.description": "Maksymalny rozmiar udziału w bajtach",
|
||||
"admin.config.share.zip-compression-level": "Poziom kompresji Zip",
|
||||
@@ -318,58 +327,58 @@ export default {
|
||||
"admin.config.smtp.password": "Hasło",
|
||||
"admin.config.smtp.password.description": "Hasło serwera SMTP",
|
||||
"admin.config.smtp.button.test": "Wyślij testowego e-maila",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.allow-registration": "Zezwól na rejestrację",
|
||||
"admin.config.oauth.allow-registration.description": "Zezwalaj użytkownikom na rejestrację za pomocą konta społecznościowego",
|
||||
"admin.config.oauth.ignore-totp": "Ignoruj TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Czy zignorować TOTP, kiedy użytkownik loguje się za pomocą konta społecznościowego",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-enabled.description": "Czy login na GitHub jest włączony",
|
||||
"admin.config.oauth.github-client-id": "ID klienta GitHub",
|
||||
"admin.config.oauth.github-client-id.description": "ID klienta aplikacji GitHub OAuth",
|
||||
"admin.config.oauth.github-client-secret": "Sekret klienta GitHub",
|
||||
"admin.config.oauth.github-client-secret.description": "Sekret klienta aplikacji GitHub OAuth",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.google-enabled.description": "Czy jest włączony login na GitHub",
|
||||
"admin.config.oauth.google-client-id": "ID klienta Google",
|
||||
"admin.config.oauth.google-client-id.description": "ID klienta aplikacji GitHub OAuth",
|
||||
"admin.config.oauth.google-client-secret": "Sekret klienta Google",
|
||||
"admin.config.oauth.google-client-secret.description": "Sekret klienta aplikacji Google OAuth",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Czy jest włączony login na GitHub",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID aplikacji Microsoft OAuth\nogólnie: Użytkownicy zarówno z osobistym kontem Microsoft, jak i kontem do pracy lub szkoły z Microsoft Entra ID mogą się zalogować do aplikacji. organizacje: Zalogować do aplikacji mogą tylko użytkownicy z kontami pracowniczymi lub szkolnymi z Microsoft Entra ID.\nkonsumenci: Zalogować się do aplikacji mogą tylko użytkownicy z osobistym kontem Microsoft.\nNazwa domeny lokatora Microsoft Entra lub identyfikator lokatora w formacie GUID: Zalogować się do aplikacji mogą tylko użytkownicy określonego najemcy Microsoft Entra (z listy członków na koncie służbowym lub szkolnym albo z listy gości na koncie osobistym Microsoft).",
|
||||
"admin.config.oauth.microsoft-client-id": "ID klienta Microsoft",
|
||||
"admin.config.oauth.microsoft-client-id.description": "ID klienta aplikacji Microsoft OAuth",
|
||||
"admin.config.oauth.microsoft-client-secret": "Sekret klienta Microsoft",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Sekret klienta aplikacji Microsoft OAuth",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-enabled.description": "Czy jest włączony login na Discord",
|
||||
"admin.config.oauth.discord-client-id": "ID klienta Discord",
|
||||
"admin.config.oauth.discord-client-id.description": "ID klienta aplikacji Discord OAuth",
|
||||
"admin.config.oauth.discord-client-secret": "Sekret klienta Discord",
|
||||
"admin.config.oauth.discord-client-secret.description": "Sekret klienta aplikacji Discord OAuth",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-enabled.description": "Czy jest włączony login OpenID",
|
||||
"admin.config.oauth.oidc-discovery-uri": "Wykrywanie URI OpenID",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Wykrywanie URI aplikacji OpenID OAuth",
|
||||
"admin.config.oauth.oidc-client-id": "ID klienta OpenID",
|
||||
"admin.config.oauth.oidc-client-id.description": "ID klienta aplikacji OpenID OAuth",
|
||||
"admin.config.oauth.oidc-client-secret": "Sekret klienta OpenID",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Sekret klienta aplikacji Discord OAuth",
|
||||
// 404
|
||||
"404.description": "Ups! Ta strona nie istnieje.",
|
||||
"404.button.home": "Wróć do strony domowej",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.title": "Błąd",
|
||||
"error.description": "Ups!",
|
||||
"error.button.back": "Wróć",
|
||||
"error.msg.default": "Coś poszło nie tak.",
|
||||
"error.msg.access_denied": "Anulowałeś proces uwierzytelniania, spróbuj ponownie.",
|
||||
"error.msg.expired_token": "Proces uwierzytelniania trwał zbyt długo, spróbuj ponownie.",
|
||||
"error.msg.no_user": "Użytkownik powiązany z tym kontem {0} nie istnieje.",
|
||||
"error.msg.no_email": "Nie można pobrać adresu e-mail z tego konta {0}.",
|
||||
"error.msg.already_linked": "To konto {0} zostało już połączone z innym kontem.",
|
||||
"error.msg.not_linked": "To konto {0} nie zostało jeszcze połączone z żadnym kontem.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "Você não poderá excluir seu compartilhamento manualmente e visualizar a contagem de visitantes.",
|
||||
"upload.modal.expires.never": "nunca",
|
||||
"upload.modal.expires.never-long": "Nunca expira",
|
||||
"upload.modal.expires.error.too-long": "Expiração excede a data de expiração máxima de {max}.",
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.expires.label": "Expiração",
|
||||
"upload.modal.expires.minute-singular": "Minuto",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Visualização não suportada",
|
||||
"share.modal.file-preview.error.not-supported.description": "Uma visualização para este tipo de arquivo não é suportada. Faça o download do arquivo para visualizá-lo.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Editar {shareId}",
|
||||
"share.edit.append-upload": "Anexar arquivo",
|
||||
"share.edit.notify.generic-error": "Ocorreu um erro ao terminar seu compartilhamento.",
|
||||
"share.edit.notify.save-success": "Compartilhamento atualizado com sucesso",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuração",
|
||||
"admin.config.category.general": "Geral",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Se o registro é permitido",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Permitir compartilhamentos sem autenticação",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Se usuários não autenticados podem criar compartilhamentos",
|
||||
"admin.config.share.max-expiration": "Expiração máxima",
|
||||
"admin.config.share.max-expiration.description": "Validade máxima de ações em horas. Defina 0 para permitir expiração ilimitada.",
|
||||
"admin.config.share.max-size": "Tamanho máximo",
|
||||
"admin.config.share.max-size.description": "Tamanho máximo do compartilhamento em bytes",
|
||||
"admin.config.share.zip-compression-level": "Nível de compressão",
|
||||
@@ -333,43 +342,43 @@ export default {
|
||||
"admin.config.oauth.google-client-id": "Client ID do Google",
|
||||
"admin.config.oauth.google-client-id.description": "ID do cliente do aplicativo GitHub OAuth",
|
||||
"admin.config.oauth.google-client-secret": "Client Secret do Google",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret do aplicativo GitHub OAuth",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Se o Microsoft login está habilitado",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-tenant.description": "O ID do Tenant do app Microsoft OAuth\ncomum: Usuários com uma conta pessoal da Microsoft e uma conta de trabalho ou escola do Microsoft Entra podem entrar no aplicativo. organizações: somente usuários com contas de trabalho ou de escola do Microsoft Entra ID podem entrar no aplicativo.\nconsumidores: Somente usuários com uma conta pessoal da Microsoft podem acessar o aplicativo.\nnome de domínio do tenant Microsoft Entra ou do ID do inquilino no formato GUID: Somente usuários de um tenant Microsoft Entra específico (membros do diretório com uma conta de trabalho ou de diretório com uma conta pessoal da Microsoft) podem entrar no aplicativo.",
|
||||
"admin.config.oauth.microsoft-client-id": "ID do Cliente Microsoft",
|
||||
"admin.config.oauth.microsoft-client-id.description": "ID do cliente do aplicativo Microsoft OAuth",
|
||||
"admin.config.oauth.microsoft-client-secret": "Segredo do Cliente Microsoft",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret do aplicativo Microsoft OAuth",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-enabled.description": "Se o login do Discord está habilitado",
|
||||
"admin.config.oauth.discord-client-id": "ID do Cliente Discord",
|
||||
"admin.config.oauth.discord-client-id.description": "ID do cliente do aplicativo Discord OAuth",
|
||||
"admin.config.oauth.discord-client-secret": "Segredo do Cliente Discord",
|
||||
"admin.config.oauth.discord-client-secret.description": "ID do cliente do aplicativo Discord OAuth",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-enabled.description": "Se o OpenID login está habilitado",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "",
|
||||
"admin.config.oauth.oidc-client-id": "ID do Cliente OpenID",
|
||||
"admin.config.oauth.oidc-client-id.description": "ID do cliente do aplicativo OpenID OAuth",
|
||||
"admin.config.oauth.oidc-client-secret": "Segredo do Cliente OpenID",
|
||||
"admin.config.oauth.oidc-client-secret.description": "ID do cliente do aplicativo OpenID OAuth",
|
||||
// 404
|
||||
"404.description": "Ops, esta página não existe.",
|
||||
"404.button.home": "Me traga de volta para casa",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.title": "Erro",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.button.back": "Voltar",
|
||||
"error.msg.default": "Algo deu errado.",
|
||||
"error.msg.access_denied": "Você cancelou o processo de autenticação, por favor, tente novamente.",
|
||||
"error.msg.expired_token": "O processo de autenticação demorou muito. Por favor, tente novamente.",
|
||||
"error.msg.no_user": "O usuário vinculado a esta conta {0} não existe.",
|
||||
"error.msg.no_email": "Não é possível obter o endereço de e-mail desta conta {0}.",
|
||||
"error.msg.already_linked": "Esta conta {0} já está vinculada a outra conta.",
|
||||
"error.msg.not_linked": "Esta conta {0} ainda não foi vinculada a nenhuma conta.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "Вы не сможете удалить свои файлы вручную и просмотреть количество посетителей.",
|
||||
"upload.modal.expires.never": "никогда",
|
||||
"upload.modal.expires.never-long": "Никогда не истекает",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.link.label": "Ссылка",
|
||||
"upload.modal.expires.label": "Истекает",
|
||||
"upload.modal.expires.minute-singular": "Минута",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Предпросмотр не поддерживается",
|
||||
"share.modal.file-preview.error.not-supported.description": "Предварительный просмотр этого типа файла не поддерживается. Пожалуйста, скачайте файл для его просмотра.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Конфигурация",
|
||||
"admin.config.category.general": "Общее",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Разрешена ли регистрация",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Разрешить неавторизованные загрузки",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Могут ли неавторизованные пользователи создавать загрузки",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "Максимальный размер",
|
||||
"admin.config.share.max-size.description": "Максимальный размер файла в байтах",
|
||||
"admin.config.share.zip-compression-level": "Уровень сжатия Zip",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "Нећете моћи ручно да избришете своје дељење и видите број посетилаца.",
|
||||
"upload.modal.expires.never": "никад",
|
||||
"upload.modal.expires.never-long": "Никада не истиче",
|
||||
"upload.modal.expires.error.too-long": "Истек премашује максимални датум истека од {max}.",
|
||||
"upload.modal.link.label": "Линк",
|
||||
"upload.modal.expires.label": "Истиче",
|
||||
"upload.modal.expires.minute-singular": "Минут",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "Преглед није подржан",
|
||||
"share.modal.file-preview.error.not-supported.description": "Преглед за овај тип датотеке није подржан. Преузмите датотеку да бисте је видели.",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "Конфигурација",
|
||||
"admin.config.category.general": "Опште",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "Да ли је регистрација дозвољена",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Дозволи дељење без аутентификације",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Да ли корисници без аутентификације могу да креирају дељења",
|
||||
"admin.config.share.max-expiration": "Максимални рок трајања",
|
||||
"admin.config.share.max-expiration.description": "Максимални рок трајања дељења у сатима. Поставите на 0 да бисте омогућили неограничено трајање.",
|
||||
"admin.config.share.max-size": "Максимална величина",
|
||||
"admin.config.share.max-size.description": "Максимална величина дељења у бајтовима",
|
||||
"admin.config.share.zip-compression-level": "Ниво zip компресије",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "คุณจะไม่สามารถลบการแชร์ของคุณด้วยตนเองและดูจำนวนผู้เข้าชมได้",
|
||||
"upload.modal.expires.never": "ไม่มีกำหนด",
|
||||
"upload.modal.expires.never-long": "ไม่มีกำหนดหมดอายุ",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.link.label": "ลิงค์",
|
||||
"upload.modal.expires.label": "หมดอายุ",
|
||||
"upload.modal.expires.minute-singular": "นาที",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "ไม่รองรับการแสดงตัวอย่าง",
|
||||
"share.modal.file-preview.error.not-supported.description": "ไม่รองรับการแสดงตัวอย่างสำหรับไฟล์ประเภทนี้ โปรดดาวน์โหลดไฟล์เพื่อดู",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "Edit {shareId}",
|
||||
"share.edit.append-upload": "Append file",
|
||||
"share.edit.notify.generic-error": "An error occurred while finishing your share.",
|
||||
"share.edit.notify.save-success": "Share updated successfully",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "การตั้งค่า",
|
||||
"admin.config.category.general": "ทั่วไป",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "อนุญาตให้ผู้ใช้ลงทะเบียนด้วยตัวเองเพื่อสร้างแชร์",
|
||||
"admin.config.share.allow-unauthenticated-shares": "อนุญาตให้แชร์โดยไม่ต้องเข้าสู่ระบบ",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "อนุญาตให้ผู้ใช้ที่ไม่ได้เข้าสู่ระบบสร้างแชร์",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "ขนาดสูงสุด",
|
||||
"admin.config.share.max-size.description": "ขนาดสูงสุดของแชร์",
|
||||
"admin.config.share.zip-compression-level": "ระดับการบีบอัดไฟล์ Zip",
|
||||
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
"upload.modal.not-signed-in-description": "你将不能删除你的共享或查看访问次数",
|
||||
"upload.modal.expires.never": "永不",
|
||||
"upload.modal.expires.never-long": "永不过期",
|
||||
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.",
|
||||
"upload.modal.link.label": "共享链接",
|
||||
"upload.modal.expires.label": "过期时间",
|
||||
"upload.modal.expires.minute-singular": "1 分钟",
|
||||
@@ -263,6 +264,12 @@ export default {
|
||||
"share.modal.file-preview.error.not-supported.title": "该文件类型不支持预览",
|
||||
"share.modal.file-preview.error.not-supported.description": "该文件类型不支持预览,请下载后打开查看",
|
||||
// END /share/[id]
|
||||
// /share/[id]/edit
|
||||
"share.edit.title": "编辑 {shareId}",
|
||||
"share.edit.append-upload": "追加文件",
|
||||
"share.edit.notify.generic-error": "保存共享的过程中发生了错误",
|
||||
"share.edit.notify.save-success": "共享已更新成功",
|
||||
// END /share/[id]/edit
|
||||
// /admin/config
|
||||
"admin.config.title": "配置管理",
|
||||
"admin.config.category.general": "通用",
|
||||
@@ -301,6 +308,8 @@ export default {
|
||||
"admin.config.share.allow-registration.description": "是否允许注册",
|
||||
"admin.config.share.allow-unauthenticated-shares": "是否允许未验证的共享",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "是否允许未验证的用户创建共享",
|
||||
"admin.config.share.max-expiration": "Max expiration",
|
||||
"admin.config.share.max-expiration.description": "Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
|
||||
"admin.config.share.max-size": "最大文件上限",
|
||||
"admin.config.share.max-size.description": "最大文件上限,单位 bytes (1GB=1024MB=1048576KB=1073741824bytes)",
|
||||
"admin.config.share.zip-compression-level": "Zip compression level",
|
||||
|
||||
@@ -77,6 +77,7 @@ const MyShares = () => {
|
||||
showCreateReverseShareModal(
|
||||
modals,
|
||||
config.get("smtp.enabled"),
|
||||
config.get("share.maxExpiration"),
|
||||
getReverseShares,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useModals } from "@mantine/modals";
|
||||
import moment from "moment";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TbInfoCircle, TbLink, TbTrash } from "react-icons/tb";
|
||||
import { TbEdit, TbInfoCircle, TbLink, TbTrash } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../components/Meta";
|
||||
import showShareInformationsModal from "../../components/account/showShareInformationsModal";
|
||||
@@ -110,6 +110,11 @@ const MyShares = () => {
|
||||
</td>
|
||||
<td>
|
||||
<Group position="right">
|
||||
<Link href={`/share/${share.id}/edit`}>
|
||||
<ActionIcon color="orange" variant="light" size={25}>
|
||||
<TbEdit />
|
||||
</ActionIcon>
|
||||
</Link>
|
||||
<ActionIcon
|
||||
color="blue"
|
||||
variant="light"
|
||||
|
||||
65
frontend/src/pages/share/[shareId]/edit.tsx
Normal file
65
frontend/src/pages/share/[shareId]/edit.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { LoadingOverlay } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import { useEffect, useState } from "react";
|
||||
import showErrorModal from "../../../components/share/showErrorModal";
|
||||
import shareService from "../../../services/share.service";
|
||||
import { Share as ShareType } from "../../../types/share.type";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import EditableUpload from "../../../components/upload/EditableUpload";
|
||||
import Meta from "../../../components/Meta";
|
||||
|
||||
export function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
return {
|
||||
props: { shareId: context.params!.shareId },
|
||||
};
|
||||
}
|
||||
|
||||
const Share = ({ shareId }: { shareId: string }) => {
|
||||
const t = useTranslate();
|
||||
const modals = useModals();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [share, setShare] = useState<ShareType>();
|
||||
|
||||
useEffect(() => {
|
||||
shareService
|
||||
.getFromOwner(shareId)
|
||||
.then((share) => {
|
||||
setShare(share);
|
||||
})
|
||||
.catch((e) => {
|
||||
const { error } = e.response.data;
|
||||
if (e.response.status == 404) {
|
||||
if (error == "share_removed") {
|
||||
showErrorModal(
|
||||
modals,
|
||||
t("share.error.removed.title"),
|
||||
e.response.data.message,
|
||||
);
|
||||
} else {
|
||||
showErrorModal(
|
||||
modals,
|
||||
t("share.error.not-found.title"),
|
||||
t("share.error.not-found.description"),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
showErrorModal(modals, t("common.error"), t("common.error.unknown"));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) return <LoadingOverlay visible />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title={t("share.edit.title", { shareId })} />
|
||||
<EditableUpload shareId={shareId} files={share?.files || []} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Share;
|
||||
@@ -42,7 +42,14 @@ const Upload = ({
|
||||
|
||||
const uploadFiles = async (share: CreateShare, files: FileUpload[]) => {
|
||||
setisUploading(true);
|
||||
createdShare = await shareService.create(share);
|
||||
|
||||
try {
|
||||
createdShare = await shareService.create(share);
|
||||
} catch (e) {
|
||||
toast.axiosError(e);
|
||||
setisUploading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const fileUploadPromises = files.map(async (file, fileIndex) =>
|
||||
// Limit the number of concurrent uploads to 3
|
||||
@@ -132,6 +139,7 @@ const Upload = ({
|
||||
"share.allowUnauthenticatedShares",
|
||||
),
|
||||
enableEmailRecepients: config.get("email.enableShareEmailRecipients"),
|
||||
maxExpirationInHours: config.get("share.maxExpiration"),
|
||||
},
|
||||
files,
|
||||
uploadFiles,
|
||||
@@ -194,7 +202,9 @@ const Upload = ({
|
||||
showCreateUploadModalCallback={showCreateUploadModalCallback}
|
||||
isUploading={isUploading}
|
||||
/>
|
||||
{files.length > 0 && <FileList files={files} setFiles={setFiles} />}
|
||||
{files.length > 0 && (
|
||||
<FileList<FileUpload> files={files} setFiles={setFiles} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,10 +19,18 @@ const completeShare = async (id: string) => {
|
||||
return (await api.post(`shares/${id}/complete`)).data;
|
||||
};
|
||||
|
||||
const revertComplete = async (id: string) => {
|
||||
return (await api.delete(`shares/${id}/complete`)).data;
|
||||
};
|
||||
|
||||
const get = async (id: string): Promise<Share> => {
|
||||
return (await api.get(`shares/${id}`)).data;
|
||||
};
|
||||
|
||||
const getFromOwner = async (id: string): Promise<Share> => {
|
||||
return (await api.get(`shares/${id}/from-owner`)).data;
|
||||
};
|
||||
|
||||
const getMetaData = async (id: string): Promise<ShareMetaData> => {
|
||||
return (await api.get(`shares/${id}/metaData`)).data;
|
||||
};
|
||||
@@ -63,6 +71,10 @@ const downloadFile = async (shareId: string, fileId: string) => {
|
||||
window.location.href = `${window.location.origin}/api/shares/${shareId}/files/${fileId}`;
|
||||
};
|
||||
|
||||
const removeFile = async (shareId: string, fileId: string) => {
|
||||
await api.delete(`shares/${shareId}/files/${fileId}`);
|
||||
};
|
||||
|
||||
const uploadFile = async (
|
||||
shareId: string,
|
||||
readerEvent: ProgressEvent<FileReader>,
|
||||
@@ -121,14 +133,17 @@ const removeReverseShare = async (id: string) => {
|
||||
export default {
|
||||
create,
|
||||
completeShare,
|
||||
revertComplete,
|
||||
getShareToken,
|
||||
get,
|
||||
getFromOwner,
|
||||
remove,
|
||||
getMetaData,
|
||||
doesFileSupportPreview,
|
||||
getMyShares,
|
||||
isShareIdAvailable,
|
||||
downloadFile,
|
||||
removeFile,
|
||||
uploadFile,
|
||||
setReverseShare,
|
||||
createReverseShare,
|
||||
|
||||
@@ -7,3 +7,5 @@ export type FileMetaData = {
|
||||
name: string;
|
||||
size: string;
|
||||
};
|
||||
|
||||
export type FileListItem = FileUpload | (FileMetaData & { deleted?: boolean });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share",
|
||||
"version": "0.19.1",
|
||||
"version": "0.20.1",
|
||||
"scripts": {
|
||||
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
||||
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
||||
|
||||
Reference in New Issue
Block a user