Compare commits

..

15 Commits

Author SHA1 Message Date
pierrbt
36fa76563e Add clickable link to reverse share's shares 2023-06-29 20:07:00 +02:00
pierrbt
f96ac5e4ba Merge branch 'stonith404:main' into main 2023-06-29 19:38:58 +02:00
pierrbt
bbbf10d233 Merge branch 'stonith404:main' into main 2023-06-23 21:09:24 +02:00
pierrbt
e9d1a9abb6 Remove useless import 2023-06-23 16:23:32 +02:00
pierrbt
8fdba0ca7c Run format 2023-06-23 16:22:57 +02:00
pierrbt
e5718700bc Set only a single click on the text, to avoid multiple notifications if the user wants to select it 2023-06-23 16:21:53 +02:00
pierrbt
e40a0c844c Creating CopyTextField component and adding it to showCompletedUpload and Shaere Modal 2023-06-23 16:14:52 +02:00
pierrbt
e647746c93 Update frontend/src/components/upload/modals/showCompletedUploadModal.tsx
Co-authored-by: Elias Schneider <login@eliasschneider.com>
2023-06-23 15:53:50 +02:00
pierrbt
9be77826e9 Formatting 2023-06-23 01:39:22 +02:00
pierrbt
a9bb05c4da Last updated 2023-06-23 00:05:53 +02:00
pierrbt
e1a9f2a27c Remove useless import 2023-06-22 23:18:09 +02:00
pierrbt
ba62c13cfa Removing the textClicked, because it was only source of disturbance 2023-06-22 23:17:50 +02:00
pierrbt
3de744d5e9 Adding a check when link is clicked, and now also on the text 2023-06-22 23:16:45 +02:00
pierrbt
61608cfe2d Adding LinkClicked to the copy button 2023-06-22 22:40:43 +02:00
pierrbt
db755ef300 Adding copy to keyboard when clicking the link 2023-06-22 22:36:56 +02:00
135 changed files with 4040 additions and 9902 deletions

View File

@@ -1,19 +0,0 @@
name: "🌐 Language request"
description: "You want to contribute to a language that isn't on Crowdin yet?"
title: "🌐 Language request: <language name in english>"
labels: [language-request]
body:
- type: input
id: language-name-native
attributes:
label: "🌐 Language name (native)"
placeholder: "Schweizerdeutsch"
validations:
required: true
- type: input
id: language-code
attributes:
label: "🌐 Language code"
placeholder: "de-CH"
validations:
required: true

View File

@@ -1,4 +1,4 @@
name: Build and Push Docker Image
name: Create Docker Image
on:
release:
@@ -10,25 +10,15 @@ 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
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
- 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 .

1
.prettierignore Normal file
View File

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

View File

