Compare commits

...

18 Commits

Author SHA1 Message Date
Elias Schneider
b8efb9f54b release: 0.22.2 2024-02-29 14:43:08 +01:00
Elias Schneider
013b9886af fix: extend access token cookie expiration 2024-02-29 14:42:05 +01:00
Elias Schneider
43bff91db2 fix: replace Nginx with Caddy to fix "premature close" error while downloading larger files 2024-02-29 14:41:45 +01:00
Elias Schneider
1aa3d8e5e8 fix: reduce refresh access token calls 2024-02-27 09:40:52 +01:00
Elias Schneider
4dae7e250a docs: improve configuration section in README 2024-02-27 09:24:07 +01:00
Elias Schneider
7e91d83f9a chore(translations): add Arabic translation files 2024-02-27 09:12:46 +01:00
Elias Schneider
e11dbfe893 chore(translations): update translations via Crowdin (#411)
* New translations en-us.ts (French)

* New translations en-us.ts (Spanish)

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

* New translations en-us.ts (Italian)

* New translations en-us.ts (Danish)

* New translations en-us.ts (German)

* New translations en-us.ts (Greek)

* New translations en-us.ts (Finnish)

* New translations en-us.ts (Japanese)

* New translations en-us.ts (Polish)

* New translations en-us.ts (Russian)

* New translations en-us.ts (Slovenian)

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

* New translations en-us.ts (Swedish)

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

* New translations en-us.ts (Chinese Traditional)

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

* New translations en-us.ts (Thai)

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

* New translations en-us.ts (Italian)

* New translations en-us.ts (Polish)
2024-02-27 09:11:25 +01:00
Elias Schneider
ea83cf3876 docs: add environment variable step to stand-alone docs 2024-02-18 21:53:11 +01:00
Elias Schneider
5ca0bffc0a release: 0.22.1 2024-02-18 21:48:23 +01:00
Elias Schneider
64515d77cf fix: user enumaration on forgot password page 2024-02-18 21:46:50 +01:00
Elias Schneider
6058dca273 Merge branch 'main' of https://github.com/stonith404/pingvin-share 2024-02-18 21:32:04 +01:00
Elias Schneider
d01cba4a06 Merge branch 'fix/replace-middleware-url' 2024-02-18 21:30:52 +01:00
Elias Schneider
98aa9f97ea chore(translations): update translations via Crowdin (#399)
* New translations en-us.ts (Italian)

* New translations en-us.ts (French)

* New translations en-us.ts (Spanish)

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

* New translations en-us.ts (Italian)

* New translations en-us.ts (Danish)

* New translations en-us.ts (German)

* New translations en-us.ts (Greek)

* New translations en-us.ts (Finnish)

* New translations en-us.ts (Japanese)

* New translations en-us.ts (Polish)

* New translations en-us.ts (Russian)

* New translations en-us.ts (Slovenian)

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

* New translations en-us.ts (Swedish)

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

* New translations en-us.ts (Chinese Traditional)

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

* New translations en-us.ts (Thai)

* New translations en-us.ts (Slovenian)

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

* New translations en-us.ts (Polish)

* New translations en-us.ts (Swedish)

* New translations en-us.ts (French)

* New translations en-us.ts (Italian)
2024-02-18 21:30:10 +01:00
Elias Schneider
9c734ec439 fix: prevent zoom on input field click on mobile 2024-02-11 16:22:19 +01:00
Elias Schneider
e663da45b1 fix: user id and totpVerified can't be changed by user 2024-02-11 16:19:19 +01:00
Elias Schneider
f52dffdaac fix: back links on error modals 2024-02-05 16:13:54 +01:00
Elias Schneider
e572506d4f refactor: run formatter 2024-02-05 16:11:49 +01:00
Elias Schneider
76df6f66d9 fix: replace middleware backend url with local backend url 2024-01-23 15:22:08 +01:00
43 changed files with 867 additions and 181 deletions

View File

@@ -1,3 +1,23 @@
## [0.22.2](https://github.com/stonith404/pingvin-share/compare/v0.22.1...v0.22.2) (2024-02-29)
### Bug Fixes
* extend access token cookie expiration ([013b988](https://github.com/stonith404/pingvin-share/commit/013b9886af5629b2ead6000b962267afc761c612))
* reduce refresh access token calls ([1aa3d8e](https://github.com/stonith404/pingvin-share/commit/1aa3d8e5e89b3696cc9554f41e9ce13806dde406))
* replace Nginx with Caddy to fix "premature close" error while downloading larger files ([43bff91](https://github.com/stonith404/pingvin-share/commit/43bff91db2ba4ec68d76e601f7bc42cb7a506bc5))
## [0.22.1](https://github.com/stonith404/pingvin-share/compare/v0.22.0...v0.22.1) (2024-02-18)
### Bug Fixes
* back links on error modals ([f52dffd](https://github.com/stonith404/pingvin-share/commit/f52dffdaac5a893804525913943f3f4f99b7c55a))
* prevent zoom on input field click on mobile ([9c734ec](https://github.com/stonith404/pingvin-share/commit/9c734ec439aeaeebe172caa41bf531e6d8b3fac3))
* replace middleware backend url with local backend url ([76df6f6](https://github.com/stonith404/pingvin-share/commit/76df6f66d965dd751146468abfafb0c6acd46310))
* user `id` and `totpVerified` can't be changed by user ([e663da4](https://github.com/stonith404/pingvin-share/commit/e663da45b1d15f5e6e33118e6a28e1504688034c))
* user enumaration on forgot password page ([64515d7](https://github.com/stonith404/pingvin-share/commit/64515d77cfc116a243d78610395ccc383ba62940))
## [0.22.0](https://github.com/stonith404/pingvin-share/compare/v0.21.5...v0.22.0) (2024-02-04) ## [0.22.0](https://github.com/stonith404/pingvin-share/compare/v0.21.5...v0.22.0) (2024-02-04)

15
Caddyfile Normal file
View File

@@ -0,0 +1,15 @@
:3000 {
# Reverse proxy for /api
reverse_proxy /api/* http://localhost:8080 {
header_up X-Forwarded-Host {host}:{server_port}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
# Reverse proxy for all other requests
reverse_proxy http://localhost:3333 {
header_up X-Forwarded-Host {host}:{server_port}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}

View File

@@ -30,12 +30,12 @@ RUN npm run build && npm prune --production
FROM node:20-alpine AS runner FROM node:20-alpine AS runner
ENV NODE_ENV=docker ENV NODE_ENV=docker
# Alpine specific dependencies # Install Caddy
RUN apk update --no-cache RUN apk update --no-cache \
RUN apk upgrade --no-cache && apk upgrade --no-cache \
RUN apk add --no-cache curl nginx && apk add --no-cache curl caddy
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf COPY ./Caddyfile /etc/caddy/Caddyfile
WORKDIR /opt/app/frontend WORKDIR /opt/app/frontend
COPY --from=frontend-builder /opt/app/public ./public COPY --from=frontend-builder /opt/app/public ./public
@@ -53,9 +53,8 @@ WORKDIR /opt/app
EXPOSE 3000 EXPOSE 3000
# Add a health check to ensure the container is healthy # Health check remains unchanged
HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost:3000/api/health || exit 1 HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost:3000/api/health || exit 1
# Application startup # Application startup updated for Caddy
# 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/img && caddy run --config /etc/caddy/Caddyfile & 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/img && nginx && PORT=3333 HOSTNAME=0.0.0.0 node frontend/server.js & cd backend && npm run prod

View File

@@ -60,10 +60,11 @@ pm2 start --name="pingvin-share-backend" npm -- run prod
cd ../frontend cd ../frontend
npm install npm install
npm run build npm run build
API_URL=http://localhost:8080 # Set the URL of the backend, default: http://localhost:8080
pm2 start --name="pingvin-share-frontend" npm -- run start 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`. **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 Caddy can be found in `./Caddyfile`.
The website is now listening on `http://localhost:3000`, have fun with Pingvin Share 🐧! The website is now listening on `http://localhost:3000`, have fun with Pingvin Share 🐧!
@@ -120,12 +121,13 @@ docker compose up -d
# Start the frontend # Start the frontend
cd ../frontend cd ../frontend
npm run build npm run build
API_URL=http://localhost:8080 # Set the URL of the backend, default: http://localhost:8080
pm2 restart pingvin-share-frontend pm2 restart pingvin-share-frontend
``` ```
### Configuration ### Configuration
You can customize Pingvin Share by going to the configuration page in your admin dashboard. You can customize Pingvin Share like changing your domain by going to the configuration page in your admin dashboard `/admin/config`.
#### Environment variables #### Environment variables

View File

@@ -1,12 +1,12 @@
{ {
"name": "pingvin-share-backend", "name": "pingvin-share-backend",
"version": "0.22.0", "version": "0.22.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pingvin-share-backend", "name": "pingvin-share-backend",
"version": "0.22.0", "version": "0.22.2",
"dependencies": { "dependencies": {
"@nestjs/cache-manager": "^2.1.0", "@nestjs/cache-manager": "^2.1.0",
"@nestjs/common": "^10.1.2", "@nestjs/common": "^10.1.2",

View File

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

View File

@@ -96,9 +96,9 @@ export class AuthController {
@Post("resetPassword/:email") @Post("resetPassword/:email")
@Throttle(5, 5 * 60) @Throttle(5, 5 * 60)
@HttpCode(204) @HttpCode(202)
async requestResetPassword(@Param("email") email: string) { async requestResetPassword(@Param("email") email: string) {
return await this.authService.requestResetPassword(email); this.authService.requestResetPassword(email);
} }
@Post("resetPassword") @Post("resetPassword")

View File

@@ -139,7 +139,7 @@ export class AuthService {
async updatePassword(user: User, newPassword: string, oldPassword?: string) { async updatePassword(user: User, newPassword: string, oldPassword?: string) {
const isPasswordValid = const isPasswordValid =
!user.password || await argon.verify(user.password, oldPassword); !user.password || (await argon.verify(user.password, oldPassword));
if (!isPasswordValid) throw new ForbiddenException("Invalid password"); if (!isPasswordValid) throw new ForbiddenException("Invalid password");
@@ -227,13 +227,16 @@ export class AuthService {
accessToken?: string, accessToken?: string,
) { ) {
if (accessToken) if (accessToken)
response.cookie("access_token", accessToken, { sameSite: "lax" }); response.cookie("access_token", accessToken, {
sameSite: "lax",
maxAge: 1000 * 60 * 60 * 24 * 30 * 3, // 3 months
});
if (refreshToken) if (refreshToken)
response.cookie("refresh_token", refreshToken, { response.cookie("refresh_token", refreshToken, {
path: "/api/auth/token", path: "/api/auth/token",
httpOnly: true, httpOnly: true,
sameSite: "strict", sameSite: "strict",
maxAge: 1000 * 60 * 60 * 24 * 30 * 3, maxAge: 1000 * 60 * 60 * 24 * 30 * 3, // 3 months
}); });
} }

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,11 @@
import { Button, Center, Stack, Text, Title, useMantineTheme } from "@mantine/core"; import {
Button,
Center,
Stack,
Text,
Title,
useMantineTheme,
} from "@mantine/core";
import { modals } from "@mantine/modals"; import { modals } from "@mantine/modals";
import Link from "next/link"; import Link from "next/link";
import React, { Dispatch, SetStateAction, useEffect, useState } from "react"; import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
@@ -116,41 +123,38 @@ const ImagePreview = () => {
const TextPreview = () => { const TextPreview = () => {
const { shareId, fileId } = React.useContext(FilePreviewContext); const { shareId, fileId } = React.useContext(FilePreviewContext);
const [ text, setText ] = useState<string>(""); const [text, setText] = useState<string>("");
const { colorScheme } = useMantineTheme(); const { colorScheme } = useMantineTheme();
useEffect(() => { useEffect(() => {
api api
.get(`/shares/${shareId}/files/${fileId}?download=false`) .get(`/shares/${shareId}/files/${fileId}?download=false`)
.then((res) => setText(res.data ?? "Preview couldn't be fetched.")); .then((res) => setText(res.data ?? "Preview couldn't be fetched."));
}, [ shareId, fileId ]); }, [shareId, fileId]);
const options = { const options = {
overrides: { overrides: {
pre: { pre: {
props: { props: {
style: { style: {
backgroundColor: colorScheme == "dark" backgroundColor:
? "rgba(50, 50, 50, 0.5)" colorScheme == "dark"
: "rgba(220, 220, 220, 0.5)", ? "rgba(50, 50, 50, 0.5)"
: "rgba(220, 220, 220, 0.5)",
padding: "0.75em", padding: "0.75em",
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
} },
} },
}, },
table: { table: {
props: { props: {
className: "md" className: "md",
} },
} },
} },
}; };
return ( return <Markdown options={options}>{text}</Markdown>;
<Markdown options={options}>
{text}
</Markdown>
);
}; };
const PdfPreview = () => { const PdfPreview = () => {

View File

@@ -8,6 +8,7 @@ const showErrorModal = (
modals: ModalsContextProps, modals: ModalsContextProps,
title: string, title: string,
text: string, text: string,
action: "go-back" | "go-home" = "go-back",
) => { ) => {
return modals.openModal({ return modals.openModal({
closeOnClickOutside: false, closeOnClickOutside: false,
@@ -15,11 +16,17 @@ const showErrorModal = (
closeOnEscape: false, closeOnEscape: false,
title: title, title: title,
children: <Body text={text} />, children: <Body text={text} action={action} />,
}); });
}; };
const Body = ({ text }: { text: string }) => { const Body = ({
text,
action,
}: {
text: string;
action: "go-back" | "go-home";
}) => {
const modals = useModals(); const modals = useModals();
const router = useRouter(); const router = useRouter();
return ( return (
@@ -29,10 +36,14 @@ const Body = ({ text }: { text: string }) => {
<Button <Button
onClick={() => { onClick={() => {
modals.closeAll(); modals.closeAll();
router.back(); if (action === "go-back") {
router.back();
} else if (action === "go-home") {
router.push("/");
}
}} }}
> >
<FormattedMessage id="common.button.go-back" /> <FormattedMessage id={`common.button.${action}`} />
</Button> </Button>
</Stack> </Stack>
</> </>

View File

@@ -114,4 +114,9 @@ export const LOCALES = {
code: "sl-SI", code: "sl-SI",
messages: slovenian, messages: slovenian,
}, },
ARABIC: {
name: "العربية",
code: "ar-EG",
messages: {},
},
}; };

View File

@@ -0,0 +1,574 @@
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",
"signIn.oauth.or": "OR",
"signIn.oauth.github": "GitHub",
"signIn.oauth.google": "Google",
"signIn.oauth.microsoft": "Microsoft",
"signIn.oauth.discord": "Discord",
"signIn.oauth.oidc": "OpenID",
// 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/totp
"totp.title": "TOTP Authentication",
"totp.button.signIn": "Sign in",
// END /auth/totp
// /auth/reset-password
"resetPassword.title": "Forgot your password?",
"resetPassword.description": "Enter your email to reset your password.",
"resetPassword.notify.success":
"A message with a link to reset your password has been sent if the email exists.",
"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.card.password.noPasswordSet":
"You don't have a password set. If you want to sign in with email and password you need to set a password.",
"account.notify.password.success": "Password changed successfully",
"account.card.oauth.title": "Social login",
"account.card.oauth.github": "GitHub",
"account.card.oauth.google": "Google",
"account.card.oauth.microsoft": "Microsoft",
"account.card.oauth.discord": "Discord",
"account.card.oauth.oidc": "OpenID",
"account.card.oauth.link": "Link",
"account.card.oauth.unlink": "Unlink",
"account.card.oauth.unlinked": "Unlinked",
"account.modal.unlink.title": "Unlink account",
"account.modal.unlink.description":
"Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
"account.notify.oauth.unlinked.success": "Unlinked successfully",
"account.card.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.expires.error.too-long":
"Expiration exceeds maximum expiration date of {max}.",
"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 this file type is unsupported. Please download the file to view it.",
// END /share/[id]
// /share/[id]/edit
"share.edit.title": "Edit {shareId}",
"share.edit.append-upload": "Append file",
"share.edit.notify.generic-error":
"An error occurred while finishing your share.",
"share.edit.notify.save-success": "Share updated successfully",
// END /share/[id]/edit
// /admin/config
"admin.config.title": "Configuration",
"admin.config.category.general": "General",
"admin.config.category.share": "Share",
"admin.config.category.email": "Email",
"admin.config.category.smtp": "SMTP",
"admin.config.category.oauth": "Social Login",
"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-expiration": "Max expiration",
"admin.config.share.max-expiration.description":
"Maximum share expiration in hours. Set to 0 to allow unlimited expiration.",
"admin.config.share.max-size": "Max size",
"admin.config.share.max-size.description": "Maximum share size in bytes",
"admin.config.share.zip-compression-level": "Zip compression level",
"admin.config.share.zip-compression-level.description":
"Adjust the level to balance between file size and compression speed. Valid values range from 0 to 9, with 0 being no compression and 9 being maximum compression. ",
"admin.config.smtp.enabled": "Enabled",
"admin.config.smtp.enabled.description":
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
"admin.config.smtp.host": "Host",
"admin.config.smtp.host.description": "Host of the SMTP server",
"admin.config.smtp.port": "Port",
"admin.config.smtp.port.description": "Port of the SMTP server",
"admin.config.smtp.email": "Email",
"admin.config.smtp.email.description":
"Email address which the emails get sent from",
"admin.config.smtp.username": "Username",
"admin.config.smtp.username.description": "Username of the SMTP server",
"admin.config.smtp.password": "Password",
"admin.config.smtp.password.description": "Password of the SMTP server",
"admin.config.smtp.button.test": "Send test email",
"admin.config.oauth.allow-registration": "Allow registration",
"admin.config.oauth.allow-registration.description":
"Allow users to register via social login",
"admin.config.oauth.ignore-totp": "Ignore TOTP",
"admin.config.oauth.ignore-totp.description":
"Whether to ignore TOTP when user using social login",
"admin.config.oauth.github-enabled": "GitHub",
"admin.config.oauth.github-enabled.description":
"Whether GitHub login is enabled",
"admin.config.oauth.github-client-id": "GitHub Client ID",
"admin.config.oauth.github-client-id.description":
"Client ID of the GitHub OAuth app",
"admin.config.oauth.github-client-secret": "GitHub Client secret",
"admin.config.oauth.github-client-secret.description":
"Client secret of the GitHub OAuth app",
"admin.config.oauth.google-enabled": "Google",
"admin.config.oauth.google-enabled.description":
"Whether Google login is enabled",
"admin.config.oauth.google-client-id": "Google Client ID",
"admin.config.oauth.google-client-id.description":
"Client ID of the Google OAuth app",
"admin.config.oauth.google-client-secret": "Google Client secret",
"admin.config.oauth.google-client-secret.description":
"Client secret of the Google OAuth app",
"admin.config.oauth.microsoft-enabled": "Microsoft",
"admin.config.oauth.microsoft-enabled.description":
"Whether Microsoft login is enabled",
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
"admin.config.oauth.microsoft-tenant.description":
"Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
"admin.config.oauth.microsoft-client-id.description":
"Client ID of the Microsoft OAuth app",
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
"admin.config.oauth.microsoft-client-secret.description":
"Client secret of the Microsoft OAuth app",
"admin.config.oauth.discord-enabled": "Discord",
"admin.config.oauth.discord-enabled.description":
"Whether Discord login is enabled",
"admin.config.oauth.discord-limited-guild": "Discord limited server ID",
"admin.config.oauth.discord-limited-guild.description":
"Limit signing in to users in a specific server. Leave it blank to disable.",
"admin.config.oauth.discord-client-id": "Discord Client ID",
"admin.config.oauth.discord-client-id.description":
"Client ID of the Discord OAuth app",
"admin.config.oauth.discord-client-secret": "Discord Client secret",
"admin.config.oauth.discord-client-secret.description":
"Client secret of the Discord OAuth app",
"admin.config.oauth.oidc-enabled": "OpenID Connect",
"admin.config.oauth.oidc-enabled.description":
"Whether OpenID Connect login is enabled",
"admin.config.oauth.oidc-discovery-uri": "OpenID Connect Discovery URI",
"admin.config.oauth.oidc-discovery-uri.description":
"Discovery URI of the OpenID Connect OAuth app",
"admin.config.oauth.oidc-username-claim": "OpenID Connect username claim",
"admin.config.oauth.oidc-username-claim.description":
"Username claim in OpenID Connect ID token. Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-client-id": "OpenID Connect Client ID",
"admin.config.oauth.oidc-client-id.description":
"Client ID of the OpenID Connect OAuth app",
"admin.config.oauth.oidc-client-secret": "OpenID Connect Client secret",
"admin.config.oauth.oidc-client-secret.description":
"Client secret of the OpenID Connect OAuth app",
// 404
"404.description": "Oops this page doesn't exist.",
"404.button.home": "Bring me back home",
// error
"error.title": "Error",
"error.description": "Oops!",
"error.button.back": "Go back",
"error.msg.default": "Something went wrong.",
"error.msg.access_denied":
"You canceled the authentication process, please try again.",
"error.msg.expired_token":
"The authentication process took too long, please try again.",
"error.msg.invalid_token": "Internal Error",
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
"error.msg.no_email": "Can't get email address from this {0} account.",
"error.msg.already_linked":
"This {0} account is already linked to another account.",
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
"error.msg.unverified_account":
"This {0} account is unverified, please try again after verification.",
"error.msg.discord_guild_permission_denied":
"You are not allowed to sign in.",
"error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.",
"error.param.provider_github": "GitHub",
"error.param.provider_google": "Google",
"error.param.provider_microsoft": "Microsoft",
"error.param.provider_discord": "Discord",
"error.param.provider_oidc": "OpenID Connect",
// 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.navigate-to-link": "Go to the link",
"common.text.or": "or",
"common.button.go-back": "Go back",
"common.button.go-home": "Go home",
"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

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Glemt din adgangskode?", "resetPassword.title": "Glemt din adgangskode?",
"resetPassword.description": "Indtast din e-mail for at nulstille 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.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Tilbage til login", "resetPassword.button.back": "Tilbage til login",
"resetPassword.text.resetPassword": "Nulstil adgangskode", "resetPassword.text.resetPassword": "Nulstil adgangskode",
"resetPassword.text.enterNewPassword": "Indtast din nye adgangskode", "resetPassword.text.enterNewPassword": "Indtast din nye adgangskode",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Go to the link",
"common.text.or": "eller", "common.text.or": "eller",
"common.button.go-back": "Gå tilbage", "common.button.go-back": "Gå tilbage",
"common.button.go-home": "Go home",
"common.notify.copied": "Linket blev kopieret til udklipsholderen", "common.notify.copied": "Linket blev kopieret til udklipsholderen",
"common.success": "Success", "common.success": "Success",
"common.error": "Fejl", "common.error": "Fejl",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Passwort vergessen?", "resetPassword.title": "Passwort vergessen?",
"resetPassword.description": "Gib deine Email Adresse ein, um dein Passwort zurückzusetzen.", "resetPassword.description": "Gib deine Email Adresse ein, um dein Passwort zurückzusetzen.",
"resetPassword.notify.success": "Ein Link zum Zurücksetzen des Passwortes wurde an deine Emailadresse versandt.", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Zurück zur Anmeldeseite", "resetPassword.button.back": "Zurück zur Anmeldeseite",
"resetPassword.text.resetPassword": "Passwort zurücksetzen", "resetPassword.text.resetPassword": "Passwort zurücksetzen",
"resetPassword.text.enterNewPassword": "Gib dein neues Passwort ein", "resetPassword.text.enterNewPassword": "Gib dein neues Passwort ein",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Link öffnen", "common.text.navigate-to-link": "Link öffnen",
"common.text.or": "oder", "common.text.or": "oder",
"common.button.go-back": "Zurück", "common.button.go-back": "Zurück",
"common.button.go-home": "Go home",
"common.notify.copied": "Dein Link wurde in die Zwischenablage kopiert", "common.notify.copied": "Dein Link wurde in die Zwischenablage kopiert",
"common.success": "Erfolg", "common.success": "Erfolg",
"common.error": "Fehler", "common.error": "Fehler",

View File

@@ -56,7 +56,7 @@ export default {
// END /auth/totp // END /auth/totp
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Ξεχάσατε τον κωδικό σας;", "resetPassword.title": "Ξεχάσατε τον κωδικό σας;",
"resetPassword.description": "Ελέγξτε το e-mail σας για να κάνετε επαναφορά του κωδικού πρόσβασής σας.", "resetPassword.description": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.notify.success": "Εισάγετε το email σας για επαναφορά κωδικού.", "resetPassword.notify.success": "Εισάγετε το email σας για επαναφορά κωδικού.",
"resetPassword.button.back": "Πίσω στη σελίδα εισόδου", "resetPassword.button.back": "Πίσω στη σελίδα εισόδου",
"resetPassword.text.resetPassword": "Επαναφορά κωδικού πρόσβασης", "resetPassword.text.resetPassword": "Επαναφορά κωδικού πρόσβασης",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Μεταβείτε στο σύνδεσμο", "common.text.navigate-to-link": "Μεταβείτε στο σύνδεσμο",
"common.text.or": "ή", "common.text.or": "ή",
"common.button.go-back": "Επιστροφή", "common.button.go-back": "Επιστροφή",
"common.button.go-home": "Go home",
"common.notify.copied": "Ο σύνδεσμος σας αντιγράφηκε στο πρόχειρο", "common.notify.copied": "Ο σύνδεσμος σας αντιγράφηκε στο πρόχειρο",
"common.success": "Επιτυχία", "common.success": "Επιτυχία",
"common.error": "Σφάλμα", "common.error": "Σφάλμα",

View File

@@ -74,7 +74,7 @@ export default {
"resetPassword.title": "Forgot your password?", "resetPassword.title": "Forgot your password?",
"resetPassword.description": "Enter your email to reset your password.", "resetPassword.description": "Enter your email to reset your password.",
"resetPassword.notify.success": "resetPassword.notify.success":
"An email has been sent with a link to reset your password.", "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Back to sign in page", "resetPassword.button.back": "Back to sign in page",
"resetPassword.text.resetPassword": "Reset password", "resetPassword.text.resetPassword": "Reset password",
"resetPassword.text.enterNewPassword": "Enter your new password", "resetPassword.text.enterNewPassword": "Enter your new password",
@@ -93,7 +93,8 @@ export default {
"account.card.password.title": "Password", "account.card.password.title": "Password",
"account.card.password.old": "Old password", "account.card.password.old": "Old password",
"account.card.password.new": "New password", "account.card.password.new": "New password",
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.", "account.card.password.noPasswordSet":
"You don't have a password set. If you want to sign in with email and password you need to set a password.",
"account.notify.password.success": "Password changed successfully", "account.notify.password.success": "Password changed successfully",
"account.card.oauth.title": "Social login", "account.card.oauth.title": "Social login",
@@ -106,10 +107,10 @@ export default {
"account.card.oauth.unlink": "Unlink", "account.card.oauth.unlink": "Unlink",
"account.card.oauth.unlinked": "Unlinked", "account.card.oauth.unlinked": "Unlinked",
"account.modal.unlink.title": "Unlink account", "account.modal.unlink.title": "Unlink account",
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.", "account.modal.unlink.description":
"Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
"account.notify.oauth.unlinked.success": "Unlinked successfully", "account.notify.oauth.unlinked.success": "Unlinked successfully",
"account.card.security.title": "Security", "account.card.security.title": "Security",
"account.card.security.totp.enable.description": "account.card.security.totp.enable.description":
"Enter your current password to start enabling TOTP", "Enter your current password to start enabling TOTP",
@@ -288,7 +289,8 @@ export default {
"upload.modal.expires.never": "never", "upload.modal.expires.never": "never",
"upload.modal.expires.never-long": "Never Expires", "upload.modal.expires.never-long": "Never Expires",
"upload.modal.expires.error.too-long": "Expiration exceeds maximum expiration date of {max}.", "upload.modal.expires.error.too-long":
"Expiration exceeds maximum expiration date of {max}.",
"upload.modal.link.label": "Link", "upload.modal.link.label": "Link",
"upload.modal.expires.label": "Expiration", "upload.modal.expires.label": "Expiration",
@@ -361,7 +363,8 @@ export default {
// /share/[id]/edit // /share/[id]/edit
"share.edit.title": "Edit {shareId}", "share.edit.title": "Edit {shareId}",
"share.edit.append-upload": "Append file", "share.edit.append-upload": "Append file",
"share.edit.notify.generic-error": "An error occurred while finishing your share.", "share.edit.notify.generic-error":
"An error occurred while finishing your share.",
"share.edit.notify.save-success": "Share updated successfully", "share.edit.notify.save-success": "Share updated successfully",
// END /share/[id]/edit // END /share/[id]/edit
@@ -447,47 +450,68 @@ export default {
"admin.config.smtp.button.test": "Send test email", "admin.config.smtp.button.test": "Send test email",
"admin.config.oauth.allow-registration": "Allow registration", "admin.config.oauth.allow-registration": "Allow registration",
"admin.config.oauth.allow-registration.description": "Allow users to register via social login", "admin.config.oauth.allow-registration.description":
"Allow users to register via social login",
"admin.config.oauth.ignore-totp": "Ignore TOTP", "admin.config.oauth.ignore-totp": "Ignore TOTP",
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login", "admin.config.oauth.ignore-totp.description":
"Whether to ignore TOTP when user using social login",
"admin.config.oauth.github-enabled": "GitHub", "admin.config.oauth.github-enabled": "GitHub",
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled", "admin.config.oauth.github-enabled.description":
"Whether GitHub login is enabled",
"admin.config.oauth.github-client-id": "GitHub Client ID", "admin.config.oauth.github-client-id": "GitHub Client ID",
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app", "admin.config.oauth.github-client-id.description":
"Client ID of the GitHub OAuth app",
"admin.config.oauth.github-client-secret": "GitHub Client secret", "admin.config.oauth.github-client-secret": "GitHub Client secret",
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app", "admin.config.oauth.github-client-secret.description":
"Client secret of the GitHub OAuth app",
"admin.config.oauth.google-enabled": "Google", "admin.config.oauth.google-enabled": "Google",
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled", "admin.config.oauth.google-enabled.description":
"Whether Google login is enabled",
"admin.config.oauth.google-client-id": "Google Client ID", "admin.config.oauth.google-client-id": "Google Client ID",
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app", "admin.config.oauth.google-client-id.description":
"Client ID of the Google OAuth app",
"admin.config.oauth.google-client-secret": "Google Client secret", "admin.config.oauth.google-client-secret": "Google Client secret",
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app", "admin.config.oauth.google-client-secret.description":
"Client secret of the Google OAuth app",
"admin.config.oauth.microsoft-enabled": "Microsoft", "admin.config.oauth.microsoft-enabled": "Microsoft",
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled", "admin.config.oauth.microsoft-enabled.description":
"Whether Microsoft login is enabled",
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant", "admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.", "admin.config.oauth.microsoft-tenant.description":
"Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID", "admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app", "admin.config.oauth.microsoft-client-id.description":
"Client ID of the Microsoft OAuth app",
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret", "admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app", "admin.config.oauth.microsoft-client-secret.description":
"Client secret of the Microsoft OAuth app",
"admin.config.oauth.discord-enabled": "Discord", "admin.config.oauth.discord-enabled": "Discord",
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled", "admin.config.oauth.discord-enabled.description":
"Whether Discord login is enabled",
"admin.config.oauth.discord-limited-guild": "Discord limited server ID", "admin.config.oauth.discord-limited-guild": "Discord limited server ID",
"admin.config.oauth.discord-limited-guild.description": "Limit signing in to users in a specific server. Leave it blank to disable.", "admin.config.oauth.discord-limited-guild.description":
"Limit signing in to users in a specific server. Leave it blank to disable.",
"admin.config.oauth.discord-client-id": "Discord Client ID", "admin.config.oauth.discord-client-id": "Discord Client ID",
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app", "admin.config.oauth.discord-client-id.description":
"Client ID of the Discord OAuth app",
"admin.config.oauth.discord-client-secret": "Discord Client secret", "admin.config.oauth.discord-client-secret": "Discord Client secret",
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app", "admin.config.oauth.discord-client-secret.description":
"Client secret of the Discord OAuth app",
"admin.config.oauth.oidc-enabled": "OpenID Connect", "admin.config.oauth.oidc-enabled": "OpenID Connect",
"admin.config.oauth.oidc-enabled.description": "Whether OpenID Connect login is enabled", "admin.config.oauth.oidc-enabled.description":
"Whether OpenID Connect login is enabled",
"admin.config.oauth.oidc-discovery-uri": "OpenID Connect Discovery URI", "admin.config.oauth.oidc-discovery-uri": "OpenID Connect Discovery URI",
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID Connect OAuth app", "admin.config.oauth.oidc-discovery-uri.description":
"Discovery URI of the OpenID Connect OAuth app",
"admin.config.oauth.oidc-username-claim": "OpenID Connect username claim", "admin.config.oauth.oidc-username-claim": "OpenID Connect username claim",
"admin.config.oauth.oidc-username-claim.description": "Username claim in OpenID Connect ID token. Leave it blank if you don't know what this config is.", "admin.config.oauth.oidc-username-claim.description":
"Username claim in OpenID Connect ID token. Leave it blank if you don't know what this config is.",
"admin.config.oauth.oidc-client-id": "OpenID Connect Client ID", "admin.config.oauth.oidc-client-id": "OpenID Connect Client ID",
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID Connect OAuth app", "admin.config.oauth.oidc-client-id.description":
"Client ID of the OpenID Connect OAuth app",
"admin.config.oauth.oidc-client-secret": "OpenID Connect Client secret", "admin.config.oauth.oidc-client-secret": "OpenID Connect Client secret",
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID Connect OAuth app", "admin.config.oauth.oidc-client-secret.description":
"Client secret of the OpenID Connect OAuth app",
// 404 // 404
"404.description": "Oops this page doesn't exist.", "404.description": "Oops this page doesn't exist.",
@@ -498,16 +522,22 @@ export default {
"error.description": "Oops!", "error.description": "Oops!",
"error.button.back": "Go back", "error.button.back": "Go back",
"error.msg.default": "Something went wrong.", "error.msg.default": "Something went wrong.",
"error.msg.access_denied": "You canceled the authentication process, please try again.", "error.msg.access_denied":
"error.msg.expired_token": "The authentication process took too long, please try again.", "You canceled the authentication process, please try again.",
"error.msg.expired_token":
"The authentication process took too long, please try again.",
"error.msg.invalid_token": "Internal Error", "error.msg.invalid_token": "Internal Error",
"error.msg.no_user": "User linked to this {0} account doesn't exist.", "error.msg.no_user": "User linked to this {0} account doesn't exist.",
"error.msg.no_email": "Can't get email address from this {0} account.", "error.msg.no_email": "Can't get email address from this {0} account.",
"error.msg.already_linked": "This {0} account is already linked to another account.", "error.msg.already_linked":
"This {0} account is already linked to another account.",
"error.msg.not_linked": "This {0} account haven't linked to any account yet.", "error.msg.not_linked": "This {0} account haven't linked to any account yet.",
"error.msg.unverified_account": "This {0} account is unverified, please try again after verification.", "error.msg.unverified_account":
"error.msg.discord_guild_permission_denied": "You are not allowed to sign in.", "This {0} account is unverified, please try again after verification.",
"error.msg.cannot_get_user_info": "Can not get your user info from this {0} account.", "error.msg.discord_guild_permission_denied":
"You are not allowed to sign in.",
"error.msg.cannot_get_user_info":
"Can not get your user info from this {0} account.",
"error.param.provider_github": "GitHub", "error.param.provider_github": "GitHub",
"error.param.provider_google": "Google", "error.param.provider_google": "Google",
"error.param.provider_microsoft": "Microsoft", "error.param.provider_microsoft": "Microsoft",
@@ -529,6 +559,7 @@ export default {
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Go to the link",
"common.text.or": "or", "common.text.or": "or",
"common.button.go-back": "Go back", "common.button.go-back": "Go back",
"common.button.go-home": "Go home",
"common.notify.copied": "Your link was copied to the clipboard", "common.notify.copied": "Your link was copied to the clipboard",
"common.success": "Success", "common.success": "Success",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "¿Olvidaste tu contraseña?", "resetPassword.title": "¿Olvidaste tu contraseña?",
"resetPassword.description": "Ingresa tu correo para restablecer 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.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Volver al inicio de sesión", "resetPassword.button.back": "Volver al inicio de sesión",
"resetPassword.text.resetPassword": "Restablecer contraseña", "resetPassword.text.resetPassword": "Restablecer contraseña",
"resetPassword.text.enterNewPassword": "Ingresa tu nueva contraseña", "resetPassword.text.enterNewPassword": "Ingresa tu nueva contraseña",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Ir al enlace", "common.text.navigate-to-link": "Ir al enlace",
"common.text.or": "o", "common.text.or": "o",
"common.button.go-back": "Volver", "common.button.go-back": "Volver",
"common.button.go-home": "Go home",
"common.notify.copied": "Tu enlace se ha copiado al portapapeles", "common.notify.copied": "Tu enlace se ha copiado al portapapeles",
"common.success": "Éxito", "common.success": "Éxito",
"common.error": "Error", "common.error": "Error",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Unohditko salasanan?", "resetPassword.title": "Unohditko salasanan?",
"resetPassword.description": "Kirjoita sähköpostiosoitteesi palauttaaksesi salasanasi.", "resetPassword.description": "Kirjoita sähköpostiosoitteesi palauttaaksesi salasanasi.",
"resetPassword.notify.success": "Sähköpostiosoite on lähetetty linkillä, jolla voit nollata salasanasi.", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Takaisin kirjautumiseen", "resetPassword.button.back": "Takaisin kirjautumiseen",
"resetPassword.text.resetPassword": "Nollaa salasana", "resetPassword.text.resetPassword": "Nollaa salasana",
"resetPassword.text.enterNewPassword": "Anna uusi salasana", "resetPassword.text.enterNewPassword": "Anna uusi salasana",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Go to the link",
"common.text.or": "tai", "common.text.or": "tai",
"common.button.go-back": "Takaisin", "common.button.go-back": "Takaisin",
"common.button.go-home": "Go home",
"common.notify.copied": "Linkki kopioitiin leikepöydälle", "common.notify.copied": "Linkki kopioitiin leikepöydälle",
"common.success": "Suoritettu", "common.success": "Suoritettu",
"common.error": "Virhe", "common.error": "Virhe",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Mot de passe oublié ?", "resetPassword.title": "Mot de passe oublié ?",
"resetPassword.description": "Saisissez votre courriel pour réinitialiser votre mot de passe.", "resetPassword.description": "Saisissez votre courriel pour réinitialiser votre mot de passe.",
"resetPassword.notify.success": "Un courriel a été envoyé avec un lien pour réinitialiser votre mot de passe.", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Retour à la page de connexion", "resetPassword.button.back": "Retour à la page de connexion",
"resetPassword.text.resetPassword": "Réinitialiser le mot de passe", "resetPassword.text.resetPassword": "Réinitialiser le mot de passe",
"resetPassword.text.enterNewPassword": "Saisissez votre nouveau mot de passe", "resetPassword.text.enterNewPassword": "Saisissez votre nouveau mot de passe",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Accéder au lien", "common.text.navigate-to-link": "Accéder au lien",
"common.text.or": "ou", "common.text.or": "ou",
"common.button.go-back": "Précédent", "common.button.go-back": "Précédent",
"common.button.go-home": "Accueil",
"common.notify.copied": "Votre lien a été copié dans le presse-papiers", "common.notify.copied": "Votre lien a été copié dans le presse-papiers",
"common.success": "Opération réussie", "common.success": "Opération réussie",
"common.error": "Erreur", "common.error": "Erreur",

View File

@@ -3,7 +3,7 @@ export default {
"navbar.upload": "Carica", "navbar.upload": "Carica",
"navbar.signin": "Registrati", "navbar.signin": "Registrati",
"navbar.home": "Home", "navbar.home": "Home",
"navbar.signup": "Registrati", "navbar.signup": "Accedi",
"navbar.links.shares": "Le mie condivisioni", "navbar.links.shares": "Le mie condivisioni",
"navbar.links.reverse": "Condivisioni Inverse", "navbar.links.reverse": "Condivisioni Inverse",
"navbar.avatar.account": "Il mio account", "navbar.avatar.account": "Il mio account",
@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Hai dimenticato la password?", "resetPassword.title": "Hai dimenticato la password?",
"resetPassword.description": "Inserisci la tua email per reimpostare la password.", "resetPassword.description": "Inserisci la tua email per reimpostare la password.",
"resetPassword.notify.success": "Abbiamo inviato un'e-mail all'indirizzo da te indicato. Clicca sul link contenuto nell'e-mail per reimpostare la password del tuo account.", "resetPassword.notify.success": "Un messaggio con un link per reimpostare la password è stato inviato se l'e-mail esiste.",
"resetPassword.button.back": "Torna alla pagina di login", "resetPassword.button.back": "Torna alla pagina di login",
"resetPassword.text.resetPassword": "Reimposta password", "resetPassword.text.resetPassword": "Reimposta password",
"resetPassword.text.enterNewPassword": "Inserisci la tua nuova password", "resetPassword.text.enterNewPassword": "Inserisci la tua nuova password",
@@ -262,7 +262,7 @@ export default {
"share.table.name": "Nome", "share.table.name": "Nome",
"share.table.size": "Dimensione", "share.table.size": "Dimensione",
"share.modal.file-preview.error.not-supported.title": "Anteprima non supportata", "share.modal.file-preview.error.not-supported.title": "Anteprima non supportata",
"share.modal.file-preview.error.not-supported.description": "A preview for this file type is unsupported. Please download the file to view it.", "share.modal.file-preview.error.not-supported.description": "Un'anteprima per questo tipo di file non è supportata. Per favore scarica il file per visualizzarlo.",
// END /share/[id] // END /share/[id]
// /share/[id]/edit // /share/[id]/edit
"share.edit.title": "Modifica {shareId}", "share.edit.title": "Modifica {shareId}",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Vai al collegamento", "common.text.navigate-to-link": "Vai al collegamento",
"common.text.or": "o", "common.text.or": "o",
"common.button.go-back": "Torna indietro", "common.button.go-back": "Torna indietro",
"common.button.go-home": "Vai alla Home Page",
"common.notify.copied": "Il tuo collegamento e' stato copiato negli appunti", "common.notify.copied": "Il tuo collegamento e' stato copiato negli appunti",
"common.success": "Successo", "common.success": "Successo",
"common.error": "Errore", "common.error": "Errore",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "パスワードを忘れてしまいましたか?", "resetPassword.title": "パスワードを忘れてしまいましたか?",
"resetPassword.description": "登録しているメールアドレスを入力してください。", "resetPassword.description": "登録しているメールアドレスを入力してください。",
"resetPassword.notify.success": "パスワードをリセットするためのリンクをメールで送信しました。", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "サインインページに戻る", "resetPassword.button.back": "サインインページに戻る",
"resetPassword.text.resetPassword": "パスワードをリセット", "resetPassword.text.resetPassword": "パスワードをリセット",
"resetPassword.text.enterNewPassword": "新規パスワードを入力", "resetPassword.text.enterNewPassword": "新規パスワードを入力",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Go to the link",
"common.text.or": "または", "common.text.or": "または",
"common.button.go-back": "戻る", "common.button.go-back": "戻る",
"common.button.go-home": "Go home",
"common.notify.copied": "リンクをクリップボードにコピーしました", "common.notify.copied": "リンクをクリップボードにコピーしました",
"common.success": "成功", "common.success": "成功",
"common.error": "エラー", "common.error": "エラー",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Wachtwoord vergeten?", "resetPassword.title": "Wachtwoord vergeten?",
"resetPassword.description": "Voer uw e-mailadres in om uw wachtwoord opnieuw in te stellen.", "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.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Terug naar login pagina", "resetPassword.button.back": "Terug naar login pagina",
"resetPassword.text.resetPassword": "Wachtwoord opnieuw instellen", "resetPassword.text.resetPassword": "Wachtwoord opnieuw instellen",
"resetPassword.text.enterNewPassword": "Voer uw nieuwe wachtwoord in", "resetPassword.text.enterNewPassword": "Voer uw nieuwe wachtwoord in",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Ga naar de koppeling", "common.text.navigate-to-link": "Ga naar de koppeling",
"common.text.or": "of", "common.text.or": "of",
"common.button.go-back": "Ga terug", "common.button.go-back": "Ga terug",
"common.button.go-home": "Go home",
"common.notify.copied": "Uw link is gekopieerd naar het klembord", "common.notify.copied": "Uw link is gekopieerd naar het klembord",
"common.success": "Succes", "common.success": "Succes",
"common.error": "Fout", "common.error": "Fout",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Nie pamiętasz hasła?", "resetPassword.title": "Nie pamiętasz hasła?",
"resetPassword.description": "Wprowadź swój e-mail, aby zresetować swoje hasło.", "resetPassword.description": "Wprowadź swój e-mail, aby zresetować swoje hasło.",
"resetPassword.notify.success": "Został wysłany e-mail z linkiem do resetowania hasła.", "resetPassword.notify.success": "Jeśli e-mail istnieje, to została wysłana wiadomość z linkiem do zresetowania hasła.",
"resetPassword.button.back": "Powrót do strony logowania", "resetPassword.button.back": "Powrót do strony logowania",
"resetPassword.text.resetPassword": "Resetuj hasło", "resetPassword.text.resetPassword": "Resetuj hasło",
"resetPassword.text.enterNewPassword": "Wprowadź nowe hasło", "resetPassword.text.enterNewPassword": "Wprowadź nowe hasło",
@@ -262,7 +262,7 @@ export default {
"share.table.name": "Nazwa", "share.table.name": "Nazwa",
"share.table.size": "Rozmiar", "share.table.size": "Rozmiar",
"share.modal.file-preview.error.not-supported.title": "Podgląd nie jest obsługiwany", "share.modal.file-preview.error.not-supported.title": "Podgląd nie jest obsługiwany",
"share.modal.file-preview.error.not-supported.description": "A preview for this file type is unsupported. Please download the file to view it.", "share.modal.file-preview.error.not-supported.description": "Podgląd dla tego typu pliku nie jest obsługiwany. Pobierz plik, aby go zobaczyć.",
// END /share/[id] // END /share/[id]
// /share/[id]/edit // /share/[id]/edit
"share.edit.title": "Edytuj {shareId}", "share.edit.title": "Edytuj {shareId}",
@@ -404,9 +404,10 @@ export default {
"common.button.generate": "Wygeneruj", "common.button.generate": "Wygeneruj",
"common.button.done": "Gotowe", "common.button.done": "Gotowe",
"common.text.link": "Link", "common.text.link": "Link",
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Przejdź do linku",
"common.text.or": "lub", "common.text.or": "lub",
"common.button.go-back": "Wróć", "common.button.go-back": "Wróć",
"common.button.go-home": "Wróć do ekranu głównego",
"common.notify.copied": "Link został skopiowany do schowka", "common.notify.copied": "Link został skopiowany do schowka",
"common.success": "Zakończono pomyślnie", "common.success": "Zakończono pomyślnie",
"common.error": "Błąd", "common.error": "Błąd",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Esqueceu a sua senha?", "resetPassword.title": "Esqueceu a sua senha?",
"resetPassword.description": "Insira o seu e-mail para redefinir 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.notify.success": "Uma mensagem com um link para redefinir sua senha foi enviada se o e-mail existir.",
"resetPassword.button.back": "Voltar para a página inicial", "resetPassword.button.back": "Voltar para a página inicial",
"resetPassword.text.resetPassword": "Redefinir senha", "resetPassword.text.resetPassword": "Redefinir senha",
"resetPassword.text.enterNewPassword": "Digite uma nova senha", "resetPassword.text.enterNewPassword": "Digite uma nova senha",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Ir para o link", "common.text.navigate-to-link": "Ir para o link",
"common.text.or": "ou", "common.text.or": "ou",
"common.button.go-back": "Voltar", "common.button.go-back": "Voltar",
"common.button.go-home": "Voltar para o Início",
"common.notify.copied": "O seu link foi copiado para a área de transferência", "common.notify.copied": "O seu link foi copiado para a área de transferência",
"common.success": "Sucesso", "common.success": "Sucesso",
"common.error": "Erro", "common.error": "Erro",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Забыли пароль?", "resetPassword.title": "Забыли пароль?",
"resetPassword.description": "Введите ваш email для восстановления пароля.", "resetPassword.description": "Введите ваш email для восстановления пароля.",
"resetPassword.notify.success": "Вам направлено письмо со ссылкой для сброса пароля.", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Вернуться на страницу входа", "resetPassword.button.back": "Вернуться на страницу входа",
"resetPassword.text.resetPassword": "Сбросить пароль", "resetPassword.text.resetPassword": "Сбросить пароль",
"resetPassword.text.enterNewPassword": "Введите новый пароль", "resetPassword.text.enterNewPassword": "Введите новый пароль",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Go to the link",
"common.text.or": "или", "common.text.or": "или",
"common.button.go-back": "Назад", "common.button.go-back": "Назад",
"common.button.go-home": "Go home",
"common.notify.copied": "Ваша ссылка скопирована в буфер обмена", "common.notify.copied": "Ваша ссылка скопирована в буфер обмена",
"common.success": "Успешно", "common.success": "Успешно",
"common.error": "Ошибочка", "common.error": "Ошибочка",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Ste pozabili svoje geslo?", "resetPassword.title": "Ste pozabili svoje geslo?",
"resetPassword.description": "Vnesite svoj e-poštni naslov za ponastavitev gesla.", "resetPassword.description": "Vnesite svoj e-poštni naslov za ponastavitev gesla.",
"resetPassword.notify.success": "Poslali smo e-pošto s povezavo do spremembe gesla.", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Nazaj na stran za prijavo", "resetPassword.button.back": "Nazaj na stran za prijavo",
"resetPassword.text.resetPassword": "Ponastavi geslo", "resetPassword.text.resetPassword": "Ponastavi geslo",
"resetPassword.text.enterNewPassword": "Vnesite novo geslo", "resetPassword.text.enterNewPassword": "Vnesite novo geslo",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Pojdi na povezavo", "common.text.navigate-to-link": "Pojdi na povezavo",
"common.text.or": "ali", "common.text.or": "ali",
"common.button.go-back": "Nazaj", "common.button.go-back": "Nazaj",
"common.button.go-home": "Pojdi domov",
"common.notify.copied": "Povezava je bila kopirana v odložišče", "common.notify.copied": "Povezava je bila kopirana v odložišče",
"common.success": "Uspešno", "common.success": "Uspešno",
"common.error": "Napaka", "common.error": "Napaka",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Заборавили сте лозинку?", "resetPassword.title": "Заборавили сте лозинку?",
"resetPassword.description": "Унесите своју е-пошту да бисте ресетовали лозинку.", "resetPassword.description": "Унесите своју е-пошту да бисте ресетовали лозинку.",
"resetPassword.notify.success": "Послат је емаил са везом за ресетовање ваше лозинке.", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Назад на страницу за пријаву", "resetPassword.button.back": "Назад на страницу за пријаву",
"resetPassword.text.resetPassword": "Обнови лозинку", "resetPassword.text.resetPassword": "Обнови лозинку",
"resetPassword.text.enterNewPassword": "Унесите вашу нову лозинку", "resetPassword.text.enterNewPassword": "Унесите вашу нову лозинку",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Go to the link",
"common.text.or": "или", "common.text.or": "или",
"common.button.go-back": "Иди назад", "common.button.go-back": "Иди назад",
"common.button.go-home": "Go home",
"common.notify.copied": "Ваша веза је копирана у clipboard", "common.notify.copied": "Ваша веза је копирана у clipboard",
"common.success": "Успешно", "common.success": "Успешно",
"common.error": "Грешка", "common.error": "Грешка",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "Glömt ditt lösenord?", "resetPassword.title": "Glömt ditt lösenord?",
"resetPassword.description": "Ange din e-postadress för att återställa ditt lösenord.", "resetPassword.description": "Ange din e-postadress för att återställa ditt lösenord.",
"resetPassword.notify.success": "Ett e-postmeddelande har skickats till dig med en länk för att återställa lösenordet.", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "Tillbaka till inloggningssidan", "resetPassword.button.back": "Tillbaka till inloggningssidan",
"resetPassword.text.resetPassword": "Återställ lösenord", "resetPassword.text.resetPassword": "Återställ lösenord",
"resetPassword.text.enterNewPassword": "Ange ditt nya lösenord", "resetPassword.text.enterNewPassword": "Ange ditt nya lösenord",
@@ -262,7 +262,7 @@ export default {
"share.table.name": "Namn", "share.table.name": "Namn",
"share.table.size": "Storlek", "share.table.size": "Storlek",
"share.modal.file-preview.error.not-supported.title": "Förhandsgranskning stöds ej", "share.modal.file-preview.error.not-supported.title": "Förhandsgranskning stöds ej",
"share.modal.file-preview.error.not-supported.description": "A preview for this file type is unsupported. Please download the file to view it.", "share.modal.file-preview.error.not-supported.description": "En förhandsvisning för filtypen stöds inte. Ladda ner filen för att se den.",
// END /share/[id] // END /share/[id]
// /share/[id]/edit // /share/[id]/edit
"share.edit.title": "Redigera {shareId}", "share.edit.title": "Redigera {shareId}",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Gå till länken", "common.text.navigate-to-link": "Gå till länken",
"common.text.or": "eller", "common.text.or": "eller",
"common.button.go-back": "Gå tillbaka", "common.button.go-back": "Gå tillbaka",
"common.button.go-home": "Gå hem",
"common.notify.copied": "Din länk har kopierats till urklipp", "common.notify.copied": "Din länk har kopierats till urklipp",
"common.success": "Slutförd", "common.success": "Slutförd",
"common.error": "Fel", "common.error": "Fel",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "ลืมรหัสผ่าน?", "resetPassword.title": "ลืมรหัสผ่าน?",
"resetPassword.description": "กรุณาใส่อีเมล์์์์์์์ของคุณเพื่อรีเซ็ตรหัสผ่าน", "resetPassword.description": "กรุณาใส่อีเมล์์์์์์์ของคุณเพื่อรีเซ็ตรหัสผ่าน",
"resetPassword.notify.success": "ส่งอีเมล์์์์์์์พร้อมลิงค์เพื่อรีเซ็ตรหัสผ่านของคุณแล้ว", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "กลับไปที่หน้าลงชื่อเข้าใช้", "resetPassword.button.back": "กลับไปที่หน้าลงชื่อเข้าใช้",
"resetPassword.text.resetPassword": "รีเซ็ตรหัสผ่าน", "resetPassword.text.resetPassword": "รีเซ็ตรหัสผ่าน",
"resetPassword.text.enterNewPassword": "ป้อนรหัสผ่านใหม่ของคุณ", "resetPassword.text.enterNewPassword": "ป้อนรหัสผ่านใหม่ของคุณ",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Go to the link",
"common.text.or": "หรือ", "common.text.or": "หรือ",
"common.button.go-back": "ย้อนกลับ", "common.button.go-back": "ย้อนกลับ",
"common.button.go-home": "Go home",
"common.notify.copied": "คัดลอกไปยังคลิปบอร์ดแล้ว", "common.notify.copied": "คัดลอกไปยังคลิปบอร์ดแล้ว",
"common.success": "สำเร็จ", "common.success": "สำเร็จ",
"common.error": "เกิดข้อผิดพลาด", "common.error": "เกิดข้อผิดพลาด",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "忘记密码?", "resetPassword.title": "忘记密码?",
"resetPassword.description": "请输入电子邮件接受重置密码邮件", "resetPassword.description": "请输入电子邮件接受重置密码邮件",
"resetPassword.notify.success": "一封包含密码重置地址的邮件已发送到你的邮箱中", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "返回登录页面", "resetPassword.button.back": "返回登录页面",
"resetPassword.text.resetPassword": "重置密码", "resetPassword.text.resetPassword": "重置密码",
"resetPassword.text.enterNewPassword": "请输入新密码", "resetPassword.text.enterNewPassword": "请输入新密码",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "访问链接", "common.text.navigate-to-link": "访问链接",
"common.text.or": "或", "common.text.or": "或",
"common.button.go-back": "返回", "common.button.go-back": "返回",
"common.button.go-home": "Go home",
"common.notify.copied": "已复制到剪贴板", "common.notify.copied": "已复制到剪贴板",
"common.success": "成功", "common.success": "成功",
"common.error": "错误", "common.error": "错误",

View File

@@ -57,7 +57,7 @@ export default {
// /auth/reset-password // /auth/reset-password
"resetPassword.title": "忘記密碼?", "resetPassword.title": "忘記密碼?",
"resetPassword.description": "請輸入Email以讓系統寄送重置密碼郵件", "resetPassword.description": "請輸入Email以讓系統寄送重置密碼郵件",
"resetPassword.notify.success": "一封包含密碼重置地址的郵件已發送到您的Email", "resetPassword.notify.success": "A message with a link to reset your password has been sent if the email exists.",
"resetPassword.button.back": "返回登入頁面", "resetPassword.button.back": "返回登入頁面",
"resetPassword.text.resetPassword": "重置密碼", "resetPassword.text.resetPassword": "重置密碼",
"resetPassword.text.enterNewPassword": "請輸入新密碼", "resetPassword.text.enterNewPassword": "請輸入新密碼",
@@ -407,6 +407,7 @@ export default {
"common.text.navigate-to-link": "Go to the link", "common.text.navigate-to-link": "Go to the link",
"common.text.or": "或", "common.text.or": "或",
"common.button.go-back": "返回", "common.button.go-back": "返回",
"common.button.go-home": "Go home",
"common.notify.copied": "已複製到剪貼簿", "common.notify.copied": "已複製到剪貼簿",
"common.success": "成功", "common.success": "成功",
"common.error": "錯誤", "common.error": "錯誤",

View File

@@ -21,9 +21,8 @@ export async function middleware(request: NextRequest) {
}; };
// Get config from backend // Get config from backend
const config = await ( const apiUrl = process.env.API_URL || "http://localhost:8080";
await fetch(`${request.nextUrl.origin}/api/configs`) const config = await (await fetch(`${apiUrl}/api/configs`)).json();
).json();
const getConfig = (key: string) => { const getConfig = (key: string) => {
return configService.get(key, config); return configService.get(key, config);

View File

@@ -12,6 +12,7 @@ import { getCookie, setCookie } from "cookies-next";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import getConfig from "next/config"; import getConfig from "next/config";
import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { IntlProvider } from "react-intl"; import { IntlProvider } from "react-intl";
@@ -49,7 +50,12 @@ function App({ Component, pageProps }: AppProps) {
}, [router.pathname]); }, [router.pathname]);
useEffect(() => { useEffect(() => {
setInterval(async () => await authService.refreshAccessToken(), 30 * 1000); const interval = setInterval(
async () => await authService.refreshAccessToken(),
2 * 60 * 1000, // 2 minutes
);
return () => clearInterval(interval);
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -80,57 +86,65 @@ function App({ Component, pageProps }: AppProps) {
const language = useRef(pageProps.language); const language = useRef(pageProps.language);
return ( return (
<IntlProvider <>
messages={i18nUtil.getLocaleByCode(language.current)?.messages} <Head>
locale={language.current} <meta
defaultLocale={LOCALES.ENGLISH.code} name="viewport"
> content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
<MantineProvider />
withGlobalStyles </Head>
withNormalizeCSS <IntlProvider
theme={{ colorScheme, ...globalStyle }} messages={i18nUtil.getLocaleByCode(language.current)?.messages}
locale={language.current}
defaultLocale={LOCALES.ENGLISH.code}
> >
<ColorSchemeProvider <MantineProvider
colorScheme={colorScheme} withGlobalStyles
toggleColorScheme={toggleColorScheme} withNormalizeCSS
theme={{ colorScheme, ...globalStyle }}
> >
<GlobalStyle /> <ColorSchemeProvider
<Notifications /> colorScheme={colorScheme}
<ModalsProvider> toggleColorScheme={toggleColorScheme}
<ConfigContext.Provider >
value={{ <GlobalStyle />
configVariables, <Notifications />
refresh: async () => { <ModalsProvider>
setConfigVariables(await configService.list()); <ConfigContext.Provider
},
}}
>
<UserContext.Provider
value={{ value={{
user, configVariables,
refreshUser: async () => { refresh: async () => {
const user = await userService.getCurrentUser(); setConfigVariables(await configService.list());
setUser(user);
return user;
}, },
}} }}
> >
{excludeDefaultLayoutRoutes.includes(route) ? ( <UserContext.Provider
<Component {...pageProps} /> value={{
) : ( user,
<> refreshUser: async () => {
<Header /> const user = await userService.getCurrentUser();
<Container> setUser(user);
<Component {...pageProps} /> return user;
</Container> },
</> }}
)} >
</UserContext.Provider> {excludeDefaultLayoutRoutes.includes(route) ? (
</ConfigContext.Provider> <Component {...pageProps} />
</ModalsProvider> ) : (
</ColorSchemeProvider> <>
</MantineProvider> <Header />
</IntlProvider> <Container>
<Component {...pageProps} />
</Container>
</>
)}
</UserContext.Provider>
</ConfigContext.Provider>
</ModalsProvider>
</ColorSchemeProvider>
</MantineProvider>
</IntlProvider>
</>
); );
} }

View File

@@ -12,7 +12,7 @@ export const config = {
const { apiURL } = getConfig().serverRuntimeConfig; const { apiURL } = getConfig().serverRuntimeConfig;
// A proxy to the API server only used in development. // A proxy to the API server only used in development.
// In production this route gets overridden by nginx. // In production this route gets overridden by Caddy.
export default (req: NextApiRequest, res: NextApiResponse) => { export default (req: NextApiRequest, res: NextApiResponse) => {
httpProxyMiddleware(req, res, { httpProxyMiddleware(req, res, {
headers: { headers: {

View File

@@ -37,6 +37,7 @@ const Share = ({ shareId }: { shareId: string }) => {
modals, modals,
t("share.error.visitor-limit-exceeded.title"), t("share.error.visitor-limit-exceeded.title"),
t("share.error.visitor-limit-exceeded.description"), t("share.error.visitor-limit-exceeded.description"),
"go-home",
); );
} else { } else {
toast.axiosError(e); toast.axiosError(e);
@@ -58,12 +59,14 @@ const Share = ({ shareId }: { shareId: string }) => {
modals, modals,
t("share.error.removed.title"), t("share.error.removed.title"),
e.response.data.message, e.response.data.message,
"go-home",
); );
} else { } else {
showErrorModal( showErrorModal(
modals, modals,
t("share.error.not-found.title"), t("share.error.not-found.title"),
t("share.error.not-found.description"), t("share.error.not-found.description"),
"go-home",
); );
} }
} else if (error == "share_password_required") { } else if (error == "share_password_required") {
@@ -71,7 +74,12 @@ const Share = ({ shareId }: { shareId: string }) => {
} else if (error == "share_token_required") { } else if (error == "share_token_required") {
getShareToken(); getShareToken();
} else { } else {
showErrorModal(modals, t("common.error"), t("common.error.unknown")); showErrorModal(
modals,
t("common.error"),
t("common.error.unknown"),
"go-home",
);
} }
}); });
}; };

View File

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

View File

@@ -36,8 +36,10 @@ const signOut = async () => {
const refreshAccessToken = async () => { const refreshAccessToken = async () => {
try { try {
const accessToken = getCookie("access_token") as string; const accessToken = getCookie("access_token") as string;
// If the access token expires in less than 2 minutes refresh it
if ( if (
!accessToken || accessToken &&
(jose.decodeJwt(accessToken).exp ?? 0) * 1000 < Date.now() + 2 * 60 * 1000 (jose.decodeJwt(accessToken).exp ?? 0) * 1000 < Date.now() + 2 * 60 * 1000
) { ) {
await api.post("/auth/token"); await api.post("/auth/token");

View File

@@ -8,11 +8,13 @@ const GlobalStyle = () => {
color: "inherit", color: "inherit",
textDecoration: "none", textDecoration: "none",
}, },
"table.md, table.md th:nth-of-type(odd), table.md td:nth-of-type(odd)": { "table.md, table.md th:nth-of-type(odd), table.md td:nth-of-type(odd)":
background: theme.colorScheme == "dark" {
? "rgba(50, 50, 50, 0.5)" background:
: "rgba(220, 220, 220, 0.5)", theme.colorScheme == "dark"
}, ? "rgba(50, 50, 50, 0.5)"
: "rgba(220, 220, 220, 0.5)",
},
"table.md td": { "table.md td": {
paddingLeft: "0.5em", paddingLeft: "0.5em",
paddingRight: "0.5em", paddingRight: "0.5em",

View File

@@ -1,22 +0,0 @@
events {}
http {
server {
listen 3000;
client_max_body_size 100M;
location /api {
proxy_pass http://localhost:8080;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://localhost:3333;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

View File

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