mirror of
https://github.com/stonith404/pingvin-share.git
synced 2024-06-27 13:20:48 +02:00
feat: localization (#196)
* Started adding locale translations :) * Added some more translations * Working on translating even more pages * More translations * Added test default locale retrieval * replace `intl.formatMessage` with custom `t` hook * add more translations * improve title syntax * add more translations * translate admin config page * translated error messages * add language selecter * minor fixes * improve language handling * add upcoming languages * add `crowdin.yml` * run formatter --------- Co-authored-by: Steve Tautonico <stautonico@gmail.com>
This commit is contained in:
parent
7c5ec8d0ea
commit
b9f6e3bd08
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `description` on the `Config` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Config" (
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"defaultValue" TEXT NOT NULL DEFAULT '',
|
||||
"value" TEXT,
|
||||
"obscured" BOOLEAN NOT NULL DEFAULT false,
|
||||
"secret" BOOLEAN NOT NULL DEFAULT true,
|
||||
"locked" BOOLEAN NOT NULL DEFAULT false,
|
||||
"order" INTEGER NOT NULL,
|
||||
|
||||
PRIMARY KEY ("name", "category")
|
||||
);
|
||||
INSERT INTO "new_Config" ("category", "defaultValue", "locked", "name", "obscured", "order", "secret", "type", "updatedAt", "value") SELECT "category", "defaultValue", "locked", "name", "obscured", "order", "secret", "type", "updatedAt", "value" FROM "Config";
|
||||
DROP TABLE "Config";
|
||||
ALTER TABLE "new_Config" RENAME TO "Config";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
|
@ -136,7 +136,6 @@ model Config {
|
|||
type String
|
||||
defaultValue String @default("")
|
||||
value String?
|
||||
description String
|
||||
obscured Boolean @default(false)
|
||||
secret Boolean @default(true)
|
||||
locked Boolean @default(false)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
import * as crypto from "crypto";
|
||||
|
||||
const configVariables: ConfigVariables = {
|
||||
internal: {
|
||||
jwtSecret: {
|
||||
description: "Long random string used to sign JWT tokens",
|
||||
type: "string",
|
||||
defaultValue: crypto.randomBytes(256).toString("base64"),
|
||||
locked: true,
|
||||
|
@ -11,20 +11,17 @@ const configVariables: ConfigVariables = {
|
|||
},
|
||||
general: {
|
||||
appName: {
|
||||
description: "Name of the application",
|
||||
type: "string",
|
||||
defaultValue: "Pingvin Share",
|
||||
secret: false,
|
||||
},
|
||||
appUrl: {
|
||||
description: "On which URL Pingvin Share is available",
|
||||
type: "string",
|
||||
defaultValue: "http://localhost:3000",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
showHomePage: {
|
||||
description: "Whether to show the home page",
|
||||
type: "boolean",
|
||||
defaultValue: "true",
|
||||
secret: false,
|
||||
|
@ -32,21 +29,17 @@ const configVariables: ConfigVariables = {
|
|||
},
|
||||
share: {
|
||||
allowRegistration: {
|
||||
description: "Whether registration is allowed",
|
||||
type: "boolean",
|
||||
defaultValue: "true",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
allowUnauthenticatedShares: {
|
||||
description: "Whether unauthorized users can create shares",
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
maxSize: {
|
||||
description: "Maximum share size in bytes",
|
||||
type: "number",
|
||||
defaultValue: "1073741824",
|
||||
|
||||
|
@ -55,61 +48,43 @@ const configVariables: ConfigVariables = {
|
|||
},
|
||||
email: {
|
||||
enableShareEmailRecipients: {
|
||||
description:
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
shareRecipientsSubject: {
|
||||
description:
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
type: "string",
|
||||
defaultValue: "Files shared with you",
|
||||
},
|
||||
shareRecipientsMessage: {
|
||||
description:
|
||||
"Message which gets sent to the share recipients.\n\nAvailable variables:\n{creator} - The username of the creator of the share\n{shareUrl} - The URL of the share\n{desc} - The description of the share\n{expires} - The expiration date of the share\n\nVariables will be replaced with the actual values.",
|
||||
type: "text",
|
||||
defaultValue:
|
||||
"Hey!\n\n{creator} shared some files with you, view or download the files with this link: {shareUrl}\n\nThe share will expire {expires}.\n\nNote: {desc}\n\nShared securely with Pingvin Share 🐧",
|
||||
},
|
||||
reverseShareSubject: {
|
||||
description:
|
||||
"Subject of the email which gets sent when someone created a share with your reverse share link.",
|
||||
type: "string",
|
||||
defaultValue: "Reverse share link used",
|
||||
},
|
||||
reverseShareMessage: {
|
||||
description:
|
||||
"Message which gets sent when someone created a share with your reverse share link. {shareUrl} will be replaced with the creator's name and the share URL.",
|
||||
type: "text",
|
||||
defaultValue:
|
||||
"Hey!\n\nA share was just created with your reverse share link: {shareUrl}\n\nShared securely with Pingvin Share 🐧",
|
||||
},
|
||||
resetPasswordSubject: {
|
||||
description:
|
||||
"Subject of the email which gets sent when a user requests a password reset.",
|
||||
type: "string",
|
||||
defaultValue: "Pingvin Share password reset",
|
||||
},
|
||||
resetPasswordMessage: {
|
||||
description:
|
||||
"Message which gets sent when a user requests a password reset. {url} will be replaced with the reset password URL.",
|
||||
type: "text",
|
||||
defaultValue:
|
||||
"Hey!\n\nYou requested a password reset. Click this link to reset your password: {url}\nThe link expires in a hour.\n\nPingvin Share 🐧",
|
||||
},
|
||||
inviteSubject: {
|
||||
description:
|
||||
"Subject of the email which gets sent when an admin invites an user.",
|
||||
type: "string",
|
||||
defaultValue: "Pingvin Share invite",
|
||||
},
|
||||
inviteMessage: {
|
||||
description:
|
||||
"Message which gets sent when an admin invites an user. {url} will be replaced with the invite URL and {password} with the password.",
|
||||
type: "text",
|
||||
defaultValue:
|
||||
"Hey!\n\nYou were invited to Pingvin Share. Click this link to accept the invite: {url}\n\nYour password is: {password}\n\nPingvin Share 🐧",
|
||||
|
@ -117,34 +92,27 @@ const configVariables: ConfigVariables = {
|
|||
},
|
||||
smtp: {
|
||||
enabled: {
|
||||
description:
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
secret: false,
|
||||
},
|
||||
host: {
|
||||
description: "Host of the SMTP server",
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
port: {
|
||||
description: "Port of the SMTP server",
|
||||
type: "number",
|
||||
defaultValue: "0",
|
||||
},
|
||||
email: {
|
||||
description: "Email address which the emails get sent from",
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
username: {
|
||||
description: "Username of the SMTP server",
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
password: {
|
||||
description: "Password of the SMTP server",
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
obscured: true,
|
||||
|
|
|
@ -14,9 +14,6 @@ export class AdminConfigDTO extends ConfigDTO {
|
|||
@Expose()
|
||||
updatedAt: Date;
|
||||
|
||||
@Expose()
|
||||
description: string;
|
||||
|
||||
@Expose()
|
||||
obscured: boolean;
|
||||
|
||||
|
|
3
crowdin.yml
Normal file
3
crowdin.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
files:
|
||||
- source: /frontend/src/i18n/translations/en.ts
|
||||
translation: /%original_path%/%two_letters_code%.ts
|
|
@ -1,5 +1,10 @@
|
|||
{
|
||||
"extends": ["eslint-config-next", "eslint:recommended", "prettier"],
|
||||
"extends": [
|
||||
"next/babel",
|
||||
"eslint-config-next",
|
||||
"eslint:recommended",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": ["react"],
|
||||
"rules": {
|
||||
"quotes": ["warn", "double", { "allowTemplateLiterals": true }],
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
|
||||
const { version } = require('./package.json');
|
||||
|
||||
const withPWA = require("next-pwa")({
|
||||
|
|
266
frontend/package-lock.json
generated
266
frontend/package-lock.json
generated
|
@ -32,6 +32,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-intl": "^6.3.1",
|
||||
"sharp": "^0.31.3",
|
||||
"yup": "^1.0.2"
|
||||
},
|
||||
|
@ -1860,6 +1861,92 @@
|
|||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",
|
||||
"integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "0.2.32",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/fast-memoize": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.0.0.tgz",
|
||||
"integrity": "sha512-U88W/qhEs5ZuX+Inw/DZHjA6w2YCTWTNzTkprzNznyWoGl8h+XtlOCW3nM78+VX7lSbvpMdnaHmWLnDnjJjuwg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz",
|
||||
"integrity": "sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/icu-skeleton-parser": "1.3.18",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz",
|
||||
"integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl": {
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.6.8.tgz",
|
||||
"integrity": "sha512-lWfFUBGvYfeQoIaW5/6TW7WdyiqfWg5wPFVnsb1eKs4JPmAz2nYBUPuKbyvBiRV87QCSNZ+tZTViFBU+bIHeqQ==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/fast-memoize": "2.0.0",
|
||||
"@formatjs/icu-messageformat-parser": "2.3.0",
|
||||
"@formatjs/intl-displaynames": "6.2.6",
|
||||
"@formatjs/intl-listformat": "7.1.9",
|
||||
"intl-messageformat": "10.3.2",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^4.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-displaynames": {
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.2.6.tgz",
|
||||
"integrity": "sha512-scf5AQTk9EjpvPhboo5sizVOvidTdMOnajv9z+0cejvl7JNl9bl/aMrNBgC72UH+bP3l45usPUKAGskV6sNIrA==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/intl-localematcher": "0.2.32",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-listformat": {
|
||||
"version": "7.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.1.9.tgz",
|
||||
"integrity": "sha512-5YikxwRqRXTVWVujhswDOTCq6gs+m9IcNbNZLa6FLtyBStAjEsuE2vAU+lPsbz9ZTST57D5fodjIh2JXT6sMWQ==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/intl-localematcher": "0.2.32",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.2.32",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
|
||||
"integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
|
||||
|
@ -2625,6 +2712,15 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-proxy": {
|
||||
"version": "1.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.3.tgz",
|
||||
|
@ -2668,14 +2764,12 @@
|
|||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.0.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz",
|
||||
"integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
|
@ -2702,8 +2796,7 @@
|
|||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.2",
|
||||
|
@ -5349,6 +5442,17 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat": {
|
||||
"version": "10.3.2",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.2.tgz",
|
||||
"integrity": "sha512-kGY1KrpxPGbWX/yz6rpWQahBh5bJC6pIbq/cTzVYlmAYjRVzP+l2MulagbZf/5mABbcLT/0RJbZC46Iw6Mhmtw==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/fast-memoize": "2.0.0",
|
||||
"@formatjs/icu-messageformat-parser": "2.3.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
|
@ -6863,6 +6967,32 @@
|
|||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-intl": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.3.1.tgz",
|
||||
"integrity": "sha512-wI7YSxS2xQRYrHPMFSHXSWNuOLrgt9L0Dg8G35xJNfDRpcyebE9H1k2/DDPxgbV4ImeG/qKJMN5SrZV7fuB/Ag==",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/icu-messageformat-parser": "2.3.0",
|
||||
"@formatjs/intl": "2.6.8",
|
||||
"@formatjs/intl-displaynames": "6.2.6",
|
||||
"@formatjs/intl-listformat": "7.1.9",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/react": "16 || 17 || 18",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"intl-messageformat": "10.3.2",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.6.0 || 17 || 18",
|
||||
"typescript": "^4.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
@ -7988,7 +8118,7 @@
|
|||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
@ -9950,6 +10080,84 @@
|
|||
"@floating-ui/dom": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"@formatjs/ecma402-abstract": {
|
||||
"version": "1.14.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",
|
||||
"integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==",
|
||||
"requires": {
|
||||
"@formatjs/intl-localematcher": "0.2.32",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/fast-memoize": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.0.0.tgz",
|
||||
"integrity": "sha512-U88W/qhEs5ZuX+Inw/DZHjA6w2YCTWTNzTkprzNznyWoGl8h+XtlOCW3nM78+VX7lSbvpMdnaHmWLnDnjJjuwg==",
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/icu-messageformat-parser": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz",
|
||||
"integrity": "sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/icu-skeleton-parser": "1.3.18",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/icu-skeleton-parser": {
|
||||
"version": "1.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz",
|
||||
"integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/intl": {
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl/-/intl-2.6.8.tgz",
|
||||
"integrity": "sha512-lWfFUBGvYfeQoIaW5/6TW7WdyiqfWg5wPFVnsb1eKs4JPmAz2nYBUPuKbyvBiRV87QCSNZ+tZTViFBU+bIHeqQ==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/fast-memoize": "2.0.0",
|
||||
"@formatjs/icu-messageformat-parser": "2.3.0",
|
||||
"@formatjs/intl-displaynames": "6.2.6",
|
||||
"@formatjs/intl-listformat": "7.1.9",
|
||||
"intl-messageformat": "10.3.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-displaynames": {
|
||||
"version": "6.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-displaynames/-/intl-displaynames-6.2.6.tgz",
|
||||
"integrity": "sha512-scf5AQTk9EjpvPhboo5sizVOvidTdMOnajv9z+0cejvl7JNl9bl/aMrNBgC72UH+bP3l45usPUKAGskV6sNIrA==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/intl-localematcher": "0.2.32",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-listformat": {
|
||||
"version": "7.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-listformat/-/intl-listformat-7.1.9.tgz",
|
||||
"integrity": "sha512-5YikxwRqRXTVWVujhswDOTCq6gs+m9IcNbNZLa6FLtyBStAjEsuE2vAU+lPsbz9ZTST57D5fodjIh2JXT6sMWQ==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/intl-localematcher": "0.2.32",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-localematcher": {
|
||||
"version": "0.2.32",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz",
|
||||
"integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
|
||||
|
@ -10460,6 +10668,15 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/http-proxy": {
|
||||
"version": "1.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.3.tgz",
|
||||
|
@ -10503,14 +10720,12 @@
|
|||
"@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "18.0.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz",
|
||||
"integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==",
|
||||
"devOptional": true,
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
|
@ -10537,8 +10752,7 @@
|
|||
"@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
|
||||
},
|
||||
"@types/trusted-types": {
|
||||
"version": "2.0.2",
|
||||
|
@ -12506,6 +12720,17 @@
|
|||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"intl-messageformat": {
|
||||
"version": "10.3.2",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.2.tgz",
|
||||
"integrity": "sha512-kGY1KrpxPGbWX/yz6rpWQahBh5bJC6pIbq/cTzVYlmAYjRVzP+l2MulagbZf/5mABbcLT/0RJbZC46Iw6Mhmtw==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/fast-memoize": "2.0.0",
|
||||
"@formatjs/icu-messageformat-parser": "2.3.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
|
@ -13565,6 +13790,23 @@
|
|||
"integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-intl": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-6.3.1.tgz",
|
||||
"integrity": "sha512-wI7YSxS2xQRYrHPMFSHXSWNuOLrgt9L0Dg8G35xJNfDRpcyebE9H1k2/DDPxgbV4ImeG/qKJMN5SrZV7fuB/Ag==",
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.14.3",
|
||||
"@formatjs/icu-messageformat-parser": "2.3.0",
|
||||
"@formatjs/intl": "2.6.8",
|
||||
"@formatjs/intl-displaynames": "6.2.6",
|
||||
"@formatjs/intl-listformat": "7.1.9",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/react": "16 || 17 || 18",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"intl-messageformat": "10.3.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
@ -14362,7 +14604,7 @@
|
|||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-intl": "^6.3.1",
|
||||
"sharp": "^0.31.3",
|
||||
"yup": "^1.0.2"
|
||||
},
|
||||
|
|
33
frontend/src/components/account/LanguagePicker.tsx
Normal file
33
frontend/src/components/account/LanguagePicker.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { Select } from "@mantine/core";
|
||||
import { getCookie, setCookie } from "cookies-next";
|
||||
import { useState } from "react";
|
||||
import { LOCALES } from "../../i18n/locales";
|
||||
|
||||
const LanguagePicker = () => {
|
||||
const [selectedLanguage, setSelectedLanguage] = useState(
|
||||
getCookie("language")?.toString()
|
||||
);
|
||||
|
||||
const languages = Object.values(LOCALES).map((locale) => ({
|
||||
value: locale.code,
|
||||
label: locale.name,
|
||||
}));
|
||||
return (
|
||||
<Select
|
||||
value={selectedLanguage}
|
||||
onChange={(value) => {
|
||||
setSelectedLanguage(value ?? "en");
|
||||
setCookie("language", value, {
|
||||
sameSite: "lax",
|
||||
expires: new Date(
|
||||
new Date().setFullYear(new Date().getFullYear() + 1)
|
||||
),
|
||||
});
|
||||
location.reload();
|
||||
}}
|
||||
data={languages}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguagePicker;
|
|
@ -9,12 +9,12 @@ import {
|
|||
import { useColorScheme } from "@mantine/hooks";
|
||||
import { useState } from "react";
|
||||
import { TbDeviceLaptop, TbMoon, TbSun } from "react-icons/tb";
|
||||
import usePreferences from "../../hooks/usePreferences";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import userPreferences from "../../utils/userPreferences.util";
|
||||
|
||||
const ThemeSwitcher = () => {
|
||||
const preferences = usePreferences();
|
||||
const [colorScheme, setColorScheme] = useState(
|
||||
preferences.get("colorScheme")
|
||||
userPreferences.get("colorScheme")
|
||||
);
|
||||
const { toggleColorScheme } = useMantineColorScheme();
|
||||
const systemColorScheme = useColorScheme();
|
||||
|
@ -23,7 +23,7 @@ const ThemeSwitcher = () => {
|
|||
<SegmentedControl
|
||||
value={colorScheme}
|
||||
onChange={(value) => {
|
||||
preferences.set("colorScheme", value);
|
||||
userPreferences.set("colorScheme", value);
|
||||
setColorScheme(value);
|
||||
toggleColorScheme(
|
||||
value == "system" ? systemColorScheme : (value as ColorScheme)
|
||||
|
@ -34,7 +34,9 @@ const ThemeSwitcher = () => {
|
|||
label: (
|
||||
<Center>
|
||||
<TbMoon size={16} />
|
||||
<Box ml={10}>Dark</Box>
|
||||
<Box ml={10}>
|
||||
<FormattedMessage id="account.theme.dark" />
|
||||
</Box>
|
||||
</Center>
|
||||
),
|
||||
value: "dark",
|
||||
|
@ -43,7 +45,9 @@ const ThemeSwitcher = () => {
|
|||
label: (
|
||||
<Center>
|
||||
<TbSun size={16} />
|
||||
<Box ml={10}>Light</Box>
|
||||
<Box ml={10}>
|
||||
<FormattedMessage id="account.theme.light" />
|
||||
</Box>
|
||||
</Center>
|
||||
),
|
||||
value: "light",
|
||||
|
@ -52,7 +56,9 @@ const ThemeSwitcher = () => {
|
|||
label: (
|
||||
<Center>
|
||||
<TbDeviceLaptop size={16} />
|
||||
<Box ml={10}>System</Box>
|
||||
<Box ml={10}>
|
||||
<FormattedMessage id="account.theme.system" />
|
||||
</Box>
|
||||
</Center>
|
||||
),
|
||||
value: "system",
|
||||
|
|
|
@ -12,7 +12,11 @@ import {
|
|||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useTranslate, {
|
||||
translateOutsideContext,
|
||||
} from "../../hooks/useTranslate.hook";
|
||||
import authService from "../../services/auth.service";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
||||
|
@ -25,8 +29,9 @@ const showEnableTotpModal = (
|
|||
password: string;
|
||||
}
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
return modals.openModal({
|
||||
title: "Enable TOTP",
|
||||
title: t("account.modal.totp.title"),
|
||||
children: (
|
||||
<CreateEnableTotpModal options={options} refreshUser={refreshUser} />
|
||||
),
|
||||
|
@ -45,6 +50,7 @@ const CreateEnableTotpModal = ({
|
|||
refreshUser: () => {};
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
code: yup
|
||||
|
@ -66,14 +72,19 @@ const CreateEnableTotpModal = ({
|
|||
<div>
|
||||
<Center>
|
||||
<Stack>
|
||||
<Text>Step 1: Add your authenticator</Text>
|
||||
<Text>
|
||||
<FormattedMessage id="account.modal.totp.step1" />
|
||||
</Text>
|
||||
<Image src={options.qrCode} alt="QR Code" />
|
||||
|
||||
<Center>
|
||||
<span>OR</span>
|
||||
<span>
|
||||
{" "}
|
||||
<FormattedMessage id="common.text.or" />
|
||||
</span>
|
||||
</Center>
|
||||
|
||||
<Tooltip label="Click to copy">
|
||||
<Tooltip label={t("account.modal.totp.clickToCopy")}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(options.secret);
|
||||
|
@ -84,17 +95,19 @@ const CreateEnableTotpModal = ({
|
|||
</Button>
|
||||
</Tooltip>
|
||||
<Center>
|
||||
<Text fz="xs">Enter manually</Text>
|
||||
<Text fz="xs"></Text>
|
||||
</Center>
|
||||
|
||||
<Text>Step 2: Validate your code</Text>
|
||||
<Text>
|
||||
<FormattedMessage id="account.modal.totp.step2" />
|
||||
</Text>
|
||||
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
authService
|
||||
.verifyTOTP(values.code, options.password)
|
||||
.then(() => {
|
||||
toast.success("Successfully enabled TOTP");
|
||||
toast.success(t("account.notify.totp.enable"));
|
||||
modals.closeAll();
|
||||
refreshUser();
|
||||
})
|
||||
|
@ -105,14 +118,14 @@ const CreateEnableTotpModal = ({
|
|||
<Col xs={9}>
|
||||
<TextInput
|
||||
variant="filled"
|
||||
label="Code"
|
||||
label={t("account.modal.totp.code")}
|
||||
placeholder="******"
|
||||
{...form.getInputProps("code")}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={3}>
|
||||
<Button variant="outline" type="submit">
|
||||
Verify
|
||||
<FormattedMessage id="account.modal.totp.verify" />
|
||||
</Button>
|
||||
</Col>
|
||||
</Grid>
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { Stack, TextInput } from "@mantine/core";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { translateOutsideContext } from "../../hooks/useTranslate.hook";
|
||||
|
||||
const showReverseShareLinkModal = (
|
||||
modals: ModalsContextProps,
|
||||
reverseShareToken: string,
|
||||
appUrl: string
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
const link = `${appUrl}/upload/${reverseShareToken}`;
|
||||
return modals.openModal({
|
||||
title: "Reverse share link",
|
||||
title: t("account.reverseShares.modal.reverse-share-link"),
|
||||
children: (
|
||||
<Stack align="stretch">
|
||||
<TextInput variant="filled" value={link} />
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { Text, Divider, Progress, Stack, Group, Flex } from "@mantine/core";
|
||||
import { Divider, Flex, Progress, Stack, Text } from "@mantine/core";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { MyShare } from "../../types/share.type";
|
||||
import moment from "moment";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { translateOutsideContext } from "../../hooks/useTranslate.hook";
|
||||
import { FileMetaData } from "../../types/File.type";
|
||||
import { MyShare } from "../../types/share.type";
|
||||
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
||||
import CopyTextField from "../upload/CopyTextField";
|
||||
import { FileMetaData } from "../../types/File.type";
|
||||
|
||||
const showShareInformationsModal = (
|
||||
modals: ModalsContextProps,
|
||||
|
@ -12,6 +14,7 @@ const showShareInformationsModal = (
|
|||
appUrl: string,
|
||||
maxShareSize: number
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
const link = `${appUrl}/share/${share.id}`;
|
||||
|
||||
let shareSize: number = 0;
|
||||
|
@ -29,34 +32,45 @@ const showShareInformationsModal = (
|
|||
: moment(share.expiration).format("LLL");
|
||||
|
||||
return modals.openModal({
|
||||
title: "Share informations",
|
||||
title: t("account.shares.modal.share-informations"),
|
||||
|
||||
children: (
|
||||
<Stack align="stretch" spacing="md">
|
||||
<Text size="sm" color="lightgray">
|
||||
<b>ID:</b> {share.id}
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.id" />:{" "}
|
||||
</b>
|
||||
{share.id}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<b>Description:</b> {share.description || "No description"}
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.description" />:{" "}
|
||||
</b>
|
||||
{share.description || "No description"}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<b>Created at:</b> {formattedCreatedAt}
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.createdAt" />:{" "}
|
||||
</b>
|
||||
{formattedCreatedAt}
|
||||
</Text>
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<b>Expires at:</b> {formattedExpiration}
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.expiresAt" />:{" "}
|
||||
</b>
|
||||
{formattedExpiration}
|
||||
</Text>
|
||||
|
||||
<Divider />
|
||||
|
||||
<CopyTextField link={link} />
|
||||
|
||||
<Divider />
|
||||
|
||||
<Text size="sm" color="lightgray">
|
||||
<b>Size:</b> {formattedShareSize} / {formattedMaxShareSize} (
|
||||
<b>
|
||||
<FormattedMessage id="account.shares.table.size" />:{" "}
|
||||
</b>
|
||||
{formattedShareSize} / {formattedMaxShareSize} (
|
||||
{shareSizeProgress.toFixed(1)}%)
|
||||
</Text>
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { Stack, TextInput } from "@mantine/core";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { translateOutsideContext } from "../../hooks/useTranslate.hook";
|
||||
|
||||
const showShareLinkModal = (
|
||||
modals: ModalsContextProps,
|
||||
shareId: string,
|
||||
appUrl: string
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
const link = `${appUrl}/share/${shareId}`;
|
||||
return modals.openModal({
|
||||
title: "Share link",
|
||||
title: t("account.shares.modal.share-link"),
|
||||
children: (
|
||||
<Stack align="stretch">
|
||||
<TextInput variant="filled" value={link} />
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useConfig from "../../../hooks/config.hook";
|
||||
import Logo from "../../Logo";
|
||||
|
||||
|
@ -42,7 +43,7 @@ const ConfigurationHeader = ({
|
|||
</Link>
|
||||
<MediaQuery smallerThan="sm" styles={{ display: "none" }}>
|
||||
<Button variant="light" component={Link} href="/admin">
|
||||
Go back
|
||||
<FormattedMessage id="common.button.go-back" />
|
||||
</Button>
|
||||
</MediaQuery>
|
||||
</Group>
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
import Link from "next/link";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { TbAt, TbMail, TbShare, TbSquare } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const categories = [
|
||||
{ name: "General", icon: <TbSquare /> },
|
||||
|
@ -53,7 +54,7 @@ const ConfigurationNavBar = ({
|
|||
>
|
||||
<Navbar.Section>
|
||||
<Text size="xs" color="dimmed" mb="sm">
|
||||
Configuration
|
||||
<FormattedMessage id="admin.config.title" />
|
||||
</Text>
|
||||
<Stack spacing="xs">
|
||||
{categories.map((category) => (
|
||||
|
@ -79,7 +80,11 @@ const ConfigurationNavBar = ({
|
|||
>
|
||||
{category.icon}
|
||||
</ThemeIcon>
|
||||
<Text size="sm">{category.name}</Text>
|
||||
<Text size="sm">
|
||||
<FormattedMessage
|
||||
id={`admin.config.category.${category.name.toLowerCase()}`}
|
||||
/>
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
|
@ -87,7 +92,7 @@ const ConfigurationNavBar = ({
|
|||
</Navbar.Section>
|
||||
<MediaQuery largerThan="sm" styles={{ display: "none" }}>
|
||||
<Button mt="xl" variant="light" component={Link} href="/admin">
|
||||
Go back
|
||||
<FormattedMessage id="common.button.go-back" />
|
||||
</Button>
|
||||
</MediaQuery>
|
||||
</Navbar>
|
||||
|
|
|
@ -2,6 +2,8 @@ import { Box, FileInput, Group, Stack, Text, Title } from "@mantine/core";
|
|||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { TbUpload } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
|
||||
const LogoConfigInput = ({
|
||||
logo,
|
||||
|
@ -11,14 +13,16 @@ const LogoConfigInput = ({
|
|||
setLogo: Dispatch<SetStateAction<File | null>>;
|
||||
}) => {
|
||||
const isMobile = useMediaQuery("(max-width: 560px)");
|
||||
const t = useTranslate();
|
||||
|
||||
return (
|
||||
<Group position="apart">
|
||||
<Stack style={{ maxWidth: isMobile ? "100%" : "40%" }} spacing={0}>
|
||||
<Title order={6}>Logo</Title>
|
||||
<Title order={6}>
|
||||
<FormattedMessage id="admin.config.general.logo" />
|
||||
</Title>
|
||||
<Text color="dimmed" size="sm" mb="xs">
|
||||
Change your logo by uploading a new image. The image must be a PNG and
|
||||
should have the format 1:1.
|
||||
<FormattedMessage id="admin.config.general.logo.description" />
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack></Stack>
|
||||
|
@ -29,7 +33,7 @@ const LogoConfigInput = ({
|
|||
value={logo}
|
||||
onChange={(v) => setLogo(v)}
|
||||
accept=".png"
|
||||
placeholder="Pick image"
|
||||
placeholder={t("admin.config.general.logo.placeholder")}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Button, Stack, Text, Textarea } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useUser from "../../../hooks/user.hook";
|
||||
import configService from "../../../services/config.service";
|
||||
import toast from "../../../utils/toast.util";
|
||||
|
@ -65,7 +66,7 @@ const TestEmailButton = ({
|
|||
}
|
||||
}}
|
||||
>
|
||||
Send test email
|
||||
<FormattedMessage id="admin.config.smtp.button.test" />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ import { useModals } from "@mantine/modals";
|
|||
import { TbCheck, TbEdit, TbTrash } from "react-icons/tb";
|
||||
import User from "../../../types/user.type";
|
||||
import showUpdateUserModal from "./showUpdateUserModal";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
const ManageUserTable = ({
|
||||
users,
|
||||
|
@ -22,9 +23,15 @@ const ManageUserTable = ({
|
|||
<Table verticalSpacing="sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Admin</th>
|
||||
<th>
|
||||
<FormattedMessage id="admin.users.table.username" />
|
||||
</th>
|
||||
<th>
|
||||
<FormattedMessage id="admin.users.table.email" />
|
||||
</th>
|
||||
<th>
|
||||
<FormattedMessage id="admin.users.table.admin" />
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -8,7 +8,9 @@ import {
|
|||
} from "@mantine/core";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import userService from "../../../services/user.service";
|
||||
import toast from "../../../utils/toast.util";
|
||||
|
||||
|
@ -34,6 +36,7 @@ const Body = ({
|
|||
smtpEnabled: boolean;
|
||||
getUsers: () => void;
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
username: "",
|
||||
|
@ -44,9 +47,14 @@ const Body = ({
|
|||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
email: yup.string().email(),
|
||||
username: yup.string().min(3),
|
||||
password: yup.string().min(8).optional(),
|
||||
email: yup.string().email(t("common.error.invalid-email")),
|
||||
username: yup
|
||||
.string()
|
||||
.min(3, t("common.error.too-short", { length: 3 })),
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.optional(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -65,14 +73,22 @@ const Body = ({
|
|||
})}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput label="Username" {...form.getInputProps("username")} />
|
||||
<TextInput label="Email" {...form.getInputProps("email")} />
|
||||
<TextInput
|
||||
label={t("admin.users.modal.create.username")}
|
||||
{...form.getInputProps("username")}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("admin.users.modal.create.email")}
|
||||
{...form.getInputProps("email")}
|
||||
/>
|
||||
{smtpEnabled && (
|
||||
<Switch
|
||||
mt="xs"
|
||||
labelPosition="left"
|
||||
label="Set password manually"
|
||||
description="If not checked, the user will receive an email with a link to set their password."
|
||||
label={t("admin.users.modal.create.manual-password")}
|
||||
description={t(
|
||||
"admin.users.modal.create.manual-password.description"
|
||||
)}
|
||||
{...form.getInputProps("setPasswordManually", {
|
||||
type: "checkbox",
|
||||
})}
|
||||
|
@ -80,7 +96,7 @@ const Body = ({
|
|||
)}
|
||||
{(form.values.setPasswordManually || !smtpEnabled) && (
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
label={t("admin.users.modal.create.password")}
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
)}
|
||||
|
@ -93,12 +109,14 @@ const Body = ({
|
|||
}}
|
||||
mt="xs"
|
||||
labelPosition="left"
|
||||
label="Admin privileges"
|
||||
description="If checked, the user will be able to access the admin panel."
|
||||
label={t("admin.users.modal.create.admin")}
|
||||
description={t("admin.users.modal.create.admin.description")}
|
||||
{...form.getInputProps("isAdmin", { type: "checkbox" })}
|
||||
/>
|
||||
<Group position="right">
|
||||
<Button type="submit">Create</Button>
|
||||
<Button type="submit">
|
||||
<FormattedMessage id="common.button.create" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
|
|
|
@ -9,7 +9,11 @@ import {
|
|||
} from "@mantine/core";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useTranslate, {
|
||||
translateOutsideContext,
|
||||
} from "../../../hooks/useTranslate.hook";
|
||||
import userService from "../../../services/user.service";
|
||||
import User from "../../../types/user.type";
|
||||
import toast from "../../../utils/toast.util";
|
||||
|
@ -19,8 +23,9 @@ const showUpdateUserModal = (
|
|||
user: User,
|
||||
getUsers: () => void
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
return modals.openModal({
|
||||
title: `Update ${user.username}`,
|
||||
title: t("admin.users.edit.update.title", { username: user.username }),
|
||||
children: <Body user={user} modals={modals} getUsers={getUsers} />,
|
||||
});
|
||||
};
|
||||
|
@ -34,6 +39,8 @@ const Body = ({
|
|||
user: User;
|
||||
getUsers: () => void;
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
|
||||
const accountForm = useForm({
|
||||
initialValues: {
|
||||
username: user.username,
|
||||
|
@ -42,8 +49,10 @@ const Body = ({
|
|||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
email: yup.string().email(),
|
||||
username: yup.string().min(3),
|
||||
email: yup.string().email(t("common.error.invalid-email")),
|
||||
username: yup
|
||||
.string()
|
||||
.min(3, t("common.error.too-short", { length: 3 })),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -54,7 +63,9 @@ const Body = ({
|
|||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
password: yup.string().min(8),
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 })),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -75,21 +86,26 @@ const Body = ({
|
|||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Username"
|
||||
label={t("admin.users.table.username")}
|
||||
{...accountForm.getInputProps("username")}
|
||||
/>
|
||||
<TextInput label="Email" {...accountForm.getInputProps("email")} />
|
||||
<TextInput
|
||||
label={t("admin.users.table.email")}
|
||||
{...accountForm.getInputProps("email")}
|
||||
/>
|
||||
<Switch
|
||||
mt="xs"
|
||||
labelPosition="left"
|
||||
label="Admin privileges"
|
||||
label={t("admin.users.edit.update.admin-privileges")}
|
||||
{...accountForm.getInputProps("isAdmin", { type: "checkbox" })}
|
||||
/>
|
||||
</Stack>
|
||||
</form>
|
||||
<Accordion>
|
||||
<Accordion.Item sx={{ borderBottom: "none" }} value="changePassword">
|
||||
<Accordion.Control px={0}>Change password</Accordion.Control>
|
||||
<Accordion.Control px={0}>
|
||||
<FormattedMessage id="admin.users.edit.update.change-password.title" />
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<form
|
||||
onSubmit={passwordForm.onSubmit(async (values) => {
|
||||
|
@ -97,17 +113,21 @@ const Body = ({
|
|||
.update(user.id, {
|
||||
password: values.password,
|
||||
})
|
||||
.then(() => toast.success("Password changed successfully"))
|
||||
.then(() =>
|
||||
toast.success(
|
||||
t("admin.users.edit.update.notify.password.success")
|
||||
)
|
||||
)
|
||||
.catch(toast.axiosError);
|
||||
})}
|
||||
>
|
||||
<Stack>
|
||||
<PasswordInput
|
||||
label="New password"
|
||||
label={t("admin.users.edit.update.change-password.field")}
|
||||
{...passwordForm.getInputProps("password")}
|
||||
/>
|
||||
<Button variant="light" type="submit">
|
||||
Save new password
|
||||
<FormattedMessage id="admin.users.edit.update.change-password.button" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
|
@ -116,7 +136,7 @@ const Body = ({
|
|||
</Accordion>
|
||||
<Group position="right">
|
||||
<Button type="submit" form="accountForm">
|
||||
Save
|
||||
<FormattedMessage id="common.button.save" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
|
|
@ -15,8 +15,10 @@ import Link from "next/link";
|
|||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
import { TbInfoCircle } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import authService from "../../services/auth.service";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
@ -24,14 +26,18 @@ import toast from "../../utils/toast.util";
|
|||
const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
const config = useConfig();
|
||||
const router = useRouter();
|
||||
const t = useTranslate();
|
||||
const { refreshUser } = useUser();
|
||||
|
||||
const [showTotp, setShowTotp] = React.useState(false);
|
||||
const [loginToken, setLoginToken] = React.useState("");
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
emailOrUsername: yup.string().required(),
|
||||
password: yup.string().min(8).required(),
|
||||
emailOrUsername: yup.string().required(t("common.error.field-required")),
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.required(t("common.error.field-required")),
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
|
@ -54,8 +60,8 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
|||
icon: <TbInfoCircle />,
|
||||
color: "blue",
|
||||
radius: "md",
|
||||
title: "Two-factor authentication required",
|
||||
message: "Please enter your two-factor authentication code",
|
||||
title: t("signIn.notify.totp-required.title"),
|
||||
message: t("signIn.notify.totp-required.description"),
|
||||
});
|
||||
setLoginToken(response.data["loginToken"]);
|
||||
} else {
|
||||
|
@ -88,13 +94,13 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
|||
return (
|
||||
<Container size={420} my={40}>
|
||||
<Title order={2} align="center" weight={900}>
|
||||
Welcome back
|
||||
<FormattedMessage id="signin.title" />
|
||||
</Title>
|
||||
{config.get("share.allowRegistration") && (
|
||||
<Text color="dimmed" size="sm" align="center" mt={5}>
|
||||
You don't have an account yet?{" "}
|
||||
<FormattedMessage id="signin.description" />{" "}
|
||||
<Anchor component={Link} href={"signUp"} size="sm">
|
||||
{"Sign up"}
|
||||
<FormattedMessage id="signin.button.signup" />
|
||||
</Anchor>
|
||||
</Text>
|
||||
)}
|
||||
|
@ -107,20 +113,20 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
|||
})}
|
||||
>
|
||||
<TextInput
|
||||
label="Email or username"
|
||||
placeholder="Your email or username"
|
||||
label={t("signin.input.email-or-username")}
|
||||
placeholder={t("signin.input.email-or-username.placeholder")}
|
||||
{...form.getInputProps("emailOrUsername")}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Your password"
|
||||
label={t("signin.input.password")}
|
||||
placeholder={t("signin.input.password.placeholder")}
|
||||
mt="md"
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
{showTotp && (
|
||||
<TextInput
|
||||
variant="filled"
|
||||
label="Code"
|
||||
label={t("account.modal.totp.code")}
|
||||
placeholder="******"
|
||||
mt="md"
|
||||
{...form.getInputProps("totp")}
|
||||
|
@ -129,12 +135,12 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
|||
{config.get("smtp.enabled") && (
|
||||
<Group position="right" mt="xs">
|
||||
<Anchor component={Link} href="/auth/resetPassword" size="xs">
|
||||
Forgot password?
|
||||
<FormattedMessage id="resetPassword.title" />
|
||||
</Anchor>
|
||||
</Group>
|
||||
)}
|
||||
<Button fullWidth mt="xl" type="submit">
|
||||
Sign in
|
||||
<FormattedMessage id="signin.button.submit" />
|
||||
</Button>
|
||||
</form>
|
||||
</Paper>
|
||||
|
|
|
@ -11,8 +11,10 @@ import {
|
|||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import authService from "../../services/auth.service";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
@ -20,12 +22,19 @@ import toast from "../../utils/toast.util";
|
|||
const SignUpForm = () => {
|
||||
const config = useConfig();
|
||||
const router = useRouter();
|
||||
const t = useTranslate();
|
||||
const { refreshUser } = useUser();
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
email: yup.string().email().required(),
|
||||
username: yup.string().min(3).required(),
|
||||
password: yup.string().min(8).required(),
|
||||
email: yup.string().email(t("common.error.invalid-email")).required(),
|
||||
username: yup
|
||||
.string()
|
||||
.min(3, t("common.error.too-short", { length: 3 }))
|
||||
.required(t("common.error.field-required")),
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.required(t("common.error.field-required")),
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
|
@ -54,13 +63,13 @@ const SignUpForm = () => {
|
|||
return (
|
||||
<Container size={420} my={40}>
|
||||
<Title order={2} align="center" weight={900}>
|
||||
Sign up
|
||||
<FormattedMessage id="signup.title" />
|
||||
</Title>
|
||||
{config.get("share.allowRegistration") && (
|
||||
<Text color="dimmed" size="sm" align="center" mt={5}>
|
||||
You have an account already?{" "}
|
||||
<FormattedMessage id="signup.description" />{" "}
|
||||
<Anchor component={Link} href={"signIn"} size="sm">
|
||||
Sign in
|
||||
<FormattedMessage id="signup.button.signin" />
|
||||
</Anchor>
|
||||
</Text>
|
||||
)}
|
||||
|
@ -71,24 +80,24 @@ const SignUpForm = () => {
|
|||
)}
|
||||
>
|
||||
<TextInput
|
||||
label="Username"
|
||||
placeholder="Your username"
|
||||
label={t("signup.input.username")}
|
||||
placeholder={t("signup.input.username.placeholder")}
|
||||
{...form.getInputProps("username")}
|
||||
/>
|
||||
<TextInput
|
||||
label="Email"
|
||||
placeholder="Your email"
|
||||
label={t("signup.input.email")}
|
||||
placeholder={t("signup.input.email.placeholder")}
|
||||
mt="md"
|
||||
{...form.getInputProps("email")}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Your password"
|
||||
label={t("signin.input.password")}
|
||||
placeholder={t("signin.input.password.placeholder")}
|
||||
mt="md"
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<Button fullWidth mt="xl" type="submit">
|
||||
Let's get started
|
||||
<FormattedMessage id="signup.button.submit" />
|
||||
</Button>
|
||||
</form>
|
||||
</Paper>
|
||||
|
|
|
@ -3,6 +3,7 @@ import Link from "next/link";
|
|||
import { TbDoorExit, TbSettings, TbUser } from "react-icons/tb";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import authService from "../../services/auth.service";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
const ActionAvatar = () => {
|
||||
const { user } = useUser();
|
||||
|
@ -16,7 +17,7 @@ const ActionAvatar = () => {
|
|||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item component={Link} href="/account" icon={<TbUser size={14} />}>
|
||||
My account
|
||||
<FormattedMessage id="navbar.avatar.account" />
|
||||
</Menu.Item>
|
||||
{user!.isAdmin && (
|
||||
<Menu.Item
|
||||
|
@ -24,7 +25,7 @@ const ActionAvatar = () => {
|
|||
href="/admin"
|
||||
icon={<TbSettings size={14} />}
|
||||
>
|
||||
Administration
|
||||
<FormattedMessage id="navbar.avatar.admin" />
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
|
@ -34,7 +35,7 @@ const ActionAvatar = () => {
|
|||
}}
|
||||
icon={<TbDoorExit size={14} />}
|
||||
>
|
||||
Sign out
|
||||
<FormattedMessage id="navbar.avatar.signout" />
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
|
|
@ -16,6 +16,7 @@ import { useRouter } from "next/router";
|
|||
import { ReactNode, useEffect, useState } from "react";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import Logo from "../Logo";
|
||||
import ActionAvatar from "./ActionAvatar";
|
||||
import NavbarShareMenu from "./NavbarShareMenu";
|
||||
|
@ -112,6 +113,7 @@ const Header = () => {
|
|||
const { user } = useUser();
|
||||
const router = useRouter();
|
||||
const config = useConfig();
|
||||
const t = useTranslate();
|
||||
|
||||
const [opened, toggleOpened] = useDisclosure(false);
|
||||
|
||||
|
@ -124,7 +126,7 @@ const Header = () => {
|
|||
const authenticatedLinks: NavLink[] = [
|
||||
{
|
||||
link: "/upload",
|
||||
label: "Upload",
|
||||
label: t("navbar.upload"),
|
||||
},
|
||||
{
|
||||
component: <NavbarShareMenu />,
|
||||
|
@ -137,27 +139,27 @@ const Header = () => {
|
|||
let unauthenticatedLinks: NavLink[] = [
|
||||
{
|
||||
link: "/auth/signIn",
|
||||
label: "Sign in",
|
||||
label: t("navbar.signin"),
|
||||
},
|
||||
];
|
||||
|
||||
if (config.get("share.allowUnauthenticatedShares")) {
|
||||
unauthenticatedLinks.unshift({
|
||||
link: "/upload",
|
||||
label: "Upload",
|
||||
label: t("navbar.upload"),
|
||||
});
|
||||
}
|
||||
|
||||
if (config.get("general.showHomePage"))
|
||||
unauthenticatedLinks.unshift({
|
||||
link: "/",
|
||||
label: "Home",
|
||||
label: t("navbar.home"),
|
||||
});
|
||||
|
||||
if (config.get("share.allowRegistration"))
|
||||
unauthenticatedLinks.push({
|
||||
link: "/auth/signUp",
|
||||
label: "Sign up",
|
||||
label: t("navbar.signup"),
|
||||
});
|
||||
|
||||
const { classes, cx } = useStyles();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ActionIcon, Menu } from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import { TbArrowLoopLeft, TbLink } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const NavbarShareMneu = () => {
|
||||
return (
|
||||
|
@ -12,14 +13,14 @@ const NavbarShareMneu = () => {
|
|||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item component={Link} href="/account/shares" icon={<TbLink />}>
|
||||
My shares
|
||||
<FormattedMessage id="navbar.links.shares" />
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
component={Link}
|
||||
href="/account/reverseShares"
|
||||
icon={<TbArrowLoopLeft />}
|
||||
>
|
||||
Reverse shares
|
||||
<FormattedMessage id="navbar.links.reverse" />
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { Button } from "@mantine/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import shareService from "../../services/share.service";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
||||
const DownloadAllButton = ({ shareId }: { shareId: string }) => {
|
||||
const [isZipReady, setIsZipReady] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const t = useTranslate();
|
||||
|
||||
const downloadAll = async () => {
|
||||
setIsLoading(true);
|
||||
await shareService
|
||||
|
@ -39,13 +43,13 @@ const DownloadAllButton = ({ shareId }: { shareId: string }) => {
|
|||
loading={isLoading}
|
||||
onClick={() => {
|
||||
if (!isZipReady) {
|
||||
toast.error("The share is preparing. Try again in a few minutes.");
|
||||
toast.error(t("share.notify.download-all-preparing"));
|
||||
} else {
|
||||
downloadAll();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Download all
|
||||
<FormattedMessage id="share.download-all" />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -19,6 +19,8 @@ import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
|||
import toast from "../../utils/toast.util";
|
||||
import TableSortIcon, { TableSort } from "../core/SortIcon";
|
||||
import showFilePreviewModal from "./modals/showFilePreviewModal";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const FileList = ({
|
||||
files,
|
||||
|
@ -34,6 +36,7 @@ const FileList = ({
|
|||
const clipboard = useClipboard();
|
||||
const config = useConfig();
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const [sort, setSort] = useState<TableSort>({
|
||||
property: undefined,
|
||||
|
@ -68,10 +71,10 @@ const FileList = ({
|
|||
|
||||
if (window.isSecureContext) {
|
||||
clipboard.copy(link);
|
||||
toast.success("Your file link was copied to the keyboard.");
|
||||
toast.success(t("common.notify.copied"));
|
||||
} else {
|
||||
modals.openModal({
|
||||
title: "File link",
|
||||
title: t("share.modal.file-link"),
|
||||
children: (
|
||||
<Stack align="stretch">
|
||||
<TextInput variant="filled" value={link} />
|
||||
|
@ -90,13 +93,13 @@ const FileList = ({
|
|||
<tr>
|
||||
<th>
|
||||
<Group spacing="xs">
|
||||
Name
|
||||
<FormattedMessage id="share.table.name" />
|
||||
<TableSortIcon sort={sort} setSort={setSort} property="name" />
|
||||
</Group>
|
||||
</th>
|
||||
<th>
|
||||
<Group spacing="xs">
|
||||
Size
|
||||
<FormattedMessage id="share.table.size" />
|
||||
<TableSortIcon sort={sort} setSort={setSort} property="size" />
|
||||
</Group>
|
||||
</th>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Button, Center, Stack, Text, Title } from "@mantine/core";
|
|||
import { modals } from "@mantine/modals";
|
||||
import Link from "next/link";
|
||||
import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import api from "../../services/api.service";
|
||||
|
||||
const FilePreviewContext = React.createContext<{
|
||||
|
@ -144,10 +145,11 @@ const UnSupportedFile = () => {
|
|||
return (
|
||||
<Center style={{ minHeight: 200 }}>
|
||||
<Stack align="center" spacing={10}>
|
||||
<Title order={3}>Preview not supported</Title>
|
||||
<Title order={3}>
|
||||
<FormattedMessage id="share.modal.file-preview.error.not-supported.title" />
|
||||
</Title>
|
||||
<Text>
|
||||
A preview for thise file type is unsupported. Please download the file
|
||||
to view it.
|
||||
<FormattedMessage id="share.modal.file-preview.error.not-supported.description" />
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { Button, Stack } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { translateOutsideContext } from "../../../hooks/useTranslate.hook";
|
||||
import CopyTextField from "../../upload/CopyTextField";
|
||||
|
||||
const showCompletedReverseShareModal = (
|
||||
|
@ -8,11 +10,12 @@ const showCompletedReverseShareModal = (
|
|||
link: string,
|
||||
getReverseShares: () => void
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
return modals.openModal({
|
||||
closeOnClickOutside: false,
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
title: "Reverse share link",
|
||||
title: t("account.reverseShares.modal.reverse-share-link"),
|
||||
children: <Body link={link} getReverseShares={getReverseShares} />,
|
||||
});
|
||||
};
|
||||
|
@ -36,7 +39,7 @@ const Body = ({
|
|||
getReverseShares();
|
||||
}}
|
||||
>
|
||||
Done
|
||||
<FormattedMessage id="common.button.done" />
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
@ -12,6 +12,8 @@ import {
|
|||
import { useForm } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import shareService from "../../../services/share.service";
|
||||
import { getExpirationPreview } from "../../../utils/date.util";
|
||||
import toast from "../../../utils/toast.util";
|
||||
|
@ -42,6 +44,7 @@ const Body = ({
|
|||
showSendEmailNotificationOption: boolean;
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
|
@ -79,7 +82,7 @@ const Body = ({
|
|||
max={99999}
|
||||
precision={0}
|
||||
variant="filled"
|
||||
label="Share expiration"
|
||||
label={t("account.reverseShares.modal.expiration.label")}
|
||||
{...form.getInputProps("expiration_num")}
|
||||
/>
|
||||
</Col>
|
||||
|
@ -91,27 +94,44 @@ const Body = ({
|
|||
{
|
||||
value: "-minutes",
|
||||
label:
|
||||
"Minute" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.minute-singular")
|
||||
: t("upload.modal.expires.minute-plural"),
|
||||
},
|
||||
{
|
||||
value: "-hours",
|
||||
label:
|
||||
"Hour" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.hour-singular")
|
||||
: t("upload.modal.expires.hour-plural"),
|
||||
},
|
||||
{
|
||||
value: "-days",
|
||||
label:
|
||||
"Day" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.day-singular")
|
||||
: t("upload.modal.expires.day-plural"),
|
||||
},
|
||||
{
|
||||
value: "-weeks",
|
||||
label:
|
||||
"Week" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.week-singular")
|
||||
: t("upload.modal.expires.week-plural"),
|
||||
},
|
||||
{
|
||||
value: "-months",
|
||||
label:
|
||||
"Month" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.month-singular")
|
||||
: t("upload.modal.expires.month-plural"),
|
||||
},
|
||||
{
|
||||
value: "-years",
|
||||
label:
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.year-singular")
|
||||
: t("upload.modal.expires.year-plural"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
@ -125,11 +145,17 @@ const Body = ({
|
|||
color: theme.colors.gray[6],
|
||||
})}
|
||||
>
|
||||
{getExpirationPreview("reverse share", form)}
|
||||
{getExpirationPreview(
|
||||
{
|
||||
expiresOn: t("account.reverseShare.expires-on"),
|
||||
neverExpires: t("account.reverseShare.never-expires"),
|
||||
},
|
||||
form
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
<FileSizeInput
|
||||
label="Max share size"
|
||||
label={t("account.reverseShares.modal.max-size.label")}
|
||||
value={form.values.maxShareSize}
|
||||
onChange={(number) => form.setFieldValue("maxShareSize", number)}
|
||||
/>
|
||||
|
@ -138,16 +164,18 @@ const Body = ({
|
|||
max={1000}
|
||||
precision={0}
|
||||
variant="filled"
|
||||
label="Max use count"
|
||||
description="The maximum number of times this reverse share link can be used"
|
||||
label={t("account.reverseShares.modal.max-use.label")}
|
||||
description={t("account.reverseShares.modal.max-use.description")}
|
||||
{...form.getInputProps("maxUseCount")}
|
||||
/>
|
||||
{showSendEmailNotificationOption && (
|
||||
<Switch
|
||||
mt="xs"
|
||||
labelPosition="left"
|
||||
label="Send email notification"
|
||||
description="Send an email notification when a share is created with this reverse share link"
|
||||
label={t("account.reverseShares.modal.send-email")}
|
||||
description={t(
|
||||
"account.reverseShares.modal.send-email.description"
|
||||
)}
|
||||
{...form.getInputProps("sendEmailNotification", {
|
||||
type: "checkbox",
|
||||
})}
|
||||
|
@ -155,7 +183,7 @@ const Body = ({
|
|||
)}
|
||||
|
||||
<Button mt="md" type="submit">
|
||||
Create
|
||||
<FormattedMessage id="common.button.create" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
import { Button, PasswordInput, Stack, Text } from "@mantine/core";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useTranslate, {
|
||||
translateOutsideContext,
|
||||
} from "../../hooks/useTranslate.hook";
|
||||
|
||||
const showEnterPasswordModal = (
|
||||
modals: ModalsContextProps,
|
||||
submitCallback: (password: string) => Promise<void>
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
return modals.openModal({
|
||||
closeOnClickOutside: false,
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
title: "Password required",
|
||||
title: t("share.modal.password.title"),
|
||||
children: <Body submitCallback={submitCallback} />,
|
||||
});
|
||||
};
|
||||
|
@ -22,10 +27,11 @@ const Body = ({
|
|||
}) => {
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordWrong, setPasswordWrong] = useState(false);
|
||||
const t = useTranslate();
|
||||
return (
|
||||
<Stack align="stretch">
|
||||
<Text size="sm">
|
||||
This access this share please enter the password for the share.
|
||||
<FormattedMessage id="share.modal.password.description" />
|
||||
</Text>
|
||||
|
||||
<form
|
||||
|
@ -37,13 +43,15 @@ const Body = ({
|
|||
<Stack>
|
||||
<PasswordInput
|
||||
variant="filled"
|
||||
placeholder="Password"
|
||||
error={passwordWrong && "Wrong password"}
|
||||
placeholder={t("share.modal.password")}
|
||||
error={passwordWrong && t("share.modal.error.invalid-password")}
|
||||
onFocus={() => setPasswordWrong(false)}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
value={password}
|
||||
/>
|
||||
<Button type="submit">Submit</Button>
|
||||
<Button type="submit">
|
||||
<FormattedMessage id="common.button.submit" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Stack>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Button, Stack, Text } from "@mantine/core";
|
|||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { useRouter } from "next/router";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const showErrorModal = (
|
||||
modals: ModalsContextProps,
|
||||
|
@ -31,7 +32,7 @@ const Body = ({ text }: { text: string }) => {
|
|||
router.back();
|
||||
}}
|
||||
>
|
||||
Go back
|
||||
<FormattedMessage id="common.button.go-back" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</>
|
||||
|
|
|
@ -2,10 +2,13 @@ import { ActionIcon, TextInput } from "@mantine/core";
|
|||
import { useClipboard } from "@mantine/hooks";
|
||||
import { useRef, useState } from "react";
|
||||
import { TbCheck, TbCopy } from "react-icons/tb";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
||||
function CopyTextField(props: { link: string }) {
|
||||
const clipboard = useClipboard({ timeout: 500 });
|
||||
const t = useTranslate();
|
||||
|
||||
const [checkState, setCheckState] = useState(false);
|
||||
const [textClicked, setTextClicked] = useState(false);
|
||||
const timerRef = useRef<number | ReturnType<typeof setTimeout> | undefined>(
|
||||
|
@ -14,7 +17,7 @@ function CopyTextField(props: { link: string }) {
|
|||
|
||||
const copyLink = () => {
|
||||
clipboard.copy(props.link);
|
||||
toast.success("The link was copied to your clipboard.");
|
||||
toast.success(t("common.notify.copied"));
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
timerRef.current = setTimeout(() => {
|
||||
setCheckState(false);
|
||||
|
@ -25,7 +28,7 @@ function CopyTextField(props: { link: string }) {
|
|||
return (
|
||||
<TextInput
|
||||
readOnly
|
||||
label="Link"
|
||||
label={t("common.text.link")}
|
||||
variant="filled"
|
||||
value={props.link}
|
||||
onClick={() => {
|
||||
|
|
|
@ -2,7 +2,8 @@ import { Button, Center, createStyles, Group, Text } from "@mantine/core";
|
|||
import { Dropzone as MantineDropzone } from "@mantine/dropzone";
|
||||
import { Dispatch, ForwardedRef, SetStateAction, useRef } from "react";
|
||||
import { TbCloudUpload, TbUpload } from "react-icons/tb";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import { FileUpload } from "../../types/File.type";
|
||||
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
@ -42,7 +43,7 @@ const Dropzone = ({
|
|||
files: FileUpload[];
|
||||
setFiles: Dispatch<SetStateAction<FileUpload[]>>;
|
||||
}) => {
|
||||
const config = useConfig();
|
||||
const t = useTranslate();
|
||||
|
||||
const { classes } = useStyles();
|
||||
const openRef = useRef<() => void>();
|
||||
|
@ -62,9 +63,9 @@ const Dropzone = ({
|
|||
|
||||
if (fileSizeSum > maxShareSize) {
|
||||
toast.error(
|
||||
`Your files exceed the maximum share size of ${byteToHumanSizeString(
|
||||
maxShareSize
|
||||
)}.`
|
||||
t("upload.dropzone.notify.file-too-big", {
|
||||
maxSize: byteToHumanSizeString(maxShareSize),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
newFiles = newFiles.map((newFile) => {
|
||||
|
@ -82,12 +83,13 @@ const Dropzone = ({
|
|||
<TbCloudUpload size={50} />
|
||||
</Group>
|
||||
<Text align="center" weight={700} size="lg" mt="xl">
|
||||
Upload files
|
||||
<FormattedMessage id="upload.dropzone.title" />
|
||||
</Text>
|
||||
<Text align="center" size="sm" mt="xs" color="dimmed">
|
||||
Drag'n'drop files here to start your share. We can accept
|
||||
only files that are less than {byteToHumanSizeString(maxShareSize)}{" "}
|
||||
in total.
|
||||
<FormattedMessage
|
||||
id="upload.dropzone.description"
|
||||
values={{ maxSize: byteToHumanSizeString(maxShareSize) }}
|
||||
/>
|
||||
</Text>
|
||||
</div>
|
||||
</MantineDropzone>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { TbTrash } from "react-icons/tb";
|
|||
import { FileUpload } from "../../types/File.type";
|
||||
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
||||
import UploadProgressIndicator from "./UploadProgressIndicator";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const FileList = ({
|
||||
files,
|
||||
|
@ -41,8 +42,12 @@ const FileList = ({
|
|||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>
|
||||
<FormattedMessage id="upload.filelist.name" />
|
||||
</th>
|
||||
<th>
|
||||
<FormattedMessage id="upload.filelist.size" />
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -3,6 +3,10 @@ import { useModals } from "@mantine/modals";
|
|||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import moment from "moment";
|
||||
import { useRouter } from "next/router";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import useTranslate, {
|
||||
translateOutsideContext,
|
||||
} from "../../../hooks/useTranslate.hook";
|
||||
import { Share } from "../../../types/share.type";
|
||||
import CopyTextField from "../CopyTextField";
|
||||
|
||||
|
@ -11,11 +15,12 @@ const showCompletedUploadModal = (
|
|||
share: Share,
|
||||
appUrl: string
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
return modals.openModal({
|
||||
closeOnClickOutside: false,
|
||||
withCloseButton: false,
|
||||
closeOnEscape: false,
|
||||
title: "Share ready",
|
||||
title: t("upload.modal.completed.share-ready"),
|
||||
children: <Body share={share} appUrl={appUrl} />,
|
||||
});
|
||||
};
|
||||
|
@ -23,6 +28,7 @@ const showCompletedUploadModal = (
|
|||
const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
|
||||
const modals = useModals();
|
||||
const router = useRouter();
|
||||
const t = useTranslate();
|
||||
|
||||
const link = `${appUrl}/share/${share.id}`;
|
||||
|
||||
|
@ -37,10 +43,10 @@ const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
|
|||
>
|
||||
{/* If our share.expiration is timestamp 0, show a different message */}
|
||||
{moment(share.expiration).unix() === 0
|
||||
? "This share will never expire."
|
||||
: `This share will expire on ${moment(share.expiration).format(
|
||||
"LLL"
|
||||
)}`}
|
||||
? t("upload.modal.completed.never-expires")
|
||||
: t("upload.modal.completed.expires-on", {
|
||||
expiration: moment(share.expiration).format("LLL"),
|
||||
})}
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
|
@ -49,7 +55,7 @@ const Body = ({ share, appUrl }: { share: Share; appUrl: string }) => {
|
|||
router.push("/upload");
|
||||
}}
|
||||
>
|
||||
Done
|
||||
<FormattedMessage id="common.button.done" />
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
@ -13,14 +13,17 @@ import {
|
|||
Text,
|
||||
Textarea,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||
import { useState } from "react";
|
||||
import { TbAlertCircle } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useTranslate, {
|
||||
translateOutsideContext,
|
||||
} from "../../../hooks/useTranslate.hook";
|
||||
import shareService from "../../../services/share.service";
|
||||
import { CreateShare } from "../../../types/share.type";
|
||||
import { getExpirationPreview } from "../../../utils/date.util";
|
||||
|
@ -36,8 +39,10 @@ const showCreateUploadModal = (
|
|||
},
|
||||
uploadCallback: (createShare: CreateShare) => void
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
|
||||
return modals.openModal({
|
||||
title: "Share",
|
||||
title: t("upload.modal.title"),
|
||||
children: (
|
||||
<CreateUploadModalBody
|
||||
options={options}
|
||||
|
@ -61,6 +66,7 @@ const CreateUploadModalBody = ({
|
|||
};
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const generatedLink = Buffer.from(Math.random().toString(), "utf8")
|
||||
.toString("base64")
|
||||
|
@ -71,11 +77,11 @@ const CreateUploadModalBody = ({
|
|||
const validationSchema = yup.object().shape({
|
||||
link: yup
|
||||
.string()
|
||||
.required()
|
||||
.min(3)
|
||||
.max(50)
|
||||
.required(t("common.error.field-required"))
|
||||
.min(3, t("common.error.too-short", { length: 3 }))
|
||||
.max(50, t("common.error.too-long", { length: 50 }))
|
||||
.matches(new RegExp("^[a-zA-Z0-9_-]*$"), {
|
||||
message: "Can only contain letters, numbers, underscores and hyphens",
|
||||
message: t("upload.modal.link.error.invalid"),
|
||||
}),
|
||||
password: yup.string().min(3).max(30),
|
||||
maxViews: yup.number().min(1),
|
||||
|
@ -100,20 +106,19 @@ const CreateUploadModalBody = ({
|
|||
withCloseButton
|
||||
onClose={() => setShowNotSignedInAlert(false)}
|
||||
icon={<TbAlertCircle size={16} />}
|
||||
title="You're not signed in"
|
||||
title={t("upload.modal.not-signed-in")}
|
||||
color="yellow"
|
||||
>
|
||||
You will be unable to delete your share manually and view the visitor
|
||||
count.
|
||||
<FormattedMessage id="upload.modal.not-signed-in-description" />
|
||||
</Alert>
|
||||
)}
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
if (!(await shareService.isShareIdAvailable(values.link))) {
|
||||
form.setFieldError("link", "This link is already in use");
|
||||
form.setFieldError("link", t("upload.modal.link.error.taken"));
|
||||
} else {
|
||||
const expiration = form.values.never_expires
|
||||
? "never"
|
||||
? t("upload.modal.expires.never")
|
||||
: form.values.expiration_num + form.values.expiration_unit;
|
||||
uploadCallback({
|
||||
id: values.link,
|
||||
|
@ -151,7 +156,7 @@ const CreateUploadModalBody = ({
|
|||
)
|
||||
}
|
||||
>
|
||||
Generate
|
||||
<FormattedMessage id="common.button.generate" />
|
||||
</Button>
|
||||
</Col>
|
||||
</Grid>
|
||||
|
@ -169,18 +174,6 @@ const CreateUploadModalBody = ({
|
|||
{!options.isReverseShare && (
|
||||
<>
|
||||
<Grid align={form.errors.link ? "center" : "flex-end"}>
|
||||
<Col xs={6}>
|
||||
<NumberInput
|
||||
min={1}
|
||||
max={99999}
|
||||
precision={0}
|
||||
variant="filled"
|
||||
label="Expiration"
|
||||
placeholder="n"
|
||||
disabled={form.values.never_expires}
|
||||
{...form.getInputProps("expiration_num")}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<Select
|
||||
disabled={form.values.never_expires}
|
||||
|
@ -190,41 +183,51 @@ const CreateUploadModalBody = ({
|
|||
{
|
||||
value: "-minutes",
|
||||
label:
|
||||
"Minute" +
|
||||
(form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.minute-singular")
|
||||
: t("upload.modal.expires.minute-plural"),
|
||||
},
|
||||
{
|
||||
value: "-hours",
|
||||
label:
|
||||
"Hour" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.hour-singular")
|
||||
: t("upload.modal.expires.hour-plural"),
|
||||
},
|
||||
{
|
||||
value: "-days",
|
||||
label:
|
||||
"Day" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.day-singular")
|
||||
: t("upload.modal.expires.day-plural"),
|
||||
},
|
||||
{
|
||||
value: "-weeks",
|
||||
label:
|
||||
"Week" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.week-singular")
|
||||
: t("upload.modal.expires.week-plural"),
|
||||
},
|
||||
{
|
||||
value: "-months",
|
||||
label:
|
||||
"Month" +
|
||||
(form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.month-singular")
|
||||
: t("upload.modal.expires.month-plural"),
|
||||
},
|
||||
{
|
||||
value: "-years",
|
||||
label:
|
||||
"Year" + (form.values.expiration_num == 1 ? "" : "s"),
|
||||
form.values.expiration_num == 1
|
||||
? t("upload.modal.expires.year-singular")
|
||||
: t("upload.modal.expires.year-plural"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Grid>
|
||||
<Checkbox
|
||||
label="Never Expires"
|
||||
label={t("upload.modal.expires.never-long")}
|
||||
{...form.getInputProps("never_expires")}
|
||||
/>
|
||||
<Text
|
||||
|
@ -234,18 +237,28 @@ const CreateUploadModalBody = ({
|
|||
color: theme.colors.gray[6],
|
||||
})}
|
||||
>
|
||||
{getExpirationPreview("share", form)}
|
||||
{getExpirationPreview(
|
||||
{
|
||||
neverExpires: t("upload.modal.completed.never-expires"),
|
||||
expiresOn: t("upload.modal.completed.expires-on"),
|
||||
},
|
||||
form
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<Accordion>
|
||||
<Accordion.Item value="description" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Description</Accordion.Control>
|
||||
<Accordion.Control>
|
||||
<FormattedMessage id="upload.modal.accordion.description.title" />
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Stack align="stretch">
|
||||
<Textarea
|
||||
variant="filled"
|
||||
placeholder="Note for the recepients"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.description.placeholder"
|
||||
)}
|
||||
{...form.getInputProps("description")}
|
||||
/>
|
||||
</Stack>
|
||||
|
@ -253,11 +266,13 @@ const CreateUploadModalBody = ({
|
|||
</Accordion.Item>
|
||||
{options.enableEmailRecepients && (
|
||||
<Accordion.Item value="recipients" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Email recipients</Accordion.Control>
|
||||
<Accordion.Control>
|
||||
<FormattedMessage id="upload.modal.accordion.email.tile" />
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<MultiSelect
|
||||
data={form.values.recipients}
|
||||
placeholder="Enter email recipients"
|
||||
placeholder={t("upload.modal.accordion.email.placeholder")}
|
||||
searchable
|
||||
{...form.getInputProps("recipients")}
|
||||
creatable
|
||||
|
@ -266,7 +281,7 @@ const CreateUploadModalBody = ({
|
|||
if (!query.match(/^\S+@\S+\.\S+$/)) {
|
||||
form.setFieldError(
|
||||
"recipients",
|
||||
"Invalid email address"
|
||||
t("upload.modal.accordion.email.invalid-email")
|
||||
);
|
||||
} else {
|
||||
form.setFieldError("recipients", null);
|
||||
|
@ -283,28 +298,36 @@ const CreateUploadModalBody = ({
|
|||
)}
|
||||
|
||||
<Accordion.Item value="security" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Security options</Accordion.Control>
|
||||
<Accordion.Control>
|
||||
<FormattedMessage id="upload.modal.accordion.security.title" />
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Stack align="stretch">
|
||||
<PasswordInput
|
||||
variant="filled"
|
||||
placeholder="No password"
|
||||
label="Password protection"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.security.password.placeholder"
|
||||
)}
|
||||
label={t("upload.modal.accordion.security.password.label")}
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<NumberInput
|
||||
min={1}
|
||||
type="number"
|
||||
variant="filled"
|
||||
placeholder="No limit"
|
||||
label="Maximal views"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.security.max-views.placeholder"
|
||||
)}
|
||||
label={t("upload.modal.accordion.security.max-views.label")}
|
||||
{...form.getInputProps("maxViews")}
|
||||
/>
|
||||
</Stack>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
<Button type="submit">Share</Button>
|
||||
<Button type="submit">
|
||||
<FormattedMessage id="common.button.share" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</>
|
||||
|
|
39
frontend/src/hooks/useTranslate.hook.ts
Normal file
39
frontend/src/hooks/useTranslate.hook.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { getCookie } from "cookies-next";
|
||||
import { createIntl, createIntlCache, useIntl } from "react-intl";
|
||||
import i18nUtil from "../utils/i18n.util";
|
||||
|
||||
const useTranslate = () => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
id: string,
|
||||
values?: Parameters<typeof intl.formatMessage>[1],
|
||||
opts?: Parameters<typeof intl.formatMessage>[2]
|
||||
) => {
|
||||
return intl.formatMessage({ id }, values, opts) as string;
|
||||
};
|
||||
};
|
||||
|
||||
const cache = createIntlCache();
|
||||
|
||||
export const translateOutsideContext = () => {
|
||||
const locale =
|
||||
getCookie("language")?.toString() ?? navigator.language.split("-")[0];
|
||||
|
||||
const intl = createIntl(
|
||||
{
|
||||
locale,
|
||||
messages: i18nUtil.getLocaleByCode(locale)?.messages,
|
||||
defaultLocale: "en",
|
||||
},
|
||||
cache
|
||||
);
|
||||
return (
|
||||
id: string,
|
||||
values?: Parameters<typeof intl.formatMessage>[1],
|
||||
opts?: Parameters<typeof intl.formatMessage>[2]
|
||||
) => {
|
||||
return intl.formatMessage({ id }, values, opts) as string;
|
||||
};
|
||||
};
|
||||
|
||||
export default useTranslate;
|
51
frontend/src/i18n/locales.ts
Normal file
51
frontend/src/i18n/locales.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import danish from "./translations/da";
|
||||
import german from "./translations/de";
|
||||
import english from "./translations/en";
|
||||
import spanish from "./translations/es";
|
||||
import french from "./translations/fr";
|
||||
import portugueze from "./translations/pt";
|
||||
import thai from "./translations/th";
|
||||
import chineseSimplified from "./translations/zh-CN";
|
||||
|
||||
export const LOCALES = {
|
||||
ENGLISH: {
|
||||
name: "English",
|
||||
code: "en",
|
||||
messages: english,
|
||||
},
|
||||
GERMAN: {
|
||||
name: "Deutsch",
|
||||
code: "de",
|
||||
messages: german,
|
||||
},
|
||||
FRENCH: {
|
||||
name: "Français",
|
||||
code: "fr",
|
||||
messages: french,
|
||||
},
|
||||
PORTUGUEZE: {
|
||||
name: "Português",
|
||||
code: "pt",
|
||||
messages: portugueze,
|
||||
},
|
||||
DANISH: {
|
||||
name: "Dansk",
|
||||
code: "da",
|
||||
messages: danish,
|
||||
},
|
||||
THAI: {
|
||||
name: "ไทย",
|
||||
code: "th",
|
||||
messages: thai,
|
||||
},
|
||||
SPANISH: {
|
||||
name: "Español",
|
||||
code: "es",
|
||||
messages: spanish,
|
||||
},
|
||||
CHINESE_SIMPLIFIED: {
|
||||
name: "简体中文",
|
||||
code: "zh-CN",
|
||||
messages: chineseSimplified,
|
||||
},
|
||||
};
|
435
frontend/src/i18n/translations/da.ts
Normal file
435
frontend/src/i18n/translations/da.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Upload",
|
||||
"navbar.signin": "Sign in",
|
||||
"navbar.home": "Home",
|
||||
"navbar.signup": "Sign Up",
|
||||
|
||||
"navbar.links.shares": "My shares",
|
||||
"navbar.links.reverse": "Reverse shares",
|
||||
|
||||
"navbar.avatar.account": "My account",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Sign out",
|
||||
// END navbar
|
||||
|
||||
// /
|
||||
"home.title": "A <h>self-hosted</h> file sharing platform.",
|
||||
|
||||
"home.description":
|
||||
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
|
||||
"home.bullet.a.name": "Self-Hosted",
|
||||
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
|
||||
"home.bullet.b.name": "Privacy",
|
||||
"home.bullet.b.description":
|
||||
"Your files are your files and should never get into the hands of third parties.",
|
||||
"home.bullet.c.name": "No annoying file size limit",
|
||||
"home.bullet.c.description":
|
||||
"Upload as big files as you want. Only your hard drive will be your limit.",
|
||||
|
||||
"home.button.start": "Get started",
|
||||
"home.button.source": "Source code",
|
||||
// END /
|
||||
|
||||
// /auth/signin
|
||||
"signin.title": "Welcome back",
|
||||
"signin.description": "You don't have an account yet?",
|
||||
"signin.button.signup": "Sign up",
|
||||
"signin.input.email-or-username": "Email or username",
|
||||
"signin.input.email-or-username.placeholder": "Your email or username",
|
||||
"signin.input.password": "Password",
|
||||
"signin.input.password.placeholder": "Your password",
|
||||
"signin.button.submit": "Sign in",
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
// /auth/signup
|
||||
"signup.title": "Create an account",
|
||||
"signup.description": "Already have an account?",
|
||||
"signup.button.signin": "Sign in",
|
||||
"signup.input.username": "Username",
|
||||
"signup.input.username.placeholder": "Your username",
|
||||
"signup.input.email": "Email",
|
||||
"signup.input.email.placeholder": "Your email",
|
||||
"signup.button.submit": "Let's get started",
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
"resetPassword.notify.success":
|
||||
"An email has been sent with a link to reset your password.",
|
||||
"resetPassword.button.back": "Back to sign in page",
|
||||
"resetPassword.text.resetPassword": "Reset password",
|
||||
"resetPassword.text.enterNewPassword": "Enter your new password",
|
||||
"resetPassword.input.password": "New password",
|
||||
"resetPassword.notify.passwordReset":
|
||||
"Your password has been reset successfully.",
|
||||
|
||||
// /account
|
||||
"account.title": "My account",
|
||||
|
||||
"account.card.info.title": "Account info",
|
||||
"account.card.info.username": "Username",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "Account updated successfully",
|
||||
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
"account.card.security.totp.disable.description":
|
||||
"Enter your current password to start enabling 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.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 informatons",
|
||||
"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-link": "Share link",
|
||||
|
||||
"account.shares.modal.delete.title": "Delete {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.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":
|
||||
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
|
||||
"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} filed failed to upload. Trying again.",
|
||||
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Upload files",
|
||||
"upload.dropzone.description":
|
||||
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
|
||||
"upload.dropzone.notify.file-too-big":
|
||||
"Your files exceed the maximum share size of {maxSize}.",
|
||||
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Name",
|
||||
"upload.filelist.size": "Size",
|
||||
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Create Share",
|
||||
"upload.modal.link.error.invalid":
|
||||
"Can only contain letters, numbers, underscores, and hyphens",
|
||||
"upload.modal.link.error.taken": "This link is already in use",
|
||||
"upload.modal.not-signed-in": "You're not signed in",
|
||||
"upload.modal.not-signed-in-description":
|
||||
"You will be unable to delete your share manually and view the visitor count.",
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.link.placeholder": "myAwesomeShare",
|
||||
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
"upload.modal.expires.minute-plural": "Minutes",
|
||||
"upload.modal.expires.hour-singular": "Hour",
|
||||
"upload.modal.expires.hour-plural": "Hours",
|
||||
"upload.modal.expires.day-singular": "Day",
|
||||
"upload.modal.expires.day-plural": "Days",
|
||||
"upload.modal.expires.week-singular": "Week",
|
||||
"upload.modal.expires.week-plural": "Weeks",
|
||||
"upload.modal.expires.month-singular": "Month",
|
||||
"upload.modal.expires.month-plural": "Months",
|
||||
"upload.modal.expires.year-singular": "Year",
|
||||
"upload.modal.expires.year-plural": "Years",
|
||||
|
||||
"upload.modal.accordion.description.title": "Description",
|
||||
"upload.modal.accordion.description.placeholder":
|
||||
"Note for the recipients of this share",
|
||||
|
||||
"upload.modal.accordion.email.title": "Email recipients",
|
||||
"upload.modal.accordion.email.placeholder": "Enter email recipients",
|
||||
"upload.modal.accordion.email.invalid-email": "Invalid email address",
|
||||
|
||||
"upload.modal.accordion.security.title": "Security options",
|
||||
"upload.modal.accordion.security.password.label": "Password protection",
|
||||
"upload.modal.accordion.security.password.placeholder": "No password",
|
||||
"upload.modal.accordion.security.max-views.label": "Maximum views",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "No limit",
|
||||
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "This share will never expire.",
|
||||
"upload.modal.completed.expires-on":
|
||||
"This share will expire on {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Share ready",
|
||||
|
||||
// END /upload
|
||||
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
"share.error.not-found.title": "Share not found",
|
||||
"share.error.not-found.description":
|
||||
"The share you're looking for doesn't exist.",
|
||||
|
||||
"share.modal.password.title": "Password required",
|
||||
"share.modal.password.description":
|
||||
"To access this share please enter the password for the share.",
|
||||
"share.modal.password": "Password",
|
||||
"share.modal.error.invalid-password": "Invalid password",
|
||||
|
||||
"share.button.download-all": "Download all",
|
||||
"share.notify.download-all-preparing":
|
||||
"The share is preparing. Try again in a few minutes.",
|
||||
|
||||
"share.modal.file-link": "File link",
|
||||
"share.table.name": "Name",
|
||||
"share.table.size": "Size",
|
||||
|
||||
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
|
||||
"share.modal.file-preview.error.not-supported.description":
|
||||
"A preview for thise file type is unsupported. Please download the file to view it.",
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description":
|
||||
"On which URL Pingvin Share is available",
|
||||
"admin.config.general.show-home-page": "Show home page",
|
||||
"admin.config.general.show-home-page.description":
|
||||
"Whether to show the home page",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description":
|
||||
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Pick image",
|
||||
|
||||
"admin.config.email.enable-share-email-recipients":
|
||||
"Enable share email recipients",
|
||||
"admin.config.email.enable-share-email-recipients.description":
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Share recipients subject",
|
||||
"admin.config.email.share-recipients-subject.description":
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
"admin.config.email.share-recipients-message": "Share recipients message",
|
||||
"admin.config.email.share-recipients-message.description":
|
||||
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual values.",
|
||||
"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 unauthorized users can create shares",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
|
||||
"admin.config.smtp.enabled": "Enabled",
|
||||
"admin.config.smtp.enabled.description":
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host of the SMTP server",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port of the SMTP server",
|
||||
"admin.config.smtp.email": "Email",
|
||||
"admin.config.smtp.email.description":
|
||||
"Email address which the emails get sent from",
|
||||
"admin.config.smtp.username": "Username",
|
||||
"admin.config.smtp.username.description": "Username of the SMTP server",
|
||||
"admin.config.smtp.password": "Password",
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
// 404
|
||||
"404.title": "404",
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Gring me back home",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
"common.button.submit": "Submit",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.share": "Share",
|
||||
"common.button.generate": "Generate",
|
||||
"common.button.done": "Done",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "or",
|
||||
"common.button.go-back": "Go back",
|
||||
"common.notify.copied": "Your link was copied to the clipboard",
|
||||
|
||||
"common.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",
|
||||
};
|
435
frontend/src/i18n/translations/de.ts
Normal file
435
frontend/src/i18n/translations/de.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Upload",
|
||||
"navbar.signin": "Sign in",
|
||||
"navbar.home": "Home",
|
||||
"navbar.signup": "Sign Up",
|
||||
|
||||
"navbar.links.shares": "My shares",
|
||||
"navbar.links.reverse": "Reverse shares",
|
||||
|
||||
"navbar.avatar.account": "My account",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Sign out",
|
||||
// END navbar
|
||||
|
||||
// /
|
||||
"home.title": "A <h>self-hosted</h> file sharing platform.",
|
||||
|
||||
"home.description":
|
||||
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
|
||||
"home.bullet.a.name": "Self-Hosted",
|
||||
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
|
||||
"home.bullet.b.name": "Privacy",
|
||||
"home.bullet.b.description":
|
||||
"Your files are your files and should never get into the hands of third parties.",
|
||||
"home.bullet.c.name": "No annoying file size limit",
|
||||
"home.bullet.c.description":
|
||||
"Upload as big files as you want. Only your hard drive will be your limit.",
|
||||
|
||||
"home.button.start": "Get started",
|
||||
"home.button.source": "Source code",
|
||||
// END /
|
||||
|
||||
// /auth/signin
|
||||
"signin.title": "Welcome back",
|
||||
"signin.description": "You don't have an account yet?",
|
||||
"signin.button.signup": "Sign up",
|
||||
"signin.input.email-or-username": "Email or username",
|
||||
"signin.input.email-or-username.placeholder": "Your email or username",
|
||||
"signin.input.password": "Password",
|
||||
"signin.input.password.placeholder": "Your password",
|
||||
"signin.button.submit": "Sign in",
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
// /auth/signup
|
||||
"signup.title": "Create an account",
|
||||
"signup.description": "Already have an account?",
|
||||
"signup.button.signin": "Sign in",
|
||||
"signup.input.username": "Username",
|
||||
"signup.input.username.placeholder": "Your username",
|
||||
"signup.input.email": "Email",
|
||||
"signup.input.email.placeholder": "Your email",
|
||||
"signup.button.submit": "Let's get started",
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
"resetPassword.notify.success":
|
||||
"An email has been sent with a link to reset your password.",
|
||||
"resetPassword.button.back": "Back to sign in page",
|
||||
"resetPassword.text.resetPassword": "Reset password",
|
||||
"resetPassword.text.enterNewPassword": "Enter your new password",
|
||||
"resetPassword.input.password": "New password",
|
||||
"resetPassword.notify.passwordReset":
|
||||
"Your password has been reset successfully.",
|
||||
|
||||
// /account
|
||||
"account.title": "My account",
|
||||
|
||||
"account.card.info.title": "Account info",
|
||||
"account.card.info.username": "Username",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "Account updated successfully",
|
||||
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
"account.card.security.totp.disable.description":
|
||||
"Enter your current password to start enabling 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.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 informatons",
|
||||
"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-link": "Share link",
|
||||
|
||||
"account.shares.modal.delete.title": "Delete {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.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":
|
||||
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
|
||||
"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} filed failed to upload. Trying again.",
|
||||
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Upload files",
|
||||
"upload.dropzone.description":
|
||||
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
|
||||
"upload.dropzone.notify.file-too-big":
|
||||
"Your files exceed the maximum share size of {maxSize}.",
|
||||
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Name",
|
||||
"upload.filelist.size": "Size",
|
||||
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Create Share",
|
||||
"upload.modal.link.error.invalid":
|
||||
"Can only contain letters, numbers, underscores, and hyphens",
|
||||
"upload.modal.link.error.taken": "This link is already in use",
|
||||
"upload.modal.not-signed-in": "You're not signed in",
|
||||
"upload.modal.not-signed-in-description":
|
||||
"You will be unable to delete your share manually and view the visitor count.",
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.link.placeholder": "myAwesomeShare",
|
||||
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
"upload.modal.expires.minute-plural": "Minutes",
|
||||
"upload.modal.expires.hour-singular": "Hour",
|
||||
"upload.modal.expires.hour-plural": "Hours",
|
||||
"upload.modal.expires.day-singular": "Day",
|
||||
"upload.modal.expires.day-plural": "Days",
|
||||
"upload.modal.expires.week-singular": "Week",
|
||||
"upload.modal.expires.week-plural": "Weeks",
|
||||
"upload.modal.expires.month-singular": "Month",
|
||||
"upload.modal.expires.month-plural": "Months",
|
||||
"upload.modal.expires.year-singular": "Year",
|
||||
"upload.modal.expires.year-plural": "Years",
|
||||
|
||||
"upload.modal.accordion.description.title": "Description",
|
||||
"upload.modal.accordion.description.placeholder":
|
||||
"Note for the recipients of this share",
|
||||
|
||||
"upload.modal.accordion.email.title": "Email recipients",
|
||||
"upload.modal.accordion.email.placeholder": "Enter email recipients",
|
||||
"upload.modal.accordion.email.invalid-email": "Invalid email address",
|
||||
|
||||
"upload.modal.accordion.security.title": "Security options",
|
||||
"upload.modal.accordion.security.password.label": "Password protection",
|
||||
"upload.modal.accordion.security.password.placeholder": "No password",
|
||||
"upload.modal.accordion.security.max-views.label": "Maximum views",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "No limit",
|
||||
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "This share will never expire.",
|
||||
"upload.modal.completed.expires-on":
|
||||
"This share will expire on {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Share ready",
|
||||
|
||||
// END /upload
|
||||
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
"share.error.not-found.title": "Share not found",
|
||||
"share.error.not-found.description":
|
||||
"The share you're looking for doesn't exist.",
|
||||
|
||||
"share.modal.password.title": "Password required",
|
||||
"share.modal.password.description":
|
||||
"To access this share please enter the password for the share.",
|
||||
"share.modal.password": "Password",
|
||||
"share.modal.error.invalid-password": "Invalid password",
|
||||
|
||||
"share.button.download-all": "Download all",
|
||||
"share.notify.download-all-preparing":
|
||||
"The share is preparing. Try again in a few minutes.",
|
||||
|
||||
"share.modal.file-link": "File link",
|
||||
"share.table.name": "Name",
|
||||
"share.table.size": "Size",
|
||||
|
||||
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
|
||||
"share.modal.file-preview.error.not-supported.description":
|
||||
"A preview for thise file type is unsupported. Please download the file to view it.",
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description":
|
||||
"On which URL Pingvin Share is available",
|
||||
"admin.config.general.show-home-page": "Show home page",
|
||||
"admin.config.general.show-home-page.description":
|
||||
"Whether to show the home page",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description":
|
||||
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Pick image",
|
||||
|
||||
"admin.config.email.enable-share-email-recipients":
|
||||
"Enable share email recipients",
|
||||
"admin.config.email.enable-share-email-recipients.description":
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Share recipients subject",
|
||||
"admin.config.email.share-recipients-subject.description":
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
"admin.config.email.share-recipients-message": "Share recipients message",
|
||||
"admin.config.email.share-recipients-message.description":
|
||||
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual values.",
|
||||
"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 unauthorized users can create shares",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
|
||||
"admin.config.smtp.enabled": "Enabled",
|
||||
"admin.config.smtp.enabled.description":
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host of the SMTP server",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port of the SMTP server",
|
||||
"admin.config.smtp.email": "Email",
|
||||
"admin.config.smtp.email.description":
|
||||
"Email address which the emails get sent from",
|
||||
"admin.config.smtp.username": "Username",
|
||||
"admin.config.smtp.username.description": "Username of the SMTP server",
|
||||
"admin.config.smtp.password": "Password",
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
// 404
|
||||
"404.title": "404",
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Gring me back home",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
"common.button.submit": "Submit",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.share": "Share",
|
||||
"common.button.generate": "Generate",
|
||||
"common.button.done": "Done",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "or",
|
||||
"common.button.go-back": "Go back",
|
||||
"common.notify.copied": "Your link was copied to the clipboard",
|
||||
|
||||
"common.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",
|
||||
};
|
435
frontend/src/i18n/translations/en.ts
Normal file
435
frontend/src/i18n/translations/en.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Upload",
|
||||
"navbar.signin": "Sign in",
|
||||
"navbar.home": "Home",
|
||||
"navbar.signup": "Sign Up",
|
||||
|
||||
"navbar.links.shares": "My shares",
|
||||
"navbar.links.reverse": "Reverse shares",
|
||||
|
||||
"navbar.avatar.account": "My account",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Sign out",
|
||||
// END navbar
|
||||
|
||||
// /
|
||||
"home.title": "A <h>self-hosted</h> file sharing platform.",
|
||||
|
||||
"home.description":
|
||||
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
|
||||
"home.bullet.a.name": "Self-Hosted",
|
||||
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
|
||||
"home.bullet.b.name": "Privacy",
|
||||
"home.bullet.b.description":
|
||||
"Your files are your files and should never get into the hands of third parties.",
|
||||
"home.bullet.c.name": "No annoying file size limit",
|
||||
"home.bullet.c.description":
|
||||
"Upload as big files as you want. Only your hard drive will be your limit.",
|
||||
|
||||
"home.button.start": "Get started",
|
||||
"home.button.source": "Source code",
|
||||
// END /
|
||||
|
||||
// /auth/signin
|
||||
"signin.title": "Welcome back",
|
||||
"signin.description": "You don't have an account yet?",
|
||||
"signin.button.signup": "Sign up",
|
||||
"signin.input.email-or-username": "Email or username",
|
||||
"signin.input.email-or-username.placeholder": "Your email or username",
|
||||
"signin.input.password": "Password",
|
||||
"signin.input.password.placeholder": "Your password",
|
||||
"signin.button.submit": "Sign in",
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
// /auth/signup
|
||||
"signup.title": "Create an account",
|
||||
"signup.description": "Already have an account?",
|
||||
"signup.button.signin": "Sign in",
|
||||
"signup.input.username": "Username",
|
||||
"signup.input.username.placeholder": "Your username",
|
||||
"signup.input.email": "Email",
|
||||
"signup.input.email.placeholder": "Your email",
|
||||
"signup.button.submit": "Let's get started",
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
"resetPassword.notify.success":
|
||||
"An email has been sent with a link to reset your password.",
|
||||
"resetPassword.button.back": "Back to sign in page",
|
||||
"resetPassword.text.resetPassword": "Reset password",
|
||||
"resetPassword.text.enterNewPassword": "Enter your new password",
|
||||
"resetPassword.input.password": "New password",
|
||||
"resetPassword.notify.passwordReset":
|
||||
"Your password has been reset successfully.",
|
||||
|
||||
// /account
|
||||
"account.title": "My account",
|
||||
|
||||
"account.card.info.title": "Account info",
|
||||
"account.card.info.username": "Username",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "Account updated successfully",
|
||||
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
"account.card.security.totp.disable.description":
|
||||
"Enter your current password to start enabling 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.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 informatons",
|
||||
"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-link": "Share link",
|
||||
|
||||
"account.shares.modal.delete.title": "Delete {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.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":
|
||||
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
|
||||
"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} filed failed to upload. Trying again.",
|
||||
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Upload files",
|
||||
"upload.dropzone.description":
|
||||
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
|
||||
"upload.dropzone.notify.file-too-big":
|
||||
"Your files exceed the maximum share size of {maxSize}.",
|
||||
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Name",
|
||||
"upload.filelist.size": "Size",
|
||||
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Create Share",
|
||||
"upload.modal.link.error.invalid":
|
||||
"Can only contain letters, numbers, underscores, and hyphens",
|
||||
"upload.modal.link.error.taken": "This link is already in use",
|
||||
"upload.modal.not-signed-in": "You're not signed in",
|
||||
"upload.modal.not-signed-in-description":
|
||||
"You will be unable to delete your share manually and view the visitor count.",
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.link.placeholder": "myAwesomeShare",
|
||||
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
"upload.modal.expires.minute-plural": "Minutes",
|
||||
"upload.modal.expires.hour-singular": "Hour",
|
||||
"upload.modal.expires.hour-plural": "Hours",
|
||||
"upload.modal.expires.day-singular": "Day",
|
||||
"upload.modal.expires.day-plural": "Days",
|
||||
"upload.modal.expires.week-singular": "Week",
|
||||
"upload.modal.expires.week-plural": "Weeks",
|
||||
"upload.modal.expires.month-singular": "Month",
|
||||
"upload.modal.expires.month-plural": "Months",
|
||||
"upload.modal.expires.year-singular": "Year",
|
||||
"upload.modal.expires.year-plural": "Years",
|
||||
|
||||
"upload.modal.accordion.description.title": "Description",
|
||||
"upload.modal.accordion.description.placeholder":
|
||||
"Note for the recipients of this share",
|
||||
|
||||
"upload.modal.accordion.email.title": "Email recipients",
|
||||
"upload.modal.accordion.email.placeholder": "Enter email recipients",
|
||||
"upload.modal.accordion.email.invalid-email": "Invalid email address",
|
||||
|
||||
"upload.modal.accordion.security.title": "Security options",
|
||||
"upload.modal.accordion.security.password.label": "Password protection",
|
||||
"upload.modal.accordion.security.password.placeholder": "No password",
|
||||
"upload.modal.accordion.security.max-views.label": "Maximum views",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "No limit",
|
||||
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "This share will never expire.",
|
||||
"upload.modal.completed.expires-on":
|
||||
"This share will expire on {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Share ready",
|
||||
|
||||
// END /upload
|
||||
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
"share.error.not-found.title": "Share not found",
|
||||
"share.error.not-found.description":
|
||||
"The share you're looking for doesn't exist.",
|
||||
|
||||
"share.modal.password.title": "Password required",
|
||||
"share.modal.password.description":
|
||||
"To access this share please enter the password for the share.",
|
||||
"share.modal.password": "Password",
|
||||
"share.modal.error.invalid-password": "Invalid password",
|
||||
|
||||
"share.button.download-all": "Download all",
|
||||
"share.notify.download-all-preparing":
|
||||
"The share is preparing. Try again in a few minutes.",
|
||||
|
||||
"share.modal.file-link": "File link",
|
||||
"share.table.name": "Name",
|
||||
"share.table.size": "Size",
|
||||
|
||||
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
|
||||
"share.modal.file-preview.error.not-supported.description":
|
||||
"A preview for thise file type is unsupported. Please download the file to view it.",
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description":
|
||||
"On which URL Pingvin Share is available",
|
||||
"admin.config.general.show-home-page": "Show home page",
|
||||
"admin.config.general.show-home-page.description":
|
||||
"Whether to show the home page",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description":
|
||||
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Pick image",
|
||||
|
||||
"admin.config.email.enable-share-email-recipients":
|
||||
"Enable share email recipients",
|
||||
"admin.config.email.enable-share-email-recipients.description":
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Share recipients subject",
|
||||
"admin.config.email.share-recipients-subject.description":
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
"admin.config.email.share-recipients-message": "Share recipients message",
|
||||
"admin.config.email.share-recipients-message.description":
|
||||
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual values.",
|
||||
"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 unauthorized users can create shares",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
|
||||
"admin.config.smtp.enabled": "Enabled",
|
||||
"admin.config.smtp.enabled.description":
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host of the SMTP server",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port of the SMTP server",
|
||||
"admin.config.smtp.email": "Email",
|
||||
"admin.config.smtp.email.description":
|
||||
"Email address which the emails get sent from",
|
||||
"admin.config.smtp.username": "Username",
|
||||
"admin.config.smtp.username.description": "Username of the SMTP server",
|
||||
"admin.config.smtp.password": "Password",
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
// 404
|
||||
"404.title": "404",
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Gring me back home",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
"common.button.submit": "Submit",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.share": "Share",
|
||||
"common.button.generate": "Generate",
|
||||
"common.button.done": "Done",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "or",
|
||||
"common.button.go-back": "Go back",
|
||||
"common.notify.copied": "Your link was copied to the clipboard",
|
||||
|
||||
"common.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",
|
||||
};
|
435
frontend/src/i18n/translations/es.ts
Normal file
435
frontend/src/i18n/translations/es.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Upload",
|
||||
"navbar.signin": "Sign in",
|
||||
"navbar.home": "Home",
|
||||
"navbar.signup": "Sign Up",
|
||||
|
||||
"navbar.links.shares": "My shares",
|
||||
"navbar.links.reverse": "Reverse shares",
|
||||
|
||||
"navbar.avatar.account": "My account",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Sign out",
|
||||
// END navbar
|
||||
|
||||
// /
|
||||
"home.title": "A <h>self-hosted</h> file sharing platform.",
|
||||
|
||||
"home.description":
|
||||
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
|
||||
"home.bullet.a.name": "Self-Hosted",
|
||||
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
|
||||
"home.bullet.b.name": "Privacy",
|
||||
"home.bullet.b.description":
|
||||
"Your files are your files and should never get into the hands of third parties.",
|
||||
"home.bullet.c.name": "No annoying file size limit",
|
||||
"home.bullet.c.description":
|
||||
"Upload as big files as you want. Only your hard drive will be your limit.",
|
||||
|
||||
"home.button.start": "Get started",
|
||||
"home.button.source": "Source code",
|
||||
// END /
|
||||
|
||||
// /auth/signin
|
||||
"signin.title": "Welcome back",
|
||||
"signin.description": "You don't have an account yet?",
|
||||
"signin.button.signup": "Sign up",
|
||||
"signin.input.email-or-username": "Email or username",
|
||||
"signin.input.email-or-username.placeholder": "Your email or username",
|
||||
"signin.input.password": "Password",
|
||||
"signin.input.password.placeholder": "Your password",
|
||||
"signin.button.submit": "Sign in",
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
// /auth/signup
|
||||
"signup.title": "Create an account",
|
||||
"signup.description": "Already have an account?",
|
||||
"signup.button.signin": "Sign in",
|
||||
"signup.input.username": "Username",
|
||||
"signup.input.username.placeholder": "Your username",
|
||||
"signup.input.email": "Email",
|
||||
"signup.input.email.placeholder": "Your email",
|
||||
"signup.button.submit": "Let's get started",
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
"resetPassword.notify.success":
|
||||
"An email has been sent with a link to reset your password.",
|
||||
"resetPassword.button.back": "Back to sign in page",
|
||||
"resetPassword.text.resetPassword": "Reset password",
|
||||
"resetPassword.text.enterNewPassword": "Enter your new password",
|
||||
"resetPassword.input.password": "New password",
|
||||
"resetPassword.notify.passwordReset":
|
||||
"Your password has been reset successfully.",
|
||||
|
||||
// /account
|
||||
"account.title": "My account",
|
||||
|
||||
"account.card.info.title": "Account info",
|
||||
"account.card.info.username": "Username",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "Account updated successfully",
|
||||
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
"account.card.security.totp.disable.description":
|
||||
"Enter your current password to start enabling 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.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 informatons",
|
||||
"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-link": "Share link",
|
||||
|
||||
"account.shares.modal.delete.title": "Delete {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.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":
|
||||
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
|
||||
"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} filed failed to upload. Trying again.",
|
||||
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Upload files",
|
||||
"upload.dropzone.description":
|
||||
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
|
||||
"upload.dropzone.notify.file-too-big":
|
||||
"Your files exceed the maximum share size of {maxSize}.",
|
||||
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Name",
|
||||
"upload.filelist.size": "Size",
|
||||
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Create Share",
|
||||
"upload.modal.link.error.invalid":
|
||||
"Can only contain letters, numbers, underscores, and hyphens",
|
||||
"upload.modal.link.error.taken": "This link is already in use",
|
||||
"upload.modal.not-signed-in": "You're not signed in",
|
||||
"upload.modal.not-signed-in-description":
|
||||
"You will be unable to delete your share manually and view the visitor count.",
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.link.placeholder": "myAwesomeShare",
|
||||
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
"upload.modal.expires.minute-plural": "Minutes",
|
||||
"upload.modal.expires.hour-singular": "Hour",
|
||||
"upload.modal.expires.hour-plural": "Hours",
|
||||
"upload.modal.expires.day-singular": "Day",
|
||||
"upload.modal.expires.day-plural": "Days",
|
||||
"upload.modal.expires.week-singular": "Week",
|
||||
"upload.modal.expires.week-plural": "Weeks",
|
||||
"upload.modal.expires.month-singular": "Month",
|
||||
"upload.modal.expires.month-plural": "Months",
|
||||
"upload.modal.expires.year-singular": "Year",
|
||||
"upload.modal.expires.year-plural": "Years",
|
||||
|
||||
"upload.modal.accordion.description.title": "Description",
|
||||
"upload.modal.accordion.description.placeholder":
|
||||
"Note for the recipients of this share",
|
||||
|
||||
"upload.modal.accordion.email.title": "Email recipients",
|
||||
"upload.modal.accordion.email.placeholder": "Enter email recipients",
|
||||
"upload.modal.accordion.email.invalid-email": "Invalid email address",
|
||||
|
||||
"upload.modal.accordion.security.title": "Security options",
|
||||
"upload.modal.accordion.security.password.label": "Password protection",
|
||||
"upload.modal.accordion.security.password.placeholder": "No password",
|
||||
"upload.modal.accordion.security.max-views.label": "Maximum views",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "No limit",
|
||||
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "This share will never expire.",
|
||||
"upload.modal.completed.expires-on":
|
||||
"This share will expire on {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Share ready",
|
||||
|
||||
// END /upload
|
||||
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
"share.error.not-found.title": "Share not found",
|
||||
"share.error.not-found.description":
|
||||
"The share you're looking for doesn't exist.",
|
||||
|
||||
"share.modal.password.title": "Password required",
|
||||
"share.modal.password.description":
|
||||
"To access this share please enter the password for the share.",
|
||||
"share.modal.password": "Password",
|
||||
"share.modal.error.invalid-password": "Invalid password",
|
||||
|
||||
"share.button.download-all": "Download all",
|
||||
"share.notify.download-all-preparing":
|
||||
"The share is preparing. Try again in a few minutes.",
|
||||
|
||||
"share.modal.file-link": "File link",
|
||||
"share.table.name": "Name",
|
||||
"share.table.size": "Size",
|
||||
|
||||
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
|
||||
"share.modal.file-preview.error.not-supported.description":
|
||||
"A preview for thise file type is unsupported. Please download the file to view it.",
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description":
|
||||
"On which URL Pingvin Share is available",
|
||||
"admin.config.general.show-home-page": "Show home page",
|
||||
"admin.config.general.show-home-page.description":
|
||||
"Whether to show the home page",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description":
|
||||
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Pick image",
|
||||
|
||||
"admin.config.email.enable-share-email-recipients":
|
||||
"Enable share email recipients",
|
||||
"admin.config.email.enable-share-email-recipients.description":
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Share recipients subject",
|
||||
"admin.config.email.share-recipients-subject.description":
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
"admin.config.email.share-recipients-message": "Share recipients message",
|
||||
"admin.config.email.share-recipients-message.description":
|
||||
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual values.",
|
||||
"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 unauthorized users can create shares",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
|
||||
"admin.config.smtp.enabled": "Enabled",
|
||||
"admin.config.smtp.enabled.description":
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host of the SMTP server",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port of the SMTP server",
|
||||
"admin.config.smtp.email": "Email",
|
||||
"admin.config.smtp.email.description":
|
||||
"Email address which the emails get sent from",
|
||||
"admin.config.smtp.username": "Username",
|
||||
"admin.config.smtp.username.description": "Username of the SMTP server",
|
||||
"admin.config.smtp.password": "Password",
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
// 404
|
||||
"404.title": "404",
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Gring me back home",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
"common.button.submit": "Submit",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.share": "Share",
|
||||
"common.button.generate": "Generate",
|
||||
"common.button.done": "Done",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "or",
|
||||
"common.button.go-back": "Go back",
|
||||
"common.notify.copied": "Your link was copied to the clipboard",
|
||||
|
||||
"common.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",
|
||||
};
|
435
frontend/src/i18n/translations/fr.ts
Normal file
435
frontend/src/i18n/translations/fr.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Upload",
|
||||
"navbar.signin": "Sign in",
|
||||
"navbar.home": "Home",
|
||||
"navbar.signup": "Sign Up",
|
||||
|
||||
"navbar.links.shares": "My shares",
|
||||
"navbar.links.reverse": "Reverse shares",
|
||||
|
||||
"navbar.avatar.account": "My account",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Sign out",
|
||||
// END navbar
|
||||
|
||||
// /
|
||||
"home.title": "A <h>self-hosted</h> file sharing platform.",
|
||||
|
||||
"home.description":
|
||||
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
|
||||
"home.bullet.a.name": "Self-Hosted",
|
||||
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
|
||||
"home.bullet.b.name": "Privacy",
|
||||
"home.bullet.b.description":
|
||||
"Your files are your files and should never get into the hands of third parties.",
|
||||
"home.bullet.c.name": "No annoying file size limit",
|
||||
"home.bullet.c.description":
|
||||
"Upload as big files as you want. Only your hard drive will be your limit.",
|
||||
|
||||
"home.button.start": "Get started",
|
||||
"home.button.source": "Source code",
|
||||
// END /
|
||||
|
||||
// /auth/signin
|
||||
"signin.title": "Welcome back",
|
||||
"signin.description": "You don't have an account yet?",
|
||||
"signin.button.signup": "Sign up",
|
||||
"signin.input.email-or-username": "Email or username",
|
||||
"signin.input.email-or-username.placeholder": "Your email or username",
|
||||
"signin.input.password": "Password",
|
||||
"signin.input.password.placeholder": "Your password",
|
||||
"signin.button.submit": "Sign in",
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
// /auth/signup
|
||||
"signup.title": "Create an account",
|
||||
"signup.description": "Already have an account?",
|
||||
"signup.button.signin": "Sign in",
|
||||
"signup.input.username": "Username",
|
||||
"signup.input.username.placeholder": "Your username",
|
||||
"signup.input.email": "Email",
|
||||
"signup.input.email.placeholder": "Your email",
|
||||
"signup.button.submit": "Let's get started",
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
"resetPassword.notify.success":
|
||||
"An email has been sent with a link to reset your password.",
|
||||
"resetPassword.button.back": "Back to sign in page",
|
||||
"resetPassword.text.resetPassword": "Reset password",
|
||||
"resetPassword.text.enterNewPassword": "Enter your new password",
|
||||
"resetPassword.input.password": "New password",
|
||||
"resetPassword.notify.passwordReset":
|
||||
"Your password has been reset successfully.",
|
||||
|
||||
// /account
|
||||
"account.title": "My account",
|
||||
|
||||
"account.card.info.title": "Account info",
|
||||
"account.card.info.username": "Username",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "Account updated successfully",
|
||||
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
"account.card.security.totp.disable.description":
|
||||
"Enter your current password to start enabling 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.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 informatons",
|
||||
"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-link": "Share link",
|
||||
|
||||
"account.shares.modal.delete.title": "Delete {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.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":
|
||||
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
|
||||
"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} filed failed to upload. Trying again.",
|
||||
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Upload files",
|
||||
"upload.dropzone.description":
|
||||
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
|
||||
"upload.dropzone.notify.file-too-big":
|
||||
"Your files exceed the maximum share size of {maxSize}.",
|
||||
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Name",
|
||||
"upload.filelist.size": "Size",
|
||||
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Create Share",
|
||||
"upload.modal.link.error.invalid":
|
||||
"Can only contain letters, numbers, underscores, and hyphens",
|
||||
"upload.modal.link.error.taken": "This link is already in use",
|
||||
"upload.modal.not-signed-in": "You're not signed in",
|
||||
"upload.modal.not-signed-in-description":
|
||||
"You will be unable to delete your share manually and view the visitor count.",
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.link.placeholder": "myAwesomeShare",
|
||||
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
"upload.modal.expires.minute-plural": "Minutes",
|
||||
"upload.modal.expires.hour-singular": "Hour",
|
||||
"upload.modal.expires.hour-plural": "Hours",
|
||||
"upload.modal.expires.day-singular": "Day",
|
||||
"upload.modal.expires.day-plural": "Days",
|
||||
"upload.modal.expires.week-singular": "Week",
|
||||
"upload.modal.expires.week-plural": "Weeks",
|
||||
"upload.modal.expires.month-singular": "Month",
|
||||
"upload.modal.expires.month-plural": "Months",
|
||||
"upload.modal.expires.year-singular": "Year",
|
||||
"upload.modal.expires.year-plural": "Years",
|
||||
|
||||
"upload.modal.accordion.description.title": "Description",
|
||||
"upload.modal.accordion.description.placeholder":
|
||||
"Note for the recipients of this share",
|
||||
|
||||
"upload.modal.accordion.email.title": "Email recipients",
|
||||
"upload.modal.accordion.email.placeholder": "Enter email recipients",
|
||||
"upload.modal.accordion.email.invalid-email": "Invalid email address",
|
||||
|
||||
"upload.modal.accordion.security.title": "Security options",
|
||||
"upload.modal.accordion.security.password.label": "Password protection",
|
||||
"upload.modal.accordion.security.password.placeholder": "No password",
|
||||
"upload.modal.accordion.security.max-views.label": "Maximum views",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "No limit",
|
||||
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "This share will never expire.",
|
||||
"upload.modal.completed.expires-on":
|
||||
"This share will expire on {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Share ready",
|
||||
|
||||
// END /upload
|
||||
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
"share.error.not-found.title": "Share not found",
|
||||
"share.error.not-found.description":
|
||||
"The share you're looking for doesn't exist.",
|
||||
|
||||
"share.modal.password.title": "Password required",
|
||||
"share.modal.password.description":
|
||||
"To access this share please enter the password for the share.",
|
||||
"share.modal.password": "Password",
|
||||
"share.modal.error.invalid-password": "Invalid password",
|
||||
|
||||
"share.button.download-all": "Download all",
|
||||
"share.notify.download-all-preparing":
|
||||
"The share is preparing. Try again in a few minutes.",
|
||||
|
||||
"share.modal.file-link": "File link",
|
||||
"share.table.name": "Name",
|
||||
"share.table.size": "Size",
|
||||
|
||||
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
|
||||
"share.modal.file-preview.error.not-supported.description":
|
||||
"A preview for thise file type is unsupported. Please download the file to view it.",
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description":
|
||||
"On which URL Pingvin Share is available",
|
||||
"admin.config.general.show-home-page": "Show home page",
|
||||
"admin.config.general.show-home-page.description":
|
||||
"Whether to show the home page",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description":
|
||||
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Pick image",
|
||||
|
||||
"admin.config.email.enable-share-email-recipients":
|
||||
"Enable share email recipients",
|
||||
"admin.config.email.enable-share-email-recipients.description":
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Share recipients subject",
|
||||
"admin.config.email.share-recipients-subject.description":
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
"admin.config.email.share-recipients-message": "Share recipients message",
|
||||
"admin.config.email.share-recipients-message.description":
|
||||
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual values.",
|
||||
"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 unauthorized users can create shares",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
|
||||
"admin.config.smtp.enabled": "Enabled",
|
||||
"admin.config.smtp.enabled.description":
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host of the SMTP server",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port of the SMTP server",
|
||||
"admin.config.smtp.email": "Email",
|
||||
"admin.config.smtp.email.description":
|
||||
"Email address which the emails get sent from",
|
||||
"admin.config.smtp.username": "Username",
|
||||
"admin.config.smtp.username.description": "Username of the SMTP server",
|
||||
"admin.config.smtp.password": "Password",
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
// 404
|
||||
"404.title": "404",
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Gring me back home",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
"common.button.submit": "Submit",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.share": "Share",
|
||||
"common.button.generate": "Generate",
|
||||
"common.button.done": "Done",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "or",
|
||||
"common.button.go-back": "Go back",
|
||||
"common.notify.copied": "Your link was copied to the clipboard",
|
||||
|
||||
"common.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",
|
||||
};
|
435
frontend/src/i18n/translations/pt.ts
Normal file
435
frontend/src/i18n/translations/pt.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Upload",
|
||||
"navbar.signin": "Sign in",
|
||||
"navbar.home": "Home",
|
||||
"navbar.signup": "Sign Up",
|
||||
|
||||
"navbar.links.shares": "My shares",
|
||||
"navbar.links.reverse": "Reverse shares",
|
||||
|
||||
"navbar.avatar.account": "My account",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Sign out",
|
||||
// END navbar
|
||||
|
||||
// /
|
||||
"home.title": "A <h>self-hosted</h> file sharing platform.",
|
||||
|
||||
"home.description":
|
||||
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
|
||||
"home.bullet.a.name": "Self-Hosted",
|
||||
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
|
||||
"home.bullet.b.name": "Privacy",
|
||||
"home.bullet.b.description":
|
||||
"Your files are your files and should never get into the hands of third parties.",
|
||||
"home.bullet.c.name": "No annoying file size limit",
|
||||
"home.bullet.c.description":
|
||||
"Upload as big files as you want. Only your hard drive will be your limit.",
|
||||
|
||||
"home.button.start": "Get started",
|
||||
"home.button.source": "Source code",
|
||||
// END /
|
||||
|
||||
// /auth/signin
|
||||
"signin.title": "Welcome back",
|
||||
"signin.description": "You don't have an account yet?",
|
||||
"signin.button.signup": "Sign up",
|
||||
"signin.input.email-or-username": "Email or username",
|
||||
"signin.input.email-or-username.placeholder": "Your email or username",
|
||||
"signin.input.password": "Password",
|
||||
"signin.input.password.placeholder": "Your password",
|
||||
"signin.button.submit": "Sign in",
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
// /auth/signup
|
||||
"signup.title": "Create an account",
|
||||
"signup.description": "Already have an account?",
|
||||
"signup.button.signin": "Sign in",
|
||||
"signup.input.username": "Username",
|
||||
"signup.input.username.placeholder": "Your username",
|
||||
"signup.input.email": "Email",
|
||||
"signup.input.email.placeholder": "Your email",
|
||||
"signup.button.submit": "Let's get started",
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
"resetPassword.notify.success":
|
||||
"An email has been sent with a link to reset your password.",
|
||||
"resetPassword.button.back": "Back to sign in page",
|
||||
"resetPassword.text.resetPassword": "Reset password",
|
||||
"resetPassword.text.enterNewPassword": "Enter your new password",
|
||||
"resetPassword.input.password": "New password",
|
||||
"resetPassword.notify.passwordReset":
|
||||
"Your password has been reset successfully.",
|
||||
|
||||
// /account
|
||||
"account.title": "My account",
|
||||
|
||||
"account.card.info.title": "Account info",
|
||||
"account.card.info.username": "Username",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "Account updated successfully",
|
||||
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
"account.card.security.totp.disable.description":
|
||||
"Enter your current password to start enabling 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.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 informatons",
|
||||
"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-link": "Share link",
|
||||
|
||||
"account.shares.modal.delete.title": "Delete {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.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":
|
||||
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
|
||||
"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} filed failed to upload. Trying again.",
|
||||
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Upload files",
|
||||
"upload.dropzone.description":
|
||||
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
|
||||
"upload.dropzone.notify.file-too-big":
|
||||
"Your files exceed the maximum share size of {maxSize}.",
|
||||
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Name",
|
||||
"upload.filelist.size": "Size",
|
||||
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Create Share",
|
||||
"upload.modal.link.error.invalid":
|
||||
"Can only contain letters, numbers, underscores, and hyphens",
|
||||
"upload.modal.link.error.taken": "This link is already in use",
|
||||
"upload.modal.not-signed-in": "You're not signed in",
|
||||
"upload.modal.not-signed-in-description":
|
||||
"You will be unable to delete your share manually and view the visitor count.",
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.link.placeholder": "myAwesomeShare",
|
||||
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
"upload.modal.expires.minute-plural": "Minutes",
|
||||
"upload.modal.expires.hour-singular": "Hour",
|
||||
"upload.modal.expires.hour-plural": "Hours",
|
||||
"upload.modal.expires.day-singular": "Day",
|
||||
"upload.modal.expires.day-plural": "Days",
|
||||
"upload.modal.expires.week-singular": "Week",
|
||||
"upload.modal.expires.week-plural": "Weeks",
|
||||
"upload.modal.expires.month-singular": "Month",
|
||||
"upload.modal.expires.month-plural": "Months",
|
||||
"upload.modal.expires.year-singular": "Year",
|
||||
"upload.modal.expires.year-plural": "Years",
|
||||
|
||||
"upload.modal.accordion.description.title": "Description",
|
||||
"upload.modal.accordion.description.placeholder":
|
||||
"Note for the recipients of this share",
|
||||
|
||||
"upload.modal.accordion.email.title": "Email recipients",
|
||||
"upload.modal.accordion.email.placeholder": "Enter email recipients",
|
||||
"upload.modal.accordion.email.invalid-email": "Invalid email address",
|
||||
|
||||
"upload.modal.accordion.security.title": "Security options",
|
||||
"upload.modal.accordion.security.password.label": "Password protection",
|
||||
"upload.modal.accordion.security.password.placeholder": "No password",
|
||||
"upload.modal.accordion.security.max-views.label": "Maximum views",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "No limit",
|
||||
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "This share will never expire.",
|
||||
"upload.modal.completed.expires-on":
|
||||
"This share will expire on {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Share ready",
|
||||
|
||||
// END /upload
|
||||
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
"share.error.not-found.title": "Share not found",
|
||||
"share.error.not-found.description":
|
||||
"The share you're looking for doesn't exist.",
|
||||
|
||||
"share.modal.password.title": "Password required",
|
||||
"share.modal.password.description":
|
||||
"To access this share please enter the password for the share.",
|
||||
"share.modal.password": "Password",
|
||||
"share.modal.error.invalid-password": "Invalid password",
|
||||
|
||||
"share.button.download-all": "Download all",
|
||||
"share.notify.download-all-preparing":
|
||||
"The share is preparing. Try again in a few minutes.",
|
||||
|
||||
"share.modal.file-link": "File link",
|
||||
"share.table.name": "Name",
|
||||
"share.table.size": "Size",
|
||||
|
||||
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
|
||||
"share.modal.file-preview.error.not-supported.description":
|
||||
"A preview for thise file type is unsupported. Please download the file to view it.",
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description":
|
||||
"On which URL Pingvin Share is available",
|
||||
"admin.config.general.show-home-page": "Show home page",
|
||||
"admin.config.general.show-home-page.description":
|
||||
"Whether to show the home page",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description":
|
||||
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Pick image",
|
||||
|
||||
"admin.config.email.enable-share-email-recipients":
|
||||
"Enable share email recipients",
|
||||
"admin.config.email.enable-share-email-recipients.description":
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Share recipients subject",
|
||||
"admin.config.email.share-recipients-subject.description":
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
"admin.config.email.share-recipients-message": "Share recipients message",
|
||||
"admin.config.email.share-recipients-message.description":
|
||||
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual values.",
|
||||
"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 unauthorized users can create shares",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
|
||||
"admin.config.smtp.enabled": "Enabled",
|
||||
"admin.config.smtp.enabled.description":
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host of the SMTP server",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port of the SMTP server",
|
||||
"admin.config.smtp.email": "Email",
|
||||
"admin.config.smtp.email.description":
|
||||
"Email address which the emails get sent from",
|
||||
"admin.config.smtp.username": "Username",
|
||||
"admin.config.smtp.username.description": "Username of the SMTP server",
|
||||
"admin.config.smtp.password": "Password",
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
// 404
|
||||
"404.title": "404",
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Gring me back home",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
"common.button.submit": "Submit",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.share": "Share",
|
||||
"common.button.generate": "Generate",
|
||||
"common.button.done": "Done",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "or",
|
||||
"common.button.go-back": "Go back",
|
||||
"common.notify.copied": "Your link was copied to the clipboard",
|
||||
|
||||
"common.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",
|
||||
};
|
435
frontend/src/i18n/translations/th.ts
Normal file
435
frontend/src/i18n/translations/th.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Upload",
|
||||
"navbar.signin": "Sign in",
|
||||
"navbar.home": "Home",
|
||||
"navbar.signup": "Sign Up",
|
||||
|
||||
"navbar.links.shares": "My shares",
|
||||
"navbar.links.reverse": "Reverse shares",
|
||||
|
||||
"navbar.avatar.account": "My account",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Sign out",
|
||||
// END navbar
|
||||
|
||||
// /
|
||||
"home.title": "A <h>self-hosted</h> file sharing platform.",
|
||||
|
||||
"home.description":
|
||||
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
|
||||
"home.bullet.a.name": "Self-Hosted",
|
||||
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
|
||||
"home.bullet.b.name": "Privacy",
|
||||
"home.bullet.b.description":
|
||||
"Your files are your files and should never get into the hands of third parties.",
|
||||
"home.bullet.c.name": "No annoying file size limit",
|
||||
"home.bullet.c.description":
|
||||
"Upload as big files as you want. Only your hard drive will be your limit.",
|
||||
|
||||
"home.button.start": "Get started",
|
||||
"home.button.source": "Source code",
|
||||
// END /
|
||||
|
||||
// /auth/signin
|
||||
"signin.title": "Welcome back",
|
||||
"signin.description": "You don't have an account yet?",
|
||||
"signin.button.signup": "Sign up",
|
||||
"signin.input.email-or-username": "Email or username",
|
||||
"signin.input.email-or-username.placeholder": "Your email or username",
|
||||
"signin.input.password": "Password",
|
||||
"signin.input.password.placeholder": "Your password",
|
||||
"signin.button.submit": "Sign in",
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
// /auth/signup
|
||||
"signup.title": "Create an account",
|
||||
"signup.description": "Already have an account?",
|
||||
"signup.button.signin": "Sign in",
|
||||
"signup.input.username": "Username",
|
||||
"signup.input.username.placeholder": "Your username",
|
||||
"signup.input.email": "Email",
|
||||
"signup.input.email.placeholder": "Your email",
|
||||
"signup.button.submit": "Let's get started",
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
"resetPassword.notify.success":
|
||||
"An email has been sent with a link to reset your password.",
|
||||
"resetPassword.button.back": "Back to sign in page",
|
||||
"resetPassword.text.resetPassword": "Reset password",
|
||||
"resetPassword.text.enterNewPassword": "Enter your new password",
|
||||
"resetPassword.input.password": "New password",
|
||||
"resetPassword.notify.passwordReset":
|
||||
"Your password has been reset successfully.",
|
||||
|
||||
// /account
|
||||
"account.title": "My account",
|
||||
|
||||
"account.card.info.title": "Account info",
|
||||
"account.card.info.username": "Username",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "Account updated successfully",
|
||||
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
"account.card.security.totp.disable.description":
|
||||
"Enter your current password to start enabling 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.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 informatons",
|
||||
"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-link": "Share link",
|
||||
|
||||
"account.shares.modal.delete.title": "Delete {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.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":
|
||||
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
|
||||
"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} filed failed to upload. Trying again.",
|
||||
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Upload files",
|
||||
"upload.dropzone.description":
|
||||
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
|
||||
"upload.dropzone.notify.file-too-big":
|
||||
"Your files exceed the maximum share size of {maxSize}.",
|
||||
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Name",
|
||||
"upload.filelist.size": "Size",
|
||||
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Create Share",
|
||||
"upload.modal.link.error.invalid":
|
||||
"Can only contain letters, numbers, underscores, and hyphens",
|
||||
"upload.modal.link.error.taken": "This link is already in use",
|
||||
"upload.modal.not-signed-in": "You're not signed in",
|
||||
"upload.modal.not-signed-in-description":
|
||||
"You will be unable to delete your share manually and view the visitor count.",
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.link.placeholder": "myAwesomeShare",
|
||||
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
"upload.modal.expires.minute-plural": "Minutes",
|
||||
"upload.modal.expires.hour-singular": "Hour",
|
||||
"upload.modal.expires.hour-plural": "Hours",
|
||||
"upload.modal.expires.day-singular": "Day",
|
||||
"upload.modal.expires.day-plural": "Days",
|
||||
"upload.modal.expires.week-singular": "Week",
|
||||
"upload.modal.expires.week-plural": "Weeks",
|
||||
"upload.modal.expires.month-singular": "Month",
|
||||
"upload.modal.expires.month-plural": "Months",
|
||||
"upload.modal.expires.year-singular": "Year",
|
||||
"upload.modal.expires.year-plural": "Years",
|
||||
|
||||
"upload.modal.accordion.description.title": "Description",
|
||||
"upload.modal.accordion.description.placeholder":
|
||||
"Note for the recipients of this share",
|
||||
|
||||
"upload.modal.accordion.email.title": "Email recipients",
|
||||
"upload.modal.accordion.email.placeholder": "Enter email recipients",
|
||||
"upload.modal.accordion.email.invalid-email": "Invalid email address",
|
||||
|
||||
"upload.modal.accordion.security.title": "Security options",
|
||||
"upload.modal.accordion.security.password.label": "Password protection",
|
||||
"upload.modal.accordion.security.password.placeholder": "No password",
|
||||
"upload.modal.accordion.security.max-views.label": "Maximum views",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "No limit",
|
||||
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "This share will never expire.",
|
||||
"upload.modal.completed.expires-on":
|
||||
"This share will expire on {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Share ready",
|
||||
|
||||
// END /upload
|
||||
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
"share.error.not-found.title": "Share not found",
|
||||
"share.error.not-found.description":
|
||||
"The share you're looking for doesn't exist.",
|
||||
|
||||
"share.modal.password.title": "Password required",
|
||||
"share.modal.password.description":
|
||||
"To access this share please enter the password for the share.",
|
||||
"share.modal.password": "Password",
|
||||
"share.modal.error.invalid-password": "Invalid password",
|
||||
|
||||
"share.button.download-all": "Download all",
|
||||
"share.notify.download-all-preparing":
|
||||
"The share is preparing. Try again in a few minutes.",
|
||||
|
||||
"share.modal.file-link": "File link",
|
||||
"share.table.name": "Name",
|
||||
"share.table.size": "Size",
|
||||
|
||||
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
|
||||
"share.modal.file-preview.error.not-supported.description":
|
||||
"A preview for thise file type is unsupported. Please download the file to view it.",
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description":
|
||||
"On which URL Pingvin Share is available",
|
||||
"admin.config.general.show-home-page": "Show home page",
|
||||
"admin.config.general.show-home-page.description":
|
||||
"Whether to show the home page",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description":
|
||||
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Pick image",
|
||||
|
||||
"admin.config.email.enable-share-email-recipients":
|
||||
"Enable share email recipients",
|
||||
"admin.config.email.enable-share-email-recipients.description":
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Share recipients subject",
|
||||
"admin.config.email.share-recipients-subject.description":
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
"admin.config.email.share-recipients-message": "Share recipients message",
|
||||
"admin.config.email.share-recipients-message.description":
|
||||
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual values.",
|
||||
"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 unauthorized users can create shares",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
|
||||
"admin.config.smtp.enabled": "Enabled",
|
||||
"admin.config.smtp.enabled.description":
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host of the SMTP server",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port of the SMTP server",
|
||||
"admin.config.smtp.email": "Email",
|
||||
"admin.config.smtp.email.description":
|
||||
"Email address which the emails get sent from",
|
||||
"admin.config.smtp.username": "Username",
|
||||
"admin.config.smtp.username.description": "Username of the SMTP server",
|
||||
"admin.config.smtp.password": "Password",
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
// 404
|
||||
"404.title": "404",
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Gring me back home",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
"common.button.submit": "Submit",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.share": "Share",
|
||||
"common.button.generate": "Generate",
|
||||
"common.button.done": "Done",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "or",
|
||||
"common.button.go-back": "Go back",
|
||||
"common.notify.copied": "Your link was copied to the clipboard",
|
||||
|
||||
"common.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",
|
||||
};
|
435
frontend/src/i18n/translations/zh-CN.ts
Normal file
435
frontend/src/i18n/translations/zh-CN.ts
Normal file
|
@ -0,0 +1,435 @@
|
|||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Upload",
|
||||
"navbar.signin": "Sign in",
|
||||
"navbar.home": "Home",
|
||||
"navbar.signup": "Sign Up",
|
||||
|
||||
"navbar.links.shares": "My shares",
|
||||
"navbar.links.reverse": "Reverse shares",
|
||||
|
||||
"navbar.avatar.account": "My account",
|
||||
"navbar.avatar.admin": "Administration",
|
||||
"navbar.avatar.signout": "Sign out",
|
||||
// END navbar
|
||||
|
||||
// /
|
||||
"home.title": "A <h>self-hosted</h> file sharing platform.",
|
||||
|
||||
"home.description":
|
||||
"Do you really want to give your personal files in the hand of third parties like WeTransfer?",
|
||||
"home.bullet.a.name": "Self-Hosted",
|
||||
"home.bullet.a.description": "Host Pingvin Share on your own machine.",
|
||||
"home.bullet.b.name": "Privacy",
|
||||
"home.bullet.b.description":
|
||||
"Your files are your files and should never get into the hands of third parties.",
|
||||
"home.bullet.c.name": "No annoying file size limit",
|
||||
"home.bullet.c.description":
|
||||
"Upload as big files as you want. Only your hard drive will be your limit.",
|
||||
|
||||
"home.button.start": "Get started",
|
||||
"home.button.source": "Source code",
|
||||
// END /
|
||||
|
||||
// /auth/signin
|
||||
"signin.title": "Welcome back",
|
||||
"signin.description": "You don't have an account yet?",
|
||||
"signin.button.signup": "Sign up",
|
||||
"signin.input.email-or-username": "Email or username",
|
||||
"signin.input.email-or-username.placeholder": "Your email or username",
|
||||
"signin.input.password": "Password",
|
||||
"signin.input.password.placeholder": "Your password",
|
||||
"signin.button.submit": "Sign in",
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
// /auth/signup
|
||||
"signup.title": "Create an account",
|
||||
"signup.description": "Already have an account?",
|
||||
"signup.button.signin": "Sign in",
|
||||
"signup.input.username": "Username",
|
||||
"signup.input.username.placeholder": "Your username",
|
||||
"signup.input.email": "Email",
|
||||
"signup.input.email.placeholder": "Your email",
|
||||
"signup.button.submit": "Let's get started",
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
"resetPassword.notify.success":
|
||||
"An email has been sent with a link to reset your password.",
|
||||
"resetPassword.button.back": "Back to sign in page",
|
||||
"resetPassword.text.resetPassword": "Reset password",
|
||||
"resetPassword.text.enterNewPassword": "Enter your new password",
|
||||
"resetPassword.input.password": "New password",
|
||||
"resetPassword.notify.passwordReset":
|
||||
"Your password has been reset successfully.",
|
||||
|
||||
// /account
|
||||
"account.title": "My account",
|
||||
|
||||
"account.card.info.title": "Account info",
|
||||
"account.card.info.username": "Username",
|
||||
"account.card.info.email": "Email",
|
||||
"account.notify.info.success": "Account updated successfully",
|
||||
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
"account.card.security.totp.disable.description":
|
||||
"Enter your current password to start enabling 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.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 informatons",
|
||||
"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-link": "Share link",
|
||||
|
||||
"account.shares.modal.delete.title": "Delete {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.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":
|
||||
"Do you really want to delete this reverse share? If you do, the associated shares will be deleted as well.",
|
||||
"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} filed failed to upload. Trying again.",
|
||||
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Upload files",
|
||||
"upload.dropzone.description":
|
||||
"Drag'n'drop files here to start your share. We can accept only files that are less than {maxSize} in total.",
|
||||
"upload.dropzone.notify.file-too-big":
|
||||
"Your files exceed the maximum share size of {maxSize}.",
|
||||
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Name",
|
||||
"upload.filelist.size": "Size",
|
||||
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Create Share",
|
||||
"upload.modal.link.error.invalid":
|
||||
"Can only contain letters, numbers, underscores, and hyphens",
|
||||
"upload.modal.link.error.taken": "This link is already in use",
|
||||
"upload.modal.not-signed-in": "You're not signed in",
|
||||
"upload.modal.not-signed-in-description":
|
||||
"You will be unable to delete your share manually and view the visitor count.",
|
||||
|
||||
"upload.modal.expires.never": "never",
|
||||
"upload.modal.expires.never-long": "Never Expires",
|
||||
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.link.placeholder": "myAwesomeShare",
|
||||
|
||||
"upload.modal.expires.label": "Expiration",
|
||||
"upload.modal.expires.minute-singular": "Minute",
|
||||
"upload.modal.expires.minute-plural": "Minutes",
|
||||
"upload.modal.expires.hour-singular": "Hour",
|
||||
"upload.modal.expires.hour-plural": "Hours",
|
||||
"upload.modal.expires.day-singular": "Day",
|
||||
"upload.modal.expires.day-plural": "Days",
|
||||
"upload.modal.expires.week-singular": "Week",
|
||||
"upload.modal.expires.week-plural": "Weeks",
|
||||
"upload.modal.expires.month-singular": "Month",
|
||||
"upload.modal.expires.month-plural": "Months",
|
||||
"upload.modal.expires.year-singular": "Year",
|
||||
"upload.modal.expires.year-plural": "Years",
|
||||
|
||||
"upload.modal.accordion.description.title": "Description",
|
||||
"upload.modal.accordion.description.placeholder":
|
||||
"Note for the recipients of this share",
|
||||
|
||||
"upload.modal.accordion.email.title": "Email recipients",
|
||||
"upload.modal.accordion.email.placeholder": "Enter email recipients",
|
||||
"upload.modal.accordion.email.invalid-email": "Invalid email address",
|
||||
|
||||
"upload.modal.accordion.security.title": "Security options",
|
||||
"upload.modal.accordion.security.password.label": "Password protection",
|
||||
"upload.modal.accordion.security.password.placeholder": "No password",
|
||||
"upload.modal.accordion.security.max-views.label": "Maximum views",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "No limit",
|
||||
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "This share will never expire.",
|
||||
"upload.modal.completed.expires-on":
|
||||
"This share will expire on {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Share ready",
|
||||
|
||||
// END /upload
|
||||
|
||||
// /share/[id]
|
||||
"share.title": "Share {shareId}",
|
||||
"share.description": "Look what I've shared with you!",
|
||||
"share.error.visitor-limit-exceeded.title": "Visitor limit exceeded",
|
||||
"share.error.visitor-limit-exceeded.description":
|
||||
"The visitor limit from this share has been exceeded.",
|
||||
"share.error.removed.title": "Share removed",
|
||||
"share.error.not-found.title": "Share not found",
|
||||
"share.error.not-found.description":
|
||||
"The share you're looking for doesn't exist.",
|
||||
|
||||
"share.modal.password.title": "Password required",
|
||||
"share.modal.password.description":
|
||||
"To access this share please enter the password for the share.",
|
||||
"share.modal.password": "Password",
|
||||
"share.modal.error.invalid-password": "Invalid password",
|
||||
|
||||
"share.button.download-all": "Download all",
|
||||
"share.notify.download-all-preparing":
|
||||
"The share is preparing. Try again in a few minutes.",
|
||||
|
||||
"share.modal.file-link": "File link",
|
||||
"share.table.name": "Name",
|
||||
"share.table.size": "Size",
|
||||
|
||||
"share.modal.file-preview.error.not-supported.title": "Preview not supported",
|
||||
"share.modal.file-preview.error.not-supported.description":
|
||||
"A preview for thise file type is unsupported. Please download the file to view it.",
|
||||
|
||||
// END /share/[id]
|
||||
|
||||
// /admin/config
|
||||
"admin.config.title": "Configuration",
|
||||
"admin.config.category.general": "General",
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
"admin.config.general.app-url.description":
|
||||
"On which URL Pingvin Share is available",
|
||||
"admin.config.general.show-home-page": "Show home page",
|
||||
"admin.config.general.show-home-page.description":
|
||||
"Whether to show the home page",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description":
|
||||
"Change your logo by uploading a new image. The image must be a PNG and should have the format 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Pick image",
|
||||
|
||||
"admin.config.email.enable-share-email-recipients":
|
||||
"Enable share email recipients",
|
||||
"admin.config.email.enable-share-email-recipients.description":
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Share recipients subject",
|
||||
"admin.config.email.share-recipients-subject.description":
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
"admin.config.email.share-recipients-message": "Share recipients message",
|
||||
"admin.config.email.share-recipients-message.description":
|
||||
"Message which gets sent to the share recipients. Available variables:\n {creator} - The username of the creator of the share\n {shareUrl} - The URL of the share\n {desc} - The description of the share\n {expires} - The expiration date of the share\n The variables will be replaced with the actual values.",
|
||||
"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 unauthorized users can create shares",
|
||||
"admin.config.share.max-size": "Max size",
|
||||
"admin.config.share.max-size.description": "Maximum share size in bytes",
|
||||
|
||||
"admin.config.smtp.enabled": "Enabled",
|
||||
"admin.config.smtp.enabled.description":
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host of the SMTP server",
|
||||
"admin.config.smtp.port": "Port",
|
||||
"admin.config.smtp.port.description": "Port of the SMTP server",
|
||||
"admin.config.smtp.email": "Email",
|
||||
"admin.config.smtp.email.description":
|
||||
"Email address which the emails get sent from",
|
||||
"admin.config.smtp.username": "Username",
|
||||
"admin.config.smtp.username.description": "Username of the SMTP server",
|
||||
"admin.config.smtp.password": "Password",
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
// 404
|
||||
"404.title": "404",
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Gring me back home",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
"common.button.submit": "Submit",
|
||||
"common.button.delete": "Delete",
|
||||
"common.button.cancel": "Cancel",
|
||||
"common.button.confirm": "Confirm",
|
||||
"common.button.disable": "Disable",
|
||||
"common.button.share": "Share",
|
||||
"common.button.generate": "Generate",
|
||||
"common.button.done": "Done",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "or",
|
||||
"common.button.go-back": "Go back",
|
||||
"common.notify.copied": "Your link was copied to the clipboard",
|
||||
|
||||
"common.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",
|
||||
};
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import Meta from "../components/Meta";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
root: {
|
||||
|
@ -42,9 +43,11 @@ const ErrorNotFound = () => {
|
|||
<>
|
||||
<Meta title="Not found" />
|
||||
<Container className={classes.root}>
|
||||
<div className={classes.label}>404</div>
|
||||
<div className={classes.label}>
|
||||
<FormattedMessage id="404.title" />
|
||||
</div>
|
||||
<Title align="center" order={3}>
|
||||
Oops this page doesn't exist.
|
||||
<FormattedMessage id="404.description" />
|
||||
</Title>
|
||||
<Text
|
||||
color="dimmed"
|
||||
|
@ -53,7 +56,7 @@ const ErrorNotFound = () => {
|
|||
></Text>
|
||||
<Group position="center">
|
||||
<Button component={Link} href="/" variant="light">
|
||||
Bring me back
|
||||
<FormattedMessage id="404.button.home" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Container>
|
||||
|
|
|
@ -13,11 +13,12 @@ import { GetServerSidePropsContext } from "next";
|
|||
import type { AppProps } from "next/app";
|
||||
import getConfig from "next/config";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { IntlProvider } from "react-intl";
|
||||
import Header from "../components/header/Header";
|
||||
import { ConfigContext } from "../hooks/config.hook";
|
||||
import usePreferences from "../hooks/usePreferences";
|
||||
import { UserContext } from "../hooks/user.hook";
|
||||
import { LOCALES } from "../i18n/locales";
|
||||
import authService from "../services/auth.service";
|
||||
import configService from "../services/config.service";
|
||||
import userService from "../services/user.service";
|
||||
|
@ -25,6 +26,8 @@ import GlobalStyle from "../styles/global.style";
|
|||
import globalStyle from "../styles/mantine.style";
|
||||
import Config from "../types/config.type";
|
||||
import { CurrentUser } from "../types/user.type";
|
||||
import i18nUtil from "../utils/i18n.util";
|
||||
import userPreferences from "../utils/userPreferences.util";
|
||||
|
||||
const excludeDefaultLayoutRoutes = ["/admin/config/[category]"];
|
||||
|
||||
|
@ -33,7 +36,6 @@ function App({ Component, pageProps }: AppProps) {
|
|||
const router = useRouter();
|
||||
|
||||
const [colorScheme, setColorScheme] = useState<ColorScheme>(systemTheme);
|
||||
const preferences = usePreferences();
|
||||
|
||||
const [user, setUser] = useState<CurrentUser | null>(pageProps.user);
|
||||
const [route, setRoute] = useState<string>(pageProps.route);
|
||||
|
@ -50,11 +52,20 @@ function App({ Component, pageProps }: AppProps) {
|
|||
setInterval(async () => await authService.refreshAccessToken(), 30 * 1000);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!pageProps.language) return;
|
||||
const cookieLanguage = getCookie("language");
|
||||
if (pageProps.language != cookieLanguage) {
|
||||
i18nUtil.setLanguageCookie(pageProps.language);
|
||||
if (cookieLanguage) location.reload();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const colorScheme =
|
||||
preferences.get("colorScheme") == "system"
|
||||
userPreferences.get("colorScheme") == "system"
|
||||
? systemTheme
|
||||
: preferences.get("colorScheme");
|
||||
: userPreferences.get("colorScheme");
|
||||
|
||||
toggleColorScheme(colorScheme);
|
||||
}, [systemTheme]);
|
||||
|
@ -66,52 +77,60 @@ function App({ Component, pageProps }: AppProps) {
|
|||
});
|
||||
};
|
||||
|
||||
const language = useRef(pageProps.language);
|
||||
|
||||
return (
|
||||
<MantineProvider
|
||||
withGlobalStyles
|
||||
withNormalizeCSS
|
||||
theme={{ colorScheme, ...globalStyle }}
|
||||
<IntlProvider
|
||||
messages={i18nUtil.getLocaleByCode(language.current)?.messages}
|
||||
locale={language.current}
|
||||
defaultLocale={LOCALES.ENGLISH.code}
|
||||
>
|
||||
<ColorSchemeProvider
|
||||
colorScheme={colorScheme}
|
||||
toggleColorScheme={toggleColorScheme}
|
||||
<MantineProvider
|
||||
withGlobalStyles
|
||||
withNormalizeCSS
|
||||
theme={{ colorScheme, ...globalStyle }}
|
||||
>
|
||||
<GlobalStyle />
|
||||
<Notifications />
|
||||
<ModalsProvider>
|
||||
<ConfigContext.Provider
|
||||
value={{
|
||||
configVariables,
|
||||
refresh: async () => {
|
||||
setConfigVariables(await configService.list());
|
||||
},
|
||||
}}
|
||||
>
|
||||
<UserContext.Provider
|
||||
<ColorSchemeProvider
|
||||
colorScheme={colorScheme}
|
||||
toggleColorScheme={toggleColorScheme}
|
||||
>
|
||||
<GlobalStyle />
|
||||
<Notifications />
|
||||
<ModalsProvider>
|
||||
<ConfigContext.Provider
|
||||
value={{
|
||||
user,
|
||||
refreshUser: async () => {
|
||||
const user = await userService.getCurrentUser();
|
||||
setUser(user);
|
||||
return user;
|
||||
configVariables,
|
||||
refresh: async () => {
|
||||
setConfigVariables(await configService.list());
|
||||
},
|
||||
}}
|
||||
>
|
||||
{excludeDefaultLayoutRoutes.includes(route) ? (
|
||||
<Component {...pageProps} />
|
||||
) : (
|
||||
<>
|
||||
<Header />
|
||||
<Container>
|
||||
<Component {...pageProps} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
</UserContext.Provider>
|
||||
</ConfigContext.Provider>
|
||||
</ModalsProvider>
|
||||
</ColorSchemeProvider>
|
||||
</MantineProvider>
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
user,
|
||||
refreshUser: async () => {
|
||||
const user = await userService.getCurrentUser();
|
||||
setUser(user);
|
||||
return user;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{excludeDefaultLayoutRoutes.includes(route) ? (
|
||||
<Component {...pageProps} />
|
||||
) : (
|
||||
<>
|
||||
<Header />
|
||||
<Container>
|
||||
<Component {...pageProps} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
</UserContext.Provider>
|
||||
</ConfigContext.Provider>
|
||||
</ModalsProvider>
|
||||
</ColorSchemeProvider>
|
||||
</MantineProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -125,11 +144,13 @@ App.getInitialProps = async ({ ctx }: { ctx: GetServerSidePropsContext }) => {
|
|||
configVariables?: Config[];
|
||||
route?: string;
|
||||
colorScheme: ColorScheme;
|
||||
language?: string;
|
||||
} = {
|
||||
route: ctx.resolvedUrl,
|
||||
colorScheme:
|
||||
(getCookie("mantine-color-scheme", ctx) as ColorScheme) ?? "light",
|
||||
};
|
||||
|
||||
if (ctx.req) {
|
||||
const cookieHeader = ctx.req.headers.cookie;
|
||||
|
||||
|
@ -142,8 +163,13 @@ App.getInitialProps = async ({ ctx }: { ctx: GetServerSidePropsContext }) => {
|
|||
pageProps.configVariables = (await axios(`${apiURL}/api/configs`)).data;
|
||||
|
||||
pageProps.route = ctx.req.url;
|
||||
}
|
||||
|
||||
const requestLanguage = i18nUtil.getLanguageFromAcceptHeader(
|
||||
ctx.req.headers["accept-language"]
|
||||
);
|
||||
|
||||
pageProps.language = ctx.req.cookies["language"] ?? requestLanguage;
|
||||
}
|
||||
return { pageProps };
|
||||
};
|
||||
|
||||
|
|
|
@ -14,18 +14,22 @@ import {
|
|||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { Tb2Fa } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import showEnableTotpModal from "../../components/account/showEnableTotpModal";
|
||||
import ThemeSwitcher from "../../components/account/ThemeSwitcher";
|
||||
import Meta from "../../components/Meta";
|
||||
import ThemeSwitcher from "../../components/account/ThemeSwitcher";
|
||||
import showEnableTotpModal from "../../components/account/showEnableTotpModal";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import authService from "../../services/auth.service";
|
||||
import userService from "../../services/user.service";
|
||||
import toast from "../../utils/toast.util";
|
||||
import LanguagePicker from "../../components/account/LanguagePicker";
|
||||
|
||||
const Account = () => {
|
||||
const { user, refreshUser } = useUser();
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const accountForm = useForm({
|
||||
initialValues: {
|
||||
|
@ -34,8 +38,10 @@ const Account = () => {
|
|||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
email: yup.string().email(),
|
||||
username: yup.string().min(3),
|
||||
email: yup.string().email(t("common.error.invalid-email")),
|
||||
username: yup
|
||||
.string()
|
||||
.min(3, t("common.error.too-short", { length: 3 })),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -47,8 +53,14 @@ const Account = () => {
|
|||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
oldPassword: yup.string().min(8),
|
||||
password: yup.string().min(8),
|
||||
oldPassword: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.required(t("common.error.field-required")),
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.required(t("common.error.field-required")),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -59,7 +71,10 @@ const Account = () => {
|
|||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
password: yup.string().min(8),
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.required(t("common.error.field-required")),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -74,23 +89,23 @@ const Account = () => {
|
|||
password: yup.string().min(8),
|
||||
code: yup
|
||||
.string()
|
||||
.min(6)
|
||||
.max(6)
|
||||
.matches(/^[0-9]+$/, { message: "Code must be a number" }),
|
||||
.min(6, t("common.error.exact-length", { length: 6 }))
|
||||
.max(6, t("common.error.exact-length", { length: 6 }))
|
||||
.matches(/^[0-9]+$/, { message: t("common.error.invalid-number") }),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title="My account" />
|
||||
<Meta title={t("account.title")} />
|
||||
<Container size="sm">
|
||||
<Title order={3} mb="xs">
|
||||
My account
|
||||
<FormattedMessage id="account.title" />
|
||||
</Title>
|
||||
<Paper withBorder p="xl">
|
||||
<Title order={5} mb="xs">
|
||||
Account Info
|
||||
<FormattedMessage id="account.card.info.title" />
|
||||
</Title>
|
||||
<form
|
||||
onSubmit={accountForm.onSubmit((values) =>
|
||||
|
@ -99,35 +114,37 @@ const Account = () => {
|
|||
username: values.username,
|
||||
email: values.email,
|
||||
})
|
||||
.then(() => toast.success("User updated successfully"))
|
||||
.then(() => toast.success(t("account.notify.info.success")))
|
||||
.catch(toast.axiosError)
|
||||
)}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
label="Username"
|
||||
label={t("account.card.info.username")}
|
||||
{...accountForm.getInputProps("username")}
|
||||
/>
|
||||
<TextInput
|
||||
label="Email"
|
||||
label={t("account.card.info.email")}
|
||||
{...accountForm.getInputProps("email")}
|
||||
/>
|
||||
<Group position="right">
|
||||
<Button type="submit">Save</Button>
|
||||
<Button type="submit">
|
||||
<FormattedMessage id="common.button.save" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</Paper>
|
||||
<Paper withBorder p="xl" mt="lg">
|
||||
<Title order={5} mb="xs">
|
||||
Password
|
||||
<FormattedMessage id="account.card.password.title" />
|
||||
</Title>
|
||||
<form
|
||||
onSubmit={passwordForm.onSubmit((values) =>
|
||||
authService
|
||||
.updatePassword(values.oldPassword, values.password)
|
||||
.then(() => {
|
||||
toast.success("Password updated successfully");
|
||||
toast.success(t("account.notify.password.success"));
|
||||
passwordForm.reset();
|
||||
})
|
||||
.catch(toast.axiosError)
|
||||
|
@ -135,15 +152,17 @@ const Account = () => {
|
|||
>
|
||||
<Stack>
|
||||
<PasswordInput
|
||||
label="Old password"
|
||||
label={t("account.card.password.old")}
|
||||
{...passwordForm.getInputProps("oldPassword")}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="New password"
|
||||
label={t("account.card.password.new")}
|
||||
{...passwordForm.getInputProps("password")}
|
||||
/>
|
||||
<Group position="right">
|
||||
<Button type="submit">Save</Button>
|
||||
<Button type="submit">
|
||||
<FormattedMessage id="common.button.save" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
|
@ -151,7 +170,7 @@ const Account = () => {
|
|||
|
||||
<Paper withBorder p="xl" mt="lg">
|
||||
<Title order={5} mb="xs">
|
||||
Security
|
||||
<FormattedMessage id="account.card.security.title" />
|
||||
</Title>
|
||||
|
||||
<Tabs defaultValue="totp">
|
||||
|
@ -169,7 +188,7 @@ const Account = () => {
|
|||
authService
|
||||
.disableTOTP(values.code, values.password)
|
||||
.then(() => {
|
||||
toast.success("Successfully disabled TOTP");
|
||||
toast.success(t("account.notify.totp.disable"));
|
||||
values.password = "";
|
||||
values.code = "";
|
||||
refreshUser();
|
||||
|
@ -179,21 +198,23 @@ const Account = () => {
|
|||
>
|
||||
<Stack>
|
||||
<PasswordInput
|
||||
description="Enter your current password to disable TOTP"
|
||||
label="Password"
|
||||
description={t(
|
||||
"account.card.security.totp.disable.description"
|
||||
)}
|
||||
label={t("account.card.password.title")}
|
||||
{...disableTotpForm.getInputProps("password")}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
variant="filled"
|
||||
label="Code"
|
||||
label={t("account.modal.totp.code")}
|
||||
placeholder="******"
|
||||
{...disableTotpForm.getInputProps("code")}
|
||||
/>
|
||||
|
||||
<Group position="right">
|
||||
<Button color="red" type="submit">
|
||||
Disable
|
||||
<FormattedMessage id="common.button.disable" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
@ -218,12 +239,16 @@ const Account = () => {
|
|||
>
|
||||
<Stack>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
description="Enter your current password to start enabling TOTP"
|
||||
label={t("account.card.password.title")}
|
||||
description={t(
|
||||
"account.card.security.totp.enable.description"
|
||||
)}
|
||||
{...enableTotpForm.getInputProps("password")}
|
||||
/>
|
||||
<Group position="right">
|
||||
<Button type="submit">Start</Button>
|
||||
<Button type="submit">
|
||||
<FormattedMessage id="account.card.security.totp.button.start" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
|
@ -234,7 +259,13 @@ const Account = () => {
|
|||
</Paper>
|
||||
<Paper withBorder p="xl" mt="lg">
|
||||
<Title order={5} mb="xs">
|
||||
Color scheme
|
||||
<FormattedMessage id="account.card.language.title" />
|
||||
</Title>
|
||||
<LanguagePicker />
|
||||
</Paper>
|
||||
<Paper withBorder p="xl" mt="lg">
|
||||
<Title order={5} mb="xs">
|
||||
<FormattedMessage id="account.card.color.title" />
|
||||
</Title>
|
||||
<ThemeSwitcher />
|
||||
</Paper>
|
||||
|
@ -245,15 +276,17 @@ const Account = () => {
|
|||
color="red"
|
||||
onClick={() =>
|
||||
modals.openConfirmModal({
|
||||
title: "Account deletion",
|
||||
title: t("account.modal.delete.title"),
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Do you really want to delete your account including all
|
||||
your active shares?
|
||||
<FormattedMessage id="account.modal.delete.description" />
|
||||
</Text>
|
||||
),
|
||||
|
||||
labels: { confirm: "Delete", cancel: "Cancel" },
|
||||
labels: {
|
||||
confirm: t("common.button.delete"),
|
||||
cancel: t("common.button.cancel"),
|
||||
},
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: async () => {
|
||||
await userService.removeCurrentUser();
|
||||
|
@ -262,7 +295,7 @@ const Account = () => {
|
|||
})
|
||||
}
|
||||
>
|
||||
Delete Account
|
||||
<FormattedMessage id="account.button.delete" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</Center>
|
||||
|
|
|
@ -17,12 +17,14 @@ import { useModals } from "@mantine/modals";
|
|||
import moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TbInfoCircle, TbLink, TbPlus, TbTrash } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../components/Meta";
|
||||
import showReverseShareLinkModal from "../../components/account/showReverseShareLinkModal";
|
||||
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
||||
import CenterLoader from "../../components/core/CenterLoader";
|
||||
import showCreateReverseShareModal from "../../components/share/modals/showCreateReverseShareModal";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import shareService from "../../services/share.service";
|
||||
import { MyReverseShare } from "../../types/share.type";
|
||||
import { byteToHumanSizeString } from "../../utils/fileSize.util";
|
||||
|
@ -31,13 +33,14 @@ import toast from "../../utils/toast.util";
|
|||
const MyShares = () => {
|
||||
const modals = useModals();
|
||||
const clipboard = useClipboard();
|
||||
const t = useTranslate();
|
||||
|
||||
const config = useConfig();
|
||||
|
||||
const [reverseShares, setReverseShares] = useState<MyReverseShare[]>();
|
||||
|
||||
const appUrl = config.get("general.appUrl");
|
||||
|
||||
const [reverseShares, setReverseShares] = useState<MyReverseShare[]>();
|
||||
|
||||
const getReverseShares = () => {
|
||||
shareService
|
||||
.getMyReverseShares()
|
||||
|
@ -51,15 +54,17 @@ const MyShares = () => {
|
|||
if (!reverseShares) return <CenterLoader />;
|
||||
return (
|
||||
<>
|
||||
<Meta title="My shares" />
|
||||
<Meta title={t("account.reverseShares.title")} />
|
||||
<Group position="apart" align="baseline" mb={20}>
|
||||
<Group align="center" spacing={3} mb={30}>
|
||||
<Title order={3}>My reverse shares</Title>
|
||||
<Title order={3}>
|
||||
<FormattedMessage id="account.reverseShares.title" />
|
||||
</Title>
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
multiline
|
||||
width={220}
|
||||
label="A reverse share allows you to generate a unique URL that allows external users to create a share."
|
||||
label={t("account.reverseShares.description")}
|
||||
events={{ hover: true, focus: false, touch: true }}
|
||||
>
|
||||
<ActionIcon>
|
||||
|
@ -77,14 +82,18 @@ const MyShares = () => {
|
|||
}
|
||||
leftIcon={<TbPlus size={20} />}
|
||||
>
|
||||
Create
|
||||
<FormattedMessage id="common.button.create" />
|
||||
</Button>
|
||||
</Group>
|
||||
{reverseShares.length == 0 ? (
|
||||
<Center style={{ height: "70vh" }}>
|
||||
<Stack align="center" spacing={10}>
|
||||
<Title order={3}>It's empty here 👀</Title>
|
||||
<Text>You don't have any reverse shares.</Text>
|
||||
<Title order={3}>
|
||||
<FormattedMessage id="account.reverseShares.title.empty" />
|
||||
</Title>
|
||||
<Text>
|
||||
<FormattedMessage id="account.reverseShares.description.empty" />
|
||||
</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : (
|
||||
|
@ -92,10 +101,18 @@ const MyShares = () => {
|
|||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shares</th>
|
||||
<th>Remaining uses</th>
|
||||
<th>Max share size</th>
|
||||
<th>Expires at</th>
|
||||
<th>
|
||||
<FormattedMessage id="account.reverseShares.table.shares" />
|
||||
</th>
|
||||
<th>
|
||||
<FormattedMessage id="account.reverseShares.table.remaining" />
|
||||
</th>
|
||||
<th>
|
||||
<FormattedMessage id="account.reverseShares.table.max-size" />
|
||||
</th>
|
||||
<th>
|
||||
<FormattedMessage id="account.reverseShares.table.expires" />
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -105,7 +122,7 @@ const MyShares = () => {
|
|||
<td style={{ width: 220 }}>
|
||||
{reverseShare.shares.length == 0 ? (
|
||||
<Text color="dimmed" size="sm">
|
||||
No shares created yet
|
||||
<FormattedMessage id="account.reverseShares.table.no-shares" />
|
||||
</Text>
|
||||
) : (
|
||||
<Accordion>
|
||||
|
@ -115,9 +132,13 @@ const MyShares = () => {
|
|||
>
|
||||
<Accordion.Control p={0}>
|
||||
<Text size="sm">
|
||||
{`${reverseShare.shares.length} share${
|
||||
reverseShare.shares.length > 1 ? "s" : ""
|
||||
}`}
|
||||
{reverseShare.shares.length == 1
|
||||
? `1 ${t(
|
||||
"account.reverseShares.table.count.singular"
|
||||
)}`
|
||||
: `${reverseShare.shares.length} ${t(
|
||||
"account.reverseShares.table.count.plural"
|
||||
)}`}
|
||||
</Text>
|
||||
</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
|
@ -140,9 +161,7 @@ const MyShares = () => {
|
|||
clipboard.copy(
|
||||
`${appUrl}/share/${share.id}`
|
||||
);
|
||||
toast.success(
|
||||
"The share link was copied to the keyboard."
|
||||
);
|
||||
toast.success(t("common.notify.copied"));
|
||||
} else {
|
||||
showShareLinkModal(
|
||||
modals,
|
||||
|
@ -183,9 +202,7 @@ const MyShares = () => {
|
|||
reverseShare.token
|
||||
}`
|
||||
);
|
||||
toast.success(
|
||||
"The link was copied to your clipboard."
|
||||
);
|
||||
toast.success(t("common.notify.copied"));
|
||||
} else {
|
||||
showReverseShareLinkModal(
|
||||
modals,
|
||||
|
@ -203,18 +220,21 @@ const MyShares = () => {
|
|||
size={25}
|
||||
onClick={() => {
|
||||
modals.openConfirmModal({
|
||||
title: `Delete reverse share`,
|
||||
title: t(
|
||||
"account.reverseShares.modal.delete.title"
|
||||
),
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Do you really want to delete this reverse share?
|
||||
If you do, the associated shares will be deleted
|
||||
as well.
|
||||
<FormattedMessage id="account.reverseShares.modal.delete.description" />
|
||||
</Text>
|
||||
),
|
||||
confirmProps: {
|
||||
color: "red",
|
||||
},
|
||||
labels: { confirm: "Delete", cancel: "Cancel" },
|
||||
labels: {
|
||||
confirm: t("common.button.delete"),
|
||||
cancel: t("common.button.cancel"),
|
||||
},
|
||||
onConfirm: () => {
|
||||
shareService.removeReverseShare(reverseShare.id);
|
||||
setReverseShares(
|
||||
|
|
|
@ -17,11 +17,13 @@ import moment from "moment";
|
|||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TbInfoCircle, TbLink, TbTrash } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../components/Meta";
|
||||
import showShareInformationsModal from "../../components/account/showShareInformationsModal";
|
||||
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
||||
import CenterLoader from "../../components/core/CenterLoader";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import shareService from "../../services/share.service";
|
||||
import { MyShare } from "../../types/share.type";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
@ -30,6 +32,7 @@ const MyShares = () => {
|
|||
const modals = useModals();
|
||||
const clipboard = useClipboard();
|
||||
const config = useConfig();
|
||||
const t = useTranslate();
|
||||
|
||||
const [shares, setShares] = useState<MyShare[]>();
|
||||
|
||||
|
@ -41,18 +44,22 @@ const MyShares = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Meta title="My shares" />
|
||||
<Meta title={t("account.shares.title")} />
|
||||
<Title mb={30} order={3}>
|
||||
My shares
|
||||
<FormattedMessage id="account.shares.title" />
|
||||
</Title>
|
||||
{shares.length == 0 ? (
|
||||
<Center style={{ height: "70vh" }}>
|
||||
<Stack align="center" spacing={10}>
|
||||
<Title order={3}>It's empty here 👀</Title>
|
||||
<Text>You don't have any shares.</Text>
|
||||
<Title order={3}>
|
||||
<FormattedMessage id="account.shares.title.empty" />
|
||||
</Title>
|
||||
<Text>
|
||||
<FormattedMessage id="account.shares.description.empty" />
|
||||
</Text>
|
||||
<Space h={5} />
|
||||
<Button component={Link} href="/upload" variant="light">
|
||||
Create one
|
||||
<FormattedMessage id="account.shares.button.create" />
|
||||
</Button>
|
||||
</Stack>
|
||||
</Center>
|
||||
|
@ -61,13 +68,21 @@ const MyShares = () => {
|
|||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>
|
||||
<FormattedMessage id="account.shares.table.name" />
|
||||
</th>
|
||||
<MediaQuery smallerThan="md" styles={{ display: "none" }}>
|
||||
<th>Description</th>
|
||||
<th>
|
||||
<FormattedMessage id="account.shares.table.description" />
|
||||
</th>
|
||||
</MediaQuery>
|
||||
|
||||
<th>Visitors</th>
|
||||
<th>Expires at</th>
|
||||
<th>
|
||||
<FormattedMessage id="account.shares.table.visitors" />
|
||||
</th>
|
||||
<th>
|
||||
<FormattedMessage id="account.shares.table.expiresAt" />
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -121,9 +136,7 @@ const MyShares = () => {
|
|||
share.id
|
||||
}`
|
||||
);
|
||||
toast.success(
|
||||
"The link was copied to your clipboard."
|
||||
);
|
||||
toast.success(t("common.notify.copied"));
|
||||
} else {
|
||||
showShareLinkModal(
|
||||
modals,
|
||||
|
@ -141,16 +154,21 @@ const MyShares = () => {
|
|||
size={25}
|
||||
onClick={() => {
|
||||
modals.openConfirmModal({
|
||||
title: `Delete share ${share.id}`,
|
||||
title: t("account.shares.modal.delete.title", {
|
||||
share: share.id,
|
||||
}),
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Do you really want to delete this share?
|
||||
<FormattedMessage id="account.shares.modal.delete.description" />
|
||||
</Text>
|
||||
),
|
||||
confirmProps: {
|
||||
color: "red",
|
||||
},
|
||||
labels: { confirm: "Confirm", cancel: "Cancel" },
|
||||
labels: {
|
||||
confirm: t("common.button.delete"),
|
||||
cancel: t("common.button.cancel"),
|
||||
},
|
||||
onConfirm: () => {
|
||||
shareService.remove(share.id);
|
||||
setShares(
|
||||
|
|
|
@ -13,25 +13,28 @@ import { useMediaQuery } from "@mantine/hooks";
|
|||
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../../components/Meta";
|
||||
import AdminConfigInput from "../../../components/admin/configuration/AdminConfigInput";
|
||||
import ConfigurationHeader from "../../../components/admin/configuration/ConfigurationHeader";
|
||||
import ConfigurationNavBar from "../../../components/admin/configuration/ConfigurationNavBar";
|
||||
import LogoConfigInput from "../../../components/admin/configuration/LogoConfigInput";
|
||||
import TestEmailButton from "../../../components/admin/configuration/TestEmailButton";
|
||||
import CenterLoader from "../../../components/core/CenterLoader";
|
||||
import Meta from "../../../components/Meta";
|
||||
import useConfig from "../../../hooks/config.hook";
|
||||
import configService from "../../../services/config.service";
|
||||
import { AdminConfig, UpdateConfig } from "../../../types/config.type";
|
||||
import {
|
||||
camelToKebab,
|
||||
capitalizeFirstLetter,
|
||||
configVariableToFriendlyName,
|
||||
} from "../../../utils/string.util";
|
||||
import toast from "../../../utils/toast.util";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
|
||||
export default function AppShellDemo() {
|
||||
const theme = useMantineTheme();
|
||||
const router = useRouter();
|
||||
const t = useTranslate();
|
||||
|
||||
const [isMobileNavBarOpened, setIsMobileNavBarOpened] = useState(false);
|
||||
const isMobile = useMediaQuery("(max-width: 560px)");
|
||||
|
@ -94,7 +97,7 @@ export default function AppShellDemo() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Meta title="Configuration" />
|
||||
<Meta title={t("admin.config.title")} />
|
||||
<AppShell
|
||||
styles={{
|
||||
main: {
|
||||
|
@ -134,26 +137,28 @@ export default function AppShellDemo() {
|
|||
spacing={0}
|
||||
>
|
||||
<Title order={6}>
|
||||
{configVariableToFriendlyName(configVariable.name)}
|
||||
<FormattedMessage
|
||||
id={`admin.config.${camelToKebab(
|
||||
configVariable.key
|
||||
)}`}
|
||||
/>
|
||||
</Title>
|
||||
{configVariable.description.split("\n").length == 1 ? (
|
||||
<Text color="dimmed" size="sm" mb="xs">
|
||||
{configVariable.description}
|
||||
</Text>
|
||||
) : (
|
||||
configVariable.description.split("\n").map((line) => (
|
||||
<Text
|
||||
key={line}
|
||||
color="dimmed"
|
||||
size="sm"
|
||||
style={{
|
||||
marginBottom: line === "" ? "1rem" : "0",
|
||||
}}
|
||||
>
|
||||
{line}
|
||||
</Text>
|
||||
))
|
||||
)}
|
||||
|
||||
<Text
|
||||
sx={{
|
||||
whiteSpace: "pre-line",
|
||||
}}
|
||||
color="dimmed"
|
||||
size="sm"
|
||||
mb="xs"
|
||||
>
|
||||
<FormattedMessage
|
||||
id={`admin.config.${camelToKebab(
|
||||
configVariable.key
|
||||
)}.description`}
|
||||
values={{ br: <br /> }}
|
||||
/>
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack></Stack>
|
||||
<Box style={{ width: isMobile ? "100%" : "50%" }}>
|
||||
|
@ -176,7 +181,9 @@ export default function AppShellDemo() {
|
|||
saveConfigVariables={saveConfigVariables}
|
||||
/>
|
||||
)}
|
||||
<Button onClick={saveConfigVariables}>Save</Button>
|
||||
<Button onClick={saveConfigVariables}>
|
||||
<FormattedMessage id="common.button.save" />
|
||||
</Button>
|
||||
</Group>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -11,7 +11,9 @@ import {
|
|||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TbRefresh, TbSettings, TbUsers } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../components/Meta";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import configService from "../../services/config.service";
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
|
@ -31,15 +33,16 @@ const useStyles = createStyles((theme) => ({
|
|||
|
||||
const Admin = () => {
|
||||
const { classes, theme } = useStyles();
|
||||
const t = useTranslate();
|
||||
|
||||
const [managementOptions, setManagementOptions] = useState([
|
||||
{
|
||||
title: "User management",
|
||||
title: t("admin.button.users"),
|
||||
icon: TbUsers,
|
||||
route: "/admin/users",
|
||||
},
|
||||
{
|
||||
title: "Configuration",
|
||||
title: t("admin.button.config"),
|
||||
icon: TbSettings,
|
||||
route: "/admin/config/general",
|
||||
},
|
||||
|
@ -63,9 +66,9 @@ const Admin = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Meta title="Administration" />
|
||||
<Meta title={t("admin.title")} />
|
||||
<Title mb={30} order={3}>
|
||||
Administration
|
||||
<FormattedMessage id="admin.title" />
|
||||
</Title>
|
||||
<Stack justify="space-between" style={{ height: "calc(100vh - 180px)" }}>
|
||||
<Paper withBorder p={40}>
|
||||
|
@ -91,7 +94,7 @@ const Admin = () => {
|
|||
|
||||
<Center>
|
||||
<Text size="xs" color="dimmed">
|
||||
Version {process.env.VERSION}
|
||||
<FormattedMessage id="admin.version" /> {process.env.VERSION}
|
||||
</Text>
|
||||
</Center>
|
||||
</Stack>
|
||||
|
|
|
@ -2,10 +2,12 @@ import { Button, Group, Space, Text, Title } from "@mantine/core";
|
|||
import { useModals } from "@mantine/modals";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TbPlus } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../components/Meta";
|
||||
import ManageUserTable from "../../components/admin/users/ManageUserTable";
|
||||
import showCreateUserModal from "../../components/admin/users/showCreateUserModal";
|
||||
import Meta from "../../components/Meta";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import userService from "../../services/user.service";
|
||||
import User from "../../types/user.type";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
@ -16,6 +18,7 @@ const Users = () => {
|
|||
|
||||
const config = useConfig();
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const getUsers = () => {
|
||||
setIsLoading(true);
|
||||
|
@ -27,14 +30,18 @@ const Users = () => {
|
|||
|
||||
const deleteUser = (user: User) => {
|
||||
modals.openConfirmModal({
|
||||
title: `Delete ${user.username}?`,
|
||||
title: t("admin.users.edit.delete.title", {
|
||||
username: user.username,
|
||||
}),
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Do you really want to delete <b>{user.username}</b> and all his
|
||||
shares?
|
||||
<FormattedMessage id="admin.users.edit.delete.description" />
|
||||
</Text>
|
||||
),
|
||||
labels: { confirm: "Delete", cancel: "Cancel" },
|
||||
labels: {
|
||||
confirm: t("common.button.delete"),
|
||||
cancel: t("common.button.cancel"),
|
||||
},
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: async () => {
|
||||
userService
|
||||
|
@ -51,10 +58,10 @@ const Users = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Meta title="User management" />
|
||||
<Meta title={t("admin.users.title")} />
|
||||
<Group position="apart" align="baseline" mb={20}>
|
||||
<Title mb={30} order={3}>
|
||||
User management
|
||||
<FormattedMessage id="admin.users.title" />
|
||||
</Title>
|
||||
<Button
|
||||
onClick={() =>
|
||||
|
@ -62,7 +69,7 @@ const Users = () => {
|
|||
}
|
||||
leftIcon={<TbPlus size={20} />}
|
||||
>
|
||||
Create
|
||||
<FormattedMessage id="common.button.create" />
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ import {
|
|||
} from "@mantine/core";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useRouter } from "next/router";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import authService from "../../../services/auth.service";
|
||||
import toast from "../../../utils/toast.util";
|
||||
|
||||
|
@ -25,6 +27,7 @@ const useStyles = createStyles((theme) => ({
|
|||
const ResetPassword = () => {
|
||||
const { classes } = useStyles();
|
||||
const router = useRouter();
|
||||
const t = useTranslate();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
|
@ -32,7 +35,10 @@ const ResetPassword = () => {
|
|||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
password: yup.string().min(8).required(),
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.required(t("common.error.field-required")),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -42,10 +48,10 @@ const ResetPassword = () => {
|
|||
return (
|
||||
<Container size={460} my={30}>
|
||||
<Title order={2} weight={900} align="center">
|
||||
Reset password
|
||||
<FormattedMessage id="resetPassword.text.resetPassword" />
|
||||
</Title>
|
||||
<Text color="dimmed" size="sm" align="center">
|
||||
Enter your new password
|
||||
<FormattedMessage id="resetPassword.text.enterNewPassword" />
|
||||
</Text>
|
||||
|
||||
<Paper withBorder shadow="md" p={30} radius="md" mt="xl">
|
||||
|
@ -54,7 +60,7 @@ const ResetPassword = () => {
|
|||
authService
|
||||
.resetPassword(resetPasswordToken, values.password)
|
||||
.then(() => {
|
||||
toast.success("Your password has been reset successfully.");
|
||||
toast.success(t("resetPassword.notify.passwordReset"));
|
||||
|
||||
router.push("/auth/signIn");
|
||||
})
|
||||
|
@ -62,13 +68,13 @@ const ResetPassword = () => {
|
|||
})}
|
||||
>
|
||||
<PasswordInput
|
||||
label="New password"
|
||||
label={t("resetPassword.text.password")}
|
||||
placeholder="••••••••••"
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<Group position="right" mt="lg">
|
||||
<Button type="submit" className={classes.control}>
|
||||
Reset password
|
||||
<FormattedMessage id="resetPassword.button.resetPassword" />
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
|
|
|
@ -15,7 +15,9 @@ import { useForm, yupResolver } from "@mantine/form";
|
|||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { TbArrowLeft } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import authService from "../../../services/auth.service";
|
||||
import toast from "../../../utils/toast.util";
|
||||
|
||||
|
@ -43,6 +45,7 @@ const useStyles = createStyles((theme) => ({
|
|||
const ResetPassword = () => {
|
||||
const { classes } = useStyles();
|
||||
const router = useRouter();
|
||||
const t = useTranslate();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
|
@ -50,7 +53,10 @@ const ResetPassword = () => {
|
|||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
email: yup.string().email().required(),
|
||||
email: yup
|
||||
.string()
|
||||
.email(t("common.error.invalid-email"))
|
||||
.required(t("common.error.field-required")),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
@ -58,10 +64,10 @@ const ResetPassword = () => {
|
|||
return (
|
||||
<Container size={460} my={30}>
|
||||
<Title order={2} weight={900} align="center">
|
||||
Forgot your password?
|
||||
<FormattedMessage id="resetPassword.title" />
|
||||
</Title>
|
||||
<Text color="dimmed" size="sm" align="center">
|
||||
Enter your email to get a reset link
|
||||
<FormattedMessage id="resetPassword.description" />
|
||||
</Text>
|
||||
|
||||
<Paper withBorder shadow="md" p={30} radius="md" mt="xl">
|
||||
|
@ -70,15 +76,15 @@ const ResetPassword = () => {
|
|||
authService
|
||||
.requestResetPassword(values.email)
|
||||
.then(() => {
|
||||
toast.success("The email has been sent.");
|
||||
toast.success(t("resetPassword.notify.success"));
|
||||
router.push("/auth/signIn");
|
||||
})
|
||||
.catch(toast.axiosError)
|
||||
)}
|
||||
>
|
||||
<TextInput
|
||||
label="Your email"
|
||||
placeholder="Your email"
|
||||
label={t("signup.input.email")}
|
||||
placeholder={t("signup.input.email.placeholder")}
|
||||
{...form.getInputProps("email")}
|
||||
/>
|
||||
<Group position="apart" mt="lg" className={classes.controls}>
|
||||
|
@ -91,11 +97,13 @@ const ResetPassword = () => {
|
|||
>
|
||||
<Center inline>
|
||||
<TbArrowLeft size={12} />
|
||||
<Box ml={5}>Back to login page</Box>
|
||||
<Box ml={5}>
|
||||
<FormattedMessage id="resetPassword.button.back" />
|
||||
</Box>
|
||||
</Center>
|
||||
</Anchor>
|
||||
<Button type="submit" className={classes.control}>
|
||||
Reset password
|
||||
<FormattedMessage id="resetPassword.text.resetPassword" />
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
|
|||
import SignInForm from "../../components/auth/SignInForm";
|
||||
import Meta from "../../components/Meta";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
|
||||
export function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
return {
|
||||
|
@ -15,6 +16,7 @@ export function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
const SignIn = ({ redirectPath }: { redirectPath?: string }) => {
|
||||
const { refreshUser } = useUser();
|
||||
const router = useRouter();
|
||||
const t = useTranslate();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(redirectPath ? true : false);
|
||||
|
||||
|
@ -34,7 +36,7 @@ const SignIn = ({ redirectPath }: { redirectPath?: string }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Meta title="Sign In" />
|
||||
<Meta title={t("signin.title")} />
|
||||
<SignInForm redirectPath={redirectPath ?? "/upload"} />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import SignUpForm from "../../components/auth/SignUpForm";
|
||||
import Meta from "../../components/Meta";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
|
||||
const SignUp = () => {
|
||||
const t = useTranslate();
|
||||
return (
|
||||
<>
|
||||
<Meta title="Sign Up" />
|
||||
<Meta title={t("signup.title")} />
|
||||
<SignUpForm />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import Link from "next/link";
|
|||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import { TbCheck } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Logo from "../components/Logo";
|
||||
import Meta from "../components/Meta";
|
||||
import useUser from "../hooks/user.hook";
|
||||
|
@ -89,12 +90,17 @@ export default function Home() {
|
|||
<div className={classes.inner}>
|
||||
<div className={classes.content}>
|
||||
<Title className={classes.title}>
|
||||
A <span className={classes.highlight}>self-hosted</span> <br />{" "}
|
||||
file sharing platform.
|
||||
<FormattedMessage
|
||||
id="home.title"
|
||||
values={{
|
||||
h: (chunks) => (
|
||||
<span className={classes.highlight}>{chunks} </span>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Title>
|
||||
<Text color="dimmed" mt="md">
|
||||
Do you really want to give your personal files in the hand of
|
||||
third parties like WeTransfer?
|
||||
<FormattedMessage id="home.description" />
|
||||
</Text>
|
||||
|
||||
<List
|
||||
|
@ -109,19 +115,26 @@ export default function Home() {
|
|||
>
|
||||
<List.Item>
|
||||
<div>
|
||||
<b>Self-Hosted</b> - Host Pingvin Share on your own machine.
|
||||
<b>
|
||||
<FormattedMessage id="home.bullet.a.name" />
|
||||
</b>{" "}
|
||||
- <FormattedMessage id="home.bullet.a.description" />
|
||||
</div>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<div>
|
||||
<b>Privacy</b> - Your files are your files and should never
|
||||
get into the hands of third parties.
|
||||
<b>
|
||||
<FormattedMessage id="home.bullet.b.name" />
|
||||
</b>{" "}
|
||||
- <FormattedMessage id="home.bullet.b.description" />
|
||||
</div>
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<div>
|
||||
<b>No annoying file size limit</b> - Upload as big files as
|
||||
you want. Only your hard drive will be your limit.
|
||||
<b>
|
||||
<FormattedMessage id="home.bullet.c.name" />
|
||||
</b>{" "}
|
||||
- <FormattedMessage id="home.bullet.c.description" />
|
||||
</div>
|
||||
</List.Item>
|
||||
</List>
|
||||
|
@ -134,7 +147,7 @@ export default function Home() {
|
|||
size="md"
|
||||
className={classes.control}
|
||||
>
|
||||
Get started
|
||||
<FormattedMessage id="home.button.start" />
|
||||
</Button>
|
||||
<Button
|
||||
component={Link}
|
||||
|
@ -145,7 +158,7 @@ export default function Home() {
|
|||
size="md"
|
||||
className={classes.control}
|
||||
>
|
||||
Source code
|
||||
<FormattedMessage id="home.button.source" />
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,7 @@ import DownloadAllButton from "../../../components/share/DownloadAllButton";
|
|||
import FileList from "../../../components/share/FileList";
|
||||
import showEnterPasswordModal from "../../../components/share/showEnterPasswordModal";
|
||||
import showErrorModal from "../../../components/share/showErrorModal";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import shareService from "../../../services/share.service";
|
||||
import { Share as ShareType } from "../../../types/share.type";
|
||||
import toast from "../../../utils/toast.util";
|
||||
|
@ -20,6 +21,7 @@ export function getServerSideProps(context: GetServerSidePropsContext) {
|
|||
const Share = ({ shareId }: { shareId: string }) => {
|
||||
const modals = useModals();
|
||||
const [share, setShare] = useState<ShareType>();
|
||||
const t = useTranslate();
|
||||
|
||||
const getShareToken = async (password?: string) => {
|
||||
await shareService
|
||||
|
@ -33,8 +35,8 @@ const Share = ({ shareId }: { shareId: string }) => {
|
|||
if (error == "share_max_views_exceeded") {
|
||||
showErrorModal(
|
||||
modals,
|
||||
"Visitor limit exceeded",
|
||||
"The visitor limit from this share has been exceeded."
|
||||
t("share.error.visitor-limit-exceeded.title"),
|
||||
t("share.error.visitor-limit-exceeded.description")
|
||||
);
|
||||
} else {
|
||||
toast.axiosError(e);
|
||||
|
@ -52,12 +54,16 @@ const Share = ({ shareId }: { shareId: string }) => {
|
|||
const { error } = e.response.data;
|
||||
if (e.response.status == 404) {
|
||||
if (error == "share_removed") {
|
||||
showErrorModal(modals, "Share removed", e.response.data.message);
|
||||
showErrorModal(
|
||||
modals,
|
||||
t("share.error.removed.title"),
|
||||
e.response.data.message
|
||||
);
|
||||
} else {
|
||||
showErrorModal(
|
||||
modals,
|
||||
"Not found",
|
||||
"This share can't be found. Please check your link."
|
||||
t("share.error.not-found.title"),
|
||||
t("share.error.not-found.description")
|
||||
);
|
||||
}
|
||||
} else if (error == "share_password_required") {
|
||||
|
@ -65,7 +71,7 @@ const Share = ({ shareId }: { shareId: string }) => {
|
|||
} else if (error == "share_token_required") {
|
||||
getShareToken();
|
||||
} else {
|
||||
showErrorModal(modals, "Error", "An unknown error occurred.");
|
||||
showErrorModal(modals, t("common.error"), t("common.error.unknown"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -77,8 +83,8 @@ const Share = ({ shareId }: { shareId: string }) => {
|
|||
return (
|
||||
<>
|
||||
<Meta
|
||||
title={`Share ${shareId}`}
|
||||
description="Look what I've shared with you."
|
||||
title={t("share.title", { shareId })}
|
||||
description={t("share.description")}
|
||||
/>
|
||||
|
||||
<Group position="apart" mb="lg">
|
||||
|
|
|
@ -4,12 +4,14 @@ import { cleanNotifications } from "@mantine/notifications";
|
|||
import { AxiosError } from "axios";
|
||||
import pLimit from "p-limit";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import Meta from "../../components/Meta";
|
||||
import Dropzone from "../../components/upload/Dropzone";
|
||||
import FileList from "../../components/upload/FileList";
|
||||
import showCompletedUploadModal from "../../components/upload/modals/showCompletedUploadModal";
|
||||
import showCreateUploadModal from "../../components/upload/modals/showCreateUploadModal";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import shareService from "../../services/share.service";
|
||||
import { FileUpload } from "../../types/File.type";
|
||||
|
@ -29,6 +31,7 @@ const Upload = ({
|
|||
isReverseShare: boolean;
|
||||
}) => {
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
|
||||
const { user } = useUser();
|
||||
const config = useConfig();
|
||||
|
@ -126,7 +129,7 @@ const Upload = ({
|
|||
if (fileErrorCount > 0) {
|
||||
if (!errorToastShown) {
|
||||
toast.error(
|
||||
`${fileErrorCount} file(s) failed to upload. Trying again.`,
|
||||
t("upload.notify.count-failed", { count: fileErrorCount }),
|
||||
{
|
||||
withCloseButton: false,
|
||||
autoClose: false,
|
||||
|
@ -152,15 +155,13 @@ const Upload = ({
|
|||
showCompletedUploadModal(modals, share, config.get("general.appUrl"));
|
||||
setFiles([]);
|
||||
})
|
||||
.catch(() =>
|
||||
toast.error("An error occurred while finishing your share.")
|
||||
);
|
||||
.catch(() => toast.error(t("upload.notify.generic-error")));
|
||||
}
|
||||
}, [files]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title="Upload" />
|
||||
<Meta title={t("upload.title")} />
|
||||
<Group position="right" mb={20}>
|
||||
<Button
|
||||
loading={isUploading}
|
||||
|
@ -183,7 +184,7 @@ const Upload = ({
|
|||
);
|
||||
}}
|
||||
>
|
||||
Share
|
||||
<FormattedMessage id="common.button.share" />
|
||||
</Button>
|
||||
</Group>
|
||||
<Dropzone
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import moment from "moment";
|
||||
|
||||
export const getExpirationPreview = (
|
||||
name: string,
|
||||
messages: {
|
||||
neverExpires: string;
|
||||
expiresOn: string;
|
||||
},
|
||||
form: {
|
||||
values: {
|
||||
never_expires?: boolean;
|
||||
|
@ -13,7 +16,7 @@ export const getExpirationPreview = (
|
|||
const value = form.values.never_expires
|
||||
? "never"
|
||||
: form.values.expiration_num + form.values.expiration_unit;
|
||||
if (value === "never") return `This ${name} will never expire.`;
|
||||
if (value === "never") return messages.neverExpires;
|
||||
|
||||
const expirationDate = moment()
|
||||
.add(
|
||||
|
@ -22,5 +25,8 @@ export const getExpirationPreview = (
|
|||
)
|
||||
.toDate();
|
||||
|
||||
return `This ${name} will expire on ${moment(expirationDate).format("LLL")}`;
|
||||
return messages.expiresOn.replace(
|
||||
"{expiration}",
|
||||
moment(expirationDate).format("LLL")
|
||||
);
|
||||
};
|
||||
|
|
42
frontend/src/utils/i18n.util.ts
Normal file
42
frontend/src/utils/i18n.util.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { setCookie } from "cookies-next";
|
||||
import { LOCALES } from "../i18n/locales";
|
||||
|
||||
const getLocaleByCode = (code: string) => {
|
||||
return Object.values(LOCALES).find((l) => l.code === code) ?? LOCALES.ENGLISH;
|
||||
};
|
||||
|
||||
// Parse the Accept-Language header and return the first supported language
|
||||
const getLanguageFromAcceptHeader = (acceptLanguage?: string) => {
|
||||
if (!acceptLanguage) return "en";
|
||||
|
||||
const languages = acceptLanguage.split(",").map((l) => l.split(";")[0]);
|
||||
const supportedLanguages = Object.values(LOCALES).map((l) => l.code);
|
||||
|
||||
for (const language of languages) {
|
||||
// Try to match the full language code first, then the language code without the region
|
||||
if (supportedLanguages.includes(language)) {
|
||||
return language;
|
||||
} else if (supportedLanguages.includes(language.split("-")[0])) {
|
||||
return language.split("-")[0];
|
||||
}
|
||||
}
|
||||
return "en";
|
||||
};
|
||||
|
||||
const isLanguageSupported = (code: string) => {
|
||||
return Object.values(LOCALES).some((l) => l.code === code);
|
||||
};
|
||||
|
||||
const setLanguageCookie = (code: string) => {
|
||||
setCookie("language", code, {
|
||||
sameSite: "lax",
|
||||
expires: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
getLocaleByCode,
|
||||
getLanguageFromAcceptHeader,
|
||||
isLanguageSupported,
|
||||
setLanguageCookie,
|
||||
};
|
|
@ -1,6 +1,5 @@
|
|||
export const configVariableToFriendlyName = (variable: string) => {
|
||||
const splitted = variable.split(/(?=[A-Z])/).join(" ");
|
||||
return splitted.charAt(0).toUpperCase() + splitted.slice(1);
|
||||
export const camelToKebab = (camelCaseString: string) => {
|
||||
return camelCaseString.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
};
|
||||
|
||||
export const capitalizeFirstLetter = (string: string) => {
|
||||
|
|
|
@ -3,6 +3,10 @@ const defaultPreferences = [
|
|||
key: "colorScheme",
|
||||
value: "system",
|
||||
},
|
||||
{
|
||||
key: "locale",
|
||||
value: "system",
|
||||
},
|
||||
];
|
||||
|
||||
const get = (key: string) => {
|
||||
|
@ -23,8 +27,9 @@ const set = (key: string, value: string) => {
|
|||
localStorage.setItem("preferences", JSON.stringify(preferences));
|
||||
}
|
||||
};
|
||||
const usePreferences = () => {
|
||||
return { get, set };
|
||||
const userPreferences = {
|
||||
get,
|
||||
set,
|
||||
};
|
||||
|
||||
export default usePreferences;
|
||||
export default userPreferences;
|
Loading…
Reference in New Issue
Block a user