@@ -1,112 +1,3 @@
## [0.18.2](https://github.com/stonith404/pingvin-share/compare/v0.18.1...v0.18.2) (2023-10-09)
### Bug Fixes
* disable image optimizations for logo to prevent caching issues with custom logos ([3891900](https://github.com/stonith404/pingvin-share/commit/38919003e9091203b507d0f0b061f4a1835ff4f4))
* memory leak while downloading large files ([97e7d71](https://github.com/stonith404/pingvin-share/commit/97e7d7190dfe219caf441dffcd7830c304c3c939))
## [0.18.1](https://github.com/stonith404/pingvin-share/compare/v0.18.0...v0.18.1) (2023-09-22)
### Bug Fixes
* permission changes of docker container brakes existing installations ([6a4108e](https://github.com/stonith404/pingvin-share/commit/6a4108ed6138e7297e66fd1e38450f23afe99aae))
## [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)
### Bug Fixes
* logo doesn't get loaded correctly ([9ba2b4c](https://github.com/stonith404/pingvin-share/commit/9ba2b4c82cdad9097b33f0451771818c7b972a6b))
* share expiration never doesn't work if using another language than English ([a47d080](https://github.com/stonith404/pingvin-share/commit/a47d080657e1d08ef06ec7425d8bdafd5a26c24a))
## [0.17.2](https://github.com/stonith404/pingvin-share/compare/v0.17.1...v0.17.2) (2023-07-31)
### Bug Fixes
* `ECONNREFUSED` with Docker ipv6 enabled ([c9a2a46](https://github.com/stonith404/pingvin-share/commit/c9a2a469c67d3c3cd08179b44e2bf82208f05177))
## [0.17.1](https://github.com/stonith404/pingvin-share/compare/v0.17.0...v0.17.1) (2023-07-30)
### Bug Fixes
* rename pt-PT.ts to pt-BR.ts ([2584bb0](https://github.com/stonith404/pingvin-share/commit/2584bb0d48c761940eafc03d5cd98d47e7a5b0ae))
## [0.17.0](https://github.com/stonith404/pingvin-share/compare/v0.16.1...v0.17.0) (2023-07-23)
### Features
* ability to define zip compression level ([7827b68](https://github.com/stonith404/pingvin-share/commit/7827b687fa022e86a2643e7a1951af8c7e80608c))
* add note to language picker ([7f0c31c](https://github.com/stonith404/pingvin-share/commit/7f0c31c2e09b3ee9aae6c3dfb54fac2f2b1dfe23))
* add share url alias `/s` ([231a2e9](https://github.com/stonith404/pingvin-share/commit/231a2e95b9734cf4704454e1945698753dbb378b))
* localization ([#196](https://github.com/stonith404/pingvin-share/issues/196)) ([b9f6e3b](https://github.com/stonith404/pingvin-share/commit/b9f6e3bd08dcfc050048fba582b35958bc7b6184))
* update default value of `maxSize` from `1073741824` to `1000000000` ([389dc87](https://github.com/stonith404/pingvin-share/commit/389dc87cac775d916d0cff9b71d3c5ff90bfe916))
### Bug Fixes
* confusion between GB and GiB ([5816b39](https://github.com/stonith404/pingvin-share/commit/5816b39fc6ef6fe6b7cf8e7925aa297561f5b796))
* mistakes in English translations ([70b425b](https://github.com/stonith404/pingvin-share/commit/70b425b3807be79a3b518cc478996c71dffcf986))
* wrong layout if button text is too long in modals ([f4c88ae](https://github.com/stonith404/pingvin-share/commit/f4c88aeb0823c2c18535c25fcf8e16afa8b53a56))
### [0.16.1](https://github.com/stonith404/pingvin-share/compare/v0.16.0...v0.16.1) (2023-07-10)
### Features
* Adding reverse share ability to copy the link ([#191](https://github.com/stonith404/pingvin-share/issues/191)) ([7574eb3](https://github.com/stonith404/pingvin-share/commit/7574eb3191f21aadd64f436e9e7c78d3e3973a07)), closes [#178](https://github.com/stonith404/pingvin-share/issues/178) [#181](https://github.com/stonith404/pingvin-share/issues/181)
* Adding reverse shares' shares a clickable link ([#190](https://github.com/stonith404/pingvin-share/issues/190)) ([0276294](https://github.com/stonith404/pingvin-share/commit/0276294f5219a7edcc762bc52391b6720cfd741d))
### Bug Fixes
* set link default value to random ([#192](https://github.com/stonith404/pingvin-share/issues/192)) ([a1ea7c0](https://github.com/stonith404/pingvin-share/commit/a1ea7c026594a54eafd52f764eecbf06e1bb4d4e)), closes [#178](https://github.com/stonith404/pingvin-share/issues/178) [#181](https://github.com/stonith404/pingvin-share/issues/181)
## [0.16.0](https://github.com/stonith404/pingvin-share/compare/v0.15.0...v0.16.0) (2023-07-09)
### Features
* Adding more informations on My Shares page (table and modal) ([#174](https://github.com/stonith404/pingvin-share/issues/174)) ([1466240](https://github.com/stonith404/pingvin-share/commit/14662404614f15bc25384d924d8cb0458ab06cd8))
* Adding the possibility of copying the link by clicking text and icons ([#171](https://github.com/stonith404/pingvin-share/issues/171)) ([348852c](https://github.com/stonith404/pingvin-share/commit/348852cfa4275f5c642669b43697f83c35333044))
## [0.15.0](https://github.com/stonith404/pingvin-share/compare/v0.14.1...v0.15.0) (2023-05-09)

View File

@@ -1,40 +1,37 @@
# Stage 1: Frontend dependencies
FROM node:20-alpine AS frontend-dependencies
# Using node slim because prisma ORM needs libc for ARM builds
# Stage 1: on frontend dependency change
FROM node:19-slim AS frontend-dependencies
WORKDIR /opt/app
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
# Stage 2: Build frontend
FROM node:20-alpine AS frontend-builder
# Stage 2: on frontend change
FROM node:19-slim AS frontend-builder
WORKDIR /opt/app
COPY ./frontend .
COPY --from=frontend-dependencies /opt/app/node_modules ./node_modules
RUN npm run build
# Stage 3: Backend dependencies
FROM node:20-alpine AS backend-dependencies
# Stage 3: on backend dependency change
FROM node:19-slim AS backend-dependencies
WORKDIR /opt/app
COPY backend/package.json backend/package-lock.json ./
RUN npm ci
# Stage 4: Build backend
FROM node:20-alpine AS backend-builder
# Stage 4:on backend change
FROM node:19-slim AS backend-builder
RUN apt-get update && apt-get install -y openssl
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:20-alpine AS runner
FROM node:19-slim AS runner
ENV NODE_ENV=docker
# Alpine specific dependencies
RUN apk update --no-cache
RUN apk upgrade --no-cache
RUN apk add --no-cache curl nginx
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
RUN apt-get update && apt-get install -y curl openssl
WORKDIR /opt/app/frontend
COPY --from=frontend-builder /opt/app/public ./public
@@ -49,12 +46,7 @@ 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
# 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 && nginx && PORT=3333 HOSTNAME=0.0.0.0 node frontend/server.js & cd backend && npm run prod
CMD cp -rn /tmp/img /opt/app/frontend/public && node frontend/server.js & cd backend && npm run prod

View File

@@ -63,8 +63,6 @@ npm run build
pm2 start --name="pingvin-share-frontend" npm -- run start
```
**Uploading Large Files**: By default, Pingvin Share uses a built-in reverse proxy to reduce the installation steps. However, this reverse proxy is not optimized for uploading large files. If you wish to upload larger files, you can either use the Docker installation or set up your own reverse proxy. An example configuration for Nginx can be found in `/nginx/nginx.conf`.
The website is now listening on `http://localhost:3000`, have fun with Pingvin Share 🐧!
### Integrations
@@ -146,15 +144,4 @@ For installation specific configuration, you can use environment variables. The
## 🖤 Contribute
### Translations
You can help to translate Pingvin Share into your language.
On [Crowdin](https://crowdin.com/project/pingvin-share) you can easily translate Pingvin Share online.
Is your language not on Crowdin? Feel free to [Request it](https://github.com/stonith404/pingvin-share/issues/new?assignees=&labels=language-request&projects=&template=language-request.yml&title=%F0%9F%8C%90+Language+request%3A+%3Clanguage+name+in+english%3E).
Any issues while translating? Feel free to participate in the [Localization discussion](https://github.com/stonith404/pingvin-share/discussions/198).
### Project
You're very welcome to contribute to Pingvin Share! Please follow the [contribution guide](/CONTRIBUTING.md) to get started.
You're very welcome to contribute to Pingvin Share! Follow the [contribution guide](/CONTRIBUTING.md) to get started.

View File

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

4695
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,28 @@
{
"name": "pingvin-share-backend",
"version": "0.18.2",
"version": "0.15.0",
"scripts": {
"build": "nest build",
"dev": "cross-env NODE_ENV=development nest start --watch",
"prod": "prisma migrate deploy && prisma db seed && node dist/src/main",
"lint": "eslint 'src/**/*.ts'",
"format": "prettier --end-of-line=auto --write 'src/**/*.ts'",
"format": "prettier --write 'src/**/*.ts'",
"test:system": "prisma migrate reset -f && nest start & wait-on http://localhost:8080/api/configs && newman run ./test/newman-system-tests.json"
},
"prisma": {
"seed": "ts-node prisma/seed/config.seed.ts"
},
"dependencies": {
"@nestjs/common": "^10.1.2",
"@nestjs/config": "^3.0.0",
"@nestjs/core": "^10.1.2",
"@nestjs/jwt": "^10.1.0",
"@nestjs/passport": "^10.0.0",
"@nestjs/platform-express": "^10.1.2",
"@nestjs/schedule": "^3.0.1",
"@nestjs/swagger": "^7.1.4",
"@nestjs/throttler": "^4.2.1",
"@prisma/client": "^5.0.0",
"@nestjs/common": "^9.3.9",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.3.9",
"@nestjs/jwt": "^10.0.2",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.3.9",
"@nestjs/schedule": "^2.2.0",
"@nestjs/swagger": "^6.2.1",
"@nestjs/throttler": "^4.0.0",
"@prisma/client": "^4.11.0",
"archiver": "^5.3.1",
"argon2": "^0.30.3",
"body-parser": "^1.20.2",
@@ -33,48 +33,48 @@
"cookie-parser": "^1.4.6",
"mime-types": "^2.1.35",
"moment": "^2.29.4",
"nodemailer": "^6.9.4",
"nodemailer": "^6.9.1",
"otplib": "^12.0.1",
"passport": "^0.6.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"qrcode-svg": "^1.1.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^5.0.1",
"rxjs": "^7.8.1",
"sharp": "^0.32.4",
"rimraf": "^4.4.0",
"rxjs": "^7.8.0",
"sharp": "^0.31.3",
"ts-node": "^10.9.1"
},
"devDependencies": {
"@nestjs/cli": "^10.1.10",
"@nestjs/schematics": "^10.0.1",
"@nestjs/testing": "^10.1.2",
"@types/archiver": "^5.3.2",
"@nestjs/cli": "^9.2.0",
"@nestjs/schematics": "^9.0.4",
"@nestjs/testing": "^9.3.9",
"@types/archiver": "^5.3.1",
"@types/clamscan": "^2.0.4",
"@types/cookie-parser": "^1.4.3",
"@types/cron": "^2.0.1",
"@types/cron": "^2.0.0",
"@types/express": "^4.17.17",
"@types/mime-types": "^2.1.1",
"@types/multer": "^1.4.7",
"@types/node": "^20.4.5",
"@types/nodemailer": "^6.4.9",
"@types/passport-jwt": "^3.0.9",
"@types/node": "^18.15.0",
"@types/nodemailer": "^6.4.7",
"@types/passport-jwt": "^3.0.8",
"@types/qrcode-svg": "^1.1.1",
"@types/sharp": "^0.31.1",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^6.2.0",
"@typescript-eslint/parser": "^6.2.0",
"@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.1",
"cross-env": "^7.0.3",
"eslint": "^8.46.0",
"eslint-config-prettier": "^8.9.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.7.0",
"eslint-plugin-prettier": "^4.2.1",
"newman": "^5.3.2",
"prettier": "^3.0.0",
"prisma": "^5.0.0",
"prettier": "^2.8.4",
"prisma": "^4.11.0",
"source-map-support": "^0.5.21",
"ts-loader": "^9.4.4",
"tsconfig-paths": "4.2.0",
"typescript": "^5.1.6",
"ts-loader": "^9.4.2",
"tsconfig-paths": "4.1.2",
"typescript": "^4.9.5",
"wait-on": "^7.0.1"
}
}

View File

@@ -1,27 +0,0 @@
/*
Warnings:
- You are about to drop the column `description` on the `Config` table. All the data in the column will be lost.
*/
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Config" (
"updatedAt" DATETIME NOT NULL,
"name" TEXT NOT NULL,
"category" TEXT NOT NULL,
"type" TEXT NOT NULL,
"defaultValue" TEXT NOT NULL DEFAULT '',
"value" TEXT,
"obscured" BOOLEAN NOT NULL DEFAULT false,
"secret" BOOLEAN NOT NULL DEFAULT true,
"locked" BOOLEAN NOT NULL DEFAULT false,
"order" INTEGER NOT NULL,
PRIMARY KEY ("name", "category")
);
INSERT INTO "new_Config" ("category", "defaultValue", "locked", "name", "obscured", "order", "secret", "type", "updatedAt", "value") SELECT "category", "defaultValue", "locked", "name", "obscured", "order", "secret", "type", "updatedAt", "value" FROM "Config";
DROP TABLE "Config";
ALTER TABLE "new_Config" RENAME TO "Config";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -136,6 +136,7 @@ model Config {
type String
defaultValue String @default("")
value String?
description String
obscured Boolean @default(false)
secret Boolean @default(true)
locked Boolean @default(false)

View File

@@ -1,9 +1,9 @@
import { Prisma, PrismaClient } from "@prisma/client";
import * as crypto from "crypto";
const configVariables: ConfigVariables = {
internal: {
jwtSecret: {
description: "Long random string used to sign JWT tokens",
type: "string",
defaultValue: crypto.randomBytes(256).toString("base64"),
locked: true,
@@ -11,16 +11,20 @@ const configVariables: ConfigVariables = {
},
general: {
appName: {
description: "Name of the application",
type: "string",
defaultValue: "Pingvin Share",
secret: false,
},
appUrl: {
description: "On which URL Pingvin Share is available",
type: "string",
defaultValue: "http://localhost:3000",
secret: false,
},
showHomePage: {
description: "Whether to show the home page",
type: "boolean",
defaultValue: "true",
secret: false,
@@ -28,64 +32,84 @@ const configVariables: ConfigVariables = {
},
share: {
allowRegistration: {
description: "Whether registration is allowed",
type: "boolean",
defaultValue: "true",
secret: false,
},
allowUnauthenticatedShares: {
description: "Whether unauthorized users can create shares",
type: "boolean",
defaultValue: "false",
secret: false,
},
maxSize: {
description: "Maximum share size in bytes",
type: "number",
defaultValue: "1000000000",
defaultValue: "1073741824",
secret: false,
},
zipCompressionLevel: {
type: "number",
defaultValue: "9",
},
},
email: {
enableShareEmailRecipients: {
description:
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
type: "boolean",
defaultValue: "false",
secret: false,
},
shareRecipientsSubject: {
description:
"Subject of the email which gets sent to the share recipients.",
type: "string",
defaultValue: "Files shared with you",
},
shareRecipientsMessage: {
description:
"Message which gets sent to the share recipients.\n\nAvailable 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\nVariables will be replaced with the actual values.",
type: "text",
defaultValue:
"Hey!\n\n{creator} shared some files with you, view or download the files with this link: {shareUrl}\n\nThe share will expire {expires}.\n\nNote: {desc}\n\nShared securely with Pingvin Share 🐧",
},
reverseShareSubject: {
description:
"Subject of the email which gets sent when someone created a share with your reverse share link.",
type: "string",
defaultValue: "Reverse share link used",
},
reverseShareMessage: {
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.",
type: "text",
defaultValue:
"Hey!\n\nA share was just created with your reverse share link: {shareUrl}\n\nShared securely with Pingvin Share 🐧",
},
resetPasswordSubject: {
description:
"Subject of the email which gets sent when a user requests a password reset.",
type: "string",
defaultValue: "Pingvin Share password reset",
},
resetPasswordMessage: {
description:
"Message which gets sent when a user requests a password reset. {url} will be replaced with the reset password URL.",
type: "text",
defaultValue:
"Hey!\n\nYou requested a password reset. Click this link to reset your password: {url}\nThe link expires in a hour.\n\nPingvin Share 🐧",
},
inviteSubject: {
description:
"Subject of the email which gets sent when an admin invites an user.",
type: "string",
defaultValue: "Pingvin Share invite",
},
inviteMessage: {
description:
"Message which gets sent when an admin invites an user. {url} will be replaced with the invite URL and {password} with the password.",
type: "text",
defaultValue:
"Hey!\n\nYou were invited to Pingvin Share. Click this link to accept the invite: {url}\n\nYour password is: {password}\n\nPingvin Share 🐧",
@@ -93,27 +117,34 @@ const configVariables: ConfigVariables = {
},
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.",
type: "boolean",
defaultValue: "false",
secret: false,
},
host: {
description: "Host of the SMTP server",
type: "string",
defaultValue: "",
},
port: {
description: "Port of the SMTP server",
type: "number",
defaultValue: "0",
},
email: {
description: "Email address which the emails get sent from",
type: "string",
defaultValue: "",
},
username: {
description: "Username of the SMTP server",
type: "string",
defaultValue: "",
},
password: {
description: "Password of the SMTP server",
type: "string",
defaultValue: "",
obscured: true,

View File

@@ -1,19 +0,0 @@
import { Controller, Get, Res } from "@nestjs/common";
import { Response } from "express";
import { PrismaService } from "./prisma/prisma.service";
@Controller("/")
export class AppController {
constructor(private prismaService: PrismaService) {}
@Get("health")
async health(@Res({ passthrough: true }) res: Response) {
try {
await this.prismaService.config.findMany();
return "OK";
} catch {
res.statusCode = 500;
return "ERROR";
}
}
}

View File

@@ -14,7 +14,6 @@ import { ShareModule } from "./share/share.module";
import { UserModule } from "./user/user.module";
import { ClamScanModule } from "./clamscan/clamscan.module";
import { ReverseShareModule } from "./reverseShare/reverseShare.module";
import { AppController } from "./app.controller";
@Module({
imports: [
@@ -34,9 +33,6 @@ import { AppController } from "./app.controller";
ClamScanModule,
ReverseShareModule,
],
controllers:[
AppController,
],
providers: [
{
provide: APP_GUARD,

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,10 +8,7 @@ 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

@@ -14,6 +14,9 @@ export class AdminConfigDTO extends ConfigDTO {
@Expose()
updatedAt: Date;
@Expose()
description: string;
@Expose()
obscured: boolean;
@@ -25,7 +28,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

@@ -7,8 +7,7 @@ const IMAGES_PATH = "../frontend/public/img";
@Injectable()
export class LogoService {
async create(file: Buffer) {
const resized = await sharp(file).resize(900).toBuffer();
fs.writeFileSync(`${IMAGES_PATH}/logo.png`, resized, "binary");
fs.writeFileSync(`${IMAGES_PATH}/logo.png`, file, "binary");
this.createFavicon(file);
this.createPWAIcons(file);
}

View File

@@ -1,9 +1,5 @@
export const DATA_DIRECTORY = process.env.DATA_DIRECTORY || "./data";
export const SHARE_DIRECTORY = `${DATA_DIRECTORY}/uploads/shares`;
export const DATABASE_URL =
process.env.DATABASE_URL ||
"file:../data/pingvin-share.db?connection_limit=1";
export const CLAMAV_HOST =
process.env.CLAMAV_HOST ||
(process.env.NODE_ENV == "docker" ? "clamav" : "127.0.0.1");
export const CLAMAV_PORT = parseInt(process.env.CLAMAV_PORT) || 3310;
export const SHARE_DIRECTORY = `${DATA_DIRECTORY}/uploads/shares`
export const DATABASE_URL = process.env.DATABASE_URL || "file:../data/pingvin-share.db?connection_limit=1";
export const CLAMAV_HOST = process.env.CLAMAV_HOST || (process.env.NODE_ENV == "docker" ? "clamav" : "127.0.0.1");
export const CLAMAV_PORT = parseInt(process.env.CLAMAV_PORT) || 3310;

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,12 +49,12 @@ 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");
const shareUrl = `${this.config.get("general.appUrl")}/s/${shareId}`;
const shareUrl = `${this.config.get("general.appUrl")}/share/${shareId}`;
await this.sendMail(
recipientEmail,
@@ -69,13 +69,13 @@ export class EmailService {
"{expires}",
moment(expiration).unix() != 0
? moment(expiration).fromNow()
: "in: never",
),
: "in: never"
)
);
}
async sendMailToReverseShareCreator(recipientEmail: string, shareId: string) {
const shareUrl = `${this.config.get("general.appUrl")}/s/${shareId}`;
const shareUrl = `${this.config.get("general.appUrl")}/share/${shareId}`;
await this.sendMail(
recipientEmail,
@@ -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

@@ -10,9 +10,6 @@ export class ReverseShareDTO {
@Expose()
shareExpiration: Date;
@Expose()
token: string;
from(partial: Partial<ReverseShareDTO>) {
return plainToClass(ReverseShareDTO, partial, {
excludeExtraneousValues: true,

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

@@ -1,7 +1,7 @@
import { Expose, plainToClass, Type } from "class-transformer";
import { ShareDTO } from "./share.dto";
import { FileDTO } from "../../file/dto/file.dto";
import { OmitType } from "@nestjs/swagger";
import {FileDTO} from "../../file/dto/file.dto";
import {OmitType} from "@nestjs/swagger";
export class MyShareDTO extends OmitType(ShareDTO, [
"files",
@@ -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 {
@@ -104,7 +104,7 @@ export class ShareService {
const files = await this.prisma.file.findMany({ where: { shareId } });
const archive = archiver("zip", {
zlib: { level: this.config.get("share.zipCompressionLevel") },
zlib: { level: 9 },
});
const writeStream = fs.createWriteStream(`${path}/archive.zip`);
@@ -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`
);
}
}

View File

@@ -1,4 +0,0 @@
files:
- source: /frontend/src/i18n/translations/en-US.ts
translation: /%original_path%/%locale%.ts
pull_request_title: "chore(translations): update translations via Crowdin"

View File

@@ -1,17 +1,11 @@
{
"extends": [
"next",
"eslint-config-next",
"eslint:recommended",
"prettier"
],
"extends": ["eslint-config-next", "eslint:recommended", "prettier"],
"plugins": ["react"],
"rules": {
"quotes": ["warn", "double", { "allowTemplateLiterals": true }],
"react-hooks/exhaustive-deps": ["off"],
"import/no-anonymous-default-export": ["off"],
"no-unused-vars": ["warn"],
"react/no-unescaped-entities": ["off"],
"@next/next/no-img-element": ["off"]
"react/no-unescaped-entities": ["off"]
}
}

View File

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

View File

@@ -1,4 +1,5 @@
/** @type {import('next').NextConfig} */
const { version } = require('./package.json');
const withPWA = require("next-pwa")({

File diff suppressed because it is too large Load Diff

View File

@@ -1,53 +1,52 @@
{
"name": "pingvin-share-frontend",
"version": "0.18.2",
"version": "0.15.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --end-of-line=auto --write \"src/**/*.ts*\""
"format": "prettier --write \"src/**/*.ts*\""
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@mantine/core": "^6.0.17",
"@mantine/dropzone": "^6.0.17",
"@mantine/form": "^6.0.17",
"@mantine/hooks": "^6.0.17",
"@mantine/modals": "^6.0.17",
"@mantine/next": "^6.0.17",
"@mantine/notifications": "^6.0.17",
"axios": "^1.4.0",
"cookies-next": "^2.1.2",
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",
"@mantine/core": "^6.0.1",
"@mantine/dropzone": "^6.0.1",
"@mantine/form": "^6.0.1",
"@mantine/hooks": "^6.0.1",
"@mantine/modals": "^6.0.1",
"@mantine/next": "^6.0.1",
"@mantine/notifications": "^6.0.1",
"axios": "^1.3.4",
"cookies-next": "^2.1.1",
"file-saver": "^2.0.5",
"jose": "^4.14.4",
"jose": "^4.13.1",
"jwt-decode": "^3.1.2",
"mime-types": "^2.1.35",
"moment": "^2.29.4",
"next": "^13.4.12",
"next": "^13.2.4",
"next-cookies": "^2.0.3",
"next-http-proxy-middleware": "^1.2.5",
"next-pwa": "^5.6.0",
"p-limit": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.10.1",
"react-intl": "^6.4.4",
"sharp": "^0.32.4",
"yup": "^1.2.0"
"react-icons": "^4.8.0",
"sharp": "^0.31.3",
"yup": "^1.0.2"
},
"devDependencies": {
"@types/mime-types": "^2.1.1",
"@types/node": "20.4.5",
"@types/react": "18.2.17",
"@types/react-dom": "18.2.7",
"axios": "^1.4.0",
"eslint": "8.46.0",
"eslint-config-next": "^13.4.12",
"eslint-config-prettier": "^8.9.0",
"prettier": "^3.0.0",
"tar": "^6.1.15",
"typescript": "^5.1.6"
"@types/node": "18.15.0",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"axios": "^1.3.4",
"eslint": "8.35.0",
"eslint-config-next": "^13.2.4",
"eslint-config-prettier": "^8.7.0",
"prettier": "^2.8.4",
"tar": "^6.1.13",
"typescript": "^4.9.5"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,4 +1,6 @@
import Image from "next/image";
const Logo = ({ height, width }: { height: number; width: number }) => {
return <img src="/img/logo.png" alt="logo" height={height} width={width} />;
return <Image src="/img/logo.png" alt="logo" height={height} width={width} />;
};
export default Logo;

View File

@@ -1,36 +0,0 @@
import { Select } from "@mantine/core";
import { getCookie, setCookie } from "cookies-next";
import { useState } from "react";
import useTranslate from "../../hooks/useTranslate.hook";
import { LOCALES } from "../../i18n/locales";
const LanguagePicker = () => {
const t = useTranslate();
const [selectedLanguage, setSelectedLanguage] = useState(
getCookie("language")?.toString(),
);
const languages = Object.values(LOCALES).map((locale) => ({
value: locale.code,
label: locale.name,
}));
return (
<Select
value={selectedLanguage}
description={t("account.card.language.description")}
onChange={(value) => {
setSelectedLanguage(value ?? "en");
setCookie("language", value, {
sameSite: "lax",
expires: new Date(
new Date().setFullYear(new Date().getFullYear() + 1),
),
});
location.reload();
}}
data={languages}
/>
);
};
export default LanguagePicker;

View File

@@ -9,12 +9,12 @@ import {
import { useColorScheme } from "@mantine/hooks";
import { useState } from "react";
import { TbDeviceLaptop, TbMoon, TbSun } from "react-icons/tb";
import { FormattedMessage } from "react-intl";
import userPreferences from "../../utils/userPreferences.util";
import usePreferences from "../../hooks/usePreferences";
const ThemeSwitcher = () => {
const preferences = usePreferences();
const [colorScheme, setColorScheme] = useState(
userPreferences.get("colorScheme"),
preferences.get("colorScheme")
);
const { toggleColorScheme } = useMantineColorScheme();
const systemColorScheme = useColorScheme();
@@ -23,10 +23,10 @@ const ThemeSwitcher = () => {
<SegmentedControl
value={colorScheme}
onChange={(value) => {
userPreferences.set("colorScheme", value);
preferences.set("colorScheme", value);
setColorScheme(value);
toggleColorScheme(
value == "system" ? systemColorScheme : (value as ColorScheme),
value == "system" ? systemColorScheme : (value as ColorScheme)
);
}}
data={[
@@ -34,9 +34,7 @@ const ThemeSwitcher = () => {
label: (
<Center>
<TbMoon size={16} />
<Box ml={10}>
<FormattedMessage id="account.theme.dark" />
</Box>
<Box ml={10}>Dark</Box>
</Center>
),
value: "dark",
@@ -45,9 +43,7 @@ const ThemeSwitcher = () => {
label: (
<Center>
<TbSun size={16} />
<Box ml={10}>
<FormattedMessage id="account.theme.light" />
</Box>
<Box ml={10}>Light</Box>
</Center>
),
value: "light",
@@ -56,9 +52,7 @@ const ThemeSwitcher = () => {
label: (
<Center>
<TbDeviceLaptop size={16} />
<Box ml={10}>
<FormattedMessage id="account.theme.system" />
</Box>
<Box ml={10}>System</Box>
</Center>
),
value: "system",

View File

@@ -1,7 +1,8 @@
import {
Button,
Center,
Group,
Col,
Grid,
Image,
Stack,
Text,
@@ -11,11 +12,7 @@ import {
import { useForm, yupResolver } from "@mantine/form";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { FormattedMessage } from "react-intl";
import * as yup from "yup";
import useTranslate, {
translateOutsideContext,
} from "../../hooks/useTranslate.hook";
import authService from "../../services/auth.service";
import toast from "../../utils/toast.util";
@@ -26,11 +23,10 @@ const showEnableTotpModal = (
qrCode: string;
secret: string;
password: string;
},
}
) => {
const t = translateOutsideContext();
return modals.openModal({
title: t("account.modal.totp.title"),
title: "Enable TOTP",
children: (
<CreateEnableTotpModal options={options} refreshUser={refreshUser} />
),
@@ -49,7 +45,6 @@ const CreateEnableTotpModal = ({
refreshUser: () => {};
}) => {
const modals = useModals();
const t = useTranslate();
const validationSchema = yup.object().shape({
code: yup
@@ -71,19 +66,14 @@ const CreateEnableTotpModal = ({
<div>
<Center>
<Stack>
<Text>
<FormattedMessage id="account.modal.totp.step1" />
</Text>
<Text>Step 1: Add your authenticator</Text>
<Image src={options.qrCode} alt="QR Code" />
<Center>
<span>
{" "}
<FormattedMessage id="common.text.or" />
</span>
<span>OR</span>
</Center>
<Tooltip label={t("account.modal.totp.clickToCopy")}>
<Tooltip label="Click to copy">
<Button
onClick={() => {
navigator.clipboard.writeText(options.secret);
@@ -94,42 +84,38 @@ const CreateEnableTotpModal = ({
</Button>
</Tooltip>
<Center>
<Text fz="xs"></Text>
<Text fz="xs">Enter manually</Text>
</Center>
<Text>
<FormattedMessage id="account.modal.totp.step2" />
</Text>
<Text>Step 2: Validate your code</Text>
<form
onSubmit={form.onSubmit((values) => {
authService
.verifyTOTP(values.code, options.password)
.then(() => {
toast.success(t("account.notify.totp.enable"));
toast.success("Successfully enabled TOTP");
modals.closeAll();
refreshUser();
})
.catch(toast.axiosError);
})}
>
<Group align="end">
<TextInput
style={{ flex: "1" }}
variant="filled"
label={t("account.modal.totp.code")}
placeholder="******"
{...form.getInputProps("code")}
/>
<Button
style={{ flex: "0 0 auto" }}
variant="outline"
type="submit"
>
<FormattedMessage id="account.modal.totp.verify" />
</Button>
</Group>
<Grid align="flex-end">
<Col xs={9}>
<TextInput
variant="filled"
label="Code"
placeholder="******"
{...form.getInputProps("code")}
/>
</Col>
<Col xs={3}>
<Button variant="outline" type="submit">
Verify
</Button>
</Col>
</Grid>
</form>
</Stack>
</Center>

View File

@@ -1,22 +0,0 @@
import { Stack, TextInput } from "@mantine/core";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { translateOutsideContext } from "../../hooks/useTranslate.hook";
const showReverseShareLinkModal = (
modals: ModalsContextProps,
reverseShareToken: string,
appUrl: string,
) => {
const t = translateOutsideContext();
const link = `${appUrl}/upload/${reverseShareToken}`;
return modals.openModal({
title: t("account.reverseShares.modal.reverse-share-link"),
children: (
<Stack align="stretch">
<TextInput variant="filled" value={link} />
</Stack>
),
});
};
export default showReverseShareLinkModal;

View File

@@ -1,21 +1,18 @@
import { Divider, Flex, Progress, Stack, Text } from "@mantine/core";
import { Text, Divider, Progress, Stack, Group, Flex } from "@mantine/core";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import moment from "moment";
import { FormattedMessage } from "react-intl";
import { translateOutsideContext } from "../../hooks/useTranslate.hook";
import { FileMetaData } from "../../types/File.type";
import { MyShare } from "../../types/share.type";
import moment from "moment";
import { byteToHumanSizeString } from "../../utils/fileSize.util";
import CopyTextField from "../upload/CopyTextField";
import { FileMetaData } from "../../types/File.type";
const showShareInformationsModal = (
modals: ModalsContextProps,
share: MyShare,
appUrl: string,
maxShareSize: number,
maxShareSize: number
) => {
const t = translateOutsideContext();
const link = `${appUrl}/s/${share.id}`;
const link = `${appUrl}/share/${share.id}`;
let shareSize: number = 0;
for (let file of share.files as FileMetaData[])
@@ -32,45 +29,34 @@ const showShareInformationsModal = (
: moment(share.expiration).format("LLL");
return modals.openModal({
title: t("account.shares.modal.share-informations"),
title: "Share informations",
children: (
<Stack align="stretch" spacing="md">
<Text size="sm" color="lightgray">
<b>
<FormattedMessage id="account.shares.table.id" />:{" "}
</b>
{share.id}
<b>ID:</b> {share.id}
</Text>
<Text size="sm" color="lightgray">
<b>
<FormattedMessage id="account.shares.table.description" />:{" "}
</b>
{share.description || "No description"}
<b>Description:</b> {share.description || "No description"}
</Text>
<Text size="sm" color="lightgray">
<b>
<FormattedMessage id="account.shares.table.createdAt" />:{" "}
</b>
{formattedCreatedAt}
<b>Created at:</b> {formattedCreatedAt}
</Text>
<Text size="sm" color="lightgray">
<b>
<FormattedMessage id="account.shares.table.expiresAt" />:{" "}
</b>
{formattedExpiration}
<b>Expires at:</b> {formattedExpiration}
</Text>
<Divider />
<CopyTextField link={link} />
<Divider />
<Text size="sm" color="lightgray">
<b>
<FormattedMessage id="account.shares.table.size" />:{" "}
</b>
{formattedShareSize} / {formattedMaxShareSize} (
<b>Size:</b> {formattedShareSize} / {formattedMaxShareSize} (
{shareSizeProgress.toFixed(1)}%)
</Text>

View File

@@ -1,16 +1,14 @@
import { Stack, TextInput } from "@mantine/core";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { translateOutsideContext } from "../../hooks/useTranslate.hook";
const showShareLinkModal = (
modals: ModalsContextProps,
shareId: string,
appUrl: string,
appUrl: string
) => {
const t = translateOutsideContext();
const link = `${appUrl}/s/${shareId}`;
const link = `${appUrl}/share/${shareId}`;
return modals.openModal({
title: t("account.shares.modal.share-link"),
title: "Share link",
children: (
<Stack align="stretch">
<TextInput variant="filled" value={link} />

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

@@ -9,7 +9,6 @@ import {
} from "@mantine/core";
import Link from "next/link";
import { Dispatch, SetStateAction } from "react";
import { FormattedMessage } from "react-intl";
import useConfig from "../../../hooks/config.hook";
import Logo from "../../Logo";
@@ -43,7 +42,7 @@ const ConfigurationHeader = ({
</Link>
<MediaQuery smallerThan="sm" styles={{ display: "none" }}>
<Button variant="light" component={Link} href="/admin">
<FormattedMessage id="common.button.go-back" />
Go back
</Button>
</MediaQuery>
</Group>

View File

@@ -12,7 +12,6 @@ import {
import Link from "next/link";
import { Dispatch, SetStateAction } from "react";
import { TbAt, TbMail, TbShare, TbSquare } from "react-icons/tb";
import { FormattedMessage } from "react-intl";
const categories = [
{ name: "General", icon: <TbSquare /> },
@@ -54,7 +53,7 @@ const ConfigurationNavBar = ({
>
<Navbar.Section>
<Text size="xs" color="dimmed" mb="sm">
<FormattedMessage id="admin.config.title" />
Configuration
</Text>
<Stack spacing="xs">
{categories.map((category) => (
@@ -80,11 +79,7 @@ const ConfigurationNavBar = ({
>
{category.icon}
</ThemeIcon>
<Text size="sm">
<FormattedMessage
id={`admin.config.category.${category.name.toLowerCase()}`}
/>
</Text>
<Text size="sm">{category.name}</Text>
</Group>
</Box>
))}
@@ -92,7 +87,7 @@ const ConfigurationNavBar = ({
</Navbar.Section>
<MediaQuery largerThan="sm" styles={{ display: "none" }}>
<Button mt="xl" variant="light" component={Link} href="/admin">
<FormattedMessage id="common.button.go-back" />
Go back
</Button>
</MediaQuery>
</Navbar>

View File

@@ -2,8 +2,6 @@ import { Box, FileInput, Group, Stack, Text, Title } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import { Dispatch, SetStateAction } from "react";
import { TbUpload } from "react-icons/tb";
import { FormattedMessage } from "react-intl";
import useTranslate from "../../../hooks/useTranslate.hook";
const LogoConfigInput = ({
logo,
@@ -13,16 +11,14 @@ const LogoConfigInput = ({
setLogo: Dispatch<SetStateAction<File | null>>;
}) => {
const isMobile = useMediaQuery("(max-width: 560px)");
const t = useTranslate();
return (
<Group position="apart">
<Stack style={{ maxWidth: isMobile ? "100%" : "40%" }} spacing={0}>
<Title order={6}>
<FormattedMessage id="admin.config.general.logo" />
</Title>
<Title order={6}>Logo</Title>
<Text color="dimmed" size="sm" mb="xs">
<FormattedMessage id="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.
</Text>
</Stack>
<Stack></Stack>
@@ -33,7 +29,7 @@ const LogoConfigInput = ({
value={logo}
onChange={(v) => setLogo(v)}
accept=".png"
placeholder={t("admin.config.general.logo.placeholder")}
placeholder="Pick image"
/>
</Box>
</Group>

View File

@@ -1,7 +1,6 @@
import { Button, Stack, Text, Textarea } from "@mantine/core";
import { useModals } from "@mantine/modals";
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import useUser from "../../../hooks/user.hook";
import configService from "../../../services/config.service";
import toast from "../../../utils/toast.util";
@@ -33,7 +32,7 @@ const TestEmailButton = ({
<Textarea minRows={4} readOnly value={e.response.data.message} />
</Stack>
),
}),
})
);
};
@@ -66,7 +65,7 @@ const TestEmailButton = ({
}
}}
>
<FormattedMessage id="admin.config.smtp.button.test" />
Send test email
</Button>
);
};

View File

@@ -3,7 +3,6 @@ import { useModals } from "@mantine/modals";
import { TbCheck, TbEdit, TbTrash } from "react-icons/tb";
import User from "../../../types/user.type";
import showUpdateUserModal from "./showUpdateUserModal";
import { FormattedMessage, useIntl } from "react-intl";
const ManageUserTable = ({
users,
@@ -23,15 +22,9 @@ const ManageUserTable = ({
<Table verticalSpacing="sm">
<thead>
<tr>
<th>
<FormattedMessage id="admin.users.table.username" />
</th>
<th>
<FormattedMessage id="admin.users.table.email" />
</th>
<th>
<FormattedMessage id="admin.users.table.admin" />
</th>
<th>Username</th>
<th>Email</th>
<th>Admin</th>
<th></th>
</tr>
</thead>

View File

@@ -8,16 +8,14 @@ import {
} from "@mantine/core";
import { useForm, yupResolver } from "@mantine/form";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { FormattedMessage } from "react-intl";
import * as yup from "yup";
import useTranslate from "../../../hooks/useTranslate.hook";
import userService from "../../../services/user.service";
import toast from "../../../utils/toast.util";
const showCreateUserModal = (
modals: ModalsContextProps,
smtpEnabled: boolean,
getUsers: () => void,
getUsers: () => void
) => {
return modals.openModal({
title: "Create user",
@@ -36,7 +34,6 @@ const Body = ({
smtpEnabled: boolean;
getUsers: () => void;
}) => {
const t = useTranslate();
const form = useForm({
initialValues: {
username: "",
@@ -47,15 +44,10 @@ const Body = ({
},
validate: yupResolver(
yup.object().shape({
email: yup.string().email(t("common.error.invalid-email")),
username: yup
.string()
.min(3, t("common.error.too-short", { length: 3 })),
password: yup
.string()
.min(8, t("common.error.too-short", { length: 8 }))
.optional(),
}),
email: yup.string().email(),
username: yup.string().min(3),
password: yup.string().min(8).optional(),
})
),
});
@@ -73,22 +65,14 @@ const Body = ({
})}
>
<Stack>
<TextInput
label={t("admin.users.modal.create.username")}
{...form.getInputProps("username")}
/>
<TextInput
label={t("admin.users.modal.create.email")}
{...form.getInputProps("email")}
/>
<TextInput label="Username" {...form.getInputProps("username")} />
<TextInput label="Email" {...form.getInputProps("email")} />
{smtpEnabled && (
<Switch
mt="xs"
labelPosition="left"
label={t("admin.users.modal.create.manual-password")}
description={t(
"admin.users.modal.create.manual-password.description",
)}
label="Set password manually"
description="If not checked, the user will receive an email with a link to set their password."
{...form.getInputProps("setPasswordManually", {
type: "checkbox",
})}
@@ -96,7 +80,7 @@ const Body = ({
)}
{(form.values.setPasswordManually || !smtpEnabled) && (
<PasswordInput
label={t("admin.users.modal.create.password")}
label="Password"
{...form.getInputProps("password")}
/>
)}
@@ -109,14 +93,12 @@ const Body = ({
}}
mt="xs"
labelPosition="left"
label={t("admin.users.modal.create.admin")}
description={t("admin.users.modal.create.admin.description")}
label="Admin privileges"
description="If checked, the user will be able to access the admin panel."
{...form.getInputProps("isAdmin", { type: "checkbox" })}
/>
<Group position="right">
<Button type="submit">
<FormattedMessage id="common.button.create" />
</Button>
<Button type="submit">Create</Button>
</Group>
</Stack>
</form>

View File

@@ -9,11 +9,7 @@ import {
} from "@mantine/core";
import { useForm, yupResolver } from "@mantine/form";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { FormattedMessage } from "react-intl";
import * as yup from "yup";
import useTranslate, {
translateOutsideContext,
} from "../../../hooks/useTranslate.hook";
import userService from "../../../services/user.service";
import User from "../../../types/user.type";
import toast from "../../../utils/toast.util";
@@ -21,11 +17,10 @@ import toast from "../../../utils/toast.util";
const showUpdateUserModal = (
modals: ModalsContextProps,
user: User,
getUsers: () => void,
getUsers: () => void
) => {
const t = translateOutsideContext();
return modals.openModal({
title: t("admin.users.edit.update.title", { username: user.username }),
title: `Update ${user.username}`,
children: <Body user={user} modals={modals} getUsers={getUsers} />,
});
};
@@ -39,8 +34,6 @@ const Body = ({
user: User;
getUsers: () => void;
}) => {
const t = useTranslate();
const accountForm = useForm({
initialValues: {
username: user.username,
@@ -49,11 +42,9 @@ const Body = ({
},
validate: yupResolver(
yup.object().shape({
email: yup.string().email(t("common.error.invalid-email")),
username: yup
.string()
.min(3, t("common.error.too-short", { length: 3 })),
}),
email: yup.string().email(),
username: yup.string().min(3),
})
),
});
@@ -63,10 +54,8 @@ const Body = ({
},
validate: yupResolver(
yup.object().shape({
password: yup
.string()
.min(8, t("common.error.too-short", { length: 8 })),
}),
password: yup.string().min(8),
})
),
});
@@ -86,26 +75,21 @@ const Body = ({
>
<Stack>
<TextInput
label={t("admin.users.table.username")}
label="Username"
{...accountForm.getInputProps("username")}
/>
<TextInput
label={t("admin.users.table.email")}
{...accountForm.getInputProps("email")}
/>
<TextInput label="Email" {...accountForm.getInputProps("email")} />
<Switch
mt="xs"
labelPosition="left"
label={t("admin.users.edit.update.admin-privileges")}
label="Admin privileges"
{...accountForm.getInputProps("isAdmin", { type: "checkbox" })}
/>
</Stack>
</form>
<Accordion>
<Accordion.Item sx={{ borderBottom: "none" }} value="changePassword">
<Accordion.Control px={0}>
<FormattedMessage id="admin.users.edit.update.change-password.title" />
</Accordion.Control>
<Accordion.Control px={0}>Change password</Accordion.Control>
<Accordion.Panel>
<form
onSubmit={passwordForm.onSubmit(async (values) => {
@@ -113,21 +97,17 @@ const Body = ({
.update(user.id, {
password: values.password,
})
.then(() =>
toast.success(
t("admin.users.edit.update.notify.password.success"),
),
)
.then(() => toast.success("Password changed successfully"))
.catch(toast.axiosError);
})}
>
<Stack>
<PasswordInput
label={t("admin.users.edit.update.change-password.field")}
label="New password"
{...passwordForm.getInputProps("password")}
/>
<Button variant="light" type="submit">
<FormattedMessage id="admin.users.edit.update.change-password.button" />
Save new password
</Button>
</Stack>
</form>
@@ -136,7 +116,7 @@ const Body = ({
</Accordion>
<Group position="right">
<Button type="submit" form="accountForm">
<FormattedMessage id="common.button.save" />
Save
</Button>
</Group>
</Stack>

View File

@@ -15,10 +15,8 @@ import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
import { TbInfoCircle } from "react-icons/tb";
import { FormattedMessage } from "react-intl";
import * as yup from "yup";
import useConfig from "../../hooks/config.hook";
import useTranslate from "../../hooks/useTranslate.hook";
import useUser from "../../hooks/user.hook";
import authService from "../../services/auth.service";
import toast from "../../utils/toast.util";
@@ -26,18 +24,14 @@ import toast from "../../utils/toast.util";
const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
const config = useConfig();
const router = useRouter();
const t = useTranslate();
const { refreshUser } = useUser();
const [showTotp, setShowTotp] = React.useState(false);
const [loginToken, setLoginToken] = React.useState("");
const validationSchema = yup.object().shape({
emailOrUsername: yup.string().required(t("common.error.field-required")),
password: yup
.string()
.min(8, t("common.error.too-short", { length: 8 }))
.required(t("common.error.field-required")),
emailOrUsername: yup.string().required(),
password: yup.string().min(8).required(),
});
const form = useForm({
@@ -60,8 +54,8 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
icon: <TbInfoCircle />,
color: "blue",
radius: "md",
title: t("signIn.notify.totp-required.title"),
message: t("signIn.notify.totp-required.description"),
title: "Two-factor authentication required",
message: "Please enter your two-factor authentication code",
});
setLoginToken(response.data["loginToken"]);
} else {
@@ -94,13 +88,13 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
return (
<Container size={420} my={40}>
<Title order={2} align="center" weight={900}>
<FormattedMessage id="signin.title" />
Welcome back
</Title>
{config.get("share.allowRegistration") && (
<Text color="dimmed" size="sm" align="center" mt={5}>
<FormattedMessage id="signin.description" />{" "}
You don't have an account yet?{" "}
<Anchor component={Link} href={"signUp"} size="sm">
<FormattedMessage id="signin.button.signup" />
{"Sign up"}
</Anchor>
</Text>
)}
@@ -113,20 +107,20 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
})}
>
<TextInput
label={t("signin.input.email-or-username")}
placeholder={t("signin.input.email-or-username.placeholder")}
label="Email or username"
placeholder="Your email or username"
{...form.getInputProps("emailOrUsername")}
/>
<PasswordInput
label={t("signin.input.password")}
placeholder={t("signin.input.password.placeholder")}
label="Password"
placeholder="Your password"
mt="md"
{...form.getInputProps("password")}
/>
{showTotp && (
<TextInput
variant="filled"
label={t("account.modal.totp.code")}
label="Code"
placeholder="******"
mt="md"
{...form.getInputProps("totp")}
@@ -135,12 +129,12 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
{config.get("smtp.enabled") && (
<Group position="right" mt="xs">
<Anchor component={Link} href="/auth/resetPassword" size="xs">
<FormattedMessage id="resetPassword.title" />
Forgot password?
</Anchor>
</Group>
)}
<Button fullWidth mt="xl" type="submit">
<FormattedMessage id="signin.button.submit" />
Sign in
</Button>
</form>
</Paper>

View File

@@ -11,10 +11,8 @@ import {
import { useForm, yupResolver } from "@mantine/form";
import Link from "next/link";
import { useRouter } from "next/router";
import { FormattedMessage } from "react-intl";
import * as yup from "yup";
import useConfig from "../../hooks/config.hook";
import useTranslate from "../../hooks/useTranslate.hook";
import useUser from "../../hooks/user.hook";
import authService from "../../services/auth.service";
import toast from "../../utils/toast.util";
@@ -22,19 +20,12 @@ import toast from "../../utils/toast.util";
const SignUpForm = () => {
const config = useConfig();
const router = useRouter();
const t = useTranslate();
const { refreshUser } = useUser();
const validationSchema = yup.object().shape({
email: yup.string().email(t("common.error.invalid-email")).required(),
username: yup
.string()
.min(3, t("common.error.too-short", { length: 3 }))
.required(t("common.error.field-required")),
password: yup
.string()
.min(8, t("common.error.too-short", { length: 8 }))
.required(t("common.error.field-required")),
email: yup.string().email().required(),
username: yup.string().min(3).required(),
password: yup.string().min(8).required(),
});
const form = useForm({
@@ -63,41 +54,41 @@ const SignUpForm = () => {
return (
<Container size={420} my={40}>
<Title order={2} align="center" weight={900}>
<FormattedMessage id="signup.title" />
Sign up
</Title>
{config.get("share.allowRegistration") && (
<Text color="dimmed" size="sm" align="center" mt={5}>
<FormattedMessage id="signup.description" />{" "}
You have an account already?{" "}
<Anchor component={Link} href={"signIn"} size="sm">
<FormattedMessage id="signup.button.signin" />
Sign in
</Anchor>
</Text>
)}
<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
label={t("signup.input.username")}
placeholder={t("signup.input.username.placeholder")}
label="Username"
placeholder="Your username"
{...form.getInputProps("username")}
/>
<TextInput
label={t("signup.input.email")}
placeholder={t("signup.input.email.placeholder")}
label="Email"
placeholder="Your email"
mt="md"
{...form.getInputProps("email")}
/>
<PasswordInput
label={t("signin.input.password")}
placeholder={t("signin.input.password.placeholder")}
label="Password"
placeholder="Your password"
mt="md"
{...form.getInputProps("password")}
/>
<Button fullWidth mt="xl" type="submit">
<FormattedMessage id="signup.button.submit" />
Let's get started
</Button>
</form>
</Paper>

View File

@@ -3,7 +3,6 @@ import Link from "next/link";
import { TbDoorExit, TbSettings, TbUser } from "react-icons/tb";
import useUser from "../../hooks/user.hook";
import authService from "../../services/auth.service";
import { FormattedMessage, useIntl } from "react-intl";
const ActionAvatar = () => {
const { user } = useUser();
@@ -17,7 +16,7 @@ const ActionAvatar = () => {
</Menu.Target>
<Menu.Dropdown>
<Menu.Item component={Link} href="/account" icon={<TbUser size={14} />}>
<FormattedMessage id="navbar.avatar.account" />
My account
</Menu.Item>
{user!.isAdmin && (
<Menu.Item
@@ -25,7 +24,7 @@ const ActionAvatar = () => {
href="/admin"
icon={<TbSettings size={14} />}
>
<FormattedMessage id="navbar.avatar.admin" />
Administration
</Menu.Item>
)}
@@ -35,7 +34,7 @@ const ActionAvatar = () => {
}}
icon={<TbDoorExit size={14} />}
>
<FormattedMessage id="navbar.avatar.signout" />
Sign out
</Menu.Item>
</Menu.Dropdown>
</Menu>

View File

@@ -16,7 +16,6 @@ import { useRouter } from "next/router";
import { ReactNode, useEffect, useState } from "react";
import useConfig from "../../hooks/config.hook";
import useUser from "../../hooks/user.hook";
import useTranslate from "../../hooks/useTranslate.hook";
import Logo from "../Logo";
import ActionAvatar from "./ActionAvatar";
import NavbarShareMenu from "./NavbarShareMenu";
@@ -113,7 +112,6 @@ const Header = () => {
const { user } = useUser();
const router = useRouter();
const config = useConfig();
const t = useTranslate();
const [opened, toggleOpened] = useDisclosure(false);
@@ -126,7 +124,7 @@ const Header = () => {
const authenticatedLinks: NavLink[] = [
{
link: "/upload",
label: t("navbar.upload"),
label: "Upload",
},
{
component: <NavbarShareMenu />,
@@ -139,27 +137,27 @@ const Header = () => {
let unauthenticatedLinks: NavLink[] = [
{
link: "/auth/signIn",
label: t("navbar.signin"),
label: "Sign in",
},
];
if (config.get("share.allowUnauthenticatedShares")) {
unauthenticatedLinks.unshift({
link: "/upload",
label: t("navbar.upload"),
label: "Upload",
});
}
if (config.get("general.showHomePage"))
unauthenticatedLinks.unshift({
link: "/",
label: t("navbar.home"),
label: "Home",
});
if (config.get("share.allowRegistration"))
unauthenticatedLinks.push({
link: "/auth/signUp",
label: t("navbar.signup"),
label: "Sign up",
});
const { classes, cx } = useStyles();

View File

@@ -1,7 +1,6 @@
import { ActionIcon, Menu } from "@mantine/core";
import Link from "next/link";
import { TbArrowLoopLeft, TbLink } from "react-icons/tb";
import { FormattedMessage } from "react-intl";
const NavbarShareMneu = () => {
return (
@@ -13,14 +12,14 @@ const NavbarShareMneu = () => {
</Menu.Target>
<Menu.Dropdown>
<Menu.Item component={Link} href="/account/shares" icon={<TbLink />}>
<FormattedMessage id="navbar.links.shares" />
My shares
</Menu.Item>
<Menu.Item
component={Link}
href="/account/reverseShares"
icon={<TbArrowLoopLeft />}
>
<FormattedMessage id="navbar.links.reverse" />
Reverse shares
</Menu.Item>
</Menu.Dropdown>
</Menu>

View File

@@ -1,15 +1,11 @@
import { Button } from "@mantine/core";
import { useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";
import useTranslate from "../../hooks/useTranslate.hook";
import shareService from "../../services/share.service";
import toast from "../../utils/toast.util";
const DownloadAllButton = ({ shareId }: { shareId: string }) => {
const [isZipReady, setIsZipReady] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const t = useTranslate();
const downloadAll = async () => {
setIsLoading(true);
await shareService
@@ -43,13 +39,13 @@ const DownloadAllButton = ({ shareId }: { shareId: string }) => {
loading={isLoading}
onClick={() => {
if (!isZipReady) {
toast.error(t("share.notify.download-all-preparing"));
toast.error("The share is preparing. Try again in a few minutes.");
} else {
downloadAll();
}
}}
>
<FormattedMessage id="share.button.download-all" />
Download all
</Button>
);
};

View File

@@ -19,8 +19,6 @@ import { byteToHumanSizeString } from "../../utils/fileSize.util";
import toast from "../../utils/toast.util";
import TableSortIcon, { TableSort } from "../core/SortIcon";
import showFilePreviewModal from "./modals/showFilePreviewModal";
import useTranslate from "../../hooks/useTranslate.hook";
import { FormattedMessage } from "react-intl";
const FileList = ({
files,
@@ -36,7 +34,6 @@ const FileList = ({
const clipboard = useClipboard();
const config = useConfig();
const modals = useModals();
const t = useTranslate();
const [sort, setSort] = useState<TableSort>({
property: undefined,
@@ -71,10 +68,10 @@ const FileList = ({
if (window.isSecureContext) {
clipboard.copy(link);
toast.success(t("common.notify.copied"));
toast.success("Your file link was copied to the keyboard.");
} else {
modals.openModal({
title: t("share.modal.file-link"),
title: "File link",
children: (
<Stack align="stretch">
<TextInput variant="filled" value={link} />
@@ -93,13 +90,13 @@ const FileList = ({
<tr>
<th>
<Group spacing="xs">
<FormattedMessage id="share.table.name" />
Name
<TableSortIcon sort={sort} setSort={setSort} property="name" />
</Group>
</th>
<th>
<Group spacing="xs">
<FormattedMessage id="share.table.size" />
Size
<TableSortIcon sort={sort} setSort={setSort} property="size" />
</Group>
</th>

View File

@@ -2,7 +2,6 @@ import { Button, Center, Stack, Text, Title } from "@mantine/core";
import { modals } from "@mantine/modals";
import Link from "next/link";
import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";
import api from "../../services/api.service";
const FilePreviewContext = React.createContext<{
@@ -44,7 +43,6 @@ const FilePreview = ({
href={`/api/shares/${shareId}/files/${fileId}?download=false`}
>
View original file
{/* Add translation? */}
</Button>
</Stack>
);
@@ -146,11 +144,10 @@ const UnSupportedFile = () => {
return (
<Center style={{ minHeight: 200 }}>
<Stack align="center" spacing={10}>
<Title order={3}>
<FormattedMessage id="share.modal.file-preview.error.not-supported.title" />
</Title>
<Title order={3}>Preview not supported</Title>
<Text>
<FormattedMessage id="share.modal.file-preview.error.not-supported.description" />
A preview for thise file type is unsupported. Please download the file
to view it.
</Text>
</Stack>
</Center>

View File

@@ -1,21 +1,18 @@
import { Button, Stack } from "@mantine/core";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { FormattedMessage } from "react-intl";
import { translateOutsideContext } from "../../../hooks/useTranslate.hook";
import CopyTextField from "../../upload/CopyTextField";
const showCompletedReverseShareModal = (
modals: ModalsContextProps,
link: string,
getReverseShares: () => void,
getReverseShares: () => void
) => {
const t = translateOutsideContext();
return modals.openModal({
closeOnClickOutside: false,
withCloseButton: false,
closeOnEscape: false,
title: t("account.reverseShares.modal.reverse-share-link"),
title: "Reverse share link",
children: <Body link={link} getReverseShares={getReverseShares} />,
});
};
@@ -39,7 +36,7 @@ const Body = ({
getReverseShares();
}}
>
<FormattedMessage id="common.button.done" />
Done
</Button>
</Stack>
);

View File

@@ -12,10 +12,6 @@ import {
import { useForm } from "@mantine/form";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { FormattedMessage } from "react-intl";
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";
@@ -25,11 +21,10 @@ import showCompletedReverseShareModal from "./showCompletedReverseShareModal";
const showCreateReverseShareModal = (
modals: ModalsContextProps,
showSendEmailNotificationOption: boolean,
getReverseShares: () => void,
getReverseShares: () => void
) => {
const t = translateOutsideContext();
return modals.openModal({
title: t("account.reverseShares.modal.title"),
title: "Create reverse share",
children: (
<Body
showSendEmailNotificationOption={showSendEmailNotificationOption}
@@ -47,7 +42,6 @@ const Body = ({
showSendEmailNotificationOption: boolean;
}) => {
const modals = useModals();
const t = useTranslate();
const form = useForm({
initialValues: {
@@ -67,7 +61,7 @@ const Body = ({
values.expiration_num + values.expiration_unit,
values.maxShareSize,
values.maxUseCount,
values.sendEmailNotification,
values.sendEmailNotification
)
.then(({ link }) => {
modals.closeAll();
@@ -85,7 +79,7 @@ const Body = ({
max={99999}
precision={0}
variant="filled"
label={t("account.reverseShares.modal.expiration.label")}
label="Share expiration"
{...form.getInputProps("expiration_num")}
/>
</Col>
@@ -97,44 +91,27 @@ const Body = ({
{
value: "-minutes",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.minute-singular")
: t("upload.modal.expires.minute-plural"),
"Minute" + (form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-hours",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.hour-singular")
: t("upload.modal.expires.hour-plural"),
"Hour" + (form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-days",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.day-singular")
: t("upload.modal.expires.day-plural"),
"Day" + (form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-weeks",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.week-singular")
: t("upload.modal.expires.week-plural"),
"Week" + (form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-months",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.month-singular")
: t("upload.modal.expires.month-plural"),
},
{
value: "-years",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.year-singular")
: t("upload.modal.expires.year-plural"),
"Month" + (form.values.expiration_num == 1 ? "" : "s"),
},
]}
/>
@@ -148,17 +125,11 @@ const Body = ({
color: theme.colors.gray[6],
})}
>
{getExpirationPreview(
{
expiresOn: t("account.reverseShare.expires-on"),
neverExpires: t("account.reverseShare.never-expires"),
},
form,
)}
{getExpirationPreview("reverse share", form)}
</Text>
</div>
<FileSizeInput
label={t("account.reverseShares.modal.max-size.label")}
label="Max share size"
value={form.values.maxShareSize}
onChange={(number) => form.setFieldValue("maxShareSize", number)}
/>
@@ -167,18 +138,16 @@ const Body = ({
max={1000}
precision={0}
variant="filled"
label={t("account.reverseShares.modal.max-use.label")}
description={t("account.reverseShares.modal.max-use.description")}
label="Max use count"
description="The maximum number of times this reverse share link can be used"
{...form.getInputProps("maxUseCount")}
/>
{showSendEmailNotificationOption && (
<Switch
mt="xs"
labelPosition="left"
label={t("account.reverseShares.modal.send-email")}
description={t(
"account.reverseShares.modal.send-email.description",
)}
label="Send email notification"
description="Send an email notification when a share is created with this reverse share link"
{...form.getInputProps("sendEmailNotification", {
type: "checkbox",
})}
@@ -186,7 +155,7 @@ const Body = ({
)}
<Button mt="md" type="submit">
<FormattedMessage id="common.button.create" />
Create
</Button>
</Stack>
</form>

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

@@ -1,21 +1,16 @@
import { Button, PasswordInput, Stack, Text } from "@mantine/core";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import useTranslate, {
translateOutsideContext,
} from "../../hooks/useTranslate.hook";
const showEnterPasswordModal = (
modals: ModalsContextProps,
submitCallback: (password: string) => Promise<void>,
submitCallback: (password: string) => Promise<void>
) => {
const t = translateOutsideContext();
return modals.openModal({
closeOnClickOutside: false,
withCloseButton: false,
closeOnEscape: false,
title: t("share.modal.password.title"),
title: "Password required",
children: <Body submitCallback={submitCallback} />,
});
};
@@ -27,11 +22,10 @@ const Body = ({
}) => {
const [password, setPassword] = useState("");
const [passwordWrong, setPasswordWrong] = useState(false);
const t = useTranslate();
return (
<Stack align="stretch">
<Text size="sm">
<FormattedMessage id="share.modal.password.description" />
This access this share please enter the password for the share.
</Text>
<form
@@ -43,15 +37,13 @@ const Body = ({
<Stack>
<PasswordInput
variant="filled"
placeholder={t("share.modal.password")}
error={passwordWrong && t("share.modal.error.invalid-password")}
placeholder="Password"
error={passwordWrong && "Wrong password"}
onFocus={() => setPasswordWrong(false)}
onChange={(e) => setPassword(e.target.value)}
value={password}
/>
<Button type="submit">
<FormattedMessage id="common.button.submit" />
</Button>
<Button type="submit">Submit</Button>
</Stack>
</form>
</Stack>

View File

@@ -2,12 +2,11 @@ import { Button, Stack, Text } from "@mantine/core";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { useRouter } from "next/router";
import { FormattedMessage } from "react-intl";
const showErrorModal = (
modals: ModalsContextProps,
title: string,
text: string,
text: string
) => {
return modals.openModal({
closeOnClickOutside: false,
@@ -32,7 +31,7 @@ const Body = ({ text }: { text: string }) => {
router.back();
}}
>
<FormattedMessage id="common.button.go-back" />
Go back
</Button>
</Stack>
</>

View File

@@ -1,23 +1,20 @@
import { ActionIcon, TextInput } from "@mantine/core";
import { useClipboard } from "@mantine/hooks";
import { useRef, useState } from "react";
import { TbCheck, TbCopy } from "react-icons/tb";
import useTranslate from "../../hooks/useTranslate.hook";
import toast from "../../utils/toast.util";
import { ActionIcon, TextInput } from "@mantine/core";
import { TbCheck, TbCopy } from "react-icons/tb";
import { useClipboard } from "@mantine/hooks";
function CopyTextField(props: { link: string }) {
const clipboard = useClipboard({ timeout: 500 });
const t = useTranslate();
const [checkState, setCheckState] = useState(false);
const [textClicked, setTextClicked] = useState(false);
const timerRef = useRef<number | ReturnType<typeof setTimeout> | undefined>(
undefined,
undefined
);
const copyLink = () => {
clipboard.copy(props.link);
toast.success(t("common.notify.copied"));
toast.success("Your link was copied to the keyboard.");
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
setCheckState(false);
@@ -28,7 +25,7 @@ function CopyTextField(props: { link: string }) {
return (
<TextInput
readOnly
label={t("common.text.link")}
label="Link"
variant="filled"
value={props.link}
onClick={() => {

View File

@@ -1,9 +1,8 @@
import { Button, Center, createStyles, Group, Text } from "@mantine/core";
import { Dropzone as MantineDropzone } from "@mantine/dropzone";
import { ForwardedRef, useRef } from "react";
import { Dispatch, ForwardedRef, SetStateAction, useRef } from "react";
import { TbCloudUpload, TbUpload } from "react-icons/tb";
import { FormattedMessage } from "react-intl";
import useTranslate from "../../hooks/useTranslate.hook";
import useConfig from "../../hooks/config.hook";
import { FileUpload } from "../../types/File.type";
import { byteToHumanSizeString } from "../../utils/fileSize.util";
import toast from "../../utils/toast.util";
@@ -35,13 +34,15 @@ const useStyles = createStyles((theme) => ({
const Dropzone = ({
isUploading,
maxShareSize,
showCreateUploadModalCallback,
files,
setFiles,
}: {
isUploading: boolean;
maxShareSize: number;
showCreateUploadModalCallback: (files: FileUpload[]) => void;
files: FileUpload[];
setFiles: Dispatch<SetStateAction<FileUpload[]>>;
}) => {
const t = useTranslate();
const config = useConfig();
const { classes } = useStyles();
const openRef = useRef<() => void>();
@@ -53,21 +54,24 @@ const Dropzone = ({
}}
disabled={isUploading}
openRef={openRef as ForwardedRef<() => void>}
onDrop={(files: FileUpload[]) => {
const fileSizeSum = files.reduce((n, { size }) => n + size, 0);
onDrop={(newFiles: FileUpload[]) => {
const fileSizeSum = [...newFiles, ...files].reduce(
(n, { size }) => n + size,
0
);
if (fileSizeSum > maxShareSize) {
toast.error(
t("upload.dropzone.notify.file-too-big", {
maxSize: byteToHumanSizeString(maxShareSize),
})
`Your files exceed the maximum share size of ${byteToHumanSizeString(
maxShareSize
)}.`
);
} else {
files = files.map((newFile) => {
newFiles = newFiles.map((newFile) => {
newFile.uploadingProgress = 0;
return newFile;
});
showCreateUploadModalCallback(files);
setFiles([...newFiles, ...files]);
}
}}
className={classes.dropzone}
@@ -78,13 +82,12 @@ const Dropzone = ({
<TbCloudUpload size={50} />
</Group>
<Text align="center" weight={700} size="lg" mt="xl">
<FormattedMessage id="upload.dropzone.title" />
Upload files
</Text>
<Text align="center" size="sm" mt="xs" color="dimmed">
<FormattedMessage
id="upload.dropzone.description"
values={{ maxSize: byteToHumanSizeString(maxShareSize) }}
/>
Drag&apos;n&apos;drop files here to start your share. We can accept
only files that are less than {byteToHumanSizeString(maxShareSize)}{" "}
in total.
</Text>
</div>
</MantineDropzone>

View File

@@ -4,7 +4,6 @@ import { TbTrash } from "react-icons/tb";
import { FileUpload } from "../../types/File.type";
import { byteToHumanSizeString } from "../../utils/fileSize.util";
import UploadProgressIndicator from "./UploadProgressIndicator";
import { FormattedMessage } from "react-intl";
const FileList = ({
files,
@@ -42,12 +41,8 @@ const FileList = ({
<Table>
<thead>
<tr>
<th>
<FormattedMessage id="upload.filelist.name" />
</th>
<th>
<FormattedMessage id="upload.filelist.size" />
</th>
<th>Name</th>
<th>Size</th>
<th></th>
</tr>
</thead>

View File

@@ -3,24 +3,19 @@ import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import moment from "moment";
import { useRouter } from "next/router";
import { FormattedMessage } from "react-intl";
import useTranslate, {
translateOutsideContext,
} from "../../../hooks/useTranslate.hook";
import { Share } from "../../../types/share.type";
import CopyTextField from "../CopyTextField";
const showCompletedUploadModal = (
modals: ModalsContextProps,
share: Share,
appUrl: string,
appUrl: string
) => {
const t = translateOutsideContext();
return modals.openModal({
closeOnClickOutside: false,
withCloseButton: false,
closeOnEscape: false,
title: t("upload.modal.completed.share-ready"),
title: "Share ready",
children: <Body share={share} appUrl={appUrl} />,
});
};
@@ -28,9 +23,8 @@ const showCompletedUploadModal = (
const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
const modals = useModals();
const router = useRouter();
const t = useTranslate();
const link = `${appUrl}/s/${share.id}`;
const link = `${appUrl}/share/${share.id}`;
return (
<Stack align="stretch">
@@ -43,10 +37,10 @@ const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
>
{/* If our share.expiration is timestamp 0, show a different message */}
{moment(share.expiration).unix() === 0
? t("upload.modal.completed.never-expires")
: t("upload.modal.completed.expires-on", {
expiration: moment(share.expiration).format("LLL"),
})}
? "This share will never expire."
: `This share will expire on ${moment(share.expiration).format(
"LLL"
)}`}
</Text>
<Button
@@ -55,7 +49,7 @@ const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
router.push("/upload");
}}
>
<FormattedMessage id="common.button.done" />
Done
</Button>
</Stack>
);

View File

@@ -5,7 +5,6 @@ import {
Checkbox,
Col,
Grid,
Group,
MultiSelect,
NumberInput,
PasswordInput,
@@ -14,19 +13,15 @@ import {
Text,
Textarea,
TextInput,
Title,
} from "@mantine/core";
import { useForm, yupResolver } from "@mantine/form";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import { useState } from "react";
import { TbAlertCircle } from "react-icons/tb";
import { FormattedMessage } from "react-intl";
import * as yup from "yup";
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";
@@ -39,17 +34,13 @@ const showCreateUploadModal = (
allowUnauthenticatedShares: boolean;
enableEmailRecepients: boolean;
},
files: FileUpload[],
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void
uploadCallback: (createShare: CreateShare) => void
) => {
const t = translateOutsideContext();
return modals.openModal({
title: t("upload.modal.title"),
title: "Share",
children: (
<CreateUploadModalBody
options={options}
files={files}
uploadCallback={uploadCallback}
/>
),
@@ -58,11 +49,9 @@ const showCreateUploadModal = (
const CreateUploadModalBody = ({
uploadCallback,
files,
options,
}: {
files: FileUpload[];
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void;
uploadCallback: (createShare: CreateShare) => void;
options: {
isUserSignedIn: boolean;
isReverseShare: boolean;
@@ -72,29 +61,24 @@ const CreateUploadModalBody = ({
};
}) => {
const modals = useModals();
const t = useTranslate();
const generatedLink = Buffer.from(Math.random().toString(), "utf8")
.toString("base64")
.substr(10, 7);
const [showNotSignedInAlert, setShowNotSignedInAlert] = useState(true);
const validationSchema = yup.object().shape({
link: yup
.string()
.required(t("common.error.field-required"))
.min(3, t("common.error.too-short", { length: 3 }))
.max(50, t("common.error.too-long", { length: 50 }))
.required()
.min(3)
.max(50)
.matches(new RegExp("^[a-zA-Z0-9_-]*$"), {
message: t("upload.modal.link.error.invalid"),
message: "Can only contain letters, numbers, underscores and hyphens",
}),
password: yup.string().min(3).max(30),
maxViews: yup.number().min(1),
});
const form = useForm({
initialValues: {
link: generatedLink,
link: "",
recipients: [] as string[],
password: undefined,
maxViews: undefined,
@@ -112,61 +96,61 @@ const CreateUploadModalBody = ({
withCloseButton
onClose={() => setShowNotSignedInAlert(false)}
icon={<TbAlertCircle size={16} />}
title={t("upload.modal.not-signed-in")}
title="You're not signed in"
color="yellow"
>
<FormattedMessage id="upload.modal.not-signed-in-description" />
You will be unable to delete your share manually and view the visitor
count.
</Alert>
)}
<form
onSubmit={form.onSubmit(async (values) => {
if (!(await shareService.isShareIdAvailable(values.link))) {
form.setFieldError("link", t("upload.modal.link.error.taken"));
form.setFieldError("link", "This link is already in use");
} else {
const expiration = form.values.never_expires
? "never"
: form.values.expiration_num + form.values.expiration_unit;
uploadCallback(
{
id: values.link,
expiration: expiration,
recipients: values.recipients,
description: values.description,
security: {
password: values.password,
maxViews: values.maxViews,
},
uploadCallback({
id: values.link,
expiration: expiration,
recipients: values.recipients,
description: values.description,
security: {
password: values.password,
maxViews: values.maxViews,
},
files
);
});
modals.closeAll();
}
})}
>
<Stack align="stretch">
<Group align="end">
<TextInput
style={{ flex: "1" }}
variant="filled"
label={t("upload.modal.link.label")}
placeholder="myAwesomeShare"
{...form.getInputProps("link")}
/>
<Button
style={{ flex: "0 0 auto" }}
variant="outline"
onClick={() =>
form.setFieldValue(
"link",
Buffer.from(Math.random().toString(), "utf8")
.toString("base64")
.substr(10, 7)
)
}
>
<FormattedMessage id="common.button.generate" />
</Button>
</Group>
<Grid align={form.errors.link ? "center" : "flex-end"}>
<Col xs={9}>
<TextInput
variant="filled"
label="Link"
placeholder="myAwesomeShare"
{...form.getInputProps("link")}
/>
</Col>
<Col xs={3}>
<Button
variant="outline"
onClick={() =>
form.setFieldValue(
"link",
Buffer.from(Math.random().toString(), "utf8")
.toString("base64")
.substr(10, 7)
)
}
>
Generate
</Button>
</Col>
</Grid>
<Text
italic
@@ -175,7 +159,8 @@ const CreateUploadModalBody = ({
color: theme.colors.gray[6],
})}
>
{`${options.appUrl}/s/${form.values.link}`}
{options.appUrl}/share/
{form.values.link == "" ? "myAwesomeShare" : form.values.link}
</Text>
{!options.isReverseShare && (
<>
@@ -186,7 +171,8 @@ const CreateUploadModalBody = ({
max={99999}
precision={0}
variant="filled"
label={t("upload.modal.expires.label")}
label="Expiration"
placeholder="n"
disabled={form.values.never_expires}
{...form.getInputProps("expiration_num")}
/>
@@ -200,51 +186,41 @@ const CreateUploadModalBody = ({
{
value: "-minutes",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.minute-singular")
: t("upload.modal.expires.minute-plural"),
"Minute" +
(form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-hours",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.hour-singular")
: t("upload.modal.expires.hour-plural"),
"Hour" + (form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-days",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.day-singular")
: t("upload.modal.expires.day-plural"),
"Day" + (form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-weeks",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.week-singular")
: t("upload.modal.expires.week-plural"),
"Week" + (form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-months",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.month-singular")
: t("upload.modal.expires.month-plural"),
"Month" +
(form.values.expiration_num == 1 ? "" : "s"),
},
{
value: "-years",
label:
form.values.expiration_num == 1
? t("upload.modal.expires.year-singular")
: t("upload.modal.expires.year-plural"),
"Year" + (form.values.expiration_num == 1 ? "" : "s"),
},
]}
/>
</Col>
</Grid>
<Checkbox
label={t("upload.modal.expires.never-long")}
label="Never Expires"
{...form.getInputProps("never_expires")}
/>
<Text
@@ -254,28 +230,18 @@ const CreateUploadModalBody = ({
color: theme.colors.gray[6],
})}
>
{getExpirationPreview(
{
neverExpires: t("upload.modal.completed.never-expires"),
expiresOn: t("upload.modal.completed.expires-on"),
},
form
)}
{getExpirationPreview("share", form)}
</Text>
</>
)}
<Accordion>
<Accordion.Item value="description" sx={{ borderBottom: "none" }}>
<Accordion.Control>
<FormattedMessage id="upload.modal.accordion.description.title" />
</Accordion.Control>
<Accordion.Control>Description</Accordion.Control>
<Accordion.Panel>
<Stack align="stretch">
<Textarea
variant="filled"
placeholder={t(
"upload.modal.accordion.description.placeholder"
)}
placeholder="Note for the recepients"
{...form.getInputProps("description")}
/>
</Stack>
@@ -283,22 +249,20 @@ const CreateUploadModalBody = ({
</Accordion.Item>
{options.enableEmailRecepients && (
<Accordion.Item value="recipients" sx={{ borderBottom: "none" }}>
<Accordion.Control>
<FormattedMessage id="upload.modal.accordion.email.title" />
</Accordion.Control>
<Accordion.Control>Email recipients</Accordion.Control>
<Accordion.Panel>
<MultiSelect
data={form.values.recipients}
placeholder={t("upload.modal.accordion.email.placeholder")}
placeholder="Enter email recipients"
searchable
{...form.getInputProps("recipients")}
creatable
autoComplete="email-recipients"
getCreateLabel={(query) => `+ ${query}`}
onCreate={(query) => {
if (!query.match(/^\S+@\S+\.\S+$/)) {
form.setFieldError(
"recipients",
t("upload.modal.accordion.email.invalid-email")
"Invalid email address"
);
} else {
form.setFieldError("recipients", null);
@@ -309,44 +273,34 @@ const CreateUploadModalBody = ({
return query;
}
}}
{...form.getInputProps("recipients")}
/>
</Accordion.Panel>
</Accordion.Item>
)}
<Accordion.Item value="security" sx={{ borderBottom: "none" }}>
<Accordion.Control>
<FormattedMessage id="upload.modal.accordion.security.title" />
</Accordion.Control>
<Accordion.Control>Security options</Accordion.Control>
<Accordion.Panel>
<Stack align="stretch">
<PasswordInput
variant="filled"
placeholder={t(
"upload.modal.accordion.security.password.placeholder"
)}
label={t("upload.modal.accordion.security.password.label")}
autoComplete="off"
placeholder="No password"
label="Password protection"
{...form.getInputProps("password")}
/>
<NumberInput
min={1}
type="number"
variant="filled"
placeholder={t(
"upload.modal.accordion.security.max-views.placeholder"
)}
label={t("upload.modal.accordion.security.max-views.label")}
placeholder="No limit"
label="Maximal views"
{...form.getInputProps("maxViews")}
/>
</Stack>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
<Button type="submit" data-autofocus>
<FormattedMessage id="common.button.share" />
</Button>
<Button type="submit">Share</Button>
</Stack>
</form>
</>

View File

@@ -3,10 +3,6 @@ const defaultPreferences = [
key: "colorScheme",
value: "system",
},
{
key: "locale",
value: "system",
},
];
const get = (key: string) => {
@@ -27,9 +23,8 @@ const set = (key: string, value: string) => {
localStorage.setItem("preferences", JSON.stringify(preferences));
}
};
const userPreferences = {
get,
set,
const usePreferences = () => {
return { get, set };
};
export default userPreferences;
export default usePreferences;

View File

@@ -1,39 +0,0 @@
import { getCookie } from "cookies-next";
import { createIntl, createIntlCache, useIntl } from "react-intl";
import i18nUtil from "../utils/i18n.util";
const useTranslate = () => {
const intl = useIntl();
return (
id: string,
values?: Parameters<typeof intl.formatMessage>[1],
opts?: Parameters<typeof intl.formatMessage>[2],
) => {
return intl.formatMessage({ id }, values, opts) as string;
};
};
const cache = createIntlCache();
export const translateOutsideContext = () => {
const locale =
getCookie("language")?.toString() ?? navigator.language.split("-")[0];
const intl = createIntl(
{
locale,
messages: i18nUtil.getLocaleByCode(locale)?.messages,
defaultLocale: "en",
},
cache,
);
return (
id: string,
values?: Parameters<typeof intl.formatMessage>[1],
opts?: Parameters<typeof intl.formatMessage>[2],
) => {
return intl.formatMessage({ id }, values, opts) as string;
};
};
export default useTranslate;

View File

@@ -1,81 +0,0 @@
import danish from "./translations/da-DK";
import german from "./translations/de-DE";
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 japanese from "./translations/ja-JP";
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 = {
ENGLISH: {
name: "English",
code: "en-US",
messages: english,
},
GERMAN: {
name: "Deutsch",
code: "de-DE",
messages: german,
},
FRENCH: {
name: "Français",
code: "fr-FR",
messages: french,
},
PORTUGUESE_BRAZIL: {
name: "Português (Brasil)",
code: "pt-BR",
messages: portuguese,
},
DANISH: {
name: "Dansk",
code: "da-DK",
messages: danish,
},
SPANISH: {
name: "Español",
code: "es-ES",
messages: spanish,
},
CHINESE_SIMPLIFIED: {
name: "简体中文",
code: "zh-CN",
messages: chineseSimplified,
},
FINNISH: {
name: "Suomi",
code: "fi-FI",
messages: finnish,
},
RUSSIAN: {
name: "Русский",
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,
},
JAPANESE: {
name: "日本語",
code: "ja-JP",
messages: japanese,
},
};

View File

@@ -1,324 +0,0 @@
export default {
// Navbar
"navbar.upload": "Upload",
"navbar.signin": "Log ind",
"navbar.home": "Hjem",
"navbar.signup": "Opret bruger",
"navbar.links.shares": "Mine delte filer",
"navbar.links.reverse": "Omvendt deling",
"navbar.avatar.account": "Min bruger",
"navbar.avatar.admin": "Administration",
"navbar.avatar.signout": "Log ud",
// END navbar
// /
"home.title": "En <h>self-hosted</h> fildelingsplatform.",
"home.description": "Er du sikker på, at du vil overlade dine personlige filer til tredjeparter som WeTransfer?",
"home.bullet.a.name": "Self-Hosted",
"home.bullet.a.description": "Host Pingvin Share på din egen maskine.",
"home.bullet.b.name": "Privatliv",
"home.bullet.b.description": "Dine filer er dine filer og bør ikke komme i hænderne på tredjeparter.",
"home.bullet.c.name": "Ingen irriterende grænse for filstørrelse",
"home.bullet.c.description": "Upload så store filer, som du vil. Kun din harddisk sætter grænsen.",
"home.button.start": "Kom i gang",
"home.button.source": "Source code",
// END /
// /auth/signin
"signin.title": "Velkommen tilbage",
"signin.description": "Har du ikke en bruger endnu?",
"signin.button.signup": "Opret bruger",
"signin.input.email-or-username": "E-mail eller brugernavn",
"signin.input.email-or-username.placeholder": "Din e-mail eller dit brugernavn",
"signin.input.password": "Adgangskode",
"signin.input.password.placeholder": "Din adgangskode",
"signin.button.submit": "Log ind",
"signIn.notify.totp-required.title": "2-faktor login påkrævet",
"signIn.notify.totp-required.description": "Indtast den aktuelle engangskode fra din 2-faktor Authenticator",
// END /auth/signin
// /auth/signup
"signup.title": "Opret en bruger",
"signup.description": "Har du allerede en bruger?",
"signup.button.signin": "Log ind",
"signup.input.username": "Brugernavn",
"signup.input.username.placeholder": "Dit brugernavn",
"signup.input.email": "E-mail",
"signup.input.email.placeholder": "Din e-mail",
"signup.button.submit": "Lad os komme i gang",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Glemt din adgangskode?",
"resetPassword.description": "Indtast din e-mail for at nulstille din adgangskode.",
"resetPassword.notify.success": "En e-mail er blevet sendt med et link til at nulstille din adgangskode.",
"resetPassword.button.back": "Tilbage til login",
"resetPassword.text.resetPassword": "Nulstil adgangskode",
"resetPassword.text.enterNewPassword": "Indtast din nye adgangskode",
"resetPassword.input.password": "Ny adgangskode",
"resetPassword.notify.passwordReset": "Adgangskoden er blevet nulstillet.",
// /account
"account.title": "Min bruger",
"account.card.info.title": "Brugerinfo",
"account.card.info.username": "Brugernavn",
"account.card.info.email": "E-mail",
"account.notify.info.success": "Brugeren blev opdateret med succes",
"account.card.password.title": "Adgangskode",
"account.card.password.old": "Gammel adgangskode",
"account.card.password.new": "Ny adgangskode",
"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": "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",
"account.modal.totp.step2": "Trin 2: Valider din kode",
"account.modal.totp.enterManually": "Indtast manuelt",
"account.modal.totp.code": "Kode",
"account.modal.totp.clickToCopy": "Klik for at kopiere",
"account.modal.totp.verify": "Bekræft",
"account.notify.totp.disable": "2-faktor blev deaktiveret",
"account.notify.totp.enable": "2-faktor blev deaktiveret",
"account.card.language.title": "Sprog",
"account.card.language.description": "The project is translated by the community. Some languages might be incomplete.",
"account.card.color.title": "Farveskema",
// ThemeSwitcher.tsx
"account.theme.dark": "Mørkt",
"account.theme.light": "Lyst",
"account.theme.system": "System",
"account.button.delete": "Slet bruger",
"account.modal.delete.title": "Slet bruger",
"account.modal.delete.description": "Er du sikker på at du vil slette din bruger, herunder alle dine aktive delinger?",
// END /account
// /account/shares
"account.shares.title": "Mine delte filer",
"account.shares.title.empty": "Der er tomt her 👀",
"account.shares.description.empty": "Du har ingen delinger.",
"account.shares.button.create": "Opret en",
"account.shares.info.title": "Share informations",
"account.shares.table.id": "ID",
"account.shares.table.name": "Navn",
"account.shares.table.description": "Beskrivelse",
"account.shares.table.visitors": "Besøgende",
"account.shares.table.expiresAt": "Udløber d",
"account.shares.table.createdAt": "Oprettet d.",
"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": "Slet share {share}",
"account.shares.modal.delete.description": "Ønsker du virkelig at slette denne deling?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Omvendt deling",
"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": "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",
"account.reverseShares.modal.expiration.hour-singular": "Time",
"account.reverseShares.modal.expiration.hour-plural": "Timer",
"account.reverseShares.modal.expiration.day-singular": "Dag",
"account.reverseShares.modal.expiration.day-plural": "Dage",
"account.reverseShares.modal.expiration.week-singular": "Uge",
"account.reverseShares.modal.expiration.week-plural": "Uger",
"account.reverseShares.modal.expiration.month-singular": "Måned",
"account.reverseShares.modal.expiration.month-plural": "Måneder",
"account.reverseShares.modal.expiration.year-singular": "År",
"account.reverseShares.modal.expiration.year-plural": "År",
"account.reverseShares.modal.max-size.label": "Maksimal størrelse for deling",
"account.reverseShares.modal.send-email": "Send e-mail notifikation",
"account.reverseShares.modal.send-email.description": "Send en e-mail notifikation, når der oprettes en deling med dette omvendte delingslink.",
"account.reverseShares.modal.max-use.label": "Maksimal anvendelser",
"account.reverseShares.modal.max-use.description": "Det maksimale antal gange, denne URL kan bruges til at oprette en deling.",
"account.reverseShare.never-expires": "Denne omvendte deling udløber aldrig.",
"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": "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": "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
// /admin
"admin.title": "Administration",
"admin.button.users": "Brugeradministration",
"admin.button.config": "Konfiguration",
"admin.version": "Version",
// END /admin
// /admin/users
"admin.users.title": "Brugeradministration",
"admin.users.table.username": "Brugernavn",
"admin.users.table.email": "E-mail",
"admin.users.table.admin": "Admin",
"admin.users.edit.update.title": "Opdater bruger {username}",
"admin.users.edit.update.admin-privileges": "Admin rettigheder",
"admin.users.edit.update.change-password.title": "Skift adgangskode",
"admin.users.edit.update.change-password.field": "Ny adgangskode",
"admin.users.edit.update.change-password.button": "Gem ny adgangskode",
"admin.users.edit.update.notify.password.success": "Adgangskoden er ændret",
"admin.users.edit.delete.title": "Slet bruger {username}",
"admin.users.edit.delete.description": "Er du sikker på du vil slette denne bruger og tilhørende delinger?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Opret bruger",
"admin.users.modal.create.username": "Brugernavn",
"admin.users.modal.create.email": "E-mail",
"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 rettigheder",
"admin.users.modal.create.admin.description": "If checked, the user will be able to access the admin panel.",
// END /admin/users
// /upload
"upload.title": "Upload",
"upload.notify.generic-error": "Der opstod en fejl under afslutningen af din deling.",
"upload.notify.count-failed": "{count} files failed to upload. Trying again.",
// Dropzone.tsx
"upload.dropzone.title": "Upload filer",
"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": "Navn",
"upload.filelist.size": "Størrelse",
// showCreateUploadModal.tsx
"upload.modal.title": "Opret Deling",
"upload.modal.link.error.invalid": "Can only contain letters, numbers, underscores, and hyphens",
"upload.modal.link.error.taken": "Det valgte link er allerede i brug",
"upload.modal.not-signed-in": "Du er ikke logget ind",
"upload.modal.not-signed-in-description": "Du vil ikke være i stand til at slette din deling manuelt og se antallet af besøgende.",
"upload.modal.expires.never": "aldrig",
"upload.modal.expires.never-long": "Udløber aldrig",
"upload.modal.link.label": "Link",
"upload.modal.expires.label": "Udløb",
"upload.modal.expires.minute-singular": "Minut",
"upload.modal.expires.minute-plural": "Minutter",
"upload.modal.expires.hour-singular": "Time",
"upload.modal.expires.hour-plural": "Timer",
"upload.modal.expires.day-singular": "Dag",
"upload.modal.expires.day-plural": "Dage",
"upload.modal.expires.week-singular": "Uge",
"upload.modal.expires.week-plural": "Uger",
"upload.modal.expires.month-singular": "Måned",
"upload.modal.expires.month-plural": "Måneder",
"upload.modal.expires.year-singular": "År",
"upload.modal.expires.year-plural": "År",
"upload.modal.accordion.description.title": "Beskrivelse",
"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",
"upload.modal.accordion.security.title": "Sikkerhedsindstillinger",
"upload.modal.accordion.security.password.label": "Adgangskodebeskyttelse",
"upload.modal.accordion.security.password.placeholder": "Ingen adgangskode",
"upload.modal.accordion.security.max-views.label": "Max antal visninger",
"upload.modal.accordion.security.max-views.placeholder": "Ingen begrænsning",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "Denne deling vil aldrig udløbe.",
"upload.modal.completed.expires-on": "Denne omvendte deling udløber den {expiration}.",
"upload.modal.completed.share-ready": "Delingen er klar",
// END /upload
// /share/[id]
"share.title": "Del {shareId}",
"share.description": "Se hvad jeg har delt med dig!",
"share.error.visitor-limit-exceeded.title": "Grænsen for besøgende overskredet",
"share.error.visitor-limit-exceeded.description": "Besøgsgrænsen for denne deling er blevet overskredet.",
"share.error.removed.title": "Deling fjernet",
"share.error.not-found.title": "Delingen blev ikke fundet",
"share.error.not-found.description": "Den deling, du leder efter, eksisterer ikke.",
"share.modal.password.title": "Adgangskode påkrævet",
"share.modal.password.description": "For at få adgang til denne deling, indtast venligst adgangskoden til delingen.",
"share.modal.password": "Adgangskode",
"share.modal.error.invalid-password": "Ugyldig adgangskode",
"share.button.download-all": "Download alle",
"share.notify.download-all-preparing": "Delingen forberedes. Prøv igen om et par minutter.",
"share.modal.file-link": "Fil link",
"share.table.name": "Navn",
"share.table.size": "Størrelse",
"share.modal.file-preview.error.not-supported.title": "Forhåndsvisning ikke understøttet",
"share.modal.file-preview.error.not-supported.description": "En forhåndsvisning for thise filtype er ikke understøttet. Download venligst filen for at se den.",
// END /share/[id]
// /admin/config
"admin.config.title": "Konfiguration",
"admin.config.category.general": "Generelt",
"admin.config.category.share": "Del",
"admin.config.category.email": "E-mail",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "App-navn",
"admin.config.general.app-name.description": "Navnet på applikationen",
"admin.config.general.app-url": "App URL",
"admin.config.general.app-url.description": "På hvilken URL Pingvin Share er tilgængelig",
"admin.config.general.show-home-page": "Vis forside",
"admin.config.general.show-home-page.description": "Om forsiden skal vises",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description": "Skift dit logo ved at uploade et nyt billede. Billedet skal være PNG og skal have formatet 1:1.",
"admin.config.general.logo.placeholder": "Vælg billede",
"admin.config.email.enable-share-email-recipients": "Aktiver deling til e-mail modtagere",
"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": "Nulstil adgangskode besked",
"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": "Invitations emne",
"admin.config.email.invite-subject.description": "Emne for den e-mail, der sendes, når en administrator inviterer en ny bruger.",
"admin.config.email.invite-message": "Invitations besked",
"admin.config.email.invite-message.description": "Besked som bliver sendt, når en administrator inviterer en bruger. {url} vil blive erstattet med invitations-URL'en og {password} med adgangskoden.",
"admin.config.share.allow-registration": "Tillad oprettelser",
"admin.config.share.allow-registration.description": "Om alle skal kunne oprette en bruger",
"admin.config.share.allow-unauthenticated-shares": "Tillad uautoriserede delinger",
"admin.config.share.allow-unauthenticated-shares.description": "Whether unauthenticated users can create shares",
"admin.config.share.max-size": "Maks. størrelse",
"admin.config.share.max-size.description": "Maksimal filstørrelse i 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": "Aktiveret",
"admin.config.smtp.enabled.description": "Om SMTP er aktiveret. Aktiver kun SMTP, hvis du har indtastet SMTP-server vært, port, e-mail, bruger og adgangskode.",
"admin.config.smtp.host": "Vært",
"admin.config.smtp.host.description": "Vært for SMTP serveren",
"admin.config.smtp.port": "Port",
"admin.config.smtp.port.description": "Porten til SMTP serveren",
"admin.config.smtp.email": "E-mail",
"admin.config.smtp.email.description": "E-mail adressen som der skal afsendes fra",
"admin.config.smtp.username": "Brugernavn",
"admin.config.smtp.username.description": "Brugernavnet til SMTP serveren",
"admin.config.smtp.password": "Adgangskode",
"admin.config.smtp.password.description": "Adgangskoden til SMTP serveren",
"admin.config.smtp.button.test": "Send test e-mail",
// 404
"404.description": "Ups! Denne side findes ikke.",
"404.button.home": "Gå tilbage",
// Common translations
"common.button.save": "Gem",
"common.button.create": "Opret",
"common.button.submit": "Submit",
"common.button.delete": "Slet",
"common.button.cancel": "Annuller",
"common.button.confirm": "Bekræft",
"common.button.disable": "Deaktiver",
"common.button.share": "Del",
"common.button.generate": "Generer",
"common.button.done": "Færdig",
"common.text.link": "Link",
"common.text.or": "eller",
"common.button.go-back": "Gå tilbage",
"common.notify.copied": "Linket blev kopieret til udklipsholderen",
"common.success": "Success",
"common.error": "Fejl",
"common.error.unknown": "En ukendt fejl opstod",
"common.error.invalid-email": "Ugyldig e-mail",
"common.error.too-short": "Skal være på mindst {length} tegn",
"common.error.too-long": "Må højst være {length} tegn",
"common.error.exact-length": "Skal være præcis {length} tegn",
"common.error.invalid-number": "Skal være et tal",
"common.error.field-required": "Dette felt er påkrævet"
};

View File

@@ -1,324 +0,0 @@
export default {
// Navbar
"navbar.upload": "Hochladen",
"navbar.signin": "Anmelden",
"navbar.home": "Startseite",
"navbar.signup": "Registrieren",
"navbar.links.shares": "Meine Freigaben",
"navbar.links.reverse": "Externe Freigaben",
"navbar.avatar.account": "Mein Konto",
"navbar.avatar.admin": "Verwaltung",
"navbar.avatar.signout": "Abmelden",
// END navbar
// /
"home.title": "Eine <h>selbst gehostete</h> Dateifreigabe-Plattform.",
"home.description": "Möchtest du wirklich deine persönlichen Dateien an Drittanbieter wie WeTransfer weitergeben?",
"home.bullet.a.name": "Selbst gehostet",
"home.bullet.a.description": "Betreibe Pingvin Share auf deinem eigenen Server.",
"home.bullet.b.name": "Privatsphäre",
"home.bullet.b.description": "Deine Dateien gehören dir und sollten niemals in die Hände Dritter gelangen.",
"home.bullet.c.name": "Keine lästige Dateigrößenbegrenzung",
"home.bullet.c.description": "Lade Dateien beliebiger Größe hoch. Nur dein Festplattenspeicher stellt die Grenze dar.",
"home.button.start": "Lege los",
"home.button.source": "Quellcode",
// END /
// /auth/signin
"signin.title": "Willkommen zurück",
"signin.description": "Du hast noch kein Konto?",
"signin.button.signup": "Registrieren",
"signin.input.email-or-username": "Email oder Benutzername",
"signin.input.email-or-username.placeholder": "Deine Email Adresse oder Benutzername",
"signin.input.password": "Passwort",
"signin.input.password.placeholder": "Dein Passwort",
"signin.button.submit": "Anmelden",
"signIn.notify.totp-required.title": "Zwei-Faktor-Authentifizierung benötigt",
"signIn.notify.totp-required.description": "Bitte füge deinen Zwei-Faktor-Authentifizierungscode ein",
// END /auth/signin
// /auth/signup
"signup.title": "Erstelle ein Konto",
"signup.description": "Besitzt du bereits ein Konto?",
"signup.button.signin": "Anmelden",
"signup.input.username": "Benutzername",
"signup.input.username.placeholder": "Dein Benutzername",
"signup.input.email": "Email",
"signup.input.email.placeholder": "Deine Emailadresse",
"signup.button.submit": "Lass uns loslegen",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Passwort vergessen?",
"resetPassword.description": "Gib deine Email Adresse ein, um dein Passwort zurückzusetzen.",
"resetPassword.notify.success": "Ein Link zum Rücksetzen des Passwortes wurde an deine Emailadresse versandt.",
"resetPassword.button.back": "Zurück zur Anmeldeseite",
"resetPassword.text.resetPassword": "Passwort zurücksetzen",
"resetPassword.text.enterNewPassword": "Gib dein neues Passwort ein",
"resetPassword.input.password": "Neues Passwort",
"resetPassword.notify.passwordReset": "Dein Passwort wurde erfolgreich zurückgesetzt.",
// /account
"account.title": "Mein Konto",
"account.card.info.title": "Kontoinformationen",
"account.card.info.username": "Benutzername",
"account.card.info.email": "Email",
"account.notify.info.success": "Konto erfolgreich aktualisiert",
"account.card.password.title": "Passwort",
"account.card.password.old": "Altes Passwort",
"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": "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",
"account.modal.totp.step2": "Schritt 2: Bestätige deinen Code",
"account.modal.totp.enterManually": "Manuell eingeben",
"account.modal.totp.code": "Code",
"account.modal.totp.clickToCopy": "Klicken zum Kopieren",
"account.modal.totp.verify": "Überprüfen",
"account.notify.totp.disable": "TOTP erfolgreich deaktiviert",
"account.notify.totp.enable": "TOTP erfolgreich aktiviert",
"account.card.language.title": "Sprache",
"account.card.language.description": "Das Projekt wird von der Community übersetzt. Einige Sprachen könnten unvollständig sein.",
"account.card.color.title": "Farbschema",
// ThemeSwitcher.tsx
"account.theme.dark": "Dunkel",
"account.theme.light": "Hell",
"account.theme.system": "System",
"account.button.delete": "Konto löschen",
"account.modal.delete.title": "Konto löschen",
"account.modal.delete.description": "Möchtest du wirklich dein Konto inklusive aller aktiven Freigaben löschen?",
// END /account
// /account/shares
"account.shares.title": "Meine Freigaben",
"account.shares.title.empty": "Es ist so leer hier 👀",
"account.shares.description.empty": "Du hast keine Freigaben eingerichtet.",
"account.shares.button.create": "Erstelle eine",
"account.shares.info.title": "Teile deine Information",
"account.shares.table.id": "ID",
"account.shares.table.name": "Name",
"account.shares.table.description": "Beschreibung",
"account.shares.table.visitors": "Besucher",
"account.shares.table.expiresAt": "Läuft ab am",
"account.shares.table.createdAt": "Erstellt am",
"account.shares.table.size": "Größe",
"account.shares.modal.share-informations": "Teile deine Information",
"account.shares.modal.share-link": "Freigabe teilen",
"account.shares.modal.delete.title": "Lösche Freigabe {share}",
"account.shares.modal.delete.description": "Möchtest du wirklich diese Freigabe löschen?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Externe Freigaben",
"account.reverseShares.description": "Eine externe Freigabe erlaubt dir eine einzigartige URL zu erstellen, die externen Benutzern erlaubt Dateien hochzuladen.",
"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",
"account.reverseShares.modal.expiration.hour-singular": "Stunde",
"account.reverseShares.modal.expiration.hour-plural": "Stunden",
"account.reverseShares.modal.expiration.day-singular": "Tag",
"account.reverseShares.modal.expiration.day-plural": "Tage",
"account.reverseShares.modal.expiration.week-singular": "Woche",
"account.reverseShares.modal.expiration.week-plural": "Wochen",
"account.reverseShares.modal.expiration.month-singular": "Monat",
"account.reverseShares.modal.expiration.month-plural": "Monate",
"account.reverseShares.modal.expiration.year-singular": "Jahr",
"account.reverseShares.modal.expiration.year-plural": "Jahre",
"account.reverseShares.modal.max-size.label": "Max. Freigabengröße",
"account.reverseShares.modal.send-email": "Email Benachrichtigung senden",
"account.reverseShares.modal.send-email.description": "Sendet eine Email Benachrichtigung, wenn eine Datei auf einer externen Freigabe hochgeladen wurde.",
"account.reverseShares.modal.max-use.label": "Maximale Nutzungen",
"account.reverseShares.modal.max-use.description": "Die maximale Anzahl von Verwendungen der URL, um Dateien hochzuladen.",
"account.reverseShare.never-expires": "Diese externe Freigabe wird nicht ablaufen.",
"account.reverseShare.expires-on": "Diese externe Freigabe wird am {expiration} ablaufen.",
"account.reverseShares.table.no-shares": "Noch keine Freigaben erstellt",
"account.reverseShares.table.count.singular": "Freigabe",
"account.reverseShares.table.count.plural": "Freigaben",
"account.reverseShares.table.shares": "Freigaben",
"account.reverseShares.table.remaining": "Verbleibende Verwendungen",
"account.reverseShares.table.max-size": "Maximale Freigabegröße",
"account.reverseShares.table.expires": "Läuft ab am",
"account.reverseShares.modal.reverse-share-link": "Link zu externer Freigabe",
"account.reverseShares.modal.delete.title": "Lösche externe Freigabe",
"account.reverseShares.modal.delete.description": "Möchtest du wirklich diese externe Freigabe löschen? In diesem Falle werden auch hiermit verbundene Freigaben gelöscht.",
// END /account/reverseShares
// /admin
"admin.title": "Verwaltung",
"admin.button.users": "Benutzerverwaltung",
"admin.button.config": "Konfiguration",
"admin.version": "Version",
// END /admin
// /admin/users
"admin.users.title": "Benutzerverwaltung",
"admin.users.table.username": "Benutzername",
"admin.users.table.email": "Email",
"admin.users.table.admin": "Administrator",
"admin.users.edit.update.title": "Benutzer {username} aktualisieren",
"admin.users.edit.update.admin-privileges": "Administratorrechte",
"admin.users.edit.update.change-password.title": "Passwort ändern",
"admin.users.edit.update.change-password.field": "Neues Passwort",
"admin.users.edit.update.change-password.button": "Neues Passwort speichern",
"admin.users.edit.update.notify.password.success": "Passwort erfolgreich geändert",
"admin.users.edit.delete.title": "Löschen des Nutzers {username}",
"admin.users.edit.delete.description": "Möchtest du wirklich diesen Benutzer und all seine Freigaben löschen?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Benutzer erstellen",
"admin.users.modal.create.username": "Benutzername",
"admin.users.modal.create.email": "Email",
"admin.users.modal.create.password": "Passwort",
"admin.users.modal.create.manual-password": "Passwort manuell festlegen",
"admin.users.modal.create.manual-password.description": "Wenn nicht aktiviert, erhält der Benutzer eine Email mit einem Link, um sein Passwort festzulegen.",
"admin.users.modal.create.admin": "Administratorrechte",
"admin.users.modal.create.admin.description": "Wenn aktiviert, kann der Benutzer auf das Administrator-Panel zugreifen.",
// END /admin/users
// /upload
"upload.title": "Upload",
"upload.notify.generic-error": "Während der Erstellung der Freigabe ist ein Fehler aufgetreten.",
"upload.notify.count-failed": "{count} Dateien konnten nicht hochgeladen werden. Wird erneut versucht.",
// Dropzone.tsx
"upload.dropzone.title": "Dateien hochladen",
"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",
"upload.filelist.size": "Größe",
// showCreateUploadModal.tsx
"upload.modal.title": "Erstelle Freigabe",
"upload.modal.link.error.invalid": "Darf nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten",
"upload.modal.link.error.taken": "Dieser Link wird bereits verwendet",
"upload.modal.not-signed-in": "Du bist nicht angemeldet",
"upload.modal.not-signed-in-description": "Du wirst deine Freigabe nicht löschen können oder die Besucheranzahl sehen.",
"upload.modal.expires.never": "niemals",
"upload.modal.expires.never-long": "Läuft nicht ab",
"upload.modal.link.label": "Link",
"upload.modal.expires.label": "Gültig bis",
"upload.modal.expires.minute-singular": "Minute",
"upload.modal.expires.minute-plural": "Minuten",
"upload.modal.expires.hour-singular": "Stunde",
"upload.modal.expires.hour-plural": "Stunden",
"upload.modal.expires.day-singular": "Tag",
"upload.modal.expires.day-plural": "Tage",
"upload.modal.expires.week-singular": "Woche",
"upload.modal.expires.week-plural": "Wochen",
"upload.modal.expires.month-singular": "Monat",
"upload.modal.expires.month-plural": "Monate",
"upload.modal.expires.year-singular": "Jahr",
"upload.modal.expires.year-plural": "Year",
"upload.modal.accordion.description.title": "Beschreibung",
"upload.modal.accordion.description.placeholder": "Hinweis für die Empfänger dieser Freigabe",
"upload.modal.accordion.email.title": "Email Empfänger",
"upload.modal.accordion.email.placeholder": "Email der Empfänger eingeben",
"upload.modal.accordion.email.invalid-email": "Ungültige Emailadresse",
"upload.modal.accordion.security.title": "Sicherheitseinstellungen",
"upload.modal.accordion.security.password.label": "Passwortschutz",
"upload.modal.accordion.security.password.placeholder": "Kein Passwort",
"upload.modal.accordion.security.max-views.label": "Maximale Aufrufe",
"upload.modal.accordion.security.max-views.placeholder": "Unbegrenzt",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "Diese Freigabe läuft niemals ab.",
"upload.modal.completed.expires-on": "Diese Freigabe wird am {expiration} ablaufen.",
"upload.modal.completed.share-ready": "Freigabe bereit",
// END /upload
// /share/[id]
"share.title": "Freigabe {shareId}",
"share.description": "Schau, was ich mit dir geteilt habe!",
"share.error.visitor-limit-exceeded.title": "Besucher Limit erreicht",
"share.error.visitor-limit-exceeded.description": "Die maximale Besucheranzahl für diese Freigabe ist überschritten.",
"share.error.removed.title": "Freigabe entfernt",
"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, 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",
"share.notify.download-all-preparing": "Die Freigabe wird vorbereitet. Versuche es in ein paar Minuten erneut.",
"share.modal.file-link": "Dateilink",
"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 lade die Datei herunter, um sie anzuzeigen.",
// END /share/[id]
// /admin/config
"admin.config.title": "Einstellungen",
"admin.config.category.general": "Allgemein",
"admin.config.category.share": "Freigabe",
"admin.config.category.email": "E-Mail",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "App-Name",
"admin.config.general.app-name.description": "Name der Applikation",
"admin.config.general.app-url": "App-URL",
"admin.config.general.app-url.description": "Auf welcher URL Pingvin Share verfügbar ist",
"admin.config.general.show-home-page": "Startseite anzeigen",
"admin.config.general.show-home-page.description": "Ob die Startseite angezeigt werden soll",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description": "Ändere dein Logo durch Hochladen eines Bildes. Das Bild muss im PNG-Format vorliegen und sollte mit Seitenverhältnis 1:1 sein.",
"admin.config.general.logo.placeholder": "Bild auswählen",
"admin.config.email.enable-share-email-recipients": "Erlaube das Teilen der Freigabe via Email",
"admin.config.email.enable-share-email-recipients.description": "Gibt an, ob Emails an Freigabe-Empfänger ermöglicht werden sollen. Aktiviere dies nur, wenn Du SMTP aktivierst hast.",
"admin.config.email.share-recipients-subject": "Betreff für Freigabe-Empfänger",
"admin.config.email.share-recipients-subject.description": "Betreff der E-Mail, die an die Freigabe-Empfänger gesendet wird.",
"admin.config.email.share-recipients-message": "Nachricht für Freigabe-Empfänger",
"admin.config.email.share-recipients-message.description": "Nachricht, die an die Freigabe-Empfänger gesendet wird. Verfügbare Variablen:\n- {creator} - Der Benutzername des Erstellers der Freigabe\n- {shareUrl} - Die URL der Freigabe\n- {desc} - Die Beschreibung der Freigabe\n- {expires} - Das Ablaufdatum der Freigabe\nVariablen werden durch die tatsächlichen Werte ersetzt.",
"admin.config.email.reverse-share-subject": "Name der externen Freigabe",
"admin.config.email.reverse-share-subject.description": "Betreff der Email, die gesendet wird, wenn jemand eine Datei mit deinem externen Freigabe-Link hochlädt.",
"admin.config.email.reverse-share-message": "Nachricht für externe Freigabe",
"admin.config.email.reverse-share-message.description": "Nachricht, die gesendet wird, wenn jemand eine Freigabe mit deinem externen Freigabe-Link erstellt. {shareUrl} wird durch den Namen des Erstellers und die Freigabe-URL ersetzt.",
"admin.config.email.reset-password-subject": "Betreff für Passwortzurücksetzung",
"admin.config.email.reset-password-subject.description": "Betreff der E-Mail, die gesendet wird, wenn ein Benutzer eine Passwortzurücksetzung anfordert.",
"admin.config.email.reset-password-message": "Nachricht für Passwortzurücksetzung",
"admin.config.email.reset-password-message.description": "Nachricht, die gesendet wird, wenn ein Benutzer eine Passwortzurücksetzung anfordert. {url} wird durch die URL für das Zurücksetzen des Passworts ersetzt.",
"admin.config.email.invite-subject": "Betreff für Einladung",
"admin.config.email.invite-subject.description": "Betreff der E-Mail, die gesendet wird, wenn ein Administrator einen Benutzer einlädt.",
"admin.config.email.invite-message": "Nachricht für Einladung",
"admin.config.email.invite-message.description": "Nachricht, die gesendet wird, wenn ein Administrator einen Benutzer einlädt. {url} wird durch die Einladungs-URL und {password} durch das Passwort ersetzt.",
"admin.config.share.allow-registration": "Registrierung erlauben",
"admin.config.share.allow-registration.description": "Gibt an, ob eine Registrierung erlaubt ist",
"admin.config.share.allow-unauthenticated-shares": "Nicht authentifizierte Freigaben erlauben",
"admin.config.share.allow-unauthenticated-shares.description": "Gibt an, ob nicht authentifizierte Benutzer Freigaben erstellen können",
"admin.config.share.max-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": "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",
"admin.config.smtp.host.description": "Host des SMTP-Servers",
"admin.config.smtp.port": "Port",
"admin.config.smtp.port.description": "Port des SMTP-Servers",
"admin.config.smtp.email": "E-Mail",
"admin.config.smtp.email.description": "E-Mail-Adresse, von der die E-Mails gesendet werden",
"admin.config.smtp.username": "Benutzername",
"admin.config.smtp.username.description": "Benutzername des SMTP-Servers",
"admin.config.smtp.password": "Passwort",
"admin.config.smtp.password.description": "Passwort des SMTP-Servers",
"admin.config.smtp.button.test": "Test-E-Mail senden",
// 404
"404.description": "Ups, diese Seite existiert nicht.",
"404.button.home": "Zurück zur Startseite",
// Common translations
"common.button.save": "Speichern",
"common.button.create": "Erstellen",
"common.button.submit": "Bestätigen",
"common.button.delete": "Löschen",
"common.button.cancel": "Abbrechen",
"common.button.confirm": "Bestätigen",
"common.button.disable": "Deaktivieren",
"common.button.share": "Teilen",
"common.button.generate": "Generieren",
"common.button.done": "Fertig",
"common.text.link": "Link",
"common.text.or": "oder",
"common.button.go-back": "Zurück",
"common.notify.copied": "Dein Link wurde in die Zwischenablage kopiert",
"common.success": "Erfolg",
"common.error": "Fehler",
"common.error.unknown": "Ein unbekannter Fehler ist aufgetreten",
"common.error.invalid-email": "Ungültige E-Mail-Adresse",
"common.error.too-short": "Muss mindestens {length} Zeichen enthalten",
"common.error.too-long": "Muss maximal {length} Zeichen enthalten",
"common.error.exact-length": "Muss genau {length} Zeichen lang sein",
"common.error.invalid-number": "Muss eine Zahl sein",
"common.error.field-required": "Dieses Feld ist erforderlich"
};

View File

@@ -1,439 +0,0 @@
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",
// 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",
// 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",
// 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",
// 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.",
// /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",
// 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?",
// 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.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?",
// 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.",
// 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",
"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.",
// END /account/reverseShares
// /admin
"admin.title": "Administration",
"admin.button.users": "User management",
"admin.button.config": "Configuration",
"admin.version": "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?",
// 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.",
// 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.",
// 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}.",
// FileList.tsx
"upload.filelist.name": "Name",
"upload.filelist.size": "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",
// 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": "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.",
// 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.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",
// 404
"404.description": "Oops this page doesn't exist.",
"404.button.home": "Bring me back 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",
};

View File

@@ -1,324 +0,0 @@
export default {
// Navbar
"navbar.upload": "Subir",
"navbar.signin": "Iniciar sesión",
"navbar.home": "Inicio",
"navbar.signup": "Registrarse",
"navbar.links.shares": "Mis compartidos",
"navbar.links.reverse": "Comparticiones inversas",
"navbar.avatar.account": "Mi cuenta",
"navbar.avatar.admin": "Administración",
"navbar.avatar.signout": "Cerrar sesión",
// END navbar
// /
"home.title": "Una plataforma <h>autoalojada</h> para compartir archivos.",
"home.description": "¿En realidad quieres dejar tus archivos personales en manos de terceros como WeTransfer?",
"home.bullet.a.name": "Autoalojada",
"home.bullet.a.description": "Aloja Pingvin Share en tu propio equipo.",
"home.bullet.b.name": "Privacidad",
"home.bullet.b.description": "Tus archivos son tus archivos y nunca deberían terminar en manos de terceros.",
"home.bullet.c.name": "Sin molestos límites de tamaño de archivo",
"home.bullet.c.description": "Sube archivos tan grandes como quieras. El único límite es la capacidad de tu disco duro.",
"home.button.start": "Comenzar",
"home.button.source": "Código fuente",
// END /
// /auth/signin
"signin.title": "Bienvenido nuevamente",
"signin.description": "¿Todavía no tienes una cuenta?",
"signin.button.signup": "Registrarse",
"signin.input.email-or-username": "Correo o nombre de usuario",
"signin.input.email-or-username.placeholder": "Tu correo o nombre de usuario",
"signin.input.password": "Contraseña",
"signin.input.password.placeholder": "Tu contraseña",
"signin.button.submit": "Iniciar sesión",
"signIn.notify.totp-required.title": "Se requiere autenticación de dos factores",
"signIn.notify.totp-required.description": "Por favor ingrese su código de autenticación de dos factores",
// END /auth/signin
// /auth/signup
"signup.title": "Crear una cuenta",
"signup.description": "¿Ya tienes una cuenta?",
"signup.button.signin": "Iniciar sesión",
"signup.input.username": "Nombre de usuario",
"signup.input.username.placeholder": "Tu nombre de usuario",
"signup.input.email": "Correo",
"signup.input.email.placeholder": "Tu correo",
"signup.button.submit": "Comencemos",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "¿Olvidaste tu contraseña?",
"resetPassword.description": "Ingresa tu correo para restablecer tu contraseña.",
"resetPassword.notify.success": "Se ha enviado un correo con el enlace para restablecer tu contraseña.",
"resetPassword.button.back": "Volver al inicio de sesión",
"resetPassword.text.resetPassword": "Restablecer contraseña",
"resetPassword.text.enterNewPassword": "Ingresa tu nueva contraseña",
"resetPassword.input.password": "Nueva contraseña",
"resetPassword.notify.passwordReset": "Tu contraseña se ha restablecido correctamente.",
// /account
"account.title": "Mi cuenta",
"account.card.info.title": "Información de cuenta",
"account.card.info.username": "Nombre de usuario",
"account.card.info.email": "Correo",
"account.notify.info.success": "Cuenta actualizada correctamente",
"account.card.password.title": "Contraseña",
"account.card.password.old": "Anterior contraseña",
"account.card.password.new": "Nueva contraseña",
"account.notify.password.success": "Contraseña cambiada correctamente",
"account.card.security.title": "Seguridad",
"account.card.security.totp.enable.description": "Ingrese su contraseña actual para habilitar TOTP",
"account.card.security.totp.disable.description": "Ingrese su contraseña actual para deshabilitar TOTP",
"account.card.security.totp.button.start": "Iniciar",
"account.modal.totp.title": "Habilitar TOTP",
"account.modal.totp.step1": "Paso 1: Añadir tu autentificador",
"account.modal.totp.step2": "Paso 2: Validar tu código",
"account.modal.totp.enterManually": "Ingresar manualmente",
"account.modal.totp.code": "Código",
"account.modal.totp.clickToCopy": "Clic para copiar",
"account.modal.totp.verify": "Verificar",
"account.notify.totp.disable": "TOTP deshabilitado correctamente",
"account.notify.totp.enable": "TOTP habilitado correctamente",
"account.card.language.title": "Idioma",
"account.card.language.description": "El proyecto ha sido traducido por la comunidad. Algunos idiomas pueden estar incompletos.",
"account.card.color.title": "Esquema de colores",
// ThemeSwitcher.tsx
"account.theme.dark": "Oscuro",
"account.theme.light": "Claro",
"account.theme.system": "Sistema",
"account.button.delete": "Eliminar Cuenta",
"account.modal.delete.title": "Eliminar Cuenta",
"account.modal.delete.description": "¿Realmente quieres eliminar tu cuenta con todos los archivos que estás compartiendo actualmente?",
// END /account
// /account/shares
"account.shares.title": "Mis compartidos",
"account.shares.title.empty": "Aquí está vacío 👀",
"account.shares.description.empty": "No tienes nada compartido.",
"account.shares.button.create": "Crear uno",
"account.shares.info.title": "Información del compartido",
"account.shares.table.id": "ID",
"account.shares.table.name": "Nombre",
"account.shares.table.description": "Descripción",
"account.shares.table.visitors": "Visitas",
"account.shares.table.expiresAt": "Expira en",
"account.shares.table.createdAt": "Creado en",
"account.shares.table.size": "Tamaño",
"account.shares.modal.share-informations": "Información del compartido",
"account.shares.modal.share-link": "Enlace",
"account.shares.modal.delete.title": "Eliminar compartido {share}",
"account.shares.modal.delete.description": "¿Seguro que quieres eliminar este compartido?",
// END /account/shares
// /account/reverseShares
"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 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",
"account.reverseShares.modal.expiration.hour-singular": "Hora",
"account.reverseShares.modal.expiration.hour-plural": "Horas",
"account.reverseShares.modal.expiration.day-singular": "Día",
"account.reverseShares.modal.expiration.day-plural": "Días",
"account.reverseShares.modal.expiration.week-singular": "Semana",
"account.reverseShares.modal.expiration.week-plural": "Semanas",
"account.reverseShares.modal.expiration.month-singular": "Mes",
"account.reverseShares.modal.expiration.month-plural": "Meses",
"account.reverseShares.modal.expiration.year-singular": "Año",
"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 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 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",
"account.reverseShares.table.shares": "Compartidos",
"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 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",
"admin.button.users": "Gestión de usuarios",
"admin.button.config": "Configuración",
"admin.version": "Versión",
// END /admin
// /admin/users
"admin.users.title": "Gestión de usuarios",
"admin.users.table.username": "Nombre de usuario",
"admin.users.table.email": "Correo",
"admin.users.table.admin": "Administrador",
"admin.users.edit.update.title": "Actualizar usuario {username}",
"admin.users.edit.update.admin-privileges": "Privilegios de administrador",
"admin.users.edit.update.change-password.title": "Cambiar contraseña",
"admin.users.edit.update.change-password.field": "Nueva contraseña",
"admin.users.edit.update.change-password.button": "Guardar nueva contraseña",
"admin.users.edit.update.notify.password.success": "Contraseña cambiada correctamente",
"admin.users.edit.delete.title": "Eliminar usuario {username}",
"admin.users.edit.delete.description": "¿Realmente quiere eliminar este usuario y todos sus archivos compartidos?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Crear usuario",
"admin.users.modal.create.username": "Nombre de usuario",
"admin.users.modal.create.email": "Correo",
"admin.users.modal.create.password": "Contraseña",
"admin.users.modal.create.manual-password": "Establecer contraseña manualmente",
"admin.users.modal.create.manual-password.description": "Si no se marca, el usuario recibirá un correo con un enlace para configurar su contraseña.",
"admin.users.modal.create.admin": "Privilegios de administrador",
"admin.users.modal.create.admin.description": "Si se marca, el usuario podrá acceder al panel de administrador.",
// END /admin/users
// /upload
"upload.title": "Subir",
"upload.notify.generic-error": "Ha ocurrido un error mientras se compartía tu archivo.",
"upload.notify.count-failed": "No se pudo cargar {count} archivos. Intentando nuevamente.",
// Dropzone.tsx
"upload.dropzone.title": "Subir archivos",
"upload.dropzone.description": "Arrastra archivos aquí para comenzar a compartir. Aceptamos archivos de un tamaño menor a {maxSize} en total.",
"upload.dropzone.notify.file-too-big": "Tus archivos exceden el tamaño máximo de {maxSize}.",
// FileList.tsx
"upload.filelist.name": "Nombre",
"upload.filelist.size": "Tamaño",
// showCreateUploadModal.tsx
"upload.modal.title": "Crear compartido",
"upload.modal.link.error.invalid": "Solo puede contener letras, números, guiones y guiones bajos",
"upload.modal.link.error.taken": "Este enlace ya está en uso",
"upload.modal.not-signed-in": "No has iniciado sesión",
"upload.modal.not-signed-in-description": "No podrás eliminar tus compartidos manualmente ni ver el número de visitas.",
"upload.modal.expires.never": "nunca",
"upload.modal.expires.never-long": "Nunca Expira",
"upload.modal.link.label": "Enlace",
"upload.modal.expires.label": "Expiración",
"upload.modal.expires.minute-singular": "Minuto",
"upload.modal.expires.minute-plural": "Minutos",
"upload.modal.expires.hour-singular": "Hora",
"upload.modal.expires.hour-plural": "Horas",
"upload.modal.expires.day-singular": "Día",
"upload.modal.expires.day-plural": "Días",
"upload.modal.expires.week-singular": "Semana",
"upload.modal.expires.week-plural": "Semanas",
"upload.modal.expires.month-singular": "Mes",
"upload.modal.expires.month-plural": "Meses",
"upload.modal.expires.year-singular": "Año",
"upload.modal.expires.year-plural": "Años",
"upload.modal.accordion.description.title": "Descripción",
"upload.modal.accordion.description.placeholder": "Nota para los destinatarios de este compartido",
"upload.modal.accordion.email.title": "Correo de los destinatarios",
"upload.modal.accordion.email.placeholder": "Ingresa los correos de los destinatarios",
"upload.modal.accordion.email.invalid-email": "Dirección de correo inválida",
"upload.modal.accordion.security.title": "Opciones de seguridad",
"upload.modal.accordion.security.password.label": "Protección por contraseña",
"upload.modal.accordion.security.password.placeholder": "Sin contraseña",
"upload.modal.accordion.security.max-views.label": "Máximo de vistas",
"upload.modal.accordion.security.max-views.placeholder": "Sin límite",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "Este compartido nunca expirará.",
"upload.modal.completed.expires-on": "Este compartido expira en {expiration}.",
"upload.modal.completed.share-ready": "Compartido listo",
// END /upload
// /share/[id]
"share.title": "Compartido {shareId}",
"share.description": "¡Mira lo que he compartido contigo!",
"share.error.visitor-limit-exceeded.title": "Se excedió el límite de visitas",
"share.error.visitor-limit-exceeded.description": "Se ha excedido el límite de visitas para este compartido.",
"share.error.removed.title": "Compartido eliminado",
"share.error.not-found.title": "Compartido no encontrado",
"share.error.not-found.description": "El compartido que estás buscando no existe.",
"share.modal.password.title": "Se requiere contraseña",
"share.modal.password.description": "Por favor ingrese la contraseña para poder acceder a este compartido.",
"share.modal.password": "Contraseña",
"share.modal.error.invalid-password": "Contraseña inválida",
"share.button.download-all": "Descargar todo",
"share.notify.download-all-preparing": "Se está preparando el compartido. Intente de nuevo en unos minutos.",
"share.modal.file-link": "Enlace del archivo",
"share.table.name": "Nombre",
"share.table.size": "Tamaño",
"share.modal.file-preview.error.not-supported.title": "Vista previa no disponible",
"share.modal.file-preview.error.not-supported.description": "La vista previa para este tipo de archivo no está disponible. Por favor descargue el archivo para verlo.",
// END /share/[id]
// /admin/config
"admin.config.title": "Configuración",
"admin.config.category.general": "General",
"admin.config.category.share": "Compartido",
"admin.config.category.email": "Correo",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "Nombre de la App",
"admin.config.general.app-name.description": "Nombre de la aplicación",
"admin.config.general.app-url": "App URL",
"admin.config.general.app-url.description": "En cuál URL está disponible Pingvin Share",
"admin.config.general.show-home-page": "Mostrar página de inicio",
"admin.config.general.show-home-page.description": "Mostrar o no la página de inicio",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description": "Cambia tu logo subiendo una nueva imagen. La imagen debe ser un PNG y debe estar en formato 1:1.",
"admin.config.general.logo.placeholder": "Elegir imagen",
"admin.config.email.enable-share-email-recipients": "Activar destinatarios por correo",
"admin.config.email.enable-share-email-recipients.description": "Si desea permitir a los destinatarios compartir por correo. Activa esto solo si tienes habilitado SMTP.",
"admin.config.email.share-recipients-subject": "Asunto destinatario",
"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 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",
"admin.config.email.reset-password-message.description": "Mensaje que se envía cuando un usuario solicita restablecer la contraseña. {url} se remplazará con la URL para restablecer la contraseña.",
"admin.config.email.invite-subject": "Asunto de la invitación",
"admin.config.email.invite-subject.description": "Asunto del correo que se envía cuando un administrador invita a un usuario.",
"admin.config.email.invite-message": "Mensaje de invitación",
"admin.config.email.invite-message.description": "Mensaje que se envía cuando un administrador invita a un usuario. {url} Se remplazará con la URL de invitación y {password} con la contraseña.",
"admin.config.share.allow-registration": "Permitir registro",
"admin.config.share.allow-registration.description": "Si se permite el registro",
"admin.config.share.allow-unauthenticated-shares": "Permitir compartir sin iniciar sesión",
"admin.config.share.allow-unauthenticated-shares.description": "Si los usuarios que no han iniciado sesión pueden compartir",
"admin.config.share.max-size": "Tamaño máximo",
"admin.config.share.max-size.description": "Tamaño máximo de los archivos, en bytes",
"admin.config.share.zip-compression-level": "Nivel de compresión del Zip",
"admin.config.share.zip-compression-level.description": "Ajustar el nivel para equilibrar entre el tamaño del archivo y la velocidad de compresión. Los valores válidos van del 0 al 9, siendo 0 sin compresión y 9 el nivel máximo de compresión. ",
"admin.config.smtp.enabled": "Habilitado",
"admin.config.smtp.enabled.description": "Si SMTP está habilitado. Active solo si ha introducido el host, el puerto, el correo, el usuario y la contraseña de su servidor SMTP.",
"admin.config.smtp.host": "Host",
"admin.config.smtp.host.description": "Host del servidor SMTP",
"admin.config.smtp.port": "Puerto",
"admin.config.smtp.port.description": "Puerto del servidor SMTP",
"admin.config.smtp.email": "Correo",
"admin.config.smtp.email.description": "Dirección de correo desde la cual se envían los correos",
"admin.config.smtp.username": "Usuario",
"admin.config.smtp.username.description": "Usuario del servidor SMTP",
"admin.config.smtp.password": "Contraseña",
"admin.config.smtp.password.description": "Contraseña del servidor SMTP",
"admin.config.smtp.button.test": "Enviar correo de prueba",
// 404
"404.description": "Oops esta página no existe.",
"404.button.home": "Regrésame al inicio",
// Common translations
"common.button.save": "Guardar",
"common.button.create": "Crear",
"common.button.submit": "Enviar",
"common.button.delete": "Eliminar",
"common.button.cancel": "Cancelar",
"common.button.confirm": "Confirmar",
"common.button.disable": "Deshabilitar",
"common.button.share": "Compartir",
"common.button.generate": "Generar",
"common.button.done": "Listo",
"common.text.link": "Enlace",
"common.text.or": "o",
"common.button.go-back": "Volver",
"common.notify.copied": "Tu enlace se ha copiado al portapapeles",
"common.success": "Éxito",
"common.error": "Error",
"common.error.unknown": "Ocurrió un error desconocido",
"common.error.invalid-email": "Correo electrónico no válido",
"common.error.too-short": "Debe tener al menos {length} caracteres",
"common.error.too-long": "Debe tener como máximo {length} caracteres",
"common.error.exact-length": "Debe tener exactamente {length} caracteres",
"common.error.invalid-number": "Debe ser un número",
"common.error.field-required": "Este campo es requerido"
};

View File

@@ -1,324 +0,0 @@
export default {
// Navbar
"navbar.upload": "Pilvetä",
"navbar.signin": "Kirjaudu Sisään",
"navbar.home": "Koti",
"navbar.signup": "Rekisteröidy",
"navbar.links.shares": "Minun jakaukset",
"navbar.links.reverse": "Takaperin jaetut",
"navbar.avatar.account": "Oma tIli",
"navbar.avatar.admin": "Ylläpito",
"navbar.avatar.signout": "Kirjaudu ulos",
// END navbar
// /
"home.title": "<h>\"Itse isännöitty\"</h> tiedostojen jakamisen alusta.",
"home.description": "Haluatko oikeasti jakaa yksityisiä tiedostojasi kolmannen osapuolen yhtiöille niin kuin WeTransfer?",
"home.bullet.a.name": "Itse-Isännöitty",
"home.bullet.a.description": "Isännöi \"Pingvin Share\" omalla palvelimellasi.",
"home.bullet.b.name": "Yksityisyys",
"home.bullet.b.description": "Sinun tiedostosi ovat sinun ja niiden ei ikinä pidä päättyä kolmannen osapuolen käsiin.",
"home.bullet.c.name": "Ei ärsyttävää tiedoston kokorajoitusta",
"home.bullet.c.description": "Lataa niin paljon isoja tiedostoja kuin tykkäät. Vain kovalevysi on rajana.",
"home.button.start": "Aloita",
"home.button.source": "Lähdekoodi",
// END /
// /auth/signin
"signin.title": "Tervetuloa takaisin",
"signin.description": "Eikö sinulla ole vielä tiliä?",
"signin.button.signup": "Rekisteröidy",
"signin.input.email-or-username": "Sähköposti tai käyttäjänimi",
"signin.input.email-or-username.placeholder": "Sähköpostisi tai käyttäjänimesi",
"signin.input.password": "Salasana",
"signin.input.password.placeholder": "Salasana",
"signin.button.submit": "Kirjaudu sisään",
"signIn.notify.totp-required.title": "Kaksivaiheinen tunnistautuminen vaadittu",
"signIn.notify.totp-required.description": "Syötä kaksivaiheisen tunnistautumisen koodi tähän",
// END /auth/signin
// /auth/signup
"signup.title": "Rekisteröidy",
"signup.description": "Onko sinulla jo tili?",
"signup.button.signin": "Kirjaudu sisään",
"signup.input.username": "Käyttäjätunnus",
"signup.input.username.placeholder": "Käyttäjätunnus",
"signup.input.email": "Sähköposti",
"signup.input.email.placeholder": "Sähköpostisi",
"signup.button.submit": "Aloitetaan",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Unohditko salasanan?",
"resetPassword.description": "Kirjoita sähköpostiosoitteesi palauttaaksesi salasanasi.",
"resetPassword.notify.success": "Sähköpostiosoite on lähetetty linkillä, jolla voit nollata salasanasi.",
"resetPassword.button.back": "Takaisin kirjautumiseen",
"resetPassword.text.resetPassword": "Nollaa salasana",
"resetPassword.text.enterNewPassword": "Anna uusi salasana",
"resetPassword.input.password": "Uusi salasana",
"resetPassword.notify.passwordReset": "Salasanan nollaus onnistui.",
// /account
"account.title": "Oma tIli",
"account.card.info.title": "Tilin tiedot",
"account.card.info.username": "Käyttäjätunnus",
"account.card.info.email": "Sähköposti",
"account.notify.info.success": "Tili päivitetty onnistuneesti",
"account.card.password.title": "Salasana",
"account.card.password.old": "Vanha salasana",
"account.card.password.new": "Uusi salasana",
"account.notify.password.success": "Salasana vaihdettu",
"account.card.security.title": "Turvallisuus",
"account.card.security.totp.enable.description": "Anna nykyinen salasanasi aloittaaksesi TOTP käytön",
"account.card.security.totp.disable.description": "Syötä nykyinen salasanasi poistaaksesi TOTP käytöstä",
"account.card.security.totp.button.start": "Aloita",
"account.modal.totp.title": "Ota Käyttöön TOTP",
"account.modal.totp.step1": "Vaihe 1: Lisää todentaja",
"account.modal.totp.step2": "Vaihe 2: Vahvista koodisi",
"account.modal.totp.enterManually": "Syötä manuaalisesti",
"account.modal.totp.code": "Koodi",
"account.modal.totp.clickToCopy": "Klikkaa kopioidaksesi",
"account.modal.totp.verify": "Vahvista",
"account.notify.totp.disable": "TOTP poistettu käytöstä",
"account.notify.totp.enable": "TOTP otettu käyttöön onnistuneesti",
"account.card.language.title": "Kieli",
"account.card.language.description": "Projekti on yhteisön kääntämä. Jotkut kielet saattavat olla puutteellisia.",
"account.card.color.title": "Väriteema",
// ThemeSwitcher.tsx
"account.theme.dark": "Tumma",
"account.theme.light": "Vaalea",
"account.theme.system": "Järjestelmä",
"account.button.delete": "Poista tili",
"account.modal.delete.title": "Poista tili",
"account.modal.delete.description": "Haluatko varmasti poistaa tilisi mukaan lukien kaikki aktiiviset jaetut tiedostot?",
// END /account
// /account/shares
"account.shares.title": "Minun jakaukset",
"account.shares.title.empty": "Täällä on tyhjää 👀",
"account.shares.description.empty": "Sinulla ei ole jaettuja tiedostoja.",
"account.shares.button.create": "Luo yksi",
"account.shares.info.title": "Jaetun tiedot",
"account.shares.table.id": "ID",
"account.shares.table.name": "Nimi",
"account.shares.table.description": "Kuvaus",
"account.shares.table.visitors": "Vierailijat",
"account.shares.table.expiresAt": "Vanhenee",
"account.shares.table.createdAt": "Luotu",
"account.shares.table.size": "Koko",
"account.shares.modal.share-informations": "Jaetun tiedot",
"account.shares.modal.share-link": "Jaa linkki",
"account.shares.modal.delete.title": "Poista jaettu {share}",
"account.shares.modal.delete.description": "Haluatko todella poistaa tämän jaetun tiedoston/ot?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Takaperin jaetut",
"account.reverseShares.description": "Käänteisen jaon avulla voit luoda ainutlaatuisen URL-osoitteen, jonka avulla ulkoiset käyttäjät voivat luoda jaon.",
"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",
"account.reverseShares.modal.expiration.hour-singular": "Tunti",
"account.reverseShares.modal.expiration.hour-plural": "Tuntia",
"account.reverseShares.modal.expiration.day-singular": "Päivä",
"account.reverseShares.modal.expiration.day-plural": "Päivää",
"account.reverseShares.modal.expiration.week-singular": "Viikko",
"account.reverseShares.modal.expiration.week-plural": "Viikkoa",
"account.reverseShares.modal.expiration.month-singular": "Kuukausi",
"account.reverseShares.modal.expiration.month-plural": "Kuukautta",
"account.reverseShares.modal.expiration.year-singular": "Vuosi",
"account.reverseShares.modal.expiration.year-plural": "Vuotta",
"account.reverseShares.modal.max-size.label": "Suurin tiedostonkoko",
"account.reverseShares.modal.send-email": "Lähetä sähköposti-ilmoitus",
"account.reverseShares.modal.send-email.description": "Lähetä sähköpostiilmoitus kun jako on luotu tällä käänteisellä jakolinkillä.",
"account.reverseShares.modal.max-use.label": "Käyttökertoja enintään",
"account.reverseShares.modal.max-use.description": "Enimmäismäärä kertoja, joilla tämä URL-osoite voidaan käyttää joita luomiseen.",
"account.reverseShare.never-expires": "Tämä käänteinen jako ei koskaan vanhene.",
"account.reverseShare.expires-on": "Tämä käänteinen jako vanhenee kun on {expiration}.",
"account.reverseShares.table.no-shares": "Ei vielä luotuja jakoja",
"account.reverseShares.table.count.singular": "jaa",
"account.reverseShares.table.count.plural": "jaot",
"account.reverseShares.table.shares": "Jaot",
"account.reverseShares.table.remaining": "Jäljellä olevat käyttökerrat",
"account.reverseShares.table.max-size": "Suurin tiedostonkoko",
"account.reverseShares.table.expires": "Vanhenee",
"account.reverseShares.modal.reverse-share-link": "Takaperin jaetun jaon linkki",
"account.reverseShares.modal.delete.title": "Poista käänteinen jako",
"account.reverseShares.modal.delete.description": "Haluatko varmasti poistaa tämän käänteisen jaon? Jos kyllä, myös siihen liittyvät jaot poistetaan.",
// END /account/reverseShares
// /admin
"admin.title": "Ylläpito",
"admin.button.users": "Käyttäjien Hallinta",
"admin.button.config": "Asetukset",
"admin.version": "Versio",
// END /admin
// /admin/users
"admin.users.title": "Käyttäjien Hallinta",
"admin.users.table.username": "Käyttäjätunnus",
"admin.users.table.email": "Sähköposti",
"admin.users.table.admin": "Ylläpitäjä",
"admin.users.edit.update.title": "Päivitä käyttäjä {username}",
"admin.users.edit.update.admin-privileges": "Ylläpitäjän oikeudet",
"admin.users.edit.update.change-password.title": "Vaihda salasana",
"admin.users.edit.update.change-password.field": "Uusi salasana",
"admin.users.edit.update.change-password.button": "Tallenna uusi salasana",
"admin.users.edit.update.notify.password.success": "Salasana vaihdettu",
"admin.users.edit.delete.title": "Poista käyttäjä {username}",
"admin.users.edit.delete.description": "Haluatko varmasti poistaa tämän käyttäjän ja kaikki hänen jaot?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Luo käyttäjä",
"admin.users.modal.create.username": "Käyttäjätunnus",
"admin.users.modal.create.email": "Sähköposti",
"admin.users.modal.create.password": "Salasana",
"admin.users.modal.create.manual-password": "Aseta salasana manuaalisesti",
"admin.users.modal.create.manual-password.description": "Jos ei ole valittuna, käyttäjä saa sähköpostiviestin, jossa on linkki, joka määrittää heidän salasanansa.",
"admin.users.modal.create.admin": "Ylläpitäjän oikeudet",
"admin.users.modal.create.admin.description": "Jos valittu, käyttäjä voi käyttää hallintapaneelia.",
// END /admin/users
// /upload
"upload.title": "Pilvetä",
"upload.notify.generic-error": "Kohdattiin odottamaton virhe jaon luomisessa.",
"upload.notify.count-failed": "{count} tiedostoa ei voitu ladata. Yritetään uudelleen.",
// Dropzone.tsx
"upload.dropzone.title": "Pilvetä tiedostoja",
"upload.dropzone.description": "Vedä ja pudota tiedostot tähän aloittaaksesi jakamisen. Voimme hyväksyä vain tiedostot, jotka ovat yhteensä alle {maxSize}.",
"upload.dropzone.notify.file-too-big": "Tiedostojen enimmäiskoko ylittää {maxSize} -arvon enimmäismäärän.",
// FileList.tsx
"upload.filelist.name": "Nimi",
"upload.filelist.size": "Koko",
// showCreateUploadModal.tsx
"upload.modal.title": "Luo Jako",
"upload.modal.link.error.invalid": "Voi sisältää vain kirjaimia, numeroita, alaviivoja ja väliviivoja",
"upload.modal.link.error.taken": "Tämä linkki on jo käytössä",
"upload.modal.not-signed-in": "Et ole kirjautunut sisään",
"upload.modal.not-signed-in-description": "Et voi poistaa jakoasi manuaalisesti ja tarkastella kävijöiden määrää.",
"upload.modal.expires.never": "ei koskaan",
"upload.modal.expires.never-long": "Ei vanhene koskaan",
"upload.modal.link.label": "Linkki",
"upload.modal.expires.label": "Vanhentuminen",
"upload.modal.expires.minute-singular": "Minuutti",
"upload.modal.expires.minute-plural": "Minuuttia",
"upload.modal.expires.hour-singular": "Tunti",
"upload.modal.expires.hour-plural": "Tuntia",
"upload.modal.expires.day-singular": "Päivä",
"upload.modal.expires.day-plural": "Päivää",
"upload.modal.expires.week-singular": "Viikko",
"upload.modal.expires.week-plural": "Viikkoa",
"upload.modal.expires.month-singular": "Kuukausi",
"upload.modal.expires.month-plural": "Kuukautta",
"upload.modal.expires.year-singular": "Vuosi",
"upload.modal.expires.year-plural": "Vuotta",
"upload.modal.accordion.description.title": "Kuvaus",
"upload.modal.accordion.description.placeholder": "Huomautus tämän jaon vastaanottajille",
"upload.modal.accordion.email.title": "Sähköpostin vastaanottajat",
"upload.modal.accordion.email.placeholder": "Syötä sähköpostin vastaanottajat",
"upload.modal.accordion.email.invalid-email": "Virheellinen sähköpostiosoite",
"upload.modal.accordion.security.title": "Turvallisuusasetukset",
"upload.modal.accordion.security.password.label": "Salasanasuojaus",
"upload.modal.accordion.security.password.placeholder": "Ei salasanaa",
"upload.modal.accordion.security.max-views.label": "Näkymien enimmäismäärä",
"upload.modal.accordion.security.max-views.placeholder": "Ei rajoitusta",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "Tämä käänteinen jako ei koskaan vanhene.",
"upload.modal.completed.expires-on": "Tämä käänteinen jako vanhenee kun on {expiration}.",
"upload.modal.completed.share-ready": "Jako valmiina",
// END /upload
// /share/[id]
"share.title": "Jaa {shareId}",
"share.description": "Katso, mitä olen jakanut kanssasi!",
"share.error.visitor-limit-exceeded.title": "Vierailijoiden raja ylitetty",
"share.error.visitor-limit-exceeded.description": "Tämän jaon kävijäraja on ylittynyt.",
"share.error.removed.title": "Jako poistettu",
"share.error.not-found.title": "Jakoa ei löydetty",
"share.error.not-found.description": "Etsimääsi sivua ei ole olemassa.",
"share.modal.password.title": "Salasana vaaditaan",
"share.modal.password.description": "Päästäksesi käsiksi tähän jakoon anna jaon salasana.",
"share.modal.password": "Salasana",
"share.modal.error.invalid-password": "Virheellinen salasana",
"share.button.download-all": "Lataa kaikki",
"share.notify.download-all-preparing": "Jako on valmistumassa. Yritä uudelleen muutaman minuutin kuluttua.",
"share.modal.file-link": "Tiedoston linkki",
"share.table.name": "Nimi",
"share.table.size": "Koko",
"share.modal.file-preview.error.not-supported.title": "Esikatselua ei tuettu",
"share.modal.file-preview.error.not-supported.description": "Esikatselua thise tiedostotyypille ei tueta. Ole hyvä ja lataa tiedosto nähdäksesi sen.",
// END /share/[id]
// /admin/config
"admin.config.title": "Asetukset",
"admin.config.category.general": "Yleiset",
"admin.config.category.share": "Jako",
"admin.config.category.email": "Sähköposti",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "Sovelluksen nimi",
"admin.config.general.app-name.description": "Sovelluksen nimi",
"admin.config.general.app-url": "Sovelluksen URL",
"admin.config.general.app-url.description": "Millä URL-osoitteella Pingvin Share on saatavilla",
"admin.config.general.show-home-page": "Näytä kotisivu",
"admin.config.general.show-home-page.description": "Näytetäänkö kotisivu vai ei",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description": "Muuta logoa lataamalla uusi kuva. Kuvan on oltava PNG ja sen on oltava formaatti 1:1.",
"admin.config.general.logo.placeholder": "Valitse kuva",
"admin.config.email.enable-share-email-recipients": "Salli sähköpostin vastaanottajien jakaminen",
"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 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.",
"admin.config.email.reverse-share-message": "Käänteisen jakoviestin viesti",
"admin.config.email.reverse-share-message.description": "Viesti joka lähetetään kun joku loi jaon käänteisen jakolinkin kanssa. {shareUrl} korvataan luojan nimellä ja jaon URL:lla.",
"admin.config.email.reset-password-subject": "Nollaa salasanan aihe",
"admin.config.email.reset-password-subject.description": "Sähköpostin aihe, joka lähetetään kun käyttäjä pyytää salasanan palauttamista.",
"admin.config.email.reset-password-message": "Nollaa salasanan viesti",
"admin.config.email.reset-password-message.description": "Viesti joka lähetetään kun käyttäjä pyytää salasanan nollausta. {url} korvataan nollaussalasanan URL-osoitteella.",
"admin.config.email.invite-subject": "Kutsun aihe",
"admin.config.email.invite-subject.description": "Sähköpostin aihe, mikä lähetetään kun ylläpitäjä kutsuu käyttäjää.",
"admin.config.email.invite-message": "Kutsun viesti",
"admin.config.email.invite-message.description": "Viesti mikä lähetetään kuin yp invaa käyttäjän. {url} korvataan kutsuosoitteella ja {password} salasanalla.",
"admin.config.share.allow-registration": "Salli rekisteröinti",
"admin.config.share.allow-registration.description": "Onko rekisteröinti sallittu",
"admin.config.share.allow-unauthenticated-shares": "Salli anonyymit jaot",
"admin.config.share.allow-unauthenticated-shares.description": "Voiko tunnistamattomat käyttäjät luoda jakoja",
"admin.config.share.max-size": "Maksimikoko",
"admin.config.share.max-size.description": "Jaon enimmäiskoko tavuissa (bytes)",
"admin.config.share.zip-compression-level": "Zip puristustaso",
"admin.config.share.zip-compression-level.description": "Säädä tasoa tiedoston koon ja pakkausnopeuden välillä. Kelvolliset arvot vaihtelevat 09, 0 ei puristusta ja 9 on suurin puristusvoima. ",
"admin.config.smtp.enabled": "Käytössä",
"admin.config.smtp.enabled.description": "Onko SMTP käytössä. Aseta tämä todeksi vain, jos olet syöttänyt SMTP-palvelimen isäntän, portin, sähköpostin, käyttäjän ja salasanan.",
"admin.config.smtp.host": "Isäntä",
"admin.config.smtp.host.description": "SMTP palvelimen isäntä",
"admin.config.smtp.port": "Portti",
"admin.config.smtp.port.description": "SMTP palvelimen portti",
"admin.config.smtp.email": "Sähköposti",
"admin.config.smtp.email.description": "Sähköpostiosoite, josta sähköpostit on lähetetty",
"admin.config.smtp.username": "Käyttäjätunnus",
"admin.config.smtp.username.description": "SMTP palvelimen käyttäjänimi",
"admin.config.smtp.password": "Salasana",
"admin.config.smtp.password.description": "SMTP palvelimen salasana",
"admin.config.smtp.button.test": "Lähetä testisähköposti",
// 404
"404.description": "Hups tätä sivua ei ole olemassa.",
"404.button.home": "Tuo minut takaisin kotiin",
// Common translations
"common.button.save": "Tallenna",
"common.button.create": "Luo",
"common.button.submit": "Lähetä",
"common.button.delete": "Poista",
"common.button.cancel": "Peruuta",
"common.button.confirm": "Vahvista",
"common.button.disable": "Poista käytöstä",
"common.button.share": "Jako",
"common.button.generate": "Luo",
"common.button.done": "Valmis",
"common.text.link": "Linkki",
"common.text.or": "tai",
"common.button.go-back": "Takaisin",
"common.notify.copied": "Linkki kopioitiin leikepöydälle",
"common.success": "Suoritettu",
"common.error": "Virhe",
"common.error.unknown": "Tapahtui tuntematon virhe",
"common.error.invalid-email": "Virheellinen sähköpostiosoite",
"common.error.too-short": "Täytyy olla vähintään {length} merkkiä",
"common.error.too-long": "Täytyy olla enintään {length} merkkiä",
"common.error.exact-length": "On oltava tarkasti {length} merkkiä pitkä",
"common.error.invalid-number": "Pitää olla luku",
"common.error.field-required": "Tämä kenttä on pakollinen"
};

View File

@@ -1,324 +0,0 @@
export default {
// Navbar
"navbar.upload": "Envoyer",
"navbar.signin": "Se connecter",
"navbar.home": "Accueil",
"navbar.signup": "S'inscrire",
"navbar.links.shares": "Mes partages",
"navbar.links.reverse": "Partages inversés",
"navbar.avatar.account": "Mon compte",
"navbar.avatar.admin": "Administration",
"navbar.avatar.signout": "Se déconnecter",
// END navbar
// /
"home.title": "Une plateforme de partage de fichier <h>auto-hébergée</h>.",
"home.description": "Voulez-vous vraiment remettre vos fichiers personnels dans les mains de tiers comme WeTransfer ?",
"home.bullet.a.name": "Auto-hébergé",
"home.bullet.a.description": "Hébergez Pingvin Share sur votre propre machine.",
"home.bullet.b.name": "Confidentialité",
"home.bullet.b.description": "Vos fichiers sont vos fichiers et ne devraient jamais être mis entre les mains de tiers.",
"home.bullet.c.name": "Aucune rébarbative limite de taille",
"home.bullet.c.description": "Téléchargez des fichiers volumineux que vous le souhaitez. Seul votre disque dur est la limite.",
"home.button.start": "Commencer",
"home.button.source": "Code source",
// END /
// /auth/signin
"signin.title": "Content de vous revoir",
"signin.description": "Pas encore de compte ?",
"signin.button.signup": "S'inscrire",
"signin.input.email-or-username": "Courriel ou pseudo",
"signin.input.email-or-username.placeholder": "Votre courriel ou pseudo",
"signin.input.password": "Mot de passe",
"signin.input.password.placeholder": "Votre mot de passe",
"signin.button.submit": "Se connecter",
"signIn.notify.totp-required.title": "Une authentification à deux facteurs est requise",
"signIn.notify.totp-required.description": "Veuillez entrer votre code d'authentification à deux facteurs",
// END /auth/signin
// /auth/signup
"signup.title": "Créer un compte",
"signup.description": "Vous avez déjà un compte ?",
"signup.button.signin": "Se connecter",
"signup.input.username": "Pseudo",
"signup.input.username.placeholder": "Votre pseudo",
"signup.input.email": "Adresse email",
"signup.input.email.placeholder": "Votre adresse email",
"signup.button.submit": "Commençons",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Mot de passe oublié ?",
"resetPassword.description": "Saisissez votre email pour réinitialiser votre mot de passe.",
"resetPassword.notify.success": "Un email a été envoyé avec un lien pour réinitialiser votre mot de passe.",
"resetPassword.button.back": "Retour à la page de connexion",
"resetPassword.text.resetPassword": "Réinitialiser le mot de passe",
"resetPassword.text.enterNewPassword": "Saisissez votre nouveau mot de passe",
"resetPassword.input.password": "Nouveau mot de passe",
"resetPassword.notify.passwordReset": "Votre mot de passe a bien été réinitialisé.",
// /account
"account.title": "Mon compte",
"account.card.info.title": "Détails du compte",
"account.card.info.username": "Pseudo",
"account.card.info.email": "Adresse email",
"account.notify.info.success": "Compte mis à jour avec succès",
"account.card.password.title": "Mot de passe",
"account.card.password.old": "Ancien mot de passe",
"account.card.password.new": "Nouveau mot de passe",
"account.notify.password.success": "Le mot de passe a été modifié avec succès",
"account.card.security.title": "Sécurité",
"account.card.security.totp.enable.description": "Entrez votre mot de passe actuel pour commencer à activer TOTP",
"account.card.security.totp.disable.description": "Entrez votre mot de passe pour désactiver TOTP",
"account.card.security.totp.button.start": "Commencer",
"account.modal.totp.title": "Activer TOTP",
"account.modal.totp.step1": "Étape 1 : Ajouter votre authentificateur",
"account.modal.totp.step2": "Étape 2 : Valider votre code",
"account.modal.totp.enterManually": "Saisir manuellement",
"account.modal.totp.code": "Code",
"account.modal.totp.clickToCopy": "Cliquez pour copier",
"account.modal.totp.verify": "Vérifier",
"account.notify.totp.disable": "TOTP désactivé",
"account.notify.totp.enable": "TOTP activé",
"account.card.language.title": "Langue",
"account.card.language.description": "Le projet est traduit par la communauté. Certaines traductions peuvent être incomplètes.",
"account.card.color.title": "Thème de couleurs",
// ThemeSwitcher.tsx
"account.theme.dark": "Sombre",
"account.theme.light": "Clair",
"account.theme.system": "Système",
"account.button.delete": "Supprimer le compte",
"account.modal.delete.title": "Supprimer le compte",
"account.modal.delete.description": "Voulez-vous vraiment supprimer votre compte, y compris tous vos partages actifs ?",
// END /account
// /account/shares
"account.shares.title": "Mes partages",
"account.shares.title.empty": "Il n'y a rien ici 👀",
"account.shares.description.empty": "Vous navez aucun partage.",
"account.shares.button.create": "Créez-en un",
"account.shares.info.title": "Détails du partage",
"account.shares.table.id": "ID",
"account.shares.table.name": "Nom",
"account.shares.table.description": "Description",
"account.shares.table.visitors": "Visiteurs",
"account.shares.table.expiresAt": "Expire le",
"account.shares.table.createdAt": "Créé le",
"account.shares.table.size": "Taille",
"account.shares.modal.share-informations": "Détails du partage",
"account.shares.modal.share-link": "Lien de partage",
"account.shares.modal.delete.title": "Supprimer le partage {share}",
"account.shares.modal.delete.description": "Voulez-vous vraiment supprimer ce partage ?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Partages inversés",
"account.reverseShares.description": "Un partage inversé vous permet de générer une URL unique qui permet à des utilisateurs externes de créer un partage.",
"account.reverseShares.title.empty": "Il n'y a rien ici 👀",
"account.reverseShares.description.empty": "Vous n'avez aucun partage inversé.",
// 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",
"account.reverseShares.modal.expiration.hour-singular": "Heure",
"account.reverseShares.modal.expiration.hour-plural": "Heures",
"account.reverseShares.modal.expiration.day-singular": "Jour",
"account.reverseShares.modal.expiration.day-plural": "Jours",
"account.reverseShares.modal.expiration.week-singular": "Semaine",
"account.reverseShares.modal.expiration.week-plural": "Semaines",
"account.reverseShares.modal.expiration.month-singular": "Mois",
"account.reverseShares.modal.expiration.month-plural": "Mois",
"account.reverseShares.modal.expiration.year-singular": "An",
"account.reverseShares.modal.expiration.year-plural": "Ans",
"account.reverseShares.modal.max-size.label": "Taille maximale du partage",
"account.reverseShares.modal.send-email": "Envoyer un email de notification",
"account.reverseShares.modal.send-email.description": "Envoyer une notification par email lorsqu'un partage est créé depuis ce partage inversé.",
"account.reverseShares.modal.max-use.label": "Nombre d'utilisation max",
"account.reverseShares.modal.max-use.description": "Le nombre maximal de fois que cette URL peut être utilisée pour créer un partage.",
"account.reverseShare.never-expires": "Ce partage inversé n'expirera jamais.",
"account.reverseShare.expires-on": "Ce partage inversé expirera le {expiration}.",
"account.reverseShares.table.no-shares": "Aucun partage créé pour le moment",
"account.reverseShares.table.count.singular": "partage",
"account.reverseShares.table.count.plural": "partages",
"account.reverseShares.table.shares": "Partages",
"account.reverseShares.table.remaining": "Utilisations restantes",
"account.reverseShares.table.max-size": "Taille maximale du partage",
"account.reverseShares.table.expires": "Expire dans",
"account.reverseShares.modal.reverse-share-link": "Lien du partage inversé",
"account.reverseShares.modal.delete.title": "Supprimer le partage inversé",
"account.reverseShares.modal.delete.description": "Voulez-vous vraiment supprimer ce partage inversé ? Si vous le faites, les partages qu'il contient seront également supprimés.",
// END /account/reverseShares
// /admin
"admin.title": "Administration",
"admin.button.users": "Gestion des utilisateurs",
"admin.button.config": "Paramètres",
"admin.version": "Version",
// END /admin
// /admin/users
"admin.users.title": "Gestion des utilisateurs",
"admin.users.table.username": "Pseudo",
"admin.users.table.email": "Adresse email",
"admin.users.table.admin": "Admin",
"admin.users.edit.update.title": "Modifier l'utilisateur {username}",
"admin.users.edit.update.admin-privileges": "Privilèges admin",
"admin.users.edit.update.change-password.title": "Changer le mot de passe",
"admin.users.edit.update.change-password.field": "Nouveau mot de passe",
"admin.users.edit.update.change-password.button": "Enregistrer le nouveau mot de passe",
"admin.users.edit.update.notify.password.success": "Le mot de passe a été modifié",
"admin.users.edit.delete.title": "Supprimer l'utilisateur {username}",
"admin.users.edit.delete.description": "Voulez-vous vraiment supprimer cet utilisateur et toutes ses partages ?",
// showCreateUserModal.tsx
"admin.users.modal.create.title": "Créer un utilisateur",
"admin.users.modal.create.username": "Surnom",
"admin.users.modal.create.email": "Email",
"admin.users.modal.create.password": "Mot de passe",
"admin.users.modal.create.manual-password": "Définir le mot de passe manuellement",
"admin.users.modal.create.manual-password.description": "S'il n'est pas coché, l'utilisateur recevra un email avec un lien pour définir son mot de passe.",
"admin.users.modal.create.admin": "Privilèges admin",
"admin.users.modal.create.admin.description": "Si coché, l'utilisateur pourra accéder au panneau d'administration.",
// END /admin/users
// /upload
"upload.title": "Envoyer",
"upload.notify.generic-error": "Une erreur est survenue durant le traitement de votre partage.",
"upload.notify.count-failed": "{count} fichier(s) n'a(ont) pas pu être envoyé(s). Veuillez réessayer.",
// Dropzone.tsx
"upload.dropzone.title": "Téléverser des fichiers",
"upload.dropzone.description": "Glissez-déposez des fichiers ici pour commencer votre partage. Ils ne peuvent avoir une taille supérieur à {maxSize} au total.",
"upload.dropzone.notify.file-too-big": "Vos fichiers dépassent la taille maximale de {maxSize}.",
// FileList.tsx
"upload.filelist.name": "Nom",
"upload.filelist.size": "Taille",
// showCreateUploadModal.tsx
"upload.modal.title": "Créer un partage",
"upload.modal.link.error.invalid": "Ne peut contenir que des lettres, des chiffres, des tirets bas et des traits d'union",
"upload.modal.link.error.taken": "Ce lien est déjà utilisé",
"upload.modal.not-signed-in": "Vous n'êtes pas connecté",
"upload.modal.not-signed-in-description": "Vous ne pourrez pas supprimer votre partage manuellement et afficher le nombre de visiteurs.",
"upload.modal.expires.never": "jamais",
"upload.modal.expires.never-long": "N'expire jamais",
"upload.modal.link.label": "Lien",
"upload.modal.expires.label": "Expiration",
"upload.modal.expires.minute-singular": "Minute",
"upload.modal.expires.minute-plural": "Minutes",
"upload.modal.expires.hour-singular": "Heure",
"upload.modal.expires.hour-plural": "Heures",
"upload.modal.expires.day-singular": "Jour",
"upload.modal.expires.day-plural": "Jours",
"upload.modal.expires.week-singular": "Semaine",
"upload.modal.expires.week-plural": "Semaines",
"upload.modal.expires.month-singular": "Mois",
"upload.modal.expires.month-plural": "Mois",
"upload.modal.expires.year-singular": "An",
"upload.modal.expires.year-plural": "Ans",
"upload.modal.accordion.description.title": "Description",
"upload.modal.accordion.description.placeholder": "Note pour les destinataires de ce partage",
"upload.modal.accordion.email.title": "Adresse courriel des destinataires",
"upload.modal.accordion.email.placeholder": "Saisir les destinataires de ce partage",
"upload.modal.accordion.email.invalid-email": "Adresse email invalide",
"upload.modal.accordion.security.title": "Options de sécurité",
"upload.modal.accordion.security.password.label": "Protection par mot de passe",
"upload.modal.accordion.security.password.placeholder": "Aucun mot de passe",
"upload.modal.accordion.security.max-views.label": "Nombre de vues maximum",
"upload.modal.accordion.security.max-views.placeholder": "Aucune limite",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "Ce partage n'expirera jamais.",
"upload.modal.completed.expires-on": "Ce partage expirera le {expiration}.",
"upload.modal.completed.share-ready": "Partage prêt",
// END /upload
// /share/[id]
"share.title": "Partage {shareId}",
"share.description": "Regardez ce que j'ai partagé !",
"share.error.visitor-limit-exceeded.title": "Limite de visiteurs dépassée",
"share.error.visitor-limit-exceeded.description": "La limite de visiteurs de ce partage a été dépassée.",
"share.error.removed.title": "Partage supprimé",
"share.error.not-found.title": "Partage introuvable",
"share.error.not-found.description": "Le partage que vous cherchez n'existe pas.",
"share.modal.password.title": "Mot de passe requis",
"share.modal.password.description": "Pour accéder à ce partage, veuillez entrer le mot de passe du partage.",
"share.modal.password": "Mot de passe",
"share.modal.error.invalid-password": "Mot de passe incorrect",
"share.button.download-all": "Télécharger tout",
"share.notify.download-all-preparing": "Le partage est en préparation. Réessayez dans quelques minutes.",
"share.modal.file-link": "Lien du fichier",
"share.table.name": "Nom",
"share.table.size": "Taille",
"share.modal.file-preview.error.not-supported.title": "Aperçu non supporté",
"share.modal.file-preview.error.not-supported.description": "Un aperçu pour ce type de fichier n'est pas pris en charge. Veuillez télécharger le fichier pour le voir.",
// END /share/[id]
// /admin/config
"admin.config.title": "Paramètres",
"admin.config.category.general": "Général",
"admin.config.category.share": "Partage",
"admin.config.category.email": "Courriel",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "Nom de l'appli",
"admin.config.general.app-name.description": "Le nom de l'application",
"admin.config.general.app-url": "URL de lappli",
"admin.config.general.app-url.description": "Depuis quel URL le partage Pingvin est disponible",
"admin.config.general.show-home-page": "Afficher la page d'accueil",
"admin.config.general.show-home-page.description": "Afficher ou non la page d'accueil",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description": "Changez de logo en envoyant une nouvelle image. L'image doit être au format PNG et doit avoir un ratio 1:1.",
"admin.config.general.logo.placeholder": "Sélectionner une image",
"admin.config.email.enable-share-email-recipients": "Autoriser le partage par courriel",
"admin.config.email.enable-share-email-recipients.description": "Permet d'envoyer le lien du partage par courriel. N'activez cette option que si vous avez activé SMTP.",
"admin.config.email.share-recipients-subject": "Sujet d'un partage",
"admin.config.email.share-recipients-subject.description": "Intitulé du courriel envoyé aux destinataires d'un partage.",
"admin.config.email.share-recipients-message": "Message d'un partage",
"admin.config.email.share-recipients-message.description": "Contenu du courriel qui est envoyé aux destinataires du partage. Variables possibles :\n• {creator} : Le pseudo de l'auteur du partage\n• {shareUrl} : L'URL du partage\n• {desc} : La description du partage\n• {expires} : La date d'expiration du partage\nLes variables seront remplacées par leur valeur réelle.",
"admin.config.email.reverse-share-subject": "Sujet d'un partage inversé",
"admin.config.email.reverse-share-subject.description": "Intitulé du courriel envoyé lorsque quelqu'un a partagé des fichiers depuis votre partage inversé.",
"admin.config.email.reverse-share-message": "Message du partage inversé",
"admin.config.email.reverse-share-message.description": "Contenu du courriel envoyé lorsque quelqu'un partage des fichiers depuis votre partage inversé. {shareUrl} sera remplacé par le nom du créateur et l'URL de partage.",
"admin.config.email.reset-password-subject": "Sujet d'une réinitialisation du mot de passe",
"admin.config.email.reset-password-subject.description": "Intitulé du courriel envoyé lorsqu'un utilisateur demande une réinitialisation de son mot de passe.",
"admin.config.email.reset-password-message": "Message de réinitialisation du mot de passe",
"admin.config.email.reset-password-message.description": "Contenu du courriel envoyé lorsqu'un utilisateur demande à réinitialiser son mot de passe. {url} sera remplacé par l'URL de réinitialisation du mot de passe.",
"admin.config.email.invite-subject": "Sujet d'une invitation",
"admin.config.email.invite-subject.description": "Intitulé du courriel envoyé lorsqu'un administrateur invite un utilisateur.",
"admin.config.email.invite-message": "Message de l'invitation",
"admin.config.email.invite-message.description": "Contenu du courriel envoyé lorsqu'un administrateur invite un utilisateur. {url} sera remplacé par l'URL d'invitation et {password} par le mot de passe.",
"admin.config.share.allow-registration": "Autoriser les inscriptions",
"admin.config.share.allow-registration.description": "Permet aux visiteurs de créer un compte.",
"admin.config.share.allow-unauthenticated-shares": "Autoriser les partages anonymes",
"admin.config.share.allow-unauthenticated-shares.description": "Autorise des utilisateurs non authentifiés à créer des partages",
"admin.config.share.max-size": "Taille max",
"admin.config.share.max-size.description": "Taille maximale du partage en octets",
"admin.config.share.zip-compression-level": "Niveau de compression",
"admin.config.share.zip-compression-level.description": "Ajustez le niveau pour trouver l'équilibre entre la taille du fichier et la vitesse de compression. Les valeurs valides vont de 0 à 9, 0 étant sans compression et 9 étant la compression maximale. ",
"admin.config.smtp.enabled": "Activer",
"admin.config.smtp.enabled.description": "Active SMTP. Activez ceci uniquement si vous avez saisi l'hôte, le port, le courriel, l'utilisateur et le mot de passe de votre serveur SMTP.",
"admin.config.smtp.host": "Hôte",
"admin.config.smtp.host.description": "Nom du serveur SMTP",
"admin.config.smtp.port": "Port",
"admin.config.smtp.port.description": "Port du serveur SMTP",
"admin.config.smtp.email": "Adresse email",
"admin.config.smtp.email.description": "Adresse à partir de laquelle les emails sont envoyés",
"admin.config.smtp.username": "Nom d'utilisateur",
"admin.config.smtp.username.description": "Nom d'utilisateur du serveur SMTP",
"admin.config.smtp.password": "Mot de passe",
"admin.config.smtp.password.description": "Mot de passe du serveur SMTP",
"admin.config.smtp.button.test": "Envoyer un email de test",
// 404
"404.description": "Désolé, mais cette page nexiste pas.",
"404.button.home": "Retour à l'accueil",
// Common translations
"common.button.save": "Sauvegarder",
"common.button.create": "Créer",
"common.button.submit": "Envoyer",
"common.button.delete": "Supprimer",
"common.button.cancel": "Annuler",
"common.button.confirm": "Confirmer",
"common.button.disable": "Désactiver",
"common.button.share": "Partager",
"common.button.generate": "Générer",
"common.button.done": "Terminer",
"common.text.link": "Lien",
"common.text.or": "ou",
"common.button.go-back": "Précédent",
"common.notify.copied": "Votre lien a été copié dans le presse-papiers",
"common.success": "Opération réussie",
"common.error": "Erreur",
"common.error.unknown": "Une erreur inconnue est survenue",
"common.error.invalid-email": "Adresse courriel invalide",
"common.error.too-short": "Doit comporter au moins {length} caractères",
"common.error.too-long": "Doit comporter au plus {length} caractères",
"common.error.exact-length": "Doit comporter exactement {length} caractères",
"common.error.invalid-number": "Doit être un nombre",
"common.error.field-required": "Ce champ est obligatoire"
};

View File

@@ -1,324 +0,0 @@
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": "<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": "おかえりなさい",
"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": "Your password has been reset successfully.",
// /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",
// 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?",
// 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.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?",
// 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.",
// 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",
"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.",
// END /account/reverseShares
// /admin
"admin.title": "Administration",
"admin.button.users": "User management",
"admin.button.config": "Configuration",
"admin.version": "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?",
// 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.",
// 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.",
// 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}.",
// FileList.tsx
"upload.filelist.name": "Name",
"upload.filelist.size": "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",
// 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": "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.",
// 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.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",
// 404
"404.description": "Oops this page doesn't exist.",
"404.button.home": "Bring me back 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"
};

View File

@@ -1,324 +0,0 @@
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

@@ -1,324 +0,0 @@
export default {
// Navbar
"navbar.upload": "Carregar",
"navbar.signin": "Iniciar sessão",
"navbar.home": "Início",
"navbar.signup": "Registar-se",
"navbar.links.shares": "Meus compartilhamentos",
"navbar.links.reverse": "Compartilhamentos reversos",
"navbar.avatar.account": "Minha conta",
"navbar.avatar.admin": "Administração",
"navbar.avatar.signout": "Terminar sessão",
// END navbar
// /
"home.title": "Uma plataforma de compartilhamento de arquivos <h>auto-hospedada</h>.",
"home.description": "Deseja realmente dar os seus arquivos pessoais na mão de terceiros como o WeTransfer?",
"home.bullet.a.name": "Auto-Hospedado",
"home.bullet.a.description": "Hospede o Pingvin Share em sua própria máquina.",
"home.bullet.b.name": "Privacidade",
"home.bullet.b.description": "Seus arquivos são seus arquivos e nunca devem cair nas mãos de terceiros.",
"home.bullet.c.name": "Sem limite de tamanho de arquivo irritante",
"home.bullet.c.description": "Carregue os arquivos grandes que desejar. Apenas o seu disco rígido será o seu limite.",
"home.button.start": "Começar",
"home.button.source": "Código-fonte",
// END /
// /auth/signin
"signin.title": "Bem-vindo de volta",
"signin.description": "Ainda não tem uma conta?",
"signin.button.signup": "Registo",
"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",
"signin.button.submit": "Iniciar sessão",
"signIn.notify.totp-required.title": "Autenticação de dois fatores necessária",
"signIn.notify.totp-required.description": "Insira seu código de autenticação de dois fatores",
// END /auth/signin
// /auth/signup
"signup.title": "Criar uma conta",
"signup.description": "Já tem uma conta?",
"signup.button.signin": "Iniciar sessão",
"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",
"signup.button.submit": "Vamos começar",
// END /auth/signup
// /auth/reset-password
"resetPassword.title": "Esqueceu a sua senha?",
"resetPassword.description": "Insira o seu e-mail para redefinir a sua senha.",
"resetPassword.notify.success": "Um e-mail foi enviado com um link para redefinir a sua senha.",
"resetPassword.button.back": "Voltar para a página inicial",
"resetPassword.text.resetPassword": "Redefinir senha",
"resetPassword.text.enterNewPassword": "Digite uma nova senha",
"resetPassword.input.password": "Nova senha",
"resetPassword.notify.passwordReset": "A sua senha foi redefinida com sucesso.",
// /account
"account.title": "A minha conta",
"account.card.info.title": "Informação sobre a conta",
"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",
"account.card.password.old": "Senha antiga",
"account.card.password.new": "Nova senha",
"account.notify.password.success": "Senha alterada com sucesso",
"account.card.security.title": "Segurança",
"account.card.security.totp.enable.description": "Digite a sua senha atual para começar a habilitar o TOTP",
"account.card.security.totp.disable.description": "Digite a sua senha atual para desabilitar o TOTP",
"account.card.security.totp.button.start": "Iniciar",
"account.modal.totp.title": "Habilitar TOTP",
"account.modal.totp.step1": "Passo 1: Adicionar o seu autenticador",
"account.modal.totp.step2": "Passo 2: Valide o seu código",
"account.modal.totp.enterManually": "Inserir manualmente",
"account.modal.totp.code": "Código",
"account.modal.totp.clickToCopy": "Clique para copiar",
"account.modal.totp.verify": "Verificar",
"account.notify.totp.disable": "TOTP desabilitado com sucesso",
"account.notify.totp.enable": "TOTP habilitado com sucesso",
"account.card.language.title": "Idioma",
"account.card.language.description": "O projeto é traduzido pela comunidade. Alguns idiomas podem estar incompletos.",
"account.card.color.title": "Esquema de cores",
// ThemeSwitcher.tsx
"account.theme.dark": "Escuro",
"account.theme.light": "Claro",
"account.theme.system": "Sistema",
"account.button.delete": "Excluir conta",
"account.modal.delete.title": "Excluir conta",
"account.modal.delete.description": "Você realmente deseja excluir a sua conta, incluindo todos os seus compartilhamentos ativos?",
// END /account
// /account/shares
"account.shares.title": "Meus compartilhamentos",
"account.shares.title.empty": "Está vazio aqui 👀",
"account.shares.description.empty": "Você não tem nenhum compartilhamento.",
"account.shares.button.create": "Crie um",
"account.shares.info.title": "Informações do compartilhamento",
"account.shares.table.id": "ID",
"account.shares.table.name": "Nome",
"account.shares.table.description": "Descrição",
"account.shares.table.visitors": "Visitantes",
"account.shares.table.expiresAt": "Expira em",
"account.shares.table.createdAt": "Criado em",
"account.shares.table.size": "Tamanho",
"account.shares.modal.share-informations": "Informações do compartilhamento",
"account.shares.modal.share-link": "Link do compartilhamento",
"account.shares.modal.delete.title": "Excluir o compartilhamento {share}",
"account.shares.modal.delete.description": "Tem certeza que deseja excluir este compartilhamento?",
// END /account/shares
// /account/reverseShares
"account.reverseShares.title": "Compartilhamentos reversos",
"account.reverseShares.description": "Um compartilhamento reverso permite gerar uma URL única que autoriza usuários externos criarem um compartilhamento.",
"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",
"account.reverseShares.modal.expiration.hour-singular": "Hora",
"account.reverseShares.modal.expiration.hour-plural": "Horas",
"account.reverseShares.modal.expiration.day-singular": "Dia",
"account.reverseShares.modal.expiration.day-plural": "Dias",
"account.reverseShares.modal.expiration.week-singular": "Semana",
"account.reverseShares.modal.expiration.week-plural": "Semanas",
"account.reverseShares.modal.expiration.month-singular": "Mês",
"account.reverseShares.modal.expiration.month-plural": "Meses",
"account.reverseShares.modal.expiration.year-singular": "Ano",
"account.reverseShares.modal.expiration.year-plural": "Anos",
"account.reverseShares.modal.max-size.label": "Tamanho máximo do compartilhamento",
"account.reverseShares.modal.send-email": "Enviar notificação por e-mail",
"account.reverseShares.modal.send-email.description": "Enviar uma notificação por e-mail quando um compartilhamento for criado com este link reverso.",
"account.reverseShares.modal.max-use.label": "Limite de uso",
"account.reverseShares.modal.max-use.description": "A quantidade máxima de vezes que esta URL pode ser usada para criar um compartilhamento.",
"account.reverseShare.never-expires": "Este compartilhamento reverso nunca irá expirar.",
"account.reverseShare.expires-on": "Este compartilhamento reverso irá expirar em {expiration}.",
"account.reverseShares.table.no-shares": "Nenhum compartilhamento criado ainda",
"account.reverseShares.table.count.singular": "compartilhar",
"account.reverseShares.table.count.plural": "compartilhamentos",
"account.reverseShares.table.shares": "Compartilhamentos",
"account.reverseShares.table.remaining": "Usos restantes",
"account.reverseShares.table.max-size": "Tamanho máximo do compartilhamento",
"account.reverseShares.table.expires": "Expira em",
"account.reverseShares.modal.reverse-share-link": "Link do compartilhamento reverso",
"account.reverseShares.modal.delete.title": "Excluir o compartilhamento reverso",
"account.reverseShares.modal.delete.description": "Você realmente deseja excluir este compartilhamento reverso? Se você o fizer, os compartilhamentos associados também serão excluídos.",
// END /account/reverseShares
// /admin
"admin.title": "Administração",
"admin.button.users": "Gerenciamento de usuários",
"admin.button.config": "Configuração",
"admin.version": "Versão",
// END /admin
// /admin/users
"admin.users.title": "Gerenciamento de usuários",
"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}",
"admin.users.edit.update.admin-privileges": "Privilégios de administrador",
"admin.users.edit.update.change-password.title": "Alterar senha",
"admin.users.edit.update.change-password.field": "Nova senha",
"admin.users.edit.update.change-password.button": "Salvar nova senha",
"admin.users.edit.update.notify.password.success": "Senha alterada com sucesso",
"admin.users.edit.delete.title": "Excluir usuário {username}",
"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 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",
"admin.users.modal.create.manual-password.description": "Se não estiver marcado, o usuário receberá um e-mail com um link para definir sua senha.",
"admin.users.modal.create.admin": "Privilégios de administrador",
"admin.users.modal.create.admin.description": "Se marcado, o usuário poderá acessar o painel de administração.",
// END /admin/users
// /upload
"upload.title": "Carregar",
"upload.notify.generic-error": "Ocorreu um erro ao terminar seu compartilhamento.",
"upload.notify.count-failed": "Falha ao enviar {count} arquivos. Tentando novamente.",
// Dropzone.tsx
"upload.dropzone.title": "Carregar arquivos",
"upload.dropzone.description": "Arraste os arquivos aqui para iniciar o seu compartilhamento. Podemos aceitar apenas arquivos que são menores que {maxSize} no total.",
"upload.dropzone.notify.file-too-big": "Seus arquivos excedem o tamanho máximo do compartilhamento {maxSize}.",
// FileList.tsx
"upload.filelist.name": "Nome",
"upload.filelist.size": "Tamanho",
// showCreateUploadModal.tsx
"upload.modal.title": "Criar Compartilhamento",
"upload.modal.link.error.invalid": "Pode conter apenas letras, números, sublinhados e hífens",
"upload.modal.link.error.taken": "Este link já está em uso",
"upload.modal.not-signed-in": "Você não está conectado",
"upload.modal.not-signed-in-description": "Você não poderá excluir seu compartilhamento manualmente e visualizar a contagem de visitantes.",
"upload.modal.expires.never": "nunca",
"upload.modal.expires.never-long": "Nunca expira",
"upload.modal.link.label": "Link",
"upload.modal.expires.label": "Expiração",
"upload.modal.expires.minute-singular": "Minuto",
"upload.modal.expires.minute-plural": "Minutos",
"upload.modal.expires.hour-singular": "Hora",
"upload.modal.expires.hour-plural": "Horas",
"upload.modal.expires.day-singular": "Dia",
"upload.modal.expires.day-plural": "Dias",
"upload.modal.expires.week-singular": "Semana",
"upload.modal.expires.week-plural": "Semanas",
"upload.modal.expires.month-singular": "Mês",
"upload.modal.expires.month-plural": "Meses",
"upload.modal.expires.year-singular": "Ano",
"upload.modal.expires.year-plural": "Anos",
"upload.modal.accordion.description.title": "Descrição",
"upload.modal.accordion.description.placeholder": "Nota para os destinatários deste compartilhamento",
"upload.modal.accordion.email.title": "Destinatários de e-mail",
"upload.modal.accordion.email.placeholder": "Insira os destinatários do e-mail",
"upload.modal.accordion.email.invalid-email": "Endereço de e-mail inválido",
"upload.modal.accordion.security.title": "Opções de segurança",
"upload.modal.accordion.security.password.label": "Protecção por senha",
"upload.modal.accordion.security.password.placeholder": "Sem senha",
"upload.modal.accordion.security.max-views.label": "Máximo de visualizações",
"upload.modal.accordion.security.max-views.placeholder": "Sem limite",
// showCompletedUploadModal.tsx
"upload.modal.completed.never-expires": "Este compartilhamento reverso nunca irá expirar.",
"upload.modal.completed.expires-on": "Este compartilhamento reverso irá expirar em {expiration}.",
"upload.modal.completed.share-ready": "Compartilhamento pronto",
// END /upload
// /share/[id]
"share.title": "Compartilhar {shareId}",
"share.description": "Veja o que eu compartilhei com você!",
"share.error.visitor-limit-exceeded.title": "Limite de visitantes excedido",
"share.error.visitor-limit-exceeded.description": "O limite de visitantes deste compartilhamento foi excedido.",
"share.error.removed.title": "Compartilhamento removido",
"share.error.not-found.title": "Compartilhamento não encontrado",
"share.error.not-found.description": "O compartilhamento que você procura não existe.",
"share.modal.password.title": "Senha necessária",
"share.modal.password.description": "Para acessar este compartilhamento, por favor digite a senha para o compartilhamento.",
"share.modal.password": "Senha",
"share.modal.error.invalid-password": "Senha inválida",
"share.button.download-all": "Transferir tudo",
"share.notify.download-all-preparing": "O compartilhamento está sendo preparado. Tente novamente em alguns minutos.",
"share.modal.file-link": "Link do arquivo",
"share.table.name": "Nome",
"share.table.size": "Tamanho",
"share.modal.file-preview.error.not-supported.title": "Visualização não suportada",
"share.modal.file-preview.error.not-supported.description": "Uma visualização para este tipo de arquivo não é suportada. Faça o download do arquivo para visualizá-lo.",
// END /share/[id]
// /admin/config
"admin.config.title": "Configuração",
"admin.config.category.general": "Geral",
"admin.config.category.share": "Compartilhamento",
"admin.config.category.email": "E-mail",
"admin.config.category.smtp": "SMTP",
"admin.config.general.app-name": "Nome da aplicação",
"admin.config.general.app-name.description": "Nome da aplicação",
"admin.config.general.app-url": "URL do Aplicativo",
"admin.config.general.app-url.description": "Em qual URL o Pingvin Share está disponível",
"admin.config.general.show-home-page": "Mostrar a página inicial",
"admin.config.general.show-home-page.description": "Mostrar ou não a página inicial",
"admin.config.general.logo": "Logo",
"admin.config.general.logo.description": "Alterar o seu logo carregando uma nova imagem. A imagem deve ser PNG e deve ter o formato 1:1.",
"admin.config.general.logo.placeholder": "Escolhe uma imagem",
"admin.config.email.enable-share-email-recipients": "Ativar compartilhamento de e-mails destinatários",
"admin.config.email.enable-share-email-recipients.description": "Se quiser permitir que e-mails compartilhem destinatários. Apenas habilite isso se você tiver ativado o SMTP.",
"admin.config.email.share-recipients-subject": "Assunto dos destinatários do compartilhamento",
"admin.config.email.share-recipients-subject.description": "Assunto do e-mail enviado para os destinatários do compartilhamento.",
"admin.config.email.share-recipients-message": "Assunto dos destinatários do compartilhamento",
"admin.config.email.share-recipients-message.description": "Mensagem que é enviada aos destinatários do compartilhamento. Variáveis disponíveis:\n {creator} - O nome de usuário do criador do compartilhamento\n {shareUrl} - O URL do compartilhamento\n {desc} - A descrição do compartilhamento\n {expires} - A data de expiração do compartilhamento\n As variáveis serão substituídas pelo valor real.",
"admin.config.email.reverse-share-subject": "Assunto do compartilhamento reverso",
"admin.config.email.reverse-share-subject.description": "Assunto do e-mail enviado quando alguém criou um compartilhamento com o seu link reverso.",
"admin.config.email.reverse-share-message": "Mensagem do compartilhamento reverso",
"admin.config.email.reverse-share-message.description": "Mensagem enviada quando alguém criou um compartilhamento com o link reverso. {shareUrl} será substituído pelo nome do criador e pela URL de compartilhamento.",
"admin.config.email.reset-password-subject": "Redefinir assunto da senha",
"admin.config.email.reset-password-subject.description": "Assunto do e-mail enviado quando um usuário solicita uma redefinição de senha.",
"admin.config.email.reset-password-message": "Mensagem de redefinição de senha",
"admin.config.email.reset-password-message.description": "Mensagem enviada quando um usuário solicita uma redefinição de senha. {url} será substituído pela URL de redefinição de senha.",
"admin.config.email.invite-subject": "Assunto do convite",
"admin.config.email.invite-subject.description": "Assunto do e-mail enviado quando um administrador convida um usuário.",
"admin.config.email.invite-message": "Mensagem de convite",
"admin.config.email.invite-message.description": "Mensagem enviada quando um administrador convida um usuário. {url} será substituído pelo URL de convite e {password} pela senha.",
"admin.config.share.allow-registration": "Permitir novos registos",
"admin.config.share.allow-registration.description": "Se o registro é permitido",
"admin.config.share.allow-unauthenticated-shares": "Permitir compartilhamentos sem autenticação",
"admin.config.share.allow-unauthenticated-shares.description": "Se usuários não autenticados podem criar compartilhamentos",
"admin.config.share.max-size": "Tamanho máximo",
"admin.config.share.max-size.description": "Tamanho máximo do compartilhamento em bytes",
"admin.config.share.zip-compression-level": "Nível de compressão",
"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 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 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",
"admin.config.smtp.button.test": "Enviar email de teste",
// 404
"404.description": "Ops, esta página não existe.",
"404.button.home": "Me traga de volta para casa",
// Common translations
"common.button.save": "Salvar",
"common.button.create": "Criar",
"common.button.submit": "Submeter",
"common.button.delete": "Excluir",
"common.button.cancel": "Cancelar",
"common.button.confirm": "Confirmar",
"common.button.disable": "Desativar",
"common.button.share": "Compartilhamento",
"common.button.generate": "Gerar",
"common.button.done": "Concluído",
"common.text.link": "Link",
"common.text.or": "ou",
"common.button.go-back": "Voltar",
"common.notify.copied": "O seu link foi copiado para a área de transferência",
"common.success": "Sucesso",
"common.error": "Erro",
"common.error.unknown": "Ocorreu um erro desconhecido",
"common.error.invalid-email": "Endereço de e-mail inválido",
"common.error.too-short": "Deve ter no mínimo {length} caracteres",
"common.error.too-long": "Deve ter no máximo {length} caracteres",
"common.error.exact-length": "Deve ter exatamente {length} caracteres",
"common.error.invalid-number": "Tem que ser um número",
"common.error.field-required": "Este campo é obrigatório"
};

View File

@@ -1,324 +0,0 @@
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": "Платформа для обмена файлами с <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": "С возвращением",
"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": "Создать аккаунт",
"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": "Введите ваш email для восстановления пароля.",
"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: Добавьте свой аутентификатор",
"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": "Обратная загрузка позволяет генерировать уникальный URL, позволяющий внешним пользователям загружать файлы.",
"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": "Максимальное количество раз, когда 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": "Электронная почта",
"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": "Получатели 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": "Эта загрузка никогда не устареет.",
"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": "Адрес на котором доступен 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": "Упс, этой страницы не существует.",
"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": "Поле обязательно для заполнения"
};

Some files were not shown because too many files have changed in this diff Show More