Compare commits

...

26 Commits

Author SHA1 Message Date
Elias Schneider
c9f1be2faf release: 0.18.0 2023-09-21 16:24:07 +02:00
Elias Schneider
57be6945f2 chore(ci/cd): cache Docker build 2023-09-21 16:09:23 +02:00
Elias Schneider
82abe52ea5 chore(translations): update translations via Crowdin (#253)
* New translations en-us.ts (German)

* New translations en-us.ts (Dutch, Belgium)

* New translations en-us.ts (Dutch, Belgium)

* New translations en-us.ts (Dutch, Belgium)

* New translations en-us.ts (Dutch, Belgium)

* New translations en-us.ts (Dutch, Belgium)

* New translations en-us.ts (Dutch, Belgium)

* New translations en-us.ts (Dutch, Belgium)

* New translations en-us.ts (Dutch, Belgium)

* New translations en-us.ts (Dutch, Belgium)
2023-09-21 16:05:42 +02:00
KdF
6fa7af7905 fix(docker): Updated to newest version of alpine linux and fixed missing dependencies (#255)
* Updated docker file

* yes

* Update Dockerfile

Co-authored-by: Elias Schneider <login@eliasschneider.com>

---------

Co-authored-by: Elias Schneider <login@eliasschneider.com>
2023-09-21 16:04:02 +02:00
Elias Schneider
13e7a30bb9 feat: show upload modal on file drop 2023-09-21 15:59:55 +02:00
Elias Schneider
955af04e32 chore(translations): add Dutch files 2023-09-18 17:48:38 +02:00
Elias Schneider
035e67f759 chore(translations): update translations via Crowdin (#250)
* New translations en-US.ts (Serbian (Cyrillic))

* New translations en-US.ts (Serbian (Cyrillic))

* New translations en-US.ts (Serbian (Cyrillic))

* New translations en-US.ts (Serbian (Cyrillic))

* New translations en-US.ts (Serbian (Cyrillic))
2023-09-18 11:23:55 +02:00
Elias Schneider
167ec782ef New translations en-US.ts (Spanish) (#248) 2023-09-12 11:47:12 +02:00
Elias Schneider
743c33475f chore(translations): add Serbian files 2023-09-12 11:45:20 +02:00
adriadam10
3f1d3b7833 Run docker container as non root user (#242)
* Run docker container as non root user

* Pass UID and GID as a variable + alpine-based image

* change apt-get to apk

* chore: remove unnecessary packages from Dockerfile

* chore: remove unnecessary `chown`

---------

Co-authored-by: Elias Schneider <login@eliasschneider.com>
2023-09-11 16:14:42 +02:00
Elias Schneider
3d76e41cd8 chore(translations): update translations via Crowdin (#239)
* New translations en-US.ts (Portuguese, Brazilian)

* New translations en-US.ts (French)
2023-09-09 20:56:57 +02:00
Elias Schneider
e9efbc17bc fix: nextjs proxy warning 2023-09-05 14:58:38 +02:00
Elias Schneider
307d176430 release: 0.17.5 2023-09-03 22:14:34 +02:00
Elias Schneider
7e24ba9721 chore(translations): update translations via Crowdin (#238)
* New translations en-US.ts (French)

* New translations en-US.ts (Spanish)

* New translations en-US.ts (Danish)

* New translations en-US.ts (German)

* New translations en-US.ts (Finnish)

* New translations en-US.ts (Russian)

* New translations en-US.ts (Chinese Simplified)

* New translations en-US.ts (Portuguese, Brazilian)

* New translations en-US.ts (Thai)
2023-09-03 22:14:11 +02:00
Elias Schneider
f9774d82d8 refactor: run formatter 2023-09-03 22:13:57 +02:00
Elias Schneider
7647a9f620 fix: missing translation 2023-09-03 22:09:55 +02:00
Elias Schneider
d4e8d4f58b fix: autocomplete on create share modal 2023-09-03 22:07:40 +02:00
Elias Schneider
4df8dea5cc chore(translations): update translations via Crowdin (#232)
* New translations en-US.ts (Danish)

* New translations en-US.ts (French)

* New translations en-US.ts (Spanish)

* New translations en-US.ts (German)

* New translations en-US.ts (Finnish)

* New translations en-US.ts (Russian)

* New translations en-US.ts (Chinese Simplified)

* New translations en-US.ts (Portuguese, Brazilian)

* New translations en-US.ts (Thai)
2023-08-17 15:02:22 +02:00
Elias Schneider
84aa100f84 chore: formatter ignore translations 2023-08-17 15:00:57 +02:00
iUnstable0
bddb87b9b3 feat(localization): Added thai language (#231)
* feat(localization): Added Thai translation

* Formatted

---------

Co-authored-by: Elias Schneider <login@eliasschneider.com>
2023-08-17 14:54:26 +02:00
Elias Schneider
18c10c0ac6 New translations en-US.ts (Danish) (#229) 2023-08-17 14:51:38 +02:00
Elias Schneider
f02e2979c4 refactor: run formatter 2023-08-17 14:47:58 +02:00
Elias Schneider
7b34cb14cb New translations en-US.ts (German) (#223) 2023-08-07 08:43:17 +02:00
Elias Schneider
019ef090ac chore(translations): update translations via Crowdin (#222)
* New translations en-US.ts (Portuguese, Brazilian)

* New translations en-US.ts (Portuguese, Brazilian)

* New translations en-US.ts (Russian)

* New translations en-US.ts (German)

* New translations en-US.ts (Russian)

* New translations en-US.ts (Finnish)

* New translations en-US.ts (Russian)
2023-08-01 12:50:41 +02:00
Elias Schneider
7304b54125 release: 0.17.4 2023-08-01 12:37:47 +02:00
Elias Schneider
ea0d5216e8 fix: redirection to localhost:3000 2023-08-01 12:35:37 +02:00
97 changed files with 1657 additions and 710 deletions

View File

@@ -1,4 +1,4 @@
name: Create Docker Image
name: Build and Push Docker Image
on:
release:
@@ -10,15 +10,25 @@ jobs:
steps:
- name: checkout code
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: login to docker registry
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Build the image
run: |
docker buildx build --push \
--tag stonith404/pingvin-share:latest \
--tag stonith404/pingvin-share:${{ github.ref_name }} \
--platform linux/amd64,linux/arm64 .
- name: Login to Docker registry
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: stonith404/pingvin-share:latest,stonith404/pingvin-share:${{ github.ref_name }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -1 +0,0 @@
/backend/src/constants.ts

View File

@@ -1,3 +1,36 @@
## [0.18.0](https://github.com/stonith404/pingvin-share/compare/v0.17.5...v0.18.0) (2023-09-21)
### Features
* show upload modal on file drop ([13e7a30](https://github.com/stonith404/pingvin-share/commit/13e7a30bb96faeb25936ff08a107834fd7af5766))
### Bug Fixes
* **docker:** Updated to newest version of alpine linux and fixed missing dependencies ([#255](https://github.com/stonith404/pingvin-share/issues/255)) ([6fa7af7](https://github.com/stonith404/pingvin-share/commit/6fa7af79051c964060bd291c9faad90fc01a1b72))
* nextjs proxy warning ([e9efbc1](https://github.com/stonith404/pingvin-share/commit/e9efbc17bcf4827e935e2018dcdf3b70a9a49991))
## [0.17.5](https://github.com/stonith404/pingvin-share/compare/v0.17.4...v0.17.5) (2023-09-03)
### Features
* **localization:** Added thai language ([#231](https://github.com/stonith404/pingvin-share/issues/231)) ([bddb87b](https://github.com/stonith404/pingvin-share/commit/bddb87b9b3ec5426a3c7a14a96caf2eb45b93ff7))
### Bug Fixes
* autocomplete on create share modal ([d4e8d4f](https://github.com/stonith404/pingvin-share/commit/d4e8d4f58b9b7d10b865eff49aa784547891c4e8))
* missing translation ([7647a9f](https://github.com/stonith404/pingvin-share/commit/7647a9f620cbc5d38e019225a680a53bd3027698))
## [0.17.4](https://github.com/stonith404/pingvin-share/compare/v0.17.3...v0.17.4) (2023-08-01)
### Bug Fixes
* redirection to `localhost:3000` ([ea0d521](https://github.com/stonith404/pingvin-share/commit/ea0d5216e89346b8d3ef0277b76fdc6302e9de15))
## [0.17.3](https://github.com/stonith404/pingvin-share/compare/v0.17.2...v0.17.3) (2023-07-31)

View File

@@ -1,37 +1,45 @@
# Using node slim because prisma ORM needs libc for ARM builds
# Stage 1: on frontend dependency change
FROM node:19-slim AS frontend-dependencies
# Stage 1: Frontend dependencies
FROM node:20-alpine AS frontend-dependencies
WORKDIR /opt/app
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
# Stage 2: on frontend change
FROM node:19-slim AS frontend-builder
# Stage 2: Build frontend
FROM node:20-alpine AS frontend-builder
WORKDIR /opt/app
COPY ./frontend .
COPY --from=frontend-dependencies /opt/app/node_modules ./node_modules
RUN npm run build
# Stage 3: on backend dependency change
FROM node:19-slim AS backend-dependencies
# Stage 3: Backend dependencies
FROM node:20-alpine AS backend-dependencies
WORKDIR /opt/app
COPY backend/package.json backend/package-lock.json ./
RUN npm ci
# Stage 4:on backend change
FROM node:19-slim AS backend-builder
RUN apt-get update && apt-get install -y openssl
# Stage 4: Build backend
FROM node:20-alpine AS backend-builder
WORKDIR /opt/app
COPY ./backend .
COPY --from=backend-dependencies /opt/app/node_modules ./node_modules
RUN npx prisma generate
RUN npm run build && npm prune --production
RUN npm run build && npm prune --production
# Stage 5: Final image
FROM node:19-slim AS runner
FROM node:20-alpine AS runner
ENV NODE_ENV=docker
RUN apt-get update && apt-get install -y curl openssl
# Alpine specific dependencies
RUN apk update --no-cache
RUN apk upgrade --no-cache
RUN apk add --no-cache curl
# Set user and group IDs for the node user
ARG UID=1000
ARG GID=1000
RUN deluser node
RUN adduser -u $UID -g $GID node -D
USER node
WORKDIR /opt/app/frontend
COPY --from=frontend-builder /opt/app/public ./public
@@ -46,8 +54,12 @@ COPY --from=backend-builder /opt/app/prisma ./prisma
COPY --from=backend-builder /opt/app/package.json ./
WORKDIR /opt/app
EXPOSE 3000
# Add a health check to ensure the container is healthy
HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost:3000/api/health || exit 1
# HOSTNAME=127.0.0.1 fixes https://github.com/vercel/next.js/issues/51684. It can be removed as soon as the issue is fixed
CMD cp -rn /tmp/img /opt/app/frontend/public && HOSTNAME=127.0.0.1 node frontend/server.js & cd backend && npm run prod
# Application startup
# HOSTNAME=0.0.0.0 fixes https://github.com/vercel/next.js/issues/51684. It can be removed as soon as the issue is fixed
CMD cp -rn /tmp/img /opt/app/frontend/public && HOSTNAME=0.0.0.0 node frontend/server.js & cd backend && npm run prod

1
backend/.prettierignore Normal file
View File

@@ -0,0 +1 @@
/src/constants.ts

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-backend",
"version": "0.17.3",
"version": "0.18.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-backend",
"version": "0.17.3",
"version": "0.18.0",
"dependencies": {
"@nestjs/common": "^10.1.2",
"@nestjs/config": "^3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-backend",
"version": "0.17.3",
"version": "0.18.0",
"scripts": {
"build": "nest build",
"dev": "cross-env NODE_ENV=development nest start --watch",

View File

@@ -33,14 +33,14 @@ export class AuthController {
constructor(
private authService: AuthService,
private authTotpService: AuthTotpService,
private config: ConfigService
private config: ConfigService,
) {}
@Post("signUp")
@Throttle(10, 5 * 60)
async signUp(
@Body() dto: AuthRegisterDTO,
@Res({ passthrough: true }) response: Response
@Res({ passthrough: true }) response: Response,
) {
if (!this.config.get("share.allowRegistration"))
throw new ForbiddenException("Registration is not allowed");
@@ -50,7 +50,7 @@ export class AuthController {
response = this.addTokensToResponse(
response,
result.refreshToken,
result.accessToken
result.accessToken,
);
return result;
@@ -61,7 +61,7 @@ export class AuthController {
@HttpCode(200)
async signIn(
@Body() dto: AuthSignInDTO,
@Res({ passthrough: true }) response: Response
@Res({ passthrough: true }) response: Response,
) {
const result = await this.authService.signIn(dto);
@@ -69,7 +69,7 @@ export class AuthController {
response = this.addTokensToResponse(
response,
result.refreshToken,
result.accessToken
result.accessToken,
);
}
@@ -81,14 +81,14 @@ export class AuthController {
@HttpCode(200)
async signInTotp(
@Body() dto: AuthSignInTotpDTO,
@Res({ passthrough: true }) response: Response
@Res({ passthrough: true }) response: Response,
) {
const result = await this.authTotpService.signInTotp(dto);
response = this.addTokensToResponse(
response,
result.refreshToken,
result.accessToken
result.accessToken,
);
return new TokenDTO().from(result);
@@ -113,12 +113,12 @@ export class AuthController {
async updatePassword(
@GetUser() user: User,
@Res({ passthrough: true }) response: Response,
@Body() dto: UpdatePasswordDTO
@Body() dto: UpdatePasswordDTO,
) {
const result = await this.authService.updatePassword(
user,
dto.oldPassword,
dto.password
dto.password,
);
response = this.addTokensToResponse(response, result.refreshToken);
@@ -129,12 +129,12 @@ export class AuthController {
@HttpCode(200)
async refreshAccessToken(
@Req() request: Request,
@Res({ passthrough: true }) response: Response
@Res({ passthrough: true }) response: Response,
) {
if (!request.cookies.refresh_token) throw new UnauthorizedException();
const accessToken = await this.authService.refreshAccessToken(
request.cookies.refresh_token
request.cookies.refresh_token,
);
response = this.addTokensToResponse(response, undefined, accessToken);
return new TokenDTO().from({ accessToken });
@@ -143,7 +143,7 @@ export class AuthController {
@Post("signOut")
async signOut(
@Req() request: Request,
@Res({ passthrough: true }) response: Response
@Res({ passthrough: true }) response: Response,
) {
await this.authService.signOut(request.cookies.access_token);
response.cookie("access_token", "accessToken", { maxAge: -1 });
@@ -176,7 +176,7 @@ export class AuthController {
private addTokensToResponse(
response: Response,
refreshToken?: string,
accessToken?: string
accessToken?: string,
) {
if (accessToken)
response.cookie("access_token", accessToken, { sameSite: "lax" });

View File

@@ -21,7 +21,7 @@ export class AuthService {
private prisma: PrismaService,
private jwtService: JwtService,
private config: ConfigService,
private emailService: EmailService
private emailService: EmailService,
) {}
async signUp(dto: AuthRegisterDTO) {
@@ -39,7 +39,7 @@ export class AuthService {
});
const { refreshToken, refreshTokenId } = await this.createRefreshToken(
user.id
user.id,
);
const accessToken = await this.createAccessToken(user, refreshTokenId);
@@ -49,7 +49,7 @@ export class AuthService {
if (e.code == "P2002") {
const duplicatedField: string = e.meta.target[0];
throw new BadRequestException(
`A user with this ${duplicatedField} already exists`
`A user with this ${duplicatedField} already exists`,
);
}
}
@@ -78,7 +78,7 @@ export class AuthService {
}
const { refreshToken, refreshTokenId } = await this.createRefreshToken(
user.id
user.id,
);
const accessToken = await this.createAccessToken(user, refreshTokenId);
@@ -158,7 +158,7 @@ export class AuthService {
{
expiresIn: "15min",
secret: this.config.get("internal.jwtSecret"),
}
},
);
}
@@ -189,7 +189,7 @@ export class AuthService {
return this.createAccessToken(
refreshTokenMetaData.user,
refreshTokenMetaData.id
refreshTokenMetaData.id,
);
}

View File

@@ -18,7 +18,7 @@ export class AuthTotpService {
constructor(
private prisma: PrismaService,
private authService: AuthService,
private config: ConfigService
private config: ConfigService,
) {}
async signInTotp(dto: AuthSignInTotpDTO) {
@@ -72,7 +72,7 @@ export class AuthTotpService {
await this.authService.createRefreshToken(user.id);
const accessToken = await this.authService.createAccessToken(
user,
refreshTokenId
refreshTokenId,
);
return { accessToken, refreshToken };
@@ -98,7 +98,7 @@ export class AuthTotpService {
const otpURL = totp.keyuri(
user.username || user.email,
this.config.get("general.appName"),
secret
secret,
);
await this.prisma.user.update({

View File

@@ -5,5 +5,5 @@ export const GetUser = createParamDecorator(
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
}
},
);

View File

@@ -8,7 +8,10 @@ import { PrismaService } from "src/prisma/prisma.service";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(config: ConfigService, private prisma: PrismaService) {
constructor(
config: ConfigService,
private prisma: PrismaService,
) {
config.get("internal.jwtSecret");
super({
jwtFromRequest: JwtStrategy.extractJWT,

View File

@@ -19,7 +19,7 @@ export class ClamScanService {
constructor(
private fileService: FileService,
private prisma: PrismaService
private prisma: PrismaService,
) {}
private ClamScan: Promise<NodeClam | null> = new NodeClam()
@@ -81,7 +81,7 @@ export class ClamScanService {
});
this.logger.warn(
`Share ${shareId} deleted because it contained ${infectedFiles.length} malicious file(s)`
`Share ${shareId} deleted because it contained ${infectedFiles.length} malicious file(s)`,
);
}
}

View File

@@ -28,7 +28,7 @@ export class ConfigController {
constructor(
private configService: ConfigService,
private logoService: LogoService,
private emailService: EmailService
private emailService: EmailService,
) {}
@Get()
@@ -41,7 +41,7 @@ export class ConfigController {
@UseGuards(JwtGuard, AdministratorGuard)
async getByCategory(@Param("category") category: string) {
return new AdminConfigDTO().fromList(
await this.configService.getByCategory(category)
await this.configService.getByCategory(category),
);
}
@@ -49,7 +49,7 @@ export class ConfigController {
@UseGuards(JwtGuard, AdministratorGuard)
async updateMany(@Body() data: UpdateConfigDTO[]) {
return new AdminConfigDTO().fromList(
await this.configService.updateMany(data)
await this.configService.updateMany(data),
);
}
@@ -66,9 +66,9 @@ export class ConfigController {
@UploadedFile(
new ParseFilePipe({
validators: [new FileTypeValidator({ fileType: "image/png" })],
})
}),
)
file: Express.Multer.File
file: Express.Multer.File,
) {
return await this.logoService.create(file.buffer);
}

View File

@@ -11,12 +11,12 @@ import { PrismaService } from "src/prisma/prisma.service";
export class ConfigService {
constructor(
@Inject("CONFIG_VARIABLES") private configVariables: Config[],
private prisma: PrismaService
private prisma: PrismaService,
) {}
get(key: `${string}.${string}`): any {
const configVariable = this.configVariables.filter(
(variable) => `${variable.category}.${variable.name}` == key
(variable) => `${variable.category}.${variable.name}` == key,
)[0];
if (!configVariable) throw new Error(`Config variable ${key} not found`);
@@ -89,7 +89,7 @@ export class ConfigService {
configVariable.type != "text"
) {
throw new BadRequestException(
`Config variable must be of type ${configVariable.type}`
`Config variable must be of type ${configVariable.type}`,
);
}

View File

@@ -25,7 +25,7 @@ export class AdminConfigDTO extends ConfigDTO {
fromList(partial: Partial<AdminConfigDTO>[]) {
return partial.map((part) =>
plainToClass(AdminConfigDTO, part, { excludeExtraneousValues: true })
plainToClass(AdminConfigDTO, part, { excludeExtraneousValues: true }),
);
}
}

View File

@@ -12,7 +12,7 @@ export class ConfigDTO {
fromList(partial: Partial<ConfigDTO>[]) {
return partial.map((part) =>
plainToClass(ConfigDTO, part, { excludeExtraneousValues: true })
plainToClass(ConfigDTO, part, { excludeExtraneousValues: true }),
);
}
}

View File

@@ -25,7 +25,7 @@ export class LogoService {
fs.promises.writeFile(
`${IMAGES_PATH}/icons/icon-${size}x${size}.png`,
resized,
"binary"
"binary",
);
}
}

View File

@@ -32,7 +32,7 @@ export class EmailService {
await this.getTransporter()
.sendMail({
from: `"${this.config.get("general.appName")}" <${this.config.get(
"smtp.email"
"smtp.email",
)}>`,
to: email,
subject,
@@ -49,7 +49,7 @@ export class EmailService {
shareId: string,
creator?: User,
description?: string,
expiration?: Date
expiration?: Date,
) {
if (!this.config.get("email.enableShareEmailRecipients"))
throw new InternalServerErrorException("Email service disabled");
@@ -69,8 +69,8 @@ export class EmailService {
"{expires}",
moment(expiration).unix() != 0
? moment(expiration).fromNow()
: "in: never"
)
: "in: never",
),
);
}
@@ -83,13 +83,13 @@ export class EmailService {
this.config
.get("email.reverseShareMessage")
.replaceAll("\\n", "\n")
.replaceAll("{shareUrl}", shareUrl)
.replaceAll("{shareUrl}", shareUrl),
);
}
async sendResetPasswordEmail(recipientEmail: string, token: string) {
const resetPasswordUrl = `${this.config.get(
"general.appUrl"
"general.appUrl",
)}/auth/resetPassword/${token}`;
await this.sendMail(
@@ -98,7 +98,7 @@ export class EmailService {
this.config
.get("email.resetPasswordMessage")
.replaceAll("\\n", "\n")
.replaceAll("{url}", resetPasswordUrl)
.replaceAll("{url}", resetPasswordUrl),
);
}
@@ -111,7 +111,7 @@ export class EmailService {
this.config
.get("email.inviteMessage")
.replaceAll("{url}", loginUrl)
.replaceAll("{password}", password)
.replaceAll("{password}", password),
);
}
@@ -119,7 +119,7 @@ export class EmailService {
await this.getTransporter()
.sendMail({
from: `"${this.config.get("general.appName")}" <${this.config.get(
"smtp.email"
"smtp.email",
)}>`,
to: recipientEmail,
subject: "Test email",

View File

@@ -28,7 +28,7 @@ export class FileController {
@Query() query: any,
@Body() body: string,
@Param("shareId") shareId: string
@Param("shareId") shareId: string,
) {
const { id, name, chunkIndex, totalChunks } = query;
@@ -39,7 +39,7 @@ export class FileController {
data,
{ index: parseInt(chunkIndex), total: parseInt(totalChunks) },
{ id, name },
shareId
shareId,
);
}
@@ -47,7 +47,7 @@ export class FileController {
@UseGuards(FileSecurityGuard)
async getZip(
@Res({ passthrough: true }) res: Response,
@Param("shareId") shareId: string
@Param("shareId") shareId: string,
) {
const zip = this.fileService.getZip(shareId);
res.set({
@@ -64,7 +64,7 @@ export class FileController {
@Res({ passthrough: true }) res: Response,
@Param("shareId") shareId: string,
@Param("fileId") fileId: string,
@Query("download") download = "true"
@Query("download") download = "true",
) {
const file = await this.fileService.get(shareId, fileId);

View File

@@ -18,14 +18,14 @@ export class FileService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
private config: ConfigService
private config: ConfigService,
) {}
async create(
data: string,
chunk: { index: number; total: number },
file: { id?: string; name: string },
shareId: string
shareId: string,
) {
if (!file.id) file.id = crypto.randomUUID();
@@ -40,7 +40,7 @@ export class FileService {
let diskFileSize: number;
try {
diskFileSize = fs.statSync(
`${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`
`${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`,
).size;
} catch {
diskFileSize = 0;
@@ -62,7 +62,7 @@ export class FileService {
// Check if share size limit is exceeded
const fileSizeSum = share.files.reduce(
(n, { size }) => n + parseInt(size),
0
0,
);
const shareSizeSum = fileSizeSum + diskFileSize + buffer.byteLength;
@@ -74,23 +74,23 @@ export class FileService {
) {
throw new HttpException(
"Max share size exceeded",
HttpStatus.PAYLOAD_TOO_LARGE
HttpStatus.PAYLOAD_TOO_LARGE,
);
}
fs.appendFileSync(
`${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`,
buffer
buffer,
);
const isLastChunk = chunk.index == chunk.total - 1;
if (isLastChunk) {
fs.renameSync(
`${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`,
`${SHARE_DIRECTORY}/${shareId}/${file.id}`
`${SHARE_DIRECTORY}/${shareId}/${file.id}`,
);
const fileSize = fs.statSync(
`${SHARE_DIRECTORY}/${shareId}/${file.id}`
`${SHARE_DIRECTORY}/${shareId}/${file.id}`,
).size;
await this.prisma.file.create({
data: {

View File

@@ -14,7 +14,7 @@ import { ShareService } from "src/share/share.service";
export class FileSecurityGuard extends ShareSecurityGuard {
constructor(
private _shareService: ShareService,
private _prisma: PrismaService
private _prisma: PrismaService,
) {
super(_shareService, _prisma);
}
@@ -24,7 +24,7 @@ export class FileSecurityGuard extends ShareSecurityGuard {
const shareId = Object.prototype.hasOwnProperty.call(
request.params,
"shareId"
"shareId",
)
? request.params.shareId
: request.params.id;
@@ -52,7 +52,7 @@ export class FileSecurityGuard extends ShareSecurityGuard {
if (share.security?.maxViews && share.security.maxViews <= share.views) {
throw new ForbiddenException(
"Maximum views exceeded",
"share_max_views_exceeded"
"share_max_views_exceeded",
);
}

View File

@@ -14,7 +14,7 @@ export class JobsService {
constructor(
private prisma: PrismaService,
private reverseShareService: ReverseShareService,
private fileService: FileService
private fileService: FileService,
) {}
@Cron("0 * * * *")
@@ -56,7 +56,7 @@ export class JobsService {
if (expiredReverseShares.length > 0) {
this.logger.log(
`Deleted ${expiredReverseShares.length} expired reverse shares`
`Deleted ${expiredReverseShares.length} expired reverse shares`,
);
}
}
@@ -77,7 +77,7 @@ export class JobsService {
for (const file of temporaryFiles) {
const stats = fs.statSync(
`${SHARE_DIRECTORY}/${shareDirectory}/${file}`
`${SHARE_DIRECTORY}/${shareDirectory}/${file}`,
);
const isOlderThanOneDay = moment(stats.mtime)
.add(1, "day")

View File

@@ -23,7 +23,7 @@ export class ReverseShareTokenWithShares extends OmitType(ReverseShareDTO, [
return partial.map((part) =>
plainToClass(ReverseShareTokenWithShares, part, {
excludeExtraneousValues: true,
})
}),
);
}
}

View File

@@ -23,7 +23,7 @@ import { ReverseShareService } from "./reverseShare.service";
export class ReverseShareController {
constructor(
private reverseShareService: ReverseShareService,
private config: ConfigService
private config: ConfigService,
) {}
@Post()
@@ -44,7 +44,7 @@ export class ReverseShareController {
if (!isValid) throw new NotFoundException("Reverse share token not found");
return new ReverseShareDTO().from(
await this.reverseShareService.getByToken(reverseShareToken)
await this.reverseShareService.getByToken(reverseShareToken),
);
}
@@ -52,7 +52,7 @@ export class ReverseShareController {
@UseGuards(JwtGuard)
async getAllByUser(@GetUser() user: User) {
return new ReverseShareTokenWithShares().fromList(
await this.reverseShareService.getAllByUser(user.id)
await this.reverseShareService.getAllByUser(user.id),
);
}

View File

@@ -10,7 +10,7 @@ export class ReverseShareService {
constructor(
private config: ConfigService,
private prisma: PrismaService,
private fileService: FileService
private fileService: FileService,
) {}
async create(data: CreateReverseShareDTO, creatorId: string) {
@@ -19,8 +19,8 @@ export class ReverseShareService {
.add(
data.shareExpiration.split("-")[0],
data.shareExpiration.split(
"-"
)[1] as moment.unitOfTime.DurationConstructor
"-",
)[1] as moment.unitOfTime.DurationConstructor,
)
.toDate();
@@ -28,7 +28,7 @@ export class ReverseShareService {
if (globalMaxShareSize < data.maxShareSize)
throw new BadRequestException(
`Max share size can't be greater than ${globalMaxShareSize} bytes.`
`Max share size can't be greater than ${globalMaxShareSize} bytes.`,
);
const reverseShare = await this.prisma.reverseShare.create({

View File

@@ -27,7 +27,7 @@ export class MyShareDTO extends OmitType(ShareDTO, [
fromList(partial: Partial<MyShareDTO>[]) {
return partial.map((part) =>
plainToClass(MyShareDTO, part, { excludeExtraneousValues: true })
plainToClass(MyShareDTO, part, { excludeExtraneousValues: true }),
);
}
}

View File

@@ -29,7 +29,7 @@ export class ShareDTO {
fromList(partial: Partial<ShareDTO>[]) {
return partial.map((part) =>
plainToClass(ShareDTO, part, { excludeExtraneousValues: true })
plainToClass(ShareDTO, part, { excludeExtraneousValues: true }),
);
}
}

View File

@@ -7,7 +7,7 @@ import { ReverseShareService } from "src/reverseShare/reverseShare.service";
export class CreateShareGuard extends JwtGuard {
constructor(
configService: ConfigService,
private reverseShareService: ReverseShareService
private reverseShareService: ReverseShareService,
) {
super(configService);
}
@@ -21,7 +21,7 @@ export class CreateShareGuard extends JwtGuard {
if (!reverseShareTokenId) return false;
const isReverseShareTokenValid = await this.reverseShareService.isValid(
reverseShareTokenId
reverseShareTokenId,
);
return isReverseShareTokenValid;

View File

@@ -16,7 +16,7 @@ export class ShareOwnerGuard implements CanActivate {
const request: Request = context.switchToHttp().getRequest();
const shareId = Object.prototype.hasOwnProperty.call(
request.params,
"shareId"
"shareId",
)
? request.params.shareId
: request.params.id;

View File

@@ -14,7 +14,7 @@ import { ShareService } from "src/share/share.service";
export class ShareSecurityGuard implements CanActivate {
constructor(
private shareService: ShareService,
private prisma: PrismaService
private prisma: PrismaService,
) {}
async canActivate(context: ExecutionContext) {
@@ -22,7 +22,7 @@ export class ShareSecurityGuard implements CanActivate {
const shareId = Object.prototype.hasOwnProperty.call(
request.params,
"shareId"
"shareId",
)
? request.params.shareId
: request.params.id;
@@ -44,13 +44,13 @@ export class ShareSecurityGuard implements CanActivate {
if (share.security?.password && !shareToken)
throw new ForbiddenException(
"This share is password protected",
"share_password_required"
"share_password_required",
);
if (!(await this.shareService.verifyShareToken(shareId, shareToken)))
throw new ForbiddenException(
"Share token required",
"share_token_required"
"share_token_required",
);
return true;

View File

@@ -16,7 +16,7 @@ export class ShareTokenSecurity implements CanActivate {
const request: Request = context.switchToHttp().getRequest();
const shareId = Object.prototype.hasOwnProperty.call(
request.params,
"shareId"
"shareId",
)
? request.params.shareId
: request.params.id;

View File

@@ -33,7 +33,7 @@ export class ShareController {
@UseGuards(JwtGuard)
async getMyShares(@GetUser() user: User) {
return new MyShareDTO().fromList(
await this.shareService.getSharesByUser(user.id)
await this.shareService.getSharesByUser(user.id),
);
}
@@ -54,11 +54,11 @@ export class ShareController {
async create(
@Body() body: CreateShareDTO,
@Req() request: Request,
@GetUser() user: User
@GetUser() user: User,
) {
const { reverse_share_token } = request.cookies;
return new ShareDTO().from(
await this.shareService.create(body, user, reverse_share_token)
await this.shareService.create(body, user, reverse_share_token),
);
}
@@ -74,7 +74,7 @@ export class ShareController {
async complete(@Param("id") id: string, @Req() request: Request) {
const { reverse_share_token } = request.cookies;
return new ShareDTO().from(
await this.shareService.complete(id, reverse_share_token)
await this.shareService.complete(id, reverse_share_token),
);
}
@@ -91,7 +91,7 @@ export class ShareController {
async getShareToken(
@Param("id") id: string,
@Res({ passthrough: true }) response: Response,
@Body() body: SharePasswordDto
@Body() body: SharePasswordDto,
) {
const token = await this.shareService.getShareToken(id, body.password);
response.cookie(`share_${id}_token`, token, {

View File

@@ -28,7 +28,7 @@ export class ShareService {
private config: ConfigService,
private jwtService: JwtService,
private reverseShareService: ReverseShareService,
private clamScanService: ClamScanService
private clamScanService: ClamScanService,
) {}
async create(share: CreateShareDTO, user?: User, reverseShareToken?: string) {
@@ -46,7 +46,7 @@ export class ShareService {
// If share is created by a reverse share token override the expiration date
const reverseShare = await this.reverseShareService.getByToken(
reverseShareToken
reverseShareToken,
);
if (reverseShare) {
expirationDate = reverseShare.shareExpiration;
@@ -57,8 +57,8 @@ export class ShareService {
.add(
share.expiration.split("-")[0],
share.expiration.split(
"-"
)[1] as moment.unitOfTime.DurationConstructor
"-",
)[1] as moment.unitOfTime.DurationConstructor,
)
.toDate();
} else {
@@ -134,13 +134,13 @@ export class ShareService {
if (share.files.length == 0)
throw new BadRequestException(
"You need at least on file in your share to complete it."
"You need at least on file in your share to complete it.",
);
// Asynchronously create a zip of all files
if (share.files.length > 1)
this.createZip(id).then(() =>
this.prisma.share.update({ where: { id }, data: { isZipReady: true } })
this.prisma.share.update({ where: { id }, data: { isZipReady: true } }),
);
// Send email for each recipient
@@ -150,7 +150,7 @@ export class ShareService {
share.id,
share.creator,
share.description,
share.expiration
share.expiration,
);
}
@@ -161,7 +161,7 @@ export class ShareService {
) {
await this.emailService.sendMailToReverseShareCreator(
share.reverseShare.creator.email,
share.id
share.id,
);
}
@@ -285,7 +285,7 @@ export class ShareService {
if (share.security?.maxViews && share.security.maxViews <= share.views) {
throw new ForbiddenException(
"Maximum views exceeded",
"share_max_views_exceeded"
"share_max_views_exceeded",
);
}
@@ -305,7 +305,7 @@ export class ShareService {
{
expiresIn: moment(expiration).diff(new Date(), "seconds") + "s",
secret: this.config.get("internal.jwtSecret"),
}
},
);
}

View File

@@ -2,5 +2,5 @@ import { OmitType, PartialType } from "@nestjs/swagger";
import { UserDTO } from "./user.dto";
export class UpdateOwnUserDTO extends PartialType(
OmitType(UserDTO, ["isAdmin", "password"] as const)
OmitType(UserDTO, ["isAdmin", "password"] as const),
) {}

View File

@@ -31,7 +31,7 @@ export class UserDTO {
fromList(partial: Partial<UserDTO>[]) {
return partial.map((part) =>
plainToClass(UserDTO, part, { excludeExtraneousValues: true })
plainToClass(UserDTO, part, { excludeExtraneousValues: true }),
);
}
}

View File

@@ -35,7 +35,7 @@ export class UserController {
@UseGuards(JwtGuard)
async updateCurrentUser(
@GetUser() user: User,
@Body() data: UpdateOwnUserDTO
@Body() data: UpdateOwnUserDTO,
) {
return new UserDTO().from(await this.userService.update(user.id, data));
}
@@ -44,7 +44,7 @@ export class UserController {
@UseGuards(JwtGuard)
async deleteCurrentUser(
@GetUser() user: User,
@Res({ passthrough: true }) response: Response
@Res({ passthrough: true }) response: Response,
) {
response.cookie("access_token", "accessToken", { maxAge: -1 });
response.cookie("refresh_token", "", {

View File

@@ -11,7 +11,7 @@ import { UpdateUserDto } from "./dto/updateUser.dto";
export class UserSevice {
constructor(
private prisma: PrismaService,
private emailService: EmailService
private emailService: EmailService,
) {}
async list() {
@@ -46,7 +46,7 @@ export class UserSevice {
if (e.code == "P2002") {
const duplicatedField: string = e.meta.target[0];
throw new BadRequestException(
`A user with this ${duplicatedField} already exists`
`A user with this ${duplicatedField} already exists`,
);
}
}
@@ -66,7 +66,7 @@ export class UserSevice {
if (e.code == "P2002") {
const duplicatedField: string = e.meta.target[0];
throw new BadRequestException(
`A user with this ${duplicatedField} already exists`
`A user with this ${duplicatedField} already exists`,
);
}
}

1
frontend/.prettierignore Normal file
View File

@@ -0,0 +1 @@
/src/i18n/translations/*

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-frontend",
"version": "0.17.3",
"version": "0.18.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-frontend",
"version": "0.17.3",
"version": "0.18.0",
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-frontend",
"version": "0.17.3",
"version": "0.18.0",
"scripts": {
"dev": "next dev",
"build": "next build",

View File

@@ -7,7 +7,7 @@ import { LOCALES } from "../../i18n/locales";
const LanguagePicker = () => {
const t = useTranslate();
const [selectedLanguage, setSelectedLanguage] = useState(
getCookie("language")?.toString()
getCookie("language")?.toString(),
);
const languages = Object.values(LOCALES).map((locale) => ({
@@ -23,7 +23,7 @@ const LanguagePicker = () => {
setCookie("language", value, {
sameSite: "lax",
expires: new Date(
new Date().setFullYear(new Date().getFullYear() + 1)
new Date().setFullYear(new Date().getFullYear() + 1),
),
});
location.reload();

View File

@@ -14,7 +14,7 @@ import userPreferences from "../../utils/userPreferences.util";
const ThemeSwitcher = () => {
const [colorScheme, setColorScheme] = useState(
userPreferences.get("colorScheme")
userPreferences.get("colorScheme"),
);
const { toggleColorScheme } = useMantineColorScheme();
const systemColorScheme = useColorScheme();
@@ -26,7 +26,7 @@ const ThemeSwitcher = () => {
userPreferences.set("colorScheme", value);
setColorScheme(value);
toggleColorScheme(
value == "system" ? systemColorScheme : (value as ColorScheme)
value == "system" ? systemColorScheme : (value as ColorScheme),
);
}}
data={[

View File

@@ -26,7 +26,7 @@ const showEnableTotpModal = (
qrCode: string;
secret: string;
password: string;
}
},
) => {
const t = translateOutsideContext();
return modals.openModal({

View File

@@ -5,7 +5,7 @@ import { translateOutsideContext } from "../../hooks/useTranslate.hook";
const showReverseShareLinkModal = (
modals: ModalsContextProps,
reverseShareToken: string,
appUrl: string
appUrl: string,
) => {
const t = translateOutsideContext();
const link = `${appUrl}/upload/${reverseShareToken}`;

View File

@@ -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}`;

View File

@@ -5,7 +5,7 @@ import { translateOutsideContext } from "../../hooks/useTranslate.hook";
const showShareLinkModal = (
modals: ModalsContextProps,
shareId: string,
appUrl: string
appUrl: string,
) => {
const t = translateOutsideContext();
const link = `${appUrl}/s/${shareId}`;

View File

@@ -21,7 +21,7 @@ const AdminConfigInput = ({
stringValue: configVariable.value ?? configVariable.defaultValue,
textValue: configVariable.value ?? configVariable.defaultValue,
numberValue: parseInt(
configVariable.value ?? configVariable.defaultValue
configVariable.value ?? configVariable.defaultValue,
),
booleanValue:
(configVariable.value ?? configVariable.defaultValue) == "true",

View File

@@ -33,7 +33,7 @@ const TestEmailButton = ({
<Textarea minRows={4} readOnly value={e.response.data.message} />
</Stack>
),
})
}),
);
};

View File

@@ -17,7 +17,7 @@ import toast from "../../../utils/toast.util";
const showCreateUserModal = (
modals: ModalsContextProps,
smtpEnabled: boolean,
getUsers: () => void
getUsers: () => void,
) => {
return modals.openModal({
title: "Create user",
@@ -55,7 +55,7 @@ const Body = ({
.string()
.min(8, t("common.error.too-short", { length: 8 }))
.optional(),
})
}),
),
});
@@ -87,7 +87,7 @@ const Body = ({
labelPosition="left"
label={t("admin.users.modal.create.manual-password")}
description={t(
"admin.users.modal.create.manual-password.description"
"admin.users.modal.create.manual-password.description",
)}
{...form.getInputProps("setPasswordManually", {
type: "checkbox",

View File

@@ -21,7 +21,7 @@ import toast from "../../../utils/toast.util";
const showUpdateUserModal = (
modals: ModalsContextProps,
user: User,
getUsers: () => void
getUsers: () => void,
) => {
const t = translateOutsideContext();
return modals.openModal({
@@ -53,7 +53,7 @@ const Body = ({
username: yup
.string()
.min(3, t("common.error.too-short", { length: 3 })),
})
}),
),
});
@@ -66,7 +66,7 @@ const Body = ({
password: yup
.string()
.min(8, t("common.error.too-short", { length: 8 })),
})
}),
),
});
@@ -115,8 +115,8 @@ const Body = ({
})
.then(() =>
toast.success(
t("admin.users.edit.update.notify.password.success")
)
t("admin.users.edit.update.notify.password.success"),
),
)
.catch(toast.axiosError);
})}

View File

@@ -76,7 +76,7 @@ const SignUpForm = () => {
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
<form
onSubmit={form.onSubmit((values) =>
signUp(values.email, values.username, values.password)
signUp(values.email, values.username, values.password),
)}
>
<TextInput

View File

@@ -44,6 +44,7 @@ const FilePreview = ({
href={`/api/shares/${shareId}/files/${fileId}?download=false`}
>
View original file
{/* Add translation? */}
</Button>
</Stack>
);

View File

@@ -8,7 +8,7 @@ import CopyTextField from "../../upload/CopyTextField";
const showCompletedReverseShareModal = (
modals: ModalsContextProps,
link: string,
getReverseShares: () => void
getReverseShares: () => void,
) => {
const t = translateOutsideContext();
return modals.openModal({

View File

@@ -13,7 +13,9 @@ import { useForm } from "@mantine/form";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { FormattedMessage } from "react-intl";
import useTranslate from "../../../hooks/useTranslate.hook";
import useTranslate, {
translateOutsideContext,
} from "../../../hooks/useTranslate.hook";
import shareService from "../../../services/share.service";
import { getExpirationPreview } from "../../../utils/date.util";
import toast from "../../../utils/toast.util";
@@ -23,10 +25,11 @@ import showCompletedReverseShareModal from "./showCompletedReverseShareModal";
const showCreateReverseShareModal = (
modals: ModalsContextProps,
showSendEmailNotificationOption: boolean,
getReverseShares: () => void
getReverseShares: () => void,
) => {
const t = translateOutsideContext();
return modals.openModal({
title: "Create reverse share",
title: t("account.reverseShares.modal.title"),
children: (
<Body
showSendEmailNotificationOption={showSendEmailNotificationOption}
@@ -64,7 +67,7 @@ const Body = ({
values.expiration_num + values.expiration_unit,
values.maxShareSize,
values.maxUseCount,
values.sendEmailNotification
values.sendEmailNotification,
)
.then(({ link }) => {
modals.closeAll();
@@ -150,7 +153,7 @@ const Body = ({
expiresOn: t("account.reverseShare.expires-on"),
neverExpires: t("account.reverseShare.never-expires"),
},
form
form,
)}
</Text>
</div>
@@ -174,7 +177,7 @@ const Body = ({
labelPosition="left"
label={t("account.reverseShares.modal.send-email")}
description={t(
"account.reverseShares.modal.send-email.description"
"account.reverseShares.modal.send-email.description",
)}
{...form.getInputProps("sendEmailNotification", {
type: "checkbox",

View File

@@ -6,7 +6,7 @@ import FilePreview from "../FilePreview";
const showFilePreviewModal = (
shareId: string,
file: FileMetaData,
modals: ModalsContextProps
modals: ModalsContextProps,
) => {
const mimeType = (mime.contentType(file.name) || "").split(";")[0];
return modals.openModal({

View File

@@ -8,7 +8,7 @@ import useTranslate, {
const showEnterPasswordModal = (
modals: ModalsContextProps,
submitCallback: (password: string) => Promise<void>
submitCallback: (password: string) => Promise<void>,
) => {
const t = translateOutsideContext();
return modals.openModal({

View File

@@ -7,7 +7,7 @@ import { FormattedMessage } from "react-intl";
const showErrorModal = (
modals: ModalsContextProps,
title: string,
text: string
text: string,
) => {
return modals.openModal({
closeOnClickOutside: false,

View File

@@ -12,7 +12,7 @@ function CopyTextField(props: { link: string }) {
const [checkState, setCheckState] = useState(false);
const [textClicked, setTextClicked] = useState(false);
const timerRef = useRef<number | ReturnType<typeof setTimeout> | undefined>(
undefined
undefined,
);
const copyLink = () => {

View File

@@ -1,6 +1,6 @@
import { Button, Center, createStyles, Group, Text } from "@mantine/core";
import { Dropzone as MantineDropzone } from "@mantine/dropzone";
import { Dispatch, ForwardedRef, SetStateAction, useRef } from "react";
import { ForwardedRef, useRef } from "react";
import { TbCloudUpload, TbUpload } from "react-icons/tb";
import { FormattedMessage } from "react-intl";
import useTranslate from "../../hooks/useTranslate.hook";
@@ -35,13 +35,11 @@ const useStyles = createStyles((theme) => ({
const Dropzone = ({
isUploading,
maxShareSize,
files,
setFiles,
showCreateUploadModalCallback,
}: {
isUploading: boolean;
maxShareSize: number;
files: FileUpload[];
setFiles: Dispatch<SetStateAction<FileUpload[]>>;
showCreateUploadModalCallback: (files: FileUpload[]) => void;
}) => {
const t = useTranslate();
@@ -55,11 +53,8 @@ const Dropzone = ({
}}
disabled={isUploading}
openRef={openRef as ForwardedRef<() => void>}
onDrop={(newFiles: FileUpload[]) => {
const fileSizeSum = [...newFiles, ...files].reduce(
(n, { size }) => n + size,
0
);
onDrop={(files: FileUpload[]) => {
const fileSizeSum = files.reduce((n, { size }) => n + size, 0);
if (fileSizeSum > maxShareSize) {
toast.error(
@@ -68,11 +63,11 @@ const Dropzone = ({
})
);
} else {
newFiles = newFiles.map((newFile) => {
files = files.map((newFile) => {
newFile.uploadingProgress = 0;
return newFile;
});
setFiles([...newFiles, ...files]);
showCreateUploadModalCallback(files);
}
}}
className={classes.dropzone}

View File

@@ -13,7 +13,7 @@ import CopyTextField from "../CopyTextField";
const showCompletedUploadModal = (
modals: ModalsContextProps,
share: Share,
appUrl: string
appUrl: string,
) => {
const t = translateOutsideContext();
return modals.openModal({

View File

@@ -26,6 +26,7 @@ import useTranslate, {
translateOutsideContext,
} from "../../../hooks/useTranslate.hook";
import shareService from "../../../services/share.service";
import { FileUpload } from "../../../types/File.type";
import { CreateShare } from "../../../types/share.type";
import { getExpirationPreview } from "../../../utils/date.util";
@@ -38,7 +39,8 @@ const showCreateUploadModal = (
allowUnauthenticatedShares: boolean;
enableEmailRecepients: boolean;
},
uploadCallback: (createShare: CreateShare) => void
files: FileUpload[],
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void
) => {
const t = translateOutsideContext();
@@ -47,6 +49,7 @@ const showCreateUploadModal = (
children: (
<CreateUploadModalBody
options={options}
files={files}
uploadCallback={uploadCallback}
/>
),
@@ -55,9 +58,11 @@ const showCreateUploadModal = (
const CreateUploadModalBody = ({
uploadCallback,
files,
options,
}: {
uploadCallback: (createShare: CreateShare) => void;
files: FileUpload[];
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void;
options: {
isUserSignedIn: boolean;
isReverseShare: boolean;
@@ -121,16 +126,19 @@ const CreateUploadModalBody = ({
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,
uploadCallback(
{
id: values.link,
expiration: expiration,
recipients: values.recipients,
description: values.description,
security: {
password: values.password,
maxViews: values.maxViews,
},
},
});
files
);
modals.closeAll();
}
})}
@@ -140,7 +148,7 @@ const CreateUploadModalBody = ({
<TextInput
style={{ flex: "1" }}
variant="filled"
label="Link"
label={t("upload.modal.link.label")}
placeholder="myAwesomeShare"
{...form.getInputProps("link")}
/>
@@ -283,8 +291,8 @@ const CreateUploadModalBody = ({
data={form.values.recipients}
placeholder={t("upload.modal.accordion.email.placeholder")}
searchable
{...form.getInputProps("recipients")}
creatable
autoComplete="email-recipients"
getCreateLabel={(query) => `+ ${query}`}
onCreate={(query) => {
if (!query.match(/^\S+@\S+\.\S+$/)) {
@@ -301,6 +309,7 @@ const CreateUploadModalBody = ({
return query;
}
}}
{...form.getInputProps("recipients")}
/>
</Accordion.Panel>
</Accordion.Item>
@@ -318,6 +327,7 @@ const CreateUploadModalBody = ({
"upload.modal.accordion.security.password.placeholder"
)}
label={t("upload.modal.accordion.security.password.label")}
autoComplete="off"
{...form.getInputProps("password")}
/>
<NumberInput
@@ -334,7 +344,7 @@ const CreateUploadModalBody = ({
</Accordion.Panel>
</Accordion.Item>
</Accordion>
<Button type="submit">
<Button type="submit" data-autofocus>
<FormattedMessage id="common.button.share" />
</Button>
</Stack>

View File

@@ -7,7 +7,7 @@ const useTranslate = () => {
return (
id: string,
values?: Parameters<typeof intl.formatMessage>[1],
opts?: Parameters<typeof intl.formatMessage>[2]
opts?: Parameters<typeof intl.formatMessage>[2],
) => {
return intl.formatMessage({ id }, values, opts) as string;
};
@@ -25,12 +25,12 @@ export const translateOutsideContext = () => {
messages: i18nUtil.getLocaleByCode(locale)?.messages,
defaultLocale: "en",
},
cache
cache,
);
return (
id: string,
values?: Parameters<typeof intl.formatMessage>[1],
opts?: Parameters<typeof intl.formatMessage>[2]
opts?: Parameters<typeof intl.formatMessage>[2],
) => {
return intl.formatMessage({ id }, values, opts) as string;
};

View File

@@ -4,8 +4,11 @@ import english from "./translations/en-US";
import spanish from "./translations/es-ES";
import finnish from "./translations/fi-FI";
import french from "./translations/fr-FR";
import dutch from "./translations/nl-BE";
import portuguese from "./translations/pt-BR";
import russian from "./translations/ru-RU";
import serbian from "./translations/sr-SP";
import thai from "./translations/th-TH";
import chineseSimplified from "./translations/zh-CN";
export const LOCALES = {
@@ -54,4 +57,19 @@ export const LOCALES = {
code: "ru-RU",
messages: russian,
},
THAI: {
name: "ไทย",
code: "th-TH",
messages: thai,
},
SERBIAN: {
name: "Srpski",
code: "sr-SP",
messages: serbian,
},
DUTCH: {
name: "Nederlands",
code: "nl-BE",
messages: dutch,
},
};

View File

@@ -65,7 +65,7 @@ export default {
"account.notify.password.success": "Adgangskoden er ændret",
"account.card.security.title": "Sikkerhed",
"account.card.security.totp.enable.description": "Indtast din nuværende adgangskode for at begynde opsætningen af 2-faktor login",
"account.card.security.totp.disable.description": "Enter your current password to disable TOTP",
"account.card.security.totp.disable.description": "Indtast din nuværende adgangskode for at begynde opsætningen af 2-faktor login",
"account.card.security.totp.button.start": "Start",
"account.modal.totp.title": "Aktiver 2-faktor login",
"account.modal.totp.step1": "Trin 1: Tilføj din 2-faktor Authenticator",
@@ -102,7 +102,7 @@ export default {
"account.shares.table.size": "Størrelse",
"account.shares.modal.share-informations": "Share informations",
"account.shares.modal.share-link": "Del link",
"account.shares.modal.delete.title": "Delete share {share}",
"account.shares.modal.delete.title": "Slet share {share}",
"account.shares.modal.delete.description": "Ønsker du virkelig at slette denne deling?",
// END /account/shares
// /account/reverseShares
@@ -111,6 +111,7 @@ export default {
"account.reverseShares.title.empty": "Der er tomt her 👀",
"account.reverseShares.description.empty": "You don't have any reverse shares.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Create reverse share",
"account.reverseShares.modal.expiration.label": "Udløb",
"account.reverseShares.modal.expiration.minute-singular": "Minut",
"account.reverseShares.modal.expiration.minute-plural": "Minutter",
@@ -133,12 +134,12 @@ export default {
"account.reverseShare.expires-on": "Denne omvendte deling udløber den {expiration}.",
"account.reverseShares.table.no-shares": "Der er ikke oprettet nogle delinger endnu",
"account.reverseShares.table.count.singular": "del",
"account.reverseShares.table.count.plural": "delinger",
"account.reverseShares.table.count.plural": "shares",
"account.reverseShares.table.shares": "Delinger",
"account.reverseShares.table.remaining": "Resterende anvendelser",
"account.reverseShares.table.max-size": "Maksimal størrelse for deling",
"account.reverseShares.table.expires": "Udløber d",
"account.reverseShares.modal.reverse-share-link": "Omvendt deling",
"account.reverseShares.modal.reverse-share-link": "Reverse share link",
"account.reverseShares.modal.delete.title": "Delete reverse share",
"account.reverseShares.modal.delete.description": "Ønsker du virkelig at slette denne omvendte deling? Hvis du gør det, vil de tilknyttede delinger også blive slettet.",
// END /account/reverseShares
@@ -168,7 +169,7 @@ export default {
"admin.users.modal.create.password": "Adgangskode",
"admin.users.modal.create.manual-password": "Angiv adgangskode manuelt",
"admin.users.modal.create.manual-password.description": "If not checked, the user will receive an email with a link to set their password.",
"admin.users.modal.create.admin": "Admin privileges",
"admin.users.modal.create.admin": "Admin rettigheder",
"admin.users.modal.create.admin.description": "If checked, the user will be able to access the admin panel.",
// END /admin/users
// /upload
@@ -205,7 +206,7 @@ export default {
"upload.modal.expires.year-singular": "År",
"upload.modal.expires.year-plural": "År",
"upload.modal.accordion.description.title": "Beskrivelse",
"upload.modal.accordion.description.placeholder": "Note for the recipients of this share",
"upload.modal.accordion.description.placeholder": "Bemærkning til modtagerne af dette share",
"upload.modal.accordion.email.title": "E-mail modtagere",
"upload.modal.accordion.email.placeholder": "Indtast e-mail modtagere",
"upload.modal.accordion.email.invalid-email": "Ugyldig e-mailadresse",
@@ -295,7 +296,7 @@ export default {
"admin.config.smtp.button.test": "Send test e-mail",
// 404
"404.description": "Ups! Denne side findes ikke.",
"404.button.home": "Bring me back home",
"404.button.home": "Gå tilbage",
// Common translations
"common.button.save": "Gem",
"common.button.create": "Opret",

View File

@@ -64,8 +64,8 @@ export default {
"account.card.password.new": "Neues Passwort",
"account.notify.password.success": "Passwort erfolgreich geändert",
"account.card.security.title": "Sicherheit",
"account.card.security.totp.enable.description": "Geben dein aktuelles Passwort ein, um TOTP zu aktivieren",
"account.card.security.totp.disable.description": "Geben dein aktuelles Passwort ein, um TOTP zu deaktivieren",
"account.card.security.totp.enable.description": "Gib dein aktuelles Passwort ein, um TOTP zu aktivieren",
"account.card.security.totp.disable.description": "Gib dein aktuelles Passwort ein, um TOTP zu deaktivieren",
"account.card.security.totp.button.start": "Starten",
"account.modal.totp.title": "TOTP aktivieren",
"account.modal.totp.step1": "Schritt 1: Füge deinen Authenticator hinzu",
@@ -111,6 +111,7 @@ export default {
"account.reverseShares.title.empty": "Es ist leer hier 👀",
"account.reverseShares.description.empty": "Du hast keine externen Freigaben erstellt.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Externe Freigabe erstellen",
"account.reverseShares.modal.expiration.label": "Gültig bis",
"account.reverseShares.modal.expiration.minute-singular": "Minute",
"account.reverseShares.modal.expiration.minute-plural": "Minuten",
@@ -177,7 +178,7 @@ export default {
"upload.notify.count-failed": "{count} Dateien konnten nicht hochgeladen werden. Wird erneut versucht.",
// Dropzone.tsx
"upload.dropzone.title": "Dateien hochladen",
"upload.dropzone.description": "Ziehen Sie Dateien hierher, um Ihre Freigabe zu starten. Wir können nur Dateien akzeptieren, die insgesamt weniger als {maxSize} groß sind.",
"upload.dropzone.description": "Ziehe Dateien hierher, um deine Freigabe zu starten. Wir können nur Dateien akzeptieren, die insgesamt weniger als {maxSize} groß sind.",
"upload.dropzone.notify.file-too-big": "Ihre Dateien überschreiten die maximale Freigabegröße von {maxSize}.",
// FileList.tsx
"upload.filelist.name": "Name",
@@ -228,7 +229,7 @@ export default {
"share.error.not-found.title": "Freigabe nicht gefunden",
"share.error.not-found.description": "Die gesuchte Freigabe existiert nicht.",
"share.modal.password.title": "Passwort erforderlich",
"share.modal.password.description": "Um auf diese Freigabe zuzugreifen, geben Sie bitte das Passwort für die Freigabe ein.",
"share.modal.password.description": "Um auf diese Freigabe zuzugreifen, gib bitte das Passwort für die Freigabe ein.",
"share.modal.password": "Passwort",
"share.modal.error.invalid-password": "Ungültiges Passwort",
"share.button.download-all": "Alles herunterladen",
@@ -237,7 +238,7 @@ export default {
"share.table.name": "Name",
"share.table.size": "Größe",
"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 laden Sie die Datei herunter, um sie anzuzeigen.",
"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]
// /admin/config
"admin.config.title": "Einstellungen",
@@ -279,7 +280,7 @@ export default {
"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",
"admin.config.share.zip-compression-level.description": "Adjust the level to balance between file size and compression speed. Valid values range from 0 to 9, with 0 being no compression and 9 being maximum compression. ",
"admin.config.share.zip-compression-level.description": "Passe den Wert an, um ein Gleichgewicht zwischen Dateigröße und Komprimierungsgeschwindigkeit herzustellen. Gültige Werte liegen zwischen 0 und 9, wobei 0 für keine Komprimierung und 9 für maximale Komprimierung steht.",
"admin.config.smtp.enabled": "Aktiviert",
"admin.config.smtp.enabled.description": "Gibt an, ob SMTP aktiviert ist. Aktiviere dies nur, wenn Du den Host, den Port, die Email, den Benutzernamen und das Passwort deines SMTP-Servers eingegeben hast.",
"admin.config.smtp.host": "Host",

View File

@@ -149,6 +149,7 @@ export default {
"You don't have any reverse shares.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Create reverse share",
"account.reverseShares.modal.expiration.label": "Expiration",
"account.reverseShares.modal.expiration.minute-singular": "Minute",
"account.reverseShares.modal.expiration.minute-plural": "Minutes",

View File

@@ -5,7 +5,7 @@ export default {
"navbar.home": "Inicio",
"navbar.signup": "Registrarse",
"navbar.links.shares": "Mis compartidos",
"navbar.links.reverse": "Reverse Shares",
"navbar.links.reverse": "Comparticiones inversas",
"navbar.avatar.account": "Mi cuenta",
"navbar.avatar.admin": "Administración",
"navbar.avatar.signout": "Cerrar sesión",
@@ -106,11 +106,12 @@ export default {
"account.shares.modal.delete.description": "¿Seguro que quieres eliminar este compartido?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Reverse Shares",
"account.reverseShares.description": "Un Reverse Share te permite generar una URL única con la que usuarios externos pueden compartir archivos.",
"account.reverseShares.title": "Comparticiones inversas",
"account.reverseShares.description": "Una compartición inversa te permite generar una URL única con la que usuarios externos pueden compartir archivos.",
"account.reverseShares.title.empty": "Aquí está vacío 👀",
"account.reverseShares.description.empty": "No tienes ningún Reverse Share.",
"account.reverseShares.description.empty": "No tienes ninguna compartición inversa.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Crear compartición inversa",
"account.reverseShares.modal.expiration.label": "Expiración",
"account.reverseShares.modal.expiration.minute-singular": "Minuto",
"account.reverseShares.modal.expiration.minute-plural": "Minutos",
@@ -126,11 +127,11 @@ export default {
"account.reverseShares.modal.expiration.year-plural": "Años",
"account.reverseShares.modal.max-size.label": "Tamaño máximo del compartido",
"account.reverseShares.modal.send-email": "Enviar notificación por correo",
"account.reverseShares.modal.send-email.description": "Enviar una notificación por correo cuando se comparta algo con este enlace de Reverse Share.",
"account.reverseShares.modal.send-email.description": "Enviar una notificación por correo cuando se comparta algo con este enlace de compartición inversa.",
"account.reverseShares.modal.max-use.label": "Máximo de usos",
"account.reverseShares.modal.max-use.description": "Cantidad máxima de veces que esta URL se puede usar para crear un compartido.",
"account.reverseShare.never-expires": "Esta Reverse Share nunca expirará.",
"account.reverseShare.expires-on": "Esta Reverse Share expirará en {expiration}.",
"account.reverseShare.never-expires": "Esta compartición inversa nunca expirará.",
"account.reverseShare.expires-on": "Esta compartición inversa expirará en {expiration}.",
"account.reverseShares.table.no-shares": "Todavía no se han creado compartidos",
"account.reverseShares.table.count.singular": "compartido",
"account.reverseShares.table.count.plural": "compartidos",
@@ -138,9 +139,9 @@ export default {
"account.reverseShares.table.remaining": "Usos restantes",
"account.reverseShares.table.max-size": "Tamaño máximo del compartido",
"account.reverseShares.table.expires": "Expira en",
"account.reverseShares.modal.reverse-share-link": "Enlace de Reverse Share",
"account.reverseShares.modal.delete.title": "Eliminar Reverse Share",
"account.reverseShares.modal.delete.description": "¿Seguro que quieres eliminar esta Reverse Share? Si lo haces, todos los archivos asociados también serán eliminados.",
"account.reverseShares.modal.reverse-share-link": "Enlace de compartición inversa",
"account.reverseShares.modal.delete.title": "Eliminar compartición inversa",
"account.reverseShares.modal.delete.description": "¿Seguro que quieres eliminar esta compartición inversa? Si lo haces, todos los archivos asociados también serán eliminados.",
// END /account/reverseShares
// /admin
"admin.title": "Administración",
@@ -260,10 +261,10 @@ export default {
"admin.config.email.share-recipients-subject.description": "Asunto del correo el cual es enviado al destinatario del compartido.",
"admin.config.email.share-recipients-message": "Mensaje destinatario",
"admin.config.email.share-recipients-message.description": "Mensaje el cual es enviado al destinatario del compartido. Variables disponibles:\n{creator} - Nombre del creador del compartido\n {shareUrl} - URL del compartido\n {desc} - Descripción del compartido\n {expires} - Fecha de expiración del compartido\nLas variables serán remplazadas con los valores reales.",
"admin.config.email.reverse-share-subject": "Asunto del Reverse Share",
"admin.config.email.reverse-share-subject.description": "Asunto del correo el cual se envía cuando alguien comparte algo con tu enlace de Reverse Share.",
"admin.config.email.reverse-share-message": "Mensaje del Reverse Share",
"admin.config.email.reverse-share-message.description": "Mensaje que se envía cuando alguien comparte algo con tu enlace de Reverse Share. {shareUrl} Se remplazará con el nombre del creador y la URL del compartido.",
"admin.config.email.reverse-share-subject": "Asunto de la compartición inversa",
"admin.config.email.reverse-share-subject.description": "Asunto del correo el cual se envía cuando alguien comparte algo con tu enlace de compartición inversa.",
"admin.config.email.reverse-share-message": "Mensaje de la compartición inversa",
"admin.config.email.reverse-share-message.description": "Mensaje que se envía cuando alguien comparte algo con tu enlace de compartición inversa. {shareUrl} Se remplazará con el nombre del creador y la URL del compartido.",
"admin.config.email.reset-password-subject": "Asunto restablecer contraseña",
"admin.config.email.reset-password-subject.description": "Asunto del correo que se envía cuando un usuario solicita restablecer la contraseña.",
"admin.config.email.reset-password-message": "Mensaje restablecer contraseña",

View File

@@ -111,6 +111,7 @@ export default {
"account.reverseShares.title.empty": "Täällä on tyhjää 👀",
"account.reverseShares.description.empty": "Sinulla ei ole käänteisiä jakoja.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Create reverse share",
"account.reverseShares.modal.expiration.label": "Vanhentuminen",
"account.reverseShares.modal.expiration.minute-singular": "Minuutti",
"account.reverseShares.modal.expiration.minute-plural": "Minuuttia",
@@ -258,7 +259,7 @@ export default {
"admin.config.email.enable-share-email-recipients.description": "Salli sähköpostien jakaminen vastaanottajille. Ota tämä käyttöön vain, jos olet ottanut SMTP:n käyttöön.",
"admin.config.email.share-recipients-subject": "Sähköpostijaon otsikko",
"admin.config.email.share-recipients-subject.description": "Sähköpostin aihe, joka saa lähetetään jaon vastaanottajille.",
"admin.config.email.share-recipients-message": "Sähköpostijaon otsikko",
"admin.config.email.share-recipients-message": "Sähköpostijaon viesti",
"admin.config.email.share-recipients-message.description": "Viesti joka lähetetään jaon vastaanottajille. Saatavilla olevat muuttujat:\n {creator} - Jaon luojan käyttäjänimi\n {shareUrl} - Jaon URL\n {desc} - Jaon kuvaus\n {expires} - Jaon päättymispäivä\n Muuttujat korvataan todellisella arvolla.",
"admin.config.email.reverse-share-subject": "Käänteisen jaon aihe",
"admin.config.email.reverse-share-subject.description": "Aihe sähköpostin joka lähetetään kun joku loi jaon käänteisen jakolinkin kanssa.",

View File

@@ -111,6 +111,7 @@ export default {
"account.reverseShares.title.empty": "Il n'y a rien ici 👀",
"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",
"account.reverseShares.modal.expiration.minute-singular": "Minute",
"account.reverseShares.modal.expiration.minute-plural": "Minutes",

View File

@@ -0,0 +1,324 @@
export default {
// Navbar
"navbar.upload": "Verzenden",
"navbar.signin": "Aanmelden",
"navbar.home": "Startpagina",
"navbar.signup": "Registreren",
"navbar.links.shares": "Mijn gedeelde bestanden",
"navbar.links.reverse": "Reverse shares",
"navbar.avatar.account": "Mijn account",
"navbar.avatar.admin": "Beheer",
"navbar.avatar.signout": "Afmelden",
// END navbar
// /
"home.title": "Een <h>zelfgehost</h> platform voor het delen van bestanden.",
"home.description": "Wil je echt je persoonlijke bestanden geven aan derden zoals WeTransfer?",
"home.bullet.a.name": "Zelf-gehost",
"home.bullet.a.description": "Host Pingvin Share op uw eigen machine.",
"home.bullet.b.name": "Privacy",
"home.bullet.b.description": "Uw bestanden zijn van u en mogen nooit in handen komen van derden.",
"home.bullet.c.name": "Geen vervelende limiet voor bestandsgrootte",
"home.bullet.c.description": "Upload zo grote bestanden als je wilt. Alleen je harde schijf is je limiet.",
"home.button.start": "Aan de slag",
"home.button.source": "Bron code",
// END /
// /auth/signin
"signin.title": "Welkom terug",
"signin.description": "Heeft u nog geen account?",
"signin.button.signup": "Registreren",
"signin.input.email-or-username": "E-mailadres of gebruikersnaam",
"signin.input.email-or-username.placeholder": "Uw e-mailadres of gebruikersnaam",
"signin.input.password": "Wachtwoord",
"signin.input.password.placeholder": "Uw wachtwoord",
"signin.button.submit": "Aanmelden",
"signIn.notify.totp-required.title": "Tweestapsverificatie vereist",
"signIn.notify.totp-required.description": "Voer uw tweestapsverificatiecode in",
// END /auth/signin
// /auth/signup
"signup.title": "Account aanmaken",
"signup.description": "Heeft u al een account?",
"signup.button.signin": "Aanmelden",
"signup.input.username": "Gebruikersnaam",
"signup.input.username.placeholder": "Uw gebruikersnaam",
"signup.input.email": "E-mailadres",
"signup.input.email.placeholder": "Uw e-mailadres",
"signup.button.submit": "Laten we beginnen",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Wachtwoord vergeten?",
"resetPassword.description": "Voer uw e-mailadres in om uw wachtwoord opnieuw in te stellen.",
"resetPassword.notify.success": "Een e-mail is verzonden met een link om uw wachtwoord te resetten.",
"resetPassword.button.back": "Terug naar login pagina",
"resetPassword.text.resetPassword": "Wachtwoord opnieuw instellen",
"resetPassword.text.enterNewPassword": "Voer uw nieuwe wachtwoord in",
"resetPassword.input.password": "Nieuw wachtwoord",
"resetPassword.notify.passwordReset": "Uw wachtwoord is met succes opnieuw ingesteld.",
// /account
"account.title": "Mijn account",
"account.card.info.title": "Account informatie",
"account.card.info.username": "Gebruikersnaam",
"account.card.info.email": "E-mailadres",
"account.notify.info.success": "Account succesvol bijgewerkt",
"account.card.password.title": "Wachtwoord",
"account.card.password.old": "Oud wachtwoord",
"account.card.password.new": "Nieuw wachtwoord",
"account.notify.password.success": "Wachtwoord succesvol gewijzigd",
"account.card.security.title": "Beveiliging",
"account.card.security.totp.enable.description": "Voer uw huidige wachtwoord in om TOTP in te schakelen",
"account.card.security.totp.disable.description": "Voer uw huidige wachtwoord in om TOTP uit te schakelen",
"account.card.security.totp.button.start": "Start",
"account.modal.totp.title": "TOTP inschakelen",
"account.modal.totp.step1": "Stap 1: Voeg uw authenticator toe",
"account.modal.totp.step2": "Stap 2: Valideer uw code",
"account.modal.totp.enterManually": "Handmatig invoeren",
"account.modal.totp.code": "Code",
"account.modal.totp.clickToCopy": "Klik om te kopiëren",
"account.modal.totp.verify": "Verifiëren",
"account.notify.totp.disable": "TOTP succesvol uitgeschakeld",
"account.notify.totp.enable": "TOTP succesvol ingeschakeld",
"account.card.language.title": "Taal",
"account.card.language.description": "Het project is vertaald door de community. Sommige talen zijn mogelijk onvolledig.",
"account.card.color.title": "Kleuren schema",
// ThemeSwitcher.tsx
"account.theme.dark": "Donker",
"account.theme.light": "Licht",
"account.theme.system": "Systeem",
"account.button.delete": "Account verwijderen",
"account.modal.delete.title": "Account verwijderen",
"account.modal.delete.description": "Weet u zeker dat u uw account met al uw gedeelde bestanden wilt verwijderen?",
// END /account
// /account/shares
"account.shares.title": "Mijn gedeelde bestanden",
"account.shares.title.empty": "Het is hier leeg 👀",
"account.shares.description.empty": "U heeft geen gedeelde bestanden.",
"account.shares.button.create": "Maak er één",
"account.shares.info.title": "Gegevens delen",
"account.shares.table.id": "ID",
"account.shares.table.name": "Naam",
"account.shares.table.description": "Beschrijving",
"account.shares.table.visitors": "Bezoekers",
"account.shares.table.expiresAt": "Verloopt op",
"account.shares.table.createdAt": "Aangemaakt op",
"account.shares.table.size": "Grootte",
"account.shares.modal.share-informations": "Gegevens delen",
"account.shares.modal.share-link": "Deel link",
"account.shares.modal.delete.title": "Delete share {share}",
"account.shares.modal.delete.description": "Do you really want to delete this share?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Reverse shares",
"account.reverseShares.description": "A reverse share allows you to generate a unique URL that allows external users to create a share.",
"account.reverseShares.title.empty": "Het is hier leeg 👀",
"account.reverseShares.description.empty": "You don't have any reverse shares.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Create reverse share",
"account.reverseShares.modal.expiration.label": "Vervaldatum",
"account.reverseShares.modal.expiration.minute-singular": "Minuut",
"account.reverseShares.modal.expiration.minute-plural": "Minuten",
"account.reverseShares.modal.expiration.hour-singular": "Uur",
"account.reverseShares.modal.expiration.hour-plural": "Uren",
"account.reverseShares.modal.expiration.day-singular": "Dag",
"account.reverseShares.modal.expiration.day-plural": "Dagen",
"account.reverseShares.modal.expiration.week-singular": "Week",
"account.reverseShares.modal.expiration.week-plural": "Weken",
"account.reverseShares.modal.expiration.month-singular": "Maand",
"account.reverseShares.modal.expiration.month-plural": "Maanden",
"account.reverseShares.modal.expiration.year-singular": "Jaar",
"account.reverseShares.modal.expiration.year-plural": "Jaren",
"account.reverseShares.modal.max-size.label": "Max share size",
"account.reverseShares.modal.send-email": "Stuur e-mail notificatie",
"account.reverseShares.modal.send-email.description": "Send an email notification when a share is created with this reverse share link.",
"account.reverseShares.modal.max-use.label": "Max uses",
"account.reverseShares.modal.max-use.description": "The maximum amount of times this URL can be used to create a share.",
"account.reverseShare.never-expires": "This reverse share will never expire.",
"account.reverseShare.expires-on": "This reverse share will expire on {expiration}.",
"account.reverseShares.table.no-shares": "No shares created yet",
"account.reverseShares.table.count.singular": "share",
"account.reverseShares.table.count.plural": "shares",
"account.reverseShares.table.shares": "Shares",
"account.reverseShares.table.remaining": "Remaining uses",
"account.reverseShares.table.max-size": "Max share size",
"account.reverseShares.table.expires": "Verloopt op",
"account.reverseShares.modal.reverse-share-link": "Reverse share link",
"account.reverseShares.modal.delete.title": "Delete reverse share",
"account.reverseShares.modal.delete.description": "Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
// END /account/reverseShares
// /admin
"admin.title": "Beheer",
"admin.button.users": "Gebruikers beheer",
"admin.button.config": "Configuratie",
"admin.version": "Versie",
// END /admin
// /admin/users
"admin.users.title": "Gebruikers beheren",
"admin.users.table.username": "Gebruikersnaam",
"admin.users.table.email": "E-mailadres",
"admin.users.table.admin": "Beheerder",
"admin.users.edit.update.title": "Gebruiker {username} bijwerken",
"admin.users.edit.update.admin-privileges": "Beheerder privileges",
"admin.users.edit.update.change-password.title": "Wachtwoord wijzigen",
"admin.users.edit.update.change-password.field": "Nieuw wachtwoord",
"admin.users.edit.update.change-password.button": "Nieuw wachtwoord opslaan",
"admin.users.edit.update.notify.password.success": "Wachtwoord succesvol gewijzigd",
"admin.users.edit.delete.title": "Gebruiker {username} verwijderen",
"admin.users.edit.delete.description": "Do you really want to delete this user and all his shares?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Gebruiker aanmaken",
"admin.users.modal.create.username": "Gebruikersnaam",
"admin.users.modal.create.email": "E-mailadres",
"admin.users.modal.create.password": "Wachtwoord",
"admin.users.modal.create.manual-password": "Wachtwoord handmatig instellen",
"admin.users.modal.create.manual-password.description": "Indien niet aangevinkt, ontvangt de gebruiker een e-mail met een link om zijn wachtwoord in te stellen.",
"admin.users.modal.create.admin": "Beheerder privileges",
"admin.users.modal.create.admin.description": "Indien aangevinkt, heeft de gebruiker toegang tot de beheeromgeving.",
// END /admin/users
// /upload
"upload.title": "Uploaden",
"upload.notify.generic-error": "An error occurred while finishing your share.",
"upload.notify.count-failed": "{count} bestanden konden niet worden geüpload. Opnieuw proberen.",
// Dropzone.tsx
"upload.dropzone.title": "Bestanden uploaden",
"upload.dropzone.description": "Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
"upload.dropzone.notify.file-too-big": "Your files exceed the maximum share size of {maxSize}.",
// FileList.tsx
"upload.filelist.name": "Naam",
"upload.filelist.size": "Grootte",
// showCreateUploadModal.tsx
"upload.modal.title": "Create Share",
"upload.modal.link.error.invalid": "Can only contain letters, numbers, underscores, and hyphens",
"upload.modal.link.error.taken": "Deze link is al in gebruik",
"upload.modal.not-signed-in": "U bent niet aangemeld",
"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.link.label": "Koppeling",
"upload.modal.expires.label": "Vervaldatum",
"upload.modal.expires.minute-singular": "Minuut",
"upload.modal.expires.minute-plural": "Minuten",
"upload.modal.expires.hour-singular": "Uur",
"upload.modal.expires.hour-plural": "Uren",
"upload.modal.expires.day-singular": "Dag",
"upload.modal.expires.day-plural": "Dagen",
"upload.modal.expires.week-singular": "Week",
"upload.modal.expires.week-plural": "Weken",
"upload.modal.expires.month-singular": "Maand",
"upload.modal.expires.month-plural": "Maanden",
"upload.modal.expires.year-singular": "Jaar",
"upload.modal.expires.year-plural": "Jaren",
"upload.modal.accordion.description.title": "Beschrijving",
"upload.modal.accordion.description.placeholder": "Note for the recipients of this share",
"upload.modal.accordion.email.title": "E-mail ontvangers",
"upload.modal.accordion.email.placeholder": "Voer e-mail ontvangers in",
"upload.modal.accordion.email.invalid-email": "Ongeldig e-mailadres",
"upload.modal.accordion.security.title": "Beveiliging opties",
"upload.modal.accordion.security.password.label": "Paswoord beveiling",
"upload.modal.accordion.security.password.placeholder": "Geen wachtwoord",
"upload.modal.accordion.security.max-views.label": "Maximum aantal weergaven",
"upload.modal.accordion.security.max-views.placeholder": "Onbeperkt",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "This share will never expire.",
"upload.modal.completed.expires-on": "This share will expire on {expiration}.",
"upload.modal.completed.share-ready": "Share ready",
// END /upload
// /share/[id]
"share.title": "Share {shareId}",
"share.description": "Kijk eens wat ik met je heb gedeeld!",
"share.error.visitor-limit-exceeded.title": "Bezoekerslimiet overschreden",
"share.error.visitor-limit-exceeded.description": "The visitor limit from this share has been exceeded.",
"share.error.removed.title": "Share removed",
"share.error.not-found.title": "Share not found",
"share.error.not-found.description": "The share you're looking for doesn't exist.",
"share.modal.password.title": "Wachtwoord vereist",
"share.modal.password.description": "To access this share please enter the password for the share.",
"share.modal.password": "Wachtwoord",
"share.modal.error.invalid-password": "Ongeldig wachtwoord",
"share.button.download-all": "Alles downloaden",
"share.notify.download-all-preparing": "The share is preparing. Try again in a few minutes.",
"share.modal.file-link": "Bestand koppeling",
"share.table.name": "Naam",
"share.table.size": "Grootte",
"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]
// /admin/config
"admin.config.title": "Configuratie",
"admin.config.category.general": "Algemeen",
"admin.config.category.share": "Delen",
"admin.config.category.email": "E-mail",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "App naam",
"admin.config.general.app-name.description": "Naam van de applicatie",
"admin.config.general.app-url": "App URL",
"admin.config.general.app-url.description": "On which URL Pingvin Share is available",
"admin.config.general.show-home-page": "Toon startpagina",
"admin.config.general.show-home-page.description": "Toon of verberg de home pagina",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description": "Verander uw logo door een nieuwe afbeelding te uploaden. De afbeelding moet PNG zijn en het formaat moet 1:1 hebben.",
"admin.config.general.logo.placeholder": "Afbeelding kiezen",
"admin.config.email.enable-share-email-recipients": "Enable share email recipients",
"admin.config.email.enable-share-email-recipients.description": "Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
"admin.config.email.share-recipients-subject": "Share recipients subject",
"admin.config.email.share-recipients-subject.description": "Subject of the email which gets sent to the share recipients.",
"admin.config.email.share-recipients-message": "Share recipients message",
"admin.config.email.share-recipients-message.description": "Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual value.",
"admin.config.email.reverse-share-subject": "Reverse share subject",
"admin.config.email.reverse-share-subject.description": "Subject of the email which gets sent when someone created a share with your reverse share link.",
"admin.config.email.reverse-share-message": "Reverse share message",
"admin.config.email.reverse-share-message.description": "Message which gets sent when someone created a share with your reverse share link. {shareUrl} will be replaced with the creator's name and the share URL.",
"admin.config.email.reset-password-subject": "Reset wachtwoord onderwerp",
"admin.config.email.reset-password-subject.description": "Onderwerp van de e-mail die wordt verzonden wanneer een gebruiker een wachtwoordreset aanvraagt.",
"admin.config.email.reset-password-message": "Wachtwoord opnieuw instellen bericht",
"admin.config.email.reset-password-message.description": "Bericht dat wordt verzonden wanneer een gebruiker een wachtwoord reset aanvraagt. {url} zal worden vervangen door de wachtwoord reset URL.",
"admin.config.email.invite-subject": "Onderwerp uitnodiging",
"admin.config.email.invite-subject.description": "Onderwerp van de e-mail die wordt verzonden wanneer een beheerder een gebruiker uitnodigt.",
"admin.config.email.invite-message": "Bericht uitnodiging",
"admin.config.email.invite-message.description": "Bericht dat wordt verzonden wanneer een beheerder een gebruiker uitnodigt. {url} zal worden vervangen door de uitnodigings-URL en {password} met het wachtwoord.",
"admin.config.share.allow-registration": "Sta registratie toe",
"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-size": "Max grootte",
"admin.config.share.max-size.description": "Maximum share size in bytes",
"admin.config.share.zip-compression-level": "Zip compressie niveau",
"admin.config.share.zip-compression-level.description": "Pas het niveau aan voor evenwicht tussen bestandsgrootte en compressie snelheid. Geldige waarden variëren van 0 tot 9, waarbij 0 geen compressie is en 9 de maximale compressie is. ",
"admin.config.smtp.enabled": "Inschakelen",
"admin.config.smtp.enabled.description": "Of SMTP is ingeschakeld. Stel dit alleen in op true als u de host hebt ingevoerd, poort, e-mail, gebruiker en wachtwoord van uw SMTP-server.",
"admin.config.smtp.host": "Host",
"admin.config.smtp.host.description": "Host van de SMTP-server",
"admin.config.smtp.port": "Poort",
"admin.config.smtp.port.description": "Poort van de SMTP-server",
"admin.config.smtp.email": "E-mail",
"admin.config.smtp.email.description": "E-mailadres waar de e-mails van worden verzonden",
"admin.config.smtp.username": "Gebruikersnaam",
"admin.config.smtp.username.description": "Gebruikersnaam van de SMTP-server",
"admin.config.smtp.password": "Wachtwoord",
"admin.config.smtp.password.description": "Wachtwoord van de SMTP-server",
"admin.config.smtp.button.test": "Test e-mail verzenden",
// 404
"404.description": "Oeps, deze pagina bestaat niet.",
"404.button.home": "Breng me terug naar huis",
// Common translations
"common.button.save": "Opslaan",
"common.button.create": "Aanmaken",
"common.button.submit": "Verzenden",
"common.button.delete": "Verwijderen",
"common.button.cancel": "Annuleer",
"common.button.confirm": "Bevestigen",
"common.button.disable": "Uitschakelen",
"common.button.share": "Delen",
"common.button.generate": "Genereren",
"common.button.done": "Voltooid",
"common.text.link": "Koppeling",
"common.text.or": "of",
"common.button.go-back": "Ga terug",
"common.notify.copied": "Uw link is gekopieerd naar het klembord",
"common.success": "Succes",
"common.error": "Fout",
"common.error.unknown": "Er is een onbekende fout opgetreden",
"common.error.invalid-email": "Ongeldig e-mailadres",
"common.error.too-short": "Moet ten minste {length} tekens bevatten",
"common.error.too-long": "Moet maximaal {length} tekens bevatten",
"common.error.exact-length": "Moet precies {length} tekens bevatten",
"common.error.invalid-number": "Moet een getal zijn",
"common.error.field-required": "Dit veld is verplicht"
};

View File

@@ -26,7 +26,7 @@ export default {
"signin.title": "Bem-vindo de volta",
"signin.description": "Ainda não tem uma conta?",
"signin.button.signup": "Registo",
"signin.input.email-or-username": "Correio ou Nome de utilizador",
"signin.input.email-or-username": "E-mail ou nome de usuário",
"signin.input.email-or-username.placeholder": "Seu e-mail ou nome de usuário",
"signin.input.password": "Senha",
"signin.input.password.placeholder": "A sua senha",
@@ -38,7 +38,7 @@ export default {
"signup.title": "Criar uma conta",
"signup.description": "Já tem uma conta?",
"signup.button.signin": "Iniciar sessão",
"signup.input.username": "Utilizador",
"signup.input.username": "Nome de usuário",
"signup.input.username.placeholder": "Seu nome de usuário",
"signup.input.email": "E-mail",
"signup.input.email.placeholder": "Seu e-mail",
@@ -56,7 +56,7 @@ export default {
// /account
"account.title": "A minha conta",
"account.card.info.title": "Informação sobre a conta",
"account.card.info.username": "Nome do usuário",
"account.card.info.username": "Nome de usuário",
"account.card.info.email": "E-mail",
"account.notify.info.success": "A conta foi atualizada com sucesso",
"account.card.password.title": "Senha",
@@ -111,6 +111,7 @@ export default {
"account.reverseShares.title.empty": "Está vazio aqui 👀",
"account.reverseShares.description.empty": "Você não tem nenhum compartilhamento reverso.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Criar compartilhamento reverso",
"account.reverseShares.modal.expiration.label": "Expiração",
"account.reverseShares.modal.expiration.minute-singular": "Minuto",
"account.reverseShares.modal.expiration.minute-plural": "Minutos",
@@ -150,7 +151,7 @@ export default {
// END /admin
// /admin/users
"admin.users.title": "Gerenciamento de usuários",
"admin.users.table.username": "Nome do usuário",
"admin.users.table.username": "Nome de usuário",
"admin.users.table.email": "E-mail",
"admin.users.table.admin": "Admin",
"admin.users.edit.update.title": "Atualizar usuário {username}",
@@ -163,7 +164,7 @@ export default {
"admin.users.edit.delete.description": "Você realmente quer excluir este usuário e todos os seus compartilhamentos?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Criar usuário",
"admin.users.modal.create.username": "Nome do usuário",
"admin.users.modal.create.username": "Nome de usuário",
"admin.users.modal.create.email": "E-mail",
"admin.users.modal.create.password": "Senha",
"admin.users.modal.create.manual-password": "Definir senha manualmente",
@@ -281,14 +282,14 @@ export default {
"admin.config.share.zip-compression-level": "Nível de compressão",
"admin.config.share.zip-compression-level.description": "Ajuste o nível para equilibrar entre o tamanho do arquivo e a velocidade de compressão. Valores válidos vão de 0 a 9, com 0 sendo sem compressão e 9 sendo compressão máxima. ",
"admin.config.smtp.enabled": "Ativado",
"admin.config.smtp.enabled.description": "Se o SMTP está habilitado. Apenas defina como verdadeiro se você digitou o host, porta, e-mail, usuário e senha do seu servidor SMTP.",
"admin.config.smtp.enabled.description": "Se o SMTP está habilitado. Apenas defina como verdadeiro se você digitou o servidor, porta, e-mail, usuário e senha do seu servidor SMTP.",
"admin.config.smtp.host": "Servidor",
"admin.config.smtp.host.description": "Nome do Servidor SMTP",
"admin.config.smtp.port": "Porta",
"admin.config.smtp.port.description": "Porta do Servidor SMTP",
"admin.config.smtp.email": "E-mail",
"admin.config.smtp.email.description": "Endereço de e-mail do qual os e-mails são enviados",
"admin.config.smtp.username": "Nome do usuário",
"admin.config.smtp.username": "Nome de usuário",
"admin.config.smtp.username.description": "Nome de usuário do servidor SMTP",
"admin.config.smtp.password": "Senha",
"admin.config.smtp.password.description": "Senha do servidor SMTP",

View File

@@ -1,438 +1,324 @@
export default {
// Navbar
"navbar.upload": "Upload",
"navbar.signin": "Sign in",
"navbar.home": "Home",
"navbar.signup": "Sign Up",
"navbar.links.shares": "My shares",
"navbar.links.reverse": "Reverse shares",
"navbar.avatar.account": "My account",
"navbar.avatar.admin": "Administration",
"navbar.avatar.signout": "Sign out",
"navbar.upload": "Загрузить",
"navbar.signin": "Вход",
"navbar.home": "Главная",
"navbar.signup": "Зарегистрироваться",
"navbar.links.shares": "Мои загрузки",
"navbar.links.reverse": "Обратные загрузки",
"navbar.avatar.account": "Мой аккаунт",
"navbar.avatar.admin": "Администрирование",
"navbar.avatar.signout": "Выйти",
// END navbar
// /
"home.title": "A <h>self-hosted</h> file sharing platform.",
"home.description":
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
"home.bullet.a.name": "Self-Hosted",
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
"home.bullet.b.name": "Privacy",
"home.bullet.b.description":
"Your files are your files and should never get into the hands of third parties.",
"home.bullet.c.name": "No annoying file size limit",
"home.bullet.c.description":
"Upload as big files as you want. Only your hard drive will be your limit.",
"home.button.start": "Get started",
"home.button.source": "Source code",
"home.title": "Платформа для обмена файлами с <h>собственного хостинга</h>.",
"home.description": "Вы действительно хотите предоставить свои личные файлы в руки третьих лиц, таких как WeTransfer?",
"home.bullet.a.name": "На собственном сервере",
"home.bullet.a.description": "Pingvin Share работает на вашей машине.",
"home.bullet.b.name": "Конфиденциальность",
"home.bullet.b.description": "Ваши файлы - это ваши файлы и никогда не должны попадать в руки третьих лиц.",
"home.bullet.c.name": "Без раздражающего ограничения размера файла",
"home.bullet.c.description": "Загружайте файлы с любым размером. Только ваш жесткий диск будет пределом.",
"home.button.start": "Начнем",
"home.button.source": "Исходный код",
// END /
// /auth/signin
"signin.title": "Welcome back",
"signin.description": "You don't have an account yet?",
"signin.button.signup": "Sign up",
"signin.input.email-or-username": "Email or username",
"signin.input.email-or-username.placeholder": "Your email or username",
"signin.input.password": "Password",
"signin.input.password.placeholder": "Your password",
"signin.button.submit": "Sign in",
"signIn.notify.totp-required.title": "Two-factor authentication required",
"signIn.notify.totp-required.description":
"Please enter your two-factor authentication code",
"signin.title": "С возвращением",
"signin.description": "У вас еще нет учетной записи?",
"signin.button.signup": "Зарегистрироваться",
"signin.input.email-or-username": "Email или логин",
"signin.input.email-or-username.placeholder": "Эл. почта или логин",
"signin.input.password": "Пароль",
"signin.input.password.placeholder": "Ваш пароль",
"signin.button.submit": "Вход",
"signIn.notify.totp-required.title": "Требуется двухфакторная аутентификация",
"signIn.notify.totp-required.description": "Пожалуйста, введите код Вашей 2-х факторной аутентификации",
// END /auth/signin
// /auth/signup
"signup.title": "Create an account",
"signup.description": "Already have an account?",
"signup.button.signin": "Sign in",
"signup.input.username": "Username",
"signup.input.username.placeholder": "Your username",
"signup.input.email": "Email",
"signup.input.email.placeholder": "Your email",
"signup.button.submit": "Let's get started",
"signup.title": "Создать аккаунт",
"signup.description": "Уже есть учётная запись?",
"signup.button.signin": "Вход",
"signup.input.username": "Логин",
"signup.input.username.placeholder": "Ваш логин (имя пользователя)",
"signup.input.email": "Электронная почта",
"signup.input.email.placeholder": "Адрес эл. почты",
"signup.button.submit": "Давайте начнём",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Forgot your password?",
"resetPassword.description": "Enter your email to reset your password.",
"resetPassword.notify.success":
"An email has been sent with a link to reset your password.",
"resetPassword.button.back": "Back to sign in page",
"resetPassword.text.resetPassword": "Reset password",
"resetPassword.text.enterNewPassword": "Enter your new password",
"resetPassword.input.password": "New password",
"resetPassword.notify.passwordReset":
"Your password has been reset successfully.",
"resetPassword.title": "Забыли пароль?",
"resetPassword.description": "Введите ваш email для восстановления пароля.",
"resetPassword.notify.success": "Вам направлено письмо со ссылкой для сброса пароля.",
"resetPassword.button.back": "Вернуться на страницу входа",
"resetPassword.text.resetPassword": "Сбросить пароль",
"resetPassword.text.enterNewPassword": "Введите новый пароль",
"resetPassword.input.password": "Новый пароль",
"resetPassword.notify.passwordReset": "Ваш пароль был успешно сброшен!",
// /account
"account.title": "My account",
"account.card.info.title": "Account info",
"account.card.info.username": "Username",
"account.card.info.email": "Email",
"account.notify.info.success": "Account updated successfully",
"account.card.password.title": "Password",
"account.card.password.old": "Old password",
"account.card.password.new": "New password",
"account.notify.password.success": "Password changed successfully",
"account.card.security.title": "Security",
"account.card.security.totp.enable.description":
"Enter your current password to start enabling TOTP",
"account.card.security.totp.disable.description":
"Enter your current password to disable TOTP",
"account.card.security.totp.button.start": "Start",
"account.modal.totp.title": "Enable TOTP",
"account.modal.totp.step1": "Step 1: Add your authenticator",
"account.modal.totp.step2": "Step 2: Validate your code",
"account.modal.totp.enterManually": "Enter manually",
"account.modal.totp.code": "Code",
"account.modal.totp.clickToCopy": "Click to copy",
"account.modal.totp.verify": "Verify",
"account.notify.totp.disable": "TOTP disabled successfully",
"account.notify.totp.enable": "TOTP enabled successfully",
"account.card.language.title": "Language",
"account.card.language.description":
"The project is translated by the community. Some languages might be incomplete.",
"account.card.color.title": "Color scheme",
"account.title": "Мой аккаунт",
"account.card.info.title": "Информация об аккаунте",
"account.card.info.username": "Логин",
"account.card.info.email": "Электронная почта",
"account.notify.info.success": "Настройки учетной записи успешно обновлены",
"account.card.password.title": "Пароль",
"account.card.password.old": "Старый пароль",
"account.card.password.new": "Новый пароль",
"account.notify.password.success": "Пароль успешно изменён",
"account.card.security.title": "Безопасность",
"account.card.security.totp.enable.description": "Введите ваш текущий пароль для начала включения TOTP",
"account.card.security.totp.disable.description": "Введите ваш текущий пароль, чтобы отключить TOTP",
"account.card.security.totp.button.start": "Начать",
"account.modal.totp.title": "Включить TOTP",
"account.modal.totp.step1": "Шаг 1: Добавьте свой аутентификатор",
"account.modal.totp.step2": "Шаг 2: Проверка кода",
"account.modal.totp.enterManually": "Ввести вручную",
"account.modal.totp.code": "Код",
"account.modal.totp.clickToCopy": "Нажмите, чтобы скопировать",
"account.modal.totp.verify": "Подтвердить",
"account.notify.totp.disable": "TOTP успешно отключен",
"account.notify.totp.enable": "TOTP успешно включен",
"account.card.language.title": "Язык",
"account.card.language.description": "Проект переведен сообществом. Некоторые языки могут быть неполными.",
"account.card.color.title": "Цветовая схема",
// ThemeSwitcher.tsx
"account.theme.dark": "Dark",
"account.theme.light": "Light",
"account.theme.system": "System",
"account.button.delete": "Delete Account",
"account.modal.delete.title": "Delete Account",
"account.modal.delete.description":
"Do you really want to delete your account including all your active shares?",
"account.theme.dark": "Тёмная",
"account.theme.light": "Светлая",
"account.theme.system": "Системная",
"account.button.delete": "Удалить аккаунт",
"account.modal.delete.title": "Удалить аккаунт",
"account.modal.delete.description": "Вы действительно хотите удалить свою учетную запись, включая все ваши загрузки?",
// END /account
// /account/shares
"account.shares.title": "My shares",
"account.shares.title.empty": "It's empty here 👀",
"account.shares.description.empty": "You don't have any shares.",
"account.shares.button.create": "Create one",
"account.shares.info.title": "Share informations",
"account.shares.title": "Мои загрузки",
"account.shares.title.empty": "Тут пусто 👀",
"account.shares.description.empty": "У вас нет загрузок.",
"account.shares.button.create": "Создать одну",
"account.shares.info.title": "Сведения",
"account.shares.table.id": "ID",
"account.shares.table.name": "Name",
"account.shares.table.description": "Description",
"account.shares.table.visitors": "Visitors",
"account.shares.table.expiresAt": "Expires at",
"account.shares.table.createdAt": "Created at",
"account.shares.table.size": "Size",
"account.shares.modal.share-informations": "Share informations",
"account.shares.modal.share-link": "Share link",
"account.shares.modal.delete.title": "Delete share {share}",
"account.shares.modal.delete.description":
"Do you really want to delete this share?",
"account.shares.table.name": "Название",
"account.shares.table.description": "Описание",
"account.shares.table.visitors": "Посетителей",
"account.shares.table.expiresAt": "Действительно до",
"account.shares.table.createdAt": "Создано",
"account.shares.table.size": "Размер",
"account.shares.modal.share-informations": "Сведения",
"account.shares.modal.share-link": "Поделиться ссылкой",
"account.shares.modal.delete.title": "Удалить загрузку {share}",
"account.shares.modal.delete.description": "Вы действительно хотите удалить эту загрузку?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Reverse shares",
"account.reverseShares.description":
"A reverse share allows you to generate a unique URL that allows external users to create a share.",
"account.reverseShares.title.empty": "It's empty here 👀",
"account.reverseShares.description.empty":
"You don't have any reverse shares.",
"account.reverseShares.title": "Обратные загрузки",
"account.reverseShares.description": "Обратная загрузка позволяет генерировать уникальный URL, позволяющий внешним пользователям загружать файлы.",
"account.reverseShares.title.empty": "Тут пусто 👀",
"account.reverseShares.description.empty": "У вас пока нет обратных загрузок.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.expiration.label": "Expiration",
"account.reverseShares.modal.expiration.minute-singular": "Minute",
"account.reverseShares.modal.expiration.minute-plural": "Minutes",
"account.reverseShares.modal.expiration.hour-singular": "Hour",
"account.reverseShares.modal.expiration.hour-plural": "Hours",
"account.reverseShares.modal.expiration.day-singular": "Day",
"account.reverseShares.modal.expiration.day-plural": "Days",
"account.reverseShares.modal.expiration.week-singular": "Week",
"account.reverseShares.modal.expiration.week-plural": "Weeks",
"account.reverseShares.modal.expiration.month-singular": "Month",
"account.reverseShares.modal.expiration.month-plural": "Months",
"account.reverseShares.modal.expiration.year-singular": "Year",
"account.reverseShares.modal.expiration.year-plural": "Years",
"account.reverseShares.modal.max-size.label": "Max share size",
"account.reverseShares.modal.send-email": "Send email notification",
"account.reverseShares.modal.send-email.description":
"Send an email notification when a share is created with this reverse share link.",
"account.reverseShares.modal.max-use.label": "Max uses",
"account.reverseShares.modal.max-use.description":
"The maximum amount of times this URL can be used to create a share.",
"account.reverseShare.never-expires": "This reverse share will never expire.",
"account.reverseShare.expires-on":
"This reverse share will expire on {expiration}.",
"account.reverseShares.table.no-shares": "No shares created yet",
"account.reverseShares.table.count.singular": "share",
"account.reverseShares.table.count.plural": "shares",
"account.reverseShares.table.shares": "Shares",
"account.reverseShares.table.remaining": "Remaining uses",
"account.reverseShares.table.max-size": "Max share size",
"account.reverseShares.table.expires": "Expires at",
"account.reverseShares.modal.reverse-share-link": "Reverse share link",
"account.reverseShares.modal.delete.title": "Delete reverse share",
"account.reverseShares.modal.delete.description":
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
"account.reverseShares.modal.title": "Create reverse share",
"account.reverseShares.modal.expiration.label": "Истекает",
"account.reverseShares.modal.expiration.minute-singular": "Минута",
"account.reverseShares.modal.expiration.minute-plural": "Минут(ы)",
"account.reverseShares.modal.expiration.hour-singular": "Час",
"account.reverseShares.modal.expiration.hour-plural": "Часов",
"account.reverseShares.modal.expiration.day-singular": "День",
"account.reverseShares.modal.expiration.day-plural": "Дней",
"account.reverseShares.modal.expiration.week-singular": "Неделя",
"account.reverseShares.modal.expiration.week-plural": "Недель",
"account.reverseShares.modal.expiration.month-singular": "Месяц",
"account.reverseShares.modal.expiration.month-plural": "Месяца(-ев)",
"account.reverseShares.modal.expiration.year-singular": "Год",
"account.reverseShares.modal.expiration.year-plural": "Года (лет)",
"account.reverseShares.modal.max-size.label": "Макс. размер загрузки",
"account.reverseShares.modal.send-email": "Отправить уведомление по эл. почте",
"account.reverseShares.modal.send-email.description": "Отправлять уведомление по электронной почте, когда загрузка создается с помощью этой обратной ссылки.",
"account.reverseShares.modal.max-use.label": "Максимум использований",
"account.reverseShares.modal.max-use.description": "Максимальное количество раз, когда URL может быть использован для создания загрузки.",
"account.reverseShare.never-expires": "Эта обратная загрузка никогда не устареет.",
"account.reverseShare.expires-on": "Эта обратная загрузка устареет {expiration}.",
"account.reverseShares.table.no-shares": "Нет созданных загрузок",
"account.reverseShares.table.count.singular": "загрузка",
"account.reverseShares.table.count.plural": "загрузки",
"account.reverseShares.table.shares": "Загрузки",
"account.reverseShares.table.remaining": "Осталось использований",
"account.reverseShares.table.max-size": "Макс. размер загрузки",
"account.reverseShares.table.expires": "Действительно до",
"account.reverseShares.modal.reverse-share-link": "Ссылка обратной загрузки",
"account.reverseShares.modal.delete.title": "Удалить обратную загрузку",
"account.reverseShares.modal.delete.description": "Вы действительно хотите удалить эту обратную загрузку? Если вы это сделаете, то все связанные обратные загрузки будут также удалены.",
// END /account/reverseShares
// /admin
"admin.title": "Administration",
"admin.button.users": "User management",
"admin.button.config": "Configuration",
"admin.version": "Version",
"admin.title": "Администрирование",
"admin.button.users": "Управление пользователями",
"admin.button.config": "Конфигурация",
"admin.version": "Версия",
// END /admin
// /admin/users
"admin.users.title": "User management",
"admin.users.table.username": "Username",
"admin.users.table.email": "Email",
"admin.users.table.admin": "Admin",
"admin.users.edit.update.title": "Update user {username}",
"admin.users.edit.update.admin-privileges": "Admin privileges",
"admin.users.edit.update.change-password.title": "Change password",
"admin.users.edit.update.change-password.field": "New password",
"admin.users.edit.update.change-password.button": "Save new password",
"admin.users.edit.update.notify.password.success":
"Password changed successfully",
"admin.users.edit.delete.title": "Delete user {username}",
"admin.users.edit.delete.description":
"Do you really want to delete this user and all his shares?",
"admin.users.title": "Управление пользователями",
"admin.users.table.username": "Логин",
"admin.users.table.email": "Электронная почта",
"admin.users.table.admin": "Администратор",
"admin.users.edit.update.title": "Обновить пользователя {username}",
"admin.users.edit.update.admin-privileges": "Права администратора",
"admin.users.edit.update.change-password.title": "Изменить пароль",
"admin.users.edit.update.change-password.field": "Новый пароль",
"admin.users.edit.update.change-password.button": "Сохранить новый пароль",
"admin.users.edit.update.notify.password.success": "Пароль успешно изменён",
"admin.users.edit.delete.title": "Удалить пользователя {username}",
"admin.users.edit.delete.description": "Вы действительно хотите удалить этого пользователя и все его загрузки?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Create user",
"admin.users.modal.create.username": "Username",
"admin.users.modal.create.email": "Email",
"admin.users.modal.create.password": "Password",
"admin.users.modal.create.manual-password": "Set password manually",
"admin.users.modal.create.manual-password.description":
"If not checked, the user will receive an email with a link to set their password.",
"admin.users.modal.create.admin": "Admin privileges",
"admin.users.modal.create.admin.description":
"If checked, the user will be able to access the admin panel.",
"admin.users.modal.create.title": "Создать пользователя",
"admin.users.modal.create.username": "Логин",
"admin.users.modal.create.email": "Электронная почта",
"admin.users.modal.create.password": "Пароль",
"admin.users.modal.create.manual-password": "Установить пароль вручную",
"admin.users.modal.create.manual-password.description": "Если флажок не установлен, пользователь получит письмо со ссылкой для установки пароля.",
"admin.users.modal.create.admin": "Права администратора",
"admin.users.modal.create.admin.description": "Если отмечено, пользователь будет иметь доступ к панели администратора.",
// END /admin/users
// /upload
"upload.title": "Upload",
"upload.notify.generic-error":
"An error occurred while finishing your share.",
"upload.notify.count-failed": "{count} files failed to upload. Trying again.",
"upload.title": "Загрузить",
"upload.notify.generic-error": "Произошла ошибка при завершении вашей загрузки.",
"upload.notify.count-failed": "Не удалось загрузить файлы {count}. Повтор попытки.",
// Dropzone.tsx
"upload.dropzone.title": "Upload files",
"upload.dropzone.description":
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
"upload.dropzone.notify.file-too-big":
"Your files exceed the maximum share size of {maxSize}.",
"upload.dropzone.title": "Загрузить файлы",
"upload.dropzone.description": "Перетащите сюда файлы для начала загрузки. Мы можем принимать только файлы, которые меньше {maxSize}.",
"upload.dropzone.notify.file-too-big": "Ваши файлы превышают максимальный размер в {maxSize}.",
// FileList.tsx
"upload.filelist.name": "Name",
"upload.filelist.size": "Size",
"upload.filelist.name": "Название",
"upload.filelist.size": "Размер",
// showCreateUploadModal.tsx
"upload.modal.title": "Create Share",
"upload.modal.link.error.invalid":
"Can only contain letters, numbers, underscores, and hyphens",
"upload.modal.link.error.taken": "This link is already in use",
"upload.modal.not-signed-in": "You're not signed in",
"upload.modal.not-signed-in-description":
"You will be unable to delete your share manually and view the visitor count.",
"upload.modal.expires.never": "never",
"upload.modal.expires.never-long": "Never Expires",
"upload.modal.link.label": "Link",
"upload.modal.expires.label": "Expiration",
"upload.modal.expires.minute-singular": "Minute",
"upload.modal.expires.minute-plural": "Minutes",
"upload.modal.expires.hour-singular": "Hour",
"upload.modal.expires.hour-plural": "Hours",
"upload.modal.expires.day-singular": "Day",
"upload.modal.expires.day-plural": "Days",
"upload.modal.expires.week-singular": "Week",
"upload.modal.expires.week-plural": "Weeks",
"upload.modal.expires.month-singular": "Month",
"upload.modal.expires.month-plural": "Months",
"upload.modal.expires.year-singular": "Year",
"upload.modal.expires.year-plural": "Years",
"upload.modal.accordion.description.title": "Description",
"upload.modal.accordion.description.placeholder":
"Note for the recipients of this share",
"upload.modal.accordion.email.title": "Email recipients",
"upload.modal.accordion.email.placeholder": "Enter email recipients",
"upload.modal.accordion.email.invalid-email": "Invalid email address",
"upload.modal.accordion.security.title": "Security options",
"upload.modal.accordion.security.password.label": "Password protection",
"upload.modal.accordion.security.password.placeholder": "No password",
"upload.modal.accordion.security.max-views.label": "Maximum views",
"upload.modal.accordion.security.max-views.placeholder": "No limit",
"upload.modal.title": "Загрузить",
"upload.modal.link.error.invalid": "Имя пользователя должно состоять только из букв, цифр, подчёркиваний и дефисов",
"upload.modal.link.error.taken": "Эта ссылка уже используется",
"upload.modal.not-signed-in": "Вы не авторизованы",
"upload.modal.not-signed-in-description": "Вы не сможете удалить свои файлы вручную и просмотреть количество посетителей.",
"upload.modal.expires.never": "никогда",
"upload.modal.expires.never-long": "Никогда не истекает",
"upload.modal.link.label": "Ссылка",
"upload.modal.expires.label": "Истекает",
"upload.modal.expires.minute-singular": "Минута",
"upload.modal.expires.minute-plural": "Минут(ы)",
"upload.modal.expires.hour-singular": "Час",
"upload.modal.expires.hour-plural": "Часов",
"upload.modal.expires.day-singular": "День",
"upload.modal.expires.day-plural": "Дней",
"upload.modal.expires.week-singular": "Неделя",
"upload.modal.expires.week-plural": "Недель",
"upload.modal.expires.month-singular": "Месяц",
"upload.modal.expires.month-plural": "Месяца(-ев)",
"upload.modal.expires.year-singular": "Год",
"upload.modal.expires.year-plural": "Года (лет)",
"upload.modal.accordion.description.title": "Описание",
"upload.modal.accordion.description.placeholder": "Примечание для получателей этой загрузки",
"upload.modal.accordion.email.title": "Получатели письма",
"upload.modal.accordion.email.placeholder": "Получатели e-mail",
"upload.modal.accordion.email.invalid-email": "Недопустимый адрес электронной почты",
"upload.modal.accordion.security.title": "Параметры безопасности",
"upload.modal.accordion.security.password.label": "Защита паролем",
"upload.modal.accordion.security.password.placeholder": "Без пароля",
"upload.modal.accordion.security.max-views.label": "Максимум просмотров",
"upload.modal.accordion.security.max-views.placeholder": "Без ограничения",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "This share will never expire.",
"upload.modal.completed.expires-on":
"This share will expire on {expiration}.",
"upload.modal.completed.share-ready": "Share ready",
"upload.modal.completed.never-expires": "Эта загрузка никогда не устареет.",
"upload.modal.completed.expires-on": "Эта загрузка устареет {expiration}.",
"upload.modal.completed.share-ready": "Готово",
// END /upload
// /share/[id]
"share.title": "Share {shareId}",
"share.description": "Look what I've shared with you!",
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
"share.error.visitor-limit-exceeded.description":
"The visitor limit from this share has been exceeded.",
"share.error.removed.title": "Share removed",
"share.error.not-found.title": "Share not found",
"share.error.not-found.description":
"The share you're looking for doesn't exist.",
"share.modal.password.title": "Password required",
"share.modal.password.description":
"To access this share please enter the password for the share.",
"share.modal.password": "Password",
"share.modal.error.invalid-password": "Invalid password",
"share.button.download-all": "Download all",
"share.notify.download-all-preparing":
"The share is preparing. Try again in a few minutes.",
"share.modal.file-link": "File link",
"share.table.name": "Name",
"share.table.size": "Size",
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
"share.modal.file-preview.error.not-supported.description":
"A preview for thise file type is unsupported. Please download the file to view it.",
"share.title": "Загрузка {shareId}",
"share.description": "Посмотрите, чем я поделился с вами!",
"share.error.visitor-limit-exceeded.title": "Превышен лимит посетителей",
"share.error.visitor-limit-exceeded.description": "Превышен лимит посетителей.",
"share.error.removed.title": "Загрузка удалена",
"share.error.not-found.title": "Загрузка не найдена",
"share.error.not-found.description": "Страница, которую вы ищете, не существует.",
"share.modal.password.title": "Требуется пароль",
"share.modal.password.description": "Для доступа к этому ресурсу введите пароль для общего доступа.",
"share.modal.password": "Пароль",
"share.modal.error.invalid-password": "Неверный пароль",
"share.button.download-all": "Скачать все",
"share.notify.download-all-preparing": "Загрузка готовится. Повторите попытку через несколько минут.",
"share.modal.file-link": "Ссылка на файл",
"share.table.name": "Название",
"share.table.size": "Размер",
"share.modal.file-preview.error.not-supported.title": "Предпросмотр не поддерживается",
"share.modal.file-preview.error.not-supported.description": "Предварительный просмотр этого типа файла не поддерживается. Пожалуйста, скачайте файл для его просмотра.",
// END /share/[id]
// /admin/config
"admin.config.title": "Configuration",
"admin.config.category.general": "General",
"admin.config.category.share": "Share",
"admin.config.category.email": "Email",
"admin.config.title": "Конфигурация",
"admin.config.category.general": "Общее",
"admin.config.category.share": "Загрузки",
"admin.config.category.email": "Электронная почта",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "App name",
"admin.config.general.app-name.description": "Name of the application",
"admin.config.general.app-url": "App URL",
"admin.config.general.app-url.description":
"On which URL Pingvin Share is available",
"admin.config.general.show-home-page": "Show home page",
"admin.config.general.show-home-page.description":
"Whether to show the home page",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description":
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
"admin.config.general.logo.placeholder": "Pick image",
"admin.config.email.enable-share-email-recipients":
"Enable share email recipients",
"admin.config.email.enable-share-email-recipients.description":
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
"admin.config.email.share-recipients-subject": "Share recipients subject",
"admin.config.email.share-recipients-subject.description":
"Subject of the email which gets sent to the share recipients.",
"admin.config.email.share-recipients-message": "Share recipients message",
"admin.config.email.share-recipients-message.description":
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual value.",
"admin.config.email.reverse-share-subject": "Reverse share subject",
"admin.config.email.reverse-share-subject.description":
"Subject of the email which gets sent when someone created a share with your reverse share link.",
"admin.config.email.reverse-share-message": "Reverse share message",
"admin.config.email.reverse-share-message.description":
"Message which gets sent when someone created a share with your reverse share link. {shareUrl} will be replaced with the creator's name and the share URL.",
"admin.config.email.reset-password-subject": "Reset password subject",
"admin.config.email.reset-password-subject.description":
"Subject of the email which gets sent when a user requests a password reset.",
"admin.config.email.reset-password-message": "Reset password message",
"admin.config.email.reset-password-message.description":
"Message which gets sent when a user requests a password reset. {url} will be replaced with the reset password URL.",
"admin.config.email.invite-subject": "Invite subject",
"admin.config.email.invite-subject.description":
"Subject of the email which gets sent when an admin invites a user.",
"admin.config.email.invite-message": "Invite message",
"admin.config.email.invite-message.description":
"Message which gets sent when an admin invites a user. {url} will be replaced with the invite URL and {password} with the password.",
"admin.config.share.allow-registration": "Allow registration",
"admin.config.share.allow-registration.description":
"Whether registration is allowed",
"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-size": "Max size",
"admin.config.share.max-size.description": "Maximum share size in bytes",
"admin.config.share.zip-compression-level": "Zip compression level",
"admin.config.share.zip-compression-level.description":
"Adjust the level to balance between file size and compression speed. Valid values range from 0 to 9, with 0 being no compression and 9 being maximum compression. ",
"admin.config.smtp.enabled": "Enabled",
"admin.config.smtp.enabled.description":
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
"admin.config.smtp.host": "Host",
"admin.config.smtp.host.description": "Host of the SMTP server",
"admin.config.smtp.port": "Port",
"admin.config.smtp.port.description": "Port of the SMTP server",
"admin.config.smtp.email": "Email",
"admin.config.smtp.email.description":
"Email address which the emails get sent from",
"admin.config.smtp.username": "Username",
"admin.config.smtp.username.description": "Username of the SMTP server",
"admin.config.smtp.password": "Password",
"admin.config.smtp.password.description": "Password of the SMTP server",
"admin.config.smtp.button.test": "Send test email",
"admin.config.general.app-name": "Название приложения",
"admin.config.general.app-name.description": "Видимое название приложения",
"admin.config.general.app-url": "URL-адрес приложения",
"admin.config.general.app-url.description": "Адрес на котором доступен Pingvin Share",
"admin.config.general.show-home-page": "Показывать домашнюю страницу",
"admin.config.general.show-home-page.description": "Показывать ли домашнюю страницу или нет",
"admin.config.general.logo": "Логотип",
"admin.config.general.logo.description": "Измените свой логотип, загрузив новое изображение. Изображение должно быть PNG и должно иметь формат 1:1.",
"admin.config.general.logo.placeholder": "Выберите изображение",
"admin.config.email.enable-share-email-recipients": "Включить обмен с получателями электронной почты",
"admin.config.email.enable-share-email-recipients.description": "Разрешить ли отправку писем получателям. Включите, только если вы включили SMTP.",
"admin.config.email.share-recipients-subject": "Заголовок письма (загрузка)",
"admin.config.email.share-recipients-subject.description": "Тема письма, которое отправляется получателям акции.",
"admin.config.email.share-recipients-message": "Сообщение письма загрузки",
"admin.config.email.share-recipients-message.description": "Сообщение, которое отправляется получателям публикации. Доступные переменные:\n {creator} - Имя пользователя создателя загрузки\n {shareUrl} - URL загрузки\n {desc} - Описание загрузки\n {expires} - Дата истечения загрузки\n Переменные будут заменены на фактическое значение.",
"admin.config.email.reverse-share-subject": "Заголовок письма (обратная загрузка)",
"admin.config.email.reverse-share-subject.description": "Тема письма, которое отправляется, когда кто-то создал загрузку с вашей обратной ссылкой.",
"admin.config.email.reverse-share-message": "Сообщение письма обратной загрузки",
"admin.config.email.reverse-share-message.description": "Сообщение, которое отправляется, когда кто-то создал загрузку с вашей обратной ссылкой. {shareUrl} будет заменен именем создателя и URL-адресом общего доступа.",
"admin.config.email.reset-password-subject": "Тема сброса пароля",
"admin.config.email.reset-password-subject.description": "Тема письма, которое отправляется, когда пользователь запрашивает сброс пароля.",
"admin.config.email.reset-password-message": "Сообщение о сбросе пароля",
"admin.config.email.reset-password-message.description": "Сообщение, которое отправляется при запросе сброса пароля. {url} будет заменён ссылкой.",
"admin.config.email.invite-subject": "Тема приглашения",
"admin.config.email.invite-subject.description": "Тема письма, которое отправляется, когда администратор приглашает пользователя.",
"admin.config.email.invite-message": "Сообщение с приглашением",
"admin.config.email.invite-message.description": "Сообщение приглашения. {url} будет заменён ссылкой приглашения, а {password} паролем.",
"admin.config.share.allow-registration": "Разрешить регистрацию",
"admin.config.share.allow-registration.description": "Разрешена ли регистрация",
"admin.config.share.allow-unauthenticated-shares": "Разрешить неавторизованные загрузки",
"admin.config.share.allow-unauthenticated-shares.description": "Могут ли неавторизованные пользователи создавать загрузки",
"admin.config.share.max-size": "Максимальный размер",
"admin.config.share.max-size.description": "Максимальный размер файла в байтах",
"admin.config.share.zip-compression-level": "Уровень сжатия Zip",
"admin.config.share.zip-compression-level.description": "Регулировка уровня баланса между размером файла и скоростью сжатия. Допустимые значения от 0 до 9, с 0 без сжатия, а 9 - максимальное сжатие. ",
"admin.config.smtp.enabled": "Включено",
"admin.config.smtp.enabled.description": "Включено ли SMTP. Установите значение true только если вы ввели хост, порт, email, пользователь и пароль вашего SMTP-сервера.",
"admin.config.smtp.host": "Хост",
"admin.config.smtp.host.description": "Сервер SMTP сервера",
"admin.config.smtp.port": "Порт",
"admin.config.smtp.port.description": "Порт SMTP сервера",
"admin.config.smtp.email": "Электронная почта",
"admin.config.smtp.email.description": "Адрес электронной почты, от которого отправляются письма",
"admin.config.smtp.username": "Логин",
"admin.config.smtp.username.description": "Имя пользователя SMTP сервера",
"admin.config.smtp.password": "Пароль",
"admin.config.smtp.password.description": "Пароль SMTP-сервера",
"admin.config.smtp.button.test": "Отправить тестовое письмо",
// 404
"404.description": "Oops this page doesn't exist.",
"404.button.home": "Bring me back home",
"404.description": "Упс, этой страницы не существует.",
"404.button.home": "Верните меня домой",
// Common translations
"common.button.save": "Save",
"common.button.create": "Create",
"common.button.submit": "Submit",
"common.button.delete": "Delete",
"common.button.cancel": "Cancel",
"common.button.confirm": "Confirm",
"common.button.disable": "Disable",
"common.button.share": "Share",
"common.button.generate": "Generate",
"common.button.done": "Done",
"common.text.link": "Link",
"common.text.or": "or",
"common.button.go-back": "Go back",
"common.notify.copied": "Your link was copied to the clipboard",
"common.success": "Success",
"common.error": "Error",
"common.error.unknown": "An unknown error occurred",
"common.error.invalid-email": "Invalid email address",
"common.error.too-short": "Must be at least {length} characters",
"common.error.too-long": "Must be at most {length} characters",
"common.error.exact-length": "Must be exactly {length} characters",
"common.error.invalid-number": "Must be a number",
"common.error.field-required": "This field is required",
};
"common.button.save": "Сохранить",
"common.button.create": "Создать",
"common.button.submit": "Отправить",
"common.button.delete": "Удалить",
"common.button.cancel": "Отмена",
"common.button.confirm": "Подтвердить",
"common.button.disable": "Отключить",
"common.button.share": "Поделиться",
"common.button.generate": "Сгенерировать",
"common.button.done": "Готово",
"common.text.link": "Ссылка",
"common.text.or": "или",
"common.button.go-back": "Назад",
"common.notify.copied": "Ваша ссылка скопирована в буфер обмена",
"common.success": "Успешно",
"common.error": "Ошибочка",
"common.error.unknown": "Произошла неизвестная ошибка",
"common.error.invalid-email": "Недопустимый адрес электронной почты",
"common.error.too-short": "Должно быть не менее {length} символов",
"common.error.too-long": "Должно быть не больше {length} символов",
"common.error.exact-length": "Должно быть ровно {length} символов",
"common.error.invalid-number": "Должно быть числом",
"common.error.field-required": "Поле обязательно для заполнения"
};

View File

@@ -0,0 +1,324 @@
export default {
// Navbar
"navbar.upload": "Пошаљи",
"navbar.signin": "Пријавите се",
"navbar.home": "Почетна",
"navbar.signup": "Региструј се",
"navbar.links.shares": "Moja дељења",
"navbar.links.reverse": "Обрнуто дељење",
"navbar.avatar.account": "Мој налог",
"navbar.avatar.admin": "Администрација",
"navbar.avatar.signout": "Одјави се",
// END navbar
// /
"home.title": "<h>self-hosted</h> платформа за дељење фајлова.",
"home.description": "Да ли заиста желите да дате своје личне датотеке у руке трећих страна као што је WeTransfer?",
"home.bullet.a.name": "Личан хостинг",
"home.bullet.a.description": "Хостујте Pingvin Share на својој машини.",
"home.bullet.b.name": "Privatnost",
"home.bullet.b.description": "Ваше датотеке су ваше датотеке и никада не би требало да дођу у руке трећих лица.",
"home.bullet.c.name": "Нема досадног ограничења величине датотеке",
"home.bullet.c.description": "Отпремите онолико великих датотека колико желите. Само ваш чврсти диск ће бити ваше ограничење.",
"home.button.start": "Почети",
"home.button.source": "Изворни код",
// END /
// /auth/signin
"signin.title": "Добродошли назад",
"signin.description": "Још увек немате налог?",
"signin.button.signup": "Региструј се",
"signin.input.email-or-username": "Е-пошта или корисничко име",
"signin.input.email-or-username.placeholder": "Ваша Е-пошта или корисничко име",
"signin.input.password": "Лозинка",
"signin.input.password.placeholder": "Ваша лозинка",
"signin.button.submit": "Пријавите се",
"signIn.notify.totp-required.title": "Потребна је двофакторска аутентификација",
"signIn.notify.totp-required.description": "Унесите свој двофакторски код за аутентификацију",
// END /auth/signin
// /auth/signup
"signup.title": "Направи налог",
"signup.description": "Већ имате налог?",
"signup.button.signin": "Пријавите се",
"signup.input.username": "Корисничко име",
"signup.input.username.placeholder": "Ваше корисничко име",
"signup.input.email": "E-пошта",
"signup.input.email.placeholder": "Ваш и-мејл",
"signup.button.submit": "Хајде да почнемо",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Заборавили сте лозинку?",
"resetPassword.description": "Унесите своју е-пошту да бисте ресетовали лозинку.",
"resetPassword.notify.success": "Послат је емаил са везом за ресетовање ваше лозинке.",
"resetPassword.button.back": "Назад на страницу за пријаву",
"resetPassword.text.resetPassword": "Обнови лозинку",
"resetPassword.text.enterNewPassword": "Унесите вашу нову лозинку",
"resetPassword.input.password": "Нова лозинка",
"resetPassword.notify.passwordReset": "Ваша лозинка је успешно ресетована.",
// /account
"account.title": "Мој налог",
"account.card.info.title": "Подаци о налогу",
"account.card.info.username": "Корисничко име",
"account.card.info.email": "E-пошта",
"account.notify.info.success": "Налог је успешно ажуриран",
"account.card.password.title": "Лозинка",
"account.card.password.old": "Стара лозинка",
"account.card.password.new": "Нова лозинка",
"account.notify.password.success": "Лозинка је успешно промењена",
"account.card.security.title": "Безбедност",
"account.card.security.totp.enable.description": "Унесите своју тренутну лозинку да бисте почели да омогућавате ТОТП",
"account.card.security.totp.disable.description": "Унесите своју тренутну лозинку да бисте онемогућили ТОТП",
"account.card.security.totp.button.start": "Започни",
"account.modal.totp.title": "Омогући ТОТП",
"account.modal.totp.step1": "Корак 1: Додајте свој аутентификатор",
"account.modal.totp.step2": "Корак 2: Потврдите свој код",
"account.modal.totp.enterManually": "Унесите ручно",
"account.modal.totp.code": "Код",
"account.modal.totp.clickToCopy": "Кликните за копирање",
"account.modal.totp.verify": "Верификуј",
"account.notify.totp.disable": "ТОТП је успешно онемогућен",
"account.notify.totp.enable": "ТОТП је успешно омогућен",
"account.card.language.title": "Језик",
"account.card.language.description": "Пројекат је преведен од стране заједнице. Неки језици могу бити непотпуни.",
"account.card.color.title": "Шема боја",
// ThemeSwitcher.tsx
"account.theme.dark": "Тамно",
"account.theme.light": "Светло",
"account.theme.system": "Систем",
"account.button.delete": "Избриши Налог",
"account.modal.delete.title": "Избриши Налог",
"account.modal.delete.description": "Да ли заиста желите да избришете свој налог укључујући све ваше активне дељења?",
// END /account
// /account/shares
"account.shares.title": "Moja дељења",
"account.shares.title.empty": "Овде је празно 👀",
"account.shares.description.empty": "Немате никаква дељења.",
"account.shares.button.create": "Направите",
"account.shares.info.title": "Делите информације",
"account.shares.table.id": "ИД",
"account.shares.table.name": "Назив",
"account.shares.table.description": "Опис",
"account.shares.table.visitors": "Посетици",
"account.shares.table.expiresAt": "Истиче у",
"account.shares.table.createdAt": "Направљено у",
"account.shares.table.size": "Величина",
"account.shares.modal.share-informations": "Делите информације",
"account.shares.modal.share-link": "Дели везу",
"account.shares.modal.delete.title": "Избриши дељење {share}",
"account.shares.modal.delete.description": "Да ли заиста желите да избришете ово дељење?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Обрнуто дељење",
"account.reverseShares.description": "Обрнуто дељење вам омогућава да генеришете јединствени URL који омогућава спољним корисницима да креирају дељење.",
"account.reverseShares.title.empty": "Овде је празно 👀",
"account.reverseShares.description.empty": "Немате обрнутих дељења.",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Креирајте обрнуто дељење",
"account.reverseShares.modal.expiration.label": "Истиче",
"account.reverseShares.modal.expiration.minute-singular": "Минут",
"account.reverseShares.modal.expiration.minute-plural": "Минуте",
"account.reverseShares.modal.expiration.hour-singular": "Сат",
"account.reverseShares.modal.expiration.hour-plural": "Сати",
"account.reverseShares.modal.expiration.day-singular": "Дан",
"account.reverseShares.modal.expiration.day-plural": "Дани",
"account.reverseShares.modal.expiration.week-singular": "Седмица",
"account.reverseShares.modal.expiration.week-plural": "Седмице",
"account.reverseShares.modal.expiration.month-singular": "Месец",
"account.reverseShares.modal.expiration.month-plural": "Месеци",
"account.reverseShares.modal.expiration.year-singular": "Година",
"account.reverseShares.modal.expiration.year-plural": "Године",
"account.reverseShares.modal.max-size.label": "Максимална величина дељења",
"account.reverseShares.modal.send-email": "Пошаљите обавештење путем е-поште",
"account.reverseShares.modal.send-email.description": "Пошаљите обавештење е-поштом када се креира дељење помоћу ове обрнуте везе за дељење.",
"account.reverseShares.modal.max-use.label": "Максималан број коришћења",
"account.reverseShares.modal.max-use.description": "Максималан број пута који овај URL може да се користи за прављење дељења.",
"account.reverseShare.never-expires": "Ово обрнуто дељење никада неће истећи.",
"account.reverseShare.expires-on": "Ово обрнуто дељење ће истећи {expiration}.",
"account.reverseShares.table.no-shares": "Још нема креираних дељења",
"account.reverseShares.table.count.singular": "дељење",
"account.reverseShares.table.count.plural": "дељења",
"account.reverseShares.table.shares": "Дељења",
"account.reverseShares.table.remaining": "Преостала употреба",
"account.reverseShares.table.max-size": "Максимална величина дељења",
"account.reverseShares.table.expires": "Истиче у",
"account.reverseShares.modal.reverse-share-link": "Линк за обрнуто дељење",
"account.reverseShares.modal.delete.title": "Обришите обрнуто дељење",
"account.reverseShares.modal.delete.description": "Да ли заиста желите да избришете ово обрнуто дељење? Ако то учините, придружена дељења ће такође бити избрисана.",
// END /account/reverseShares
// /admin
"admin.title": "Администрација",
"admin.button.users": "Управљање корисницима",
"admin.button.config": "Конфигурација",
"admin.version": "Верзија",
// END /admin
// /admin/users
"admin.users.title": "Управљање корисницима",
"admin.users.table.username": "Корисничко име",
"admin.users.table.email": "E-пошта",
"admin.users.table.admin": "Админ",
"admin.users.edit.update.title": "Ажурирајте корисника {username}",
"admin.users.edit.update.admin-privileges": "Администраторске привилегије",
"admin.users.edit.update.change-password.title": "Промените лозинку",
"admin.users.edit.update.change-password.field": "Нова лозинка",
"admin.users.edit.update.change-password.button": "Снимите нову лозинку",
"admin.users.edit.update.notify.password.success": "Лозинка је успешно промењена",
"admin.users.edit.delete.title": "Обришите корисника {username}",
"admin.users.edit.delete.description": "Да ли заиста желите да избришете овог корисника и сва његова дељења?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Креирај корисника",
"admin.users.modal.create.username": "Корисничко име",
"admin.users.modal.create.email": "E-пошта",
"admin.users.modal.create.password": "Лозинка",
"admin.users.modal.create.manual-password": "Поставите лозинку ручно",
"admin.users.modal.create.manual-password.description": "Ако није означено, корисник ће добити е-пошту са везом за постављање лозинке.",
"admin.users.modal.create.admin": "Администраторске привилегије",
"admin.users.modal.create.admin.description": "Ако је означено, корисник ће моћи да приступи административном панелу.",
// END /admin/users
// /upload
"upload.title": "Пошаљи",
"upload.notify.generic-error": "Дошло је до грешке при довршавању дељења.",
"upload.notify.count-failed": "Отпремање {count} датотека није успело. Покушавам поново.",
// Dropzone.tsx
"upload.dropzone.title": "Пошаљи датотеке",
"upload.dropzone.description": "Превуците и отпустите датотеке овде да бисте започели дељење. Можемо да прихватимо само датотеке које су укупно мање од {maxSize}.",
"upload.dropzone.notify.file-too-big": "Ваше датотеке премашују максималну величину дељења од {maxSize}.",
// FileList.tsx
"upload.filelist.name": "Назив",
"upload.filelist.size": "Величина",
// showCreateUploadModal.tsx
"upload.modal.title": "Направи дељење",
"upload.modal.link.error.invalid": "Може да садржи само слова, бројеве, доње црте и цртице",
"upload.modal.link.error.taken": "Овај линк је већ у употреби",
"upload.modal.not-signed-in": "Нисте пријављени",
"upload.modal.not-signed-in-description": "Нећете моћи ручно да избришете своје дељење и видите број посетилаца.",
"upload.modal.expires.never": "никад",
"upload.modal.expires.never-long": "Никада не истиче",
"upload.modal.link.label": "Линк",
"upload.modal.expires.label": "Истиче",
"upload.modal.expires.minute-singular": "Минут",
"upload.modal.expires.minute-plural": "Минуте",
"upload.modal.expires.hour-singular": "Сат",
"upload.modal.expires.hour-plural": "Сати",
"upload.modal.expires.day-singular": "Дан",
"upload.modal.expires.day-plural": "Дани",
"upload.modal.expires.week-singular": "Седмица",
"upload.modal.expires.week-plural": "Седмице",
"upload.modal.expires.month-singular": "Месец",
"upload.modal.expires.month-plural": "Месеци",
"upload.modal.expires.year-singular": "Година",
"upload.modal.expires.year-plural": "Године",
"upload.modal.accordion.description.title": "Опис",
"upload.modal.accordion.description.placeholder": "Напомена за примаоце овог дељења",
"upload.modal.accordion.email.title": "Примаоци е-поште",
"upload.modal.accordion.email.placeholder": "Унесите примаоце е-поште",
"upload.modal.accordion.email.invalid-email": "Неисправна адреса е-поште",
"upload.modal.accordion.security.title": "Сигурносна подешавања",
"upload.modal.accordion.security.password.label": "Заштита лозинком",
"upload.modal.accordion.security.password.placeholder": "Нема лозинке",
"upload.modal.accordion.security.max-views.label": "Максималан број прегледа",
"upload.modal.accordion.security.max-views.placeholder": "Без лимита",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "Ово дељење никада неће истећи.",
"upload.modal.completed.expires-on": "Ово дељење ће истећи {expiration}.",
"upload.modal.completed.share-ready": "Дељење је спремно",
// END /upload
// /share/[id]
"share.title": "Дељење {shareId}",
"share.description": "Погледајте шта сам поделио са вама!",
"share.error.visitor-limit-exceeded.title": "Прекорачено је ограничење посетилаца",
"share.error.visitor-limit-exceeded.description": "Ограничење посетилаца из овог дељења је премашено.",
"share.error.removed.title": "Дељење је уклоњено",
"share.error.not-found.title": "Дељење није пронађено",
"share.error.not-found.description": "Удео који тражите не постоји.",
"share.modal.password.title": "Обавезна лозинка",
"share.modal.password.description": "Да бисте приступили овом дељењу, унесите лозинку за дељење.",
"share.modal.password": "Лозинка",
"share.modal.error.invalid-password": "Неисправна лозинка",
"share.button.download-all": "Преузми све",
"share.notify.download-all-preparing": "Дељење се спрема. Покушајте поново за неколико минута.",
"share.modal.file-link": "Веза датотеке",
"share.table.name": "Назив",
"share.table.size": "Величина",
"share.modal.file-preview.error.not-supported.title": "Преглед није подржан",
"share.modal.file-preview.error.not-supported.description": "Преглед за овај тип датотеке није подржан. Преузмите датотеку да бисте је видели.",
// END /share/[id]
// /admin/config
"admin.config.title": "Конфигурација",
"admin.config.category.general": "Опште",
"admin.config.category.share": "Дељење",
"admin.config.category.email": "E-пошта",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "Назив апликације",
"admin.config.general.app-name.description": "Назив апликације",
"admin.config.general.app-url": "URL апликације",
"admin.config.general.app-url.description": "На којој URL адреси је доступан Pingvin Share",
"admin.config.general.show-home-page": "Прикажи почетну страницу",
"admin.config.general.show-home-page.description": "Да ли да се прикаже почетна страница",
"admin.config.general.logo": "Лого",
"admin.config.general.logo.description": "Промените свој логотип тако што ћете отпремити нову слику. Слика мора да буде PNG и треба да има формат 1:1.",
"admin.config.general.logo.placeholder": "Изабери слику",
"admin.config.email.enable-share-email-recipients": "Омогућите дељење прималаца е-поште",
"admin.config.email.enable-share-email-recipients.description": "Да ли да дозволите да имејлови деле примаоце. Омогућите ово само ако сте омогућили SMTP.",
"admin.config.email.share-recipients-subject": "Поделите наслов примаоца",
"admin.config.email.share-recipients-subject.description": "Тема е-поруке која се шаље примаоцима дељења.",
"admin.config.email.share-recipients-message": "Делите поруку примаоца",
"admin.config.email.share-recipients-message.description": "Порука која се шаље примаоцима дељења. Доступне променљиве:\n {creator} корисничко име креатора дељења\n {shareUrl} URL дељења\n {desc} Опис дељења\n {expires} - Датум истека акције\n Променљиве ће бити замењене стварном вредношћу.",
"admin.config.email.reverse-share-subject": "Тема обрнутог дељења",
"admin.config.email.reverse-share-subject.description": "Тема е-поруке која се шаље када је неко направио дељење са вашом обрнутим линком за дељење.",
"admin.config.email.reverse-share-message": "Обрнуто дељење порука",
"admin.config.email.reverse-share-message.description": "Порука која се шаље када је неко направио дељење са вашом обрнутом везом за дељење. {shareUrl} ће бити замењен именом креатора и УРЛ-ом за дељење.",
"admin.config.email.reset-password-subject": "Ресетујте тему лозинке",
"admin.config.email.reset-password-subject.description": "Предмет е-поште која се шаље када корисник затражи ресетовање лозинке.",
"admin.config.email.reset-password-message": "Порука за ресетовање лозинке",
"admin.config.email.reset-password-message.description": "Порука која се шаље када корисник затражи ресетовање лозинке. {url} ће бити замењен УРЛ-ом за ресетовање лозинке.",
"admin.config.email.invite-subject": "Позивна тема",
"admin.config.email.invite-subject.description": "Тема е-поруке која се шаље када администратор позове корисника.",
"admin.config.email.invite-message": "Позивна порука",
"admin.config.email.invite-message.description": "Порука која се шаље када администратор позове корисника. {url} ће бити замењен УРЛ-ом позива, а {password} лозинком.",
"admin.config.share.allow-registration": "Дозволи регистрацију",
"admin.config.share.allow-registration.description": "Да ли је регистрација дозвољена",
"admin.config.share.allow-unauthenticated-shares": "Дозволи дељење без аутентификације",
"admin.config.share.allow-unauthenticated-shares.description": "Да ли корисници без аутентификације могу да креирају дељења",
"admin.config.share.max-size": "Максимална величина",
"admin.config.share.max-size.description": "Максимална величина дељења у бајтовима",
"admin.config.share.zip-compression-level": "Ниво zip компресије",
"admin.config.share.zip-compression-level.description": "Подесите ниво да бисте балансирали између величине датотеке и брзине компресије. Важеће вредности се крећу од 0 до 9, при чему је 0 без компресије, а 9 је максимална компресија. ",
"admin.config.smtp.enabled": "Омогућено",
"admin.config.smtp.enabled.description": "Да ли је SMTP омогућен. Поставите ово на тачно само ако сте унели хост, порт, е-пошту, корисника и лозинку вашег SMTP сервера.",
"admin.config.smtp.host": "Домаћин",
"admin.config.smtp.host.description": "Домаћин SMTP сервера",
"admin.config.smtp.port": "Порт",
"admin.config.smtp.port.description": "Порт домаћина SMTP сервера",
"admin.config.smtp.email": "E-пошта",
"admin.config.smtp.email.description": "Адреса е-поште са које се е-поруке шаљу",
"admin.config.smtp.username": "Корисничко име",
"admin.config.smtp.username.description": "Корисничко име SMTP сервера",
"admin.config.smtp.password": "Лозинка",
"admin.config.smtp.password.description": "Лозинка SMTP сервера",
"admin.config.smtp.button.test": "Пошаљи тестну е-пошту",
// 404
"404.description": "Опа - Ова страна не постоји.",
"404.button.home": "Врати ме на почетак",
// Common translations
"common.button.save": "Сачувај",
"common.button.create": "Направи",
"common.button.submit": "Пошаљи",
"common.button.delete": "Обриши",
"common.button.cancel": "Откажи",
"common.button.confirm": "Потврди",
"common.button.disable": "Онемогући",
"common.button.share": "Дељење",
"common.button.generate": "Генериши",
"common.button.done": "Готово",
"common.text.link": "Линк",
"common.text.or": "или",
"common.button.go-back": "Иди назад",
"common.notify.copied": "Ваша веза је копирана у clipboard",
"common.success": "Успешно",
"common.error": "Грешка",
"common.error.unknown": "Дошло је до непознате грешке",
"common.error.invalid-email": "Неисправна адреса е-поште",
"common.error.too-short": "Мора да има најмање {length} знакова",
"common.error.too-long": "Мора да има највише {length} знакова",
"common.error.exact-length": "Мора да има тачно {length} знакова",
"common.error.invalid-number": "Мора бити број",
"common.error.field-required": "Поље је обавезно"
};

View File

@@ -0,0 +1,324 @@
export default {
// Navbar
"navbar.upload": "อัพโหลด",
"navbar.signin": "เข้าสู่ระบบ",
"navbar.home": "หน้าหลัก",
"navbar.signup": "สมัครบัญชี",
"navbar.links.shares": "แชร์ของฉัน",
"navbar.links.reverse": "รีเวิร์สแชร์",
"navbar.avatar.account": "บัญชีของฉัน",
"navbar.avatar.admin": "แผงควบคุมระบบ",
"navbar.avatar.signout": "ออกจากระบบ",
// END navbar
// /
"home.title": "แพลตฟอร์มสำหรับแชร์ไฟล์ที่คุณสามารถโฮสต์ด้วยตนเอง.",
"home.description": "คุณอยากให้บริษัทภายนอกเช่น WeTransfer เข้าถึงไฟล์ส่วนตัวของคุณหรือเปล่า?",
"home.bullet.a.name": "Self-Hosted",
"home.bullet.a.description": "โฮสต์ Pingvin Share บนเครื่องของคุณเอง.",
"home.bullet.b.name": "ความเป็นส่วนตัว",
"home.bullet.b.description": "ไฟล์ของคุณคือไฟล์ของคุณ มันไม่ควรตกอยู่บนมือของบุคคลที่สาม",
"home.bullet.c.name": "ไม่มีการจำกัดขนาดไฟล์ที่น่ารำคาญ",
"home.bullet.c.description": "อัพโหลดใหญ่เท่าไหนก็ได้ จำกัดอย่างเดียวคือความจุของเคื่องคุณ",
"home.button.start": "เริ่มต้น",
"home.button.source": "ซอร์สโค้ด",
// END /
// /auth/signin
"signin.title": "ยินดีต้อนรับกลับ",
"signin.description": "ยังไม่มีบัญชี?",
"signin.button.signup": "สมัครบัญชี",
"signin.input.email-or-username": "อีเมล์์์์์์์์หรือชื่อผู้ใช้",
"signin.input.email-or-username.placeholder": "อีเมล์์์์์์์หรือชื่อผู้ใช้ของคุณ",
"signin.input.password": "รหัสผ่าน",
"signin.input.password.placeholder": "รหัสผ่านของคุณ",
"signin.button.submit": "เข้าสู่ระบบ",
"signIn.notify.totp-required.title": "ยืนยันตรวจสอบสิทธิ์สองปัจจัย",
"signIn.notify.totp-required.description": "กรุณาใส่รหัสยืนยันตัวตนสองปัจจัย",
// END /auth/signin
// /auth/signup
"signup.title": "สมัครบัญชี",
"signup.description": "มีบัญชีแล้ว?",
"signup.button.signin": "เข้าสู่ระบบ",
"signup.input.username": "ชื่อผู้ใช้",
"signup.input.username.placeholder": "ชื่อผู้ใช้ของคุณ",
"signup.input.email": "อีเมล์์์์์์์์",
"signup.input.email.placeholder": "อีเมล์์์์์์์์ของคุณ",
"signup.button.submit": "เริ่มต้นกัน",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "ลืมรหัสผ่าน?",
"resetPassword.description": "กรุณาใส่อีเมล์์์์์์์ของคุณเพื่อรีเซ็ตรหัสผ่าน",
"resetPassword.notify.success": "ส่งอีเมล์์์์์์์พร้อมลิงค์เพื่อรีเซ็ตรหัสผ่านของคุณแล้ว",
"resetPassword.button.back": "กลับไปที่หน้าลงชื่อเข้าใช้",
"resetPassword.text.resetPassword": "รีเซ็ตรหัสผ่าน",
"resetPassword.text.enterNewPassword": "ป้อนรหัสผ่านใหม่ของคุณ",
"resetPassword.input.password": "รหัสผ่านใหม่",
"resetPassword.notify.passwordReset": "รหัสผ่านของคุณรีเซ็ตเรียบร้อยแล้ว",
// /account
"account.title": "บัญชีของฉัน",
"account.card.info.title": "ข้อมูลบัญชี",
"account.card.info.username": "ชื่อผู้ใช้",
"account.card.info.email": "อีเมล์์์์์์์",
"account.notify.info.success": "อัปเดตบัญชีเรียบร้อยแล้ว",
"account.card.password.title": "รหัสผ่าน",
"account.card.password.old": "รหัสผ่านเก่า",
"account.card.password.new": "รหัสผ่านใหม่",
"account.notify.password.success": "อัปเดตรหัสผ่านเรียบร้อยแล้ว",
"account.card.security.title": "ความปลอดภัย",
"account.card.security.totp.enable.description": "ใส่รหัสผ่านปัจจุบันของคุณเพื่อเริ่มต้นการเปิดใช้งาน TOTP",
"account.card.security.totp.disable.description": "ใส่รหัสผ่านปัจจุบันของคุณเพื่อปิดใช้งาน TOTP",
"account.card.security.totp.button.start": "เริ่มต้น",
"account.modal.totp.title": "เปิดใช้งาน TOTP",
"account.modal.totp.step1": "ขั้นตอนที่ 1: สแกนรหัส QR",
"account.modal.totp.step2": "ขั้นตอนที่ 2: ป้อนรหัสยืนยันตัวตน",
"account.modal.totp.enterManually": "ป้อนด้วยตนเอง",
"account.modal.totp.code": "รหัส",
"account.modal.totp.clickToCopy": "คลิกเพื่อคัดลอก",
"account.modal.totp.verify": "ยืนยัน",
"account.notify.totp.disable": "TOTP ถูกปิดใช้งานเรียบร้อยแล้ว",
"account.notify.totp.enable": "TOTP ถูกเปิดใช้งานเรียบร้อยแล้ว",
"account.card.language.title": "ภาษา",
"account.card.language.description": "โปรเจคนี้ถูกแปลโดยชุมชน บางภาษาอาจยังไม่สมบูรณ์",
"account.card.color.title": "ธีมสี",
// ThemeSwitcher.tsx
"account.theme.dark": "มืด",
"account.theme.light": "สว่าง",
"account.theme.system": "ตามระบบ",
"account.button.delete": "ลบบัญชี",
"account.modal.delete.title": "ลบบัญชีของคุณ",
"account.modal.delete.description": "คุณต้องการลบบัญชีของคุณหรือไม่ รวมถึงแชร์ที่คุณสร้างไว้ทั้งหมด?",
// END /account
// /account/shares
"account.shares.title": "แชร์ของฉัน",
"account.shares.title.empty": "มันว่างเปล่าที่นี่ 👀",
"account.shares.description.empty": "คุณยังไม่ได้สร้างแชร์ใดๆ",
"account.shares.button.create": "สร้างแชร์",
"account.shares.info.title": "ข้อมูลแชร์",
"account.shares.table.id": "ID",
"account.shares.table.name": "ชื่อ",
"account.shares.table.description": "คำอธิบาย",
"account.shares.table.visitors": "ผู้เข้าชม",
"account.shares.table.expiresAt": "หมดอายุ",
"account.shares.table.createdAt": "สร้างเมื่อ",
"account.shares.table.size": "ขนาด",
"account.shares.modal.share-informations": "ข้อมูลแชร์",
"account.shares.modal.share-link": "แชร์ลิงค์",
"account.shares.modal.delete.title": "ลบแชร์ {share}",
"account.shares.modal.delete.description": "คุณต้องการลบแชร์นี้หรือไม่?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "รีเวิร์สแชร์ของฉัน",
"account.reverseShares.description": "รีเวิร์สแชร์สร้างลิงค์สำหรับคนภายนอกเพื่อที่จะแชร์ไฟล์ให้คุณ",
"account.reverseShares.title.empty": "มันว่างเปล่าที่นี่ 👀",
"account.reverseShares.description.empty": "คุณยังไม่ได้สร้างรีเวิร์สแชร์ใดๆ",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Create reverse share",
"account.reverseShares.modal.expiration.label": "ลิงค์หมดอายุใน",
"account.reverseShares.modal.expiration.minute-singular": "นาที",
"account.reverseShares.modal.expiration.minute-plural": "นาที",
"account.reverseShares.modal.expiration.hour-singular": "ชั่วโมง",
"account.reverseShares.modal.expiration.hour-plural": "ชั่วโมง",
"account.reverseShares.modal.expiration.day-singular": "วัน",
"account.reverseShares.modal.expiration.day-plural": "วัน",
"account.reverseShares.modal.expiration.week-singular": "สัปดาห์",
"account.reverseShares.modal.expiration.week-plural": "สัปดาห์",
"account.reverseShares.modal.expiration.month-singular": "เดือน",
"account.reverseShares.modal.expiration.month-plural": "เดือน",
"account.reverseShares.modal.expiration.year-singular": "ปี",
"account.reverseShares.modal.expiration.year-plural": "ปี",
"account.reverseShares.modal.max-size.label": "ขนาดสูงสุดของแชร์",
"account.reverseShares.modal.send-email": "ส่งอีเมล์์์์์์์แจ้งเตือน",
"account.reverseShares.modal.send-email.description": "ส่งอีเมล์์์์์์์แจ้งเตือนเมื่อมีการสร้างแชร์ด้วยลิงค์รีเวิร์สนี้",
"account.reverseShares.modal.max-use.label": "จำนวนการใช้งานสูงสุด",
"account.reverseShares.modal.max-use.description": "จำนวนครั้งสูงสุดที่ลิงค์นี้สามารถใช้งานได้",
"account.reverseShare.never-expires": "ลิงค์นี้ไม่มีวันหมดอายุ",
"account.reverseShare.expires-on": "ลิงค์นี้จะหมดอายุใน {expiration}.",
"account.reverseShares.table.no-shares": "ยังไม่มีการสร้างแชร์",
"account.reverseShares.table.count.singular": "แชร์",
"account.reverseShares.table.count.plural": "แชร์",
"account.reverseShares.table.shares": "แชร์",
"account.reverseShares.table.remaining": "เหลืออีก {count} ครั้ง",
"account.reverseShares.table.max-size": "ขนาดสูงสุดของแชร์",
"account.reverseShares.table.expires": "หมดอายุ",
"account.reverseShares.modal.reverse-share-link": "ลิงค์รีเวิร์สแชร์",
"account.reverseShares.modal.delete.title": "ลบลิงค์รีเวิร์สแชร์",
"account.reverseShares.modal.delete.description": "คุณต้องการลบลิงค์รีเวิร์สแชร์นี้หรือไม่? หากคุณทำเช่นนั้นแชร์ที่เกี่ยวข้องจะถูกลบด้วย",
// END /account/reverseShares
// /admin
"admin.title": "แผงควบคุมระบบ",
"admin.button.users": "การจัดการผู้ใช้",
"admin.button.config": "การตั้งค่า",
"admin.version": "เวอร์ชัน",
// END /admin
// /admin/users
"admin.users.title": "การจัดการผู้ใช้",
"admin.users.table.username": "ชื่อผู้ใช้",
"admin.users.table.email": "อีเมล์์์์์์์",
"admin.users.table.admin": "ผู้ดูแลระบบ",
"admin.users.edit.update.title": "อัปเดตผู้ใช้ {username}",
"admin.users.edit.update.admin-privileges": "สิทธิ์ของผู้ดูแลระบบ",
"admin.users.edit.update.change-password.title": "เปลี่ยนรหัสผ่าน",
"admin.users.edit.update.change-password.field": "รหัสผ่านใหม่",
"admin.users.edit.update.change-password.button": "บันทึกรหัสผ่านใหม่",
"admin.users.edit.update.notify.password.success": "รหัสผ่านเปลี่ยนเรียบร้อยแล้ว",
"admin.users.edit.delete.title": "ลบผู้ใช้ {username}",
"admin.users.edit.delete.description": "คุณต้องการลบผู้ใช้นี้และการแชร์ทั้งหมดของเขาหรือไม่?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "สร้างผู้ใช้",
"admin.users.modal.create.username": "ชื่อผู้ใช้",
"admin.users.modal.create.email": "อีเมล์",
"admin.users.modal.create.password": "รหัสผ่าน",
"admin.users.modal.create.manual-password": "ตั้งรหัสผ่านด้วยตนเอง",
"admin.users.modal.create.manual-password.description": "หากไม่ติ๊กเลือก ผู้ใช้จะได้รับอีเมล์พร้อมลิงก์เพื่อตั้งรหัสผ่านด้วยตนเอง",
"admin.users.modal.create.admin": "สิทธิ์ของผู้ดูแลระบบ",
"admin.users.modal.create.admin.description": "หากติ๊กเลือก ผู้ใช้จะสามารถเข้าถึงแผงควบคุมระบบได้",
// END /admin/users
// /upload
"upload.title": "อัปโหลด",
"upload.notify.generic-error": "เกิดข้อผิดพลาดขณะที่กำลังจัดการการแชร์ของคุณ",
"upload.notify.count-failed": "มีไฟล์จำนวน {count} ไฟล์ที่ไม่สามารถอัปโหลดได้ กำลังลองอีกครั้ง",
// Dropzone.tsx
"upload.dropzone.title": "อัปโหลดไฟล์",
"upload.dropzone.description": "ลากและวางไฟล์ที่นี่เพื่อเริ่มการแชร์ของคุณ สามารถรับไฟล์ที่มีขนาดรวมไม่เกิน {maxSize} เท่านั้น",
"upload.dropzone.notify.file-too-big": "ไฟล์ของคุณเกินขนาดสูงสุดของการแชร์ {maxSize}",
// FileList.tsx
"upload.filelist.name": "ชื่อ",
"upload.filelist.size": "ขนาด",
// showCreateUploadModal.tsx
"upload.modal.title": "สร้างการแชร์",
"upload.modal.link.error.invalid": "สามารถใช้ได้เฉพาะตัวอักษร ตัวเลข ขีดล่าง และขีดเส้น",
"upload.modal.link.error.taken": "ลิงก์นี้ถูกใช้งานแล้ว",
"upload.modal.not-signed-in": "คุณยังไม่ได้เข้าสู่ระบบ",
"upload.modal.not-signed-in-description": "คุณจะไม่สามารถลบการแชร์ของคุณด้วยตนเองและดูจำนวนผู้เข้าชมได้",
"upload.modal.expires.never": "ไม่มีกำหนด",
"upload.modal.expires.never-long": "ไม่มีกำหนดหมดอายุ",
"upload.modal.link.label": "ลิงค์",
"upload.modal.expires.label": "หมดอายุ",
"upload.modal.expires.minute-singular": "นาที",
"upload.modal.expires.minute-plural": "นาที",
"upload.modal.expires.hour-singular": "ชั่วโมง",
"upload.modal.expires.hour-plural": "ชั่วโมง",
"upload.modal.expires.day-singular": "วัน",
"upload.modal.expires.day-plural": "วัน",
"upload.modal.expires.week-singular": "สัปดาห์",
"upload.modal.expires.week-plural": "สัปดาห์",
"upload.modal.expires.month-singular": "เดือน",
"upload.modal.expires.month-plural": "เดือน",
"upload.modal.expires.year-singular": "ปี",
"upload.modal.expires.year-plural": "ปี",
"upload.modal.accordion.description.title": "คำอธิบาย",
"upload.modal.accordion.description.placeholder": "หมายเหตุสำหรับผู้รับการแชร์นี้",
"upload.modal.accordion.email.title": "ผู้รับอีเมล์",
"upload.modal.accordion.email.placeholder": "ป้อนผู้รับอีเมล์",
"upload.modal.accordion.email.invalid-email": "ที่อยู่อีเมล์ไม่ถูกต้อง",
"upload.modal.accordion.security.title": "ตัวเลือกความปลอดภัย",
"upload.modal.accordion.security.password.label": "การป้องกันรหัสผ่าน",
"upload.modal.accordion.security.password.placeholder": "ไม่มีรหัสผ่าน",
"upload.modal.accordion.security.max-views.label": "จำนวนการเข้าชมสูงสุด",
"upload.modal.accordion.security.max-views.placeholder": "ไม่จำกัด",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "การแชร์นี้จะไม่มีวันหมดอายุ",
"upload.modal.completed.expires-on": "การแชร์นี้จะหมดอายุเมื่อวันที่ {expiration}",
"upload.modal.completed.share-ready": "แชร์พร้อมใช้งาน",
// END /upload
// /share/[id]
"share.title": "แชร์ {shareId}",
"share.description": "ดูสิ่งที่ฉันแชร์กับคุณ!",
"share.error.visitor-limit-exceeded.title": "เกินขีดจำกัดผู้เข้าชม",
"share.error.visitor-limit-exceeded.description": "การแชร์นี้ได้เกินขีดจำกัดผู้เข้าชมแล้ว",
"share.error.removed.title": "การแชร์ถูกลบ",
"share.error.not-found.title": "ไม่พบการแชร์นี้",
"share.error.not-found.description": "การแชร์ที่คุณกำลังมองหาไม่มีอยู่จริง",
"share.modal.password.title": "ต้องการรหัสผ่าน",
"share.modal.password.description": "กรุณาป้อนรหัสผ่านเพื่อเข้าถึงการแชร์นี้",
"share.modal.password": "รหัสผ่าน",
"share.modal.error.invalid-password": "รหัสผ่านไม่ถูกต้อง",
"share.button.download-all": "ดาวน์โหลดทั้งหมด",
"share.notify.download-all-preparing": "กำลังเตรียมการแชร์นี้ โปรดลองอีกครั้งในไม่กี่นาที",
"share.modal.file-link": "ลิงค์ไฟล์",
"share.table.name": "ชื่อ",
"share.table.size": "ขนาด",
"share.modal.file-preview.error.not-supported.title": "ไม่รองรับการแสดงตัวอย่าง",
"share.modal.file-preview.error.not-supported.description": "ไม่รองรับการแสดงตัวอย่างสำหรับไฟล์ประเภทนี้ โปรดดาวน์โหลดไฟล์เพื่อดู",
// END /share/[id]
// /admin/config
"admin.config.title": "การตั้งค่า",
"admin.config.category.general": "ทั่วไป",
"admin.config.category.share": "การแชร์",
"admin.config.category.email": "อีเมล์์์์์์",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "ชื่อแอพ",
"admin.config.general.app-name.description": "ชื่อแอพพลิเคชัน",
"admin.config.general.app-url": "URL ของแอพ",
"admin.config.general.app-url.description": "URL ที่สามารถเข้าถึงแอพพลิเคชัน Pingvin Share ได้",
"admin.config.general.show-home-page": "แสดงหน้าแรก",
"admin.config.general.show-home-page.description": "หากติ๊ก เว็บไซต์จะแสดงหน้าหลักเวลาเข้าถึง URL หลัก",
"admin.config.general.logo": "โลโก้",
"admin.config.general.logo.description": "เปลี่ยนโลโก้โดยอัปโหลดรูปภาพใหม่ รูปภาพต้องเป็น PNG และควรมีขนาดอัตราส่วน 1:1",
"admin.config.general.logo.placeholder": "คลิกที่นี่หรือลากไฟล์มา",
"admin.config.email.enable-share-email-recipients": "เปิดใช้งานผู้รับอีเมล์์์์์์ของการแชร์",
"admin.config.email.enable-share-email-recipients.description": "เมื่อเปิดใช้งานผู้รับอีเมล์์์์์์ของการแชร์ ผู้ใช้ที่สร้างการแชร์จะสามารถเพิ่มผู้รับอีเมล์์์์์์์ของการแชร์ได้ เปิดเฉพาะเมื่อคุณเปิดใช้งาน SMTP",
"admin.config.email.share-recipients-subject": "หัวเรื่องผู้รับอีเมล์์์์์์ของการแชร์",
"admin.config.email.share-recipients-subject.description": "หัวเรื่องของอีเมล์์์์์์์์ที่ส่งไปยังผู้รับอีเมล์์์์์์ของการแชร์",
"admin.config.email.share-recipients-message": "ข้อความผู้รับอีเมล์์์์์์ของการแชร์",
"admin.config.email.share-recipients-message.description": "ข้อความที่ส่งไปยังผู้รับอีเมล์์์์์์ของการแชร์ ตัวแปรที่ใช้ได้:\n {creator} - ชื่อผู้สร้างการแชร์\n {shareUrl} - URL ของการแชร์\n {desc} - คำอธิบายของการแชร์\n {expires} - วันหมดอายุของการแชร์\n ตัวแปรจะถูกแทนที่ด้วยค่าจริง",
"admin.config.email.reverse-share-subject": "หัวเรื่องการแชร์รีเวิร์ส",
"admin.config.email.reverse-share-subject.description": "หัวเรื่องของอีเมล์์์์์์ที่ส่งไปยังผู้สร้างการแชร์รีเวิร์ส",
"admin.config.email.reverse-share-message": "ข้อความการแชร์รีเวิร์ส",
"admin.config.email.reverse-share-message.description": "ข้อความที่ส่งไปยังผู้สร้างการแชร์รีเวิร์ส ตัวแปรที่ใช้ได้:\n {shareUrl} - ชื่อผู้สร้างแชร์รีเวิร์สและ URL ของการแชร์\n ตัวแปรจะถูกแทนที่ด้วยค่าจริง",
"admin.config.email.reset-password-subject": "หัวเรื่องการรีเซ็ตรหัสผ่าน",
"admin.config.email.reset-password-subject.description": "หัวเรื่องของอีเมล์์์์์์ที่ส่งไปยังผู้ใช้เมื่อขอรีเซ็ตรหัสผ่าน",
"admin.config.email.reset-password-message": "ข้อความการรีเซ็ตรหัสผ่าน",
"admin.config.email.reset-password-message.description": "ข้อความที่ส่งไปยังผู้ใช้เมื่อขอรีเซ็ตรหัสผ่าน ตัวแปรที่ใช้ได้:\n {url} - URL สำหรับรีเซ็ตรหัสผ่าน\n ตัวแปรจะถูกแทนที่ด้วยค่าจริง",
"admin.config.email.invite-subject": "หัวเรื่องการเชิญ",
"admin.config.email.invite-subject.description": "หัวเรื่องของอีเมล์์์์์์ที่ส่งไปยังผู้ใช้เมื่อผู้ดูแลระบบเชิญผู้ใช้",
"admin.config.email.invite-message": "ข้อความการเชิญ",
"admin.config.email.invite-message.description": "ข้อความที่ส่งไปยังผู้ใช้เมื่อผู้ดูแลระบบเชิญผู้ใช้ ตัวแปรที่ใช้ได้:\n {url} - URL สำหรับเชิญผู้ใช้\n {password} - รหัสผ่านของผู้ใช้\n ตัวแปรจะถูกแทนที่ด้วยค่าจริง",
"admin.config.share.allow-registration": "อนุญาตให้ลงทะเบียนด้วยตัวเอง",
"admin.config.share.allow-registration.description": "อนุญาตให้ผู้ใช้ลงทะเบียนด้วยตัวเองเพื่อสร้างแชร์",
"admin.config.share.allow-unauthenticated-shares": "อนุญาตให้แชร์โดยไม่ต้องเข้าสู่ระบบ",
"admin.config.share.allow-unauthenticated-shares.description": "อนุญาตให้ผู้ใช้ที่ไม่ได้เข้าสู่ระบบสร้างแชร์",
"admin.config.share.max-size": "ขนาดสูงสุด",
"admin.config.share.max-size.description": "ขนาดสูงสุดของแชร์",
"admin.config.share.zip-compression-level": "ระดับการบีบอัดไฟล์ Zip",
"admin.config.share.zip-compression-level.description": "ปรับระดับเพื่อปรับความสมดุลระหว่างขนาดไฟล์และความเร็วในการบีบอัด ค่าอยู่ระหว่าง 0-9 โดย 0 คือไม่มีการบีบอัดและ 9 คือการบีบอัดสูงสุด",
"admin.config.smtp.enabled": "เปิด",
"admin.config.smtp.enabled.description": "เปิดใช้งาน SMTP สำหรับการส่งอีเมล์์์์์์ เปิดได้เท่านั้นต่อเมื่อคุณใส่ข้อมูลโฮสต์ พอร์ต อีเมล์ ผู้ใช้ และรหัสผ่านของเซิร์ฟเวอร์ SMTP ของคุณ",
"admin.config.smtp.host": "โฮสต์",
"admin.config.smtp.host.description": "โฮสต์ของเซิร์ฟเวอร์ SMTP",
"admin.config.smtp.port": "พอร์ต",
"admin.config.smtp.port.description": "พอร์ตของเซิร์ฟเวอร์ SMTP",
"admin.config.smtp.email": "อีเมล์์์์์์์",
"admin.config.smtp.email.description": "อีเมล์์์์์์์ที่ใช้สำหรับการส่งอีเมล์์์์์์์",
"admin.config.smtp.username": "ชื่อผู้ใช้",
"admin.config.smtp.username.description": "ชื่อผู้ใช้ของเซิร์ฟเวอร์ SMTP",
"admin.config.smtp.password": "รหัสผ่าน",
"admin.config.smtp.password.description": "รหัสผ่านของเซิร์ฟเวอร์ SMTP",
"admin.config.smtp.button.test": "ส่งอีเมล์์์์์์ทดสอบ",
// 404
"404.description": "ไม่พบหน้าที่คุณกำลังมองหา",
"404.button.home": "หน้าแรก",
// Common translations
"common.button.save": "บันทึก",
"common.button.create": "สร้าง",
"common.button.submit": "ส่ง",
"common.button.delete": "ลบ",
"common.button.cancel": "ยกเลิก",
"common.button.confirm": "ยืนยัน",
"common.button.disable": "ปิดการใช้งาน",
"common.button.share": "แชร์",
"common.button.generate": "สุ่ม",
"common.button.done": "เสร็จสิ้น",
"common.text.link": "ลิงค์",
"common.text.or": "หรือ",
"common.button.go-back": "ย้อนกลับ",
"common.notify.copied": "คัดลอกไปยังคลิปบอร์ดแล้ว",
"common.success": "สำเร็จ",
"common.error": "เกิดข้อผิดพลาด",
"common.error.unknown": "เกิดข้อผิดพลาดที่ไม่รู้จัก",
"common.error.invalid-email": "ที่อยู่อีเมล์ไม่ถูกต้อง",
"common.error.too-short": "ต้องมีอย่างน้อย {length} ตัวอักษร",
"common.error.too-long": "ต้องมีไม่เกิน {length} ตัวอักษร",
"common.error.exact-length": "ต้องมีความยาว {length} ตัวอักษร",
"common.error.invalid-number": "ต้องเป็นตัวเลข",
"common.error.field-required": "ต้องกรอกข้อมูลนี้"
};

View File

@@ -111,6 +111,7 @@ export default {
"account.reverseShares.title.empty": "这里空空如也 👀",
"account.reverseShares.description.empty": "你没有创建任何预留共享",
// showCreateReverseShareModal.tsx
"account.reverseShares.modal.title": "Create reverse share",
"account.reverseShares.modal.expiration.label": "过期时间",
"account.reverseShares.modal.expiration.minute-singular": "1 分钟",
"account.reverseShares.modal.expiration.minute-plural": "分钟",

View File

@@ -35,7 +35,7 @@ export async function middleware(request: NextRequest) {
try {
const claims = jwtDecode<{ exp: number; isAdmin: boolean }>(
accessToken as string
accessToken as string,
);
if (claims.exp * 1000 > Date.now()) {
user = claims;

View File

@@ -43,9 +43,7 @@ const ErrorNotFound = () => {
<>
<Meta title="Not found" />
<Container className={classes.root}>
<div className={classes.label}>
404
</div>
<div className={classes.label}>404</div>
<Title align="center" order={3}>
<FormattedMessage id="404.description" />
</Title>

View File

@@ -41,7 +41,7 @@ function App({ Component, pageProps }: AppProps) {
const [route, setRoute] = useState<string>(pageProps.route);
const [configVariables, setConfigVariables] = useState<Config[]>(
pageProps.configVariables
pageProps.configVariables,
);
useEffect(() => {
@@ -165,7 +165,7 @@ App.getInitialProps = async ({ ctx }: { ctx: GetServerSidePropsContext }) => {
pageProps.route = ctx.req.url;
const requestLanguage = i18nUtil.getLanguageFromAcceptHeader(
ctx.req.headers["accept-language"]
ctx.req.headers["accept-language"],
);
pageProps.language = ctx.req.cookies["language"] ?? requestLanguage;

View File

@@ -42,7 +42,7 @@ const Account = () => {
username: yup
.string()
.min(3, t("common.error.too-short", { length: 3 })),
})
}),
),
});
@@ -61,7 +61,7 @@ const Account = () => {
.string()
.min(8, t("common.error.too-short", { length: 8 }))
.required(t("common.error.field-required")),
})
}),
),
});
@@ -75,7 +75,7 @@ const Account = () => {
.string()
.min(8, t("common.error.too-short", { length: 8 }))
.required(t("common.error.field-required")),
})
}),
),
});
@@ -92,7 +92,7 @@ const Account = () => {
.min(6, t("common.error.exact-length", { length: 6 }))
.max(6, t("common.error.exact-length", { length: 6 }))
.matches(/^[0-9]+$/, { message: t("common.error.invalid-number") }),
})
}),
),
});
@@ -115,7 +115,7 @@ const Account = () => {
email: values.email,
})
.then(() => toast.success(t("account.notify.info.success")))
.catch(toast.axiosError)
.catch(toast.axiosError),
)}
>
<Stack>
@@ -147,7 +147,7 @@ const Account = () => {
toast.success(t("account.notify.password.success"));
passwordForm.reset();
})
.catch(toast.axiosError)
.catch(toast.axiosError),
)}
>
<Stack>
@@ -199,7 +199,7 @@ const Account = () => {
<Stack>
<PasswordInput
description={t(
"account.card.security.totp.disable.description"
"account.card.security.totp.disable.description",
)}
label={t("account.card.password.title")}
{...disableTotpForm.getInputProps("password")}
@@ -241,7 +241,7 @@ const Account = () => {
<PasswordInput
label={t("account.card.password.title")}
description={t(
"account.card.security.totp.enable.description"
"account.card.security.totp.enable.description",
)}
{...enableTotpForm.getInputProps("password")}
/>

View File

@@ -77,7 +77,7 @@ const MyShares = () => {
showCreateReverseShareModal(
modals,
config.get("smtp.enabled"),
getReverseShares
getReverseShares,
)
}
leftIcon={<TbPlus size={20} />}
@@ -134,10 +134,10 @@ const MyShares = () => {
<Text size="sm">
{reverseShare.shares.length == 1
? `1 ${t(
"account.reverseShares.table.count.singular"
"account.reverseShares.table.count.singular",
)}`
: `${reverseShare.shares.length} ${t(
"account.reverseShares.table.count.plural"
"account.reverseShares.table.count.plural",
)}`}
</Text>
</Accordion.Control>
@@ -158,15 +158,13 @@ const MyShares = () => {
size={25}
onClick={() => {
if (window.isSecureContext) {
clipboard.copy(
`${appUrl}/s/${share.id}`
);
clipboard.copy(`${appUrl}/s/${share.id}`);
toast.success(t("common.notify.copied"));
} else {
showShareLinkModal(
modals,
share.id,
config.get("general.appUrl")
config.get("general.appUrl"),
);
}
}}
@@ -200,14 +198,14 @@ const MyShares = () => {
clipboard.copy(
`${config.get("general.appUrl")}/upload/${
reverseShare.token
}`
}`,
);
toast.success(t("common.notify.copied"));
} else {
showReverseShareLinkModal(
modals,
reverseShare.token,
config.get("general.appUrl")
config.get("general.appUrl"),
);
}
}}
@@ -221,7 +219,7 @@ const MyShares = () => {
onClick={() => {
modals.openConfirmModal({
title: t(
"account.reverseShares.modal.delete.title"
"account.reverseShares.modal.delete.title",
),
children: (
<Text size="sm">
@@ -239,8 +237,8 @@ const MyShares = () => {
shareService.removeReverseShare(reverseShare.id);
setReverseShares(
reverseShares.filter(
(item) => item.id !== reverseShare.id
)
(item) => item.id !== reverseShare.id,
),
);
},
});

View File

@@ -119,7 +119,7 @@ const MyShares = () => {
modals,
share,
config.get("general.appUrl"),
parseInt(config.get("share.maxSize"))
parseInt(config.get("share.maxSize")),
);
}}
>
@@ -132,16 +132,14 @@ const MyShares = () => {
onClick={() => {
if (window.isSecureContext) {
clipboard.copy(
`${config.get("general.appUrl")}/s/${
share.id
}`
`${config.get("general.appUrl")}/s/${share.id}`,
);
toast.success(t("common.notify.copied"));
} else {
showShareLinkModal(
modals,
share.id,
config.get("general.appUrl")
config.get("general.appUrl"),
);
}
}}
@@ -172,7 +170,7 @@ const MyShares = () => {
onConfirm: () => {
shareService.remove(share.id);
setShares(
shares.filter((item) => item.id !== share.id)
shares.filter((item) => item.id !== share.id),
);
},
});

View File

@@ -56,7 +56,7 @@ export default function AppShellDemo() {
.then(() => {
setLogo(null);
toast.success(
"Logo updated successfully. It may take a few minutes to update on the website."
"Logo updated successfully. It may take a few minutes to update on the website.",
);
})
.catch(toast.axiosError);
@@ -76,7 +76,7 @@ export default function AppShellDemo() {
const updateConfigVariable = (configVariable: UpdateConfig) => {
const index = updatedConfigVariables.findIndex(
(item) => item.key === configVariable.key
(item) => item.key === configVariable.key,
);
if (index > -1) {
@@ -139,7 +139,7 @@ export default function AppShellDemo() {
<Title order={6}>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key
configVariable.key,
)}`}
/>
</Title>
@@ -154,7 +154,7 @@ export default function AppShellDemo() {
>
<FormattedMessage
id={`admin.config.${camelToKebab(
configVariable.key
configVariable.key,
)}.description`}
values={{ br: <br /> }}
/>

View File

@@ -12,7 +12,7 @@ export const config = {
const { apiURL } = getConfig().serverRuntimeConfig;
export default (req: NextApiRequest, res: NextApiResponse) => {
return httpProxyMiddleware(req, res, {
httpProxyMiddleware(req, res, {
headers: {
"X-Forwarded-For": req.socket?.remoteAddress ?? "",
},

View File

@@ -39,7 +39,7 @@ const ResetPassword = () => {
.string()
.min(8, t("common.error.too-short", { length: 8 }))
.required(t("common.error.field-required")),
})
}),
),
});

View File

@@ -57,7 +57,7 @@ const ResetPassword = () => {
.string()
.email(t("common.error.invalid-email"))
.required(t("common.error.field-required")),
})
}),
),
});
@@ -79,7 +79,7 @@ const ResetPassword = () => {
toast.success(t("resetPassword.notify.success"));
router.push("/auth/signIn");
})
.catch(toast.axiosError)
.catch(toast.axiosError),
)}
>
<TextInput

View File

@@ -36,7 +36,7 @@ const Share = ({ shareId }: { shareId: string }) => {
showErrorModal(
modals,
t("share.error.visitor-limit-exceeded.title"),
t("share.error.visitor-limit-exceeded.description")
t("share.error.visitor-limit-exceeded.description"),
);
} else {
toast.axiosError(e);
@@ -57,13 +57,13 @@ const Share = ({ shareId }: { shareId: string }) => {
showErrorModal(
modals,
t("share.error.removed.title"),
e.response.data.message
e.response.data.message,
);
} else {
showErrorModal(
modals,
t("share.error.not-found.title"),
t("share.error.not-found.description")
t("share.error.not-found.description"),
);
}
} else if (error == "share_password_required") {

View File

@@ -29,7 +29,7 @@ const Share = ({ reverseShareToken }: { reverseShareToken: string }) => {
showErrorModal(
modals,
"Invalid Link",
"This link is invalid. Please check your link."
"This link is invalid. Please check your link.",
);
setIsLoading(false);
});

View File

@@ -40,7 +40,7 @@ const Upload = ({
maxShareSize ??= parseInt(config.get("share.maxSize"));
const uploadFiles = async (share: CreateShare) => {
const uploadFiles = async (share: CreateShare, files: FileUpload[]) => {
setisUploading(true);
createdShare = await shareService.create(share);
@@ -120,6 +120,24 @@ const Upload = ({
Promise.all(fileUploadPromises);
};
const showCreateUploadModalCallback = (files: FileUpload[]) => {
setFiles(files);
showCreateUploadModal(
modals,
{
isUserSignedIn: user ? true : false,
isReverseShare,
appUrl: config.get("general.appUrl"),
allowUnauthenticatedShares: config.get(
"share.allowUnauthenticatedShares"
),
enableEmailRecepients: config.get("email.enableShareEmailRecipients"),
},
files,
uploadFiles
);
};
useEffect(() => {
// Check if there are any files that failed to upload
const fileErrorCount = files.filter(
@@ -166,31 +184,14 @@ const Upload = ({
<Button
loading={isUploading}
disabled={files.length <= 0}
onClick={() => {
showCreateUploadModal(
modals,
{
isUserSignedIn: user ? true : false,
isReverseShare,
appUrl: config.get("general.appUrl"),
allowUnauthenticatedShares: config.get(
"share.allowUnauthenticatedShares"
),
enableEmailRecepients: config.get(
"email.enableShareEmailRecipients"
),
},
uploadFiles
);
}}
onClick={() => showCreateUploadModalCallback(files)}
>
<FormattedMessage id="common.button.share" />
</Button>
</Group>
<Dropzone
maxShareSize={maxShareSize}
files={files}
setFiles={setFiles}
showCreateUploadModalCallback={showCreateUploadModalCallback}
isUploading={isUploading}
/>
{files.length > 0 && <FileList files={files} setFiles={setFiles} />}

View File

@@ -19,7 +19,7 @@ const signInTotp = async (
emailOrUsername: string,
password: string,
totp: string,
loginToken: string
loginToken: string,
) => {
const emailOrUsernameBody = emailOrUsername.includes("@")
? { email: emailOrUsername }

View File

@@ -18,7 +18,7 @@ const get = (key: string, configVariables: Config[]): any => {
if (!configVariables) return null;
const configVariable = configVariables.filter(
(variable) => variable.key == key
(variable) => variable.key == key,
)[0];
if (!configVariable) throw new Error(`Config variable ${key} not found`);
@@ -42,7 +42,7 @@ const sendTestEmail = async (email: string) => {
const isNewReleaseAvailable = async () => {
const response = (
await axios.get(
"https://api.github.com/repos/stonith404/pingvin-share/releases/latest"
"https://api.github.com/repos/stonith404/pingvin-share/releases/latest",
)
).data;
return response.tag_name.replace("v", "") != process.env.VERSION;

View File

@@ -71,7 +71,7 @@ const uploadFile = async (
name: string;
},
chunkIndex: number,
totalChunks: number
totalChunks: number,
): Promise<FileUploadResponse> => {
const data = readerEvent.target!.result;
@@ -92,7 +92,7 @@ const createReverseShare = async (
shareExpiration: string,
maxShareSize: number,
maxUseCount: number,
sendEmailNotification: boolean
sendEmailNotification: boolean,
) => {
return (
await api.post("reverseShares", {

View File

@@ -26,7 +26,7 @@ export type AdminConfigGroupedByCategory = {
description: string;
obscured: boolean;
category: string;
}
},
];
};

View File

@@ -11,7 +11,7 @@ export const getExpirationPreview = (
expiration_num: number;
expiration_unit: string;
};
}
},
) => {
const value = form.values.never_expires
? "never"
@@ -21,12 +21,12 @@ export const getExpirationPreview = (
const expirationDate = moment()
.add(
value.split("-")[0],
value.split("-")[1] as moment.unitOfTime.DurationConstructor
value.split("-")[1] as moment.unitOfTime.DurationConstructor,
)
.toDate();
return messages.expiresOn.replace(
"{expiration}",
moment(expirationDate).format("LLL")
moment(expirationDate).format("LLL"),
);
};

View File

@@ -12,7 +12,7 @@ const getLanguageFromAcceptHeader = (acceptLanguage?: string) => {
const languages = acceptLanguage.split(",").map((l) => l.split(";")[0]);
const supportedLanguages = Object.values(LOCALES).map((l) => l.code);
const supportedLanguagesWithoutRegion = supportedLanguages.map(
(l) => l.split("-")[0]
(l) => l.split("-")[0],
);
for (const language of languages) {
@@ -23,7 +23,7 @@ const getLanguageFromAcceptHeader = (acceptLanguage?: string) => {
supportedLanguagesWithoutRegion.includes(language.split("-")[0])
) {
const similarLanguage = supportedLanguages.find((l) =>
l.startsWith(language.split("-")[0])
l.startsWith(language.split("-")[0]),
);
return similarLanguage;
}

View File

@@ -20,7 +20,7 @@ const axiosError = (axiosError: any) =>
const success = (
message: string,
config?: Omit<NotificationProps, "message">
config?: Omit<NotificationProps, "message">,
) =>
showNotification({
icon: <TbCheck />,

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share",
"version": "0.17.3",
"version": "0.18.0",
"scripts": {
"format": "cd frontend && npm run format && cd ../backend && npm run format",
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",